Skip to content

Commit

Permalink
Make it easier to obtain prime coordinates from the presets
Browse files Browse the repository at this point in the history
ref #19
  • Loading branch information
frostburn committed Jun 10, 2024
1 parent d40ecaa commit 6daea02
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 28 deletions.
22 changes: 18 additions & 4 deletions src/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
describe('Modulo val calculator', () => {
it('works for 24', () => {
const logs = LOG_PRIMES.slice(0, 24);
const mv = modVal(logs, 24);
const mv = modVal(0, logs, 24);
expect(mv).toEqual([
0, 14, 8, 19, 11, 17, 2, 6, 13, 21, 23, 5, 9, 10, 12, 16, 20, 22, 1, 4, 3,
7, 15, 18,
Expand All @@ -30,7 +30,7 @@ describe('Modulo val calculator', () => {

it('works for 72', () => {
const logs = LOG_PRIMES.slice(0, 72);
const mv = modVal(logs, 72);
const mv = modVal(0, logs, 72);
expect(mv).toEqual([
0, 42, 23, 58, 33, 50, 6, 18, 38, 62, 69, 15, 26, 31, 40, 52, 64, 67, 5,
11, 14, 22, 27, 34, 43, 47, 49, 53, 55, 59, 71, 2, 7, 9, 16, 17, 21, 25,
Expand Down Expand Up @@ -210,6 +210,20 @@ describe("Scott Dakota's PR24 lattice", () => {
]);
expect(edges).toHaveLength(0);
});

it('works with tritave-equivalence', () => {
const options = scottDakota24(1);
expect(options).toEqual({
horizontalCoordinates: [
29, 0, 33, 12, 8, 26, 31, 26, 8, 3, 5, 22, 29, 31, 34, 22, 17, 5, 3, 1,
1, 12, 17, 33,
],
verticalCoordinates: [
12, -0, -5, 16, -14, -14, 9, 14, 14, -9, -12, -16, -12, -9, -0, 16, 17,
12, 9, 5, -5, -16, -17, 5,
],
});
});
});

describe('Prime ring 72 coordinates', () => {
Expand Down Expand Up @@ -264,7 +278,7 @@ describe('Prime ring 72 coordinates', () => {

describe('Coordinate aligner', () => {
it('aligns PR72 horizontally by rotating', () => {
const options = primeRing72(undefined, false);
const options = primeRing72(0, undefined, false);
const lengths: number[] = [];
for (let i = 0; i < options.horizontalCoordinates.length; ++i) {
lengths.push(
Expand All @@ -290,7 +304,7 @@ describe('Coordinate aligner', () => {
});

it('aligns PR72 with a Tonnetz lattice by shearing', () => {
const options = primeRing72(undefined, false);
const options = primeRing72(0, undefined, false);
const lengths: number[] = [];
for (let i = 0; i < options.horizontalCoordinates.length; ++i) {
lengths.push(
Expand Down
22 changes: 21 additions & 1 deletion src/__tests__/lattice-3d.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ describe('Wilson-Grady-Pakkanen lattice', () => {
describe('Prime sphere coordinates', () => {
it('produces coordinates for the 11-limit', () => {
const {horizontalCoordinates, verticalCoordinates, depthwiseCoordinates} =
primeSphere(LOG_PRIMES.slice(0, 5));
primeSphere(0, LOG_PRIMES.slice(0, 5));
const coords: string[] = [];
for (let i = 0; i < 5; ++i) {
coords.push(
Expand All @@ -61,4 +61,24 @@ describe('Prime sphere coordinates', () => {
'1.968, 0.086, -0.237', // 11/8 does whatever
]);
});

it('produces tritave-equivalent coordinates for the 11-limit', () => {
const {horizontalCoordinates, verticalCoordinates, depthwiseCoordinates} =
primeSphere(1, LOG_PRIMES.slice(0, 5));
const coords: string[] = [];
for (let i = 0; i < 5; ++i) {
coords.push(
`${horizontalCoordinates[i].toFixed(3)}, ${verticalCoordinates[
i
].toFixed(3)}, ${depthwiseCoordinates[i].toFixed(3)}`
);
}
expect(coords).toEqual([
'1.680, 0.733, 0.000',
'0.000, 0.000, 0.000',
'1.976, -0.218, -0.000',
'0.867, -0.991, 0.000',
'0.589, 0.022, -0.912',
]);
});
});
50 changes: 31 additions & 19 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,12 +280,14 @@ function allUnique(vector: number[]) {

/**
* Calculate rounded logarithms modulo divisions. All forced to be unique.
* @param equaveIndex Index of the prime of equivalence.
* @param logs Logarithms of primes.
* @param divisions Number of divisions of the first prime.
* @param searchResolution Resolution for GPV search. Set to 0 to disable (default).
* @returns Array of steps for each prime modulo the number of divisions.
*/
export function modVal(
equaveIndex: number,
logs: number[],
divisions: number,
searchResolution = 0
Expand All @@ -296,21 +298,21 @@ export function modVal(
// Try to find a GPV.
for (let i = 0; i < searchResolution; ++i) {
const offset = (0.5 * i) / searchResolution;
const normalizer = (divisions + offset) / logs[0];
const normalizer = (divisions + offset) / logs[equaveIndex];
const modval = logs.map(l => mmod(Math.round(l * normalizer), divisions));
if (allUnique(modval)) {
return modval;
}
if (i) {
const normalizer = (divisions - offset) / logs[0];
const normalizer = (divisions - offset) / logs[equaveIndex];
const modval = logs.map(l => mmod(Math.round(l * normalizer), divisions));
if (allUnique(modval)) {
return modval;
}
}
}
// No GPV with unique entries found. Use force.
const normalizer = divisions / logs[0];
const normalizer = divisions / logs[equaveIndex];
const val = logs.map(l => Math.round(l * normalizer), divisions);
for (let i = 1; i < val.length; ++i) {
const reserved = new Set<number>();
Expand Down Expand Up @@ -353,12 +355,16 @@ export function kraigGrady9(equaveIndex = 0): LatticeOptions {

/**
* Compute prime ring 24 coordinates based on Scott Dakota's conventions.
* @param equaveIndex Index of the prime to use as the interval of equivalence.
* @param logs Logarithms of (formal) primes with the prime of equivalence first. Defaults to the actual primes.
* @returns An array of horizontal coordinates for each prime and the same for vertical coordinates.
*/
export function scottDakota24(logs?: number[]): LatticeOptions {
export function scottDakota24(
equaveIndex = 0,
logs?: number[]
): LatticeOptions {
logs ??= LOG_PRIMES.slice(0, 24);
const mv = modVal(logs, 24);
const mv = modVal(equaveIndex, logs, 24);
const horizontalCoordinates: number[] = [];
const verticalCoordinates: number[] = [];
for (const steps of mv) {
Expand All @@ -373,13 +379,18 @@ export function scottDakota24(logs?: number[]): LatticeOptions {

/**
* Compute prime ring 72 coordinates.
* @param equaveIndex Index of the prime to use as the interval of equivalence.
* @param logs Logarithms of (formal) primes with the prime of equivalence first. Defaults to the actual primes.
* @param round Round coordinates to nearest integers.
* @returns An array of horizontal coordinates for each prime and the same for vertical coordinates.
*/
export function primeRing72(logs?: number[], round = true): LatticeOptions {
export function primeRing72(
equaveIndex = 0,
logs?: number[],
round = true
): LatticeOptions {
logs ??= LOG_PRIMES.slice(0, 72);
const mv = modVal(logs, 72);
const mv = modVal(equaveIndex, logs, 72);
const horizontalCoordinates: number[] = [];
const verticalCoordinates: number[] = [];
for (const steps of mv) {
Expand Down Expand Up @@ -410,23 +421,24 @@ export function align(
tonnetzIndex?: number
) {
const {horizontalCoordinates, verticalCoordinates} = options;
const l = Math.max(horizontalCoordinates.length, verticalCoordinates.length);

if (tonnetzIndex === undefined) {
const x = horizontalCoordinates[horizontalIndex];
const y = verticalCoordinates[horizontalIndex];
const x = horizontalCoordinates[horizontalIndex] ?? 0;
const y = verticalCoordinates[horizontalIndex] ?? 0;
const c = 1 / Math.sqrt(1 + (y * y) / (x * x));
const s = (y / x) * c;
for (let i = 0; i < horizontalCoordinates.length; ++i) {
const u = horizontalCoordinates[i];
const v = verticalCoordinates[i];
for (let i = 0; i < l; ++i) {
const u = horizontalCoordinates[i] ?? 0;
const v = verticalCoordinates[i] ?? 0;
horizontalCoordinates[i] = u * c + v * s;
verticalCoordinates[i] = v * c - u * s;
}
} else {
const x1 = horizontalCoordinates[horizontalIndex];
const y1 = verticalCoordinates[horizontalIndex];
const x2 = horizontalCoordinates[tonnetzIndex];
const y2 = verticalCoordinates[tonnetzIndex];
const x1 = horizontalCoordinates[horizontalIndex] ?? 0;
const y1 = verticalCoordinates[horizontalIndex] ?? 0;
const x2 = horizontalCoordinates[tonnetzIndex] ?? 0;
const y2 = verticalCoordinates[tonnetzIndex] ?? 0;

const r1 = Math.hypot(x1, y1);
const R2 = x2 * x2 + y2 * y2;
Expand All @@ -446,9 +458,9 @@ export function align(
const a10 = (-r1 * x2 + u2 * x1) / (x1 * y2 - x2 * y1);
const a11 = (v2 * x1) / (x1 * y2 - x2 * y1);

for (let i = 0; i < horizontalCoordinates.length; ++i) {
const u = horizontalCoordinates[i];
const v = verticalCoordinates[i];
for (let i = 0; i < l; ++i) {
const u = horizontalCoordinates[i] ?? 0;
const v = verticalCoordinates[i] ?? 0;
horizontalCoordinates[i] = a00 * u + a10 * v;
verticalCoordinates[i] = a01 * u + a11 * v;
}
Expand Down
14 changes: 10 additions & 4 deletions src/lattice-3d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {dot, monzosEqual, sub} from 'xen-dev-utils';
import {LOG_PRIMES, dot, monzosEqual, sub} from 'xen-dev-utils';
import {EdgeType} from './types';
import {connect, project, unproject} from './utils';

Expand Down Expand Up @@ -236,16 +236,22 @@ export function WGP9(equaveIndex = 0): LatticeOptions3D {

/**
* Compute coordinates based on sizes of primes that lie on the surface of a sphere offset on the x-axis.
* @param logs Logarithms of (formal) primes with the prime of equivalence first.
* @param equaveIndex Index of the prime to use as the interval of equivalence.
* @param logs Logarithms of (formal) primes with the prime of equivalence first. Defaults to the first 24 actual primes.
* @param searchResolution Search resolution for optimizing orthogonality of the resulting set.
* @returns An array of horizontal coordinates for each prime and the same for vertical and depthwise coordinates.
*/
export function primeSphere(logs: number[], searchResolution = 1024) {
export function primeSphere(
equaveIndex = 0,
logs?: number[],
searchResolution = 1024
) {
logs ??= LOG_PRIMES.slice(0, 24);
const dp = (2 * Math.PI) / searchResolution;
const horizontalCoordinates: number[] = [];
const verticalCoordinates: number[] = [];
const depthwiseCoordinates: number[] = [];
const dt = (2 * Math.PI) / logs[0];
const dt = (2 * Math.PI) / logs[equaveIndex];
for (const log of logs) {
const theta = log * dt;
const x = 1 - Math.cos(theta);
Expand Down

0 comments on commit 6daea02

Please sign in to comment.