Skip to content

Fix 44.1k export resampling and add 16-bit TPDF dithering#4

Merged
entrepeneur4lyf merged 4 commits into
mainfrom
fix/export-44100-resample-dither
Feb 12, 2026
Merged

Fix 44.1k export resampling and add 16-bit TPDF dithering#4
entrepeneur4lyf merged 4 commits into
mainfrom
fix/export-44100-resample-dither

Conversation

@entrepeneur4lyf
Copy link
Copy Markdown
Owner

@entrepeneur4lyf entrepeneur4lyf commented Feb 12, 2026

User description

Summary

Fixes incorrect playback speed/duration when exporting to 44.1 kHz and adds proper 16-bit WAV dithering.

Problem

Exporting at 44.1 kHz could produce a WAV that played too fast (for example, ~5:24 source becoming ~5:00).

Root Cause

In the worker export path, audio was rendered at the source sample rate (commonly 48 kHz) but encoded with a 44.1 kHz WAV header without resampling first.

What Changed

  1. Added sample-rate conversion before worker-path encoding:

    • New helper: resampleAudioBuffer(...) in web/ui/renderer.js
    • Worker export flow now resamples to the selected output rate before encodeWAVAsync(...) in web/app.js
    • Added cancellation check after resampling in export flow
  2. Added built-in 16-bit TPDF dithering (no FFmpeg required):

    • Implemented in both encodeWAV(...) and encodeWAVAsync(...) in web/ui/encoder.js
    • Dither is applied only for 16-bit export
    • 24-bit path remains undithered
  3. Encoder robustness updates:

    • Normalized bit-depth handling with safeBitDepth
    • WAV header bit depth now uses safeBitDepth
    • Added 16-bit post-dither clamping to [-32768, 32767]

Impact

  • 44.1 kHz exports now preserve correct song duration/speed.
  • 16-bit exports have improved quantization behavior (reduced distortion artifacts).
  • No FFmpeg/WASM dependency needed for dithering.

Validation

  • npm run build:web passes.

Manual Verification

  1. Load a 48 kHz source file (~5:24).
  2. Export as 44.1 kHz WAV (16-bit).
  3. Confirm exported duration remains ~5:24 in DAW/player.
  4. Sanity-check 48 kHz / 24-bit export path.

PR Type

Bug fix, Enhancement


Description

  • Fix 44.1 kHz export playback speed by resampling before encoding

  • Add 16-bit TPDF dithering to reduce quantization noise artifacts

  • Normalize bit-depth handling with safeBitDepth validation

  • Add post-dither clamping to prevent 16-bit overflow


Diagram Walkthrough

flowchart LR
  A["Audio Render<br/>Source Rate"] --> B["Check Sample Rate<br/>Match"]
  B -->|Mismatch| C["Resample to<br/>Target Rate"]
  B -->|Match| D["Encode WAV"]
  C --> D
  D --> E["Apply TPDF<br/>Dither 16-bit"]
  E --> F["Clamp & Write<br/>WAV Data"]
  F --> G["Output WAV<br/>File"]
Loading

File Walkthrough

Relevant files
Bug fix
app.js
Add resampling before worker export encoding                         

web/app.js

  • Import resampleAudioBuffer helper function from renderer
  • Add resampling step before WAV encoding when sample rates differ
  • Pass resampled buffer to encodeWAVAsync instead of original
  • Add cancellation check after resampling completes
+11/-1   
Enhancement
encoder.js
Add TPDF dithering and normalize bit-depth handling           

web/ui/encoder.js

  • Implement triangularDither() function for TPDF dithering in LSB domain
  • Normalize bit-depth handling with safeBitDepth variable (defaults to
    16)
  • Apply dithering only for 16-bit exports in both encodeWAV and
    encodeWAVAsync
  • Add post-dither clamping to [-32768, 32767] range for 16-bit samples
  • Update WAV header to use safeBitDepth instead of raw bitDepth
+26/-10 
renderer.js
Add audio buffer resampling utility function                         

web/ui/renderer.js

  • Add new resampleAudioBuffer() export function using
    OfflineAudioContext
  • Handles sample rate conversion with proper channel and duration
    calculation
  • Returns original buffer if sample rates already match
  • Ensures output sample data and WAV header remain aligned
+24/-0   

