mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-13 14:26:58 -04:00
refactor tonal functions to 'register'
This commit is contained in:
@@ -1,2 +1,5 @@
|
||||
import './tonal.mjs';
|
||||
import './voicings.mjs';
|
||||
|
||||
export * from './tonal.mjs';
|
||||
export * from './voicings.mjs';
|
||||
|
||||
+10
-14
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||
*/
|
||||
|
||||
import { Note, Interval, Scale } from '@tonaljs/tonal';
|
||||
import { Pattern, _mod } from '@strudel.cycles/core';
|
||||
import { register, _mod } from '@strudel.cycles/core';
|
||||
|
||||
// transpose note inside scale by offset steps
|
||||
// function scaleOffset(scale: string, offset: number, note: string) {
|
||||
@@ -74,8 +74,8 @@ function scaleOffset(scale, offset, note) {
|
||||
* "c2 c3".fast(2).transpose("<1P -2M 4P 3m>".slow(2)).note()
|
||||
*/
|
||||
|
||||
Pattern.prototype._transpose = function (intervalOrSemitones) {
|
||||
return this.withHap((hap) => {
|
||||
export const transpose = register('transpose', function (intervalOrSemitones, pat) {
|
||||
return pat.withHap((hap) => {
|
||||
const interval = !isNaN(Number(intervalOrSemitones))
|
||||
? Interval.fromSemitones(intervalOrSemitones /* as number */)
|
||||
: String(intervalOrSemitones);
|
||||
@@ -87,7 +87,7 @@ Pattern.prototype._transpose = function (intervalOrSemitones) {
|
||||
// tone.js doesn't understand multiple sharps flats e.g. F##3 has to be turned into G3
|
||||
return hap.withValue(() => Note.simplify(Note.transpose(hap.value, interval)));
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
// example: transpose(3).late(0.2) will be equivalent to compose(transpose(3), late(0.2))
|
||||
// TODO: add Pattern.define(name, function, options) that handles all the meta programming stuff
|
||||
@@ -110,8 +110,8 @@ Pattern.prototype._transpose = function (intervalOrSemitones) {
|
||||
* .note()
|
||||
*/
|
||||
|
||||
Pattern.prototype._scaleTranspose = function (offset /* : number | string */) {
|
||||
return this.withHap((hap) => {
|
||||
export const scaleTranspose = register('scaleTranspose', function (offset /* : number | string */, pat) {
|
||||
return pat.withHap((hap) => {
|
||||
if (!hap.context.scale) {
|
||||
throw new Error('can only use scaleTranspose after .scale');
|
||||
}
|
||||
@@ -120,7 +120,7 @@ Pattern.prototype._scaleTranspose = function (offset /* : number | string */) {
|
||||
}
|
||||
return hap.withValue(() => scaleOffset(hap.context.scale, Number(offset), hap.value));
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Turns numbers into notes in the scale (zero indexed). Also sets scale for other scale operations, like {@link Pattern#scaleTranspose}.
|
||||
@@ -141,8 +141,8 @@ Pattern.prototype._scaleTranspose = function (offset /* : number | string */) {
|
||||
* .note()
|
||||
*/
|
||||
|
||||
Pattern.prototype._scale = function (scale /* : string */) {
|
||||
return this.withHap((hap) => {
|
||||
export const scale = register('scale', function (scale /* : string */, pat) {
|
||||
return pat.withHap((hap) => {
|
||||
let note = hap.value;
|
||||
const asNumber = Number(note);
|
||||
if (!isNaN(asNumber)) {
|
||||
@@ -152,8 +152,4 @@ Pattern.prototype._scale = function (scale /* : string */) {
|
||||
}
|
||||
return hap.withValue(() => note).setContext({ ...hap.context, scale });
|
||||
});
|
||||
};
|
||||
|
||||
Pattern.prototype.define('transpose', (a, pat) => pat.transpose(a), { composable: true, patternified: true });
|
||||
Pattern.prototype.define('scale', (a, pat) => pat.scale(a), { composable: true, patternified: true });
|
||||
Pattern.prototype.define('scaleTranspose', (a, pat) => pat.scaleTranspose(a), { composable: true, patternified: true });
|
||||
});
|
||||
|
||||
+58
-36
@@ -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 });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user