refactor tonal functions to 'register'
This commit is contained in:
parent
4f7c013c78
commit
bc43fc9585
6 changed files with 84 additions and 63 deletions
|
|
@ -4,31 +4,47 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
|
|||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Pattern as _Pattern, stack, Hap, reify } from '@strudel.cycles/core';
|
||||
import { stack, register } from '@strudel.cycles/core';
|
||||
import _voicings from 'chord-voicings';
|
||||
const { dictionaryVoicing, minTopNoteDiff, lefthand } = _voicings.default || _voicings; // parcel module resolution fuckup
|
||||
|
||||
const getVoicing = (chord, lastVoicing, range = ['F3', 'A4']) =>
|
||||
dictionaryVoicing({
|
||||
export const voicingDictionaries = {
|
||||
lefthand: { dictionary: lefthand, range: ['F3', 'A4'] },
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new custom voicing dictionary.
|
||||
*
|
||||
* @name addVoicings
|
||||
* @memberof Pattern
|
||||
* @param {string} name identifier for the voicing dictionary
|
||||
* @param {Object} dictionary maps chord symbol to possible voicings
|
||||
* @param {Array} range min, max note
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* addVoicings('cookie', {
|
||||
* 7: ['3M 7m 9M 12P 15P', '7m 10M 13M 16M 19P'],
|
||||
* '^7': ['3M 6M 9M 12P 14M', '7M 10M 13M 16M 19P'],
|
||||
* m7: ['8P 11P 14m 17m 19P', '5P 8P 11P 14m 17m'],
|
||||
* m7b5: ['3m 5d 8P 11P 14m', '5d 8P 11P 14m 17m'],
|
||||
* o7: ['3m 6M 9M 11A 15P'],
|
||||
* '7alt': ['3M 7m 10m 13m 15P'],
|
||||
* '7#11': ['7m 10m 13m 15P 17m'],
|
||||
* }, ['C3', 'C6'])
|
||||
*/
|
||||
export const addVoicings = (name, dictionary, range = ['F3', 'A4']) => {
|
||||
Object.assign(voicingDictionaries, { [name]: { dictionary, range } });
|
||||
};
|
||||
|
||||
const getVoicing = (chord, dictionaryName, lastVoicing) => {
|
||||
const { dictionary, range } = voicingDictionaries[dictionaryName];
|
||||
return dictionaryVoicing({
|
||||
chord,
|
||||
dictionary: lefthand,
|
||||
dictionary,
|
||||
range,
|
||||
picker: minTopNoteDiff,
|
||||
lastVoicing,
|
||||
});
|
||||
|
||||
const Pattern = _Pattern;
|
||||
|
||||
Pattern.prototype.fmapNested = function (func) {
|
||||
return new Pattern((span) =>
|
||||
this.query(span)
|
||||
.map((event) =>
|
||||
reify(func(event))
|
||||
.query(span)
|
||||
.map((hap) => new Hap(event.whole, event.part, hap.value, hap.context)),
|
||||
)
|
||||
.flat(),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -37,32 +53,38 @@ Pattern.prototype.fmapNested = function (func) {
|
|||
*
|
||||
* @name voicings
|
||||
* @memberof Pattern
|
||||
* @param {range} range note range for possible voicings (optional, defaults to `['F3', 'A4']`)
|
||||
* @param {string} dictionary which voicing dictionary to use.
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* stack("<C^7 A7 Dm7 G7>".voicings(), "<C3 A2 D3 G2>").note()
|
||||
* stack("<C^7 A7 Dm7 G7>".voicings('lefthand'), "<C3 A2 D3 G2>").note()
|
||||
*/
|
||||
|
||||
Pattern.prototype.voicings = function (range) {
|
||||
export const voicings = register('voicings', function (dictionary, pat) {
|
||||
let lastVoicing;
|
||||
if (!range?.length) {
|
||||
// allows to pass empty array, if too lazy to specify range
|
||||
range = ['F3', 'A4'];
|
||||
}
|
||||
return this.fmapNested((event) => {
|
||||
lastVoicing = getVoicing(event.value, lastVoicing, range);
|
||||
return stack(...lastVoicing).withContext(() => ({
|
||||
return pat
|
||||
.fmap((value) => {
|
||||
lastVoicing = getVoicing(value, dictionary, lastVoicing);
|
||||
return stack(...lastVoicing);
|
||||
})
|
||||
.outerJoin();
|
||||
/* .withContext(() => ({
|
||||
locations: event.context.locations || [],
|
||||
}));
|
||||
});
|
||||
};
|
||||
})); */
|
||||
});
|
||||
|
||||
Pattern.prototype._rootNotes = function (octave = 2) {
|
||||
return this.fmap((value) => {
|
||||
/**
|
||||
* Maps the chords of the incoming pattern to root notes in the given octave.
|
||||
*
|
||||
* @name rootNotes
|
||||
* @memberof Pattern
|
||||
* @param {octave} octave octave to use
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* "<C^7 A7 Dm7 G7>".rootNotes(2).note()
|
||||
*/
|
||||
export const rootNotes = register('rootNotes', function (octave, pat) {
|
||||
return pat.fmap((value) => {
|
||||
const root = value.match(/^([a-gA-G][b#]?).*$/)[1];
|
||||
return root + octave;
|
||||
});
|
||||
};
|
||||
|
||||
Pattern.prototype.define('voicings', (range, pat) => pat.voicings(range), { composable: true });
|
||||
Pattern.prototype.define('rootNotes', (oct, pat) => pat.rootNotes(oct), { composable: true, patternified: true });
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue