Audio Reconstruction Pipeline#5
Conversation
…ing bound validation
add frame count state fix double increment of the framecount
📝 WalkthroughWalkthroughThis PR adds an audio watermarking output synthesis pipeline that reconstructs audio from modified spectral data. It introduces inverse FFT, PCM conversion, overlap-add synthesis, and WAV file writing modules. The profiler and modulator are updated to support state-tracked spectral processing and expanded output generation. Changes
Sequence DiagramsequenceDiagram
participant Profiler as Profiler<br/>(processFrame)
participant FFT as FFT Analysis
participant Modulator as Modulator<br/>(embedChips)
participant IFFT as IFFT<br/>(Recombination)
participant OLA as Overlap-Add<br/>(Reconstruction)
participant PCM as PCM Converter
participant Writer as WAV Writer
Profiler->>FFT: windowedFrame
FFT->>FFT: realTransform +<br/>spectrum completion
FFT-->>Profiler: {fftComplex,<br/>powerSpectrum}
Profiler->>Modulator: fftComplex +<br/>chipMap
Modulator->>Modulator: embed chips<br/>into spectrum
Modulator-->>Profiler: modifiedFFT
Profiler->>IFFT: modifiedFFT<br/>(complex spectrum)
IFFT->>IFFT: inverseTransform
IFFT-->>OLA: timeFrame (PCM)
OLA->>OLA: overlap +<br/>add with buffer
OLA-->>PCM: hopFrame<br/>(512 samples)
PCM->>PCM: scale & clamp<br/>to [-32768,32767]
PCM-->>Writer: int16Frame
Writer->>Writer: accumulate<br/>PCM frames
Writer->>Writer: create WAV<br/>(16-bit, 16kHz)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (3)
src/core/recombination/writer.ts (1)
7-10: Remove dead commented-out code.The commented concatenation approach (lines 7-10) and reset (line 20) should either be implemented or removed to avoid confusion. If the current array-push approach is intentional, clean up the dead code.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/core/recombination/writer.ts` around lines 7 - 10, The file contains dead commented code that attempts to concatenate Int16Array buffers (newBuffer, newBuffer.set) and a commented reset of pcmAccumulator; remove these commented lines or restore a real implementation so the codebase isn't confusing. Specifically, either delete the commented block referencing newBuffer/newBuffer.set and the commented reset of pcmAccumulator in src/core/recombination/writer.ts, or replace the current array-push approach with a working concatenation function that updates pcmAccumulator by creating a new Int16Array, copying pcmAccumulator and pcmFrame into it (using set) and assigning it back to pcmAccumulator; ensure references to pcmAccumulator and pcmFrame are updated consistently.src/core/modulator/embedChips.ts (1)
18-20: Consider logging or throwing on out-of-bounds bin indices.The bounds check silently skips bins with
iIndex >= fftComplex.length. While this prevents crashes, it may mask bugs in the upstreamchipMapgeneration (e.g.,BinMapper.mapToBinsproducing invalid indices).Consider logging a warning or throwing an error to surface configuration issues during development.
💡 Optional: Add diagnostic logging
if (iIndex >= fftComplex.length) { + console.warn(`embedFrameChips: binIndex ${binIndex} out of bounds, skipping`); continue; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/core/modulator/embedChips.ts` around lines 18 - 20, The loop silently skips out-of-bounds bin indices (iIndex >= fftComplex.length); update the code in embedChips.ts where iIndex and fftComplex are used (and where chipMap / BinMapper.mapToBins feed indices) to surface this issue by either logging a warning with context (include iIndex, fftComplex.length, chipId/chipMap entry and any upstream mapping result) or throwing an Error during development; ensure you reference the same variables (iIndex, fftComplex, chipMap) so the log/exception clearly shows the invalid index and the source mapping to help debug upstream BinMapper.mapToBins output.src/core/profiler/processFrame.ts (1)
33-40: Move per-frame logging off the hot path.These
console.logcalls run for every 1024-sample frame in the recording loop. In real-time capture, stdout I/O can become the bottleneck and introduce avoidable latency/dropouts. Please gate them behind a debug flag or remove them before shipping.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/core/profiler/processFrame.ts` around lines 33 - 40, The per-frame console.log calls in processFrame.ts (the two logs around Math.min/Math.max and Found ${safeBins.length}) are on the hot path and must be gated or removed; update the code that calls Math.min/Math.max and the safeBins length log to only run when a debug flag is enabled (e.g., a module-level DEBUG constant or a logger.isDebugEnabled check) so normal real-time capture bypasses stdout I/O, leaving the core functions processFFT, computeBarkEnergy, estimateMasking, and identifySafeBins unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@package.json`:
- Line 33: The dependency "wavefile" in package.json is old (last updated May
2022) and may be a maintenance risk; review its usage (search for
imports/references to "wavefile" in the codebase), decide whether to keep it,
pin a specific version, or replace it with an actively maintained alternative
(e.g., a maintained WAV library) and update package.json accordingly, and add a
short comment in the repo README or a dependencies.md noting the decision and
any rationale for choosing to keep or replace "wavefile".
In `@src/core/profiler/processFrame.ts`:
- Around line 17-23: The function processSTFT currently defaults frameState to a
new { count: 0 } each call which resets frameIndex/PN seed; remove the default
parameter so frameState is required in processSTFT's signature and update all
callers (notably the calls in recorder.ts) to create one shared const frameState
= { count: 0 } in recorder.ts and pass that single object into every
processSTFT(AudioRingBuffer, bitstream, bitPtr, pnGen, frameState) invocation so
the count advances across buffers and the BinMapper seed progresses with the
stream.
In `@src/core/profiler/recorder.ts`:
- Around line 31-35: The recorder() function should reset module-level state
before starting a new session to avoid cross-session contamination: add and
export functions like resetOverlapBuffer() in reconstructor.ts (to clear
overlapBuffer) and resetAccumulator() in writer.ts (to reinitialize
pcmAccumulator), then import and call resetOverlapBuffer() and
resetAccumulator() at the start of recorder() (before calling
processIFFT/overlapAdd/collectOutput) and/or immediately after saveWAV() to
ensure overlapAdd and collectOutput/saveWAV don’t carry state between
recordings.
In `@src/core/recombination/ifft.ts`:
- Line 6: The constant outputGain = 0.5 lacks context: update the code comments
near the outputGain declaration in src/core/recombination/ifft.ts to explain why
the extra 0.5 scaling is applied on top of fft.js's inverseTransform() (which
already applies 1/N), stating whether this is intentional attenuation, dynamic
range control, or clipping prevention, and justify the specific value (e.g.,
empirical clipping reduction, headroom requirements, or matching downstream
expected amplitude); reference the inverseTransform() normalization and mention
the resulting net scaling (1/2048) and any tests/measurements used to choose 0.5
so future maintainers can understand and adjust outputGain if needed.
In `@src/core/recombination/pcmConverter.ts`:
- Around line 6-19: The floatToInt16 function assumes input is within [-1,1]
which can cause hard clipping; modify floatToInt16 to first detect the peak
absolute sample (maxAbs) across floatFrame and, if maxAbs > 1.0, compute a
uniform scale factor (1.0 / maxAbs) and multiply every sample by that factor
before the existing clamping/rounding conversion so the buffer is
gain-normalized; alternatively add an optional parameter (e.g., normalize?:
boolean) to enable this peak-scaling behavior, but ensure the peak-detection and
uniform scaling occur inside floatToInt16 before filling intFrame.
In `@src/core/recombination/reconstructor.ts`:
- Around line 4-20: The overlap-add logic treats every frame as an interior hop,
causing the first output to be attenuated and trailing samples to be left
unflushed; fix by (1) adding a local primed flag (e.g., overlapPrimed) and
update overlapAdd to skip the 1.08 division on the very first call (use raw
overlapBuffer + timeFrame for the initial hop) and set overlapPrimed=true after
that call, and (2) add an exported flushOverlapAdd (or flushOverlapBuffer)
function that returns the remaining HOP_SIZE trailing samples (applying any
final scaling if needed), clears overlapBuffer and resets overlapPrimed so
recorder.saveWAV can call it to append the final hop; reference symbols:
overlapAdd, overlapBuffer, HOP_SIZE, and create flushOverlapAdd / overlapPrimed
for use by recorder.saveWAV.
In `@src/core/recombination/writer.ts`:
- Around line 15-21: The saveWAV function is passing pcmAccumulator (an array of
Int16Array chunks) directly to wav.fromScratch which expects a single flat
TypedArray for mono audio; fix by flattening pcmAccumulator into one Int16Array
(concatenate all chunks in pcmAccumulator into a single Int16Array) and pass
that flat array to wavefile.WaveFile.fromScratch(1, 16000, "16", flatSamples),
then write the buffer as before and re-enable/reset pcmAccumulator (uncomment or
reassign it to a new empty Int16Array) so accumulated chunks are cleared after
saving.
---
Nitpick comments:
In `@src/core/modulator/embedChips.ts`:
- Around line 18-20: The loop silently skips out-of-bounds bin indices (iIndex
>= fftComplex.length); update the code in embedChips.ts where iIndex and
fftComplex are used (and where chipMap / BinMapper.mapToBins feed indices) to
surface this issue by either logging a warning with context (include iIndex,
fftComplex.length, chipId/chipMap entry and any upstream mapping result) or
throwing an Error during development; ensure you reference the same variables
(iIndex, fftComplex, chipMap) so the log/exception clearly shows the invalid
index and the source mapping to help debug upstream BinMapper.mapToBins output.
In `@src/core/profiler/processFrame.ts`:
- Around line 33-40: The per-frame console.log calls in processFrame.ts (the two
logs around Math.min/Math.max and Found ${safeBins.length}) are on the hot path
and must be gated or removed; update the code that calls Math.min/Math.max and
the safeBins length log to only run when a debug flag is enabled (e.g., a
module-level DEBUG constant or a logger.isDebugEnabled check) so normal
real-time capture bypasses stdout I/O, leaving the core functions processFFT,
computeBarkEnergy, estimateMasking, and identifySafeBins unchanged.
In `@src/core/recombination/writer.ts`:
- Around line 7-10: The file contains dead commented code that attempts to
concatenate Int16Array buffers (newBuffer, newBuffer.set) and a commented reset
of pcmAccumulator; remove these commented lines or restore a real implementation
so the codebase isn't confusing. Specifically, either delete the commented block
referencing newBuffer/newBuffer.set and the commented reset of pcmAccumulator in
src/core/recombination/writer.ts, or replace the current array-push approach
with a working concatenation function that updates pcmAccumulator by creating a
new Int16Array, copying pcmAccumulator and pcmFrame into it (using set) and
assigning it back to pcmAccumulator; ensure references to pcmAccumulator and
pcmFrame are updated consistently.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2872ca8f-e45a-4cc1-8d52-1fed5a9a75fa
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (10)
.gitignorepackage.jsonsrc/core/modulator/embedChips.tssrc/core/profiler/fft.tssrc/core/profiler/processFrame.tssrc/core/profiler/recorder.tssrc/core/recombination/ifft.tssrc/core/recombination/pcmConverter.tssrc/core/recombination/reconstructor.tssrc/core/recombination/writer.ts
| "chalk": "^5.6.2", | ||
| "fft.js": "^4.0.4" | ||
| "fft.js": "^4.0.4", | ||
| "wavefile": "^11.0.0" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check latest wavefile version on npm
curl -s https://registry.npmjs.org/wavefile/latest | jq '.version'
# Check for security advisories
gh api graphql -f query='
{
securityVulnerabilities(first: 5, ecosystem: NPM, package: "wavefile") {
nodes {
advisory {
summary
severity
publishedAt
}
vulnerableVersionRange
firstPatchedVersion {
identifier
}
}
}
}'Repository: Forgata/deephide
Length of output: 229
🏁 Script executed:
#!/bin/bash
# Get full package info from npm registry
curl -s https://registry.npmjs.org/wavefile | jq '{
"latest_version": .["dist-tags"].latest,
"all_versions": (.versions | keys | sort | .[-5:]),
"description": .description,
"repository": .repository.url,
"last_modified": .time.modified
}'
# Try to check for known vulnerabilities using npm advisory endpoint
curl -s https://registry.npmjs.org/-/npm/v1/security/advisories/bulk -X POST \
-H "Content-Type: application/json" \
-d '{"wavefile":["11.0.0"]}' 2>/dev/null | jq . || echo "Advisory endpoint not available"Repository: Forgata/deephide
Length of output: 365
The wavefile package version 11.0.0 exists with no known vulnerabilities, but consider maintenance risk.
The package version exists on npm with no security advisories. However, wavefile hasn't been updated since May 2022 (3+ years old), which may pose a maintenance risk. Consider evaluating whether to use this unmaintained package or switch to an actively maintained alternative for WAV file handling.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@package.json` at line 33, The dependency "wavefile" in package.json is old
(last updated May 2022) and may be a maintenance risk; review its usage (search
for imports/references to "wavefile" in the codebase), decide whether to keep
it, pin a specific version, or replace it with an actively maintained
alternative (e.g., a maintained WAV library) and update package.json
accordingly, and add a short comment in the repo README or a dependencies.md
noting the decision and any rationale for choosing to keep or replace
"wavefile".
| export function processSTFT( | ||
| audioBuffer: AudioRingBuffer, | ||
| bitstream: Uint8Array, | ||
| bitPtr: { index: number }, | ||
| pnGen: PNGenerator, | ||
| frameState: FrameState = { count: 0 }, | ||
| ) { |
There was a problem hiding this comment.
Make frameState mandatory instead of defaulting it.
src/core/profiler/recorder.ts still calls processSTFT() without this argument, so the default { count: 0 } is recreated on every invocation. That resets frameIndex and keeps the BinMapper seed at 0, causing the same safe-bin shuffle to repeat across buffers instead of advancing with the stream.
Suggested change
interface FrameState {
count: number;
}
export function processSTFT(
audioBuffer: AudioRingBuffer,
bitstream: Uint8Array,
bitPtr: { index: number },
pnGen: PNGenerator,
- frameState: FrameState = { count: 0 },
+ frameState: FrameState,
) {Create one shared const frameState = { count: 0 }; in src/core/profiler/recorder.ts and pass it to every call.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/core/profiler/processFrame.ts` around lines 17 - 23, The function
processSTFT currently defaults frameState to a new { count: 0 } each call which
resets frameIndex/PN seed; remove the default parameter so frameState is
required in processSTFT's signature and update all callers (notably the calls in
recorder.ts) to create one shared const frameState = { count: 0 } in recorder.ts
and pass that single object into every processSTFT(AudioRingBuffer, bitstream,
bitPtr, pnGen, frameState) invocation so the count advances across buffers and
the BinMapper seed progresses with the stream.
| const timeframe = processIFFT(frame.spectrum); | ||
| const synthFrame = overlapAdd(timeframe); | ||
| const pcmFrame = floatToInt16(synthFrame); | ||
|
|
||
| collectOutput(pcmFrame); |
There was a problem hiding this comment.
Module-level state is not reset between recording sessions.
Both overlapAdd (in reconstructor.ts) and collectOutput/saveWAV (in writer.ts) use module-level state (overlapBuffer and pcmAccumulator) that persists across calls to recorder(). If this function is invoked multiple times:
overlapBufferretains samples from the previous session, corrupting the first output frame(s)pcmAccumulatoraccumulates indefinitely (the reset on line 20 ofwriter.tsis commented out), causing subsequent WAV files to contain all prior recordings
Consider adding and calling reset functions at the start of recorder() or after saveWAV().
🛠️ Suggested approach
Add reset functions to both modules and call them appropriately:
In reconstructor.ts:
export function resetOverlapBuffer(): void {
overlapBuffer.fill(0);
}In writer.ts:
export function resetAccumulator(): void {
pcmAccumulator = [];
}Then in recorder.ts, import and call these at the start:
export async function recorder(bitstream: Uint8Array) {
+ resetOverlapBuffer();
+ resetAccumulator();
const frameSize = 512;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/core/profiler/recorder.ts` around lines 31 - 35, The recorder() function
should reset module-level state before starting a new session to avoid
cross-session contamination: add and export functions like resetOverlapBuffer()
in reconstructor.ts (to clear overlapBuffer) and resetAccumulator() in writer.ts
(to reinitialize pcmAccumulator), then import and call resetOverlapBuffer() and
resetAccumulator() at the start of recorder() (before calling
processIFFT/overlapAdd/collectOutput) and/or immediately after saveWAV() to
ensure overlapAdd and collectOutput/saveWAV don’t carry state between
recordings.
| const FRAME_SIZE = 1024; | ||
| const f = new FFT(FRAME_SIZE); | ||
|
|
||
| const outputGain = 0.5; |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
fft.js npm package inverseTransform normalization
💡 Result:
In the fft.js npm package (Fedor Indutny’s fft.js), inverseTransform(out, data) already applies normalization: after running the inverse FFT, it divides every element in the complex output buffer by this.size (i.e., 1/N scaling, applied to both real and imaginary interleaved slots). So transform() is unscaled, and inverseTransform() is scaled by 1/N. [1][2]
Implications:
inverseTransform(transform(x))returns (approximately) the original signal without you doing extra scaling. [1][2]- If you want an unnormalized inverse, multiply the
inverseTransformoutput byN(or alternatively divide the forward transform byN). [2]
Sources:
[1] fft.js README usage/API (shows inverseTransform as the inverse FFT entry point) (github.com)
[2] fft.js/lib/fft.js implementation of inverseTransform (out[i] /= this.size) (github.com)
Citations:
🏁 Script executed:
cat src/core/recombination/ifft.tsRepository: Forgata/deephide
Length of output: 781
Add documentation explaining the outputGain = 0.5 scaling factor.
The hardcoded gain value lacks context. Since fft.js's inverseTransform() already applies 1/N normalization (dividing by 1024), the additional 0.5 multiplier results in total scaling of 1/2048 on the real output. This combined scaling is unusual and should be documented: explain whether it's intentional signal attenuation, dynamic range control, or clipping prevention, and justify why this specific value was chosen.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/core/recombination/ifft.ts` at line 6, The constant outputGain = 0.5
lacks context: update the code comments near the outputGain declaration in
src/core/recombination/ifft.ts to explain why the extra 0.5 scaling is applied
on top of fft.js's inverseTransform() (which already applies 1/N), stating
whether this is intentional attenuation, dynamic range control, or clipping
prevention, and justify the specific value (e.g., empirical clipping reduction,
headroom requirements, or matching downstream expected amplitude); reference the
inverseTransform() normalization and mention the resulting net scaling (1/2048)
and any tests/measurements used to choose 0.5 so future maintainers can
understand and adjust outputGain if needed.
| export function floatToInt16(floatFrame: Float32Array): Int16Array { | ||
| const intFrame = new Int16Array(floatFrame.length); | ||
|
|
||
| for (let i = 0; i < floatFrame.length; i++) { | ||
| let s = floatFrame[i]! * 32767.0; | ||
|
|
||
| if (s > 32767.0) s = 32767.0; | ||
| if (s < -32768.0) s = -32768.0; | ||
|
|
||
| intFrame[i] = Math.round(s); | ||
| } | ||
|
|
||
| return intFrame; | ||
| } |
There was a problem hiding this comment.
Input range assumption may cause hard clipping.
This function assumes floatFrame values are in the range [-1.0, 1.0]. Based on the upstream pipeline (IFFT with outputGain=0.5 and overlap-add dividing by 1.08), the effective gain is ~0.463, but the raw IFFT output magnitude depends on the input spectrum and is not inherently normalized.
If the modified spectrum produces time-domain values exceeding ±1.0 after scaling, clamping at lines 12-13 will hard-clip the audio, causing audible distortion. Consider adding a normalization step upstream or implementing peak detection to scale the entire buffer before conversion.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/core/recombination/pcmConverter.ts` around lines 6 - 19, The floatToInt16
function assumes input is within [-1,1] which can cause hard clipping; modify
floatToInt16 to first detect the peak absolute sample (maxAbs) across floatFrame
and, if maxAbs > 1.0, compute a uniform scale factor (1.0 / maxAbs) and multiply
every sample by that factor before the existing clamping/rounding conversion so
the buffer is gain-normalized; alternatively add an optional parameter (e.g.,
normalize?: boolean) to enable this peak-scaling behavior, but ensure the
peak-detection and uniform scaling occur inside floatToInt16 before filling
intFrame.
| let overlapBuffer = new Float32Array(HOP_SIZE).fill(0); | ||
|
|
||
| /** | ||
| * Stitches IFFT frames using Overlap-Add logic. | ||
| * @param timeFrame 1024 samples | ||
| * @returns 512 samples of finished PCM | ||
| */ | ||
|
|
||
| export function overlapAdd(timeFrame: Float32Array): Float32Array { | ||
| const output = new Float32Array(HOP_SIZE); | ||
|
|
||
| for (let i = 0; i < HOP_SIZE; i++) { | ||
| output[i] = (overlapBuffer[i]! + timeFrame[i]!) / 1.08; | ||
| overlapBuffer[i] = timeFrame[i + HOP_SIZE]!; | ||
| } | ||
|
|
||
| return output; |
There was a problem hiding this comment.
Handle the first/last OLA hops explicitly.
On the first call, overlapBuffer is still all zeros, so Line 16 attenuates the opening hop as if a previous frame existed. On the last call, Lines 17-20 keep the trailing 512 samples in overlapBuffer, but src/core/profiler/recorder.ts never flushes them before saveWAV(). The reconstructed output will start with incorrect gain and end one hop short. Please add explicit priming/finalization handling for the overlap buffer instead of treating every frame as an interior frame.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/core/recombination/reconstructor.ts` around lines 4 - 20, The overlap-add
logic treats every frame as an interior hop, causing the first output to be
attenuated and trailing samples to be left unflushed; fix by (1) adding a local
primed flag (e.g., overlapPrimed) and update overlapAdd to skip the 1.08
division on the very first call (use raw overlapBuffer + timeFrame for the
initial hop) and set overlapPrimed=true after that call, and (2) add an exported
flushOverlapAdd (or flushOverlapBuffer) function that returns the remaining
HOP_SIZE trailing samples (applying any final scaling if needed), clears
overlapBuffer and resets overlapPrimed so recorder.saveWAV can call it to append
the final hop; reference symbols: overlapAdd, overlapBuffer, HOP_SIZE, and
create flushOverlapAdd / overlapPrimed for use by recorder.saveWAV.
| export function saveWAV(filename: string = "output.wav"): void { | ||
| const wav = new wavefile.WaveFile(); | ||
| wav.fromScratch(1, 16000, "16", pcmAccumulator); | ||
| fs.writeFileSync(filename, wav.toBuffer()); | ||
| console.log(`\n stealth audio saved: ${filename}`); | ||
| // pcmAccumulator = new Int16Array(0); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
wavefile npm fromScratch samples parameter type
💡 Result:
In wavefile’s WaveFile.fromScratch(numChannels, sampleRate, bitDepthCode, samples, options?), the samples parameter type is Array | TypedArray. [1]
For multi-channel audio, samples can be provided either:
- Interleaved (single
TypedArray/Arrayof numbers), or - De-interleaved (array of per-channel arrays); wavefile will interleave them for you (as shown in the stereo example). [2]
Sample values are plain numbers, expected to match the chosen bitDepthCode range (e.g., -32768..32767 for "16", -1.0..1.0 for "32f"/"64"). [3]
Sources: [1] (rochars.github.io) [2] (npm.io) [3] (npm.io)
Citations:
- 1: https://rochars.github.io/wavefile/docs/module-wavefile.WaveFile.html
- 2: https://npm.io/package/wavefile
- 3: https://npm.io/package/wavefile
Flatten pcmAccumulator chunks before passing to wavefile.
Line 17 passes pcmAccumulator (an Int16Array[] of accumulated chunks) directly to wav.fromScratch(). Since this is mono audio (1 channel), wavefile expects a single flat TypedArray or an array with one channel. Passing an array of chunks will either fail or be misinterpreted as multiple channels.
Combine the chunks into a single Int16Array:
Fix
export function saveWAV(filename: string = "output.wav"): void {
+ const totalLength = pcmAccumulator.reduce((sum, chunk) => sum + chunk.length, 0);
+ const flatSamples = new Int16Array(totalLength);
+ let offset = 0;
+ for (const chunk of pcmAccumulator) {
+ flatSamples.set(chunk, offset);
+ offset += chunk.length;
+ }
+
const wav = new wavefile.WaveFile();
- wav.fromScratch(1, 16000, "16", pcmAccumulator);
+ wav.fromScratch(1, 16000, "16", flatSamples);
fs.writeFileSync(filename, wav.toBuffer());
console.log(`\n stealth audio saved: ${filename}`);Also uncomment the reset (line 21) if pcmAccumulator should be cleared after saving.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/core/recombination/writer.ts` around lines 15 - 21, The saveWAV function
is passing pcmAccumulator (an array of Int16Array chunks) directly to
wav.fromScratch which expects a single flat TypedArray for mono audio; fix by
flattening pcmAccumulator into one Int16Array (concatenate all chunks in
pcmAccumulator into a single Int16Array) and pass that flat array to
wavefile.WaveFile.fromScratch(1, 16000, "16", flatSamples), then write the
buffer as before and re-enable/reset pcmAccumulator (uncomment or reassign it to
a new empty Int16Array) so accumulated chunks are cleared after saving.
DeepHide Audio Reconstruction Pipeline
Key Changes
.wav.Technical Verification
Summary by CodeRabbit