Artifact
9bd285dd5ccae3a8d136e1aa1e17a1ac263a25490e2596583ee928146d9680cc:
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;