UNPKG

webdaw-modules

Version:

a set of modules for building a web-based DAW

554 lines (473 loc) 16.5 kB
function position() { "use strict"; var //import round, // → defined in util.js floor, // → defined in util.js typeString, // → defined in util.js supportedTypes = "barsandbeats barsbeats time millis ticks perc percentage", supportedReturnTypes = "barsandbeats barsbeats time millis ticks all", //local bpm, nominator, denominator, ticksPerBeat, ticksPerBar, ticksPerSixteenth, millisPerTick, secondsPerTick, numSixteenth, ticks, millis, diffTicks, diffMillis, bar, beat, sixteenth, tick, type, index, returnType = "all", beyondEndOfSong = true, //public (song) millisToTicks, ticksToMillis, barsToMillis, barsToTicks, ticksToBars, millisToBars, //private checkBarsAndBeats, getDataFromEvent, getPositionData, calculateBarsAndBeats, //protected getPosition, checkPosition, fromMillis, fromTicks, fromBars; function getTimeEvent(song, unit, target) { // finds the time event that comes the closest before the target position var timeEvents = song.timeEvents, i, event; for (i = timeEvents.length - 1; i >= 0; i--) { event = timeEvents[i]; if (event[unit] <= target) { index = i; return event; } } } millisToTicks = function(song, targetMillis, beos) { beyondEndOfSong = beos === undefined ? true : false; fromMillis(song, targetMillis); //return round(ticks); return ticks; }; ticksToMillis = function(song, targetTicks, beos) { beyondEndOfSong = beos === undefined ? true : false; fromTicks(song, targetTicks); return millis; }; barsToMillis = function(song, position, beos) { // beos = beyondEndOfSong position = ["barsbeats"].concat(position); getPosition(song, position, "millis", beos); return millis; }; barsToTicks = function(song, position, beos) { // beos = beyondEndOfSong position = ["barsbeats"].concat(position); getPosition(song, position, "ticks", beos); //return round(ticks); return ticks; }; ticksToBars = function(song, ticks, beos) { beyondEndOfSong = beos === undefined ? true : false; fromTicks(song, ticks); calculateBarsAndBeats(); returnType = "barsandbeats"; return getPositionData(); }; millisToBars = function(song, millis, beos) { beyondEndOfSong = beos === undefined ? true : false; fromMillis(song, millis); calculateBarsAndBeats(); returnType = "barsandbeats"; return getPositionData(); }; fromMillis = function(song, targetMillis, event) { var lastEvent = song.lastEvent; if (beyondEndOfSong === false) { if (targetMillis > lastEvent.millis) { targetMillis = lastEvent.millis; } } if (event === undefined) { event = getTimeEvent(song, "millis", targetMillis); } 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; }; fromTicks = function(song, targetTicks, event) { var lastEvent = song.lastEvent; if (beyondEndOfSong === false) { if (targetTicks > lastEvent.ticks) { targetTicks = lastEvent.ticks; } } if (event === undefined) { event = getTimeEvent(song, "ticks", targetTicks); } 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; }; fromBars = function(song, targetBar, targetBeat, targetSixteenth, targetTick, event) { //console.time('fromBars'); var i = 0, diffBars, diffBeats, diffSixteenth, diffTick, lastEvent = song.lastEvent; if (beyondEndOfSong === false) { if (targetBar > lastEvent.bar) { targetBar = lastEvent.bar; } } targetBar = checkBarsAndBeats(targetBar); targetBeat = checkBarsAndBeats(targetBeat); targetSixteenth = checkBarsAndBeats(targetSixteenth); targetTick = checkBarsAndBeats(targetTick, true); if (event === undefined) { event = getTimeEvent(song, "bar", targetBar); } 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'); }; calculateBarsAndBeats = function() { // 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); }; getDataFromEvent = function(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); }; getPositionData = function(song) { var timeData, tickAsString, 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; tickAsString = tick === 0 ? "000" : tick < 10 ? "00" + tick : tick < 100 ? "0" + tick : tick; //positionData.barsAsString = (bar + 1) + ':' + (beat + 1) + ':' + (sixteenth + 1) + ':' + tickAsString; positionData.barsAsString = bar + ":" + beat + ":" + sixteenth + ":" + tickAsString; break; case "time": timeData = sequencer.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; tickAsString = tick === 0 ? "000" : tick < 10 ? "00" + tick : tick < 100 ? "0" + tick : tick; //positionData.barsAsString = (bar + 1) + ':' + (beat + 1) + ':' + (sixteenth + 1) + ':' + tickAsString; positionData.barsAsString = bar + ":" + beat + ":" + sixteenth + ":" + tickAsString; // time timeData = sequencer.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; } return positionData; }; checkBarsAndBeats = function(value, isTick) { value = isNaN(value) ? (isTick ? 0 : 1) : value; value = round(value); //value = value > maxValue ? maxValue : value; if (isTick) { value = value < 0 ? 0 : value; } else { value = value < 1 ? 1 : value; } return value; }; //@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'] checkPosition = function(args) { returnType = "all"; beyondEndOfSong = true; //console.log('----> checkPosition:', args); if (typeString(args) === "array") { var 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); //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; }; function getPosition2(song, unit, target, type, event) { if (unit === "millis") { fromMillis(song, target, event); } else if (unit === "ticks") { fromTicks(song, target, event); } if (type === "all") { calculateBarsAndBeats(); } return getPositionData(song); } getPosition = function(song, args) { //console.log('getPosition', args); var position = checkPosition(args), millis, tmp, snap; if (position === false) { console.error("wrong position data", args); 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; }; sequencer.protectedScope.getPosition = getPosition; sequencer.protectedScope.getPosition2 = getPosition2; sequencer.protectedScope.checkPosition = checkPosition; sequencer.protectedScope.millisToTicks = millisToTicks; sequencer.protectedScope.ticksToMillis = ticksToMillis; sequencer.protectedScope.ticksToBars = ticksToBars; sequencer.protectedScope.millisToBars = millisToBars; sequencer.protectedScope.barsToTicks = barsToTicks; sequencer.protectedScope.barsToMillis = barsToMillis; sequencer.protectedScope.addInitMethod(function() { round = sequencer.protectedScope.round; floor = sequencer.protectedScope.floor; typeString = sequencer.protectedScope.typeString; }); }