Skip to content

Commit

Permalink
WIP: Implement high prime limit tempering for single vals
Browse files Browse the repository at this point in the history
ref #331
  • Loading branch information
frostburn committed Nov 19, 2022
1 parent c33df91 commit 58905e5
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 36 deletions.
32 changes: 25 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"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",
"temperaments": "github:xenharmonic-devs/temperaments",
"vue": "^3.2.33",
"vue-router": "^4.1.5",
"webmidi": "^3.0.21",
Expand Down
2 changes: 2 additions & 0 deletions src/assets/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

--color-heading: #000;
--color-text: #000;
--color-text-mute: #777;

--color-accent: #008080;
--color-accent-text: #fff;
Expand Down Expand Up @@ -41,6 +42,7 @@

--color-heading: #fff;
--color-text: #fff;
--color-text-mute: #555;

--color-accent: #008080;
--color-accent-text: #fff;
Expand Down
137 changes: 109 additions & 28 deletions src/components/modals/modification/TemperScale.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
<script setup lang="ts">
import { DEFAULT_NUMBER_OF_COMPONENTS } from "@/constants";
import type Scale from "@/scale";
import { Mapping } from "@/tempering";
import { ref, watch } from "vue";
import Scale from "@/scale";
import { Mapping, stretchToEdo } 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 { Fraction, PRIME_CENTS } from "xen-dev-utils";
import { mapByVal } from "temperaments";
import { Interval, type IntervalOptions } from "@/interval";
import ExtendedMonzo from "@/monzo";
const props = defineProps<{
scale: Scale;
Expand All @@ -22,6 +25,7 @@ const state = makeState(method);
const mappingString = ref("1200, 1897.2143, 2788.8573");
// method: "vals"
const valsString = state.valsString;
const convertToEdoSteps = ref(false);
// medhod: "commas"
const commasString = state.commasString;
// Generic
Expand All @@ -39,6 +43,7 @@ const vals = state.vals;
const commas = state.commas;
const subgroup = state.subgroup;
const options = state.options;
const edoUnavailable = computed(() => vals.value.length !== 1);
watch(subgroupError, (newValue) =>
subgroupInput.value!.setCustomValidity(newValue)
Expand All @@ -48,36 +53,91 @@ watch(subgroupError, (newValue) =>
function modify() {
try {
let mapping: Mapping;
if (method.value === "mapping") {
const vector = splitText(mappingString.value).map((component) =>
parseFloat(component)
if (
method.value === "vals" &&
!edoUnavailable.value &&
!subgroupString.value.length
) {
// XXX: Use core's softer fraction converter. This throws errors.
const monzos = [...Array(props.scale.size + 1).keys()].map((i) =>
props.scale.getMonzo(i).toFraction()
);
while (vector.length < DEFAULT_NUMBER_OF_COMPONENTS) {
vector.push(PRIME_CENTS[vector.length]);
const octave = new Fraction(2);
monzos.push(octave);
const steps = mapByVal(monzos, vals.value[0]);
const edo = steps.pop();
let scale: Scale;
if (convertToEdoSteps.value) {
const options: IntervalOptions = {
preferredEtDenominator: edo,
preferredEtEquave: octave,
};
const equave = new Interval(
ExtendedMonzo.fromEqualTemperament(
new Fraction(steps.pop()!, edo),
octave,
DEFAULT_NUMBER_OF_COMPONENTS
),
"equal temperament",
undefined,
options
);
scale = new Scale(
steps.map(
(step) =>
new Interval(
ExtendedMonzo.fromEqualTemperament(
new Fraction(step, edo),
octave,
DEFAULT_NUMBER_OF_COMPONENTS
),
"equal temperament",
undefined,
options
)
),
equave,
props.scale.baseFrequency
);
} else {
scale = stretchToEdo(props.scale, steps, edo!);
}
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
emit(
"update:scale",
scale.mergeOptions({ centsFractionDigits: props.centsFractionDigits })
);
} else {
mapping = Mapping.fromCommas(
commas.value,
DEFAULT_NUMBER_OF_COMPONENTS,
subgroup.value,
options.value
let mapping: Mapping;
if (method.value === "mapping") {
const vector = splitText(mappingString.value).map((component) =>
parseFloat(component)
);
while (vector.length < DEFAULT_NUMBER_OF_COMPONENTS) {
vector.push(PRIME_CENTS[vector.length]);
}
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
);
} else {
mapping = Mapping.fromCommas(
commas.value,
DEFAULT_NUMBER_OF_COMPONENTS,
subgroup.value,
options.value
);
}
emit(
"update:scale",
mapping
.apply(props.scale)
.mergeOptions({ centsFractionDigits: props.centsFractionDigits })
);
}
emit(
"update:scale",
mapping
.apply(props.scale)
.mergeOptions({ centsFractionDigits: props.centsFractionDigits })
);
} catch (error_) {
if (error_ instanceof Error) {
error.value = error_.message;
Expand Down Expand Up @@ -150,6 +210,21 @@ function modify() {
v-model="valsString"
/>
</div>
<div class="control" v-show="method === 'vals'">
<div class="radio-group">
<span>
<input
type="checkbox"
id="edo-steps"
:disabled="edoUnavailable"
v-model="convertToEdoSteps"
/>
<label for="edo-steps" :class="{ disabled: edoUnavailable }">
Convert to edo-steps</label
>
</span>
</div>
</div>

<div class="control" v-show="method === 'commas'">
<label for="commas">Comma list</label>
Expand Down Expand Up @@ -249,3 +324,9 @@ function modify() {
</template>
</Modal>
</template>

<style>
.disabled {
color: var(--color-text-mute);
}
</style>
47 changes: 47 additions & 0 deletions src/tempering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,50 @@ export function makeRank2FromCommas(
}
return makeRank2(temperament, size, options);
}

export function stretchToEdo(
interval: Interval,
steps: number,
edo: number
): Interval;
export function stretchToEdo(scale: Scale, steps: number[], edo: number): Scale;
export function stretchToEdo(
intervalOrScale: Interval | Scale,
steps: number | number[],
edo: number
): Interval | Scale {
if (intervalOrScale instanceof Interval) {
if (Array.isArray(steps)) {
throw new Error("Steps must be a single number");
}
const interval = intervalOrScale;
const monzo = interval.monzo;
const totalCents = monzo.totalCents();
if (!totalCents) {
return interval;
}
const targetCents = (1200.0 * steps) / edo + monzo.cents;
const tempered = monzo.stretch(targetCents / totalCents);
return new Interval(
tempered,
interval.type,
interval.name,
interval.options
);
}
if (!Array.isArray(steps)) {
throw new Error("Steps must be an array of numbers");
}
const scale = intervalOrScale;
if (steps.length !== scale.size + 1) {
throw new Error("Steps must align with the scale");
}
const intervals = scale.intervals.map((interval, i) =>
stretchToEdo(interval, steps[i], edo)
);
return new Scale(
intervals,
stretchToEdo(scale.equave, steps[steps.length - 1], edo),
scale.baseFrequency
);
}

0 comments on commit 58905e5

Please sign in to comment.