diff --git a/packages/xen/tune.mjs b/packages/xen/tune.mjs index 01303bf52..7584b7f2f 100644 --- a/packages/xen/tune.mjs +++ b/packages/xen/tune.mjs @@ -7,12 +7,42 @@ 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 + * @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)) { 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 r * freq); } 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') { 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 { throw new Error('unknown scale name: "' + scale + '"'); } @@ -47,15 +57,53 @@ function xenOffset(xenScale, offset, index = 0) { 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 + +/** + * 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 + * @memberof Pattern + * @param {(string | number[] )} scaleNameOrRatios + * @tags tonal + * @example + * // A major triad in 31edo: + * "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() + * @example + * // xen also supports all scale names that + * // tune does: + * "0 1 2 3 4 5".xen("hexany15").freq() + * // equiv to: + * // "0 1 2 3 4 5".tune("hexany15").mul("220").freq() + */ + +// 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); - 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); }); }); +// 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)); diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index b205e4e5f..7d7b12e1a 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -13215,6 +13215,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 ]", @@ -14127,6 +14218,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.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.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.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.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.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.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.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.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 2 1`] = ` +[ + "[ 0/1 → 1/6 | freq:220 ]", + "[ 1/6 → 1/3 | freq:275 ]", + "[ 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.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.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.3333333 ]", + "[ 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/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 b8b27ac7f..a661c7b08 100644 --- a/website/src/pages/learn/xen.mdx +++ b/website/src/pages/learn/xen.mdx @@ -1,17 +1,21 @@ --- -title: Xen Harmonic Functions +title: Xenharmonic Functions layout: ../../layouts/MainLayout.astro --- import { MiniRepl } from '../../docs/MiniRepl'; import { JsDoc } from '../../docs/JsDoc'; -# Xen Harmonic Functions +# 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) +{/* TODO (maybe): combine jsdoc things in tune.mjs with here */} + Here's an example of how to configure a basic hexany scale: @@ -93,3 +97,23 @@ 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. + +You can also give tune a list of frequencies to use as the scale: + + + +### xen(scaleOrRatios) + +{/* TODO add explanation of EDO to documentation */} + +