Seven Solid stores wired up around the architecture's reactivity model
(§7.1). Each store uses createStore for object state + createSignal for
Float32Arrays where appropriate; setters mutate the store and schedule a
debounced (200ms) localStorage write through src/stores/persistence.ts.
- bus.ts: typed signal bus with prefix wildcards (ml.*, ui.*, mode.*,
pin.*, snap.*). Singleton coreBus for app-wide events.
- ml-store.ts: shape final, methods stub-throw "not implemented" until
stream 7 wires WASM. Outputs and weights are separate Float32Array
signals so the store proxy doesn't run on every audio-rate tick.
- input-store.ts: full input pipeline config (zoom, anchor, deadzone,
curve, smoothing, momentum, invert) + persisted live state.
- output-store.ts: globalCurve, smoothing, slewRate, freezeOutput,
freezeMask. Mask not persisted (engine-specific).
- mode-store.ts: activeModeId + per-mode { paramName → ParamOverride }.
- control-store.ts: Boldness/Memory/Precision compound axes with
interpolation tables and offset-based overrides (trim-pot model).
Tables and 6 built-in CONTROL_PRESETS mirror legacy
js/ui/control-surface.js exactly. interpolateAxis() exposed for
testing. Stream 10 wires resolveParams() into other stores.
- session-store.ts: ring-buffered snapshot stack (max 20), A/B
capture/toggle/accept/revert, region pins (max 5), param pins
(toggle + mask builder), named session presets.
- index.ts: public re-exports for components and modes.
Stream 8 of the rewrite (meml-911).
116 lines
3.5 KiB
TypeScript
116 lines
3.5 KiB
TypeScript
/**
|
|
* ML store — placeholder shape for the ML engine state.
|
|
*
|
|
* Stream 7 wires WASM under this. For now the methods that mutate the engine
|
|
* throw `not implemented`. The shape of the store and the signal types are
|
|
* final — modes and primitives can read them.
|
|
*
|
|
* Why a Solid store + a separate Float32Array signal:
|
|
* - `createStore` is great for object-like state with fine reactivity.
|
|
* - Float32Array outputs are large and frequently updated; `createSignal`
|
|
* with explicit reference replacement is cheaper.
|
|
*/
|
|
|
|
import { createSignal, type Accessor } from 'solid-js';
|
|
import { createStore, produce } from 'solid-js/store';
|
|
import { coreBus } from './bus';
|
|
|
|
export interface MLStoreState {
|
|
exampleCount: number;
|
|
/** Last training loss (final loss of last train() call). null = none yet. */
|
|
lastLoss: number | null;
|
|
/** Last training run loss curve (per iteration). */
|
|
lossHistory: number[];
|
|
/** Current input vector size (matches active mode). */
|
|
inputSize: number;
|
|
/** Current output vector size (matches active mode). */
|
|
outputSize: number;
|
|
/** True while a synchronous or async training call is running. */
|
|
training: boolean;
|
|
/** True when WASM is fully initialised. */
|
|
ready: boolean;
|
|
}
|
|
|
|
const EMPTY_OUTPUTS = new Float32Array(0);
|
|
const NOT_IMPLEMENTED = (op: string): never => {
|
|
throw new Error(
|
|
`[ml-store] ${op} not implemented in stream-8 scaffold; awaits stream 7 (WASM bindings)`
|
|
);
|
|
};
|
|
|
|
const [state, setState] = createStore<MLStoreState>({
|
|
exampleCount: 0,
|
|
lastLoss: null,
|
|
lossHistory: [],
|
|
inputSize: 2,
|
|
outputSize: 126,
|
|
training: false,
|
|
ready: false,
|
|
});
|
|
|
|
const [outputs, setOutputs] = createSignal<Float32Array>(EMPTY_OUTPUTS, {
|
|
equals: false, // always notify even if reference reused
|
|
});
|
|
|
|
const [weights, setWeights] = createSignal<Float32Array>(EMPTY_OUTPUTS, {
|
|
equals: false,
|
|
});
|
|
|
|
export const mlStore = {
|
|
// ---- read ----
|
|
state,
|
|
outputs: outputs as Accessor<Float32Array>,
|
|
weights: weights as Accessor<Float32Array>,
|
|
|
|
// ---- internal setters (used by future WASM wiring; exposed for stub
|
|
// wiring during this stream so primitive demos can drive values) ----
|
|
__setOutputs: setOutputs,
|
|
__setState: setState,
|
|
__setWeights: setWeights,
|
|
|
|
// ---- ML lifecycle (stubbed) ----
|
|
initialize(_inputSize: number, _outputSize: number): Promise<void> {
|
|
return NOT_IMPLEMENTED('initialize');
|
|
},
|
|
setInput(_idx: number, _value: number): void {
|
|
NOT_IMPLEMENTED('setInput');
|
|
},
|
|
process(): void {
|
|
NOT_IMPLEMENTED('process');
|
|
},
|
|
addExample(_features: ReadonlyArray<number>, _labels: ReadonlyArray<number>): void {
|
|
NOT_IMPLEMENTED('addExample');
|
|
},
|
|
train(_lr?: number, _maxIter?: number): number {
|
|
return NOT_IMPLEMENTED('train');
|
|
},
|
|
trainAsync(_lr?: number, _maxIter?: number): Promise<number> {
|
|
return NOT_IMPLEMENTED('trainAsync');
|
|
},
|
|
drawWeights(_spread: number): void {
|
|
NOT_IMPLEMENTED('drawWeights');
|
|
},
|
|
moveWeights(_speed: number, _spread: number, _pinMask?: Uint8Array): void {
|
|
NOT_IMPLEMENTED('moveWeights');
|
|
},
|
|
evalLoss(): number | null {
|
|
return null;
|
|
},
|
|
inferBatch(_points: ReadonlyArray<readonly [number, number]>): Float32Array {
|
|
return NOT_IMPLEMENTED('inferBatch');
|
|
},
|
|
getLayerStats(): Float32Array {
|
|
return EMPTY_OUTPUTS;
|
|
},
|
|
reset(): void {
|
|
NOT_IMPLEMENTED('reset');
|
|
},
|
|
clearExamples(): void {
|
|
setState(produce((s) => {
|
|
s.exampleCount = 0;
|
|
}));
|
|
coreBus.emit('ml.examples_cleared', undefined);
|
|
},
|
|
};
|
|
|
|
export type MLStore = typeof mlStore;
|