@audio/decode 3.11.0 → 3.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/audio-decode.js CHANGED
@@ -77,7 +77,7 @@ export async function* decodeChunked(source, format) {
77
77
 
78
78
  // --- format registration ---
79
79
 
80
- function reg(name, load) {
80
+ function reg(name, load, dedupe) {
81
81
  decode[name] = fmt(name, async () => {
82
82
  let mod = await load()
83
83
  // @audio/* packages export { decoder, default }
@@ -86,7 +86,8 @@ function reg(name, load) {
86
86
  return streamDecoder(
87
87
  chunk => codec.decode(chunk),
88
88
  codec.flush ? () => codec.flush() : null,
89
- codec.free ? () => codec.free() : null
89
+ codec.free ? () => codec.free() : null,
90
+ dedupe
90
91
  )
91
92
  }
92
93
  // wasm-audio-decoders export class with .ready
@@ -96,7 +97,8 @@ function reg(name, load) {
96
97
  return streamDecoder(
97
98
  chunk => codec.decode(chunk),
98
99
  codec.flush ? () => codec.flush() : null,
99
- codec.free ? () => codec.free() : null
100
+ codec.free ? () => codec.free() : null,
101
+ dedupe
100
102
  )
101
103
  })
102
104
  }
@@ -121,23 +123,25 @@ function fmt(name, init) {
121
123
  }
122
124
 
123
125
  // --- codecs ---
126
+ // dedupe (3rd arg): decoder upmixes mono sources to duplicate stereo (verified for mp3;
127
+ // aac/wma flagged conservatively — same lossy-wasm family). Exact containers never dedupe.
124
128
 
125
- reg('mp3', () => import('@audio/decode-mp3'))
129
+ reg('mp3', () => import('@audio/decode-mp3'), true)
126
130
  reg('flac', () => import('@audio/decode-flac'))
127
131
  reg('opus', () => import('@audio/decode-opus'))
128
132
  reg('oga', () => import('@audio/decode-vorbis'))
129
133
 
130
- reg('m4a', () => import('@audio/decode-aac'))
134
+ reg('m4a', () => import('@audio/decode-aac'), true)
131
135
 
132
136
  reg('wav', () => import('@audio/decode-wav'))
133
137
  reg('qoa', () => import('@audio/decode-qoa'))
134
138
 
135
- reg('aac', () => import('@audio/decode-aac'))
139
+ reg('aac', () => import('@audio/decode-aac'), true)
136
140
  reg('aiff', () => import('@audio/decode-aiff'))
137
141
  reg('caf', () => import('@audio/decode-caf'))
138
142
  reg('webm', () => import('@audio/decode-webm'))
139
143
  reg('amr', () => import('@audio/decode-amr'))
140
- reg('wma', () => import('@audio/decode-wma'))
144
+ reg('wma', () => import('@audio/decode-wma'), true)
141
145
 
142
146
  /**
143
147
  * StreamDecoder — a callable function:
@@ -146,26 +150,30 @@ reg('wma', () => import('@audio/decode-wma'))
146
150
  * dec.flush() — flush without freeing
147
151
  * dec.free() — release resources without flushing
148
152
  */
149
- function streamDecoder(onDecode, onFlush, onFree) {
153
+ function streamDecoder(onDecode, onFlush, onFree, dedupe) {
150
154
  let done = false
155
+ // dedupe: this codec's decoder upmixes mono sources to duplicate stereo — collapse it
156
+ // back. Sticky per stream: the moment channels genuinely differ, never collapse again
157
+ // (a real stereo file with a dual-mono passage must not flicker channel count).
158
+ let dd = dedupe ? { live: true } : null
151
159
  let fn = async (chunk) => {
152
160
  if (chunk) {
153
161
  if (done) throw Error('Decoder already freed')
154
- try { return norm(await onDecode(chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk))) }
162
+ try { return norm(await onDecode(chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk)), dd) }
155
163
  catch (e) { done = true; onFree?.(); throw e }
156
164
  }
157
165
  // null/undefined = end of stream
158
166
  if (done) return EMPTY
159
167
  done = true
160
168
  try {
161
- let result = onFlush ? norm(await onFlush()) : EMPTY
169
+ let result = onFlush ? norm(await onFlush(), dd) : EMPTY
162
170
  onFree?.()
163
171
  return result
164
172
  } catch (e) { onFree?.(); throw e }
165
173
  }
166
174
  fn.flush = async () => {
167
175
  if (done) return EMPTY
168
- return onFlush ? norm(await onFlush()) : EMPTY
176
+ return onFlush ? norm(await onFlush(), dd) : EMPTY
169
177
  }
170
178
  fn.free = () => {
171
179
  if (done) return
@@ -176,17 +184,19 @@ function streamDecoder(onDecode, onFlush, onFree) {
176
184
  }
177
185
 
178
186
  // extract { channelData, sampleRate } from codec result
179
- function norm(r) {
187
+ function norm(r, dd) {
180
188
  if (!r?.channelData?.length) return EMPTY
181
189
  let { channelData, sampleRate, samplesDecoded } = r
182
190
  if (samplesDecoded != null && samplesDecoded < channelData[0].length)
183
191
  channelData = channelData.map(ch => ch.subarray(0, samplesDecoded))
184
192
  if (!channelData[0]?.length) return EMPTY
185
- // collapse duplicate stereo to mono (some decoders always output 2ch for mono sources)
186
- if (channelData.length === 2) {
193
+ // collapse duplicate stereo to mono — only for codecs flagged as mono-upmixing;
194
+ // exact containers (wav/flac/…) keep their declared width (dual-mono is legitimate)
195
+ if (dd?.live && channelData.length === 2) {
187
196
  let a = channelData[0], b = channelData[1], same = true
188
197
  for (let i = 0; i < a.length; i += 37) { if (a[i] !== b[i]) { same = false; break } }
189
198
  if (same) channelData = [a]
199
+ else dd.live = false
190
200
  }
191
201
  return { channelData, sampleRate }
192
202
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@audio/decode",
3
- "version": "3.11.0",
3
+ "version": "3.11.2",
4
4
  "description": "Decode audio data in node or browser",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -92,5 +92,6 @@
92
92
  },
93
93
  "engines": {
94
94
  "node": ">=18"
95
- }
95
+ },
96
+ "funding": "https://github.com/sponsors/audiojs"
96
97
  }
package/readme.md CHANGED
@@ -151,4 +151,6 @@ worker.onmessage = ({ data }) => { /* { channelData, sampleRate } */ }
151
151
  * [ffmpeg.wasm](https://github.com/ffmpegwasm/ffmpeg.wasm) – full encoding/decoding library.
152
152
  -->
153
153
 
154
+ <p align="center">Licensing: the umbrella and most codec atoms are MIT; three atoms inherit their upstream codec implementation's license — <a href="./packages/decode-aac">@audio/decode-aac</a> GPL-2.0, <a href="./packages/decode-wma">@audio/decode-wma</a> GPL-2.0-or-later, <a href="./packages/decode-amr">@audio/decode-amr</a> Apache-2.0. Install only the codecs whose licenses fit your project — the umbrella loads them on demand.</p>
155
+
154
156
  <p align="center"><a href="./LICENSE">MIT</a> • <a href="https://github.com/krishnized/license/">ॐ</a>