UNPKG

zusound

Version:

Sound feedback middleware for Zustand state management

432 lines (395 loc) 14.3 kB
import { StoreMutatorIdentifier, StateCreator } from 'zustand'; /** * Represents a single sonic event (sound) that will be played * based on a state change in the application */ type SonicChunk = { /** * Unique identifier for this chunk, typically the key path of the changed state * Example: "user.preferences.theme" */ id: string; /** * Waveform type that determines the timbre of the sound * - sine: smooth, pure tone (used for numbers by default) * - square: harsh, electronic sound (used for strings by default) * - sawtooth: bright, buzzy sound (used for booleans by default) * - triangle: softer, hollow sound (used for objects and removals by default) */ type: 'sine' | 'square' | 'sawtooth' | 'triangle'; /** * The type of value change that occurred * - add: New value added to the state (currently unused, treated as 'change') * - remove: Value removed from the state * - change: Value changed in the state */ valueType: 'add' | 'remove' | 'change'; /** * Base frequency of the sound in Hertz (Hz) * Typically ranges from 110Hz to 880Hz in the current implementation */ frequency: number; /** * Volume/amplitude of the sound (0.0 to 1.0) * Default: 0.5 for changes, 0.3 for removals */ magnitude: number; /** * Duration of the sound in milliseconds * Minimum value is typically 50ms */ duration: number; /** * Fine pitch adjustment in cents (1/100 of a semitone) * Used to create subtle variations based on value types and magnitudes * Range: -600 to 600 cents in current implementation */ detune: number; }; /** * Types for the diff package */ /** * Represents a change type in a diff operation */ type ChangeType = 'add' | 'remove' | 'change' /** * Represents a single change entry with metadata */ interface DiffEntry<T> { /** * The new value after the change */ value: T /** * The previous value before the change (undefined for additions) */ previousValue?: T /** * The type of change that occurred */ type: ChangeType } /** * A detailed diff result containing change type information for object properties. * If T is not an object, it directly uses DiffEntry<T>. */ type DetailedDiff<T> = T extends object ? { [K in keyof Partial<T>]: DiffEntry<T[K]> } : DiffEntry<T> // Handle non-object types directly /** * A simple diff result containing only the changed properties with their new values. * If T is not an object, it represents the new value directly. */ type DiffResult<T = unknown> = T extends object ? Partial<T> : T /** * Options for the diff calculation */ interface DiffOptions { /** * Whether to include detailed change information (type and previous value) * @default false */ detailed?: boolean /** * Whether to track keys that were added * @default true */ trackAdded?: boolean /** * Whether to track keys that were removed * @default true */ trackRemoved?: boolean } /** * Calculates the difference between two states * * @param prevState - The previous state object * @param nextState - The next state object * @param options - Configuration options for the diff calculation * @returns A partial object containing only the changed properties */ declare function calculateDiffBase<T>(prevState: T, nextState: T, options?: DiffOptions): Partial<T> | DetailedDiff<T>; /** * Simplified version of calculateDiffBase that only returns changed values * without detailed change information * * @param prevState - The previous state object * @param nextState - The next state object * @returns A partial object containing only the changed properties */ declare function calculateSimpleDiff<T>(prevState: T, nextState: T): Partial<T>; /** * Version of calculateDiffBase that includes detailed change information * * @param prevState - The previous state object * @param nextState - The next state object * @returns A detailed diff object with change type information */ declare function calculateDetailedDiff<T>(prevState: T, nextState: T): DetailedDiff<T>; /** * zusound Diff Module * * This module provides utilities for calculating differences between two state objects. * It includes both simple and detailed diff calculation functions. * * The diff result is used by the sonification module to create sound representations * of state changes. */ declare const calculateDiff: typeof calculateSimpleDiff; /** * Represents the type of a store enhanced with the trace middleware. * In this basic version, it doesn't alter the public signature significantly, * but defining it maintains the pattern for potential future extensions. */ type WithZusound<S> = S // Keep this simple for now /** * Augments the Zustand vanilla module definition. * This allows TypeScript to recognize the custom mutator type added by this middleware. */ declare module 'zustand/vanilla' { // eslint-disable-next-line @typescript-eslint/no-unused-vars interface StoreMutators<S, A> { // This defines the state mutation caused by the middleware zusound: WithZusound<S> } } // DiffResult is now imported from '../diff' /** Data structure containing information about a single state transition. */ interface TraceData<T = unknown> { /** The calculated difference between prevState and nextState. */ diff: DiffResult<T> // Use imported DiffResult type /** Timestamp when the update started processing. */ timestampStart: number /** Duration of the update in milliseconds. */ duration: number } /** Detail payload for the trace event */ interface ZusoundTraceEventDetail<T = unknown> { traceData: TraceData<T> } /** Configuration options for the trace middleware. */ interface TraceOptions<T> { /** * A callback function that receives the trace data after each state update. * Use this to log, send data, etc. */ onTrace?: (traceData: TraceData<T>) => void /** * Optional custom diffing function. * Defaults to the `calculateDiff` function from the diff package. */ diffFn?: (prevState: T, nextState: T) => DiffResult<T> // Use imported DiffResult type } /** * Interface for zusound middleware options. * Extends the core TraceOptions with middleware-specific settings. */ interface ZusoundOptions<T> extends Omit<TraceOptions<T>, 'onTrace'> { /** Enable/disable sound feedback (default: true in dev, false in prod) */ enabled?: boolean /** Log state diffs to window.__zusound_logger__ (default: false) */ logDiffs?: boolean /** Allow in production (default: false) */ allowInProduction?: boolean /** * Optional custom diffing function. Overrides the one in TraceOptions if provided. * Defaults to the `calculateDiff` function from the diff package. */ diffFn?: (prevState: T, nextState: T) => DiffResult<T> /** * Optional callback executed after sonification for each state change. * Receives trace data (state, diff, duration, etc.). This is separate from * the internal onTrace used by the core trace middleware. */ onTrace?: (traceData: TraceData<T>) => void /** * Automatically initialize the sonification listener when middleware is used (default: true). * Set to false if you want to manually control when sonification starts using initSonificationListener. */ initSonification?: boolean } /** * Type definition for the zusound middleware function. * This type captures the middleware's generic parameters and return type. */ type Zusound = < T extends object, Mps extends [StoreMutatorIdentifier, unknown][] = [], Mcs extends [StoreMutatorIdentifier, unknown][] = [], >( initializer: StateCreator<T, [...Mps] | [...Mps], Mcs>, // Adjusted Mps signature for compatibility options?: ZusoundOptions<T> ) => StateCreator<T, Mps, [...Mcs]> type ZusoundTraceEvent<T = unknown> = CustomEvent<ZusoundTraceEventDetail<T>>; /** * zusound middleware for Zustand * Applies state change sonification and optional visualization. */ declare const zusound: Zusound; /** * Middleware package for Zustand state tracking with sound feedback. * * This package provides optimized APIs that implement best practices for the core functionality: * - Exposes a refined interface over the core tracing capabilities * - Allows configuration of sonification parameters and thresholds * - Maintains consistent behavior while providing tuning capabilities * * The middleware serves as the recommended entry point for most applications, * with sensible defaults and a streamlined API surface. */ declare global { interface ImportMeta { env?: { PROD?: boolean; DEV?: boolean; }; } } /** * Play a sonic chunk using the Web Audio API. * Dispatches visualization events regardless of playback success. * @param chunk - The sonic chunk to play * @returns Promise resolving to true if audio playback started, false otherwise. */ declare function playSonicChunk(chunk: SonicChunk): Promise<boolean>; /** * Generates sonic chunks and plays them. * Visualizer listens independently via dispatched events. * @param diff - State changes * @param duration - Sound duration * @param _persistVisualizer - No longer used for UI control, kept for potential future options */ declare function sonifyChanges<T>(diff: Partial<T>, duration: number, _persistVisualizer?: boolean): void; /** * Class to manage the lifecycle of a single AudioContext instance. */ declare class AudioContextManager { private static instance; private audioContext; private isAutoplayBlocked; private hasUserInteracted; private constructor(); /** Get the singleton instance of AudioContextManager. */ static getInstance(): AudioContextManager; /** Get or initialize the AudioContext. */ getContext(): AudioContext; /** Check if audio playback is currently likely blocked by browser autoplay policy */ isAudioBlocked(): boolean; /** * Attempt to resume audio context if suspended. * Returns status indicating success, failure, or if it was already running. * @returns Promise resolving to an object { resumed: boolean, blocked: boolean } */ tryResumeAudioContext(): Promise<{ resumed: boolean; blocked: boolean; }>; /** Clean up audio resources. */ cleanup(): Promise<void>; } /** * Audio configuration constants for the sonification module */ declare const AUDIO_CONFIG: { /** * Base frequency (A2 note) for sound generation in Hz */ BASE_FREQUENCY: number; /** * Minimum duration for any sonic chunk in milliseconds */ MIN_DURATION_MS: number; /** * Default magnitude (volume) values for different change types */ DEFAULT_MAGNITUDE: { CHANGE: number; REMOVE: number; }; /** * Pentatonic scale frequency ratios for musical sound mapping * These represent: Root, Major 2nd, Major 3rd, Perfect 5th, Major 6th */ SCALE: number[]; /** * Delay between consecutive sounds in milliseconds * Used for creating a staggered playback effect */ STAGGER_DELAY_MS: number; }; /** * zusound Sonification Module * * This module provides tools to convert state changes in a Zustand store * into auditory feedback through the Web Audio API. * * Key features: * - Converts state changes (diffs) into sonic representations * - Maps different data types to different sound characteristics * - Provides utilities for playing these sounds with proper timing * * Basic usage: * ``` * // Automatically initialized when using the middleware (default behavior) * import { zusound } from "zusound"; * * // For manual initialization: * import { initSonificationListener } from "zusound"; * initSonificationListener(); * ``` */ /** * Initializes the sonification event listener. * Call this function during app setup to start listening for state change events. * * Note: This is automatically called by the middleware unless * { initSonification: false } is specified in the options. */ declare function initSonificationListener(): void; /** * Removes the sonification event listener. * Call this function to stop listening for state change events. */ declare function removeSonificationListener(): void; /** Shows a persistent visualizer in the top-right corner of the screen */ declare function showPersistentVisualizer(): void; /** Hides and cleans up the persistent visualizer */ declare function hidePersistentVisualizer(): void; /** * Interface for the custom event detail payload * containing the sonic chunk data to be visualized. */ interface ZusoundEventDetail { chunk: SonicChunk } /** * Type definition for the custom 'zusound' event dispatched * when a sonification event occurs, intended for the visualizer. */ interface ZusoundEvent extends CustomEvent { detail: ZusoundEventDetail } // Update the global WindowEventMap to include the custom 'zusound' event declare global { interface WindowEventMap { zusound: ZusoundEvent } // Remove the declaration for __VISUALIZER_SINGLETON__ // var __VISUALIZER_SINGLETON__: unknown; // No longer needed } /** * Ensures the visualizer singleton is initialized and listening for events. * Useful for pre-warming the visualizer if needed. */ declare function ensureVisualizerReady(): void; /** * Manually triggers the visualization for a given SonicChunk. * This dispatches the 'zusound' custom event, which the visualizer listens for. * Useful for testing or custom visualization triggers. * * @param chunk - The SonicChunk data to visualize. */ declare function visualizeSonicChunk(chunk: SonicChunk): void; export { AUDIO_CONFIG, AudioContextManager, type ChangeType, type DetailedDiff, type DiffEntry, type DiffOptions, type DiffResult, type SonicChunk, type TraceData, type Zusound, type ZusoundOptions, type ZusoundTraceEvent, type ZusoundTraceEventDetail, calculateDetailedDiff, calculateDiff, calculateDiffBase, calculateSimpleDiff, ensureVisualizerReady, hidePersistentVisualizer, initSonificationListener, playSonicChunk, removeSonificationListener, showPersistentVisualizer, sonifyChanges, visualizeSonicChunk, zusound };