Melody events (#391)
* support for custom playTone * added music.onEvent, music.setPlayTone * updated docs * updated sample
This commit is contained in:
		@@ -164,15 +164,16 @@
 | 
			
		||||
  "led.unplot": "Turn off the specified LED using x, y coordinates (x is horizontal, y is vertical). (0,0) is upper left.",
 | 
			
		||||
  "led.unplot|param|x": "TODO",
 | 
			
		||||
  "led.unplot|param|y": "TODO",
 | 
			
		||||
  "music": "Generation of music tones through pin ``P0``.",
 | 
			
		||||
  "music": "Generation of music tones.",
 | 
			
		||||
  "music.beat": "Returns the duration of a beat in milli-seconds",
 | 
			
		||||
  "music.beginMelody": "Starts playing a melody through pin ``P0``.\nNotes are expressed as a string of characters with this format: NOTE[octave][:duration]",
 | 
			
		||||
  "music.beginMelody": "Starts playing a melody.\nNotes are expressed as a string of characters with this format: NOTE[octave][:duration]",
 | 
			
		||||
  "music.beginMelody|param|options": "melody options, once / forever, in the foreground / background",
 | 
			
		||||
  "music.builtInMelody": "Gets the melody array of a built-in melody.",
 | 
			
		||||
  "music.changeTempoBy": "Change the tempo by the specified amount",
 | 
			
		||||
  "music.changeTempoBy|param|bpm": "The change in beats per minute to the tempo, eg: 20",
 | 
			
		||||
  "music.noteFrequency": "Gets the frequency of a note.",
 | 
			
		||||
  "music.noteFrequency|param|name": "the note name, eg: Note.C",
 | 
			
		||||
  "music.onEvent": "Registers code to run on various melody events",
 | 
			
		||||
  "music.playTone": "Plays a tone through pin ``P0`` for the given duration.",
 | 
			
		||||
  "music.playTone|param|frequency": "pitch of the tone to play in Hertz (Hz)",
 | 
			
		||||
  "music.playTone|param|ms": "tone duration in milliseconds (ms)",
 | 
			
		||||
@@ -180,6 +181,7 @@
 | 
			
		||||
  "music.rest|param|ms": "rest duration in milliseconds (ms)",
 | 
			
		||||
  "music.ringTone": "Plays a tone through pin ``P0``.",
 | 
			
		||||
  "music.ringTone|param|frequency": "pitch of the tone to play in Hertz (Hz)",
 | 
			
		||||
  "music.setPlayTone": "Sets a custom playTone function for playing melodies",
 | 
			
		||||
  "music.setTempo": "Sets the tempo to the specified amount",
 | 
			
		||||
  "music.setTempo|param|bpm": "The new tempo in beats per minute, eg: 120",
 | 
			
		||||
  "music.tempo": "Returns the tempo in beats per minute. Tempo is the speed (bpm = beats per minute) at which notes play. The larger the tempo value, the faster the notes will play.",
 | 
			
		||||
 
 | 
			
		||||
@@ -149,6 +149,16 @@
 | 
			
		||||
  "MelodyOptions.Forever|block": "forever",
 | 
			
		||||
  "MelodyOptions.OnceInBackground|block": "once in background",
 | 
			
		||||
  "MelodyOptions.Once|block": "once",
 | 
			
		||||
  "MusicEvent.BackgroundMelodyEnded|block": "background melody ended",
 | 
			
		||||
  "MusicEvent.BackgroundMelodyNotePlayed|block": "background melody note played",
 | 
			
		||||
  "MusicEvent.BackgroundMelodyPaused|block": "background melody paused",
 | 
			
		||||
  "MusicEvent.BackgroundMelodyRepeated|block": "background melody repeated",
 | 
			
		||||
  "MusicEvent.BackgroundMelodyResumed|block": "background melody resumed",
 | 
			
		||||
  "MusicEvent.BackgroundMelodyStarted|block": "background melody started",
 | 
			
		||||
  "MusicEvent.MelodyEnded|block": "melody ended",
 | 
			
		||||
  "MusicEvent.MelodyNotePlayed|block": "melody note played",
 | 
			
		||||
  "MusicEvent.MelodyRepeated|block": "melody repeated",
 | 
			
		||||
  "MusicEvent.MelodyStarted|block": "melody started",
 | 
			
		||||
  "Note.CSharp3|block": "C#3",
 | 
			
		||||
  "Note.CSharp4|block": "C#4",
 | 
			
		||||
  "Note.CSharp5|block": "C#5",
 | 
			
		||||
@@ -234,10 +244,11 @@
 | 
			
		||||
  "led.unplot|block": "unplot|x %x|y %y",
 | 
			
		||||
  "led|block": "led",
 | 
			
		||||
  "music.beat|block": "%fraction|beat",
 | 
			
		||||
  "music.beginMelody|block": "start|melody %melody=device_builtin_melody| repeating %options",
 | 
			
		||||
  "music.beginMelody|block": "start melody %melody=device_builtin_melody| repeating %options",
 | 
			
		||||
  "music.builtInMelody|block": "%melody",
 | 
			
		||||
  "music.changeTempoBy|block": "change tempo by (bpm)|%value",
 | 
			
		||||
  "music.noteFrequency|block": "%note",
 | 
			
		||||
  "music.onEvent|block": "music on %value",
 | 
			
		||||
  "music.playTone|block": "play|tone %note=device_note|for %duration=device_beat",
 | 
			
		||||
  "music.rest|block": "rest(ms)|%duration=device_beat",
 | 
			
		||||
  "music.ringTone|block": "ring tone (Hz)|%note=device_note",
 | 
			
		||||
 
 | 
			
		||||
@@ -137,13 +137,38 @@ enum MelodyOptions {
 | 
			
		||||
    ForeverInBackground = 8
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum MusicEvent {
 | 
			
		||||
    //% block="melody note played"
 | 
			
		||||
    MelodyNotePlayed = 1,
 | 
			
		||||
    //% block="melody started"
 | 
			
		||||
    MelodyStarted = 2,
 | 
			
		||||
    //% block="melody ended"
 | 
			
		||||
    MelodyEnded = 3,
 | 
			
		||||
    //% block="melody repeated"
 | 
			
		||||
    MelodyRepeated = 4,
 | 
			
		||||
    //% block="background melody note played"
 | 
			
		||||
    BackgroundMelodyNotePlayed = MelodyNotePlayed | 0xf0,
 | 
			
		||||
    //% block="background melody started"
 | 
			
		||||
    BackgroundMelodyStarted = MelodyStarted | 0xf0,
 | 
			
		||||
    //% block="background melody ended"
 | 
			
		||||
    BackgroundMelodyEnded = MelodyEnded | 0xf0,
 | 
			
		||||
    //% block="background melody repeated"
 | 
			
		||||
    BackgroundMelodyRepeated = MelodyRepeated | 0xf0,
 | 
			
		||||
    //% block="background melody paused"
 | 
			
		||||
    BackgroundMelodyPaused = 5 | 0xf0,
 | 
			
		||||
    //% block="background melody resumed"
 | 
			
		||||
    BackgroundMelodyResumed = 6 | 0xf0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Generation of music tones through pin ``P0``.
 | 
			
		||||
 * Generation of music tones.
 | 
			
		||||
 */
 | 
			
		||||
//% color=#D83B01 weight=98 icon="\uf025"
 | 
			
		||||
namespace music {
 | 
			
		||||
    let beatsPerMinute: number = 120;
 | 
			
		||||
    let freqTable: number[] = [];
 | 
			
		||||
    let _playTone: (frequency: number, duration: number) => void;
 | 
			
		||||
    const MICROBIT_MELODY_ID = 2000;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Plays a tone through pin ``P0`` for the given duration.
 | 
			
		||||
@@ -155,7 +180,8 @@ namespace music {
 | 
			
		||||
    //% parts="headphone"
 | 
			
		||||
    //% useEnumVal = 1
 | 
			
		||||
    export function playTone(frequency: number, ms: number): void {
 | 
			
		||||
        pins.analogPitch(frequency, ms);
 | 
			
		||||
        if (_playTone) _playTone(frequency, ms);
 | 
			
		||||
        else pins.analogPitch(frequency, ms);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -167,7 +193,7 @@ namespace music {
 | 
			
		||||
    //% parts="headphone"
 | 
			
		||||
    //% useEnumVal = 1
 | 
			
		||||
    export function ringTone(frequency: number): void {
 | 
			
		||||
        pins.analogPitch(frequency, 0);
 | 
			
		||||
        playTone(frequency, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -270,25 +296,40 @@ namespace music {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts playing a melody through pin ``P0``.
 | 
			
		||||
     * Registers code to run on various melody events
 | 
			
		||||
     */
 | 
			
		||||
    //% blockId=melody_on_event block="music on %value"
 | 
			
		||||
    //% help=music/on-event weight=59
 | 
			
		||||
    export function onEvent(value: MusicEvent, handler: Action) {
 | 
			
		||||
        control.onEvent(MICROBIT_MELODY_ID, value, handler);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts playing a melody.
 | 
			
		||||
     * Notes are expressed as a string of characters with this format: NOTE[octave][:duration]
 | 
			
		||||
     * @param melody the melody array to play, eg: ['g5:1']
 | 
			
		||||
     * @param options melody options, once / forever, in the foreground / background
 | 
			
		||||
     */
 | 
			
		||||
    //% help=music/begin-melody weight=60
 | 
			
		||||
    //% blockId=device_start_melody block="start|melody %melody=device_builtin_melody| repeating %options"
 | 
			
		||||
    //% help=music/begin-melody weight=60 blockGap=8
 | 
			
		||||
    //% blockId=device_start_melody block="start melody %melody=device_builtin_melody| repeating %options"
 | 
			
		||||
    //% parts="headphone"
 | 
			
		||||
    export function beginMelody(melodyArray: string[], options: MelodyOptions = MelodyOptions.Once) {
 | 
			
		||||
        init();
 | 
			
		||||
        if (currentMelody != undefined) {
 | 
			
		||||
            if (((options & MelodyOptions.OnceInBackground) == 0)
 | 
			
		||||
                && ((options & MelodyOptions.ForeverInBackground) == 0)
 | 
			
		||||
                && currentMelody.background == true) {
 | 
			
		||||
                && currentMelody.background) {
 | 
			
		||||
                currentBackgroundMelody = currentMelody;
 | 
			
		||||
                currentMelody = null;
 | 
			
		||||
                control.raiseEvent(MICROBIT_MELODY_ID, MusicEvent.BackgroundMelodyPaused);
 | 
			
		||||
            }
 | 
			
		||||
            if (currentMelody)
 | 
			
		||||
                control.raiseEvent(MICROBIT_MELODY_ID, currentMelody.background ? MusicEvent.BackgroundMelodyEnded : MusicEvent.MelodyEnded);
 | 
			
		||||
            currentMelody = new Melody(melodyArray, options);
 | 
			
		||||
            control.raiseEvent(MICROBIT_MELODY_ID, currentMelody.background ? MusicEvent.BackgroundMelodyStarted : MusicEvent.MelodyStarted);
 | 
			
		||||
        } else {
 | 
			
		||||
            currentMelody = new Melody(melodyArray, options);
 | 
			
		||||
            control.raiseEvent(MICROBIT_MELODY_ID, currentMelody.background ? MusicEvent.BackgroundMelodyStarted : MusicEvent.MelodyStarted);
 | 
			
		||||
            // Only start the fiber once
 | 
			
		||||
            control.inBackground(() => {
 | 
			
		||||
                while (currentMelody.hasNextNote()) {
 | 
			
		||||
@@ -297,13 +338,25 @@ namespace music {
 | 
			
		||||
                        // Swap the background melody back
 | 
			
		||||
                        currentMelody = currentBackgroundMelody;
 | 
			
		||||
                        currentBackgroundMelody = null;
 | 
			
		||||
                        control.raiseEvent(MICROBIT_MELODY_ID, MusicEvent.MelodyEnded);
 | 
			
		||||
                        control.raiseEvent(MICROBIT_MELODY_ID, MusicEvent.BackgroundMelodyResumed);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                currentMelody = null;
 | 
			
		||||
                control.raiseEvent(MICROBIT_MELODY_ID, currentMelody.background ? MusicEvent.BackgroundMelodyEnded : MusicEvent.MelodyEnded);
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets a custom playTone function for playing melodies
 | 
			
		||||
     */
 | 
			
		||||
    //% help=music/set-play-tone
 | 
			
		||||
    //% advanced=true
 | 
			
		||||
    export function setPlayTone(f: (frequency: number, duration: number) => void) {
 | 
			
		||||
        _playTone = f;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function playNextNote(melody: Melody): void {
 | 
			
		||||
        // cache elements
 | 
			
		||||
        let currNote = melody.nextNote();
 | 
			
		||||
@@ -346,7 +399,12 @@ namespace music {
 | 
			
		||||
        }
 | 
			
		||||
        melody.currentDuration = currentDuration;
 | 
			
		||||
        melody.currentOctave = currentOctave;
 | 
			
		||||
        melody.currentPos = melody.repeating == true && currentPos == melody.melodyArray.length - 1 ? 0 : currentPos + 1;
 | 
			
		||||
        const repeating = melody.repeating && currentPos == melody.melodyArray.length - 1;
 | 
			
		||||
        melody.currentPos = repeating ? 0 : currentPos + 1;
 | 
			
		||||
 | 
			
		||||
        control.raiseEvent(MICROBIT_MELODY_ID, melody.background ? MusicEvent.BackgroundMelodyNotePlayed : MusicEvent.MelodyNotePlayed);
 | 
			
		||||
        if (repeating)
 | 
			
		||||
            control.raiseEvent(MICROBIT_MELODY_ID, melody.background ? MusicEvent.BackgroundMelodyRepeated : MusicEvent.MelodyRepeated);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    class Melody {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user