twelvetet
Version:
A minimalistic twelve-tone equal temperament libray for Javascript
393 lines (325 loc) • 12.5 kB
JavaScript
;
var twelvetetSpn = require('twelvetet-spn');
var log2 = Math.log2;
var pow = Math.pow;
var round = Math.round;
var TWELFTH_ROOT_OF_TWO = pow(2, 1 / 12);
function next(frequency, semitones) {
return frequency * pow(TWELFTH_ROOT_OF_TWO, semitones);
}
function interval(fromFrequency, toFrequency) {
return 12 * log2(toFrequency / fromFrequency);
}
function isFrequency(value) {
return typeof value === 'number' && isFinite(value) && value > 0;
}
function normalize(frequency, tuningFrequency) {
var semitones = round(interval(tuningFrequency, frequency));
return next(tuningFrequency, semitones);
}
var classCallCheck = function (instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
};
var floor = Math.floor;
var round$1 = Math.round;
/**
* Represents a pitch.
*
* @class Pitch
* @inner
* @param {Number} inputFrequency A positive number representing the input frequency in hertz.
* @param {Number} tuningFrequency A positive number representing the tuning frequency in hertz.
*/
var Pitch = function () {
function Pitch(inputFrequency, tuningFrequency) {
classCallCheck(this, Pitch);
if (!isFrequency(inputFrequency)) {
throw new TypeError("Missing or invalid argument, 'inputFrequency'.");
}
if (!isFrequency(tuningFrequency)) {
throw new TypeError("Missing or invalid argument, 'tuningFrequency'");
}
this._inputFrequency = inputFrequency;
this._tuningFrequency = tuningFrequency;
this._frequency = normalize(inputFrequency, tuningFrequency);
}
/**
* Returns the [pitch class]{@link https://en.wikipedia.org/wiki/Pitch_class}
*
* @function class
* @memberof Pitch
* @instance
* @returns {Number} An integer between 0 and 11 representing the [pitch class]{@link https://en.wikipedia.org/wiki/Pitch_class}
*/
Pitch.prototype.class = function _class() {
// NOTE: original formula was `(9 + semitones % 12) % 12` but `-11 % 12` returns `-11`
// instead of the expected `1` because the remainder from the modulo operation takes the
// sign of the dividend. https://mzl.la/2oCl8yz
return (9 + 12 + round$1(interval(this._tuningFrequency, this._frequency)) % 12) % 12;
};
/**
* Returns the pitch octave.
*
* @function octave
* @memberof Pitch
* @instance
* @returns {Number} An integer representing the pitch octave.
*/
Pitch.prototype.octave = function octave() {
return floor(4 + (9 + interval(this._tuningFrequency, this._frequency)) / 12);
};
/**
* Returns the number of semitones between the input and the normalized frequencies.
*
* @function offset
* @memberof Pitch
* @instance
* @returns {Number} The number of semitones between the input and the normalized frequencies.
*/
Pitch.prototype.offset = function offset() {
return interval(this._inputFrequency, this._frequency);
};
/**
* Returns the next pitch at the given number of semitones away from the current pitch.
*
* @function next
* @memberof Pitch
* @instance
* @param {Number} [semitones = 1] An integer representing the number of semitones.
* @returns {Pitch}
* @example
* import TwelveTet from 'twelvetet'
*
* const tuningFrequency = 440
* const twelvetet = new TwelveTet(tuningFrequency)
*
* const pitch = twelvetet.pitch('A4')
* const pitches = {
* 'A#4': pitch.next(), // or pitch.next(1)
* 'B4': pitch.next(2), // or pitch.next().next()
* 'G#4': pitch.next(-1)
* 'G4': pitch.next(-2)
* }
*/
Pitch.prototype.next = function next$$1() {
var semitones = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
if (!isInteger(semitones)) {
throw new TypeError("Missing or invalid argument, 'semitones'. Integer expected.");
}
var frequency = next(this._frequency, semitones);
return new Pitch(frequency, this._tuningFrequency);
};
/**
* Returns the previous pitch at the given number of semitones away from the current pitch.
*
* @function previous
* @memberof Pitch
* @instance
* @param {Number} [semitones = 1] An integer representing the number of semitones.
* @returns {Pitch}
* @example
* import TwelveTet from 'twelvetet'
*
* const tuningFrequency = 440
* const twelvetet = new TwelveTet(tuningFrequency)
*
* const pitch = twelvetet.pitch('A4')
* const pitches = {
* 'G#4': pitch.previous(), // or pitch.previous(1)
* 'G4': pitch.previous(2), // or pitch.previous().previous()
* 'A#4': pitch.previous(-1)
* 'B4': pitch.previous(-2)
* }
*/
Pitch.prototype.previous = function previous() {
var semitones = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
if (!isInteger(semitones)) {
throw new TypeError("Missing or invalid argument, 'semitones'. Integer expected.");
}
var frequency = next(this._frequency, -semitones);
return new Pitch(frequency, this._tuningFrequency);
};
/**
* Returns the number of semitones between the current pitch and the pitch represented by the given value
*
* @function intervalTo
* @memberof Pitch
* @instance
* @param {Number|String|Pitch} value A value representing a pitch. It can be any of the following:
* <ul>
* <li>a positive number representing a frequency in hertz. If the frequency is out-of-tune, `intervalTo` returns the interval between the frequency of the current pitch and the normalized frequency.</li>
* <li>a string representing scientific pitch notation</li>
* <li>an instance of [Pitch]{@link Pitch}.</li>
* </ul>
*/
Pitch.prototype.intervalTo = function intervalTo(value) {
return round$1(interval(this._frequency, castFrequency(value, this._tuningFrequency)));
};
/**
* Returns the number of semitones between the pitch represented by the given value and the current pitch.
*
* @function intervalFrom
* @memberof Pitch
* @instance
* @param {Number|String|Pitch} value A value representing a pitch. It can be any of the following:
* <ul>
* <li>a positive number representing a frequency in hertz. If the frequency is out-of-tune, `intervalFrom` returns the interval between the normalized frequency and the frequency of the current pitch.</li>
* <li>a string representing scientific pitch notation</li>
* <li>an instance of [Pitch]{@link Pitch}. If the pitch is from an out-of-tune frequency, `intervalFrom` returns the interval between the normalized frequency and the frequency of the current pitch.</li>
* </ul>
*/
Pitch.prototype.intervalFrom = function intervalFrom(value) {
return round$1(interval(castFrequency(value, this._tuningFrequency), this._frequency));
};
/**
* Returns scientific notation of the current pitch
* @function toString
* @memberof Pitch
* @instance
* @param {Boolean} [useFlat=false] If true, use the flat enharmonic equivalent.
* @example
* import TwelveTet from 'twelvetet'
*
* const tuningFrequency = 440
* const twelvetet = new TwelveTet(tuningFrequency)
*
* const pitch = twelvetet.pitch('A#4')
* console.log(pitch) // 'A#4'
* console.log(pitch.toString()) // 'A#4'
* console.log(pitch.toString(true)) // 'Bb4'
*/
Pitch.prototype.toString = function toString() {
var useFlat = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
var results = twelvetetSpn.format([this.class(), this.octave()]);
return results[2] || results[useFlat ? 3 : 1];
};
/**
* Returns the normalized frequency of the pitch.
*
* @function valueOf
* @memberof Pitch
* @instance
* @example
* import TwelveTet from 'twelvetet'
*
* const tuningFrequency = 440
* const twelvetet = new TwelveTet(tuningFrequency)
*
* // returns the normalized frequency
* const pitch = twelvetet.pitch(438)
* console.log(+pitch) // 440
* console.log(pitch.valueOf()) // 440
*/
Pitch.prototype.valueOf = function valueOf() {
return this._frequency;
};
/**
* Returns a boolean indicating whether the two pitches are equal.
*
* @function equals
* @memberof Pitch
* @instance
* @param {Number|String|Pitch} value A value representing a pitch. It can be any of the following:
* <ul>
* <li>a positive number representing a frequency in hertz. If the frequency is out-of-tune, `intervalFrom` returns the interval between the normalized frequency and the frequency of the current pitch.</li>
* <li>a string representing scientific pitch notation</li>
* <li>an instance of [Pitch]{@link Pitch}. If the pitch is from an out-of-tune frequency, `intervalFrom` returns the interval between the normalized frequency and the frequency of the current pitch.</li>
* </ul>
* @returns {Boolean}
*/
Pitch.prototype.equals = function equals(value) {
var pitch = Pitch.create(value, this._tuningFrequency);
return this.class() === pitch.class() && this.octave() === pitch.octave();
};
return Pitch;
}();
Pitch.create = function (value, tuningFrequency) {
return new Pitch(castFrequency(value, tuningFrequency), tuningFrequency);
};
function castFrequency(value, tuningFrequency) {
if (value instanceof Pitch) {
return value._inputFrequency;
}
if (typeof value === 'string') {
var result = twelvetetSpn.parse(value);
if (result == null) {
throw new Error("Invalid argument, 'value'.");
}
return next(tuningFrequency, result[1] * 12 + result[0] - 57);
}
if (!isFrequency(value)) {
throw new TypeError("Missing or invalid argument, 'value'.");
}
return value;
// if (isFrequency(value)) {
// return normalize(value, tuningFrequency)
// }
//
// throw new TypeError("Missing or invalid argument, 'value'.")
}
function isInteger(value) {
return typeof value === 'number' && isFinite(value) && floor(value) === value;
}
/**
* @class TwelveTet
* @param {Number} [tuningFrequency=440] The tuning frequency in hertz.
* @example
* import TwelveTet from 'twelvetet'
*
* let twelvetet
*
* // instantiate with default tuning frequency of 440 Hz
* twelvetet = new TwelveTet()
*
* // instantiate with given tuning frequency
* const tuningFrequency = 432
* twelvetet = new TwelveTet(tuningFrequency)
*/
var TwelveTet = function () {
function TwelveTet() {
var tuningFrequency = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 440;
classCallCheck(this, TwelveTet);
this._tuningFrequency = tuningFrequency;
}
/**
* Returns a pitch
*
* @function pitch
* @memberof TwelveTet
* @instance
* @param {Number|String|Pitch} value
* @example
* import TwelveTet from 'twelvetet'
*
* const tuningFrequency = 440
* const twelvetet = new TwelveTet(tuningFrequency)
*
*
* let pitch
*
* // create a pitch with the given frequency
* pitch = twelvetet.pitch(440)
* console.log(+pitch) // 440
* console.log(pitch.toString()) // 'A4'
*
* // create a pitch with an out-of-tune frequency
* pitch = twelvetet.pitch(438)
* console.log(+pitch) // 440
* console.log(pitch.toString()) // 'A4'
* console.log(pitch.offset()) // 0.07887184708183335
*
* // create a pitch with scientific notation
* pitch = twelvetet.pitch('A4')
*
* // create a pitch with another pitch
* pitch = twelvetet.pitch(pitch.next())
* console.log(pitch.toString()) // 'A#4'
*/
TwelveTet.prototype.pitch = function pitch(value) {
return Pitch.create(value, this._tuningFrequency);
};
return TwelveTet;
}();
module.exports = TwelveTet;