Skip to content

Commit 9a46e37

Browse files
committed
TuneCell and Modulation History code refactor and cleanup
1 parent c406b62 commit 9a46e37

32 files changed

Lines changed: 1659 additions & 485 deletions

src/app.jsx

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
deriveCurrentFundamentalForHistory,
1212
replayModulationHistoryForFrame,
1313
spellWorkspaceForFrame,
14-
} from "./notation/notation-frame-runtime.js";
14+
} from "./tuning/modulation-frame-runtime.js";
1515
import { parseExactInterval } from "./tuning/interval.js";
1616

1717
import useSynthWiring from "./use-synth-wiring.js";
@@ -622,6 +622,12 @@ const App = () => {
622622
const [modulationState, setModulationState] = useState(null);
623623
const [deferredModulationHistory, setDeferredModulationHistory] = useState([]);
624624
const [presetModulationLibrary, setPresetModulationLibrary] = useState([]);
625+
const [presetRuntimeResetRevision, setPresetRuntimeResetRevision] = useState(0);
626+
627+
const { onImport, importCount, bumpImportCount } = useImport(settings, setSettings, {
628+
onReady: () => setReady(true),
629+
onUserInteraction: () => setUserHasInteracted(true),
630+
});
625631

626632
const {
627633
activeSource,
@@ -638,6 +644,9 @@ const App = () => {
638644
} = usePresets(settings, setSettings, {
639645
synthRef,
640646
onUserInteraction: () => setUserHasInteracted(true),
647+
bumpImportCount,
648+
bumpPresetRuntimeReset: () =>
649+
setPresetRuntimeResetRevision((revision) => revision + 1),
641650
currentModulationLibrary: modulationState?.history ?? presetModulationLibrary,
642651
setPresetModulationLibrary,
643652
onPresetModulationLibraryLoaded: (library) => {
@@ -648,11 +657,6 @@ const App = () => {
648657
},
649658
});
650659

651-
const { onImport, importCount, bumpImportCount } = useImport(settings, setSettings, {
652-
onReady: () => setReady(true),
653-
onUserInteraction: () => setUserHasInteracted(true),
654-
});
655-
656660
const {
657661
synth,
658662
midi,
@@ -896,12 +900,17 @@ const App = () => {
896900
const tuningImpactKey = useMemo(() => settingsImpactKey(settings, "tuning"), [settings]);
897901
const structuralImpactKey = useMemo(() => settingsImpactKey(settings, "structural"), [settings]);
898902
const keysReconstructionImpactKey = useMemo(
899-
() => settingsImpactKey(settings, "keysReconstruction", { midiAccess, midiTick }),
900-
[settings, midiAccess, midiTick],
903+
() =>
904+
settingsImpactKey(settings, "keysReconstruction", {
905+
midiAccess,
906+
midiTick,
907+
presetRuntimeResetRevision,
908+
}),
909+
[settings, midiAccess, midiTick, presetRuntimeResetRevision],
901910
);
902911
const musicalSurfaceResetImpactKey = useMemo(
903-
() => settingsImpactKey(settings, "musicalSurfaceReset"),
904-
[settings],
912+
() => settingsImpactKey(settings, "musicalSurfaceReset", { presetRuntimeResetRevision }),
913+
[settings, presetRuntimeResetRevision],
905914
);
906915
const colorImpactKey = useMemo(() => settingsImpactKey(settings, "colors"), [settings]);
907916
const inputRuntimeImpactKey = useMemo(() => settingsImpactKey(settings, "inputRuntime"), [settings]);
@@ -963,6 +972,13 @@ const App = () => {
963972
const deferredModulationHistoryRef = useRef(modulationHistory);
964973
deferredModulationHistoryRef.current = modulationHistory;
965974
useEffect(() => {
975+
if ((modulationState?.mode ?? "idle") === "idle") {
976+
const nextHistory = deferredModulationHistoryRef.current;
977+
setDeferredModulationHistory(
978+
Array.isArray(nextHistory) ? nextHistory.map((entry) => ({ ...entry })) : [],
979+
);
980+
return;
981+
}
966982
let timeoutId = null;
967983
const syncWhenNotesAreClear = () => {
968984
if (keysRef.current && !keysRef.current.isSoundInteractionIdle?.()) {
@@ -978,7 +994,7 @@ const App = () => {
978994
return () => {
979995
if (timeoutId != null) clearTimeout(timeoutId);
980996
};
981-
}, [activeModulationHistoryKey]);
997+
}, [activeModulationHistoryKey, modulationState?.mode]);
982998
const deferredModulationHistoryKey = useMemo(
983999
() => modulationHistoryKey(deferredModulationHistory),
9841000
[deferredModulationHistory],

src/app.test.js

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { act } from "preact/test-utils";
1717
import { parseExactInterval } from "./tuning/interval.js";
1818

1919
let lastKeyboardProps = null;
20+
let lastUsePresetsOptions = null;
2021
let mockDetectedController = null;
2122
let mockControllerById = null;
2223

@@ -115,19 +116,22 @@ vi.mock("./use-query", () => ({
115116
ExtractJoinedString: {},
116117
}));
117118
vi.mock("./use-presets.js", () => ({
118-
default: () => ({
119-
activeSource: "",
120-
activePresetName: "",
121-
isPresetDirty: false,
122-
persistOnReload: false,
123-
setPersistOnReload: vi.fn(),
124-
presetChanged: vi.fn(),
125-
onLoadCustomPreset: vi.fn(),
126-
onClearUserPresets: vi.fn(),
127-
onRevertBuiltin: vi.fn(),
128-
onRevertUser: vi.fn(),
129-
onUserScaleEdit: vi.fn(),
130-
}),
119+
default: (_settings, _setSettings, options) => {
120+
lastUsePresetsOptions = options;
121+
return {
122+
activeSource: "",
123+
activePresetName: "",
124+
isPresetDirty: false,
125+
persistOnReload: false,
126+
setPersistOnReload: vi.fn(),
127+
presetChanged: vi.fn(),
128+
onLoadCustomPreset: vi.fn(),
129+
onClearUserPresets: vi.fn(),
130+
onRevertBuiltin: vi.fn(),
131+
onRevertUser: vi.fn(),
132+
onUserScaleEdit: vi.fn(),
133+
};
134+
},
131135
SCALE_KEYS_TO_CLEAR: [],
132136
}));
133137
vi.mock("./use-import.js", () => ({
@@ -203,12 +207,39 @@ describe("Loading", () => {
203207

204208
beforeEach(() => {
205209
lastKeyboardProps = null;
210+
lastUsePresetsOptions = null;
206211
mockDetectedController = null;
207212
mockControllerById = null;
208213
synthWiringState.linnstrumentRawPorts = null;
209214
vi.clearAllMocks();
210215
});
211216

217+
describe("preset runtime reset wiring", () => {
218+
it("changes the keyboard reconstruction key only when the sidebar preset refresh path requests it", async () => {
219+
window.matchMedia = vi.fn().mockReturnValue({
220+
matches: false,
221+
addEventListener: vi.fn(),
222+
removeEventListener: vi.fn(),
223+
});
224+
render(<App />);
225+
226+
await waitFor(() => {
227+
expect(lastKeyboardProps).not.toBeNull();
228+
expect(lastUsePresetsOptions?.bumpPresetRuntimeReset).toBeTypeOf("function");
229+
});
230+
231+
const initialReconstructionKey = lastKeyboardProps.reconstructionKey;
232+
233+
await act(async () => {
234+
lastUsePresetsOptions.bumpPresetRuntimeReset();
235+
});
236+
237+
await waitFor(() => {
238+
expect(lastKeyboardProps.reconstructionKey).not.toBe(initialReconstructionKey);
239+
});
240+
});
241+
});
242+
212243
describe("modulationRouteLabelPair", () => {
213244
it("renders an equave offset for an octave-displaced target ratio", () => {
214245
const pair = modulationRouteLabelPair(

src/input/keys-expression-runtime.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,10 @@ export function reapplyCurrentInputBends() {
526526

527527
export function refreshSoundingHexNeighbors() {
528528
const refresh = (hex) => {
529-
const [, , , , , centsPrev, centsNext] = this.hexCoordsToCents(hex.coords);
529+
const pitchAtCoords = typeof this.hexCoordsToLiveCents === "function"
530+
? this.hexCoordsToLiveCents(hex.coords)
531+
: this.hexCoordsToCents(hex.coords);
532+
const [, , , , , centsPrev, centsNext] = pitchAtCoords;
530533
hex.cents_prev = centsPrev;
531534
hex.cents_next = centsNext;
532535
};

src/keyboard/keys-frame-runtime.js

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
// that describe the current harmonic transposition and any fixed-do geometry
44
// shift. It does not interpret controller input or render anything directly.
55

6+
import { deriveGeometryShiftForHistory } from "../tuning/modulation-geometry-runtime.js";
7+
68
export function createKeysFrame(options = {}) {
79
return {
810
id: options.id ?? "",
@@ -69,25 +71,7 @@ export function deriveFrameForHistory(options = {}) {
6971
return sum + count * (targetDegree - sourceDegree);
7072
}, 0)
7173
: 0;
72-
const geometryShift = fixedDoEnabled
73-
? activeRoutes.reduce((sum, route) => {
74-
const count = Number.isFinite(route?.count) ? Math.trunc(route.count) : 0;
75-
const deltaRSteps = Number.isFinite(route?.deltaRSteps)
76-
? Math.trunc(route.deltaRSteps)
77-
: Number.isFinite(route?.surfaceDeltaX)
78-
? Math.trunc(route.surfaceDeltaX)
79-
: 0;
80-
const deltaDrSteps = Number.isFinite(route?.deltaDrSteps)
81-
? Math.trunc(route.deltaDrSteps)
82-
: Number.isFinite(route?.surfaceDeltaY)
83-
? Math.trunc(route.surfaceDeltaY)
84-
: 0;
85-
return {
86-
geometryShiftRSteps: sum.geometryShiftRSteps + count * deltaRSteps,
87-
geometryShiftDrSteps: sum.geometryShiftDrSteps + count * deltaDrSteps,
88-
};
89-
}, { geometryShiftRSteps: 0, geometryShiftDrSteps: 0 })
90-
: { geometryShiftRSteps: 0, geometryShiftDrSteps: 0 };
74+
const geometryShift = deriveGeometryShiftForHistory(activeRoutes, fixedDoEnabled);
9175
const effectiveFundamental = fundamental * Math.pow(2, transpositionCents / 1200);
9276
const route = activeRoutes[activeRoutes.length - 1] ?? null;
9377

@@ -97,8 +81,8 @@ export function deriveFrameForHistory(options = {}) {
9781
targetDegree: route?.targetDegree ?? null,
9882
transpositionSteps,
9983
transpositionCents,
100-
geometryShiftRSteps: geometryShift.geometryShiftRSteps,
101-
geometryShiftDrSteps: geometryShift.geometryShiftDrSteps,
84+
geometryShiftRSteps: geometryShift.deltaRSteps,
85+
geometryShiftDrSteps: geometryShift.deltaDrSteps,
10286
effectiveFundamental,
10387
});
10488
}

0 commit comments

Comments
 (0)