@audio/aiff-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 +21 -0
- package/README.md +53 -0
- package/aiff-decode.d.ts +16 -0
- package/aiff-decode.js +489 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 audiojs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# aiff-decode
|
|
2
|
+
|
|
3
|
+
Decode AIFF and AIFF-C audio to PCM float samples.<br>
|
|
4
|
+
Part of [audio-decode](https://github.com/audiojs/audio-decode).
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
npm i @audio/aiff-decode
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
import decode from '@audio/aiff-decode'
|
|
16
|
+
|
|
17
|
+
let { channelData, sampleRate } = await decode(aiffBuffer)
|
|
18
|
+
// channelData: Float32Array[] (one per channel)
|
|
19
|
+
// sampleRate: number
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Streaming
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
import { decoder } from '@audio/aiff-decode'
|
|
26
|
+
|
|
27
|
+
let dec = await decoder()
|
|
28
|
+
let result = dec.decode(chunk)
|
|
29
|
+
dec.free()
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## API
|
|
33
|
+
|
|
34
|
+
### `decode(src): Promise<AudioData>`
|
|
35
|
+
|
|
36
|
+
Whole-file decode. Accepts `Uint8Array` or `ArrayBuffer`.
|
|
37
|
+
|
|
38
|
+
### `decoder(): Promise<AIFFDecoder>`
|
|
39
|
+
|
|
40
|
+
Creates a decoder instance.
|
|
41
|
+
|
|
42
|
+
- **`dec.decode(data)`** — decode chunk, returns `{ channelData, sampleRate }`
|
|
43
|
+
- **`dec.flush()`** — flush (returns empty — AIFF is stateless)
|
|
44
|
+
- **`dec.free()`** — release resources
|
|
45
|
+
|
|
46
|
+
## Formats
|
|
47
|
+
|
|
48
|
+
- AIFF — 8, 16, 24, 32-bit signed integer PCM (big-endian)
|
|
49
|
+
- AIFF-C — `NONE`/`twos` (BE PCM), `sowt` (LE PCM), `fl32`/`fl64` (float), `alaw`, `ulaw`
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
MIT — [krishnized](https://github.com/krishnized/license)
|
package/aiff-decode.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface AudioData {
|
|
2
|
+
channelData: Float32Array[];
|
|
3
|
+
sampleRate: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface AIFFDecoder {
|
|
7
|
+
decode(data: Uint8Array): AudioData;
|
|
8
|
+
flush(): AudioData;
|
|
9
|
+
free(): void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Decode AIFF/AIFF-C audio buffer to PCM samples */
|
|
13
|
+
export default function decode(src: ArrayBuffer | Uint8Array): Promise<AudioData>;
|
|
14
|
+
|
|
15
|
+
/** Create decoder instance */
|
|
16
|
+
export function decoder(): Promise<AIFFDecoder>;
|
package/aiff-decode.js
ADDED
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AIFF/AIFF-C decoder — pure JS
|
|
3
|
+
* Decodes AIFF and AIFF-C audio to Float32Array PCM samples
|
|
4
|
+
*
|
|
5
|
+
* let { channelData, sampleRate } = await decode(aiffbuf)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const EMPTY = Object.freeze({ channelData: [], sampleRate: 0 })
|
|
9
|
+
|
|
10
|
+
// A-law expansion table (ITU-T G.711)
|
|
11
|
+
const ALAW_TBL = new Int16Array(256)
|
|
12
|
+
// mu-law expansion table (ITU-T G.711)
|
|
13
|
+
const ULAW_TBL = new Int16Array(256)
|
|
14
|
+
|
|
15
|
+
;(function buildTables() {
|
|
16
|
+
for (let i = 0; i < 256; i++) {
|
|
17
|
+
// A-law
|
|
18
|
+
let ax = i ^ 0x55, seg = (ax >> 4) & 7, val = ((ax & 0x0F) << 4) + 8
|
|
19
|
+
if (seg) val = (val + 256) << (seg - 1)
|
|
20
|
+
ALAW_TBL[i] = (ax & 0x80) ? val : -val
|
|
21
|
+
|
|
22
|
+
// mu-law
|
|
23
|
+
let ux = ~i & 0xFF
|
|
24
|
+
seg = (ux >> 4) & 7
|
|
25
|
+
val = ((ux & 0x0F) << 3) + 132
|
|
26
|
+
val <<= seg
|
|
27
|
+
ULAW_TBL[i] = (ux & 0x80) ? (val - 132) : -(val - 132)
|
|
28
|
+
}
|
|
29
|
+
})()
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Read 80-bit IEEE 754 extended precision float
|
|
33
|
+
*/
|
|
34
|
+
function readF80(b, o) {
|
|
35
|
+
let sign = (b[o] >> 7) & 1
|
|
36
|
+
let exp = ((b[o] & 0x7F) << 8) | b[o + 1]
|
|
37
|
+
let hi = ((b[o + 2] << 24) | (b[o + 3] << 16) | (b[o + 4] << 8) | b[o + 5]) >>> 0
|
|
38
|
+
let lo = ((b[o + 6] << 24) | (b[o + 7] << 16) | (b[o + 8] << 8) | b[o + 9]) >>> 0
|
|
39
|
+
if (exp === 0 && hi === 0 && lo === 0) return 0
|
|
40
|
+
if (exp === 0x7FFF) return sign ? -Infinity : Infinity
|
|
41
|
+
let f = (hi * 4294967296 + lo) / 9223372036854775808 // / 2^63
|
|
42
|
+
return (sign ? -1 : 1) * f * Math.pow(2, exp - 16383)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function str4(b, o) {
|
|
46
|
+
return String.fromCharCode(b[o], b[o + 1], b[o + 2], b[o + 3])
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function r32(b, o) {
|
|
50
|
+
return ((b[o] << 24) | (b[o + 1] << 16) | (b[o + 2] << 8) | b[o + 3]) >>> 0
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function r16(b, o) {
|
|
54
|
+
return (b[o] << 8) | b[o + 1]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Parse AIFF/AIFF-C and decode to Float32Array channels
|
|
59
|
+
*/
|
|
60
|
+
function parseAiff(buf) {
|
|
61
|
+
if (!buf || buf.length < 12) return EMPTY
|
|
62
|
+
|
|
63
|
+
let b = buf instanceof Uint8Array ? buf : new Uint8Array(buf)
|
|
64
|
+
if (b.length < 12) return EMPTY
|
|
65
|
+
|
|
66
|
+
// Validate FORM header
|
|
67
|
+
if (str4(b, 0) !== 'FORM') throw Error('Not an AIFF file')
|
|
68
|
+
let form = str4(b, 8)
|
|
69
|
+
if (form !== 'AIFF' && form !== 'AIFC') throw Error('Not an AIFF file')
|
|
70
|
+
let isAIFC = form === 'AIFC'
|
|
71
|
+
|
|
72
|
+
let nCh = 0, nFrames = 0, bps = 0, sr = 0, comp = 'NONE'
|
|
73
|
+
let ssndOff = -1, ssndSize = 0
|
|
74
|
+
|
|
75
|
+
// Parse chunks
|
|
76
|
+
let pos = 12, end = Math.min(8 + r32(b, 4), b.length)
|
|
77
|
+
while (pos + 8 <= end) {
|
|
78
|
+
let ckId = str4(b, pos)
|
|
79
|
+
let ckSize = r32(b, pos + 4)
|
|
80
|
+
let ckData = pos + 8
|
|
81
|
+
|
|
82
|
+
if (ckId === 'COMM') {
|
|
83
|
+
if (ckData + 18 > b.length) throw Error('Truncated COMM chunk')
|
|
84
|
+
nCh = r16(b, ckData)
|
|
85
|
+
nFrames = r32(b, ckData + 2)
|
|
86
|
+
bps = r16(b, ckData + 6)
|
|
87
|
+
sr = readF80(b, ckData + 8)
|
|
88
|
+
if (isAIFC && ckSize >= 22 && ckData + 22 <= b.length) {
|
|
89
|
+
comp = str4(b, ckData + 18)
|
|
90
|
+
}
|
|
91
|
+
} else if (ckId === 'SSND') {
|
|
92
|
+
if (ckData + 8 > b.length) throw Error('Truncated SSND chunk')
|
|
93
|
+
let dataOff = r32(b, ckData)
|
|
94
|
+
ssndOff = ckData + 8 + dataOff
|
|
95
|
+
ssndSize = ckSize - 8 - dataOff
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
pos = ckData + ckSize + (ckSize & 1) // pad to even
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!nCh || !nFrames || !sr || ssndOff < 0) return EMPTY
|
|
102
|
+
|
|
103
|
+
// Determine byte depth and decode strategy
|
|
104
|
+
let byteDepth = Math.ceil(bps / 8)
|
|
105
|
+
let frameBytes = byteDepth * nCh
|
|
106
|
+
let actualFrames = Math.min(nFrames, Math.floor(Math.min(ssndSize, b.length - ssndOff) / frameBytes))
|
|
107
|
+
if (actualFrames <= 0) return EMPTY
|
|
108
|
+
|
|
109
|
+
let channelData = Array.from({ length: nCh }, () => new Float32Array(actualFrames))
|
|
110
|
+
let p = ssndOff
|
|
111
|
+
|
|
112
|
+
// Compression-aware decode
|
|
113
|
+
let cUpper = comp.toUpperCase()
|
|
114
|
+
if (cUpper === 'NONE' || cUpper === 'TWOS' || comp === 'twos') {
|
|
115
|
+
// Big-endian signed integer PCM
|
|
116
|
+
decodePCM_BE(b, p, channelData, actualFrames, nCh, bps, byteDepth)
|
|
117
|
+
} else if (comp === 'sowt') {
|
|
118
|
+
// Little-endian signed integer PCM
|
|
119
|
+
decodePCM_LE(b, p, channelData, actualFrames, nCh, bps, byteDepth)
|
|
120
|
+
} else if (comp === 'fl32' || comp === 'FL32') {
|
|
121
|
+
decodeFloat32_BE(b, p, channelData, actualFrames, nCh)
|
|
122
|
+
} else if (comp === 'fl64' || comp === 'FL64') {
|
|
123
|
+
decodeFloat64_BE(b, p, channelData, actualFrames, nCh)
|
|
124
|
+
} else if (comp === 'alaw') {
|
|
125
|
+
decodeLaw(b, p, channelData, actualFrames, nCh, ALAW_TBL)
|
|
126
|
+
} else if (comp === 'ulaw' || comp === 'ULAW') {
|
|
127
|
+
decodeLaw(b, p, channelData, actualFrames, nCh, ULAW_TBL)
|
|
128
|
+
} else if (comp === 'ima4') {
|
|
129
|
+
return decodeIMA4(b, ssndOff, ssndSize, nCh, nFrames, sr)
|
|
130
|
+
} else if (comp === 'GSM ' || comp === 'gsm ') {
|
|
131
|
+
return decodeGSM(b, ssndOff, ssndSize, nCh, nFrames, sr)
|
|
132
|
+
} else {
|
|
133
|
+
throw Error('Unsupported AIFF-C compression: ' + comp)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return { channelData, sampleRate: sr }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function decodePCM_BE(b, p, channels, frames, nCh, bps, byteDepth) {
|
|
140
|
+
if (bps === 8) {
|
|
141
|
+
// 8-bit: unsigned, center at 128
|
|
142
|
+
for (let i = 0; i < frames; i++)
|
|
143
|
+
for (let c = 0; c < nCh; c++)
|
|
144
|
+
channels[c][i] = (b[p++] - 128) / 128
|
|
145
|
+
} else if (bps === 16) {
|
|
146
|
+
for (let i = 0; i < frames; i++)
|
|
147
|
+
for (let c = 0; c < nCh; c++) {
|
|
148
|
+
let v = (b[p] << 8) | b[p + 1]; p += 2
|
|
149
|
+
channels[c][i] = (v > 32767 ? v - 65536 : v) / 32768
|
|
150
|
+
}
|
|
151
|
+
} else if (bps === 24) {
|
|
152
|
+
for (let i = 0; i < frames; i++)
|
|
153
|
+
for (let c = 0; c < nCh; c++) {
|
|
154
|
+
let v = (b[p] << 16) | (b[p + 1] << 8) | b[p + 2]; p += 3
|
|
155
|
+
channels[c][i] = (v > 8388607 ? v - 16777216 : v) / 8388608
|
|
156
|
+
}
|
|
157
|
+
} else if (bps === 32) {
|
|
158
|
+
for (let i = 0; i < frames; i++)
|
|
159
|
+
for (let c = 0; c < nCh; c++) {
|
|
160
|
+
let v = ((b[p] << 24) | (b[p + 1] << 16) | (b[p + 2] << 8) | b[p + 3]); p += 4
|
|
161
|
+
channels[c][i] = v / 2147483648
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
throw Error('Unsupported bit depth: ' + bps)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function decodePCM_LE(b, p, channels, frames, nCh, bps, byteDepth) {
|
|
169
|
+
if (bps === 8) {
|
|
170
|
+
for (let i = 0; i < frames; i++)
|
|
171
|
+
for (let c = 0; c < nCh; c++)
|
|
172
|
+
channels[c][i] = (b[p++] - 128) / 128
|
|
173
|
+
} else if (bps === 16) {
|
|
174
|
+
for (let i = 0; i < frames; i++)
|
|
175
|
+
for (let c = 0; c < nCh; c++) {
|
|
176
|
+
let v = b[p] | (b[p + 1] << 8); p += 2
|
|
177
|
+
channels[c][i] = (v > 32767 ? v - 65536 : v) / 32768
|
|
178
|
+
}
|
|
179
|
+
} else if (bps === 24) {
|
|
180
|
+
for (let i = 0; i < frames; i++)
|
|
181
|
+
for (let c = 0; c < nCh; c++) {
|
|
182
|
+
let v = b[p] | (b[p + 1] << 8) | (b[p + 2] << 16); p += 3
|
|
183
|
+
channels[c][i] = (v > 8388607 ? v - 16777216 : v) / 8388608
|
|
184
|
+
}
|
|
185
|
+
} else if (bps === 32) {
|
|
186
|
+
for (let i = 0; i < frames; i++)
|
|
187
|
+
for (let c = 0; c < nCh; c++) {
|
|
188
|
+
let v = b[p] | (b[p + 1] << 8) | (b[p + 2] << 16) | (b[p + 3] << 24); p += 4
|
|
189
|
+
channels[c][i] = v / 2147483648
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
throw Error('Unsupported bit depth: ' + bps)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function decodeFloat32_BE(b, p, channels, frames, nCh) {
|
|
197
|
+
let dv = new DataView(b.buffer, b.byteOffset, b.byteLength)
|
|
198
|
+
for (let i = 0; i < frames; i++)
|
|
199
|
+
for (let c = 0; c < nCh; c++) {
|
|
200
|
+
channels[c][i] = dv.getFloat32(p, false); p += 4
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function decodeFloat64_BE(b, p, channels, frames, nCh) {
|
|
205
|
+
let dv = new DataView(b.buffer, b.byteOffset, b.byteLength)
|
|
206
|
+
for (let i = 0; i < frames; i++)
|
|
207
|
+
for (let c = 0; c < nCh; c++) {
|
|
208
|
+
channels[c][i] = dv.getFloat64(p, false); p += 8
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function decodeLaw(b, p, channels, frames, nCh, tbl) {
|
|
213
|
+
for (let i = 0; i < frames; i++)
|
|
214
|
+
for (let c = 0; c < nCh; c++)
|
|
215
|
+
channels[c][i] = tbl[b[p++]] / 32768
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ===== IMA4 ADPCM (Apple IMA ADPCM) =====
|
|
219
|
+
// 34 bytes/block/channel → 64 samples. COMM nFrames = number of blocks.
|
|
220
|
+
|
|
221
|
+
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])
|
|
222
|
+
const IMA_IDX = new Int8Array([-1,-1,-1,-1,2,4,6,8,-1,-1,-1,-1,2,4,6,8])
|
|
223
|
+
|
|
224
|
+
function decodeIMA4(b, ssndOff, ssndSize, nCh, nBlocks, sr) {
|
|
225
|
+
let blockBytes = 34 * nCh
|
|
226
|
+
let totalSamples = nBlocks * 64
|
|
227
|
+
let channelData = Array.from({ length: nCh }, () => new Float32Array(totalSamples))
|
|
228
|
+
let p = ssndOff, sample = 0
|
|
229
|
+
|
|
230
|
+
for (let blk = 0; blk < nBlocks && p + blockBytes <= ssndOff + ssndSize; blk++) {
|
|
231
|
+
for (let c = 0; c < nCh; c++) {
|
|
232
|
+
let bp = p + c * 34
|
|
233
|
+
// Preamble: 16-bit BE — high 9 bits = predictor, low 7 bits = step index
|
|
234
|
+
let preamble = (b[bp] << 8) | b[bp + 1]
|
|
235
|
+
let predictor = preamble & 0xFF80 // top 9 bits, sign-extended via int16
|
|
236
|
+
if (predictor > 32767) predictor -= 65536
|
|
237
|
+
let stepIdx = preamble & 0x7F
|
|
238
|
+
if (stepIdx > 88) stepIdx = 88
|
|
239
|
+
|
|
240
|
+
let out = channelData[c]
|
|
241
|
+
for (let i = 0; i < 32; i++) {
|
|
242
|
+
let byte = b[bp + 2 + i]
|
|
243
|
+
// High nibble first, then low nibble
|
|
244
|
+
for (let nibbleIdx = 0; nibbleIdx < 2; nibbleIdx++) {
|
|
245
|
+
let nibble = nibbleIdx === 0 ? (byte >> 4) & 0xF : byte & 0xF
|
|
246
|
+
let step = IMA_STEP[stepIdx]
|
|
247
|
+
let diff = step >> 3
|
|
248
|
+
if (nibble & 1) diff += step >> 2
|
|
249
|
+
if (nibble & 2) diff += step >> 1
|
|
250
|
+
if (nibble & 4) diff += step
|
|
251
|
+
if (nibble & 8) diff = -diff
|
|
252
|
+
predictor += diff
|
|
253
|
+
if (predictor > 32767) predictor = 32767
|
|
254
|
+
if (predictor < -32768) predictor = -32768
|
|
255
|
+
stepIdx += IMA_IDX[nibble]
|
|
256
|
+
if (stepIdx < 0) stepIdx = 0
|
|
257
|
+
if (stepIdx > 88) stepIdx = 88
|
|
258
|
+
out[sample + i * 2 + nibbleIdx] = predictor / 32768
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
p += blockBytes
|
|
263
|
+
sample += 64
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return { channelData, sampleRate: sr }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ===== GSM 6.10 (Full Rate) =====
|
|
270
|
+
// 33 bytes/frame → 160 samples. RPE-LTP speech codec.
|
|
271
|
+
// Ported from libgsm by Jutta Degener and Carsten Bormann, TU Berlin.
|
|
272
|
+
|
|
273
|
+
// Table 4.3b: Quantization levels of the LTP gain quantizer
|
|
274
|
+
const GSM_QLB = [3277, 11469, 21299, 32767]
|
|
275
|
+
// Table 4.6: Normalized direct mantissa used to compute xM/xmax
|
|
276
|
+
const GSM_FAC = [18431, 20479, 22527, 24575, 26623, 28671, 30719, 32767]
|
|
277
|
+
|
|
278
|
+
// Saturating 16-bit add/sub
|
|
279
|
+
function sadd(a, b) { let s = a + b; return s > 32767 ? 32767 : s < -32768 ? -32768 : s }
|
|
280
|
+
function ssub(a, b) { let s = a - b; return s > 32767 ? 32767 : s < -32768 ? -32768 : s }
|
|
281
|
+
// GSM_MULT_R(a,b): (a*b + 16384) >> 15, with saturation for MIN_WORD*MIN_WORD
|
|
282
|
+
function multr(a, b) {
|
|
283
|
+
if (a === -32768 && b === -32768) return 32767
|
|
284
|
+
return ((a * b + 16384) >> 15) | 0
|
|
285
|
+
}
|
|
286
|
+
// Arithmetic shift right (sign-preserving)
|
|
287
|
+
function asr(x, n) {
|
|
288
|
+
if (n >= 16) return x < 0 ? -1 : 0
|
|
289
|
+
if (n <= -16) return 0
|
|
290
|
+
if (n < 0) return x << -n
|
|
291
|
+
return x >> n // JS >> is always arithmetic
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function decodeGSM(b, ssndOff, ssndSize, nCh, nFrames, sr) {
|
|
295
|
+
let totalFrames = Math.min(nFrames, Math.floor(ssndSize / 33))
|
|
296
|
+
let channelData = [new Float32Array(totalFrames * 160)]
|
|
297
|
+
let out = channelData[0], p = ssndOff
|
|
298
|
+
|
|
299
|
+
// Decoder state (mirrors struct gsm_state)
|
|
300
|
+
let dp0 = new Int32Array(280) // int32 to avoid overflow in intermediate calcs
|
|
301
|
+
let v = new Int32Array(9)
|
|
302
|
+
let LARpp = [new Int32Array(8), new Int32Array(8)]
|
|
303
|
+
let j = 0, nrp = 40, msr = 0
|
|
304
|
+
|
|
305
|
+
for (let f = 0; f < totalFrames; f++) {
|
|
306
|
+
let { LARc, sub } = gsmBits(b, p)
|
|
307
|
+
let wt = new Int32Array(160)
|
|
308
|
+
|
|
309
|
+
// Process 4 subframes: RPE decode + long-term synthesis
|
|
310
|
+
for (let s = 0; s < 4; s++) {
|
|
311
|
+
let sf = sub[s]
|
|
312
|
+
let erp = new Int32Array(40)
|
|
313
|
+
gsmRpeDecode(sf.xmaxc, sf.Mc, sf.xmc, erp)
|
|
314
|
+
|
|
315
|
+
// Long-term synthesis filtering (4.3.2)
|
|
316
|
+
let Nr = (sf.Nc < 40 || sf.Nc > 120) ? nrp : sf.Nc
|
|
317
|
+
nrp = Nr
|
|
318
|
+
let brp = GSM_QLB[sf.bc]
|
|
319
|
+
// drp is dp0 offset by 120; drp[k] = dp0[120+k], drp[k-Nr] = dp0[120+k-Nr]
|
|
320
|
+
for (let k = 0; k < 40; k++) {
|
|
321
|
+
let drpp = multr(brp, dp0[120 + k - Nr])
|
|
322
|
+
dp0[120 + k] = sadd(erp[k], drpp)
|
|
323
|
+
}
|
|
324
|
+
for (let k = 0; k < 40; k++) wt[s * 40 + k] = dp0[120 + k]
|
|
325
|
+
|
|
326
|
+
// Shift dp0: drp[-120+k] = drp[-80+k] for k=0..119
|
|
327
|
+
for (let k = 0; k < 120; k++) dp0[k] = dp0[k + 40]
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Short-term synthesis filter with LARp interpolation (4.2.8-4.2.9)
|
|
331
|
+
let LARpp_j = LARpp[j]
|
|
332
|
+
let LARpp_j_1 = LARpp[j ^ 1]
|
|
333
|
+
j ^= 1 // toggle for next frame
|
|
334
|
+
|
|
335
|
+
gsmDecodeLAR(LARc, LARpp_j)
|
|
336
|
+
|
|
337
|
+
let sr_out = new Int32Array(160)
|
|
338
|
+
let LARp = new Int32Array(8)
|
|
339
|
+
|
|
340
|
+
// Coefficients_0_12: LARp = 3/4 * prev + 1/4 * curr
|
|
341
|
+
for (let i = 0; i < 8; i++)
|
|
342
|
+
LARp[i] = sadd(sadd(LARpp_j_1[i] >> 2, LARpp_j[i] >> 2), LARpp_j_1[i] >> 1)
|
|
343
|
+
gsmLARpToRp(LARp)
|
|
344
|
+
gsmSynthFilter(v, LARp, 13, wt, 0, sr_out, 0)
|
|
345
|
+
|
|
346
|
+
// Coefficients_13_26: LARp = 1/2 * prev + 1/2 * curr
|
|
347
|
+
for (let i = 0; i < 8; i++)
|
|
348
|
+
LARp[i] = sadd(LARpp_j_1[i] >> 1, LARpp_j[i] >> 1)
|
|
349
|
+
gsmLARpToRp(LARp)
|
|
350
|
+
gsmSynthFilter(v, LARp, 14, wt, 13, sr_out, 13)
|
|
351
|
+
|
|
352
|
+
// Coefficients_27_39: LARp = 1/4 * prev + 3/4 * curr
|
|
353
|
+
for (let i = 0; i < 8; i++)
|
|
354
|
+
LARp[i] = sadd(sadd(LARpp_j_1[i] >> 2, LARpp_j[i] >> 2), LARpp_j[i] >> 1)
|
|
355
|
+
gsmLARpToRp(LARp)
|
|
356
|
+
gsmSynthFilter(v, LARp, 13, wt, 27, sr_out, 27)
|
|
357
|
+
|
|
358
|
+
// Coefficients_40_159: LARp = curr
|
|
359
|
+
for (let i = 0; i < 8; i++) LARp[i] = LARpp_j[i]
|
|
360
|
+
gsmLARpToRp(LARp)
|
|
361
|
+
gsmSynthFilter(v, LARp, 120, wt, 40, sr_out, 40)
|
|
362
|
+
|
|
363
|
+
// Postprocessing: de-emphasis + truncation/upscaling
|
|
364
|
+
for (let k = 0; k < 160; k++) {
|
|
365
|
+
msr = sadd(sr_out[k], multr(msr, 28180))
|
|
366
|
+
let s = sadd(msr, msr) & 0xFFF8
|
|
367
|
+
out[f * 160 + k] = (s > 32767 ? s - 65536 : s) / 32768
|
|
368
|
+
}
|
|
369
|
+
p += 33
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return { channelData, sampleRate: sr }
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// GSM 6.10 bit unpacking: 33 bytes → LARc[8] + 4 subframes
|
|
376
|
+
function gsmBits(buf, off) {
|
|
377
|
+
let bc = 0
|
|
378
|
+
function r(n) {
|
|
379
|
+
let val = 0
|
|
380
|
+
for (let i = 0; i < n; i++) {
|
|
381
|
+
val = (val << 1) | ((buf[off + (bc >> 3)] >> (7 - (bc & 7))) & 1)
|
|
382
|
+
bc++
|
|
383
|
+
}
|
|
384
|
+
return val
|
|
385
|
+
}
|
|
386
|
+
let LARc = [r(6),r(6),r(5),r(5),r(4),r(4),r(3),r(3)]
|
|
387
|
+
let sub = []
|
|
388
|
+
for (let s = 0; s < 4; s++)
|
|
389
|
+
sub.push({ Nc: r(7), bc: r(2), Mc: r(2), xmaxc: r(6), xmc: [r(3),r(3),r(3),r(3),r(3),r(3),r(3),r(3),r(3),r(3),r(3),r(3),r(3)] })
|
|
390
|
+
return { LARc, sub }
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// 4.2.8: Decoding of the coded Log-Area Ratios
|
|
394
|
+
function gsmDecodeLAR(LARc, LARpp) {
|
|
395
|
+
// B*2, MIC, INVA from tables 4.1 and 4.2
|
|
396
|
+
const B2 = [0, 0, 4096, -5120, 188, -3584, -682, -2288]
|
|
397
|
+
const MIC = [-32, -32, -16, -16, -8, -8, -4, -4]
|
|
398
|
+
const INVA = [13107, 13107, 13107, 13107, 19223, 17476, 31454, 29708]
|
|
399
|
+
for (let i = 0; i < 8; i++) {
|
|
400
|
+
let t = sadd(LARc[i], MIC[i]) << 10
|
|
401
|
+
t = ssub(t, B2[i])
|
|
402
|
+
t = multr(INVA[i], t)
|
|
403
|
+
LARpp[i] = sadd(t, t)
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// 4.2.9.2: LARp to reflection coefficients (in-place)
|
|
408
|
+
function gsmLARpToRp(LARp) {
|
|
409
|
+
for (let i = 0; i < 8; i++) {
|
|
410
|
+
if (LARp[i] < 0) {
|
|
411
|
+
let t = LARp[i] === -32768 ? 32767 : -LARp[i]
|
|
412
|
+
LARp[i] = -(t < 11059 ? t << 1 : t < 20070 ? t + 11059 : sadd(t >> 2, 26112))
|
|
413
|
+
} else {
|
|
414
|
+
let t = LARp[i]
|
|
415
|
+
LARp[i] = t < 11059 ? t << 1 : t < 20070 ? t + 11059 : sadd(t >> 2, 26112)
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Short-term synthesis filter (4.2.10, synthesis direction)
|
|
421
|
+
function gsmSynthFilter(v, rrp, k, wt, wtOff, sr, srOff) {
|
|
422
|
+
for (let n = 0; n < k; n++) {
|
|
423
|
+
let sri = wt[wtOff + n]
|
|
424
|
+
for (let i = 7; i >= 0; i--) {
|
|
425
|
+
sri = ssub(sri, multr(rrp[i], v[i]))
|
|
426
|
+
v[i + 1] = sadd(v[i], multr(rrp[i], sri))
|
|
427
|
+
}
|
|
428
|
+
sr[srOff + n] = v[0] = sri
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// 4.2.16-4.2.17: RPE decoding (inverse quantization + grid positioning)
|
|
433
|
+
function gsmRpeDecode(xmaxc, Mc, xMc, erp) {
|
|
434
|
+
// 4.2.15: Compute exp and mant from xmaxc
|
|
435
|
+
let exp = 0, mant
|
|
436
|
+
if (xmaxc > 15) exp = (xmaxc >> 3) - 1
|
|
437
|
+
mant = xmaxc - (exp << 3)
|
|
438
|
+
if (mant === 0) { exp = -4; mant = 7 }
|
|
439
|
+
else { while (mant <= 7) { mant = (mant << 1) | 1; exp-- }; mant -= 8 }
|
|
440
|
+
|
|
441
|
+
// 4.2.16: APCM inverse quantization
|
|
442
|
+
let fac = GSM_FAC[mant]
|
|
443
|
+
let shift = 6 - exp
|
|
444
|
+
let round = shift > 1 ? 1 << (shift - 1) : 0 // gsm_asl(1, shift-1)
|
|
445
|
+
|
|
446
|
+
let xMp = new Int32Array(13)
|
|
447
|
+
for (let i = 0; i < 13; i++) {
|
|
448
|
+
let t = ((xMc[i] << 1) - 7) << 12
|
|
449
|
+
t = multr(fac, t)
|
|
450
|
+
t = sadd(t, round)
|
|
451
|
+
xMp[i] = asr(t, shift)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// 4.2.17: RPE grid positioning
|
|
455
|
+
for (let k = 0; k < 40; k++) erp[k] = 0
|
|
456
|
+
for (let i = 0; i < 13; i++) erp[Mc + 3 * i] = xMp[i]
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Whole-file decode
|
|
461
|
+
* @param {Uint8Array|ArrayBuffer} src
|
|
462
|
+
* @returns {Promise<{channelData: Float32Array[], sampleRate: number}>}
|
|
463
|
+
*/
|
|
464
|
+
export default async function decode(src) {
|
|
465
|
+
let buf = src instanceof Uint8Array ? src : src instanceof ArrayBuffer ? new Uint8Array(src) : src
|
|
466
|
+
let dec = await decoder()
|
|
467
|
+
try {
|
|
468
|
+
return dec.decode(buf)
|
|
469
|
+
} finally {
|
|
470
|
+
dec.free()
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Create decoder instance
|
|
476
|
+
* @returns {Promise<{decode(chunk: Uint8Array): {channelData, sampleRate}, flush(), free()}>}
|
|
477
|
+
*/
|
|
478
|
+
export async function decoder() {
|
|
479
|
+
let freed = false
|
|
480
|
+
return {
|
|
481
|
+
decode(data) {
|
|
482
|
+
if (freed) throw Error('Decoder already freed')
|
|
483
|
+
if (!data?.length) return EMPTY
|
|
484
|
+
return parseAiff(data)
|
|
485
|
+
},
|
|
486
|
+
flush() { return EMPTY },
|
|
487
|
+
free() { freed = true }
|
|
488
|
+
}
|
|
489
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@audio/aiff-decode",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Decode AIFF/AIFF-C audio to PCM samples",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "aiff-decode.js",
|
|
7
|
+
"types": "aiff-decode.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./aiff-decode.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"aiff-decode.js",
|
|
14
|
+
"aiff-decode.d.ts",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"aiff",
|
|
19
|
+
"aifc",
|
|
20
|
+
"audio",
|
|
21
|
+
"decode",
|
|
22
|
+
"decoder",
|
|
23
|
+
"pcm"
|
|
24
|
+
],
|
|
25
|
+
"publishConfig": { "access": "public" },
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"author": "audiojs",
|
|
28
|
+
"homepage": "https://github.com/audiojs/aiff-decode#readme",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/audiojs/aiff-decode.git"
|
|
32
|
+
},
|
|
33
|
+
"engines": { "node": ">=16" },
|
|
34
|
+
"bugs": "https://github.com/audiojs/aiff-decode/issues",
|
|
35
|
+
"scripts": {
|
|
36
|
+
"test": "node test.js"
|
|
37
|
+
}
|
|
38
|
+
}
|