audio-source-composer
Version:
Audio Source Composer
188 lines (154 loc) • 6.95 kB
JavaScript
import {Values} from "../../../song";
export default class InstructionIterator {
constructor(instructionList, stats={}, instructionCallback=null) {
this.instructions = instructionList;
if(!stats.beatsPerMinute)
throw new Error("Missing stats.beatsPerMinute");
if(!stats.timeDivision)
throw new Error("Missing stats.timeDivision");
this.stats = stats;
this.instructionCallback = instructionCallback || function() {};
stats.currentIndex = -1; // TODO: rename to index?
stats.positionTicks = 0;
stats.positionSeconds = 0;
stats.lastInstructionPositionInTicks = 0;
// this.beatsPerMinute = beatsPerMinute;
// this.timeDivision = timeDivision;
//
// this.positionTicks = 0;
// this.endPositionTicks = 0;
// this.positionSeconds = 0;
// this.endPositionSeconds = 0;
// this.lastInstructionPositionInTicks = 0;
// this.lastInstructionPositionInSeconds = 0;
// this.currentIndex = -1;
this.generator = this.run();
}
getBeatsPerMinute() { return this.stats.beatsPerMinute; }
getTimeDivision() { return this.stats.timeDivision; }
getPositionInTicks() { return this.stats.positionTicks; }
getPositionInSeconds() { return this.stats.positionSeconds; }
getIndex() { return this.stats.currentIndex; }
hasReachedEnd() {
return this.stats.currentIndex >= this.instructions.length - 1;
}
processInstructionData(instructionData) {
let deltaDurationTicks = instructionData[0]; // .deltaDurationTicks;
if(deltaDurationTicks > 0) {
const stats = this.stats;
// if(this.quantizationTicks !== null)
// deltaDurationTicks = this.incrementPositionByQuantizedDelta(deltaDurationTicks, this.quantizationTicks, callback);
const instructionPositionTicks = stats.lastInstructionPositionInTicks + deltaDurationTicks;
stats.lastInstructionPositionInTicks = instructionPositionTicks;
// this.positionTicks = this.lastInstructionPositionInTicks;
if(stats.positionTicks >= instructionPositionTicks)
console.warn(`Next instruction appears before current position ${stats.positionTicks} >= ${instructionPositionTicks}`);
this.incrementPositionByDelta(instructionPositionTicks - stats.positionTicks);
}
this.instructionCallback(instructionData, this.stats);
}
incrementPositionByDelta(deltaDurationTicks) {
const stats = this.stats;
// console.log('incrementPositionByDelta', deltaDurationTicks);
stats.positionTicks += deltaDurationTicks;
if(stats.endPositionTicks < stats.positionTicks)
stats.endPositionTicks = stats.positionTicks;
const elapsedTime = Values.instance.durationTicksToSeconds(deltaDurationTicks, stats.timeDivision, stats.beatsPerMinute);
stats.positionSeconds += elapsedTime;
if(stats.endPositionSeconds < stats.positionSeconds)
stats.endPositionSeconds = stats.positionSeconds;
// this.lastInstructionPositionInSeconds = this.positionSeconds;
}
* run() {
const count = this.instructions.length;
const stats = this.stats;
for(stats.currentIndex=0; stats.currentIndex<count; stats.currentIndex++) {
const instructionData = this.instructions[stats.currentIndex]; // new Instruction(this.instructions[stats.currentIndex]);
this.processInstructionData(instructionData);
// this.incrementPositionByInstruction(instruction);
yield instructionData;
}
}
getInstructionData(index) {
if(index >= this.instructions.length)
throw new Error("Instruction is out of index: " + index);
return this.instructions[index];
}
// currentInstruction() {
// const stats = this.stats;
// if (stats.currentIndex === -1)
// throw new Error("Iterator has not been started");
// return this.getInstructionData(stats.currentIndex);
// }
nextInstructionData() {
return this.generator.next().value;
}
/** Seeking **/
seekToIndex(index, callback=null) {
if (!Number.isInteger(index))
throw new Error("Invalid seek index: " + typeof index);
const stats = this.stats;
while (index > stats.currentIndex) {
const instruction = this.nextInstructionData();
if(callback)
callback(instruction);
}
}
seekToEnd(callback=null) {
while (true) {
const instruction = this.nextInstructionData();
if(!instruction)
break;
if(callback)
callback(instruction);
}
}
seekToPosition(positionSeconds) {
const stats = this.stats;
while (!this.hasReachedEnd()) {
const nextInstructionData = this.getInstructionData(stats.currentIndex + 1);
const elapsedTime = Values.instance.durationTicksToSeconds(nextInstructionData[0], stats.timeDivision, stats.beatsPerMinute);
if(stats.positionSeconds + elapsedTime >= positionSeconds) {
break;
}
const instruction = this.nextInstructionData();
if(!instruction)
break;
}
}
seekToPositionTicks(positionTicks, callback=null) {
const stats = this.stats;
while (!this.hasReachedEnd() && stats.positionTicks <= positionTicks) {
const nextInstructionData = this.getInstructionData(stats.currentIndex + 1);
if(stats.positionTicks + nextInstructionData[0] > positionTicks) {
break;
}
const instruction = this.nextInstructionData();
if(!instruction)
break;
if(callback)
callback(instruction);
}
// TODO
// const remainingTicks = positionTicks - this.positionTicks;
// deltaDurationTicks = this.incrementPositionByQuantizedDelta(deltaDurationTicks, this.quantizationTicks, callback);
}
/** Iterator **/
[Symbol.iterator]() { return this.generator; }
/** Static **/
static getIteratorFromSong(song, trackName, stats={}, instructionCallback=null) {
const songData = song.data;
if(!songData.tracks[trackName])
throw new Error("Invalid instruction track: " + trackName);
const instructionList = songData.tracks[trackName];
if(!stats.timeDivision)
stats.timeDivision = songData.timeDivision;
if(!stats.beatsPerMinute)
stats.beatsPerMinute = songData.beatsPerMinute;
return new InstructionIterator(
instructionList,
stats,
instructionCallback
)
}
}