UNPKG

pxt-common-packages

Version:
203 lines (180 loc) 6.01 kB
namespace music { export enum PlaybackMode { //% block="until done" UntilDone, //% block="in background" InBackground, //% block="looping in background" LoopingInBackground } let stateStack: PlayableState[]; class PlayableState { looping: Playable[]; constructor() { this.looping = []; } stopLooping() { for (const p of this.looping) { p.stopped = true; } this.looping = []; } } function state() { _init(); return stateStack[stateStack.length - 1]; } function _init() { if (stateStack) return; stateStack = [new PlayableState()]; game.addScenePushHandler(() => { stateStack.push(new PlayableState()); }); game.addScenePopHandler(() => { stateStack.pop(); if (stateStack.length === 0) stateStack.push(new PlayableState()); }); } export class Playable { stopped: boolean; constructor() { } play(playbackMode: PlaybackMode) { // subclass } loop() { state().looping.push(this); this.stopped = false; control.runInParallel(() => { while (!this.stopped) { this.play(PlaybackMode.UntilDone); } }); } } export class MelodyPlayable extends Playable { constructor(public melody: Melody) { super(); } play(playbackMode: PlaybackMode) { if (playbackMode === PlaybackMode.InBackground) { this.melody.play(music.volume()); } else if (playbackMode === PlaybackMode.UntilDone) { this.melody.playUntilDone(music.volume()); } else { this.melody.loop(music.volume()); } } } export class TonePlayable extends Playable { constructor(public pitch: number, public duration: number) { super(); } play(playbackMode: PlaybackMode) { if (playbackMode === PlaybackMode.InBackground) { control.runInParallel(() => music.playTone(this.pitch, this.duration)); } else if (playbackMode === PlaybackMode.UntilDone) { music.playTone(this.pitch, this.duration); if (this.duration > 2000) { pause(this.duration); } } else { this.loop(); } } } /** * Play a song, melody, or other sound. The music plays until finished or can play as a * background task. * @param toPlay the song or melody to play * @param playbackMode play the song or melody until it's finished or as background task */ //% blockId="music_playable_play" //% block="play $toPlay $playbackMode" //% toPlay.shadow=music_melody_playable //% group="Sounds" //% help="music/play" export function play(toPlay: Playable, playbackMode: PlaybackMode) { toPlay.play(playbackMode); } /** * Create a Playable object for a melody. * @param melody the melody to make playable */ //% blockId="music_melody_playable" //% block="sound $melody" //% toolboxParent=music_playable_play //% toolboxParentArgument=toPlay //% group="Sounds" //% duplicateShadowOnDrag //% blockHidden //% help=music/melody-playable export function melodyPlayable(melody: Melody): Playable { return new MelodyPlayable(melody); } /** * Create a Playable object for a melody string containg notes. * @param melody the melody string to make playable */ //% blockId="music_string_playable" //% block="melody $melody at tempo $tempo|(bpm)" //% toolboxParent=music_playable_play //% toolboxParentArgument=toPlay //% weight=85 blockGap=8 //% help=music/melody-editor //% group="Songs" //% duplicateShadowOnDrag //% melody.shadow=melody_editor //% tempo.min=40 tempo.max=500 //% tempo.defl=120 //% help=music/string-playable export function stringPlayable(melody: string, tempo: number): Playable { let notes: string[] = melody.split(" ").filter(n => !!n); let formattedMelody = ""; let newOctave = false; // build melody string, replace '-' with 'R' and add tempo // creates format like "C5-174 B4 A G F E D C " for (let i = 0; i < notes.length; i++) { if (notes[i] === "-") { notes[i] = "R"; } else if (notes[i] === "C5") { newOctave = true; } else if (newOctave) { // change the octave if necesary notes[i] += "4"; newOctave = false; } // add tempo after first note if (i == 0) { formattedMelody += notes[i] + "-" + tempo + " "; } else { formattedMelody += notes[i] + " "; } } return new MelodyPlayable(new Melody(formattedMelody)); } /** * Create a Playable object for a single tone and its duration. * @param note the note or tone frequency to play * @param duration the duration of the tone in milliseconds (ms) */ //% blockId="music_tone_playable" //% block="tone $note for $duration" //% toolboxParent=music_playable_play //% toolboxParentArgument=toPlay //% group="Tone" //% duplicateShadowOnDrag //% note.shadow=device_note //% duration.shadow=device_beat //% parts="headphone" //% help=music/tone-playable export function tonePlayable(note: number, duration: number): Playable { return new TonePlayable(note, duration); } export function _stopPlayables() { state().stopLooping(); } }