carta1 1.1.1 → 1.1.3
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/codec/coding/quantization.js +2 -4
- package/codec/core/constants.js +4 -72
- package/codec/io/processor.js +4 -12
- package/codec/pipeline/decoder.js +1 -1
- package/codec/transforms/fft.js +104 -96
- package/codec/transforms/mdct.js +8 -8
- package/codec/utils.js +0 -121
- package/dist/carta1-worker.min.js +1 -1
- package/dist/carta1-worker.min.js.map +1 -1
- package/dist/carta1.min.js +1 -1
- package/dist/carta1.min.js.map +1 -1
- package/package.json +1 -1
|
@@ -35,7 +35,6 @@ export function quantize(coefficients, scaleFactorIndex, bitsPerSample) {
|
|
|
35
35
|
const length = coefficients.length
|
|
36
36
|
const out = new Int32Array(length)
|
|
37
37
|
if (bitsPerSample === 0 || scaleFactorIndex === 0) {
|
|
38
|
-
// Zero fast path
|
|
39
38
|
out.fill(0, 0, length)
|
|
40
39
|
return out
|
|
41
40
|
}
|
|
@@ -45,7 +44,7 @@ export function quantize(coefficients, scaleFactorIndex, bitsPerSample) {
|
|
|
45
44
|
const normFactor = quantRange / scaleFactor
|
|
46
45
|
|
|
47
46
|
const hi = quantRange
|
|
48
|
-
const lo = -quantRange
|
|
47
|
+
const lo = -quantRange
|
|
49
48
|
|
|
50
49
|
for (let i = 0; i < length; i++) {
|
|
51
50
|
const x = coefficients[i] * normFactor
|
|
@@ -70,11 +69,10 @@ export function dequantize(quantized, scaleFactorIndex, bitsPerSample) {
|
|
|
70
69
|
|
|
71
70
|
const scaleFactor = SCALE_FACTORS[scaleFactorIndex]
|
|
72
71
|
const quantRange = (1 << (bitsPerSample - QUANTIZATION_SIGN_BIT_SHIFT)) - 1
|
|
73
|
-
const denormFactor = scaleFactor / quantRange
|
|
74
72
|
|
|
75
73
|
const deq = new Float32Array(quantized.length)
|
|
76
74
|
for (let i = 0; i < quantized.length; i++) {
|
|
77
|
-
deq[i] = quantized[i] *
|
|
75
|
+
deq[i] = (quantized[i] * scaleFactor) / quantRange
|
|
78
76
|
}
|
|
79
77
|
return deq
|
|
80
78
|
}
|
package/codec/core/constants.js
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
export const SAMPLE_RATE = 44100
|
|
7
7
|
export const SAMPLES_PER_FRAME = 512
|
|
8
8
|
export const FRAME_RATE = SAMPLE_RATE / SAMPLES_PER_FRAME
|
|
9
|
-
export const CODEC_DELAY = 266
|
|
10
9
|
|
|
11
10
|
// AEA file format
|
|
12
11
|
export const AEA_MAGIC = new Uint8Array([0x00, 0x08, 0x00, 0x00])
|
|
@@ -37,16 +36,6 @@ export const BFU_AMOUNTS_COUNT = 8
|
|
|
37
36
|
export const BFU_AMOUNTS = new Int32Array([20, 28, 32, 36, 40, 44, 48, 52])
|
|
38
37
|
export const BFU_BAND_BOUNDARIES = new Int32Array([20, 36, 52])
|
|
39
38
|
|
|
40
|
-
// BFU center frequencies (Hz)
|
|
41
|
-
export const BFU_FREQUENCIES = new Float32Array([
|
|
42
|
-
172.3, 516.8, 861.3, 1205.9, 1464.3, 1636.5, 1808.8, 1981.1, 2239.5, 2584.0,
|
|
43
|
-
2928.5, 3273.0, 3574.5, 3832.9, 4091.3, 4349.7, 4608.1, 4866.5, 5124.9,
|
|
44
|
-
5383.3, 5641.7, 5900.1, 6158.5, 6416.9, 6696.8, 6998.3, 7299.8, 7601.2,
|
|
45
|
-
7945.8, 8333.3, 8720.9, 9108.5, 9517.7, 9948.3, 10379.0, 10809.7, 11283.4,
|
|
46
|
-
11800.2, 12317.0, 12833.8, 13350.6, 13867.4, 14384.2, 14901.0, 15590.0,
|
|
47
|
-
16451.4, 17312.7, 18174.0, 19035.4, 19896.7, 20758.0, 21619.3,
|
|
48
|
-
])
|
|
49
|
-
|
|
50
39
|
// BFU start positions
|
|
51
40
|
export const BFU_START_LONG = new Int32Array([
|
|
52
41
|
0, 8, 16, 24, 32, 36, 40, 44, 48, 56, 64, 72, 80, 86, 92, 98, 104, 110, 116,
|
|
@@ -69,7 +58,7 @@ export const MDCT_SIZE_LONG = 512
|
|
|
69
58
|
|
|
70
59
|
// Window functions
|
|
71
60
|
export const WINDOW_SHORT = (() => {
|
|
72
|
-
const table = new
|
|
61
|
+
const table = new Float64Array(32)
|
|
73
62
|
for (let i = 0; i < 32; i++) {
|
|
74
63
|
table[i] = Math.sin(((i + 0.5) * Math.PI) / 64)
|
|
75
64
|
}
|
|
@@ -117,40 +106,6 @@ export const QMF_ODD = (() => {
|
|
|
117
106
|
return odd
|
|
118
107
|
})()
|
|
119
108
|
|
|
120
|
-
// Psychoacoustic model
|
|
121
|
-
export const BARK_SCALE = (() => {
|
|
122
|
-
const table = new Float32Array(NUM_BFUS)
|
|
123
|
-
for (let i = 0; i < NUM_BFUS; i++) {
|
|
124
|
-
const freq = BFU_FREQUENCIES[i]
|
|
125
|
-
table[i] =
|
|
126
|
-
13.0 * Math.atan(0.00076 * freq) + 3.5 * Math.atan((freq / 7500) ** 2)
|
|
127
|
-
}
|
|
128
|
-
return table
|
|
129
|
-
})()
|
|
130
|
-
|
|
131
|
-
export const SPREADING_MATRIX = (() => {
|
|
132
|
-
const matrix = new Float32Array(NUM_BFUS * NUM_BFUS)
|
|
133
|
-
for (let masker = 0; masker < NUM_BFUS; masker++) {
|
|
134
|
-
for (let masked = 0; masked < NUM_BFUS; masked++) {
|
|
135
|
-
const distance = BARK_SCALE[masker] - BARK_SCALE[masked]
|
|
136
|
-
let spreading = -100.0
|
|
137
|
-
|
|
138
|
-
if (distance >= -3.0 && distance < -1.0) {
|
|
139
|
-
spreading = 17.0 * (distance + 3.0) - 70.0
|
|
140
|
-
} else if (distance >= -1.0 && distance < 0) {
|
|
141
|
-
spreading = 36.0 * distance
|
|
142
|
-
} else if (distance >= 0 && distance < 1.0) {
|
|
143
|
-
spreading = -27.0 * distance
|
|
144
|
-
} else if (distance >= 1.0 && distance < 8.0) {
|
|
145
|
-
spreading = -(distance - 1.0) * 4.0 - 27.0
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
matrix[masker * NUM_BFUS + masked] = spreading
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return matrix
|
|
152
|
-
})()
|
|
153
|
-
|
|
154
109
|
// FFT sizes for transient detection
|
|
155
110
|
export const FFT_SIZE_LOW = 128
|
|
156
111
|
export const FFT_SIZE_MID = 128
|
|
@@ -167,9 +122,6 @@ export const MDCT_SHORT_BLOCK_SIZE = 32
|
|
|
167
122
|
export const MDCT_OVERLAP_SIZE = 32
|
|
168
123
|
export const MDCT_TAIL_WINDOW_SIZE = 16
|
|
169
124
|
|
|
170
|
-
// Quantization
|
|
171
|
-
export const QUANTIZATION_SIGN_BIT_SHIFT = 1
|
|
172
|
-
|
|
173
125
|
// WAV format
|
|
174
126
|
export const WAV_HEADER_SIZE = 44
|
|
175
127
|
export const WAV_BYTES_PER_SAMPLE = 2
|
|
@@ -184,40 +136,20 @@ export const FRAME_WORD_LENGTH_BITS = 4
|
|
|
184
136
|
export const FRAME_SCALE_FACTOR_BITS = 6
|
|
185
137
|
|
|
186
138
|
// Quantization
|
|
139
|
+
export const QUANTIZATION_SIGN_BIT_SHIFT = 1
|
|
187
140
|
export const MAX_WORD_LENGTH_INDEX = 15
|
|
188
141
|
export const WORD_LENGTH_BITS = new Int32Array([
|
|
189
142
|
0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
|
|
190
143
|
])
|
|
191
|
-
export const QUANT_DISTORTION_TABLE = (() => {
|
|
192
|
-
const table = new Float32Array(MAX_WORD_LENGTH_INDEX + 1)
|
|
193
|
-
table[0] = 1.0
|
|
194
|
-
for (let wl = 1; wl <= MAX_WORD_LENGTH_INDEX; wl++) {
|
|
195
|
-
const bitsPerSample = WORD_LENGTH_BITS[wl]
|
|
196
|
-
const quantRange = (1 << (bitsPerSample - 1)) - 1
|
|
197
|
-
table[wl] = 1.0 / (12.0 * quantRange * quantRange)
|
|
198
|
-
}
|
|
199
|
-
return table
|
|
200
|
-
})()
|
|
201
|
-
export const DISTORTION_DELTA_DB = (() => {
|
|
202
|
-
const table = new Float32Array(MAX_WORD_LENGTH_INDEX)
|
|
203
|
-
for (let i = 0; i < MAX_WORD_LENGTH_INDEX; i++) {
|
|
204
|
-
table[i] =
|
|
205
|
-
10 *
|
|
206
|
-
Math.log10(
|
|
207
|
-
QUANT_DISTORTION_TABLE[i + 1] / (QUANT_DISTORTION_TABLE[i] || 1.0)
|
|
208
|
-
)
|
|
209
|
-
}
|
|
210
|
-
return table
|
|
211
|
-
})()
|
|
212
144
|
export const SCALE_FACTORS = (() => {
|
|
213
|
-
const table = new
|
|
145
|
+
const table = new Float64Array(64)
|
|
214
146
|
for (let i = 0; i < 64; i++) {
|
|
215
147
|
table[i] = Math.pow(2.0, i / 3.0 - 21)
|
|
216
148
|
}
|
|
217
149
|
return table
|
|
218
150
|
})()
|
|
219
151
|
|
|
220
|
-
//
|
|
152
|
+
// Bit allocation
|
|
221
153
|
export const INV_POWER_OF_TWO = (() => {
|
|
222
154
|
const maxBits = WORD_LENGTH_BITS[MAX_WORD_LENGTH_INDEX]
|
|
223
155
|
const table = new Float64Array(maxBits + 1)
|
package/codec/io/processor.js
CHANGED
|
@@ -30,12 +30,6 @@ import {
|
|
|
30
30
|
WAV_DATA_OFFSET,
|
|
31
31
|
} from '../core/constants.js'
|
|
32
32
|
import { serializeFrame, deserializeFrame, AeaFile } from './serialization.js'
|
|
33
|
-
import {
|
|
34
|
-
withFlushSamples,
|
|
35
|
-
withDelayCompensation,
|
|
36
|
-
withStereoFlushSamples,
|
|
37
|
-
withStereoDelayCompensation,
|
|
38
|
-
} from '../utils.js'
|
|
39
33
|
|
|
40
34
|
/**
|
|
41
35
|
* Audio processing utility class providing high-level encoding and decoding operations
|
|
@@ -83,7 +77,7 @@ export class AudioProcessor {
|
|
|
83
77
|
const options = encoderOptions || new EncoderOptions()
|
|
84
78
|
const encoder = encode(options)
|
|
85
79
|
let frameIndex = 0
|
|
86
|
-
for await (const frame of
|
|
80
|
+
for await (const frame of audioFrames) {
|
|
87
81
|
const result = encoder(frame)
|
|
88
82
|
yield result
|
|
89
83
|
|
|
@@ -107,9 +101,7 @@ export class AudioProcessor {
|
|
|
107
101
|
const rightEncoder = encode(options)
|
|
108
102
|
let frameIndex = 0
|
|
109
103
|
|
|
110
|
-
for await (const [leftFrame, rightFrame] of
|
|
111
|
-
audioFrames
|
|
112
|
-
)) {
|
|
104
|
+
for await (const [leftFrame, rightFrame] of audioFrames) {
|
|
113
105
|
const leftResult = leftEncoder(leftFrame)
|
|
114
106
|
const rightResult = rightEncoder(rightFrame)
|
|
115
107
|
|
|
@@ -167,7 +159,7 @@ export class AudioProcessor {
|
|
|
167
159
|
}
|
|
168
160
|
|
|
169
161
|
// Always apply delay compensation
|
|
170
|
-
yield*
|
|
162
|
+
yield* rawPcmFrames()
|
|
171
163
|
}
|
|
172
164
|
|
|
173
165
|
/**
|
|
@@ -220,7 +212,7 @@ export class AudioProcessor {
|
|
|
220
212
|
}
|
|
221
213
|
|
|
222
214
|
// Always apply stereo delay compensation
|
|
223
|
-
yield*
|
|
215
|
+
yield* rawPcmFrames()
|
|
224
216
|
}
|
|
225
217
|
|
|
226
218
|
/**
|
|
@@ -361,7 +361,7 @@ export function qmfSynthesisStage(context) {
|
|
|
361
361
|
// Apply high band delay compensation
|
|
362
362
|
const delayedHigh = bufferPool.qmfWorkBuffers.highBandDelay[bands[2].length]
|
|
363
363
|
delayedHigh.set(delays.highBand)
|
|
364
|
-
delayedHigh.set(bands[2])
|
|
364
|
+
delayedHigh.set(bands[2], delays.highBand.length)
|
|
365
365
|
|
|
366
366
|
const highBand = delayedHigh.slice(0, bands[0].length * 2)
|
|
367
367
|
delays.highBand = delayedHigh.slice(bands[0].length * 2)
|
package/codec/transforms/fft.js
CHANGED
|
@@ -1,136 +1,144 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Carta1 Audio Codec - Fast Fourier Transform
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* This module implements a radix-2 Cooley-Tukey FFT algorithm for frequency domain
|
|
5
|
+
* analysis used in psychoacoustic modeling and transient detection.
|
|
4
6
|
*/
|
|
5
7
|
export class FFT {
|
|
6
8
|
static _cache = new Map()
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
|
-
* Perform
|
|
10
|
-
* @param {Float32Array} real - Real part of
|
|
11
|
-
* @param {Float32Array} imag - Imaginary part of
|
|
11
|
+
* Perform in-place FFT on complex data
|
|
12
|
+
* @param {Float32Array} real - Real part of input/output data
|
|
13
|
+
* @param {Float32Array} imag - Imaginary part of input/output data
|
|
12
14
|
*/
|
|
13
15
|
static fft(real, imag) {
|
|
14
16
|
const n = real.length
|
|
15
|
-
if (n !== imag.length)
|
|
16
|
-
|
|
17
|
+
if (n !== imag.length)
|
|
18
|
+
throw new Error('Real and imaginary arrays must have the same length.')
|
|
19
|
+
if ((n & (n - 1)) !== 0) throw new Error('Input size must be a power of 2.')
|
|
17
20
|
if (n <= 1) return
|
|
18
21
|
|
|
19
22
|
let cache = FFT._cache.get(n)
|
|
20
23
|
if (!cache) {
|
|
21
|
-
|
|
24
|
+
// build plan in Float64 for precision
|
|
25
|
+
const log2n = Math.log2(n)
|
|
26
|
+
|
|
27
|
+
const bitRev = new Uint32Array(n)
|
|
28
|
+
for (let i = 1; i < n; i++) {
|
|
29
|
+
bitRev[i] = (bitRev[i >>> 1] >>> 1) | ((i & 1) << (log2n - 1))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const cosTbl = new Float64Array(n / 2)
|
|
33
|
+
const sinTbl = new Float64Array(n / 2)
|
|
34
|
+
for (let i = 0; i < n / 2; i++) {
|
|
35
|
+
const angle = (-2 * Math.PI * i) / n
|
|
36
|
+
// compute in f64, then enforce |W| = 1 to remove tiny drift
|
|
37
|
+
let c = Math.cos(angle)
|
|
38
|
+
let s = Math.sin(angle)
|
|
39
|
+
const invm = 1 / Math.hypot(c, s) // ~1, but fixes cos^2+sin^2 drift
|
|
40
|
+
cosTbl[i] = c * invm
|
|
41
|
+
sinTbl[i] = s * invm
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
cache = { bitRev, cos: cosTbl, sin: sinTbl }
|
|
22
45
|
FFT._cache.set(n, cache)
|
|
23
46
|
}
|
|
24
47
|
|
|
25
|
-
const bitRev = cache
|
|
26
|
-
|
|
27
|
-
|
|
48
|
+
const { bitRev, cos: cosTbl, sin: sinTbl } = cache
|
|
49
|
+
|
|
50
|
+
// work in Float64 to avoid per-stage Float32 rounding
|
|
51
|
+
const needCopyBack =
|
|
52
|
+
!(real instanceof Float64Array) || !(imag instanceof Float64Array)
|
|
53
|
+
const r = needCopyBack ? new Float64Array(real) : real
|
|
54
|
+
const im = needCopyBack ? new Float64Array(imag) : imag
|
|
28
55
|
|
|
29
|
-
//
|
|
56
|
+
// bit reversal
|
|
30
57
|
for (let i = 0; i < n; i++) {
|
|
31
58
|
const j = bitRev[i]
|
|
32
59
|
if (j > i) {
|
|
33
|
-
let
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
60
|
+
let t = r[i]
|
|
61
|
+
r[i] = r[j]
|
|
62
|
+
r[j] = t
|
|
63
|
+
t = im[i]
|
|
64
|
+
im[i] = im[j]
|
|
65
|
+
im[j] = t
|
|
39
66
|
}
|
|
40
67
|
}
|
|
41
68
|
|
|
42
|
-
//
|
|
69
|
+
// size-2 butterflies
|
|
43
70
|
for (let i = 0; i < n; i += 2) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
71
|
+
const i1 = i + 1
|
|
72
|
+
const er = r[i],
|
|
73
|
+
ei = im[i]
|
|
74
|
+
const tr = r[i1],
|
|
75
|
+
ti = im[i1]
|
|
76
|
+
r[i] = er + tr
|
|
77
|
+
im[i] = ei + ti
|
|
78
|
+
r[i1] = er - tr
|
|
79
|
+
im[i1] = ei - ti
|
|
52
80
|
}
|
|
53
81
|
|
|
54
|
-
//
|
|
82
|
+
// size-4 butterflies (uses exact W = -i at k=1)
|
|
55
83
|
if (n >= 4) {
|
|
56
84
|
for (let i = 0; i < n; i += 4) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
const e0 = i,
|
|
86
|
+
o0 = i + 2
|
|
87
|
+
const er0 = r[e0],
|
|
88
|
+
ei0 = im[e0]
|
|
89
|
+
const tr0 = r[o0],
|
|
90
|
+
ti0 = im[o0]
|
|
91
|
+
r[e0] = er0 + tr0
|
|
92
|
+
im[e0] = ei0 + ti0
|
|
93
|
+
r[o0] = er0 - tr0
|
|
94
|
+
im[o0] = ei0 - ti0
|
|
95
|
+
|
|
96
|
+
const e1 = i + 1,
|
|
97
|
+
o1 = i + 3
|
|
98
|
+
const tr1 = im[o1] // multiply by -i
|
|
99
|
+
const ti1 = -r[o1]
|
|
100
|
+
const er1 = r[e1],
|
|
101
|
+
ei1 = im[e1]
|
|
102
|
+
r[e1] = er1 + tr1
|
|
103
|
+
im[e1] = ei1 + ti1
|
|
104
|
+
r[o1] = er1 - tr1
|
|
105
|
+
im[o1] = ei1 - ti1
|
|
77
106
|
}
|
|
78
107
|
}
|
|
79
108
|
|
|
80
|
-
//
|
|
109
|
+
// size >= 8
|
|
81
110
|
for (let stride = 8; stride <= n; stride <<= 1) {
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
for (let
|
|
85
|
-
|
|
86
|
-
let
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
let ti = r2 * s + i2 * c
|
|
105
|
-
real[base + k] = r1 + tr
|
|
106
|
-
imag[base + k] = i1 + ti
|
|
107
|
-
real[base + k + halfStride] = r1 - tr
|
|
108
|
-
imag[base + k + halfStride] = i1 - ti
|
|
109
|
-
tw += tblStep
|
|
111
|
+
const half = stride >>> 1
|
|
112
|
+
const step = (n / stride) | 0
|
|
113
|
+
for (let i = 0; i < n; i += stride) {
|
|
114
|
+
let k = 0
|
|
115
|
+
for (let j = 0; j < half; j++, k += step) {
|
|
116
|
+
const even = i + j
|
|
117
|
+
const odd = even + half
|
|
118
|
+
|
|
119
|
+
const cr = cosTbl[k],
|
|
120
|
+
sr = sinTbl[k]
|
|
121
|
+
const orr = r[odd],
|
|
122
|
+
oii = im[odd]
|
|
123
|
+
|
|
124
|
+
const tr = orr * cr - oii * sr
|
|
125
|
+
const ti = orr * sr + oii * cr
|
|
126
|
+
|
|
127
|
+
const er = r[even],
|
|
128
|
+
ei = im[even]
|
|
129
|
+
r[even] = er + tr
|
|
130
|
+
im[even] = ei + ti
|
|
131
|
+
r[odd] = er - tr
|
|
132
|
+
im[odd] = ei - ti
|
|
110
133
|
}
|
|
111
134
|
}
|
|
112
135
|
}
|
|
113
|
-
}
|
|
114
136
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const bitRev = new Uint32Array(n)
|
|
121
|
-
const log2n = Math.log2(n) | 0
|
|
122
|
-
for (let i = 1; i < n; i++) {
|
|
123
|
-
bitRev[i] = (bitRev[i >>> 1] >>> 1) | ((i & 1) << (log2n - 1))
|
|
124
|
-
}
|
|
125
|
-
const halfN = n >>> 1
|
|
126
|
-
const cosTbl = new Float32Array(halfN)
|
|
127
|
-
const sinTbl = new Float32Array(halfN)
|
|
128
|
-
const ang = (-2 * Math.PI) / n
|
|
129
|
-
for (let i = 0; i < halfN; i++) {
|
|
130
|
-
const a = ang * i
|
|
131
|
-
cosTbl[i] = Math.cos(a)
|
|
132
|
-
sinTbl[i] = Math.sin(a)
|
|
137
|
+
if (needCopyBack) {
|
|
138
|
+
for (let i = 0; i < n; i++) {
|
|
139
|
+
real[i] = r[i]
|
|
140
|
+
imag[i] = im[i]
|
|
141
|
+
}
|
|
133
142
|
}
|
|
134
|
-
return { bitRev, cos: cosTbl, sin: sinTbl }
|
|
135
143
|
}
|
|
136
144
|
}
|
package/codec/transforms/mdct.js
CHANGED
|
@@ -28,7 +28,7 @@ class MDCTBase {
|
|
|
28
28
|
const omega = (2.0 * Math.PI) / size
|
|
29
29
|
const scaleRoot = Math.sqrt(scale / size)
|
|
30
30
|
|
|
31
|
-
this.sinCosTable = new
|
|
31
|
+
this.sinCosTable = new Float64Array(this.halfSize)
|
|
32
32
|
for (let i = 0; i < this.quarterSize; i++) {
|
|
33
33
|
const angle = omega * i + alpha
|
|
34
34
|
this.sinCosTable[i * 2] = scaleRoot * Math.cos(angle)
|
|
@@ -160,13 +160,13 @@ export class IMDCT extends MDCTBase {
|
|
|
160
160
|
// Pre-FFT processing
|
|
161
161
|
for (let i = 0; i < this.fftSize; i++) {
|
|
162
162
|
const i2 = i * 2
|
|
163
|
-
const r = input[i2]
|
|
164
|
-
const im = input[this.halfSize - 1 - i2]
|
|
163
|
+
const r = -input[i2]
|
|
164
|
+
const im = -input[this.halfSize - 1 - i2]
|
|
165
165
|
const c = this.sinCosTable[i2]
|
|
166
166
|
const s = this.sinCosTable[i2 + 1]
|
|
167
167
|
|
|
168
|
-
real[i] =
|
|
169
|
-
imag[i] =
|
|
168
|
+
real[i] = im * s + r * c
|
|
169
|
+
imag[i] = im * c - r * s
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
FFT.fft(real, imag)
|
|
@@ -216,9 +216,9 @@ export const mdct64 = new MDCT(MDCT_SIZE_SHORT, 0.5)
|
|
|
216
216
|
export const mdct256 = new MDCT(MDCT_SIZE_MID, 0.5)
|
|
217
217
|
export const mdct512 = new MDCT(MDCT_SIZE_LONG, 1.0)
|
|
218
218
|
|
|
219
|
-
export const imdct64 = new IMDCT(MDCT_SIZE_SHORT, MDCT_SIZE_SHORT *
|
|
220
|
-
export const imdct256 = new IMDCT(MDCT_SIZE_MID, MDCT_SIZE_MID *
|
|
221
|
-
export const imdct512 = new IMDCT(MDCT_SIZE_LONG, MDCT_SIZE_LONG *
|
|
219
|
+
export const imdct64 = new IMDCT(MDCT_SIZE_SHORT, MDCT_SIZE_SHORT * 8)
|
|
220
|
+
export const imdct256 = new IMDCT(MDCT_SIZE_MID, MDCT_SIZE_MID * 8)
|
|
221
|
+
export const imdct512 = new IMDCT(MDCT_SIZE_LONG, MDCT_SIZE_LONG * 4)
|
|
222
222
|
|
|
223
223
|
/**
|
|
224
224
|
* Perform overlap-add operation for MDCT reconstruction
|
package/codec/utils.js
CHANGED
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
* for delay compensation and frame padding.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { CODEC_DELAY, SAMPLES_PER_FRAME } from './core/constants.js'
|
|
10
|
-
|
|
11
9
|
/**
|
|
12
10
|
* Throws an error with the given message
|
|
13
11
|
* @param {string} msg - Error message
|
|
@@ -31,121 +29,6 @@ export function pipe(context, ...stages) {
|
|
|
31
29
|
}
|
|
32
30
|
}
|
|
33
31
|
|
|
34
|
-
/**
|
|
35
|
-
* Pads audio frames and adds flush samples for proper codec delay handling
|
|
36
|
-
* @param {AsyncIterable<Float32Array>} iter - Input frame stream
|
|
37
|
-
* @param {number} [samplesToFlush=CODEC_DELAY] - Number of samples to flush
|
|
38
|
-
* @yields {Float32Array} Padded frames with flush samples
|
|
39
|
-
*/
|
|
40
|
-
export async function* withFlushSamples(iter, samplesToFlush = CODEC_DELAY) {
|
|
41
|
-
let lastFrameLength = SAMPLES_PER_FRAME
|
|
42
|
-
for await (const frame of iter) {
|
|
43
|
-
lastFrameLength = frame.length
|
|
44
|
-
if (lastFrameLength === SAMPLES_PER_FRAME) {
|
|
45
|
-
yield frame
|
|
46
|
-
} else {
|
|
47
|
-
// We need to pad
|
|
48
|
-
const paddedFrame = new Float32Array(SAMPLES_PER_FRAME)
|
|
49
|
-
paddedFrame.set(frame)
|
|
50
|
-
yield paddedFrame
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
if (SAMPLES_PER_FRAME - lastFrameLength < samplesToFlush) {
|
|
54
|
-
yield new Float32Array(SAMPLES_PER_FRAME)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Applies delay compensation to PCM frame stream by dropping initial samples
|
|
60
|
-
* @param {AsyncIterable<Float32Array>} pcmFrameStream - Input PCM frame stream
|
|
61
|
-
* @param {number} [samplesToDrop=CODEC_DELAY] - Number of samples to drop for delay compensation
|
|
62
|
-
* @yields {Float32Array} Delay-compensated PCM frames
|
|
63
|
-
*/
|
|
64
|
-
export async function* withDelayCompensation(
|
|
65
|
-
pcmFrameStream,
|
|
66
|
-
samplesToDrop = CODEC_DELAY
|
|
67
|
-
) {
|
|
68
|
-
let buffer = new Float32Array(0)
|
|
69
|
-
let droppedInitialSamples = false
|
|
70
|
-
|
|
71
|
-
for await (const frame of pcmFrameStream) {
|
|
72
|
-
// Drop samples from the very first frame only
|
|
73
|
-
if (!droppedInitialSamples) {
|
|
74
|
-
droppedInitialSamples = true
|
|
75
|
-
if (samplesToDrop >= frame.length) {
|
|
76
|
-
// Entire first frame is dropped
|
|
77
|
-
samplesToDrop -= frame.length
|
|
78
|
-
continue
|
|
79
|
-
}
|
|
80
|
-
// Keep only the portion after dropped samples
|
|
81
|
-
buffer = frame.slice(samplesToDrop)
|
|
82
|
-
samplesToDrop = 0
|
|
83
|
-
continue
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Concatenate buffer with new frame
|
|
87
|
-
const combined = new Float32Array(buffer.length + frame.length)
|
|
88
|
-
combined.set(buffer)
|
|
89
|
-
combined.set(frame, buffer.length)
|
|
90
|
-
|
|
91
|
-
// Emit all complete frames
|
|
92
|
-
let offset = 0
|
|
93
|
-
while (offset + SAMPLES_PER_FRAME <= combined.length) {
|
|
94
|
-
yield combined.slice(offset, offset + SAMPLES_PER_FRAME)
|
|
95
|
-
offset += SAMPLES_PER_FRAME
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Buffer the remainder for next iteration
|
|
99
|
-
buffer = combined.slice(offset)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Emit final partial frame
|
|
103
|
-
if (buffer.length > 0) {
|
|
104
|
-
yield buffer
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Adapts a mono transform function to work with stereo audio streams
|
|
110
|
-
* @param {Function} monoTransform - Transform function for mono audio
|
|
111
|
-
* @param {...*} args - Additional arguments to pass to the transform
|
|
112
|
-
* @returns {Function} Stereo-adapted transform function
|
|
113
|
-
*/
|
|
114
|
-
export function withStereo(monoTransform, ...args) {
|
|
115
|
-
return async function* (stereoIter) {
|
|
116
|
-
// Buffer to store frames temporarily
|
|
117
|
-
const leftFrames = []
|
|
118
|
-
const rightFrames = []
|
|
119
|
-
|
|
120
|
-
// Collect all frames first (necessary for proper synchronization)
|
|
121
|
-
for await (const [left, right] of stereoIter) {
|
|
122
|
-
leftFrames.push(left)
|
|
123
|
-
rightFrames.push(right)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Create mono iterators
|
|
127
|
-
const leftIter = (async function* () {
|
|
128
|
-
for (const frame of leftFrames) yield frame
|
|
129
|
-
})()
|
|
130
|
-
|
|
131
|
-
const rightIter = (async function* () {
|
|
132
|
-
for (const frame of rightFrames) yield frame
|
|
133
|
-
})()
|
|
134
|
-
|
|
135
|
-
// Apply transform to each channel
|
|
136
|
-
const leftTransformed = monoTransform(leftIter, ...args)
|
|
137
|
-
const rightTransformed = monoTransform(rightIter, ...args)
|
|
138
|
-
|
|
139
|
-
// Zip back together
|
|
140
|
-
for await (const left of leftTransformed) {
|
|
141
|
-
const { value: right } = await rightTransformed.next()
|
|
142
|
-
if (right !== undefined) {
|
|
143
|
-
yield [left, right]
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
32
|
/**
|
|
150
33
|
* Reverses the spectral order of coefficients for mid/high band processing
|
|
151
34
|
*
|
|
@@ -185,7 +68,3 @@ export function extractBandCoefficients(coefficients, bandIndex) {
|
|
|
185
68
|
const offsets = [0, 128, 256, 512]
|
|
186
69
|
return coefficients.subarray(offsets[bandIndex], offsets[bandIndex + 1])
|
|
187
70
|
}
|
|
188
|
-
|
|
189
|
-
// Stereo-adapted utility functions
|
|
190
|
-
export const withStereoFlushSamples = withStereo(withFlushSamples)
|
|
191
|
-
export const withStereoDelayCompensation = withStereo(withDelayCompensation)
|