Skip to content

Commit

Permalink
Upgrade temperaments dependency for lightweight algorithms for large …
Browse files Browse the repository at this point in the history
…subgroups

ref #397
  • Loading branch information
frostburn committed Jan 7, 2023
1 parent eff139b commit b33a8e7
Show file tree
Hide file tree
Showing 7 changed files with 320 additions and 66 deletions.
238 changes: 194 additions & 44 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
"jszip": "^3.10.1",
"moment-of-symmetry": "github:xenharmonic-devs/moment-of-symmetry#v0.2.1",
"qs": "^6.11.0",
"temperaments": "github:xenharmonic-devs/temperaments#v0.3.0",
"scale-workshop-core": "github:xenharmonic-devs/scale-workshop-core#v0.0.1",
"temperaments": "^0.4.2",
"vue": "^3.2.33",
"vue-router": "^4.1.5",
"webmidi": "^3.0.21",
"xen-dev-utils": "github:xenharmonic-devs/xen-dev-utils#v0.1.0",
"scale-workshop-core": "github:xenharmonic-devs/scale-workshop-core#v0.0.1"
"xen-dev-utils": "^0.1.2"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.2.0",
Expand Down
33 changes: 32 additions & 1 deletion src/__tests__/tempering.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,40 @@ import {
makeRank2FromVals,
mosPatternsRank2FromCommas,
mosPatternsRank2FromVals,
toPrimeMapping,
} from "../tempering";
import { arraysEqual, Fraction, valueToCents } from "xen-dev-utils";
import {
arraysEqual,
Fraction,
PRIME_CENTS,
valueToCents,
} from "xen-dev-utils";
import { ExtendedMonzo, Interval, Scale } from "scale-workshop-core";
import { Subgroup } from "temperaments";
import { DEFAULT_NUMBER_OF_COMPONENTS } from "../constants";

describe("Prime map converter", () => {
it("does (almost) nothing in 5-limit JI", () => {
const fiveLimit = PRIME_CENTS.slice(0, 3);
const mapping = toPrimeMapping(fiveLimit, new Subgroup(5));
expect(mapping[0]).toBeCloseTo(fiveLimit[0]);
expect(mapping[1]).toBeCloseTo(fiveLimit[1]);
expect(mapping[2]).toBeCloseTo(fiveLimit[2]);
expect(mapping.length).toBe(DEFAULT_NUMBER_OF_COMPONENTS);
expect(mapping[3]).toBeCloseTo(PRIME_CENTS[3]);
});

it("converts a mapping in 2.3.13/5 to 2.3.5.7.11.13...", () => {
const original = [1200, 1901, 1654];
const mapping = toPrimeMapping(original, new Subgroup("2.3.13/5"));
expect(mapping[0]).toBeCloseTo(1200);
expect(mapping[1]).toBeCloseTo(1901);
expect(mapping[3]).toBeCloseTo(PRIME_CENTS[3]);
expect(mapping[4]).toBeCloseTo(PRIME_CENTS[4]);
expect(mapping[5] - mapping[2]).toBeCloseTo(1654);
expect(mapping[6]).toBeCloseTo(PRIME_CENTS[6]);
});
});

