xen.mjs - <short description TODO> Copyright (C) 2022 Strudel contributors - see https://codeberg.org/uzu/strudel/src/branch/main/packages/xen/xen.mjs 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/.
returns a list of frequency ratios for given edo scale
Given a base frequency such as 220 and an edo scale, returns an array of frequencies representing the given edo scale in that base
Assumes a base of 220. Returns a filtered scale based on 'indices' NOTE: indices functionality is unused
35function getXenScale(scale, indices) { 36 let tune = new Tune(); 37 if (typeof scale === 'string') { 38 if (isEdo(scale)) { 39 scale = edo(scale); 40 } else if (presets[scale]) { 41 scale = presets[scale]; 42 } else if (tune.isValidScale(scale)) { 43 tune.loadScale(scale); 44 scale = tune.scale; 45 } else { 46 throw new Error('unknown scale name: "' + scale + '"'); 47 } 48 } 49 scale = _withBase(defaultBase, scale); 50 if (!indices) { 51 return scale; 52 } 53 return scale.filter((_, i) => indices.includes(i)); 54}
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 minor triad in 31edo: i("0 8 18").xen("31edo").piano() @example // You can also use xen with frequency ratios. // This is equivalent to the above: i("0 1 2").xen([ Math.pow(2, 0/31), Math.pow(2, 8/31), Math.pow(2, 18/31), ]).piano() @example // xen also supports all scale names that // tune does: i("0 1 2 3 4 5").xen("hexany15") // equiv to: // "0 1 2 3 4 5".tune("hexany15").mul("220").freq() @example i("0 1 2 3 4 5 6 7").xen("<5edo 10edo 15edo hexany15>")
99export const xen = register('xen', function (scaleNameOrRatios, pat) { 100 return pat.withHaps((haps) => { 101 haps = haps.map((hap) => { 102 let hVal = hap.value; 103 const isObject = typeof hVal === 'object'; 104 if (!isObject) { 105 throw new Error(`Expected hap to have control 'i' set, but received ${hap.value.i}, try wrapping input in i()`); 106 } 107 const { i, ...otherValues } = hVal; 108 const scale = getXenScale(scaleNameOrRatios); 109 let freq = xenOffset(scale, parseNumeral(hVal.i)); 110 // 10 is somewhat arbitrary 111 freq = trimFreq(freq); 112 hap.value = { ...otherValues, freq }; 113 return isEdo(scaleNameOrRatios) 114 ? hap.setContext({ ...hap.context, edoSize: scaleNameOrRatios.match(/^([1-9]+[0-9]*)edo$/)[1] }) 115 : hap; 116 }); 117 return removeUndefineds(haps); 118 }); 119});
Assumes pattern of frequencies tuned to some base frequency, such as the output of xen
Because xen defaults to 220Hz, so will withBase.
but you can specify a different original base with the standard optional array syntax ':'
@name withBase
@param {number} base
@param {number} (optional) originalBase
@tags tonal
@example 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()
@returns Pattern
138export const withBase = register('withBase', (b, pat) => { 139 let base; 140 let originalBase = 220; 141 if (Array.isArray(b)) { 142 base = b[0]; 143 originalBase = b[1]; 144 } else { 145 base = b; 146 } 147 return pat.withHaps((haps) => { 148 haps = haps.map((hap) => { 149 let hVal = hap.value; 150 const isObject = typeof hVal === 'object'; 151 let freq = isObject ? hVal.freq : hVal; 152 if (!freq) return hap; 153 freq = (freq * base) / originalBase; 154 hap.value = isObject ? { ...hap.value, freq } : { freq }; 155 return hap; 156 }); 157 return removeUndefineds(haps); 158 }); 159});
Frequency transpose. Assumes pattern either has freq set, or has values that can be interpreted as frequencies
amt has optional edoSize param, defaults to 12.
If haps have edoSize param set, such as from the output of xen("31edo"),
ftrans will fallback to that instead of 12 as the default.
Transposes the frequency by amt edoSteps
@name ftranspose
@synonyms ftrans, fTrans, ftranspose, fTranspose
@tags tonal
@param {number} amt
@param {number} edoSize (optional)
@returns {Pattern}
@example i("0 1 2").xen("12edo").ftrans("7") // n("0 1 2").scale("A:chromatic").trans("7") @example i("0 8 18").xen("31edo").ftrans("<8 -8>") @example // to transpose by steps of an edo, use "step:edo" : i("0 7 8 18").xen("31edo").ftrans("<0 1:31 1:12>") @example // it can also work with frequency values directly freq("200 300 400").ftrans("<0 7:31 7>")
f = frequency (Hz) n = edo (steps per octave) x = number of steps if 0\n = f, then x\n = f * 2^(x/n) example: 5edo, 0\5 = 220 Hz, then 2\5 = 220*2^(2/5) = 290.29 Hz
194export const { ftrans, fTrans, ftranspose, fTranspose } = register( 195 ['ftrans', 'fTrans', 'ftranspose', 'fTranspose'], 196 (amt, pat) => { 197 let edoSize; 198 let numSteps; 199 if (Array.isArray(amt)) { 200 edoSize = amt[1]; 201 numSteps = amt[0]; 202 } else { 203 numSteps = amt; 204 } 205 return pat.withHaps((haps) => { 206 haps = haps.map((hap) => { 207 let hVal = hap.value; 208 const isObject = typeof hVal === 'object'; 209 hVal = isObject ? hVal : { freq: hVal }; 210 let { freq, ...otherValues } = hVal; 211 if (edoSize == undefined && hap.context.edoSize != undefined) { 212 edoSize = hap.context.edoSize; 213 } else if (edoSize == undefined) { 214 edoSize = 12; 215 } 216 freq = freq * Math.pow(2, numSteps / edoSize); 217 freq = trimFreq(freq); 218 hap.value = isObject ? { ...otherValues, freq } : freq; 219 return hap.setContext({ ...hap.context, edoSize }); 220 }); 221 return removeUndefineds(haps); 222 }); 223 }, 224);
not sure there's a point to having this and the above, seems like a proto version of the above.