@audio/decode-caf 1.1.1 → 1.2.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/README.md +1 -0
- package/decode-caf.js +60 -5
- package/package.json +1 -1
package/README.md
CHANGED
package/decode-caf.js
CHANGED
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
const EMPTY = Object.freeze({ channelData: [], sampleRate: 0 })
|
|
9
9
|
|
|
10
|
+
// IMA/QuickTime ADPCM tables
|
|
11
|
+
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
|
+
const IMA_IDX = new Int8Array([-1,-1,-1,-1,2,4,6,8,-1,-1,-1,-1,2,4,6,8])
|
|
13
|
+
|
|
10
14
|
/**
|
|
11
15
|
* Whole-file decode
|
|
12
16
|
* @param {Uint8Array|ArrayBuffer} src
|
|
@@ -23,7 +27,7 @@ export default async function decode(src) {
|
|
|
23
27
|
* @returns {Promise<{decode(chunk: Uint8Array): {channelData, sampleRate}, flush(), free()}>}
|
|
24
28
|
*/
|
|
25
29
|
export async function decoder() {
|
|
26
|
-
let hdr = null, left = null, freed = false
|
|
30
|
+
let hdr = null, left = null, freed = false, dataLeft = Infinity
|
|
27
31
|
return {
|
|
28
32
|
decode(data) {
|
|
29
33
|
if (freed) throw Error('Decoder already freed')
|
|
@@ -33,12 +37,17 @@ export async function decoder() {
|
|
|
33
37
|
if (!hdr) {
|
|
34
38
|
hdr = scanCafHdr(chunk)
|
|
35
39
|
if (!hdr) { left = chunk.slice(); return EMPTY }
|
|
40
|
+
dataLeft = hdr.dataSize
|
|
36
41
|
chunk = chunk.subarray(hdr.dataStart)
|
|
37
42
|
}
|
|
43
|
+
if (dataLeft <= 0) return EMPTY // past the data chunk — ignore trailing chunks
|
|
38
44
|
let fb = hdr.frameBytes
|
|
39
|
-
let
|
|
40
|
-
|
|
41
|
-
if (chunk.length >
|
|
45
|
+
let avail = Math.min(chunk.length, dataLeft) // don't read past the declared data size
|
|
46
|
+
let complete = Math.floor(avail / fb) * fb
|
|
47
|
+
if (!complete) { if (chunk.length && dataLeft > chunk.length) left = chunk.slice(); return EMPTY }
|
|
48
|
+
dataLeft -= complete
|
|
49
|
+
let rest = chunk.subarray(complete)
|
|
50
|
+
if (rest.length && dataLeft > 0) left = rest.subarray(0, Math.min(rest.length, dataLeft)).slice()
|
|
42
51
|
return decodeCafRaw(chunk.subarray(0, complete), hdr)
|
|
43
52
|
},
|
|
44
53
|
flush() { left = null; return EMPTY },
|
|
@@ -78,13 +87,16 @@ function scanCafHdr(buf) {
|
|
|
78
87
|
if (!desc) return null
|
|
79
88
|
let dataStart = off + 4 // skip editCount
|
|
80
89
|
if (dataStart > buf.length) return null
|
|
90
|
+
// -1 (0xFFFF…) size means "until EOF" — read to end; else payload minus 4-byte editCount
|
|
91
|
+
let dataSize = (sizeHi === 0xFFFFFFFF && sizeLo === 0xFFFFFFFF) ? Infinity : Math.max(0, size - 4)
|
|
81
92
|
let { formatID, formatFlags, channelsPerFrame: ch, bitsPerChannel: bits } = desc
|
|
82
93
|
let bytesPerSample = bits >> 3
|
|
83
94
|
let frameBytes
|
|
84
95
|
if (formatID === 'alaw' || formatID === 'ulaw') frameBytes = ch
|
|
96
|
+
else if (formatID === 'ima4') frameBytes = desc.bytesPerPacket || 34 * ch // packet = 34 bytes/channel
|
|
85
97
|
else frameBytes = ch * bytesPerSample
|
|
86
98
|
if (!frameBytes) return null
|
|
87
|
-
return { ...desc, dataStart, frameBytes }
|
|
99
|
+
return { ...desc, dataStart, frameBytes, dataSize }
|
|
88
100
|
}
|
|
89
101
|
if (size < 0) break
|
|
90
102
|
if (sizeHi === 0xFFFFFFFF && sizeLo === 0xFFFFFFFF) break
|
|
@@ -101,10 +113,53 @@ function decodeCafRaw(raw, hdr) {
|
|
|
101
113
|
if (formatID === 'lpcm') samples = decodeLPCM(raw, formatFlags, bits, ch)
|
|
102
114
|
else if (formatID === 'alaw') samples = decodeAlaw(raw, ch)
|
|
103
115
|
else if (formatID === 'ulaw') samples = decodeUlaw(raw, ch)
|
|
116
|
+
else if (formatID === 'ima4') samples = decodeIma4(raw, hdr)
|
|
104
117
|
else throw Error('CAF: unsupported format ' + formatID)
|
|
105
118
|
return { channelData: samples, sampleRate }
|
|
106
119
|
}
|
|
107
120
|
|
|
121
|
+
// QuickTime IMA4 ADPCM — 34-byte packets per channel → 64 frames each.
|
|
122
|
+
// The predictor runs continuously across the whole stream; each packet's 2-byte
|
|
123
|
+
// preamble only snapshots the high 9 bits, so resetting per packet would lose up
|
|
124
|
+
// to 127 LSB. State (predictor + step index) is carried on hdr across chunks.
|
|
125
|
+
function decodeIma4(raw, hdr) {
|
|
126
|
+
let nCh = hdr.channelsPerFrame, fpp = hdr.framesPerPacket || 64
|
|
127
|
+
let pktBytes = 34 * nCh, nPk = Math.floor(raw.length / pktBytes)
|
|
128
|
+
if (!nPk) return []
|
|
129
|
+
let st = (hdr.ima ||= { pred: new Int32Array(nCh), step: new Int32Array(nCh), started: false })
|
|
130
|
+
let init = !st.started
|
|
131
|
+
let ch = Array.from({ length: nCh }, () => new Float32Array(nPk * fpp))
|
|
132
|
+
for (let pk = 0; pk < nPk; pk++) {
|
|
133
|
+
for (let c = 0; c < nCh; c++) {
|
|
134
|
+
let bp = pk * pktBytes + c * 34
|
|
135
|
+
if (init && pk === 0) {
|
|
136
|
+
let pre = (raw[bp] << 8) | raw[bp + 1]
|
|
137
|
+
let p = pre & 0xFF80; if (p > 32767) p -= 65536
|
|
138
|
+
st.pred[c] = p
|
|
139
|
+
st.step[c] = Math.min(pre & 0x7F, 88)
|
|
140
|
+
}
|
|
141
|
+
let pred = st.pred[c], ix = st.step[c], out = ch[c], base = pk * fpp
|
|
142
|
+
for (let i = 0; i < 32; i++) {
|
|
143
|
+
let byte = raw[bp + 2 + i]
|
|
144
|
+
for (let h = 0; h < 2; h++) {
|
|
145
|
+
let nib = h === 0 ? byte & 0x0F : (byte >> 4) & 0x0F
|
|
146
|
+
let step = IMA_STEP[ix], diff = step >> 3
|
|
147
|
+
if (nib & 1) diff += step >> 2
|
|
148
|
+
if (nib & 2) diff += step >> 1
|
|
149
|
+
if (nib & 4) diff += step
|
|
150
|
+
pred += (nib & 8) ? -diff : diff
|
|
151
|
+
if (pred > 32767) pred = 32767; else if (pred < -32768) pred = -32768
|
|
152
|
+
ix += IMA_IDX[nib]; if (ix < 0) ix = 0; else if (ix > 88) ix = 88
|
|
153
|
+
out[base + i * 2 + h] = pred / 32768
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
st.pred[c] = pred; st.step[c] = ix
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
st.started = true
|
|
160
|
+
return ch
|
|
161
|
+
}
|
|
162
|
+
|
|
108
163
|
function decodeLPCM(data, flags, bits, ch) {
|
|
109
164
|
let isFloat = flags & 1, isLE = flags & 2
|
|
110
165
|
let bytesPerSample = bits >> 3
|