@waveform-playlist/recording 9.4.1 → 9.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -12,7 +12,7 @@ interface RecordingState {
12
12
  }
13
13
  interface RecordingData {
14
14
  buffer: AudioBuffer | null;
15
- peaks: Int8Array | Int16Array;
15
+ peaks: (Int8Array | Int16Array)[];
16
16
  duration: number;
17
17
  }
18
18
  interface MicrophoneDevice {
@@ -22,7 +22,9 @@ interface MicrophoneDevice {
22
22
  }
23
23
  interface RecordingOptions {
24
24
  /**
25
- * Number of channels to record (1 = mono, 2 = stereo)
25
+ * Number of channels to record (1 = mono, 2 = stereo).
26
+ * The actual channel count is auto-detected from the microphone stream;
27
+ * this value is used as a fallback if the stream doesn't report its channel count.
26
28
  * Default: 1 (mono)
27
29
  * Note: Sample rate is determined by the global AudioContext
28
30
  */
@@ -32,6 +34,11 @@ interface RecordingOptions {
32
34
  * Default: 1024
33
35
  */
34
36
  samplesPerPixel?: number;
37
+ /**
38
+ * Bit depth for peak values (8 or 16)
39
+ * Default: 16
40
+ */
41
+ bits?: 8 | 16;
35
42
  /**
36
43
  * Specific device ID to use for recording
37
44
  */
@@ -47,7 +54,7 @@ interface UseRecordingReturn {
47
54
  isRecording: boolean;
48
55
  isPaused: boolean;
49
56
  duration: number;
50
- peaks: Int8Array | Int16Array;
57
+ peaks: (Int8Array | Int16Array)[];
51
58
  audioBuffer: AudioBuffer | null;
52
59
  level: number;
53
60
  peakLevel: number;
@@ -178,7 +185,7 @@ interface UseIntegratedRecordingReturn {
178
185
  resumeRecording: () => void;
179
186
  requestMicAccess: () => Promise<void>;
180
187
  changeDevice: (deviceId: string) => Promise<void>;
181
- recordingPeaks: Int8Array | Int16Array;
188
+ recordingPeaks: (Int8Array | Int16Array)[];
182
189
  }
183
190
  declare function useIntegratedRecording(tracks: ClipTrack[], setTracks: (tracks: ClipTrack[]) => void, selectedTrackId: string | null, options?: IntegratedRecordingOptions): UseIntegratedRecordingReturn;
184
191
 
@@ -276,8 +283,9 @@ declare function generatePeaks(samples: Float32Array, samplesPerPixel: number, b
276
283
  */
277
284
  declare function concatenateAudioData(chunks: Float32Array[]): Float32Array;
278
285
  /**
279
- * Convert Float32Array to AudioBuffer
286
+ * Convert channel data to AudioBuffer.
287
+ * Accepts either per-channel Float32Array[] or a single Float32Array (mono, backwards compatible).
280
288
  */
281
- declare function createAudioBuffer(audioContext: AudioContext, samples: Float32Array, sampleRate: number, channelCount?: number): AudioBuffer;
289
+ declare function createAudioBuffer(audioContext: AudioContext, channelData: Float32Array[] | Float32Array, sampleRate: number, channelCount?: number): AudioBuffer;
282
290
 
283
291
  export { type IntegratedRecordingOptions, type MicrophoneDevice, MicrophoneSelector, type MicrophoneSelectorProps, RecordButton, type RecordButtonProps, type RecordingData, RecordingIndicator, type RecordingIndicatorProps, type RecordingOptions, type RecordingState, type UseIntegratedRecordingReturn, type UseMicrophoneAccessReturn, type UseMicrophoneLevelOptions, type UseMicrophoneLevelReturn, type UseRecordingReturn, VUMeter, type VUMeterProps, concatenateAudioData, createAudioBuffer, generatePeaks, useIntegratedRecording, useMicrophoneAccess, useMicrophoneLevel, useRecording };
package/dist/index.d.ts CHANGED
@@ -12,7 +12,7 @@ interface RecordingState {
12
12
  }
13
13
  interface RecordingData {
14
14
  buffer: AudioBuffer | null;
15
- peaks: Int8Array | Int16Array;
15
+ peaks: (Int8Array | Int16Array)[];
16
16
  duration: number;
17
17
  }
18
18
  interface MicrophoneDevice {
@@ -22,7 +22,9 @@ interface MicrophoneDevice {
22
22
  }
23
23
  interface RecordingOptions {
24
24
  /**
25
- * Number of channels to record (1 = mono, 2 = stereo)
25
+ * Number of channels to record (1 = mono, 2 = stereo).
26
+ * The actual channel count is auto-detected from the microphone stream;
27
+ * this value is used as a fallback if the stream doesn't report its channel count.
26
28
  * Default: 1 (mono)
27
29
  * Note: Sample rate is determined by the global AudioContext
28
30
  */
@@ -32,6 +34,11 @@ interface RecordingOptions {
32
34
  * Default: 1024
33
35
  */
34
36
  samplesPerPixel?: number;
37
+ /**
38
+ * Bit depth for peak values (8 or 16)
39
+ * Default: 16
40
+ */
41
+ bits?: 8 | 16;
35
42
  /**
36
43
  * Specific device ID to use for recording
37
44
  */
@@ -47,7 +54,7 @@ interface UseRecordingReturn {
47
54
  isRecording: boolean;
48
55
  isPaused: boolean;
49
56
  duration: number;
50
- peaks: Int8Array | Int16Array;
57
+ peaks: (Int8Array | Int16Array)[];
51
58
  audioBuffer: AudioBuffer | null;
52
59
  level: number;
53
60
  peakLevel: number;
@@ -178,7 +185,7 @@ interface UseIntegratedRecordingReturn {
178
185
  resumeRecording: () => void;
179
186
  requestMicAccess: () => Promise<void>;
180
187
  changeDevice: (deviceId: string) => Promise<void>;
181
- recordingPeaks: Int8Array | Int16Array;
188
+ recordingPeaks: (Int8Array | Int16Array)[];
182
189
  }
183
190
  declare function useIntegratedRecording(tracks: ClipTrack[], setTracks: (tracks: ClipTrack[]) => void, selectedTrackId: string | null, options?: IntegratedRecordingOptions): UseIntegratedRecordingReturn;
184
191
 
@@ -276,8 +283,9 @@ declare function generatePeaks(samples: Float32Array, samplesPerPixel: number, b
276
283
  */
277
284
  declare function concatenateAudioData(chunks: Float32Array[]): Float32Array;
278
285
  /**
279
- * Convert Float32Array to AudioBuffer
286
+ * Convert channel data to AudioBuffer.
287
+ * Accepts either per-channel Float32Array[] or a single Float32Array (mono, backwards compatible).
280
288
  */
281
- declare function createAudioBuffer(audioContext: AudioContext, samples: Float32Array, sampleRate: number, channelCount?: number): AudioBuffer;
289
+ declare function createAudioBuffer(audioContext: AudioContext, channelData: Float32Array[] | Float32Array, sampleRate: number, channelCount?: number): AudioBuffer;
282
290
 
283
291
  export { type IntegratedRecordingOptions, type MicrophoneDevice, MicrophoneSelector, type MicrophoneSelectorProps, RecordButton, type RecordButtonProps, type RecordingData, RecordingIndicator, type RecordingIndicatorProps, type RecordingOptions, type RecordingState, type UseIntegratedRecordingReturn, type UseMicrophoneAccessReturn, type UseMicrophoneLevelOptions, type UseMicrophoneLevelReturn, type UseRecordingReturn, VUMeter, type VUMeterProps, concatenateAudioData, createAudioBuffer, generatePeaks, useIntegratedRecording, useMicrophoneAccess, useMicrophoneLevel, useRecording };
package/dist/index.js CHANGED
@@ -58,10 +58,13 @@ function concatenateAudioData(chunks) {
58
58
  }
59
59
  return result;
60
60
  }
61
- function createAudioBuffer(audioContext, samples, sampleRate, channelCount = 1) {
62
- const buffer = audioContext.createBuffer(channelCount, samples.length, sampleRate);
63
- const typedSamples = new Float32Array(samples);
64
- buffer.copyToChannel(typedSamples, 0);
61
+ function createAudioBuffer(audioContext, channelData, sampleRate, channelCount = 1) {
62
+ const channels = channelData instanceof Float32Array ? [channelData] : channelData;
63
+ const length = channels[0]?.length ?? 0;
64
+ const buffer = audioContext.createBuffer(channelCount, length, sampleRate);
65
+ for (let ch = 0; ch < Math.min(channelCount, channels.length); ch++) {
66
+ buffer.copyToChannel(new Float32Array(channels[ch]), ch);
67
+ }
65
68
  return buffer;
66
69
  }
67
70
 
@@ -120,17 +123,19 @@ function appendPeaks(existingPeaks, newSamples, samplesPerPixel, totalSamplesPro
120
123
  // src/hooks/useRecording.ts
121
124
  var import_tone = require("tone");
122
125
  var import_meta = {};
126
+ function emptyPeaks(bits) {
127
+ return bits === 8 ? new Int8Array(0) : new Int16Array(0);
128
+ }
123
129
  function useRecording(stream, options = {}) {
124
- const { channelCount = 1, samplesPerPixel = 1024 } = options;
130
+ const { channelCount = 1, samplesPerPixel = 1024, bits = 16 } = options;
125
131
  const [isRecording, setIsRecording] = (0, import_react.useState)(false);
126
132
  const [isPaused, setIsPaused] = (0, import_react.useState)(false);
127
133
  const [duration, setDuration] = (0, import_react.useState)(0);
128
- const [peaks, setPeaks] = (0, import_react.useState)(new Int16Array(0));
134
+ const [peaks, setPeaks] = (0, import_react.useState)([emptyPeaks(bits)]);
129
135
  const [audioBuffer, setAudioBuffer] = (0, import_react.useState)(null);
130
136
  const [error, setError] = (0, import_react.useState)(null);
131
137
  const [level, setLevel] = (0, import_react.useState)(0);
132
138
  const [peakLevel, setPeakLevel] = (0, import_react.useState)(0);
133
- const bits = 16;
134
139
  const workletLoadedRef = (0, import_react.useRef)(false);
135
140
  const workletNodeRef = (0, import_react.useRef)(null);
136
141
  const mediaStreamSourceRef = (0, import_react.useRef)(null);
@@ -166,36 +171,60 @@ function useRecording(stream, options = {}) {
166
171
  await context.resume();
167
172
  }
168
173
  await loadWorklet();
174
+ const detectedChannelCount = stream.getAudioTracks()[0]?.getSettings().channelCount;
175
+ if (detectedChannelCount === void 0) {
176
+ console.warn(
177
+ `[waveform-playlist] Could not detect stream channel count, using fallback: ${channelCount}`
178
+ );
179
+ }
180
+ const streamChannelCount = detectedChannelCount ?? channelCount;
169
181
  const source = context.createMediaStreamSource(stream);
170
182
  mediaStreamSourceRef.current = source;
171
- const workletNode = context.createAudioWorkletNode("recording-processor");
183
+ const workletNode = context.createAudioWorkletNode("recording-processor", {
184
+ channelCount: streamChannelCount,
185
+ channelCountMode: "explicit"
186
+ });
172
187
  workletNodeRef.current = workletNode;
173
- source.connect(workletNode);
188
+ recordedChunksRef.current = Array.from({ length: streamChannelCount }, () => []);
189
+ totalSamplesRef.current = 0;
190
+ setPeaks(Array.from({ length: streamChannelCount }, () => emptyPeaks(bits)));
191
+ setAudioBuffer(null);
192
+ setLevel(0);
193
+ setPeakLevel(0);
174
194
  workletNode.port.onmessage = (event) => {
175
- const { samples } = event.data;
176
- recordedChunksRef.current.push(samples);
177
- totalSamplesRef.current += samples.length;
178
- setPeaks(
179
- (prevPeaks) => appendPeaks(
180
- prevPeaks,
181
- samples,
182
- samplesPerPixel,
183
- totalSamplesRef.current - samples.length,
184
- bits
185
- )
186
- );
195
+ const { channels } = event.data;
196
+ if (!channels || channels.length === 0) {
197
+ console.warn("[waveform-playlist] Recording worklet sent empty or missing channels data");
198
+ return;
199
+ }
200
+ for (let ch = 0; ch < channels.length; ch++) {
201
+ if (!recordedChunksRef.current[ch]) {
202
+ console.warn(
203
+ `[waveform-playlist] Unexpected channel ${ch} from worklet (expected ${recordedChunksRef.current.length})`
204
+ );
205
+ recordedChunksRef.current[ch] = [];
206
+ }
207
+ recordedChunksRef.current[ch].push(channels[ch]);
208
+ }
209
+ const samplesProcessedBefore = totalSamplesRef.current;
210
+ totalSamplesRef.current += channels[0].length;
211
+ setPeaks((prevPeaks) => {
212
+ const updated = [];
213
+ for (let ch = 0; ch < channels.length; ch++) {
214
+ const prev = prevPeaks[ch] ?? emptyPeaks(bits);
215
+ updated.push(
216
+ appendPeaks(prev, channels[ch], samplesPerPixel, samplesProcessedBefore, bits)
217
+ );
218
+ }
219
+ return updated;
220
+ });
187
221
  };
222
+ source.connect(workletNode);
188
223
  workletNode.port.postMessage({
189
224
  command: "start",
190
225
  sampleRate: context.sampleRate,
191
- channelCount
226
+ channelCount: streamChannelCount
192
227
  });
193
- recordedChunksRef.current = [];
194
- totalSamplesRef.current = 0;
195
- setPeaks(new Int16Array(0));
196
- setAudioBuffer(null);
197
- setLevel(0);
198
- setPeakLevel(0);
199
228
  isRecordingRef.current = true;
200
229
  isPausedRef.current = false;
201
230
  setIsRecording(true);
@@ -213,7 +242,7 @@ function useRecording(stream, options = {}) {
213
242
  console.error("Failed to start recording:", err);
214
243
  setError(err instanceof Error ? err : new Error("Failed to start recording"));
215
244
  }
216
- }, [stream, channelCount, samplesPerPixel, loadWorklet]);
245
+ }, [stream, channelCount, samplesPerPixel, bits, loadWorklet]);
217
246
  const stopRecording = (0, import_react.useCallback)(async () => {
218
247
  if (!isRecording) {
219
248
  return null;
@@ -224,7 +253,8 @@ function useRecording(stream, options = {}) {
224
253
  if (mediaStreamSourceRef.current) {
225
254
  try {
226
255
  mediaStreamSourceRef.current.disconnect(workletNodeRef.current);
227
- } catch {
256
+ } catch (err) {
257
+ console.warn("[waveform-playlist] Source disconnect during stop:", String(err));
228
258
  }
229
259
  }
230
260
  workletNodeRef.current.disconnect();
@@ -233,10 +263,11 @@ function useRecording(stream, options = {}) {
233
263
  cancelAnimationFrame(animationFrameRef.current);
234
264
  animationFrameRef.current = null;
235
265
  }
236
- const allSamples = concatenateAudioData(recordedChunksRef.current);
237
266
  const context = (0, import_tone.getContext)();
238
267
  const rawContext = context.rawContext;
239
- const buffer = createAudioBuffer(rawContext, allSamples, rawContext.sampleRate, channelCount);
268
+ const numChannels = recordedChunksRef.current.length || channelCount;
269
+ const channelData = recordedChunksRef.current.map((chunks) => concatenateAudioData(chunks));
270
+ const buffer = createAudioBuffer(rawContext, channelData, rawContext.sampleRate, numChannels);
240
271
  setAudioBuffer(buffer);
241
272
  setDuration(buffer.duration);
242
273
  isRecordingRef.current = false;
@@ -283,7 +314,8 @@ function useRecording(stream, options = {}) {
283
314
  if (mediaStreamSourceRef.current) {
284
315
  try {
285
316
  mediaStreamSourceRef.current.disconnect(workletNodeRef.current);
286
- } catch {
317
+ } catch (err) {
318
+ console.warn("[waveform-playlist] Source disconnect during cleanup:", String(err));
287
319
  }
288
320
  }
289
321
  workletNodeRef.current.disconnect();
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/hooks/useRecording.ts","../src/utils/audioBufferUtils.ts","../src/utils/peaksGenerator.ts","../src/hooks/useMicrophoneAccess.ts","../src/hooks/useMicrophoneLevel.ts","../src/hooks/useIntegratedRecording.ts","../src/components/RecordButton.tsx","../src/components/MicrophoneSelector.tsx","../src/components/RecordingIndicator.tsx","../src/components/VUMeter.tsx"],"sourcesContent":["/**\n * @waveform-playlist/recording\n *\n * Audio recording support using AudioWorklet for waveform-playlist\n */\n\n// Hooks\nexport {\n useRecording,\n useMicrophoneAccess,\n useMicrophoneLevel,\n useIntegratedRecording,\n} from './hooks';\nexport type {\n UseMicrophoneLevelOptions,\n UseMicrophoneLevelReturn,\n UseIntegratedRecordingReturn,\n IntegratedRecordingOptions,\n} from './hooks';\n\n// Components\nexport { RecordButton, MicrophoneSelector, RecordingIndicator, VUMeter } from './components';\nexport type {\n RecordButtonProps,\n MicrophoneSelectorProps,\n RecordingIndicatorProps,\n VUMeterProps,\n} from './components';\n\n// Types\nexport type {\n RecordingState,\n RecordingData,\n MicrophoneDevice,\n RecordingOptions,\n UseRecordingReturn,\n UseMicrophoneAccessReturn,\n} from './types';\n\n// Utilities\nexport { generatePeaks } from './utils/peaksGenerator';\nexport { createAudioBuffer, concatenateAudioData } from './utils/audioBufferUtils';\n","/**\n * Main recording hook using AudioWorklet\n */\n\nimport { useState, useRef, useCallback, useEffect } from 'react';\nimport { UseRecordingReturn, RecordingOptions } from '../types';\nimport { concatenateAudioData, createAudioBuffer } from '../utils/audioBufferUtils';\nimport { appendPeaks } from '../utils/peaksGenerator';\nimport { getContext } from 'tone';\n\nexport function useRecording(\n stream: MediaStream | null,\n options: RecordingOptions = {}\n): UseRecordingReturn {\n const { channelCount = 1, samplesPerPixel = 1024 } = options;\n\n // State\n const [isRecording, setIsRecording] = useState(false);\n const [isPaused, setIsPaused] = useState(false);\n const [duration, setDuration] = useState(0);\n const [peaks, setPeaks] = useState<Int8Array | Int16Array>(new Int16Array(0));\n const [audioBuffer, setAudioBuffer] = useState<AudioBuffer | null>(null);\n const [error, setError] = useState<Error | null>(null);\n const [level, setLevel] = useState(0); // Current RMS level (0-1)\n const [peakLevel, setPeakLevel] = useState(0); // Peak level since recording started (0-1)\n\n const bits: 8 | 16 = 16; // Match the bit depth used by the final waveform\n\n // Global flag to prevent loading worklet multiple times\n // (AudioWorklet processors can only be registered once per AudioContext)\n const workletLoadedRef = useRef<boolean>(false);\n\n // Refs for AudioWorklet and data accumulation\n const workletNodeRef = useRef<AudioWorkletNode | null>(null);\n const mediaStreamSourceRef = useRef<MediaStreamAudioSourceNode | null>(null);\n const recordedChunksRef = useRef<Float32Array[]>([]);\n const totalSamplesRef = useRef(0);\n const animationFrameRef = useRef<number | null>(null);\n const startTimeRef = useRef<number>(0);\n const isRecordingRef = useRef<boolean>(false);\n const isPausedRef = useRef<boolean>(false);\n\n // Load AudioWorklet module\n const loadWorklet = useCallback(async () => {\n // Skip if already loaded to prevent \"already registered\" error\n if (workletLoadedRef.current) {\n return;\n }\n\n try {\n const context = getContext();\n // Load the worklet module\n // Use a relative path that works when bundled\n const workletUrl = new URL('./worklet/recording-processor.worklet.js', import.meta.url).href;\n\n // Use Tone's addAudioWorkletModule for cross-browser compatibility\n await context.addAudioWorkletModule(workletUrl);\n workletLoadedRef.current = true;\n } catch (err) {\n console.error('Failed to load AudioWorklet module:', err);\n throw new Error('Failed to load recording processor');\n }\n }, []);\n\n // Start recording\n const startRecording = useCallback(async () => {\n if (!stream) {\n setError(new Error('No microphone stream available'));\n return;\n }\n\n try {\n setError(null);\n\n // Use Tone.js Context for cross-browser compatibility\n const context = getContext();\n\n // Resume AudioContext if suspended\n if (context.state === 'suspended') {\n await context.resume();\n }\n\n // Load worklet module\n await loadWorklet();\n\n // Create MediaStreamSource from Tone's context\n // Each hook creates its own source to avoid cross-context issues in Firefox\n const source = context.createMediaStreamSource(stream);\n mediaStreamSourceRef.current = source;\n\n // Create AudioWorklet node using Tone's method\n const workletNode = context.createAudioWorkletNode('recording-processor');\n workletNodeRef.current = workletNode;\n\n // Connect source to worklet (but not to destination - no monitoring)\n source.connect(workletNode);\n\n //Listen for audio data from worklet\n workletNode.port.onmessage = (event: MessageEvent) => {\n const { samples } = event.data;\n\n // Accumulate samples\n recordedChunksRef.current.push(samples);\n totalSamplesRef.current += samples.length;\n\n // Update peaks incrementally for live waveform visualization\n setPeaks((prevPeaks) =>\n appendPeaks(\n prevPeaks,\n samples,\n samplesPerPixel,\n totalSamplesRef.current - samples.length,\n bits\n )\n );\n\n // Note: VU meter levels come from useMicrophoneLevel (AnalyserNode)\n // We don't update level/peakLevel here to avoid conflicting state updates\n };\n\n // Start the worklet processor\n workletNode.port.postMessage({\n command: 'start',\n sampleRate: context.sampleRate,\n channelCount,\n });\n\n // Reset state\n recordedChunksRef.current = [];\n totalSamplesRef.current = 0;\n setPeaks(new Int16Array(0));\n setAudioBuffer(null);\n setLevel(0);\n setPeakLevel(0);\n isRecordingRef.current = true;\n isPausedRef.current = false;\n setIsRecording(true);\n setIsPaused(false);\n startTimeRef.current = performance.now();\n\n // Start duration update loop\n const updateDuration = () => {\n if (isRecordingRef.current && !isPausedRef.current) {\n const elapsed = (performance.now() - startTimeRef.current) / 1000;\n setDuration(elapsed);\n animationFrameRef.current = requestAnimationFrame(updateDuration);\n }\n };\n updateDuration();\n } catch (err) {\n console.error('Failed to start recording:', err);\n setError(err instanceof Error ? err : new Error('Failed to start recording'));\n }\n }, [stream, channelCount, samplesPerPixel, loadWorklet]);\n\n // Stop recording\n const stopRecording = useCallback(async (): Promise<AudioBuffer | null> => {\n if (!isRecording) {\n return null;\n }\n\n try {\n // Stop the worklet\n if (workletNodeRef.current) {\n workletNodeRef.current.port.postMessage({ command: 'stop' });\n\n // Disconnect worklet from source\n if (mediaStreamSourceRef.current) {\n try {\n mediaStreamSourceRef.current.disconnect(workletNodeRef.current);\n } catch {\n // Source may have already been disconnected when stream changed\n // This is fine - just ignore the error\n }\n }\n workletNodeRef.current.disconnect();\n }\n\n // Cancel animation frame\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n\n // Create final AudioBuffer from accumulated chunks\n const allSamples = concatenateAudioData(recordedChunksRef.current);\n const context = getContext();\n // Use rawContext for createBuffer (native AudioContext method)\n const rawContext = context.rawContext as AudioContext;\n const buffer = createAudioBuffer(rawContext, allSamples, rawContext.sampleRate, channelCount);\n\n setAudioBuffer(buffer);\n setDuration(buffer.duration);\n isRecordingRef.current = false;\n isPausedRef.current = false;\n setIsRecording(false);\n setIsPaused(false);\n setLevel(0);\n // Keep peakLevel to show the peak reached during recording\n\n return buffer;\n } catch (err) {\n console.error('Failed to stop recording:', err);\n setError(err instanceof Error ? err : new Error('Failed to stop recording'));\n return null;\n }\n }, [isRecording, channelCount]);\n\n // Pause recording\n const pauseRecording = useCallback(() => {\n if (isRecording && !isPaused) {\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n isPausedRef.current = true;\n setIsPaused(true);\n }\n }, [isRecording, isPaused]);\n\n // Resume recording\n const resumeRecording = useCallback(() => {\n if (isRecording && isPaused) {\n isPausedRef.current = false;\n setIsPaused(false);\n startTimeRef.current = performance.now() - duration * 1000;\n\n const updateDuration = () => {\n if (isRecordingRef.current && !isPausedRef.current) {\n const elapsed = (performance.now() - startTimeRef.current) / 1000;\n setDuration(elapsed);\n animationFrameRef.current = requestAnimationFrame(updateDuration);\n }\n };\n updateDuration();\n }\n }, [isRecording, isPaused, duration]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (workletNodeRef.current) {\n workletNodeRef.current.port.postMessage({ command: 'stop' });\n\n // Disconnect worklet from source\n if (mediaStreamSourceRef.current) {\n try {\n mediaStreamSourceRef.current.disconnect(workletNodeRef.current);\n } catch {\n // Source may have already been disconnected when stream changed\n // This is fine - just ignore the error\n }\n }\n workletNodeRef.current.disconnect();\n }\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n // Don't close the global AudioContext - it's shared across the app\n };\n }, []);\n\n return {\n isRecording,\n isPaused,\n duration,\n peaks,\n audioBuffer,\n level,\n peakLevel,\n startRecording,\n stopRecording,\n pauseRecording,\n resumeRecording,\n error,\n };\n}\n","/**\n * Utility functions for working with AudioBuffers during recording\n */\n\n/**\n * Concatenate multiple Float32Arrays into a single array\n */\nexport function concatenateAudioData(chunks: Float32Array[]): Float32Array {\n const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);\n const result = new Float32Array(totalLength);\n\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n\n return result;\n}\n\n/**\n * Convert Float32Array to AudioBuffer\n */\nexport function createAudioBuffer(\n audioContext: AudioContext,\n samples: Float32Array,\n sampleRate: number,\n channelCount: number = 1\n): AudioBuffer {\n const buffer = audioContext.createBuffer(channelCount, samples.length, sampleRate);\n\n // Copy samples to buffer (for now, just mono)\n // Create a new Float32Array to ensure correct type\n const typedSamples = new Float32Array(samples);\n buffer.copyToChannel(typedSamples, 0);\n\n return buffer;\n}\n\n/**\n * Append new samples to an existing AudioBuffer\n */\nexport function appendToAudioBuffer(\n audioContext: AudioContext,\n existingBuffer: AudioBuffer | null,\n newSamples: Float32Array,\n sampleRate: number\n): AudioBuffer {\n if (!existingBuffer) {\n return createAudioBuffer(audioContext, newSamples, sampleRate);\n }\n\n // Get existing samples\n const existingData = existingBuffer.getChannelData(0);\n\n // Concatenate using concatenateAudioData helper\n const combined = concatenateAudioData([existingData, newSamples]);\n\n // Create new buffer\n return createAudioBuffer(audioContext, combined, sampleRate);\n}\n\n/**\n * Calculate duration in seconds from sample count and sample rate\n */\nexport function calculateDuration(sampleCount: number, sampleRate: number): number {\n return sampleCount / sampleRate;\n}\n","/**\n * Peak generation for real-time waveform visualization during recording\n * Matches the format used by webaudio-peaks: min/max pairs with bit depth\n */\n\n/**\n * Generate peaks from audio samples in standard min/max pair format\n *\n * @param samples - Audio samples to process\n * @param samplesPerPixel - Number of samples to represent in each peak\n * @param bits - Bit depth for peak values (8 or 16)\n * @returns Int8Array or Int16Array of peak values (min/max pairs)\n */\nexport function generatePeaks(\n samples: Float32Array,\n samplesPerPixel: number,\n bits: 8 | 16 = 16\n): Int8Array | Int16Array {\n const numPeaks = Math.ceil(samples.length / samplesPerPixel);\n const peakArray = bits === 8 ? new Int8Array(numPeaks * 2) : new Int16Array(numPeaks * 2);\n const maxValue = 2 ** (bits - 1);\n\n for (let i = 0; i < numPeaks; i++) {\n const start = i * samplesPerPixel;\n const end = Math.min(start + samplesPerPixel, samples.length);\n\n let min = 0;\n let max = 0;\n\n for (let j = start; j < end; j++) {\n const value = samples[j];\n if (value < min) min = value;\n if (value > max) max = value;\n }\n\n // Store as min/max pairs scaled to bit depth\n // Clamp to valid range: Int16 is [-32768, 32767], Int8 is [-128, 127]\n peakArray[i * 2] = Math.max(-maxValue, Math.floor(min * maxValue));\n peakArray[i * 2 + 1] = Math.min(maxValue - 1, Math.floor(max * maxValue));\n }\n\n return peakArray;\n}\n\n/**\n * Append new peaks to existing peaks array\n * This is used for incremental peak updates during recording\n */\nexport function appendPeaks(\n existingPeaks: Int8Array | Int16Array,\n newSamples: Float32Array,\n samplesPerPixel: number,\n totalSamplesProcessed: number,\n bits: 8 | 16 = 16\n): Int8Array | Int16Array {\n const maxValue = 2 ** (bits - 1);\n\n // Check if we have a partial peak from the last update\n const remainder = totalSamplesProcessed % samplesPerPixel;\n let offset = 0;\n\n // If there's a partial peak, we need to update the last peak\n if (remainder > 0 && existingPeaks.length > 0) {\n const samplesToComplete = samplesPerPixel - remainder;\n const endIndex = Math.min(samplesToComplete, newSamples.length);\n\n // Get current min/max from last peak\n let min = existingPeaks[existingPeaks.length - 2] / maxValue;\n let max = existingPeaks[existingPeaks.length - 1] / maxValue;\n\n // Update with new samples\n for (let i = 0; i < endIndex; i++) {\n const value = newSamples[i];\n if (value < min) min = value;\n if (value > max) max = value;\n }\n\n // Update last peak\n const updated = new (bits === 8 ? Int8Array : Int16Array)(existingPeaks.length);\n updated.set(existingPeaks);\n updated[existingPeaks.length - 2] = Math.max(-maxValue, Math.floor(min * maxValue));\n updated[existingPeaks.length - 1] = Math.min(maxValue - 1, Math.floor(max * maxValue));\n\n offset = endIndex;\n\n // Generate peaks for remaining samples and concatenate\n const newPeaks = generatePeaks(newSamples.slice(offset), samplesPerPixel, bits);\n const result = new (bits === 8 ? Int8Array : Int16Array)(updated.length + newPeaks.length);\n result.set(updated);\n result.set(newPeaks, updated.length);\n return result;\n }\n\n // No partial peak, just concatenate\n const newPeaks = generatePeaks(newSamples.slice(offset), samplesPerPixel, bits);\n const result = new (bits === 8 ? Int8Array : Int16Array)(existingPeaks.length + newPeaks.length);\n result.set(existingPeaks);\n result.set(newPeaks, existingPeaks.length);\n return result;\n}\n","/**\n * Hook for managing microphone access and device enumeration\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { UseMicrophoneAccessReturn, MicrophoneDevice } from '../types';\n\nexport function useMicrophoneAccess(): UseMicrophoneAccessReturn {\n const [stream, setStream] = useState<MediaStream | null>(null);\n const [devices, setDevices] = useState<MicrophoneDevice[]>([]);\n const [hasPermission, setHasPermission] = useState(false);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n // Enumerate audio input devices\n const enumerateDevices = useCallback(async () => {\n try {\n const allDevices = await navigator.mediaDevices.enumerateDevices();\n const audioInputs = allDevices\n .filter((device) => device.kind === 'audioinput')\n .map((device) => ({\n deviceId: device.deviceId,\n label: device.label || `Microphone ${device.deviceId.slice(0, 8)}`,\n groupId: device.groupId,\n }));\n\n setDevices(audioInputs);\n } catch (err) {\n console.error('Failed to enumerate devices:', err);\n setError(err instanceof Error ? err : new Error('Failed to enumerate devices'));\n }\n }, []);\n\n // Request microphone access\n const requestAccess = useCallback(\n async (deviceId?: string, audioConstraints?: MediaTrackConstraints) => {\n setIsLoading(true);\n setError(null);\n\n try {\n // Stop existing stream if any\n if (stream) {\n stream.getTracks().forEach((track) => track.stop());\n }\n\n // Build audio constraints\n const audio: MediaTrackConstraints & { latency?: number } = {\n // Recording-optimized defaults: prioritize raw audio quality and low latency\n echoCancellation: false,\n noiseSuppression: false,\n autoGainControl: false,\n latency: 0, // Low latency mode (not in TS types yet, but supported in modern browsers)\n // User-provided constraints override defaults\n ...audioConstraints,\n // Device ID override (if specified)\n ...(deviceId && { deviceId: { exact: deviceId } }),\n };\n\n const constraints: MediaStreamConstraints = {\n audio,\n video: false,\n };\n\n const newStream = await navigator.mediaDevices.getUserMedia(constraints);\n setStream(newStream);\n setHasPermission(true);\n\n // Enumerate devices after getting permission (labels will be available)\n await enumerateDevices();\n } catch (err) {\n console.error('Failed to access microphone:', err);\n setError(err instanceof Error ? err : new Error('Failed to access microphone'));\n setHasPermission(false);\n } finally {\n setIsLoading(false);\n }\n },\n [stream, enumerateDevices]\n );\n\n // Stop the stream and revoke access\n const stopStream = useCallback(() => {\n if (stream) {\n stream.getTracks().forEach((track) => track.stop());\n setStream(null);\n setHasPermission(false);\n }\n }, [stream]);\n\n // Check initial permission state and enumerate devices\n useEffect(() => {\n // Try to enumerate devices (labels won't be available without permission)\n enumerateDevices();\n\n // Cleanup on unmount\n return () => {\n if (stream) {\n stream.getTracks().forEach((track) => track.stop());\n }\n };\n }, [enumerateDevices, stream]);\n\n return {\n stream,\n devices,\n hasPermission,\n isLoading,\n requestAccess,\n stopStream,\n error,\n };\n}\n","/**\n * Hook for monitoring microphone input levels\n *\n * Uses Tone.js Meter for real-time audio level monitoring.\n */\n\nimport { useEffect, useState, useRef } from 'react';\nimport { Meter, getContext, connect } from 'tone';\n\nexport interface UseMicrophoneLevelOptions {\n /**\n * How often to update the level (in Hz)\n * Default: 60 (60fps)\n */\n updateRate?: number;\n\n /**\n * FFT size for the analyser\n * Default: 256\n */\n fftSize?: number;\n\n /**\n * Smoothing time constant (0-1)\n * Higher values = smoother but slower response\n * Default: 0.8\n */\n smoothingTimeConstant?: number;\n}\n\nexport interface UseMicrophoneLevelReturn {\n /**\n * Current audio level (0-1)\n * 0 = silence, 1 = maximum level\n */\n level: number;\n\n /**\n * Peak level since last reset (0-1)\n */\n peakLevel: number;\n\n /**\n * Reset the peak level\n */\n resetPeak: () => void;\n}\n\n/**\n * Monitor microphone input levels in real-time\n *\n * @param stream - MediaStream from getUserMedia\n * @param options - Configuration options\n * @returns Object with current level and peak level\n *\n * @example\n * ```typescript\n * const { stream } = useMicrophoneAccess();\n * const { level, peakLevel, resetPeak } = useMicrophoneLevel(stream);\n *\n * return <VUMeter level={level} peakLevel={peakLevel} />;\n * ```\n */\nexport function useMicrophoneLevel(\n stream: MediaStream | null,\n options: UseMicrophoneLevelOptions = {}\n): UseMicrophoneLevelReturn {\n const { updateRate = 60, smoothingTimeConstant = 0.8 } = options;\n\n const [level, setLevel] = useState(0);\n const [peakLevel, setPeakLevel] = useState(0);\n\n const meterRef = useRef<Meter | null>(null);\n const sourceRef = useRef<MediaStreamAudioSourceNode | null>(null);\n const animationFrameRef = useRef<number | null>(null);\n\n const resetPeak = () => setPeakLevel(0);\n\n useEffect(() => {\n if (!stream) {\n setLevel(0);\n setPeakLevel(0);\n return;\n }\n\n let isMounted = true;\n\n // Setup audio monitoring\n const setupMonitoring = async () => {\n if (!isMounted) return;\n\n // Get Tone's context and resume if needed\n const context = getContext();\n if (context.state === 'suspended') {\n await context.resume();\n }\n\n if (!isMounted) return;\n\n // Create Tone.js Meter for level monitoring\n // Pass context to ensure it's created in the same context as the source\n const meter = new Meter({ smoothing: smoothingTimeConstant, context });\n meterRef.current = meter;\n\n // Create MediaStreamSource from the SAME context as the meter\n // Note: This creates a separate source from useRecording, but that's OK\n // since we're only using it for level monitoring (not recording)\n const source = context.createMediaStreamSource(stream);\n sourceRef.current = source;\n\n // Connect source to meter using Tone's connect function\n connect(source, meter);\n\n // Start level monitoring\n const updateInterval = 1000 / updateRate;\n let lastUpdateTime = 0;\n\n const updateLevel = (timestamp: number) => {\n if (!isMounted || !meterRef.current) return;\n\n if (timestamp - lastUpdateTime >= updateInterval) {\n lastUpdateTime = timestamp;\n\n // Meter.getValue() returns dB, convert to 0-1 range\n const db = meterRef.current.getValue();\n const dbValue = typeof db === 'number' ? db : db[0];\n // dB is typically -Infinity to 0, map -100dB..0dB to 0..1\n // Using -100dB as floor since Firefox seems to report lower values\n const normalized = Math.max(0, Math.min(1, (dbValue + 100) / 100));\n\n setLevel(normalized);\n setPeakLevel((prev) => Math.max(prev, normalized));\n }\n\n animationFrameRef.current = requestAnimationFrame(updateLevel);\n };\n\n animationFrameRef.current = requestAnimationFrame(updateLevel);\n };\n\n setupMonitoring();\n\n // Cleanup\n return () => {\n isMounted = false;\n\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n\n // Disconnect and clean up\n if (sourceRef.current) {\n try {\n sourceRef.current.disconnect();\n } catch {\n // Ignore disconnect errors\n }\n sourceRef.current = null;\n }\n\n if (meterRef.current) {\n meterRef.current.dispose();\n meterRef.current = null;\n }\n };\n }, [stream, smoothingTimeConstant, updateRate]);\n\n return {\n level,\n peakLevel,\n resetPeak,\n };\n}\n","/**\n * Hook for integrated multi-track recording\n * Combines recording functionality with track management\n */\n\nimport { useState, useCallback, useEffect } from 'react';\nimport { useRecording } from './useRecording';\nimport { useMicrophoneAccess } from './useMicrophoneAccess';\nimport { useMicrophoneLevel } from './useMicrophoneLevel';\nimport type { MicrophoneDevice } from '../types';\nimport { type ClipTrack, type AudioClip } from '@waveform-playlist/core';\nimport { resumeGlobalAudioContext } from '@waveform-playlist/playout';\n\nexport interface IntegratedRecordingOptions {\n /**\n * Current playback/cursor position in seconds\n * Recording will start from max(currentTime, lastClipEndTime)\n */\n currentTime?: number;\n\n /**\n * MediaTrackConstraints for audio recording\n * These will override the recording-optimized defaults (echo cancellation off, low latency)\n */\n audioConstraints?: MediaTrackConstraints;\n\n /**\n * Number of channels to record (1 = mono, 2 = stereo)\n * Default: 1 (mono)\n */\n channelCount?: number;\n\n /**\n * Samples per pixel for peak generation\n * Default: 1024\n */\n samplesPerPixel?: number;\n}\n\nexport interface UseIntegratedRecordingReturn {\n // Recording state\n isRecording: boolean;\n isPaused: boolean;\n duration: number;\n level: number;\n peakLevel: number;\n error: Error | null;\n\n // Microphone state\n stream: MediaStream | null;\n devices: MicrophoneDevice[];\n hasPermission: boolean;\n selectedDevice: string | null;\n\n // Recording controls\n startRecording: () => void;\n stopRecording: () => void;\n pauseRecording: () => void;\n resumeRecording: () => void;\n requestMicAccess: () => Promise<void>;\n changeDevice: (deviceId: string) => Promise<void>;\n\n // Track state (for live waveform during recording)\n recordingPeaks: Int8Array | Int16Array;\n}\n\nexport function useIntegratedRecording(\n tracks: ClipTrack[],\n setTracks: (tracks: ClipTrack[]) => void,\n selectedTrackId: string | null,\n options: IntegratedRecordingOptions = {}\n): UseIntegratedRecordingReturn {\n const { currentTime = 0, audioConstraints, ...recordingOptions } = options;\n\n // Track if we're currently monitoring (for auto-resume audio context)\n const [isMonitoring, setIsMonitoring] = useState(false);\n const [selectedDevice, setSelectedDevice] = useState<string | null>(null);\n const [hookError, setHookError] = useState<Error | null>(null);\n\n // Microphone access\n const { stream, devices, hasPermission, requestAccess, error: micError } = useMicrophoneAccess();\n\n // Microphone level (for VU meter)\n const { level, peakLevel } = useMicrophoneLevel(stream);\n\n // Recording\n const {\n isRecording,\n isPaused,\n duration,\n peaks,\n audioBuffer: _recordedAudioBuffer,\n startRecording: startRec,\n stopRecording: stopRec,\n pauseRecording,\n resumeRecording,\n error: recError,\n } = useRecording(stream, recordingOptions);\n\n // Start recording handler\n const startRecording = useCallback(async () => {\n if (!selectedTrackId) {\n setHookError(\n new Error('Cannot start recording: no track selected. Select or create a track first.')\n );\n return;\n }\n\n try {\n setHookError(null);\n // Resume audio context if needed\n if (!isMonitoring) {\n await resumeGlobalAudioContext();\n setIsMonitoring(true);\n }\n\n await startRec();\n } catch (err) {\n setHookError(err instanceof Error ? err : new Error(String(err)));\n }\n }, [selectedTrackId, isMonitoring, startRec]);\n\n // Stop recording and add clip to selected track\n const stopRecording = useCallback(async () => {\n let buffer: AudioBuffer | null;\n try {\n buffer = await stopRec();\n } catch (err) {\n setHookError(err instanceof Error ? err : new Error(String(err)));\n return;\n }\n\n // Add clip to track after recording completes\n if (buffer && selectedTrackId) {\n const selectedTrackIndex = tracks.findIndex((t) => t.id === selectedTrackId);\n if (selectedTrackIndex === -1) {\n const err = new Error(\n `Recording completed but track \"${selectedTrackId}\" no longer exists. The recorded audio could not be saved.`\n );\n console.error(`[waveform-playlist] ${err.message}`);\n setHookError(err);\n return;\n }\n\n const selectedTrack = tracks[selectedTrackIndex];\n\n // Calculate start position: max(currentTime, lastClipEndTime)\n const currentTimeSamples = Math.floor(currentTime * buffer.sampleRate);\n\n let lastClipEndSample = 0;\n if (selectedTrack.clips.length > 0) {\n // Find the end time of the last clip (in samples)\n const endSamples = selectedTrack.clips.map(\n (clip) => clip.startSample + clip.durationSamples\n );\n lastClipEndSample = Math.max(...endSamples);\n }\n\n // Use whichever is greater: cursor position or last clip end\n const startSample = Math.max(currentTimeSamples, lastClipEndSample);\n\n // Create new clip from recording\n const newClip: AudioClip = {\n id: `clip-${Date.now()}`,\n audioBuffer: buffer,\n startSample,\n durationSamples: buffer.length,\n offsetSamples: 0,\n sampleRate: buffer.sampleRate,\n sourceDurationSamples: buffer.length,\n gain: 1.0,\n name: `Recording ${new Date().toLocaleTimeString()}`,\n };\n\n // Add clip to track\n const newTracks = tracks.map((track, index) => {\n if (index === selectedTrackIndex) {\n return {\n ...track,\n clips: [...track.clips, newClip],\n };\n }\n return track;\n });\n\n setTracks(newTracks);\n }\n }, [selectedTrackId, tracks, setTracks, currentTime, stopRec]);\n\n // Auto-select the first device when devices become available\n useEffect(() => {\n // Only auto-select if we have permission, devices are available, and nothing is selected yet\n if (hasPermission && devices.length > 0 && selectedDevice === null) {\n setSelectedDevice(devices[0].deviceId);\n }\n }, [hasPermission, devices, selectedDevice]);\n\n // Request microphone access\n const requestMicAccess = useCallback(async () => {\n try {\n setHookError(null);\n await requestAccess(undefined, audioConstraints);\n await resumeGlobalAudioContext();\n setIsMonitoring(true);\n } catch (err) {\n setHookError(err instanceof Error ? err : new Error(String(err)));\n }\n }, [requestAccess, audioConstraints]);\n\n // Change device\n const changeDevice = useCallback(\n async (deviceId: string) => {\n try {\n setHookError(null);\n setSelectedDevice(deviceId);\n await requestAccess(deviceId, audioConstraints);\n await resumeGlobalAudioContext();\n setIsMonitoring(true);\n } catch (err) {\n setHookError(err instanceof Error ? err : new Error(String(err)));\n }\n },\n [requestAccess, audioConstraints]\n );\n\n return {\n // Recording state\n isRecording,\n isPaused,\n duration,\n level,\n peakLevel,\n error: hookError || micError || recError,\n\n // Microphone state\n stream,\n devices,\n hasPermission,\n selectedDevice,\n\n // Recording controls\n startRecording,\n stopRecording,\n pauseRecording,\n resumeRecording,\n requestMicAccess,\n changeDevice,\n\n // Track state\n recordingPeaks: peaks,\n };\n}\n","/**\n * RecordButton - Control button for starting/stopping recording\n */\n\nimport React from 'react';\nimport styled from 'styled-components';\n\nexport interface RecordButtonProps {\n isRecording: boolean;\n onClick: () => void;\n disabled?: boolean;\n className?: string;\n}\n\nconst Button = styled.button<{ $isRecording: boolean }>`\n padding: 0.5rem 1rem;\n font-size: 0.875rem;\n font-weight: 500;\n border: none;\n border-radius: 0.25rem;\n cursor: pointer;\n transition: all 0.2s ease-in-out;\n background: ${(props) => (props.$isRecording ? '#dc3545' : '#e74c3c')};\n color: white;\n\n &:hover:not(:disabled) {\n background: ${(props) => (props.$isRecording ? '#c82333' : '#c0392b')};\n transform: translateY(-1px);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n }\n\n &:active:not(:disabled) {\n transform: translateY(0);\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n &:focus {\n outline: none;\n box-shadow: 0 0 0 3px rgba(231, 76, 60, 0.3);\n }\n`;\n\nconst RecordingIndicator = styled.span`\n display: inline-block;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: white;\n margin-right: 0.5rem;\n animation: pulse 1.5s ease-in-out infinite;\n\n @keyframes pulse {\n 0%,\n 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.3;\n }\n }\n`;\n\nexport const RecordButton: React.FC<RecordButtonProps> = ({\n isRecording,\n onClick,\n disabled = false,\n className,\n}) => {\n return (\n <Button\n $isRecording={isRecording}\n onClick={onClick}\n disabled={disabled}\n className={className}\n aria-label={isRecording ? 'Stop recording' : 'Start recording'}\n >\n {isRecording && <RecordingIndicator />}\n {isRecording ? 'Stop Recording' : 'Record'}\n </Button>\n );\n};\n","/**\n * MicrophoneSelector - Dropdown for selecting microphone input device\n */\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport { BaseSelect, BaseLabel } from '@waveform-playlist/ui-components';\nimport { MicrophoneDevice } from '../types';\n\nexport interface MicrophoneSelectorProps {\n devices: MicrophoneDevice[];\n selectedDeviceId?: string;\n onDeviceChange: (deviceId: string) => void;\n disabled?: boolean;\n className?: string;\n}\n\nconst Select = styled(BaseSelect)`\n min-width: 200px;\n`;\n\nconst Label = styled(BaseLabel)`\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n`;\n\nexport const MicrophoneSelector: React.FC<MicrophoneSelectorProps> = ({\n devices,\n selectedDeviceId,\n onDeviceChange,\n disabled = false,\n className,\n}) => {\n const handleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {\n onDeviceChange(event.target.value);\n };\n\n // Use first device if no selection provided\n const currentValue = selectedDeviceId || (devices.length > 0 ? devices[0].deviceId : '');\n\n return (\n <Label className={className}>\n Microphone\n <Select\n value={currentValue}\n onChange={handleChange}\n disabled={disabled || devices.length === 0}\n >\n {devices.length === 0 ? (\n <option value=\"\">No microphones found</option>\n ) : (\n devices.map((device) => (\n <option key={device.deviceId} value={device.deviceId}>\n {device.label}\n </option>\n ))\n )}\n </Select>\n </Label>\n );\n};\n","/**\n * RecordingIndicator - Shows recording status, duration, and visual indicator\n */\n\nimport React from 'react';\nimport styled from 'styled-components';\n\nexport interface RecordingIndicatorProps {\n isRecording: boolean;\n isPaused?: boolean;\n duration: number; // in seconds\n formatTime?: (seconds: number) => string;\n className?: string;\n}\n\nconst Container = styled.div<{ $isRecording: boolean }>`\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.5rem 0.75rem;\n background: ${(props) => (props.$isRecording ? '#fff3cd' : 'transparent')};\n border-radius: 0.25rem;\n transition: background 0.2s ease-in-out;\n`;\n\nconst Dot = styled.div<{ $isRecording: boolean; $isPaused: boolean }>`\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background: ${(props) => (props.$isPaused ? '#ffc107' : '#dc3545')};\n opacity: ${(props) => (props.$isRecording ? 1 : 0)};\n transition: opacity 0.2s ease-in-out;\n\n ${(props) =>\n props.$isRecording &&\n !props.$isPaused &&\n `\n animation: blink 1.5s ease-in-out infinite;\n\n @keyframes blink {\n 0%, 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.3;\n }\n }\n `}\n`;\n\nconst Duration = styled.span`\n font-family: 'Courier New', Monaco, monospace;\n font-size: 1rem;\n font-weight: 600;\n color: #495057;\n min-width: 70px;\n`;\n\nconst Status = styled.span<{ $isPaused: boolean }>`\n font-size: 0.75rem;\n font-weight: 500;\n color: ${(props) => (props.$isPaused ? '#ffc107' : '#dc3545')};\n text-transform: uppercase;\n`;\n\nconst defaultFormatTime = (seconds: number): string => {\n const mins = Math.floor(seconds / 60);\n const secs = Math.floor(seconds % 60);\n return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;\n};\n\nexport const RecordingIndicator: React.FC<RecordingIndicatorProps> = ({\n isRecording,\n isPaused = false,\n duration,\n formatTime = defaultFormatTime,\n className,\n}) => {\n return (\n <Container $isRecording={isRecording} className={className}>\n <Dot $isRecording={isRecording} $isPaused={isPaused} />\n <Duration>{formatTime(duration)}</Duration>\n {isRecording && <Status $isPaused={isPaused}>{isPaused ? 'Paused' : 'Recording'}</Status>}\n </Container>\n );\n};\n","/**\n * VU Meter Component\n *\n * Displays real-time audio input levels with color-coded zones\n * and peak indicator.\n */\n\nimport React from 'react';\nimport styled from 'styled-components';\n\nexport interface VUMeterProps {\n /**\n * Current audio level (0-1)\n */\n level: number;\n\n /**\n * Peak level (0-1)\n * Optional - if provided, shows peak indicator\n */\n peakLevel?: number;\n\n /**\n * Width of the meter in pixels\n * Default: 200\n */\n width?: number;\n\n /**\n * Height of the meter in pixels\n * Default: 20\n */\n height?: number;\n\n /**\n * Additional CSS class name\n */\n className?: string;\n}\n\nconst MeterContainer = styled.div<{ $width: number; $height: number }>`\n position: relative;\n width: ${(props) => props.$width}px;\n height: ${(props) => props.$height}px;\n background: #2c3e50;\n border-radius: 4px;\n overflow: hidden;\n box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);\n`;\n\n// Helper to get gradient color based on level\nconst getLevelGradient = (level: number): string => {\n if (level < 0.6) return 'linear-gradient(90deg, #27ae60, #2ecc71)';\n if (level < 0.85) return 'linear-gradient(90deg, #f39c12, #f1c40f)';\n return 'linear-gradient(90deg, #c0392b, #e74c3c)';\n};\n\n// Use .attrs() for frequently changing styles to avoid generating new CSS classes\nconst MeterFill = styled.div.attrs<{ $level: number; $height: number }>((props) => ({\n style: {\n width: `${props.$level * 100}%`,\n height: `${props.$height}px`,\n background: getLevelGradient(props.$level),\n boxShadow: props.$level > 0.01 ? '0 0 8px rgba(255, 255, 255, 0.3)' : 'none',\n },\n}))<{ $level: number; $height: number }>`\n position: absolute;\n left: 0;\n top: 0;\n transition:\n width 0.05s ease-out,\n background 0.1s ease-out;\n`;\n\n// Use .attrs() for frequently changing left position\nconst PeakIndicator = styled.div.attrs<{ $peakLevel: number; $height: number }>((props) => ({\n style: {\n left: `${props.$peakLevel * 100}%`,\n height: `${props.$height}px`,\n },\n}))<{ $peakLevel: number; $height: number }>`\n position: absolute;\n top: 0;\n width: 2px;\n background: #ecf0f1;\n box-shadow: 0 0 4px rgba(236, 240, 241, 0.8);\n transition: left 0.1s ease-out;\n`;\n\nconst ScaleMarkers = styled.div<{ $height: number }>`\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: ${(props) => props.$height}px;\n pointer-events: none;\n`;\n\nconst ScaleMark = styled.div<{ $position: number; $height: number }>`\n position: absolute;\n left: ${(props) => props.$position}%;\n top: 0;\n width: 1px;\n height: ${(props) => props.$height}px;\n background: rgba(255, 255, 255, 0.2);\n`;\n\n/**\n * VU Meter component for displaying audio input levels\n *\n * @example\n * ```typescript\n * import { useMicrophoneLevel, VUMeter } from '@waveform-playlist/recording';\n *\n * const { level, peakLevel } = useMicrophoneLevel(stream);\n *\n * return <VUMeter level={level} peakLevel={peakLevel} width={300} height={24} />;\n * ```\n */\nconst VUMeterComponent: React.FC<VUMeterProps> = ({\n level,\n peakLevel,\n width = 200,\n height = 20,\n className,\n}) => {\n // Clamp values to 0-1 range\n const clampedLevel = Math.max(0, Math.min(1, level));\n const clampedPeak = peakLevel !== undefined ? Math.max(0, Math.min(1, peakLevel)) : 0;\n\n return (\n <MeterContainer $width={width} $height={height} className={className}>\n <MeterFill $level={clampedLevel} $height={height} />\n\n {peakLevel !== undefined && clampedPeak > 0 && (\n <PeakIndicator $peakLevel={clampedPeak} $height={height} />\n )}\n\n <ScaleMarkers $height={height}>\n <ScaleMark $position={60} $height={height} />\n <ScaleMark $position={85} $height={height} />\n </ScaleMarkers>\n </MeterContainer>\n );\n};\n\n// Memoize to prevent unnecessary re-renders when parent updates\nexport const VUMeter = React.memo(VUMeterComponent);\n"],"mappings":"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","names":["RecordingIndicator","newPeaks","result","import_react","import_react","import_tone","import_react","styled","import_styled_components","import_jsx_runtime","styled","import_styled_components","import_jsx_runtime","styled","RecordingIndicator","import_react","import_styled_components","import_jsx_runtime","styled","React"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/hooks/useRecording.ts","../src/utils/audioBufferUtils.ts","../src/utils/peaksGenerator.ts","../src/hooks/useMicrophoneAccess.ts","../src/hooks/useMicrophoneLevel.ts","../src/hooks/useIntegratedRecording.ts","../src/components/RecordButton.tsx","../src/components/MicrophoneSelector.tsx","../src/components/RecordingIndicator.tsx","../src/components/VUMeter.tsx"],"sourcesContent":["/**\n * @waveform-playlist/recording\n *\n * Audio recording support using AudioWorklet for waveform-playlist\n */\n\n// Hooks\nexport {\n useRecording,\n useMicrophoneAccess,\n useMicrophoneLevel,\n useIntegratedRecording,\n} from './hooks';\nexport type {\n UseMicrophoneLevelOptions,\n UseMicrophoneLevelReturn,\n UseIntegratedRecordingReturn,\n IntegratedRecordingOptions,\n} from './hooks';\n\n// Components\nexport { RecordButton, MicrophoneSelector, RecordingIndicator, VUMeter } from './components';\nexport type {\n RecordButtonProps,\n MicrophoneSelectorProps,\n RecordingIndicatorProps,\n VUMeterProps,\n} from './components';\n\n// Types\nexport type {\n RecordingState,\n RecordingData,\n MicrophoneDevice,\n RecordingOptions,\n UseRecordingReturn,\n UseMicrophoneAccessReturn,\n} from './types';\n\n// Utilities\nexport { generatePeaks } from './utils/peaksGenerator';\nexport { createAudioBuffer, concatenateAudioData } from './utils/audioBufferUtils';\n","/**\n * Main recording hook using AudioWorklet\n */\n\nimport { useState, useRef, useCallback, useEffect } from 'react';\nimport { UseRecordingReturn, RecordingOptions } from '../types';\nimport { concatenateAudioData, createAudioBuffer } from '../utils/audioBufferUtils';\nimport { appendPeaks } from '../utils/peaksGenerator';\nimport { getContext } from 'tone';\n\nfunction emptyPeaks(bits: 8 | 16): Int8Array | Int16Array {\n return bits === 8 ? new Int8Array(0) : new Int16Array(0);\n}\n\nexport function useRecording(\n stream: MediaStream | null,\n options: RecordingOptions = {}\n): UseRecordingReturn {\n const { channelCount = 1, samplesPerPixel = 1024, bits = 16 } = options;\n\n // State\n const [isRecording, setIsRecording] = useState(false);\n const [isPaused, setIsPaused] = useState(false);\n const [duration, setDuration] = useState(0);\n // Per-channel peaks for multi-channel live preview\n const [peaks, setPeaks] = useState<(Int8Array | Int16Array)[]>([emptyPeaks(bits)]);\n const [audioBuffer, setAudioBuffer] = useState<AudioBuffer | null>(null);\n const [error, setError] = useState<Error | null>(null);\n const [level, setLevel] = useState(0); // Current RMS level (0-1)\n const [peakLevel, setPeakLevel] = useState(0); // Peak level since recording started (0-1)\n\n // Global flag to prevent loading worklet multiple times\n // (AudioWorklet processors can only be registered once per AudioContext)\n const workletLoadedRef = useRef<boolean>(false);\n\n // Refs for AudioWorklet and data accumulation\n const workletNodeRef = useRef<AudioWorkletNode | null>(null);\n const mediaStreamSourceRef = useRef<MediaStreamAudioSourceNode | null>(null);\n // Per-channel sample accumulation: recordedChunksRef[channelIndex] = Float32Array[]\n const recordedChunksRef = useRef<Float32Array[][]>([]);\n const totalSamplesRef = useRef(0);\n const animationFrameRef = useRef<number | null>(null);\n const startTimeRef = useRef<number>(0);\n const isRecordingRef = useRef<boolean>(false);\n const isPausedRef = useRef<boolean>(false);\n\n // Load AudioWorklet module\n const loadWorklet = useCallback(async () => {\n // Skip if already loaded to prevent \"already registered\" error\n if (workletLoadedRef.current) {\n return;\n }\n\n try {\n const context = getContext();\n // Load the worklet module\n // Use a relative path that works when bundled\n const workletUrl = new URL('./worklet/recording-processor.worklet.js', import.meta.url).href;\n\n // Use Tone's addAudioWorkletModule for cross-browser compatibility\n await context.addAudioWorkletModule(workletUrl);\n workletLoadedRef.current = true;\n } catch (err) {\n console.error('Failed to load AudioWorklet module:', err);\n throw new Error('Failed to load recording processor');\n }\n }, []);\n\n // Start recording\n const startRecording = useCallback(async () => {\n if (!stream) {\n setError(new Error('No microphone stream available'));\n return;\n }\n\n try {\n setError(null);\n\n // Use Tone.js Context for cross-browser compatibility\n const context = getContext();\n\n // Resume AudioContext if suspended\n if (context.state === 'suspended') {\n await context.resume();\n }\n\n // Load worklet module\n await loadWorklet();\n\n // Detect actual channel count from the stream's audio track settings.\n // Falls back to the user-provided channelCount option.\n const detectedChannelCount = stream.getAudioTracks()[0]?.getSettings().channelCount;\n if (detectedChannelCount === undefined) {\n console.warn(\n `[waveform-playlist] Could not detect stream channel count, using fallback: ${channelCount}`\n );\n }\n const streamChannelCount = detectedChannelCount ?? channelCount;\n\n // Create MediaStreamSource from Tone's context\n // Each hook creates its own source to avoid cross-context issues in Firefox\n const source = context.createMediaStreamSource(stream);\n mediaStreamSourceRef.current = source;\n\n // Create AudioWorklet node — set channelCount to match the stream\n const workletNode = context.createAudioWorkletNode('recording-processor', {\n channelCount: streamChannelCount,\n channelCountMode: 'explicit',\n });\n workletNodeRef.current = workletNode;\n\n // Reset state before connecting — prevents race where a worklet message\n // arrives before refs are cleared, corrupting samplesProcessedBefore calculations\n recordedChunksRef.current = Array.from({ length: streamChannelCount }, () => []);\n totalSamplesRef.current = 0;\n setPeaks(Array.from({ length: streamChannelCount }, () => emptyPeaks(bits)));\n setAudioBuffer(null);\n setLevel(0);\n setPeakLevel(0);\n\n // Listen for audio data from worklet\n workletNode.port.onmessage = (event: MessageEvent) => {\n const { channels } = event.data as { channels: Float32Array[] };\n\n if (!channels || channels.length === 0) {\n console.warn('[waveform-playlist] Recording worklet sent empty or missing channels data');\n return;\n }\n\n // Accumulate per-channel samples\n for (let ch = 0; ch < channels.length; ch++) {\n if (!recordedChunksRef.current[ch]) {\n console.warn(\n `[waveform-playlist] Unexpected channel ${ch} from worklet (expected ${recordedChunksRef.current.length})`\n );\n recordedChunksRef.current[ch] = [];\n }\n recordedChunksRef.current[ch].push(channels[ch]);\n }\n // Capture sample offset before incrementing — used by peak alignment\n const samplesProcessedBefore = totalSamplesRef.current;\n totalSamplesRef.current += channels[0].length;\n setPeaks((prevPeaks) => {\n // Ensure we have an entry per channel\n const updated: (Int8Array | Int16Array)[] = [];\n for (let ch = 0; ch < channels.length; ch++) {\n const prev = prevPeaks[ch] ?? emptyPeaks(bits);\n updated.push(\n appendPeaks(prev, channels[ch], samplesPerPixel, samplesProcessedBefore, bits)\n );\n }\n return updated;\n });\n\n // Note: VU meter levels come from useMicrophoneLevel (AnalyserNode)\n // We don't update level/peakLevel here to avoid conflicting state updates\n };\n\n // Connect and start — after state reset and handler setup\n source.connect(workletNode);\n workletNode.port.postMessage({\n command: 'start',\n sampleRate: context.sampleRate,\n channelCount: streamChannelCount,\n });\n isRecordingRef.current = true;\n isPausedRef.current = false;\n setIsRecording(true);\n setIsPaused(false);\n startTimeRef.current = performance.now();\n\n // Start duration update loop\n const updateDuration = () => {\n if (isRecordingRef.current && !isPausedRef.current) {\n const elapsed = (performance.now() - startTimeRef.current) / 1000;\n setDuration(elapsed);\n animationFrameRef.current = requestAnimationFrame(updateDuration);\n }\n };\n updateDuration();\n } catch (err) {\n console.error('Failed to start recording:', err);\n setError(err instanceof Error ? err : new Error('Failed to start recording'));\n }\n }, [stream, channelCount, samplesPerPixel, bits, loadWorklet]);\n\n // Stop recording\n const stopRecording = useCallback(async (): Promise<AudioBuffer | null> => {\n if (!isRecording) {\n return null;\n }\n\n try {\n // Stop the worklet\n if (workletNodeRef.current) {\n workletNodeRef.current.port.postMessage({ command: 'stop' });\n\n // Disconnect worklet from source\n if (mediaStreamSourceRef.current) {\n try {\n mediaStreamSourceRef.current.disconnect(workletNodeRef.current);\n } catch (err) {\n console.warn('[waveform-playlist] Source disconnect during stop:', String(err));\n }\n }\n workletNodeRef.current.disconnect();\n }\n\n // Cancel animation frame\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n\n // Create final AudioBuffer from accumulated per-channel chunks\n const context = getContext();\n const rawContext = context.rawContext as AudioContext;\n const numChannels = recordedChunksRef.current.length || channelCount;\n const channelData = recordedChunksRef.current.map((chunks) => concatenateAudioData(chunks));\n const buffer = createAudioBuffer(rawContext, channelData, rawContext.sampleRate, numChannels);\n\n setAudioBuffer(buffer);\n setDuration(buffer.duration);\n isRecordingRef.current = false;\n isPausedRef.current = false;\n setIsRecording(false);\n setIsPaused(false);\n setLevel(0);\n // Keep peakLevel to show the peak reached during recording\n\n return buffer;\n } catch (err) {\n console.error('Failed to stop recording:', err);\n setError(err instanceof Error ? err : new Error('Failed to stop recording'));\n return null;\n }\n }, [isRecording, channelCount]);\n\n // Pause recording\n const pauseRecording = useCallback(() => {\n if (isRecording && !isPaused) {\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n isPausedRef.current = true;\n setIsPaused(true);\n }\n }, [isRecording, isPaused]);\n\n // Resume recording\n const resumeRecording = useCallback(() => {\n if (isRecording && isPaused) {\n isPausedRef.current = false;\n setIsPaused(false);\n startTimeRef.current = performance.now() - duration * 1000;\n\n const updateDuration = () => {\n if (isRecordingRef.current && !isPausedRef.current) {\n const elapsed = (performance.now() - startTimeRef.current) / 1000;\n setDuration(elapsed);\n animationFrameRef.current = requestAnimationFrame(updateDuration);\n }\n };\n updateDuration();\n }\n }, [isRecording, isPaused, duration]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (workletNodeRef.current) {\n workletNodeRef.current.port.postMessage({ command: 'stop' });\n\n // Disconnect worklet from source\n if (mediaStreamSourceRef.current) {\n try {\n mediaStreamSourceRef.current.disconnect(workletNodeRef.current);\n } catch (err) {\n console.warn('[waveform-playlist] Source disconnect during cleanup:', String(err));\n }\n }\n workletNodeRef.current.disconnect();\n }\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n // Don't close the global AudioContext - it's shared across the app\n };\n }, []);\n\n return {\n isRecording,\n isPaused,\n duration,\n peaks,\n audioBuffer,\n level,\n peakLevel,\n startRecording,\n stopRecording,\n pauseRecording,\n resumeRecording,\n error,\n };\n}\n","/**\n * Utility functions for working with AudioBuffers during recording\n */\n\n/**\n * Concatenate multiple Float32Arrays into a single array\n */\nexport function concatenateAudioData(chunks: Float32Array[]): Float32Array {\n const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);\n const result = new Float32Array(totalLength);\n\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n\n return result;\n}\n\n/**\n * Convert channel data to AudioBuffer.\n * Accepts either per-channel Float32Array[] or a single Float32Array (mono, backwards compatible).\n */\nexport function createAudioBuffer(\n audioContext: AudioContext,\n channelData: Float32Array[] | Float32Array,\n sampleRate: number,\n channelCount: number = 1\n): AudioBuffer {\n // Backwards compatibility: single Float32Array → wrap as mono\n const channels: Float32Array[] =\n channelData instanceof Float32Array ? [channelData] : channelData;\n\n const length = channels[0]?.length ?? 0;\n const buffer = audioContext.createBuffer(channelCount, length, sampleRate);\n\n for (let ch = 0; ch < Math.min(channelCount, channels.length); ch++) {\n buffer.copyToChannel(new Float32Array(channels[ch]), ch);\n }\n\n return buffer;\n}\n\n/**\n * Append new samples to an existing AudioBuffer (mono convenience)\n */\nexport function appendToAudioBuffer(\n audioContext: AudioContext,\n existingBuffer: AudioBuffer | null,\n newSamples: Float32Array,\n sampleRate: number\n): AudioBuffer {\n if (!existingBuffer) {\n return createAudioBuffer(audioContext, [newSamples], sampleRate);\n }\n\n // Get existing samples\n const existingData = existingBuffer.getChannelData(0);\n\n // Concatenate using concatenateAudioData helper\n const combined = concatenateAudioData([existingData, newSamples]);\n\n // Create new buffer\n return createAudioBuffer(audioContext, [combined], sampleRate);\n}\n\n/**\n * Calculate duration in seconds from sample count and sample rate\n */\nexport function calculateDuration(sampleCount: number, sampleRate: number): number {\n return sampleCount / sampleRate;\n}\n","/**\n * Peak generation for real-time waveform visualization during recording\n * Matches the format used by webaudio-peaks: min/max pairs with bit depth\n */\n\n/**\n * Generate peaks from audio samples in standard min/max pair format\n *\n * @param samples - Audio samples to process\n * @param samplesPerPixel - Number of samples to represent in each peak\n * @param bits - Bit depth for peak values (8 or 16)\n * @returns Int8Array or Int16Array of peak values (min/max pairs)\n */\nexport function generatePeaks(\n samples: Float32Array,\n samplesPerPixel: number,\n bits: 8 | 16 = 16\n): Int8Array | Int16Array {\n const numPeaks = Math.ceil(samples.length / samplesPerPixel);\n const peakArray = bits === 8 ? new Int8Array(numPeaks * 2) : new Int16Array(numPeaks * 2);\n const maxValue = 2 ** (bits - 1);\n\n for (let i = 0; i < numPeaks; i++) {\n const start = i * samplesPerPixel;\n const end = Math.min(start + samplesPerPixel, samples.length);\n\n let min = 0;\n let max = 0;\n\n for (let j = start; j < end; j++) {\n const value = samples[j];\n if (value < min) min = value;\n if (value > max) max = value;\n }\n\n // Store as min/max pairs scaled to bit depth\n // Clamp to valid range: Int16 is [-32768, 32767], Int8 is [-128, 127]\n peakArray[i * 2] = Math.max(-maxValue, Math.floor(min * maxValue));\n peakArray[i * 2 + 1] = Math.min(maxValue - 1, Math.floor(max * maxValue));\n }\n\n return peakArray;\n}\n\n/**\n * Append new peaks to existing peaks array\n * This is used for incremental peak updates during recording\n */\nexport function appendPeaks(\n existingPeaks: Int8Array | Int16Array,\n newSamples: Float32Array,\n samplesPerPixel: number,\n totalSamplesProcessed: number,\n bits: 8 | 16 = 16\n): Int8Array | Int16Array {\n const maxValue = 2 ** (bits - 1);\n\n // Check if we have a partial peak from the last update\n const remainder = totalSamplesProcessed % samplesPerPixel;\n let offset = 0;\n\n // If there's a partial peak, we need to update the last peak\n if (remainder > 0 && existingPeaks.length > 0) {\n const samplesToComplete = samplesPerPixel - remainder;\n const endIndex = Math.min(samplesToComplete, newSamples.length);\n\n // Get current min/max from last peak\n let min = existingPeaks[existingPeaks.length - 2] / maxValue;\n let max = existingPeaks[existingPeaks.length - 1] / maxValue;\n\n // Update with new samples\n for (let i = 0; i < endIndex; i++) {\n const value = newSamples[i];\n if (value < min) min = value;\n if (value > max) max = value;\n }\n\n // Update last peak\n const updated = new (bits === 8 ? Int8Array : Int16Array)(existingPeaks.length);\n updated.set(existingPeaks);\n updated[existingPeaks.length - 2] = Math.max(-maxValue, Math.floor(min * maxValue));\n updated[existingPeaks.length - 1] = Math.min(maxValue - 1, Math.floor(max * maxValue));\n\n offset = endIndex;\n\n // Generate peaks for remaining samples and concatenate\n const newPeaks = generatePeaks(newSamples.slice(offset), samplesPerPixel, bits);\n const result = new (bits === 8 ? Int8Array : Int16Array)(updated.length + newPeaks.length);\n result.set(updated);\n result.set(newPeaks, updated.length);\n return result;\n }\n\n // No partial peak, just concatenate\n const newPeaks = generatePeaks(newSamples.slice(offset), samplesPerPixel, bits);\n const result = new (bits === 8 ? Int8Array : Int16Array)(existingPeaks.length + newPeaks.length);\n result.set(existingPeaks);\n result.set(newPeaks, existingPeaks.length);\n return result;\n}\n","/**\n * Hook for managing microphone access and device enumeration\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { UseMicrophoneAccessReturn, MicrophoneDevice } from '../types';\n\nexport function useMicrophoneAccess(): UseMicrophoneAccessReturn {\n const [stream, setStream] = useState<MediaStream | null>(null);\n const [devices, setDevices] = useState<MicrophoneDevice[]>([]);\n const [hasPermission, setHasPermission] = useState(false);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n // Enumerate audio input devices\n const enumerateDevices = useCallback(async () => {\n try {\n const allDevices = await navigator.mediaDevices.enumerateDevices();\n const audioInputs = allDevices\n .filter((device) => device.kind === 'audioinput')\n .map((device) => ({\n deviceId: device.deviceId,\n label: device.label || `Microphone ${device.deviceId.slice(0, 8)}`,\n groupId: device.groupId,\n }));\n\n setDevices(audioInputs);\n } catch (err) {\n console.error('Failed to enumerate devices:', err);\n setError(err instanceof Error ? err : new Error('Failed to enumerate devices'));\n }\n }, []);\n\n // Request microphone access\n const requestAccess = useCallback(\n async (deviceId?: string, audioConstraints?: MediaTrackConstraints) => {\n setIsLoading(true);\n setError(null);\n\n try {\n // Stop existing stream if any\n if (stream) {\n stream.getTracks().forEach((track) => track.stop());\n }\n\n // Build audio constraints\n const audio: MediaTrackConstraints & { latency?: number } = {\n // Recording-optimized defaults: prioritize raw audio quality and low latency\n echoCancellation: false,\n noiseSuppression: false,\n autoGainControl: false,\n latency: 0, // Low latency mode (not in TS types yet, but supported in modern browsers)\n // User-provided constraints override defaults\n ...audioConstraints,\n // Device ID override (if specified)\n ...(deviceId && { deviceId: { exact: deviceId } }),\n };\n\n const constraints: MediaStreamConstraints = {\n audio,\n video: false,\n };\n\n const newStream = await navigator.mediaDevices.getUserMedia(constraints);\n setStream(newStream);\n setHasPermission(true);\n\n // Enumerate devices after getting permission (labels will be available)\n await enumerateDevices();\n } catch (err) {\n console.error('Failed to access microphone:', err);\n setError(err instanceof Error ? err : new Error('Failed to access microphone'));\n setHasPermission(false);\n } finally {\n setIsLoading(false);\n }\n },\n [stream, enumerateDevices]\n );\n\n // Stop the stream and revoke access\n const stopStream = useCallback(() => {\n if (stream) {\n stream.getTracks().forEach((track) => track.stop());\n setStream(null);\n setHasPermission(false);\n }\n }, [stream]);\n\n // Check initial permission state and enumerate devices\n useEffect(() => {\n // Try to enumerate devices (labels won't be available without permission)\n enumerateDevices();\n\n // Cleanup on unmount\n return () => {\n if (stream) {\n stream.getTracks().forEach((track) => track.stop());\n }\n };\n }, [enumerateDevices, stream]);\n\n return {\n stream,\n devices,\n hasPermission,\n isLoading,\n requestAccess,\n stopStream,\n error,\n };\n}\n","/**\n * Hook for monitoring microphone input levels\n *\n * Uses Tone.js Meter for real-time audio level monitoring.\n */\n\nimport { useEffect, useState, useRef } from 'react';\nimport { Meter, getContext, connect } from 'tone';\n\nexport interface UseMicrophoneLevelOptions {\n /**\n * How often to update the level (in Hz)\n * Default: 60 (60fps)\n */\n updateRate?: number;\n\n /**\n * FFT size for the analyser\n * Default: 256\n */\n fftSize?: number;\n\n /**\n * Smoothing time constant (0-1)\n * Higher values = smoother but slower response\n * Default: 0.8\n */\n smoothingTimeConstant?: number;\n}\n\nexport interface UseMicrophoneLevelReturn {\n /**\n * Current audio level (0-1)\n * 0 = silence, 1 = maximum level\n */\n level: number;\n\n /**\n * Peak level since last reset (0-1)\n */\n peakLevel: number;\n\n /**\n * Reset the peak level\n */\n resetPeak: () => void;\n}\n\n/**\n * Monitor microphone input levels in real-time\n *\n * @param stream - MediaStream from getUserMedia\n * @param options - Configuration options\n * @returns Object with current level and peak level\n *\n * @example\n * ```typescript\n * const { stream } = useMicrophoneAccess();\n * const { level, peakLevel, resetPeak } = useMicrophoneLevel(stream);\n *\n * return <VUMeter level={level} peakLevel={peakLevel} />;\n * ```\n */\nexport function useMicrophoneLevel(\n stream: MediaStream | null,\n options: UseMicrophoneLevelOptions = {}\n): UseMicrophoneLevelReturn {\n const { updateRate = 60, smoothingTimeConstant = 0.8 } = options;\n\n const [level, setLevel] = useState(0);\n const [peakLevel, setPeakLevel] = useState(0);\n\n const meterRef = useRef<Meter | null>(null);\n const sourceRef = useRef<MediaStreamAudioSourceNode | null>(null);\n const animationFrameRef = useRef<number | null>(null);\n\n const resetPeak = () => setPeakLevel(0);\n\n useEffect(() => {\n if (!stream) {\n setLevel(0);\n setPeakLevel(0);\n return;\n }\n\n let isMounted = true;\n\n // Setup audio monitoring\n const setupMonitoring = async () => {\n if (!isMounted) return;\n\n // Get Tone's context and resume if needed\n const context = getContext();\n if (context.state === 'suspended') {\n await context.resume();\n }\n\n if (!isMounted) return;\n\n // Create Tone.js Meter for level monitoring\n // Pass context to ensure it's created in the same context as the source\n const meter = new Meter({ smoothing: smoothingTimeConstant, context });\n meterRef.current = meter;\n\n // Create MediaStreamSource from the SAME context as the meter\n // Note: This creates a separate source from useRecording, but that's OK\n // since we're only using it for level monitoring (not recording)\n const source = context.createMediaStreamSource(stream);\n sourceRef.current = source;\n\n // Connect source to meter using Tone's connect function\n connect(source, meter);\n\n // Start level monitoring\n const updateInterval = 1000 / updateRate;\n let lastUpdateTime = 0;\n\n const updateLevel = (timestamp: number) => {\n if (!isMounted || !meterRef.current) return;\n\n if (timestamp - lastUpdateTime >= updateInterval) {\n lastUpdateTime = timestamp;\n\n // Meter.getValue() returns dB, convert to 0-1 range\n const db = meterRef.current.getValue();\n const dbValue = typeof db === 'number' ? db : db[0];\n // dB is typically -Infinity to 0, map -100dB..0dB to 0..1\n // Using -100dB as floor since Firefox seems to report lower values\n const normalized = Math.max(0, Math.min(1, (dbValue + 100) / 100));\n\n setLevel(normalized);\n setPeakLevel((prev) => Math.max(prev, normalized));\n }\n\n animationFrameRef.current = requestAnimationFrame(updateLevel);\n };\n\n animationFrameRef.current = requestAnimationFrame(updateLevel);\n };\n\n setupMonitoring();\n\n // Cleanup\n return () => {\n isMounted = false;\n\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n\n // Disconnect and clean up\n if (sourceRef.current) {\n try {\n sourceRef.current.disconnect();\n } catch {\n // Ignore disconnect errors\n }\n sourceRef.current = null;\n }\n\n if (meterRef.current) {\n meterRef.current.dispose();\n meterRef.current = null;\n }\n };\n }, [stream, smoothingTimeConstant, updateRate]);\n\n return {\n level,\n peakLevel,\n resetPeak,\n };\n}\n","/**\n * Hook for integrated multi-track recording\n * Combines recording functionality with track management\n */\n\nimport { useState, useCallback, useEffect } from 'react';\nimport { useRecording } from './useRecording';\nimport { useMicrophoneAccess } from './useMicrophoneAccess';\nimport { useMicrophoneLevel } from './useMicrophoneLevel';\nimport type { MicrophoneDevice } from '../types';\nimport { type ClipTrack, type AudioClip } from '@waveform-playlist/core';\nimport { resumeGlobalAudioContext } from '@waveform-playlist/playout';\n\nexport interface IntegratedRecordingOptions {\n /**\n * Current playback/cursor position in seconds\n * Recording will start from max(currentTime, lastClipEndTime)\n */\n currentTime?: number;\n\n /**\n * MediaTrackConstraints for audio recording\n * These will override the recording-optimized defaults (echo cancellation off, low latency)\n */\n audioConstraints?: MediaTrackConstraints;\n\n /**\n * Number of channels to record (1 = mono, 2 = stereo)\n * Default: 1 (mono)\n */\n channelCount?: number;\n\n /**\n * Samples per pixel for peak generation\n * Default: 1024\n */\n samplesPerPixel?: number;\n}\n\nexport interface UseIntegratedRecordingReturn {\n // Recording state\n isRecording: boolean;\n isPaused: boolean;\n duration: number;\n level: number;\n peakLevel: number;\n error: Error | null;\n\n // Microphone state\n stream: MediaStream | null;\n devices: MicrophoneDevice[];\n hasPermission: boolean;\n selectedDevice: string | null;\n\n // Recording controls\n startRecording: () => void;\n stopRecording: () => void;\n pauseRecording: () => void;\n resumeRecording: () => void;\n requestMicAccess: () => Promise<void>;\n changeDevice: (deviceId: string) => Promise<void>;\n\n // Track state (for live waveform during recording)\n recordingPeaks: (Int8Array | Int16Array)[];\n}\n\nexport function useIntegratedRecording(\n tracks: ClipTrack[],\n setTracks: (tracks: ClipTrack[]) => void,\n selectedTrackId: string | null,\n options: IntegratedRecordingOptions = {}\n): UseIntegratedRecordingReturn {\n const { currentTime = 0, audioConstraints, ...recordingOptions } = options;\n\n // Track if we're currently monitoring (for auto-resume audio context)\n const [isMonitoring, setIsMonitoring] = useState(false);\n const [selectedDevice, setSelectedDevice] = useState<string | null>(null);\n const [hookError, setHookError] = useState<Error | null>(null);\n\n // Microphone access\n const { stream, devices, hasPermission, requestAccess, error: micError } = useMicrophoneAccess();\n\n // Microphone level (for VU meter)\n const { level, peakLevel } = useMicrophoneLevel(stream);\n\n // Recording\n const {\n isRecording,\n isPaused,\n duration,\n peaks,\n audioBuffer: _recordedAudioBuffer,\n startRecording: startRec,\n stopRecording: stopRec,\n pauseRecording,\n resumeRecording,\n error: recError,\n } = useRecording(stream, recordingOptions);\n\n // Start recording handler\n const startRecording = useCallback(async () => {\n if (!selectedTrackId) {\n setHookError(\n new Error('Cannot start recording: no track selected. Select or create a track first.')\n );\n return;\n }\n\n try {\n setHookError(null);\n // Resume audio context if needed\n if (!isMonitoring) {\n await resumeGlobalAudioContext();\n setIsMonitoring(true);\n }\n\n await startRec();\n } catch (err) {\n setHookError(err instanceof Error ? err : new Error(String(err)));\n }\n }, [selectedTrackId, isMonitoring, startRec]);\n\n // Stop recording and add clip to selected track\n const stopRecording = useCallback(async () => {\n let buffer: AudioBuffer | null;\n try {\n buffer = await stopRec();\n } catch (err) {\n setHookError(err instanceof Error ? err : new Error(String(err)));\n return;\n }\n\n // Add clip to track after recording completes\n if (buffer && selectedTrackId) {\n const selectedTrackIndex = tracks.findIndex((t) => t.id === selectedTrackId);\n if (selectedTrackIndex === -1) {\n const err = new Error(\n `Recording completed but track \"${selectedTrackId}\" no longer exists. The recorded audio could not be saved.`\n );\n console.error(`[waveform-playlist] ${err.message}`);\n setHookError(err);\n return;\n }\n\n const selectedTrack = tracks[selectedTrackIndex];\n\n // Calculate start position: max(currentTime, lastClipEndTime)\n const currentTimeSamples = Math.floor(currentTime * buffer.sampleRate);\n\n let lastClipEndSample = 0;\n if (selectedTrack.clips.length > 0) {\n // Find the end time of the last clip (in samples)\n const endSamples = selectedTrack.clips.map(\n (clip) => clip.startSample + clip.durationSamples\n );\n lastClipEndSample = Math.max(...endSamples);\n }\n\n // Use whichever is greater: cursor position or last clip end\n const startSample = Math.max(currentTimeSamples, lastClipEndSample);\n\n // Create new clip from recording\n const newClip: AudioClip = {\n id: `clip-${Date.now()}`,\n audioBuffer: buffer,\n startSample,\n durationSamples: buffer.length,\n offsetSamples: 0,\n sampleRate: buffer.sampleRate,\n sourceDurationSamples: buffer.length,\n gain: 1.0,\n name: `Recording ${new Date().toLocaleTimeString()}`,\n };\n\n // Add clip to track\n const newTracks = tracks.map((track, index) => {\n if (index === selectedTrackIndex) {\n return {\n ...track,\n clips: [...track.clips, newClip],\n };\n }\n return track;\n });\n\n setTracks(newTracks);\n }\n }, [selectedTrackId, tracks, setTracks, currentTime, stopRec]);\n\n // Auto-select the first device when devices become available\n useEffect(() => {\n // Only auto-select if we have permission, devices are available, and nothing is selected yet\n if (hasPermission && devices.length > 0 && selectedDevice === null) {\n setSelectedDevice(devices[0].deviceId);\n }\n }, [hasPermission, devices, selectedDevice]);\n\n // Request microphone access\n const requestMicAccess = useCallback(async () => {\n try {\n setHookError(null);\n await requestAccess(undefined, audioConstraints);\n await resumeGlobalAudioContext();\n setIsMonitoring(true);\n } catch (err) {\n setHookError(err instanceof Error ? err : new Error(String(err)));\n }\n }, [requestAccess, audioConstraints]);\n\n // Change device\n const changeDevice = useCallback(\n async (deviceId: string) => {\n try {\n setHookError(null);\n setSelectedDevice(deviceId);\n await requestAccess(deviceId, audioConstraints);\n await resumeGlobalAudioContext();\n setIsMonitoring(true);\n } catch (err) {\n setHookError(err instanceof Error ? err : new Error(String(err)));\n }\n },\n [requestAccess, audioConstraints]\n );\n\n return {\n // Recording state\n isRecording,\n isPaused,\n duration,\n level,\n peakLevel,\n error: hookError || micError || recError,\n\n // Microphone state\n stream,\n devices,\n hasPermission,\n selectedDevice,\n\n // Recording controls\n startRecording,\n stopRecording,\n pauseRecording,\n resumeRecording,\n requestMicAccess,\n changeDevice,\n\n // Track state\n recordingPeaks: peaks,\n };\n}\n","/**\n * RecordButton - Control button for starting/stopping recording\n */\n\nimport React from 'react';\nimport styled from 'styled-components';\n\nexport interface RecordButtonProps {\n isRecording: boolean;\n onClick: () => void;\n disabled?: boolean;\n className?: string;\n}\n\nconst Button = styled.button<{ $isRecording: boolean }>`\n padding: 0.5rem 1rem;\n font-size: 0.875rem;\n font-weight: 500;\n border: none;\n border-radius: 0.25rem;\n cursor: pointer;\n transition: all 0.2s ease-in-out;\n background: ${(props) => (props.$isRecording ? '#dc3545' : '#e74c3c')};\n color: white;\n\n &:hover:not(:disabled) {\n background: ${(props) => (props.$isRecording ? '#c82333' : '#c0392b')};\n transform: translateY(-1px);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n }\n\n &:active:not(:disabled) {\n transform: translateY(0);\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n &:focus {\n outline: none;\n box-shadow: 0 0 0 3px rgba(231, 76, 60, 0.3);\n }\n`;\n\nconst RecordingIndicator = styled.span`\n display: inline-block;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: white;\n margin-right: 0.5rem;\n animation: pulse 1.5s ease-in-out infinite;\n\n @keyframes pulse {\n 0%,\n 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.3;\n }\n }\n`;\n\nexport const RecordButton: React.FC<RecordButtonProps> = ({\n isRecording,\n onClick,\n disabled = false,\n className,\n}) => {\n return (\n <Button\n $isRecording={isRecording}\n onClick={onClick}\n disabled={disabled}\n className={className}\n aria-label={isRecording ? 'Stop recording' : 'Start recording'}\n >\n {isRecording && <RecordingIndicator />}\n {isRecording ? 'Stop Recording' : 'Record'}\n </Button>\n );\n};\n","/**\n * MicrophoneSelector - Dropdown for selecting microphone input device\n */\n\nimport React from 'react';\nimport styled from 'styled-components';\nimport { BaseSelect, BaseLabel } from '@waveform-playlist/ui-components';\nimport { MicrophoneDevice } from '../types';\n\nexport interface MicrophoneSelectorProps {\n devices: MicrophoneDevice[];\n selectedDeviceId?: string;\n onDeviceChange: (deviceId: string) => void;\n disabled?: boolean;\n className?: string;\n}\n\nconst Select = styled(BaseSelect)`\n min-width: 200px;\n`;\n\nconst Label = styled(BaseLabel)`\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n`;\n\nexport const MicrophoneSelector: React.FC<MicrophoneSelectorProps> = ({\n devices,\n selectedDeviceId,\n onDeviceChange,\n disabled = false,\n className,\n}) => {\n const handleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {\n onDeviceChange(event.target.value);\n };\n\n // Use first device if no selection provided\n const currentValue = selectedDeviceId || (devices.length > 0 ? devices[0].deviceId : '');\n\n return (\n <Label className={className}>\n Microphone\n <Select\n value={currentValue}\n onChange={handleChange}\n disabled={disabled || devices.length === 0}\n >\n {devices.length === 0 ? (\n <option value=\"\">No microphones found</option>\n ) : (\n devices.map((device) => (\n <option key={device.deviceId} value={device.deviceId}>\n {device.label}\n </option>\n ))\n )}\n </Select>\n </Label>\n );\n};\n","/**\n * RecordingIndicator - Shows recording status, duration, and visual indicator\n */\n\nimport React from 'react';\nimport styled from 'styled-components';\n\nexport interface RecordingIndicatorProps {\n isRecording: boolean;\n isPaused?: boolean;\n duration: number; // in seconds\n formatTime?: (seconds: number) => string;\n className?: string;\n}\n\nconst Container = styled.div<{ $isRecording: boolean }>`\n display: flex;\n align-items: center;\n gap: 0.75rem;\n padding: 0.5rem 0.75rem;\n background: ${(props) => (props.$isRecording ? '#fff3cd' : 'transparent')};\n border-radius: 0.25rem;\n transition: background 0.2s ease-in-out;\n`;\n\nconst Dot = styled.div<{ $isRecording: boolean; $isPaused: boolean }>`\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background: ${(props) => (props.$isPaused ? '#ffc107' : '#dc3545')};\n opacity: ${(props) => (props.$isRecording ? 1 : 0)};\n transition: opacity 0.2s ease-in-out;\n\n ${(props) =>\n props.$isRecording &&\n !props.$isPaused &&\n `\n animation: blink 1.5s ease-in-out infinite;\n\n @keyframes blink {\n 0%, 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.3;\n }\n }\n `}\n`;\n\nconst Duration = styled.span`\n font-family: 'Courier New', Monaco, monospace;\n font-size: 1rem;\n font-weight: 600;\n color: #495057;\n min-width: 70px;\n`;\n\nconst Status = styled.span<{ $isPaused: boolean }>`\n font-size: 0.75rem;\n font-weight: 500;\n color: ${(props) => (props.$isPaused ? '#ffc107' : '#dc3545')};\n text-transform: uppercase;\n`;\n\nconst defaultFormatTime = (seconds: number): string => {\n const mins = Math.floor(seconds / 60);\n const secs = Math.floor(seconds % 60);\n return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;\n};\n\nexport const RecordingIndicator: React.FC<RecordingIndicatorProps> = ({\n isRecording,\n isPaused = false,\n duration,\n formatTime = defaultFormatTime,\n className,\n}) => {\n return (\n <Container $isRecording={isRecording} className={className}>\n <Dot $isRecording={isRecording} $isPaused={isPaused} />\n <Duration>{formatTime(duration)}</Duration>\n {isRecording && <Status $isPaused={isPaused}>{isPaused ? 'Paused' : 'Recording'}</Status>}\n </Container>\n );\n};\n","/**\n * VU Meter Component\n *\n * Displays real-time audio input levels with color-coded zones\n * and peak indicator.\n */\n\nimport React from 'react';\nimport styled from 'styled-components';\n\nexport interface VUMeterProps {\n /**\n * Current audio level (0-1)\n */\n level: number;\n\n /**\n * Peak level (0-1)\n * Optional - if provided, shows peak indicator\n */\n peakLevel?: number;\n\n /**\n * Width of the meter in pixels\n * Default: 200\n */\n width?: number;\n\n /**\n * Height of the meter in pixels\n * Default: 20\n */\n height?: number;\n\n /**\n * Additional CSS class name\n */\n className?: string;\n}\n\nconst MeterContainer = styled.div<{ $width: number; $height: number }>`\n position: relative;\n width: ${(props) => props.$width}px;\n height: ${(props) => props.$height}px;\n background: #2c3e50;\n border-radius: 4px;\n overflow: hidden;\n box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);\n`;\n\n// Helper to get gradient color based on level\nconst getLevelGradient = (level: number): string => {\n if (level < 0.6) return 'linear-gradient(90deg, #27ae60, #2ecc71)';\n if (level < 0.85) return 'linear-gradient(90deg, #f39c12, #f1c40f)';\n return 'linear-gradient(90deg, #c0392b, #e74c3c)';\n};\n\n// Use .attrs() for frequently changing styles to avoid generating new CSS classes\nconst MeterFill = styled.div.attrs<{ $level: number; $height: number }>((props) => ({\n style: {\n width: `${props.$level * 100}%`,\n height: `${props.$height}px`,\n background: getLevelGradient(props.$level),\n boxShadow: props.$level > 0.01 ? '0 0 8px rgba(255, 255, 255, 0.3)' : 'none',\n },\n}))<{ $level: number; $height: number }>`\n position: absolute;\n left: 0;\n top: 0;\n transition:\n width 0.05s ease-out,\n background 0.1s ease-out;\n`;\n\n// Use .attrs() for frequently changing left position\nconst PeakIndicator = styled.div.attrs<{ $peakLevel: number; $height: number }>((props) => ({\n style: {\n left: `${props.$peakLevel * 100}%`,\n height: `${props.$height}px`,\n },\n}))<{ $peakLevel: number; $height: number }>`\n position: absolute;\n top: 0;\n width: 2px;\n background: #ecf0f1;\n box-shadow: 0 0 4px rgba(236, 240, 241, 0.8);\n transition: left 0.1s ease-out;\n`;\n\nconst ScaleMarkers = styled.div<{ $height: number }>`\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: ${(props) => props.$height}px;\n pointer-events: none;\n`;\n\nconst ScaleMark = styled.div<{ $position: number; $height: number }>`\n position: absolute;\n left: ${(props) => props.$position}%;\n top: 0;\n width: 1px;\n height: ${(props) => props.$height}px;\n background: rgba(255, 255, 255, 0.2);\n`;\n\n/**\n * VU Meter component for displaying audio input levels\n *\n * @example\n * ```typescript\n * import { useMicrophoneLevel, VUMeter } from '@waveform-playlist/recording';\n *\n * const { level, peakLevel } = useMicrophoneLevel(stream);\n *\n * return <VUMeter level={level} peakLevel={peakLevel} width={300} height={24} />;\n * ```\n */\nconst VUMeterComponent: React.FC<VUMeterProps> = ({\n level,\n peakLevel,\n width = 200,\n height = 20,\n className,\n}) => {\n // Clamp values to 0-1 range\n const clampedLevel = Math.max(0, Math.min(1, level));\n const clampedPeak = peakLevel !== undefined ? Math.max(0, Math.min(1, peakLevel)) : 0;\n\n return (\n <MeterContainer $width={width} $height={height} className={className}>\n <MeterFill $level={clampedLevel} $height={height} />\n\n {peakLevel !== undefined && clampedPeak > 0 && (\n <PeakIndicator $peakLevel={clampedPeak} $height={height} />\n )}\n\n <ScaleMarkers $height={height}>\n <ScaleMark $position={60} $height={height} />\n <ScaleMark $position={85} $height={height} />\n </ScaleMarkers>\n </MeterContainer>\n );\n};\n\n// Memoize to prevent unnecessary re-renders when parent updates\nexport const VUMeter = React.memo(VUMeterComponent);\n"],"mappings":"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","names":["RecordingIndicator","newPeaks","result","import_react","import_react","import_tone","import_react","styled","import_styled_components","import_jsx_runtime","styled","import_styled_components","import_jsx_runtime","styled","RecordingIndicator","import_react","import_styled_components","import_jsx_runtime","styled","React"]}