creation of i control for tune and xen functions

This commit is contained in:
tyow
2026-02-04 20:28:19 -05:00
parent 4439599117
commit cbf472fe0a
5 changed files with 163 additions and 110 deletions
+12
View File
@@ -394,6 +394,18 @@ export const { source, src } = registerControl('source', 'src');
*/
// also see https://codeberg.org/uzu/strudel/pulls/63
export const { n } = registerControl('n');
/**
* Selects the given degree. Currently used in `xen` and `tune`:
*
* @name i
* @tags tonal
* @param {number | Pattern} value
* @example
* i("0 1 2 3 4 5 6 7").xen("<5edo 10edo 15edo hexany15>")
*/
export const { i } = registerControl('i');
/**
* Plays the given note name or midi number. A note name consists of
*
+12 -5
View File
@@ -8,23 +8,23 @@ import Tune from './tunejs.js';
import { register } from '@strudel/core';
/**
* Assumes a numerical pattern of EDO steps. Accepts a scale name or list of frequencies (see all available names at the link on the reference). Returns a new pattern with all values mapped to a frequency ratio. Similar to `xen`.
* Assumes pattern contains numerical scale degrees on the `i` control (see examples below). Accepts a scale name or list of frequencies (see all available names at the link on the reference). Returns a new pattern with all values mapped to a frequency ratio. Similar to `xen`.
* @name tune
* @returns Pattern
* @memberof Pattern
* @param {(string | number[] )} scale
* @example
* "0 1 2 3 4 5".tune("hexany15").mul("220").freq()
* i("0 1 2 3 4 5").tune("hexany15").mul("220").freq()
* @example
* // You can set your root to be a
* // particular note with getFreq:
* "4 8 9 10 - - 5 7 9 11 - -".tune("tranh3")
* i("4 8 9 10 - - 5 7 9 11 - -").tune("tranh3")
* .mul(getFreq('c3'))
* .freq().clip(.5).room(1)
* @example
* // You can also give tune a list of
* // frequencies to use as the scale:
* "0 1 2 3 4".tune([
* i("0 1 2 3 4").tune([
* 261.6255653006,
* 302.72962012827,
* 350.29154279212,
@@ -32,6 +32,7 @@ import { register } from '@strudel/core';
* 469.00678383895,
* 523.2511306012
* ]).mul(220).freq();
*
* @tags tonal
*/
@@ -45,6 +46,12 @@ export const tune = register('tune', (scale, pat) => {
// if the tonic is a frequency, why are we putting in "1"
tune.tonicize(1);
return pat.withHap((hap) => {
return hap.withValue(() => tune.note(hap.value));
if (typeof hap.value !== 'object') {
throw new Error(`Expected hap to have control 'i' set, but received ${hap.value.i}, try wrapping input in i()`)
}
// const { i, ...otherValues } = hap.value;
// hap.value = { ...otherValues, freq: tune.note(i)}
// return hap
return hap.withValue(() => tune.note(hap.value.i));
});
});
+16 -18
View File
@@ -77,11 +77,11 @@ const trimFreq = (freq) => parseFloat(freq.toPrecision(10));
* @tags tonal
* @example
* // A minor triad in 31edo:
* "0 8 18".xen("31edo").piano()
* i("0 8 18").xen("31edo").piano()
* @example
* // You can also use xen with frequency ratios.
* // This is equivalent to the above:
* "0 1 2".xen([
* i("0 1 2").xen([
* Math.pow(2, 0/31),
* Math.pow(2, 8/31),
* Math.pow(2, 18/31),
@@ -89,29 +89,27 @@ const trimFreq = (freq) => parseFloat(freq.toPrecision(10));
* @example
* // xen also supports all scale names that
* // tune does:
* "0 1 2 3 4 5".xen("hexany15")
* i("0 1 2 3 4 5").xen("hexany15")
* // equiv to:
* // "0 1 2 3 4 5".tune("hexany15").mul("220").freq()
* @example
* n("0 1 2 3 4 5 6 7").xen("<5edo 10edo 15edo hexany15>")
* i("0 1 2 3 4 5 6 7").xen("<5edo 10edo 15edo hexany15>")
*/
// TODO feat: change root frequency
// TODO add explanation for what "31edo" etc. are
// TODO (maybe): should this return freq ratios like tune does, for parity's sake?
export const xen = register('xen', function (scaleNameOrRatios, pat) {
return pat.withHaps((haps) => {
haps = haps.map((hap) => {
let hVal = hap.value;
const isObject = typeof hVal === 'object';
// If hVal is a pure value, place it on `n` so that we interpret it as an edoStep
hVal = isObject ? hVal : { n: hVal };
const { n, value, ...otherValues } = hVal;
if (!isObject) {
throw new Error(`Expected hap to have control 'i' set, but received ${hap.value.i}, try wrapping input in i()`)
}
const { i, ...otherValues } = hVal;
const scale = getXenScale(scaleNameOrRatios);
let freq = xenOffset(scale, parseNumeral(hVal.n));
let freq = xenOffset(scale, parseNumeral(hVal.i));
// 10 is somewhat arbitrary
freq = trimFreq(freq);
hap.value = isObject ? { ...otherValues, freq } : { freq };
hap.value = { ...otherValues, freq };
return isEdo(scaleNameOrRatios)
? hap.setContext({ ...hap.context, edoSize: scaleNameOrRatios.match(/^([1-9]+[0-9]*)edo$/)[1] })
: hap;
@@ -130,7 +128,7 @@ export const xen = register('xen', function (scaleNameOrRatios, pat) {
* @tags tonal
*
* @example
* "[0 1 2 3] [3 4] [4 3 2 1]".xen("hexany23").withBase("<220 [300 200]>")
* i("[0 1 2 3] [3 4] [4 3 2 1]").xen("hexany23").withBase("<220 [300 200]>")
* @example
* mini([1 / 1, 16 / 15, 9 / 8, 6 / 5, 5 / 4].join(' ')).withBase("220:1")
* // mini([1 / 1, 16 / 15, 9 / 8, 6 / 5, 5 / 4].join(' ')).mul(220).freq()
@@ -174,16 +172,16 @@ export const withBase = register('withBase', (b, pat) => {
* @returns {Pattern}
*
* @example
* "0 1 2".xen("12edo").ftrans("7")
* i("0 1 2").xen("12edo").ftrans("7")
* // n("0 1 2").scale("A:chromatic").trans("7")
* @example
* "0 8 18".xen("31edo").ftrans("<8 -8>")
* i("0 8 18").xen("31edo").ftrans("<8 -8>")
* @example
* // to transpose by steps of an edo, use "step:edo" :
* "0 7 8 18".xen("31edo").ftrans("<0 1:31 1:12>")
* i("0 7 8 18").xen("31edo").ftrans("<0 1:31 1:12>")
* @example
* // it can also work with frequency values directly
* "200 300 400".ftrans("<0 7:31 7>").freq()
* freq("200 300 400").ftrans("<0 7:31 7>")
*/
/* f = frequency (Hz)
@@ -208,7 +206,7 @@ export const { ftrans, fTrans, ftranspose, fTranspose } = register(
let hVal = hap.value;
const isObject = typeof hVal === 'object';
hVal = isObject ? hVal : { freq: hVal };
let { freq, value, ...otherValues } = hVal;
let { freq, ...otherValues } = hVal;
if (edoSize == undefined && hap.context.edoSize != undefined) {
edoSize = hap.context.edoSize;
} else if (edoSize == undefined) {