UNPKG

qambi

Version:

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

667 lines (553 loc) 18.8 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); exports.millisToTicks = millisToTicks; exports.ticksToMillis = ticksToMillis; exports.barsToMillis = barsToMillis; exports.barsToTicks = barsToTicks; exports.ticksToBars = ticksToBars; exports.millisToBars = millisToBars; exports.getPosition2 = getPosition2; exports.calculatePosition = calculatePosition; var _util = require('./util'); var supportedTypes = 'barsandbeats barsbeats time millis ticks perc percentage', supportedReturnTypes = 'barsandbeats barsbeats time millis ticks all', floor = Math.floor, round = Math.round; var //local bpm = void 0, nominator = void 0, denominator = void 0, ticksPerBeat = void 0, ticksPerBar = void 0, ticksPerSixteenth = void 0, millisPerTick = void 0, secondsPerTick = void 0, numSixteenth = void 0, ticks = void 0, millis = void 0, diffTicks = void 0, diffMillis = void 0, bar = void 0, beat = void 0, sixteenth = void 0, tick = void 0, // type, index = void 0, returnType = 'all', beyondEndOfSong = true; function getTimeEvent(song, unit, target) { // finds the time event that comes the closest before the target position var timeEvents = song._timeEvents; //console.log(song._timeEvents, unit, target) for (var i = timeEvents.length - 1; i >= 0; i--) { var event = timeEvents[i]; //console.log(unit, target, event) if (event[unit] <= target) { index = i; return event; } } return null; } function millisToTicks(song, targetMillis) { var beos = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; beyondEndOfSong = beos; fromMillis(song, targetMillis); //return round(ticks); return ticks; } function ticksToMillis(song, targetTicks) { var beos = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; beyondEndOfSong = beos; fromTicks(song, targetTicks); return millis; } function barsToMillis(song, position, beos) { // beos = beyondEndOfSong calculatePosition(song, { type: 'barsbeat', position: position, result: 'millis', beos: beos }); return millis; } function barsToTicks(song, position, beos) { // beos = beyondEndOfSong calculatePosition(song, { type: 'barsbeats', position: position, result: 'ticks', beos: beos }); //return round(ticks); return ticks; } function ticksToBars(song, target) { var beos = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; beyondEndOfSong = beos; fromTicks(song, target); calculateBarsAndBeats(); returnType = 'barsandbeats'; return getPositionData(); } function millisToBars(song, target) { var beos = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; beyondEndOfSong = beos; fromMillis(song, target); calculateBarsAndBeats(); returnType = 'barsandbeats'; return getPositionData(); } // main calculation function for millis position function fromMillis(song, targetMillis, event) { var lastEvent = song._lastEvent; if (beyondEndOfSong === false) { if (targetMillis > lastEvent.millis) { targetMillis = lastEvent.millis; } } if (typeof event === 'undefined') { event = getTimeEvent(song, 'millis', targetMillis); } //console.log(event) getDataFromEvent(event); // if the event is not exactly at target millis, calculate the diff if (event.millis === targetMillis) { diffMillis = 0; diffTicks = 0; } else { diffMillis = targetMillis - event.millis; diffTicks = diffMillis / millisPerTick; } millis += diffMillis; ticks += diffTicks; return ticks; } // main calculation function for ticks position function fromTicks(song, targetTicks, event) { var lastEvent = song._lastEvent; if (beyondEndOfSong === false) { if (targetTicks > lastEvent.ticks) { targetTicks = lastEvent.ticks; } } if (typeof event === 'undefined') { event = getTimeEvent(song, 'ticks', targetTicks); } //console.log(event) getDataFromEvent(event); // if the event is not exactly at target ticks, calculate the diff if (event.ticks === targetTicks) { diffTicks = 0; diffMillis = 0; } else { diffTicks = targetTicks - ticks; diffMillis = diffTicks * millisPerTick; } ticks += diffTicks; millis += diffMillis; return millis; } // main calculation function for bars and beats position function fromBars(song, targetBar, targetBeat, targetSixteenth, targetTick) { var event = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : null; //console.time('fromBars'); var i = 0, diffBars = void 0, diffBeats = void 0, diffSixteenth = void 0, diffTick = void 0, lastEvent = song._lastEvent; if (beyondEndOfSong === false) { if (targetBar > lastEvent.bar) { targetBar = lastEvent.bar; } } if (event === null) { event = getTimeEvent(song, 'bar', targetBar); } //console.log(event) getDataFromEvent(event); //correct wrong position data, for instance: '3,3,2,788' becomes '3,4,4,068' in a 4/4 measure at PPQ 480 while (targetTick >= ticksPerSixteenth) { targetSixteenth++; targetTick -= ticksPerSixteenth; } while (targetSixteenth > numSixteenth) { targetBeat++; targetSixteenth -= numSixteenth; } while (targetBeat > nominator) { targetBar++; targetBeat -= nominator; } event = getTimeEvent(song, 'bar', targetBar, index); for (i = index; i >= 0; i--) { event = song._timeEvents[i]; if (event.bar <= targetBar) { getDataFromEvent(event); break; } } // get the differences diffTick = targetTick - tick; diffSixteenth = targetSixteenth - sixteenth; diffBeats = targetBeat - beat; diffBars = targetBar - bar; //bar is always less then or equal to targetBar, so diffBars is always >= 0 //console.log('diff',diffBars,diffBeats,diffSixteenth,diffTick); //console.log('millis',millis,ticksPerBar,ticksPerBeat,ticksPerSixteenth,millisPerTick); // convert differences to milliseconds and ticks diffMillis = diffBars * ticksPerBar * millisPerTick; diffMillis += diffBeats * ticksPerBeat * millisPerTick; diffMillis += diffSixteenth * ticksPerSixteenth * millisPerTick; diffMillis += diffTick * millisPerTick; diffTicks = diffMillis / millisPerTick; //console.log(diffBars, ticksPerBar, millisPerTick, diffMillis, diffTicks); // set all current position data bar = targetBar; beat = targetBeat; sixteenth = targetSixteenth; tick = targetTick; //console.log(tick, targetTick) millis += diffMillis; //console.log(targetBar, targetBeat, targetSixteenth, targetTick, ' -> ', millis); ticks += diffTicks; //console.timeEnd('fromBars'); } function calculateBarsAndBeats() { // spread the difference in tick over bars, beats and sixteenth var tmp = round(diffTicks); while (tmp >= ticksPerSixteenth) { sixteenth++; tmp -= ticksPerSixteenth; while (sixteenth > numSixteenth) { sixteenth -= numSixteenth; beat++; while (beat > nominator) { beat -= nominator; bar++; } } } tick = round(tmp); } // store properties of event in local scope function getDataFromEvent(event) { bpm = event.bpm; nominator = event.nominator; denominator = event.denominator; ticksPerBar = event.ticksPerBar; ticksPerBeat = event.ticksPerBeat; ticksPerSixteenth = event.ticksPerSixteenth; numSixteenth = event.numSixteenth; millisPerTick = event.millisPerTick; secondsPerTick = event.secondsPerTick; bar = event.bar; beat = event.beat; sixteenth = event.sixteenth; tick = event.tick; ticks = event.ticks; millis = event.millis; //console.log(bpm, event.type); //console.log('ticks', ticks, 'millis', millis, 'bar', bar); } function getPositionData(song) { var timeData = void 0, positionData = {}; switch (returnType) { case 'millis': //positionData.millis = millis; positionData.millis = round(millis * 1000) / 1000; positionData.millisRounded = round(millis); break; case 'ticks': //positionData.ticks = ticks; positionData.ticks = round(ticks); //positionData.ticksUnrounded = ticks; break; case 'barsbeats': case 'barsandbeats': positionData.bar = bar; positionData.beat = beat; positionData.sixteenth = sixteenth; positionData.tick = tick; //positionData.barsAsString = (bar + 1) + ':' + (beat + 1) + ':' + (sixteenth + 1) + ':' + tickAsString; positionData.barsAsString = bar + ':' + beat + ':' + sixteenth + ':' + getTickAsString(tick); break; case 'time': timeData = (0, _util.getNiceTime)(millis); positionData.hour = timeData.hour; positionData.minute = timeData.minute; positionData.second = timeData.second; positionData.millisecond = timeData.millisecond; positionData.timeAsString = timeData.timeAsString; break; case 'all': // millis //positionData.millis = millis; positionData.millis = round(millis * 1000) / 1000; positionData.millisRounded = round(millis); // ticks //positionData.ticks = ticks; positionData.ticks = round(ticks); //positionData.ticksUnrounded = ticks; // barsbeats positionData.bar = bar; positionData.beat = beat; positionData.sixteenth = sixteenth; positionData.tick = tick; //positionData.barsAsString = (bar + 1) + ':' + (beat + 1) + ':' + (sixteenth + 1) + ':' + tickAsString; positionData.barsAsString = bar + ':' + beat + ':' + sixteenth + ':' + getTickAsString(tick); // time timeData = (0, _util.getNiceTime)(millis); positionData.hour = timeData.hour; positionData.minute = timeData.minute; positionData.second = timeData.second; positionData.millisecond = timeData.millisecond; positionData.timeAsString = timeData.timeAsString; // extra data positionData.bpm = round(bpm * song.playbackSpeed, 3); positionData.nominator = nominator; positionData.denominator = denominator; positionData.ticksPerBar = ticksPerBar; positionData.ticksPerBeat = ticksPerBeat; positionData.ticksPerSixteenth = ticksPerSixteenth; positionData.numSixteenth = numSixteenth; positionData.millisPerTick = millisPerTick; positionData.secondsPerTick = secondsPerTick; // use ticks to make tempo changes visible by a faster moving playhead positionData.percentage = ticks / song._durationTicks; //positionData.percentage = millis / song.durationMillis; break; default: return null; } return positionData; } function getTickAsString(t) { if (t === 0) { t = '000'; } else if (t < 10) { t = '00' + t; } else if (t < 100) { t = '0' + t; } return t; } // used by playhead function getPosition2(song, unit, target, type, event) { if (unit === 'millis') { fromMillis(song, target, event); } else if (unit === 'ticks') { fromTicks(song, target, event); } returnType = type; if (returnType === 'all') { calculateBarsAndBeats(); } return getPositionData(song); } // improved version of getPosition function calculatePosition(song, settings) { var type = settings.type, target = settings.target, _settings$result = settings.result, result = _settings$result === undefined ? 'all' : _settings$result, _settings$beos = settings.beos, beos = _settings$beos === undefined ? true : _settings$beos, _settings$snap = settings.snap, snap = _settings$snap === undefined ? -1 : _settings$snap; if (supportedReturnTypes.indexOf(result) === -1) { console.warn('unsupported return type, \'all\' used instead of \'' + result + '\''); result = 'all'; } returnType = result; beyondEndOfSong = beos; if (supportedTypes.indexOf(type) === -1) { console.error('unsupported type ' + type); return false; } switch (type) { case 'barsbeats': case 'barsandbeats': var _target = _slicedToArray(target, 4), _target$ = _target[0], targetbar = _target$ === undefined ? 1 : _target$, _target$2 = _target[1], targetbeat = _target$2 === undefined ? 1 : _target$2, _target$3 = _target[2], targetsixteenth = _target$3 === undefined ? 1 : _target$3, _target$4 = _target[3], targettick = _target$4 === undefined ? 0 : _target$4; //console.log(targetbar, targetbeat, targetsixteenth, targettick) fromBars(song, targetbar, targetbeat, targetsixteenth, targettick); return getPositionData(song); case 'time': // calculate millis out of time array: hours, minutes, seconds, millis var _target2 = _slicedToArray(target, 4), _target2$ = _target2[0], targethour = _target2$ === undefined ? 0 : _target2$, _target2$2 = _target2[1], targetminute = _target2$2 === undefined ? 0 : _target2$2, _target2$3 = _target2[2], targetsecond = _target2$3 === undefined ? 0 : _target2$3, _target2$4 = _target2[3], targetmillisecond = _target2$4 === undefined ? 0 : _target2$4; var _millis = 0; _millis += targethour * 60 * 60 * 1000; //hours _millis += targetminute * 60 * 1000; //minutes _millis += targetsecond * 1000; //seconds _millis += targetmillisecond; //milliseconds fromMillis(song, _millis); calculateBarsAndBeats(); return getPositionData(song); case 'millis': fromMillis(song, target); calculateBarsAndBeats(); return getPositionData(song); case 'ticks': //console.log(song, target) fromTicks(song, target); calculateBarsAndBeats(); return getPositionData(song); case 'perc': case 'percentage': //millis = position[1] * song.durationMillis; //fromMillis(song, millis); //console.log(millis); ticks = target * song._durationTicks; // target must be in ticks! //console.log(ticks, song._durationTicks) if (snap !== -1) { ticks = floor(ticks / snap) * snap; //fromTicks(song, ticks); //console.log(ticks); } fromTicks(song, ticks); calculateBarsAndBeats(); var tmp = getPositionData(song); //console.log('diff', position[1] - tmp.percentage); return tmp; default: return false; } } /* //@param: 'millis', 1000, [true] //@param: 'ticks', 1000, [true] //@param: 'barsandbeats', 1, ['all', true] //@param: 'barsandbeats', 60, 4, 3, 120, ['all', true] //@param: 'barsandbeats', 60, 4, 3, 120, [true, 'all'] function checkPosition(type, args, returnType = 'all'){ beyondEndOfSong = true; console.log('----> checkPosition:', args, typeString(args)); if(typeString(args) === 'array'){ let numArgs = args.length, position, i, a, positionLength; type = args[0]; // support for [['millis', 3000]] if(typeString(args[0]) === 'array'){ //console.warn('this shouldn\'t happen!'); args = args[0]; type = args[0]; numArgs = args.length; } position = [type]; console.log('check position', args, numArgs, supportedTypes.indexOf(type)); //console.log('arg', 0, '->', type); if(supportedTypes.indexOf(type) !== -1){ for(i = 1; i < numArgs; i++){ a = args[i]; //console.log('arg', i, '->', a); if(a === true || a === false){ beyondEndOfSong = a; }else if(isNaN(a)){ if(supportedReturnTypes.indexOf(a) !== -1){ returnType = a; }else{ return false; } }else { position.push(a); } } //check number of arguments -> either 1 number or 4 numbers in position, e.g. ['barsbeats', 1] or ['barsbeats', 1, 1, 1, 0], // or ['perc', 0.56, numberOfTicksToSnapTo] positionLength = position.length; if(positionLength !== 2 && positionLength !== 3 && positionLength !== 5){ return false; } //console.log(position, returnType, beyondEndOfSong); //console.log('------------------------------------') return position; } } return false; } export function getPosition(song, type, args){ //console.log('getPosition', args); if(typeof args === 'undefined'){ return { millis: 0 } } let position = checkPosition(type, args), millis, tmp, snap; if(position === false){ error('wrong position data'); return false; } switch(type){ case 'barsbeats': case 'barsandbeats': fromBars(song, position[1], position[2], position[3], position[4]); return getPositionData(song); case 'time': // calculate millis out of time array: hours, minutes, seconds, millis millis = 0; tmp = position[1] || 0; millis += tmp * 60 * 60 * 1000; //hours tmp = position[2] || 0; millis += tmp * 60 * 1000; //minutes tmp = position[3] || 0; millis += tmp * 1000; //seconds tmp = position[4] || 0; millis += tmp; //milliseconds fromMillis(song, millis); calculateBarsAndBeats(); return getPositionData(song); case 'millis': fromMillis(song, position[1]); calculateBarsAndBeats(); return getPositionData(song); case 'ticks': fromTicks(song, position[1]); calculateBarsAndBeats(); return getPositionData(song); case 'perc': case 'percentage': snap = position[2]; //millis = position[1] * song.durationMillis; //fromMillis(song, millis); //console.log(millis); ticks = position[1] * song.durationTicks; if(snap !== undefined){ ticks = floor(ticks/snap) * snap; //fromTicks(song, ticks); //console.log(ticks); } fromTicks(song, ticks); calculateBarsAndBeats(); tmp = getPositionData(song); //console.log('diff', position[1] - tmp.percentage); return tmp; } return false; } */