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
teoria-jsteoria (Clojure)
'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
Runtimes (1)