@waveform-playlist/recording 9.4.0 → 9.5.0

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/worklet/recording-processor.worklet.ts"],"sourcesContent":["/**\n * RecordingProcessor - AudioWorklet processor for capturing raw audio data\n *\n * This processor runs in the AudioWorklet thread and captures audio samples\n * at the browser's native sample rate. It buffers samples and sends them to\n * the main thread at regular intervals (~16ms) for peak generation and\n * waveform visualization.\n *\n * Message Format (to main thread):\n * {\n * samples: Float32Array, // Audio samples for this chunk\n * sampleRate: number, // Sample rate of the audio\n * channelCount: number // Number of channels\n * }\n *\n * Note: VU meter levels are handled by AnalyserNode in useMicrophoneLevel hook,\n * not by this worklet.\n */\n\n// Type declarations for AudioWorklet context\ndeclare const sampleRate: number;\n\ninterface AudioParamDescriptor {\n name: string;\n defaultValue?: number;\n minValue?: number;\n maxValue?: number;\n automationRate?: 'a-rate' | 'k-rate';\n}\n\ndeclare class AudioWorkletProcessor {\n readonly port: MessagePort;\n process(\n inputs: Float32Array[][],\n outputs: Float32Array[][],\n parameters: Record<string, Float32Array>\n ): boolean;\n}\ndeclare function registerProcessor(\n name: string,\n processorCtor: (new (options?: AudioWorkletNodeOptions) => AudioWorkletProcessor) & {\n parameterDescriptors?: AudioParamDescriptor[];\n }\n): void;\n\ninterface RecordingProcessorMessage {\n samples: Float32Array;\n sampleRate: number;\n channelCount: number;\n}\n\nclass RecordingProcessor extends AudioWorkletProcessor {\n private buffers: Float32Array[];\n private bufferSize: number;\n private samplesCollected: number;\n private isRecording: boolean;\n private channelCount: number;\n\n constructor() {\n super();\n\n // Buffer size for ~16ms at 48kHz (approximately one animation frame)\n // This will be adjusted based on actual sample rate\n this.bufferSize = 0;\n this.buffers = [];\n this.samplesCollected = 0;\n this.isRecording = false;\n this.channelCount = 1;\n\n // Listen for control messages from main thread\n this.port.onmessage = (event) => {\n const { command, sampleRate, channelCount } = event.data;\n\n if (command === 'start') {\n this.isRecording = true;\n this.channelCount = channelCount || 1;\n\n // Calculate buffer size for ~16ms chunks (60 fps)\n // At 48kHz: 48000 * 0.016 = 768 samples\n this.bufferSize = Math.floor((sampleRate || 48000) * 0.016);\n\n // Initialize buffers for each channel\n this.buffers = [];\n for (let i = 0; i < this.channelCount; i++) {\n this.buffers[i] = new Float32Array(this.bufferSize);\n }\n this.samplesCollected = 0;\n } else if (command === 'stop') {\n this.isRecording = false;\n\n // Send any remaining buffered samples\n if (this.samplesCollected > 0) {\n this.flushBuffers();\n }\n }\n };\n }\n\n process(\n inputs: Float32Array[][],\n _outputs: Float32Array[][],\n _parameters: Record<string, Float32Array>\n ): boolean {\n if (!this.isRecording) {\n return true; // Keep processor alive\n }\n\n const input = inputs[0];\n if (!input || input.length === 0) {\n return true; // No input yet, keep alive\n }\n\n const frameCount = input[0].length;\n\n // Process each channel\n for (let channel = 0; channel < Math.min(input.length, this.channelCount); channel++) {\n const inputChannel = input[channel];\n const buffer = this.buffers[channel];\n\n // Copy samples to buffer\n for (let i = 0; i < frameCount; i++) {\n buffer[this.samplesCollected + i] = inputChannel[i];\n }\n }\n\n this.samplesCollected += frameCount;\n\n // When buffer is full, send to main thread\n if (this.samplesCollected >= this.bufferSize) {\n this.flushBuffers();\n }\n\n return true; // Keep processor alive\n }\n\n private flushBuffers(): void {\n // For now, we'll mix down to mono or send the first channel\n // This simplifies peak generation and waveform display\n const samples = this.buffers[0].slice(0, this.samplesCollected);\n\n // Send to main thread\n this.port.postMessage({\n samples: samples,\n sampleRate: sampleRate,\n channelCount: this.channelCount,\n } as RecordingProcessorMessage);\n\n // Reset buffer\n this.samplesCollected = 0;\n }\n}\n\n// Register the processor\nregisterProcessor('recording-processor', RecordingProcessor);\n"],"mappings":";AAmDA,IAAM,qBAAN,cAAiC,sBAAsB;AAAA,EAOrD,cAAc;AACZ,UAAM;AAIN,SAAK,aAAa;AAClB,SAAK,UAAU,CAAC;AAChB,SAAK,mBAAmB;AACxB,SAAK,cAAc;AACnB,SAAK,eAAe;AAGpB,SAAK,KAAK,YAAY,CAAC,UAAU;AAC/B,YAAM,EAAE,SAAS,YAAAA,aAAY,aAAa,IAAI,MAAM;AAEpD,UAAI,YAAY,SAAS;AACvB,aAAK,cAAc;AACnB,aAAK,eAAe,gBAAgB;AAIpC,aAAK,aAAa,KAAK,OAAOA,eAAc,QAAS,KAAK;AAG1D,aAAK,UAAU,CAAC;AAChB,iBAAS,IAAI,GAAG,IAAI,KAAK,cAAc,KAAK;AAC1C,eAAK,QAAQ,CAAC,IAAI,IAAI,aAAa,KAAK,UAAU;AAAA,QACpD;AACA,aAAK,mBAAmB;AAAA,MAC1B,WAAW,YAAY,QAAQ;AAC7B,aAAK,cAAc;AAGnB,YAAI,KAAK,mBAAmB,GAAG;AAC7B,eAAK,aAAa;AAAA,QACpB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,QACE,QACA,UACA,aACS;AACT,QAAI,CAAC,KAAK,aAAa;AACrB,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,OAAO,CAAC;AACtB,QAAI,CAAC,SAAS,MAAM,WAAW,GAAG;AAChC,aAAO;AAAA,IACT;AAEA,UAAM,aAAa,MAAM,CAAC,EAAE;AAG5B,aAAS,UAAU,GAAG,UAAU,KAAK,IAAI,MAAM,QAAQ,KAAK,YAAY,GAAG,WAAW;AACpF,YAAM,eAAe,MAAM,OAAO;AAClC,YAAM,SAAS,KAAK,QAAQ,OAAO;AAGnC,eAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,eAAO,KAAK,mBAAmB,CAAC,IAAI,aAAa,CAAC;AAAA,MACpD;AAAA,IACF;AAEA,SAAK,oBAAoB;AAGzB,QAAI,KAAK,oBAAoB,KAAK,YAAY;AAC5C,WAAK,aAAa;AAAA,IACpB;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,eAAqB;AAG3B,UAAM,UAAU,KAAK,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,gBAAgB;AAG9D,SAAK,KAAK,YAAY;AAAA,MACpB;AAAA,MACA;AAAA,MACA,cAAc,KAAK;AAAA,IACrB,CAA8B;AAG9B,SAAK,mBAAmB;AAAA,EAC1B;AACF;AAGA,kBAAkB,uBAAuB,kBAAkB;","names":["sampleRate"]}
1
+ {"version":3,"sources":["../../src/worklet/recording-processor.worklet.ts"],"sourcesContent":["/**\n * RecordingProcessor - AudioWorklet processor for capturing raw audio data\n *\n * This processor runs in the AudioWorklet thread and captures audio samples\n * at the browser's native sample rate. It buffers samples and sends them to\n * the main thread at regular intervals (~16ms) for peak generation and\n * waveform visualization.\n *\n * Message Format (to main thread):\n * {\n * channels: Float32Array[], // Per-channel audio samples for this chunk\n * sampleRate: number, // Sample rate of the audio\n * channelCount: number // Number of channels\n * }\n *\n * Note: VU meter levels are handled by AnalyserNode in useMicrophoneLevel hook,\n * not by this worklet.\n */\n\n// Type declarations for AudioWorklet context\ndeclare const sampleRate: number;\n\ninterface AudioParamDescriptor {\n name: string;\n defaultValue?: number;\n minValue?: number;\n maxValue?: number;\n automationRate?: 'a-rate' | 'k-rate';\n}\n\ndeclare class AudioWorkletProcessor {\n readonly port: MessagePort;\n process(\n inputs: Float32Array[][],\n outputs: Float32Array[][],\n parameters: Record<string, Float32Array>\n ): boolean;\n}\ndeclare function registerProcessor(\n name: string,\n processorCtor: (new (options?: AudioWorkletNodeOptions) => AudioWorkletProcessor) & {\n parameterDescriptors?: AudioParamDescriptor[];\n }\n): void;\n\ninterface RecordingProcessorMessage {\n channels: Float32Array[];\n sampleRate: number;\n channelCount: number;\n}\n\nclass RecordingProcessor extends AudioWorkletProcessor {\n private buffers: Float32Array[];\n private bufferSize: number;\n private samplesCollected: number;\n private isRecording: boolean;\n private channelCount: number;\n\n constructor() {\n super();\n\n // Buffer size for ~16ms at 48kHz (approximately one animation frame)\n // This will be adjusted based on actual sample rate\n this.bufferSize = 0;\n this.buffers = [];\n this.samplesCollected = 0;\n this.isRecording = false;\n this.channelCount = 1;\n\n // Listen for control messages from main thread\n this.port.onmessage = (event) => {\n const { command, sampleRate, channelCount } = event.data;\n\n if (command === 'start') {\n this.isRecording = true;\n this.channelCount = channelCount || 1;\n\n // Calculate buffer size for ~16ms chunks (60 fps)\n // At 48kHz: 48000 * 0.016 = 768 samples\n this.bufferSize = Math.floor((sampleRate || 48000) * 0.016);\n\n // Initialize buffers for each channel\n this.buffers = [];\n for (let i = 0; i < this.channelCount; i++) {\n this.buffers[i] = new Float32Array(this.bufferSize);\n }\n this.samplesCollected = 0;\n } else if (command === 'stop') {\n this.isRecording = false;\n\n // Send any remaining buffered samples\n if (this.samplesCollected > 0) {\n this.flushBuffers();\n }\n }\n };\n }\n\n process(\n inputs: Float32Array[][],\n _outputs: Float32Array[][],\n _parameters: Record<string, Float32Array>\n ): boolean {\n if (!this.isRecording) {\n return true; // Keep processor alive\n }\n\n const input = inputs[0];\n if (!input || input.length === 0) {\n return true; // No input yet, keep alive\n }\n\n const frameCount = input[0].length;\n\n if (this.bufferSize <= 0) {\n return true; // Not yet configured via 'start' command\n }\n\n let offset = 0;\n\n // Process samples in chunks that fit within the buffer.\n // The AudioWorklet quantum (128 samples) may not divide evenly into\n // bufferSize (e.g., 705 at 44100Hz), so a single frame can cross\n // the buffer boundary. Without this loop, samples beyond bufferSize\n // are silently dropped by the typed array, causing audio gaps.\n while (offset < frameCount) {\n const remaining = this.bufferSize - this.samplesCollected;\n const toCopy = Math.min(remaining, frameCount - offset);\n\n for (let channel = 0; channel < Math.min(input.length, this.channelCount); channel++) {\n const inputChannel = input[channel];\n const buffer = this.buffers[channel];\n\n for (let i = 0; i < toCopy; i++) {\n buffer[this.samplesCollected + i] = inputChannel[offset + i];\n }\n }\n\n this.samplesCollected += toCopy;\n offset += toCopy;\n\n // When buffer is full, send to main thread\n if (this.samplesCollected >= this.bufferSize) {\n this.flushBuffers();\n }\n }\n\n return true; // Keep processor alive\n }\n\n private flushBuffers(): void {\n // Send all channel buffers to main thread\n const channels: Float32Array[] = [];\n for (let i = 0; i < this.channelCount; i++) {\n channels.push(this.buffers[i].slice(0, this.samplesCollected));\n }\n\n this.port.postMessage({\n channels,\n sampleRate: sampleRate,\n channelCount: this.channelCount,\n } as RecordingProcessorMessage);\n\n // Reset buffer\n this.samplesCollected = 0;\n }\n}\n\n// Register the processor\nregisterProcessor('recording-processor', RecordingProcessor);\n"],"mappings":";AAmDA,IAAM,qBAAN,cAAiC,sBAAsB;AAAA,EAOrD,cAAc;AACZ,UAAM;AAIN,SAAK,aAAa;AAClB,SAAK,UAAU,CAAC;AAChB,SAAK,mBAAmB;AACxB,SAAK,cAAc;AACnB,SAAK,eAAe;AAGpB,SAAK,KAAK,YAAY,CAAC,UAAU;AAC/B,YAAM,EAAE,SAAS,YAAAA,aAAY,aAAa,IAAI,MAAM;AAEpD,UAAI,YAAY,SAAS;AACvB,aAAK,cAAc;AACnB,aAAK,eAAe,gBAAgB;AAIpC,aAAK,aAAa,KAAK,OAAOA,eAAc,QAAS,KAAK;AAG1D,aAAK,UAAU,CAAC;AAChB,iBAAS,IAAI,GAAG,IAAI,KAAK,cAAc,KAAK;AAC1C,eAAK,QAAQ,CAAC,IAAI,IAAI,aAAa,KAAK,UAAU;AAAA,QACpD;AACA,aAAK,mBAAmB;AAAA,MAC1B,WAAW,YAAY,QAAQ;AAC7B,aAAK,cAAc;AAGnB,YAAI,KAAK,mBAAmB,GAAG;AAC7B,eAAK,aAAa;AAAA,QACpB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,QACE,QACA,UACA,aACS;AACT,QAAI,CAAC,KAAK,aAAa;AACrB,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,OAAO,CAAC;AACtB,QAAI,CAAC,SAAS,MAAM,WAAW,GAAG;AAChC,aAAO;AAAA,IACT;AAEA,UAAM,aAAa,MAAM,CAAC,EAAE;AAE5B,QAAI,KAAK,cAAc,GAAG;AACxB,aAAO;AAAA,IACT;AAEA,QAAI,SAAS;AAOb,WAAO,SAAS,YAAY;AAC1B,YAAM,YAAY,KAAK,aAAa,KAAK;AACzC,YAAM,SAAS,KAAK,IAAI,WAAW,aAAa,MAAM;AAEtD,eAAS,UAAU,GAAG,UAAU,KAAK,IAAI,MAAM,QAAQ,KAAK,YAAY,GAAG,WAAW;AACpF,cAAM,eAAe,MAAM,OAAO;AAClC,cAAM,SAAS,KAAK,QAAQ,OAAO;AAEnC,iBAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,iBAAO,KAAK,mBAAmB,CAAC,IAAI,aAAa,SAAS,CAAC;AAAA,QAC7D;AAAA,MACF;AAEA,WAAK,oBAAoB;AACzB,gBAAU;AAGV,UAAI,KAAK,oBAAoB,KAAK,YAAY;AAC5C,aAAK,aAAa;AAAA,MACpB;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,eAAqB;AAE3B,UAAM,WAA2B,CAAC;AAClC,aAAS,IAAI,GAAG,IAAI,KAAK,cAAc,KAAK;AAC1C,eAAS,KAAK,KAAK,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,gBAAgB,CAAC;AAAA,IAC/D;AAEA,SAAK,KAAK,YAAY;AAAA,MACpB;AAAA,MACA;AAAA,MACA,cAAc,KAAK;AAAA,IACrB,CAA8B;AAG9B,SAAK,mBAAmB;AAAA,EAC1B;AACF;AAGA,kBAAkB,uBAAuB,kBAAkB;","names":["sampleRate"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waveform-playlist/recording",
3
- "version": "9.4.0",
3
+ "version": "9.5.0",
4
4
  "description": "Audio recording support for waveform-playlist using AudioWorklet",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -43,9 +43,9 @@
43
43
  "vitest": "^3.0.0"
44
44
  },
45
45
  "dependencies": {
46
- "@waveform-playlist/core": "9.4.0",
47
- "@waveform-playlist/ui-components": "9.4.0",
48
- "@waveform-playlist/playout": "9.4.0"
46
+ "@waveform-playlist/core": "9.5.0",
47
+ "@waveform-playlist/ui-components": "9.5.0",
48
+ "@waveform-playlist/playout": "9.5.0"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "react": "^18.0.0",