fix: use lamejs Mp3Encoder API for proper module initialization

Switched from low-level Lame API to Mp3Encoder class which:
- Properly initializes all required modules (Lame, BitStream, etc.)
- Handles module dependencies via setModules() calls
- Provides a simpler encodeBuffer/flush API
- Resolves "init_bit_stream_w is not defined" error

Updated TypeScript declarations to export Mp3Encoder and WavHeader
from lamejs/src/js/index.js

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-19 08:19:48 +01:00
parent 51114330ea
commit c3e295f695
2 changed files with 23 additions and 63 deletions

View File

@@ -136,12 +136,8 @@ export async function audioBufferToMp3(
audioBuffer: AudioBuffer, audioBuffer: AudioBuffer,
options: ExportOptions = { format: 'mp3', bitrate: 192 } options: ExportOptions = { format: 'mp3', bitrate: 192 }
): Promise<ArrayBuffer> { ): Promise<ArrayBuffer> {
// Import lamejs modules directly // Import Mp3Encoder from lamejs
const [{ default: MPEGMode }, { default: Lame }, { default: BitStream }] = await Promise.all([ const { Mp3Encoder } = await import('lamejs/src/js/index.js');
import('lamejs/src/js/MPEGMode'),
import('lamejs/src/js/Lame'),
import('lamejs/src/js/BitStream'),
]);
const { bitrate = 192, normalize } = options; const { bitrate = 192, normalize } = options;
const numberOfChannels = Math.min(audioBuffer.numberOfChannels, 2); // MP3 supports max 2 channels const numberOfChannels = Math.min(audioBuffer.numberOfChannels, 2); // MP3 supports max 2 channels
@@ -171,51 +167,31 @@ export async function audioBufferToMp3(
rightPcm[i] = Math.max(-32768, Math.min(32767, (right[i] / peak) * 32767)); rightPcm[i] = Math.max(-32768, Math.min(32767, (right[i] / peak) * 32767));
} }
// Create encoder using lamejs modules // Create MP3 encoder
const lame = new Lame(); const mp3encoder = new Mp3Encoder(numberOfChannels, sampleRate, bitrate);
const gfp = lame.lame_init();
gfp.num_channels = numberOfChannels;
gfp.in_samplerate = sampleRate;
gfp.brate = bitrate;
gfp.mode = numberOfChannels === 1 ? MPEGMode.MONO : MPEGMode.STEREO;
gfp.quality = 3; // 0=best (very slow), 9=worst (fast)
gfp.write_id3tag_automatic = 0;
lame.lame_init_params(gfp);
const mp3Data: Int8Array[] = []; const mp3Data: Int8Array[] = [];
const mp3buf = new Int8Array(samples * 1.25 + 7200); // Estimate output size
const sampleBlockSize = 1152; // Standard MP3 frame size const sampleBlockSize = 1152; // Standard MP3 frame size
// Encode in blocks
for (let i = 0; i < samples; i += sampleBlockSize) { for (let i = 0; i < samples; i += sampleBlockSize) {
const leftChunk = leftPcm.subarray(i, Math.min(i + sampleBlockSize, samples)); const leftChunk = leftPcm.subarray(i, Math.min(i + sampleBlockSize, samples));
const rightChunk = numberOfChannels > 1 const rightChunk = numberOfChannels > 1
? rightPcm.subarray(i, Math.min(i + sampleBlockSize, samples)) ? rightPcm.subarray(i, Math.min(i + sampleBlockSize, samples))
: leftChunk; : leftChunk;
const bytesEncoded = lame.lame_encode_buffer( const mp3buf = mp3encoder.encodeBuffer(leftChunk, rightChunk);
gfp, if (mp3buf.length > 0) {
leftChunk, mp3Data.push(mp3buf);
rightChunk,
leftChunk.length,
mp3buf,
0
);
if (bytesEncoded > 0) {
mp3Data.push(new Int8Array(mp3buf.subarray(0, bytesEncoded)));
} }
} }
// Flush remaining data // Flush remaining data
const flushBytes = lame.lame_encode_flush(gfp, mp3buf, 0); const mp3buf = mp3encoder.flush();
if (flushBytes > 0) { if (mp3buf.length > 0) {
mp3Data.push(new Int8Array(mp3buf.subarray(0, flushBytes))); mp3Data.push(mp3buf);
} }
lame.lame_close(gfp);
// Combine all chunks // Combine all chunks
const totalLength = mp3Data.reduce((acc, arr) => acc + arr.length, 0); const totalLength = mp3Data.reduce((acc, arr) => acc + arr.length, 0);
const result = new Uint8Array(totalLength); const result = new Uint8Array(totalLength);

40
types/lamejs.d.ts vendored
View File

@@ -1,31 +1,15 @@
declare module 'lamejs/src/js/MPEGMode' { declare module 'lamejs/src/js/index.js' {
const MPEGMode: { export class Mp3Encoder {
STEREO: number; constructor(channels: number, samplerate: number, kbps: number);
JOINT_STEREO: number; encodeBuffer(left: Int16Array, right: Int16Array): Int8Array;
DUAL_CHANNEL: number; flush(): Int8Array;
MONO: number; }
NOT_SET: number;
};
export default MPEGMode;
}
declare module 'lamejs/src/js/Lame' { export class WavHeader {
export default class Lame { dataOffset: number;
lame_init(): any; dataLen: number;
lame_init_params(gfp: any): number; channels: number;
lame_encode_buffer( sampleRate: number;
gfp: any, static readHeader(dataView: DataView): WavHeader;
left: Int16Array,
right: Int16Array,
samples: number,
mp3buf: Int8Array,
mp3bufPos: number
): number;
lame_encode_flush(gfp: any, mp3buf: Int8Array, mp3bufPos: number): number;
lame_close(gfp: any): number;
} }
} }
declare module 'lamejs/src/js/BitStream' {
export default class BitStream {}
}