diff --git a/app.js b/app.js
index 7397c20..d324c43 100644
--- a/app.js
+++ b/app.js
@@ -8,15 +8,17 @@ const maxInput = document.querySelector('#generate-tone input:nth-of-type(2)')
const synth = new Tone.Synth().toDestination()
let newTone
-let lowest = 27.5
-let highest = 4186
+let lowest = 'a0'
+let highest = 'c8'
minInput.setAttribute('placeholder', lowest)
maxInput.setAttribute('placeholder', highest)
generateButton.addEventListener('click', () => {
Tone.start()
- newTone = (Math.random() * (highest - lowest)) + lowest
+ const minFreq = teoria.note(lowest).fq()
+ const maxFreq = teoria.note(highest).fq()
+ newTone = (Math.random() * (maxFreq - minFreq)) + minFreq
toneDisplay.innerHTML = newTone
})
@@ -29,9 +31,9 @@ stopButton.addEventListener('click', ()=>{
})
minInput.addEventListener('change', ()=>{
- lowest = parseFloat(minInput.value)
+ lowest = minInput.value
})
maxInput.addEventListener('change', ()=>{
- highest = parseFloat(maxInput.value)
+ highest = maxInput.value
})
diff --git a/index.html b/index.html
index 8ea3705..fc13041 100644
--- a/index.html
+++ b/index.html
@@ -4,6 +4,7 @@
+
@@ -15,8 +16,8 @@
Tone:
-
-
+
+
diff --git a/teoria.js b/teoria.js
new file mode 100644
index 0000000..439d7ed
--- /dev/null
+++ b/teoria.js
@@ -0,0 +1,1404 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.teoria = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) ? val : val - 2;
+ },
+
+ type: function() {
+ return knowledge.intervals[this.base()][0] <= 1 ? 'perfect' : 'minor';
+ },
+
+ base: function() {
+ var product = vector.mul(knowledge.sharp, this.qualityValue());
+ var fifth = vector.sub(this.coord, product)[1];
+ fifth = this.value() > 0 ? fifth + 5 : -(fifth - 5) % 7;
+ fifth = fifth < 0 ? knowledge.intervalFromFifth.length + fifth : fifth;
+
+ var name = knowledge.intervalFromFifth[fifth];
+ if (name === 'unison' && this.number() >= 8)
+ name = 'octave';
+
+ return name;
+ },
+
+ direction: function(dir) {
+ if (dir) {
+ var is = this.value() >= 1 ? 'up' : 'down';
+ if (is !== dir)
+ this.coord = vector.mul(this.coord, -1);
+
+ return this;
+ }
+ else
+ return this.value() >= 1 ? 'up' : 'down';
+ },
+
+ simple: function(ignore) {
+ // Get the (upwards) base interval (with quality)
+ var simple = knowledge.intervals[this.base()];
+ var toAdd = vector.mul(knowledge.sharp, this.qualityValue());
+ simple = vector.add(simple, toAdd);
+
+ // Turn it around if necessary
+ if (!ignore)
+ simple = this.direction() === 'down' ? vector.mul(simple, -1) : simple;
+
+ return new Interval(simple);
+ },
+
+ isCompound: function() {
+ return this.number() > 8;
+ },
+
+ octaves: function() {
+ var toSubtract, without, octaves;
+
+ if (this.direction() === 'up') {
+ toSubtract = vector.mul(knowledge.sharp, this.qualityValue());
+ without = vector.sub(this.coord, toSubtract);
+ octaves = without[0] - knowledge.intervals[this.base()][0];
+ } else {
+ toSubtract = vector.mul(knowledge.sharp, -this.qualityValue());
+ without = vector.sub(this.coord, toSubtract);
+ octaves = -(without[0] + knowledge.intervals[this.base()][0]);
+ }
+
+ return octaves;
+ },
+
+ invert: function() {
+ var i = this.base();
+ var qual = this.qualityValue();
+ var acc = this.type() === 'minor' ? -(qual - 1) : -qual;
+ var idx = 9 - knowledge.stepNumber[i] - 1;
+ var coord = knowledge.intervals[knowledge.intervalsIndex[idx]];
+ coord = vector.add(coord, vector.mul(knowledge.sharp, acc));
+
+ return new Interval(coord);
+ },
+
+ quality: function(lng) {
+ var quality = knowledge.alterations[this.type()][this.qualityValue() + 2];
+
+ return lng ? knowledge.qualityLong[quality] : quality;
+ },
+
+ qualityValue: function() {
+ if (this.direction() === 'down')
+ return Math.floor((-this.coord[1] - 2) / 7) + 1;
+ else
+ return Math.floor((this.coord[1] - 2) / 7) + 1;
+ },
+
+ equal: function(interval) {
+ return this.coord[0] === interval.coord[0] &&
+ this.coord[1] === interval.coord[1];
+ },
+
+ greater: function(interval) {
+ var semi = this.semitones();
+ var isemi = interval.semitones();
+
+ // If equal in absolute size, measure which interval is bigger
+ // For example P4 is bigger than A3
+ return (semi === isemi) ?
+ (this.number() > interval.number()) : (semi > isemi);
+ },
+
+ smaller: function(interval) {
+ return !this.equal(interval) && !this.greater(interval);
+ },
+
+ add: function(interval) {
+ return new Interval(vector.add(this.coord, interval.coord));
+ },
+
+ toString: function(ignore) {
+ // If given true, return the positive value
+ var number = ignore ? this.number() : this.value();
+
+ return this.quality() + number;
+ }
+};
+
+Interval.toCoord = function(simple) {
+ var coord = toCoord(simple);
+ if (!coord)
+ throw new Error('Invalid simple format interval');
+
+ return new Interval(coord);
+};
+
+Interval.from = function(from, to) {
+ return from.interval(to);
+};
+
+Interval.between = function(from, to) {
+ return new Interval(vector.sub(to.coord, from.coord));
+};
+
+Interval.invert = function(sInterval) {
+ return Interval.toCoord(sInterval).invert().toString();
+};
+
+module.exports = Interval;
+
+},{"./knowledge":4,"./vector":8,"interval-coords":12}],4:[function(require,module,exports){
+// Note coordinates [octave, fifth] relative to C
+module.exports = {
+ notes: {
+ c: [0, 0],
+ d: [-1, 2],
+ e: [-2, 4],
+ f: [1, -1],
+ g: [0, 1],
+ a: [-1, 3],
+ b: [-2, 5],
+ h: [-2, 5]
+ },
+
+ intervals: {
+ unison: [0, 0],
+ second: [3, -5],
+ third: [2, -3],
+ fourth: [1, -1],
+ fifth: [0, 1],
+ sixth: [3, -4],
+ seventh: [2, -2],
+ octave: [1, 0]
+ },
+
+ intervalFromFifth: ['second', 'sixth', 'third', 'seventh', 'fourth',
+ 'unison', 'fifth'],
+
+ intervalsIndex: ['unison', 'second', 'third', 'fourth', 'fifth',
+ 'sixth', 'seventh', 'octave', 'ninth', 'tenth',
+ 'eleventh', 'twelfth', 'thirteenth', 'fourteenth',
+ 'fifteenth'],
+
+// linear index to fifth = (2 * index + 1) % 7
+ fifths: ['f', 'c', 'g', 'd', 'a', 'e', 'b'],
+ accidentals: ['bb', 'b', '', '#', 'x'],
+
+ sharp: [-4, 7],
+ A4: [3, 3],
+
+ durations: {
+ '0.25': 'longa',
+ '0.5': 'breve',
+ '1': 'whole',
+ '2': 'half',
+ '4': 'quarter',
+ '8': 'eighth',
+ '16': 'sixteenth',
+ '32': 'thirty-second',
+ '64': 'sixty-fourth',
+ '128': 'hundred-twenty-eighth'
+ },
+
+ qualityLong: {
+ P: 'perfect',
+ M: 'major',
+ m: 'minor',
+ A: 'augmented',
+ AA: 'doubly augmented',
+ d: 'diminished',
+ dd: 'doubly diminished'
+ },
+
+ alterations: {
+ perfect: ['dd', 'd', 'P', 'A', 'AA'],
+ minor: ['dd', 'd', 'm', 'M', 'A', 'AA']
+ },
+
+ symbols: {
+ 'min': ['m3', 'P5'],
+ 'm': ['m3', 'P5'],
+ '-': ['m3', 'P5'],
+
+ 'M': ['M3', 'P5'],
+ '': ['M3', 'P5'],
+
+ '+': ['M3', 'A5'],
+ 'aug': ['M3', 'A5'],
+
+ 'dim': ['m3', 'd5'],
+ 'o': ['m3', 'd5'],
+
+ 'maj': ['M3', 'P5', 'M7'],
+ 'dom': ['M3', 'P5', 'm7'],
+ 'ø': ['m3', 'd5', 'm7'],
+
+ '5': ['P5']
+ },
+
+ chordShort: {
+ 'major': 'M',
+ 'minor': 'm',
+ 'augmented': 'aug',
+ 'diminished': 'dim',
+ 'half-diminished': '7b5',
+ 'power': '5',
+ 'dominant': '7'
+ },
+
+ stepNumber: {
+ 'unison': 1,
+ 'first': 1,
+ 'second': 2,
+ 'third': 3,
+ 'fourth': 4,
+ 'fifth': 5,
+ 'sixth': 6,
+ 'seventh': 7,
+ 'octave': 8,
+ 'ninth': 9,
+ 'eleventh': 11,
+ 'thirteenth': 13
+ },
+
+ // Adjusted Shearer syllables - Chromatic solfege system
+ // Some intervals are not provided for. These include:
+ // dd2 - Doubly diminished second
+ // dd3 - Doubly diminished third
+ // AA3 - Doubly augmented third
+ // dd6 - Doubly diminished sixth
+ // dd7 - Doubly diminished seventh
+ // AA7 - Doubly augmented seventh
+ intervalSolfege: {
+ 'dd1': 'daw',
+ 'd1': 'de',
+ 'P1': 'do',
+ 'A1': 'di',
+ 'AA1': 'dai',
+ 'd2': 'raw',
+ 'm2': 'ra',
+ 'M2': 're',
+ 'A2': 'ri',
+ 'AA2': 'rai',
+ 'd3': 'maw',
+ 'm3': 'me',
+ 'M3': 'mi',
+ 'A3': 'mai',
+ 'dd4': 'faw',
+ 'd4': 'fe',
+ 'P4': 'fa',
+ 'A4': 'fi',
+ 'AA4': 'fai',
+ 'dd5': 'saw',
+ 'd5': 'se',
+ 'P5': 'so',
+ 'A5': 'si',
+ 'AA5': 'sai',
+ 'd6': 'law',
+ 'm6': 'le',
+ 'M6': 'la',
+ 'A6': 'li',
+ 'AA6': 'lai',
+ 'd7': 'taw',
+ 'm7': 'te',
+ 'M7': 'ti',
+ 'A7': 'tai',
+ 'dd8': 'daw',
+ 'd8': 'de',
+ 'P8': 'do',
+ 'A8': 'di',
+ 'AA8': 'dai'
+ }
+};
+
+},{}],5:[function(require,module,exports){
+var scientific = require('scientific-notation');
+var helmholtz = require('helmholtz');
+var pitchFq = require('pitch-fq');
+var knowledge = require('./knowledge');
+var vector = require('./vector');
+var Interval = require('./interval');
+
+function pad(str, ch, len) {
+ for (; len > 0; len--) {
+ str += ch;
+ }
+
+ return str;
+}
+
+
+function Note(coord, duration) {
+ if (!(this instanceof Note)) return new Note(coord, duration);
+ duration = duration || {};
+
+ this.duration = { value: duration.value || 4, dots: duration.dots || 0 };
+ this.coord = coord;
+}
+
+Note.prototype = {
+ octave: function() {
+ return this.coord[0] + knowledge.A4[0] - knowledge.notes[this.name()][0] +
+ this.accidentalValue() * 4;
+ },
+
+ name: function() {
+ var value = this.accidentalValue();
+ var idx = this.coord[1] + knowledge.A4[1] - value * 7 + 1;
+ return knowledge.fifths[idx];
+ },
+
+ accidentalValue: function() {
+ return Math.round((this.coord[1] + knowledge.A4[1] - 2) / 7);
+ },
+
+ accidental: function() {
+ return knowledge.accidentals[this.accidentalValue() + 2];
+ },
+
+ /**
+ * Returns the key number of the note
+ */
+ key: function(white) {
+ if (white)
+ return this.coord[0] * 7 + this.coord[1] * 4 + 29;
+ else
+ return this.coord[0] * 12 + this.coord[1] * 7 + 49;
+ },
+
+ /**
+ * Returns a number ranging from 0-127 representing a MIDI note value
+ */
+ midi: function() {
+ return this.key() + 20;
+ },
+
+ /**
+ * Calculates and returns the frequency of the note.
+ * Optional concert pitch (def. 440)
+ */
+ fq: function(concertPitch) {
+ return pitchFq(this.coord, concertPitch);
+ },
+
+ /**
+ * Returns the pitch class index (chroma) of the note
+ */
+ chroma: function() {
+ var value = (vector.sum(vector.mul(this.coord, [12, 7])) - 3) % 12;
+
+ return (value < 0) ? value + 12 : value;
+ },
+
+ interval: function(interval) {
+ if (typeof interval === 'string') interval = Interval.toCoord(interval);
+
+ if (interval instanceof Interval)
+ return new Note(vector.add(this.coord, interval.coord), this.duration);
+ else if (interval instanceof Note)
+ return new Interval(vector.sub(interval.coord, this.coord));
+ },
+
+ transpose: function(interval) {
+ this.coord = vector.add(this.coord, interval.coord);
+ return this;
+ },
+
+ /**
+ * Returns the Helmholtz notation form of the note (fx C,, d' F# g#'')
+ */
+ helmholtz: function() {
+ var octave = this.octave();
+ var name = this.name();
+ name = octave < 3 ? name.toUpperCase() : name.toLowerCase();
+ var padchar = octave < 3 ? ',' : '\'';
+ var padcount = octave < 2 ? 2 - octave : octave - 3;
+
+ return pad(name + this.accidental(), padchar, padcount);
+ },
+
+ /**
+ * Returns the scientific notation form of the note (fx E4, Bb3, C#7 etc.)
+ */
+ scientific: function() {
+ return this.name().toUpperCase() + this.accidental() + this.octave();
+ },
+
+ /**
+ * Returns notes that are enharmonic with this note.
+ */
+ enharmonics: function(oneaccidental) {
+ var key = this.key(), limit = oneaccidental ? 2 : 3;
+
+ return ['m3', 'm2', 'm-2', 'm-3']
+ .map(this.interval.bind(this))
+ .filter(function(note) {
+ var acc = note.accidentalValue();
+ var diff = key - (note.key() - acc);
+
+ if (diff < limit && diff > -limit) {
+ var product = vector.mul(knowledge.sharp, diff - acc);
+ note.coord = vector.add(note.coord, product);
+ return true;
+ }
+ });
+ },
+
+ solfege: function(scale, showOctaves) {
+ var interval = scale.tonic.interval(this), solfege, stroke, count;
+ if (interval.direction() === 'down')
+ interval = interval.invert();
+
+ if (showOctaves) {
+ count = (this.key(true) - scale.tonic.key(true)) / 7;
+ count = (count >= 0) ? Math.floor(count) : -(Math.ceil(-count));
+ stroke = (count >= 0) ? '\'' : ',';
+ }
+
+ solfege = knowledge.intervalSolfege[interval.simple(true).toString()];
+ return (showOctaves) ? pad(solfege, stroke, Math.abs(count)) : solfege;
+ },
+
+ scaleDegree: function(scale) {
+ var inter = scale.tonic.interval(this);
+
+ // If the direction is down, or we're dealing with an octave - invert it
+ if (inter.direction() === 'down' ||
+ (inter.coord[1] === 0 && inter.coord[0] !== 0)) {
+ inter = inter.invert();
+ }
+
+ inter = inter.simple(true).coord;
+
+ return scale.scale.reduce(function(index, current, i) {
+ var coord = Interval.toCoord(current).coord;
+ return coord[0] === inter[0] && coord[1] === inter[1] ? i + 1 : index;
+ }, 0);
+ },
+
+ /**
+ * Returns the name of the duration value,
+ * such as 'whole', 'quarter', 'sixteenth' etc.
+ */
+ durationName: function() {
+ return knowledge.durations[this.duration.value];
+ },
+
+ /**
+ * Returns the duration of the note (including dots)
+ * in seconds. The first argument is the tempo in beats
+ * per minute, the second is the beat unit (i.e. the
+ * lower numeral in a time signature).
+ */
+ durationInSeconds: function(bpm, beatUnit) {
+ var secs = (60 / bpm) / (this.duration.value / 4) / (beatUnit / 4);
+ return secs * 2 - secs / Math.pow(2, this.duration.dots);
+ },
+
+ /**
+ * Returns the name of the note, with an optional display of octave number
+ */
+ toString: function(dont) {
+ return this.name() + this.accidental() + (dont ? '' : this.octave());
+ }
+};
+
+Note.fromString = function(name, dur) {
+ var coord = scientific(name);
+ if (!coord) coord = helmholtz(name);
+ return new Note(coord, dur);
+};
+
+Note.fromKey = function(key) {
+ var octave = Math.floor((key - 4) / 12);
+ var distance = key - (octave * 12) - 4;
+ var name = knowledge.fifths[(2 * Math.round(distance / 2) + 1) % 7];
+ var subDiff = vector.sub(knowledge.notes[name], knowledge.A4);
+ var note = vector.add(subDiff, [octave + 1, 0]);
+ var diff = (key - 49) - vector.sum(vector.mul(note, [12, 7]));
+
+ var arg = diff ? vector.add(note, vector.mul(knowledge.sharp, diff)) : note;
+ return new Note(arg);
+};
+
+Note.fromFrequency = function(fq, concertPitch) {
+ var key, cents, originalFq;
+ concertPitch = concertPitch || 440;
+
+ key = 49 + 12 * ((Math.log(fq) - Math.log(concertPitch)) / Math.log(2));
+ key = Math.round(key);
+ originalFq = concertPitch * Math.pow(2, (key - 49) / 12);
+ cents = 1200 * (Math.log(fq / originalFq) / Math.log(2));
+
+ return { note: Note.fromKey(key), cents: cents };
+};
+
+Note.fromMIDI = function(note) {
+ return Note.fromKey(note - 20);
+};
+
+module.exports = Note;
+
+},{"./interval":3,"./knowledge":4,"./vector":8,"helmholtz":11,"pitch-fq":14,"scientific-notation":15}],6:[function(require,module,exports){
+var knowledge = require('./knowledge');
+var Interval = require('./interval');
+
+var scales = {
+ aeolian: ['P1', 'M2', 'm3', 'P4', 'P5', 'm6', 'm7'],
+ blues: ['P1', 'm3', 'P4', 'd5', 'P5', 'm7'],
+ chromatic: ['P1', 'm2', 'M2', 'm3', 'M3', 'P4',
+ 'A4', 'P5', 'm6', 'M6', 'm7', 'M7'],
+ dorian: ['P1', 'M2', 'm3', 'P4', 'P5', 'M6', 'm7'],
+ doubleharmonic: ['P1', 'm2', 'M3', 'P4', 'P5', 'm6', 'M7'],
+ harmonicminor: ['P1', 'M2', 'm3', 'P4', 'P5', 'm6', 'M7'],
+ ionian: ['P1', 'M2', 'M3', 'P4', 'P5', 'M6', 'M7'],
+ locrian: ['P1', 'm2', 'm3', 'P4', 'd5', 'm6', 'm7'],
+ lydian: ['P1', 'M2', 'M3', 'A4', 'P5', 'M6', 'M7'],
+ majorpentatonic: ['P1', 'M2', 'M3', 'P5', 'M6'],
+ melodicminor: ['P1', 'M2', 'm3', 'P4', 'P5', 'M6', 'M7'],
+ minorpentatonic: ['P1', 'm3', 'P4', 'P5', 'm7'],
+ mixolydian: ['P1', 'M2', 'M3', 'P4', 'P5', 'M6', 'm7'],
+ phrygian: ['P1', 'm2', 'm3', 'P4', 'P5', 'm6', 'm7'],
+ wholetone: ['P1', 'M2', 'M3', 'A4', 'A5', 'A6']
+};
+
+// synonyms
+scales.harmonicchromatic = scales.chromatic;
+scales.minor = scales.aeolian;
+scales.major = scales.ionian;
+scales.flamenco = scales.doubleharmonic;
+
+function Scale(tonic, scale) {
+ if (!(this instanceof Scale)) return new Scale(tonic, scale);
+ var scaleName, i;
+ if (!('coord' in tonic)) {
+ throw new Error('Invalid Tonic');
+ }
+
+ if (typeof scale === 'string') {
+ scaleName = scale;
+ scale = scales[scale];
+ if (!scale)
+ throw new Error('Invalid Scale');
+ } else {
+ for (i in scales) {
+ if (scales.hasOwnProperty(i)) {
+ if (scales[i].toString() === scale.toString()) {
+ scaleName = i;
+ break;
+ }
+ }
+ }
+ }
+
+ this.name = scaleName;
+ this.tonic = tonic;
+ this.scale = scale;
+}
+
+Scale.prototype = {
+ notes: function() {
+ var notes = [];
+
+ for (var i = 0, length = this.scale.length; i < length; i++) {
+ notes.push(this.tonic.interval(this.scale[i]));
+ }
+
+ return notes;
+ },
+
+ simple: function() {
+ return this.notes().map(function(n) { return n.toString(true); });
+ },
+
+ type: function() {
+ var length = this.scale.length - 2;
+ if (length < 8) {
+ return ['di', 'tri', 'tetra', 'penta', 'hexa', 'hepta', 'octa'][length] +
+ 'tonic';
+ }
+ },
+
+ get: function(i) {
+ var isStepStr = typeof i === 'string' && i in knowledge.stepNumber;
+ i = isStepStr ? knowledge.stepNumber[i] : i;
+ var len = this.scale.length;
+ var interval, octaves;
+
+ if (i < 0) {
+ interval = this.scale[i % len + len - 1];
+ octaves = Math.floor((i - 1) / len);
+ } else if (i % len === 0) {
+ interval = this.scale[len - 1];
+ octaves = (i / len) - 1;
+ } else {
+ interval = this.scale[i % len - 1];
+ octaves = Math.floor(i / len);
+ }
+
+ return this.tonic.interval(interval).interval(new Interval([octaves, 0]));
+ },
+
+ solfege: function(index, showOctaves) {
+ if (index)
+ return this.get(index).solfege(this, showOctaves);
+
+ return this.notes().map(function(n) {
+ return n.solfege(this, showOctaves);
+ });
+ },
+
+ interval: function(interval) {
+ interval = (typeof interval === 'string') ?
+ Interval.toCoord(interval) : interval;
+ return new Scale(this.tonic.interval(interval), this.scale);
+ },
+
+ transpose: function(interval) {
+ var scale = this.interval(interval);
+ this.scale = scale.scale;
+ this.tonic = scale.tonic;
+
+ return this;
+ }
+};
+Scale.KNOWN_SCALES = Object.keys(scales);
+
+module.exports = Scale;
+
+},{"./interval":3,"./knowledge":4}],7:[function(require,module,exports){
+var knowledge = require('./knowledge');
+
+module.exports = function(teoria) {
+ var Note = teoria.Note;
+ var Chord = teoria.Chord;
+ var Scale = teoria.Scale;
+
+ Note.prototype.chord = function(chord) {
+ var isShortChord = chord in knowledge.chordShort;
+ chord = isShortChord ? knowledge.chordShort[chord] : chord;
+
+ return new Chord(this, chord);
+ };
+
+ Note.prototype.scale = function(scale) {
+ return new Scale(this, scale);
+ };
+};
+
+},{"./knowledge":4}],8:[function(require,module,exports){
+module.exports = {
+ add: function(note, interval) {
+ return [note[0] + interval[0], note[1] + interval[1]];
+ },
+
+ sub: function(note, interval) {
+ return [note[0] - interval[0], note[1] - interval[1]];
+ },
+
+ mul: function(note, interval) {
+ if (typeof interval === 'number')
+ return [note[0] * interval, note[1] * interval];
+ else
+ return [note[0] * interval[0], note[1] * interval[1]];
+ },
+
+ sum: function(coord) {
+ return coord[0] + coord[1];
+ }
+};
+
+},{}],9:[function(require,module,exports){
+var accidentalValues = {
+ 'bb': -2,
+ 'b': -1,
+ '': 0,
+ '#': 1,
+ 'x': 2
+};
+
+module.exports = function accidentalNumber(acc) {
+ return accidentalValues[acc];
+}
+
+module.exports.interval = function accidentalInterval(acc) {
+ var val = accidentalValues[acc];
+ return [-4 * val, 7 * val];
+}
+
+},{}],10:[function(require,module,exports){
+var SYMBOLS = {
+ 'm': ['m3', 'P5'],
+ 'mi': ['m3', 'P5'],
+ 'min': ['m3', 'P5'],
+ '-': ['m3', 'P5'],
+
+ 'M': ['M3', 'P5'],
+ 'ma': ['M3', 'P5'],
+ '': ['M3', 'P5'],
+
+ '+': ['M3', 'A5'],
+ 'aug': ['M3', 'A5'],
+
+ 'dim': ['m3', 'd5'],
+ 'o': ['m3', 'd5'],
+
+ 'maj': ['M3', 'P5', 'M7'],
+ 'dom': ['M3', 'P5', 'm7'],
+ 'ø': ['m3', 'd5', 'm7'],
+
+ '5': ['P5'],
+
+ '6/9': ['M3', 'P5', 'M6', 'M9']
+};
+
+module.exports = function(symbol) {
+ var c, parsing = 'quality', additionals = [], name, chordLength = 2
+ var notes = ['P1', 'M3', 'P5', 'm7', 'M9', 'P11', 'M13'];
+ var explicitMajor = false;
+
+ function setChord(name) {
+ var intervals = SYMBOLS[name];
+ for (var i = 0, len = intervals.length; i < len; i++) {
+ notes[i + 1] = intervals[i];
+ }
+
+ chordLength = intervals.length;
+ }
+
+ // Remove whitespace, commas and parentheses
+ symbol = symbol.replace(/[,\s\(\)]/g, '');
+ for (var i = 0, len = symbol.length; i < len; i++) {
+ if (!(c = symbol[i]))
+ return;
+
+ if (parsing === 'quality') {
+ var sub3 = (i + 2) < len ? symbol.substr(i, 3).toLowerCase() : null;
+ var sub2 = (i + 1) < len ? symbol.substr(i, 2).toLowerCase() : null;
+ if (sub3 in SYMBOLS)
+ name = sub3;
+ else if (sub2 in SYMBOLS)
+ name = sub2;
+ else if (c in SYMBOLS)
+ name = c;
+ else
+ name = '';
+
+ if (name)
+ setChord(name);
+
+ if (name === 'M' || name === 'ma' || name === 'maj')
+ explicitMajor = true;
+
+
+ i += name.length - 1;
+ parsing = 'extension';
+ } else if (parsing === 'extension') {
+ c = (c === '1' && symbol[i + 1]) ? +symbol.substr(i, 2) : +c;
+
+ if (!isNaN(c) && c !== 6) {
+ chordLength = (c - 1) / 2;
+
+ if (chordLength !== Math.round(chordLength))
+ return new Error('Invalid interval extension: ' + c.toString(10));
+
+ if (name === 'o' || name === 'dim')
+ notes[3] = 'd7';
+ else if (explicitMajor)
+ notes[3] = 'M7';
+
+ i += c >= 10 ? 1 : 0;
+ } else if (c === 6) {
+ notes[3] = 'M6';
+ chordLength = Math.max(3, chordLength);
+ } else
+ i -= 1;
+
+ parsing = 'alterations';
+ } else if (parsing === 'alterations') {
+ var alterations = symbol.substr(i).split(/(#|b|add|maj|sus|M)/i),
+ next, flat = false, sharp = false;
+
+ if (alterations.length === 1)
+ return new Error('Invalid alteration');
+ else if (alterations[0].length !== 0)
+ return new Error('Invalid token: \'' + alterations[0] + '\'');
+
+ var ignore = false;
+ alterations.forEach(function(alt, i, arr) {
+ if (ignore || !alt.length)
+ return ignore = false;
+
+ var next = arr[i + 1], lower = alt.toLowerCase();
+ if (alt === 'M' || lower === 'maj') {
+ if (next === '7')
+ ignore = true;
+
+ chordLength = Math.max(3, chordLength);
+ notes[3] = 'M7';
+ } else if (lower === 'sus') {
+ var type = 'P4';
+ if (next === '2' || next === '4') {
+ ignore = true;
+
+ if (next === '2')
+ type = 'M2';
+ }
+
+ notes[1] = type; // Replace third with M2 or P4
+ } else if (lower === 'add') {
+ if (next === '9')
+ additionals.push('M9');
+ else if (next === '11')
+ additionals.push('P11');
+ else if (next === '13')
+ additionals.push('M13');
+
+ ignore = true
+ } else if (lower === 'b') {
+ flat = true;
+ } else if (lower === '#') {
+ sharp = true;
+ } else {
+ var token = +alt, quality, intPos;
+ if (isNaN(token) || String(token).length !== alt.length)
+ return new Error('Invalid token: \'' + alt + '\'');
+
+ if (token === 6) {
+ if (sharp)
+ notes[3] = 'A6';
+ else if (flat)
+ notes[3] = 'm6';
+ else
+ notes[3] = 'M6';
+
+ chordLength = Math.max(3, chordLength);
+ return;
+ }
+
+ // Calculate the position in the 'note' array
+ intPos = (token - 1) / 2;
+ if (chordLength < intPos)
+ chordLength = intPos;
+
+ if (token < 5 || token === 7 || intPos !== Math.round(intPos))
+ return new Error('Invalid interval alteration: ' + token);
+
+ quality = notes[intPos][0];
+
+ // Alterate the quality of the interval according the accidentals
+ if (sharp) {
+ if (quality === 'd')
+ quality = 'm';
+ else if (quality === 'm')
+ quality = 'M';
+ else if (quality === 'M' || quality === 'P')
+ quality = 'A';
+ } else if (flat) {
+ if (quality === 'A')
+ quality = 'M';
+ else if (quality === 'M')
+ quality = 'm';
+ else if (quality === 'm' || quality === 'P')
+ quality = 'd';
+ }
+
+ sharp = flat = false;
+ notes[intPos] = quality + token;
+ }
+ });
+ parsing = 'ended';
+ } else if (parsing === 'ended') {
+ break;
+ }
+ }
+
+ return notes.slice(0, chordLength + 1).concat(additionals);
+}
+
+},{}],11:[function(require,module,exports){
+var coords = require('notecoord');
+var accval = require('accidental-value');
+
+module.exports = function helmholtz(name) {
+ var name = name.replace(/\u2032/g, "'").replace(/\u0375/g, ',');
+ var parts = name.match(/^(,*)([a-h])(x|#|bb|b?)([,\']*)$/i);
+
+ if (!parts || name !== parts[0])
+ throw new Error('Invalid formatting');
+
+ var note = parts[2];
+ var octaveFirst = parts[1];
+ var octaveLast = parts[4];
+ var lower = note === note.toLowerCase();
+ var octave;
+
+ if (octaveFirst) {
+ if (lower)
+ throw new Error('Invalid formatting - found commas before lowercase note');
+
+ octave = 2 - octaveFirst.length;
+ } else if (octaveLast) {
+ if (octaveLast.match(/^'+$/) && lower)
+ octave = 3 + octaveLast.length;
+ else if (octaveLast.match(/^,+$/) && !lower)
+ octave = 2 - octaveLast.length;
+ else
+ throw new Error('Invalid formatting - mismatch between octave ' +
+ 'indicator and letter case')
+ } else
+ octave = lower ? 3 : 2;
+
+ var accidentalValue = accval.interval(parts[3].toLowerCase());
+ var coord = coords(note.toLowerCase());
+
+ coord[0] += octave;
+ coord[0] += accidentalValue[0] - coords.A4[0];
+ coord[1] += accidentalValue[1] - coords.A4[1];
+
+ return coord;
+};
+
+},{"accidental-value":9,"notecoord":13}],12:[function(require,module,exports){
+var pattern = /^(AA|A|P|M|m|d|dd)(-?\d+)$/;
+
+// The interval it takes to raise a note a semitone
+var sharp = [-4, 7];
+
+var pAlts = ['dd', 'd', 'P', 'A', 'AA'];
+var mAlts = ['dd', 'd', 'm', 'M', 'A', 'AA'];
+
+var baseIntervals = [
+ [0, 0],
+ [3, -5],
+ [2, -3],
+ [1, -1],
+ [0, 1],
+ [3, -4],
+ [2, -2],
+ [1, 0]
+];
+
+module.exports = function(simple) {
+ var parser = simple.match(pattern);
+ if (!parser) return null;
+
+ var quality = parser[1];
+ var number = +parser[2];
+ var sign = number < 0 ? -1 : 1;
+
+ number = sign < 0 ? -number : number;
+
+ var lower = number > 8 ? (number % 7 || 7) : number;
+ var octaves = (number - lower) / 7;
+
+ var base = baseIntervals[lower - 1];
+ var alts = base[0] <= 1 ? pAlts : mAlts;
+ var alt = alts.indexOf(quality) - 2;
+
+ // this happens, if the alteration wasn't suitable for this type
+ // of interval, such as P2 or M5 (no "perfect second" or "major fifth")
+ if (alt === -3) return null;
+
+ return [
+ sign * (base[0] + octaves + sharp[0] * alt),
+ sign * (base[1] + sharp[1] * alt)
+ ];
+}
+
+// Copy to avoid overwriting internal base intervals
+module.exports.coords = baseIntervals.slice(0);
+
+},{}],13:[function(require,module,exports){
+// First coord is octaves, second is fifths. Distances are relative to c
+var notes = {
+ c: [0, 0],
+ d: [-1, 2],
+ e: [-2, 4],
+ f: [1, -1],
+ g: [0, 1],
+ a: [-1, 3],
+ b: [-2, 5],
+ h: [-2, 5]
+};
+
+module.exports = function(name) {
+ return name in notes ? [notes[name][0], notes[name][1]] : null;
+};
+
+module.exports.notes = notes;
+module.exports.A4 = [3, 3]; // Relative to C0 (scientic notation, ~16.35Hz)
+module.exports.sharp = [-4, 7];
+
+},{}],14:[function(require,module,exports){
+module.exports = function(coord, stdPitch) {
+ if (typeof coord === 'number') {
+ stdPitch = coord;
+ return function(coord) {
+ return stdPitch * Math.pow(2, (coord[0] * 12 + coord[1] * 7) / 12);
+ }
+ }
+
+ stdPitch = stdPitch || 440;
+ return stdPitch * Math.pow(2, (coord[0] * 12 + coord[1] * 7) / 12);
+}
+
+},{}],15:[function(require,module,exports){
+var coords = require('notecoord');
+var accval = require('accidental-value');
+
+module.exports = function scientific(name) {
+ var format = /^([a-h])(x|#|bb|b?)(-?\d*)/i;
+
+ var parser = name.match(format);
+ if (!(parser && name === parser[0] && parser[3].length)) return;
+
+ var noteName = parser[1];
+ var octave = +parser[3];
+ var accidental = parser[2].length ? parser[2].toLowerCase() : '';
+
+ var accidentalValue = accval.interval(accidental);
+ var coord = coords(noteName.toLowerCase());
+
+ coord[0] += octave;
+ coord[0] += accidentalValue[0] - coords.A4[0];
+ coord[1] += accidentalValue[1] - coords.A4[1];
+
+ return coord;
+};
+
+},{"accidental-value":9,"notecoord":13}]},{},[1])(1)
+});
\ No newline at end of file