edo.mjsannotatededo.mjssource114 lines · 4.6 KB · raw

edo.mjs - Equal division of the octave (EDO) scale functions for strudel Copyright (C) 2025 Rob McKinnon and Strudel contributors - see https://codeberg.org/uzu/strudel/src/branch/main/packages/edo/edo.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/.

7import { register, pure, noteToMidi, isNote, tokenizeNote } from '@strudel/core';
8import { EdoScale } from './edoscale.mjs';
9import { Intervals } from './intervals.mjs';
10import { Pitches } from './pitches.mjs';
12const pitchesCache = new Map();

Turns numbers into notes in the given EDO scale (zero indexed).

An EDO scale definition looks like this:

e.g. C:LLsLLLs:2:1 <- this is the C major scale, 12 EDO

e.g. C:LLsLLL:3:1 <- this is the Gorgo 6 note scale, 16 EDO

An EDO scale, e.g. C:LLsLLLs:2:1, consists of a root note (e.g. C) followed by semicolon (':') and then a Large/small step notation sequence (e.g. LLsLLLs) followed by semicolon, then the large step size (e.g. 2) followed by semicolon, then the small step size (e.g. 1).

The number of divisions of the octave is calculated as the sum of the steps in the EDO scale definition.

e.g. C:LLsLLLs:2:1 is 2+2+1+2+2+2+1 = 12 EDO, 7 note scale

e.g. C:LLsLLL:3:1 is 3+3+1+3+3+3 = 16 EDO, 6 note scale

The root note defaults to octave 3, if no octave number is given.

@name edoScale @param {string} scale Definition of EDO scale. @returns Pattern @example n("0 2 4 6 4 2").edoScale("C:LLsLLLs:2:1") @example n("[0,7] 4 [2,7] 4") .edoScale("G2:<LLsLLL LLLLsL>:3:1") .s("piano")._pitchwheel() @example n(rand.range(0,5).segment(6)) .edoScale("<G2 C3>:LLsLL:3:1") .s("piano")._pitchwheel()

53export const edoScale = register(
54  'edoScale',
55  function (scaleDefinition, pat) {
56    // console.log(scaleDefinition);

if (Array.isArray(scale)) {

59    const key = scaleDefinition.flat().join(':');
60    // }
61    // console.log(scaleDefinition);
62    let pitches;
63    if (pitchesCache.has(key)) {
64      pitches = pitchesCache.get(key);
65    } else {
66      // console.log({ key });
67      const [base_note, sequence, large, small] = scaleDefinition;
68      const root_octave = tokenizeNote(base_note)[2] || 3;
69      // console.log({ root_octave });
70      const scale = new EdoScale(large, small, sequence);
71      const intervals = new Intervals(scale);
72      // console.log({ intervals });
73      pitches = new Pitches(scale, intervals, 440, noteToMidi(base_note), root_octave);
74      pitchesCache.set(key, pitches);
75      // console.log({ base_note: noteToMidi(base_note) });
76      // console.log({ sequence });
77      // console.log({ edivisions: scale.edivisions });
78      // console.log({ intervals });
79      // console.log({ pat });
80      // console.log({ pitches: pitches });
81    }
82    return pat
83      .fmap((value) => {
84        const isObject = typeof value === 'object';
85        const n = isObject ? value.n : value;
86        if (isObject) {
87          delete value.n; // remove n so it won't cause trouble
88        }
89        if (isNote(n)) {
90          // legacy..
91          return pure(n);
92        }
93        const deg = (typeof n === 'string' ? parseInt(n, 10) : Number.isInteger(n) ? n : Math.round(n)) + 1;
94
95        const [oct, degree] = pitches.octdeg(deg);
96        const freq = pitches.octdegfreq(oct, degree);
97        const note = pitches.octdegmidi(oct, degree);
98        const edo = pitches.scale.edivisions;
99        const root = pitches.base_freq;
100        const degreeIndexes = pitches.scale.divisions;
101        const intLabels = pitches.intervals.intLabels;
102        // const color = 'red';
103        value = pure(isObject ? { ...value, degree, degreeIndexes, intLabels, root, freq, edo } : note);
104        // value = pure(isObject ? { ...value, key } : note);
105        // value = pure(isObject ? { ...value, edo } : note);
106        // console.log({ value });
107        return value;
108      })
109      .outerJoin()
110      .withHap((hap) => hap.setContext({ ...hap.context, scaleDefinition }));
111  },
112  true,
113  true, // preserve tactus
114);