Optimize audio buffer handling to avoid repeated copies#48
Optimize audio buffer handling to avoid repeated copies#48
Conversation
Replace Buffer.concat([buffer, chunk]) on every data event with a remainder-based approach. The old pattern copied the entire accumulated buffer (~O(n^2)); the new approach only concatenates the small remainder (always < one frame = 640 bytes) with the incoming chunk. Closes #35 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Optimizes Electron audio capture buffering in createAudioStream to avoid repeated full-buffer concatenations on each PortAudio data event, reducing worst-case time complexity and copy overhead while preserving frame extraction and stop-flush behavior.
Changes:
- Replaced “grow-the-entire-buffer” concatenation with a small remainder-based buffering approach.
- Extracts frames via an offset into the current chunk (plus small remainder) and stores only leftover bytes for the next event.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let offset = 0; | ||
| while (offset + frameBytes <= data.length) { | ||
| const frame = data.subarray(offset, offset + frameBytes); | ||
| offset += frameBytes; |
There was a problem hiding this comment.
The loop will never terminate if frameBytes is 0 (e.g., options.frameMs <= 0 or options.channels <= 0), because offset never advances. Please add input validation (throw early) or guard here to ensure frameBytes > 0 before entering the while loop.
| // Keep only the leftover bytes (always < frameBytes) | ||
| remainder = offset < data.length ? data.subarray(offset) : Buffer.alloc(0); |
There was a problem hiding this comment.
remainder is assigned as a subarray view into data. When data is chunk (or a large concat), this keeps the entire underlying buffer alive until the next data event even though only < frameBytes are needed. Consider copying the leftover bytes into a new small Buffer (bounded to < frameBytes) to avoid retaining potentially large backing buffers and to avoid relying on the lifetime/mutability of the native chunk buffer across async boundaries.
| // Keep only the leftover bytes (always < frameBytes) | |
| remainder = offset < data.length ? data.subarray(offset) : Buffer.alloc(0); | |
| // Keep only the leftover bytes (always < frameBytes), copying them into a new Buffer | |
| if (offset < data.length) { | |
| const remaining = data.length - offset; | |
| const newRemainder = Buffer.alloc(remaining); | |
| data.copy(newRemainder, 0, offset, data.length); | |
| remainder = newRemainder; | |
| } else { | |
| remainder = Buffer.alloc(0); | |
| } |
Summary
Buffer.concat([buffer, chunk])on every PortAudiodataevent (O(n^2) worst-case) with a remainder-based approachTest plan
Closes #35
🤖 Generated with Claude Code