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) {
+113 -76
View File
@@ -5706,6 +5706,43 @@ exports[`runs examples > example "hush" example index 0 1`] = `
]
`;
exports[`runs examples > example "i" example index 0 1`] = `
[
"[ 0/1 → 1/8 | freq:220 ]",
"[ 1/8 → 1/4 | freq:252.7136381 ]",
"[ 1/4 → 3/8 | freq:290.2917404 ]",
"[ 3/8 → 1/2 | freq:333.4576446 ]",
"[ 1/2 → 5/8 | freq:383.0422479 ]",
"[ 5/8 → 3/4 | freq:440 ]",
"[ 3/4 → 7/8 | freq:505.4272762 ]",
"[ 7/8 → 1/1 | freq:580.5834807 ]",
"[ 1/1 → 9/8 | freq:220 ]",
"[ 9/8 → 5/4 | freq:235.7901618 ]",
"[ 5/4 → 11/8 | freq:252.7136381 ]",
"[ 11/8 → 3/2 | freq:270.8517709 ]",
"[ 3/2 → 13/8 | freq:290.2917404 ]",
"[ 13/8 → 7/4 | freq:311.1269837 ]",
"[ 7/4 → 15/8 | freq:333.4576446 ]",
"[ 15/8 → 2/1 | freq:357.3910544 ]",
"[ 2/1 → 17/8 | freq:220 ]",
"[ 17/8 → 9/4 | freq:230.404707 ]",
"[ 9/4 → 19/8 | freq:241.3014955 ]",
"[ 19/8 → 5/2 | freq:252.7136381 ]",
"[ 5/2 → 21/8 | freq:264.6655079 ]",
"[ 21/8 → 11/4 | freq:277.182631 ]",
"[ 11/4 → 23/8 | freq:290.2917404 ]",
"[ 23/8 → 3/1 | freq:304.0208336 ]",
"[ 3/1 → 25/8 | freq:220 ]",
"[ 25/8 → 13/4 | freq:275 ]",
"[ 13/4 → 27/8 | freq:293.3333333 ]",
"[ 27/8 → 7/2 | freq:330 ]",
"[ 7/2 → 29/8 | freq:352 ]",
"[ 29/8 → 15/4 | freq:440 ]",
"[ 15/4 → 31/8 | freq:550 ]",
"[ 31/8 → 4/1 | freq:586.6666667 ]",
]
`;
exports[`runs examples > example "inhabit" example index 0 1`] = `
[
"[ 0/1 → 1/8 | s:bd ]",
@@ -13289,92 +13326,92 @@ exports[`runs examples > example "tri" example index 0 1`] = `
exports[`runs examples > example "tune" example index 0 1`] = `
[
"[ 0/1 → 1/6 | freq:0 ]",
"[ 1/6 → 1/3 | freq:220 ]",
"[ 1/3 → 1/2 | freq:440 ]",
"[ 1/2 → 2/3 | freq:660 ]",
"[ 2/3 → 5/6 | freq:880 ]",
"[ 5/6 → 1/1 | freq:1100 ]",
"[ 1/1 → 7/6 | freq:0 ]",
"[ 7/6 → 4/3 | freq:220 ]",
"[ 4/3 → 3/2 | freq:440 ]",
"[ 3/2 → 5/3 | freq:660 ]",
"[ 5/3 → 11/6 | freq:880 ]",
"[ 11/6 → 2/1 | freq:1100 ]",
"[ 2/1 → 13/6 | freq:0 ]",
"[ 13/6 → 7/3 | freq:220 ]",
"[ 7/3 → 5/2 | freq:440 ]",
"[ 5/2 → 8/3 | freq:660 ]",
"[ 8/3 → 17/6 | freq:880 ]",
"[ 17/6 → 3/1 | freq:1100 ]",
"[ 3/1 → 19/6 | freq:0 ]",
"[ 19/6 → 10/3 | freq:220 ]",
"[ 10/3 → 7/2 | freq:440 ]",
"[ 7/2 → 11/3 | freq:660 ]",
"[ 11/3 → 23/6 | freq:880 ]",
"[ 23/6 → 4/1 | freq:1100 ]",
"[ 0/1 → 1/6 | freq:{i:0} ]",
"[ 1/6 → 1/3 | freq:{i:1} ]",
"[ 1/3 → 1/2 | freq:{i:2} ]",
"[ 1/2 → 2/3 | freq:{i:3} ]",
"[ 2/3 → 5/6 | freq:{i:4} ]",
"[ 5/6 → 1/1 | freq:{i:5} ]",
"[ 1/1 → 7/6 | freq:{i:0} ]",
"[ 7/6 → 4/3 | freq:{i:1} ]",
"[ 4/3 → 3/2 | freq:{i:2} ]",
"[ 3/2 → 5/3 | freq:{i:3} ]",
"[ 5/3 → 11/6 | freq:{i:4} ]",
"[ 11/6 → 2/1 | freq:{i:5} ]",
"[ 2/1 → 13/6 | freq:{i:0} ]",
"[ 13/6 → 7/3 | freq:{i:1} ]",
"[ 7/3 → 5/2 | freq:{i:2} ]",
"[ 5/2 → 8/3 | freq:{i:3} ]",
"[ 8/3 → 17/6 | freq:{i:4} ]",
"[ 17/6 → 3/1 | freq:{i:5} ]",
"[ 3/1 → 19/6 | freq:{i:0} ]",
"[ 19/6 → 10/3 | freq:{i:1} ]",
"[ 10/3 → 7/2 | freq:{i:2} ]",
"[ 7/2 → 11/3 | freq:{i:3} ]",
"[ 11/3 → 23/6 | freq:{i:4} ]",
"[ 23/6 → 4/1 | freq:{i:5} ]",
]
`;
exports[`runs examples > example "tune" example index 1 1`] = `
[
"[ 0/1 → 1/12 | freq:523.2511306011972 clip:0.5 room:1 ]",
"[ 1/12 → 1/6 | freq:1046.5022612023945 clip:0.5 room:1 ]",
"[ 1/6 → 1/4 | freq:1177.3150438526939 clip:0.5 room:1 ]",
"[ 1/4 → 1/3 | freq:1308.1278265029932 clip:0.5 room:1 ]",
"[ 1/2 → 7/12 | freq:654.0639132514966 clip:0.5 room:1 ]",
"[ 7/12 → 2/3 | freq:915.6894785520951 clip:0.5 room:1 ]",
"[ 2/3 → 3/4 | freq:1177.3150438526939 clip:0.5 room:1 ]",
"[ 3/4 → 5/6 | freq:1438.9406091532924 clip:0.5 room:1 ]",
"[ 1/1 → 13/12 | freq:523.2511306011972 clip:0.5 room:1 ]",
"[ 13/12 → 7/6 | freq:1046.5022612023945 clip:0.5 room:1 ]",
"[ 7/6 → 5/4 | freq:1177.3150438526939 clip:0.5 room:1 ]",
"[ 5/4 → 4/3 | freq:1308.1278265029932 clip:0.5 room:1 ]",
"[ 3/2 → 19/12 | freq:654.0639132514966 clip:0.5 room:1 ]",
"[ 19/12 → 5/3 | freq:915.6894785520951 clip:0.5 room:1 ]",
"[ 5/3 → 7/4 | freq:1177.3150438526939 clip:0.5 room:1 ]",
"[ 7/4 → 11/6 | freq:1438.9406091532924 clip:0.5 room:1 ]",
"[ 2/1 → 25/12 | freq:523.2511306011972 clip:0.5 room:1 ]",
"[ 25/12 → 13/6 | freq:1046.5022612023945 clip:0.5 room:1 ]",
"[ 13/6 → 9/4 | freq:1177.3150438526939 clip:0.5 room:1 ]",
"[ 9/4 → 7/3 | freq:1308.1278265029932 clip:0.5 room:1 ]",
"[ 5/2 → 31/12 | freq:654.0639132514966 clip:0.5 room:1 ]",
"[ 31/12 → 8/3 | freq:915.6894785520951 clip:0.5 room:1 ]",
"[ 8/3 → 11/4 | freq:1177.3150438526939 clip:0.5 room:1 ]",
"[ 11/4 → 17/6 | freq:1438.9406091532924 clip:0.5 room:1 ]",
"[ 3/1 → 37/12 | freq:523.2511306011972 clip:0.5 room:1 ]",
"[ 37/12 → 19/6 | freq:1046.5022612023945 clip:0.5 room:1 ]",
"[ 19/6 → 13/4 | freq:1177.3150438526939 clip:0.5 room:1 ]",
"[ 13/4 → 10/3 | freq:1308.1278265029932 clip:0.5 room:1 ]",
"[ 7/2 → 43/12 | freq:654.0639132514966 clip:0.5 room:1 ]",
"[ 43/12 → 11/3 | freq:915.6894785520951 clip:0.5 room:1 ]",
"[ 11/3 → 15/4 | freq:1177.3150438526939 clip:0.5 room:1 ]",
"[ 15/4 → 23/6 | freq:1438.9406091532924 clip:0.5 room:1 ]",
"[ 0/1 → 1/12 | freq:{i:4} clip:0.5 room:1 ]",
"[ 1/12 → 1/6 | freq:{i:8} clip:0.5 room:1 ]",
"[ 1/6 → 1/4 | freq:{i:9} clip:0.5 room:1 ]",
"[ 1/4 → 1/3 | freq:{i:10} clip:0.5 room:1 ]",
"[ 1/2 → 7/12 | freq:{i:5} clip:0.5 room:1 ]",
"[ 7/12 → 2/3 | freq:{i:7} clip:0.5 room:1 ]",
"[ 2/3 → 3/4 | freq:{i:9} clip:0.5 room:1 ]",
"[ 3/4 → 5/6 | freq:{i:11} clip:0.5 room:1 ]",
"[ 1/1 → 13/12 | freq:{i:4} clip:0.5 room:1 ]",
"[ 13/12 → 7/6 | freq:{i:8} clip:0.5 room:1 ]",
"[ 7/6 → 5/4 | freq:{i:9} clip:0.5 room:1 ]",
"[ 5/4 → 4/3 | freq:{i:10} clip:0.5 room:1 ]",
"[ 3/2 → 19/12 | freq:{i:5} clip:0.5 room:1 ]",
"[ 19/12 → 5/3 | freq:{i:7} clip:0.5 room:1 ]",
"[ 5/3 → 7/4 | freq:{i:9} clip:0.5 room:1 ]",
"[ 7/4 → 11/6 | freq:{i:11} clip:0.5 room:1 ]",
"[ 2/1 → 25/12 | freq:{i:4} clip:0.5 room:1 ]",
"[ 25/12 → 13/6 | freq:{i:8} clip:0.5 room:1 ]",
"[ 13/6 → 9/4 | freq:{i:9} clip:0.5 room:1 ]",
"[ 9/4 → 7/3 | freq:{i:10} clip:0.5 room:1 ]",
"[ 5/2 → 31/12 | freq:{i:5} clip:0.5 room:1 ]",
"[ 31/12 → 8/3 | freq:{i:7} clip:0.5 room:1 ]",
"[ 8/3 → 11/4 | freq:{i:9} clip:0.5 room:1 ]",
"[ 11/4 → 17/6 | freq:{i:11} clip:0.5 room:1 ]",
"[ 3/1 → 37/12 | freq:{i:4} clip:0.5 room:1 ]",
"[ 37/12 → 19/6 | freq:{i:8} clip:0.5 room:1 ]",
"[ 19/6 → 13/4 | freq:{i:9} clip:0.5 room:1 ]",
"[ 13/4 → 10/3 | freq:{i:10} clip:0.5 room:1 ]",
"[ 7/2 → 43/12 | freq:{i:5} clip:0.5 room:1 ]",
"[ 43/12 → 11/3 | freq:{i:7} clip:0.5 room:1 ]",
"[ 11/3 → 15/4 | freq:{i:9} clip:0.5 room:1 ]",
"[ 15/4 → 23/6 | freq:{i:11} clip:0.5 room:1 ]",
]
`;
exports[`runs examples > example "tune" example index 2 1`] = `
[
"[ 0/1 → 1/5 | freq:0 ]",
"[ 1/5 → 2/5 | freq:220 ]",
"[ 2/5 → 3/5 | freq:440 ]",
"[ 3/5 → 4/5 | freq:660 ]",
"[ 4/5 → 1/1 | freq:880 ]",
"[ 1/1 → 6/5 | freq:0 ]",
"[ 6/5 → 7/5 | freq:220 ]",
"[ 7/5 → 8/5 | freq:440 ]",
"[ 8/5 → 9/5 | freq:660 ]",
"[ 9/5 → 2/1 | freq:880 ]",
"[ 2/1 → 11/5 | freq:0 ]",
"[ 11/5 → 12/5 | freq:220 ]",
"[ 12/5 → 13/5 | freq:440 ]",
"[ 13/5 → 14/5 | freq:660 ]",
"[ 14/5 → 3/1 | freq:880 ]",
"[ 3/1 → 16/5 | freq:0 ]",
"[ 16/5 → 17/5 | freq:220 ]",
"[ 17/5 → 18/5 | freq:440 ]",
"[ 18/5 → 19/5 | freq:660 ]",
"[ 19/5 → 4/1 | freq:880 ]",
"[ 0/1 → 1/5 | freq:{i:0} ]",
"[ 1/5 → 2/5 | freq:{i:1} ]",
"[ 2/5 → 3/5 | freq:{i:2} ]",
"[ 3/5 → 4/5 | freq:{i:3} ]",
"[ 4/5 → 1/1 | freq:{i:4} ]",
"[ 1/1 → 6/5 | freq:{i:0} ]",
"[ 6/5 → 7/5 | freq:{i:1} ]",
"[ 7/5 → 8/5 | freq:{i:2} ]",
"[ 8/5 → 9/5 | freq:{i:3} ]",
"[ 9/5 → 2/1 | freq:{i:4} ]",
"[ 2/1 → 11/5 | freq:{i:0} ]",
"[ 11/5 → 12/5 | freq:{i:1} ]",
"[ 12/5 → 13/5 | freq:{i:2} ]",
"[ 13/5 → 14/5 | freq:{i:3} ]",
"[ 14/5 → 3/1 | freq:{i:4} ]",
"[ 3/1 → 16/5 | freq:{i:0} ]",
"[ 16/5 → 17/5 | freq:{i:1} ]",
"[ 17/5 → 18/5 | freq:{i:2} ]",
"[ 18/5 → 19/5 | freq:{i:3} ]",
"[ 19/5 → 4/1 | freq:{i:4} ]",
]
`;
+10 -11
View File
@@ -20,7 +20,7 @@ These functions allow the use of scales other than your typical chromatic 12 bas
Here's an example of how to configure a basic hexany scale:
<MiniRepl client:idle tune={`"0 1 2 3 4 5".tune("hexany15").mul("220").freq()`} />
<MiniRepl client:idle tune={`i("0 1 2 3 4 5").tune("hexany15").mul("220").freq()`} />
Try other scales like `hexany1`, `iraq`, `gumbeng`, `gunkali`, or `tranh3`
@@ -30,7 +30,7 @@ You can set your root to be a particular note with `getFreq`
<MiniRepl
client:idle
tune={`"4 8 9 10 - - 5 7 9 11 - -".tune("tranh3")
tune={`i("4 8 9 10 - - 5 7 9 11 - -").tune("tranh3")
.mul(getFreq('c3'))
.freq().clip(.5).room(1)`}
/>
@@ -39,7 +39,7 @@ Some tunings become more pronounced with a longer reverb decay:
<MiniRepl
client:idle
tune={`"<[5 6 8 10] - [5 7 9 12] -> -".tune("gumbeng")
tune={`i("<[5 6 8 10] - [5 7 9 12] -> -").tune("gumbeng")
.mul(getFreq('c3'))
.freq().clip(.8).room("3:10").rdim(10000).rfade(5)`}
/>
@@ -48,7 +48,7 @@ Additionally, you can combo this with `fmap` so that the base note changes:
<MiniRepl
client:idle
tune={`"9 11 12 10 - - -".tune("gunkali")
tune={`i("9 11 12 10 - - -").tune("gunkali")
.mul("<c3 c3 a3 d#3>".fmap(getFreq))
.freq().legato("2 .7").room("1:15").rdim(8500).rlp(14000).rfade(8)`}
/>
@@ -57,8 +57,7 @@ Combining this with various polyrhythm tricks can become very evocative:
<MiniRepl
client:idle
tune={`"<[0 3 1 -] [-1 4 2 8]> ~ ~,<-4 -5>"
.transpose(4)
tune={`i("<[0 3 1 -] [-1 4 2 8]> ~ ~,<-4 -5>".add(4))
.tune("iraq")
.mul("<c3 d3 c#3>".fmap(getFreq))
.freq().clip(.5).room(1).rfade(9)`}
@@ -71,7 +70,7 @@ Take the `sanza` tuning:
<MiniRepl
client:idle
tune={`"4 5 6 7 8 9".tune("sanza")
tune={`i("4 5 6 7 8 9").tune("sanza")
.mul(getFreq('c3'))
.freq()`}
/>
@@ -79,15 +78,15 @@ Take the `sanza` tuning:
Notes 7 and 9 will clash quite a bit if you arp them normally. Many tunings will have this sort of sound, and it can feel distracting on its own.
See how close they are on the pitch wheel?
<MiniRepl client:idle tune={`"[7 9]!3".tune("sanza").mul(getFreq('c3')).freq()._pitchwheel()`} />
<MiniRepl client:idle tune={`i("[7 9]!3").tune("sanza").mul(getFreq('c3')).freq()._pitchwheel()`} />
This quality is often due to how the tunings were formed with instruments that were played differently than a piano.
As such, some tunings are much better strummed, with the subtle clash of the detuned notes actually making the sound much more magical:
<MiniRepl
client:idle
tune={`"[0 1 2 3 4 5 6]@0.3 -"
.transpose("<2 5 8 1>")
tune={`i("[0 1 2 3 4 5 6]@0.3 -"
.add("<2 5 8 1>"))
.tune("sanza")
.mul(getFreq('c3')).freq()
.legato("3").room(1).rfade(5)`}
@@ -102,7 +101,7 @@ You can also give tune a list of frequencies to use as the scale:
<MiniRepl
client:idle
tune={`"0 1 2 3 4".tune([
tune={`i("0 1 2 3 4").tune([
261.6255653006,
302.72962012827,
350.29154279212,