David Schmudde / Dec 03 2019
Remix of Clojure by Nextjournal
Teoria
Running http://saebekassebil.github.io/teoria/.
mkdir teoria && cd $_
curl -O http://saebekassebil.github.io/teoria/waveplot.js
curl -O http://saebekassebil.github.io/teoria/style.css
curl -O https://raw.githubusercontent.com/saebekassebil/teoria/0f4bbe8fb0d6a43fd9c96a309ce781c3841114d2/teoria.js
ls
4.3s
teoria (Bash in Clojure)
(def waveplot-js (str "<script>" (slurp "/teoria/waveplot.js") "</script>"))
(def style-css (str "<style type='text/css'>" (slurp "/teoria/style.css") "</style>"))
0.1s
teoria (Clojure)
'user/style-css
(def teoria-js
"<script>(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<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
var Note = require('./lib/note');
var Interval = require('./lib/interval');
var Chord = require('./lib/chord');
var Scale = require('./lib/scale');
var teoria;
// never thought I would write this, but: Legacy support
function intervalConstructor(from, to) {
// Construct a Interval object from string representation
if (typeof from === 'string')
return Interval.toCoord(from);
if (typeof to === 'string' && from instanceof Note)
return Interval.from(from, Interval.toCoord(to));
if (to instanceof Interval && from instanceof Note)
return Interval.from(from, to);
if (to instanceof Note && from instanceof Note)
return Interval.between(from, to);
throw new Error('Invalid parameters');
}
intervalConstructor.toCoord = Interval.toCoord;
intervalConstructor.from = Interval.from;
intervalConstructor.between = Interval.between;
intervalConstructor.invert = Interval.invert;
function noteConstructor(name, duration) {
if (typeof name === 'string')
return Note.fromString(name, duration);
else
return new Note(name, duration);
}
noteConstructor.fromString = Note.fromString;
noteConstructor.fromKey = Note.fromKey;
noteConstructor.fromFrequency = Note.fromFrequency;
noteConstructor.fromMIDI = Note.fromMIDI;
function chordConstructor(name, symbol) {
if (typeof name === 'string') {
var root, octave;
root = name.match(/^([a-h])(x|#|bb|b?)/i);
if (root && root[0]) {
octave = typeof symbol === 'number' ? symbol.toString(10) : '4';
return new Chord(Note.fromString(root[0].toLowerCase() + octave),
name.substr(root[0].length));
}
} else if (name instanceof Note)
return new Chord(name, symbol);
throw new Error('Invalid Chord. Couldn\\x27t find note name');
}
function scaleConstructor(tonic, scale) {
tonic = (tonic instanceof Note) ? tonic : teoria.note(tonic);
return new Scale(tonic, scale);
}
teoria = {
note: noteConstructor,
chord: chordConstructor,
interval: intervalConstructor,
scale: scaleConstructor,
Note: Note,
Chord: Chord,
Scale: Scale,
Interval: Interval
};
require('./lib/sugar')(teoria);
exports = module.exports = teoria;
},{\"./lib/chord\":2,\"./lib/interval\":3,\"./lib/note\":5,\"./lib/scale\":6,\"./lib/sugar\":7}],2:[function(require,module,exports){
var daccord = require('daccord');
var knowledge = require('./knowledge');
var Note = require('./note');
var Interval = require('./interval');
function Chord(root, name) {
if (!(this instanceof Chord)) return new Chord(root, name);
name = name || '';
this.name = root.name().toUpperCase() + root.accidental() + name;
this.symbol = name;
this.root = root;
this.intervals = [];
this._voicing = [];
var bass = name.split('/');
if (bass.length === 2 && bass[1].trim() !== '9') {
name = bass[0];
bass = bass[1].trim();
} else {
bass = null;
}
this.intervals = daccord(name).map(Interval.toCoord);
this._voicing = this.intervals.slice();
if (bass) {
var intervals = this.intervals, bassInterval, note;
// Make sure the bass is atop of the root note
note = Note.fromString(bass + (root.octave() + 1)); // crude
bassInterval = Interval.between(root, note);
bass = bassInterval.simple();
bassInterval = bassInterval.invert().direction('down');
this._voicing = [bassInterval];
for (var i = 0, length = intervals.length; i < length; i++) {
if (!intervals[i].simple().equal(bass))
this._voicing.push(intervals[i]);
}
}
}
Chord.prototype = {
notes: function() {
var root = this.root;
return this.voicing().map(function(interval) {
return root.interval(interval);
});
},
simple: function() {
return this.notes().map(function(n) { return n.toString(true); });
},
bass: function() {
return this.root.interval(this._voicing[0]);
},
voicing: function(voicing) {
// Get the voicing
if (!voicing) {
return this._voicing;
}
// Set the voicing
this._voicing = [];
for (var i = 0, length = voicing.length; i < length; i++) {
this._voicing[i] = Interval.toCoord(voicing[i]);
}
return this;
},
resetVoicing: function() {
this._voicing = this.intervals;
},
dominant: function(additional) {
additional = additional || '';
return new Chord(this.root.interval('P5'), additional);
},
subdominant: function(additional) {
additional = additional || '';
return new Chord(this.root.interval('P4'), additional);
},
parallel: function(additional) {
additional = additional || '';
var quality = this.quality();
if (this.chordType() !== 'triad' || quality === 'diminished' ||
quality === 'augmented') {
throw new Error('Only major/minor triads have parallel chords');
}
if (quality === 'major') {
return new Chord(this.root.interval('m3', 'down'), 'm');
} else {
return new Chord(this.root.interval('m3', 'up'));
}
},
quality: function() {
var third, fifth, seventh, intervals = this.intervals;
for (var i = 0, length = intervals.length; i < length; i++) {
if (intervals[i].number() === 3) {
third = intervals[i];
} else if (intervals[i].number() === 5) {
fifth = intervals[i];
} else if (intervals[i].number() === 7) {
seventh = intervals[i];
}
}
if (!third) {
return;
}
third = (third.direction() === 'down') ? third.invert() : third;
third = third.simple().toString();
if (fifth) {
fifth = (fifth.direction === 'down') ? fifth.invert() : fifth;
fifth = fifth.simple().toString();
}
if (seventh) {
seventh = (seventh.direction === 'down') ? seventh.invert() : seventh;
seventh = seventh.simple().toString();
}
if (third === 'M3') {
if (fifth === 'A5') {
return 'augmented';
} else if (fifth === 'P5') {
return (seventh === 'm7') ? 'dominant' : 'major';
}
return 'major';
} else if (third === 'm3') {
if (fifth === 'P5') {
return 'minor';
} else if (fifth === 'd5') {
return (seventh === 'm7') ? 'half-diminished' : 'diminished';
}
return 'minor';
}
},
chordType: function() { // In need of better name
var length = this.intervals.length, interval, has, invert, i, name;
if (length === 2) {
return 'dyad';
} else if (length === 3) {
has = {unison: false, third: false, fifth: false};
for (i = 0; i < length; i++) {
interval = this.intervals[i];
invert = interval.invert();
if (interval.base() in has) {
has[interval.base()] = true;
} else if (invert.base() in has) {
has[invert.base()] = true;
}
}
name = (has.unison && has.third && has.fifth) ? 'triad' : 'trichord';
} else if (length === 4) {
has = {unison: false, third: false, fifth: false, seventh: false};
for (i = 0; i < length; i++) {
interval = this.intervals[i];
invert = interval.invert();
if (interval.base() in has) {
has[interval.base()] = true;
} else if (invert.base() in has) {
has[invert.base()] = true;
}
}
if (has.unison && has.third && has.fifth && has.seventh) {
name = 'tetrad';
}
}
return name || 'unknown';
},
get: function(interval) {
if (typeof interval === 'string' && interval in knowledge.stepNumber) {
var intervals = this.intervals, i, length;
interval = knowledge.stepNumber[interval];
for (i = 0, length = intervals.length; i < length; i++) {
if (intervals[i].number() === interval) {
return this.root.interval(intervals[i]);
}
}
return null;
} else {
throw new Error('Invalid interval name');
}
},
interval: function(interval) {
return new Chord(this.root.interval(interval), this.symbol);
},
transpose: function(interval) {
this.root.transpose(interval);
this.name = this.root.name().toUpperCase() +
this.root.accidental() + this.symbol;
return this;
},
toString: function() {
return this.name;
}
};
module.exports = Chord;
},{\"./interval\":3,\"./knowledge\":4,\"./note\":5,\"daccord\":10}],3:[function(require,module,exports){
var knowledge = require('./knowledge');
var vector = require('./vector');
var toCoord = require('interval-coords');
function Interval(coord) {
if (!(this instanceof Interval)) return new Interval(coord);
this.coord = coord;
}
Interval.prototype = {
name: function() {
return knowledge.intervalsIndex[this.number() - 1];
},
semitones: function() {
return vector.sum(vector.mul(this.coord, [12, 7]));
},
number: function() {
return Math.abs(this.value());
},
value: function() {
var toMultiply = Math.floor((this.coord[1] - 2) / 7) + 1;
var product = vector.mul(knowledge.sharp, toMultiply);
var without = vector.sub(this.coord, product);
var i = knowledge.intervalFromFifth[without[1] + 5];
var diff = without[0] - knowledge.intervals[i][0];
var val = knowledge.stepNumber[i] + diff * 7;
return (val > 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 ? ',' : '\\x27';
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) ? '\\x27' : ',';
}
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: \\x27' + alterations[0] + '\\x27');
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: \\x27' + alt + '\\x27');
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?)([,\\x27]*)$/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)
}); </script>")
0.1s
'user/teoria-js
{:nextjournal/viewer "html" :nextjournal.viewer/value
(str
"
<!DOCTYPE html>
<html>
<head>
<title>Teoria.js - Music Theory in JavaScript</title>
<!-- <link href='style.css' rel='stylesheet' type='text/css' /> -->
" style-css "
</head>
<body>
<div id='container'>
<div id='plotframe'>
<svg width='500px' height='300px' overflow='hidden' id='plot'></svg>
</div>
<ul id='panel'></ul>
<input id='chordinput' type='text' value='Amaj7' />
<p id='description'>Write a chord in the input (try <i>C13<sup>b9</sup></i>
or maybe <i>Fsus4<sup>maj#11</sup></i>) above and hit enter.</p>
</div>"
teoria-js
waveplot-js
"
</body></html>
")}
0.7s
teoria (Clojure)
{:deps
{org.clojure/clojure {:mvn/version "1.10.0"}
org.clojure/tools.deps.alpha
{:git/url "https://github.com/clojure/tools.deps.alpha.git"
:sha "f6c080bd0049211021ea59e516d1785b08302515"}
compliment {:mvn/version "0.3.9"}}}
deps.edn
Extensible Data Notation