Summary by CodeRabbit

  • Bug Fixes
    • Fixed audio exports when source and target sample rates don't match—automatic conversion is now applied during the export process
    • Enhanced 16-bit WAV export quality with improved audio processing

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 12, 2026

Warning

Rate limit exceeded

@entrepeneur4lyf has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 22 minutes and 24 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

The changes integrate audio resampling into the export workflow, add triangular dithering for 16-bit WAV encoding, and implement safer bit-depth handling during audio encoding. A new resampleAudioBuffer function is introduced and called conditionally during export if sample rates mismatch.

Changes

Cohort / File(s) Summary
Audio Resampling
web/ui/renderer.js
Adds new resampleAudioBuffer() function that resamples AudioBuffer using OfflineAudioContext when sample rates differ. Function is duplicated in the file (appears twice as identical exports).
Export Integration
web/app.js
Integrates resampleAudioBuffer into export workflow with progress tracking at 83% during resampling. Adds cancellation guards before and after resampling step, ensuring correct sample rate for WAV encoding.
WAV Encoding Enhancement
web/ui/encoder.js
Implements triangular dithering for 16-bit samples and adds safeBitDepth variable to guard bit-depth computations. Updates both sync and async WAV encoding paths with proper clamping for 16-bit and 24-bit samples.

Sequence Diagram

sequenceDiagram
    participant App as app.js<br/>(Export Flow)
    participant Worker as Worker<br/>(Render)
    participant Renderer as renderer.js<br/>(Resampling)
    participant Encoder as encoder.js<br/>(WAV Encoding)
    participant Output as Output<br/>(WAV Blob)

    App->>Worker: Render with target sample rate
    Worker-->>App: Return audioBuffer
    App->>App: Check if sample rates match
    alt Sample rate mismatch
        App->>Renderer: resampleAudioBuffer(buffer, targetRate)
        Renderer->>Renderer: Create OfflineAudioContext
        Renderer-->>App: Return resampled buffer
        App->>App: Update progress to 83%
    end
    App->>App: Guard: Check for cancellation
    App->>Encoder: encodeWAV(resampledBuffer, bitDepth)
    Encoder->>Encoder: Apply triangular dither (16-bit)
    Encoder->>Encoder: Clamp samples to target bit-depth range
    Encoder-->>App: Return encoded WAV data
    App->>Output: Create Blob from WAV data
    Output-->>App: Export complete
Loading

Estimated Code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Hoppy resampling through the air,
Dithering bits with fuzzy care,
Sample rates aligned just right,
WAV encodings, clean and tight!
Export magic, fluffy and free,
Audio perfection—hop, hop, hooray! 🎵

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the two main changes: fixing 44.1k resampling in export and adding 16-bit TPDF dithering, which align with the PR objectives and file changes.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/export-44100-resample-dither

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review
Copy link
Copy Markdown
Contributor

qodo-code-review Bot commented Feb 12, 2026

PR Compliance Guide 🔍

(Compliance updated until commit 30a1bd1)

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Duplicate resample block: The export resampling block appears duplicated/mis-indented, which can lead to
syntax/runtime errors and prevents robust handling of the resampling/encoding flow.

