diff --git a/src/__tests__/scale.spec.ts b/src/__tests__/scale.spec.ts index 63a7cc1..69f7d18 100644 --- a/src/__tests__/scale.spec.ts +++ b/src/__tests__/scale.spec.ts @@ -3,7 +3,7 @@ import {describe, it, expect} from 'vitest'; import {ExtendedMonzo} from '../monzo'; import {Scale} from '../scale'; import {Interval, IntervalOptions} from '../interval'; -import {arraysEqual, Fraction} from 'xen-dev-utils'; +import {arraysEqual, Fraction, valueToCents} from 'xen-dev-utils'; describe('Scale', () => { it('supports just intonation', () => { @@ -1054,6 +1054,30 @@ describe('Scale', () => { expect(scale.getMonzo(2).cents).toBeTruthy(); }); + it('keeps the unison in place during random variance', () => { + const scale = Scale.fromIntervalArray([ + new Interval( + ExtendedMonzo.fromEqualTemperament( + new Fraction(3, 15), + new Fraction(9), + 2 + ), + 'equal temperament', + undefined, + {preferredEtDenominator: 15, preferredEtEquave: new Fraction(9)} + ), + new Interval(ExtendedMonzo.fromFraction(9, 2), 'ratio'), + ]).vary(10, false); + expect(scale.intervals[0].totalCents()).toBe(0); + expect(scale.getCents(1)).toBeGreaterThanOrEqual( + (valueToCents(9) * 3) / 15 - 10 + ); + expect(scale.getCents(1)).toBeLessThanOrEqual( + (valueToCents(9) * 3) / 15 + 10 + ); + expect(scale.getCents(2)).toBeCloseTo(valueToCents(9)); + }); + it('respells the equave when approximated in equal temperament', () => { const scale = Scale.fromIntervalArray([ new Interval( diff --git a/src/scale.ts b/src/scale.ts index b079c8d..d342417 100644 --- a/src/scale.ts +++ b/src/scale.ts @@ -846,7 +846,9 @@ export class Scale { * @returns A new scale with variance added. */ vary(maxCents: number, varyEquave = false) { - const intervals = this.intervals.map(interval => interval.vary(maxCents)); + const intervals = this.intervals.map((interval, i) => + i ? interval.vary(maxCents) : interval.clone() + ); const result = this.variant(intervals); if (varyEquave) { result.equave = this.equave.vary(maxCents);