webdaw-modules
Version:
a set of modules for building a web-based DAW
1,163 lines (977 loc) • 39.6 kB
JavaScript
function songFollowEvent() {
'use strict';
var
//import
findEvent, // → defined in find_position.js
typeString, // → defined in util.js
getPosition, // → defined in get_position.js
midiEventNameByNumber, // → defined in midi_event_names.js
midiEventNumberByName, // → defined in midi_event_names.js
listenerIndex = 0, // global id that increases per created eventlistener
supportedTimeEvents = {
//bars and beats
bar: 1,
beat: 1,
sixteenth: 1,
tick: 0, // the granularity of requestAnimationFrame is too coarse for tick values
ticks: 1, // from start of song
barsbeats: 1, // as number (will be interpreted as bar) or as array of numbers: [bar,beat,sixteenth,tick]
barsandbeats: 1, // same as above
//time
hour: 1,
minute: 1,
second: 1,
millisecond: 0, // the granularity of requestAnimationFrame is too coarse for millisecond values
millis: 1, // from start of song
time: 1 // as number (will be interpreted as second) or as array of numbers: [hour,minute,second,millisecond]
},
supportedOperators = '= == === > >= < <= != !== %=', //'= == === > >= < <= != !== *= ^= ~= $=',
//supportedOperatorsRegex = new RegExp(' ' + supportedOperators.replace(/\s+/g,' | ').replace(/\*/,'\\*') + ' '),
supportedOperatorsRegex = new RegExp(' ' + supportedOperators.replace(/\s+/g, ' | ') + ' '),
//private
getEvents,
checkOperatorConflict,
//private class, only accessible in Song
FollowEvent;
FollowEvent = function (song) {
this.song = song;
this.allListenersById = {};
this.allListenersByType = {};
this.searchPatterns = [];
this.eventListenersBySearchstring = {};
this.positionListenersBySearchstring = {};
this.allListenersByType = {
'event': {
// all events that are registered as event, keys are the event ids, values are array with listener ids
'instance': {},
// all events that are registered after a search action, for instance: addEventListener('event', 'velocity > 50', callback)
'searchstring': {}
},
'position': {
// millis, ticks, time, barsbeats
'ticks': [],
// every bar, beat, sixteenth, hour, minute, second
'repetitive': {},
// on single conditional values of bar, beat, sixteenth, hour, minute, second, e.g. bar > 3, bar %= 2, bar === 3
'conditional_simple': {},
// on conditional values of bar, beat, sixteenth, hour, minute, second, e.g. bar > 3 < 10
'conditional_complex': {}
}
};
};
FollowEvent.prototype.updateSong = function () {
//adjust all event listeners based on registered events and events found by a searchstring
var i, j, e, eventId, events, tmp, data,
listenerIds = [], listeners,
listener, listenerId;
listeners = this.allListenersByType.event.instance;
// loop over event ids
for (eventId in listeners) {
if (listeners.hasOwnProperty(eventId)) {
// check if event has been removed
if (this.song.eventsById[eventId] === undefined) {
// get all listeners that are registered to this event and delete them
listenerIds = listeners[eventId];
for (i = listenerIds.length - 1; i >= 0; i--) {
listenerId = listenerIds[i];
delete this.allListenersById[listenerId];
}
//delete listeners[eventId];
delete this.allListenersByType.event.instance[eventId];
}
}
}
//console.log(listeners);
// remove all listeners that are currently bound to found events
listeners = this.allListenersByType.event.searchstring;
listenerIds = [];
for (eventId in listeners) {
// step 1: collect all ids of listeners
if (listeners.hasOwnProperty(eventId)) {
listenerIds = listenerIds.concat(listeners[eventId]);
}
}
// step 2: delete the listeners
for (i = listenerIds.length - 1; i >= 0; i--) {
delete this.allListenersById[listenerIds[i]];
}
// all event listeners have been removed, now create a new object and run search string on updated song
this.allListenersByType.event.searchstring = {};
tmp = this.allListenersByType.event.searchstring;
//@TODO: this can't possibly work!! -> but it actually does..
for (i = this.searchPatterns.length - 1; i >= 0; i--) {
data = this.searchPatterns[i];
events = findEvent(this.song, data.searchstring);
//console.log(data, events);
//add listeners for both note on and note off and ignore the other event types
if (tmp.type === 'note') {
events = findEvent(events, 'type = NOTE_ON OR type = NOTE_OFF');
}
for (j = events.length - 1; j >= 0; j--) {
e = events[j];
listenerId = 'event_' + listenerIndex++;
listener = {
id: listenerId,
event: e,
type: data.type,
subtype: data.subtype,
callback: data.callback
};
this.allListenersById[listenerId] = listener;
if (tmp[e.id] === undefined) {
tmp[e.id] = [];
}
tmp[e.id].push(listenerId);
//listenerIds.push(listenerId);
}
}
//console.log(allListenersByType.event.searchstring);
//console.log(allListenersByType.event.instance);
//console.log(allListenersById);
};
FollowEvent.prototype.update = function () {
var
i,
position = this.song,
ticks = position.ticks,
events = [],
numEvents,
event;
//events = this.song.playhead.activeEvents;
events = this.song.playhead.collectedEvents;
// events = this.song.playhead.changedEvents; -> do something with a snapshot here
numEvents = events.length;
/*
if(numEvents !== undefined && numEvents > 0){
console.log(numEvents, position.barsAsString, this.bar, this.beat);
}
*/
//call event listeners registered to specific midi events
for (i = 0; i < numEvents; i++) {
event = events[i];
//console.log(event, event.ticks);
this.callEventListeners(event.id, event);
}
this.callListenersPositionTicks(ticks);
if (position.bar !== this.bar) {
this.bar = position.bar;
this.callListenersPositionRepetitive('bar');
this.callListenersPositionConditionalSimple('bar', this.bar);
this.callListenersPositionConditionalComplex('bar', this.bar);
}
if (position.beat !== this.beat) {
this.beat = position.beat;
this.callListenersPositionRepetitive('beat');
this.callListenersPositionConditionalSimple('beat', this.beat);
this.callListenersPositionConditionalComplex('beat', this.beat);
}
if (position.sixteenth !== this.sixteenth) {
this.sixteenth = position.sixteenth;
this.callListenersPositionRepetitive('sixteenth');
this.callListenersPositionConditionalSimple('sixteenth', this.sixteenth);
this.callListenersPositionConditionalComplex('sixteenth', this.sixteenth);
}
if (position.hour !== this.hour) {
this.hour = position.hour;
this.callListenersPositionRepetitive('hour');
this.callListenersPositionConditionalSimple('hour', this.hour);
this.callListenersPositionConditionalComplex('hour', this.hour);
}
if (position.minute !== this.minute) {
this.minute = position.minute;
this.callListenersPositionRepetitive('minute');
this.callListenersPositionConditionalSimple('minute', this.minute);
this.callListenersPositionConditionalComplex('minute', this.minute);
}
if (position.second !== this.second) {
this.second = position.second;
this.callListenersPositionRepetitive('second');
this.callListenersPositionConditionalSimple('second', this.second);
this.callListenersPositionConditionalComplex('second', this.second);
}
};
// call callbacks that are bound to a specific event
FollowEvent.prototype.callEventListeners = function (eventId, event) {
var i, id, tmp,
listener,
listenerIds = [];
tmp = this.allListenersByType.event.instance;
if (tmp[eventId]) {
listenerIds = listenerIds.concat(tmp[eventId]);
}
tmp = this.allListenersByType.event.searchstring;
if (tmp[eventId]) {
listenerIds = listenerIds.concat(tmp[eventId]);
}
if (listenerIds.length > 0) {
//console.log(listenerIds, event.id, event.ticks);
}
for (i = listenerIds.length - 1; i >= 0; i--) {
id = listenerIds[i];
listener = this.allListenersById[id];
if (listener.called !== true) {
listener.called = true;
listener.callback(event);
//console.log('called', event.id)
}
}
};
// for instance: addEventListener('position', 'bar') -> call callback every bar
FollowEvent.prototype.callListenersPositionRepetitive = function (positionType) {
var listener,
listenerIds = this.allListenersByType.position.repetitive[positionType],
me = this;
if (listenerIds) {
listenerIds.forEach(function (id) {
listener = me.allListenersById[id];
listener.callback(listener.searchstring);
});
}
};
// can be repetitive or fire once:
// addEventListener('position', 'beat === 2') -> call callback every second beat
// addEventListener('position', 'bar === 2') -> call callback at start of bar 2
FollowEvent.prototype.callListenersPositionConditionalSimple = function (positionType, value) {
var listener,
data,
operator,
call = false,
listenerIds = this.allListenersByType.position.conditional_simple[positionType],
me = this;
//console.log(positionType, listenerIds);
if (listenerIds) {
listenerIds.forEach(function (id) {
// -> check condition
listener = me.allListenersById[id];
data = listener.data;
operator = listener.operator;
switch (operator) {
case '>':
call = value > data;
break;
case '<':
call = value < data;
break;
case '%=':
//call = (value + 1) % (data + 1) === 0;
call = value % data === 0;
break;
case '!=':
case '!==':
call = value !== data;
break;
case '===':
call = value === data;
break;
}
if (call === true) {
listener.callback(listener.searchstring);
}
});
}
};
// for instance: addEventListener('position', 'bar > 2 < 7') -> call callback every bar from bar 3 to 6
FollowEvent.prototype.callListenersPositionConditionalComplex = function (positionType, value) {
var listener,
value1,
value2,
operator1,
operator2,
listenerIds = this.allListenersByType.position.conditional_complex[positionType],
me = this;
if (listenerIds) {
listenerIds.forEach(function (id) {
// -> check condition(s)
listener = me.allListenersById[id];
value1 = listener.value1;
value2 = listener.value2;
operator1 = listener.operator1;
operator2 = listener.operator2;
if (operator1 === '<') {
if (value < value1 || value > value2) {
listener.callback(listener.searchstring);
}
} else if (operator1 === '>') {
//console.log(value1,value2,value,operator1,operator2);
if (value > value1 && value < value2) {
listener.callback(listener.searchstring);
}
}
});
}
};
FollowEvent.prototype.callListenersPositionTicks = function (ticks) {
var tmp = this.allListenersByType.position.ticks,
i, maxi = tmp.length,
listener;
for (i = 0; i < maxi; i++) {
listener = this.allListenersById[tmp[i]];
//console.log(listener,ticks);
if (ticks >= listener.ticks && !listener.called) {
listener.callback(listener.searchstring);
listener.called = true;
}
}
};
/*
event types: event, note or position
event → when the playhead passes the event
note → both on the note on and the note off event, the events array may be an array of notes or an array of events
position
'bar' -> every bar
'bar = 3' -> start of bar 3
'bar > 3 < 7' -> every bar after bar 3 and before bar 7
'beat' -> every beat
'beat = 3' -> start of beat 3
'beat > 3' -> every beat after beat 3
etc.
addEventListener('event',eventsArray,callback)
addEventListener('event',findEvent(eventsArray,'type = NOTE_ON'),callback)
addEventListener('note',eventsArray,callback)
addEventListener('note',findEvent(eventsArray,'velocity = 90'),callback)
addEventListener('event','bar > 5', callback)
addEventListener('note','id = 45654544345',callback)
addEventListener('position','bar',callback)
addEventListener('position','bar > 5 < 17',callback)
addEventListener('position','beat',callback)
addEventListener('position','second',callback)
addEventListener('position','second > 5',callback)
addEventListener('position','barsbeats = 1,2,0,0',callback)
*/
FollowEvent.prototype.addEventListener = function (type, data, callback) {
var i, events, event, storeArray, tmp, subtype,
listener, listenerId, listenerIds = [],
dataType = typeString(data);
//console.log(type,data,callback);
//console.log(dataType, data);
if (typeString(callback) !== 'function') {
console.error(callback, 'is not a function; please provide a function for callback');
return -1;
}
if (type === 'position') {
listenerId = this.addPositionEventListener(data, callback);
//console.log(allListenersByType, allListenersById);
return listenerId;
}
if (dataType === 'string') {
events = findEvent(this.song, data);
// store the search string so we can run it again after the song has changed
this.searchPatterns.push({
searchstring: data,
callback: callback,
type: 'event',
subtype: 'searchstring'
});
//console.log(data, events);
if (events.length === 0) {
return -1;
}
subtype = 'searchstring';
storeArray = this.allListenersByType.event.searchstring;
this.eventListenersBySearchstring[data] = tmp = [];
} else {
events = getEvents(type, data);
if (events === -1) {
return -1;
}
subtype = 'instance';
storeArray = this.allListenersByType.event.instance;
}
//add listeners for both note on and note off and ignore the other event types
if (type === 'note') {
events = findEvent(events, 'type = NOTE_ON OR type = NOTE_OFF');
}
for (i = events.length - 1; i >= 0; i--) {
event = events[i];
listenerId = 'event_' + listenerIndex++;
listener = {
id: listenerId,
event: event,
type: type,
subtype: subtype,
callback: callback
};
//allListeners.push(listener);
this.allListenersById[listenerId] = listener;
if (storeArray[event.id] === undefined) {
storeArray[event.id] = [];
}
storeArray[event.id].push(listenerId);
listenerIds.push(listenerId);
if (subtype === 'searchstring') {
tmp.push(listenerId);
}
}
//console.log(this.allListenersById, this.allListenersByType);
if (subtype === 'searchstring' || dataType === 'array' || type === 'note') {
return listenerIds;
} else {
//console.log('num listeners:', listenerIds.length);
return listenerIds[0];
}
};
/*
'bar'; -> repetitive
'bar == 3'; -> fire once
'bar < 3 > 5'; -> conditionally repetitive
'bar = 3 AND beat > 1' -> not supported here! (doesn't make sense, use events or position instead)
- check if has valid operators
- some types need an operator -> filter
- check values that the operator has to act upon
- split into repetitive and one-shot listeners
*/
FollowEvent.prototype.addPositionEventListener = function (data, callback) {
var tmp,
listenerId, listener,
millis,
searchString = data.split(/[\s+]/g),
len = searchString.length,
type = searchString[0],
operator1 = searchString[1],
value1 = searchString[2],
operator2 = searchString[3],
value2 = searchString[4],
value1Type = typeString(value1);
//hasOperator = supportedOperatorsRegex.test(data);
//console.log(data, searchString, len);
//console.log(type, value1, operator1, value2, operator2);
if (len !== 1 && len !== 3 && len !== 5) {
console.error('invalid search string, please consult documentation');
return false;
}
/*
//split position data into an array -> is now done in find_event.js -> not anymore ;)
if(value1 && value1.indexOf(',') !== -1){
value1 = value1.split(',');
}
if(value2 && value2.indexOf(',') !== -1){
value2 = value2.split(',');
}
*/
if (supportedTimeEvents[type] !== 1) {
console.error(type, 'is not a supported event id, please consult documentation');
return -1;
}
if (operator1 === '=' || operator1 === '==') {
operator1 = '===';
}
// check values per type
switch (type) {
// these type can only fire once
case 'barsbeats':
case 'barsandbeats':
case 'time':
//case 'musical_time':
//case 'linear_time':
case 'ticks':
case 'millis':
if (operator1 === undefined || operator1 !== '===') {
console.error(type, 'can only be used conditionally with the operators \'===\', \'==\' or \'=\'');
return -1;
}
// if(isNaN(value1) && typeString(value1) !== 'array'){
// console.error('please provide a number or an array of numbers');
// return -1;
// }
if (operator2) {
console.warn('this position event type can only be used with a single condition, ignoring second condition');
}
break;
// these type fire repeatedly or once
case 'bar': // -> fired once if used with === operator
case 'beat':
case 'sixteenth':
//case 'tick':
case 'hour': // -> fired once if used with === operator
case 'minute':
case 'second':
//case 'millisecond':
if (value1 && isNaN(value1)) {
console.error('please provide a number');
return -1;
}
if (value2 && isNaN(value2)) {
console.error('please provide a number');
return -1;
}
break;
}
// check operators
if (operator1 && supportedOperators.indexOf(operator1) === -1) {
console.error(operator1, 'is not a supported operator, please use any of', supportedOperators);
return -1;
}
if (operator1 && value1 === undefined) {
console.error('operator without value');
return;
}
if (operator2 && supportedOperators.indexOf(operator1) === -1) {
console.error(operator2, 'is not a supported operator, please use any of', supportedOperators);
return -1;
}
if (operator2 && value2 === undefined) {
console.error('operator without value');
return;
}
if (operator1 && operator2 && checkOperatorConflict(operator1, operator2) === false) {
console.error('you can\'t use ' + operator1 + ' together with ' + operator2);
return -1;
}
// check callback
if (typeString(callback) !== 'function') {
console.error(callback, 'is not a function; please provide a function for callback');
return -1;
}
// simplify searchstring and adjust values
switch (type) {
// event types that fire repeatedly or once
case 'bar':
case 'beat':
case 'sixteenth':
// make zero based
value1 = value1 - 0;
value2 = value2 - 0;
//case 'tick':
case 'hour':
case 'minute':
case 'second':
//case 'millisecond':
// make zero based
value1 = value1 - 0;
value2 = value2 - 0;
// convert <= to < and >= to > to make it easier
if (operator1) {
if (operator1 === '<=') {
value1++;
operator1 = '<';
} else if (operator1 === '>=') {
value1--;
operator1 = '>';
}
}
if (operator2) {
if (operator2 === '<=') {
value2++;
operator2 = '<';
} else if (operator2 === '>=') {
value2--;
operator2 = '>';
}
}
break;
// event types that fire only once, these get all converted to type 'ticks'
case 'ticks':
// we already have the position value in ticks
value1 = value1 - 0;
break;
case 'barsbeats':
case 'barsandbeats':
//case 'musical_time':
// convert position value to ticks
if (!isNaN(value1)) {
// only a single number is provided, we consider it to be the bar number
value1 = getPosition(this.song, ['barsbeats', value1, 1, 1, 0]).ticks;
} else if (value1Type === 'string') {
// a full barsandbeats array is provided: bar, beat, sixteenth, ticks
value1 = value1.replace(/[\[\]\s]/g, '');
value1 = value1.split(',');
value1 = getPosition(this.song, ['barsbeats', value1[0], value1[1] || 1, value1[2] || 1, value1[3] || 0]).ticks;
/*
}else if(value1Type === 'array'){
// a full barsandbeats array is provided: bar, beat, sixteenth, ticks
value1 = getPosition(this.song, ['barsbeats', value1[0], value1[1], value1[2], value1[3]]).ticks;
*/
} else {
console.error('please provide a number or an array of numbers');
}
type = 'ticks';
break;
case 'millis':
// convert position value to ticks
value1 = getPosition(this.song, ['millis', value1]).ticks;
type = 'ticks';
break;
case 'time':
//case 'linear_time':
// convert position value to ticks
if (!isNaN(value1)) {
// a single number is provided, we treat this as the value for minutes
millis = value1 * 60 * 1000; //seconds
value1 = getPosition(this.song, ['millis', millis]).ticks;
} else if (value1Type === 'string') {
// a full barsandbeats array is provided: bar, beat, sixteenth, ticks
value1 = value1.replace(/[\[\]\s]/g, '');
console.log(value1);
value1 = value1.split(',');
if (value1.length === 1) {
millis = value1[0] * 60 * 1000;
value1 = getPosition(this.song, ['millis', millis]).ticks;
} else {
value1 = getPosition(this.song, ['time', value1[0], value1[1], value1[2], value1[3]]).ticks;
}
/*
}else if(value1Type === 'array'){
// a full time array is provided: hours, minutes, seconds, millis
value1 = getPosition(this.song, ['time', value1[0], value1[1], value1[2], value1[3]]).ticks;
*/
} else {
console.error('please provide a number or an array of numbers');
}
console.log(value1);
type = 'ticks';
break;
}
listenerId = 'position_' + listenerIndex++;
//console.log(value1,value2);
if (type === 'ticks') {
//console.log(value1,listenerId)
listener = {
id: listenerId,
callback: callback,
type: 'position',
subtype: 'ticks',
ticks: value1,
searchstring: data
};
this.allListenersByType.position.ticks.push(listenerId);
} else if (!operator1 && !operator2) {
// every bar, beat, sixteenth, hour, minute, second
listener = {
id: listenerId,
callback: callback,
type: 'position',
subtype: 'repetitive',
position_type: type,
searchstring: data
};
tmp = this.allListenersByType.position.repetitive;
if (tmp[type] === undefined) {
tmp[type] = [];
}
tmp[type].push(listenerId);
} else if (operator1 && !operator2) {
// every time a bar, beat, sixteenth, hour, minute, second meets a certain simple condition, can be both repetitive and fire once
listener = {
id: listenerId,
callback: callback,
type: 'position',
subtype: 'conditional_simple',
position_type: type,
data: value1,
operator: operator1,
searchstring: data
};
tmp = this.allListenersByType.position.conditional_simple;
if (tmp[type] === undefined) {
tmp[type] = [];
}
tmp[type].push(listenerId);
} else if (operator1 && operator2) {
// every time a bar, beat, sixteenth, hour, minute, second meets a certain complex condition
listener = {
id: listenerId,
callback: callback,
type: 'position',
subtype: 'conditional_complex',
position_type: type,
value1: value1,
value2: value2,
operator1: operator1,
operator2: operator2,
searchstring: data
};
tmp = this.allListenersByType.position.conditional_complex;
if (tmp[type] === undefined) {
tmp[type] = [];
}
tmp[type].push(listenerId);
}
this.allListenersById[listenerId] = listener;
this.positionListenersBySearchstring[data] = listenerId;
return listenerId;
};
// @param type, data, callback
// @param id
FollowEvent.prototype.removeEventListener = function (args) {
var
//args = Array.prototype.slice.call(arguments),
arg0,
numArgs,
type, subtype, data, callback, id, ids, tmp,
listener, listenerId,
event, eventId, eventIds,
i, j, k, removeMe,
listenerIds,
filteredListenerIds = [],
removedListenerIds = [],
dataType;
//console.log(args);
//args = Array.prototype.slice.call(args[0]);
arg0 = args[0];
numArgs = args.length;
if (numArgs === 1) {
if (typeString(arg0) === 'array') {
ids = arg0;
} else {
ids = [arg0];
}
//console.log(ids);
for (i = ids.length - 1; i >= 0; i--) {
id = ids[i];
//console.log(id);
if (this.allListenersById[id] !== undefined) {
listener = this.allListenersById[id];
type = listener.type;
subtype = listener.subtype;
if (type === 'position') {
// reference to an array of all the listeners bound to this event type
listenerIds = this.allListenersByType[type][subtype][listener.position_type];
// loop over listeners and filter the one that has to be removed
for (j = listenerIds.length - 1; j >= 0; j--) {
listenerId = listenerIds[j];
if (listenerId !== id) {
filteredListenerIds.push(listenerId);
}
}
// add the filtered array back
this.allListenersByType[listener.type][listener.subtype][listener.position_type] = [].concat(filteredListenerIds);
delete this.allListenersById[id];
} else if (type === 'event' || type === 'note') {
event = listener.event;
eventId = event.id;
listenerIds = this.allListenersByType.event[subtype][eventId];
for (j = listenerIds.length - 1; j >= 0; j--) {
listenerId = listenerIds[j];
listener = this.allListenersById[listenerId];
if (listenerId !== id) {
filteredListenerIds.push(listenerId);
break;
}
}
// add the filtered array back per event
this.allListenersByType.event[subtype][eventId] = [].concat(filteredListenerIds);
delete this.allListenersById[id];
//@TODO: we have to add allListenersByType.notes
/*
if(type === 'note'){
console.log(event);
if(event.type === sequencer.NOTE_ON){
eventId = event.midiNote.noteOff.id;
tmp = allListenersByType.event[subtype][eventId];
listenerIds = listenerIds.concat(tmp);
}else if(event.type === sequencer.NOTE_OFF){
eventId = event.midiNote.noteOn.id;
tmp = allListenersByType.event[subtype][eventId];
listenerIds = listenerIds.concat(tmp);
}
for(j = listenerIds.length - 1; j >= 0; j--){
listenerId = listenerIds[j];
listener = allListenersById[listenerId];
if(listenerId !== id){
filteredListenerIds.push(listenerId);
break;
}
}
// add the filtered array back per event
allListenersByType.event[subtype][eventId] = [].concat(filteredListenerIds);
delete allListenersById[id];
}
*/
}
//console.log(this.allListenersById,this.allListenersByType);
} else {
console.warn('no event listener found with id', id);
}
}
} else if (numArgs === 2 || numArgs === 3) {
type = args[0];
data = args[1];
callback = args[2];
dataType = typeString(data);
//console.log(type, data, callback, dataType);
switch (type) {
case 'position':
if (dataType === 'string') {
// get the id of the listener by the searchstring
id = this.positionListenersBySearchstring[data];
// get the listener by id
listener = this.allListenersById[id];
// reference to an array of all the listeners bound to this event type
listenerIds = this.allListenersByType[listener.type][listener.subtype][listener.position_type];
// loop over listeners and filter the one that has to be removed
for (i = listenerIds.length - 1; i >= 0; i--) {
listenerId = listenerIds[i];
if (listenerId !== id) {
filteredListenerIds.push(listenerId);
}
}
// add the filtered array back
this.allListenersByType[listener.type][listener.subtype][listener.position_type] = [].concat(filteredListenerIds);
delete this.allListenersById[id];
delete this.positionListenersBySearchstring[data];
//console.log(allListenersById, allListenersByType, positionListenersBySearchstring);
}
break;
case 'event':
case 'note':
if (dataType === 'string') {
// get all listener ids that are connected to this searchstring
listenerIds = this.eventListenersBySearchstring[data];
for (i = listenerIds.length - 1; i >= 0; i--) {
// collect all ids of listeners that need to be removed
removedListenerIds.push(listenerIds[i]);
}
// loop over all searchstring listeners and filter the ones that have to be removed
eventIds = this.allListenersByType.event.searchstring;
for (eventId in eventIds) {
if (eventIds.hasOwnProperty(eventId)) {
listenerIds = eventIds[eventId];
filteredListenerIds = [];
for (i = listenerIds.length - 1; i >= 0; i--) {
listenerId = listenerIds[i];
removeMe = false;
for (j = removedListenerIds.length - 1; j >= 0; j--) {
//console.log(listenerId, removedListenerIds[j], callback);
if (listenerId === removedListenerIds[j]) {
removeMe = true;
/*
if(callback === undefined){
removeMe = true;
}else{
removeMe = allListenersById[listenerId].callback === callback
}
*/
break;
}
}
if (removeMe === false) {
filteredListenerIds.push(listenerId);
} else {
// remove the listeners from the id library
delete this.allListenersById[listenerIds[i]];
}
}
// add the filtered array back
this.allListenersByType.event.searchstring[eventId] = [].concat(filteredListenerIds);
}
}
// delete the searchstring listeners array
delete this.eventListenersBySearchstring[data];
//console.log(allListenersById,eventListenersBySearchstring,allListenersByType);
} else if (dataType === 'object') {
if (data.className !== 'MidiEvent' && data.className !== 'MidiNote') {
console.error('please provide a midi event or a midi note');
return;
}
if (data.className === 'MidiNote') {
id = data.noteOn.id;
} else if (data.className === 'MidiEvent') {
id = data.id;
}
if (this.allListenersByType.event.instance[id] !== undefined) {
type = 'instance';
listenerIds = this.allListenersByType.event.instance[id];
} else if (this.allListenersByType.event.searchstring[id] !== undefined) {
type = 'searchstring';
listenerIds = this.allListenersByType.event.searchstring[id];
}
if (listenerIds === undefined) {
console.warn('no event listener bound to event with id', id);
return;
}
if (data.className === 'MidiNote') {
ids = this.allListenersByType.event[type][data.noteOff.id];
listenerIds = listenerIds.concat(ids);
}
//console.log(listenerIds);
for (i = listenerIds.length - 1; i >= 0; i--) {
listenerId = listenerIds[i];
listener = this.allListenersById[listenerId];
if (callback && listener.callback !== callback) {
filteredListenerIds.push(listener.id);
} else {
delete this.allListenersById[listener.id];
}
this.allListenersByType.event[type][listener.event.id] = [].concat(filteredListenerIds);
}
//console.log(allListenersById,allListenersByType);
}
break;
}
}
};
// set the 'called' key of every listener to false, this is necessary if the playhead is moved (by a loop of by the user)
FollowEvent.prototype.resetAllListeners = function () {
var listeners = this.allListenersById, key, listener;
for (key in listeners) {
if (listeners.hasOwnProperty(key)) {
listener = listeners[key];
listener.called = false;
//console.log(listener);
}
}
};
getEvents = function (type, data) {
var dataType = typeString(data),
events = [], i, e;
if (dataType !== 'array' && data !== undefined && data.className !== 'MidiEvent' && data.className !== 'MidiNote') {
console.error(data, ' is not valid data for event type \'event\', please consult documentation');
return -1;
}
if (dataType === 'array') {
for (i = data.length - 1; i >= 0; i--) {
e = data[i];
if (type === 'event' && e.className !== 'MidiEvent' && e.className !== 'AudioEvent') {
console.warn('skipping', e, 'because it is not a MidiEvent nor an AudioEvent');
continue;
} else if (type === 'note' && e.className !== 'MidiNote') {
console.warn('skipping', e, 'because it is not a MidiNote');
continue;
}
events.push(e);
}
} else {
if (type === 'event') {
if (data.className !== 'MidiEvent' && data.className !== 'AudioEvent') {
console.error(data, ' is not a MidiEvent nor an AudioEvent');
return -1;
} else {
events = [data];
}
} else if (type === 'note') {
if (data.className !== 'MidiNote') {
console.error(data, ' is not a MidiNote');
return -1;
} else {
events = [data.noteOn, data.noteOff];
}
}
}
return events;
};
checkOperatorConflict = function (operator1, operator2) {
switch (operator1) {
case '=':
case '==':
case '===':
return false;
}
switch (operator2) {
case '=':
case '==':
case '===':
return false;
}
return true;
};
sequencer.protectedScope.createFollowEvent = function (song) {
return new FollowEvent(song);
};
sequencer.protectedScope.addInitMethod(function () {
typeString = sequencer.protectedScope.typeString;
getPosition = sequencer.protectedScope.getPosition;
midiEventNumberByName = sequencer.midiEventNumberByName;
midiEventNameByNumber = sequencer.midiEventNameByNumber;
findEvent = sequencer.findEvent;
});
}