webdaw-modules
Version:
a set of modules for building a web-based DAW
1,386 lines (1,149 loc) • 42.2 kB
JavaScript
function keyEditor() {
'use strict';
var
//private
KeyEditor,
updateDataKeys = 'newEvents newNotes newParts changedEvents changedNotes changedParts removedEvents removedNotes removedParts'.split(' '),
// default values
tickWidth = 0.1,
pitchHeight = 10,
barsPerPage = 4,
snapValueX = 0, // means snaps is off -> ticks value do not get rounded
//snapValueX = 1, // means snaps to all ticks
snapValueY = 'chromatic',
eventWidth = 2,
ceil = Math.ceil,
//import
createIteratorFactory,
getPosition,
createPlayhead,
getScaffoldingBars,
typeString,
objectToArray,
arrayToObject,
debug,
round,
floor,
createNote,
//public
//getLines,
//xToTicks,
//yToPitch,
//private
setPageData,
checkNextPage,
checkScrollPosition,
dispatchEvent,
handleKeys;
KeyEditor = function (song, config) {
this.song = song;
this.song.keyEditor = this;
this.playhead = createPlayhead(this.song, 'barsbeats ticks millis', 'keyeditor');
this.numBars = song.bars;
this.newNumBars = this.numBars;
this.eventListeners = {};
this.interrupt = false;
this.iteratorFactory = createIteratorFactory(this.song, this);
this.verticalLine = this.iteratorFactory.createVerticalLineIterator(this);
this.horizontalLine = this.iteratorFactory.createHorizontalLineIterator(this);
this.eventIterator = this.iteratorFactory.createEventIterator(this);
this.noteIterator = this.iteratorFactory.createNoteIterator(this);
this.partIterator = this.iteratorFactory.createPartIterator(this);
this.exactFitVertical = config.exactFitVertical || false;
this.exactFitHorizontal = config.exactFitHorizontal || false;
this.activeEvents = [];
this.activeNotes = [];
this.activeParts = [];
this.newEvents = [];
this.newNotes = [];
this.newParts = [];
this.changedEvents = [];
this.changedNotes = [];
this.changedParts = [];
this.removedEvents = [];
this.removedNotes = [];
this.removedParts = [];
this.recordedNotesObj = {};
this.recordedEventsObj = {};
this.snapshot = {
activeEvents: this.activeEvents,
activeNotes: this.activeNotes,
activeParts: this.activeParts,
newEvents: this.newEvents,
newNotes: this.newNotes,
newParts: this.newParts,
changedEvents: this.changedEvents,
changedNotes: this.changedNotes,
changedParts: this.changedParts,
removedEvents: this.removedEvents,
removedNotes: this.removedNotes,
removedParts: this.removedParts
};
if (config.paginate) {
this.paginate = true;
this.pageNo = 0;
this.barsPerPage = config.barsPerPage;
this.pageWidth = config.pageWidth;
this.pageHeight = config.pageHeight;
this.width = this.pageWidth;
this.lowestNote = config.lowestNote || song.lowestNote;
this.highestNote = config.highestNote || song.highestNote;
this.pitchRange = this.highestNote - this.lowestNote;
if (this.exactFitVertical) {
this.pitchHeight = this.height / this.pitchRange;
this.height = this.pageHeight;
} else {
this.pitchHeight = config.pitchHeight || pitchHeight;
this.height = this.pitchHeight * this.pitchRange;
}
//this.startBar = 0;//make this configurable
setPageData(this, 0);
checkNextPage(this);
} else {
this.setStartPosition(config.startPosition || 1);
this.setEndPosition(config.endPosition || song.bars + 1);
this.numTicks = this.endTicks - this.startTicks;
if (config.width) {
this.width = config.width;
this.tickWidth = this.width / this.numTicks;
} else if (config.tickWidth) {
this.tickWidth = config.tickWidth;
this.width = this.numTicks * this.tickWidth;
this.exactFitHorizontal = false;
} else if (config.barsPerPage && config.viewportWidth) {
//@TODO: add support for time measurement changes
this.barsPerPage = config.barsPerPage;
this.viewportWidth = config.viewportWidth;
this.tickWidth = this.viewportWidth / (this.startPosition.ticksPerBar * this.barsPerPage);
this.width = this.numTicks * this.tickWidth;
this.scrollX = 0;
this.scrollPosition = 0;
this.viewportTicks = this.viewportWidth / this.tickWidth;
this.maxScrollPosition = ceil(this.width / this.viewportWidth);
this.scrollLimit = this.viewportWidth / this.tickWidth;
checkScrollPosition(this);
this.exactFitHorizontal = false;
} else if (config.viewportWidth) {
this.viewportWidth = this.width = config.viewportWidth;
this.tickWidth = this.viewportWidth / this.numTicks;
this.exactFitHorizontal = true;
} else {
this.tickWidth = tickWidth;
this.width = this.numTicks * this.tickWidth;
this.exactFitHorizontal = false;
}
this.lowestNote = config.lowestNote || song.lowestNote;
this.highestNote = config.highestNote || song.highestNote;
this.pitchRange = config.pitchRange || this.highestNote - this.lowestNote + 1;
//console.log(this.pitchRange);
if (config.height) {
this.height = config.height;
this.pitchHeight = this.height / this.pitchRange;
} else if (config.pitchHeight) {
this.pitchHeight = config.pitchHeight;
this.height = this.pitchRange * this.pitchHeight;
this.exactFitVertical = false;
} else if (config.viewportHeight) {
this.viewportHeight = this.height = config.viewportHeight;
this.pitchHeight = this.viewportHeight / this.pitchRange;
this.exactFitVertical = true;
} else {
this.pitchHeight = pitchHeight;
this.height = this.pitchRange * this.pitchHeight;
this.exactFitVertical = false;
}
// this.verticalLine.setStartPosition(this.startPosition);
// this.verticalLine.setEndPosition(this.endPosition);
//this.verticalLine.reset(this.startPosition, this.endPosition);
//this.horizontalLine.reset();
//this.eventIterator.reset();
//this.noteIterator.reset();
//this.partIterator.reset();
//console.log(this.tickWidth,this.pitchHeight);
}
this.scrollX = 0;
this.scrollY = 0;
this.currentPage = 1;
this.numPages = ceil(this.width / this.viewportWidth);
this.snapValueX = config.snapX === undefined ? snapValueX : config.snapX;
this.snapValueY = config.snapY === undefined ? snapValueY : config.snapY;
this.setSnapX(this.snapValueX);
this.setSnapY(this.snapValueY);
//console.log(this.maxScrollPosition);
};
KeyEditor.prototype.setBarsPerPage = function (bbp) {
this.interrupt = true;
var tmp = round(this.scrollX / (this.viewportWidth / this.barsPerPage));
this.barsPerPage = bbp;
this.tickWidth = this.viewportWidth / (this.startPosition.ticksPerBar * this.barsPerPage);
this.viewportTicks = this.viewportWidth / this.tickWidth;
this.width = this.numTicks * this.tickWidth;
this.verticalLine.reset();
this.horizontalLine.reset();
this.eventIterator.reset();
this.partIterator.reset();
this.scrollLimit = this.viewportWidth / this.tickWidth;
this.maxScrollPosition = ceil(this.width / this.viewportWidth);
this.snapWidth = this.tickWidth * this.snapTicks;
this.numPages = ceil(this.numBars / this.barsPerPage);
this.currentPage = floor(this.song.ticks / (this.barsPerPage * this.song.ticksPerBar)) + 1;
dispatchEvent(this, 'scale', {});
if (this.song.playing) {
this.scrollPosition = floor(this.song.ticks / this.viewportTicks);
} else {
//console.log(tmp,this.scrollPosition);
this.scrollPosition = ((this.viewportWidth / this.barsPerPage) * tmp) / this.viewportWidth;
dispatchEvent(this, 'scroll', { x: (this.scrollPosition * this.viewportWidth) });
}
this.interrupt = false;
};
KeyEditor.prototype.setViewport = function (w, h) {
var draw = false;
if (this.barsPerPage && w !== this.viewportWidth) {
//@TODO: add support for time measurement changes
this.viewportWidth = w;
this.tickWidth = this.viewportWidth / (this.startPosition.ticksPerBar * this.barsPerPage);
this.viewportTicks = this.viewportWidth / this.tickWidth;
this.width = this.numTicks * this.tickWidth;
draw = true;
} else if (this.exactFitHorizontal === true && w !== this.width) {
this.viewportWidth = this.width = w;
this.tickWidth = this.width / this.numTicks;
draw = true;
}
if (this.exactFitVertical === true && h !== this.height) {
this.viewportHeight = this.height = h;
this.pitchHeight = this.height / this.pitchRange;
draw = true;
}
if (draw) {
this.verticalLine.reset();
this.horizontalLine.reset();
this.eventIterator.reset();
this.noteIterator.reset();
this.partIterator.reset();
dispatchEvent(this, 'draw', {});
}
};
KeyEditor.prototype.updateSong = function (data) {
this.iteratorFactory.updateSong();
var key, i = 0, j, k, arr, tmp;
for (i = updateDataKeys.length - 1; i >= 0; i--) {
key = updateDataKeys[i];
switch (key) {
case 'newNotes':
case 'changedNotes':
arr = data[key];
for (j = arr.length - 1; j >= 0; j--) {
tmp = arr[j];
tmp.bbox = this.getNoteRect(tmp);
}
break;
case 'newParts':
case 'changedParts':
arr = data[key];
for (j = arr.length - 1; j >= 0; j--) {
tmp = arr[j];
tmp.bbox = this.getPartRect(tmp);
}
break;
}
}
/*
this.newNumBars = data.numBars;
// delete numBars otherwise the for loop below doesn't work anymore
delete data.numBars;
for(key in data){
if(data.hasOwnProperty(key)){
arr = data[key];
for(j = arr.length - 1; j >= 0; j--){
tmp = arr[j];
k = floor(i/3);
//console.log(i,k);
switch(k){
case 0: // event arrays
//console.log(k,i);
//tmp.bbox = getEventRect(tmp);
// arr[j] = {
// event: tmp
// }
break;
case 1: // note arrays
//console.log(k,i);
if(tmp.bbox)
console.log(1,tmp.bbox.x)
tmp.bbox = this.getNoteRect(tmp);
console.log(2,tmp.bbox.x)
// arr[j] = {
// note: tmp,
// bbox: this.getNoteRect(tmp)
// }
break;
case 2: // part arrays
//console.log(k,i);
//console.log(tmp);
tmp.bbox = this.getPartRect(tmp);
// arr[j] = {
// part: tmp,
// bbox: this.getPartRect(tmp)
// }
break;
}
}
i++;
}
}
*/
this.newNumBars = data.numBars;
this.newEvents = this.newEvents.concat(data.newEvents);
this.changedEvents = this.changedEvents.concat(data.changedEvents);
this.removedEvents = this.removedEvents.concat(data.removedEvents);
this.removedEventsObj = arrayToObject(this.removedEvents, 'id');
this.newNotes = this.newNotes.concat(data.newNotes);
this.changedNotes = this.changedNotes.concat(data.changedNotes);
this.removedNotes = this.removedNotes.concat(data.removedNotes);
this.removedNotesObj = arrayToObject(this.removedNotes, 'id');
this.newParts = this.newParts.concat(data.newParts);
this.changedParts = this.changedParts.concat(data.changedParts);
this.removedParts = this.removedParts.concat(data.removedParts);
this.removedPartsObj = arrayToObject(this.removedParts, 'id');
};
KeyEditor.prototype.setStartPosition = function (pos) {
if (typeString(pos) !== 'array') {
pos = ['barsandbeats', pos, 1, 1, 0];
}
this.startPosition = getPosition(this.song, pos);
this.startTicks = this.startPosition.ticks;
this.startMillis = this.startPosition.millis;
//console.log('start',pos,this.startTicks);
};
KeyEditor.prototype.setEndPosition = function (pos) {
if (typeString(pos) !== 'array') {
pos = ['barsandbeats', pos, 1, 1, 0];
}
this.endPosition = getPosition(this.song, pos);
this.endTicks = this.endPosition.ticks;
this.endMillis = this.endPosition.millis;
//console.log('end',pos,this.endTicks,this.endPosition);
};
KeyEditor.prototype.addEventListener = function (id, cb) {
var ids = id.split(' '),
tmp,
editor = this,
eventId;
ids.forEach(function (id) {
tmp = editor.eventListeners[id];
if (tmp === undefined) {
editor.eventListeners[id] = [];
tmp = editor.eventListeners[id];
}
eventId = id + '-' + tmp.length;
tmp.push(cb);
});
};
KeyEditor.prototype.nextPage = function () {
setPageData(this, this.startBar + this.barsPerPage);
dispatchEvent(this, 'pagechange', { pageNo: this.pageNo, lastPage: this.lastPage });
};
KeyEditor.prototype.prevPage = function () {
setPageData(this, this.startBar - this.barsPerPage);
dispatchEvent(this, 'pagechange', { pageNo: this.pageNo, lastPage: this.lastPage });
};
KeyEditor.prototype.gotoPage = function (n) {
console.warn('ooops, not implemented yet!');
return;
// n = n - 1;
// if (n < 0 || n > this.lastPage) {
// return;
// }
// this.pageNo = n;
// dispatchEvent(this, 'pagechange', { pageNo: this.pageNo, lastPage: this.lastPage });
// setPageData(this, this.pageNo);
};
KeyEditor.prototype.scroll = function (action) {
//this.scrollPosition = floor(this.scrollX/this.viewportWidth);
var x,
tmp = round(this.scrollX / (this.viewportWidth / this.barsPerPage));
this.scrollPosition = ((this.viewportWidth / this.barsPerPage) * tmp) / this.viewportWidth;
switch (action) {
case '>':
this.scrollPosition += 1;
this.scrollPosition = this.scrollPosition > this.maxScrollPosition ? this.maxScrollPosition : this.scrollPosition;
break;
case '>>':
this.scrollPosition = this.maxScrollPosition;
break;
case '<':
this.scrollPosition -= 1;
this.scrollPosition = this.scrollPosition < 0 ? 0 : this.scrollPosition;
break;
case '<<':
this.scrollPosition = 0;
break;
default:
if (isNaN(action)) {
return;
}
this.scrollPosition = parseInt(action);
}
x = this.scrollPosition * this.viewportWidth;
this.scrollLimit = (x + this.viewportWidth) / this.tickWidth;
this.currentPage = ceil(x / this.viewportWidth) + 1;
if (this.currentPage === 0) {
this.currentPage = 1;
} else if (this.currentPage > this.maxScrollPosition) {
this.currentPage = this.maxScrollPosition;
}
//console.log('bar',(this.scrollPosition * this.barsPerPage),'scroll',this.scrollPosition);
dispatchEvent(this, 'scroll', { x: x });
};
KeyEditor.prototype.updateScroll = function (scrollX, scrollY) {
this.scrollX = scrollX;
this.scrollY = scrollY;
this.scrollLimit = (scrollX + this.viewportWidth) / this.tickWidth;
};
KeyEditor.prototype.getEventRect = function (event) {
//console.log(note.number);
var
x = this.ticksToX(event.ticks - this.startTicks, false),
y = this.pitchToY(event.number),
w = eventWidth * this.tickWidth,
h = this.pitchHeight;
return {
x: x,
y: y,
width: w,
height: h,
top: y,
left: x,
bottom: y + h,
right: x + w
};
};
KeyEditor.prototype.getNoteRect = function (note) {
//console.log(note.number);
var
x = this.ticksToX(note.ticks - this.startTicks, false),//(note.ticks - this.startTicks) * this.tickWidth,
y = this.pitchToY(note.number),
w = note.durationTicks * this.tickWidth,
h = this.pitchHeight,
start, end, diff;
if (note.endless) {
w = (this.song.ticks - note.noteOn.ticks) * this.tickWidth;
}
///*
if (this.paginate) {
start = note.ticks;
end = note.noteOff.ticks;
if (start < this.startTicks) {
diff = this.startTicks - start;
start = start + diff - this.startTicks;
x = start * this.tickWidth;
end = end > this.endTicks ? this.endTicks : end;
w = (end - this.startTicks) * this.tickWidth;
} else {
return false;
}
}
//*/
return {
x: x,
y: y,
width: w,
height: h,
top: y,
left: x,
bottom: y + h,
right: x + w
};
};
KeyEditor.prototype.getPartRect = function (part) {
var stats = part.getStats('noteNumber all'),
//firstEvent = part.events[0],
//lastEvent = part.events[part.events.length - 1],
bbox = {
// left: (firstEvent.ticks - this.startTicks) * this.tickWidth,
// right: (lastEvent.ticks - this.startTicks) * this.tickWidth,
// top: this.height - ((stats.max - this.lowestNote + 1) * this.pitchHeight),
// bottom: this.height - ((stats.min - this.lowestNote + 1) * this.pitchHeight) + this.pitchHeight,
top: this.pitchToY(stats.max),// - this.pitchHeight,
bottom: this.pitchToY(stats.min) + this.pitchHeight,
left: this.ticksToX(part.start.ticks - this.startTicks, false),
right: this.ticksToX(part.end.ticks - this.startTicks, false),
//left: this.ticksToX(part.events[0].ticks, false),
//right: this.ticksToX(part.events[part.events.length - 1].ticks, false)
};
//console.log(stats.min, stats.max);
bbox.x = bbox.left;
bbox.y = bbox.top;
bbox.width = bbox.right - bbox.left;
bbox.height = bbox.bottom - bbox.top;
part.bbox = bbox;
part.stats = stats;
//console.log(part.id,stats,bbox);
return bbox;
};
KeyEditor.prototype.getBBox = function (arg) {
var type, data;
if (typeString(arg) === 'string') {
switch (arg.substring(0, 1)) {
case 'E':
type = 'event';
if (event.type === 144 && event.endEvent !== undefined) {
data = this.song.findEvent('id = ' + arg);
} else {
console.error('argument not supported, please check documentation');
return;
}
break;
case 'P':
type = 'part';
data = this.song.getPart(arg);
break;
case 'T':
type = 'track';
break;
default:
console.error('argument not supported, please check documentation');
return;
}
} else {
switch (arg.className) {
case 'AudioEvent':
type = 'audio';
break;
case 'MidiEvent':
type = 'event';
break;
case 'Part':
type = 'part';
break;
case 'Track':
type = 'track';
break;
default:
console.error('argument not supported, please check documentation');
return;
}
}
if (data === undefined) {
console.error(arg, 'could not be found');
return;
}
switch (type) {
case 'event':
return this.getNoteRect(data);
//break;
case 'part':
return this.getPartRect(data);
//break;
}
};
KeyEditor.prototype.startMoveNote = function (note, x, y) {
if (note.className !== 'MidiNote') {
if (sequencer.debug >= sequencer.WARN) {
console.warn(note, 'is not a MidiNote');
}
return;
}
//sequencer.unscheduleEvent(note);
this.selectedNote = note;
this.gripX = x - this.selectedNote.bbox.x;
};
KeyEditor.prototype.stopMoveNote = function () {
this.selectedNote = undefined;
};
KeyEditor.prototype.moveNote = function (x, y) {
if (this.selectedNote === undefined) {
return;
}
var
newPitch = this.yToPitch(y).number,
oldPitch = this.selectedNote.pitch,
newTicks = this.xToTicks(x - this.gripX),
oldTicks = this.selectedNote.ticks,
part = this.selectedNote.part,
update = false;
//console.log(newTicks, oldTicks, this.gripX, x);
if (newPitch !== oldPitch) {
part.transposeNote(this.selectedNote, newPitch - oldPitch);
update = true;
}
if (newTicks !== oldTicks) {
part.moveNote(this.selectedNote, newTicks - oldTicks);
update = true;
}
if (update === true) {
this.song.update();
}
};
KeyEditor.prototype.startMovePart = function (part, x, y) {
if (part.className !== 'Part') {
if (sequencer.debug >= sequencer.WARN) {
console.warn(part, 'is not a Part');
}
return;
}
this.selectedPart = part;
this.selectedPart.pitch = this.yToPitch(y).number;
this.gripX = x - this.selectedPart.bbox.x;
};
KeyEditor.prototype.stopMovePart = function () {
this.selectedPart = undefined;
};
KeyEditor.prototype.movePart = function (x, y, autoUpdate) {
// console.log(this.selectedPart);
if (this.selectedPart === undefined) {
return;
}
if (typeof autoUpdate === 'undefined') {
autoUpdate = true;
}
// console.log(autoUpdate);
var
newPitch = this.yToPitch(y).number,
oldPitch = this.selectedPart.pitch,
newTicks = this.xToTicks(x - this.gripX),
oldTicks = this.selectedPart.ticks,
update = false;
if (newPitch !== oldPitch) {
this.selectedPart.track.transposePart(this.selectedPart, newPitch - oldPitch);
this.selectedPart.pitch = newPitch;
update = true;
}
if (newTicks !== oldTicks) {
this.selectedPart.track.movePart(this.selectedPart, newTicks - oldTicks);
update = true;
}
if (update === true && autoUpdate === true) {
this.song.update();
}
};
KeyEditor.prototype.getTicksAt = KeyEditor.prototype.xToTicks = function (x, snap) {
var ticks = ((x + this.scrollX) / this.width) * this.numTicks;
//console.log(this.scrollX,this.width,this.numTicks,ticks);
if (snap !== false && this.snapTicks !== 0) {
//ticks = floor(ticks/this.snapTicks) * this.snapTicks;
ticks = round(ticks / this.snapTicks) * this.snapTicks;
}
//console.log(ticks, this.snapTicks);
return ticks;
};
KeyEditor.prototype.getPitchAt = KeyEditor.prototype.yToPitch = function (y) {
//var note = this.highestNote - floor(((y + this.scrollY)/this.height) * this.pitchRange);
var note = this.highestNote - round(((y + this.scrollY) / this.height) * this.pitchRange);
note = createNote(note);
return note;
};
KeyEditor.prototype.getXAt = KeyEditor.prototype.ticksToX = function (ticks, snap) {
// var p = ticks/this.numTicks,
// x = (p * this.width) - this.scrollX;
var x = (ticks - this.startTicks) * this.tickWidth;
if (snap !== false && this.snapWidth !== 0) {
//x = (floor(x/this.snapWidth) * this.snapWidth);
x = (round(x / this.snapWidth) * this.snapWidth);
}
return x;
};
KeyEditor.prototype.getYAt = KeyEditor.prototype.pitchToY = function (noteNumber) {
var y = this.height - ((noteNumber - this.lowestNote + 1) * this.pitchHeight);
return y;
};
KeyEditor.prototype.getPositionAt = function (x) {
var ticks = this.getTicksAt(x);
// console.time('get position')
// var position = getPosition(this.song,['ticks',ticks]);
// console.timeEnd('get position')
// return position;
//console.time('get position')
this.playhead.set('ticks', ticks, false);
//console.timeEnd('get position')
return this.playhead.get();
};
KeyEditor.prototype.getPlayheadX = function (compensateForScroll) {
var x = ((this.song.ticks / this.song.durationTicks) * this.width);
//var x = ((this.song.millis/this.song.durationMillis) * this.width);
//var x = (this.song.percentage * this.width);
x = compensateForScroll === true ? x - this.scrollX : x;
return x;
};
KeyEditor.prototype.setPlayheadToX = function (x) {
var ticks = this.xToTicks(x, false);
this.song.setPlayhead('ticks', ticks);
};
KeyEditor.prototype.getPlayheadPosition = function (compensateForScroll) {
//return (sequencer.percentage * this.width);// - this.scrollX;
//return ((sequencer.millis/song.durationMillis) * this.width);// - this.scrollX;
//var x = ((this.song.millis/this.song.durationMillis) * this.width);
// change to ticks to make tempo changes visible by a faster moving playhead
var x = ((this.song.ticks / this.song.durationTicks) * this.width);
x = compensateForScroll === true ? x - this.scrollX : x;
return x;
};
KeyEditor.prototype.setPlayheadPosition = function (type, value) {
//console.log(this.scrollX,value, this.scrollX + value);
var ticks;
switch (type) {
case 'x':
ticks = this.xToTicks(value, false);
break;
case 'ticks':
ticks = value;
break;
case 'millis':
ticks = this.playhead.set('millis', value).ticks;
break;
case 'barsbeats':
case 'barsandbeats':
ticks = getPosition(this.song, ['barsbeats', value]).ticks;
break;
}
this.song.setPlayhead('ticks', ticks);
};
KeyEditor.prototype.getEventAt = function (x, y) {
var position = this.getSongPosition(x),
pitch = this.getPitchAt(y);
};
KeyEditor.prototype.getEventsInRect = function (x, y, w, h) {
var startPos = this.getSongPosition(x),
endPos = this.getSongPosition(x + w),
startPitch = this.getPitchAt(y + h),
endPitch = this.getPitchAt(y);
};
KeyEditor.prototype.getNoteAt = function (x, y) {
var position = this.getSongPosition(x),
pitch = this.getPitchAt(y);
};
KeyEditor.prototype.getNotesInRect = function (x, y, w, h) {
var startPos = this.getSongPosition(x),
endPos = this.getSongPosition(x + w),
startPitch = this.getPitchAt(y + h),
endPitch = this.getPitchAt(y);
};
// takes x,y and returns snapped x,y
KeyEditor.prototype.snap = function (x, y) {
return {
x: this.snapX(x),
y: this.snapY(y)
};
};
// takes x returns snapped x
KeyEditor.prototype.snapX = function (x) {
//return floor((x + this.scrollX)/this.snapWidth) * this.snapWidth;
return round((x + this.scrollX) / this.snapWidth) * this.snapWidth;
};
// takes y returns snapped y
KeyEditor.prototype.snapY = function (y) {
//return floor((y + this.scrollY)/this.snapHeight) * this.snapHeight;
return round((y + this.scrollY) / this.snapHeight) * this.snapHeight;
};
KeyEditor.prototype.setSnapX = function (snapX) {
if (snapX === undefined) {
return;
}
//console.log('in', snapX);
// 4 -> 1, 8 -> 0.5 16 -> 0.25
var beatLength = 4 / this.song.denominator;
if (snapX === 'off') {
this.snapTicks = 0;
} else if (snapX === 'tick') {
this.snapTicks = 1;
} else if (snapX === 'beat') {
// TODO: dependent on current time signature!
this.snapTicks = this.song.ppq * beatLength;
} else if (snapX === 'bar') {
// TODO: dependent on current time signature!
this.snapTicks = (this.song.ppq * this.song.nominator) * beatLength;
} else if (isNaN(snapX) && snapX.indexOf('ticks') !== -1) {
this.snapTicks = snapX.replace(/ticks/, '');
if (isNaN(this.snapTicks)) {
this.snapTicks = this.song.ppq / 4;// sixteenth note
} else {
this.snapTicks = parseInt(this.snapTicks);
}
} else {
if (isNaN(snapX) || snapX === 0) {
// by default snap is off
snapX = 0;
this.snapTicks = 0;
} else {
snapX = parseInt(snapX);
this.snapTicks = (4 / snapX) * this.song.ppq;
}
}
//console.log(snapX,this.snapTicks, beatLength);
this.snapValueX = snapX;
this.snapWidth = this.tickWidth * this.snapTicks;
};
KeyEditor.prototype.setSnapY = function (snapY) {
if (snapY === undefined) {
return;
}
this.snapValueY = snapY;
//todo: add other scales then chromatic
this.snapHeight = this.pitchHeight;
};
KeyEditor.prototype.removeNote = function (note) {
//note.part.removeNote(note);
//console.log(note.id);
note.part.removeEvents(note.noteOn, note.noteOff);
this.song.update();
};
KeyEditor.prototype.removePart = function (part) {
part.track.removePart(part);
this.song.update();
};
KeyEditor.prototype.prepareForRecording = function () {
this.recordedEventsObj = {};
this.recordedNotesObj = {};
};
KeyEditor.prototype.getSnapshot = function () {
var activeEventsObj,
activeNotesObj,
activePartsObj,
recordedNotesSong,
//recordingNotesSong,
recordedEventsSong,
nonActiveEvents = [],
nonActiveNotes = [],
nonActiveParts = [],
prevActiveEvents = [].concat(this.activeEvents),
prevActiveNotes = [].concat(this.activeNotes),
prevActiveParts = [].concat(this.activeParts),
recordedEvents = [],
recordedNotes = [],
recordingNotes = [],
//prevRemovedNotes = [].concat(this.removedNotes),
s, e, n, p, i, j, tmp, length,
startBar, endBar;
this.activeEvents = [];
this.activeNotes = [];
this.activeParts = [];
this.activeStateChangedEvents = [];
this.activeStateChangedNotes = [];
this.activeStateChangedParts = [];
//if(this.song.bars > this.numBars){
if (this.newNumBars !== this.numBars) {
startBar = this.numBars;
endBar = this.song.lastBar + 1;
//console.log(startBar,endBar)
//this.verticalLine.setStartPosition(getPosition(song, ['barsbeats', startBar, 1, 1, 0]));
//this.verticalLine.setEndPosition(getPosition(song, ['barsbeats', endBar, 1, 1, 0]));
this.endPosition = getPosition(this.song, ['barsbeats', endBar, 1, 1, 0, true]);
this.verticalLine.reset(getPosition(this.song, ['barsbeats', startBar, 1, 1, 0, true]), this.endPosition);
this.numBars = this.song.bars;
//console.log(this.song.lastBar, this.endPosition.barsAsString);
this.endTicks = this.endPosition.ticks;
this.numTicks = this.song.durationTicks;
this.width = this.numTicks * this.tickWidth;
//console.log('new width', this.width, this.numTicks, this.tickWidth);
//console.log('song has gotten longer boy!', this.song.bars, this.newNumBars, this.numBars, this.width);
this.maxScrollPosition = ceil(this.width / this.viewportWidth);
//this.numPages = ceil(this.width/this.viewportWidth);
this.numPages = ceil(this.numBars / this.barsPerPage);
}
activeEventsObj = this.song.activeEvents;
for (i in activeEventsObj) {
if (activeEventsObj.hasOwnProperty(i)) {
tmp = activeEventsObj[i];
this.activeEvents.push(tmp);
if (tmp.active !== true) {
tmp.active = true;
this.activeStateChangedEvents.push(tmp);
}
}
}
activeNotesObj = this.song.activeNotes;
for (i in activeNotesObj) {
if (activeNotesObj.hasOwnProperty(i)) {
tmp = activeNotesObj[i];
this.activeNotes.push(tmp);
//console.log(tmp, tmp.active);
if (tmp.active !== true) {
tmp.active = true;
this.activeStateChangedNotes.push(tmp);
}
}
}
activePartsObj = this.song.activeParts;
for (i in activePartsObj) {
if (activePartsObj.hasOwnProperty(i)) {
tmp = activePartsObj[i];
this.activeParts.push(tmp);
if (tmp.active !== true) {
tmp.active = true;
this.activeStateChangedParts.push(tmp);
}
}
}
// fixing issue #4
recordedEventsSong = this.song.recordedEvents;
if (recordedEventsSong) {
length = recordedEventsSong.length;
for (i = 0; i < length; i++) {
tmp = recordedEventsSong[i];
if (this.recordedEventsObj[tmp.id] === undefined) {
tmp.bbox = this.getEventRect(tmp);
recordedEvents.push(tmp);
this.recordedEventsObj[tmp.id] = tmp;
}
}
}
// fixing issue #4
recordedNotesSong = this.song.recordedNotes;
if (recordedNotesSong) {
length = recordedNotesSong.length;
for (i = 0; i < length; i++) {
tmp = recordedNotesSong[i];
if (this.recordedNotesObj[tmp.id] === undefined) {
this.recordedNotesObj[tmp.id] = tmp;
tmp.bbox = this.getNoteRect(tmp);
recordedNotes.push(tmp);
//console.log('recordedNotes', tmp);
} else if (tmp.endless === true) {
tmp.bbox = this.getNoteRect(tmp);
recordingNotes.push(tmp);
//console.log('endless1', tmp);
} else if (tmp.endless === false) {
tmp.bbox = this.getNoteRect(tmp);
recordingNotes.push(tmp);
//console.log('endless2', tmp);
tmp.endless = undefined;
}
//console.log(tmp.bbox.width);
}
}
/*
recordingNotesObj = this.song.recordingNotes;
for(i in recordingNotesObj){
if(recordingNotesObj.hasOwnProperty(i)){
tmp = recordingNotesObj[i];
tmp.bbox = this.getNoteRect(tmp);
recordingNotes.push(tmp);
}
}
*/
for (i = prevActiveEvents.length - 1; i >= 0; i--) {
tmp = prevActiveEvents[i];
if (tmp === undefined) {
console.warn('event is undefined');
continue;
}
if (activeEventsObj[tmp.id] === undefined) {
nonActiveEvents.push(tmp);
if (tmp.active !== false) {
tmp.active = false;
this.activeStateChangedEvents.push(tmp);
}
}
}
for (i = prevActiveNotes.length - 1; i >= 0; i--) {
tmp = prevActiveNotes[i];
if (tmp === undefined) {
console.warn('note is undefined');
continue;
}
if (activeNotesObj[tmp.id] === undefined) {
nonActiveNotes.push(tmp);
if (tmp.active !== false) {
tmp.active = false;
this.activeStateChangedNotes.push(tmp);
}
}
}
for (i = prevActiveParts.length - 1; i >= 0; i--) {
tmp = prevActiveParts[i];
if (tmp === undefined) {
console.warn('part is undefined');
continue;
}
if (activePartsObj[tmp.id] === undefined) {
nonActiveParts.push(tmp);
if (tmp.active !== false) {
tmp.active = false;
this.activeStateChangedParts.push(tmp);
}
}
}
if (this.song.playing) {
// this.currentPage = floor(sequencer.ticks / this.viewportTicks) + 1;
this.currentPage = floor(this.song.ticks / (this.barsPerPage * this.song.ticksPerBar)) + 1;
}
/*
tmp = this.song.parts;
n = false;
// check for empty parts and remove them -> @TODO: this should be done in track and/or part!
for(i = tmp.length - 1; i >= 0; i--){
p = tmp[i];
console.log(p.keepWhenEmpty);
if(p.keepWhenEmpty === true){
continue;
}
if(p.events.length === 0){
//console.log('empty part!');
p.track.removePart(p);
n = true;
}
}
if(n){
this.song.update();
}
*/
s = {
events: {
active: this.activeEvents,
inActive: this.nonActiveEvents,
recorded: recordedEvents,
new: this.newEvents,
changed: this.changedEvents,
removed: this.removedEvents,
stateChanged: this.activeStateChangedEvents
},
notes: {
active: this.activeNotes,
inActive: nonActiveNotes,
recorded: recordedNotes,
recording: recordingNotes,
new: this.newNotes,
changed: this.changedNotes,
removed: this.removedNotes,
stateChanged: this.activeStateChangedNotes
},
parts: {
active: this.activeParts,
inActive: nonActiveParts,
new: this.newParts,
changed: this.changedParts,
removed: this.removedParts,
stateChanged: this.activeStateChangedParts
},
hasNewBars: startBar !== endBar,
newWidth: this.width,
pageNo: this.currentPage,
lastPage: this.numPages
//newWidth: song.durationTicks * this.tickWidth
// hasNewBars: function(){
// if(startBar === endBar){
// return false;
// }
// }
};
this.newEvents = [];
this.changedEvents = [];
this.removedEvents = [];
this.newNotes = [];
this.changedNotes = [];
this.removedNotes = [];
this.newParts = [];
this.changedParts = [];
this.removedParts = [];
/*
tmp = this.song.parts;
n = false;
// check for empty parts and remove them -> @TODO: this should be done in track and/or part!
for(i = tmp.length - 1; i >= 0; i--){
p = tmp[i];
if(p.keepWhenEmpty === true){
continue;
}
if(p.events.length === 0){
//console.log('empty part!');
p.track.removePart(p);
n = true;
}
}
if(n){
this.song.update();
}
*/
return s;
};
// flipping pages
setPageData = function (editor, startBar) {
//editor.pageNo = no;
editor.numTicks = 0;
editor.startBar = startBar > 0 ? startBar : 0;
editor.startBar = editor.startBar > editor.numBars - editor.barsPerPage ? editor.numBars - editor.barsPerPage : editor.startBar;
editor.endBar = startBar + editor.barsPerPage;
editor.endBar = editor.endBar > editor.numBars ? editor.numBars : editor.endBar;
editor.endBar = editor.endBar < editor.barsPerPage ? editor.barsPerPage : editor.endBar;
console.log(startBar, editor.startBar, editor.endBar, editor.numBars, editor.numBars - editor.barsPerPage);
var i;
for (i = editor.startBar; i < editor.endBar; i++) {
editor.numTicks += editor.bars[i].ticksPerBar;
}
editor.tickWidth = editor.pageWidth / editor.numTicks;
editor.startPosition = editor.bars[editor.startBar];
editor.endPosition = editor.bars[editor.endBar];
editor.startTicks = editor.startPosition.ticks;
editor.endTicks = editor.endPosition.ticks;
editor.verticalLine.reset();
editor.horizontalLine.reset();
editor.eventIterator.reset();
//console.log('nextPage',editor.startPosition,editor.endPosition);
};
checkNextPage = function (editor) {
if (editor.song.playing() && editor.song.ticks >= editor.endTicks) {
//console.log('nextpage');
editor.nextPage();
//dispatchEvent(this, 'pagechange', {pageNo: this.pageNo, lastPage: this.lastPage});
}
requestAnimationFrame(function () {
checkNextPage(editor);
});
};
checkScrollPosition = function (editor) {
//console.log(editor.song.ticks,editor.scrollLimit,interrupt);
if (editor.song.playing && editor.interrupt === false) {
if (editor.song.ticks >= editor.scrollLimit) {
dispatchEvent(editor, 'scroll', { x: editor.scrollX + editor.viewportWidth });
editor.scrollLimit += (editor.viewportWidth / editor.tickWidth);
//editor.currentPage++;
} else {
var x = (floor(editor.song.ticks / editor.viewportTicks) * editor.viewportTicks) * editor.tickWidth;
if (editor.scrollX !== x) {
dispatchEvent(editor, 'scroll', { x: x });
}
}
}
requestAnimationFrame(function () {
checkScrollPosition(editor);
});
};
dispatchEvent = function (editor, id, data) {
//console.log(id,eventListeners);
var listeners = editor.eventListeners[id];
if (listeners) {
listeners.forEach(function (cb) {
cb(data);
});
}
};
handleKeys = function (editor) {
var p = editor.selectedPart,
n = editor.selectedNote;
if (p !== undefined) {
p.track.removePart(p);
this.song.update();
} else if (n !== undefined) {
n.part.removeNote(n);
this.song.update();
}
};
sequencer.createKeyEditor = function (song, config) {
return new KeyEditor(song, config);
};
sequencer.protectedScope.addInitMethod(function () {
getPosition = sequencer.protectedScope.getPosition;
createPlayhead = sequencer.protectedScope.createPlayhead;
createNote = sequencer.createNote;
debug = sequencer.debug;
floor = sequencer.protectedScope.floor;
round = sequencer.protectedScope.round;
typeString = sequencer.protectedScope.typeString;
objectToArray = sequencer.protectedScope.objectToArray;
arrayToObject = sequencer.protectedScope.arrayToObject;
getScaffoldingBars = sequencer.protectedScope.getScaffoldingBars;
createIteratorFactory = sequencer.protectedScope.createKeyEditorIteratorFactory;
});
}