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.jscurl -O http://saebekassebil.github.io/teoria/style.csscurl -O https://raw.githubusercontent.com/saebekassebil/teoria/0f4bbe8fb0d6a43fd9c96a309ce781c3841114d2/teoria.jsls4.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 supportfunction 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 Cmodule.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']};// synonymsscales.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 semitonevar 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 intervalsmodule.exports.coords = baseIntervals.slice(0);},{}],13:[function(require,module,exports){// First coord is octaves, second is fifths. Distances are relative to cvar 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