UNPKG

teoria

Version:
226 lines (185 loc) 5.94 kB
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;