carta1 1.2.0 → 1.3.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.
@@ -2,9 +2,34 @@
2
2
  * Carta1 QMF analysis application.
3
3
  */
4
4
 
5
- import { analyzeQmfPair } from '../signal/bands.js'
5
+ import { QMF_ANALYSIS_DELAY_SAMPLES } from '../core/constants.js'
6
+ import { analyzeQmf } from '../signal/qmf.js'
6
7
  import { throwError } from '../utils.js'
7
8
 
9
+ /**
10
+ * Split one sample row through a two-band configurable QMF stage.
11
+ *
12
+ * @param {Float32Array} input
13
+ * @param {Float32Array} delay
14
+ * @param {object} scratch
15
+ * @returns {Float32Array[]}
16
+ */
17
+ function analyzePair(input, delay, scratch) {
18
+ const window =
19
+ scratch.analysisWindows[input.length] ??
20
+ throwError(`QMF analysis window for ${input.length} samples is required`)
21
+ window.set(delay)
22
+ window.set(input, delay.length)
23
+
24
+ const sampleCount = input.length / 2
25
+ const bands = [new Float32Array(sampleCount), new Float32Array(sampleCount)]
26
+ analyzeQmf(window, bands, scratch)
27
+ delay.set(
28
+ window.subarray(input.length, input.length + QMF_ANALYSIS_DELAY_SAMPLES)
29
+ )
30
+ return bands
31
+ }
32
+
8
33
  /**
9
34
  * Split PCM into the three ATRAC1 frequency bands.
10
35
  *
@@ -15,35 +40,29 @@ import { throwError } from '../utils.js'
15
40
  export function analyzeBands(context) {
16
41
  const bufferPool =
17
42
  context?.bufferPool ?? throwError('analyzeBands: bufferPool is required')
18
- const delays = bufferPool.qmfDelays
43
+ const delays = bufferPool.qmfAnalysisDelays
44
+ const scratch = bufferPool.qmfScratch
19
45
 
20
46
  /**
21
47
  * @param {Float32Array} pcmSamples
22
48
  * @returns {object}
23
49
  */
24
50
  return (pcmSamples) => {
25
- const stage1 = analyzeQmfPair(
26
- pcmSamples,
27
- delays.lowBand,
28
- bufferPool.qmfWorkBuffers
29
- )
30
- delays.lowBand = stage1.newDelay
51
+ const stage1 = analyzePair(pcmSamples, delays.lowBand, scratch)
52
+ const stage2 = analyzePair(stage1[0], delays.midBand, scratch)
31
53
 
32
- const stage2 = analyzeQmfPair(
33
- stage1.lowBand,
34
- delays.midBand,
35
- bufferPool.qmfWorkBuffers
36
- )
37
- delays.midBand = stage2.newDelay
54
+ const delayedHigh = scratch.highBandWindows[stage1[1].length]
55
+ delayedHigh.set(bufferPool.qmfHighBandDelay)
56
+ delayedHigh.set(stage1[1], bufferPool.qmfHighBandDelay.length)
38
57
 
39
- const delayedHigh =
40
- bufferPool.qmfWorkBuffers.highBandDelay[stage1.highBand.length]
41
- delayedHigh.set(delays.highBand)
42
- delayedHigh.set(stage1.highBand, delays.highBand.length)
43
-
44
- const highBand = delayedHigh.slice(0, stage1.highBand.length)
45
- delays.highBand = delayedHigh.slice(stage1.highBand.length)
58
+ const highBand = delayedHigh.slice(0, stage1[1].length)
59
+ bufferPool.qmfHighBandDelay.set(
60
+ delayedHigh.subarray(
61
+ stage1[1].length,
62
+ stage1[1].length + bufferPool.qmfHighBandDelay.length
63
+ )
64
+ )
46
65
 
47
- return { bands: [stage2.lowBand, stage2.highBand, highBand] }
66
+ return { bands: [stage2[0], stage2[1], highBand] }
48
67
  }
49
68
  }
