From d8645e9a7192d0164bef206cae41bfcad6af70f8 Mon Sep 17 00:00:00 2001 From: tyow Date: Fri, 23 Jan 2026 13:30:36 -0500 Subject: [PATCH 01/10] prelim work on uzu/strudel#1944 --- packages/xen/xen.mjs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/xen/xen.mjs b/packages/xen/xen.mjs index cc96f4110..92ea0e62a 100644 --- a/packages/xen/xen.mjs +++ b/packages/xen/xen.mjs @@ -6,6 +6,8 @@ This program is free software: you can redistribute it and/or modify it under th import { register, _mod, parseNumeral } from '@strudel/core'; + +// returns a list of frequency ratios for given edo scale export function edo(name) { if (!/^[1-9]+[0-9]*edo$/.test(name)) { throw new Error('not an edo scale: "' + name + '"'); @@ -18,12 +20,15 @@ const presets = { '12ji': [1 / 1, 16 / 15, 9 / 8, 6 / 5, 5 / 4, 4 / 3, 45 / 32, 3 / 2, 8 / 5, 5 / 3, 16 / 9, 15 / 8], }; +// Given a base frequency such as 220 and an edo scale, returns +// an array of frequencies representing the given edo scale in that base function withBase(freq, scale) { return scale.map((r) => r * freq); } const defaultBase = 220; +// Assumes a base of 220. Returns a filtered scale based on 'indices' function getXenScale(scale, indices) { if (typeof scale === 'string') { if (/^[1-9]+[0-9]*edo$/.test(scale)) { @@ -41,12 +46,17 @@ function getXenScale(scale, indices) { return scale.filter((_, i) => indices.includes(i)); } + function xenOffset(xenScale, offset, index = 0) { const i = _mod(index + offset, xenScale.length); const oct = Math.floor(offset / xenScale.length); return xenScale[i] * Math.pow(2, oct); } +// accepts a scale name such as 31edo, and a pattern +// pattern expected to follow format such that a value can be mapped +// to an edostep within the scale. Returns the pattern with +// values mapped to the frequencies associated with the given edosteps // scaleNameOrRatios: string || number[], steps?: number export const xen = register('xen', function (scaleNameOrRatios, pat) { return pat.withHap((hap) => { @@ -56,6 +66,7 @@ export const xen = register('xen', function (scaleNameOrRatios, pat) { }); }); +// not sure there's a point to having this and the above, seems like a proto version of the above. export const tuning = register('tuning', function (ratios, pat) { return pat.withHap((hap) => { const frequency = xenOffset(ratios, parseNumeral(hap.value)); From a5b867f1b14d06f423f0ec1bd0a1f74a2f5ca29a Mon Sep 17 00:00:00 2001 From: tyow Date: Fri, 23 Jan 2026 16:32:41 -0500 Subject: [PATCH 02/10] added documentation for xen() re: uzu/strudel#1944 --- packages/xen/xen.mjs | 23 +++++++++++++++++++++++ website/src/pages/learn/xen.mdx | 13 ++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/xen/xen.mjs b/packages/xen/xen.mjs index 92ea0e62a..48d4e6970 100644 --- a/packages/xen/xen.mjs +++ b/packages/xen/xen.mjs @@ -58,6 +58,29 @@ function xenOffset(xenScale, offset, index = 0) { // to an edostep within the scale. Returns the pattern with // values mapped to the frequencies associated with the given edosteps // scaleNameOrRatios: string || number[], steps?: number + +/** + * Assumes a numerical pattern of EDO steps. Returns a new pattern with all values + * mapped to their associated frequency + * + * @name xen + * @returns Pattern + * @memberof Pattern + * @param {(string | number[] )} scaleNameOrRatios + * @tags music_theory + * @example + * "0 8 18".xen("31edo").freq().piano() + * @example + * // You can also use xen with frequency ratios. + * // This is equivalent to the above: + * "0 1 2".xen([ + * Math.pow(2, 0/31), + * Math.pow(2, 8/31), + * Math.pow(2, 18/31), + * ]).freq().piano() + */ + +// TODO support tunings defined in './tunejs.js' export const xen = register('xen', function (scaleNameOrRatios, pat) { return pat.withHap((hap) => { const scale = getXenScale(scaleNameOrRatios); diff --git a/website/src/pages/learn/xen.mdx b/website/src/pages/learn/xen.mdx index b8b27ac7f..0dd27270b 100644 --- a/website/src/pages/learn/xen.mdx +++ b/website/src/pages/learn/xen.mdx @@ -6,7 +6,7 @@ layout: ../../layouts/MainLayout.astro import { MiniRepl } from '../../docs/MiniRepl'; import { JsDoc } from '../../docs/JsDoc'; -# Xen Harmonic Functions +# Xen Harmonic Functions (experimental) These functions allow the use of scales other than your typical chromatic 12 based ones. @@ -93,3 +93,14 @@ Note the legato and reverb effects make sure the sound of the strumming gets to tones sound even more alive, too. The `tranh3` tuning has a similar set of notes, with two clashing. You might trying plugging that in above and see if you find a favorite strumming pattern. + +### xen(scaleOrRatios) + + + +{/* */} From 93cf9fcb6a4ed6608d1805aeff1ac60ca5cc1e5f Mon Sep 17 00:00:00 2001 From: tyow Date: Fri, 23 Jan 2026 17:18:38 -0500 Subject: [PATCH 03/10] minor: remove space between Xen and Harmonic re uzu/strudel#1944 --- website/src/config.ts | 2 +- website/src/pages/learn/xen.mdx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/website/src/config.ts b/website/src/config.ts index eb39d2958..eb8bd1af8 100644 --- a/website/src/config.ts +++ b/website/src/config.ts @@ -104,7 +104,7 @@ export const SIDEBAR: Sidebar = { Understand: [ { text: 'Coding syntax', link: 'learn/code' }, { text: 'Pitch', link: 'understand/pitch' }, - { text: 'Xen Harmonic Functions', link: 'learn/xen' }, + { text: 'Xenharmonic Functions', link: 'learn/xen' }, { text: 'Cycles', link: 'understand/cycles' }, { text: 'Voicings', link: 'understand/voicings' }, { text: 'Pattern Alignment', link: 'technical-manual/alignment' }, diff --git a/website/src/pages/learn/xen.mdx b/website/src/pages/learn/xen.mdx index 0dd27270b..0dd0ea5fe 100644 --- a/website/src/pages/learn/xen.mdx +++ b/website/src/pages/learn/xen.mdx @@ -1,12 +1,12 @@ --- -title: Xen Harmonic Functions +title: Xenharmonic Functions layout: ../../layouts/MainLayout.astro --- import { MiniRepl } from '../../docs/MiniRepl'; import { JsDoc } from '../../docs/JsDoc'; -# Xen Harmonic Functions (experimental) +# Xenharmonic Functions (experimental) These functions allow the use of scales other than your typical chromatic 12 based ones. From 2b3f719484ac8963afd4f852541e373606b2bf1d Mon Sep 17 00:00:00 2001 From: tyow Date: Fri, 23 Jan 2026 18:08:34 -0500 Subject: [PATCH 04/10] feat: added Tune scales to Xen, improved documentation re uzu/strudel#1944 --- packages/xen/tune.mjs | 4 ++++ packages/xen/tunejs.js | 18 ++++++++++++++---- packages/xen/xen.mjs | 18 ++++++++++++++++-- website/src/pages/learn/xen.mdx | 11 +++-------- 4 files changed, 37 insertions(+), 14 deletions(-) diff --git a/packages/xen/tune.mjs b/packages/xen/tune.mjs index 01303bf52..22b345695 100644 --- a/packages/xen/tune.mjs +++ b/packages/xen/tune.mjs @@ -7,12 +7,16 @@ This program is free software: you can redistribute it and/or modify it under th import Tune from './tunejs.js'; import { register } from '@strudel/core'; +// Tune.scale seems to be in ratio format +// + export const tune = register('tune', (scale, pat) => { const tune = new Tune(); if (!tune.isValidScale(scale)) { throw new Error('not a valid tune.js scale name: "' + scale + '". See http://abbernie.github.io/tune/scales.html'); } tune.loadScale(scale); + // 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)); diff --git a/packages/xen/tunejs.js b/packages/xen/tunejs.js index 9f4253709..e4e272518 100644 --- a/packages/xen/tunejs.js +++ b/packages/xen/tunejs.js @@ -78,14 +78,21 @@ Tune.prototype.frequency = function(stepIn, octaveIn) { } // which scale degree (0 - scale length) is our input + // 60 % 12 = 0 var scaleDegree = stepIn % this.scale.length + // what's this doing + // 0 scaleDegree = 12 + // seems to simply be for a negative result from above, maybe another way to do it, but ok for now while (scaleDegree < 0) { scaleDegree += this.scale.length } + // tonic is currently always 1 + // so this is 1*scale[scaledegree] var freq = this.tonic*this.scale[scaleDegree] + // map it to octave freq = freq*(Math.pow(2,octave)) // truncate irrational numbers @@ -137,10 +144,13 @@ Tune.prototype.MIDI = function(stepIn,octaveIn) { } /* Load a new scale */ +// sets .scale to ratios Tune.prototype.loadScale = function(scale){ /* load the scale */ + let name + if (typeof scale === 'string') name = scale; var freqs = isArrayOfNumbers(scale) ? scale : TuningList[scale].frequencies this.scale = [] for (var i=0;i { diff --git a/website/src/pages/learn/xen.mdx b/website/src/pages/learn/xen.mdx index 0dd0ea5fe..584d91b13 100644 --- a/website/src/pages/learn/xen.mdx +++ b/website/src/pages/learn/xen.mdx @@ -8,6 +8,8 @@ import { JsDoc } from '../../docs/JsDoc'; # Xenharmonic Functions (experimental) +{/* TODO expand explanation of xenharmony */} + These functions allow the use of scales other than your typical chromatic 12 based ones. ### tune(scale) @@ -96,11 +98,4 @@ The `tranh3` tuning has a similar set of notes, with two clashing. You might try ### xen(scaleOrRatios) - - -{/* */} + \ No newline at end of file From 3bbaf77b20196560a869b543116740c31c2ac4a8 Mon Sep 17 00:00:00 2001 From: tyow Date: Sat, 24 Jan 2026 14:18:51 -0500 Subject: [PATCH 05/10] added jsdoc documentation for tune re uzu/strudel#1944 --- packages/xen/tune.mjs | 31 +++++++++++++++++++++++++++++-- packages/xen/xen.mjs | 11 ++++++----- website/src/pages/learn/xen.mdx | 17 +++++++++++++++++ 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/packages/xen/tune.mjs b/packages/xen/tune.mjs index 22b345695..1f7076b93 100644 --- a/packages/xen/tune.mjs +++ b/packages/xen/tune.mjs @@ -7,9 +7,36 @@ This program is free software: you can redistribute it and/or modify it under th import Tune from './tunejs.js'; import { register } from '@strudel/core'; -// Tune.scale seems to be in ratio format -// +/** + * 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`. + * @name tune + * @returns Pattern + * @memberof Pattern + * @param {(string | number[] )} scale + * @example + * "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") + * .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([ + * 261.6255653006, + * 302.72962012827, + * 350.29154279212, + * 405.32593044476, + * 469.00678383895, + * 523.2511306012 + * ]).mul(220).freq(); + * @tags tonal +*/ + +// Tune.scale seems to be in ratio format export const tune = register('tune', (scale, pat) => { const tune = new Tune(); if (!tune.isValidScale(scale)) { diff --git a/packages/xen/xen.mjs b/packages/xen/xen.mjs index fe57ed580..a4b311424 100644 --- a/packages/xen/xen.mjs +++ b/packages/xen/xen.mjs @@ -29,6 +29,7 @@ function withBase(freq, scale) { const defaultBase = 220; // Assumes a base of 220. Returns a filtered scale based on 'indices' +// NOTE: indices functionality is unused function getXenScale(scale, indices) { let tune = new Tune() if (typeof scale === 'string') { @@ -66,14 +67,13 @@ function xenOffset(xenScale, offset, index = 0) { // scaleNameOrRatios: string || number[], steps?: number /** - * Assumes a numerical pattern of EDO steps. Returns a new pattern with all values - * mapped to their associated frequency + * Assumes a numerical pattern of EDO steps. Accepts all scale names of `tune` as well as any arbitrary edo scale. Returns a new pattern with all values mapped to their associated frequency, assuming a base frequency of 220hz. * * @name xen * @returns Pattern * @memberof Pattern * @param {(string | number[] )} scaleNameOrRatios - * @tags music_theory + * @tags tonal * @example * // A major tried in 31edo: * "0 8 18".xen("31edo").freq().piano() @@ -93,8 +93,9 @@ function xenOffset(xenScale, offset, index = 0) { * // "0 1 2 3 4 5".tune("hexany15").mul("220").freq() */ -// TODO how do you reference another function in jsdoc (tune, above) -// TODO support tunings defined in './tunejs.js' +// 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.withHap((hap) => { const scale = getXenScale(scaleNameOrRatios); diff --git a/website/src/pages/learn/xen.mdx b/website/src/pages/learn/xen.mdx index 584d91b13..b32c93c85 100644 --- a/website/src/pages/learn/xen.mdx +++ b/website/src/pages/learn/xen.mdx @@ -13,6 +13,7 @@ import { JsDoc } from '../../docs/JsDoc'; These functions allow the use of scales other than your typical chromatic 12 based ones. ### tune(scale) +{/* TODO (maybe): combine jsdoc things in tune.mjs with here */} @@ -96,6 +97,22 @@ tones sound even more alive, too. The `tranh3` tuning has a similar set of notes, with two clashing. You might trying plugging that in above and see if you find a favorite strumming pattern. +You can also give tune a list of frequencies to use as the scale: + + + ### xen(scaleOrRatios) +{/* TODO add explanation of EDO to documentation */} + \ No newline at end of file From 05ae80b0cad20f9c36b7e5e2042791bc0b190be3 Mon Sep 17 00:00:00 2001 From: tyow Date: Sat, 24 Jan 2026 14:30:15 -0500 Subject: [PATCH 06/10] formatting uzu/strudel#1944 --- packages/xen/tune.mjs | 21 ++- packages/xen/xen.mjs | 31 ++--- test/__snapshots__/examples.test.mjs.snap | 154 ++++++++++++++++++++++ website/src/pages/learn/xen.mdx | 3 +- 4 files changed, 180 insertions(+), 29 deletions(-) diff --git a/packages/xen/tune.mjs b/packages/xen/tune.mjs index 1f7076b93..7584b7f2f 100644 --- a/packages/xen/tune.mjs +++ b/packages/xen/tune.mjs @@ -7,7 +7,6 @@ This program is free software: you can redistribute it and/or modify it under th 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`. * @name tune @@ -17,24 +16,24 @@ import { register } from '@strudel/core'; * @example * "0 1 2 3 4 5".tune("hexany15").mul("220").freq() * @example - * // You can set your root to be a + * // You can set your root to be a * // particular note with getFreq: - * `"4 8 9 10 - - 5 7 9 11 - -".tune("tranh3") + * "4 8 9 10 - - 5 7 9 11 - -".tune("tranh3") * .mul(getFreq('c3')) - * .freq().clip(.5).room(1)` + * .freq().clip(.5).room(1) * @example - * // You can also give tune a list of + * // You can also give tune a list of * // frequencies to use as the scale: * "0 1 2 3 4".tune([ - * 261.6255653006, - * 302.72962012827, - * 350.29154279212, - * 405.32593044476, - * 469.00678383895, + * 261.6255653006, + * 302.72962012827, + * 350.29154279212, + * 405.32593044476, + * 469.00678383895, * 523.2511306012 * ]).mul(220).freq(); * @tags tonal -*/ + */ // Tune.scale seems to be in ratio format export const tune = register('tune', (scale, pat) => { diff --git a/packages/xen/xen.mjs b/packages/xen/xen.mjs index a4b311424..7e042228e 100644 --- a/packages/xen/xen.mjs +++ b/packages/xen/xen.mjs @@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th */ import { register, _mod, parseNumeral } from '@strudel/core'; -import Tune from './tunejs.js' +import Tune from './tunejs.js'; // returns a list of frequency ratios for given edo scale export function edo(name) { @@ -31,18 +31,16 @@ const defaultBase = 220; // Assumes a base of 220. Returns a filtered scale based on 'indices' // NOTE: indices functionality is unused function getXenScale(scale, indices) { - let tune = new Tune() + let tune = new Tune(); if (typeof scale === 'string') { if (/^[1-9]+[0-9]*edo$/.test(scale)) { scale = edo(scale); } else if (presets[scale]) { scale = presets[scale]; - } - else if (tune.isValidScale(scale)) { - tune.loadScale(scale) - scale = tune.scale - } - else { + } else if (tune.isValidScale(scale)) { + tune.loadScale(scale); + scale = tune.scale; + } else { throw new Error('unknown scale name: "' + scale + '"'); } } @@ -53,7 +51,6 @@ function getXenScale(scale, indices) { return scale.filter((_, i) => indices.includes(i)); } - function xenOffset(xenScale, offset, index = 0) { const i = _mod(index + offset, xenScale.length); const oct = Math.floor(offset / xenScale.length); @@ -68,7 +65,7 @@ function xenOffset(xenScale, offset, index = 0) { /** * Assumes a numerical pattern of EDO steps. Accepts all scale names of `tune` as well as any arbitrary edo scale. Returns a new pattern with all values mapped to their associated frequency, assuming a base frequency of 220hz. - * + * * @name xen * @returns Pattern * @memberof Pattern @@ -78,18 +75,18 @@ function xenOffset(xenScale, offset, index = 0) { * // A major tried in 31edo: * "0 8 18".xen("31edo").freq().piano() * @example - * // You can also use xen with frequency ratios. + * // You can also use xen with frequency ratios. * // This is equivalent to the above: * "0 1 2".xen([ - * Math.pow(2, 0/31), - * Math.pow(2, 8/31), - * Math.pow(2, 18/31), + * Math.pow(2, 0/31), + * Math.pow(2, 8/31), + * Math.pow(2, 18/31), * ]).freq().piano() - * @example - * // xen also supports all scale names that + * @example + * // xen also supports all scale names that * // tune does: * "0 1 2 3 4 5".xen("hexany15").freq() - * // equiv to: + * // equiv to: * // "0 1 2 3 4 5".tune("hexany15").mul("220").freq() */ diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 4d2675308..f4ccec78a 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -13088,6 +13088,97 @@ 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 ]", +] +`; + +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 ]", +] +`; + +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 ]", +] +`; + exports[`runs examples > example "undegrade" example index 0 1`] = ` [ "[ 0/1 → 1/8 | s:hh ]", @@ -14000,6 +14091,69 @@ exports[`runs examples > example "wtphaserand" example index 0 1`] = ` ] `; +exports[`runs examples > example "xen" example index 0 1`] = ` +[ + "[ 0/1 → 1/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]", + "[ 1/3 → 2/3 | freq:263.09212028971103 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", + "[ 2/3 → 1/1 | freq:329.01393411660587 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", + "[ 1/1 → 4/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]", + "[ 4/3 → 5/3 | freq:263.09212028971103 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", + "[ 5/3 → 2/1 | freq:329.01393411660587 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", + "[ 2/1 → 7/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]", + "[ 7/3 → 8/3 | freq:263.09212028971103 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", + "[ 8/3 → 3/1 | freq:329.01393411660587 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", + "[ 3/1 → 10/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]", + "[ 10/3 → 11/3 | freq:263.09212028971103 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", + "[ 11/3 → 4/1 | freq:329.01393411660587 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", +] +`; + +exports[`runs examples > example "xen" example index 1 1`] = ` +[ + "[ 0/1 → 1/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]", + "[ 1/3 → 2/3 | freq:263.09212028971103 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", + "[ 2/3 → 1/1 | freq:329.01393411660587 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", + "[ 1/1 → 4/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]", + "[ 4/3 → 5/3 | freq:263.09212028971103 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", + "[ 5/3 → 2/1 | freq:329.01393411660587 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", + "[ 2/1 → 7/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]", + "[ 7/3 → 8/3 | freq:263.09212028971103 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", + "[ 8/3 → 3/1 | freq:329.01393411660587 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", + "[ 3/1 → 10/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]", + "[ 10/3 → 11/3 | freq:263.09212028971103 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", + "[ 11/3 → 4/1 | freq:329.01393411660587 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", +] +`; + +exports[`runs examples > example "xen" example index 2 1`] = ` +[ + "[ 0/1 → 1/6 | freq:220 ]", + "[ 1/6 → 1/3 | freq:275 ]", + "[ 1/3 → 1/2 | freq:293.33333333333616 ]", + "[ 1/2 → 2/3 | freq:330 ]", + "[ 2/3 → 5/6 | freq:352 ]", + "[ 5/6 → 1/1 | freq:440 ]", + "[ 1/1 → 7/6 | freq:220 ]", + "[ 7/6 → 4/3 | freq:275 ]", + "[ 4/3 → 3/2 | freq:293.33333333333616 ]", + "[ 3/2 → 5/3 | freq:330 ]", + "[ 5/3 → 11/6 | freq:352 ]", + "[ 11/6 → 2/1 | freq:440 ]", + "[ 2/1 → 13/6 | freq:220 ]", + "[ 13/6 → 7/3 | freq:275 ]", + "[ 7/3 → 5/2 | freq:293.33333333333616 ]", + "[ 5/2 → 8/3 | freq:330 ]", + "[ 8/3 → 17/6 | freq:352 ]", + "[ 17/6 → 3/1 | freq:440 ]", + "[ 3/1 → 19/6 | freq:220 ]", + "[ 19/6 → 10/3 | freq:275 ]", + "[ 10/3 → 7/2 | freq:293.33333333333616 ]", + "[ 7/2 → 11/3 | freq:330 ]", + "[ 11/3 → 23/6 | freq:352 ]", + "[ 23/6 → 4/1 | freq:440 ]", +] +`; + exports[`runs examples > example "xfade" example index 0 1`] = ` [ "[ 0/1 → 1/8 | s:hh gain:0 ]", diff --git a/website/src/pages/learn/xen.mdx b/website/src/pages/learn/xen.mdx index b32c93c85..a661c7b08 100644 --- a/website/src/pages/learn/xen.mdx +++ b/website/src/pages/learn/xen.mdx @@ -13,6 +13,7 @@ import { JsDoc } from '../../docs/JsDoc'; These functions allow the use of scales other than your typical chromatic 12 based ones. ### tune(scale) + {/* TODO (maybe): combine jsdoc things in tune.mjs with here */} @@ -115,4 +116,4 @@ You can also give tune a list of frequencies to use as the scale: {/* TODO add explanation of EDO to documentation */} - \ No newline at end of file + From 71f538b1153ad57674702d8dd0a4cb0f3258b47d Mon Sep 17 00:00:00 2001 From: tyow Date: Mon, 26 Jan 2026 17:50:38 -0500 Subject: [PATCH 07/10] Small wording changes, bring up to date with main, uzu/strudel#1944 --- packages/xen/xen.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/xen/xen.mjs b/packages/xen/xen.mjs index 7e042228e..83f8afc28 100644 --- a/packages/xen/xen.mjs +++ b/packages/xen/xen.mjs @@ -64,7 +64,7 @@ function xenOffset(xenScale, offset, index = 0) { // scaleNameOrRatios: string || number[], steps?: number /** - * Assumes a numerical pattern of EDO steps. Accepts all scale names of `tune` as well as any arbitrary edo scale. Returns a new pattern with all values mapped to their associated frequency, assuming a base frequency of 220hz. + * Assumes a numerical pattern of scale steps, and a scale. Scales accepted are all preset scale names of `tune`, arbitrary edos such as 31edo, or an array of frequency ratios. Returns a new pattern with all values mapped to their associated frequency, assuming a base frequency of 220hz. * * @name xen * @returns Pattern @@ -72,7 +72,7 @@ function xenOffset(xenScale, offset, index = 0) { * @param {(string | number[] )} scaleNameOrRatios * @tags tonal * @example - * // A major tried in 31edo: + * // A major triad in 31edo: * "0 8 18".xen("31edo").freq().piano() * @example * // You can also use xen with frequency ratios. From a09349e45729f53f6b8cccb89bdb0b810b3fa08e Mon Sep 17 00:00:00 2001 From: tyow Date: Mon, 26 Jan 2026 17:52:25 -0500 Subject: [PATCH 08/10] add note about octave assumption uzu/strudel#1944 --- packages/xen/xen.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xen/xen.mjs b/packages/xen/xen.mjs index 83f8afc28..775baef81 100644 --- a/packages/xen/xen.mjs +++ b/packages/xen/xen.mjs @@ -64,7 +64,7 @@ function xenOffset(xenScale, offset, index = 0) { // scaleNameOrRatios: string || number[], steps?: number /** - * Assumes a numerical pattern of scale steps, and a scale. Scales accepted are all preset scale names of `tune`, arbitrary edos such as 31edo, or an array of frequency ratios. Returns a new pattern with all values mapped to their associated frequency, assuming a base frequency of 220hz. + * Assumes a numerical pattern of scale steps, and a scale. Scales accepted are all preset scale names of `tune`, arbitrary edos such as 31edo, or an array of frequency ratios. Assumes scales repeat at octave (2/1). Returns a new pattern with all values mapped to their associated frequency, assuming a base frequency of 220hz. * * @name xen * @returns Pattern From 207ce9cbecd163902472bc168aa4ae9238cba1de Mon Sep 17 00:00:00 2001 From: tyow Date: Fri, 30 Jan 2026 16:59:33 -0500 Subject: [PATCH 09/10] added frequency rounding --- packages/xen/xen.mjs | 4 ++- test/__snapshots__/examples.test.mjs.snap | 40 +++++++++++------------ 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/packages/xen/xen.mjs b/packages/xen/xen.mjs index 775baef81..3c627f116 100644 --- a/packages/xen/xen.mjs +++ b/packages/xen/xen.mjs @@ -96,7 +96,9 @@ function xenOffset(xenScale, offset, index = 0) { export const xen = register('xen', function (scaleNameOrRatios, pat) { return pat.withHap((hap) => { const scale = getXenScale(scaleNameOrRatios); - const frequency = xenOffset(scale, parseNumeral(hap.value)); + let frequency = xenOffset(scale, parseNumeral(hap.value)); + // 10 is somewhat arbitrary + frequency = parseFloat(frequency.toPrecision(10)) return hap.withValue(() => frequency); }); }); diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index f4ccec78a..bdd16f1c1 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -14094,34 +14094,34 @@ exports[`runs examples > example "wtphaserand" example index 0 1`] = ` exports[`runs examples > example "xen" example index 0 1`] = ` [ "[ 0/1 → 1/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]", - "[ 1/3 → 2/3 | freq:263.09212028971103 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", - "[ 2/3 → 1/1 | freq:329.01393411660587 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", + "[ 1/3 → 2/3 | freq:263.0921203 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", + "[ 2/3 → 1/1 | freq:329.0139341 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", "[ 1/1 → 4/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]", - "[ 4/3 → 5/3 | freq:263.09212028971103 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", - "[ 5/3 → 2/1 | freq:329.01393411660587 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", + "[ 4/3 → 5/3 | freq:263.0921203 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", + "[ 5/3 → 2/1 | freq:329.0139341 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", "[ 2/1 → 7/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]", - "[ 7/3 → 8/3 | freq:263.09212028971103 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", - "[ 8/3 → 3/1 | freq:329.01393411660587 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", + "[ 7/3 → 8/3 | freq:263.0921203 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", + "[ 8/3 → 3/1 | freq:329.0139341 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", "[ 3/1 → 10/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]", - "[ 10/3 → 11/3 | freq:263.09212028971103 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", - "[ 11/3 → 4/1 | freq:329.01393411660587 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", + "[ 10/3 → 11/3 | freq:263.0921203 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", + "[ 11/3 → 4/1 | freq:329.0139341 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", ] `; exports[`runs examples > example "xen" example index 1 1`] = ` [ "[ 0/1 → 1/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]", - "[ 1/3 → 2/3 | freq:263.09212028971103 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", - "[ 2/3 → 1/1 | freq:329.01393411660587 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", + "[ 1/3 → 2/3 | freq:263.0921203 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", + "[ 2/3 → 1/1 | freq:329.0139341 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", "[ 1/1 → 4/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]", - "[ 4/3 → 5/3 | freq:263.09212028971103 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", - "[ 5/3 → 2/1 | freq:329.01393411660587 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", + "[ 4/3 → 5/3 | freq:263.0921203 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", + "[ 5/3 → 2/1 | freq:329.0139341 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", "[ 2/1 → 7/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]", - "[ 7/3 → 8/3 | freq:263.09212028971103 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", - "[ 8/3 → 3/1 | freq:329.01393411660587 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", + "[ 7/3 → 8/3 | freq:263.0921203 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", + "[ 8/3 → 3/1 | freq:329.0139341 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", "[ 3/1 → 10/3 | freq:220 clip:1 s:piano release:0.1 pan:0.5138888888888888 ]", - "[ 10/3 → 11/3 | freq:263.09212028971103 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", - "[ 11/3 → 4/1 | freq:329.01393411660587 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", + "[ 10/3 → 11/3 | freq:263.0921203 clip:1 s:piano release:0.1 pan:0.5277777777777778 ]", + "[ 11/3 → 4/1 | freq:329.0139341 clip:1 s:piano release:0.1 pan:0.5462962962962963 ]", ] `; @@ -14129,25 +14129,25 @@ exports[`runs examples > example "xen" example index 2 1`] = ` [ "[ 0/1 → 1/6 | freq:220 ]", "[ 1/6 → 1/3 | freq:275 ]", - "[ 1/3 → 1/2 | freq:293.33333333333616 ]", + "[ 1/3 → 1/2 | freq:293.3333333 ]", "[ 1/2 → 2/3 | freq:330 ]", "[ 2/3 → 5/6 | freq:352 ]", "[ 5/6 → 1/1 | freq:440 ]", "[ 1/1 → 7/6 | freq:220 ]", "[ 7/6 → 4/3 | freq:275 ]", - "[ 4/3 → 3/2 | freq:293.33333333333616 ]", + "[ 4/3 → 3/2 | freq:293.3333333 ]", "[ 3/2 → 5/3 | freq:330 ]", "[ 5/3 → 11/6 | freq:352 ]", "[ 11/6 → 2/1 | freq:440 ]", "[ 2/1 → 13/6 | freq:220 ]", "[ 13/6 → 7/3 | freq:275 ]", - "[ 7/3 → 5/2 | freq:293.33333333333616 ]", + "[ 7/3 → 5/2 | freq:293.3333333 ]", "[ 5/2 → 8/3 | freq:330 ]", "[ 8/3 → 17/6 | freq:352 ]", "[ 17/6 → 3/1 | freq:440 ]", "[ 3/1 → 19/6 | freq:220 ]", "[ 19/6 → 10/3 | freq:275 ]", - "[ 10/3 → 7/2 | freq:293.33333333333616 ]", + "[ 10/3 → 7/2 | freq:293.3333333 ]", "[ 7/2 → 11/3 | freq:330 ]", "[ 11/3 → 23/6 | freq:352 ]", "[ 23/6 → 4/1 | freq:440 ]", From 14be1ddf7fd7fd262b97e617839054f6d805766e Mon Sep 17 00:00:00 2001 From: tyow Date: Fri, 30 Jan 2026 17:22:52 -0500 Subject: [PATCH 10/10] code format --- packages/xen/xen.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/xen/xen.mjs b/packages/xen/xen.mjs index 3c627f116..54b81a2b9 100644 --- a/packages/xen/xen.mjs +++ b/packages/xen/xen.mjs @@ -97,8 +97,8 @@ export const xen = register('xen', function (scaleNameOrRatios, pat) { return pat.withHap((hap) => { const scale = getXenScale(scaleNameOrRatios); let frequency = xenOffset(scale, parseNumeral(hap.value)); - // 10 is somewhat arbitrary - frequency = parseFloat(frequency.toPrecision(10)) + // 10 is somewhat arbitrary + frequency = parseFloat(frequency.toPrecision(10)); return hap.withValue(() => frequency); }); });