Melody events (#391)
* support for custom playTone * added music.onEvent, music.setPlayTone * updated docs * updated sample
This commit is contained in:
@ -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