@audio/decode-caf 1.3.3 → 1.4.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,8 +1,6 @@
1
1
  # @audio/decode-caf
2
2
 
3
- Decode Core Audio Format (CAF) audio to PCM float samples.
4
-
5
- Part of [decode](https://github.com/audiojs/decode).
3
+ Decode Core Audio Format audio to PCM float samples.
6
4
 
7
5
  ## Install
8
6
 
@@ -13,44 +11,40 @@ npm i @audio/decode-caf
13
11
  ## Usage
14
12
 
15
13
  ```js
16
- import decode from '@audio/decode-caf'
17
-
18
- let { channelData, sampleRate } = await decode(cafBuffer)
19
- ```
20
-
21
- ### Streaming
14
+ import decode, { decoder } from '@audio/decode-caf'
22
15
 
23
- ```js
24
- import { decoder } from '@audio/decode-caf'
16
+ let { channelData, sampleRate } = decode(cafBytes)
25
17
 
26
- let dec = await decoder()
18
+ let dec = decoder()
27
19
  let result = dec.decode(chunk)
28
20
  dec.free()
29
21
  ```
30
22
 
23
+ `decode()` and `decoder()` are synchronous.
24
+
31
25
  ## API
32
26
 
33
- ### `decode(src): Promise<AudioData>`
27
+ ### `decode(src): AudioData`
34
28
 
35
- Whole-file decode. Accepts `Uint8Array` or `ArrayBuffer`.
29
+ Decode a complete `Uint8Array` or `ArrayBuffer`.
36
30
 
37
- ### `decoder(): Promise<CAFDecoder>`
31
+ ### `decoder(): CAFDecoder`
38
32
 
39
- Creates a decoder instance.
33
+ Create a decoder instance.
40
34
 
41
- - **`dec.decode(data)`** — decode chunk, returns `{ channelData, sampleRate }`
42
- - **`dec.flush()`** — flush (returns empty — CAF is stateless)
43
- - **`dec.free()`** — release resources
35
+ - `dec.decode(data)`: decode a chunk.
36
+ - `dec.flush()`: discard buffered partial data and return an empty result.
37
+ - `dec.free()`: release resources.
44
38
 
45
39
  ## Formats
46
40
 
47
- - `lpcm` — 8, 16, 24, 32-bit signed integer (LE/BE), 32/64-bit float (LE/BE)
48
- - `alaw` — G.711 A-law
49
- - `ulaw` — G.711 mu-law
50
- - `ima4` — IMA/QuickTime ADPCM (iOS Voice Memos, GarageBand)
41
+ - `lpcm`: 8, 16, 24, and 32-bit signed integer; 32 and 64-bit float; little-endian or big-endian.
42
+ - `alaw`: G.711 A-law.
43
+ - `ulaw`: G.711 µ-law.
44
+ - `ima4`: IMA/QuickTime ADPCM.
51
45
 
52
- ## License
46
+ Other formats (AAC and ALAC packets, as in Apple Loops) throw `CAF: unsupported format '<id>'`.
53
47
 
54
- MIT
48
+ ## License
55
49
 
56
- <a href="https://github.com/krishnized/license/">ॐ</a>
50
+ [ॐ](https://github.com/krishnized/license/) · [MIT](./LICENSE)
package/decode-caf.d.ts CHANGED
@@ -1,16 +1,16 @@
1
- interface AudioData {
1
+ export interface AudioData {
2
2
  channelData: Float32Array[];
3
3
  sampleRate: number;
4
4
  }
5
5
 
6
6
  interface CAFDecoder {
7
- decode(data: Uint8Array): AudioData;
7
+ decode(data: Uint8Array | ArrayBuffer): AudioData;
8
8
  flush(): AudioData;
9
9
  free(): void;
10
10
  }
11
11
 
12
- /** Whole-file decode of CAF audio */
13
- export default function decode(src: ArrayBuffer | Uint8Array): Promise<AudioData>;
12
+ /** Decode a complete CAF file synchronously. */
13
+ export default function decode(src: ArrayBuffer | Uint8Array): AudioData;
14
14
 
15
- /** Create decoder instance */
16
- export function decoder(): Promise<CAFDecoder>;
15
+ /** Create a synchronous decoder. */
16
+ export function decoder(): CAFDecoder;
package/decode-caf.js CHANGED
@@ -2,31 +2,25 @@
2
2
  * CAF (Core Audio Format) decoder
3
3
  * Decodes CAF containers with lpcm, alaw, ulaw audio to Float32 PCM
4
4
  *
5
- * let { channelData, sampleRate } = await decode(cafbuf)
5
+ * let { channelData, sampleRate } = decode(cafbuf)
6
6
  */
7
7
 
8
8
  const EMPTY = Object.freeze({ channelData: [], sampleRate: 0 })
9
+ const FORMATS = new Set(['lpcm', 'alaw', 'ulaw', 'ima4'])
9
10
 
10
11
  // IMA/QuickTime ADPCM tables
11
12
  const IMA_STEP = new Int16Array([7,8,9,10,11,12,13,14,16,17,19,21,23,25,28,31,34,37,41,45,50,55,60,66,73,80,88,97,107,118,130,143,157,173,190,209,230,253,279,307,337,371,408,449,494,544,598,658,724,796,876,963,1060,1166,1282,1411,1552,1707,1878,2066,2272,2499,2749,3024,3327,3660,4026,4428,4871,5358,5894,6484,7132,7845,8630,9493,10442,11487,12635,13899,15289,16818,18500,20350,22385,24623,27086,29794,32767])
12
13
  const IMA_IDX = new Int8Array([-1,-1,-1,-1,2,4,6,8,-1,-1,-1,-1,2,4,6,8])
13
14
 
14
- /**
15
- * Whole-file decode
16
- * @param {Uint8Array|ArrayBuffer} src
17
- * @returns {Promise<{channelData: Float32Array[], sampleRate: number}>}
18
- */
19
- export default async function decode(src) {
20
- let dec = await decoder()
15
+ /** Decode a complete CAF file synchronously. */
16
+ export default function decode(src) {
17
+ let dec = decoder()
21
18
  try { return dec.decode(src) }
22
19
  finally { dec.free() }
23
20
  }
24
21
 
25
- /**
26
- * Create decoder instance (streaming-aware)
27
- * @returns {Promise<{decode(chunk: Uint8Array): {channelData, sampleRate}, flush(), free()}>}
28
- */
29
- export async function decoder() {
22
+ /** Create a synchronous streaming decoder. */
23
+ export function decoder() {
30
24
  let hdr = null, left = null, freed = false, dataLeft = Infinity
31
25
  return {
32
26
  decode(data) {
@@ -40,9 +34,9 @@ export async function decoder() {
40
34
  dataLeft = hdr.dataSize
41
35
  chunk = chunk.subarray(hdr.dataStart)
42
36
  }
43
- if (dataLeft <= 0) return EMPTY // past the data chunk — ignore trailing chunks
37
+ if (dataLeft <= 0) return EMPTY // Ignore chunks after the declared audio data.
44
38
  let fb = hdr.frameBytes
45
- let avail = Math.min(chunk.length, dataLeft) // don't read past the declared data size
39
+ let avail = Math.min(chunk.length, dataLeft) // Do not read past the declared data size.
46
40
  let complete = Math.floor(avail / fb) * fb
47
41
  if (!complete) { if (chunk.length && dataLeft > chunk.length) left = chunk.slice(); return EMPTY }
48
42
  dataLeft -= complete
@@ -83,6 +77,8 @@ function scanCafHdr(buf) {
83
77
  channelsPerFrame: dv.getUint32(off + 24, false),
84
78
  bitsPerChannel: dv.getUint32(off + 28, false),
85
79
  }
80
+ // compressed formats (aac, alac, ...) carry packets this decoder can't read: say so, don't wait forever
81
+ if (!FORMATS.has(desc.formatID)) throw Error(`CAF: unsupported format '${desc.formatID}'`)
86
82
  } else if (type === 'data') {
87
83
  if (!desc) return null
88
84
  let dataStart = off + 4 // skip editCount
@@ -95,7 +91,7 @@ function scanCafHdr(buf) {
95
91
  if (formatID === 'alaw' || formatID === 'ulaw') frameBytes = ch
96
92
  else if (formatID === 'ima4') frameBytes = desc.bytesPerPacket || 34 * ch // packet = 34 bytes/channel
97
93
  else frameBytes = ch * bytesPerSample
98
- if (!frameBytes) return null
94
+ if (!frameBytes) throw Error(`CAF: invalid ${formatID} description (${ch} channels, ${bits} bits)`)
99
95
  return { ...desc, dataStart, frameBytes, dataSize }
100
96
  }
101
97
  if (size < 0) break
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@audio/decode-caf",
3
- "version": "1.3.3",
3
+ "version": "1.4.1",
4
4
  "description": "Decode CAF (Core Audio Format) audio to PCM samples",
5
5
  "type": "module",
6
6
  "main": "decode-caf.js",