@audio/wma-decode 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,12 @@
1
+ wma-decode - WMA audio decoder
2
+ Copyright (c) 2025 audiojs
3
+
4
+ This library is free software; you can redistribute it and/or modify it
5
+ under the terms of the GNU Lesser General Public License as published by
6
+ the Free Software Foundation; either version 2.1 of the License, or (at
7
+ your option) any later version.
8
+
9
+ This library is distributed in the hope that it will be useful, but
10
+ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
12
+ License for more details.
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # wma-decode
2
+
3
+ Decode WMA audio to PCM float samples. ASF demuxer in pure JS, WMA decoding via [RockBox](https://www.rockbox.org/) fixed-point decoder compiled to WASM (70 KB).
4
+
5
+ Part of [audio-decode](https://github.com/audiojs/audio-decode).
6
+
7
+ ## Install
8
+
9
+ ```
10
+ npm i @audio/wma-decode
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```js
16
+ import decode from '@audio/wma-decode'
17
+
18
+ let { channelData, sampleRate } = await decode(wmaBuffer)
19
+ ```
20
+
21
+ ### Streaming
22
+
23
+ ```js
24
+ import { decoder } from '@audio/wma-decode'
25
+
26
+ let dec = await decoder()
27
+ let result = dec.decode(chunk)
28
+ dec.free()
29
+ ```
30
+
31
+ ### ASF demuxer only
32
+
33
+ ```js
34
+ import { demuxASF } from '@audio/wma-decode'
35
+
36
+ let { channels, sampleRate, bitRate, packets } = demuxASF(buffer)
37
+ ```
38
+
39
+ ## API
40
+
41
+ ### `decode(src): Promise<AudioData>`
42
+
43
+ Whole-file decode. Accepts `Uint8Array` or `ArrayBuffer`.
44
+
45
+ ### `decoder(): Promise<WMADecoder>`
46
+
47
+ Creates a decoder instance.
48
+
49
+ - **`dec.decode(data)`** — decode chunk, returns `{ channelData, sampleRate }`
50
+ - **`dec.flush()`** — flush (returns empty)
51
+ - **`dec.free()`** — release WASM memory
52
+
53
+ ### `demuxASF(buf): ASFInfo`
54
+
55
+ Parse ASF container without decoding. Returns stream properties and raw packets.
56
+
57
+ ## Formats
58
+
59
+ - WMA v1 (0x0160)
60
+ - WMA v2 (0x0161)
61
+
62
+ WMA Pro and Lossless are not supported. An FFmpeg-based build is available via `build-ffmpeg.sh` for those formats.
63
+
64
+ ## Building WASM
65
+
66
+ ```
67
+ npm run build
68
+ ```
69
+
70
+ RockBox source is included in `lib/rockbox-wma/` (3 files, 152 KB).
71
+
72
+ ## License
73
+
74
+ GPL-2.0+ (RockBox)
75
+
76
+ <a href="https://github.com/krishnized/license/">ॐ</a>
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@audio/wma-decode",
3
+ "version": "1.0.0",
4
+ "description": "Decode WMA audio via RockBox WASM",
5
+ "type": "module",
6
+ "main": "wma-decode.js",
7
+ "types": "wma-decode.d.ts",
8
+ "exports": {
9
+ ".": "./wma-decode.js",
10
+ "./package.json": "./package.json"
11
+ },
12
+ "files": [
13
+ "wma-decode.js",
14
+ "wma-decode.d.ts",
15
+ "src/wma.wasm.cjs",
16
+ "LICENSE"
17
+ ],
18
+ "scripts": {
19
+ "build": "bash build.sh",
20
+ "prepack": "npm run build",
21
+ "test": "node test.js"
22
+ },
23
+ "keywords": [
24
+ "wma",
25
+ "windows-media-audio",
26
+ "asf",
27
+ "audio",
28
+ "decode",
29
+ "decoder",
30
+ "wasm",
31
+ "rockbox"
32
+ ],
33
+ "publishConfig": { "access": "public" },
34
+ "license": "GPL-2.0-or-later",
35
+ "author": "audiojs",
36
+ "homepage": "https://github.com/audiojs/wma-decode#readme",
37
+ "bugs": "https://github.com/audiojs/wma-decode/issues",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/audiojs/wma-decode.git"
41
+ },
42
+ "engines": {
43
+ "node": ">=16"
44
+ }
45
+ }
Binary file
@@ -0,0 +1,33 @@
1
+ interface AudioData {
2
+ channelData: Float32Array[];
3
+ sampleRate: number;
4
+ }
5
+
6
+ interface WmaDecoder {
7
+ decode(data: Uint8Array): AudioData;
8
+ flush(): AudioData;
9
+ free(): void;
10
+ }
11
+
12
+ /** Decode WMA audio buffer to PCM samples */
13
+ export default function decode(src: ArrayBuffer | Uint8Array): Promise<AudioData>;
14
+
15
+ /** Create streaming decoder instance */
16
+ export function decoder(): Promise<WmaDecoder>;
17
+
18
+ /** Parse ASF data packet, extract compressed audio payloads */
19
+ export function parsePacket(pkt: Uint8Array, packetSize: number): Uint8Array[];
20
+
21
+ /** Parse ASF container, extract audio properties and raw packets */
22
+ export function demuxASF(buf: Uint8Array): {
23
+ channels: number;
24
+ sampleRate: number;
25
+ bitRate: number;
26
+ blockAlign: number;
27
+ bitsPerSample: number;
28
+ formatTag: number;
29
+ codecData: Uint8Array | null;
30
+ duration: number;
31
+ packetSize: number;
32
+ packets: Uint8Array[];
33
+ };
package/wma-decode.js ADDED
@@ -0,0 +1,488 @@
1
+ /**
2
+ * WMA decoder — ASF demuxer (pure JS) + RockBox wmadec (WASM)
3
+ * Decodes WMA v1/v2 in ASF containers
4
+ *
5
+ * let { channelData, sampleRate } = await decode(wmabuf)
6
+ */
7
+
8
+ const EMPTY = Object.freeze({ channelData: [], sampleRate: 0 })
9
+
10
+ // ASF GUIDs (16 bytes each, little-endian)
11
+ const GUID = {
12
+ header: [0x30,0x26,0xb2,0x75,0x8e,0x66,0xcf,0x11,0xa6,0xd9,0x00,0xaa,0x00,0x62,0xce,0x6c],
13
+ fileProps: [0xa1,0xdc,0xab,0x8c,0x47,0xa9,0xcf,0x11,0x8e,0xe4,0x00,0xc0,0x0c,0x20,0x53,0x65],
14
+ streamProps: [0x91,0x07,0xdc,0xb7,0xb7,0xa9,0xcf,0x11,0x8e,0xe6,0x00,0xc0,0x0c,0x20,0x53,0x65],
15
+ audioMedia: [0x40,0x9e,0x69,0xf8,0x4d,0x5b,0xcf,0x11,0xa8,0xfd,0x00,0x80,0x5f,0x5c,0x44,0x2b],
16
+ data: [0x36,0x26,0xb2,0x75,0x8e,0x66,0xcf,0x11,0xa6,0xd9,0x00,0xaa,0x00,0x62,0xce,0x6c],
17
+ headerExt: [0xb5,0x03,0xbf,0x5f,0x2e,0xa9,0xcf,0x11,0x8e,0xe3,0x00,0xc0,0x0c,0x20,0x53,0x65],
18
+ }
19
+
20
+ function guidEq(buf, off, guid) {
21
+ for (let i = 0; i < 16; i++) if (buf[off + i] !== guid[i]) return false
22
+ return true
23
+ }
24
+
25
+ // Little-endian readers
26
+ function u16(b, o) { return b[o] | (b[o + 1] << 8) }
27
+ function u32(b, o) { return (b[o] | (b[o + 1] << 8) | (b[o + 2] << 16) | (b[o + 3] << 24)) >>> 0 }
28
+ function u64(b, o) {
29
+ // JS safe up to 2^53; read low 32 + high 32
30
+ let lo = u32(b, o), hi = u32(b, o + 4)
31
+ return hi * 0x100000000 + lo
32
+ }
33
+
34
+ /**
35
+ * Parse ASF container — extract audio stream properties + data packets
36
+ * @param {Uint8Array} buf
37
+ * @returns {{ channels, sampleRate, bitRate, blockAlign, bitsPerSample, formatTag, codecData, duration, packetSize, packets }}
38
+ */
39
+ export function demuxASF(buf) {
40
+ if (!(buf instanceof Uint8Array)) buf = new Uint8Array(buf)
41
+ if (buf.length < 30) throw Error('Not an ASF/WMA file')
42
+
43
+ // Verify ASF Header Object GUID
44
+ if (!guidEq(buf, 0, GUID.header)) throw Error('Not an ASF/WMA file')
45
+
46
+ let headerSize = u64(buf, 16)
47
+ let numObjects = u32(buf, 24)
48
+ // reserved1(1) + reserved2(1) at offset 28-29
49
+
50
+ let audio = null // WAVEFORMATEX fields
51
+ let packetSize = 0
52
+ let duration = 0 // in seconds
53
+ let datOff = 0 // data object offset
54
+ let datSize = 0 // data object total size
55
+ let totalPackets = 0
56
+
57
+ // Parse header sub-objects
58
+ let pos = 30
59
+ let headerEnd = Math.min(Number(headerSize), buf.length)
60
+ for (let i = 0; i < numObjects && pos < headerEnd - 24; i++) {
61
+ let objSize = u64(buf, pos + 16)
62
+ if (objSize < 24) break
63
+ let objEnd = pos + Math.min(Number(objSize), headerEnd - pos)
64
+
65
+ if (guidEq(buf, pos, GUID.streamProps)) {
66
+ audio = parseStreamProps(buf, pos + 24, objEnd) || audio
67
+ } else if (guidEq(buf, pos, GUID.fileProps)) {
68
+ let fp = parseFileProps(buf, pos + 24, objEnd)
69
+ if (fp) { packetSize = fp.packetSize; duration = fp.duration }
70
+ } else if (guidEq(buf, pos, GUID.headerExt)) {
71
+ // Header Extension Object — nested sub-objects skipped (no metadata needed for decode)
72
+ }
73
+
74
+ pos = objEnd
75
+ }
76
+
77
+ if (!audio) throw Error('No audio stream in ASF')
78
+
79
+ // Find Data Object after header
80
+ pos = Number(headerSize)
81
+ if (pos + 50 <= buf.length && guidEq(buf, pos, GUID.data)) {
82
+ datSize = u64(buf, pos + 16)
83
+ // fileId(16) + totalPackets(8) + reserved(2) = 26 bytes after object header
84
+ totalPackets = u64(buf, pos + 24 + 16)
85
+ datOff = pos + 24 + 26 // start of packet data
86
+ }
87
+
88
+ // Extract packets
89
+ let packets = []
90
+ if (datOff && packetSize > 0) {
91
+ let datEnd = Math.min(pos + Number(datSize), buf.length)
92
+ let ppos = datOff
93
+ while (ppos + packetSize <= datEnd) {
94
+ packets.push(buf.subarray(ppos, ppos + packetSize))
95
+ ppos += packetSize
96
+ }
97
+ } else if (datOff && totalPackets > 0) {
98
+ // Variable-size packets — fallback: treat remaining data as one blob
99
+ let datEnd = Math.min(pos + Number(datSize), buf.length)
100
+ if (datOff < datEnd) packets.push(buf.subarray(datOff, datEnd))
101
+ }
102
+
103
+ return {
104
+ channels: audio.channels,
105
+ sampleRate: audio.sampleRate,
106
+ bitRate: audio.avgBytesPerSec * 8,
107
+ blockAlign: audio.blockAlign,
108
+ bitsPerSample: audio.bitsPerSample,
109
+ formatTag: audio.formatTag,
110
+ codecData: audio.codecData,
111
+ duration,
112
+ packetSize,
113
+ packets
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Parse Stream Properties Object body
119
+ * Returns audio WAVEFORMATEX fields if this is an audio stream, null otherwise
120
+ */
121
+ function parseStreamProps(buf, start, end) {
122
+ // Stream Type GUID(16) + Error Correction Type GUID(16) + Time Offset(8)
123
+ // + Type-Specific Data Length(4) + Error Correction Data Length(4)
124
+ // + Flags(2) + Reserved(4)
125
+ if (start + 54 > end) return null
126
+
127
+ // Check that stream type is Audio Media
128
+ if (!guidEq(buf, start, GUID.audioMedia)) return null
129
+
130
+ // flags(2) + reserved(4) = 6 bytes after type/ec data lengths
131
+ let waveOff = start + 54
132
+
133
+ if (waveOff + 18 > end) return null
134
+
135
+ // WAVEFORMATEX
136
+ let formatTag = u16(buf, waveOff)
137
+ let channels = u16(buf, waveOff + 2)
138
+ let sampleRate = u32(buf, waveOff + 4)
139
+ let avgBytesPerSec = u32(buf, waveOff + 8)
140
+ let blockAlign = u16(buf, waveOff + 12)
141
+ let bitsPerSample = u16(buf, waveOff + 14)
142
+ let cbSize = u16(buf, waveOff + 16)
143
+
144
+ // Codec-specific data follows WAVEFORMATEX
145
+ let codecData = null
146
+ if (cbSize > 0 && waveOff + 18 + cbSize <= end) {
147
+ codecData = buf.subarray(waveOff + 18, waveOff + 18 + cbSize)
148
+ }
149
+
150
+ return { formatTag, channels, sampleRate, avgBytesPerSec, blockAlign, bitsPerSample, codecData }
151
+ }
152
+
153
+ /**
154
+ * Parse File Properties Object body
155
+ * Returns { packetSize, duration } or null
156
+ */
157
+ function parseFileProps(buf, start, end) {
158
+ // File ID(16) + File Size(8) + Creation Date(8) + Data Packets Count(8)
159
+ // + Play Duration(8) + Send Duration(8) + Preroll(8)
160
+ // + Flags(4) + Min Data Packet Size(4) + Max Data Packet Size(4)
161
+ // + Max Bitrate(4)
162
+ if (start + 80 > end) return null
163
+
164
+ // Play Duration is 100-ns units (offset 40)
165
+ let playDur = u64(buf, start + 40)
166
+ // Preroll in ms (offset 56)
167
+ let preroll = u64(buf, start + 56)
168
+ // Duration in seconds
169
+ let duration = Math.max(0, playDur / 10000000 - preroll / 1000)
170
+
171
+ // Max packet size at offset 72 (min at 68, always equal for WMA)
172
+ let packetSize = u32(buf, start + 72)
173
+
174
+ return { packetSize, duration }
175
+ }
176
+
177
+ /**
178
+ * Parse ASF data packet — extract compressed audio payload(s)
179
+ * Returns array of payload buffers
180
+ */
181
+ export function parsePacket(pkt, packetSize) {
182
+ if (!pkt || pkt.length < 3) return []
183
+
184
+ let pos = 0
185
+
186
+ // Error Correction — first byte flags
187
+ let ecFlags = pkt[pos++]
188
+ if (ecFlags & 0x80) {
189
+ // Error correction present
190
+ let ecLen = ecFlags & 0x0F
191
+ // Opaque data flag is bit 4
192
+ pos += ecLen
193
+ }
194
+
195
+ if (pos >= pkt.length) return []
196
+
197
+ // Payload Parsing Information
198
+ let ppFlags = pkt[pos++]
199
+ let lenFlags = pkt[pos++]
200
+
201
+ let multiplePayloads = !!(ppFlags & 0x01)
202
+ let seqType = (ppFlags >> 1) & 0x03
203
+ let padType = (ppFlags >> 3) & 0x03
204
+ let pktLenType = (ppFlags >> 5) & 0x03
205
+
206
+ let repType = lenFlags & 0x03
207
+ let offType = (lenFlags >> 2) & 0x03
208
+ let medNumType = (lenFlags >> 4) & 0x03
209
+
210
+ // Skip optional packet length field
211
+ if (pktLenType === 1) pos += 1
212
+ else if (pktLenType === 2) pos += 2
213
+ else if (pktLenType === 3) pos += 4
214
+
215
+ // Sequence (skip)
216
+ if (seqType === 1) pos += 1
217
+ else if (seqType === 2) pos += 2
218
+ else if (seqType === 3) pos += 4
219
+
220
+ // Padding length
221
+ let padLen = 0
222
+ if (padType === 1) { padLen = pkt[pos++] }
223
+ else if (padType === 2) { padLen = u16(pkt, pos); pos += 2 }
224
+ else if (padType === 3) { padLen = u32(pkt, pos); pos += 4 }
225
+
226
+ // Send time (4 bytes) + duration (2 bytes)
227
+ pos += 6
228
+
229
+ if (pos >= pkt.length) return []
230
+
231
+ let payloads = []
232
+
233
+ if (!multiplePayloads) {
234
+ // Single payload — rest of packet minus padding
235
+ pos += 1 // stream number (always 1 byte)
236
+
237
+ // Media object number
238
+ pos += fieldSize(medNumType)
239
+
240
+ // Offset into media object
241
+ pos += fieldSize(offType)
242
+
243
+ // Replicated data length
244
+ let repLen = readVarField(pkt, pos, repType)
245
+ pos += fieldSize(repType)
246
+
247
+ // Skip replicated data
248
+ if (repLen === 1) {
249
+ // Compressed payload: repLen==1 means compressed
250
+ pos += 1 // presentation time delta
251
+ } else {
252
+ pos += repLen
253
+ }
254
+
255
+ let payloadLen = pkt.length - pos - padLen
256
+ if (payloadLen > 0 && pos + payloadLen <= pkt.length) {
257
+ payloads.push(pkt.subarray(pos, pos + payloadLen))
258
+ }
259
+ } else {
260
+ // Multiple payloads
261
+ let payloadFlags = pkt[pos++]
262
+ let numPayloads = payloadFlags & 0x3F
263
+ let payLenType = (payloadFlags >> 6) & 0x03
264
+
265
+ for (let i = 0; i < numPayloads && pos < pkt.length; i++) {
266
+ // Stream number (1 byte, lower 7 = number, bit 7 = key frame)
267
+ pos += 1
268
+
269
+ // Media object number
270
+ pos += fieldSize(medNumType)
271
+
272
+ // Offset into media object
273
+ pos += fieldSize(offType)
274
+
275
+ // Replicated data
276
+ let repLen = readVarField(pkt, pos, repType)
277
+ pos += fieldSize(repType)
278
+
279
+ if (repLen === 1) {
280
+ pos += 1 // compressed: presentation time delta
281
+ } else {
282
+ pos += repLen
283
+ }
284
+
285
+ // Payload length
286
+ let payLen = 0
287
+ if (payLenType === 1) { payLen = pkt[pos++] }
288
+ else if (payLenType === 2) { payLen = u16(pkt, pos); pos += 2 }
289
+ else if (payLenType === 3) { payLen = u32(pkt, pos); pos += 4 }
290
+ else { payLen = pkt.length - pos }
291
+
292
+ if (payLen > 0 && pos + payLen <= pkt.length) {
293
+ payloads.push(pkt.subarray(pos, pos + payLen))
294
+ }
295
+ pos += payLen
296
+ }
297
+ }
298
+
299
+ return payloads
300
+ }
301
+
302
+ function fieldSize(type) {
303
+ return type === 0 ? 0 : type === 1 ? 1 : type === 2 ? 2 : 4
304
+ }
305
+
306
+ function readVarField(buf, off, type) {
307
+ if (type === 0) return 0
308
+ if (type === 1) return buf[off] || 0
309
+ if (type === 2) return (off + 2 <= buf.length) ? u16(buf, off) : 0
310
+ return (off + 4 <= buf.length) ? u32(buf, off) : 0
311
+ }
312
+
313
+ // ===== WMA format tag names =====
314
+
315
+ const WMA_FORMATS = {
316
+ 0x0160: 'WMAv1',
317
+ 0x0161: 'WMAv2',
318
+ 0x0162: 'WMAPro',
319
+ 0x0163: 'WMALossless',
320
+ }
321
+
322
+ // ===== WASM module loader =====
323
+
324
+ let _modP
325
+
326
+ async function getMod() {
327
+ if (_modP) return _modP
328
+ let p = (async () => {
329
+ let createWMA
330
+ if (typeof process !== 'undefined' && process.versions?.node) {
331
+ let m = 'module'
332
+ let { createRequire } = await import(m)
333
+ createWMA = createRequire(import.meta.url)('./src/wma.wasm.cjs')
334
+ } else {
335
+ let mod = await import('./src/wma.wasm.cjs')
336
+ createWMA = mod.default || mod
337
+ }
338
+ return createWMA()
339
+ })()
340
+ _modP = p
341
+ try { return await p }
342
+ catch (e) { _modP = null; throw e }
343
+ }
344
+
345
+ /**
346
+ * Whole-file decode
347
+ * @param {Uint8Array|ArrayBuffer} src
348
+ * @returns {Promise<{channelData: Float32Array[], sampleRate: number}>}
349
+ */
350
+ export default async function decode(src) {
351
+ let buf = src instanceof Uint8Array ? src : new Uint8Array(src)
352
+ let dec = await decoder()
353
+ try {
354
+ return dec.decode(buf)
355
+ } finally {
356
+ dec.free()
357
+ }
358
+ }
359
+
360
+ /**
361
+ * Create decoder instance
362
+ * @returns {Promise<{decode(chunk: Uint8Array): {channelData, sampleRate}, flush(), free()}>}
363
+ */
364
+ export async function decoder() {
365
+ return new WMADecoder(await getMod())
366
+ }
367
+
368
+ class WMADecoder {
369
+ constructor(mod) {
370
+ this.m = mod
371
+ this.h = null
372
+ this.sr = 0
373
+ this.ch = 0
374
+ this.done = false
375
+ this._ptr = 0
376
+ this._cap = 0
377
+ }
378
+
379
+ decode(data) {
380
+ if (this.done) throw Error('Decoder already freed')
381
+ if (!data?.length) return EMPTY
382
+
383
+ let buf = data instanceof Uint8Array ? data : new Uint8Array(data)
384
+ let asf = demuxASF(buf)
385
+
386
+ if (!asf.packets.length) return EMPTY
387
+
388
+ let fmt = WMA_FORMATS[asf.formatTag]
389
+ if (!fmt) throw Error('Unsupported WMA format tag: 0x' + asf.formatTag.toString(16))
390
+
391
+ this.sr = asf.sampleRate
392
+ this.ch = asf.channels
393
+
394
+ let m = this.m
395
+
396
+ // Init WASM decoder with audio properties
397
+ let extraPtr = 0, extraLen = 0
398
+ if (asf.codecData?.length) {
399
+ extraPtr = this._alloc(asf.codecData.length)
400
+ m.HEAPU8.set(asf.codecData, extraPtr)
401
+ extraLen = asf.codecData.length
402
+ }
403
+
404
+ this.h = m._wma_create(
405
+ asf.channels, asf.sampleRate, asf.bitRate,
406
+ asf.blockAlign, asf.formatTag, asf.bitsPerSample,
407
+ extraPtr, extraLen
408
+ )
409
+ if (!this.h) throw Error('WMA decoder init failed')
410
+
411
+ // Extract payloads from packets and decode
412
+ // Each payload is one blockAlign-sized WMA superframe
413
+ let chunks = []
414
+ let totalPerCh = 0
415
+ let channels = asf.channels
416
+ let errors = 0
417
+ let ba = asf.blockAlign
418
+
419
+ for (let pkt of asf.packets) {
420
+ let payloads = parsePacket(pkt, asf.packetSize)
421
+ for (let payload of payloads) {
422
+ // Split payload into blockAlign-sized frames
423
+ for (let off = 0; off + ba <= payload.length; off += ba) {
424
+ let frame = payload.subarray(off, off + ba)
425
+ let ptr = this._alloc(ba)
426
+ m.HEAPU8.set(frame, ptr)
427
+ let out = m._wma_decode(this.h, ptr, ba)
428
+ if (!out) { errors++; continue }
429
+
430
+ let n = m._wma_samples()
431
+ let sr = m._wma_samplerate()
432
+ if (sr) this.sr = sr
433
+ let ch = m._wma_channels()
434
+ if (ch) channels = ch
435
+
436
+ let spc = n / channels
437
+ let samples = new Float32Array(m.HEAPF32.buffer, out, n).slice()
438
+ chunks.push({ data: samples, ch: channels, spc })
439
+ totalPerCh += spc
440
+ }
441
+ }
442
+ }
443
+
444
+ if (!totalPerCh) {
445
+ if (errors) throw Error(errors + ' frame(s) failed to decode')
446
+ return EMPTY
447
+ }
448
+
449
+ // De-interleave
450
+ let channelData = Array.from({ length: channels }, () => new Float32Array(totalPerCh))
451
+ let pos = 0
452
+ for (let { data, ch, spc } of chunks) {
453
+ for (let c = 0; c < ch; c++) {
454
+ let out = channelData[c]
455
+ for (let s = 0; s < spc; s++) out[pos + s] = data[s * ch + c]
456
+ }
457
+ pos += spc
458
+ }
459
+
460
+ return { channelData, sampleRate: this.sr }
461
+ }
462
+
463
+ flush() { return EMPTY }
464
+
465
+ free() {
466
+ if (this.done) return
467
+ this.done = true
468
+ if (this.h) {
469
+ this.m._wma_close(this.h)
470
+ this.m._wma_free_buf()
471
+ this.h = null
472
+ }
473
+ if (this._ptr) {
474
+ this.m._free(this._ptr)
475
+ this._ptr = 0
476
+ this._cap = 0
477
+ }
478
+ }
479
+
480
+ _alloc(len) {
481
+ if (len > this._cap) {
482
+ if (this._ptr) this.m._free(this._ptr)
483
+ this._cap = len
484
+ this._ptr = this.m._malloc(len)
485
+ }
486
+ return this._ptr
487
+ }
488
+ }