mirror of
https://github.com/Tonejs/Tone.js
synced 2024-11-15 16:17:58 +00:00
309 lines
7.9 KiB
JavaScript
Executable file
309 lines
7.9 KiB
JavaScript
Executable file
/*
|
|
* MUSIC.js - a music creation library containing functions and data sets to generate notes, intervals, chords, scales, ...
|
|
* (currently for twelve-tone equal temperament tuning only)
|
|
*
|
|
* developed by Greg Jopa and Piers Titus
|
|
*
|
|
*/
|
|
var MUSIC = {
|
|
// notes - two dimensional [octave, fifth] - relative to the 'main' note
|
|
notes: {
|
|
'Fb': [6, -10],
|
|
'Cb': [5, -9],
|
|
'Gb': [5, -8],
|
|
'Db': [4, -7],
|
|
'Ab': [4, -6],
|
|
'Eb': [3, -5],
|
|
'Bb': [3, -4],
|
|
|
|
'F': [2, -3],
|
|
'C': [1, -2],
|
|
'G': [1, -1],
|
|
'D': [0, 0],
|
|
'A': [0, 1],
|
|
'E': [-1, 2],
|
|
'B': [-1, 3],
|
|
|
|
'F#': [-2, 4],
|
|
'C#': [-3, 5],
|
|
'G#': [-3, 6],
|
|
'D#': [-4, 7],
|
|
'A#': [-4, 8],
|
|
'E#': [-5, 9],
|
|
'B#': [-5, 10]
|
|
},
|
|
|
|
baseFreq: 440, // A4 'main' note
|
|
baseOffset: [4, 1], // offset of base note from D0
|
|
|
|
// intervals - two dimensional [octave, fifth] - relative to the 'main' note
|
|
intervals: {
|
|
'unison': [0, 0],
|
|
'minor second': [3, -5],
|
|
'major second': [-1, 2],
|
|
'minor third': [2, -3],
|
|
'major third': [-2, 4],
|
|
'fourth': [1, -1],
|
|
'augmented fourth': [-3, 6],
|
|
'tritone': [-3, 6],
|
|
'diminished fifth': [4, -6],
|
|
'fifth': [0, 1],
|
|
'minor sixth': [3, -4],
|
|
'major sixth': [-1, 3],
|
|
'minor seventh': [2, -2],
|
|
'major seventh': [-2, 5],
|
|
'octave': [1, 0]
|
|
},
|
|
|
|
intervals_semitones: {
|
|
0: [0, 0],
|
|
1: [3, -5],
|
|
2: [-1, 2],
|
|
3: [2, -3],
|
|
4: [-2, 4],
|
|
5: [1, -1],
|
|
6: [-3, 6],
|
|
7: [0, 1],
|
|
8: [3, -4],
|
|
9: [-1, 3],
|
|
10: [2, -2],
|
|
11: [-2, 5],
|
|
12: [1, 0]
|
|
},
|
|
|
|
scales: {
|
|
'major': ['major second', 'major third', 'fourth', 'fifth', 'major sixth', 'major seventh'],
|
|
'natural minor': ['major second', 'minor third', 'fourth', 'fifth', 'minor sixth', 'minor seventh'],
|
|
'harmonic minor': ['major second', 'minor third', 'fourth', 'fifth', 'minor sixth', 'major seventh'],
|
|
'major pentatonic': ['major second', 'major third', 'fifth', 'major sixth'],
|
|
'minor pentatonic': ['minor third', 'fourth', 'minor sixth', 'minor seventh']
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Note class
|
|
*
|
|
* @param {Number}x2 coord
|
|
*
|
|
* @constructor
|
|
*/
|
|
function Note(coord) {
|
|
this.coord = coord;
|
|
}
|
|
|
|
Note.prototype.frequency = function() {
|
|
return MUSIC.baseFreq * Math.pow(2.0, (this.coord[0] * 1200 + this.coord[1] * 700) / 1200);
|
|
}
|
|
|
|
Note.prototype.accidental = function() {
|
|
return Math.round((this.coord[1] + MUSIC.baseOffset[1]) / 7);
|
|
}
|
|
|
|
Note.prototype.octave = function() {
|
|
// calculate octave of base note without accidentals
|
|
var acc = this.accidental();
|
|
return this.coord[0] + MUSIC.baseOffset[0] + 4 * acc + Math.floor((this.coord[1] + MUSIC.baseOffset[1] - 7 * acc) / 2);
|
|
}
|
|
|
|
Note.prototype.latin = function() {
|
|
var noteNames = ['F', 'C', 'G', 'D', 'A', 'E', 'B'];
|
|
var accidentals = ['bb', 'b', '', '#', 'x'];
|
|
var acc = this.accidental();
|
|
return noteNames[this.coord[1] + MUSIC.baseOffset[1] - acc * 7 + 3] + accidentals[acc + 2];
|
|
}
|
|
|
|
Note.fromLatin = function(name) {
|
|
|
|
var out = [],
|
|
j = 0,
|
|
i, coord;
|
|
|
|
var n = name.split(/(\d+)/);
|
|
|
|
if (n.length > 3) {
|
|
|
|
for (i = 0; i < (n.length - 1) / 2; i++) {
|
|
|
|
coord = MUSIC.notes[n[j]];
|
|
coord = [coord[0] + parseInt(n[j + 1]), coord[1]];
|
|
|
|
coord[0] -= MUSIC.baseOffset[0];
|
|
coord[1] -= MUSIC.baseOffset[1];
|
|
|
|
out[i] = new Note(coord);
|
|
j += 2;
|
|
}
|
|
return out;
|
|
}
|
|
else
|
|
{
|
|
coord = MUSIC.notes[n[0]];
|
|
coord = [coord[0] + parseInt(n[1]), coord[1]];
|
|
|
|
coord[0] -= MUSIC.baseOffset[0];
|
|
coord[1] -= MUSIC.baseOffset[1];
|
|
|
|
return new Note(coord);
|
|
}
|
|
}
|
|
|
|
Note.prototype.scale = function(name) {
|
|
|
|
var out = [],
|
|
i;
|
|
|
|
var scale = MUSIC.scales[name];
|
|
|
|
out.push(this.add('unison'));
|
|
|
|
for (i = 0; i < scale.length; i++) {
|
|
out[i + 1] = this.add(Interval.fromName(scale[i]));
|
|
}
|
|
|
|
out.push(this.add('octave'));
|
|
|
|
return out;
|
|
}
|
|
|
|
Note.prototype.add = function(interval) {
|
|
|
|
var out = [],
|
|
i;
|
|
|
|
// if input is string try to parse it as interval
|
|
if (typeof(interval) == 'string') {
|
|
interval = Interval.fromName(interval);
|
|
}
|
|
|
|
// if input is an array return an array
|
|
if (interval.length) {
|
|
|
|
for (i = 0; i < interval.length; i++) {
|
|
out[i] = this.add(interval[i]);
|
|
}
|
|
add_addsubtract_func(out);
|
|
|
|
return out;
|
|
}
|
|
else {
|
|
return new Note([this.coord[0] + interval.coord[0], this.coord[1] + interval.coord[1]]);
|
|
}
|
|
}
|
|
|
|
Note.prototype.subtract = function(interval) {
|
|
|
|
var out = [],
|
|
i, coord;
|
|
|
|
// if input is string try to parse it as interval
|
|
if (typeof(interval) == 'string') {
|
|
interval = Interval.fromName(interval);
|
|
}
|
|
|
|
// if input is an array return an array
|
|
if (interval.length) {
|
|
|
|
for (i = 0; i < interval.length; i++) {
|
|
out[i] = this.subtract(interval[i]);
|
|
}
|
|
|
|
add_addsubtract_func(out);
|
|
|
|
return out;
|
|
|
|
}
|
|
else {
|
|
coord = [this.coord[0] - interval.coord[0], this.coord[1] - interval.coord[1]];
|
|
if (typeof(interval.frequency) == 'function') {
|
|
// if input is another note return the difference as interval
|
|
return new Interval(coord);
|
|
} else {
|
|
return new Note(coord);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Interval class
|
|
*
|
|
* @param {Number}x2 coord
|
|
*
|
|
* @constructor
|
|
*/
|
|
function Interval(coord) {
|
|
this.coord = coord;
|
|
}
|
|
|
|
Interval.fromName = function(name) {
|
|
return new Interval(MUSIC.intervals[name]);
|
|
}
|
|
|
|
Interval.fromSemitones = function(num) {
|
|
return new Interval(MUSIC.intervals_semitones[num]);
|
|
}
|
|
|
|
Interval.fromTonesSemitones = function(tone_semitone) {
|
|
// multiply [tones, semitones] vector with [-1 2;3 -5] to get coordinate from tones and semitones
|
|
return new Interval([tone_semitone[0] * -1 + tone_semitone[1] * 3, tone_semitone[0] * 2 + tone_semitone[1] * -5]);
|
|
}
|
|
|
|
Interval.prototype.tone_semitone = function() {
|
|
// multiply coord vector with [5 2;3 1] to get coordinate in tones and semitones
|
|
// [5 2;3 1] is the inverse of [-1 2;3 -5], which is the coordinates of [tone; semitone]
|
|
return [this.coord[0] * 5 + this.coord[1] * 3, this.coord[0] * 2 + this.coord[1] * 1];
|
|
}
|
|
|
|
Interval.prototype.semitone = function() {
|
|
// number of semitones of interval = tones * 2 + semitones
|
|
var tone_semitone = this.tone_semitone();
|
|
return tone_semitone[0] * 2 + tone_semitone[1];
|
|
}
|
|
|
|
Interval.prototype.add = function(interval) {
|
|
if (typeof(interval) == 'string') {
|
|
interval = Interval.fromName(interval);
|
|
}
|
|
return new Interval([this.coord[0] + interval.coord[0], this.coord[1] + interval.coord[1]]);
|
|
}
|
|
|
|
Interval.prototype.subtract = function(interval) {
|
|
if (typeof(interval) == 'string') {
|
|
interval = Interval.fromName(interval);
|
|
}
|
|
return new Note([this.coord[0] - interval.coord[0], this.coord[1] - interval.coord[1]]);
|
|
}
|
|
|
|
|
|
/**
|
|
* function to add the .add and .subtract functions to an array. Those functions now are executed for each element in an array.
|
|
*/
|
|
function add_addsubtract_func(array) {
|
|
array.add = function(that) {
|
|
var out = [],
|
|
x;
|
|
|
|
for (x in this) {
|
|
if (typeof(this[x]) == 'object') {
|
|
out[x] = this[x].add(that);
|
|
}
|
|
}
|
|
add_addsubtract_func(out);
|
|
return out;
|
|
};
|
|
array.subtract = function(that) {
|
|
var out = [],
|
|
x;
|
|
|
|
for (x in this) {
|
|
if (typeof(this[x]) == 'object') {
|
|
out[x] = this[x].subtract(that);
|
|
}
|
|
}
|
|
|
|
add_addsubtract_func(out);
|
|
|
|
return out;
|
|
};
|
|
return array;
|
|
}
|