User:ArrowHead294/Purely consistent EDOs by odd limit
Below is a table of the first 50 EDOs that are purely consistent (approximate all harmonics with <25% relative error) in the 7-, 15-, 31-, and 63-odd-limits.
The smallest EDOs purely consistent in the 7-, 15-, 31-, and 63-odd-limits are 10, 87, 311, and 3159811, respectively.
Additional results
The smallest EDOs purely consistent in the 7-, 15-, 31-, and 63-odd-limits are 10, 87, 311, and 3159811, respectively.
Additionally, there are (12) 7-odd-limit purely-consistent EDOs less than 87, (3) 15-odd-limit purely-consistent EDOs less than 311, and (481) 31-odd-limit purely consistent EDOs less than 3159811.
Code used
This is the JavaScript code I use to find purely-consistent EDOs by odd limit.
Original
function et_error(interval, et)
{
return [1200, et * 100].map(x => Math.round(x * 100 * (Math.round(et * Math.log2(interval)) / et - Math.log2(interval))) / 100).concat(Math.round(et * Math.log2(interval)) % et);
}
function min_et(h, max_err_pct, max_edos)
{
var a = 0, i = 1, j = 0, start = Date.now(), output = new Array();
h = Math.round(Math.abs(h));
h = h + 1 - (h % 2);
while (j <= max_edos)
{
a = 1;
for (var k = 3; k <= h; k += 2)
{
if (Math.abs(et_error(k, i)[1]) > max_err_pct)
{
a = 0;
}
}
if (a == 1)
{
j++;
console.log(i + " | " + (Date.now() - start)/1000 + "s");
}
i++;
}
}
Current
(Note: This code was written with the assistance of Claude Opus 5, an artificial intelligence tool.)
/**
* 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;
}
These lists were generated using JavaScript code run in Mozilla Firefox's console. The amount of time it took me to generate these lists is as follows:
- 7-odd-limit: 5ms
- 15-odd-limit: 62ms
- 31-odd-limit: 11.9s
- 63-odd-limit: 2h 53m 26.6s