@@ -196,7 +196,7 @@ export function performFFT(samples, fftSize) {
196
196
  real.set(samples.subarray(0, copyLen))
197
197
 
198
198
  // Perform FFT
199
- FFT.fft(real, imag)
199
+ FFT.transform(real, imag)
200
200
 
201
201
  // Calculate magnitude spectrum for positive frequencies
202
202
  const magnitude = new Float32Array(fftSize / 2)
@@ -11,14 +11,14 @@ import {
11
11
  } from '../core/constants.js'
12
12
  import { calculateBandOffset } from '../core/geometry.js'
13
13
  import { MDCT_BAND_CONFIGS, WINDOW_SHORT } from '../core/tables.js'
14
- import {
15
- mdct64,
16
- mdct256,
17
- mdct512,
18
- reverseSpectrum,
19
- } from '../signal/spectrum.js'
14
+ import { MDCT } from '../signal/mdct.js'
15
+ import { reverseSpectrum } from '../signal/order.js'
20
16
  import { throwError } from '../utils.js'
21
17
 
18
+ const shortMdct = new MDCT(MDCT_SIZE_SHORT, 0.5)
19
+ const middleMdct = new MDCT(MDCT_SIZE_MID, 0.5)
20
+ const longMdct = new MDCT(MDCT_SIZE_LONG, 1)
21
+
22
22
  /**
23
23
  * Build the ATRAC1 spectrum from analyzed bands.
24
24
  *
@@ -31,7 +31,7 @@ export function transformSpectrum(context) {
31
31
  context?.bufferPool ??
32
32
  throwError('transformSpectrum: bufferPool is required')
33
33
  const overlapBuffers = bufferPool.mdctOverlap
34
- const transformFunctions = [mdct256, mdct256, mdct512]
34
+ const transformFunctions = [middleMdct, middleMdct, longMdct]
35
35
 
36
36
  /**
37
37
  * @param {Float32Array} samples
@@ -113,7 +113,7 @@ export function transformSpectrum(context) {
113
113
  applyTailWindowing(blockSamples, overlapBuffer, MDCT_SHORT_BLOCK_SIZE)
114
114
  mdctInput.set(blockSamples, MDCT_OVERLAP_SIZE)
115
115
 
116
- let spectrum = mdct64.transform(mdctInput, bufferPool.mdctBuffers)
116
+ let spectrum = shortMdct.transform(mdctInput, bufferPool.mdctBuffers)
117
117
  if (bandIndex > 0) {
118
118
  spectrum = reverseSpectrum(spectrum, bufferPool.reversalBuffers)
119
119
  }
@@ -34,8 +34,17 @@ export const MDCT_SHORT_BLOCK_SIZE = 32
34
34
  export const MDCT_OVERLAP_SIZE = 32
35
35
  export const MDCT_TAIL_WINDOW_SIZE = 16
36
36
 
37
+ export const QMF_BANDS = 2
37
38
  export const QMF_TAPS = 48
38
- export const QMF_DELAY = 46
39
+ export const QMF_ANALYSIS_START_SAMPLE = 0
40
+ export const QMF_ANALYSIS_STEP_SAMPLES = QMF_BANDS
41
+ export const QMF_ANALYSIS_WINDOW_SAMPLES = QMF_TAPS
42
+ export const QMF_ANALYSIS_MODULATION_SCALE = 1
43
+ export const QMF_ANALYSIS_TERMS = 24
44
+ export const QMF_ANALYSIS_DELAY_SAMPLES = QMF_TAPS - QMF_BANDS
45
+ export const QMF_SYNTHESIS_DELAY_ROWS = 24
46
+ export const QMF_SYNTHESIS_TERMS = 24
47
+ export const QMF_SYNTHESIS_DELAY_SAMPLES = QMF_BANDS * QMF_SYNTHESIS_DELAY_ROWS
39
48
  export const QMF_HIGH_BAND_DELAY = 39
40
49
 
41
50
  export const FFT_SIZE_LOW = 128
@@ -2,7 +2,14 @@
2
2
  * Stable ATRAC1 geometry, signal, allocation, and quantization tables.
3
3
  */
4
4
 
5
- import { MAX_WORD_LENGTH_INDEX, QMF_TAPS } from './constants.js'
5
+ import {
6
+ MAX_WORD_LENGTH_INDEX,
7
+ QMF_ANALYSIS_TERMS,
8
+ QMF_BANDS,
9
+ QMF_SYNTHESIS_DELAY_ROWS,
10
+ QMF_SYNTHESIS_TERMS,
11
+ QMF_TAPS,
12
+ } from './constants.js'
6
13
 
7
14
  export const AEA_MAGIC = new Uint8Array([0x00, 0x08, 0x00, 0x00])
8
15
 
@@ -70,6 +77,97 @@ export const QMF_ODD = (() => {
70
77
  return odd
71
78
  })()
72
79
 
80
+ /** Sample positions read by each component of the configurable QMF analysis. */
81
+ export const QMF_ANALYSIS_SAMPLE_OFFSETS = (() => {
82
+ const offsets = new Uint8Array(QMF_BANDS * QMF_ANALYSIS_TERMS)
83
+ for (let term = 0; term < QMF_ANALYSIS_TERMS; term++) {
84
+ offsets[term] = QMF_TAPS - 1 - term * QMF_BANDS
85
+ offsets[QMF_ANALYSIS_TERMS + term] = QMF_TAPS - QMF_BANDS - term * QMF_BANDS
86
+ }
87
+ return offsets
88
+ })()
89
+
90
+ /** Prototype-filter coefficients in component-major QMF analysis order. */
91
+ export const QMF_ANALYSIS_FILTER_COEFFICIENTS = (() => {
92
+ const coefficients = new Float32Array(QMF_BANDS * QMF_ANALYSIS_TERMS)
93
+ coefficients.set(QMF_EVEN)
94
+ coefficients.set(QMF_ODD, QMF_ANALYSIS_TERMS)
95
+ return coefficients
96
+ })()
97
+
98
+ /** Dense modulation applied after Carta1 QMF analysis filtering. */
99
+ export const QMF_ANALYSIS_MODULATION_COEFFICIENTS = new Float32Array([
100
+ 1, 1, 1, -1,
101
+ ])
102
+
103
+ /* eslint-disable no-loss-of-precision -- Float32Array applies the intended float32 rounding. */
104
+
105
+ /** Reference factors used by the optimized sixteen-band modulation path. */
106
+ export const QMF_ANALYSIS_PI_OVER_8_BUTTERFLY_SCALES = new Float32Array([
107
+ 1.8477590084075928, 0.7653668522834778, 1.8477590084075928,
108
+ 0.7653668522834778,
109
+ ])
110
+
111
+ /** Reference half-angle factors used by sixteen-band modulation. */
112
+ export const QMF_ANALYSIS_HALF_BUTTERFLY_SCALES = new Float32Array([
113
+ 0.7071067690849304, 0.2071067839860916, 1.2071068286895752,
114
+ ])
115
+
116
+ /** Reference odd pi/32 cosines used by sixteen-band modulation. */
117
+ export const QMF_ANALYSIS_ODD_PI_OVER_32_COSINES = new Float32Array([
118
+ 1.990369439125061, 1.913880705833435, 1.7638425827026367, 1.5460208654403687,
119
+ 1.2687865495681763, 0.9427934885025024, 0.5805693864822388,
120
+ 0.1960342824459076,
121
+ ])
122
+
123
+ /** Reference odd pi/16 cosines used by sixteen-band modulation. */
124
+ export const QMF_ANALYSIS_ODD_PI_OVER_16_COSINES = new Float32Array([
125
+ 1.9615705013275146, 1.662939190864563, 1.111140489578247, 0.39018064737319946,
126
+ ])
127
+
128
+ /** Reference odd pi/64 cosines used by sixteen-band modulation. */
129
+ export const QMF_ANALYSIS_ODD_PI_OVER_64_COSINES = new Float32Array([
130
+ 1.9975908994674683, 1.9783530235290527, 1.9400625228881836,
131
+ 1.8830881118774414, 1.807978630065918, 1.7154572010040283, 1.606415033340454,
132
+ 1.4819022417068481, 1.3431179523468018, 1.1913986206054688,
133
+ 1.0282055139541626, 0.8551101684570312, 0.6737797260284424,
134
+ 0.48596036434173584, 0.2934609651565552, 0.09813535213470459,
135
+ ])
136
+
137
+ /* eslint-enable no-loss-of-precision */
138
+
139
+ /** Delay-row offsets read by each sample of configurable QMF synthesis. */
140
+ export const QMF_SYNTHESIS_DELAY_ROW_OFFSETS = (() => {
141
+ const offsets = new Uint8Array(QMF_BANDS * QMF_SYNTHESIS_TERMS)
142
+ for (let sample = 0; sample < QMF_BANDS; sample++) {
143
+ const sampleOffset = sample * QMF_SYNTHESIS_TERMS
144
+ for (let term = 0; term < QMF_SYNTHESIS_TERMS; term++) {
145
+ offsets[sampleOffset + term] = QMF_SYNTHESIS_DELAY_ROWS - 1 - term
146
+ }
147
+ }
148
+ return offsets
149
+ })()
150
+
151
+ /** Delay components read by each sample of configurable QMF synthesis. */
152
+ export const QMF_SYNTHESIS_DELAY_COMPONENTS = (() => {
153
+ const components = new Uint8Array(QMF_BANDS * QMF_SYNTHESIS_TERMS)
154
+ components.fill(1, 0, QMF_SYNTHESIS_TERMS)
155
+ return components
156
+ })()
157
+
158
+ /** Prototype-filter coefficients in output-sample-major QMF synthesis order. */
159
+ export const QMF_SYNTHESIS_FILTER_COEFFICIENTS = (() => {
160
+ const coefficients = new Float64Array(QMF_BANDS * QMF_SYNTHESIS_TERMS)
161
+ coefficients.set(QMF_ODD)
162
+ coefficients.set(QMF_EVEN, QMF_SYNTHESIS_TERMS)
163
+ return coefficients
164
+ })()
165
+
166
+ /** Dense modulation applied before Carta1 QMF synthesis filtering. */
167
+ export const QMF_SYNTHESIS_MODULATION_COEFFICIENTS = new Float64Array([
168
+ 0.5, 0.5, 0.5, -0.5,
169
+ ])
170
+
73
171
  export const MDCT_BAND_CONFIGS = [
74
172
  { size: 128, windowStart: 48 },
75
173
  { size: 128, windowStart: 48 },
@@ -8,7 +8,7 @@ import {
8
8
  BFU_START_SHORT,
9
9
  WORD_LENGTH_BITS,
10
10
  } from './core/tables.js'
11
- import { dequantize as dequantizeCoefficient } from './quantization/spectrum.js'
11
+ import * as spectrumQuantization from './quantization/spectrum.js'
12
12
 
13
13
  /**
14
14
  * Reconstruct the continuous ATRAC1 spectrum.
@@ -40,7 +40,7 @@ export function dequantize() {
40
40
  blockModes[band] === 0 ? BFU_START_LONG[bfu] : BFU_START_SHORT[bfu]
41
41
 
42
42
  if (bitsPerSample > 0) {
43
- const dequantized = dequantizeCoefficient(
43
+ const dequantized = spectrumQuantization.dequantize(
44
44
  quantizedCoefficients[bfu],
45
45
  scaleFactorIndices[bfu],
46
46
  bitsPerSample
package/codec/index.js CHANGED
@@ -7,8 +7,8 @@ import { decode } from './pipeline/decoder.js'
7
7
  import { AeaFile } from './boundary/container.js'
8
8
  import { deserializeFrame, serializeFrame } from './syntax/frame.js'
9
9
  import { quantize, dequantize } from './quantization/spectrum.js'
10
- import { analyzeBands as qmfAnalysisStage } from './analysis/bands.js'
11
- import { transformSpectrum as mdctStage } from './analysis/spectrum.js'
10
+ import { analyzeBands } from './analysis/bands.js'
11
+ import { transformSpectrum } from './analysis/spectrum.js'
12
12
  import { pipe } from './utils.js'
13
13
  import { BufferPool } from './state.js'
14
14
  import { EncoderOptions } from './core/options.js'
@@ -26,8 +26,8 @@ export {
26
26
  pipe,
27
27
  encode,
28
28
  decode,
29
- qmfAnalysisStage,
30
- mdctStage,
29
+ analyzeBands as qmfAnalysisStage,
30
+ transformSpectrum as mdctStage,
31
31
  serializeFrame,
32
32
  deserializeFrame,
33
33
  quantize,
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import { WORD_LENGTH_BITS } from '../core/tables.js'
6
- import { quantize as quantizeCoefficient } from './spectrum.js'
6
+ import * as spectrumQuantization from './spectrum.js'
7
7
 
8
8
  /**
9
9
  * Materialize retained allocation as quantized ATRAC1 symbols.
@@ -30,7 +30,11 @@ export function quantize() {
30
30
  const data = bfuData[bfu].subarray(0, bfuSizes[bfu])
31
31
  const bitsPerSample = WORD_LENGTH_BITS[wordLengthIndices[bfu]]
32
32
  quantizedCoefficients.push(
33
- quantizeCoefficient(data, scaleFactorIndices[bfu], bitsPerSample)
33
+ spectrumQuantization.quantize(
34
+ data,
35
+ scaleFactorIndices[bfu],
36
+ bitsPerSample
37
+ )
34
38
  )
35
39
  }
36
40
 
@@ -1,66 +1,65 @@
1
- /**
2
- * Carta1 Fast Fourier Transform operator.
3
- *
4
- * This module implements a radix-2 Cooley-Tukey FFT algorithm for frequency domain
5
- * analysis used in psychoacoustic modeling and transient detection.
6
- */
1
+ /** Complex radix-2 Fast Fourier Transform. */
7
2
 
8
3
  export class FFT {
9
4
  /**
10
- * Perform in-place FFT on complex data
11
- * @param {Float32Array} real - Real part of input/output data
12
- * @param {Float32Array} imag - Imaginary part of input/output data
5
+ * Transform split complex data in place.
6
+ *
7
+ * @param {Float32Array} real Real input and output components.
8
+ * @param {Float32Array} imaginary Imaginary input and output components.
13
9
  */
14
- static fft(real, imag) {
10
+ static transform(real, imaginary) {
15
11
  const size = real.length
16
12
  if (size === 1) return
17
13
 
18
14
  const bits = Math.log2(size)
19
-
20
- // Bit reversal
21
- for (let i = 0; i < size; i++) {
15
+ for (let index = 0; index < size; index++) {
22
16
  let reversed = 0
23
- let temp = i
24
- for (let b = 0; b < bits; b++) {
25
- reversed = (reversed << 1) | (temp & 1)
26
- temp >>= 1
17
+ let remaining = index
18
+ for (let bit = 0; bit < bits; bit++) {
19
+ reversed = (reversed << 1) | (remaining & 1)
20
+ remaining >>= 1
27
21
  }
28
- if (reversed > i) {
29
- ;[real[i], real[reversed]] = [real[reversed], real[i]]
30
- ;[imag[i], imag[reversed]] = [imag[reversed], imag[i]]
22
+ if (reversed > index) {
23
+ const realValue = real[index]
24
+ real[index] = real[reversed]
25
+ real[reversed] = realValue
26
+ const imaginaryValue = imaginary[index]
27
+ imaginary[index] = imaginary[reversed]
28
+ imaginary[reversed] = imaginaryValue
31
29
  }
32
30
  }
33
31
 
34
- // Cooley-Tukey decimation-in-time
35
32
  for (let stride = 2; stride <= size; stride <<= 1) {
36
33
  const halfStride = stride >> 1
37
34
  const angle = (-2 * Math.PI) / stride
38
- const wReal = Math.cos(angle)
39
- const wImag = Math.sin(angle)
35
+ const stepReal = Math.cos(angle)
36
+ const stepImaginary = Math.sin(angle)
40
37
 
41
38
  for (let start = 0; start < size; start += stride) {
42
39
  let twiddleReal = 1
43
- let twiddleImag = 0
40
+ let twiddleImaginary = 0
44
41
 
45
- for (let k = 0; k < halfStride; k++) {
46
- const evenIndex = start + k
42
+ for (let offset = 0; offset < halfStride; offset++) {
43
+ const evenIndex = start + offset
47
44
  const oddIndex = evenIndex + halfStride
48
-
49
45
  const evenReal = real[evenIndex]
50
- const evenImag = imag[evenIndex]
46
+ const evenImaginary = imaginary[evenIndex]
51
47
  const oddReal = real[oddIndex]
52
- const oddImag = imag[oddIndex]
53
-
54
- const tReal = oddReal * twiddleReal - oddImag * twiddleImag
55
- const tImag = oddReal * twiddleImag + oddImag * twiddleReal
48
+ const oddImaginary = imaginary[oddIndex]
49
+ const rotatedReal =
50
+ oddReal * twiddleReal - oddImaginary * twiddleImaginary
51
+ const rotatedImaginary =
52
+ oddReal * twiddleImaginary + oddImaginary * twiddleReal
56
53
 
57
- real[evenIndex] = evenReal + tReal
58
- imag[evenIndex] = evenImag + tImag
59
- real[oddIndex] = evenReal - tReal
60
- imag[oddIndex] = evenImag - tImag
54
+ real[evenIndex] = evenReal + rotatedReal
55
+ imaginary[evenIndex] = evenImaginary + rotatedImaginary
56
+ real[oddIndex] = evenReal - rotatedReal
57
+ imaginary[oddIndex] = evenImaginary - rotatedImaginary
61
58
 
62
- const nextReal = twiddleReal * wReal - twiddleImag * wImag
63
- twiddleImag = twiddleReal * wImag + twiddleImag * wReal
59
+ const nextReal =
60
+ twiddleReal * stepReal - twiddleImaginary * stepImaginary
61
+ twiddleImaginary =
62
+ twiddleReal * stepImaginary + twiddleImaginary * stepReal
64
63
  twiddleReal = nextReal
65
64
  }
66
65
  }
@@ -1,23 +1,18 @@
1
1
  /**
2
- * Carta1 spectrum signal operators.
2
+ * Modified discrete cosine transforms backed by a complex FFT.
3
3
  *
4
- * This module implements forward and inverse MDCT transforms for the ATRAC1 codec.
5
- * The MDCT provides time-frequency analysis with perfect reconstruction and
6
- * 50% overlap between adjacent blocks to avoid blocking artifacts.
4
+ * The evaluation order is part of Carta1's numerical contract. The staged
5
+ * Float32 decomposition used by Carta5 is mathematically equivalent but does
6
+ * not preserve Carta1's encoded output or perceptual-quality floor.
7
7
  */
8
8
 
9
9
  import { FFT } from './fft.js'
10
- import {
11
- MDCT_SIZE_SHORT,
12
- MDCT_SIZE_MID,
13
- MDCT_SIZE_LONG,
14
- } from '../core/constants.js'
15
10
  import { throwError } from '../utils.js'
16
11
 
17
12
  /**
18
13
  * Base class for MDCT and IMDCT transforms
19
14
  */
20
- class MDCTBase {
15
+ class MdctBase {
21
16
  constructor(size, scale) {
22
17
  this.size = size
23
18
  this.halfSize = size >> 1
@@ -40,7 +35,7 @@ class MDCTBase {
40
35
  /**
41
36
  * Forward MDCT transform
42
37
  */
43
- export class MDCT extends MDCTBase {
38
+ export class MDCT extends MdctBase {
44
39
  constructor(size, scale) {
45
40
  super(size, scale)
46
41
  }
@@ -51,7 +46,12 @@ export class MDCT extends MDCTBase {
51
46
  * @param {Object} mdctBuffers - Optional work buffers for FFT
52
47
  * @returns {Float32Array} Frequency-domain MDCT coefficients
53
48
  */
54
- transform(input, mdctBuffers = null) {
49
+ transform(
50
+ input,
51
+ mdctBuffers = null,
52
+ destination = null,
53
+ destinationOffset = 0
54
+ ) {
55
55
  const buffers =
56
56
  mdctBuffers?.[this.fftSize] ??
57
57
  throwError(`MDCT.transform: mdctBuffers[${this.fftSize}] is required`)
@@ -61,9 +61,9 @@ export class MDCT extends MDCTBase {
61
61
  `MDCT.transform: mdctBuffers[${this.fftSize}].real is required`
62
62
  )
63
63
  const imag =
64
- buffers?.imag ??
64
+ buffers?.imaginary ??
65
65
  throwError(
66
- `MDCT.transform: mdctBuffers[${this.fftSize}].imag is required`
66
+ `MDCT.transform: mdctBuffers[${this.fftSize}].imaginary is required`
67
67
  )
68
68
 
69
69
  const n4 = this.quarterSize
@@ -104,18 +104,18 @@ export class MDCT extends MDCTBase {
104
104
  imag[idx] = im * c - r * s
105
105
  }
106
106
 
107
- FFT.fft(real, imag)
107
+ FFT.transform(real, imag)
108
108
 
109
109
  // Post-FFT processing
110
- const output = new Float32Array(this.halfSize)
110
+ const output = destination ?? new Float32Array(this.halfSize)
111
111
  for (let i = 0; i < this.fftSize; i++) {
112
112
  const c = this.sinCosTable[i * 2]
113
113
  const s = this.sinCosTable[i * 2 + 1]
114
114
  const re = real[i]
115
115
  const im = imag[i]
116
116
 
117
- output[i * 2] = -re * c - im * s
118
- output[this.halfSize - 1 - i * 2] = -re * s + im * c
117
+ output[destinationOffset + i * 2] = -re * c - im * s
118
+ output[destinationOffset + this.halfSize - 1 - i * 2] = -re * s + im * c
119
119
  }
120
120
 
121
121
  return output
@@ -125,7 +125,7 @@ export class MDCT extends MDCTBase {
125
125
  /**
126
126
  * Inverse MDCT transform
127
127
  */
128
- export class IMDCT extends MDCTBase {
128
+ export class IMDCT extends MdctBase {
129
129
  constructor(size, scale = null) {
130
130
  super(size, scale || size)
131
131
  }
@@ -136,7 +136,12 @@ export class IMDCT extends MDCTBase {
136
136
  * @param {Object} mdctBuffers - Optional work buffers for FFT
137
137
  * @returns {Float32Array} Time-domain output samples
138
138
  */
139
- transform(input, mdctBuffers = null) {
139
+ transform(
140
+ input,
141
+ mdctBuffers = null,
142
+ destination = null,
143
+ destinationOffset = 0
144
+ ) {
140
145
  const buffers =
141
146
  mdctBuffers?.[this.fftSize] ??
142
147
  throwError(`IMDCT.transform: mdctBuffers[${this.fftSize}] is required`)
@@ -146,9 +151,9 @@ export class IMDCT extends MDCTBase {
146
151
  `IMDCT.transform: mdctBuffers[${this.fftSize}].real is required`
147
152
  )
148
153
  const imag =
149
- buffers?.imag ??
154
+ buffers?.imaginary ??
150
155
  throwError(
151
- `IMDCT.transform: mdctBuffers[${this.fftSize}].imag is required`
156
+ `IMDCT.transform: mdctBuffers[${this.fftSize}].imaginary is required`
152
157
  )
153
158
 
154
159
  const n4 = this.quarterSize
@@ -169,10 +174,10 @@ export class IMDCT extends MDCTBase {
169
174
  imag[i] = im * c - r * s
170
175
  }
171
176
 
172
- FFT.fft(real, imag)
177
+ FFT.transform(real, imag)
173
178
 
174
179
  // Post-FFT butterfly
175
- const output = new Float32Array(this.size)
180
+ const output = destination ?? new Float32Array(this.size)
176
181
 
177
182
  for (let i = 0; i < this.fftSize / 2; i++) {
178
183
  const i2 = i * 2
@@ -184,10 +189,10 @@ export class IMDCT extends MDCTBase {
184
189
  const r1 = re * c + im * s
185
190
  const i1 = re * s - im * c
186
191
 
187
- output[n34 - 1 - i2] = r1
188
- output[n34 + i2] = r1
189
- output[n4 + i2] = i1
190
- output[n4 - 1 - i2] = -i1
192
+ output[destinationOffset + n34 - 1 - i2] = r1
193
+ output[destinationOffset + n34 + i2] = r1
194
+ output[destinationOffset + n4 + i2] = i1
195
+ output[destinationOffset + n4 - 1 - i2] = -i1
191
196
  }
192
197
 
193
198
  for (let i = this.fftSize / 2; i < this.fftSize; i++) {
@@ -201,60 +206,12 @@ export class IMDCT extends MDCTBase {
201
206
  const r1 = re * c + im * s
202
207
  const i1 = re * s - im * c
203
208
 
204
- output[n34 - 1 - idx] = r1
205
- output[idx - n4] = -r1
206
- output[n4 + idx] = i1
207
- output[5 * n4 - 1 - idx] = i1
209
+ output[destinationOffset + n34 - 1 - idx] = r1
210
+ output[destinationOffset + idx - n4] = -r1
211
+ output[destinationOffset + n4 + idx] = i1
212
+ output[destinationOffset + 5 * n4 - 1 - idx] = i1
208
213
  }
209
214
 
210
215
  return output
211
216
  }
212
217
  }
213
-
214
- // Pre-instantiated transforms
215
- export const mdct64 = new MDCT(MDCT_SIZE_SHORT, 0.5)
216
- export const mdct256 = new MDCT(MDCT_SIZE_MID, 0.5)
217
- export const mdct512 = new MDCT(MDCT_SIZE_LONG, 1.0)
218
-
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
-
223
- /**
224
- * Perform overlap-add operation for MDCT reconstruction
225
- * @param {Float32Array} prev - Previous block samples
226
- * @param {Float32Array} curr - Current block samples
227
- * @param {Float32Array} window - Window function coefficients
228
- * @returns {Float32Array} Overlap-added output samples
229
- */
230
- export function overlapAdd(prev, curr, window) {
231
- const size = prev.length
232
- const output = new Float32Array(size * 2)
233
-
234
- for (let i = 0; i < size; i++) {
235
- const w1 = window[i]
236
- const w2 = window[2 * size - 1 - i]
237
- const p = prev[i]
238
- const c = curr[size - 1 - i]
239
-
240
- output[i] = p * w2 - c * w1
241
- output[2 * size - 1 - i] = p * w1 + c * w2
242
- }
243
-
244
- return output
245
- }
246
-
247
- /**
248
- * Reverse spectral order for ATRAC1 mid and high bands.
249
- *
250
- * @param {Float32Array} spectrum
251
- * @param {Object} reversalBuffers
252
- * @returns {Float32Array}
253
- */
254
- export function reverseSpectrum(spectrum, reversalBuffers) {
255
- const reversed = reversalBuffers[spectrum.length]
256
- for (let i = 0; i < spectrum.length; i++) {
257
- reversed[i] = spectrum[spectrum.length - 1 - i]
258
- }
259
- return reversed
260
- }
@@ -0,0 +1,16 @@
1
+ /** ATRAC1 spectral ordering. */
2
+
3
+ /**
4
+ * Reverse spectral order for ATRAC1 mid and high bands.
5
+ *
6
+ * @param {Float32Array} spectrum Spectrum in transform order.
7
+ * @param {object} reversalBuffers Reusable outputs indexed by spectrum length.
8
+ * @returns {Float32Array} Reversed spectrum.
9
+ */
10
+ export function reverseSpectrum(spectrum, reversalBuffers) {
11
+ const reversed = reversalBuffers[spectrum.length]
12
+ for (let index = 0; index < spectrum.length; index++) {
13
+ reversed[index] = spectrum[spectrum.length - 1 - index]
14
+ }
15
+ return reversed
16
+ }