aac-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 +9 -0
- package/README.md +60 -0
- package/aac-decode.d.ts +16 -0
- package/aac-decode.js +337 -0
- package/package.json +35 -0
- package/src/aac.wasm.cjs +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
This work is offered to Krishna (https://github.com/krishnized/license).
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
This package is licensed under the GNU General Public License v2.0 (GPL-2.0),
|
|
6
|
+
as required by the included FAAD2 library.
|
|
7
|
+
|
|
8
|
+
FAAD2 Copyright (C) 2003-2005 M. Bakker, Nero AG, http://www.nero.com
|
|
9
|
+
Full GPL-2.0 text: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# aac-decode
|
|
2
|
+
|
|
3
|
+
Decode AAC/M4A audio to PCM float samples. FAAD2 compiled to WASM — works in Node.js and browsers, no native dependencies.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
npm i aac-decode
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import decode from 'aac-decode'
|
|
15
|
+
|
|
16
|
+
// M4A or raw ADTS — auto-detected
|
|
17
|
+
let { channelData, sampleRate } = await decode(uint8array)
|
|
18
|
+
// channelData: Float32Array[] (one per channel)
|
|
19
|
+
// sampleRate: number
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Streaming
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
import { decoder } from 'aac-decode'
|
|
26
|
+
|
|
27
|
+
let dec = await decoder()
|
|
28
|
+
let { channelData, sampleRate } = dec.decode(chunk)
|
|
29
|
+
dec.free()
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## API
|
|
33
|
+
|
|
34
|
+
### `decode(src: Uint8Array | ArrayBuffer): Promise<AudioData>`
|
|
35
|
+
|
|
36
|
+
Whole-file decode. Auto-detects M4A (MP4 container) vs raw ADTS.
|
|
37
|
+
|
|
38
|
+
### `decoder(): Promise<AACDecoder>`
|
|
39
|
+
|
|
40
|
+
Creates a decoder instance for manual control.
|
|
41
|
+
|
|
42
|
+
- **`dec.decode(data)`** — decode chunk, returns `{ channelData, sampleRate }`
|
|
43
|
+
- **`dec.flush()`** — flush remaining (returns empty for AAC)
|
|
44
|
+
- **`dec.free()`** — release WASM memory
|
|
45
|
+
|
|
46
|
+
### `AudioData`
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
{ channelData: Float32Array[], sampleRate: number }
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Formats
|
|
53
|
+
|
|
54
|
+
- M4A / MP4 with AAC audio
|
|
55
|
+
- Raw ADTS streams (.aac)
|
|
56
|
+
- LC, HE-AAC v1/v2 (SBR, PS)
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
GPL-2.0 (FAAD2) — [krishnized](https://github.com/krishnized/license)
|
package/aac-decode.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface AudioData {
|
|
2
|
+
channelData: Float32Array[];
|
|
3
|
+
sampleRate: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface AACDecoder {
|
|
7
|
+
decode(data: Uint8Array): AudioData;
|
|
8
|
+
flush(): AudioData;
|
|
9
|
+
free(): void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Whole-file decode — auto-detects M4A vs ADTS */
|
|
13
|
+
export default function decode(src: ArrayBuffer | Uint8Array): Promise<AudioData>;
|
|
14
|
+
|
|
15
|
+
/** Create streaming decoder instance */
|
|
16
|
+
export function decoder(): Promise<AACDecoder>;
|
package/aac-decode.js
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AAC decoder — FAAD2 compiled to WASM
|
|
3
|
+
* Decodes M4A (MP4/AAC) and raw ADTS streams
|
|
4
|
+
*
|
|
5
|
+
* let { channelData, sampleRate } = await decode(m4abuf)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
let _modP
|
|
9
|
+
|
|
10
|
+
async function getMod() {
|
|
11
|
+
if (_modP) return _modP
|
|
12
|
+
let p = (async () => {
|
|
13
|
+
let createAAC
|
|
14
|
+
if (typeof process !== 'undefined' && process.versions?.node) {
|
|
15
|
+
let { createRequire } = await import('module')
|
|
16
|
+
createAAC = createRequire(import.meta.url)('./src/aac.wasm.cjs')
|
|
17
|
+
} else {
|
|
18
|
+
let mod = await import('./src/aac.wasm.cjs')
|
|
19
|
+
createAAC = mod.default || mod
|
|
20
|
+
}
|
|
21
|
+
return createAAC()
|
|
22
|
+
})()
|
|
23
|
+
_modP = p
|
|
24
|
+
try { return await p }
|
|
25
|
+
catch (e) { _modP = null; throw e }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Whole-file decode
|
|
30
|
+
* @param {Uint8Array|ArrayBuffer} src
|
|
31
|
+
* @returns {Promise<{channelData: Float32Array[], sampleRate: number}>}
|
|
32
|
+
*/
|
|
33
|
+
export default async function decode(src) {
|
|
34
|
+
let buf = src instanceof Uint8Array ? src : new Uint8Array(src)
|
|
35
|
+
let dec = await decoder()
|
|
36
|
+
try {
|
|
37
|
+
return dec.decode(buf)
|
|
38
|
+
} finally {
|
|
39
|
+
dec.free()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Create decoder instance
|
|
45
|
+
* @returns {Promise<{decode(chunk: Uint8Array): {channelData, sampleRate}, flush(), free()}>}
|
|
46
|
+
*/
|
|
47
|
+
export async function decoder() {
|
|
48
|
+
return new AACDecoder(await getMod())
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const EMPTY = Object.freeze({ channelData: [], sampleRate: 0 })
|
|
52
|
+
|
|
53
|
+
class AACDecoder {
|
|
54
|
+
constructor(mod) {
|
|
55
|
+
this.m = mod
|
|
56
|
+
this.h = null
|
|
57
|
+
this.sr = 0
|
|
58
|
+
this.ch = 0
|
|
59
|
+
this.done = false
|
|
60
|
+
this._ptr = 0
|
|
61
|
+
this._cap = 0
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
decode(data) {
|
|
65
|
+
if (this.done) throw Error('Decoder already freed')
|
|
66
|
+
if (!data?.length) return EMPTY
|
|
67
|
+
|
|
68
|
+
let buf = data instanceof Uint8Array ? data : new Uint8Array(data)
|
|
69
|
+
|
|
70
|
+
// detect M4A (ftyp box at offset 4)
|
|
71
|
+
if (buf.length > 8 && buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70)
|
|
72
|
+
return this._decodeM4A(buf)
|
|
73
|
+
|
|
74
|
+
return this._decodeADTS(buf)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
flush() { return EMPTY }
|
|
78
|
+
|
|
79
|
+
free() {
|
|
80
|
+
if (this.done) return
|
|
81
|
+
this.done = true
|
|
82
|
+
if (this.h) {
|
|
83
|
+
this.m._aac_close(this.h)
|
|
84
|
+
this.m._aac_free_buf()
|
|
85
|
+
this.h = null
|
|
86
|
+
}
|
|
87
|
+
if (this._ptr) {
|
|
88
|
+
this.m._free(this._ptr)
|
|
89
|
+
this._ptr = 0
|
|
90
|
+
this._cap = 0
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
_alloc(len) {
|
|
95
|
+
if (len > this._cap) {
|
|
96
|
+
if (this._ptr) this.m._free(this._ptr)
|
|
97
|
+
this._cap = len
|
|
98
|
+
this._ptr = this.m._malloc(len)
|
|
99
|
+
}
|
|
100
|
+
return this._ptr
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
_decodeADTS(buf) {
|
|
104
|
+
let m = this.m
|
|
105
|
+
if (!this.h) {
|
|
106
|
+
this.h = m._aac_create()
|
|
107
|
+
let srP = m._aac_sr_ptr(), chP = m._aac_ch_ptr()
|
|
108
|
+
let ptr = this._alloc(buf.length)
|
|
109
|
+
m.HEAPU8.set(buf, ptr)
|
|
110
|
+
let consumed = m._aac_init(this.h, ptr, buf.length, srP, chP)
|
|
111
|
+
if (consumed < 0) throw Error('ADTS init failed (code ' + consumed + ')')
|
|
112
|
+
this.sr = m.getValue(srP, 'i32')
|
|
113
|
+
this.ch = m.getValue(chP, 'i8')
|
|
114
|
+
if (!this.ch) throw Error('ADTS init: no channels detected')
|
|
115
|
+
buf = buf.subarray(consumed)
|
|
116
|
+
}
|
|
117
|
+
return this._feedFrames(buf)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
_decodeM4A(buf) {
|
|
121
|
+
let { asc, frames } = demuxM4A(buf)
|
|
122
|
+
if (!asc || !frames.length) return EMPTY
|
|
123
|
+
|
|
124
|
+
let m = this.m
|
|
125
|
+
this.h = m._aac_create()
|
|
126
|
+
|
|
127
|
+
let srP = m._aac_sr_ptr(), chP = m._aac_ch_ptr()
|
|
128
|
+
let ptr = this._alloc(asc.length)
|
|
129
|
+
m.HEAPU8.set(asc, ptr)
|
|
130
|
+
let err = m._aac_init2(this.h, ptr, asc.length, srP, chP)
|
|
131
|
+
if (err < 0) throw Error('M4A init failed (code ' + err + ')')
|
|
132
|
+
|
|
133
|
+
this.sr = m.getValue(srP, 'i32')
|
|
134
|
+
this.ch = m.getValue(chP, 'i8')
|
|
135
|
+
if (!this.ch) throw Error('M4A init: no channels in ASC')
|
|
136
|
+
|
|
137
|
+
return this._feedFrames(frames)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
_feedFrames(input) {
|
|
141
|
+
let m = this.m, h = this.h
|
|
142
|
+
let isArray = Array.isArray(input)
|
|
143
|
+
let chunks = []
|
|
144
|
+
let totalPerCh = 0, channels = this.ch
|
|
145
|
+
|
|
146
|
+
let errors = 0
|
|
147
|
+
|
|
148
|
+
let decodeOne = (frame) => {
|
|
149
|
+
let ptr = this._alloc(frame.length)
|
|
150
|
+
m.HEAPU8.set(frame, ptr)
|
|
151
|
+
let out = m._aac_decode(h, ptr, frame.length)
|
|
152
|
+
let consumed = m._aac_consumed()
|
|
153
|
+
if (!out) { errors++; return consumed }
|
|
154
|
+
|
|
155
|
+
let n = m._aac_samples()
|
|
156
|
+
let sr = m._aac_samplerate()
|
|
157
|
+
if (sr) this.sr = sr
|
|
158
|
+
let ch = m._aac_channels()
|
|
159
|
+
if (ch) channels = ch
|
|
160
|
+
|
|
161
|
+
let spc = n / channels
|
|
162
|
+
let data = new Float32Array(m.HEAPF32.buffer, out, n).slice()
|
|
163
|
+
chunks.push({ data, ch: channels, spc })
|
|
164
|
+
totalPerCh += spc
|
|
165
|
+
return consumed
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (isArray) {
|
|
169
|
+
for (let frame of input) decodeOne(frame)
|
|
170
|
+
} else {
|
|
171
|
+
let off = 0
|
|
172
|
+
while (off < input.length) {
|
|
173
|
+
let consumed = decodeOne(input.subarray(off))
|
|
174
|
+
if (!consumed) break
|
|
175
|
+
off += consumed
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!totalPerCh) {
|
|
180
|
+
if (errors) throw Error(errors + ' frame(s) failed to decode')
|
|
181
|
+
return EMPTY
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let channelData = Array.from({ length: channels }, () => new Float32Array(totalPerCh))
|
|
185
|
+
let pos = 0
|
|
186
|
+
for (let { data, ch, spc } of chunks) {
|
|
187
|
+
for (let c = 0; c < ch; c++) {
|
|
188
|
+
let out = channelData[c]
|
|
189
|
+
for (let s = 0; s < spc; s++) out[pos + s] = data[s * ch + c]
|
|
190
|
+
}
|
|
191
|
+
pos += spc
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { channelData, sampleRate: this.sr }
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
// ===== M4A demuxer =====
|
|
200
|
+
|
|
201
|
+
function demuxM4A(buf) {
|
|
202
|
+
let asc = null, stsz = null, stco = null, stsc = null
|
|
203
|
+
let mdatOff = 0, mdatLen = 0
|
|
204
|
+
|
|
205
|
+
parseBoxes(buf, 0, buf.length, (type, data, off) => {
|
|
206
|
+
if (type === 'esds') asc = parseEsds(data)
|
|
207
|
+
else if (type === 'stsz') stsz = parseStsz(data)
|
|
208
|
+
else if (type === 'stco') stco = parseStco(data)
|
|
209
|
+
else if (type === 'co64') stco = parseCo64(data)
|
|
210
|
+
else if (type === 'stsc') stsc = parseStsc(data)
|
|
211
|
+
else if (type === 'mdat') { mdatOff = off; mdatLen = data.length }
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
if (!asc) return { asc: null, frames: [] }
|
|
215
|
+
|
|
216
|
+
let frames = (stsz && stco)
|
|
217
|
+
? extractFrames(buf, stsz, stco, stsc)
|
|
218
|
+
: mdatLen ? scanMdat(buf, mdatOff, mdatLen) : []
|
|
219
|
+
|
|
220
|
+
return { asc, frames }
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const CONTAINERS = new Set(['moov', 'trak', 'mdia', 'minf', 'stbl', 'udta', 'meta', 'edts', 'sinf'])
|
|
224
|
+
|
|
225
|
+
function parseBoxes(buf, start, end, cb) {
|
|
226
|
+
let off = start
|
|
227
|
+
while (off < end - 8) {
|
|
228
|
+
let size = r32(buf, off)
|
|
229
|
+
let type = String.fromCharCode(buf[off + 4], buf[off + 5], buf[off + 6], buf[off + 7])
|
|
230
|
+
|
|
231
|
+
if (size === 0) {
|
|
232
|
+
size = end - off
|
|
233
|
+
} else if (size === 1 && off + 16 <= end) {
|
|
234
|
+
size = r32(buf, off + 12)
|
|
235
|
+
if (size < 16) break
|
|
236
|
+
} else if (size < 8) {
|
|
237
|
+
break
|
|
238
|
+
}
|
|
239
|
+
if (off + size > end) size = end - off
|
|
240
|
+
|
|
241
|
+
let bodyOff = off + 8
|
|
242
|
+
|
|
243
|
+
if (type === 'stsd') parseSampleDesc(buf, bodyOff, size - 8, cb)
|
|
244
|
+
else if (CONTAINERS.has(type)) parseBoxes(buf, bodyOff + (type === 'meta' ? 4 : 0), off + size, cb)
|
|
245
|
+
else cb(type, buf.subarray(bodyOff, off + size), bodyOff)
|
|
246
|
+
|
|
247
|
+
off += size
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function parseSampleDesc(buf, off, len, cb) {
|
|
252
|
+
let entries = r32(buf, off + 4), pos = off + 8
|
|
253
|
+
for (let i = 0; i < entries && pos < off + len; i++) {
|
|
254
|
+
let eSize = r32(buf, pos)
|
|
255
|
+
let eType = String.fromCharCode(buf[pos + 4], buf[pos + 5], buf[pos + 6], buf[pos + 7])
|
|
256
|
+
if (eType === 'mp4a' && eSize > 36) parseBoxes(buf, pos + 36, pos + eSize, cb)
|
|
257
|
+
pos += eSize
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function parseEsds(data) {
|
|
262
|
+
let off = 4
|
|
263
|
+
while (off < data.length - 2) {
|
|
264
|
+
let tag = data[off++], len = 0, b
|
|
265
|
+
do { b = data[off++]; len = (len << 7) | (b & 0x7f) } while (b & 0x80 && off < data.length)
|
|
266
|
+
if (tag === 0x03) off += 3
|
|
267
|
+
else if (tag === 0x04) off += 13
|
|
268
|
+
else if (tag === 0x05) return data.subarray(off, off + len)
|
|
269
|
+
else off += len
|
|
270
|
+
}
|
|
271
|
+
return null
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function parseStsz(data) {
|
|
275
|
+
let sz = r32(data, 4), n = r32(data, 8)
|
|
276
|
+
if (sz) return Array(n).fill(sz)
|
|
277
|
+
let sizes = new Array(n)
|
|
278
|
+
for (let i = 0; i < n; i++) sizes[i] = r32(data, 12 + i * 4)
|
|
279
|
+
return sizes
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function parseStco(data) {
|
|
283
|
+
let n = r32(data, 4), o = new Array(n)
|
|
284
|
+
for (let i = 0; i < n; i++) o[i] = r32(data, 8 + i * 4)
|
|
285
|
+
return o
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function parseCo64(data) {
|
|
289
|
+
let n = r32(data, 4), o = new Array(n)
|
|
290
|
+
for (let i = 0; i < n; i++) o[i] = r32(data, 8 + i * 8 + 4)
|
|
291
|
+
return o
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function parseStsc(data) {
|
|
295
|
+
let n = r32(data, 4), e = new Array(n)
|
|
296
|
+
for (let i = 0; i < n; i++) e[i] = { first: r32(data, 8 + i * 12), spc: r32(data, 12 + i * 12) }
|
|
297
|
+
return e
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function extractFrames(buf, stsz, stco, stsc) {
|
|
301
|
+
let frames = [], si = 0
|
|
302
|
+
for (let ci = 0; ci < stco.length; ci++) {
|
|
303
|
+
let spc = 1
|
|
304
|
+
if (stsc?.length) {
|
|
305
|
+
let cn = ci + 1
|
|
306
|
+
for (let j = stsc.length - 1; j >= 0; j--)
|
|
307
|
+
if (cn >= stsc[j].first) { spc = stsc[j].spc; break }
|
|
308
|
+
}
|
|
309
|
+
let off = stco[ci]
|
|
310
|
+
for (let s = 0; s < spc && si < stsz.length; s++) {
|
|
311
|
+
let sz = stsz[si++]
|
|
312
|
+
if (off + sz <= buf.length) frames.push(buf.subarray(off, off + sz))
|
|
313
|
+
off += sz
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return frames
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function scanMdat(buf, off, len) {
|
|
320
|
+
let frames = [], end = off + len, pos = off
|
|
321
|
+
while (pos < end - 7) {
|
|
322
|
+
if (buf[pos] === 0xFF && (buf[pos + 1] & 0xF6) === 0xF0) {
|
|
323
|
+
let flen = ((buf[pos + 3] & 0x03) << 11) | (buf[pos + 4] << 3) | (buf[pos + 5] >> 5)
|
|
324
|
+
if (flen > 0 && pos + flen <= end) {
|
|
325
|
+
frames.push(buf.subarray(pos, pos + flen))
|
|
326
|
+
pos += flen
|
|
327
|
+
continue
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
pos++
|
|
331
|
+
}
|
|
332
|
+
return frames
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function r32(buf, off) {
|
|
336
|
+
return (buf[off] << 24 | buf[off + 1] << 16 | buf[off + 2] << 8 | buf[off + 3]) >>> 0
|
|
337
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aac-decode",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Decode AAC/M4A audio via FAAD2 WASM",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "aac-decode.js",
|
|
7
|
+
"types": "aac-decode.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./aac-decode.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "bash build.sh",
|
|
14
|
+
"prepack": "npm run build",
|
|
15
|
+
"test": "node test.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"aac-decode.js",
|
|
19
|
+
"aac-decode.d.ts",
|
|
20
|
+
"src/aac.wasm.cjs",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"keywords": ["aac", "m4a", "mp4", "audio", "decode", "decoder", "wasm", "faad2"],
|
|
24
|
+
"license": "GPL-2.0",
|
|
25
|
+
"author": "audiojs",
|
|
26
|
+
"homepage": "https://github.com/audiojs/aac-decode#readme",
|
|
27
|
+
"bugs": "https://github.com/audiojs/aac-decode/issues",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/audiojs/aac-decode.git"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=16"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/aac.wasm.cjs
ADDED
|
Binary file
|