@audio/decode-aiff 1.0.0 → 1.1.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/decode-aiff.js +99 -5
- package/package.json +7 -3
package/decode-aiff.js
CHANGED
|
@@ -465,7 +465,9 @@ export default async function decode(src) {
|
|
|
465
465
|
let buf = src instanceof Uint8Array ? src : src instanceof ArrayBuffer ? new Uint8Array(src) : src
|
|
466
466
|
let dec = await decoder()
|
|
467
467
|
try {
|
|
468
|
-
|
|
468
|
+
let result = dec.decode(buf)
|
|
469
|
+
if (!result.channelData.length) result = dec.flush()
|
|
470
|
+
return result
|
|
469
471
|
} finally {
|
|
470
472
|
dec.free()
|
|
471
473
|
}
|
|
@@ -476,14 +478,106 @@ export default async function decode(src) {
|
|
|
476
478
|
* @returns {Promise<{decode(chunk: Uint8Array): {channelData, sampleRate}, flush(), free()}>}
|
|
477
479
|
*/
|
|
478
480
|
export async function decoder() {
|
|
479
|
-
let freed = false
|
|
481
|
+
let hdr = null, left = null, freed = false
|
|
480
482
|
return {
|
|
481
483
|
decode(data) {
|
|
482
484
|
if (freed) throw Error('Decoder already freed')
|
|
483
485
|
if (!data?.length) return EMPTY
|
|
484
|
-
|
|
486
|
+
let chunk = data instanceof Uint8Array ? data : new Uint8Array(data)
|
|
487
|
+
if (left) { chunk = cat(left, chunk); left = null }
|
|
488
|
+
if (!hdr) {
|
|
489
|
+
hdr = scanAiffHdr(chunk)
|
|
490
|
+
if (!hdr) { left = chunk.slice(); return EMPTY }
|
|
491
|
+
// IMA4/GSM: buffer all SSND data, decode on flush
|
|
492
|
+
if (hdr.comp === 'ima4' || hdr.comp === 'GSM ' || hdr.comp === 'gsm ') {
|
|
493
|
+
left = chunk.slice()
|
|
494
|
+
return EMPTY
|
|
495
|
+
}
|
|
496
|
+
chunk = chunk.subarray(hdr.dataStart)
|
|
497
|
+
}
|
|
498
|
+
let fb = hdr.frameBytes
|
|
499
|
+
let complete = Math.floor(chunk.length / fb) * fb
|
|
500
|
+
if (!complete) { if (chunk.length) left = chunk.slice(); return EMPTY }
|
|
501
|
+
if (chunk.length > complete) left = chunk.subarray(complete).slice()
|
|
502
|
+
return decodeAiffRaw(chunk.subarray(0, complete), hdr)
|
|
485
503
|
},
|
|
486
|
-
flush() {
|
|
487
|
-
|
|
504
|
+
flush() {
|
|
505
|
+
if (hdr && left && (hdr.comp === 'ima4' || hdr.comp === 'GSM ' || hdr.comp === 'gsm ')) {
|
|
506
|
+
let result = parseAiff(left)
|
|
507
|
+
left = null
|
|
508
|
+
return result
|
|
509
|
+
}
|
|
510
|
+
left = null
|
|
511
|
+
return EMPTY
|
|
512
|
+
},
|
|
513
|
+
free() { freed = true; left = null; hdr = null },
|
|
488
514
|
}
|
|
489
515
|
}
|
|
516
|
+
|
|
517
|
+
function cat(a, b) {
|
|
518
|
+
let r = new Uint8Array(a.length + b.length)
|
|
519
|
+
r.set(a); r.set(b, a.length)
|
|
520
|
+
return r
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function scanAiffHdr(b) {
|
|
524
|
+
if (!b || b.length < 12) return null
|
|
525
|
+
if (str4(b, 0) !== 'FORM') throw Error('Not an AIFF file')
|
|
526
|
+
let form = str4(b, 8)
|
|
527
|
+
if (form !== 'AIFF' && form !== 'AIFC') throw Error('Not an AIFF file')
|
|
528
|
+
let isAIFC = form === 'AIFC'
|
|
529
|
+
let nCh = 0, bps = 0, sr = 0, comp = 'NONE', commFound = false
|
|
530
|
+
let pos = 12, end = Math.min(8 + r32(b, 4), b.length)
|
|
531
|
+
while (pos + 8 <= end) {
|
|
532
|
+
let ckId = str4(b, pos), ckSize = r32(b, pos + 4), ckData = pos + 8
|
|
533
|
+
if (ckId === 'COMM') {
|
|
534
|
+
if (ckData + 18 > b.length) return null
|
|
535
|
+
nCh = r16(b, ckData)
|
|
536
|
+
bps = r16(b, ckData + 6)
|
|
537
|
+
sr = readF80(b, ckData + 8)
|
|
538
|
+
if (isAIFC && ckSize >= 22 && ckData + 22 <= b.length) comp = str4(b, ckData + 18)
|
|
539
|
+
commFound = true
|
|
540
|
+
} else if (ckId === 'SSND') {
|
|
541
|
+
if (!commFound) return null
|
|
542
|
+
if (ckData + 8 > b.length) return null
|
|
543
|
+
let dataOff = r32(b, ckData)
|
|
544
|
+
let dataStart = ckData + 8 + dataOff
|
|
545
|
+
let cUpper = comp.toUpperCase()
|
|
546
|
+
let byteDepth = Math.ceil(bps / 8)
|
|
547
|
+
let frameBytes
|
|
548
|
+
if (comp === 'fl32' || comp === 'FL32') frameBytes = nCh * 4
|
|
549
|
+
else if (comp === 'fl64' || comp === 'FL64') frameBytes = nCh * 8
|
|
550
|
+
else if (comp === 'alaw' || comp === 'ulaw' || comp === 'ULAW') frameBytes = nCh
|
|
551
|
+
else if (comp === 'ima4' || comp === 'GSM ' || comp === 'gsm ') frameBytes = 1 // block-based, handled via flush
|
|
552
|
+
else frameBytes = nCh * byteDepth
|
|
553
|
+
return { nCh, bps, sr, comp, byteDepth, frameBytes, dataStart, isAIFC }
|
|
554
|
+
}
|
|
555
|
+
pos = ckData + ckSize + (ckSize & 1)
|
|
556
|
+
}
|
|
557
|
+
return null
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function decodeAiffRaw(raw, hdr) {
|
|
561
|
+
let { nCh, bps, sr, comp, byteDepth, frameBytes } = hdr
|
|
562
|
+
let frames = Math.floor(raw.length / frameBytes)
|
|
563
|
+
if (!frames) return EMPTY
|
|
564
|
+
let channelData = Array.from({ length: nCh }, () => new Float32Array(frames))
|
|
565
|
+
let cUpper = comp.toUpperCase()
|
|
566
|
+
let p = 0
|
|
567
|
+
if (cUpper === 'NONE' || cUpper === 'TWOS' || comp === 'twos') {
|
|
568
|
+
decodePCM_BE(raw, 0, channelData, frames, nCh, bps, byteDepth)
|
|
569
|
+
} else if (comp === 'sowt') {
|
|
570
|
+
decodePCM_LE(raw, 0, channelData, frames, nCh, bps, byteDepth)
|
|
571
|
+
} else if (comp === 'fl32' || comp === 'FL32') {
|
|
572
|
+
decodeFloat32_BE(raw, 0, channelData, frames, nCh)
|
|
573
|
+
} else if (comp === 'fl64' || comp === 'FL64') {
|
|
574
|
+
decodeFloat64_BE(raw, 0, channelData, frames, nCh)
|
|
575
|
+
} else if (comp === 'alaw') {
|
|
576
|
+
decodeLaw(raw, 0, channelData, frames, nCh, ALAW_TBL)
|
|
577
|
+
} else if (comp === 'ulaw' || comp === 'ULAW') {
|
|
578
|
+
decodeLaw(raw, 0, channelData, frames, nCh, ULAW_TBL)
|
|
579
|
+
} else {
|
|
580
|
+
throw Error('Unsupported AIFF-C compression: ' + comp)
|
|
581
|
+
}
|
|
582
|
+
return { channelData, sampleRate: sr }
|
|
583
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@audio/decode-aiff",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Decode AIFF/AIFF-C audio to PCM samples",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "decode-aiff.js",
|
|
@@ -22,7 +22,9 @@
|
|
|
22
22
|
"decoder",
|
|
23
23
|
"pcm"
|
|
24
24
|
],
|
|
25
|
-
"publishConfig": {
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
26
28
|
"license": "MIT",
|
|
27
29
|
"author": "audiojs",
|
|
28
30
|
"homepage": "https://github.com/audiojs/decode-aiff#readme",
|
|
@@ -30,7 +32,9 @@
|
|
|
30
32
|
"type": "git",
|
|
31
33
|
"url": "git+https://github.com/audiojs/decode-aiff.git"
|
|
32
34
|
},
|
|
33
|
-
"engines": {
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=16"
|
|
37
|
+
},
|
|
34
38
|
"bugs": "https://github.com/audiojs/decode-aiff/issues",
|
|
35
39
|
"scripts": {
|
|
36
40
|
"test": "node test.js"
|