UNPKG

cxchord

Version:
316 lines (315 loc) 13.1 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.ChordMatcher = exports.ChordMatch = void 0; var CxBayes_1 = require("./CxBayes"); var CxChordInst_1 = require("./CxChordInst"); var CxForms_1 = require("./CxForms"); var CxRules_1 = require("./CxRules"); var _ = require("lodash"); var ChordMatch = /** @class */ (function () { function ChordMatch(hypo, chordEntry, _mapEntry, sharpOrFlat) { if (sharpOrFlat === void 0) { sharpOrFlat = 'flat'; } this.hypo = hypo; this.notes = []; this.inv = hypo.inv; this.type = hypo.key; for (var i = 0; i < chordEntry.chordInv[0].length; i++) { var note = chordEntry.chordInv[0][i] + chordEntry.offset[0]; this.notes.push(note); } this.bass = chordEntry.getBassName(hypo, sharpOrFlat); this.root = chordEntry.getRootName(hypo, sharpOrFlat); this.list = this.root + "," + this.hypo.key; if (this.bass !== this.root) { this.list += ",/" + this.bass; } this.chord = (0, CxForms_1.getExtName)(this.list); } return ChordMatch; }()); exports.ChordMatch = ChordMatch; var ChordMatcher = /** @class */ (function (_super) { __extends(ChordMatcher, _super); function ChordMatcher(debugKey) { if (debugKey === void 0) { debugKey = "Maj"; } var _this = _super.call(this) || this; _this.debugKey = debugKey; _this.chord = null; _this.bayes = null; _this.rules = null; _this.favorJazzChords = false; _this.priorChords = []; return _this; } ChordMatcher.prototype.getMatches = function (sharpOrFlat) { if (sharpOrFlat === void 0) { sharpOrFlat = 'flat'; } var post = this.bayes.getPosterior(); var res = []; for (var i = 0; i < post.length; i++) if (i == 0 || post[i].post == post[i - 1].post) { var chordEntry = this.chord; var chordMapEntry = this.chordMapWithInv[post[i].hypo.key][post[i].hypo.inv]; var entry = new ChordMatch(post[i].hypo, chordEntry, chordMapEntry, sharpOrFlat); res.push(entry); } else { break; } return res; }; ChordMatcher.prototype.getMatch = function (idx, sharpOrFlat) { if (idx === void 0) { idx = 0; } if (sharpOrFlat === void 0) { sharpOrFlat = 'flat'; } var res = this.getMatches(sharpOrFlat); if (idx < 0 || idx >= res.length) throw Error("getMatch index: " + idx + " is out of range"); else return res[idx]; }; ChordMatcher.prototype.getPosterior = function () { return this.bayes.getPosterior(); }; ChordMatcher.prototype.getChord = function () { return this.chord; }; ChordMatcher.prototype.favorJazz = function (favor) { if (favor === void 0) { favor = true; } this.favorJazzChords = favor; if (this.chord !== null) { this.chord.favorJazzChords = favor; } }; ChordMatcher.prototype.addRootOffset = function (_arr, root, _addOctave) { if (_arr === void 0) { _arr = []; } if (_addOctave === void 0) { _addOctave = true; } var arr = []; if (root == 0) { arr = _arr; } else { for (var i = 0; i < _arr.length; i++) { var note = _arr[i] + root % 12; note = note < 0 ? note + 12 : note; arr.push(note); } } return _.sortedUniq(arr); }; ChordMatcher.prototype.addToArray = function (_arr, value) { var arr = []; for (var i = 0; i < _arr.length; i++) { arr.push(_arr[i] + value); } return arr; }; ChordMatcher.prototype.doMatch = function (chord, _limit) { if (_limit === void 0) { _limit = chord.chordInv[0].length; } // deno-lint-ignore no-this-alias var self = this; var idx = 0; _.forIn(this.chordMapWithInv, function (hypothesis, key) { var _a; idx++; for (var inv = 0; inv < hypothesis.length; inv++) { idx += inv; if (!_.has(chord.matchedNotes, key)) { chord.matchedNotes[key] = { invertions: [], extensions: [], knockouts: [], mustHave: [], rootNotes: [], conflicts: [], roots: [], group: hypothesis[inv].group }; } // Save the root note for the inversion chord.matchedNotes[key].rootNotes.push(hypothesis[inv].root); // // Check for matching notes // var intersection = _.intersection(chord.chordInv[0], hypothesis[inv].notes); if (!_.isArray(intersection)) throw Error('inversion Intersection is not an array'); chord.matchedNotes[key].invertions.push(intersection); // if ( chord.chordInv[0].length == intersection.length ) this.fullMatches = true // // The following checks should also check against the the root of no-root chords // var _hypoToMatch = []; var chordToMatch = []; var invRoot = (hypothesis[inv].root < 0 ? 12 + hypothesis[inv].root : hypothesis[inv].root) % 12; if ((0, CxForms_1.isNoRootChord)(key) && hypothesis[inv].root < 0) { var tmpArr = []; tmpArr.push(invRoot); _hypoToMatch = tmpArr.concat(hypothesis[inv].notes).sort(); chordToMatch = chord.addRootOffset(chord.chordInv[0], hypothesis[inv].root); } else { _hypoToMatch = hypothesis[inv].notes; chordToMatch = chord.chordInv[0]; } // // Check for the root notes // For chord without the root it should not be present // For chord with roots it should score higher if present // var indexOfRoot = chordToMatch.indexOf(invRoot) >= 0 ? chordToMatch.indexOf(invRoot) : chordToMatch.indexOf(invRoot + 12); chord.matchedNotes[key].roots.push(indexOfRoot); // // Check for must Have notes // var mustHaveTrans = void 0; var mustHaveMatch = void 0; if (!_.isUndefined(CxForms_1.mustHave[key])) { mustHaveTrans = self.addRootOffset(CxForms_1.mustHave[key], hypothesis[inv].root, false); mustHaveMatch = _.intersection(chordToMatch, mustHaveTrans); chord.matchedNotes[key].mustHave.push(mustHaveMatch.length - mustHaveTrans.length); } else chord.matchedNotes[key].mustHave.push(0); // // Now check the extensions // var extensionNotes = CxForms_1.extensions[key]; var remainingNotes = _.difference(chord.chordInv[0], intersection); var extensionMatch = _.intersection(remainingNotes, extensionNotes); chord.matchedNotes[key].extensions.push(extensionMatch); // // Now check the knockouts // var knockoutTrans = void 0; var knockoutMatch = void 0; var KOs = (0, CxForms_1.getKnockouts)(key); if (KOs.length > 0) { knockoutTrans = self.addRootOffset(CxForms_1.knockouts[key], hypothesis[inv].root); knockoutMatch = _.intersection(chord.chordInv[0], knockoutTrans); } chord.matchedNotes[key].knockouts.push((_a = knockoutMatch) !== null && _a !== void 0 ? _a : []); // // Check for Conflicting notes // var conflictCount = 0; var conflictTrans = void 0; if (!_.isUndefined(CxForms_1.conflicts[key])) { for (var i = 0; i < CxForms_1.conflicts[key].length; i++) { conflictTrans = self.addRootOffset(CxForms_1.conflicts[key][i], hypothesis[inv].root); var conflictMatch = _.intersection(chordToMatch, conflictTrans); if (conflictMatch.length == conflictTrans.length) { conflictCount += 1; } // Check in 2 octaves conflictTrans = self.addToArray(conflictTrans, 12); conflictMatch = _.intersection(chord.chordInv[0], conflictTrans); if (conflictMatch.length == conflictTrans.length) { conflictCount += 1; } } } chord.matchedNotes[key].conflicts.push(conflictCount); } }); return chord; }; // // Tone number and name support // ChordMatcher.prototype.match = function (midiChord) { if (_.isUndefined(midiChord) || _.isEmpty(midiChord)) throw "match: supplied Chord is empty"; if (_.isNumber(midiChord[0])) return this.matchNotes(midiChord); // else return this.matchNoteNames(midiChord); }; ChordMatcher.prototype.matchNoteNames = function (midiNames) { var midiChord = []; for (var i = 0; i < midiNames.length; i++) try { var noteNo = (0, CxForms_1.getNoteNumber)(midiNames[i]); midiChord.push(noteNo); } catch (e) { throw e; } return this.matchNotes(midiChord); }; ChordMatcher.prototype.matchNotes = function (midiChord) { this.bayes = new CxBayes_1.BayesChordCalculator(this.chordMapWithInv); this.chord = new CxChordInst_1.ChordInstance(midiChord); this.chord.favorJazzChords = this.favorJazzChords; // // TODO: Application of rules should be configurable or even dynamic for best matching capabilities // // Do chord tone matches // this.doMatch(this.chord); this.rules = new CxRules_1.Rules(this.chord, this.debugKey); // // Apply the Bayes Rules // // Even distribution // var ruleE = this.rules.get('EvenDistribution'); this.bayes.applyRule(ruleE); // // Count Notes Rule // // var ruleN: Rule = this.rules.get('CountNotes') // this.bayes.applyRule(ruleN) // // Knockout Rule // var ruleK = this.rules.get('Knockouts'); this.bayes.applyRule(ruleK); // // Matched Notes rule // var ruleM = this.rules.get('MatchedNotes'); this.bayes.applyRule(ruleM); // // MustHave Notes rule // var ruleH = this.rules.get('MustHave'); this.bayes.applyRule(ruleH); // // Favor Jazz Chords // // if ( this.favorJazzChords ) { // var ruleJ: Rule = this.rules.get('FavorJazz') // this.bayes.applyRule(ruleJ) // } // // Conflict Notes rule // // var ruleX: Rule = this.rules.get('Conflicts') // this.bayes.applyRule(ruleX) // // Root is present Rule // var ruleR = this.rules.get('RootFound'); this.bayes.applyRule(ruleR); // // Group Rule // // var ruleG: Rule = this.rules.get('ChordGroup') // this.bayes.applyRule(ruleG) // this.priorChords.push(this.chord) // return this.chord; }; return ChordMatcher; }(CxForms_1.ChordForms)); exports.ChordMatcher = ChordMatcher;