UNPKG

@tonaljs/voicing

Version:

Voicings and Voice Leading for Chords

1 lines 5.31 kB
{"version":3,"sources":["../index.ts"],"sourcesContent":["import Chord from \"@tonaljs/chord\";\nimport Interval from \"@tonaljs/interval\";\nimport Note from \"@tonaljs/note\";\nimport Range from \"@tonaljs/range\";\nimport VoiceLeading from \"@tonaljs/voice-leading\";\nimport VoicingDictionary from \"@tonaljs/voicing-dictionary\";\n\nconst defaultRange = [\"C3\", \"C5\"];\nconst defaultDictionary = VoicingDictionary.all;\nconst defaultVoiceLeading = VoiceLeading.topNoteDiff;\n\nexport function get(\n chord: string,\n range: string[] = defaultRange,\n dictionary = defaultDictionary,\n voiceLeading = defaultVoiceLeading,\n lastVoicing?: string[],\n) {\n const voicings = search(chord, range, dictionary);\n if (!lastVoicing || !lastVoicing.length) {\n // notes = voicings[Math.ceil(voicings.length / 2)]; // pick middle voicing..\n return voicings[0]; // pick lowest voicing..\n } else {\n // calculates the distance between the last note and the given voicings top note\n // sort voicings with differ\n return voiceLeading(voicings, lastVoicing);\n }\n}\n\nexport function search(\n chord: string,\n range = defaultRange,\n dictionary = VoicingDictionary.triads,\n): string[][] {\n const [tonic, symbol] = Chord.tokenize(chord);\n const sets = VoicingDictionary.lookup(symbol, dictionary);\n // find equivalent symbol that is used as a key in dictionary:\n if (!sets) {\n return [];\n }\n // resolve array of interval arrays for the wanted symbol\n const voicings = sets.map((intervals) => intervals.split(\" \"));\n const notesInRange = Range.chromatic(range); // gives array of notes inside range\n return voicings.reduce((voiced: string[][], voicing: string[]) => {\n // transpose intervals relative to first interval (e.g. 3m 5P > 1P 3M)\n const relativeIntervals = voicing.map(\n (interval) => Interval.subtract(interval, voicing[0]) || \"\",\n );\n // get enharmonic correct pitch class the bottom note\n const bottomPitchClass = Note.transpose(tonic, voicing[0]);\n // get all possible start notes for voicing\n const starts = notesInRange\n // only get the start notes:\n .filter((note) => Note.chroma(note) === Note.chroma(bottomPitchClass))\n // filter out start notes that will overshoot the top end of the range\n .filter(\n (note) =>\n (Note.midi(\n Note.transpose(\n note,\n relativeIntervals[relativeIntervals.length - 1],\n ),\n ) || 0) <= (Note.midi(range[1]) || 0),\n )\n // replace Range.chromatic notes with the correct enharmonic equivalents\n .map((note) => Note.enharmonic(note, bottomPitchClass));\n // render one voicing for each start note\n const notes = starts.map((start) =>\n relativeIntervals.map((interval) => Note.transpose(start, interval)),\n );\n return voiced.concat(notes);\n }, []);\n}\n\nexport function sequence(\n chords: string[],\n range = defaultRange,\n dictionary = defaultDictionary,\n voiceLeading = defaultVoiceLeading,\n lastVoicing?: string[],\n) {\n const { voicings } = chords.reduce<{\n voicings: string[][];\n lastVoicing: string[] | undefined;\n }>(\n ({ voicings, lastVoicing }, chord) => {\n const voicing = get(chord, range, dictionary, voiceLeading, lastVoicing);\n lastVoicing = voicing;\n voicings.push(voicing);\n return { voicings, lastVoicing };\n },\n { voicings: [], lastVoicing },\n );\n return voicings;\n}\n\n/** @deprecated */\nexport default {\n get,\n search,\n sequence,\n};\n"],"mappings":";AAAA,OAAO,WAAW;AAClB,OAAO,cAAc;AACrB,OAAO,UAAU;AACjB,OAAO,WAAW;AAClB,OAAO,kBAAkB;AACzB,OAAO,uBAAuB;AAE9B,IAAM,eAAe,CAAC,MAAM,IAAI;AAChC,IAAM,oBAAoB,kBAAkB;AAC5C,IAAM,sBAAsB,aAAa;AAElC,SAAS,IACd,OACA,QAAkB,cAClB,aAAa,mBACb,eAAe,qBACf,aACA;AACA,QAAM,WAAW,OAAO,OAAO,OAAO,UAAU;AAChD,MAAI,CAAC,eAAe,CAAC,YAAY,QAAQ;AAEvC,WAAO,SAAS,CAAC;AAAA,EACnB,OAAO;AAGL,WAAO,aAAa,UAAU,WAAW;AAAA,EAC3C;AACF;AAEO,SAAS,OACd,OACA,QAAQ,cACR,aAAa,kBAAkB,QACnB;AACZ,QAAM,CAAC,OAAO,MAAM,IAAI,MAAM,SAAS,KAAK;AAC5C,QAAM,OAAO,kBAAkB,OAAO,QAAQ,UAAU;AAExD,MAAI,CAAC,MAAM;AACT,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,WAAW,KAAK,IAAI,CAAC,cAAc,UAAU,MAAM,GAAG,CAAC;AAC7D,QAAM,eAAe,MAAM,UAAU,KAAK;AAC1C,SAAO,SAAS,OAAO,CAAC,QAAoB,YAAsB;AAEhE,UAAM,oBAAoB,QAAQ;AAAA,MAChC,CAAC,aAAa,SAAS,SAAS,UAAU,QAAQ,CAAC,CAAC,KAAK;AAAA,IAC3D;AAEA,UAAM,mBAAmB,KAAK,UAAU,OAAO,QAAQ,CAAC,CAAC;AAEzD,UAAM,SAAS,aAEZ,OAAO,CAAC,SAAS,KAAK,OAAO,IAAI,MAAM,KAAK,OAAO,gBAAgB,CAAC,EAEpE;AAAA,MACC,CAAC,UACE,KAAK;AAAA,QACJ,KAAK;AAAA,UACH;AAAA,UACA,kBAAkB,kBAAkB,SAAS,CAAC;AAAA,QAChD;AAAA,MACF,KAAK,OAAO,KAAK,KAAK,MAAM,CAAC,CAAC,KAAK;AAAA,IACvC,EAEC,IAAI,CAAC,SAAS,KAAK,WAAW,MAAM,gBAAgB,CAAC;AAExD,UAAM,QAAQ,OAAO;AAAA,MAAI,CAAC,UACxB,kBAAkB,IAAI,CAAC,aAAa,KAAK,UAAU,OAAO,QAAQ,CAAC;AAAA,IACrE;AACA,WAAO,OAAO,OAAO,KAAK;AAAA,EAC5B,GAAG,CAAC,CAAC;AACP;AAEO,SAAS,SACd,QACA,QAAQ,cACR,aAAa,mBACb,eAAe,qBACf,aACA;AACA,QAAM,EAAE,SAAS,IAAI,OAAO;AAAA,IAI1B,CAAC,EAAE,UAAAA,WAAU,aAAAC,aAAY,GAAG,UAAU;AACpC,YAAM,UAAU,IAAI,OAAO,OAAO,YAAY,cAAcA,YAAW;AACvE,MAAAA,eAAc;AACd,MAAAD,UAAS,KAAK,OAAO;AACrB,aAAO,EAAE,UAAAA,WAAU,aAAAC,aAAY;AAAA,IACjC;AAAA,IACA,EAAE,UAAU,CAAC,GAAG,YAAY;AAAA,EAC9B;AACA,SAAO;AACT;AAGA,IAAO,gBAAQ;AAAA,EACb;AAAA,EACA;AAAA,EACA;AACF;","names":["voicings","lastVoicing"]}