1/* 2pitches.mjs - defines Pitches for equal division of the octave (EDO) scale 3 - Port of pitfalls/lib/Pitches.lua - see <https://github.com/robmckinnon/pitfalls/blob/main/lib/Pitches.lua> 4Copyright (C) 2025 Rob McKinnon and Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/edo/pitches.mjs> 5This 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/>. 6*/ 7 8function ratio(division, edivisions) { 9 return division === 0 ? 1 : Math.pow(2, division / edivisions); 10} 11 12// This function gets frequency based on index, edo, octave, and base frequency 13function get_freq(base_freq, edo, index, oct, base_octave) { 14 let f = base_freq * ratio(index - 1, edo); 15 if (oct < base_octave) { 16 f /= Math.pow(2, base_octave - oct); 17 } else if (oct > base_octave) { 18 f *= Math.pow(2, oct - base_octave); 19 } 20 return f; 21} 22 23// Convert MIDI number to Hz using a given tuning multiplier 24function midi_to_hz(n, tuning) { 25 return tuning * Math.pow(2, (n - 69) / 12.0); 26} 27 28const denom = Math.log(2); 29function hz_to_midi(freq, tuning) { 30 return 12.0 * (Math.log(freq / tuning) / denom) + 69; 31} 32 33export class Pitches { 34 constructor(scale, intervals, tuning, midi_start, root_octave) { 35 this.scale = scale; 36 this.intervals = intervals; 37 this.base_freq = midi_to_hz(midi_start, tuning); 38 this.root_octave = root_octave; 39 this.freqs = {}; 40 this.midis = {}; 41 this.degrees = {}; 42 this.octdegfreqs = {}; 43 this.octdegmidis = {}; 44 let index = 0; 45 let f = null; 46 47 for (let oct = 0; oct <= 8; oct++) { 48 this.octdegfreqs[oct] = {}; 49 this.octdegmidis[oct] = {}; 50 f = get_freq(this.base_freq, scale.edivisions, scale.tonic, oct, this.root_octave); 51 for (let deg = 0; deg < scale.length; deg++) { 52 index = index + 1; 53 this.freqs[index] = parseFloat((f * intervals.ratio(deg)).toFixed(3)); 54 this.midis[index] = parseFloat(hz_to_midi(f * intervals.ratio(deg), tuning).toFixed(4)); 55 this.degrees[index] = deg; 56 this.octdegfreqs[oct][deg + 1] = this.freqs[index]; 57 this.octdegmidis[oct][deg + 1] = this.midis[index]; 58 } 59 } 60 this.base_freq = parseFloat(this.base_freq).toFixed(4); 61 } 62 63 base_freq() { 64 return this.base_freq; 65 } 66 67 degree(index) { 68 return this.degrees[index]; 69 } 70 71 freq(index) { 72 return this.freqs[index]; 73 } 74 75 octdeg(deg) { 76 const higherOcatve = deg > this.scale.length; 77 const octave = this.root_octave + (higherOcatve ? Math.floor((deg - 1) / this.scale.length) : 0); 78 const degree = higherOcatve ? (deg % this.scale.length === 0 ? this.scale.length : deg % this.scale.length) : deg; 79 // console.log([octave, degree]); 80 return [octave, degree]; 81 } 82 83 octdegfreq(oct, deg) { 84 if (this.octdegfreqs[oct]) { 85 return this.octdegfreqs[oct][deg]; 86 } else { 87 return null; 88 } 89 } 90 91 octdegmidi(oct, deg) { 92 if (this.octdegmidis[oct]) { 93 return this.octdegmidis[oct][deg]; 94 } else { 95 return null; 96 } 97 } 98}