@cutoff/audio-ui-core 1.0.0-preview.20260123.1125
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/LICENSE.md +690 -0
- package/dist/constants/cssVars.d.ts +42 -0
- package/dist/constants/cssVars.d.ts.map +1 -0
- package/dist/constants/cursors.d.ts +2 -0
- package/dist/constants/cursors.d.ts.map +1 -0
- package/dist/constants/interaction.d.ts +24 -0
- package/dist/constants/interaction.d.ts.map +1 -0
- package/dist/constants/styles.d.ts +17 -0
- package/dist/constants/styles.d.ts.map +1 -0
- package/dist/constants/theme.d.ts +66 -0
- package/dist/constants/theme.d.ts.map +1 -0
- package/dist/constants/themeDefaults.d.ts +16 -0
- package/dist/constants/themeDefaults.d.ts.map +1 -0
- package/dist/controller/BooleanInteractionController.d.ts +141 -0
- package/dist/controller/BooleanInteractionController.d.ts.map +1 -0
- package/dist/controller/ContinuousInteractionController.d.ts +114 -0
- package/dist/controller/ContinuousInteractionController.d.ts.map +1 -0
- package/dist/controller/DiscreteInteractionController.d.ts +51 -0
- package/dist/controller/DiscreteInteractionController.d.ts.map +1 -0
- package/dist/controller/NoteInteractionController.d.ts +88 -0
- package/dist/controller/NoteInteractionController.d.ts.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1404 -0
- package/dist/index.js.map +1 -0
- package/dist/model/AudioParameter.d.ts +437 -0
- package/dist/model/AudioParameter.d.ts.map +1 -0
- package/dist/styles/styles.css +256 -0
- package/dist/styles/themes.css +193 -0
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/colorUtils.d.ts +102 -0
- package/dist/utils/colorUtils.d.ts.map +1 -0
- package/dist/utils/math.d.ts +108 -0
- package/dist/utils/math.d.ts.map +1 -0
- package/dist/utils/normalizedProps.d.ts +22 -0
- package/dist/utils/normalizedProps.d.ts.map +1 -0
- package/dist/utils/noteUtils.d.ts +71 -0
- package/dist/utils/noteUtils.d.ts.map +1 -0
- package/dist/utils/propCompare.d.ts +22 -0
- package/dist/utils/propCompare.d.ts.map +1 -0
- package/dist/utils/sizing.d.ts +39 -0
- package/dist/utils/sizing.d.ts.map +1 -0
- package/dist/utils/svg.d.ts +27 -0
- package/dist/utils/svg.d.ts.map +1 -0
- package/dist/utils/valueFormatters.d.ts +167 -0
- package/dist/utils/valueFormatters.d.ts.map +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/constants/styles.ts","../src/constants/theme.ts","../src/constants/themeDefaults.ts","../src/constants/cssVars.ts","../src/constants/cursors.ts","../src/constants/interaction.ts","../src/controller/ContinuousInteractionController.ts","../src/controller/DiscreteInteractionController.ts","../src/controller/BooleanInteractionController.ts","../src/controller/NoteInteractionController.ts","../src/model/AudioParameter.ts","../src/utils/math.ts","../src/utils/svg.ts","../src/utils/sizing.ts","../src/utils/colorUtils.ts","../src/utils/normalizedProps.ts","../src/utils/noteUtils.ts","../src/utils/valueFormatters.ts","../src/utils/propCompare.ts"],"sourcesContent":["/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n/**\n * Centralized collection of CSS class names used across AudioUI components.\n * Keeping them in one place makes it easier to enforce prefixing conventions\n * and minimizes the risk of typos in string literals.\n */\nexport const CLASSNAMES = {\n /** Root class applied to all AudioUI components */\n root: \"audioui\",\n /** Container helper class for components that manage their own SVG sizing */\n container: \"audioui-component-container\",\n /** Highlight class used for interactive states */\n highlight: \"audioui-highlight\",\n /** Icon wrapper class for HTML overlay - ensures SVG icons fill their container */\n iconWrapper: \"audioui-icon-wrapper\",\n} as const;\n\nexport type AudioUiClassName = (typeof CLASSNAMES)[keyof typeof CLASSNAMES];\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n/**\n * Predefined theme colors as CSS color values\n * These are just the primary color - variants (primary50, primary20) are computed automatically by components\n *\n * @example\n * ```tsx\n * import { themeColors, setThemeColor } from '@cutoff/audio-ui-react';\n *\n * // Set global theme color\n * setThemeColor(themeColors.blue);\n *\n * // Or use in component props\n * <Knob value={50} color={themeColors.blue} />\n * ```\n */\nexport const themeColors = {\n /** Default adaptive color (white in dark mode, black in light mode) */\n default: \"var(--audioui-theme-default)\",\n /** Blue theme color */\n blue: \"var(--audioui-theme-blue)\",\n /** Orange theme color */\n orange: \"var(--audioui-theme-orange)\",\n /** Pink theme color */\n pink: \"var(--audioui-theme-pink)\",\n /** Green theme color */\n green: \"var(--audioui-theme-green)\",\n /** Purple theme color */\n purple: \"var(--audioui-theme-purple)\",\n /** Yellow theme color */\n yellow: \"var(--audioui-theme-yellow)\",\n} as const;\n\n/**\n * Predefined theme colors as direct HSL values\n * Use these when CSS variables aren't available or you need direct color values\n */\nexport const themeColorsDirect = {\n default: {\n light: \"hsl(0, 0%, 10%)\",\n dark: \"hsl(0, 0%, 96%)\",\n },\n blue: {\n light: \"hsl(204, 88%, 52%)\",\n dark: \"hsl(204, 88%, 53%)\",\n },\n orange: {\n light: \"hsl(29, 100%, 48%)\",\n dark: \"hsl(29, 100%, 50%)\",\n },\n pink: {\n light: \"hsl(332, 92%, 52%)\",\n dark: \"hsl(332, 95%, 54%)\",\n },\n green: {\n light: \"hsl(160, 95%, 44%)\",\n dark: \"hsl(160, 98%, 37%)\",\n },\n purple: {\n light: \"hsl(252, 96%, 54%)\",\n dark: \"hsl(252, 100%, 67%)\",\n },\n yellow: {\n light: \"hsl(50, 100%, 50%)\",\n dark: \"hsl(50, 100%, 50%)\",\n },\n} as const;\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n/**\n * Default theme values for themable components.\n * These constants provide a single source of truth for default values\n * and can be easily adjusted to change the global appearance.\n */\n\n/**\n * Default roundness value (normalized 0.0-1.0)\n * @default 0.3\n */\nexport const DEFAULT_ROUNDNESS = 0.3;\n\n/**\n * Default thickness value (normalized 0.0-1.0)\n * @default 0.4\n */\nexport const DEFAULT_THICKNESS = 0.4;\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n/**\n * Shared constants for CSS custom property names used throughout the library.\n * Using string constants makes it easier to update prefixes in the future and\n * keeps TypeScript aware of the possible values.\n */\nexport const CSS_VARS = {\n // Theming parameters\n roundnessBase: \"--audioui-roundness-base\",\n primaryColor: \"--audioui-primary-color\",\n adaptiveDefaultColor: \"--audioui-adaptive-default-color\",\n adaptive50: \"--audioui-adaptive-50\",\n adaptive20: \"--audioui-adaptive-20\",\n adaptiveLight: \"--audioui-adaptive-light\",\n adaptiveDark: \"--audioui-adaptive-dark\",\n primary50: \"--audioui-primary-50\",\n primary20: \"--audioui-primary-20\",\n primaryLight: \"--audioui-primary-light\",\n primaryDark: \"--audioui-primary-dark\",\n\n // Text and typography\n textColor: \"--audioui-text-color\",\n defaultFontSize: \"--audioui-default-font-size\",\n\n // Interactive highlight\n highlightEffect: \"--audioui-highlight-effect\",\n highlightTransitionDuration: \"--audioui-highlight-transition-duration\",\n\n // Cursor styles\n cursorClickable: \"--audioui-cursor-clickable\",\n cursorBidirectional: \"--audioui-cursor-bidirectional\",\n cursorHorizontal: \"--audioui-cursor-horizontal\",\n cursorVertical: \"--audioui-cursor-vertical\",\n cursorCircular: \"--audioui-cursor-circular\",\n cursorNoneditable: \"--audioui-cursor-noneditable\",\n cursorDisabled: \"--audioui-cursor-disabled\",\n\n // Keys colors\n keysIvory: \"--audioui-keys-ivory\",\n keysIvoryStroke: \"--audioui-keys-ivory-stroke\",\n keysEbony: \"--audioui-keys-ebony\",\n keysEbonyStroke: \"--audioui-keys-ebony-stroke\",\n\n // Component style customization\n knobCapFill: \"--audioui-knob-cap-fill\",\n buttonStrokeWidth: \"--audioui-button-stroke-width\",\n\n // Slider component colors\n sliderTrackColor: \"--audioui-slider-track-color\",\n sliderStripColor: \"--audioui-slider-strip-color\",\n sliderCursorColor: \"--audioui-slider-cursor-color\",\n sliderCursorBorderColor: \"--audioui-slider-cursor-border-color\",\n sliderCursorBorderWidth: \"--audioui-slider-cursor-border-width\",\n} as const;\n\nexport type AudioUiCssVar = (typeof CSS_VARS)[keyof typeof CSS_VARS];\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nexport const CIRCULAR_CURSOR =\n \"url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48Y2lyY2xlIGN4PSIxNiIgY3k9IjE2IiByPSIxMCIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSI0Ii8+PGNpcmNsZSBjeD0iMTYiIGN5PSIxNiIgcj0iMTAiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPg==') 16 16, move\";\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n/**\n * Standard sensitivity for continuous controls.\n * Represents normalized value change per pixel of drag.\n * 0.005 means 200 pixels for a full 0-1 sweep.\n * This aligns with standard \"virtual throw\" in audio software.\n */\nexport const DEFAULT_CONTINUOUS_SENSITIVITY = 0.005;\n\n/**\n * Standard sensitivity for wheel interactions.\n * Represents normalized value change per unit of wheel delta.\n */\nexport const DEFAULT_WHEEL_SENSITIVITY = 0.005;\n\n/**\n * Target pixels per discrete step.\n * Used for adaptive sensitivity: if a step exists, we ensure\n * it doesn't take more than this many pixels to traverse it.\n * This prevents \"dead zones\" in low-resolution parameters.\n */\nexport const TARGET_PIXELS_PER_STEP = 30;\n\n/**\n * Default step size for keyboard interaction (normalized 0..1).\n */\nexport const DEFAULT_KEYBOARD_STEP = 0.05;\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport { InteractionDirection, InteractionMode } from \"../types\";\nimport {\n DEFAULT_CONTINUOUS_SENSITIVITY,\n DEFAULT_KEYBOARD_STEP,\n DEFAULT_WHEEL_SENSITIVITY,\n} from \"../constants/interaction\";\n\nexport interface ContinuousInteractionConfig {\n /**\n * Function to adjust the value based on a delta.\n * ...\n */\n adjustValue: (delta: number, sensitivity?: number) => void;\n\n /**\n * Interaction mode: drag, wheel, or both.\n * @default \"both\"\n */\n interactionMode?: InteractionMode;\n\n /**\n * Direction of the drag interaction.\n * @default \"both\"\n */\n direction?: InteractionDirection;\n\n /**\n * Sensitivity of the control.\n * ...\n * @default 0.005\n */\n sensitivity?: number;\n\n /**\n * Separate sensitivity for wheel events.\n * If not provided, defaults to DEFAULT_WHEEL_SENSITIVITY (0.005).\n */\n wheelSensitivity?: number;\n\n /**\n * Step size for keyboard interaction (normalized 0..1)\n * @default 0.05\n */\n keyboardStep?: number;\n\n /**\n * Normalized step size of the parameter (0..1).\n * Used for adaptive wheel interaction (accumulating deltas until a step is reached).\n * If not provided, wheel interaction is continuous.\n */\n step?: number;\n\n /**\n * Callback when a drag interaction starts\n */\n onDragStart?: () => void;\n\n /**\n * Callback when a drag interaction ends\n */\n onDragEnd?: () => void;\n\n /**\n * Whether the control is disabled\n */\n disabled?: boolean;\n}\n\n/**\n * Framework-agnostic controller for handling user interactions (Drag, Wheel, Keyboard)\n * for continuous controls.\n */\nexport class ContinuousInteractionController {\n private config: Required<\n Omit<ContinuousInteractionConfig, \"wheelSensitivity\" | \"step\" | \"onDragStart\" | \"onDragEnd\">\n > & {\n wheelSensitivity?: number;\n step?: number;\n onDragStart?: () => void;\n onDragEnd?: () => void;\n };\n private startX = 0;\n private startY = 0;\n private centerX = 0;\n private centerY = 0;\n private isDragging = false;\n private wheelAccumulator = 0;\n private dragAccumulator = 0;\n\n constructor(config: ContinuousInteractionConfig) {\n this.config = {\n interactionMode: \"both\",\n direction: \"both\",\n sensitivity: DEFAULT_CONTINUOUS_SENSITIVITY,\n keyboardStep: DEFAULT_KEYBOARD_STEP,\n disabled: false,\n // adjustValue is provided in config\n ...config,\n };\n\n this.handleGlobalMouseMove = this.handleGlobalMouseMove.bind(this);\n this.handleGlobalMouseUp = this.handleGlobalMouseUp.bind(this);\n this.handleGlobalTouchMove = this.handleGlobalTouchMove.bind(this);\n }\n\n /**\n * Updates the configuration of the controller.\n * @param config Partial configuration to update.\n */\n public updateConfig(config: Partial<ContinuousInteractionConfig>) {\n Object.assign(this.config, config);\n }\n\n /**\n * Handles the start of a mouse drag interaction.\n * Should be called from the component's onMouseDown handler.\n * @param clientX The X coordinate of the mouse event.\n * @param clientY The Y coordinate of the mouse event.\n * @param target The event target (used for circular center calculation).\n */\n public handleMouseDown = (clientX: number, clientY: number, target?: EventTarget) => {\n if (this.config.interactionMode === \"wheel\") return;\n this.startDrag(clientX, clientY, target);\n };\n\n /**\n * Handles the start of a touch interaction.\n * Should be called from the component's onTouchStart handler.\n * @param clientX The X coordinate of the touch event.\n * @param clientY The Y coordinate of the touch event.\n * @param target The event target.\n */\n public handleTouchStart = (clientX: number, clientY: number, target?: EventTarget) => {\n if (this.config.interactionMode === \"wheel\") return;\n this.startDrag(clientX, clientY, target);\n };\n\n private startDrag(x: number, y: number, target?: EventTarget) {\n if (this.config.disabled) return;\n\n this.startX = x;\n this.startY = y;\n this.isDragging = true;\n this.dragAccumulator = 0;\n this.config.onDragStart?.();\n\n if (this.config.direction === \"circular\" && target && (target as HTMLElement).getBoundingClientRect) {\n const rect = (target as HTMLElement).getBoundingClientRect();\n this.centerX = rect.left + rect.width / 2;\n this.centerY = rect.top + rect.height / 2;\n }\n\n document.body.style.userSelect = \"none\";\n\n // Cursor selection based on interaction direction - uses CSS variables for customization\n // The logic (when to show which cursor) is fixed, but cursor types are customizable via CSS\n let cursor = \"var(--audioui-cursor-vertical)\";\n if (this.config.direction === \"horizontal\") cursor = \"var(--audioui-cursor-horizontal)\";\n if (this.config.direction === \"both\") cursor = \"var(--audioui-cursor-bidirectional)\";\n if (this.config.direction === \"circular\") cursor = \"var(--audioui-cursor-circular)\";\n\n document.body.style.cursor = cursor;\n\n window.addEventListener(\"mousemove\", this.handleGlobalMouseMove);\n window.addEventListener(\"mouseup\", this.handleGlobalMouseUp);\n window.addEventListener(\"touchmove\", this.handleGlobalTouchMove, { passive: false });\n window.addEventListener(\"touchend\", this.handleGlobalMouseUp);\n }\n\n private handleGlobalMouseMove(e: MouseEvent) {\n if (!this.isDragging) return;\n e.preventDefault();\n this.processDrag(e.clientX, e.clientY);\n }\n\n private handleGlobalTouchMove(e: TouchEvent) {\n if (!this.isDragging) return;\n e.preventDefault();\n const touch = e.touches[0];\n this.processDrag(touch.clientX, touch.clientY);\n }\n\n private processDrag(x: number, y: number) {\n let delta = 0;\n if (this.config.direction === \"vertical\") {\n // Vertical drag: Up (negative Y) typically means increase value\n // Invert Y delta so dragging up increases value: (startY - y)\n delta = this.startY - y;\n } else if (this.config.direction === \"horizontal\") {\n // Horizontal drag: Right (positive X) means increase value\n delta = x - this.startX;\n } else if (this.config.direction === \"both\") {\n // Both directions: Sum horizontal (right+) and vertical (up+) movements\n // This allows diagonal drags to combine both axes for faster adjustment\n delta = x - this.startX + (this.startY - y);\n } else if (this.config.direction === \"circular\") {\n // Circular drag: Calculate angular change around the center point\n // atan2(y, x): 0° is right (x+, y0), 90° is down (x0, y+), clockwise rotation\n const currentAngle = Math.atan2(y - this.centerY, x - this.centerX);\n const startAngle = Math.atan2(this.startY - this.centerY, this.startX - this.centerX);\n\n let angleDelta = currentAngle - startAngle;\n\n // Handle wrapping across ±180° boundary (shortest path)\n // Without this, dragging across the 180° line would cause a large jump\n if (angleDelta > Math.PI) angleDelta -= 2 * Math.PI;\n else if (angleDelta < -Math.PI) angleDelta += 2 * Math.PI;\n\n // Convert radians to degrees (1 degree ≈ 1 pixel for sensitivity matching)\n delta = angleDelta * (180 / Math.PI);\n }\n\n if (delta !== 0) {\n const normalizedDelta = delta * this.config.sensitivity;\n\n if (this.config.step) {\n this.dragAccumulator += normalizedDelta;\n\n if (Math.abs(this.dragAccumulator) >= this.config.step) {\n // How many whole steps?\n const stepsToMove = Math.trunc(this.dragAccumulator / this.config.step);\n const valueChange = stepsToMove * this.config.step;\n\n this.config.adjustValue(valueChange, 1.0); // Pass 1.0 as sensitivity since we calculated the full normalized delta\n\n // Keep the remainder in the accumulator\n this.dragAccumulator -= valueChange;\n }\n } else {\n this.config.adjustValue(delta, this.config.sensitivity);\n }\n\n this.startX = x;\n this.startY = y;\n }\n }\n\n /**\n * Handles global mouse up event to end drag.\n */\n private handleGlobalMouseUp() {\n if (!this.isDragging) return;\n\n this.isDragging = false;\n this.config.onDragEnd?.();\n\n document.body.style.userSelect = \"\";\n document.body.style.cursor = \"\";\n\n window.removeEventListener(\"mousemove\", this.handleGlobalMouseMove);\n window.removeEventListener(\"mouseup\", this.handleGlobalMouseUp);\n window.removeEventListener(\"touchmove\", this.handleGlobalTouchMove);\n window.removeEventListener(\"touchend\", this.handleGlobalMouseUp);\n }\n\n /**\n * Handles wheel events.\n * Should be called from the component's onWheel handler.\n * @param e The wheel event.\n */\n public handleWheel = (e: WheelEvent) => {\n if (this.config.disabled) return;\n if (this.config.interactionMode !== \"wheel\" && this.config.interactionMode !== \"both\") return;\n\n if (e.preventDefault) e.preventDefault();\n if (e.stopPropagation) e.stopPropagation();\n\n const delta = e.deltaY;\n // Use separate wheel sensitivity default (or user provided), ignoring adaptive drag sensitivity\n const effectiveSensitivity = this.config.wheelSensitivity ?? DEFAULT_WHEEL_SENSITIVITY;\n\n // If we have a discrete step size, we use an accumulator to ensure we don't land between steps\n // This solves \"too fast\" issues on notched mice (ensures 1 notch >= 1 step)\n // and \"too slow\" issues on trackpads (accumulates small deltas until 1 step)\n if (this.config.step) {\n this.wheelAccumulator += delta * effectiveSensitivity;\n\n if (Math.abs(this.wheelAccumulator) >= this.config.step) {\n const stepsToMove = Math.trunc(this.wheelAccumulator / this.config.step);\n // Move exactly N steps.\n // Note: we pass 1.0 as sensitivity so the first arg is the absolute normalized delta.\n this.config.adjustValue(stepsToMove * this.config.step, 1.0);\n this.wheelAccumulator -= stepsToMove * this.config.step;\n }\n } else {\n this.config.adjustValue(delta, effectiveSensitivity);\n }\n };\n\n /**\n * Handles keyboard events (Arrow keys, Home, End).\n * Should be called from the component's onKeyDown handler.\n * @param e The keyboard event.\n */\n public handleKeyDown = (e: KeyboardEvent) => {\n if (this.config.disabled) return;\n\n let delta = 0;\n switch (e.key) {\n case \"ArrowUp\":\n case \"ArrowRight\":\n delta = 1;\n break;\n case \"ArrowDown\":\n case \"ArrowLeft\":\n delta = -1;\n break;\n case \"Home\":\n // Large negative delta to reach minimum\n delta = -1 / this.config.sensitivity;\n break;\n case \"End\":\n // Large positive delta to reach maximum\n delta = 1 / this.config.sensitivity;\n break;\n default:\n return;\n }\n\n e.preventDefault();\n\n // adjustValue(d, s) -> d * s. We want `keyboardStep`, so:\n // passed_delta = keyboardStep / sensitivity * direction\n const effectiveDelta = delta * (this.config.keyboardStep / this.config.sensitivity);\n this.config.adjustValue(effectiveDelta, this.config.sensitivity);\n };\n\n /**\n * Cleans up event listeners.\n */\n public dispose() {\n this.handleGlobalMouseUp();\n }\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nexport interface DiscreteInteractionConfig {\n /** Current value of the control */\n value: string | number;\n /** List of available options */\n options: Array<{ value: string | number }>;\n /** Callback to update the value */\n onValueChange: (value: string | number) => void;\n /** Whether the control is disabled */\n disabled?: boolean;\n}\n\n/**\n * Framework-agnostic controller for discrete interaction logic.\n * Handles the logic for cycling and stepping through discrete options.\n */\nexport class DiscreteInteractionController {\n constructor(private config: DiscreteInteractionConfig) {}\n\n /**\n * Updates the configuration.\n * @param config New configuration object.\n */\n public updateConfig(config: DiscreteInteractionConfig) {\n this.config = config;\n }\n\n private getCurrentIndex(): number {\n const idx = this.config.options.findIndex((opt) => opt.value === this.config.value);\n return idx === -1 ? 0 : idx;\n }\n\n private setValueAtIndex(index: number) {\n if (this.config.disabled) return;\n const opt = this.config.options[index];\n if (opt) {\n this.config.onValueChange(opt.value);\n }\n }\n\n /**\n * Cycles to the next value, wrapping around to the start if needed.\n */\n public cycleNext() {\n if (this.config.options.length <= 1) return;\n const currentIdx = this.getCurrentIndex();\n const nextIdx = (currentIdx + 1) % this.config.options.length;\n this.setValueAtIndex(nextIdx);\n }\n\n /**\n * Steps to the next value, clamping at the end.\n */\n public stepNext() {\n if (this.config.options.length <= 1) return;\n const currentIdx = this.getCurrentIndex();\n if (currentIdx < this.config.options.length - 1) {\n this.setValueAtIndex(currentIdx + 1);\n }\n }\n\n /**\n * Steps to the previous value, clamping at the start.\n */\n public stepPrev() {\n if (this.config.options.length <= 1) return;\n const currentIdx = this.getCurrentIndex();\n if (currentIdx > 0) {\n this.setValueAtIndex(currentIdx - 1);\n }\n }\n\n /**\n * Handles click events.\n * Cycles to the next value if the event was not already prevented (e.g. by a drag).\n * @param defaultPrevented Whether the event's default action has been prevented.\n */\n public handleClick = (defaultPrevented: boolean) => {\n if (this.config.disabled) return;\n if (!defaultPrevented) {\n this.cycleNext();\n }\n };\n\n /**\n * Handles keyboard events for discrete controls.\n * Returns true if the event was handled (and thus should be prevented), false otherwise.\n */\n public handleKeyDown = (key: string): boolean => {\n if (this.config.disabled) return false;\n\n switch (key) {\n case \" \":\n case \"Enter\":\n this.cycleNext();\n return true;\n case \"ArrowUp\":\n case \"ArrowRight\":\n this.stepNext();\n return true;\n case \"ArrowDown\":\n case \"ArrowLeft\":\n this.stepPrev();\n return true;\n }\n return false;\n };\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nexport type BooleanInteractionMode = \"toggle\" | \"momentary\";\n\nexport interface BooleanInteractionConfig {\n /** Current value of the control */\n value: boolean;\n /** Interaction mode: toggle (latch) or momentary */\n mode: BooleanInteractionMode;\n /** Callback to update the value */\n onValueChange: (value: boolean) => void;\n /** Whether the control is disabled */\n disabled?: boolean;\n}\n\n/**\n * Framework-agnostic controller for boolean interaction logic.\n *\n * Handles the logic for toggle and momentary button interactions, including:\n * - Mouse down/up events for activation and release\n * - Global pointer tracking (works even when press starts outside button)\n * - Mouse enter/leave events for drag-in/drag-out behavior\n * - Keyboard events (Enter/Space) for activation and release\n * - Press state tracking for momentary mode\n *\n * **Drag-In/Drag-Out Behavior:**\n * The controller tracks global pointer state to enable hardware-like button interactions:\n * - **Momentary Mode**: Press inside → turns on; drag out while pressed → turns off; drag back in while pressed → turns on again. Works even when press starts outside the button.\n * - **Toggle Mode**: Press inside → toggles state; drag out while pressed → no change; drag back in while pressed → toggles again. Works even when press starts outside the button.\n *\n * This enables step sequencer-like interactions where multiple buttons can be activated with a single drag gesture.\n *\n * This controller is designed to be framework-agnostic and can be used with any UI framework\n * by wrapping it in framework-specific hooks (e.g., `useBooleanInteraction` for React).\n */\nexport class BooleanInteractionController {\n private isPressed: boolean = false;\n private isGlobalPointerDown: boolean = false;\n\n constructor(private config: BooleanInteractionConfig) {}\n\n /**\n * Updates the controller configuration.\n *\n * This method should be called whenever the configuration changes (e.g., value, mode, disabled state).\n * The controller will use the new configuration for all subsequent interactions.\n *\n * @param {BooleanInteractionConfig} config - New configuration object\n */\n public updateConfig(config: BooleanInteractionConfig) {\n this.config = config;\n }\n\n /**\n * Handles global pointer down events (mouse or touch).\n *\n * This tracks when a pointer is pressed anywhere on the page, not just on this button.\n * This allows the button to respond to drag-in behavior even when the press starts outside.\n *\n * @param {boolean} defaultPrevented - Whether the event's default action has been prevented. If true, the handler does nothing.\n */\n public handleGlobalPointerDown = (defaultPrevented: boolean) => {\n if (this.config.disabled) return;\n if (defaultPrevented) return;\n this.isGlobalPointerDown = true;\n };\n\n /**\n * Handles mouse down events on the button element.\n *\n * Behavior depends on interaction mode:\n * - **Toggle mode**: Flips the current value (true ↔ false)\n * - **Momentary mode**: Sets value to true and tracks press state for later release\n *\n * @param {boolean} defaultPrevented - Whether the event's default action has been prevented. If true, the handler does nothing.\n */\n public handleMouseDown = (defaultPrevented: boolean) => {\n if (this.config.disabled) return;\n if (defaultPrevented) return;\n\n this.isGlobalPointerDown = true;\n\n if (this.config.mode === \"toggle\") {\n // Toggle mode: flip the value on each press\n this.config.onValueChange(!this.config.value);\n } else {\n // Momentary mode: set to true on press, will be set to false on release\n this.isPressed = true;\n this.config.onValueChange(true);\n }\n };\n\n /**\n * Handles mouse up events on the button element.\n *\n * For momentary mode: Sets value to false if the button is currently pressed.\n * This prevents false releases if the button wasn't actually pressed.\n * Toggle mode: No action (toggle happens on mousedown only).\n *\n * Note: This is called when mouse is released on the button. The global pointer up\n * handler will also be called to reset global state.\n *\n * @param {boolean} defaultPrevented - Whether the event's default action has been prevented. If true, the handler does nothing.\n */\n public handleMouseUp = (defaultPrevented: boolean) => {\n if (this.config.disabled) return;\n if (defaultPrevented) return;\n\n // Only handle release for momentary buttons that are currently pressed\n // This prevents false releases if the button wasn't actually pressed\n if (this.config.mode === \"momentary\" && this.isPressed) {\n this.isPressed = false;\n this.config.onValueChange(false);\n }\n // Note: Global pointer state is reset by handleGlobalPointerUp\n };\n\n /**\n * Handles global pointer up events (mouse or touch).\n *\n * This method is called when a pointer up event occurs anywhere on the page.\n * It resets the global pointer state and handles release for momentary buttons.\n *\n * Only affects momentary mode buttons that are currently pressed.\n */\n public handleGlobalPointerUp = () => {\n if (this.config.disabled) return;\n\n this.isGlobalPointerDown = false;\n\n if (this.config.mode === \"momentary\" && this.isPressed) {\n this.isPressed = false;\n this.config.onValueChange(false);\n }\n };\n\n /**\n * Handles mouse enter events (pointer enters the button element).\n *\n * When a pointer enters the button while globally pressed:\n * - **Momentary mode**: Sets value to true\n * - **Toggle mode**: Toggles the value\n *\n * This enables drag-in behavior: pressing outside and dragging into the button activates it.\n */\n public handleMouseEnter = () => {\n if (this.config.disabled) return;\n\n // Only react if pointer is globally pressed (drag-in scenario)\n if (this.isGlobalPointerDown) {\n if (this.config.mode === \"toggle\") {\n // Toggle mode: flip the value when entering while pressed\n this.config.onValueChange(!this.config.value);\n } else {\n // Momentary mode: set to true when entering while pressed\n this.isPressed = true;\n this.config.onValueChange(true);\n }\n }\n };\n\n /**\n * Handles mouse leave events (pointer leaves the button element).\n *\n * When a pointer leaves the button while globally pressed:\n * - **Momentary mode**: Sets value to false\n * - **Toggle mode**: No action (state remains as-is)\n *\n * This enables drag-out behavior: pressing inside and dragging out deactivates momentary buttons.\n */\n public handleMouseLeave = () => {\n if (this.config.disabled) return;\n\n // Only react if pointer is globally pressed (drag-out scenario)\n if (this.isGlobalPointerDown) {\n if (this.config.mode === \"momentary\" && this.isPressed) {\n // Momentary mode: set to false when leaving while pressed\n this.isPressed = false;\n this.config.onValueChange(false);\n }\n // Toggle mode: no action when leaving (state remains as-is)\n }\n };\n\n /**\n * Handles keyboard key down events.\n *\n * Supported keys:\n * - `Enter` or `Space`: Activates the button\n * - Toggle mode: Flips the value\n * - Momentary mode: Sets value to true\n *\n * @param {string} key - The keyboard key that was pressed\n * @returns {boolean} `true` if the event was handled (and should be prevented), `false` otherwise\n */\n public handleKeyDown = (key: string): boolean => {\n if (this.config.disabled) return false;\n\n if (key === \"Enter\" || key === \" \") {\n if (this.config.mode === \"toggle\") {\n // Toggle mode: flip the value\n this.config.onValueChange(!this.config.value);\n } else {\n // Momentary mode: set to true\n this.config.onValueChange(true);\n }\n return true;\n }\n return false;\n };\n\n /**\n * Handles keyboard key up events.\n *\n * Supported keys (momentary mode only):\n * - `Enter` or `Space`: Releases the button (sets value to false)\n *\n * Toggle mode: No action (toggle happens on keydown only).\n *\n * @param {string} key - The keyboard key that was released\n * @returns {boolean} `true` if the event was handled (and should be prevented), `false` otherwise\n */\n public handleKeyUp = (key: string): boolean => {\n if (this.config.disabled) return false;\n\n if (this.config.mode === \"momentary\" && (key === \"Enter\" || key === \" \")) {\n this.config.onValueChange(false);\n return true;\n }\n return false;\n };\n\n /**\n * Gets the current press state (for momentary buttons).\n *\n * This is primarily used for internal state tracking. External code typically\n * doesn't need to check this, as the value reflects the button state.\n *\n * @returns {boolean} `true` if the button is currently pressed, `false` otherwise\n */\n public getIsPressed(): boolean {\n return this.isPressed;\n }\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n/**\n * Configuration for the NoteInteractionController.\n */\nexport interface NoteInteractionConfig {\n /** Callback triggered when a note is pressed (on) */\n onNoteOn: (note: number, pointerId: number | string) => void;\n /** Callback triggered when a note is released (off) */\n onNoteOff: (note: number, pointerId: number | string) => void;\n /** Whether the interaction is disabled */\n disabled?: boolean;\n}\n\n/**\n * Framework-agnostic controller for keyboard/note interaction logic.\n *\n * Handles polyphonic glissando-style interactions:\n * - Tracks multiple concurrent pointers (mouse, multi-touch)\n * - Detects note changes when sliding across keys\n * - Triggers onNoteOn/onNoteOff events\n *\n * This controller is designed to be used by any UI framework by providing\n * coordinates-to-note mapping and pointer events.\n *\n * @example\n * ```ts\n * const controller = new NoteInteractionController({\n * onNoteOn: (note) => console.log('Note On:', note),\n * onNoteOff: (note) => console.log('Note Off:', note)\n * });\n *\n * // On mouse/touch down\n * controller.handlePointerDown(1, 60); // C4\n *\n * // On movement\n * controller.handlePointerMove(1, 62); // D4 (triggers Note Off 60, Note On 62)\n *\n * // On mouse/touch up\n * controller.handlePointerUp(1); // triggers Note Off 62\n * ```\n */\nexport class NoteInteractionController {\n private pointerNotes: Map<number | string, number | null> = new Map();\n\n /**\n * Creates a new NoteInteractionController.\n * @param config Initial configuration\n */\n constructor(private config: NoteInteractionConfig) {}\n\n /**\n * Updates the controller configuration.\n * @param config New configuration object\n */\n public updateConfig(config: NoteInteractionConfig) {\n this.config = config;\n }\n\n /**\n * Handles the start of a pointer interaction (down).\n *\n * Tracks the pointer state globally and triggers onNoteOn if the pointer\n * is over a specific note.\n *\n * @param pointerId Unique identifier for the pointer\n * @param note The MIDI note number at the pointer location, or null if none\n */\n public handlePointerDown(pointerId: number | string, note: number | null) {\n if (this.config.disabled) return;\n\n // If this pointer was already active, release it first (safety)\n this.handlePointerUp(pointerId);\n\n // Always track that the pointer is down, even if not over a specific note\n this.pointerNotes.set(pointerId, note);\n\n if (note !== null) {\n this.config.onNoteOn(note, pointerId);\n }\n }\n\n /**\n * Handles pointer movement (move).\n *\n * Enables glissando behavior by detecting when a tracked pointer moves\n * from one note to another.\n *\n * @param pointerId Unique identifier for the pointer\n * @param note The MIDI note number at the current pointer location, or null if none\n */\n public handlePointerMove(pointerId: number | string, note: number | null) {\n if (this.config.disabled) return;\n\n // If pointer is not tracked as \"down\", ignore movement (prevents glissando when mouse is up)\n if (!this.pointerNotes.has(pointerId)) return;\n\n const currentNote = this.pointerNotes.get(pointerId);\n\n // If note changed (glissando)\n if (note !== currentNote) {\n // Note off previous\n if (currentNote !== null && currentNote !== undefined) {\n this.config.onNoteOff(currentNote, pointerId);\n }\n\n // Update tracked note (can be null if moved to non-key area while down)\n this.pointerNotes.set(pointerId, note);\n\n // Note on new\n if (note !== null) {\n this.config.onNoteOn(note, pointerId);\n }\n }\n }\n\n /**\n * Handles the end of a pointer interaction (up).\n *\n * Releases any active note associated with the pointer and removes it\n * from the tracking map.\n *\n * @param pointerId Unique identifier for the pointer\n */\n public handlePointerUp(pointerId: number | string) {\n const currentNote = this.pointerNotes.get(pointerId);\n if (currentNote !== undefined) {\n if (currentNote !== null) {\n this.config.onNoteOff(currentNote, pointerId);\n }\n this.pointerNotes.delete(pointerId);\n }\n }\n\n /**\n * Cancels all active pointer interactions.\n * Useful when the component is unmounted or focus is lost.\n */\n public cancelAll() {\n this.pointerNotes.forEach((note, pointerId) => {\n if (note !== null) {\n this.config.onNoteOff(note, pointerId);\n }\n });\n this.pointerNotes.clear();\n }\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport type { MidiResolution } from \"../types\";\n\nexport type AudioParameterType = \"continuous\" | \"boolean\" | \"discrete\";\n\n/**\n * A scale function that transforms normalized values (0..1) to scaled values (0..1)\n * Both forward and inverse must be provided for bidirectional conversion.\n *\n * The scale is applied in the normalized domain (0..1) to create non-linear mappings\n * between the real value domain and the normalized/MIDI domain.\n */\nexport interface ScaleFunction {\n /**\n * Transform normalized value (0..1) to scaled value (0..1)\n * Used when converting Real -> Normalized (in _normalizeReal)\n *\n * @param normalized - A value in the range [0, 1] representing position in real domain\n * @returns A scaled value in the range [0, 1] for MIDI quantization\n */\n forward: (normalized: number) => number;\n\n /**\n * Transform scaled value (0..1) back to normalized value (0..1)\n * Used when converting Normalized -> Real (in _denormalizeReal)\n *\n * @param scaled - A value in the range [0, 1] from MIDI domain\n * @returns A normalized value in the range [0, 1] for real domain conversion\n */\n inverse: (scaled: number) => number;\n\n /**\n * Optional name for debugging/documentation\n */\n name?: string;\n}\n\n/**\n * Predefined scale functions for common audio parameter transformations\n */\nexport const LinearScale: ScaleFunction = {\n forward: (n) => n,\n inverse: (s) => s,\n name: \"linear\",\n};\n\nexport const LogScale: ScaleFunction = {\n forward: (n) => {\n // Formula: log(n * (e - 1) + 1) / log(e)\n // Maps 0 -> 0, 1 -> 1 with logarithmic distribution\n if (n <= 0) return 0;\n if (n >= 1) return 1;\n return Math.log(n * (Math.E - 1) + 1) / Math.log(Math.E);\n },\n inverse: (s) => {\n if (s <= 0) return 0;\n if (s >= 1) return 1;\n return (Math.pow(Math.E, s) - 1) / (Math.E - 1);\n },\n name: \"log\",\n};\n\nexport const ExpScale: ScaleFunction = {\n forward: (n) => {\n // Formula: (e^n - 1) / (e - 1)\n // Maps 0 -> 0, 1 -> 1 with exponential distribution\n if (n <= 0) return 0;\n if (n >= 1) return 1;\n return (Math.pow(Math.E, n) - 1) / (Math.E - 1);\n },\n inverse: (s) => {\n if (s <= 0) return 0;\n if (s >= 1) return 1;\n return Math.log(s * (Math.E - 1) + 1) / Math.log(Math.E);\n },\n name: \"exp\",\n};\n\nexport type ScaleType = ScaleFunction | \"linear\" | \"log\" | \"exp\";\n\n/**\n * Definition for a single option in a discrete parameter.\n *\n * This type represents the parameter model definition (value, label, MIDI mapping).\n * It is separate from visual content, which is provided via React children in components.\n */\nexport interface DiscreteOption {\n /** The value associated with this option */\n value: number | string;\n /** The label for this option (used for display, accessibility, and parameter model) */\n label: string;\n /** Optional explicit MIDI value for custom mapping strategy */\n midiValue?: number;\n}\n\ninterface BaseAudioParameter<T = number | boolean | string> {\n id: string;\n name: string; // 'title' in MIDI 2.0 PE\n type: AudioParameterType;\n\n // The physical MIDI resolution (7, 14 bits, etc.)\n // Default: 32 (High resolution for internal precision)\n midiResolution?: MidiResolution;\n /** Default value for the parameter */\n defaultValue?: T;\n}\n\nexport interface ContinuousParameter extends BaseAudioParameter<number> {\n type: \"continuous\";\n min: number;\n max: number;\n step?: number; // Granularity of the REAL value (e.g. 0.1, or 1 for Integers)\n unit?: string; // \"dB\", \"Hz\"\n scale?: ScaleType;\n /** Whether the parameter operates in bipolar mode (centered around zero) */\n bipolar?: boolean;\n}\n\nexport interface BooleanParameter extends BaseAudioParameter<boolean> {\n type: \"boolean\";\n mode?: \"toggle\" | \"momentary\";\n trueLabel?: string; // e.g. \"On\"\n falseLabel?: string;\n}\n\nexport interface DiscreteParameter extends BaseAudioParameter<number | string> {\n type: \"discrete\";\n /** Array of option definitions (parameter model) */\n options: DiscreteOption[];\n\n midiMapping?: \"spread\" | \"sequential\" | \"custom\";\n}\n\nexport type AudioParameter = ContinuousParameter | BooleanParameter | DiscreteParameter;\n\n/**\n * Implementation class that handles normalization, denormalization, and MIDI conversion.\n *\n * ARCHITECTURE NOTE:\n * This class uses the MIDI Integer Value as the central \"Pivot\" / Source of Truth.\n *\n * Flow:\n * Real Value -> [Quantize] -> MIDI Integer -> [Normalize] -> Normalized Float (0..1)\n * Normalized Float -> [Scale] -> MIDI Integer -> [Convert] -> Real Value\n *\n * This ensures deterministic behavior and alignment with hardware standards.\n */\nexport class AudioParameterConverter {\n private maxMidi: number;\n private scaleFunction: ScaleFunction | null = null;\n\n constructor(public config: AudioParameter) {\n // Default to 32-bit resolution for high precision internal math\n // 32-bit = 4,294,967,296 steps (safe in JS Number 53-bit)\n const resolution = config.midiResolution ?? 32;\n this.maxMidi = Math.pow(2, resolution) - 1;\n\n if (config.type === \"continuous\") {\n const scale = (config as ContinuousParameter).scale;\n this.scaleFunction = this.resolveScale(scale);\n }\n }\n\n private resolveScale(scale?: ScaleType): ScaleFunction {\n if (!scale) return LinearScale;\n if (typeof scale === \"string\") {\n switch (scale) {\n case \"linear\":\n return LinearScale;\n case \"log\":\n return LogScale;\n case \"exp\":\n return ExpScale;\n default:\n return LinearScale;\n }\n }\n return scale; // Already a ScaleFunction\n }\n\n /**\n * [Internal] Pure math normalization from Real to 0..1.\n *\n * This is the first step in the conversion pipeline. It normalizes the real value\n * to 0..1 in the real domain, then applies scale transformation (if not linear).\n * The scale transformation happens in the normalized domain to create non-linear\n * mappings (e.g., logarithmic for volume, exponential for envelope curves).\n */\n private _normalizeReal(realValue: number | boolean | string): number {\n switch (this.config.type) {\n case \"continuous\": {\n const conf = this.config as ContinuousParameter;\n const val = realValue as number;\n\n const normalized = Math.max(0, Math.min(1, (val - conf.min) / (conf.max - conf.min)));\n\n if (this.scaleFunction && this.scaleFunction !== LinearScale) {\n return this.scaleFunction.forward(normalized);\n }\n\n return normalized;\n }\n case \"boolean\": {\n return realValue ? 1.0 : 0.0;\n }\n case \"discrete\": {\n const conf = this.config as DiscreteParameter;\n const index = conf.options.findIndex((o) => o.value === realValue);\n if (index === -1) return 0;\n const count = conf.options.length;\n return count > 1 ? index / (count - 1) : 0;\n }\n }\n }\n\n /**\n * [Internal] Pure math denormalization from 0..1 to Real.\n *\n * This is the inverse of `_normalizeReal()`. It first applies the inverse scale\n * transformation (if not linear), then denormalizes to the real value domain.\n * Finally, it applies step quantization in the real domain (step is always linear,\n * regardless of scale type).\n */\n private _denormalizeReal(normalized: number): number | boolean | string {\n const clamped = Math.max(0, Math.min(1, normalized));\n\n switch (this.config.type) {\n case \"continuous\": {\n const conf = this.config as ContinuousParameter;\n\n let scaled = clamped;\n if (this.scaleFunction && this.scaleFunction !== LinearScale) {\n scaled = this.scaleFunction.inverse(clamped);\n }\n\n let val = conf.min + scaled * (conf.max - conf.min);\n\n // Step quantization is always applied in the real value domain (after scale transformation).\n // This means step creates a linear grid in real units (e.g., 0.1 dB increments),\n // regardless of whether the scale is linear, logarithmic, or exponential.\n //\n // This design works well for:\n // - Linear scales: Natural 1:1 mapping\n // - Log/exp scales with linear units (dB, ms): Step still makes sense in real units\n //\n // For log scales with non-linear units (e.g., frequency in Hz), consider:\n // - Omitting step entirely for smooth control\n // - Using a very small step to allow fine control while providing some quantization\n if (conf.step) {\n const steps = Math.round((val - conf.min) / conf.step);\n val = conf.min + steps * conf.step;\n // Fix floating point precision artifacts (e.g., 42.0000004 -> 42)\n val = Math.round(val * 1e10) / 1e10;\n }\n\n return Math.max(conf.min, Math.min(conf.max, val));\n }\n case \"boolean\": {\n return clamped >= 0.5;\n }\n case \"discrete\": {\n const conf = this.config as DiscreteParameter;\n const count = conf.options.length;\n if (count === 0) return 0;\n const index = Math.round(clamped * (count - 1));\n return conf.options[index].value;\n }\n }\n }\n\n /**\n * Convert Real Value to MIDI Integer (The Pivot).\n *\n * This is the central conversion method that quantizes real-world values to MIDI integers.\n * The MIDI integer serves as the source of truth, ensuring deterministic behavior and\n * alignment with hardware standards.\n *\n * The conversion flow:\n * 1. Normalize the real value to 0..1 (applying scale transformation if needed)\n * 2. Quantize to the configured MIDI resolution (7-bit = 0-127, 14-bit = 0-16383, etc.)\n *\n * @param realValue The real-world value (number, boolean, or string depending on parameter type)\n * @returns The quantized MIDI integer value\n *\n * @example\n * ```ts\n * const converter = new AudioParameterConverter({\n * type: \"continuous\",\n * min: 0,\n * max: 100,\n * midiResolution: 7\n * });\n * converter.toMidi(50); // 64 (50% of 0-100 maps to 64 in 0-127 range)\n * ```\n */\n toMidi(realValue: number | boolean | string): number {\n switch (this.config.type) {\n case \"continuous\": {\n const norm = this._normalizeReal(realValue);\n return Math.round(norm * this.maxMidi);\n }\n case \"boolean\": {\n return realValue ? this.maxMidi : 0;\n }\n case \"discrete\": {\n const conf = this.config as DiscreteParameter;\n const mapping = conf.midiMapping ?? \"spread\";\n\n if (mapping === \"custom\") {\n // Custom mapping: use explicit midiValue from option definition\n const opt = conf.options.find((o) => o.value === realValue);\n if (opt?.midiValue !== undefined) return opt.midiValue;\n }\n\n if (mapping === \"sequential\") {\n // Sequential mapping: option index directly maps to MIDI value (0, 1, 2, ...)\n const index = conf.options.findIndex((o) => o.value === realValue);\n return index === -1 ? 0 : index;\n }\n\n // Spread mapping: distribute options evenly across MIDI range (default)\n // This provides maximum resolution for hardware controllers\n const norm = this._normalizeReal(realValue);\n return Math.round(norm * this.maxMidi);\n }\n }\n }\n\n /**\n * Convert MIDI Integer to Real Value.\n *\n * This method performs the inverse of `toMidi()`, converting a quantized MIDI integer\n * back to a real-world value. The conversion flow:\n * 1. Normalize the MIDI integer to 0..1\n * 2. Apply inverse scale transformation (if not linear)\n * 3. Denormalize to the real value domain\n * 4. Apply step quantization (if configured)\n *\n * @param midiValue The MIDI integer value (will be clamped to valid range)\n * @returns The real-world value (number, boolean, or string depending on parameter type)\n *\n * @example\n * ```ts\n * const converter = new AudioParameterConverter({\n * type: \"continuous\",\n * min: 0,\n * max: 100,\n * step: 1,\n * midiResolution: 7\n * });\n * converter.fromMidi(64); // 50 (64/127 ≈ 0.5, maps to 50 in 0-100 range)\n * ```\n */\n fromMidi(midiValue: number): number | boolean | string {\n const clampedMidi = Math.max(0, Math.min(this.maxMidi, midiValue));\n\n switch (this.config.type) {\n case \"continuous\": {\n const norm = clampedMidi / this.maxMidi;\n return this._denormalizeReal(norm);\n }\n case \"boolean\": {\n // Threshold at 50%: < 50% is false, >= 50% is true\n const threshold = this.maxMidi / 2;\n return clampedMidi >= threshold;\n }\n case \"discrete\": {\n const conf = this.config as DiscreteParameter;\n const mapping = conf.midiMapping ?? \"spread\";\n\n if (mapping === \"custom\") {\n // Custom mapping: find the option with the closest midiValue to the input\n // This allows non-uniform spacing (e.g., some options closer together)\n let bestOpt = conf.options[0];\n let minDiff = Infinity;\n for (const opt of conf.options) {\n if (opt.midiValue !== undefined) {\n const diff = Math.abs(opt.midiValue - clampedMidi);\n if (diff < minDiff) {\n minDiff = diff;\n bestOpt = opt;\n }\n }\n }\n return bestOpt.value;\n }\n\n if (mapping === \"sequential\") {\n // Sequential mapping: MIDI value directly maps to option index\n const index = clampedMidi;\n if (index >= 0 && index < conf.options.length) {\n return conf.options[index].value;\n }\n // Clamp to last option if out of range\n return conf.options[conf.options.length - 1]?.value ?? 0;\n }\n\n // Spread mapping: distribute MIDI range evenly across options (default)\n // This provides maximum resolution and smooth transitions\n const norm = clampedMidi / this.maxMidi;\n return this._denormalizeReal(norm);\n }\n }\n }\n\n normalize(realValue: number | boolean | string): number {\n const midi = this.toMidi(realValue);\n return midi / this.maxMidi;\n }\n\n denormalize(normalized: number): number | boolean | string {\n const midi = Math.round(normalized * this.maxMidi);\n return this.fromMidi(midi);\n }\n\n /**\n * Format a value for display as a string.\n *\n * This method generates a human-readable string representation of the value,\n * including appropriate units and precision based on the parameter configuration.\n *\n * - Continuous parameters: Includes unit suffix and precision based on step size\n * - Boolean parameters: Uses trueLabel/falseLabel or defaults to \"On\"/\"Off\"\n * - Discrete parameters: Returns the label of the matching option\n *\n * @param value The value to format (number, boolean, or string)\n * @returns Formatted string representation\n *\n * @example\n * ```ts\n * const converter = new AudioParameterConverter({\n * type: \"continuous\",\n * min: 0,\n * max: 100,\n * step: 0.1,\n * unit: \"dB\"\n * });\n * converter.format(50.5); // \"50.5 dB\"\n *\n * const boolConverter = new AudioParameterConverter({\n * type: \"boolean\",\n * trueLabel: \"Enabled\",\n * falseLabel: \"Disabled\"\n * });\n * boolConverter.format(true); // \"Enabled\"\n * ```\n */\n format(value: number | boolean | string): string {\n switch (this.config.type) {\n case \"continuous\": {\n const conf = this.config as ContinuousParameter;\n const val = value as number;\n const precision = conf.step ? Math.max(0, Math.ceil(Math.log10(1 / conf.step))) : 1;\n const fixed = val.toFixed(precision);\n return `${parseFloat(fixed).toString()}${conf.unit ? \" \" + conf.unit : \"\"}`;\n }\n case \"boolean\": {\n const conf = this.config as BooleanParameter;\n return (value ? conf.trueLabel : conf.falseLabel) ?? (value ? \"On\" : \"Off\");\n }\n case \"discrete\": {\n const conf = this.config as DiscreteParameter;\n const opt = conf.options.find((o) => o.value === value);\n return opt?.label ?? String(value);\n }\n }\n }\n\n /**\n * Get the maximum display text for sizing purposes.\n * Returns the longest formatted string among all possible values.\n * Useful for RadialText referenceText prop to ensure consistent sizing.\n *\n * @param options - Optional configuration\n * @param options.includeUnit - Whether to include the unit in the result (default: true).\n * Set to false when displaying value and unit on separate lines.\n * @returns The longest formatted display string\n *\n * @example\n * ```ts\n * const converter = new AudioParameterConverter(volumeParam);\n * // Single line with unit\n * <RadialText text={currentValue} referenceText={converter.getMaxDisplayText()} />\n *\n * // Multiline: value on first line, unit on second\n * <RadialText\n * text={[formattedValue, unit]}\n * referenceText={[converter.getMaxDisplayText({ includeUnit: false }), unit]}\n * />\n * ```\n */\n getMaxDisplayText(options?: { includeUnit?: boolean }): string {\n const includeUnit = options?.includeUnit ?? true;\n\n switch (this.config.type) {\n case \"continuous\": {\n const conf = this.config as ContinuousParameter;\n if (includeUnit) {\n const minStr = this.format(conf.min);\n const maxStr = this.format(conf.max);\n // Return the longer string\n return minStr.length >= maxStr.length ? minStr : maxStr;\n } else {\n // Format without unit\n const precision = conf.step ? Math.max(0, Math.ceil(Math.log10(1 / conf.step))) : 1;\n const minFixed = conf.min.toFixed(precision);\n const maxFixed = conf.max.toFixed(precision);\n const minStr = parseFloat(minFixed).toString();\n const maxStr = parseFloat(maxFixed).toString();\n return minStr.length >= maxStr.length ? minStr : maxStr;\n }\n }\n case \"boolean\": {\n const conf = this.config as BooleanParameter;\n const trueStr = conf.trueLabel ?? \"On\";\n const falseStr = conf.falseLabel ?? \"Off\";\n return trueStr.length >= falseStr.length ? trueStr : falseStr;\n }\n case \"discrete\": {\n const conf = this.config as DiscreteParameter;\n let longest = \"\";\n for (const opt of conf.options) {\n const label = opt.label ?? String(opt.value);\n if (label.length > longest.length) {\n longest = label;\n }\n }\n return longest;\n }\n }\n }\n}\n\n/**\n * Configuration for creating a generic continuous control parameter (for knobs, sliders, etc.).\n */\nexport interface ContinuousControlConfig {\n id?: string;\n name?: string;\n label?: string;\n min?: number;\n max?: number;\n step?: number;\n bipolar?: boolean;\n unit?: string;\n defaultValue?: number;\n scale?: ScaleType;\n midiResolution?: MidiResolution;\n}\n\n/**\n * Factory for common AudioParameter configurations (including MIDI-flavoured presets).\n *\n * This factory provides convenient methods for creating standard parameter configurations\n * that match common MIDI and audio industry conventions. All factory methods generate\n * parameter IDs automatically from the name.\n */\nexport const AudioParameterFactory = {\n /**\n * Creates a standard 7-bit MIDI CC parameter (0-127).\n *\n * This is the most common MIDI control change format, used for most hardware controllers\n * and software synthesizers. The parameter uses 7-bit resolution (128 steps).\n *\n * @param name The parameter name (used to generate ID and name fields)\n * @returns A ContinuousParameter configured for 7-bit MIDI CC\n *\n * @example\n * ```ts\n * const volumeParam = AudioParameterFactory.createMidiStandard7Bit(\"Volume\");\n * // { type: \"continuous\", min: 0, max: 127, step: 1, midiResolution: 7, ... }\n * ```\n */\n createMidiStandard7Bit: (name: string): ContinuousParameter => ({\n id: `cc-${name.toLowerCase().replace(/\\s+/g, \"-\")}`,\n name,\n type: \"continuous\",\n min: 0,\n max: 127,\n step: 1,\n midiResolution: 7,\n unit: \"\",\n }),\n\n /**\n * Creates a standard 14-bit MIDI CC parameter (0-16383).\n *\n * This provides higher resolution than 7-bit CC, useful for parameters that require\n * fine-grained control. The parameter uses 14-bit resolution (16,384 steps).\n *\n * @param name The parameter name (used to generate ID and name fields)\n * @returns A ContinuousParameter configured for 14-bit MIDI CC\n *\n * @example\n * ```ts\n * const fineTuneParam = AudioParameterFactory.createMidiStandard14Bit(\"Fine Tune\");\n * // { type: \"continuous\", min: 0, max: 16383, step: 1, midiResolution: 14, ... }\n * ```\n */\n createMidiStandard14Bit: (name: string): ContinuousParameter => ({\n id: `cc14-${name.toLowerCase().replace(/\\s+/g, \"-\")}`,\n name,\n type: \"continuous\",\n min: 0,\n max: 16383,\n step: 1,\n midiResolution: 14,\n unit: \"\",\n }),\n\n /**\n * Creates a bipolar 7-bit MIDI CC parameter (-64 to 63, centered at 0).\n *\n * This is useful for parameters that have a center point, such as pan controls or\n * modulation depth. The range is symmetric around zero, with 64 steps in each direction.\n *\n * @param name The parameter name (used to generate ID and name fields)\n * @returns A ContinuousParameter configured for bipolar 7-bit MIDI CC\n *\n * @example\n * ```ts\n * const panParam = AudioParameterFactory.createMidiBipolar7Bit(\"Pan\");\n * // { type: \"continuous\", min: -64, max: 63, step: 1, defaultValue: 0, ... }\n * ```\n */\n createMidiBipolar7Bit: (name: string): ContinuousParameter => ({\n id: `cc-bipolar-${name.toLowerCase().replace(/\\s+/g, \"-\")}`,\n name,\n type: \"continuous\",\n min: -64,\n max: 63,\n step: 1,\n midiResolution: 7,\n unit: \"\",\n defaultValue: 0,\n bipolar: true,\n }),\n\n /**\n * Creates a bipolar 14-bit MIDI CC parameter (-8192 to 8191, centered at 0).\n *\n * This provides high-resolution bipolar control, useful for parameters that require\n * fine-grained adjustment around a center point. The range is symmetric around zero,\n * with 8,192 steps in each direction.\n *\n * @param name The parameter name (used to generate ID and name fields)\n * @returns A ContinuousParameter configured for bipolar 14-bit MIDI CC\n *\n * @example\n * ```ts\n * const finePanParam = AudioParameterFactory.createMidiBipolar14Bit(\"Fine Pan\");\n * // { type: \"continuous\", min: -8192, max: 8191, step: 1, defaultValue: 0, ... }\n * ```\n */\n createMidiBipolar14Bit: (name: string): ContinuousParameter => ({\n id: `cc14-bipolar-${name.toLowerCase().replace(/\\s+/g, \"-\")}`,\n name,\n type: \"continuous\",\n min: -8192,\n max: 8191,\n step: 1,\n midiResolution: 14,\n unit: \"\",\n defaultValue: 0,\n bipolar: true,\n }),\n\n /**\n * Creates a bipolar parameter with custom range (centered at 0).\n *\n * This is a flexible factory method for creating bipolar parameters with any range.\n * The parameter is symmetric around zero, useful for pan controls, modulation depth,\n * or any parameter that has a neutral center point.\n *\n * @param name The parameter name (used to generate ID and name fields)\n * @param range The range value (default: 100). The parameter will span from -range to +range\n * @param unit Optional unit suffix (e.g., \"dB\", \"%\")\n * @returns A ContinuousParameter configured for bipolar control\n *\n * @example\n * ```ts\n * const panParam = AudioParameterFactory.createBipolar(\"Pan\", 100, \"%\");\n * // { type: \"continuous\", min: -100, max: 100, step: 1, defaultValue: 0, unit: \"%\", ... }\n *\n * const modDepthParam = AudioParameterFactory.createBipolar(\"Mod Depth\", 50);\n * // { type: \"continuous\", min: -50, max: 50, step: 1, defaultValue: 0, ... }\n * ```\n */\n createBipolar: (name: string, range: number = 100, unit: string = \"\"): ContinuousParameter => ({\n id: `bipolar-${name.toLowerCase().replace(/\\s+/g, \"-\")}`,\n name,\n type: \"continuous\",\n min: -range,\n max: range,\n step: 1,\n unit,\n defaultValue: 0,\n bipolar: true,\n }),\n\n /**\n * Creates a boolean switch parameter (Off/On).\n *\n * This factory method creates a boolean parameter suitable for on/off controls,\n * with support for both toggle (latch) and momentary modes.\n *\n * @param name The parameter name (used to generate ID and name fields)\n * @param mode The switch mode: \"toggle\" (latch) or \"momentary\" (only active while pressed)\n * @returns A BooleanParameter configured as a switch\n *\n * @example\n * ```ts\n * const powerParam = AudioParameterFactory.createSwitch(\"Power\", \"toggle\");\n * // { type: \"boolean\", mode: \"toggle\", trueLabel: \"On\", falseLabel: \"Off\", ... }\n *\n * const recordParam = AudioParameterFactory.createSwitch(\"Record\", \"momentary\");\n * // { type: \"boolean\", mode: \"momentary\", ... }\n * ```\n */\n createSwitch: (name: string, mode: \"toggle\" | \"momentary\" = \"toggle\"): BooleanParameter => ({\n id: `sw-${name.toLowerCase().replace(/\\s+/g, \"-\")}`,\n name,\n type: \"boolean\",\n mode,\n defaultValue: false,\n trueLabel: \"On\",\n falseLabel: \"Off\",\n midiResolution: 7,\n }),\n\n /**\n * Creates a discrete (selector) parameter.\n *\n * This factory method creates a discrete parameter suitable for mode selectors,\n * preset switches, or any control that cycles through discrete options.\n *\n * @param name The parameter name (used to generate ID and name fields)\n * @param options Array of option objects, each with a value and label\n * @returns A DiscreteParameter configured as a selector\n *\n * @example\n * ```ts\n * const waveParam = AudioParameterFactory.createSelector(\"Waveform\", [\n * { value: \"sine\", label: \"Sine\" },\n * { value: \"square\", label: \"Square\" },\n * { value: \"sawtooth\", label: \"Sawtooth\" }\n * ]);\n * ```\n */\n createSelector: (name: string, options: Array<{ value: string | number; label: string }>): DiscreteParameter => ({\n id: `sel-${name.toLowerCase().replace(/\\s+/g, \"-\")}`,\n name,\n type: \"discrete\",\n options,\n defaultValue: options[0]?.value,\n midiResolution: 7,\n midiMapping: \"spread\",\n }),\n\n /**\n * Creates a generic continuous control parameter (for ad-hoc UI controls like Knob/Slider).\n *\n * This is the most flexible factory method, allowing you to specify all aspects of a\n * continuous parameter. It's useful when you need a parameter that doesn't fit the\n * standard MIDI presets.\n *\n * The method handles bipolar mode automatically: if `bipolar` is true, it adjusts\n * min/max to be symmetric around zero when only one boundary is provided. For default values:\n * - If `defaultValue` is provided, it is used (even when `bipolar=true`)\n * - If `defaultValue` is not provided and `bipolar=true`, the center of the range is calculated\n * (0 for symmetric ranges, otherwise the midpoint)\n * - If `defaultValue` is not provided and `bipolar=false`, falls back to `min` or 0\n *\n * @param config Configuration object with optional fields for all parameter properties\n * @returns A ContinuousParameter configured according to the provided config\n *\n * @example\n * ```ts\n * const volumeParam = AudioParameterFactory.createControl({\n * name: \"Volume\",\n * min: 0,\n * max: 100,\n * step: 0.1,\n * unit: \"dB\",\n * scale: \"log\"\n * });\n *\n * const panParam = AudioParameterFactory.createControl({\n * name: \"Pan\",\n * bipolar: true,\n * unit: \"%\"\n * });\n * // Automatically sets min: -100, max: 100, defaultValue: 0\n *\n * const customBipolarParam = AudioParameterFactory.createControl({\n * name: \"Custom\",\n * min: 0,\n * max: 127,\n * bipolar: true,\n * defaultValue: 64\n * });\n * // Uses provided defaultValue: 64 (center of 0-127 range)\n * ```\n */\n createControl: (config: ContinuousControlConfig): ContinuousParameter => {\n const { id, name, label, min, max, step, bipolar, unit = \"\", defaultValue, scale, midiResolution } = config;\n\n let effectiveMin = min;\n let effectiveMax = max;\n let effectiveDefault = defaultValue;\n\n // Handle bipolar mode: ensure symmetric range around zero\n if (bipolar) {\n if (min === undefined && max === undefined) {\n // No range specified: default to -100 to 100\n effectiveMin = -100;\n effectiveMax = 100;\n } else if (min === undefined && max !== undefined) {\n // Only max specified: make symmetric (e.g., max=100 -> min=-100)\n effectiveMin = -max!;\n } else if (min !== undefined && max === undefined) {\n // Only min specified: make symmetric (e.g., min=-100 -> max=100)\n effectiveMax = -min!;\n }\n // Bipolar parameters default to center: use provided defaultValue if given,\n // otherwise calculate center of the range (which may not be 0 if range isn't symmetric)\n if (effectiveDefault === undefined) {\n const finalMin = effectiveMin ?? 0;\n const finalMax = effectiveMax ?? 100;\n // If range is symmetric around zero, default to 0; otherwise use center of range\n if (finalMin === -finalMax) {\n effectiveDefault = 0;\n } else {\n effectiveDefault = (finalMin + finalMax) / 2;\n }\n }\n } else {\n // Unipolar: use provided defaultValue or fall back to min or 0\n effectiveDefault = effectiveDefault ?? min ?? 0;\n }\n\n return {\n id: id ?? \"adhoc-control\",\n type: \"continuous\",\n name: name ?? label ?? \"\",\n min: effectiveMin ?? 0,\n max: effectiveMax ?? 100,\n step,\n unit,\n defaultValue: effectiveDefault,\n scale,\n midiResolution,\n bipolar: bipolar ?? false,\n };\n },\n};\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n/**\n * Shared SVG helper utilities for audio-ui components\n */\n\n/**\n * Convert polar coordinates to Cartesian coordinates.\n *\n * This function is used extensively in circular controls (knobs, rotary switches) to position\n * elements around a circle. The angle system follows SVG conventions where 0° is at 3 o'clock\n * and angles increase clockwise.\n *\n * @param centerX - X coordinate of the center point\n * @param centerY - Y coordinate of the center point\n * @param radius - Radius from center\n * @param angleInDegrees - Angle in degrees (0° = 3 o'clock, increasing clockwise)\n * @returns Object with x and y Cartesian coordinates\n *\n * @example\n * ```ts\n * // Position at 12 o'clock (top center)\n * const { x, y } = polarToCartesian(50, 50, 30, 360);\n * // x ≈ 50, y ≈ 20\n * ```\n */\nexport const polarToCartesian = (\n centerX: number,\n centerY: number,\n radius: number,\n angleInDegrees: number\n): { x: number; y: number } => {\n const angleInRadians = ((angleInDegrees - 90) * Math.PI) / 180.0;\n return {\n x: centerX + radius * Math.cos(angleInRadians),\n y: centerY + radius * Math.sin(angleInRadians),\n };\n};\n\n/**\n * Result of arc angle calculations\n */\nexport interface ArcAngleResult {\n /** Normalized value (0-1, clamped) */\n normalizedValue: number;\n /** Openness in degrees (0-360, clamped) */\n openness: number;\n /** Start angle of the arc range in degrees (offset by rotation) */\n startAngle: number;\n /** End angle of the arc range in degrees (offset by rotation) */\n endAngle: number;\n /** Current angle in degrees based on normalized value (offset by rotation) */\n valueToAngle: number;\n /** Start angle for the value arc (foreground). Handles bipolar logic (starts at center) vs unipolar (starts at range start). */\n valueStartAngle: number;\n}\n\n/**\n * Calculates arc angles for rotary controls (ValueRing, RotaryImage components, or any other circular control).\n *\n * Calculates the angular range based on openness and converts a normalized value (0-1)\n * to an angle within that range.\n *\n * The angle system:\n * - 0 degrees is at 3 o'clock, increasing clockwise\n * - Standard knob (90° openness) goes from ~225° (7:30) to ~495° (4:30)\n * - 360° corresponds to UP (12 o'clock)\n *\n * @param normalizedValue Normalized value between 0 and 1\n * @param openness Openness of the arc in degrees (0-360, default 90)\n * @param rotation Rotation angle offset in degrees (default 0)\n * @param bipolar Whether to start the value arc from the center (12 o'clock) instead of the start angle (default false)\n * @param positions Optional number of discrete positions. When defined, the value will snap to the nearest position. Defaults to undefined (continuous mode).\n * @returns Calculated angles and normalized values\n *\n * @example\n * ```ts\n * // Standard knob at 50% value\n * const result = calculateArcAngles(0.5, 90);\n * // result.valueToAngle = 360 (12 o'clock)\n * // result.startAngle = 225 (7:30)\n * // result.endAngle = 495 (4:30)\n *\n * // Bipolar knob (pan control) at center\n * const bipolar = calculateArcAngles(0.5, 90, 0, true);\n * // bipolar.valueStartAngle = 360 (starts from center)\n *\n * // Discrete positions (5-way switch)\n * const discrete = calculateArcAngles(0.37, 90, 0, false, 5);\n * // discrete.normalizedValue = 0.25 (snapped to nearest position)\n * ```\n */\nexport function calculateArcAngles(\n normalizedValue: number,\n openness: number = 90,\n rotation: number = 0,\n bipolar: boolean = false,\n positions?: number\n): ArcAngleResult {\n const clampedValue = Math.max(0, Math.min(1, normalizedValue));\n\n let snappedValue = clampedValue;\n if (positions !== undefined && positions >= 1) {\n if (positions === 1) {\n snappedValue = 0.5;\n } else {\n // For N positions: positions 0 to N-1, position i maps to normalizedValue = i / (N - 1)\n const position = Math.round(clampedValue * (positions - 1));\n snappedValue = position / (positions - 1);\n }\n }\n\n const clampedOpenness = Math.max(0, Math.min(360, openness));\n\n // Calculate angular range: 0° is at 3 o'clock, increasing clockwise\n // Standard knob (90° openness) goes from ~225° (7:30) to ~495° (4:30)\n const start = 180 + clampedOpenness / 2;\n const end = 540 - clampedOpenness / 2;\n const maxStartAngle = start;\n const maxEndAngle = end;\n const maxArcAngle = end - start;\n\n const baseValueToAngle = snappedValue * maxArcAngle + maxStartAngle;\n\n const startAngle = maxStartAngle - rotation;\n const endAngle = maxEndAngle - rotation;\n\n // For bipolar: start at top (360°) minus rotation. For unipolar: start at min angle\n let valueStartAngle = startAngle;\n if (bipolar) {\n valueStartAngle = 360 - rotation;\n }\n\n const valueToAngle = baseValueToAngle - rotation;\n\n return {\n normalizedValue: snappedValue,\n openness: clampedOpenness,\n startAngle,\n endAngle,\n valueToAngle,\n valueStartAngle,\n };\n}\n\n/**\n * Calculates the cursor Y position for linear controls (sliders, faders).\n *\n * The cursor represents the current value position along a linear strip.\n * The strip extends along the Y-axis (in unrotated coordinate space) from\n * (cy - length/2) to (cy + length/2).\n *\n * The cursor position is independent of bipolar mode - it always maps:\n * - value 0 = bottom (cy + length/2)\n * - value 1 = top (cy - length/2)\n *\n * Bipolar mode only affects how the rectangle is drawn (from center vs from bottom),\n * not the cursor position itself.\n *\n * @param cy Y coordinate of the strip center point\n * @param length Length of the strip\n * @param normalizedValue Normalized value between 0 and 1\n * @returns Y coordinate of the cursor center\n *\n * @example\n * ```ts\n * // Cursor moves from bottom to top based on value\n * const cursorY = calculateLinearPosition(150, 260, 0.65);\n * // cursorY = 31 (65% from bottom to top)\n * ```\n */\nexport function calculateLinearPosition(cy: number, length: number, normalizedValue: number): number {\n // Clamp normalized value to valid range [0, 1]\n const clampedValue = Math.max(0, Math.min(1, normalizedValue));\n\n // Cursor position: value 0 maps to bottom (cy + length/2), value 1 maps to top (cy - length/2)\n // Interpolate from bottom to top\n const bottomY = cy + length / 2;\n return bottomY - clampedValue * length;\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport { polarToCartesian } from \"./math\";\n\n/**\n * Calculate SVG arc path for circular controls (like knobs).\n *\n * This function generates the SVG path data for drawing arcs, which is essential for rendering\n * circular controls. The path is rounded to avoid hydration mismatches between server and client.\n *\n * The angle system follows SVG conventions:\n * - 0° is at 3 o'clock (right)\n * - Angles increase clockwise\n * - 360° is at 12 o'clock (top)\n *\n * @param cx - X coordinate of the center point\n * @param cy - Y coordinate of the center point\n * @param startAngle - Starting angle in degrees\n * @param endAngle - Ending angle in degrees\n * @param radius - Radius of the arc\n * @param direction - Direction to draw the arc. \"counter-clockwise\" (default) draws from End to Start (standard for static shapes). \"clockwise\" draws from Start to End (useful for path animations).\n * @returns SVG path string for the arc (e.g., \"M 10 20 A 30 30 0 0 1 40 50\")\n *\n * @example\n * ```ts\n * // Draw a 90-degree arc from 7:30 to 4:30 (standard knob range)\n * const path = calculateArcPath(50, 50, 225, 495, 30);\n * ```\n */\nexport const calculateArcPath = (\n cx: number,\n cy: number,\n startAngle: number,\n endAngle: number,\n radius: number,\n direction: \"clockwise\" | \"counter-clockwise\" = \"counter-clockwise\"\n): string => {\n if (startAngle > endAngle) {\n [startAngle, endAngle] = [endAngle, startAngle];\n }\n\n // Round coordinates to avoid hydration mismatches\n const r = (n: number) => Math.round(n * 10000) / 10000;\n\n const start = polarToCartesian(cx, cy, radius, startAngle);\n const end = polarToCartesian(cx, cy, radius, endAngle);\n const largeArcFlag = endAngle - startAngle <= 180 ? \"0\" : \"1\";\n\n // Counter-clockwise: End -> Start (Sweep 0). Clockwise: Start -> End (Sweep 1)\n const [p1, p2, sweepFlag] = direction === \"counter-clockwise\" ? [end, start, 0] : [start, end, 1];\n\n return [\"M\", r(p1.x), r(p1.y), \"A\", r(radius), r(radius), 0, largeArcFlag, sweepFlag, r(p2.x), r(p2.y)].join(\" \");\n};\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport { SizeType } from \"../types\";\n\n/**\n * Maps size values to CSS class names for square components (Button, Knob, CycleButton)\n */\nexport const squareSizeClassMap: Record<SizeType, string> = {\n xsmall: \"audioui-size-square-xsmall\",\n small: \"audioui-size-square-small\",\n normal: \"audioui-size-square-normal\",\n large: \"audioui-size-square-large\",\n xlarge: \"audioui-size-square-xlarge\",\n};\n\n/**\n * Maps size values to CSS class names for horizontal slider\n */\nexport const horizontalSliderSizeClassMap: Record<SizeType, string> = {\n xsmall: \"audioui-size-hslider-xsmall\",\n small: \"audioui-size-hslider-small\",\n normal: \"audioui-size-hslider-normal\",\n large: \"audioui-size-hslider-large\",\n xlarge: \"audioui-size-hslider-xlarge\",\n};\n\n/**\n * Maps size values to CSS class names for vertical slider\n */\nexport const verticalSliderSizeClassMap: Record<SizeType, string> = {\n xsmall: \"audioui-size-vslider-xsmall\",\n small: \"audioui-size-vslider-small\",\n normal: \"audioui-size-vslider-normal\",\n large: \"audioui-size-vslider-large\",\n xlarge: \"audioui-size-vslider-xlarge\",\n};\n\n/**\n * Maps size values to CSS class names for keys\n */\nexport const keysSizeClassMap: Record<SizeType, string> = {\n xsmall: \"audioui-size-keys-xsmall\",\n small: \"audioui-size-keys-small\",\n normal: \"audioui-size-keys-normal\",\n large: \"audioui-size-keys-large\",\n xlarge: \"audioui-size-keys-xlarge\",\n};\n\n/**\n * Gets the appropriate size class name for a component\n * @param componentType The type of component ('knob', 'button', 'keys', or 'slider')\n * @param size The size value\n * @param orientation The orientation for slider components ('vertical' or 'horizontal')\n * @returns The CSS class name for the component size\n */\nexport function getSizeClassForComponent(\n componentType: \"knob\" | \"button\" | \"keys\" | \"slider\",\n size: SizeType = \"normal\",\n orientation: \"vertical\" | \"horizontal\" = \"vertical\"\n): string {\n switch (componentType) {\n case \"knob\":\n case \"button\":\n return squareSizeClassMap[size];\n case \"keys\":\n return keysSizeClassMap[size];\n case \"slider\":\n return orientation === \"horizontal\" ? horizontalSliderSizeClassMap[size] : verticalSliderSizeClassMap[size];\n default:\n throw new Error(`Unknown component type: ${componentType}`);\n }\n}\n\n/**\n * Gets the CSS variable references for a component's size dimensions.\n * Used to apply size as inline styles (which override AdaptiveBox's default 100%).\n * @param componentType The type of component ('knob', 'button', 'keys', or 'slider')\n * @param size The size value\n * @param orientation The orientation for slider components ('vertical' or 'horizontal')\n * @returns An object with width and height CSS variable references\n */\nexport function getSizeStyleForComponent(\n componentType: \"knob\" | \"button\" | \"keys\" | \"slider\",\n size: SizeType = \"normal\",\n orientation: \"vertical\" | \"horizontal\" = \"vertical\"\n): { width: string; height: string } {\n switch (componentType) {\n case \"knob\":\n case \"button\":\n return {\n width: `var(--audioui-size-square-${size})`,\n height: `var(--audioui-size-square-${size})`,\n };\n case \"keys\":\n return {\n width: `var(--audioui-size-keys-width-${size})`,\n height: `var(--audioui-size-keys-height-${size})`,\n };\n case \"slider\":\n if (orientation === \"horizontal\") {\n return {\n width: `var(--audioui-size-hslider-width-${size})`,\n height: `var(--audioui-size-hslider-height-${size})`,\n };\n } else {\n return {\n width: `var(--audioui-size-vslider-width-${size})`,\n height: `var(--audioui-size-vslider-height-${size})`,\n };\n }\n default:\n throw new Error(`Unknown component type: ${componentType}`);\n }\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport { CSS_VARS } from \"../constants/cssVars\";\n\n/**\n * Utility functions for color manipulation\n *\n * This module provides simplified color manipulation functions that leverage\n * modern CSS capabilities instead of reimplementing color parsing logic.\n */\n\n/**\n * Named colors mapping to their HSL values.\n * Keys are lowercase for O(1) lookup performance.\n */\nconst namedColors: Record<string, string> = {\n blue: \"hsl(204, 88%, 53%)\",\n orange: \"hsl(29, 100%, 50%)\",\n pink: \"hsl(332, 95%, 54%)\",\n green: \"hsl(160, 98%, 37%)\",\n purple: \"hsl(252, 100%, 67%)\",\n yellow: \"hsl(50, 100%, 50%)\",\n white: \"hsl(0, 0%, 100%)\",\n};\n\nconst HSL_REGEX = /hsl\\((\\d+),\\s*(\\d+)%,\\s*(\\d+)%\\)/;\n\n/**\n * Detects if the current document is in dark mode.\n *\n * Checks both the document's class list (for `.dark` class) and the system preference\n * via `prefers-color-scheme` media query. This ensures compatibility with frameworks\n * like Tailwind CSS that use class-based dark mode.\n *\n * @returns true if dark mode is active, false otherwise\n */\nexport function isDarkMode(): boolean {\n if (typeof window === \"undefined\") return false;\n return (\n document.documentElement.classList.contains(\"dark\") || window.matchMedia(\"(prefers-color-scheme: dark)\").matches\n );\n}\n\n/**\n * Gets the adaptive default color (white in dark mode, black in light mode).\n *\n * Uses a CSS variable to ensure SSR and client render the same value, avoiding hydration mismatches.\n * The actual color value is resolved by the browser based on the `.dark` class or system preference.\n *\n * @returns A CSS color value that adapts to the current color scheme\n */\nexport function getAdaptiveDefaultColor(): string {\n return `var(${CSS_VARS.adaptiveDefaultColor})`;\n}\n\n/**\n * Generates a luminosity-based variant of a color.\n *\n * For named colors (blue, orange, pink, etc.), this function uses pre-computed HSL values\n * for precise control. For other colors, it uses CSS `color-mix()` with black to darken.\n *\n * This is useful for creating visual hierarchies (e.g., primary50, primary20 variants)\n * where you want progressively darker versions of a color.\n *\n * @param baseColor The base color (any valid CSS color value)\n * @param luminosityPercentage The percentage of the original luminosity (0-100)\n * @returns A CSS color value with adjusted luminosity\n *\n * @example\n * ```ts\n * generateLuminosityVariant(\"blue\", 50); // 50% darker blue\n * generateLuminosityVariant(\"#ff0000\", 20); // Much darker red\n * ```\n */\nexport function generateLuminosityVariant(baseColor: string, luminosityPercentage: number): string {\n const namedColor = namedColors[baseColor.toLowerCase()];\n if (namedColor) {\n const hslMatch = namedColor.match(HSL_REGEX);\n if (hslMatch) {\n const h = hslMatch[1];\n const s = hslMatch[2];\n const l = parseInt(hslMatch[3], 10);\n const newL = Math.max(0, Math.min(100, l * (luminosityPercentage / 100)));\n return `hsl(${h}, ${s}%, ${newL}%)`;\n }\n }\n\n // Use color-mix with black to darken (works for any valid CSS color)\n const darkening = 100 - luminosityPercentage;\n return `color-mix(in srgb, ${baseColor}, black ${darkening}%)`;\n}\n\n/**\n * Generates a transparency-based variant of a color.\n *\n * Uses CSS `color-mix()` to blend the color with transparent, creating a semi-transparent\n * version. This is useful for hover states, disabled states, or layered visual effects.\n *\n * @param baseColor The base color (any valid CSS color value)\n * @param opacityPercentage The opacity percentage (0-100, where 100 is fully opaque)\n * @returns A CSS color value with adjusted opacity\n *\n * @example\n * ```ts\n * generateTransparencyVariant(\"blue\", 50); // 50% transparent blue\n * ```\n */\nexport function generateTransparencyVariant(baseColor: string, opacityPercentage: number): string {\n return `color-mix(in srgb, ${baseColor} ${opacityPercentage}%, transparent)`;\n}\n\n/**\n * Generates a highlight color variant suitable for drop-shadow and glow effects.\n *\n * For HSL colors, this increases both saturation and lightness to create a brighter,\n * more vibrant version. For other color formats, it blends with white.\n *\n * This is typically used for focus states, active states, or visual feedback effects.\n *\n * @param baseColor The base color (any valid CSS color value)\n * @returns A CSS color value optimized for highlight effects\n *\n * @example\n * ```ts\n * generateHighlightColor(\"blue\"); // Brighter, more saturated blue for highlights\n * ```\n */\nexport function generateHighlightColor(baseColor: string): string {\n const namedColor = namedColors[baseColor.toLowerCase()];\n if (namedColor) {\n baseColor = namedColor;\n }\n\n // For highlight effects: slightly brighter and more saturated\n const hslMatch = baseColor.match(HSL_REGEX);\n if (hslMatch) {\n const h = parseInt(hslMatch[1], 10);\n const s = Math.min(100, parseInt(hslMatch[2], 10) + 10); // Increase saturation\n const l = Math.min(70, parseInt(hslMatch[3], 10) + 10); // Increase lightness but cap at 70%\n return `hsl(${h}, ${s}%, ${l}%)`;\n }\n\n return `color-mix(in srgb, ${baseColor} 80%, white 20%)`;\n}\n\n/**\n * Generates a complete set of color variants for a component.\n *\n * This is the main utility for creating theme color palettes. It generates:\n * - `primary`: The base color\n * - `primary50`: A 50% variant (luminosity or transparency based on `variant` parameter)\n * - `primary20`: A 20% variant (luminosity or transparency based on `variant` parameter)\n * - `highlight`: A brighter variant optimized for focus/active states\n *\n * The `variant` parameter determines how `primary50` and `primary20` are calculated:\n * - `\"luminosity\"`: Darker versions of the base color (good for backgrounds, tracks)\n * - `\"transparency\"`: Semi-transparent versions (good for overlays, disabled states)\n *\n * @param baseColor The base color (any valid CSS color value)\n * @param variant The variant type ('luminosity' or 'transparency')\n * @returns An object with primary, primary50, primary20, and highlight color values\n *\n * @example\n * ```ts\n * const colors = generateColorVariants(\"blue\", \"luminosity\");\n * // { primary: \"hsl(204, 88%, 53%)\", primary50: \"...\", primary20: \"...\", highlight: \"...\" }\n * ```\n */\nexport function generateColorVariants(\n baseColor: string,\n variant: \"luminosity\" | \"transparency\" = \"luminosity\"\n): {\n primary: string;\n primary50: string;\n primary20: string;\n highlight: string;\n} {\n const namedColor = namedColors[baseColor.toLowerCase()];\n const normalizedColor = namedColor ?? baseColor;\n\n const highlight = generateHighlightColor(normalizedColor);\n\n if (variant === \"luminosity\") {\n return {\n primary: normalizedColor,\n primary50: generateLuminosityVariant(normalizedColor, 50),\n primary20: generateLuminosityVariant(normalizedColor, 20),\n highlight,\n };\n } else {\n return {\n primary: normalizedColor,\n primary50: generateTransparencyVariant(normalizedColor, 50),\n primary20: generateTransparencyVariant(normalizedColor, 20),\n highlight,\n };\n }\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n/**\n * Utility functions for translating normalized (0.0-1.0) roundness and thickness values\n * to component-specific legacy ranges.\n *\n * All components accept normalized values (0.0-1.0) and translate them internally\n * to their legacy ranges for rendering.\n */\n\n/**\n * Clamp a value to the 0.0-1.0 range\n */\nexport function clampNormalized(value: number): number {\n return Math.max(0.0, Math.min(1.0, value));\n}\n\n/**\n * Translate normalized roundness (0.0-1.0) to KnobView legacy value.\n * 0.0 = square (legacy: 0), >0.0 = round (legacy: 1+)\n */\nexport function translateKnobRoundness(normalized: number): number {\n const clamped = clampNormalized(normalized);\n return clamped === 0 ? 0 : 1;\n}\n\nexport function translateKnobThickness(normalized: number): number {\n const clamped = clampNormalized(normalized);\n return Math.round(1 + clamped * 19);\n}\n\nexport function translateSliderRoundness(normalized: number): number {\n const clamped = clampNormalized(normalized);\n return Math.round(clamped * 20);\n}\n\nexport function translateSliderThickness(normalized: number): number {\n const clamped = clampNormalized(normalized);\n return Math.round(1 + clamped * 49);\n}\n\nexport function translateButtonRoundness(normalized: number): number {\n const clamped = clampNormalized(normalized);\n return Math.round(clamped * 50);\n}\n\nexport function translateKeysRoundness(normalized: number): number {\n const clamped = clampNormalized(normalized);\n return Math.round(clamped * 12);\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n/**\n * Utility functions for working with musical notes and MIDI note numbers\n *\n * Terminology:\n * - Note: A pitch with octave (e.g., \"C4\", \"F#5\")\n * - NoteName: The actual note without octave (e.g., \"C\", \"F#\")\n * - NoteNum: The MIDI note number (e.g., 60 for C4)\n */\n\n/**\n * Array of note names in chromatic order (C to B with sharps)\n */\nexport const CHROMATIC_NOTES = [\"C\", \"C#\", \"D\", \"D#\", \"E\", \"F\", \"F#\", \"G\", \"G#\", \"A\", \"A#\", \"B\"] as const;\n\n/**\n * Array of white key note names in order (C to B)\n */\nexport const WHITE_KEY_NAMES = [\"C\", \"D\", \"E\", \"F\", \"G\", \"A\", \"B\"] as const;\n\n/**\n * Mapping from white key index to chromatic offset\n * This represents the semitone positions of white keys in a standard piano layout\n * C=0, D=2, E=4, F=5, G=7, A=9, B=11\n */\nexport const WHITE_KEY_TO_CHROMATIC = [0, 2, 4, 5, 7, 9, 11] as const;\n\n/**\n * Mapping from diatonic note names to chromatic indices\n */\nexport const DIATONIC_TO_CHROMATIC: Record<string, number> = {\n C: 0,\n D: 2,\n E: 4,\n F: 5,\n G: 7,\n A: 9,\n B: 11,\n};\n\n/**\n * Set of chromatic positions that correspond to white keys (C, D, E, F, G, A, B)\n * Used to determine if a note is a white key or black key\n */\nexport const WHITE_KEY_POSITIONS = new Set([0, 2, 4, 5, 7, 9, 11]);\n\n/**\n * Lookup maps for fast note conversion\n * NUM_TO_NOTE_MAP: Maps MIDI note numbers to note strings\n * NOTE_TO_NUM_MAP: Maps note strings to MIDI note numbers\n */\nconst NUM_TO_NOTE_MAP: string[] = [];\nconst NOTE_TO_NUM_MAP = new Map<string, number>();\n\n// Pre-populate lookup tables for all 128 MIDI notes (0-127)\nfor (let noteNum = 0; noteNum < 128; noteNum++) {\n const octave = Math.floor(noteNum / 12) - 1; // MIDI note 60 is C4\n const noteIndex = noteNum % 12;\n const noteName = CHROMATIC_NOTES[noteIndex];\n const note = `${noteName}${octave}`;\n\n // Store in lookup tables\n NUM_TO_NOTE_MAP[noteNum] = note;\n NOTE_TO_NUM_MAP.set(note, noteNum);\n}\n\n/**\n * Converts a MIDI note number to a note string using a pre-computed lookup table\n * MIDI note 60 is middle C (C4)\n *\n * @param noteNum - The MIDI note number to convert\n * @returns The corresponding note (e.g., \"C4\", \"F#5\")\n */\nexport const noteNumToNote = (noteNum: number): string => {\n // Check if the note number is within valid MIDI range (0-127)\n if (noteNum >= 0 && noteNum < 128) {\n return NUM_TO_NOTE_MAP[noteNum];\n }\n\n // Fallback to calculation for out-of-range values\n const octave = Math.floor(noteNum / 12) - 1;\n const noteIndex = noteNum % 12;\n const noteName = CHROMATIC_NOTES[noteIndex];\n return `${noteName}${octave}`;\n};\n\n/**\n * Converts a note string to a MIDI note number using a pre-computed lookup table\n *\n * @param note - The note to convert (e.g., \"C4\", \"F#5\")\n * @returns The corresponding MIDI note number, or -1 if the note is invalid\n */\nexport const noteToNoteNum = (note: string): number => {\n // Check if the note is in the lookup table\n const noteNum = NOTE_TO_NUM_MAP.get(note);\n if (noteNum !== undefined) {\n return noteNum;\n }\n\n // Fallback to calculation for notes not in the lookup table\n const match = note.match(/^([A-G](#)?)(-?\\d+)$/);\n if (!match) return -1;\n\n const [, noteName, , octave] = match;\n\n // Find the index of the noteName in the chromatic scale\n const noteIndex = CHROMATIC_NOTES.findIndex((n) => n === noteName);\n if (noteIndex === -1) return -1;\n\n // Calculate MIDI note number: (octave + 1) * 12 + noteIndex\n // MIDI note 60 is C4, so C4 = (4 + 1) * 12 + 0 = 60\n return (parseInt(octave) + 1) * 12 + noteIndex;\n};\n\n/**\n * Creates a Set of MIDI note numbers from an array of notes (strings and/or numbers)\n * This can be used for efficient note lookups, especially when memoized in components\n *\n * @param notesOn - Array of notes that are on (can contain string notes and/or MIDI note numbers)\n * @returns A Set of MIDI note numbers\n */\nexport const createNoteNumSet = (notesOn: (string | number)[]): Set<number> => {\n const noteNumSet = new Set<number>();\n\n for (const note of notesOn) {\n if (typeof note === \"number\") {\n // If the note is already a number, add it directly\n noteNumSet.add(note);\n } else {\n // If the note is a string, convert it to a number and add it\n const noteNum = noteToNoteNum(note);\n if (noteNum !== -1) {\n noteNumSet.add(noteNum);\n }\n }\n }\n\n return noteNumSet;\n};\n\n/**\n * Checks if a note is in the notesOn array, handling both string notes and MIDI note numbers\n * Uses a Set for O(1) lookups instead of array includes\n *\n * @param noteInput - The note to check (either a string note like \"C4\" or a MIDI note number)\n * @param notesOn - Array of notes that are on (can contain string notes and/or MIDI note numbers)\n * @returns True if the note is in the notesOn array, false otherwise\n */\nexport const isNoteOn = (noteInput: string | number, notesOn: (string | number)[]): boolean => {\n if (notesOn.length === 0) return false;\n\n // Convert all notes to a Set of MIDI note numbers for O(1) lookups\n const noteNumSet = createNoteNumSet(notesOn);\n\n // If noteInput is a string (note), convert it to a MIDI note number\n if (typeof noteInput === \"string\") {\n const noteNum = noteToNoteNum(noteInput);\n return noteNum !== -1 && noteNumSet.has(noteNum);\n }\n\n // If noteInput is a number (MIDI note number), check if it's in the Set\n return noteNumSet.has(noteInput);\n};\n\n/**\n * Checks if a given MIDI note number corresponds to a black key\n *\n * @param noteNum - The MIDI note number\n * @returns true if black key, false if white key\n */\nexport const isBlackKey = (noteNum: number): boolean => {\n // 0=C (white), 1=C# (black), 2=D (white), 3=D# (black), 4=E (white)\n // 5=F (white), 6=F# (black), 7=G (white), 8=G# (black), 9=A (white)\n // 10=A# (black), 11=B (white)\n const chromaticIndex = noteNum % 12;\n return !WHITE_KEY_POSITIONS.has(chromaticIndex);\n};\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n/**\n * Collection of reusable value formatters for control components\n */\n\n/**\n * Calculates the center value for a range according to MIDI conventions.\n *\n * This is used for bipolar controls (pan, modulation depth) where the center point\n * represents zero or neutral. The formula ensures proper centering for both even\n * and odd ranges.\n *\n * @param min The minimum value\n * @param max The maximum value\n * @returns The center value according to MIDI conventions (floor((max - min + 1) / 2) + min)\n *\n * @example\n * ```ts\n * calculateCenterValue(-64, 63); // 0 (bipolar 7-bit MIDI)\n * calculateCenterValue(0, 100); // 50\n * ```\n */\nexport const calculateCenterValue = (min: number, max: number): number => {\n return Math.floor((max - min + 1) / 2) + min;\n};\n\n/**\n * Formats a value with a bipolar display (adds + sign for positive values).\n *\n * This is useful for displaying values that can be positive or negative, such as\n * pan controls or modulation depth. Zero and negative values are displayed as-is,\n * while positive values get a \"+\" prefix.\n *\n * @param value The value to format\n * @returns Formatted string with + prefix for positive values\n *\n * @example\n * ```ts\n * bipolarFormatter(50); // \"+50\"\n * bipolarFormatter(-50); // \"-50\"\n * bipolarFormatter(0); // \"0\"\n * ```\n */\nexport const bipolarFormatter = (value: number): string => {\n return value > 0 ? `+${value}` : value.toString();\n};\n\n/**\n * Formats a value with MIDI bipolar convention (shifts value by center value).\n *\n * This formatter is designed for MIDI-style bipolar parameters where the raw value\n * is in the range [min, max], but you want to display it as an offset from center.\n * For example, a pan control with range [0, 127] centered at 64 would display as\n * \"+3\" when the value is 67.\n *\n * @param value The value to format\n * @param min The minimum value\n * @param max The maximum value\n * @returns Formatted string with the value shifted by center value\n *\n * @example\n * ```ts\n * midiBipolarFormatter(67, 0, 127); // \"+3\" (67 - 64 = 3)\n * midiBipolarFormatter(60, 0, 127); // \"-4\" (60 - 64 = -4)\n * ```\n */\nexport const midiBipolarFormatter = (value: number, min: number, max: number): string => {\n const centerValue = calculateCenterValue(min, max);\n const shiftedValue = value - centerValue;\n return bipolarFormatter(shiftedValue);\n};\n\n/**\n * Creates a formatter function that appends a unit suffix to values.\n *\n * This is a higher-order function that returns a formatter. Useful for creating\n * reusable formatters for parameters with units (dB, Hz, ms, etc.).\n *\n * @param unit The unit to append (e.g., \"dB\", \"Hz\", \"ms\")\n * @returns A formatter function that appends the unit\n *\n * @example\n * ```ts\n * const dbFormatter = withUnit(\"dB\");\n * dbFormatter(-6.0); // \"-6.0dB\"\n *\n * // Can be combined with other formatters\n * const formatter = combineFormatters(withPrecision(1), withUnit(\"Hz\"));\n * formatter(440.5); // \"440.5Hz\"\n * ```\n */\nexport const withUnit = (unit: string) => {\n return (value: number): string => `${value}${unit}`;\n};\n\n/**\n * Creates a formatter function that formats values with fixed decimal precision.\n *\n * This is a higher-order function that returns a formatter. Useful for ensuring\n * consistent decimal display across your application.\n *\n * @param precision Number of decimal places (0-20)\n * @returns A formatter function that formats with the specified precision\n *\n * @example\n * ```ts\n * const twoDecimals = withPrecision(2);\n * twoDecimals(3.14159); // \"3.14\"\n *\n * // Can be combined with other formatters\n * const formatter = combineFormatters(withPrecision(1), withUnit(\"dB\"));\n * formatter(-6.02); // \"-6.0dB\"\n * ```\n */\nexport const withPrecision = (precision: number) => {\n return (value: number): string => value.toFixed(precision);\n};\n\n/**\n * Combines multiple formatters into a single formatter function.\n *\n * Formatters are applied in sequence, with each formatter receiving the output\n * of the previous one (after parsing back to a number if needed). This allows\n * you to compose complex formatting pipelines.\n *\n * @param formatters Array of formatter functions to apply in sequence\n * @returns A formatter function that applies all formatters in sequence\n *\n * @example\n * ```ts\n * // Combine precision and unit formatting\n * const formatter = combineFormatters(\n * withPrecision(1),\n * withUnit(\"dB\")\n * );\n * formatter(-6.02); // \"-6.0dB\"\n *\n * // Combine multiple transformations\n * const complexFormatter = combineFormatters(\n * (v) => (v * 100).toString(), // Convert to percentage\n * withUnit(\"%\")\n * );\n * complexFormatter(0.5); // \"50%\"\n * ```\n */\nexport const combineFormatters = (...formatters: ((value: number, min?: number, max?: number) => string)[]) => {\n return (value: number, min?: number, max?: number): string => {\n let result: string | number = value;\n for (const formatter of formatters) {\n // If the result is already a string (from previous formatter),\n // convert back to number for the next formatter if possible\n if (typeof result === \"string\") {\n const parsed = parseFloat(result);\n if (!isNaN(parsed)) {\n result = formatter(parsed, min, max);\n }\n } else {\n result = formatter(result, min, max);\n }\n }\n return result.toString();\n };\n};\n\n/**\n * Formats a value as a percentage based on its position in the min-max range.\n *\n * The percentage is calculated as: ((value - min) / (max - min)) * 100\n * and rounded to the nearest integer.\n *\n * @param value The value to format\n * @param min The minimum value (corresponds to 0%)\n * @param max The maximum value (corresponds to 100%)\n * @returns Formatted percentage string (e.g., \"50%\")\n *\n * @example\n * ```ts\n * percentageFormatter(50, 0, 100); // \"50%\"\n * percentageFormatter(25, 0, 100); // \"25%\"\n * percentageFormatter(75, 0, 200); // \"38%\" (75/200 = 37.5%, rounded to 38%)\n * ```\n */\nexport const percentageFormatter = (value: number, min: number, max: number): string => {\n const percentage = ((value - min) / (max - min)) * 100;\n return `${Math.round(percentage)}%`;\n};\n\n/**\n * Formats a frequency value with appropriate units (Hz or kHz).\n *\n * Values >= 1000 Hz are displayed in kHz with one decimal place.\n * Values < 1000 Hz are displayed in Hz as integers.\n *\n * This is optimized for audio frequency display where values can range from\n * sub-audio (20 Hz) to ultrasonic (20 kHz+).\n *\n * @param value The frequency value in Hz\n * @returns Formatted string with appropriate units (Hz, kHz)\n *\n * @example\n * ```ts\n * frequencyFormatter(440); // \"440Hz\"\n * frequencyFormatter(1000); // \"1.0kHz\"\n * frequencyFormatter(15000); // \"15.0kHz\"\n * ```\n */\nexport const frequencyFormatter = (value: number): string => {\n if (value >= 1000) {\n return `${(value / 1000).toFixed(1)}kHz`;\n }\n return `${Math.round(value)}Hz`;\n};\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport isEqual from \"fast-deep-equal\";\n\n/**\n * Creates a comparison function for memoization to prevent unnecessary re-renders.\n *\n * This utility creates a function that performs shallow comparison of primitive props\n * and uses fast-deep-equal for deep comparison of objects like style.\n *\n * @param options - Configuration options for the comparison\n * @param options.deepCompareProps - Array of prop names that should be deeply compared\n * @param options.alwaysCompareProps - Array of prop names that should always be directly compared (===)\n * @returns A comparison function\n *\n * @example\n * // Basic usage with style deep comparison\n * const comparator = createPropComparator({\n * deepCompareProps: ['style']\n * });\n */\nexport function createPropComparator<T extends Record<string, unknown>>({\n deepCompareProps = [\"style\"],\n alwaysCompareProps = [\"children\"],\n}: {\n deepCompareProps?: Array<keyof T>;\n alwaysCompareProps?: Array<keyof T>;\n} = {}) {\n // Convert arrays to Sets for O(1) lookups\n const deepCompareSet = new Set(deepCompareProps);\n const alwaysCompareSet = new Set(alwaysCompareProps);\n\n return function propsAreEqual(prevProps: T, nextProps: T): boolean {\n // First check always-compare props with direct equality\n for (const prop of alwaysCompareSet) {\n if (prevProps[prop] !== nextProps[prop]) {\n return false;\n }\n }\n\n // Handle props that need deep comparison\n for (const prop of deepCompareSet) {\n if (!isEqual(prevProps[prop], nextProps[prop])) {\n return false;\n }\n }\n\n // For all other props, do shallow comparison\n const prevKeys = Object.keys(prevProps).filter(\n (key) => !deepCompareSet.has(key as keyof T) && !alwaysCompareSet.has(key as keyof T)\n );\n\n for (const key of prevKeys) {\n if (prevProps[key] !== nextProps[key]) {\n return false;\n }\n }\n\n return true;\n };\n}\n"],"names":["CLASSNAMES","themeColors","themeColorsDirect","DEFAULT_ROUNDNESS","DEFAULT_THICKNESS","CSS_VARS","CIRCULAR_CURSOR","DEFAULT_CONTINUOUS_SENSITIVITY","DEFAULT_WHEEL_SENSITIVITY","TARGET_PIXELS_PER_STEP","DEFAULT_KEYBOARD_STEP","ContinuousInteractionController","config","__publicField","clientX","clientY","target","e","delta","effectiveSensitivity","stepsToMove","effectiveDelta","x","y","rect","cursor","touch","currentAngle","startAngle","angleDelta","normalizedDelta","valueChange","DiscreteInteractionController","defaultPrevented","key","idx","opt","index","nextIdx","currentIdx","BooleanInteractionController","NoteInteractionController","pointerId","note","currentNote","LinearScale","n","s","LogScale","ExpScale","AudioParameterConverter","resolution","scale","realValue","conf","normalized","o","count","clamped","scaled","val","steps","norm","mapping","midiValue","clampedMidi","threshold","bestOpt","minDiff","diff","midi","value","precision","fixed","options","includeUnit","minStr","maxStr","minFixed","maxFixed","trueStr","falseStr","longest","label","AudioParameterFactory","name","range","unit","mode","id","min","max","step","bipolar","defaultValue","midiResolution","effectiveMin","effectiveMax","effectiveDefault","finalMin","finalMax","polarToCartesian","centerX","centerY","radius","angleInDegrees","angleInRadians","calculateArcAngles","normalizedValue","openness","rotation","positions","clampedValue","snappedValue","clampedOpenness","start","end","maxStartAngle","maxEndAngle","maxArcAngle","baseValueToAngle","endAngle","valueStartAngle","valueToAngle","calculateLinearPosition","cy","length","calculateArcPath","cx","direction","largeArcFlag","p1","p2","sweepFlag","squareSizeClassMap","horizontalSliderSizeClassMap","verticalSliderSizeClassMap","keysSizeClassMap","getSizeClassForComponent","componentType","size","orientation","getSizeStyleForComponent","namedColors","HSL_REGEX","isDarkMode","getAdaptiveDefaultColor","generateLuminosityVariant","baseColor","luminosityPercentage","namedColor","hslMatch","h","l","newL","darkening","generateTransparencyVariant","opacityPercentage","generateHighlightColor","generateColorVariants","variant","normalizedColor","highlight","clampNormalized","translateKnobRoundness","translateKnobThickness","translateSliderRoundness","translateSliderThickness","translateButtonRoundness","translateKeysRoundness","CHROMATIC_NOTES","WHITE_KEY_NAMES","WHITE_KEY_TO_CHROMATIC","DIATONIC_TO_CHROMATIC","WHITE_KEY_POSITIONS","NUM_TO_NOTE_MAP","NOTE_TO_NUM_MAP","noteNum","octave","noteIndex","noteNumToNote","noteToNoteNum","match","noteName","createNoteNumSet","notesOn","noteNumSet","isNoteOn","noteInput","isBlackKey","chromaticIndex","calculateCenterValue","bipolarFormatter","midiBipolarFormatter","centerValue","shiftedValue","withUnit","withPrecision","combineFormatters","formatters","result","formatter","parsed","percentageFormatter","percentage","frequencyFormatter","createPropComparator","deepCompareProps","alwaysCompareProps","deepCompareSet","alwaysCompareSet","prevProps","nextProps","prop","isEqual","prevKeys"],"mappings":"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"}
|