change call signature of renderVoicing

+ pull logic in from voicings function
This commit is contained in:
Felix Roos 2023-07-12 22:55:34 +02:00
parent b0cbeda796
commit 81b142704c
3 changed files with 23 additions and 20 deletions

View file

@ -116,16 +116,20 @@ describe('tonleiter', () => {
expect(scaleStep([60, 63, 67], -4)).toBe(43); expect(scaleStep([60, 63, 67], -4)).toBe(43);
}); });
test('renderVoicing', () => { test('renderVoicing', () => {
const voicingDictionary = { const dictionary = {
m7: [ m7: [
'3 7 10 14', // b3 5 b7 9 '3 7 10 14', // b3 5 b7 9
'10 14 15 19', // b7 9 b3 5 '10 14 15 19', // b7 9 b3 5
], ],
}; };
expect(renderVoicing('Em7', 'Bb4', voicingDictionary)).toEqual(['G3', 'B3', 'D4', 'Gb4']); expect(renderVoicing({ chord: 'Em7', anchor: 'Bb4', dictionary })).toEqual(['G3', 'B3', 'D4', 'Gb4']);
expect(renderVoicing('Cm7', 'D5', voicingDictionary)).toEqual(['Eb4', 'G4', 'Bb4', 'D5']); expect(renderVoicing({ chord: 'Cm7', anchor: 'D5', dictionary })).toEqual(['Eb4', 'G4', 'Bb4', 'D5']);
expect(renderVoicing('Cm7', 'G5', voicingDictionary)).toEqual(['Bb4', 'D5', 'Eb5', 'G5']); expect(renderVoicing({ chord: 'Cm7', anchor: 'G5', dictionary })).toEqual(['Bb4', 'D5', 'Eb5', 'G5']);
expect(renderVoicing('Cm7', 'g5', voicingDictionary)).toEqual(['Bb4', 'D5', 'Eb5', 'G5']); expect(renderVoicing({ chord: 'Cm7', anchor: 'g5', dictionary })).toEqual(['Bb4', 'D5', 'Eb5', 'G5']);
expect(renderVoicing({ chord: 'Cm7', anchor: 'g5', dictionary, n: 0 })).toEqual([70]); // Bb4
expect(renderVoicing({ chord: 'Cm7', anchor: 'g5', dictionary, n: 1 })).toEqual([74]); // D5
expect(renderVoicing({ chord: 'Cm7', anchor: 'g5', dictionary, n: 4 })).toEqual([82]); // Bb5
expect(renderVoicing({ chord: 'Cm7', anchor: 'g5', dictionary, offset: 1 })).toEqual(['Eb5', 'G5', 'Bb5', 'D6']);
// expect(voiceBelow('G4', 'Cm7', voicingDictionary)).toEqual(['Bb3', 'D4', 'Eb4', 'G4']); // expect(voiceBelow('G4', 'Cm7', voicingDictionary)).toEqual(['Bb3', 'D4', 'Eb4', 'G4']);
// TODO: test with offset // TODO: test with offset
}); });

View file

@ -42,7 +42,7 @@ export const note2chroma = (note) => {
export const midi2chroma = (midi) => midi % 12; export const midi2chroma = (midi) => midi % 12;
// TODO: test and use in voicing function // TODO: test and use in voicing function
export const x2chroma = (x) => { export const pitch2chroma = (x) => {
if (isNoteWithOctave(x)) { if (isNoteWithOctave(x)) {
return note2chroma(x); return note2chroma(x);
} }
@ -87,19 +87,19 @@ export function scaleStep(notes, offset) {
return notes[offset % notes.length] + octOffset; return notes[offset % notes.length] + octOffset;
} }
export function renderVoicing(chord, anchor, voicingDictionary, offset = 0, n) { export function renderVoicing({ chord, anchor, dictionary, offset = 0, n, mode }) {
anchor = anchor?.note || anchor || 'c5';
const [root, symbol] = tokenizeChord(chord); const [root, symbol] = tokenizeChord(chord);
const maxPc = note2pc(anchor); const anchorChroma = pitch2chroma(anchor);
const maxChroma = pc2chroma(maxPc);
const rootChroma = pc2chroma(root); const rootChroma = pc2chroma(root);
const voicings = voicingDictionary[symbol].map((voicing) => const voicings = dictionary[symbol].map((voicing) =>
(typeof voicing === 'string' ? voicing.split(' ') : voicing).map(step2semitones), (typeof voicing === 'string' ? voicing.split(' ') : voicing).map(step2semitones),
); );
let minDistance, bestIndex; let minDistance, bestIndex;
// calculate distances up from voicing top notes // calculate distances up from voicing top notes
let chromaDiffs = voicings.map((v, i) => { let chromaDiffs = voicings.map((v, i) => {
const diff = _mod(maxChroma - v.slice(-1)[0] - rootChroma, 12); const diff = _mod(anchorChroma - v.slice(-1)[0] - rootChroma, 12);
if (minDistance === undefined || diff < minDistance) { if (minDistance === undefined || diff < minDistance) {
minDistance = diff; minDistance = diff;
bestIndex = i; bestIndex = i;
@ -114,10 +114,13 @@ export function renderVoicing(chord, anchor, voicingDictionary, offset = 0, n) {
const topMidi = maxMidi - chromaDiffs[indexWithOffset] + octDiff; const topMidi = maxMidi - chromaDiffs[indexWithOffset] + octDiff;
const voicingMidi = voicing.map((v) => topMidi - voicing[voicing.length - 1] + v); const voicingMidi = voicing.map((v) => topMidi - voicing[voicing.length - 1] + v);
const notes = voicingMidi.map((n) => midi2note(n)); let notes = voicingMidi.map((n) => midi2note(n));
if (n !== undefined) { if (n !== undefined) {
return [scaleStep(notes, n)]; return [scaleStep(notes, n)];
} }
if (mode === 'below') {
notes = notes.filter((n) => x2midi(n) !== note2midi(anchor));
}
return notes; return notes;
} }

View file

@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/ */
import { stack, register } from '@strudel.cycles/core'; import { stack, register } from '@strudel.cycles/core';
import { renderVoicing, note2midi, x2midi } from './tonleiter.mjs'; import { renderVoicing } from './tonleiter.mjs';
import _voicings from 'chord-voicings'; import _voicings from 'chord-voicings';
const { dictionaryVoicing, minTopNoteDiff } = _voicings.default || _voicings; // parcel module resolution fuckup const { dictionaryVoicing, minTopNoteDiff } = _voicings.default || _voicings; // parcel module resolution fuckup
@ -139,16 +139,12 @@ export const rootNotes = register('rootNotes', function (octave, pat) {
export const voicing = register('voicing', function (pat) { export const voicing = register('voicing', function (pat) {
return pat return pat
.fmap((value) => { .fmap((value) => {
// TODO: default dictionary? let { dictionary, ...rest } = value;
let { anchor, mode, offset, chord, n, dictionary, ...rest } = value;
anchor = anchor?.note || anchor || 'c5';
if (typeof dictionary === 'string') { if (typeof dictionary === 'string') {
dictionary = voicingRegistry[dictionary]?.dictionary; dictionary = voicingRegistry[dictionary]?.dictionary;
} }
let notes = renderVoicing(chord, anchor, dictionary, offset, n); let notes = renderVoicing({ ...value, dictionary });
if (mode === 'below') {
notes = notes.filter((n) => x2midi(n) !== note2midi(anchor));
}
return stack(...notes) return stack(...notes)
.note() .note()
.set(rest); .set(rest);