User:ArrowHead294/Purely consistent EDOs by odd limit/Appendix: Difference between revisions
ArrowHead294 (talk | contribs) mNo edit summary |
ArrowHead294 (talk | contribs) m Updated version, optimising so I don't have to call the error function all the time |
||
| Line 24: | Line 24: | ||
This is the {{w|JavaScript}} code I use to find purely-consistent EDOs by odd limit. | This is the {{w|JavaScript}} code I use to find purely-consistent EDOs by odd limit. | ||
=== Original === | |||
<syntaxhighlight lang="javascript"> | <syntaxhighlight lang="javascript"> | ||
function et_error(interval, et) | function et_error(interval, et) | ||
| Line 53: | Line 54: | ||
i++; | i++; | ||
} | } | ||
} | |||
</syntaxhighlight> | |||
=== Current === | |||
(Note: This code was written with the assistance of Claude Opus 5, an artificial intelligence tool.) | |||
<syntaxhighlight lang="javascript"> | |||
/** | |||
* Generates a list of equal divisions of the octave that approximate the harmonic series well. | |||
* | |||
* An EDO qualifies when every odd harmonic from 3 to `h` falls within `thresh` of one of its steps, measured as a fraction of | |||
* a step. Even harmonics are skipped: they are octave transpositions of odd ones, and the octave is exact in any EDO. | |||
* | |||
* Searches upward without an upper bound, logging each hit with the elapsed time, and returns once `max_edos` have been | |||
* found. Qualifying EDOs always exist, but they thin out fast, so high `h` can take a very long time to find. | |||
* | |||
* @param {Number} h - Highest harmonic to check. Internally this value is rounded up to the current or next highest odd | |||
* number. This means inputting 32 and 33 will both check through 33. Anything below 3 accepts every EDO. | |||
* Usually this is set to one less than a power of two (e.g. 7, 15, 31, or 63) | |||
* @param {Number} max_edos - How many EDOs to find before stopping. | |||
* @param {Number} [start=1] - Lowest EDO to consider; "1 equal division of the octave" is simply whole-octave steps. | |||
* @param {Number} [thresh=0.25] - Tolerance as a fraction of a step. Defaults to 0.25 | |||
* @returns {Number[]} - The list of EDOs found, in ascending order. | |||
*/ | |||
function min_et(h, max_edos, start = 1, thresh = 0.25) | |||
{ | |||
h = Math.round(Math.abs(h)); | |||
h = h + 1 - (h % 2); | |||
start = Math.max(1, Math.round(start)); | |||
// Odd primes <=h | |||
let comp = new Int16Array(h + 1), primes = []; | |||
for (let p = 3; p <= h; p += 2) | |||
{ | |||
if (comp[p]) | |||
{ | |||
continue; | |||
} | |||
primes.push(p); | |||
for (let q = p * p; q <= h; q += 2 * p) | |||
{ | |||
comp[q] = 1; | |||
} | |||
} | |||
let m = primes.length, out = []; | |||
if (m === 0) | |||
{ | |||
for (let n = start; out.length < max_edos; n++) | |||
{ | |||
out.push(n); | |||
} | |||
return out; | |||
} | |||
// Per-prime tolerance: p, p^2, p^3, ... p^t collapse to |e_p| < thresh/t | |||
let logs = new Float64Array(m), tol = new Float64Array(m); | |||
for (let i = 0; i < m; i++) | |||
{ | |||
logs[i] = Math.log2(primes[i]); | |||
let t = 0; | |||
for (let q = primes[i]; q <= h; q *= primes[i]) | |||
{ | |||
t++; | |||
} | |||
tol[i] = thresh / t; | |||
} | |||
// Composites with >= 2 distinct factors, bucketed by largest factor | |||
let mixed = Array.from({length: m}, () => []); | |||
for (let k = 9; k <= h; k += 2) | |||
{ | |||
let n = k, top = -1, d = 0; | |||
let term = []; | |||
for (let i = 0; i < m && n > 1; i++) | |||
{ | |||
let p = primes[i]; | |||
if (n % p) | |||
{ | |||
continue; | |||
} | |||
let e = 0; | |||
while (n % p === 0) | |||
{ | |||
n /= p; | |||
e++; | |||
} | |||
term.push(i, e); | |||
top = i; | |||
d++; | |||
} | |||
if (d >= 2) | |||
{ | |||
mixed[top].push(Int32Array.from(term)); | |||
} | |||
} | |||
// 3-gap data for the 3-limit filter | |||
let err = new Float64Array(m); | |||
let a = logs[0], b = tol[0], L = 2 * b; // Target arc has length L | |||
let jm = 0, dm = 0, jn = 0, dn = 0; | |||
for (let j = 1; ; j++) | |||
{ | |||
let t = j * a; t -= Math.floor(t); | |||
if (!jm && t < L) | |||
{ | |||
jm = j; dm = t; | |||
} | |||
if (!jn && t > 1 - L) | |||
{ | |||
jn = j; dn = 1 - t; | |||
} | |||
if (jm && jn) | |||
{ | |||
break; | |||
} | |||
} | |||
let jmn = jm + jn, dmn = dm - dn, lim = L - dm; | |||
let N = start, x = start * a + b; x -= Math.floor(x); | |||
while (x >= L) | |||
{ | |||
N++; x += a - 1; | |||
if (x >= 1) | |||
{ | |||
x -= 1; | |||
} | |||
} | |||
// Search | |||
let t0 = Date.now(); | |||
let steps = 0; | |||
while (out.length < max_edos) | |||
{ | |||
err[0] = x - b; | |||
let ok = true; | |||
for (let i = 1; i < m; i++) | |||
{ | |||
let y = N * logs[i], e = y - Math.round(y); | |||
if (e >= tol[i] || e <= -tol[i]) | |||
{ | |||
ok = false; | |||
break; | |||
} | |||
err[i] = e; | |||
let g = mixed[i]; | |||
for (let t = 0; t < g.length; t++) | |||
{ | |||
let term = g[t]; | |||
let s = 0; | |||
for (let q = 0; q < term.length; q += 2) | |||
{ | |||
s += term[q + 1] * err[term[q]]; | |||
} | |||
s -= Math.round(s); | |||
if (s >= thresh || s <= -thresh) | |||
{ | |||
ok = false; | |||
break; | |||
} | |||
} | |||
if (!ok) | |||
{ | |||
break; | |||
} | |||
} | |||
if (ok) | |||
{ | |||
out.push(N); | |||
console.log(N + " | " + (Date.now() - t0) / 1000 + "s"); | |||
} | |||
// Jump to the next N that passes the 3-limit test | |||
if (x < lim) | |||
{ | |||
N += jm; | |||
x += dm; | |||
} | |||
else if (x >= dn) | |||
{ | |||
N += jn; | |||
x -= dn; | |||
} | |||
else | |||
{ | |||
N += jmn; x += dmn; | |||
} | |||
if ((++steps & 0xFFFFF) === 0) | |||
{ | |||
x = N * a + b; | |||
x -= Math.floor(x); | |||
} | |||
} | |||
return out; | |||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||