diff --git a/src/components/ScaleBuilder.vue b/src/components/ScaleBuilder.vue index 83266f4b..267cbed8 100644 --- a/src/components/ScaleBuilder.vue +++ b/src/components/ScaleBuilder.vue @@ -12,6 +12,8 @@ import { importFile, type ImporterKey } from '@/importers' import { mtof } from 'xen-dev-utils' import type { Scale } from 'scale-workshop-core' import { useStateStore } from '@/stores/state' +import { useModalStore } from '@/stores/modal' +import { useApproximateByRatiosStore } from '@/stores/approximate-by-ratios' // Export const KorgExportModal = defineAsyncComponent( @@ -95,6 +97,8 @@ const TemperModal = defineAsyncComponent( ) const state = useStateStore() +const modal = useModalStore() +const approx = useApproximateByRatiosStore() const joinedLines = computed({ get() { @@ -261,11 +265,21 @@ function clickInvert() { scaleDataArea.value!.focus() } +function clickRotate() { + modal.initialize(state.scale.size) + showRotateModal.value = true +} + function clickSubset() { - subsetModal.value.initialize() + modal.initialize(state.scale.size) showSubsetModal.value = true } +function clickApproximateByRatios() { + approx.initialize() + showApproximateByRatiosModal.value = true +} + function clickShareUrl() { showShareUrlModal.value = true shareUrlModal.value.initialize() @@ -345,13 +359,11 @@ function confirmPreset() {
  • Sort ascending
  • Reduce
  • Invert
  • -
  • Rotate
  • +
  • Rotate
  • Subset
  • Stretch/compress
  • Random variance
  • -
  • Approximate by ratios
  • +
  • Approximate by ratios
  • Approximate by harmonics
  • diff --git a/src/components/modals/generation/CombinationProductSet.vue b/src/components/modals/generation/CombinationProductSet.vue index c3f8f55e..7cbf711c 100644 --- a/src/components/modals/generation/CombinationProductSet.vue +++ b/src/components/modals/generation/CombinationProductSet.vue @@ -1,46 +1,38 @@ @@ -236,23 +112,23 @@ function edoClick(info: MosScaleInfo) {
    - + - + - +
    -
    +
    @@ -270,60 +146,67 @@ function edoClick(info: MosScaleInfo) { type="number" min="1" max="1000" - v-model="numberOfSmallSteps" + v-model="modal.numberOfSmallSteps" />
    - +
    - +
    - +
    - + - + - +
    -
    +
    - + - +
    -
    -
    +
    +
    -
    +
    - +
    - + @@ -348,10 +231,15 @@ function edoClick(info: MosScaleInfo) { [{{ info.name.split(';')[0] }}] - diff --git a/src/components/modals/generation/RankOne.vue b/src/components/modals/generation/RankOne.vue deleted file mode 100644 index 1904fe79..00000000 --- a/src/components/modals/generation/RankOne.vue +++ /dev/null @@ -1,111 +0,0 @@ - - - diff --git a/src/components/modals/generation/RankTwo.vue b/src/components/modals/generation/RankTwo.vue index 086165bf..6835c30d 100644 --- a/src/components/modals/generation/RankTwo.vue +++ b/src/components/modals/generation/RankTwo.vue @@ -1,344 +1,152 @@ @@ -30,7 +30,7 @@ function modify() { type="number" min="1" class="control" - v-model="denominator" + v-model="modal.largeInteger" />
    diff --git a/src/components/modals/modification/ApproximateByRatios.vue b/src/components/modals/modification/ApproximateByRatios.vue index 2f9ff8cf..52f90963 100644 --- a/src/components/modals/modification/ApproximateByRatios.vue +++ b/src/components/modals/modification/ApproximateByRatios.vue @@ -4,111 +4,26 @@ import Modal from '@/components/ModalDialog.vue' import { approximateOddLimitWithErrors, approximatePrimeLimitWithErrors, - clamp, Fraction, getConvergents, - primeLimit as getPrimeLimit, PRIMES, valueToCents } from 'xen-dev-utils' import { DEFAULT_NUMBER_OF_COMPONENTS } from '@/constants' import { fractionToString, ExtendedMonzo, Interval, type Scale } from 'scale-workshop-core' +import { useApproximateByRatiosStore } from '@/stores/approximate-by-ratios' const MAX_LENGTH = 128 const props = defineProps<{ scale: Scale }>() -const emit = defineEmits(['update:scale', 'cancel']) -const degree = ref(1) -const approximationIndex = ref(0) -const approximationSelect = ref(null) -const maxError = ref(20) -const method = ref<'convergents' | 'odd' | 'prime'>('convergents') -const includeSemiconvergents = ref(true) -const includeNonMonotonic = ref(false) -const oddLimit = ref(9) -const primeLimit = ref(7) -const maxExponent = ref(2) -const safeOddLimit = computed(() => clamp(3, 101, 2 * Math.floor(oddLimit.value / 2) + 1)) -const safePrimeLimit = computed(() => { - const value = primeLimit.value - if (value < 3) { - return 3 - } - if (value < 5) { - return 3 - } - if (value < 7) { - return 5 - } - if (value < 11) { - return 7 - } - if (value < 13) { - return 11 - } - if (value < 17) { - return 13 - } - if (value < 19) { - return 17 - } - if (value < 23) { - return 19 - } - if (value < 29) { - return 23 - } - if (value > 29) { - return 29 - } - return 3 -}) -const safeMaxExponent = computed(() => { - let maxSafe = 8 - if (safePrimeLimit.value > 7) { - maxSafe = 6 - } - if (safePrimeLimit.value > 13) { - maxSafe = 3 - } - if (safePrimeLimit.value >= 29) { - maxSafe = 2 - } - return clamp(1, maxSafe, maxExponent.value) -}) +const emit = defineEmits(['update:scale', 'cancel']) -// Make the number input skip to the next prime when incremented -function modifyPrimeLimit(event: Event) { - const oldValue = primeLimit.value - const newValue = parseInt((event.target as HTMLInputElement).value) - if (isNaN(newValue)) { - return - } - if (PRIMES.includes(newValue)) { - primeLimit.value = newValue - return - } - const index = PRIMES.indexOf(primeLimit.value) - if (newValue < oldValue && index > 1) { - primeLimit.value = PRIMES[index - 1] - return - } - if (newValue > oldValue && index < PRIMES.length - 1) { - primeLimit.value = PRIMES[index + 1] - return - } -} +const approx = useApproximateByRatiosStore() -function primeLimitString(fraction: Fraction) { - const limit = getPrimeLimit(fraction, false, 97) - if (limit < Infinity) { - return `${limit}-limit` - } - return '>97-limit' -} +const approximationSelect = ref(null) type Approximation = { fraction: Fraction @@ -117,73 +32,76 @@ type Approximation = { } const approximationsWithErrorsAndLimits = computed(() => { - const selected = props.scale.getMonzo(degree.value) + const selected = props.scale.getMonzo(approx.degree) const selectedCents = selected.totalCents() - if (method.value === 'convergents') { + if (approx.method === 'convergents') { const approximations = getConvergents( selected.valueOf(), undefined, 2 * MAX_LENGTH, // Extra length buffer - includeSemiconvergents.value, - includeNonMonotonic.value + approx.includeSemiconvergents, + approx.includeNonMonotonic ) const result: Approximation[] = [] approximations.forEach((fraction) => { const error = Math.abs(selectedCents - valueToCents(fraction.valueOf())) - if (error <= maxError.value) { - const limit = primeLimitString(fraction) + if (error <= approx.maxError) { + const limit = approx.primeLimitString(fraction) result.push({ fraction, error, limit }) } }) return result.slice(0, MAX_LENGTH) - } else if (method.value === 'odd') { - const approximationsAndErrors = approximateOddLimitWithErrors(selectedCents, safeOddLimit.value) + } else if (approx.method === 'odd') { + const approximationsAndErrors = approximateOddLimitWithErrors( + selectedCents, + approx.safeOddLimit + ) const result: Approximation[] = [] approximationsAndErrors.forEach(([fraction, error]) => { - if (error <= maxError.value) { - const limit = primeLimitString(fraction) + if (error <= approx.maxError) { + const limit = approx.primeLimitString(fraction) result.push({ fraction, error, limit }) } }) if (!result.length) { const [fraction, error] = approximationsAndErrors[0] - return [{ fraction, error, limit: primeLimitString(fraction) }] + return [{ fraction, error, limit: approx.primeLimitString(fraction) }] } return result.slice(0, MAX_LENGTH) } const approximationsAndErrors = approximatePrimeLimitWithErrors( selectedCents, - PRIMES.indexOf(safePrimeLimit.value), - safeMaxExponent.value, - Math.min(600, maxError.value), + PRIMES.indexOf(approx.safePrimeLimit), + approx.safeMaxExponent, + Math.min(600, approx.maxError), MAX_LENGTH ) if (!approximationsAndErrors.length) { const [fraction, error] = approximatePrimeLimitWithErrors( selectedCents, - PRIMES.indexOf(safePrimeLimit.value), - safeMaxExponent.value, + PRIMES.indexOf(approx.safePrimeLimit), + approx.safeMaxExponent, undefined, 1 )[0] - return [{ fraction, error, limit: primeLimitString(fraction) }] + return [{ fraction, error, limit: approx.primeLimitString(fraction) }] } return approximationsAndErrors.map(([fraction, error]) => ({ fraction, error, - limit: primeLimitString(fraction) + limit: approx.primeLimitString(fraction) })) }) watch(approximationsWithErrorsAndLimits, (newValue) => { if (approximationSelect.value !== null) { - if (!newValue.length || newValue[0].error > maxError.value) { + if (!newValue.length || newValue[0].error > approx.maxError) { approximationSelect.value.setCustomValidity('Error exceeds the maximum value.') } else { approximationSelect.value.setCustomValidity('') } } - approximationIndex.value = Math.min(approximationIndex.value, newValue.length - 1) + approx.approximationIndex = Math.min(approx.approximationIndex, newValue.length - 1) }) function modifyAndAdvance() { @@ -192,14 +110,14 @@ function modifyAndAdvance() { } const replacement = new Interval( ExtendedMonzo.fromFraction( - approximationsWithErrorsAndLimits.value[approximationIndex.value].fraction, + approximationsWithErrorsAndLimits.value[approx.approximationIndex].fraction, DEFAULT_NUMBER_OF_COMPONENTS ), 'ratio' ) - emit('update:scale', props.scale.replaceDegree(degree.value, replacement)) - degree.value = Math.min(props.scale.size, degree.value + 1) - approximationIndex.value = 0 + emit('update:scale', props.scale.replaceDegree(approx.degree, replacement)) + approx.degree = Math.min(props.scale.size, approx.degree + 1) + approx.approximationIndex = 0 } @@ -213,11 +131,11 @@ function modifyAndAdvance() {

    Select scale degrees and apply rational replacements one by one.

    - +
    - +
    @@ -225,7 +143,7 @@ function modifyAndAdvance() { ref="approximationSelect" id="approximation" class="control" - v-model="approximationIndex" + v-model="approx.approximationIndex" >