David Schmudde / Dec 03 2019
Remix of Clojure by  Nextjournal
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