UNPKG

pxt-common-packages

Version:
204 lines (165 loc) 6.43 kB
namespace music.sequencer { const SEQUENCER_STOP_MESSAGE = 3243; const SEQUENCER_TICK_MESSAGE = 3244; const SEQUENCER_STATE_CHANGE_MESSAGE = 3245; const SEQUENCER_LOOPED_MESSAGE = 3246; export class Sequencer { currentTick: number; isPlaying: boolean; isLooping: boolean; isRunning: boolean; constructor(public song: Song) { this.currentTick = 0; this.isPlaying = false; this.isLooping = false; } start(loop: boolean) { this.currentTick = 0; this.isLooping = loop; this.isPlaying = true; if (this.isRunning) return; this.isRunning = true; control.runInParallel(() => { while (this.isPlaying) { this.scheduleCurrentTick(); this.currentTick ++; if (this.currentTick >= this.song.beatsPerMeasure * this.song.measures * this.song.ticksPerBeat) { if (this.isLooping) this.currentTick = 0; else this.isPlaying = false; } pause(this.tickToMs(1)) } this.isRunning = false; }) } stop() { this.isPlaying = false; } tickToMs(ticks: number) { return ((60000 / this.song.beatsPerMinute) / this.song.ticksPerBeat) * ticks; } protected scheduleCurrentTick() { for (const track of this.song.tracks) { if (track.currentNoteEvent.startTick === this.currentTick) { if (track.isMelodicTrack) { this.scheduleMelodicTrack(track as MelodicTrack); } else { this.scheduleDrumTrack(track as DrumTrack); } track.advanceNoteEvent(); } } } protected scheduleMelodicTrack(track: MelodicTrack) { for (let i = 0; i < track.currentNoteEvent.polyphony; i++) { playInstructions( 0, renderInstrument( track.instrument, lookupFrequency(track.currentNoteEvent.getNote(i, track.instrument.octave)), this.tickToMs(track.currentNoteEvent.endTick - track.currentNoteEvent.startTick), music.volume() ) ); } } protected scheduleDrumTrack(track: DrumTrack) { for (let i = 0; i < track.currentNoteEvent.polyphony; i++) { playInstructions( 0, renderDrumInstrument( track.drums[track.currentNoteEvent.getNote(i, undefined)], music.volume() ) ); } } } let activeSimSequencers: _SimulatorSequencer[]; export function _stopAllSimSequencers() { if (activeSimSequencers) { for (const seq of activeSimSequencers) { seq.stop(); seq.dispose(); } activeSimSequencers = []; } } // Simulator only! Does nothing on hardware export class _SimulatorSequencer { protected id: number; constructor() { if (!activeSimSequencers) activeSimSequencers = []; activeSimSequencers.push(this); this.id = _createSequencer(); this.setVolume(music.volume()); } play(song: Buffer, loop: boolean) { this.setVolume(music.volume()); _sequencerPlaySong(this.id, song, loop) } stop() { _sequencerStop(this.id); } setVolume(volume: number) { _sequencerSetVolume(this.id, volume); } setTrackVolume(trackIndex: number, volume: number) { _sequencerSetTrackVolume(this.id, trackIndex, volume) } setDrumTrackVolume(trackIndex: number, drumIndex: number, volume: number) { _sequencerSetDrumTrackVolume(this.id, drumIndex, trackIndex, volume) } state() { return _sequencerState(this.id) || "stop"; } currentTick() { return _sequencerCurrentTick(this.id); } dispose() { _sequencerDispose(this.id); } onTick(handler: (tick: number) => void) { control.onEvent(SEQUENCER_TICK_MESSAGE, this.id, () => { handler(this.currentTick()); }); } onStateChange(handler: (state: string) => void) { control.onEvent(SEQUENCER_STATE_CHANGE_MESSAGE, this.id, () => { handler(this.state()); }); } onStop(handler: () => void) { control.onEvent(SEQUENCER_STOP_MESSAGE, this.id, () => { handler(); }); } onLooped(handler: () => void) { control.onEvent(SEQUENCER_LOOPED_MESSAGE, this.id, () => { handler(); }); } } //% promise //% shim=music::_createSequencer declare function _createSequencer(): number //% shim=music::_sequencerState declare function _sequencerState(id: number): string; //% shim=music::_sequencerCurrentTick declare function _sequencerCurrentTick(id: number): number; //% shim=music::_sequencerPlaySong declare function _sequencerPlaySong(id: number, song: Buffer, loop: boolean): void; //% shim=music::_sequencerStop declare function _sequencerStop(id: number): void; //% shim=music::_sequencerSetVolume declare function _sequencerSetVolume(id: number, volume: number): void; //% shim=music::_sequencerSetVolumeForAll declare function _sequencerSetVolumeForAll(volume: number): void; //% shim=music::_sequencerSetTrackVolume declare function _sequencerSetTrackVolume(id: number, trackIndex: number, volume: number): void; //% shim=music::_sequencerSetDrumTrackVolume declare function _sequencerSetDrumTrackVolume(id: number, trackIndex: number, drumIndex: number, volume: number): void; //% shim=music::_sequencerDispose declare function _sequencerDispose(id: number): void; }