UNPKG

qambi

Version:

MIDI sequencer, loads MIDI files, can record and playback MIDI, uses WebMIDI and WebAudio

474 lines (403 loc) 16.7 kB
'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;