describe("Temperament Mapping", () => {
it("calculates POTE meantone", () => {
Expand Down
9 changes: 6 additions & 3 deletions src/components/modals/generation/RankTwo.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<script setup lang="ts">
import { DEFAULT_NUMBER_OF_COMPONENTS } from "@/constants";
import {
DEFAULT_NUMBER_OF_COMPONENTS,
MAX_INTERACTIVE_SUBGROUP_SIZE,
} from "@/constants";
import {
makeRank2FromVals,
makeRank2FromCommas,
Expand Down Expand Up @@ -100,7 +103,7 @@ const [mosPatterns, mosPatternsError] = computedAndError(() => {
return [];
}
// Huge subgroups get too expensive to evaluate interactively
if (subgroup.value.basis.length > 6) {
if (subgroup.value.basis.length > MAX_INTERACTIVE_SUBGROUP_SIZE) {
return expensiveMosPatterns.value;
}
return mosPatternsRank2FromVals(
Expand All @@ -116,7 +119,7 @@ const [mosPatterns, mosPatternsError] = computedAndError(() => {
return [];
}
// Huge subgroups get too expensive to evaluate interactively
if (subgroup.value.basis.length > 6) {
if (subgroup.value.basis.length > MAX_INTERACTIVE_SUBGROUP_SIZE) {
return expensiveMosPatterns.value;
}
return mosPatternsRank2FromCommas(
Expand Down
81 changes: 66 additions & 15 deletions src/components/modals/modification/TemperScale.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
<script setup lang="ts">
import { DEFAULT_NUMBER_OF_COMPONENTS } from "@/constants";
import { Mapping } from "@/tempering";
import { ref, watch } from "vue";
import {
DEFAULT_NUMBER_OF_COMPONENTS,
MAX_GEO_SUBGROUP_SIZE,
} from "@/constants";
import { Mapping, toPrimeMapping } from "@/tempering";
import { computed, ref, watch } from "vue";
import Modal from "@/components/ModalDialog.vue";
import { makeState, splitText } from "@/components/modals/tempering-state";
import { PRIME_CENTS } from "xen-dev-utils";
import type { Scale } from "scale-workshop-core";
import { tenneyVals, vanishCommas } from "temperaments";
const props = defineProps<{
scale: Scale;
Expand Down Expand Up @@ -40,6 +44,10 @@ const commas = state.commas;
const subgroup = state.subgroup;
const options = state.options;
const constraintsDisabled = computed(
() => subgroup.value.basis.length > MAX_GEO_SUBGROUP_SIZE
);
watch(subgroupError, (newValue) =>
subgroupInput.value!.setCustomValidity(newValue)
);
Expand All @@ -58,19 +66,60 @@ function modify() {
}
mapping = new Mapping(vector.slice(0, DEFAULT_NUMBER_OF_COMPONENTS));
} else if (method.value === "vals") {
mapping = Mapping.fromVals(
vals.value,
DEFAULT_NUMBER_OF_COMPONENTS,
subgroup.value,
options.value
);
if (constraintsDisabled.value) {
// Subgroup is too large to use geometric methods. Use O(n²) projection instead.
const weights = options.value.weights;
// True constraints are not supported so CTE is interpreted as POTE.
const temperEquaves =
options.value.temperEquaves && tempering.value !== "CTE";
const jip = subgroup.value.jip();
const valVectors = vals.value.map((val) =>
subgroup.value.fromWarts(val)
);
let mappingVector = tenneyVals(valVectors, jip, weights);
if (!temperEquaves) {
mappingVector = mappingVector.map(
(m) => (jip[0] * m) / mappingVector[0]
);
}
mapping = new Mapping(toPrimeMapping(mappingVector, subgroup.value));
} else {
mapping = Mapping.fromVals(
vals.value,
DEFAULT_NUMBER_OF_COMPONENTS,
subgroup.value,
options.value
);
}
} else {
mapping = Mapping.fromCommas(
commas.value,
DEFAULT_NUMBER_OF_COMPONENTS,
subgroup.value,
options.value
);
if (constraintsDisabled.value) {
// Subgroup is too large to use geometric methods. Use O(n) gradient descent instead.
// True constraints are not supported so CTE is interpreted as pure equaves.
const temperEquaves =
options.value.temperEquaves && tempering.value !== "CTE";
const weights = options.value.weights;
const jip = subgroup.value.jip();
const commaMonzos = commas.value.map(
(comma) => subgroup.value.toMonzoAndResidual(comma)[0]
);
const mappingVector = vanishCommas(
commaMonzos,
jip,
weights,
temperEquaves
);
mapping = new Mapping(toPrimeMapping(mappingVector, subgroup.value));
} else {
mapping = Mapping.fromCommas(
commas.value,
DEFAULT_NUMBER_OF_COMPONENTS,
subgroup.value,
options.value
);
}
}
emit(
"update:scale",
Expand Down Expand Up @@ -214,6 +263,7 @@ function modify() {
id="tempering-CTE"
value="CTE"
@focus="error = ''"
:disabled="constraintsDisabled"
v-model="tempering"
/>
<label for="tempering-CTE"> CTE </label>
Expand All @@ -225,6 +275,7 @@ function modify() {
<textarea
id="constraints"
@focus="error = ''"
:disabled="constraintsDisabled"
v-model="constraintsString"
></textarea>
</div>
Expand Down
7 changes: 7 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,10 @@ export const KORG = {

// Browser interaction
export const LEFT_MOUSE_BTN = 0;

// Sanity limits for tempering

// Anything larger than this isn't evaluated interactively
export const MAX_INTERACTIVE_SUBGROUP_SIZE = 6;
// Anything larger than this uses O(n²) methods (if available) instead of O(exp(n))
export const MAX_GEO_SUBGROUP_SIZE = 9;
12 changes: 12 additions & 0 deletions src/tempering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ import {
} from "xen-dev-utils";
import { Interval, Scale } from "scale-workshop-core";

export function toPrimeMapping(mapping: number[], subgroup: Subgroup) {
const result = subgroup.toPrimeMapping(mapping);

while (result.length > DEFAULT_NUMBER_OF_COMPONENTS) {
result.pop();
}
while (result.length < DEFAULT_NUMBER_OF_COMPONENTS) {
result.push(PRIME_CENTS[result.length]);
}
return result as number[];
}

export class Mapping {
vector: number[];

Expand Down

0 comments on commit b33a8e7

Please sign in to comment.