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:
@@ -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
40
types/lamejs.d.ts
vendored
@@ -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 {}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user