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.
@@ -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 - 1
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] * denormFactor
75
+ deq[i] = (quantized[i] * scaleFactor) / quantRange
78
76
  }
79
77
  return deq
80
78
  }
@@ -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 Float32Array(32)
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 Float32Array(64)
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
- // RDO bit allocation
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)
@@ -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 withFlushSamples(audioFrames)) {
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 withStereoFlushSamples(
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* withDelayCompensation(rawPcmFrames())
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* withStereoDelayCompensation(rawPcmFrames())
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)
@@ -1,136 +1,144 @@
1
1
  /**
2
2
  * Carta1 Audio Codec - Fast Fourier Transform
3
- * Optimized radix-2 Cooley-Tukey FFT implementation
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 an in-place forward FFT on complex data.
10
- * @param {Float32Array} real - Real part of the input/output data
11
- * @param {Float32Array} imag - Imaginary part of the input/output data
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) throw new Error('Mismatched array lengths.')
16
- if ((n & (n - 1)) !== 0) throw new Error('Length must be power of 2.')
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
- cache = FFT._buildCache(n)
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.bitRev
26
- const cosTbl = cache.cos
27
- const sinTbl = cache.sin
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
- // Bit reversal
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 tr = real[i]
34
- let ti = imag[i]
35
- real[i] = real[j]
36
- imag[i] = imag[j]
37
- real[j] = tr
38
- imag[j] = ti
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
- // First stage: stride=2
69
+ // size-2 butterflies
43
70
  for (let i = 0; i < n; i += 2) {
44
- let r0 = real[i],
45
- i0 = imag[i]
46
- let r1 = real[i + 1],
47
- i1 = imag[i + 1]
48
- real[i] = r0 + r1
49
- imag[i] = i0 + i1
50
- real[i + 1] = r0 - r1
51
- imag[i + 1] = i0 - i1
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
- // Second stage: stride=4
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
- let r0 = real[i],
58
- i0 = imag[i]
59
- let r2 = real[i + 2],
60
- i2 = imag[i + 2]
61
- real[i] = r0 + r2
62
- imag[i] = i0 + i2
63
- real[i + 2] = r0 - r2
64
- imag[i + 2] = i0 - i2
65
-
66
- r0 = real[i + 1]
67
- i0 = imag[i + 1]
68
- r2 = real[i + 3]
69
- i2 = imag[i + 3]
70
- // W^1 = -i
71
- let tR = i2
72
- let tI = -r2
73
- real[i + 1] = r0 + tR
74
- imag[i + 1] = i0 + tI
75
- real[i + 3] = r0 - tR
76
- imag[i + 3] = i0 - tI
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
- // Higher stages
109
+ // size >= 8
81
110
  for (let stride = 8; stride <= n; stride <<= 1) {
82
- const halfStride = stride >>> 1
83
- const tblStep = n / stride
84
- for (let base = 0; base < n; base += stride) {
85
- // k = 0
86
- let r0 = real[base],
87
- i0 = imag[base]
88
- let r1 = real[base + halfStride],
89
- i1 = imag[base + halfStride]
90
- real[base] = r0 + r1
91
- imag[base] = i0 + i1
92
- real[base + halfStride] = r0 - r1
93
- imag[base + halfStride] = i0 - i1
94
-
95
- let tw = tblStep
96
- for (let k = 1; k < halfStride; k++) {
97
- const c = cosTbl[tw]
98
- const s = sinTbl[tw]
99
- r1 = real[base + k]
100
- i1 = imag[base + k]
101
- let r2 = real[base + k + halfStride]
102
- let i2 = imag[base + k + halfStride]
103
- let tr = r2 * c - i2 * s
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
- * Build cache tables for FFT of size n
117
- * @private
118
- */
119
- static _buildCache(n) {
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
  }
@@ -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 Float32Array(this.halfSize)
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] = -2.0 * (im * s + r * c)
169
- imag[i] = -2.0 * (im * c - r * s)
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 * 4)
220
- export const imdct256 = new IMDCT(MDCT_SIZE_MID, MDCT_SIZE_MID * 4)
221
- export const imdct512 = new IMDCT(MDCT_SIZE_LONG, MDCT_SIZE_LONG * 2)
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)