Adding support for music melody arrays (#358)
* Adding support for music melody arrays in Typescript
This commit is contained in:
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user