@waveform-playlist/worklets 10.0.0 → 10.1.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/meter-processor.worklet.ts"],"sourcesContent":["/**\n * MeterProcessor — AudioWorklet processor for sample-accurate peak/RMS metering\n *\n * Pass-through node: audio flows through unchanged while levels are computed.\n * Accumulates peak (max absolute sample) and RMS (root mean square) across all\n * 128-sample quantums, posting results at ~updateRate Hz via postMessage.\n *\n * RMS Strategy: Simple interval average (not sliding window).\n * Trade-off: A sliding window (like openDAW's 100ms circular buffer) provides\n * smoother loudness display. Our interval-based approach may appear jumpier\n * since each update only reflects ~16ms of audio. For visual metering at 60fps\n * the difference is subtle. A circular buffer can be added later without\n * changing the message format or hook API.\n */\n\
|
|
1
|
+
{"version":3,"sources":["../../src/worklet/meter-processor.worklet.ts"],"sourcesContent":["/**\n * MeterProcessor — AudioWorklet processor for sample-accurate peak/RMS metering\n *\n * Pass-through node: audio flows through unchanged while levels are computed.\n * Accumulates peak (max absolute sample) and RMS (root mean square) across all\n * 128-sample quantums, posting results at ~updateRate Hz via postMessage.\n *\n * RMS Strategy: Simple interval average (not sliding window).\n * Trade-off: A sliding window (like openDAW's 100ms circular buffer) provides\n * smoother loudness display. Our interval-based approach may appear jumpier\n * since each update only reflects ~16ms of audio. For visual metering at 60fps\n * the difference is subtle. A circular buffer can be added later without\n * changing the message format or hook API.\n */\n\ninterface MeterProcessorOptions {\n numberOfChannels: number;\n updateRate: number;\n}\n\nclass MeterProcessor extends AudioWorkletProcessor {\n private numberOfChannels: number;\n private blocksPerUpdate: number;\n private blocksProcessed: number;\n private maxPeak: number[];\n private sumSquares: number[];\n private sampleCount: number[];\n\n constructor(options: { processorOptions: MeterProcessorOptions }) {\n super();\n const { numberOfChannels, updateRate } = options.processorOptions;\n this.numberOfChannels = numberOfChannels;\n this.blocksPerUpdate = Math.max(1, Math.floor(sampleRate / (128 * updateRate)));\n this.blocksProcessed = 0;\n this.maxPeak = new Array(numberOfChannels).fill(0);\n this.sumSquares = new Array(numberOfChannels).fill(0);\n this.sampleCount = new Array(numberOfChannels).fill(0);\n }\n\n process(\n inputs: Float32Array[][],\n outputs: Float32Array[][],\n _parameters: Record<string, Float32Array>\n ): boolean {\n const input = inputs[0];\n const output = outputs[0];\n\n if (!input || input.length === 0) {\n return true;\n }\n\n for (let ch = 0; ch < output.length; ch++) {\n const inputChannel = input[ch];\n const outputChannel = output[ch];\n if (inputChannel && outputChannel) {\n outputChannel.set(inputChannel);\n }\n }\n\n for (let ch = 0; ch < this.numberOfChannels; ch++) {\n const inputChannel = input[ch];\n if (!inputChannel) continue;\n\n let peak = this.maxPeak[ch];\n let sum = this.sumSquares[ch];\n\n for (let i = 0; i < inputChannel.length; i++) {\n const sample = inputChannel[i];\n const abs = Math.abs(sample);\n if (abs > peak) peak = abs;\n sum += sample * sample;\n }\n\n this.maxPeak[ch] = peak;\n this.sumSquares[ch] = sum;\n this.sampleCount[ch] += inputChannel.length;\n }\n\n this.blocksProcessed++;\n\n if (this.blocksProcessed >= this.blocksPerUpdate) {\n const peak: number[] = [];\n const rms: number[] = [];\n\n for (let ch = 0; ch < this.numberOfChannels; ch++) {\n peak.push(this.maxPeak[ch]);\n const count = this.sampleCount[ch];\n rms.push(count > 0 ? Math.sqrt(this.sumSquares[ch] / count) : 0);\n }\n\n this.port.postMessage({ peak, rms });\n\n this.maxPeak.fill(0);\n this.sumSquares.fill(0);\n this.sampleCount.fill(0);\n this.blocksProcessed = 0;\n }\n\n return true;\n }\n}\n\nregisterProcessor('meter-processor', MeterProcessor);\n"],"mappings":";;;AAoBA,IAAM,iBAAN,cAA6B,sBAAsB;AAAA,EAQjD,YAAY,SAAsD;AAChE,UAAM;AACN,UAAM,EAAE,kBAAkB,WAAW,IAAI,QAAQ;AACjD,SAAK,mBAAmB;AACxB,SAAK,kBAAkB,KAAK,IAAI,GAAG,KAAK,MAAM,cAAc,MAAM,WAAW,CAAC;AAC9E,SAAK,kBAAkB;AACvB,SAAK,UAAU,IAAI,MAAM,gBAAgB,EAAE,KAAK,CAAC;AACjD,SAAK,aAAa,IAAI,MAAM,gBAAgB,EAAE,KAAK,CAAC;AACpD,SAAK,cAAc,IAAI,MAAM,gBAAgB,EAAE,KAAK,CAAC;AAAA,EACvD;AAAA,EAEA,QACE,QACA,SACA,aACS;AACT,UAAM,QAAQ,OAAO,CAAC;AACtB,UAAM,SAAS,QAAQ,CAAC;AAExB,QAAI,CAAC,SAAS,MAAM,WAAW,GAAG;AAChC,aAAO;AAAA,IACT;AAEA,aAAS,KAAK,GAAG,KAAK,OAAO,QAAQ,MAAM;AACzC,YAAM,eAAe,MAAM,EAAE;AAC7B,YAAM,gBAAgB,OAAO,EAAE;AAC/B,UAAI,gBAAgB,eAAe;AACjC,sBAAc,IAAI,YAAY;AAAA,MAChC;AAAA,IACF;AAEA,aAAS,KAAK,GAAG,KAAK,KAAK,kBAAkB,MAAM;AACjD,YAAM,eAAe,MAAM,EAAE;AAC7B,UAAI,CAAC,aAAc;AAEnB,UAAI,OAAO,KAAK,QAAQ,EAAE;AAC1B,UAAI,MAAM,KAAK,WAAW,EAAE;AAE5B,eAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,cAAM,SAAS,aAAa,CAAC;AAC7B,cAAM,MAAM,KAAK,IAAI,MAAM;AAC3B,YAAI,MAAM,KAAM,QAAO;AACvB,eAAO,SAAS;AAAA,MAClB;AAEA,WAAK,QAAQ,EAAE,IAAI;AACnB,WAAK,WAAW,EAAE,IAAI;AACtB,WAAK,YAAY,EAAE,KAAK,aAAa;AAAA,IACvC;AAEA,SAAK;AAEL,QAAI,KAAK,mBAAmB,KAAK,iBAAiB;AAChD,YAAM,OAAiB,CAAC;AACxB,YAAM,MAAgB,CAAC;AAEvB,eAAS,KAAK,GAAG,KAAK,KAAK,kBAAkB,MAAM;AACjD,aAAK,KAAK,KAAK,QAAQ,EAAE,CAAC;AAC1B,cAAM,QAAQ,KAAK,YAAY,EAAE;AACjC,YAAI,KAAK,QAAQ,IAAI,KAAK,KAAK,KAAK,WAAW,EAAE,IAAI,KAAK,IAAI,CAAC;AAAA,MACjE;AAEA,WAAK,KAAK,YAAY,EAAE,MAAM,IAAI,CAAC;AAEnC,WAAK,QAAQ,KAAK,CAAC;AACnB,WAAK,WAAW,KAAK,CAAC;AACtB,WAAK,YAAY,KAAK,CAAC;AACvB,WAAK,kBAAkB;AAAA,IACzB;AAEA,WAAO;AAAA,EACT;AACF;AAEA,kBAAkB,mBAAmB,cAAc;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/worklet/meter-processor.worklet.ts"],"sourcesContent":["/**\n * MeterProcessor — AudioWorklet processor for sample-accurate peak/RMS metering\n *\n * Pass-through node: audio flows through unchanged while levels are computed.\n * Accumulates peak (max absolute sample) and RMS (root mean square) across all\n * 128-sample quantums, posting results at ~updateRate Hz via postMessage.\n *\n * RMS Strategy: Simple interval average (not sliding window).\n * Trade-off: A sliding window (like openDAW's 100ms circular buffer) provides\n * smoother loudness display. Our interval-based approach may appear jumpier\n * since each update only reflects ~16ms of audio. For visual metering at 60fps\n * the difference is subtle. A circular buffer can be added later without\n * changing the message format or hook API.\n */\n\
|
|
1
|
+
{"version":3,"sources":["../../src/worklet/meter-processor.worklet.ts"],"sourcesContent":["/**\n * MeterProcessor — AudioWorklet processor for sample-accurate peak/RMS metering\n *\n * Pass-through node: audio flows through unchanged while levels are computed.\n * Accumulates peak (max absolute sample) and RMS (root mean square) across all\n * 128-sample quantums, posting results at ~updateRate Hz via postMessage.\n *\n * RMS Strategy: Simple interval average (not sliding window).\n * Trade-off: A sliding window (like openDAW's 100ms circular buffer) provides\n * smoother loudness display. Our interval-based approach may appear jumpier\n * since each update only reflects ~16ms of audio. For visual metering at 60fps\n * the difference is subtle. A circular buffer can be added later without\n * changing the message format or hook API.\n */\n\ninterface MeterProcessorOptions {\n numberOfChannels: number;\n updateRate: number;\n}\n\nclass MeterProcessor extends AudioWorkletProcessor {\n private numberOfChannels: number;\n private blocksPerUpdate: number;\n private blocksProcessed: number;\n private maxPeak: number[];\n private sumSquares: number[];\n private sampleCount: number[];\n\n constructor(options: { processorOptions: MeterProcessorOptions }) {\n super();\n const { numberOfChannels, updateRate } = options.processorOptions;\n this.numberOfChannels = numberOfChannels;\n this.blocksPerUpdate = Math.max(1, Math.floor(sampleRate / (128 * updateRate)));\n this.blocksProcessed = 0;\n this.maxPeak = new Array(numberOfChannels).fill(0);\n this.sumSquares = new Array(numberOfChannels).fill(0);\n this.sampleCount = new Array(numberOfChannels).fill(0);\n }\n\n process(\n inputs: Float32Array[][],\n outputs: Float32Array[][],\n _parameters: Record<string, Float32Array>\n ): boolean {\n const input = inputs[0];\n const output = outputs[0];\n\n if (!input || input.length === 0) {\n return true;\n }\n\n for (let ch = 0; ch < output.length; ch++) {\n const inputChannel = input[ch];\n const outputChannel = output[ch];\n if (inputChannel && outputChannel) {\n outputChannel.set(inputChannel);\n }\n }\n\n for (let ch = 0; ch < this.numberOfChannels; ch++) {\n const inputChannel = input[ch];\n if (!inputChannel) continue;\n\n let peak = this.maxPeak[ch];\n let sum = this.sumSquares[ch];\n\n for (let i = 0; i < inputChannel.length; i++) {\n const sample = inputChannel[i];\n const abs = Math.abs(sample);\n if (abs > peak) peak = abs;\n sum += sample * sample;\n }\n\n this.maxPeak[ch] = peak;\n this.sumSquares[ch] = sum;\n this.sampleCount[ch] += inputChannel.length;\n }\n\n this.blocksProcessed++;\n\n if (this.blocksProcessed >= this.blocksPerUpdate) {\n const peak: number[] = [];\n const rms: number[] = [];\n\n for (let ch = 0; ch < this.numberOfChannels; ch++) {\n peak.push(this.maxPeak[ch]);\n const count = this.sampleCount[ch];\n rms.push(count > 0 ? Math.sqrt(this.sumSquares[ch] / count) : 0);\n }\n\n this.port.postMessage({ peak, rms });\n\n this.maxPeak.fill(0);\n this.sumSquares.fill(0);\n this.sampleCount.fill(0);\n this.blocksProcessed = 0;\n }\n\n return true;\n }\n}\n\nregisterProcessor('meter-processor', MeterProcessor);\n"],"mappings":";AAoBA,IAAM,iBAAN,cAA6B,sBAAsB;AAAA,EAQjD,YAAY,SAAsD;AAChE,UAAM;AACN,UAAM,EAAE,kBAAkB,WAAW,IAAI,QAAQ;AACjD,SAAK,mBAAmB;AACxB,SAAK,kBAAkB,KAAK,IAAI,GAAG,KAAK,MAAM,cAAc,MAAM,WAAW,CAAC;AAC9E,SAAK,kBAAkB;AACvB,SAAK,UAAU,IAAI,MAAM,gBAAgB,EAAE,KAAK,CAAC;AACjD,SAAK,aAAa,IAAI,MAAM,gBAAgB,EAAE,KAAK,CAAC;AACpD,SAAK,cAAc,IAAI,MAAM,gBAAgB,EAAE,KAAK,CAAC;AAAA,EACvD;AAAA,EAEA,QACE,QACA,SACA,aACS;AACT,UAAM,QAAQ,OAAO,CAAC;AACtB,UAAM,SAAS,QAAQ,CAAC;AAExB,QAAI,CAAC,SAAS,MAAM,WAAW,GAAG;AAChC,aAAO;AAAA,IACT;AAEA,aAAS,KAAK,GAAG,KAAK,OAAO,QAAQ,MAAM;AACzC,YAAM,eAAe,MAAM,EAAE;AAC7B,YAAM,gBAAgB,OAAO,EAAE;AAC/B,UAAI,gBAAgB,eAAe;AACjC,sBAAc,IAAI,YAAY;AAAA,MAChC;AAAA,IACF;AAEA,aAAS,KAAK,GAAG,KAAK,KAAK,kBAAkB,MAAM;AACjD,YAAM,eAAe,MAAM,EAAE;AAC7B,UAAI,CAAC,aAAc;AAEnB,UAAI,OAAO,KAAK,QAAQ,EAAE;AAC1B,UAAI,MAAM,KAAK,WAAW,EAAE;AAE5B,eAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,cAAM,SAAS,aAAa,CAAC;AAC7B,cAAM,MAAM,KAAK,IAAI,MAAM;AAC3B,YAAI,MAAM,KAAM,QAAO;AACvB,eAAO,SAAS;AAAA,MAClB;AAEA,WAAK,QAAQ,EAAE,IAAI;AACnB,WAAK,WAAW,EAAE,IAAI;AACtB,WAAK,YAAY,EAAE,KAAK,aAAa;AAAA,IACvC;AAEA,SAAK;AAEL,QAAI,KAAK,mBAAmB,KAAK,iBAAiB;AAChD,YAAM,OAAiB,CAAC;AACxB,YAAM,MAAgB,CAAC;AAEvB,eAAS,KAAK,GAAG,KAAK,KAAK,kBAAkB,MAAM;AACjD,aAAK,KAAK,KAAK,QAAQ,EAAE,CAAC;AAC1B,cAAM,QAAQ,KAAK,YAAY,EAAE;AACjC,YAAI,KAAK,QAAQ,IAAI,KAAK,KAAK,KAAK,WAAW,EAAE,IAAI,KAAK,IAAI,CAAC;AAAA,MACjE;AAEA,WAAK,KAAK,YAAY,EAAE,MAAM,IAAI,CAAC;AAEnC,WAAK,QAAQ,KAAK,CAAC;AACnB,WAAK,WAAW,KAAK,CAAC;AACtB,WAAK,YAAY,KAAK,CAAC;AACvB,WAAK,kBAAkB;AAAA,IACzB;AAEA,WAAO;AAAA,EACT;AACF;AAEA,kBAAkB,mBAAmB,cAAc;","names":[]}
|
|
@@ -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 * 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 the meter-processor worklet in\n * useMicrophoneLevel hook, not by this worklet.\n */\n\
|
|
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 the meter-processor worklet in\n * useMicrophoneLevel hook, not by this worklet.\n */\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":";;;AAyBA,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"]}
|
|
@@ -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 * 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 the meter-processor worklet in\n * useMicrophoneLevel hook, not by this worklet.\n */\n\
|
|
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 the meter-processor worklet in\n * useMicrophoneLevel hook, not by this worklet.\n */\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":";AAyBA,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/worklets",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.1.0",
|
|
4
4
|
"description": "AudioWorklet processors for waveform-playlist (metering, recording)",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"README.md"
|
|
37
37
|
],
|
|
38
38
|
"devDependencies": {
|
|
39
|
+
"@types/audioworklet": "^0.0.96",
|
|
39
40
|
"tsup": "^8.0.1",
|
|
40
41
|
"typescript": "^5.3.3",
|
|
41
42
|
"vitest": "^3.0.0"
|