@audio/decode-aiff 1.0.0 → 1.1.1

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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # decode-aiff
1
+ # @audio/decode-aiff
2
2
 
3
3
  Decode AIFF and AIFF-C audio to PCM float samples.<br>
4
4
  Part of [audio-decode](https://github.com/audiojs/audio-decode).
@@ -6,13 +6,13 @@ Part of [audio-decode](https://github.com/audiojs/audio-decode).
6
6
  ## Install
7
7
 
8
8
  ```
9
- npm i @audio/aiff-decode
9
+ npm i @audio/decode-aiff
10
10
  ```
11
11
 
12
12
  ## Usage
13
13
 
14
14
  ```js
15
- import decode from '@audio/aiff-decode'
15
+ import decode from '@audio/decode-aiff'
16
16
 
17
17
  let { channelData, sampleRate } = await decode(aiffBuffer)
18
18
  // channelData: Float32Array[] (one per channel)
@@ -22,7 +22,7 @@ let { channelData, sampleRate } = await decode(aiffBuffer)
22
22
  ### Streaming
23
23
 
24
24
  ```js
25
- import { decoder } from '@audio/aiff-decode'
25
+ import { decoder } from '@audio/decode-aiff'
26
26
 
27
27
  let dec = await decoder()
28
28
  let result = dec.decode(chunk)
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
- return dec.decode(buf)
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
- return parseAiff(data)
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() { return EMPTY },
487
- free() { freed = true }
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.0.0",
3
+ "version": "1.1.1",
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": { "access": "public" },
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": { "node": ">=16" },
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"