johnny-five-electron
Version:
Temporary fork to support Electron (to be deprecated)
331 lines (289 loc) • 6.09 kB
JavaScript
var Board = require("../lib/board.js"),
Timer = require("nanotimer");
function Piezo(opts) {
Board.Component.call(
this, opts = Board.Options(opts)
);
// Hardware instance properties
this.mode = this.io.MODES.OUTPUT;
this.pin = opts.pin || 3;
this.io.pinMode(this.pin, this.mode);
// Piezo instance properties
this.isPlaying = false;
}
// These notes are rounded up at .5 otherwise down.
Piezo.Notes = {
"c0": 16,
"c#0": 17,
"d0": 18,
"d#0": 19,
"e0": 21,
"f0": 22,
"f#0": 23,
"g0": 25,
"g#0": 26,
"a0": 28,
"a#0": 29,
"b0": 31,
"c1": 33,
"c#1": 35,
"d1": 37,
"d#1": 39,
"e1": 41,
"f1": 44,
"f#1": 47,
"g1": 49,
"g#1": 52,
"a1": 55,
"a#1": 58,
"b1": 62,
"c2": 65,
"c#2": 69,
"d2": 73,
"d#2": 78,
"e2": 82,
"f2": 87,
"f#2": 93,
"g2": 98,
"g#2": 104,
"a2": 110,
"a#2": 117,
"b2": 124,
"c3": 131,
"c#3": 139,
"d3": 147,
"d#3": 156,
"e3": 165,
"f3": 175,
"f#3": 185,
"g3": 196,
"g#3": 208,
"a3": 220,
"a#3": 233,
"b3": 247,
"c4": 262,
"c#4": 277,
"d4": 294,
"d#4": 311,
"e4": 330,
"f4": 349,
"f#4": 370,
"g4": 392,
"g#4": 415,
"a4": 440,
"a#4": 466,
"b4": 494,
"c5": 523,
"c#5": 554,
"d5": 587,
"d#5": 622,
"e5": 659,
"f5": 698,
"f#5": 740,
"g5": 784,
"g#5": 831,
"a5": 880,
"a#5": 932,
"b5": 988,
"c6": 1047,
"c#6": 1109,
"d6": 1175,
"d#6": 1245,
"e6": 1319,
"f6": 1397,
"f#6": 1480,
"g6": 1568,
"g#6": 1661,
"a6": 1760,
"a#6": 1865,
"b6": 1976,
"c7": 2093,
"c#7": 2217,
"d7": 2349,
"d#7": 2489,
"e7": 2637,
"f7": 2794,
"f#7": 2960,
"g7": 3136,
"g#7": 3322,
"a7": 3520,
"a#7": 3729,
"b7": 3951,
"c8": 4186,
"c#8": 4435,
"d8": 4699,
"d#8": 4978,
"e8": 5274,
"f8": 5588,
"f#8": 5920,
"g8": 6272,
"g#8": 6645,
"a8": 7040,
"a#8": 7459,
"b8": 7902
};
function clearTimer() {
if (!this.timer) {
return;
}
this.timer.clearInterval();
delete this.timer;
}
/**
* Get the tone from the current note. note
* could be an int, string, array or null.
* If int or null, leave alone. Otherwise,
* derive what the tone should be.
* @return int | null
*/
function parseTone(note) {
var tone = note;
if (Array.isArray(note)) {
tone = note[0];
}
if (typeof tone === "string") {
tone = tone.toLowerCase();
var needsOctave = isNaN(tone.substr(tone.length -1));
if (needsOctave) {
tone = tone + Piezo.prototype.defaultOctave().toString();
}
tone = (Piezo.Notes[tone]) ? Piezo.Notes[tone] : null;
}
if (isNaN(tone)) {
tone = null;
}
return tone;
}
/**
* Obtain the beat/duration count from the current
* note. This is either an int or undefined. Default
* to 1.
* @return int (default 1)
*/
function parseBeat(note) {
var beat = 1;
if (Array.isArray(note) && note[1] !== undefined) {
// If extant, beat will be second element of note
beat = note[1];
}
return beat;
}
/**
* Validate the octave provided to ensure the value is
* supported and won't crash the board.
* @return bool
*/
function isValidOctave(octave) {
// octave should be a number.
if (octave && !isNaN(octave) &&
(octave >= 0 && octave <= 8)) {
return true;
}
return false;
}
Piezo.prototype.defaultOctave = function(octave) {
if (typeof this.defaultOctave.value === "undefined") {
this.defaultOctave.value = 4;
}
if (isValidOctave(octave)) {
this.defaultOctave.value = octave;
}
return this.defaultOctave.value;
};
Piezo.prototype.note = function(note, duration) {
var tone = parseTone(note);
return this.tone(tone, duration);
};
Piezo.prototype.tone = function(tone, duration) {
if (isNaN(tone) || isNaN(duration)) {
// Very Bad Things happen if one tries to play a NaN tone
throw new Error(
"Piezo.tone: invalid tone or duration"
);
}
clearTimer.call(this);
var timer = this.timer = new Timer();
var value = 1;
timer.setInterval(function() {
value = value === 1 ? 0 : 1;
this.io.digitalWrite(this.pin, value);
if ((timer.difTime / 1000000) > duration) {
clearTimer.call(this);
}
}.bind(this), null, tone + "u", function() {});
return this;
};
Piezo.prototype.frequency = function(frequency, duration) {
var period = 1 / frequency;
var duty = period / 2;
var tone = Math.round(duty * 1000000);
return this.tone(tone, duration);
};
function stringToSong(stringSong, beats) {
beats = beats || 1;
var notes = stringSong.match(/\S+/g);
var song = [];
var note, lastNote;
while (notes.length) {
note = notes.shift();
if (/^[0-9]+$/.test(note)) {
note = parseInt(note, 10);
}
lastNote = song[song.length - 1];
if (lastNote && lastNote[0] === note) {
lastNote[1] += beats;
} else {
song.push([note, beats]);
}
}
return song;
}
Piezo.prototype.play = function(tune, callback) {
if (typeof tune !== "object") {
tune = {
song: tune
};
}
if (typeof tune.song === "string") {
tune.song = stringToSong(tune.song, tune.beats);
}
var duration,
tempo = tune.tempo || 250,
beatDuration = Math.round(60000 / tempo), // Length for a single beat in ms
song = tune.song || [],
i = 0;
if (song && !Array.isArray(song)) {
song = [song];
}
var next = function() {
var note, beat;
note = parseTone(song[i]);
beat = parseBeat(song[i]);
duration = beat * beatDuration;
if (i++ === song.length) {
// Song is over
this.isPlaying = false;
if (typeof callback === "function") {
callback(tune);
}
return;
}
if (note === null) {
this.noTone();
} else {
this.frequency(note, duration);
}
setTimeout(next, duration);
}.bind(this);
// We are playing a song
this.isPlaying = true;
next();
return this;
};
Piezo.prototype.noTone = function() {
this.io.digitalWrite(this.pin, 0);
clearTimer.call(this);
return this;
};
Piezo.prototype.off = Piezo.prototype.noTone;
module.exports = Piezo;