@wavecraft/core 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -125,6 +125,12 @@ export declare interface IpcResponse {
125
125
  */
126
126
  export declare function isBrowserEnvironment(): boolean;
127
127
 
128
+ export declare function isIpcError(obj: unknown): obj is IpcError;
129
+
130
+ export declare function isIpcNotification(obj: unknown): obj is IpcNotification;
131
+
132
+ export declare function isIpcResponse(obj: unknown): obj is IpcResponse;
133
+
128
134
  /**
129
135
  * Environment Detection
130
136
  *
@@ -137,6 +143,11 @@ export declare function isBrowserEnvironment(): boolean;
137
143
  */
138
144
  export declare function isWebViewEnvironment(): boolean;
139
145
 
146
+ /**
147
+ * Audio Math Utilities
148
+ *
149
+ * Pure functions for audio calculations with no side effects.
150
+ */
140
151
  /**
141
152
  * Convert linear amplitude to decibels
142
153
  * @param linear Linear amplitude (0.0 to 1.0+)
@@ -204,6 +215,11 @@ export declare enum LogLevel {
204
215
  ERROR = 3
205
216
  }
206
217
 
218
+ /**
219
+ * Metering Types
220
+ *
221
+ * Types related to audio metering.
222
+ */
207
223
  /**
208
224
  * Meter frame data (all values in linear scale, not dB)
209
225
  */
@@ -283,7 +299,7 @@ export declare interface ParameterChangedNotification {
283
299
 
284
300
  export declare class ParameterClient {
285
301
  private static instance;
286
- private bridge;
302
+ private readonly bridge;
287
303
  private constructor();
288
304
  /**
289
305
  * Get singleton instance
@@ -330,6 +346,11 @@ export declare interface ParameterInfo {
330
346
  group?: string;
331
347
  }
332
348
 
349
+ /**
350
+ * Parameter Types
351
+ *
352
+ * Types related to plugin parameters.
353
+ */
333
354
  export declare type ParameterType = 'float' | 'bool' | 'enum';
334
355
 
335
356
  /**
@@ -359,9 +380,7 @@ export declare type RequestId = string | number;
359
380
  export declare function requestResize(width: number, height: number): Promise<boolean>;
360
381
 
361
382
  /**
362
- * Window resize utilities
363
- *
364
- * Provides functions for requesting window resize from the host DAW.
383
+ * useWindowResizeSync - Automatic window resize sync to host
365
384
  */
366
385
  export declare interface RequestResizeParams {
367
386
  width: number;
@@ -444,6 +463,9 @@ export declare function useConnectionStatus(): ConnectionStatus;
444
463
 
445
464
  export declare function useLatencyMonitor(intervalMs?: number): UseLatencyMonitorResult;
446
465
 
466
+ /**
467
+ * useLatencyMonitor - Hook for monitoring IPC latency
468
+ */
447
469
  export declare interface UseLatencyMonitorResult {
448
470
  latency: number | null;
449
471
  avg: number;
@@ -451,6 +473,14 @@ export declare interface UseLatencyMonitorResult {
451
473
  count: number;
452
474
  }
453
475
 
476
+ /**
477
+ * Hook to poll meter frames at a specified interval
478
+ *
479
+ * @param intervalMs - Polling interval in milliseconds (default: 50ms = 20fps)
480
+ * @returns Current meter frame or null if not available
481
+ */
482
+ export declare function useMeterFrame(intervalMs?: number): MeterFrame | null;
483
+
454
484
  export declare function useParameter(id: string): UseParameterResult;
455
485
 
456
486
  /**
@@ -482,6 +512,9 @@ export declare interface UseParameterResult {
482
512
  error: Error | null;
483
513
  }
484
514
 
515
+ /**
516
+ * useRequestResize - Hook for requesting window resize
517
+ */
485
518
  /**
486
519
  * React hook for requesting window resize
487
520
  *
@@ -505,6 +538,25 @@ export declare interface UseParameterResult {
505
538
  */
506
539
  export declare function useRequestResize(): (width: number, height: number) => Promise<boolean>;
507
540
 
541
+ /**
542
+ * Hook that automatically syncs window resize events to the host
543
+ *
544
+ * This hook listens for browser window resize events and notifies the host
545
+ * DAW of size changes. Useful when the user resizes the plugin window via
546
+ * the DAW's window controls or edge dragging.
547
+ *
548
+ * @example
549
+ * ```tsx
550
+ * function App() {
551
+ * // Automatically sync window size changes to host
552
+ * useWindowResizeSync();
553
+ *
554
+ * return <div>Plugin UI</div>;
555
+ * }
556
+ * ```
557
+ */
558
+ export declare function useWindowResizeSync(): void;
559
+
508
560
  /**
509
561
  * WebSocket transport implementation with automatic reconnection
510
562
  *
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { useState, useCallback, useEffect, useMemo } from "react";
1
+ import { useState, useEffect, useCallback, useMemo } from "react";
2
2
  import { dbToLinear, linearToDb } from "./meters.js";
3
3
  function isWebViewEnvironment() {
4
4
  return globalThis.__WAVECRAFT_IPC__ !== void 0;
@@ -13,16 +13,19 @@ const ERROR_INVALID_PARAMS = -32602;
13
13
  const ERROR_INTERNAL = -32603;
14
14
  const ERROR_PARAM_NOT_FOUND = -32e3;
15
15
  const ERROR_PARAM_OUT_OF_RANGE = -32001;
16
- const METHOD_GET_PARAMETER = "getParameter";
17
- const METHOD_SET_PARAMETER = "setParameter";
18
- const METHOD_GET_ALL_PARAMETERS = "getAllParameters";
19
- const NOTIFICATION_PARAMETER_CHANGED = "parameterChanged";
20
16
  function isIpcResponse(obj) {
21
17
  return typeof obj === "object" && obj !== null && "jsonrpc" in obj && "id" in obj && ("result" in obj || "error" in obj);
22
18
  }
23
19
  function isIpcNotification(obj) {
24
20
  return typeof obj === "object" && obj !== null && "jsonrpc" in obj && "method" in obj && !("id" in obj);
25
21
  }
22
+ function isIpcError(obj) {
23
+ return typeof obj === "object" && obj !== null && "code" in obj && "message" in obj;
24
+ }
25
+ const METHOD_GET_PARAMETER = "getParameter";
26
+ const METHOD_SET_PARAMETER = "setParameter";
27
+ const METHOD_GET_ALL_PARAMETERS = "getAllParameters";
28
+ const NOTIFICATION_PARAMETER_CHANGED = "parameterChanged";
26
29
  var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
27
30
  LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
28
31
  LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
@@ -500,9 +503,7 @@ const _ParameterClient = class _ParameterClient {
500
503
  * Get singleton instance
501
504
  */
502
505
  static getInstance() {
503
- if (!_ParameterClient.instance) {
504
- _ParameterClient.instance = new _ParameterClient();
505
- }
506
+ _ParameterClient.instance ?? (_ParameterClient.instance = new _ParameterClient());
506
507
  return _ParameterClient.instance;
507
508
  }
508
509
  /**
@@ -553,22 +554,18 @@ const _ParameterClient = class _ParameterClient {
553
554
  };
554
555
  _ParameterClient.instance = null;
555
556
  let ParameterClient = _ParameterClient;
556
- let client = null;
557
- function getClient() {
558
- client ?? (client = ParameterClient.getInstance());
559
- return client;
560
- }
561
557
  function useParameter(id) {
562
558
  const [param, setParam] = useState(null);
563
559
  const [isLoading, setIsLoading] = useState(true);
564
560
  const [error, setError] = useState(null);
565
561
  useEffect(() => {
566
562
  let isMounted = true;
563
+ const client = ParameterClient.getInstance();
567
564
  async function loadParameter() {
568
565
  try {
569
566
  setIsLoading(true);
570
567
  setError(null);
571
- const allParams = await getClient().getAllParameters();
568
+ const allParams = await client.getAllParameters();
572
569
  const foundParam = allParams.find((p) => p.id === id);
573
570
  if (isMounted) {
574
571
  if (foundParam) {
@@ -593,7 +590,8 @@ function useParameter(id) {
593
590
  };
594
591
  }, [id]);
595
592
  useEffect(() => {
596
- const unsubscribe = getClient().onParameterChanged((changedId, value) => {
593
+ const client = ParameterClient.getInstance();
594
+ const unsubscribe = client.onParameterChanged((changedId, value) => {
597
595
  if (changedId === id) {
598
596
  setParam((prev) => prev ? { ...prev, value } : null);
599
597
  }
@@ -602,8 +600,9 @@ function useParameter(id) {
602
600
  }, [id]);
603
601
  const setValue = useCallback(
604
602
  async (value) => {
603
+ const client = ParameterClient.getInstance();
605
604
  try {
606
- await getClient().setParameter(id, value);
605
+ await client.setParameter(id, value);
607
606
  setParam((prev) => prev ? { ...prev, value } : null);
608
607
  } catch (err) {
609
608
  setError(err instanceof Error ? err : new Error(String(err)));
@@ -619,10 +618,11 @@ function useAllParameters() {
619
618
  const [isLoading, setIsLoading] = useState(true);
620
619
  const [error, setError] = useState(null);
621
620
  const reload = useCallback(async () => {
621
+ const client = ParameterClient.getInstance();
622
622
  try {
623
623
  setIsLoading(true);
624
624
  setError(null);
625
- const allParams = await getClient().getAllParameters();
625
+ const allParams = await client.getAllParameters();
626
626
  setParams(allParams);
627
627
  } catch (err) {
628
628
  setError(err instanceof Error ? err : new Error(String(err)));
@@ -634,50 +634,15 @@ function useAllParameters() {
634
634
  reload();
635
635
  }, [reload]);
636
636
  useEffect(() => {
637
+ const client = ParameterClient.getInstance();
637
638
  const handleParamChange = (changedId, value) => {
638
639
  setParams((prev) => prev.map((p) => p.id === changedId ? { ...p, value } : p));
639
640
  };
640
- const unsubscribe = getClient().onParameterChanged(handleParamChange);
641
+ const unsubscribe = client.onParameterChanged(handleParamChange);
641
642
  return unsubscribe;
642
643
  }, []);
643
644
  return { params, isLoading, error, reload };
644
645
  }
645
- function useLatencyMonitor(intervalMs = 1e3) {
646
- const [latency, setLatency] = useState(null);
647
- const [measurements, setMeasurements] = useState([]);
648
- const bridge = IpcBridge.getInstance();
649
- useEffect(() => {
650
- let isMounted = true;
651
- async function measure() {
652
- if (!bridge.isConnected()) {
653
- return;
654
- }
655
- try {
656
- const ms = await getClient().ping();
657
- if (isMounted) {
658
- setLatency(ms);
659
- setMeasurements((prev) => [...prev.slice(-99), ms]);
660
- }
661
- } catch (err) {
662
- logger.debug("Ping failed", { error: err });
663
- }
664
- }
665
- measure();
666
- const intervalId = setInterval(measure, intervalMs);
667
- return () => {
668
- isMounted = false;
669
- clearInterval(intervalId);
670
- };
671
- }, [intervalMs, bridge]);
672
- const avg = measurements.length > 0 ? measurements.reduce((sum, val) => sum + val, 0) / measurements.length : 0;
673
- const max = measurements.length > 0 ? Math.max(...measurements) : 0;
674
- return {
675
- latency,
676
- avg,
677
- max,
678
- count: measurements.length
679
- };
680
- }
681
646
  function useParameterGroups(parameters) {
682
647
  return useMemo(() => {
683
648
  const grouped = /* @__PURE__ */ new Map();
@@ -687,7 +652,7 @@ function useParameterGroups(parameters) {
687
652
  existing.push(param);
688
653
  grouped.set(groupName, existing);
689
654
  }
690
- const groups = Array.from(grouped.entries()).map(([name, parameters2]) => ({ name, parameters: parameters2 })).sort((a, b) => {
655
+ const groups = Array.from(grouped.entries()).map(([name, params]) => ({ name, parameters: params })).sort((a, b) => {
691
656
  if (a.name === "Parameters") return -1;
692
657
  if (b.name === "Parameters") return 1;
693
658
  return a.name.localeCompare(b.name);
@@ -734,11 +699,87 @@ function useConnectionStatus() {
734
699
  }, []);
735
700
  return status;
736
701
  }
702
+ function useLatencyMonitor(intervalMs = 1e3) {
703
+ const [latency, setLatency] = useState(null);
704
+ const [measurements, setMeasurements] = useState([]);
705
+ const bridge = IpcBridge.getInstance();
706
+ useEffect(() => {
707
+ let isMounted = true;
708
+ const client = ParameterClient.getInstance();
709
+ async function measure() {
710
+ if (!bridge.isConnected()) {
711
+ return;
712
+ }
713
+ try {
714
+ const ms = await client.ping();
715
+ if (isMounted) {
716
+ setLatency(ms);
717
+ setMeasurements((prev) => [...prev.slice(-99), ms]);
718
+ }
719
+ } catch (err) {
720
+ logger.debug("Ping failed", { error: err });
721
+ }
722
+ }
723
+ measure();
724
+ const intervalId = setInterval(measure, intervalMs);
725
+ return () => {
726
+ isMounted = false;
727
+ clearInterval(intervalId);
728
+ };
729
+ }, [intervalMs, bridge]);
730
+ const avg = measurements.length > 0 ? measurements.reduce((sum, val) => sum + val, 0) / measurements.length : 0;
731
+ const max = measurements.length > 0 ? Math.max(...measurements) : 0;
732
+ return {
733
+ latency,
734
+ avg,
735
+ max,
736
+ count: measurements.length
737
+ };
738
+ }
739
+ function useMeterFrame(intervalMs = 50) {
740
+ const [frame, setFrame] = useState(null);
741
+ useEffect(() => {
742
+ let isMounted = true;
743
+ const bridge = IpcBridge.getInstance();
744
+ async function fetchFrame() {
745
+ if (!bridge.isConnected()) return;
746
+ try {
747
+ const result = await bridge.invoke("getMeterFrame");
748
+ if (isMounted && result.frame) {
749
+ setFrame(result.frame);
750
+ }
751
+ } catch {
752
+ }
753
+ }
754
+ fetchFrame();
755
+ const intervalId = setInterval(fetchFrame, intervalMs);
756
+ return () => {
757
+ isMounted = false;
758
+ clearInterval(intervalId);
759
+ };
760
+ }, [intervalMs]);
761
+ return frame;
762
+ }
737
763
  async function requestResize(width, height) {
738
764
  const bridge = IpcBridge.getInstance();
739
765
  const result = await bridge.invoke("requestResize", { width, height });
740
766
  return result.accepted;
741
767
  }
768
+ function useWindowResizeSync() {
769
+ useEffect(() => {
770
+ const handleResize = () => {
771
+ const width = window.innerWidth;
772
+ const height = window.innerHeight;
773
+ requestResize(width, height).catch((err) => {
774
+ logger.error("Failed to notify host of resize", { error: err, width, height });
775
+ });
776
+ };
777
+ window.addEventListener("resize", handleResize);
778
+ return () => {
779
+ window.removeEventListener("resize", handleResize);
780
+ };
781
+ }, []);
782
+ }
742
783
  function useRequestResize() {
743
784
  return requestResize;
744
785
  }
@@ -768,6 +809,9 @@ export {
768
809
  dbToLinear,
769
810
  getMeterFrame,
770
811
  isBrowserEnvironment,
812
+ isIpcError,
813
+ isIpcNotification,
814
+ isIpcResponse,
771
815
  isWebViewEnvironment,
772
816
  linearToDb,
773
817
  logger,
@@ -775,8 +819,10 @@ export {
775
819
  useAllParameters,
776
820
  useConnectionStatus,
777
821
  useLatencyMonitor,
822
+ useMeterFrame,
778
823
  useParameter,
779
824
  useParameterGroups,
780
- useRequestResize
825
+ useRequestResize,
826
+ useWindowResizeSync
781
827
  };
782
828
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/environment.ts","../src/types.ts","../src/logger/Logger.ts","../src/transports/NativeTransport.ts","../src/transports/WebSocketTransport.ts","../src/transports/index.ts","../src/IpcBridge.ts","../src/ParameterClient.ts","../src/hooks.ts","../src/useParameterGroups.ts","../src/useConnectionStatus.ts","../src/resize.ts","../src/meter-ipc.ts"],"sourcesContent":["/**\n * Environment Detection\n *\n * Determines if the code is running in WKWebView (production)\n * or a browser (development).\n */\n\n/**\n * Check if running in a WKWebView environment (production)\n * @returns true if globalThis.wavecraft IPC primitives are available\n */\nexport function isWebViewEnvironment(): boolean {\n return globalThis.__WAVECRAFT_IPC__ !== undefined;\n}\n\n/**\n * Check if running in a browser environment (development)\n * @returns true if IPC primitives are NOT available\n */\nexport function isBrowserEnvironment(): boolean {\n return !isWebViewEnvironment();\n}\n","/**\n * IPC Types - TypeScript definitions matching Rust protocol\n *\n * These types must stay in sync with engine/crates/protocol/src/ipc.rs\n */\n\n// ============================================================================\n// JSON-RPC 2.0 Message Types\n// ============================================================================\n\nexport type RequestId = string | number;\n\nexport interface IpcRequest {\n jsonrpc: '2.0';\n id: RequestId;\n method: string;\n params?: unknown;\n}\n\nexport interface IpcResponse {\n jsonrpc: '2.0';\n id: RequestId;\n result?: unknown;\n error?: IpcError;\n}\n\nexport interface IpcNotification {\n jsonrpc: '2.0';\n method: string;\n params?: unknown;\n}\n\nexport interface IpcError {\n code: number;\n message: string;\n data?: unknown;\n}\n\n// ============================================================================\n// Error Codes (matching Rust constants)\n// ============================================================================\n\nexport const ERROR_PARSE = -32700;\nexport const ERROR_INVALID_REQUEST = -32600;\nexport const ERROR_METHOD_NOT_FOUND = -32601;\nexport const ERROR_INVALID_PARAMS = -32602;\nexport const ERROR_INTERNAL = -32603;\nexport const ERROR_PARAM_NOT_FOUND = -32000;\nexport const ERROR_PARAM_OUT_OF_RANGE = -32001;\n\n// ============================================================================\n// Parameter Types\n// ============================================================================\n\nexport type ParameterType = 'float' | 'bool' | 'enum';\n\nexport interface ParameterInfo {\n id: string;\n name: string;\n type: ParameterType;\n value: number;\n default: number;\n unit?: string;\n group?: string;\n}\n\n// ============================================================================\n// Method-Specific Types\n// ============================================================================\n\n// getParameter\nexport interface GetParameterParams {\n id: string;\n}\n\nexport interface GetParameterResult {\n id: string;\n value: number;\n}\n\n// setParameter\nexport interface SetParameterParams {\n id: string;\n value: number;\n}\n\nexport type SetParameterResult = Record<string, never>;\n\n// getAllParameters\nexport interface GetAllParametersResult {\n parameters: ParameterInfo[];\n}\n\n// Notification: parameterChanged\nexport interface ParameterChangedNotification {\n id: string;\n value: number;\n}\n\n// ============================================================================\n// Method Names (matching Rust constants)\n// ============================================================================\n\nexport const METHOD_GET_PARAMETER = 'getParameter';\nexport const METHOD_SET_PARAMETER = 'setParameter';\nexport const METHOD_GET_ALL_PARAMETERS = 'getAllParameters';\nexport const NOTIFICATION_PARAMETER_CHANGED = 'parameterChanged';\n\n// ============================================================================\n// Injected IPC Primitives (from Rust)\n// ============================================================================\n\nexport interface WavecraftIpcPrimitives {\n postMessage: (message: string) => void;\n setReceiveCallback: (callback: (message: string) => void) => void;\n onParamUpdate?: (listener: (notification: unknown) => void) => () => void;\n _receive: (message: string) => void; // Internal, called by Rust\n _onParamUpdate?: (message: unknown) => void; // Internal, called by Rust\n}\n\ndeclare global {\n var __WAVECRAFT_IPC__: WavecraftIpcPrimitives | undefined;\n}\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\nexport function isIpcResponse(obj: unknown): obj is IpcResponse {\n return (\n typeof obj === 'object' &&\n obj !== null &&\n 'jsonrpc' in obj &&\n 'id' in obj &&\n ('result' in obj || 'error' in obj)\n );\n}\n\nexport function isIpcNotification(obj: unknown): obj is IpcNotification {\n return (\n typeof obj === 'object' && obj !== null && 'jsonrpc' in obj && 'method' in obj && !('id' in obj)\n );\n}\n\nexport function isIpcError(obj: unknown): obj is IpcError {\n return typeof obj === 'object' && obj !== null && 'code' in obj && 'message' in obj;\n}\n","/**\n * Logger - Structured logging abstraction for the UI.\n *\n * Wraps browser console API with severity levels and structured context.\n * In production builds, logs can be filtered by level at runtime.\n */\n\nexport enum LogLevel {\n DEBUG = 0,\n INFO = 1,\n WARN = 2,\n ERROR = 3,\n}\n\nexport interface LogContext {\n [key: string]: unknown;\n}\n\n/**\n * Logger class providing structured logging with severity levels.\n *\n * Example usage:\n * ```typescript\n * const logger = new Logger({ minLevel: LogLevel.INFO });\n * logger.info('Parameter updated', { id: 'gain', value: 0.5 });\n * logger.error('IPC failed', { method: 'getParameter', error });\n * ```\n */\nexport class Logger {\n private minLevel: LogLevel;\n\n constructor(options: { minLevel?: LogLevel } = {}) {\n this.minLevel = options.minLevel ?? LogLevel.DEBUG;\n }\n\n /**\n * Set the minimum log level at runtime.\n */\n setMinLevel(level: LogLevel): void {\n this.minLevel = level;\n }\n\n /**\n * Log debug message (verbose tracing).\n */\n debug(message: string, context?: LogContext): void {\n if (this.minLevel <= LogLevel.DEBUG) {\n console.debug(`[DEBUG] ${message}`, context ?? {});\n }\n }\n\n /**\n * Log informational message.\n */\n info(message: string, context?: LogContext): void {\n if (this.minLevel <= LogLevel.INFO) {\n console.info(`[INFO] ${message}`, context ?? {});\n }\n }\n\n /**\n * Log warning message.\n */\n warn(message: string, context?: LogContext): void {\n if (this.minLevel <= LogLevel.WARN) {\n console.warn(`[WARN] ${message}`, context ?? {});\n }\n }\n\n /**\n * Log error message.\n */\n error(message: string, context?: LogContext): void {\n if (this.minLevel <= LogLevel.ERROR) {\n console.error(`[ERROR] ${message}`, context ?? {});\n }\n }\n}\n\n/**\n * Global logger instance for the UI.\n * Configure once at app startup, use throughout the codebase.\n */\nexport const logger = new Logger({\n minLevel: import.meta.env.DEV ? LogLevel.DEBUG : LogLevel.INFO,\n});\n","/**\n * NativeTransport - WKWebView IPC transport\n *\n * Wraps the native IPC primitives injected by the Rust engine\n * into a WKWebView. This transport is always connected.\n */\n\nimport type { Transport, NotificationCallback } from './Transport';\nimport type { IpcResponse, IpcNotification, RequestId } from '../types';\nimport { isIpcResponse, isIpcNotification } from '../types';\nimport { logger } from '../logger/Logger';\n\ninterface PendingRequest {\n resolve: (response: string) => void;\n reject: (error: Error) => void;\n timeoutId: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Native WKWebView transport implementation\n *\n * Uses the __WAVECRAFT_IPC__ primitives injected by the Rust engine.\n */\nexport class NativeTransport implements Transport {\n private readonly pendingRequests = new Map<RequestId, PendingRequest>();\n private readonly notificationCallbacks = new Set<NotificationCallback>();\n private readonly primitives: typeof globalThis.__WAVECRAFT_IPC__;\n\n constructor() {\n this.primitives = globalThis.__WAVECRAFT_IPC__;\n\n if (!this.primitives) {\n throw new Error(\n 'NativeTransport: __WAVECRAFT_IPC__ primitives not found. ' +\n 'Ensure this runs in a WKWebView with injected IPC.'\n );\n }\n\n // Set up receive callback for responses\n this.primitives.setReceiveCallback((message: string) => {\n this.handleIncomingMessage(message);\n });\n\n // Set up parameter update listener for pushed updates\n if (this.primitives.onParamUpdate) {\n this.primitives.onParamUpdate((notification: unknown) => {\n if (isIpcNotification(notification)) {\n this.handleNotification(notification);\n }\n });\n }\n }\n\n /**\n * Send a JSON-RPC request and wait for response\n */\n async send(request: string): Promise<string> {\n if (!this.primitives) {\n throw new Error('NativeTransport: Primitives not available');\n }\n\n const parsedRequest = JSON.parse(request);\n const id = parsedRequest.id;\n\n if (id === undefined || id === null) {\n throw new Error('NativeTransport: Request must have an id');\n }\n\n // Create promise for response\n const responsePromise = new Promise<string>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n this.pendingRequests.delete(id);\n reject(new Error(`Request timeout: ${parsedRequest.method}`));\n }, 5000); // 5 second timeout\n\n this.pendingRequests.set(id, { resolve, reject, timeoutId });\n });\n\n // Send request\n this.primitives.postMessage(request);\n\n return responsePromise;\n }\n\n /**\n * Register a callback for incoming notifications\n */\n onNotification(callback: NotificationCallback): () => void {\n this.notificationCallbacks.add(callback);\n\n // Return cleanup function\n return () => {\n this.notificationCallbacks.delete(callback);\n };\n }\n\n /**\n * Check if transport is connected (native is always connected)\n */\n isConnected(): boolean {\n return true;\n }\n\n /**\n * Clean up resources\n */\n dispose(): void {\n // Cancel all pending requests\n for (const [id, { reject, timeoutId }] of this.pendingRequests.entries()) {\n clearTimeout(timeoutId);\n reject(new Error('Transport disposed'));\n this.pendingRequests.delete(id);\n }\n\n // Clear notification callbacks\n this.notificationCallbacks.clear();\n }\n\n /**\n * Handle incoming message (response or notification)\n */\n private handleIncomingMessage(message: string): void {\n try {\n const parsed = JSON.parse(message);\n\n if (isIpcResponse(parsed)) {\n this.handleResponse(parsed);\n } else if (isIpcNotification(parsed)) {\n this.handleNotification(parsed);\n }\n } catch (error) {\n logger.error('Failed to parse incoming message', { error });\n }\n }\n\n /**\n * Handle JSON-RPC response\n */\n private handleResponse(response: IpcResponse): void {\n const pending = this.pendingRequests.get(response.id);\n\n if (pending) {\n clearTimeout(pending.timeoutId);\n this.pendingRequests.delete(response.id);\n pending.resolve(JSON.stringify(response));\n }\n }\n\n /**\n * Handle notification and dispatch to listeners\n */\n private handleNotification(notification: IpcNotification): void {\n const notificationJson = JSON.stringify(notification);\n\n for (const callback of this.notificationCallbacks) {\n try {\n callback(notificationJson);\n } catch (error) {\n logger.error('Error in notification callback', { error });\n }\n }\n }\n}\n","/**\n * WebSocketTransport - Browser WebSocket IPC transport\n *\n * Connects to the standalone dev server over WebSocket for\n * browser-based UI development with real engine communication.\n */\n\nimport type { Transport, NotificationCallback } from './Transport';\nimport type { IpcResponse, IpcNotification, RequestId } from '../types';\nimport { isIpcResponse, isIpcNotification } from '../types';\nimport { logger } from '../logger/Logger';\n\ninterface PendingRequest {\n resolve: (response: string) => void;\n reject: (error: Error) => void;\n timeoutId: ReturnType<typeof setTimeout>;\n}\n\ninterface WebSocketTransportOptions {\n /** WebSocket server URL (e.g., ws://127.0.0.1:9000) */\n url: string;\n /** Reconnection delay in milliseconds (default: 1000) */\n reconnectDelayMs?: number;\n /** Maximum reconnection attempts (default: 5, use Infinity for unlimited) */\n maxReconnectAttempts?: number;\n}\n\n/**\n * WebSocket transport implementation with automatic reconnection\n *\n * Connects to the standalone dev server for browser-based UI development.\n */\nexport class WebSocketTransport implements Transport {\n private readonly url: string;\n private readonly reconnectDelayMs: number;\n private readonly maxReconnectAttempts: number;\n\n private ws: WebSocket | null = null;\n private isConnecting = false;\n private reconnectAttempts = 0;\n private reconnectTimeoutId: ReturnType<typeof setTimeout> | null = null;\n private isDisposed = false;\n private maxAttemptsReached = false; // Flag to stop reconnection after max attempts\n\n private readonly pendingRequests = new Map<RequestId, PendingRequest>();\n private readonly notificationCallbacks = new Set<NotificationCallback>();\n\n constructor(options: WebSocketTransportOptions) {\n this.url = options.url;\n this.reconnectDelayMs = options.reconnectDelayMs ?? 1000;\n this.maxReconnectAttempts = options.maxReconnectAttempts ?? 5;\n\n // Start connection immediately\n this.connect();\n }\n\n /**\n * Send a JSON-RPC request and wait for response\n */\n async send(request: string): Promise<string> {\n if (!this.isConnected()) {\n throw new Error('WebSocketTransport: Not connected');\n }\n\n const parsedRequest = JSON.parse(request);\n const id = parsedRequest.id;\n\n if (id === undefined || id === null) {\n throw new Error('WebSocketTransport: Request must have an id');\n }\n\n // Create promise for response\n const responsePromise = new Promise<string>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n this.pendingRequests.delete(id);\n reject(new Error(`Request timeout: ${parsedRequest.method}`));\n }, 5000); // 5 second timeout\n\n this.pendingRequests.set(id, { resolve, reject, timeoutId });\n });\n\n // Send request\n if (!this.ws) {\n throw new Error('WebSocketTransport: Connection lost');\n }\n this.ws.send(request);\n\n return responsePromise;\n }\n\n /**\n * Register a callback for incoming notifications\n */\n onNotification(callback: NotificationCallback): () => void {\n this.notificationCallbacks.add(callback);\n\n return () => {\n this.notificationCallbacks.delete(callback);\n };\n }\n\n /**\n * Check if transport is connected\n */\n isConnected(): boolean {\n return this.ws !== null && this.ws.readyState === WebSocket.OPEN;\n }\n\n /**\n * Clean up resources and close connection\n */\n dispose(): void {\n this.isDisposed = true;\n\n // Clear reconnect timer\n if (this.reconnectTimeoutId) {\n clearTimeout(this.reconnectTimeoutId);\n this.reconnectTimeoutId = null;\n }\n\n // Close WebSocket\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n\n // Cancel all pending requests\n for (const [id, { reject, timeoutId }] of this.pendingRequests.entries()) {\n clearTimeout(timeoutId);\n reject(new Error('Transport disposed'));\n this.pendingRequests.delete(id);\n }\n\n // Clear notification callbacks\n this.notificationCallbacks.clear();\n }\n\n /**\n * Attempt to connect to WebSocket server\n */\n private connect(): void {\n if (this.isDisposed || this.isConnecting || this.isConnected()) {\n return;\n }\n\n this.isConnecting = true;\n\n try {\n this.ws = new WebSocket(this.url);\n\n this.ws.onopen = (): void => {\n this.isConnecting = false;\n this.reconnectAttempts = 0;\n logger.info('WebSocketTransport connected', { url: this.url });\n };\n\n this.ws.onmessage = (event: MessageEvent): void => {\n this.handleIncomingMessage(event.data);\n };\n\n this.ws.onerror = (error: Event): void => {\n logger.error('WebSocketTransport connection error', { error });\n };\n\n this.ws.onclose = (): void => {\n this.isConnecting = false;\n this.ws = null;\n\n if (!this.isDisposed && !this.maxAttemptsReached) {\n this.scheduleReconnect();\n }\n };\n } catch (error) {\n this.isConnecting = false;\n logger.error('WebSocketTransport failed to create WebSocket', { error, url: this.url });\n this.scheduleReconnect();\n }\n }\n\n /**\n * Schedule reconnection attempt with exponential backoff\n */\n private scheduleReconnect(): void {\n if (this.isDisposed || this.maxAttemptsReached) {\n return;\n }\n\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n this.maxAttemptsReached = true;\n logger.error('WebSocketTransport max reconnect attempts reached', {\n maxAttempts: this.maxReconnectAttempts,\n });\n // Close the WebSocket to stop browser reconnection attempts\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n return;\n }\n\n this.reconnectAttempts++;\n const delay = this.reconnectDelayMs * Math.pow(2, this.reconnectAttempts - 1); // Exponential backoff\n\n logger.debug('WebSocketTransport reconnecting', {\n delayMs: delay,\n attempt: this.reconnectAttempts,\n maxAttempts: this.maxReconnectAttempts,\n });\n\n this.reconnectTimeoutId = setTimeout(() => {\n this.reconnectTimeoutId = null;\n this.connect();\n }, delay);\n }\n\n /**\n * Handle incoming message (response or notification)\n */\n private handleIncomingMessage(message: string): void {\n try {\n const parsed = JSON.parse(message);\n\n if (isIpcResponse(parsed)) {\n this.handleResponse(parsed);\n } else if (isIpcNotification(parsed)) {\n this.handleNotification(parsed);\n }\n } catch (error) {\n logger.error('WebSocketTransport failed to parse incoming message', { error, message });\n }\n }\n\n /**\n * Handle JSON-RPC response\n */\n private handleResponse(response: IpcResponse): void {\n const pending = this.pendingRequests.get(response.id);\n\n if (pending) {\n clearTimeout(pending.timeoutId);\n this.pendingRequests.delete(response.id);\n pending.resolve(JSON.stringify(response));\n }\n }\n\n /**\n * Handle notification and dispatch to listeners\n */\n private handleNotification(notification: IpcNotification): void {\n const notificationJson = JSON.stringify(notification);\n\n for (const callback of this.notificationCallbacks) {\n try {\n callback(notificationJson);\n } catch (error) {\n logger.error('WebSocketTransport notification callback error', {\n error,\n method: notification.method,\n });\n }\n }\n }\n}\n","/**\n * Transport Factory\n *\n * Provides automatic transport selection based on runtime environment:\n * - WKWebView: NativeTransport\n * - Browser: WebSocketTransport\n */\n\nimport type { Transport } from './Transport';\nimport { NativeTransport } from './NativeTransport';\nimport { WebSocketTransport } from './WebSocketTransport';\nimport { isWebViewEnvironment } from '../environment';\n\n// Export transport types\nexport type { Transport, NotificationCallback } from './Transport';\nexport { NativeTransport } from './NativeTransport';\nexport { WebSocketTransport } from './WebSocketTransport';\n\n// Environment detection at module scope (evaluated once)\nconst IS_WEBVIEW = isWebViewEnvironment();\n\n/**\n * Singleton transport instance\n */\nlet transportInstance: Transport | null = null;\n\n/**\n * Get the transport instance (singleton)\n *\n * Automatically selects:\n * - NativeTransport in WKWebView (production)\n * - WebSocketTransport in browser (development)\n *\n * @returns Transport instance\n */\nexport function getTransport(): Transport {\n if (transportInstance) {\n return transportInstance;\n }\n\n if (IS_WEBVIEW) {\n // Native WKWebView transport\n transportInstance = new NativeTransport();\n } else {\n // WebSocket transport for browser development\n const wsUrl = import.meta.env.VITE_WS_URL || 'ws://127.0.0.1:9000';\n transportInstance = new WebSocketTransport({ url: wsUrl });\n }\n\n return transportInstance;\n}\n\n/**\n * Check if transport is available\n *\n * @returns true if transport exists and is connected\n */\nexport function hasTransport(): boolean {\n return transportInstance?.isConnected() ?? false;\n}\n\n/**\n * Dispose the current transport (mainly for tests)\n */\nexport function disposeTransport(): void {\n if (transportInstance) {\n transportInstance.dispose();\n transportInstance = null;\n }\n}\n","/**\n * IpcBridge - Low-level IPC communication layer\n *\n * Provides a Promise-based API for sending requests and receiving responses\n * using pluggable transport implementations (NativeTransport, WebSocketTransport).\n */\n\nimport type { IpcRequest, IpcResponse, IpcNotification } from './types';\nimport { isIpcNotification } from './types';\nimport type { Transport } from './transports';\nimport { getTransport } from './transports';\nimport { logger } from './logger/Logger';\n\ntype EventCallback<T> = (data: T) => void;\n\nexport class IpcBridge {\n private static instance: IpcBridge | null = null;\n private nextId = 1;\n private readonly eventListeners = new Map<string, Set<EventCallback<unknown>>>();\n private transport: Transport | null = null;\n private isInitialized = false;\n private lastDisconnectWarning = 0;\n private readonly DISCONNECT_WARNING_INTERVAL_MS = 5000; // Log warning max once per 5s\n\n private constructor() {\n // Lazy initialization on first use\n }\n\n /**\n * Initialize the IPC bridge (lazy)\n */\n private initialize(): void {\n if (this.isInitialized) {\n return;\n }\n\n // Get transport (auto-selected based on environment)\n this.transport = getTransport();\n\n // Subscribe to notifications from transport\n this.transport.onNotification((notificationJson: string) => {\n try {\n const parsed = JSON.parse(notificationJson);\n if (isIpcNotification(parsed)) {\n this.handleNotification(parsed);\n }\n } catch (error) {\n logger.error('Failed to parse notification', { error });\n }\n });\n\n this.isInitialized = true;\n }\n\n /**\n * Get singleton instance\n */\n public static getInstance(): IpcBridge {\n IpcBridge.instance ??= new IpcBridge();\n return IpcBridge.instance;\n }\n\n /**\n * Check if the bridge is connected\n */\n public isConnected(): boolean {\n // Trigger lazy initialization so transport gets created\n this.initialize();\n return this.transport?.isConnected() ?? false;\n }\n\n /**\n * Invoke a method and wait for response\n */\n public async invoke<TResult>(method: string, params?: unknown): Promise<TResult> {\n // Lazy initialization on first use\n this.initialize();\n\n if (!this.transport?.isConnected()) {\n // Rate-limit disconnect warnings to avoid console spam\n const now = Date.now();\n if (now - this.lastDisconnectWarning > this.DISCONNECT_WARNING_INTERVAL_MS) {\n logger.warn('Transport not connected, call will fail. Waiting for reconnection...');\n this.lastDisconnectWarning = now;\n }\n throw new Error('IpcBridge: Transport not connected');\n }\n\n const id = this.nextId++;\n const request: IpcRequest = {\n jsonrpc: '2.0',\n id,\n method,\n params,\n };\n\n // Serialize request\n const requestJson = JSON.stringify(request);\n\n // Send via transport (transport handles timeout internally)\n const responseJson = await this.transport.send(requestJson);\n\n // Parse response\n const response: IpcResponse = JSON.parse(responseJson);\n\n // Check for error\n if (response.error) {\n throw new Error(`IPC Error ${response.error.code}: ${response.error.message}`);\n }\n\n return response.result as TResult;\n }\n\n /**\n * Subscribe to notification events\n */\n public on<T>(event: string, callback: EventCallback<T>): () => void {\n // Lazy initialization on first use\n this.initialize();\n\n if (!this.eventListeners.has(event)) {\n this.eventListeners.set(event, new Set());\n }\n\n const listeners = this.eventListeners.get(event);\n if (!listeners) {\n throw new Error(`Event listener set not found for event: ${event}`);\n }\n listeners.add(callback as EventCallback<unknown>);\n\n // Return unsubscribe function\n return () => {\n listeners.delete(callback as EventCallback<unknown>);\n };\n }\n\n /**\n * Handle notification and dispatch to listeners\n */\n private handleNotification(notification: IpcNotification): void {\n const listeners = this.eventListeners.get(notification.method);\n if (!listeners || listeners.size === 0) {\n return;\n }\n\n for (const listener of listeners) {\n try {\n listener(notification.params);\n } catch (error) {\n logger.error('Error in event listener', { event: notification.method, error });\n }\n }\n }\n}\n","/**\n * ParameterClient - High-level typed API for parameter operations\n *\n * Provides typed methods for interacting with plugin parameters.\n */\n\nimport { IpcBridge } from './IpcBridge';\nimport type {\n ParameterInfo,\n GetParameterResult,\n SetParameterResult,\n GetAllParametersResult,\n ParameterChangedNotification,\n} from './types';\nimport {\n METHOD_GET_PARAMETER,\n METHOD_SET_PARAMETER,\n METHOD_GET_ALL_PARAMETERS,\n NOTIFICATION_PARAMETER_CHANGED,\n} from './types';\n\ntype ParameterChangeCallback = (id: string, value: number) => void;\n\nexport class ParameterClient {\n private static instance: ParameterClient | null = null;\n private bridge: IpcBridge;\n\n private constructor() {\n this.bridge = IpcBridge.getInstance();\n }\n\n /**\n * Get singleton instance\n */\n public static getInstance(): ParameterClient {\n if (!ParameterClient.instance) {\n ParameterClient.instance = new ParameterClient();\n }\n return ParameterClient.instance;\n }\n\n /**\n * Get a single parameter's current value and metadata\n */\n public async getParameter(id: string): Promise<GetParameterResult> {\n return this.bridge.invoke<GetParameterResult>(METHOD_GET_PARAMETER, { id });\n }\n\n /**\n * Set a parameter's value\n * @param id Parameter ID\n * @param value Normalized value [0.0, 1.0]\n */\n public async setParameter(id: string, value: number): Promise<void> {\n await this.bridge.invoke<SetParameterResult>(METHOD_SET_PARAMETER, {\n id,\n value,\n });\n }\n\n /**\n * Get all parameters with their current values and metadata\n */\n public async getAllParameters(): Promise<ParameterInfo[]> {\n const result = await this.bridge.invoke<GetAllParametersResult>(METHOD_GET_ALL_PARAMETERS);\n return result.parameters;\n }\n\n /**\n * Test connectivity with Rust backend\n * @returns Roundtrip time in milliseconds\n */\n public async ping(): Promise<number> {\n const start = performance.now();\n await this.bridge.invoke('ping');\n const end = performance.now();\n return end - start;\n }\n\n /**\n * Subscribe to parameter change notifications\n * @returns Unsubscribe function\n */\n public onParameterChanged(callback: ParameterChangeCallback): () => void {\n return this.bridge.on<ParameterChangedNotification>(NOTIFICATION_PARAMETER_CHANGED, (data) => {\n if (data && typeof data === 'object' && 'id' in data && 'value' in data) {\n callback(data.id as string, data.value as number);\n }\n });\n }\n}\n","/**\n * React Hooks - High-level React integration for parameter management\n *\n * The hooks use the ParameterClient which automatically selects the correct\n * transport (WebSocket for browser dev, Native for WKWebView production).\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { ParameterClient } from './ParameterClient';\nimport { IpcBridge } from './IpcBridge';\nimport type { ParameterInfo } from './types';\nimport { logger } from './logger/Logger';\n\n// Lazy client initialization\nlet client: ParameterClient | null = null;\nfunction getClient(): ParameterClient {\n client ??= ParameterClient.getInstance();\n return client;\n}\n\n// ============================================================================\n// useParameter - Hook for managing a single parameter\n// ============================================================================\n\nexport interface UseParameterResult {\n param: ParameterInfo | null;\n setValue: (value: number) => Promise<void>;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useParameter(id: string): UseParameterResult {\n const [param, setParam] = useState<ParameterInfo | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n // Load initial parameter value\n useEffect(() => {\n let isMounted = true;\n\n async function loadParameter(): Promise<void> {\n try {\n setIsLoading(true);\n setError(null);\n\n // Get all parameters and find the one we want\n const allParams = await getClient().getAllParameters();\n const foundParam = allParams.find((p) => p.id === id);\n\n if (isMounted) {\n if (foundParam) {\n setParam(foundParam);\n } else {\n setError(new Error(`Parameter not found: ${id}`));\n }\n }\n } catch (err) {\n if (isMounted) {\n setError(err instanceof Error ? err : new Error(String(err)));\n }\n } finally {\n if (isMounted) {\n setIsLoading(false);\n }\n }\n }\n\n loadParameter();\n\n return (): void => {\n isMounted = false;\n };\n }, [id]);\n\n // Subscribe to parameter changes\n useEffect(() => {\n const unsubscribe = getClient().onParameterChanged((changedId, value) => {\n if (changedId === id) {\n setParam((prev) => (prev ? { ...prev, value } : null));\n }\n });\n\n return unsubscribe;\n }, [id]);\n\n // Set parameter value\n const setValue = useCallback(\n async (value: number) => {\n try {\n await getClient().setParameter(id, value);\n // Optimistically update local state\n setParam((prev) => (prev ? { ...prev, value } : null));\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n throw err;\n }\n },\n [id]\n );\n\n return { param, setValue, isLoading, error };\n}\n\n// ============================================================================\n// useAllParameters - Hook for loading all parameters\n// ============================================================================\n\nexport interface UseAllParametersResult {\n params: ParameterInfo[];\n isLoading: boolean;\n error: Error | null;\n reload: () => Promise<void>;\n}\n\nexport function useAllParameters(): UseAllParametersResult {\n const [params, setParams] = useState<ParameterInfo[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const reload = useCallback(async () => {\n try {\n setIsLoading(true);\n setError(null);\n const allParams = await getClient().getAllParameters();\n setParams(allParams);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n }, []);\n\n // Load on mount\n useEffect(() => {\n reload();\n }, [reload]);\n\n // Subscribe to parameter changes\n useEffect(() => {\n // Note: Nesting depth warning accepted here - inline mapper is idiomatic React pattern\n const handleParamChange = (changedId: string, value: number): void => {\n setParams((prev) => prev.map((p) => (p.id === changedId ? { ...p, value } : p)));\n };\n\n const unsubscribe = getClient().onParameterChanged(handleParamChange);\n\n return unsubscribe;\n }, []);\n\n return { params, isLoading, error, reload };\n}\n\n// ============================================================================\n// useLatencyMonitor - Hook for monitoring IPC latency\n// ============================================================================\n\nexport interface UseLatencyMonitorResult {\n latency: number | null;\n avg: number;\n max: number;\n count: number;\n}\n\nexport function useLatencyMonitor(intervalMs = 1000): UseLatencyMonitorResult {\n const [latency, setLatency] = useState<number | null>(null);\n const [measurements, setMeasurements] = useState<number[]>([]);\n const bridge = IpcBridge.getInstance();\n\n useEffect(() => {\n let isMounted = true;\n\n async function measure(): Promise<void> {\n // Only measure when connected\n if (!bridge.isConnected()) {\n return;\n }\n\n try {\n const ms = await getClient().ping();\n if (isMounted) {\n setLatency(ms);\n setMeasurements((prev) => [...prev.slice(-99), ms]); // Keep last 100\n }\n } catch (err) {\n logger.debug('Ping failed', { error: err });\n }\n }\n\n // Initial measurement\n measure();\n\n // Periodic measurements\n const intervalId = setInterval(measure, intervalMs);\n\n return (): void => {\n isMounted = false;\n clearInterval(intervalId);\n };\n }, [intervalMs, bridge]);\n\n // Calculate statistics\n const avg =\n measurements.length > 0\n ? measurements.reduce((sum, val) => sum + val, 0) / measurements.length\n : 0;\n\n const max = measurements.length > 0 ? Math.max(...measurements) : 0;\n\n return {\n latency,\n avg,\n max,\n count: measurements.length,\n };\n}\n","/**\n * Hook for organizing parameters into groups based on their group metadata.\n *\n * This hook takes an array of parameters and organizes them into groups\n * for better UI organization. Parameters without a group are placed in\n * a default \"Parameters\" group.\n */\n\nimport { useMemo } from 'react';\nimport type { ParameterInfo } from './types';\n\nexport interface ParameterGroup {\n name: string;\n parameters: ParameterInfo[];\n}\n\n/**\n * Organize parameters into groups based on their group metadata.\n *\n * @param parameters - Array of all parameters\n * @returns Array of parameter groups, each containing parameters for that group\n *\n * @example\n * ```tsx\n * const { parameters } = useAllParameters();\n * const groups = useParameterGroups(parameters);\n *\n * return (\n * <>\n * {groups.map(group => (\n * <ParameterGroup key={group.name} group={group} />\n * ))}\n * </>\n * );\n * ```\n */\nexport function useParameterGroups(parameters: ParameterInfo[]): ParameterGroup[] {\n return useMemo(() => {\n // Group parameters by their group field\n const grouped = new Map<string, ParameterInfo[]>();\n\n for (const param of parameters) {\n const groupName = param.group ?? 'Parameters';\n const existing = grouped.get(groupName) ?? [];\n existing.push(param);\n grouped.set(groupName, existing);\n }\n\n // Convert map to array of groups, sorted by group name\n // Exception: \"Parameters\" (default group) always comes first\n const groups: ParameterGroup[] = Array.from(grouped.entries())\n .map(([name, parameters]) => ({ name, parameters }))\n .sort((a, b) => {\n if (a.name === 'Parameters') return -1;\n if (b.name === 'Parameters') return 1;\n return a.name.localeCompare(b.name);\n });\n\n return groups;\n }, [parameters]);\n}\n","/**\n * useConnectionStatus - Monitor transport connection status\n *\n * Provides real-time connection status updates for the IPC transport.\n * Useful for showing connection indicators in the UI.\n */\n\nimport { useEffect, useState } from 'react';\nimport { IpcBridge } from './IpcBridge';\nimport { isWebViewEnvironment } from './environment';\n\nexport type TransportType = 'native' | 'websocket' | 'none';\n\nexport interface ConnectionStatus {\n /** Whether transport is connected and ready */\n connected: boolean;\n /** Type of transport being used */\n transport: TransportType;\n}\n\n/**\n * Hook to monitor IPC connection status\n *\n * Polls the transport every second to detect connection changes.\n * Native transport is always connected, WebSocket may reconnect.\n *\n * @returns Connection status object\n */\nexport function useConnectionStatus(): ConnectionStatus {\n const [status, setStatus] = useState<ConnectionStatus>(() => {\n const bridge = IpcBridge.getInstance();\n const connected = bridge.isConnected();\n\n let transport: TransportType;\n if (isWebViewEnvironment()) {\n transport = 'native';\n } else if (connected) {\n transport = 'websocket';\n } else {\n transport = 'none';\n }\n\n return { connected, transport };\n });\n\n useEffect(() => {\n const bridge = IpcBridge.getInstance();\n\n // Poll connection status every second\n const intervalId = setInterval(() => {\n const connected = bridge.isConnected();\n\n let transport: TransportType;\n if (isWebViewEnvironment()) {\n transport = 'native';\n } else if (connected) {\n transport = 'websocket';\n } else {\n transport = 'none';\n }\n\n setStatus((prevStatus) => {\n // Only update if status changed (avoid unnecessary re-renders)\n if (prevStatus.connected !== connected || prevStatus.transport !== transport) {\n return { connected, transport };\n }\n return prevStatus;\n });\n }, 1000);\n\n // Cleanup interval on unmount\n return (): void => {\n clearInterval(intervalId);\n };\n }, []);\n\n return status;\n}\n","/**\n * Window resize utilities\n *\n * Provides functions for requesting window resize from the host DAW.\n */\n\nimport { IpcBridge } from './IpcBridge';\n\nexport interface RequestResizeParams {\n width: number;\n height: number;\n}\n\nexport interface RequestResizeResult {\n accepted: boolean;\n}\n\n/**\n * Request resize of the editor window\n *\n * @param width - Desired width in logical pixels\n * @param height - Desired height in logical pixels\n * @returns Promise that resolves to true if accepted, false if rejected\n *\n * @example\n * ```ts\n * const accepted = await requestResize(1024, 768);\n * if (accepted) {\n * console.log('Resize accepted by host');\n * } else {\n * console.warn('Resize rejected by host');\n * }\n * ```\n */\nexport async function requestResize(width: number, height: number): Promise<boolean> {\n const bridge = IpcBridge.getInstance();\n\n const result = await bridge.invoke<RequestResizeResult>('requestResize', { width, height });\n\n return result.accepted;\n}\n\n/**\n * React hook for requesting window resize\n *\n * @returns Function to request resize\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const resize = useRequestResize();\n *\n * const handleExpand = async () => {\n * const accepted = await resize(1200, 900);\n * if (!accepted) {\n * alert('Host rejected resize request');\n * }\n * };\n *\n * return <button onClick={handleExpand}>Expand</button>;\n * }\n * ```\n */\nexport function useRequestResize(): (width: number, height: number) => Promise<boolean> {\n return requestResize;\n}\n","/**\n * Meter polling API for audio visualization (IPC-based)\n */\n\nimport { IpcBridge } from './IpcBridge';\nimport type { MeterFrame, GetMeterFrameResult } from './meters';\n\n/**\n * Get the latest meter frame from the audio engine\n */\nexport async function getMeterFrame(): Promise<MeterFrame | null> {\n const bridge = IpcBridge.getInstance();\n const result = await bridge.invoke<GetMeterFrameResult>('getMeterFrame');\n return result.frame;\n}\n"],"names":["LogLevel","parameters"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/utils/environment.ts","../src/types/ipc.ts","../src/types/parameters.ts","../src/logger/Logger.ts","../src/transports/NativeTransport.ts","../src/transports/WebSocketTransport.ts","../src/transports/index.ts","../src/ipc/IpcBridge.ts","../src/ipc/ParameterClient.ts","../src/hooks/useParameter.ts","../src/hooks/useAllParameters.ts","../src/hooks/useParameterGroups.ts","../src/hooks/useConnectionStatus.ts","../src/hooks/useLatencyMonitor.ts","../src/hooks/useMeterFrame.ts","../src/hooks/useWindowResizeSync.ts","../src/hooks/useRequestResize.ts","../src/meter-ipc.ts"],"sourcesContent":["/**\n * Environment Detection\n *\n * Determines if the code is running in WKWebView (production)\n * or a browser (development).\n */\n\n/**\n * Check if running in a WKWebView environment (production)\n * @returns true if globalThis.wavecraft IPC primitives are available\n */\nexport function isWebViewEnvironment(): boolean {\n return globalThis.__WAVECRAFT_IPC__ !== undefined;\n}\n\n/**\n * Check if running in a browser environment (development)\n * @returns true if IPC primitives are NOT available\n */\nexport function isBrowserEnvironment(): boolean {\n return !isWebViewEnvironment();\n}\n","/**\n * IPC Types - TypeScript definitions matching Rust protocol\n *\n * These types must stay in sync with engine/crates/protocol/src/ipc.rs\n */\n\n// ============================================================================\n// JSON-RPC 2.0 Message Types\n// ============================================================================\n\nexport type RequestId = string | number;\n\nexport interface IpcRequest {\n jsonrpc: '2.0';\n id: RequestId;\n method: string;\n params?: unknown;\n}\n\nexport interface IpcResponse {\n jsonrpc: '2.0';\n id: RequestId;\n result?: unknown;\n error?: IpcError;\n}\n\nexport interface IpcNotification {\n jsonrpc: '2.0';\n method: string;\n params?: unknown;\n}\n\nexport interface IpcError {\n code: number;\n message: string;\n data?: unknown;\n}\n\n// ============================================================================\n// Error Codes (matching Rust constants)\n// ============================================================================\n\nexport const ERROR_PARSE = -32700;\nexport const ERROR_INVALID_REQUEST = -32600;\nexport const ERROR_METHOD_NOT_FOUND = -32601;\nexport const ERROR_INVALID_PARAMS = -32602;\nexport const ERROR_INTERNAL = -32603;\nexport const ERROR_PARAM_NOT_FOUND = -32000;\nexport const ERROR_PARAM_OUT_OF_RANGE = -32001;\n\n// ============================================================================\n// Injected IPC Primitives (from Rust)\n// ============================================================================\n\nexport interface WavecraftIpcPrimitives {\n postMessage: (message: string) => void;\n setReceiveCallback: (callback: (message: string) => void) => void;\n onParamUpdate?: (listener: (notification: unknown) => void) => () => void;\n _receive: (message: string) => void; // Internal, called by Rust\n _onParamUpdate?: (message: unknown) => void; // Internal, called by Rust\n}\n\ndeclare global {\n var __WAVECRAFT_IPC__: WavecraftIpcPrimitives | undefined;\n}\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\nexport function isIpcResponse(obj: unknown): obj is IpcResponse {\n return (\n typeof obj === 'object' &&\n obj !== null &&\n 'jsonrpc' in obj &&\n 'id' in obj &&\n ('result' in obj || 'error' in obj)\n );\n}\n\nexport function isIpcNotification(obj: unknown): obj is IpcNotification {\n return (\n typeof obj === 'object' && obj !== null && 'jsonrpc' in obj && 'method' in obj && !('id' in obj)\n );\n}\n\nexport function isIpcError(obj: unknown): obj is IpcError {\n return typeof obj === 'object' && obj !== null && 'code' in obj && 'message' in obj;\n}\n","/**\n * Parameter Types\n *\n * Types related to plugin parameters.\n */\n\nexport type ParameterType = 'float' | 'bool' | 'enum';\n\nexport interface ParameterInfo {\n id: string;\n name: string;\n type: ParameterType;\n value: number;\n default: number;\n unit?: string;\n group?: string;\n}\n\n// getParameter\nexport interface GetParameterParams {\n id: string;\n}\n\nexport interface GetParameterResult {\n id: string;\n value: number;\n}\n\n// setParameter\nexport interface SetParameterParams {\n id: string;\n value: number;\n}\n\nexport type SetParameterResult = Record<string, never>;\n\n// getAllParameters\nexport interface GetAllParametersResult {\n parameters: ParameterInfo[];\n}\n\n// Notification: parameterChanged\nexport interface ParameterChangedNotification {\n id: string;\n value: number;\n}\n\n// Method Names (matching Rust constants)\nexport const METHOD_GET_PARAMETER = 'getParameter';\nexport const METHOD_SET_PARAMETER = 'setParameter';\nexport const METHOD_GET_ALL_PARAMETERS = 'getAllParameters';\nexport const NOTIFICATION_PARAMETER_CHANGED = 'parameterChanged';\n","/**\n * Logger - Structured logging abstraction for the UI.\n *\n * Wraps browser console API with severity levels and structured context.\n * In production builds, logs can be filtered by level at runtime.\n */\n\nexport enum LogLevel {\n DEBUG = 0,\n INFO = 1,\n WARN = 2,\n ERROR = 3,\n}\n\nexport interface LogContext {\n [key: string]: unknown;\n}\n\n/**\n * Logger class providing structured logging with severity levels.\n *\n * Example usage:\n * ```typescript\n * const logger = new Logger({ minLevel: LogLevel.INFO });\n * logger.info('Parameter updated', { id: 'gain', value: 0.5 });\n * logger.error('IPC failed', { method: 'getParameter', error });\n * ```\n */\nexport class Logger {\n private minLevel: LogLevel;\n\n constructor(options: { minLevel?: LogLevel } = {}) {\n this.minLevel = options.minLevel ?? LogLevel.DEBUG;\n }\n\n /**\n * Set the minimum log level at runtime.\n */\n setMinLevel(level: LogLevel): void {\n this.minLevel = level;\n }\n\n /**\n * Log debug message (verbose tracing).\n */\n debug(message: string, context?: LogContext): void {\n if (this.minLevel <= LogLevel.DEBUG) {\n console.debug(`[DEBUG] ${message}`, context ?? {});\n }\n }\n\n /**\n * Log informational message.\n */\n info(message: string, context?: LogContext): void {\n if (this.minLevel <= LogLevel.INFO) {\n console.info(`[INFO] ${message}`, context ?? {});\n }\n }\n\n /**\n * Log warning message.\n */\n warn(message: string, context?: LogContext): void {\n if (this.minLevel <= LogLevel.WARN) {\n console.warn(`[WARN] ${message}`, context ?? {});\n }\n }\n\n /**\n * Log error message.\n */\n error(message: string, context?: LogContext): void {\n if (this.minLevel <= LogLevel.ERROR) {\n console.error(`[ERROR] ${message}`, context ?? {});\n }\n }\n}\n\n/**\n * Global logger instance for the UI.\n * Configure once at app startup, use throughout the codebase.\n */\nexport const logger = new Logger({\n minLevel: import.meta.env.DEV ? LogLevel.DEBUG : LogLevel.INFO,\n});\n","/**\n * NativeTransport - WKWebView IPC transport\n *\n * Wraps the native IPC primitives injected by the Rust engine\n * into a WKWebView. This transport is always connected.\n */\n\nimport type { Transport, NotificationCallback } from './Transport';\nimport type { IpcResponse, IpcNotification, RequestId } from '../types/ipc';\nimport { isIpcResponse, isIpcNotification } from '../types/ipc';\nimport { logger } from '../logger/Logger';\n\ninterface PendingRequest {\n resolve: (response: string) => void;\n reject: (error: Error) => void;\n timeoutId: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Native WKWebView transport implementation\n *\n * Uses the __WAVECRAFT_IPC__ primitives injected by the Rust engine.\n */\nexport class NativeTransport implements Transport {\n private readonly pendingRequests = new Map<RequestId, PendingRequest>();\n private readonly notificationCallbacks = new Set<NotificationCallback>();\n private readonly primitives: typeof globalThis.__WAVECRAFT_IPC__;\n\n constructor() {\n this.primitives = globalThis.__WAVECRAFT_IPC__;\n\n if (!this.primitives) {\n throw new Error(\n 'NativeTransport: __WAVECRAFT_IPC__ primitives not found. ' +\n 'Ensure this runs in a WKWebView with injected IPC.'\n );\n }\n\n // Set up receive callback for responses\n this.primitives.setReceiveCallback((message: string) => {\n this.handleIncomingMessage(message);\n });\n\n // Set up parameter update listener for pushed updates\n if (this.primitives.onParamUpdate) {\n this.primitives.onParamUpdate((notification: unknown) => {\n if (isIpcNotification(notification)) {\n this.handleNotification(notification);\n }\n });\n }\n }\n\n /**\n * Send a JSON-RPC request and wait for response\n */\n async send(request: string): Promise<string> {\n if (!this.primitives) {\n throw new Error('NativeTransport: Primitives not available');\n }\n\n const parsedRequest = JSON.parse(request);\n const id = parsedRequest.id;\n\n if (id === undefined || id === null) {\n throw new Error('NativeTransport: Request must have an id');\n }\n\n // Create promise for response\n const responsePromise = new Promise<string>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n this.pendingRequests.delete(id);\n reject(new Error(`Request timeout: ${parsedRequest.method}`));\n }, 5000); // 5 second timeout\n\n this.pendingRequests.set(id, { resolve, reject, timeoutId });\n });\n\n // Send request\n this.primitives.postMessage(request);\n\n return responsePromise;\n }\n\n /**\n * Register a callback for incoming notifications\n */\n onNotification(callback: NotificationCallback): () => void {\n this.notificationCallbacks.add(callback);\n\n // Return cleanup function\n return () => {\n this.notificationCallbacks.delete(callback);\n };\n }\n\n /**\n * Check if transport is connected (native is always connected)\n */\n isConnected(): boolean {\n return true;\n }\n\n /**\n * Clean up resources\n */\n dispose(): void {\n // Cancel all pending requests\n for (const [id, { reject, timeoutId }] of this.pendingRequests.entries()) {\n clearTimeout(timeoutId);\n reject(new Error('Transport disposed'));\n this.pendingRequests.delete(id);\n }\n\n // Clear notification callbacks\n this.notificationCallbacks.clear();\n }\n\n /**\n * Handle incoming message (response or notification)\n */\n private handleIncomingMessage(message: string): void {\n try {\n const parsed = JSON.parse(message);\n\n if (isIpcResponse(parsed)) {\n this.handleResponse(parsed);\n } else if (isIpcNotification(parsed)) {\n this.handleNotification(parsed);\n }\n } catch (error) {\n logger.error('Failed to parse incoming message', { error });\n }\n }\n\n /**\n * Handle JSON-RPC response\n */\n private handleResponse(response: IpcResponse): void {\n const pending = this.pendingRequests.get(response.id);\n\n if (pending) {\n clearTimeout(pending.timeoutId);\n this.pendingRequests.delete(response.id);\n pending.resolve(JSON.stringify(response));\n }\n }\n\n /**\n * Handle notification and dispatch to listeners\n */\n private handleNotification(notification: IpcNotification): void {\n const notificationJson = JSON.stringify(notification);\n\n for (const callback of this.notificationCallbacks) {\n try {\n callback(notificationJson);\n } catch (error) {\n logger.error('Error in notification callback', { error });\n }\n }\n }\n}\n","/**\n * WebSocketTransport - Browser WebSocket IPC transport\n *\n * Connects to the standalone dev server over WebSocket for\n * browser-based UI development with real engine communication.\n */\n\nimport type { Transport, NotificationCallback } from './Transport';\nimport type { IpcResponse, IpcNotification, RequestId } from '../types/ipc';\nimport { isIpcResponse, isIpcNotification } from '../types/ipc';\nimport { logger } from '../logger/Logger';\n\ninterface PendingRequest {\n resolve: (response: string) => void;\n reject: (error: Error) => void;\n timeoutId: ReturnType<typeof setTimeout>;\n}\n\ninterface WebSocketTransportOptions {\n /** WebSocket server URL (e.g., ws://127.0.0.1:9000) */\n url: string;\n /** Reconnection delay in milliseconds (default: 1000) */\n reconnectDelayMs?: number;\n /** Maximum reconnection attempts (default: 5, use Infinity for unlimited) */\n maxReconnectAttempts?: number;\n}\n\n/**\n * WebSocket transport implementation with automatic reconnection\n *\n * Connects to the standalone dev server for browser-based UI development.\n */\nexport class WebSocketTransport implements Transport {\n private readonly url: string;\n private readonly reconnectDelayMs: number;\n private readonly maxReconnectAttempts: number;\n\n private ws: WebSocket | null = null;\n private isConnecting = false;\n private reconnectAttempts = 0;\n private reconnectTimeoutId: ReturnType<typeof setTimeout> | null = null;\n private isDisposed = false;\n private maxAttemptsReached = false; // Flag to stop reconnection after max attempts\n\n private readonly pendingRequests = new Map<RequestId, PendingRequest>();\n private readonly notificationCallbacks = new Set<NotificationCallback>();\n\n constructor(options: WebSocketTransportOptions) {\n this.url = options.url;\n this.reconnectDelayMs = options.reconnectDelayMs ?? 1000;\n this.maxReconnectAttempts = options.maxReconnectAttempts ?? 5;\n\n // Start connection immediately\n this.connect();\n }\n\n /**\n * Send a JSON-RPC request and wait for response\n */\n async send(request: string): Promise<string> {\n if (!this.isConnected()) {\n throw new Error('WebSocketTransport: Not connected');\n }\n\n const parsedRequest = JSON.parse(request);\n const id = parsedRequest.id;\n\n if (id === undefined || id === null) {\n throw new Error('WebSocketTransport: Request must have an id');\n }\n\n // Create promise for response\n const responsePromise = new Promise<string>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n this.pendingRequests.delete(id);\n reject(new Error(`Request timeout: ${parsedRequest.method}`));\n }, 5000); // 5 second timeout\n\n this.pendingRequests.set(id, { resolve, reject, timeoutId });\n });\n\n // Send request\n if (!this.ws) {\n throw new Error('WebSocketTransport: Connection lost');\n }\n this.ws.send(request);\n\n return responsePromise;\n }\n\n /**\n * Register a callback for incoming notifications\n */\n onNotification(callback: NotificationCallback): () => void {\n this.notificationCallbacks.add(callback);\n\n return () => {\n this.notificationCallbacks.delete(callback);\n };\n }\n\n /**\n * Check if transport is connected\n */\n isConnected(): boolean {\n return this.ws !== null && this.ws.readyState === WebSocket.OPEN;\n }\n\n /**\n * Clean up resources and close connection\n */\n dispose(): void {\n this.isDisposed = true;\n\n // Clear reconnect timer\n if (this.reconnectTimeoutId) {\n clearTimeout(this.reconnectTimeoutId);\n this.reconnectTimeoutId = null;\n }\n\n // Close WebSocket\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n\n // Cancel all pending requests\n for (const [id, { reject, timeoutId }] of this.pendingRequests.entries()) {\n clearTimeout(timeoutId);\n reject(new Error('Transport disposed'));\n this.pendingRequests.delete(id);\n }\n\n // Clear notification callbacks\n this.notificationCallbacks.clear();\n }\n\n /**\n * Attempt to connect to WebSocket server\n */\n private connect(): void {\n if (this.isDisposed || this.isConnecting || this.isConnected()) {\n return;\n }\n\n this.isConnecting = true;\n\n try {\n this.ws = new WebSocket(this.url);\n\n this.ws.onopen = (): void => {\n this.isConnecting = false;\n this.reconnectAttempts = 0;\n logger.info('WebSocketTransport connected', { url: this.url });\n };\n\n this.ws.onmessage = (event: MessageEvent): void => {\n this.handleIncomingMessage(event.data);\n };\n\n this.ws.onerror = (error: Event): void => {\n logger.error('WebSocketTransport connection error', { error });\n };\n\n this.ws.onclose = (): void => {\n this.isConnecting = false;\n this.ws = null;\n\n if (!this.isDisposed && !this.maxAttemptsReached) {\n this.scheduleReconnect();\n }\n };\n } catch (error) {\n this.isConnecting = false;\n logger.error('WebSocketTransport failed to create WebSocket', { error, url: this.url });\n this.scheduleReconnect();\n }\n }\n\n /**\n * Schedule reconnection attempt with exponential backoff\n */\n private scheduleReconnect(): void {\n if (this.isDisposed || this.maxAttemptsReached) {\n return;\n }\n\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n this.maxAttemptsReached = true;\n logger.error('WebSocketTransport max reconnect attempts reached', {\n maxAttempts: this.maxReconnectAttempts,\n });\n // Close the WebSocket to stop browser reconnection attempts\n if (this.ws) {\n this.ws.close();\n this.ws = null;\n }\n return;\n }\n\n this.reconnectAttempts++;\n const delay = this.reconnectDelayMs * Math.pow(2, this.reconnectAttempts - 1); // Exponential backoff\n\n logger.debug('WebSocketTransport reconnecting', {\n delayMs: delay,\n attempt: this.reconnectAttempts,\n maxAttempts: this.maxReconnectAttempts,\n });\n\n this.reconnectTimeoutId = setTimeout(() => {\n this.reconnectTimeoutId = null;\n this.connect();\n }, delay);\n }\n\n /**\n * Handle incoming message (response or notification)\n */\n private handleIncomingMessage(message: string): void {\n try {\n const parsed = JSON.parse(message);\n\n if (isIpcResponse(parsed)) {\n this.handleResponse(parsed);\n } else if (isIpcNotification(parsed)) {\n this.handleNotification(parsed);\n }\n } catch (error) {\n logger.error('WebSocketTransport failed to parse incoming message', { error, message });\n }\n }\n\n /**\n * Handle JSON-RPC response\n */\n private handleResponse(response: IpcResponse): void {\n const pending = this.pendingRequests.get(response.id);\n\n if (pending) {\n clearTimeout(pending.timeoutId);\n this.pendingRequests.delete(response.id);\n pending.resolve(JSON.stringify(response));\n }\n }\n\n /**\n * Handle notification and dispatch to listeners\n */\n private handleNotification(notification: IpcNotification): void {\n const notificationJson = JSON.stringify(notification);\n\n for (const callback of this.notificationCallbacks) {\n try {\n callback(notificationJson);\n } catch (error) {\n logger.error('WebSocketTransport notification callback error', {\n error,\n method: notification.method,\n });\n }\n }\n }\n}\n","/**\n * Transport Factory\n *\n * Provides automatic transport selection based on runtime environment:\n * - WKWebView: NativeTransport\n * - Browser: WebSocketTransport\n */\n\nimport type { Transport } from './Transport';\nimport { NativeTransport } from './NativeTransport';\nimport { WebSocketTransport } from './WebSocketTransport';\nimport { isWebViewEnvironment } from '../utils/environment';\n\n// Export transport types\nexport type { Transport, NotificationCallback } from './Transport';\nexport { NativeTransport } from './NativeTransport';\nexport { WebSocketTransport } from './WebSocketTransport';\n\n// Environment detection at module scope (evaluated once)\nconst IS_WEBVIEW = isWebViewEnvironment();\n\n/**\n * Singleton transport instance\n */\nlet transportInstance: Transport | null = null;\n\n/**\n * Get the transport instance (singleton)\n *\n * Automatically selects:\n * - NativeTransport in WKWebView (production)\n * - WebSocketTransport in browser (development)\n *\n * @returns Transport instance\n */\nexport function getTransport(): Transport {\n if (transportInstance) {\n return transportInstance;\n }\n\n if (IS_WEBVIEW) {\n // Native WKWebView transport\n transportInstance = new NativeTransport();\n } else {\n // WebSocket transport for browser development\n const wsUrl = import.meta.env.VITE_WS_URL || 'ws://127.0.0.1:9000';\n transportInstance = new WebSocketTransport({ url: wsUrl });\n }\n\n return transportInstance;\n}\n\n/**\n * Check if transport is available\n *\n * @returns true if transport exists and is connected\n */\nexport function hasTransport(): boolean {\n return transportInstance?.isConnected() ?? false;\n}\n\n/**\n * Dispose the current transport (mainly for tests)\n */\nexport function disposeTransport(): void {\n if (transportInstance) {\n transportInstance.dispose();\n transportInstance = null;\n }\n}\n","/**\n * IpcBridge - Low-level IPC communication layer\n *\n * Provides a Promise-based API for sending requests and receiving responses\n * using pluggable transport implementations (NativeTransport, WebSocketTransport).\n */\n\nimport type { IpcRequest, IpcResponse, IpcNotification } from '../types/ipc';\nimport { isIpcNotification } from '../types/ipc';\nimport type { Transport } from '../transports';\nimport { getTransport } from '../transports';\nimport { logger } from '../logger/Logger';\n\ntype EventCallback<T> = (data: T) => void;\n\nexport class IpcBridge {\n private static instance: IpcBridge | null = null;\n private nextId = 1;\n private readonly eventListeners = new Map<string, Set<EventCallback<unknown>>>();\n private transport: Transport | null = null;\n private isInitialized = false;\n private lastDisconnectWarning = 0;\n private readonly DISCONNECT_WARNING_INTERVAL_MS = 5000; // Log warning max once per 5s\n\n private constructor() {\n // Lazy initialization on first use\n }\n\n /**\n * Initialize the IPC bridge (lazy)\n */\n private initialize(): void {\n if (this.isInitialized) {\n return;\n }\n\n // Get transport (auto-selected based on environment)\n this.transport = getTransport();\n\n // Subscribe to notifications from transport\n this.transport.onNotification((notificationJson: string) => {\n try {\n const parsed = JSON.parse(notificationJson);\n if (isIpcNotification(parsed)) {\n this.handleNotification(parsed);\n }\n } catch (error) {\n logger.error('Failed to parse notification', { error });\n }\n });\n\n this.isInitialized = true;\n }\n\n /**\n * Get singleton instance\n */\n public static getInstance(): IpcBridge {\n IpcBridge.instance ??= new IpcBridge();\n return IpcBridge.instance;\n }\n\n /**\n * Check if the bridge is connected\n */\n public isConnected(): boolean {\n // Trigger lazy initialization so transport gets created\n this.initialize();\n return this.transport?.isConnected() ?? false;\n }\n\n /**\n * Invoke a method and wait for response\n */\n public async invoke<TResult>(method: string, params?: unknown): Promise<TResult> {\n // Lazy initialization on first use\n this.initialize();\n\n if (!this.transport?.isConnected()) {\n // Rate-limit disconnect warnings to avoid console spam\n const now = Date.now();\n if (now - this.lastDisconnectWarning > this.DISCONNECT_WARNING_INTERVAL_MS) {\n logger.warn('Transport not connected, call will fail. Waiting for reconnection...');\n this.lastDisconnectWarning = now;\n }\n throw new Error('IpcBridge: Transport not connected');\n }\n\n const id = this.nextId++;\n const request: IpcRequest = {\n jsonrpc: '2.0',\n id,\n method,\n params,\n };\n\n // Serialize request\n const requestJson = JSON.stringify(request);\n\n // Send via transport (transport handles timeout internally)\n const responseJson = await this.transport.send(requestJson);\n\n // Parse response\n const response: IpcResponse = JSON.parse(responseJson);\n\n // Check for error\n if (response.error) {\n throw new Error(`IPC Error ${response.error.code}: ${response.error.message}`);\n }\n\n return response.result as TResult;\n }\n\n /**\n * Subscribe to notification events\n */\n public on<T>(event: string, callback: EventCallback<T>): () => void {\n // Lazy initialization on first use\n this.initialize();\n\n if (!this.eventListeners.has(event)) {\n this.eventListeners.set(event, new Set());\n }\n\n const listeners = this.eventListeners.get(event);\n if (!listeners) {\n throw new Error(`Event listener set not found for event: ${event}`);\n }\n listeners.add(callback as EventCallback<unknown>);\n\n // Return unsubscribe function\n return () => {\n listeners.delete(callback as EventCallback<unknown>);\n };\n }\n\n /**\n * Handle notification and dispatch to listeners\n */\n private handleNotification(notification: IpcNotification): void {\n const listeners = this.eventListeners.get(notification.method);\n if (!listeners || listeners.size === 0) {\n return;\n }\n\n for (const listener of listeners) {\n try {\n listener(notification.params);\n } catch (error) {\n logger.error('Error in event listener', { event: notification.method, error });\n }\n }\n }\n}\n","/**\n * ParameterClient - High-level typed API for parameter operations\n *\n * Provides typed methods for interacting with plugin parameters.\n */\n\nimport { IpcBridge } from './IpcBridge';\nimport type {\n ParameterInfo,\n GetParameterResult,\n SetParameterResult,\n GetAllParametersResult,\n ParameterChangedNotification,\n} from '../types/parameters';\nimport {\n METHOD_GET_PARAMETER,\n METHOD_SET_PARAMETER,\n METHOD_GET_ALL_PARAMETERS,\n NOTIFICATION_PARAMETER_CHANGED,\n} from '../types/parameters';\n\ntype ParameterChangeCallback = (id: string, value: number) => void;\n\nexport class ParameterClient {\n private static instance: ParameterClient | null = null;\n private readonly bridge: IpcBridge;\n\n private constructor() {\n this.bridge = IpcBridge.getInstance();\n }\n\n /**\n * Get singleton instance\n */\n public static getInstance(): ParameterClient {\n ParameterClient.instance ??= new ParameterClient();\n return ParameterClient.instance;\n }\n\n /**\n * Get a single parameter's current value and metadata\n */\n public async getParameter(id: string): Promise<GetParameterResult> {\n return this.bridge.invoke<GetParameterResult>(METHOD_GET_PARAMETER, { id });\n }\n\n /**\n * Set a parameter's value\n * @param id Parameter ID\n * @param value Normalized value [0.0, 1.0]\n */\n public async setParameter(id: string, value: number): Promise<void> {\n await this.bridge.invoke<SetParameterResult>(METHOD_SET_PARAMETER, {\n id,\n value,\n });\n }\n\n /**\n * Get all parameters with their current values and metadata\n */\n public async getAllParameters(): Promise<ParameterInfo[]> {\n const result = await this.bridge.invoke<GetAllParametersResult>(METHOD_GET_ALL_PARAMETERS);\n return result.parameters;\n }\n\n /**\n * Test connectivity with Rust backend\n * @returns Roundtrip time in milliseconds\n */\n public async ping(): Promise<number> {\n const start = performance.now();\n await this.bridge.invoke('ping');\n const end = performance.now();\n return end - start;\n }\n\n /**\n * Subscribe to parameter change notifications\n * @returns Unsubscribe function\n */\n public onParameterChanged(callback: ParameterChangeCallback): () => void {\n return this.bridge.on<ParameterChangedNotification>(NOTIFICATION_PARAMETER_CHANGED, (data) => {\n if (data && typeof data === 'object' && 'id' in data && 'value' in data) {\n callback(data.id, data.value);\n }\n });\n }\n}\n","/**\n * useParameter - Hook for managing a single parameter\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { ParameterClient } from '../ipc/ParameterClient';\nimport type { ParameterInfo } from '../types/parameters';\n\nexport interface UseParameterResult {\n param: ParameterInfo | null;\n setValue: (value: number) => Promise<void>;\n isLoading: boolean;\n error: Error | null;\n}\n\nexport function useParameter(id: string): UseParameterResult {\n const [param, setParam] = useState<ParameterInfo | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n // Load initial parameter value\n useEffect(() => {\n let isMounted = true;\n const client = ParameterClient.getInstance();\n\n async function loadParameter(): Promise<void> {\n try {\n setIsLoading(true);\n setError(null);\n\n // Get all parameters and find the one we want\n const allParams = await client.getAllParameters();\n const foundParam = allParams.find((p) => p.id === id);\n\n if (isMounted) {\n if (foundParam) {\n setParam(foundParam);\n } else {\n setError(new Error(`Parameter not found: ${id}`));\n }\n }\n } catch (err) {\n if (isMounted) {\n setError(err instanceof Error ? err : new Error(String(err)));\n }\n } finally {\n if (isMounted) {\n setIsLoading(false);\n }\n }\n }\n\n loadParameter();\n\n return (): void => {\n isMounted = false;\n };\n }, [id]);\n\n // Subscribe to parameter changes\n useEffect(() => {\n const client = ParameterClient.getInstance();\n const unsubscribe = client.onParameterChanged((changedId, value) => {\n if (changedId === id) {\n setParam((prev) => (prev ? { ...prev, value } : null));\n }\n });\n\n return unsubscribe;\n }, [id]);\n\n // Set parameter value\n const setValue = useCallback(\n async (value: number) => {\n const client = ParameterClient.getInstance();\n try {\n await client.setParameter(id, value);\n // Optimistically update local state\n setParam((prev) => (prev ? { ...prev, value } : null));\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n throw err;\n }\n },\n [id]\n );\n\n return { param, setValue, isLoading, error };\n}\n","/**\n * useAllParameters - Hook for loading all parameters\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { ParameterClient } from '../ipc/ParameterClient';\nimport type { ParameterInfo } from '../types/parameters';\n\nexport interface UseAllParametersResult {\n params: ParameterInfo[];\n isLoading: boolean;\n error: Error | null;\n reload: () => Promise<void>;\n}\n\nexport function useAllParameters(): UseAllParametersResult {\n const [params, setParams] = useState<ParameterInfo[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const reload = useCallback(async () => {\n const client = ParameterClient.getInstance();\n try {\n setIsLoading(true);\n setError(null);\n const allParams = await client.getAllParameters();\n setParams(allParams);\n } catch (err) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n }, []);\n\n // Load on mount\n useEffect(() => {\n reload();\n }, [reload]);\n\n // Subscribe to parameter changes\n useEffect(() => {\n const client = ParameterClient.getInstance();\n // Note: Nesting depth warning accepted here - inline mapper is idiomatic React pattern\n const handleParamChange = (changedId: string, value: number): void => {\n setParams((prev) => prev.map((p) => (p.id === changedId ? { ...p, value } : p)));\n };\n\n const unsubscribe = client.onParameterChanged(handleParamChange);\n\n return unsubscribe;\n }, []);\n\n return { params, isLoading, error, reload };\n}\n","/**\n * useParameterGroups - Organize parameters into groups\n *\n * This hook takes an array of parameters and organizes them into groups\n * for better UI organization. Parameters without a group are placed in\n * a default \"Parameters\" group.\n */\n\nimport { useMemo } from 'react';\nimport type { ParameterInfo } from '../types/parameters';\n\nexport interface ParameterGroup {\n name: string;\n parameters: ParameterInfo[];\n}\n\n/**\n * Organize parameters into groups based on their group metadata.\n *\n * @param parameters - Array of all parameters\n * @returns Array of parameter groups, each containing parameters for that group\n *\n * @example\n * ```tsx\n * const { parameters } = useAllParameters();\n * const groups = useParameterGroups(parameters);\n *\n * return (\n * <>\n * {groups.map(group => (\n * <ParameterGroup key={group.name} group={group} />\n * ))}\n * </>\n * );\n * ```\n */\nexport function useParameterGroups(parameters: ParameterInfo[]): ParameterGroup[] {\n return useMemo(() => {\n // Group parameters by their group field\n const grouped = new Map<string, ParameterInfo[]>();\n\n for (const param of parameters) {\n const groupName = param.group ?? 'Parameters';\n const existing = grouped.get(groupName) ?? [];\n existing.push(param);\n grouped.set(groupName, existing);\n }\n\n // Convert map to array of groups, sorted by group name\n // Exception: \"Parameters\" (default group) always comes first\n const groups: ParameterGroup[] = Array.from(grouped.entries())\n .map(([name, params]) => ({ name, parameters: params }))\n .sort((a, b) => {\n if (a.name === 'Parameters') return -1;\n if (b.name === 'Parameters') return 1;\n return a.name.localeCompare(b.name);\n });\n\n return groups;\n }, [parameters]);\n}\n","/**\n * useConnectionStatus - Monitor transport connection status\n *\n * Provides real-time connection status updates for the IPC transport.\n * Useful for showing connection indicators in the UI.\n */\n\nimport { useEffect, useState } from 'react';\nimport { IpcBridge } from '../ipc/IpcBridge';\nimport { isWebViewEnvironment } from '../utils/environment';\n\nexport type TransportType = 'native' | 'websocket' | 'none';\n\nexport interface ConnectionStatus {\n /** Whether transport is connected and ready */\n connected: boolean;\n /** Type of transport being used */\n transport: TransportType;\n}\n\n/**\n * Hook to monitor IPC connection status\n *\n * Polls the transport every second to detect connection changes.\n * Native transport is always connected, WebSocket may reconnect.\n *\n * @returns Connection status object\n */\nexport function useConnectionStatus(): ConnectionStatus {\n const [status, setStatus] = useState<ConnectionStatus>(() => {\n const bridge = IpcBridge.getInstance();\n const connected = bridge.isConnected();\n\n let transport: TransportType;\n if (isWebViewEnvironment()) {\n transport = 'native';\n } else if (connected) {\n transport = 'websocket';\n } else {\n transport = 'none';\n }\n\n return { connected, transport };\n });\n\n useEffect(() => {\n const bridge = IpcBridge.getInstance();\n\n // Poll connection status every second\n const intervalId = setInterval(() => {\n const connected = bridge.isConnected();\n\n let transport: TransportType;\n if (isWebViewEnvironment()) {\n transport = 'native';\n } else if (connected) {\n transport = 'websocket';\n } else {\n transport = 'none';\n }\n\n setStatus((prevStatus) => {\n // Only update if status changed (avoid unnecessary re-renders)\n if (prevStatus.connected !== connected || prevStatus.transport !== transport) {\n return { connected, transport };\n }\n return prevStatus;\n });\n }, 1000);\n\n // Cleanup interval on unmount\n return (): void => {\n clearInterval(intervalId);\n };\n }, []);\n\n return status;\n}\n","/**\n * useLatencyMonitor - Hook for monitoring IPC latency\n */\n\nimport { useState, useEffect } from 'react';\nimport { IpcBridge } from '../ipc/IpcBridge';\nimport { ParameterClient } from '../ipc/ParameterClient';\nimport { logger } from '../logger';\n\nexport interface UseLatencyMonitorResult {\n latency: number | null;\n avg: number;\n max: number;\n count: number;\n}\n\nexport function useLatencyMonitor(intervalMs = 1000): UseLatencyMonitorResult {\n const [latency, setLatency] = useState<number | null>(null);\n const [measurements, setMeasurements] = useState<number[]>([]);\n const bridge = IpcBridge.getInstance();\n\n useEffect(() => {\n let isMounted = true;\n const client = ParameterClient.getInstance();\n\n async function measure(): Promise<void> {\n // Only measure when connected\n if (!bridge.isConnected()) {\n return;\n }\n\n try {\n const ms = await client.ping();\n if (isMounted) {\n setLatency(ms);\n setMeasurements((prev) => [...prev.slice(-99), ms]); // Keep last 100\n }\n } catch (err) {\n logger.debug('Ping failed', { error: err });\n }\n }\n\n // Initial measurement\n measure();\n\n // Periodic measurements\n const intervalId = setInterval(measure, intervalMs);\n\n return (): void => {\n isMounted = false;\n clearInterval(intervalId);\n };\n }, [intervalMs, bridge]);\n\n // Calculate statistics\n const avg =\n measurements.length > 0\n ? measurements.reduce((sum, val) => sum + val, 0) / measurements.length\n : 0;\n\n const max = measurements.length > 0 ? Math.max(...measurements) : 0;\n\n return {\n latency,\n avg,\n max,\n count: measurements.length,\n };\n}\n","/**\n * useMeterFrame - Hook for polling audio meter data\n */\n\nimport { useState, useEffect } from 'react';\nimport { IpcBridge } from '../ipc/IpcBridge';\nimport type { MeterFrame, GetMeterFrameResult } from '../types/metering';\n\n/**\n * Hook to poll meter frames at a specified interval\n *\n * @param intervalMs - Polling interval in milliseconds (default: 50ms = 20fps)\n * @returns Current meter frame or null if not available\n */\nexport function useMeterFrame(intervalMs = 50): MeterFrame | null {\n const [frame, setFrame] = useState<MeterFrame | null>(null);\n\n useEffect(() => {\n let isMounted = true;\n const bridge = IpcBridge.getInstance();\n\n async function fetchFrame(): Promise<void> {\n if (!bridge.isConnected()) return;\n\n try {\n const result = await bridge.invoke<GetMeterFrameResult>('getMeterFrame');\n if (isMounted && result.frame) {\n setFrame(result.frame);\n }\n } catch {\n // Silently ignore meter fetch errors\n }\n }\n\n // Initial fetch\n fetchFrame();\n\n // Periodic polling\n const intervalId = setInterval(fetchFrame, intervalMs);\n\n return (): void => {\n isMounted = false;\n clearInterval(intervalId);\n };\n }, [intervalMs]);\n\n return frame;\n}\n","/**\n * useWindowResizeSync - Automatic window resize sync to host\n */\n\nimport { useEffect } from 'react';\nimport { IpcBridge } from '../ipc/IpcBridge';\nimport { logger } from '../logger';\n\nexport interface RequestResizeParams {\n width: number;\n height: number;\n}\n\nexport interface RequestResizeResult {\n accepted: boolean;\n}\n\n/**\n * Request resize of the editor window\n *\n * @param width - Desired width in logical pixels\n * @param height - Desired height in logical pixels\n * @returns Promise that resolves to true if accepted, false if rejected\n *\n * @example\n * ```ts\n * const accepted = await requestResize(1024, 768);\n * if (accepted) {\n * console.log('Resize accepted by host');\n * } else {\n * console.warn('Resize rejected by host');\n * }\n * ```\n */\nexport async function requestResize(width: number, height: number): Promise<boolean> {\n const bridge = IpcBridge.getInstance();\n\n const result = await bridge.invoke<RequestResizeResult>('requestResize', { width, height });\n\n return result.accepted;\n}\n\n/**\n * Hook that automatically syncs window resize events to the host\n *\n * This hook listens for browser window resize events and notifies the host\n * DAW of size changes. Useful when the user resizes the plugin window via\n * the DAW's window controls or edge dragging.\n *\n * @example\n * ```tsx\n * function App() {\n * // Automatically sync window size changes to host\n * useWindowResizeSync();\n *\n * return <div>Plugin UI</div>;\n * }\n * ```\n */\nexport function useWindowResizeSync(): void {\n useEffect(() => {\n const handleResize = (): void => {\n const width = window.innerWidth;\n const height = window.innerHeight;\n\n requestResize(width, height).catch((err) => {\n logger.error('Failed to notify host of resize', { error: err, width, height });\n });\n };\n\n window.addEventListener('resize', handleResize);\n\n return (): void => {\n window.removeEventListener('resize', handleResize);\n };\n }, []);\n}\n","/**\n * useRequestResize - Hook for requesting window resize\n */\n\nimport { requestResize } from './useWindowResizeSync';\n\n/**\n * React hook for requesting window resize\n *\n * @returns Function to request resize\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const resize = useRequestResize();\n *\n * const handleExpand = async () => {\n * const accepted = await resize(1200, 900);\n * if (!accepted) {\n * alert('Host rejected resize request');\n * }\n * };\n *\n * return <button onClick={handleExpand}>Expand</button>;\n * }\n * ```\n */\nexport function useRequestResize(): (width: number, height: number) => Promise<boolean> {\n return requestResize;\n}\n","/**\n * Meter polling API for audio visualization (IPC-based)\n */\n\nimport { IpcBridge } from './ipc/IpcBridge';\nimport type { MeterFrame, GetMeterFrameResult } from './types/metering';\n\n/**\n * Get the latest meter frame from the audio engine\n */\nexport async function getMeterFrame(): Promise<MeterFrame | null> {\n const bridge = IpcBridge.getInstance();\n const result = await bridge.invoke<GetMeterFrameResult>('getMeterFrame');\n return result.frame;\n}\n"],"names":["LogLevel"],"mappings":"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"}
package/dist/meters.d.ts CHANGED
@@ -20,6 +20,11 @@ export declare interface GetMeterFrameResult {
20
20
  frame: MeterFrame | null;
21
21
  }
22
22
 
23
+ /**
24
+ * Audio Math Utilities
25
+ *
26
+ * Pure functions for audio calculations with no side effects.
27
+ */
23
28
  /**
24
29
  * Convert linear amplitude to decibels
25
30
  * @param linear Linear amplitude (0.0 to 1.0+)
@@ -27,6 +32,11 @@ export declare interface GetMeterFrameResult {
27
32
  */
28
33
  export declare function linearToDb(linear: number, floor?: number): number;
29
34
 
35
+ /**
36
+ * Metering Types
37
+ *
38
+ * Types related to audio metering.
39
+ */
30
40
  /**
31
41
  * Meter frame data (all values in linear scale, not dB)
32
42
  */
@@ -1 +1 @@
1
- {"version":3,"file":"meters.js","sources":["../src/meters.ts"],"sourcesContent":["/**\n * @wavecraft/core/meters - Pure audio math utilities\n *\n * These utilities have no IPC side effects and can be used\n * in tests or standalone applications.\n *\n * @packageDocumentation\n */\n\n/**\n * Meter frame data (all values in linear scale, not dB)\n */\nexport interface MeterFrame {\n peak_l: number;\n peak_r: number;\n rms_l: number;\n rms_r: number;\n timestamp: number;\n}\n\n/**\n * Result from getMeterFrame method\n */\nexport interface GetMeterFrameResult {\n frame: MeterFrame | null;\n}\n\n/**\n * Convert linear amplitude to decibels\n * @param linear Linear amplitude (0.0 to 1.0+)\n * @param floor Minimum dB value to return (default: -60)\n */\nexport function linearToDb(linear: number, floor: number = -60): number {\n if (linear <= 0) {\n return floor;\n }\n const db = 20 * Math.log10(linear);\n return Math.max(db, floor);\n}\n\n/**\n * Convert decibels to linear amplitude\n * @param db Decibels\n */\nexport function dbToLinear(db: number): number {\n return Math.pow(10, db / 20);\n}\n"],"names":[],"mappings":"AAgCO,SAAS,WAAW,QAAgB,QAAgB,KAAa;AACtE,MAAI,UAAU,GAAG;AACf,WAAO;AAAA,EACT;AACA,QAAM,KAAK,KAAK,KAAK,MAAM,MAAM;AACjC,SAAO,KAAK,IAAI,IAAI,KAAK;AAC3B;AAMO,SAAS,WAAW,IAAoB;AAC7C,SAAO,KAAK,IAAI,IAAI,KAAK,EAAE;AAC7B;"}
1
+ {"version":3,"file":"meters.js","sources":["../src/utils/audio-math.ts"],"sourcesContent":["/**\n * Audio Math Utilities\n *\n * Pure functions for audio calculations with no side effects.\n */\n\n/**\n * Convert linear amplitude to decibels\n * @param linear Linear amplitude (0.0 to 1.0+)\n * @param floor Minimum dB value to return (default: -60)\n */\nexport function linearToDb(linear: number, floor: number = -60): number {\n if (linear <= 0) {\n return floor;\n }\n const db = 20 * Math.log10(linear);\n return Math.max(db, floor);\n}\n\n/**\n * Convert decibels to linear amplitude\n * @param db Decibels\n */\nexport function dbToLinear(db: number): number {\n return Math.pow(10, db / 20);\n}\n"],"names":[],"mappings":"AAWO,SAAS,WAAW,QAAgB,QAAgB,KAAa;AACtE,MAAI,UAAU,GAAG;AACf,WAAO;AAAA,EACT;AACA,QAAM,KAAK,KAAK,KAAK,MAAM,MAAM;AACjC,SAAO,KAAK,IAAI,IAAI,KAAK;AAC3B;AAMO,SAAS,WAAW,IAAoB;AAC7C,SAAO,KAAK,IAAI,IAAI,KAAK,EAAE;AAC7B;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wavecraft/core",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Core SDK for Wavecraft audio plugins — IPC bridge, hooks, and utilities",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",