Referred Code
let exportBuffer = result.audioBuffer;
if (exportBuffer.sampleRate !== parsedSampleRate) {
  updateProgress(83, `Resampling to ${parsedSampleRate / 1000}kHz...`);
let exportBuffer = result.audioBuffer;
if (exportBuffer.sampleRate !== parsedSampleRate) {
  updateProgress(86, `Resampling to ${parsedSampleRate / 1000}kHz...`);
  exportBuffer = await resampleAudioBuffer(exportBuffer, parsedSampleRate);
}
if (processingCancelled) {
  throw new Error('Cancelled');

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Sample rate validation: resampleAudioBuffer only checks falsy targetSampleRate and equality, but does not validate
that the provided sample rate is a finite, positive, and reasonable numeric value before
constructing OfflineAudioContext.

Referred Code
export async function resampleAudioBuffer(sourceBuffer, targetSampleRate) {
  if (!sourceBuffer || !targetSampleRate || sourceBuffer.sampleRate === targetSampleRate) {
    return sourceBuffer;
  }

  const numChannels = sourceBuffer.numberOfChannels;
  const numSamples = Math.ceil(sourceBuffer.duration * targetSampleRate);
  const offlineCtx = new OfflineAudioContext(numChannels, numSamples, targetSampleRate);
  const source = offlineCtx.createBufferSource();

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

Previous compliance checks

Compliance check up to commit 88c8cde
Security Compliance
Resource exhaustion

Description: Potential client-side DoS via resource exhaustion: resampleAudioBuffer() computes
numSamples = Math.ceil(sourceBuffer.duration * targetSampleRate) and creates an
OfflineAudioContext(numChannels, numSamples, targetSampleRate) without explicit upper
bounds, so extremely long inputs and/or unusually high targetSampleRate can trigger very
large allocations and heavy CPU use (tab freeze/crash).
renderer.js [172-187]

Referred Code
export async function resampleAudioBuffer(sourceBuffer, targetSampleRate) {
  if (!sourceBuffer || !targetSampleRate || sourceBuffer.sampleRate === targetSampleRate) {
    return sourceBuffer;
  }

  const numChannels = sourceBuffer.numberOfChannels;
  const numSamples = Math.ceil(sourceBuffer.duration * targetSampleRate);
  const offlineCtx = new OfflineAudioContext(numChannels, numSamples, targetSampleRate);
  const source = offlineCtx.createBufferSource();

  source.buffer = sourceBuffer;
  source.connect(offlineCtx.destination);
  source.start(0);

  return offlineCtx.startRendering();
}
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Missing validation: resampleAudioBuffer() does not validate that targetSampleRate is a finite positive number
and does not handle/annotate potential OfflineAudioContext.startRendering() failures,
which could surface as unhandled export errors.

Referred Code
export async function resampleAudioBuffer(sourceBuffer, targetSampleRate) {
  if (!sourceBuffer || !targetSampleRate || sourceBuffer.sampleRate === targetSampleRate) {
    return sourceBuffer;
  }

  const numChannels = sourceBuffer.numberOfChannels;
  const numSamples = Math.ceil(sourceBuffer.duration * targetSampleRate);
  const offlineCtx = new OfflineAudioContext(numChannels, numSamples, targetSampleRate);
  const source = offlineCtx.createBufferSource();

  source.buffer = sourceBuffer;
  source.connect(offlineCtx.destination);
  source.start(0);

  return offlineCtx.startRendering();
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Weak input checks: resampleAudioBuffer() accepts targetSampleRate without strict validation (e.g.,
non-numeric/NaN/negative/unsupported rates) before constructing OfflineAudioContext, which
may lead to runtime exceptions or inconsistent behavior for malformed inputs.

Referred Code
export async function resampleAudioBuffer(sourceBuffer, targetSampleRate) {
  if (!sourceBuffer || !targetSampleRate || sourceBuffer.sampleRate === targetSampleRate) {
    return sourceBuffer;
  }

  const numChannels = sourceBuffer.numberOfChannels;
  const numSamples = Math.ceil(sourceBuffer.duration * targetSampleRate);
  const offlineCtx = new OfflineAudioContext(numChannels, numSamples, targetSampleRate);
  const source = offlineCtx.createBufferSource();

Learn more about managing compliance generic rules or creating your own custom rules

@entrepeneur4lyf entrepeneur4lyf self-assigned this Feb 12, 2026
@qodo-code-review
Copy link
Copy Markdown
Contributor

qodo-code-review Bot commented Feb 12, 2026

PR Code Suggestions ✨

Latest suggestions up to 30a1bd1

CategorySuggestion                                                                                                                                    Impact
Incremental [*]
Fix broken resampling block

Remove the duplicated let exportBuffer declaration and if block to fix a syntax
error and ensure the resampling logic executes correctly.

web/app.js [1422-1429]

 let exportBuffer = result.audioBuffer;
 if (exportBuffer.sampleRate !== parsedSampleRate) {
   updateProgress(86, `Resampling to ${parsedSampleRate / 1000}kHz...`);
+  exportBuffer = await resampleAudioBuffer(exportBuffer, parsedSampleRate);
+}

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 10

__

Why: The suggestion correctly identifies a critical syntax error due to a copy-paste mistake, which would break the application's export functionality.

High
Make dithering state per export

Refactor the dithering logic to use a factory function, creating a separate,
independent dither state for each encoding operation to prevent race conditions.

web/ui/encoder.js [12-24]

-const DITHER_BUFFER_SIZE = 256;
-const ditherBuffer = new Float32Array(DITHER_BUFFER_SIZE);
-for (let i = 0; i < DITHER_BUFFER_SIZE; i++) {
-  ditherBuffer[i] = (Math.random() + Math.random()) - 1;
-}
-let ditherIndex = 0;
+function createTriangularDither() {
+  const DITHER_BUFFER_SIZE = 4096;
+  const ditherBuffer = new Float32Array(DITHER_BUFFER_SIZE);
+  for (let i = 0; i < DITHER_BUFFER_SIZE; i++) {
+    ditherBuffer[i] = (Math.random() + Math.random()) - 1;
+  }
+  let ditherIndex = 0;
 
-function triangularDither() {
-  // TPDF in integer (LSB) domain: [-1, 1]
-  const dither = ditherBuffer[ditherIndex];
-  ditherIndex = (ditherIndex + 1) % DITHER_BUFFER_SIZE;
-  return dither;
+  return function triangularDither() {
+    // TPDF in integer (LSB) domain: [-1, 1]
+    const dither = ditherBuffer[ditherIndex];
+    ditherIndex = (ditherIndex + 1) % DITHER_BUFFER_SIZE;
+    return dither;
+  };
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a potential race condition with the global dither state, proposing a factory function to ensure each encoding operation is independent and thread-safe.

Low
Possible issue
Avoid resample length rounding errors

Calculate the resampled buffer length using the source buffer's sample count
(length) instead of its duration to prevent floating-point rounding errors and
potential audio artifacts.

web/ui/renderer.js [172-187]

 export async function resampleAudioBuffer(sourceBuffer, targetSampleRate) {
   if (!sourceBuffer || !targetSampleRate || sourceBuffer.sampleRate === targetSampleRate) {
     return sourceBuffer;
   }
 
   const numChannels = sourceBuffer.numberOfChannels;
-  const numSamples = Math.ceil(sourceBuffer.duration * targetSampleRate);
+  const ratio = targetSampleRate / sourceBuffer.sampleRate;
+  const numSamples = Math.max(1, Math.round(sourceBuffer.length * ratio));
   const offlineCtx = new OfflineAudioContext(numChannels, numSamples, targetSampleRate);
   const source = offlineCtx.createBufferSource();
 
   source.buffer = sourceBuffer;
   source.connect(offlineCtx.destination);
   source.start(0);
 
   return offlineCtx.startRendering();
 }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that using sourceBuffer.duration for resampling can lead to rounding errors, and proposes a more precise calculation based on sourceBuffer.length and the sample rate ratio.

Medium
  • More

Previous suggestions

✅ Suggestions up to commit 88c8cde
CategorySuggestion                                                                                                                                    Impact
General
Handle resampling errors gracefully
Suggestion Impact:Wrapped the resampleAudioBuffer call in a try/catch and logged an error when resampling fails, allowing export to proceed with the original buffer.

code diff:

         let exportBuffer = result.audioBuffer;
         if (exportBuffer.sampleRate !== parsedSampleRate) {
           updateProgress(83, `Resampling to ${parsedSampleRate / 1000}kHz...`);
-          exportBuffer = await resampleAudioBuffer(exportBuffer, parsedSampleRate);
+          try {
+            exportBuffer = await resampleAudioBuffer(exportBuffer, parsedSampleRate);
+          } catch (err) {
+            console.error('Resampling failed, using original buffer', err);
+          }

Add a try/catch block around the resampleAudioBuffer call to handle potential
errors gracefully, preventing the export process from crashing by falling back
to the original buffer.

web/app.js [1422-1429]

 let exportBuffer = result.audioBuffer;
 if (exportBuffer.sampleRate !== parsedSampleRate) {
   updateProgress(83, `Resampling to ${parsedSampleRate / 1000}kHz...`);
-  exportBuffer = await resampleAudioBuffer(exportBuffer, parsedSampleRate);
+  try {
+    exportBuffer = await resampleAudioBuffer(exportBuffer, parsedSampleRate);
+  } catch (err) {
+    console.error('Resampling failed, using original buffer', err);
+  }
 }
 if (processingCancelled) {
   throw new Error('Cancelled');
 }

[Suggestion processed]

Suggestion importance[1-10]: 7

__

Why: This is a good suggestion for improving robustness. Wrapping the resampleAudioBuffer call in a try/catch block ensures that a potential failure during resampling does not crash the entire export process, allowing it to proceed with the original buffer.

Medium
Improve dither performance with pre-generation
Suggestion Impact:Added a precomputed ditherBuffer with an index and updated triangularDither() to return buffered values instead of generating new random values each call.

code diff:

+const DITHER_BUFFER_SIZE = 256;
+const ditherBuffer = new Float32Array(DITHER_BUFFER_SIZE);
+for (let i = 0; i < DITHER_BUFFER_SIZE; i++) {
+  ditherBuffer[i] = (Math.random() + Math.random()) - 1;
+}
+let ditherIndex = 0;
+
 function triangularDither() {
   // TPDF in integer (LSB) domain: [-1, 1]
-  return (Math.random() + Math.random()) - 1;
+  const dither = ditherBuffer[ditherIndex];
+  ditherIndex = (ditherIndex + 1) % DITHER_BUFFER_SIZE;
+  return dither;

Improve dithering performance by pre-generating a buffer of dither noise and
cycling through it, instead of calling Math.random() for every sample.

web/ui/encoder.js [12-15]

+const DITHER_BUFFER_SIZE = 256;
+const ditherBuffer = new Float32Array(DITHER_BUFFER_SIZE);
+for (let i = 0; i < DITHER_BUFFER_SIZE; i++) {
+  ditherBuffer[i] = (Math.random() + Math.random()) - 1;
+}
+let ditherIndex = 0;
+
 function triangularDither() {
   // TPDF in integer (LSB) domain: [-1, 1]
-  return (Math.random() + Math.random()) - 1;
+  const dither = ditherBuffer[ditherIndex];
+  ditherIndex = (ditherIndex + 1) % DITHER_BUFFER_SIZE;
+  return dither;
 }

[Suggestion processed]

Suggestion importance[1-10]: 6

__

Why: This is a valid performance optimization. Calling Math.random() for every audio sample can be slow for large files, and pre-generating a dither buffer is a standard and effective technique to improve encoding speed.

Low
Refactor duplicated sample processing logic

Refactor the duplicated sample processing logic from encodeWAV and
encodeWAVAsync into a new shared helper function to improve code maintainability
and reduce redundancy.

web/ui/encoder.js [72-83]

-if (safeBitDepth === 16) {
-  const clampedSample = Math.max(-32768, Math.min(32767, intSample));
-  view.setInt16(offset, clampedSample, true);
-  offset += 2;
-} else if (safeBitDepth === 24) {
-  // Clamp to prevent overflow in bitwise operations
-  const clampedSample = Math.max(-8388607, Math.min(8388607, intSample));
-  view.setUint8(offset, clampedSample & 0xFF);
-  view.setUint8(offset + 1, (clampedSample >> 8) & 0xFF);
-  view.setUint8(offset + 2, (clampedSample >> 16) & 0xFF);
-  offset += 3;
+function processSample(view, offset, sample, safeBitDepth, maxVal) {
+  const scaled = sample * maxVal;
+  const intSample = safeBitDepth === 16
+    ? Math.round(scaled + triangularDither())
+    : Math.round(scaled);
+
+  if (safeBitDepth === 16) {
+    const clampedSample = Math.max(-32768, Math.min(32767, intSample));
+    view.setInt16(offset, clampedSample, true);
+    return 2;
+  } else { // 24-bit
+    const clampedSample = Math.max(-8388607, Math.min(8388607, intSample));
+    view.setUint8(offset, clampedSample & 0xFF);
+    view.setUint8(offset + 1, (clampedSample >> 8) & 0xFF);
+    view.setUint8(offset + 2, (clampedSample >> 16) & 0xFF);
+    return 3;
+  }
 }
 
+// In encodeWAV and encodeWAVAsync loops:
+// const sample = Math.max(-1, Math.min(1, channelData[ch][i]));
+// offset += processSample(view, offset, sample, safeBitDepth, maxVal);
+

[Suggestion processed]

Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies duplicated code between encodeWAV and encodeWAVAsync and proposes a valid refactoring to improve maintainability by extracting the logic into a shared helper function.

Low

Comment thread web/app.js
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@web/app.js`:
- Around line 1422-1438: The progress bar regresses from 85% to 83% during
resampling; update the resampling progress call so it never goes below the
current encoding start point. Replace the updateProgress(83, ...) call used
before calling resampleAudioBuffer(exportBuffer, parsedSampleRate) with a value
>=85 (e.g. updateProgress(86, `Resampling to ${parsedSampleRate / 1000}kHz...`)
or compute a value between 85 and the encoding start) so progress is monotonic;
keep using the same symbols exportBuffer, parsedSampleRate, updateProgress, and
resampleAudioBuffer.

Comment thread web/app.js
Co-authored-by: qodo-code-review[bot] <151058649+qodo-code-review[bot]@users.noreply.github.com>
Comment thread web/ui/encoder.js
entrepeneur4lyf and others added 2 commits February 12, 2026 14:50
Co-authored-by: qodo-code-review[bot] <151058649+qodo-code-review[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Comment thread web/ui/encoder.js
@qodo-code-review
Copy link
Copy Markdown
Contributor

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: build-check

Failed stage: Run npm run build:linux [❌]

Failed test name: ""

Failure summary:

The action failed because the Vite production build errored out with a JavaScript parse/syntax
error:
- vite:build-html reported Expected a semicolon while parsing web/app.js at 1442:8.
- The
error points to the catch (workerErr) { line in
/home/runner/work/Web-Audio-Mastering/Web-Audio-Mastering/web/app.js:1442:8 (referenced from
web/index.html), causing Rollup/Vite to abort and the process to exit with code 1.

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

180:  �[32m✓�[39m 1 modules transformed.
181:  �[36mvite v7.3.1 �[32mbuilding client environment for production...�[36m�[39m
182:  transforming...
183:  �[32m✓�[39m 1 modules transformed.
184:  rendering chunks...
185:  computing gzip size...
186:  �[2m../dist-electron/�[22m�[36mmain.js  �[39m�[1m�[2m2.52 kB�[22m�[1m�[22m�[2m │ gzip: 1.14 kB�[22m
187:  �[32m✓ built in 65ms�[39m
188:  �[36mvite v7.3.1 �[32mbuilding client environment for production...�[36m�[39m
189:  transforming...
190:  �[32m✓�[39m 1 modules transformed.
191:  rendering chunks...
192:  computing gzip size...
193:  �[2m../dist-electron/�[22m�[36mpreload.js  �[39m�[1m�[2m0.61 kB�[22m�[1m�[22m�[2m │ gzip: 0.32 kB�[22m
194:  �[32m✓ built in 10ms�[39m
195:  �[31m✗�[39m Build failed in 136ms
196:  �[31merror during build:
197:  �[31m[vite:build-html] web/app.js (1442:8): Expected a semicolon�[31m
198:  file: �[36m/home/runner/work/Web-Audio-Mastering/Web-Audio-Mastering/web/app.js:1442:8 (/home/runner/work/Web-Audio-Mastering/Web-Audio-Mastering/web/index.html)�[31m
199:  �[33m
200:  1440:           shouldCancel: () => processingCancelled
201:  1441:         });
202:  1442:       } catch (workerErr) {
203:  ^
204:  1443:         if (processingCancelled || workerErr?.message === 'Cancelled') {
205:  1444:           throw workerErr;
206:  �[31m
207:  at getRollupError (file:///home/runner/work/Web-Audio-Mastering/Web-Audio-Mastering/node_modules/rollup/dist/es/shared/parseAst.js:401:41)
208:  at ParseError.initialise (file:///home/runner/work/Web-Audio-Mastering/Web-Audio-Mastering/node_modules/rollup/dist/es/shared/node-entry.js:14534:28)
209:  at convertNode (file:///home/runner/work/Web-Audio-Mastering/Web-Audio-Mastering/node_modules/rollup/dist/es/shared/node-entry.js:16418:10)
210:  at convertProgram (file:///home/runner/work/Web-Audio-Mastering/Web-Audio-Mastering/node_modules/rollup/dist/es/shared/node-entry.js:15658:12)
211:  at Module.setSource (file:///home/runner/work/Web-Audio-Mastering/Web-Audio-Mastering/node_modules/rollup/dist/es/shared/node-entry.js:17418:24)
212:  at async ModuleLoader.addModuleSource (file:///home/runner/work/Web-Audio-Mastering/Web-Audio-Mastering/node_modules/rollup/dist/es/shared/node-entry.js:21497:13)�[39m
213:  ##[error]Process completed with exit code 1.
214:  Post job cleanup.

@entrepeneur4lyf entrepeneur4lyf merged commit d7c5442 into main Feb 12, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant