UNPKG

qambi

Version:

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

191 lines (159 loc) 4.93 kB
import fetch from 'isomorphic-fetch' import {parseMIDIFile} from './midifile' import {MIDIEvent} from './midi_event' import {Part} from './part' import {Track} from './track' import {Song} from './song' import {base64ToBinary} from './util' import {status, json, arrayBuffer} from './fetch_helpers' import {getSettings} from './settings' function toSong(parsed, settings){ let tracks = parsed.tracks let ppq = parsed.header.ticksPerBeat // the PPQ as set in the loaded MIDI file let ppqFactor = 1 // check if we need to overrule the PPQ ofs the loaded MIDI file if(typeof settings.overrulePPQ === 'undefined' || settings.overrulePPQ === true){ let newPPQ = getSettings().ppq ppqFactor = newPPQ / ppq ppq = newPPQ } let timeEvents = [] let bpm = -1 let nominator = -1 let denominator = -1 let newTracks = [] for(let track of tracks.values()){ let lastTicks, lastType let ticks = 0 let type let channel = -1 let trackName let trackInstrumentName let events = []; for(let event of track){ ticks += (event.deltaTime * ppqFactor); if(channel === -1 && typeof event.channel !== 'undefined'){ channel = event.channel; } type = event.subtype; //console.log(event.deltaTime, ticks, type); switch(event.subtype){ case 'trackName': trackName = event.text; break; case 'instrumentName': if(event.text){ trackInstrumentName = event.text; } break; case 'noteOn': events.push(new MIDIEvent(ticks, 0x90, event.noteNumber, event.velocity)) break; case 'noteOff': events.push(new MIDIEvent(ticks, 0x80, event.noteNumber, event.velocity)) break; case 'setTempo': // sometimes 2 tempo events have the same position in ticks // we use the last in these cases (same as Cubase) let tmp = 60000000 / event.microsecondsPerBeat; if(ticks === lastTicks && type === lastType){ //console.info('tempo events on the same tick', ticks, tmp); timeEvents.pop(); } if(bpm === -1){ bpm = tmp; } timeEvents.push(new MIDIEvent(ticks, 0x51, tmp)) break; case 'timeSignature': // sometimes 2 time signature events have the same position in ticks // we use the last in these cases (same as Cubase) if(lastTicks === ticks && lastType === type){ console.info('time signature events on the same tick', ticks, event.numerator, event.denominator); timeEvents.pop(); } if(nominator === -1){ nominator = event.numerator denominator = event.denominator } timeEvents.push(new MIDIEvent(ticks, 0x58, event.numerator, event.denominator)) break; case 'controller': events.push(new MIDIEvent(ticks, 0xB0, event.controllerType, event.value)); break; case 'programChange': events.push(new MIDIEvent(ticks, 0xC0, event.programNumber)); break; case 'pitchBend': events.push(new MIDIEvent(ticks, 0xE0, event.value)); break; default: //console.log(track.name, event.type); } lastType = type lastTicks = ticks } if(events.length > 0){ //console.count(events.length) newTracks.push(new Track({ name: trackName, parts: [ new Part({ events: events }) ] })) } } let song = new Song({ ppq, bpm, nominator, denominator, tracks: newTracks, timeEvents: timeEvents }) //song.update() return song } export function songFromMIDIFileSync(data, settings = {}){ let song = null; if(data instanceof ArrayBuffer === true){ let buffer = new Uint8Array(data); song = toSong(parseMIDIFile(buffer), settings); }else if(typeof data.header !== 'undefined' && typeof data.tracks !== 'undefined'){ // a MIDI file that has already been parsed song = toSong(data, settings); }else{ // a base64 encoded MIDI file data = base64ToBinary(data); if(data instanceof ArrayBuffer === true){ let buffer = new Uint8Array(data); song = toSong(parseMIDIFile(buffer), settings); }else{ console.error('wrong data'); } } return song // { // ppq = newPPQ, // bpm = newBPM, // playbackSpeed = newPlaybackSpeed, // } = settings } export function songFromMIDIFile(url, settings = {}){ return new Promise((resolve, reject) => { // fetch(url, { // mode: 'no-cors' // }) fetch(url) .then(status) .then(arrayBuffer) .then(data => { resolve(songFromMIDIFileSync(data, settings)) }) .catch(e => { reject(e) }) }) }