11/**
2- * Audio encoder: whole-file and streaming
2+ * Audio encoder: whole-file and chunked
33 * @module encode-audio
44 *
55 * let buf = await encode.wav(channelData, { sampleRate: 44100 })
66 *
7+ * for await (let bytes of encode.mp3(source, { sampleRate: 44100 })) { ... }
8+ *
79 * let enc = await encode.mp3({ sampleRate: 44100, bitrate: 128 })
810 * let chunk = await enc(channelData)
911 * let final = await enc() // flush + free
1012 */
1113
1214const EMPTY = new Uint8Array ( 0 )
1315
14- const encode = { }
16+ /**
17+ * Encode audio — delegates to format-specific encoder.
18+ * encode('wav', channelData, { sampleRate }) → Promise<Uint8Array>
19+ * encode('wav', source(), { sampleRate }) → AsyncGenerator<Uint8Array>
20+ * encode('wav', { sampleRate }) → Promise<StreamEncoder>
21+ */
22+ function encode ( format , data , opts ) {
23+ if ( ! encode [ format ] ) throw Error ( 'Unknown format: ' + format )
24+ return encode [ format ] ( data , opts )
25+ }
1526export default encode
1627
28+ function isAudioData ( d ) {
29+ return d instanceof Float32Array || Array . isArray ( d ) || ( d ?. getChannelData && d ?. numberOfChannels )
30+ }
31+
32+ /**
33+ * Encode a stream of PCM chunks to the given format.
34+ * @param {AsyncIterable<Float32Array[]|Float32Array> } source
35+ * @param {string } format
36+ * @param {object } opts - encoder options (sampleRate required)
37+ * @returns {AsyncGenerator<Uint8Array> }
38+ */
39+ async function * encodeChunked ( source , format , opts ) {
40+ let enc = await encode [ format ] ( opts )
41+ try {
42+ for await ( let chunk of source ) {
43+ let buf = await enc ( chunk )
44+ if ( buf . length ) yield buf
45+ }
46+ let final = await enc ( )
47+ if ( final . length ) yield final
48+ } catch ( e ) { enc . free ( ) ; throw e }
49+ }
50+ export { encodeChunked }
51+
1752// --- format registration ---
1853
1954function reg ( name , load ) {
20- encode [ name ] = fmt ( async ( opts ) => {
55+ encode [ name ] = fmt ( name , async ( opts ) => {
2156 let init = ( await load ( ) ) . default
2257 let codec = await init ( opts )
2358 return streamEncoder ( ch => codec . encode ( ch ) , ( ) => codec . flush ( ) , ( ) => codec . free ( ) )
@@ -36,26 +71,31 @@ reg('opus', () => import('@audio/encode-opus'))
3671 * 1 arg (opts) → streaming encoder function
3772 * 2 args (data, opts) → whole-file encode
3873 */
39- function fmt ( init ) {
40- let fn = async ( data , opts ) => {
74+ function fmt ( name , init ) {
75+ let fn = ( data , opts ) => {
4176 // 1 arg = streaming: encode.mp3({ sampleRate })
4277 if ( ! opts ) return init ( data )
78+ // 2 args, async iterable = chunked: encode.mp3(source(), { sampleRate })
79+ if ( data && ( typeof data [ Symbol . asyncIterator ] === 'function' || typeof data [ Symbol . iterator ] === 'function' && ! isAudioData ( data ) ) )
80+ return encodeChunked ( data , name , opts )
4381 // 2 args = whole-file: encode.mp3(channelData, { sampleRate })
44- if ( ! opts . sampleRate ) throw Error ( 'sampleRate is required' )
45- let ch = channels ( data )
46- if ( ! ch . length || ! ch [ 0 ] . length ) return EMPTY
47- let enc = await init ( { channels : ch . length , ...opts } )
48- try {
49- let result = await enc ( ch )
50- let flushed = await enc ( )
51- return merge ( result , flushed )
52- } catch ( e ) { enc . free ( ) ; throw e }
82+ return wholeFile ( data , opts , init )
5383 }
54- // TODO: remove .stream in next major
55- fn . stream = init
5684 return fn
5785}
5886
87+ async function wholeFile ( data , opts , init ) {
88+ if ( ! opts . sampleRate ) throw Error ( 'sampleRate is required' )
89+ let ch = channels ( data )
90+ if ( ! ch . length || ! ch [ 0 ] . length ) return EMPTY
91+ let enc = await init ( { channels : ch . length , ...opts } )
92+ try {
93+ let result = await enc ( ch )
94+ let flushed = await enc ( )
95+ return merge ( result , flushed )
96+ } catch ( e ) { enc . free ( ) ; throw e }
97+ }
98+
5999// normalize input to Float32Array[]
60100function channels ( data ) {
61101 if ( ! data ) return [ ]
@@ -98,8 +138,6 @@ export function streamEncoder(onEncode, onFlush, onFree) {
98138 return result
99139 } catch ( e ) { onFree ?. ( ) ; throw e }
100140 }
101- // TODO: remove .encode in next major
102- fn . encode = fn
103141 fn . flush = async ( ) => {
104142 if ( done ) return EMPTY
105143 return onFlush ? norm ( await onFlush ( ) ) : EMPTY
0 commit comments