Skip to content

Commit

Permalink
Make it possible to create non-integer equal temperaments as cETs
Browse files Browse the repository at this point in the history
Disable relative steps and absolute degrees inputs when divisions is not an integer.

ref #326
  • Loading branch information
frostburn committed Nov 26, 2023
1 parent 9e08e53 commit 7fbdf60
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 36 deletions.
2 changes: 2 additions & 0 deletions src/components/ScaleBuilder.vue
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,8 @@ function copyToClipboard() {

<EqualTemperamentModal
:show="showEqualTemperamentModal"
:centsFractionDigits="centsFractionDigits"
:decimalFractionDigits="decimalFractionDigits"
@update:scaleName="emit('update:scaleName', $event)"
@update:scale="
showEqualTemperamentModal = false;
Expand Down
109 changes: 73 additions & 36 deletions src/components/modals/generation/EqualTemperament.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import { splitText } from "@/components/modals/tempering-state";
import { clamp } from "xen-dev-utils";
import { ExtendedMonzo, Interval, Scale } from "scale-workshop-core";
const props = defineProps<{
centsFractionDigits: number;
decimalFractionDigits: number;
}>();
const emit = defineEmits(["update:scale", "update:scaleName", "cancel"]);
const octave = new Interval(
Expand All @@ -21,41 +26,41 @@ const equave = ref(octave);
const jumpsString = ref("1 1 1 1 1");
const degreesString = ref("1 2 3 4 5");
const jumpsElement = ref<HTMLTextAreaElement | null>(null);
const degreesElement = ref<HTMLTextAreaElement | null>(null);
const divisionsElement = ref<HTMLInputElement | null>(null);
const singleStepOnly = computed(
() => divisions.value !== Math.round(divisions.value) || divisions.value < 1
);
const safeScaleSize = computed(() =>
Math.round(clamp(1, 1024, divisions.value))
);
watch(divisions, (newValue) => {
if (isNaN(newValue) || newValue === 0) {
divisionsElement.value!.setCustomValidity("Step size is zero");
} else {
divisionsElement.value!.setCustomValidity("");
}
});
const jumps = computed(() =>
splitText(jumpsString.value).map((token) => parseInt(token))
);
const degrees = computed(() =>
splitText(degreesString.value).map((token) => parseInt(token))
);
watch(jumps, (newValue) => {
if (newValue.includes(NaN)) {
jumpsElement.value!.setCustomValidity("Step size not an integer");
} else {
jumpsElement.value!.setCustomValidity("");
}
});
watch(degrees, (newValue) => {
if (newValue.includes(NaN)) {
degreesElement.value!.setCustomValidity("Degree not an integer");
function updateFromDivisions() {
if (singleStepOnly.value) {
jumpsString.value = "";
degreesString.value = "";
} else {
degreesElement.value!.setCustomValidity("");
jumpsString.value = Array(safeScaleSize.value).fill("1").join(" ");
degreesString.value = [...Array(safeScaleSize.value).keys()]
.map((k) => (k + 1).toString())
.join(" ");
}
});
function updateFromDivisions() {
jumpsString.value = Array(safeScaleSize.value).fill("1").join(" ");
degreesString.value = [...Array(safeScaleSize.value).keys()]
.map((k) => (k + 1).toString())
.join(" ");
}
function updateFromJumps() {
Expand Down Expand Up @@ -87,16 +92,37 @@ function updateFromDegrees() {
}
function generate() {
// Implicit use of safeScaleSize. Note that small subsets of huge EDOs cause no issues.
const scale = Scale.fromEqualTemperamentSubset(degrees.value, equave.value);
// Obtain effective divisions from the scale just generated.
const effectiveDivisions =
scale.getInterval(0).options.preferredEtDenominator;
emit(
"update:scaleName",
`${effectiveDivisions} equal divisions of ${equave.value.toString()}`
);
emit("update:scale", scale);
const lineOptions = {
centsFractionDigits: props.centsFractionDigits,
decimalFractionDigits: props.decimalFractionDigits,
};
if (singleStepOnly.value) {
const stepCents = equave.value.totalCents() / divisions.value;
const scale = Scale.fromIntervalArray([
new Interval(
ExtendedMonzo.fromCents(stepCents, DEFAULT_NUMBER_OF_COMPONENTS),
"cents",
undefined,
lineOptions
),
]);
emit("update:scaleName", `${scale.equave.name} cET`);
emit("update:scale", scale);
} else {
// Implicit use of safeScaleSize. Note that small subsets of huge EDOs cause no issues.
const scale = Scale.fromEqualTemperamentSubset(
degrees.value,
equave.value.mergeOptions(lineOptions)
);
// Obtain effective divisions from the scale just generated.
const effectiveDivisions =
scale.getInterval(0).options.preferredEtDenominator;
emit(
"update:scaleName",
`${effectiveDivisions} equal divisions of ${equave.value.toString()}`
);
emit("update:scale", scale);
}
}
</script>

Expand All @@ -110,28 +136,33 @@ function generate() {
<div class="control">
<label for="divisions">Number of divisions</label>
<input
ref="divisionsElement"
id="divisions"
type="number"
min="1"
step="any"
v-model="divisions"
@input="updateFromDivisions"
/>
</div>
<div class="control">
<label for="jumps">Relative steps</label>
<label for="jumps" :class="{ disabled: singleStepOnly }"
>Relative steps</label
>
<textarea
ref="jumpsElement"
id="jumps"
v-model="jumpsString"
:disabled="singleStepOnly"
@input="updateFromJumps"
></textarea>
</div>
<div class="control">
<label for="degrees">Absolute degrees</label>
<label for="degrees" :class="{ disabled: singleStepOnly }"
>Absolute degrees</label
>
<textarea
ref="degreesElement"
id="degrees"
v-model="degreesString"
:disabled="singleStepOnly"
@input="updateFromDegrees"
></textarea>
</div>
Expand All @@ -147,3 +178,9 @@ function generate() {
</template>
</Modal>
</template>

<style scoped>
label.disabled {
color: var(--color-text-mute);
}
</style>

0 comments on commit 7fbdf60

Please sign in to comment.