UNPKG

audio-source-composer

Version:

Audio Source Composer

273 lines (226 loc) 10.9 kB
import InstructionIterator from "../instruction/iterator/InstructionIterator"; import {Instruction} from "../index"; import Values from "../values/Values"; import ArgType from "../instruction/argument/ArgType"; export default class TrackIterator { constructor(song, startingTrackName, startingStats= {}, filterProgramCommand=null) { this.song = song; if (!this.song.hasTrack(startingTrackName)) throw new Error("Invalid instruction track: " + startingTrackName); this.filterProgramCommand = filterProgramCommand || function() { return true; }; this.activeIterators = []; // program: trackStats.program, // Current program which all notes route through // destination: trackStats.destination, // Current destination sent to all playFrequency calls startingStats.startTime = 0; startingStats.startPosition = 0; // Necessary for position calculations startingStats.trackName = startingTrackName; if(!startingStats.beatsPerMinute) startingStats.beatsPerMinute = song.data.beatsPerMinute; if(!startingStats.timeDivision) startingStats.timeDivision = song.data.timeDivision; // Time division is passed to sub-groups this.processCommandInstructionCallback = (instructionData, stats) => this.processCommandInstruction(instructionData, stats); // Start Track iteration this.startTrackIteration(startingStats); } onLoadProgram(trackStats, program) { trackStats.program = null; // TODO: use dummy program here? } onExecuteProgram(trackStats, commandString, params) { // console.log("onExecuteProgram", trackStats.trackName, commandString, params); } /** Actions **/ startTrackIteration(trackStats) { trackStats.endPositionTicks = 0; trackStats.endPositionSeconds = 0; const iterator = this.instructionGetIterator(trackStats, this.processCommandInstructionCallback); this.activeIterators.push(iterator); return trackStats; } processCommandInstruction(instructionData, stats) { const processor = new Instruction(instructionData); const [commandString, argTypeList] = processor.processInstructionArgList(); switch(commandString) { case 'program': // Set Program (can be changed many times per track) const program = instructionData[2]; this.onLoadProgram(stats, program); // TODO: support OTG program break; case 'playTrack': if(!this.filterProgramCommand(commandString, stats)) break; // case 't': if(instructionData[1][0] === '@') { } else { instructionData = instructionData.slice().splice(1, 1); } let trackArgs = this.processArgList(stats, instructionData, Instruction.trackCommand); if(trackArgs[0].indexOf('*') !== -1) { this.onPlayWildcardTrack(stats, ...trackArgs) } else { this.onPlayTrack(stats, ...trackArgs) } break; default: if(!this.filterProgramCommand(commandString, stats)) break; let programArgs; if(argTypeList) { programArgs = this.processArgList(stats, instructionData, argTypeList); } else { programArgs = instructionData.slice(1); } // TODO: calculate bpm changes // Execute command: this.onExecuteProgram(stats, commandString, programArgs); break; // case 'destination': // TODO: Append destination? (does not handle note processing) // case 'd': // // if(!trackStats.originalDestination) // // trackStats.originalDestination = trackStats.destination; // trackStats.destination = instruction.loadDestinationFromParams(trackStats.destination, this.song); // // // this.song.programLoadInstance() // break; // default: } } onPlayWildcardTrack(parentStats, wildCardTrackName, trackDuration=null, trackStartTime=0, trackFrequency=null) { let trackNameList = Object.keys(this.song.data.tracks); trackNameList = TrackIterator.getWildCardTracks(wildCardTrackName, trackNameList); if(trackNameList.length > 0) { for (const trackName of trackNameList) { this.onPlayTrack(parentStats, trackName, trackDuration, trackStartTime, trackFrequency); } } else { console.warn("No tracks matched wildcard track: " + wildCardTrackName, Object.keys(this.song.data.tracks)); } } onPlayTrack(parentStats, trackName, trackDuration=null, trackStartPosition=null, trackFrequency=null) { if(parentStats.trackName === trackName) { console.warn("Skipping recursive track name: " + trackName); return; } const subTrackStats = { // program: trackStats.program, // Current program which all notes route through // destination: trackStats.destination, // Current destination sent to all playFrequency calls // parentStats: trackStats, startTime: parentStats.startTime + parentStats.positionSeconds, startPosition: trackStartPosition || 0, // trackStats.positionSeconds, trackName, beatsPerMinute: parentStats.beatsPerMinute, timeDivision: parentStats.timeDivision, // Time division is passed to sub-groups }; if(trackFrequency !== null) subTrackStats.transpose = Values.FREQ_A4 / trackFrequency; if(trackDuration !== null) subTrackStats.duration = trackDuration; if(parentStats.program) subTrackStats.program = parentStats.program; if(parentStats.destination) subTrackStats.destination = parentStats.destination; // console.log("onPlayTrack", trackName, trackDuration, trackStartTime, trackFrequency, subTrackStats); // TODO: process track instruction parameters this.startTrackIteration(subTrackStats); return subTrackStats; } processArgList(stats, instructionData, argTypeList) { let newArgs = []; let argIndex = 1; for (let i = 0; i < argTypeList.length; i++) { const argType = argTypeList[i]; if (argType.consumesArgument) { const value = typeof instructionData[argIndex] === "undefined" ? null : instructionData[argIndex]; // if(typeof instructionData[argIndex] !== "undefined") { const arg = argType.process(value, stats); newArgs.push(arg); if (value !== null && argType === ArgType.duration) this.processDuration(value, newArgs[i], stats); argIndex++ // } } else { const arg = argType.process(null, stats); newArgs.push(arg); } } return newArgs; } processDuration(durationTicks, durationSeconds, stats) { const trackEndPositionInTicks = stats.positionTicks + durationTicks; if (trackEndPositionInTicks > stats.endPositionTicks) stats.endPositionTicks = trackEndPositionInTicks; const trackPlaybackEndTime = stats.positionSeconds + durationSeconds; if (trackPlaybackEndTime > stats.endPositionSeconds) stats.endPositionSeconds = trackPlaybackEndTime; } instructionGetIterator(trackStats, instructionCallback=null) { if(!trackStats.trackName) throw new Error("Invalid trackStats.trackName"); return InstructionIterator.getIteratorFromSong( this.song, trackStats.trackName, trackStats, instructionCallback ) } /** @deprecated **/ getPositionInSeconds() { let totalPositionSeconds = 0; for(let i=0; i<this.activeIterators.length; i++) { const iterator = this.activeIterators[i]; const startPosition = iterator.stats.startPosition; const positionSeconds = startPosition + iterator.getPositionInSeconds(); if (positionSeconds > totalPositionSeconds) totalPositionSeconds = positionSeconds; } return totalPositionSeconds; } getEndPositionInSeconds() { let totalEndPositionSeconds = 0; for(let i=0; i<this.activeIterators.length; i++) { const iterator = this.activeIterators[i]; const endPositionSeconds = iterator.stats.startPosition + iterator.stats.endPositionSeconds; if (endPositionSeconds > totalEndPositionSeconds) totalEndPositionSeconds = endPositionSeconds; } return totalEndPositionSeconds; } hasReachedEnd() { for(let i=0; i<this.activeIterators.length; i++) { const iterator = this.activeIterators[i]; if(!iterator.hasReachedEnd()) return false; } return true; } /** Seeking **/ seekToEnd(callback=null, seekLength=1) { let seekPosition=0, finished = false; while(!finished) { seekPosition += seekLength; // Seek before finished = this.seekToPosition(seekPosition, callback); } // return this; } seekToPosition(positionSeconds) { let finished = true; for(let i=0; i<this.activeIterators.length; i++) { const iterator = this.activeIterators[i]; const stats = iterator.stats; if(!iterator.hasReachedEnd()) { iterator.seekToPosition(positionSeconds - stats.startPosition); } if (!iterator.hasReachedEnd()) { finished = false; } else { // TODO: update parent stats with end position? // const endPositionSeconds = stats.startPosition + stats.endPositionSeconds; // console.log("Track ends: ", i, stats.trackName, "in", endPositionSeconds); } } return finished; } static getWildCardTracks(wildCardTrackName, trackNameList) { var escapeRegex = (str) => str.replace(/([.*+?^=!:${}()|[\]/\\])/g, "\\$1"); const trackNameMatch = new RegExp("^" + wildCardTrackName.split("*").map(escapeRegex).join(".*") + "$"); return trackNameList.filter(trackName => trackNameMatch.test(trackName)) } }