Adding support for music melody arrays (#358)
* Adding support for music melody arrays in Typescript
This commit is contained in:
parent
92508d2daf
commit
e947f4859b
29
docs/reference/music/play-builtin-melody.md
Normal file
29
docs/reference/music/play-builtin-melody.md
Normal file
@ -0,0 +1,29 @@
|
||||
# Play Built-in Melody
|
||||
|
||||
Play a built in musical melody through pin ``P0`` of the @boardname@.
|
||||
|
||||
## Simulator
|
||||
|
||||
This function only works on the @boardname@ and in some browsers.
|
||||
|
||||
```sig
|
||||
music.playBuiltinMelody(Melodies.Entertainer)
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
* ``melody`` is the kind of built-in melody you want to play
|
||||
|
||||
## Example
|
||||
|
||||
This example plays the ``Entertainer`` melody.
|
||||
|
||||
```blocks
|
||||
music.playBuiltinMelody(Melodies.Entertainer)
|
||||
```
|
||||
|
||||
### See also
|
||||
|
||||
[play tone](/reference/music/play-tone), [rest](/reference/music/rest), [ring tone](/reference/music/ring-tone) , [tempo](/reference/music/tempo), [set tempo](/reference/music/set-tempo),
|
||||
[change tempo by](/reference/music/change-tempo-by)
|
||||
|
@ -166,6 +166,9 @@
|
||||
"led.unplot|param|y": "TODO",
|
||||
"music": "Generation of music tones through pin ``P0``.",
|
||||
"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|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.",
|
||||
|
@ -115,6 +115,30 @@
|
||||
"LedSpriteProperty.Y|block": "y",
|
||||
"Math.randomBoolean|block": "pick random true or false",
|
||||
"Math|block": "Math",
|
||||
"Melodies.BaDing|block": "ba ding",
|
||||
"Melodies.Baddy|block": "baddy",
|
||||
"Melodies.Birthday|block": "birthday",
|
||||
"Melodies.Blues|block": "blues",
|
||||
"Melodies.Chase|block": "chase",
|
||||
"Melodies.Dadadadum|block": "dadadum",
|
||||
"Melodies.Entertainer|block": "entertainer",
|
||||
"Melodies.Funeral|block": "funereal",
|
||||
"Melodies.Funk|block": "funk",
|
||||
"Melodies.JumpDown|block": "jump down",
|
||||
"Melodies.JumpUp|block": "jump up",
|
||||
"Melodies.Nyan|block": "nyan",
|
||||
"Melodies.Ode|block": "ode",
|
||||
"Melodies.PowerDown|block": "power down",
|
||||
"Melodies.PowerUp|block": "power up",
|
||||
"Melodies.Prelude|block": "prelude",
|
||||
"Melodies.Punchline|block": "punchline",
|
||||
"Melodies.Ringtone|block": "ringtone",
|
||||
"Melodies.Wawawawaa|block": "wawawawaa",
|
||||
"Melodies.Wedding|block": "wedding",
|
||||
"MelodyOptions.ForeverInBackground|block": "forever in background",
|
||||
"MelodyOptions.Forever|block": "forever",
|
||||
"MelodyOptions.OnceInBackground|block": "once in background",
|
||||
"MelodyOptions.Once|block": "once",
|
||||
"Note.CSharp3|block": "C#3",
|
||||
"Note.CSharp4|block": "C#4",
|
||||
"Note.CSharp5|block": "C#5",
|
||||
@ -200,6 +224,8 @@
|
||||
"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.builtInMelody|block": "%melody",
|
||||
"music.changeTempoBy|block": "change tempo by (bpm)|%value",
|
||||
"music.noteFrequency|block": "%note",
|
||||
"music.playTone|block": "play|tone %note=device_note|for %duration=device_beat",
|
||||
|
119
libs/core/melodies.ts
Normal file
119
libs/core/melodies.ts
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2016 The MicroPython-on-micro:bit Developers, as listed
|
||||
in the accompanying AUTHORS file
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
// Melodies from file microbitmusictunes.c https://github.com/bbcmicrobit/MicroPython
|
||||
|
||||
enum Melodies {
|
||||
//% block="dadadum" blockIdentity=music.builtInMelody
|
||||
Dadadadum = 0,
|
||||
//% block="entertainer" blockIdentity=music.builtInMelody
|
||||
Entertainer,
|
||||
//% block="prelude" blockIdentity=music.builtInMelody
|
||||
Prelude,
|
||||
//% block="ode" blockIdentity=music.builtInMelody
|
||||
Ode,
|
||||
//% block="nyan" blockIdentity=music.builtInMelody
|
||||
Nyan,
|
||||
//% block="ringtone" blockIdentity=music.builtInMelody
|
||||
Ringtone,
|
||||
//% block="funk" blockIdentity=music.builtInMelody
|
||||
Funk,
|
||||
//% block="blues" blockIdentity=music.builtInMelody
|
||||
Blues,
|
||||
//% block="birthday" blockIdentity=music.builtInMelody
|
||||
Birthday,
|
||||
//% block="wedding" blockIdentity=music.builtInMelody
|
||||
Wedding,
|
||||
//% block="funereal" blockIdentity=music.builtInMelody
|
||||
Funeral,
|
||||
//% block="punchline" blockIdentity=music.builtInMelody
|
||||
Punchline,
|
||||
//% block="baddy" blockIdentity=music.builtInMelody
|
||||
Baddy,
|
||||
//% block="chase" blockIdentity=music.builtInMelody
|
||||
Chase,
|
||||
//% block="ba ding" blockIdentity=music.builtInMelody
|
||||
BaDing,
|
||||
//% block="wawawawaa" blockIdentity=music.builtInMelody
|
||||
Wawawawaa,
|
||||
//% block="jump up" blockIdentity=music.builtInMelody
|
||||
JumpUp,
|
||||
//% block="jump down" blockIdentity=music.builtInMelody
|
||||
JumpDown,
|
||||
//% block="power up" blockIdentity=music.builtInMelody
|
||||
PowerUp,
|
||||
//% block="power down" blockIdentity=music.builtInMelody
|
||||
PowerDown,
|
||||
}
|
||||
|
||||
namespace music {
|
||||
|
||||
export function getMelody(melody: Melodies): string[] {
|
||||
switch (melody) {
|
||||
case Melodies.Dadadadum:
|
||||
return ['r4:2', 'g', 'g', 'g', 'eb:8', 'r:2', 'f', 'f', 'f', 'd:8'];
|
||||
case Melodies.Entertainer:
|
||||
return ['d4:1', 'd#', 'e', 'c5:2', 'e4:1', 'c5:2', 'e4:1', 'c5:3', 'c:1', 'd', 'd#', 'e', 'c', 'd', 'e:2', 'b4:1', 'd5:2', 'c:4'];
|
||||
case Melodies.Prelude:
|
||||
return ['c4:1', 'e', 'g', 'c5', 'e', 'g4', 'c5', 'e', 'c4', 'e', 'g', 'c5', 'e', 'g4', 'c5', 'e', 'c4', 'd', 'g', 'd5', 'f', 'g4', 'd5', 'f', 'c4', 'd', 'g', 'd5', 'f', 'g4', 'd5', 'f', 'b3', 'd4', 'g', 'd5', 'f', 'g4', 'd5', 'f', 'b3', 'd4', 'g', 'd5', 'f', 'g4', 'd5', 'f', 'c4', 'e', 'g', 'c5', 'e', 'g4', 'c5', 'e', 'c4', 'e', 'g', 'c5', 'e', 'g4', 'c5', 'e'];
|
||||
case Melodies.Ode:
|
||||
return ['e4', 'e', 'f', 'g', 'g', 'f', 'e', 'd', 'c', 'c', 'd', 'e', 'e:6', 'd:2', 'd:8', 'e:4', 'e', 'f', 'g', 'g', 'f', 'e', 'd', 'c', 'c', 'd', 'e', 'd:6', 'c:2', 'c:8'];
|
||||
case Melodies.Nyan:
|
||||
return ['f#5:2', 'g#', 'c#:1', 'd#:2', 'b4:1', 'd5:1', 'c#', 'b4:2', 'b', 'c#5', 'd', 'd:1', 'c#', 'b4:1', 'c#5:1', 'd#', 'f#', 'g#', 'd#', 'f#', 'c#', 'd', 'b4', 'c#5', 'b4', 'd#5:2', 'f#', 'g#:1', 'd#', 'f#', 'c#', 'd#', 'b4', 'd5', 'd#', 'd', 'c#', 'b4', 'c#5', 'd:2', 'b4:1', 'c#5', 'd#', 'f#', 'c#', 'd', 'c#', 'b4', 'c#5:2', 'b4', 'c#5', 'b4', 'f#:1', 'g#', 'b:2', 'f#:1', 'g#', 'b', 'c#5', 'd#', 'b4', 'e5', 'd#', 'e', 'f#', 'b4:2', 'b', 'f#:1', 'g#', 'b', 'f#', 'e5', 'd#', 'c#', 'b4', 'f#', 'd#', 'e', 'f#', 'b:2', 'f#:1', 'g#', 'b:2', 'f#:1', 'g#', 'b', 'b', 'c#5', 'd#', 'b4', 'f#', 'g#', 'f#', 'b:2', 'b:1', 'a#', 'b', 'f#', 'g#', 'b', 'e5', 'd#', 'e', 'f#', 'b4:2', 'c#5'];
|
||||
case Melodies.Ringtone:
|
||||
return ['c4:1', 'd', 'e:2', 'g', 'd:1', 'e', 'f:2', 'a', 'e:1', 'f', 'g:2', 'b', 'c5:4'];
|
||||
case Melodies.Funk:
|
||||
return ['c2:2', 'c', 'd#', 'c:1', 'f:2', 'c:1', 'f:2', 'f#', 'g', 'c', 'c', 'g', 'c:1', 'f#:2', 'c:1', 'f#:2', 'f', 'd#'];
|
||||
case Melodies.Blues:
|
||||
return ['c2:2', 'e', 'g', 'a', 'a#', 'a', 'g', 'e', 'c2:2', 'e', 'g', 'a', 'a#', 'a', 'g', 'e', 'f', 'a', 'c3', 'd', 'd#', 'd', 'c', 'a2', 'c2:2', 'e', 'g', 'a', 'a#', 'a', 'g', 'e', 'g', 'b', 'd3', 'f', 'f2', 'a', 'c3', 'd#', 'c2:2', 'e', 'g', 'e', 'g', 'f', 'e', 'd'];
|
||||
case Melodies.Birthday:
|
||||
return ['c4:3', 'c:1', 'd:4', 'c:4', 'f', 'e:8', 'c:3', 'c:1', 'd:4', 'c:4', 'g', 'f:8', 'c:3', 'c:1', 'c5:4', 'a4', 'f', 'e', 'd', 'a#:3', 'a#:1', 'a:4', 'f', 'g', 'f:8'];
|
||||
case Melodies.Wedding:
|
||||
return ['c4:4', 'f:3', 'f:1', 'f:8', 'c:4', 'g:3', 'e:1', 'f:8', 'c:4', 'f:3', 'a:1', 'c5:4', 'a4:3', 'f:1', 'f:4', 'e:3', 'f:1', 'g:8'];
|
||||
case Melodies.Funeral:
|
||||
return ['c3:4', 'c:3', 'c:1', 'c:4', 'd#:3', 'd:1', 'd:3', 'c:1', 'c:3', 'b2:1', 'c3:4'];
|
||||
case Melodies.Punchline:
|
||||
return ['c4:3', 'g3:1', 'f#', 'g', 'g#:3', 'g', 'r', 'b', 'c4'];
|
||||
case Melodies.Baddy:
|
||||
return ['c3:3', 'r', 'd:2', 'd#', 'r', 'c', 'r', 'f#:8'];
|
||||
case Melodies.Chase:
|
||||
return ['a4:1', 'b', 'c5', 'b4', 'a:2', 'r', 'a:1', 'b', 'c5', 'b4', 'a:2', 'r', 'a:2', 'e5', 'd#', 'e', 'f', 'e', 'd#', 'e', 'b4:1', 'c5', 'd', 'c', 'b4:2', 'r', 'b:1', 'c5', 'd', 'c', 'b4:2', 'r', 'b:2', 'e5', 'd#', 'e', 'f', 'e', 'd#', 'e'];
|
||||
case Melodies.BaDing:
|
||||
return ['b5:1', 'e6:3'];
|
||||
case Melodies.Wawawawaa:
|
||||
return ['e3:3', 'r:1', 'd#:3', 'r:1', 'd:4', 'r:1', 'c#:8'];
|
||||
case Melodies.JumpUp:
|
||||
return ['c5:1', 'd', 'e', 'f', 'g'];
|
||||
case Melodies.JumpDown:
|
||||
return ['g5:1', 'f', 'e', 'd', 'c'];
|
||||
case Melodies.PowerUp:
|
||||
return ['g4:1', 'c5', 'e', 'g:2', 'e:1', 'g:3'];
|
||||
case Melodies.PowerDown:
|
||||
return ['g5:1', 'd#', 'c', 'g4:2', 'b:1', 'c5:3'];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
@ -126,12 +126,24 @@ enum BeatFraction {
|
||||
Breve = 64
|
||||
}
|
||||
|
||||
enum MelodyOptions {
|
||||
//% block="once""
|
||||
Once = 1,
|
||||
//% block="forever"
|
||||
Forever = 2,
|
||||
//% block="once in background"
|
||||
OnceInBackground = 4,
|
||||
//% block="forever in background"
|
||||
ForeverInBackground = 8
|
||||
}
|
||||
|
||||
/**
|
||||
* Generation of music tones through pin ``P0``.
|
||||
*/
|
||||
//% color=#D83B01 weight=98 icon="\uf025"
|
||||
namespace music {
|
||||
let beatsPerMinute: number = 120;
|
||||
let freqTable: number[] = [];
|
||||
|
||||
/**
|
||||
* Plays a tone through pin ``P0`` for the given duration.
|
||||
@ -185,6 +197,7 @@ namespace music {
|
||||
|
||||
function init() {
|
||||
if (beatsPerMinute <= 0) beatsPerMinute = 120;
|
||||
if (freqTable.length == 0) freqTable = [28, 29, 31, 33, 35, 37, 39, 41, 44, 46, 49, 52, 55, 58, 62, 65, 69, 73, 78, 82, 87, 92, 98, 104, 110, 117, 123, 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, 4186]
|
||||
}
|
||||
|
||||
/**
|
||||
@ -240,4 +253,127 @@ namespace music {
|
||||
beatsPerMinute = Math.max(1, bpm);
|
||||
}
|
||||
}
|
||||
|
||||
let currentMelody: Melody;
|
||||
let currentBackgroundMelody: Melody;
|
||||
|
||||
/**
|
||||
* Gets the melody array of a built-in melody.
|
||||
* @param name the note name, eg: Note.C
|
||||
*/
|
||||
//% weight=50 help=music/builtin-melody
|
||||
//% blockId=device_builtin_melody block="%melody"
|
||||
//% blockHidden=true
|
||||
export function builtInMelody(melody: Melodies): string[] {
|
||||
return getMelody(melody);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts playing a melody through pin ``P0``.
|
||||
* 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/start-melody weight=60
|
||||
//% 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) {
|
||||
currentBackgroundMelody = currentMelody;
|
||||
}
|
||||
currentMelody = new Melody(melodyArray, options);
|
||||
} else {
|
||||
currentMelody = new Melody(melodyArray, options);
|
||||
// Only start the fiber once
|
||||
control.inBackground(() => {
|
||||
while (currentMelody.hasNextNote()) {
|
||||
playNextNote(currentMelody);
|
||||
if (!currentMelody.hasNextNote() && currentBackgroundMelody) {
|
||||
// Swap the background melody back
|
||||
currentMelody = currentBackgroundMelody;
|
||||
currentBackgroundMelody = null;
|
||||
}
|
||||
}
|
||||
currentMelody = null;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function playNextNote(melody: Melody): void {
|
||||
// cache elements
|
||||
let currNote = melody.nextNote();
|
||||
let currentPos = melody.currentPos;
|
||||
let currentDuration = melody.currentDuration;
|
||||
let currentOctave = melody.currentOctave;
|
||||
|
||||
let note: number;
|
||||
let isrest: boolean = false;
|
||||
let beatPos: number;
|
||||
let parsingOctave: boolean = true;
|
||||
|
||||
for (let pos = 0; pos < currNote.length; pos++) {
|
||||
let noteChar = currNote.charAt(pos);
|
||||
switch (noteChar) {
|
||||
case 'a': case 'A': note = 1; break;
|
||||
case 'b': case 'B': note = 3; break;
|
||||
case 'c': case 'C': note = 4; break;
|
||||
case 'd': case 'D': note = 6; break;
|
||||
case 'e': case 'E': note = 8; break;
|
||||
case 'f': case 'F': note = 9; break;
|
||||
case 'g': case 'G': note = 11; break;
|
||||
case 'r': case 'R': isrest = true; break;
|
||||
case '#': note++; break;
|
||||
case 'b': note--; break;
|
||||
case ':': parsingOctave = false; beatPos = pos; break;
|
||||
default: if (parsingOctave) currentOctave = parseInt(noteChar);
|
||||
}
|
||||
}
|
||||
if (!parsingOctave) {
|
||||
currentDuration = parseInt(currNote.substr(beatPos + 1, currNote.length - beatPos));
|
||||
}
|
||||
let beat = (60000 / beatsPerMinute) / 4;
|
||||
if (isrest) {
|
||||
music.rest(currentDuration * beat)
|
||||
} else {
|
||||
let keyNumber = note + (12 * (currentOctave - 1));
|
||||
let frequency = keyNumber >= 0 && keyNumber < freqTable.length ? freqTable[keyNumber] : 0;
|
||||
music.playTone(frequency, currentDuration * beat);
|
||||
}
|
||||
melody.currentDuration = currentDuration;
|
||||
melody.currentOctave = currentOctave;
|
||||
melody.currentPos = melody.repeating == true && currentPos == melody.melodyArray.length - 1 ? 0 : currentPos + 1;
|
||||
}
|
||||
|
||||
class Melody {
|
||||
public melodyArray: string[];
|
||||
public currentDuration: number;
|
||||
public currentOctave: number;
|
||||
public currentPos: number;
|
||||
public repeating: boolean;
|
||||
public background: boolean;
|
||||
|
||||
constructor(melodyArray: string[], options: MelodyOptions) {
|
||||
this.melodyArray = melodyArray;
|
||||
this.repeating = ((options & MelodyOptions.Forever) != 0);
|
||||
this.repeating = this.repeating ? true : ((options & MelodyOptions.ForeverInBackground) != 0)
|
||||
this.background = ((options & MelodyOptions.OnceInBackground) != 0);
|
||||
this.background = this.background ? true : ((options & MelodyOptions.ForeverInBackground) != 0);
|
||||
this.currentDuration = 4; //Default duration (Crotchet)
|
||||
this.currentOctave = 4; //Middle octave
|
||||
this.currentPos = 0;
|
||||
}
|
||||
|
||||
hasNextNote() {
|
||||
return this.repeating || this.currentPos < this.melodyArray.length;
|
||||
}
|
||||
|
||||
nextNote(): string {
|
||||
const currentNote = this.melodyArray[this.currentPos];
|
||||
return currentNote;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@
|
||||
"led.cpp",
|
||||
"led.ts",
|
||||
"music.ts",
|
||||
"melodies.ts",
|
||||
"pins.cpp",
|
||||
"pins.ts",
|
||||
"serial.cpp",
|
||||
|
Loading…
Reference in New Issue
Block a user