@lyre/note
Version:
Utilities to express, parse and output musical notes in Scientific and Helmholtz formats.
196 lines (166 loc) • 5.47 kB
JavaScript
"use strict";
var InvalidNote = require('./Exception/InvalidNote.js');
var Stringify = require('./Stringify.js');
/**
* Handles the creation and manipulationg of Western music notes.
*/
class Note {
/**
* Sets the note from a string.
* @param {string} note Valid note string such as C, Ab and G#.
* @param {integer} [octave] Integer of the octave, optional.
* @throw {InvalidNote} If the note is invalid.
*/
constructor(note, octave = null) {
this.setNote(note);
this.setOctave(octave);
}
/**
* Sets the note from a string. Case insensitive.
* @param {string} note Case insensitive note string such as C, Ab and G#.
* @throw {InvalidNote} If the note string is invalid.
*/
setNote(note) {
if(!Note._notes.hasOwnProperty(note)) {
throw new InvalidNote(note + " is not a valid note");
}
this.note = note;
}
/**
* Returns the note without octave identifier.
* @returns {string} The note as a string.
*/
getNote() {
return this.note;
}
/**
* Sets the nth octave of the note.
* @param {integer} n The octave integer, such as C4 for middle C.
*/
setOctave(n) {
if(n !== null && !Number.isInteger(n)) {
throw new InvalidNote("Expected octave to be an integer or null");
}
this.octave = n;
}
/**
* Gets the note's octave.
* @returns {integer} The octave integer, such as C4 for middle C.
*/
getOctave() {
return this.octave;
}
/**
* Returns true if the note is sharp.
* @returns {Boolean} True if the note is sharp.
*/
isSharp() {
return this.note.charAt(1) === '#';
}
/**
* Returns true if note is flat.
* @returns {Boolean} True if the note is flat.
*/
isFlat() {
return this.note.charAt(1) === 'b';
}
/**
* Checks if a note is valid. Case sensitive.
*
* @param {string} note String representation of a note, such as C, Ab and G#.
* @return {boolean} True if the note is valid, else false.
*/
isValid(note) {
return Note._notes.hasOwnProperty(note);
}
/**
* Returns the flat representation of the note.
* @return {string} The flat note.
*/
getFlat() {
var index = Note._notes[this.note]; //get index from lookup table
return Note._indexToFlat[index];
}
/**
* Returns the sharp representation of the note.
* @return {string} The sharp note.
*/
getSharp() {
var index = Note._notes[this.note];
return Note._indexToSharp[index];
}
/**
* Returns true if the note is natural, ie it has no sharps or flats.
* @return {boolean} true if no sharps or flats, else false.
*/
isNatural() {
return this.note.length === 1;
}
/**
* Gets a new Note transposed to the specificed half steps. If the current
* note is flat, it returns flat, otherwise sharp is returned.
* @param {int} halfSteps The amount of half steps. Negative numbers transpose down, positives transpose up.
* @return {Note} If the current note is flat, it will return a flat note,
* else sharp is returned.
*/
getTransposition(halfSteps) {
var currentIndex = Note._notes[this.note];
var index = (currentIndex + halfSteps) % 12;
var note, octave = null;
//overflow negative indexes
if(index < 0) {
index = 12 + index;
}
if(this.isFlat()) {
note = Note._indexToFlat[index];
} else {
note = Note._indexToSharp[index];
}
//only transpose octaves if we have an octave
if(Number.isInteger(this.getOctave())) {
octave = this.getOctave() + Math.floor((currentIndex + halfSteps) / 12);
}
return new Note(note, octave);
}
/**
* Transposes the current note to the specified half steps. If the current
* note is flat, it returns flat, otherwise sharp is returned.
* @param {integer} halfSteps The amount of half steps. Negative numbers transpose down, positives transpose up.
* @returns {Note} Returns self.
*/
transpose(halfSteps) {
var transNote = this.getTransposition(halfSteps);
this.setNote(transNote.getNote());
this.setOctave(transNote.getOctave());
return this;
}
toString(method = Stringify.Scientific) {
return method(this);
}
};
/**
* Lookup table to convert notes to their positional index.
* @access private
*/
Note._notes = {
'C': 0, 'C#': 1, 'Db': 1, 'D': 2, 'D#': 3, 'Eb': 3, 'E': 4
, 'F': 5, 'F#': 6, 'Gb': 6, 'G': 7, 'G#': 8, 'Ab': 9, 'A': 9
, 'A#': 10, 'Bb': 10, 'B': 11
};
/**
* Lookup table to convert sharp note position to note.
* @access private
*/
Note._indexToSharp = {
0: 'C', 1: 'C#', 2: 'D', 3: 'D#', 4: 'E', 5: 'F', 6: 'F#', 7: 'G'
, 8: 'G#', 9: 'A', 10: 'A#', 11: 'B'
};
/**
* Lookup table to convert flat note position to note.
* @access private
*/
Note._indexToFlat = {
0: 'C', 1: 'Db', 2: 'D', 3: 'Eb', 4: 'E', 5: 'F', 6: 'Gb', 7: 'G'
, 8: 'Ab', 9: 'A', 10: 'Bb', 11: 'B'
};
module.exports = Note;