qambi
Version:
MIDI sequencer, loads MIDI files, can record and playback MIDI, uses WebMIDI and WebAudio
474 lines (403 loc) • 16.7 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); // millis
var _init_midi = require('./init_midi');
var _init_audio = require('./init_audio');
var _midi_event = require('./midi_event');
var _util = require('./util');
var _eventlistener = require('./eventlistener');
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
// millis
var Scheduler = function () {
function Scheduler(song) {
_classCallCheck(this, Scheduler);
this.song = song;
this.notes = new Map();
this.bufferTime = song.bufferTime;
}
_createClass(Scheduler, [{
key: 'init',
value: function init(millis) {
this.songCurrentMillis = millis;
this.songStartMillis = millis;
this.events = this.song._allEvents;
this.numEvents = this.events.length;
this.index = 0;
this.maxtime = 0;
this.prevMaxtime = 0;
this.beyondLoop = false; // tells us if the playhead has already passed the looped section
this.precountingDone = false;
this.looped = false;
this.setIndex(this.songStartMillis);
}
}, {
key: 'updateSong',
value: function updateSong() {
//this.songCurrentMillis = this.song._currentMillis
this.events = this.song._allEvents;
this.numEvents = this.events.length;
this.index = 0;
this.maxtime = 0;
//this.precountingDone = false
this.setIndex(this.song._currentMillis);
}
}, {
key: 'setTimeStamp',
value: function setTimeStamp(timeStamp) {
this.timeStamp = timeStamp; // timestamp WebAudio context -> for internal instruments
this.timeStamp2 = performance.now(); // timestamp since opening webpage -> for external instruments
}
// get the index of the event that has its millis value at or right after the provided millis value
}, {
key: 'setIndex',
value: function setIndex(millis) {
var i = 0;
var event = void 0;
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = this.events[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
event = _step.value;
if (event.millis >= millis) {
this.index = i;
break;
}
i++;
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
this.beyondLoop = millis > this.song._rightLocator.millis;
// this.notes = new Map()
//this.looped = false
this.precountingDone = false;
}
}, {
key: 'getEvents',
value: function getEvents() {
var events = [];
if (this.song._loop === true && this.song._loopDuration < this.bufferTime) {
this.maxtime = this.songStartMillis + this.song._loopDuration - 1;
//console.log(this.maxtime, this.song.loopDuration);
}
if (this.song._loop === true) {
if (this.maxtime >= this.song._rightLocator.millis && this.beyondLoop === false) {
//console.log('LOOP', this.maxtime, this.song._rightLocator.millis)
var diff = this.maxtime - this.song._rightLocator.millis;
this.maxtime = this.song._leftLocator.millis + diff;
//console.log('-------LOOPED', this.maxtime, diff, this.song._leftLocator.millis, this.song._rightLocator.millis);
if (this.looped === false) {
this.looped = true;
var leftMillis = this.song._leftLocator.millis;
var rightMillis = this.song._rightLocator.millis;
for (var i = this.index; i < this.numEvents; i++) {
var event = this.events[i];
//console.log(event)
if (event.millis < rightMillis) {
event.time = this.timeStamp + event.millis - this.songStartMillis;
event.time2 = this.timeStamp2 + event.millis - this.songStartMillis;
events.push(event);
if (event.type === 144) {
this.notes.set(event.midiNoteId, event.midiNote);
}
//console.log(event.midiNoteId, event.type)
this.index++;
} else {
break;
}
}
// stop overflowing notes-> add a new note off event at the position of the right locator (end of the loop)
var endTicks = this.song._rightLocator.ticks - 1;
var endMillis = this.song.calculatePosition({ type: 'ticks', target: endTicks, result: 'millis' }).millis;
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = this.notes.values()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var note = _step2.value;
var noteOn = note.noteOn;
var noteOff = note.noteOff;
if (noteOff.millis <= rightMillis) {
continue;
}
var _event = new _midi_event.MIDIEvent(endTicks, 128, noteOn.data1, 0);
_event.millis = endMillis;
_event._part = noteOn._part;
_event._track = noteOn._track;
_event.midiNote = note;
_event.midiNoteId = note.id;
_event.time = this.timeStamp + _event.millis - this.songStartMillis;
_event.time2 = this.timeStamp2 + _event.millis - this.songStartMillis;
//console.log('added', event)
events.push(_event);
}
/*
// stop overflowing audio samples
for(i in this.scheduledAudioEvents){
if(this.scheduledAudioEvents.hasOwnProperty(i)){
audioEvent = this.scheduledAudioEvents[i];
if(audioEvent.endMillis > this.song.loopEnd){
audioEvent.stopSample(this.song.loopEnd/1000);
delete this.scheduledAudioEvents[i];
//console.log('stopping audio event', i);
}
}
}
*/
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
this.notes = new Map();
this.setIndex(leftMillis);
this.timeStamp += this.song._loopDuration;
this.songCurrentMillis -= this.song._loopDuration;
//console.log(events.length)
// get the audio events that start before song.loopStart
//this.getDanglingAudioEvents(this.song.loopStart, events);
}
} else {
this.looped = false;
}
}
//console.log('scheduler', this.looped)
// main loop
for (var _i = this.index; _i < this.numEvents; _i++) {
var _event2 = this.events[_i];
//console.log(event.millis, this.maxtime)
if (_event2.millis < this.maxtime) {
//event.time = this.timeStamp + event.millis - this.songStartMillis;
if (_event2.type === 'audio') {
// to be implemented
} else {
_event2.time = this.timeStamp + _event2.millis - this.songStartMillis;
_event2.time2 = this.timeStamp2 + _event2.millis - this.songStartMillis;
events.push(_event2);
}
this.index++;
} else {
break;
}
}
return events;
}
}, {
key: 'update',
value: function update(diff) {
var i, event, numEvents, track, events;
this.prevMaxtime = this.maxtime;
if (this.song.precounting) {
this.songCurrentMillis += diff;
this.maxtime = this.songCurrentMillis + this.bufferTime;
//console.log(this.songCurrentMillis)
events = this.song._metronome.getPrecountEvents(this.maxtime);
// if(events.length > 0){
// console.log(context.currentTime * 1000)
// console.log(events)
// }
if (this.maxtime > this.song._metronome.endMillis && this.precountingDone === false) {
var _events;
this.precountingDone = true;
this.timeStamp += this.song._precountDuration;
// start scheduling events of the song -> add the first events of the song
this.songCurrentMillis = this.songStartMillis;
//console.log('---->', this.songCurrentMillis)
this.songCurrentMillis += diff;
this.maxtime = this.songCurrentMillis + this.bufferTime;
(_events = events).push.apply(_events, _toConsumableArray(this.getEvents()));
//console.log(events)
}
} else {
this.songCurrentMillis += diff;
this.maxtime = this.songCurrentMillis + this.bufferTime;
events = this.getEvents();
//events = this.song._getEvents2(this.maxtime, (this.timeStamp - this.songStartMillis))
//events = this.getEvents2(this.maxtime, (this.timeStamp - this.songStartMillis))
//console.log('done', this.songCurrentMillis, diff, this.index, events.length)
}
// if(this.song.useMetronome === true){
// let metronomeEvents = this.song._metronome.getEvents2(this.maxtime, (this.timeStamp - this.songStartMillis))
// // if(metronomeEvents.length > 0){
// // console.log(this.maxtime, metronomeEvents)
// // }
// // metronomeEvents.forEach(e => {
// // e.time = (this.timeStamp + e.millis - this.songStartMillis)
// // })
// events.push(...metronomeEvents)
// }
numEvents = events.length;
// if(numEvents > 5){
// console.log(numEvents)
// }
//console.log(this.maxtime, this.song._currentMillis, '[diff]', this.maxtime - this.prevMaxtime)
for (i = 0; i < numEvents; i++) {
event = events[i];
track = event._track;
// console.log(this.maxtime, this.prevMaxtime, event.millis)
// if(event.millis > this.maxtime){
// // skip events that were harvest accidently while jumping the playhead -> should happen very rarely if ever
// console.log('skip', event)
// continue
// }
if (event._part === null || track === null) {
console.log(event);
this.notes.set(event.midiNoteId, event.midiNote);
continue;
}
if (event._part.muted === true || track.muted === true || event.muted === true) {
continue;
}
if ((event.type === 144 || event.type === 128) && typeof event.midiNote === 'undefined') {
// this is usually caused by the same note on the same ticks value, which is probably a bug in the midi file
//console.info('no midiNoteId', event)
continue;
}
// /console.log(event.ticks, event.time, event.millis, event.type, event._track.name)
if (event.type === 'audio') {
// to be implemented
} else {
track.processMIDIEvent(event);
if (track.name === this.song.id + '_metronome' && this.song.useMetronome) {
(0, _eventlistener.dispatchEvent)({
type: 'metronome',
data: event
});
}
//console.log(context.currentTime * 1000, event.time, this.index)
if (event.type === 144) {
this.notes.set(event.midiNoteId, event.midiNote);
} else if (event.type === 128) {
this.notes.delete(event.midiNoteId);
}
// if(this.notes.size > 0){
// console.log(this.notes)
// }
}
}
//console.log(this.index, this.numEvents)
//return this.index >= 10
return this.index >= this.numEvents; // last event of song
}
/*
unschedule(){
let min = this.song._currentMillis
let max = min + (bufferTime * 1000)
//console.log('reschedule', this.notes.size)
this.notes.forEach((note, id) => {
// console.log(note)
// console.log(note.noteOn.millis, note.noteOff.millis, min, max)
if(typeof note === 'undefined' || note.state === 'removed'){
//sample.unschedule(0, unscheduleCallback);
//console.log('NOTE IS UNDEFINED')
//sample.stop(0)
this.notes.delete(id)
}else if((note.noteOn.millis >= min || note.noteOff.millis < max) === false){
//sample.stop(0)
let noteOn = note.noteOn
let noteOff = new MIDIEvent(0, 128, noteOn.data1, 0)
noteOff.midiNoteId = note.id
noteOff.time = 0//context.currentTime + min
note._track.processMIDIEvent(noteOff)
this.notes.delete(id)
console.log('STOPPING', id, note._track.name)
}
})
//console.log('NOTES', this.notes.size)
//this.notes.clear()
}
*/
}, {
key: 'allNotesOff',
value: function allNotesOff() {
var _this = this;
var timeStamp = performance.now();
var outputs = (0, _init_midi.getMIDIOutputs)();
outputs.forEach(function (output) {
output.send([0xB0, 0x7B, 0x00], timeStamp + _this.bufferTime); // stop all notes
output.send([0xB0, 0x79, 0x00], timeStamp + _this.bufferTime); // reset all controllers
});
}
}]);
return Scheduler;
}();
/*
getEvents2(maxtime, timestamp){
let loop = true
let event
let result = []
//console.log(this.timeEventsIndex, this.songEventsIndex, this.metronomeEventsIndex)
while(loop){
let stop = false
if(this.timeEventsIndex < this.numTimeEvents){
event = this.timeEvents[this.timeEventsIndex]
if(event.millis < maxtime){
this.millisPerTick = event.millisPerTick
//console.log(this.millisPerTick)
this.timeEventsIndex++
}else{
stop = true
}
}
if(this.songEventsIndex < this.numSongEvents){
event = this.songEvents[this.songEventsIndex]
if(event.type === 0x2F){
loop = false
break
}
let millis = event.ticks * this.millisPerTick
if(millis < maxtime){
event.time = millis + timestamp
event.millis = millis
result.push(event)
this.songEventsIndex++
}else{
stop = true
}
}
if(this.song.useMetronome === true && this.metronomeEventsIndex < this.numMetronomeEvents){
event = this.metronomeEvents[this.metronomeEventsIndex]
let millis = event.ticks * this.millisPerTick
if(millis < maxtime){
event.time = millis + timestamp
event.millis = millis
result.push(event)
this.metronomeEventsIndex++
}else{
stop = true
}
}
if(stop){
loop = false
break
}
}
sortEvents(result)
return result
}
*/
exports.default = Scheduler;