UNPKG

webdaw-modules

Version:

a set of modules for building a web-based DAW

586 lines (483 loc) 17.5 kB
function midiSystem() { 'use strict'; var context, // defined in open_module.js typeString, // defined in util.js objectForEach, // defined in util.js createMidiNote, // defined in midi_note.js createMidiEvent, // defined in midi_event.js slice = Array.prototype.slice, songMidiEventListener, midiAccess, midiInputsOrder, midiOutputsOrder, midiInitialized = false, midiEventListenerId = 0; function initMidi(cb) { // console.log(midiInitialized, navigator.requestMIDIAccess); if (midiInitialized === true) { cb(); return; } midiInitialized = true; if (navigator.requestMIDIAccess !== undefined) { navigator.requestMIDIAccess().then( // on success function midiAccessOnSuccess(midi) { if (midi._jazzInstances !== undefined) { sequencer.jazz = midi._jazzInstances[0]._Jazz.version; sequencer.midi = true; } else { sequencer.webmidi = true; sequencer.midi = true; } midiAccess = midi; midiAccess.onstatechange = getDevices; if (!midiAccess.inputs || !midiAccess.outputs) { // Firefox WebMIDI API support is still in progress cb(); } else { getDevices(); //console.log(midi, sequencer.midi, sequencer.webmidi, sequencer.jazz); cb(); } }, // on error function midiAccessOnError(e) { console.log('MIDI could not be initialized:', e); cb(); } ); // browsers without WebMIDI API } else { if (sequencer.browser === 'chrome') { console.log('Web MIDI API not enabled'); } else { console.log('Web MIDI API not supported'); } cb(); } } function getDevices(e) { //console.log('getDevices', e); var inputs, outputs; midiInputsOrder = []; midiOutputsOrder = []; inputs = midiAccess.inputs; inputs.forEach(function (input) { midiInputsOrder.push({ name: input.name, id: input.id }); sequencer.midiInputs[input.id] = input; }); midiInputsOrder.sort(function (a, b) { var nameA = a.name.toLowerCase(), nameB = b.name.toLowerCase(); if (nameA < nameB) { //sort string ascending return -1; } else if (nameA > nameB) { return 1; } return 0; //default return value (no sorting) }); sequencer.numMidiInputs = midiInputsOrder.length; outputs = midiAccess.outputs; outputs.forEach(function (output) { midiOutputsOrder.push({ name: output.name, id: output.id }); sequencer.midiOutputs[output.id] = output; }); midiOutputsOrder.sort(function (a, b) { var nameA = a.name.toLowerCase(), nameB = b.name.toLowerCase(); if (nameA < nameB) { //sort string ascending return -1; } else if (nameA > nameB) { return 1; } return 0; //default return value (no sorting) }); sequencer.numMidiOutputs = midiOutputsOrder.length; } function initMidiSong(song) { songMidiEventListener = function (e) { //console.log(e); handleMidiMessageSong(e, song, this); }; // by default a song listens to all available midi-in ports objectForEach(sequencer.midiInputs, function (port) { //port.addEventListener('midimessage', songMidiEventListener, false); port.onmidimessage = songMidiEventListener; song.midiInputs[port.id] = port; //console.log(port); }); //console.log(sequencer.midiInputs); objectForEach(sequencer.midiOutputs, function (port) { song.midiOutputs[port.id] = port; }); song.numMidiInputs = sequencer.numMidiInputs; song.numMidiOutputs = sequencer.numMidiOutputs; } function setMidiInputSong(id, flag, song) { var input = sequencer.midiInputs[id], tracks = song.tracks, maxi = song.numTracks - 1, i, track; flag = flag === undefined ? true : flag; if (input === undefined) { if (sequencer.debug === true) { console.log('no midi input with id', id, 'found'); } return; } if (flag === false) { delete song.midiInputs[id]; //input.removeEventListener('midimessage', songMidiEventListener, false); input.onmidimessage = null; song.numMidiInputs--; } else if (input !== undefined) { song.midiInputs[id] = input; //input.addEventListener('midimessage', songMidiEventListener, false); input.onmidimessage = songMidiEventListener; song.numMidiInputs++; } for (i = maxi; i >= 0; i--) { track = tracks[i]; track.setMidiInput(id, flag); // if(flag === false){ // delete track.midiInputs[id]; // } } } function setMidiOutputSong(id, flag, song) { var output = sequencer.midiOutputs[id], tracks = song.tracks, maxi = song.numTracks - 1, i, track, time; flag = flag === undefined ? true : flag; if (output === undefined) { if (sequencer.debug === true) { console.log('no midi output with id', id, 'found'); } return; } if (flag === false) { delete song.midiOutputs[id]; song.numMidiOutputs--; time = song.scheduler.lastEventTime + 100; output.send([0xB0, 0x7B, 0x00], time); // stop all notes output.send([0xB0, 0x79, 0x00], time); // reset all controllers } else if (output !== undefined) { song.midiOutputs[id] = output; song.numMidiOutputs++; } for (i = maxi; i >= 0; i--) { track = tracks[i]; track.setMidiOutput(id, flag); // if(flag === false){ // delete track.midiOutputs[id]; // } } } function handleMidiMessageSong(midiMessageEvent, song, input) { var data = midiMessageEvent.data, i, track, tracks = song.tracks, numTracks = song.numTracks, midiEvent, listeners; //console.log(midiMessageEvent.data); midiEvent = createMidiEvent(song.ticks, data[0], data[1], data[2]); for (i = 0; i < numTracks; i++) { track = tracks[i]; //console.log(track.midiInputs, input); /* if(midiEvent.channel === track.channel || track.channel === 0 || track.channel === 'any'){ handleMidiMessageTrack(midiEvent, track); } */ // like in Cubase, midi events from all devices, sent on any midi channel are forwarded to all tracks // set track.monitor to false if you don't want to receive midi events on a certain track // note that track.monitor is by default set to false and that track.monitor is automatically set to true // if you are recording on that track // console.log(track.monitor, track.id, input.id); if (track.monitor === true && track.midiInputs[input.id] !== undefined) { handleMidiMessageTrack(midiEvent, track, input); } } listeners = song.midiEventListeners[midiEvent.type]; if (listeners === undefined) { return; } objectForEach(listeners, function (listener) { listener(midiEvent, input); }); } //function handleMidiMessageTrack(midiMessageEvent, track, input){ function handleMidiMessageTrack(midiEvent, track, input) { var song = track.song, note, listeners, channel; //data = midiMessageEvent.data, //midiEvent = createMidiEvent(song.ticks, data[0], data[1], data[2]); //midiEvent.source = midiMessageEvent.srcElement.name; //console.log(midiMessageEvent) //console.log('---->', midiEvent.type); // add the exact time of this event so we can calculate its ticks position midiEvent.recordMillis = context.currentTime * 1000; // millis midiEvent.state = 'recorded'; if (midiEvent.type === 144) { note = createMidiNote(midiEvent); track.recordingNotes[midiEvent.data1] = note; //track.song.recordingNotes[note.id] = note; } else if (midiEvent.type === 128) { note = track.recordingNotes[midiEvent.data1]; // check if the note exists: if the user plays notes on her keyboard before the midi system has // been fully initialized, it can happen that the first incoming midi event is a NOTE OFF event if (note === undefined) { return; } note.addNoteOff(midiEvent); delete track.recordingNotes[midiEvent.data1]; //delete track.song.recordingNotes[note.id]; } //console.log(song.preroll, song.recording, track.recordEnabled); if ((song.prerolling || song.recording) && track.recordEnabled === 'midi') { if (midiEvent.type === 144) { track.song.recordedNotes.push(note); } track.recordPart.addEvent(midiEvent); // song.recordedEvents is used in the key editor track.song.recordedEvents.push(midiEvent); } else if (track.enableRetrospectiveRecording) { track.retrospectiveRecording.push(midiEvent); } // call all midi event listeners listeners = track.midiEventListeners[midiEvent.type]; if (listeners !== undefined) { objectForEach(listeners, function (listener) { listener(midiEvent, input); }); } channel = track.channel; if (channel === 'any' || channel === undefined || isNaN(channel) === true) { channel = 0; } objectForEach(track.midiOutputs, function (output) { //console.log('midi out', output, midiEvent.type); if (midiEvent.type === 128 || midiEvent.type === 144 || midiEvent.type === 176) { //console.log(midiEvent.type, midiEvent.data1, midiEvent.data2); output.send([midiEvent.type, midiEvent.data1, midiEvent.data2]); // }else if(midiEvent.type === 192){ // output.send([midiEvent.type + channel, midiEvent.data1]); } //output.send([midiEvent.status + channel, midiEvent.data1, midiEvent.data2]); }); // @TODO: maybe a track should be able to send its event to both a midi-out port and an internal heartbeat song? //console.log(track.routeToMidiOut); if (track.routeToMidiOut === false) { midiEvent.track = track; track.instrument.processEvent(midiEvent); } } function addMidiEventListener(args, obj) { // obj can be a track or a song args = slice.call(args); var id = midiEventListenerId++, types = {}, ids = [], listener, loop; // should I inline this? loop = function (args, i, maxi) { for (i = 0; i < maxi; i++) { var arg = args[i], type = typeString(arg); //console.log(type); if (type === 'array') { loop(arg, 0, arg.length); } else if (type === 'function') { listener = arg; } else if (isNaN(arg) === false) { arg = parseInt(arg, 10); if (sequencer.checkEventType(arg) !== false) { types[arg] = arg; } } else if (type === 'string') { if (sequencer.checkEventType(arg) !== false) { arg = sequencer.midiEventNumberByName(arg); types[arg] = arg; } } } }; loop(args, 0, args.length); //console.log('types', types, 'listener', listener); objectForEach(types, function (type) { //console.log(type); if (obj.midiEventListeners[type] === undefined) { obj.midiEventListeners[type] = {}; } obj.midiEventListeners[type][id] = listener; ids.push(type + '_' + id); }); //console.log(obj.midiEventListeners); return ids.length === 1 ? ids[0] : ids; } function removeMidiEventListener(id, obj) { var type; id = id.split('_'); type = id[0]; id = id[1]; delete obj.midiEventListeners[type][id]; } function removeMidiEventListeners() { } function getMidiPortsAsDropdown(config, obj) { var select = document.createElement('select'), option, ports, type = config.type, id = config.id || type, div = config.div, firstOption = config.firstOption; if (type !== 'input' && type !== 'output') { console.log('please set type to "input" or "output"'); return; } if (firstOption === undefined) { firstOption = type === 'input' ? 'choose MIDI in' : 'choose MIDI out'; } select.id = id; ports = type === 'input' ? obj.midiInputs : obj.midiOutputs; if (firstOption !== false) { option = document.createElement('option'); option.value = -1; option.innerHTML = firstOption; select.appendChild(option); } objectForEach(ports, function (port) { option = document.createElement('option'); option.value = port.id; option.innerHTML = port.name; select.appendChild(option); }); if (div) { div.appendChild(select); } return select; } sequencer.getMidiPortsAsDropdown = function () { getMidiPortsAsDropdown(sequencer); }; sequencer.getMidiInputsAsDropdown = function (config) { config = config || { type: 'input' }; return getMidiPortsAsDropdown(config, sequencer); }; sequencer.getMidiOutputsAsDropdown = function (config) { config = config || { type: 'output' }; return getMidiPortsAsDropdown(config, sequencer); }; function getMidiInputs(cb, obj) { var i, maxi; if (obj === sequencer) { for (i = 0, maxi = midiInputsOrder.length; i < maxi; i++) { cb(obj.midiInputs[midiInputsOrder[i].id], i); } } else { objectForEach(obj.midiInputs, function (port) { cb(port, i); }); } } function getMidiOutputs(cb, obj) { var i, maxi; if (obj === sequencer) { for (i = 0, maxi = midiOutputsOrder.length; i < maxi; i++) { cb(obj.midiOutputs[midiOutputsOrder[i].id], i); } } else { objectForEach(obj.midiOutputs, function (port, i) { cb(port, i); }); } } sequencer.getMidiInputs = function (cb) { getMidiInputs(cb, sequencer); }; sequencer.getMidiOutputs = function (cb) { getMidiOutputs(cb, sequencer); }; sequencer.protectedScope.addInitMethod(function () { context = sequencer.protectedScope.context; createMidiNote = sequencer.createMidiNote; createMidiEvent = sequencer.createMidiEvent; typeString = sequencer.protectedScope.typeString; objectForEach = sequencer.protectedScope.objectForEach; }); // close_module.js sequencer.protectedScope.initMidi = initMidi; // song.js sequencer.protectedScope.initMidiSong = initMidiSong; sequencer.protectedScope.getMidiInputs = getMidiInputs; sequencer.protectedScope.getMidiOutputs = getMidiOutputs; sequencer.protectedScope.setMidiInputSong = setMidiInputSong; sequencer.protectedScope.setMidiOutputSong = setMidiOutputSong; sequencer.protectedScope.addMidiEventListener = addMidiEventListener; sequencer.protectedScope.getMidiPortsAsDropdown = getMidiPortsAsDropdown; sequencer.protectedScope.removeMidiEventListener = removeMidiEventListener; sequencer.protectedScope.removeMidiEventListeners = removeMidiEventListeners; sequencer.protectedScope.handleMidiMessageTrack = handleMidiMessageTrack; } /* function handleMidiMessageTrack(e, track){ var data = e.data, midiEvent, song = track.song, note, listeners; //console.log(track.recordPart); if(song){ midiEvent = sequencer.createMidiEvent(song.ticks, data[0], data[1], data[2]); //console.log(midiEvent); if(midiEvent.type === 144){ note = createMidiNote(midiEvent); track.recordingNotes[midiEvent.data1] = note; //track.song.recordingNotes[note.id] = note; }else if(midiEvent.type === 128){ note = track.recordingNotes[midiEvent.data1]; note.addNoteOff(midiEvent); delete track.recordingNotes[midiEvent.data1]; //delete track.song.recordingNotes[note.id]; } if(song.recording && song.playing && track.recordEnabled){ if(midiEvent.type === 144){ track.song.recordedNotes.push(note); } track.recordPart.addEvent(midiEvent); track.song.recordedEvents.push(midiEvent); }else if(track.enableRetrospectiveRecording){ track.retrospectiveRecording.push(midiEvent); } // call all midi event listeners listeners = track.midiEventListeners[midiEvent.type]; if(listeners !== undefined){ objectForEach(listeners, function(listener, id){ listener(midiEvent); }); } }else{ console.error('unexpected situation!'); // does this ever happen? midiEvent = sequencer.createMidiEvent(0, data[0], data[1], data[2]); midiEvent.millis = song.recordTimestamp - sequencer.getTime(); if(track.enableRetrospectiveRecording){ track.retrospectiveRecording.push(midiEvent); } //@TODO: add parser for retrospective recording: convert millis to ticks } if(track.midiOutput !== undefined){ track.midiOutput.send([data[0], data[1], data[2]]); }else{ midiEvent.track = track; track.instrument.processEvent(midiEvent); } } */