wip: new voicing function
This commit is contained in:
parent
9438c69bed
commit
110e179e9e
4 changed files with 57 additions and 1 deletions
|
|
@ -573,6 +573,12 @@ const generic_params = [
|
||||||
// TODO: dedup with synth param, see https://tidalcycles.org/docs/reference/synthesizers/#superpiano
|
// TODO: dedup with synth param, see https://tidalcycles.org/docs/reference/synthesizers/#superpiano
|
||||||
// ['velocity'],
|
// ['velocity'],
|
||||||
['voice'], // TODO: synth param
|
['voice'], // TODO: synth param
|
||||||
|
|
||||||
|
// voicings
|
||||||
|
['chord'], // https://github.com/tidalcycles/strudel/issues/506
|
||||||
|
['voiceBelow', 'voicebelow'], // https://github.com/tidalcycles/strudel/issues/506
|
||||||
|
['voiceMax', 'voicemax'], // https://github.com/tidalcycles/strudel/issues/506
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the level of reverb.
|
* Sets the level of reverb.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,8 @@ describe('tonleiter', () => {
|
||||||
expect(pc2chroma('D')).toBe(2);
|
expect(pc2chroma('D')).toBe(2);
|
||||||
expect(pc2chroma('Db')).toBe(1);
|
expect(pc2chroma('Db')).toBe(1);
|
||||||
expect(pc2chroma('Dbb')).toBe(0);
|
expect(pc2chroma('Dbb')).toBe(0);
|
||||||
|
//lowercase
|
||||||
|
// expect(pc2chroma('c')).toBe(0); // TODO
|
||||||
});
|
});
|
||||||
test('rotateChroma', () => {
|
test('rotateChroma', () => {
|
||||||
expect(rotateChroma(0, 1)).toBe(1);
|
expect(rotateChroma(0, 1)).toBe(1);
|
||||||
|
|
@ -80,6 +82,7 @@ describe('tonleiter', () => {
|
||||||
expect(note2pc('C5')).toBe('C');
|
expect(note2pc('C5')).toBe('C');
|
||||||
// expect(note2pc('C52')).toBe('C'); // <- 2 digits fail
|
// expect(note2pc('C52')).toBe('C'); // <- 2 digits fail
|
||||||
expect(note2pc('Bb3')).toBe('Bb');
|
expect(note2pc('Bb3')).toBe('Bb');
|
||||||
|
//expect(note2pc('F')).toBe('F'); // <- fails
|
||||||
});
|
});
|
||||||
test('note2oct', () => {
|
test('note2oct', () => {
|
||||||
expect(note2oct('C5')).toBe(5);
|
expect(note2oct('C5')).toBe(5);
|
||||||
|
|
@ -103,10 +106,12 @@ describe('tonleiter', () => {
|
||||||
const voicingDictionary = {
|
const voicingDictionary = {
|
||||||
m7: [
|
m7: [
|
||||||
'3 7 10 14', // b3 5 b7 9
|
'3 7 10 14', // b3 5 b7 9
|
||||||
'10 14 5 19', // b7 9 b3 5
|
'10 14 15 19', // b7 9 b3 5
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
expect(voiceBelow('Bb4', 'Em7', voicingDictionary)).toEqual(['G3', 'B3', 'D4', 'Gb4']);
|
expect(voiceBelow('Bb4', 'Em7', voicingDictionary)).toEqual(['G3', 'B3', 'D4', 'Gb4']);
|
||||||
expect(voiceBelow('D5', 'Cm7', voicingDictionary)).toEqual(['Eb4', 'G4', 'Bb4', 'D5']);
|
expect(voiceBelow('D5', 'Cm7', voicingDictionary)).toEqual(['Eb4', 'G4', 'Bb4', 'D5']);
|
||||||
|
expect(voiceBelow('G5', 'Cm7', voicingDictionary)).toEqual(['Bb4', 'D5', 'Eb5', 'G5']);
|
||||||
|
// expect(voiceBelow('G4', 'Cm7', voicingDictionary)).toEqual(['Bb3', 'D4', 'Eb4', 'G4']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { isNote, isNoteWithOctave } from '@strudel.cycles/core';
|
||||||
|
|
||||||
// https://codesandbox.io/s/stateless-voicings-g2tmz0?file=/src/lib.js:0-2515
|
// https://codesandbox.io/s/stateless-voicings-g2tmz0?file=/src/lib.js:0-2515
|
||||||
|
|
||||||
const flats = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'];
|
const flats = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B'];
|
||||||
|
|
@ -30,6 +32,27 @@ export const note2midi = (note) => {
|
||||||
const [pc, oct] = [note2pc(note), note2oct(note)];
|
const [pc, oct] = [note2pc(note), note2oct(note)];
|
||||||
return pc2chroma(pc) + oct * 12 + 12;
|
return pc2chroma(pc) + oct * 12 + 12;
|
||||||
};
|
};
|
||||||
|
export const note2chroma = (note) => {
|
||||||
|
return pc2chroma(note2pc(note));
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: test
|
||||||
|
export const midi2chroma = (midi) => midi % 12;
|
||||||
|
|
||||||
|
// TODO: test and use in voicing function
|
||||||
|
export const x2chroma = (x) => {
|
||||||
|
if (isNoteWithOctave(x)) {
|
||||||
|
return note2chroma(x);
|
||||||
|
}
|
||||||
|
if (isNote(x)) {
|
||||||
|
//pc
|
||||||
|
return pc2chroma(x);
|
||||||
|
}
|
||||||
|
if (typeof x === 'number') {
|
||||||
|
// expect midi
|
||||||
|
return midi2chroma(x);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// duplicate: util.mjs (does not support sharp flag)
|
// duplicate: util.mjs (does not support sharp flag)
|
||||||
export const midi2note = (midi, sharp = false) => {
|
export const midi2note = (midi, sharp = false) => {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +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 { voiceBelow, note2midi } 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
|
||||||
|
|
||||||
|
|
@ -129,7 +130,28 @@ export const voicings = register('voicings', function (dictionary, pat) {
|
||||||
*/
|
*/
|
||||||
export const rootNotes = register('rootNotes', function (octave, pat) {
|
export const rootNotes = register('rootNotes', function (octave, pat) {
|
||||||
return pat.fmap((value) => {
|
return pat.fmap((value) => {
|
||||||
|
value = value.chord || value;
|
||||||
const root = value.match(/^([a-gA-G][b#]?).*$/)[1];
|
const root = value.match(/^([a-gA-G][b#]?).*$/)[1];
|
||||||
return root + octave;
|
return root + octave;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const voicing = register('voicing', function (dictionary, pat) {
|
||||||
|
return pat
|
||||||
|
.fmap((value) => {
|
||||||
|
let { voiceMax: max, voiceBelow: below, chord, ...rest } = value;
|
||||||
|
let top = max || below;
|
||||||
|
top = top?.note || top || 'c5';
|
||||||
|
if (typeof dictionary === 'string') {
|
||||||
|
dictionary = voicingRegistry[dictionary]?.dictionary;
|
||||||
|
}
|
||||||
|
let notes = voiceBelow(top, chord, dictionary);
|
||||||
|
if (below) {
|
||||||
|
notes = notes.filter((n) => note2midi(n) !== note2midi(top));
|
||||||
|
}
|
||||||
|
return stack(...notes)
|
||||||
|
.note()
|
||||||
|
.set(rest);
|
||||||
|
})
|
||||||
|
.outerJoin();
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue