carta1 1.1.8 → 1.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,128 +1,202 @@
1
1
  # Carta1
2
2
 
3
- An ATRAC1 audio codec implementation in JS.
3
+ Carta1 is an ATRAC1 encoder and decoder in JavaScript. It converts PCM WAVE
4
+ audio to Sony's AEA container, decodes AEA back to PCM WAVE, exposes stateful
5
+ frame and stream APIs, and ships browser Web Worker bundles.
4
6
 
5
- ## Features
7
+ ## Supported format
6
8
 
7
- - Pure JavaScript implementation of ATRAC1 codec
8
- - Encoding WAV to AEA (ATRAC1) format
9
- - Decoding AEA to WAV format
10
- - Command-line interface
11
- - Browser-compatible with Web Workers
12
- - Streaming support for large files
9
+ | Property | Support |
10
+ | ------------------ | --------------------------------------------- |
11
+ | Sample rate | 44.1 kHz |
12
+ | Channels | Mono or stereo |
13
+ | Frame size | 512 PCM samples per channel |
14
+ | Encoded sound unit | 212 bytes per channel |
15
+ | Nominal bitrate | About 146 kbps per channel / 292 kbps stereo |
16
+ | Container | AEA |
17
+ | JavaScript PCM | Planar `Float32Array`, normalized `-1` to `1` |
18
+
19
+ Stereo channels are encoded independently and stored as interleaved AEA sound
20
+ units.
13
21
 
14
22
  ## Installation
15
23
 
24
+ Carta1 is an ES module and requires Node.js 20.16 or newer.
25
+
16
26
  ```bash
17
27
  npm install carta1
18
28
  ```
19
29
 
20
- ## CLI Usage
21
-
22
- ### Encoding
30
+ To work from a repository checkout:
23
31
 
24
32
  ```bash
25
- carta1 --encode input.wav output.aea
33
+ npm ci
34
+ npm run check
26
35
  ```
27
36
 
28
- ### Decoding
37
+ ## CLI
38
+
39
+ Run the installed executable directly, or use `npx carta1` from a project that
40
+ depends on Carta1:
29
41
 
30
42
  ```bash
43
+ carta1 --encode input.wav output.aea
31
44
  carta1 --decode input.aea output.wav
32
- ```
33
-
34
- ### Options
35
-
36
- - `-e, --encode` - Encode WAV to AEA
37
- - `-d, --decode` - Decode AEA to WAV
38
- - `-j, --json` - Dump AEA file structure to JSON
39
- - `-q, --quiet` - Suppress all output except errors
40
- - `-f, --force` - Overwrite output file if it exists
41
- - `-t, --title <title>` - Custom title for AEA file metadata (encoding only)
45
+ carta1 --json input.aea structure.json
42
46
 
43
- ## Browser Usage
44
-
45
- ```html
46
- <script src="https://unpkg.com/carta1/dist/carta1-worker.min.js"></script>
47
- <script src="https://unpkg.com/carta1/dist/carta1-worker-interface.min.js"></script>
47
+ npx carta1 --encode input.wav output.aea
48
48
  ```
49
49
 
50
- ```javascript
51
- // Create codec instance
52
- const codec = new Carta1Worker('carta1-worker.min.js')
50
+ The encoder accepts mono or stereo PCM WAVE input. Carta1 is designed for
51
+ 44.1 kHz audio; it warns when the input sample rate differs. Decoding writes
52
+ signed 16-bit PCM WAVE output.
53
+
54
+ | Option | Meaning |
55
+ | --------------------- | ---------------------------------------------------- |
56
+ | `-e, --encode` | Encode PCM WAVE to AEA. |
57
+ | `-d, --decode` | Decode AEA to PCM WAVE. |
58
+ | `-j, --json` | Write AEA metadata and frame structure as JSON. |
59
+ | `-q, --quiet` | Suppress normal output and progress. |
60
+ | `-f, --force` | Overwrite an existing output file. |
61
+ | `-t, --title <title>` | Set the AEA title while encoding. |
62
+ | `-b, --bias <value>` | Set the bit-allocation bias; defaults to `1.0`. |
63
+ | `-m, --modes <modes>` | Fix low, mid, and high block modes, such as `0,0,0`. |
64
+ | `-V, --version` | Print the Carta1 version. |
65
+ | `-h, --help` | Print command help. |
66
+
67
+ Exactly one of `--encode`, `--decode`, and `--json` is required. The input and
68
+ output are positional paths. Existing output is preserved unless `--force` is
69
+ provided.
70
+
71
+ ## JavaScript API
72
+
73
+ ### Complete AEA files
74
+
75
+ Use `encodeAeaPcm()` and `decodeAeaPcm()` when the complete input fits in
76
+ memory:
77
+
78
+ ```js
79
+ import { decodeAeaPcm, encodeAeaPcm } from 'carta1'
80
+
81
+ const aea = await encodeAeaPcm([left, right], {
82
+ allocationBias: 1.0,
83
+ title: 'Example',
84
+ })
85
+ const [decodedLeft, decodedRight] = await decodeAeaPcm(aea)
86
+ ```
53
87
 
54
- // Encode PCM to ATRAC1
55
- const result = await codec.encode(pcmData, options)
88
+ `encodeAeaPcm()` returns a `Uint8Array` containing a complete AEA file.
89
+ `decodeAeaPcm()` accepts AEA bytes or a `Blob` and returns normalized planar
90
+ `Float32Array` channels. A mono input uses `[mono]`. The final partial input
91
+ frame is zero-padded.
56
92
 
57
- // Decode ATRAC1 to PCM
58
- const wavData = await codec.decode(aeaData)
93
+ ### Stateful frames
59
94
 
60
- // Clean up
61
- codec.terminate()
62
- ```
95
+ `encode()` and `decode()` create closures for chronological, complete mono
96
+ frames. Reuse each closure for one stream:
63
97
 
64
- ## API Reference
98
+ ```js
99
+ import { decode, encode, EncoderOptions } from 'carta1'
65
100
 
66
- ### Node.js
101
+ const encodeFrame = encode(new EncoderOptions({ transientThresholdLow: 1.0 }))
102
+ const decodeFrame = decode()
67
103
 
68
- ```javascript
69
- import { encode, decode, AudioProcessor } from 'carta1'
104
+ const encoded = encodeFrame(new Float32Array(512))
105
+ const decoded = decodeFrame(encoded)
106
+ ```
70
107
 
71
- // Encode PCM frames
72
- const encoder = encode(options)
73
- const encodedFrame = encoder(pcmFrame)
108
+ Use separate closures for separate streams and for the left and right channels
109
+ of stereo audio. `AudioProcessor.encodeStream()` and `decodeStream()` handle
110
+ that ownership automatically.
74
111
 
75
- // Decode ATRAC1 frames
76
- const decoder = decode()
77
- const pcmFrame = decoder(encodedFrame)
78
- ```
112
+ ### Encoder options
79
113
 
80
- ### Browser Worker API
114
+ `EncoderOptions` validates these settings:
81
115
 
82
- #### `new Carta1Worker(workerPath)`
116
+ - `transientThresholdLow`, `transientThresholdMid`, and
117
+ `transientThresholdHigh` tune short-block detection per frequency band.
118
+ - `allocationBias` changes how strongly allocation favors louder spectral
119
+ components.
120
+ - `fixedBlockModes` accepts `[low, mid, high]`, where low and mid are `0` or
121
+ `2`, and high is `0` or `3`; this bypasses transient detection.
83
122
 
84
- Create a new codec worker instance.
123
+ ## Browser worker
85
124
 
86
- #### `encode(pcmData, options)`
125
+ The production build writes a worker and an ES module client to `dist/`. Serve
126
+ both from a location allowed by your application's worker and Content Security
127
+ Policy settings:
87
128
 
88
- Encode PCM audio data to ATRAC1 format.
129
+ ```js
130
+ import { Carta1Worker } from '/vendor/carta1-worker-interface.min.js'
89
131
 
90
- #### `decode(aeaData)`
132
+ const codec = new Carta1Worker('/vendor/carta1-worker.min.js')
91
133
 
92
- Decode ATRAC1 data to PCM format.
134
+ const { aeaBlob } = await codec.encode([left, right], {
135
+ allocationBias: 1.0,
136
+ })
137
+ const parsed = await codec.parseAeaBlob(aeaBlob)
138
+ const { wavBlob, info } = await codec.decode({
139
+ aeaData: parsed.frameData,
140
+ info: parsed.info,
141
+ })
142
+ const optionMetadata = await codec.getEncoderOptions()
93
143
 
94
- #### `parseAeaBlob(blob)`
144
+ codec.terminate()
145
+ ```
95
146
 
96
- Parse AEA file blob to extract metadata.
147
+ Always call `terminate()` when the worker is no longer needed. The build also
148
+ produces `dist/carta1.min.js`, a UMD bundle exposing the main JavaScript API as
149
+ the global `Carta1` object.
97
150
 
98
- #### `terminate()`
151
+ ## Compatibility and limitations
99
152
 
100
- Clean up worker resources.
153
+ - Encoding is fixed to the 44.1 kHz ATRAC1 profile described above; Carta1 does
154
+ not resample input.
155
+ - JavaScript APIs use normalized planar PCM. Web Audio channel data already
156
+ follows this convention.
157
+ - Stateful frame APIs must receive frames in order and must not be shared by
158
+ independent streams.
159
+ - AEA stores whole frames rather than an exact source sample count, so a final
160
+ partial frame is represented with zero padding.
161
+ - Browser worker assets must be served from a location permitted by the page's
162
+ worker and Content Security Policy rules.
101
163
 
102
164
  ## Development
103
165
 
104
- ```bash
105
- # Install dependencies
106
- npm install
107
-
108
- # Run tests
109
- npm test
166
+ Implementation concerns are split without hiding the top-level pipeline:
110
167
 
111
- # Build minified versions
112
- npm run build
168
+ | Path | Responsibility |
169
+ | ------------------- | --------------------------------------------------- |
170
+ | `codec/pipeline/` | Ordered encoder and decoder stage composition. |
171
+ | `codec/analysis/` | Transient analysis and encoder decisions. |
172
+ | `codec/coding/` | Bit allocation and quantization. |
173
+ | `codec/transforms/` | FFT, QMF, MDCT, and inverse transforms. |
174
+ | `codec/io/` | Bitstreams, AEA serialization, and stream adapters. |
175
+ | `codec/core/` | Constants, options, and reusable buffer ownership. |
176
+ | `codec/browser/` | Web Worker implementation and client. |
177
+ | `bin/` | Command-line boundary. |
178
+ | `tests/` | Unit, serialization, and pipeline tests. |
113
179
 
114
- # Lint code
115
- npm run lint
180
+ Common commands are:
116
181
 
117
- # Format code
118
- npm run format
182
+ ```bash
183
+ npm test # Run the test suite once
184
+ npm run test:watch # Re-run tests while editing
185
+ npm run lint # Check JavaScript and formatting
186
+ npm run format # Apply repository formatting
187
+ npm run build # Build the three browser bundles
188
+ npm run check # Run lint, tests, and the production build
119
189
  ```
120
190
 
121
- ## Acknowledgements
191
+ Run `npm run check` before submitting a change. Pull requests and pushes run
192
+ the same gate in CI.
122
193
 
123
- This project would not have been possible without:
194
+ ## Acknowledgements
124
195
 
125
- - [**AtracDEnc**](https://github.com/dcherednik/atracdenc) by [Daniel Cherednik](https://github.com/dcherednik) - The reference ATRAC implementation that provided invaluable insights into the codec's inner workings and served as the primary source for understanding the ATRAC1 format specification.
196
+ This project would not have been possible without
197
+ [AtracDEnc](https://github.com/dcherednik/atracdenc) by
198
+ [Daniel Cherednik](https://github.com/dcherednik), the reference ATRAC
199
+ implementation used to understand the ATRAC1 format and codec behavior.
126
200
 
127
201
  ## License
128
202
 
@@ -31,8 +31,35 @@ import {
31
31
  INV_POWER_OF_TWO,
32
32
  SCALE_FACTORS,
33
33
  BFU_AMOUNTS,
34
+ WORD_LENGTH_DELTA_BITS,
35
+ DISTORTION_DELTA_FACTORS,
34
36
  } from '../core/constants.js'
35
37
 
38
+ const biasedScaleFactorCache = new Map()
39
+
40
+ /**
41
+ * Builds and returns a cached table of biased scale factors.
42
+ *
43
+ * @param {number} bias
44
+ * @returns {Float64Array}
45
+ */
46
+ function buildBiasedScaleFactorTable(bias) {
47
+ if (biasedScaleFactorCache.has(bias)) {
48
+ return biasedScaleFactorCache.get(bias)
49
+ }
50
+
51
+ const out = new Float64Array(64)
52
+ if (bias === 1) {
53
+ out.set(SCALE_FACTORS)
54
+ } else {
55
+ for (let i = 0; i < 64; i++) {
56
+ out[i] = Math.pow(SCALE_FACTORS[i], bias)
57
+ }
58
+ }
59
+ biasedScaleFactorCache.set(bias, out)
60
+ return out
61
+ }
62
+
36
63
  /**
37
64
  * Allocate bits by finding the optimal BFU count and distribution using RDO.
38
65
  * This function searches through the valid BFU counts to find the one that
@@ -40,23 +67,23 @@ import {
40
67
  *
41
68
  * @param {Array<Float32Array>} bfuData
42
69
  * @param {Int32Array} bfuSizes
43
- * @param {number} maxBfuCount The maximum BFU index to consider (e.g., from BFU_AMOUNTS)
70
+ * @param {number} maxBfuCount
44
71
  * @param {number} allocationBias
45
72
  * @returns {{bfuCount:number, allocation:Int32Array, scaleFactorIndices:Int32Array}}
46
73
  */
47
74
  export function allocateBits(bfuData, bfuSizes, maxBfuCount, allocationBias) {
48
75
  const allScaleFactorIndices = new Int32Array(maxBfuCount)
49
76
  const zeroBitDistortions = new Float32Array(maxBfuCount)
77
+ const biasedScaleFactors = buildBiasedScaleFactorTable(allocationBias)
50
78
 
51
79
  for (let i = 0; i < maxBfuCount; i++) {
52
80
  const sz = bfuSizes[i] | 0
53
81
  if (sz === 0) continue
54
82
 
55
- const sfi = findScaleFactor(bfuData[i].subarray(0, sz))
83
+ const sfi = findScaleFactor(bfuData[i], sz)
56
84
  allScaleFactorIndices[i] = sfi
57
85
  if (sfi > 0) {
58
- const scaleFactor = SCALE_FACTORS[sfi]
59
- const effectiveScaleFactor = Math.pow(scaleFactor, allocationBias)
86
+ const effectiveScaleFactor = biasedScaleFactors[sfi]
60
87
  zeroBitDistortions[i] = effectiveScaleFactor * 2.0 * sz
61
88
  }
62
89
  }
@@ -78,7 +105,7 @@ export function allocateBits(bfuData, bfuSizes, maxBfuCount, allocationBias) {
78
105
  candidateBfuCount,
79
106
  bfuSizes,
80
107
  availableBits,
81
- allocationBias,
108
+ biasedScaleFactors,
82
109
  allScaleFactorIndices
83
110
  )
84
111
 
@@ -88,7 +115,7 @@ export function allocateBits(bfuData, bfuSizes, maxBfuCount, allocationBias) {
88
115
  bfuSizes,
89
116
  rdoResult.wordLengths,
90
117
  rdoResult.scaleFactorIndices,
91
- allocationBias,
118
+ biasedScaleFactors,
92
119
  zeroBitDistortions
93
120
  )
94
121
 
@@ -118,14 +145,14 @@ export function allocateBits(bfuData, bfuSizes, maxBfuCount, allocationBias) {
118
145
  * Calculates the total distortion for a given allocation.
119
146
  * This includes quantization distortion for coded bands and truncation
120
147
  * distortion for uncoded bands.
121
- * @param {number} activeBfuCount - The number of BFUs that were coded.
122
- * @param {number} maxBfuCount - The total number of BFUs considered.
123
- * @param {Int32Array} bfuSizes - Sizes of each BFU.
124
- * @param {Int32Array} wordLengths - The allocated word length for each active BFU.
125
- * @param {Int32Array} scaleFactorIndices - The scale factor index for each BFU.
126
- * @param {number} allocationBias - The RDO bias factor.
127
- * @param {Float32Array} zeroBitDistortions - Pre-calculated distortion for uncoded bands.
128
- * @returns {number} The total calculated distortion.
148
+ * @param {number} activeBfuCount
149
+ * @param {number} maxBfuCount
150
+ * @param {Int32Array} bfuSizes
151
+ * @param {Int32Array} wordLengths
152
+ * @param {Int32Array} scaleFactorIndices
153
+ * @param {number} biasedScaleFactors
154
+ * @param {Float32Array} zeroBitDistortions
155
+ * @returns {number}
129
156
  */
130
157
  function calculateTotalDistortion(
131
158
  activeBfuCount,
@@ -133,7 +160,7 @@ function calculateTotalDistortion(
133
160
  bfuSizes,
134
161
  wordLengths,
135
162
  scaleFactorIndices,
136
- allocationBias,
163
+ biasedScaleFactors,
137
164
  zeroBitDistortions
138
165
  ) {
139
166
  let totalDistortion = 0.0
@@ -149,8 +176,7 @@ function calculateTotalDistortion(
149
176
  const sfi = scaleFactorIndices[i]
150
177
  if (sfi === 0) continue
151
178
 
152
- const scaleFactor = SCALE_FACTORS[sfi]
153
- const effectiveScaleFactor = Math.pow(scaleFactor, allocationBias)
179
+ const effectiveScaleFactor = biasedScaleFactors[sfi]
154
180
  const distortionFactor = INV_POWER_OF_TWO[bits]
155
181
 
156
182
  totalDistortion += effectiveScaleFactor * distortionFactor * bfuSizes[i]
@@ -170,105 +196,100 @@ function calculateTotalDistortion(
170
196
  * @param {number} activeBfuCount
171
197
  * @param {Int32Array} bfuSizes
172
198
  * @param {number} remainingBits
173
- * @param {number} allocationBias
174
- * @param {Int32Array} allScaleFactorIndices - Pre-calculated SFIs for all potential BFUs.
199
+ * @param {Float64Array} biasedScaleFactors
200
+ * @param {Int32Array} allScaleFactorIndices
175
201
  * @returns {{wordLengths: Int32Array, scaleFactorIndices: Int32Array}}
176
202
  */
177
203
  function distributeBitsRDO(
178
204
  activeBfuCount,
179
205
  bfuSizes,
180
206
  remainingBits,
181
- allocationBias,
207
+ biasedScaleFactors,
182
208
  allScaleFactorIndices
183
209
  ) {
184
- const scaleFactorTable = allScaleFactorIndices
185
210
  const wordLengths = new Int32Array(activeBfuCount)
186
211
 
187
- const heapIndices = []
188
- const heapPriorities = []
189
-
190
- const deltaDistPerCoeff = (bfuIndex, currentWl, nextWl) => {
191
- const sfi = scaleFactorTable[bfuIndex]
192
- const scaleFactor = SCALE_FACTORS[sfi]
193
- if (scaleFactor === 0) return 0
194
-
195
- const effectiveScaleFactor = Math.pow(scaleFactor, allocationBias)
196
-
197
- const bits1 = WORD_LENGTH_BITS[currentWl] | 0
198
- const bits2 = WORD_LENGTH_BITS[nextWl] | 0
199
-
200
- const f1 = bits1 === 0 ? 2.0 : INV_POWER_OF_TWO[bits1]
201
- const f2 = INV_POWER_OF_TWO[bits2]
202
-
203
- return effectiveScaleFactor * (f1 - f2)
204
- }
212
+ const heapIndices = new Int32Array(activeBfuCount)
213
+ const heapPriorities = new Float32Array(activeBfuCount)
214
+ let heapSize = 0
205
215
 
206
216
  for (let bfuIndex = 0; bfuIndex < activeBfuCount; bfuIndex++) {
207
217
  const sz = bfuSizes[bfuIndex] | 0
208
218
  if (sz === 0) continue
209
219
 
210
- const sfi = scaleFactorTable[bfuIndex]
220
+ const sfi = allScaleFactorIndices[bfuIndex]
211
221
  if (sfi === 0) continue
212
222
 
213
- const currentWl = 0
214
- const nextWl = 1
215
- const deltaBits =
216
- (WORD_LENGTH_BITS[nextWl] - WORD_LENGTH_BITS[currentWl]) | 0
223
+ const deltaBits = WORD_LENGTH_DELTA_BITS[0]
217
224
  if (deltaBits <= 0) continue
218
225
 
219
- const ddPerCoeff = deltaDistPerCoeff(bfuIndex, currentWl, nextWl)
220
- heapIndices.push(bfuIndex)
221
- heapPriorities.push(ddPerCoeff / deltaBits)
226
+ const distortionDelta =
227
+ biasedScaleFactors[sfi] * DISTORTION_DELTA_FACTORS[0]
228
+
229
+ heapIndices[heapSize] = bfuIndex
230
+ heapPriorities[heapSize] = distortionDelta / deltaBits
231
+ heapSize++
232
+ }
233
+
234
+ if (heapSize === 0) {
235
+ return { wordLengths, scaleFactorIndices: allScaleFactorIndices }
222
236
  }
223
237
 
224
238
  // Heapify
225
- for (let i = (heapIndices.length >> 1) - 1; i >= 0; i--)
226
- siftDown(heapIndices, heapPriorities, i)
239
+ for (let i = (heapSize >> 1) - 1; i >= 0; i--) {
240
+ siftDown(heapIndices, heapPriorities, i, heapSize)
241
+ }
227
242
 
228
- // Greedy spending
229
- while (remainingBits > 0 && heapIndices.length > 0) {
243
+ // Greedy spending loop
244
+ while (remainingBits > 0 && heapSize > 0) {
230
245
  const bfu = heapIndices[0]
231
246
  const cur = wordLengths[bfu] | 0
232
- const nxt = cur + 1
233
-
234
247
  const sz = bfuSizes[bfu] | 0
235
- const deltaBits = (WORD_LENGTH_BITS[nxt] - WORD_LENGTH_BITS[cur]) | 0
248
+ const deltaBits = WORD_LENGTH_DELTA_BITS[cur]
236
249
  const cost = deltaBits * sz
237
250
 
238
251
  if (cost > remainingBits || cost <= 0) {
239
- popRoot(heapIndices, heapPriorities)
252
+ const last = heapSize - 1
253
+ heapIndices[0] = heapIndices[last]
254
+ heapPriorities[0] = heapPriorities[last]
255
+ heapSize--
256
+ if (heapSize > 0) siftDown(heapIndices, heapPriorities, 0, heapSize)
240
257
  continue
241
258
  }
242
259
 
243
260
  remainingBits -= cost
261
+ const nxt = cur + 1
244
262
  wordLengths[bfu] = nxt
245
263
 
246
- if (nxt < MAX_WORD_LENGTH_INDEX) {
247
- const nxt2 = nxt + 1
248
- const deltaBits2 = (WORD_LENGTH_BITS[nxt2] - WORD_LENGTH_BITS[nxt]) | 0
249
- if (deltaBits2 > 0) {
250
- heapPriorities[0] = deltaDistPerCoeff(bfu, nxt, nxt2) / deltaBits2
251
- siftDown(heapIndices, heapPriorities, 0)
252
- } else {
253
- popRoot(heapIndices, heapPriorities)
254
- }
264
+ const deltaBitsNext = WORD_LENGTH_DELTA_BITS[nxt]
265
+ if (nxt < MAX_WORD_LENGTH_INDEX && deltaBitsNext > 0) {
266
+ const sfi = allScaleFactorIndices[bfu]
267
+ const distortionDelta =
268
+ biasedScaleFactors[sfi] * DISTORTION_DELTA_FACTORS[nxt]
269
+ heapPriorities[0] = distortionDelta / deltaBitsNext
270
+ siftDown(heapIndices, heapPriorities, 0, heapSize)
255
271
  } else {
256
- popRoot(heapIndices, heapPriorities)
272
+ const last = heapSize - 1
273
+ heapIndices[0] = heapIndices[last]
274
+ heapPriorities[0] = heapPriorities[last]
275
+ heapSize--
276
+ if (heapSize > 0) siftDown(heapIndices, heapPriorities, 0, heapSize)
257
277
  }
258
278
  }
259
279
 
260
- return { wordLengths, scaleFactorIndices: scaleFactorTable }
280
+ return { wordLengths, scaleFactorIndices: allScaleFactorIndices }
261
281
  }
262
282
 
263
283
  /**
264
284
  * Find the optimal scale factor index for a set of coefficients.
265
285
  * Same semantics as the original.
266
286
  * @param {Float32Array} coefficients
287
+ * @param {number} length
267
288
  * @returns {number}
268
289
  */
269
- export function findScaleFactor(coefficients) {
290
+ export function findScaleFactor(coefficients, length) {
270
291
  let maxAmplitude = 0.0
271
- for (let i = 0; i < coefficients.length; i++) {
292
+ for (let i = 0; i < length; i++) {
272
293
  const a = Math.abs(coefficients[i])
273
294
  if (a > maxAmplitude) maxAmplitude = a
274
295
  }
@@ -277,25 +298,6 @@ export function findScaleFactor(coefficients) {
277
298
  return Math.max(0, Math.min(63, index))
278
299
  }
279
300
 
280
- /**
281
- * Removes the root element from a max-heap and maintains heap property.
282
- *
283
- * This function efficiently removes the maximum priority element from the heap
284
- * by replacing it with the last element and then restoring the heap property
285
- * through sifting down.
286
- *
287
- * @param {Array<number>} heapIndices
288
- * @param {Array<number>} heapPriorities
289
- */
290
- function popRoot(heapIndices, heapPriorities) {
291
- const last = heapIndices.length - 1
292
- heapIndices[0] = heapIndices[last]
293
- heapPriorities[0] = heapPriorities[last]
294
- heapIndices.pop()
295
- heapPriorities.pop()
296
- if (heapIndices.length) siftDown(heapIndices, heapPriorities, 0)
297
- }
298
-
299
301
  /**
300
302
  * Restores max-heap property by sifting an element down from a given position.
301
303
  *
@@ -304,12 +306,12 @@ function popRoot(heapIndices, heapPriorities) {
304
306
  * with its children and swaps with the larger child until the heap property
305
307
  * is satisfied, or it reaches a leaf position.
306
308
  *
307
- * @param {Array<number>} heapIndices
308
- * @param {Array<number>} heapPriorities
309
+ * @param {Int32Array} heapIndices
310
+ * @param {Float32Array} heapPriorities
309
311
  * @param {number} startIndex
312
+ * @param {number} heapSize
310
313
  */
311
- function siftDown(heapIndices, heapPriorities, startIndex) {
312
- const heapSize = heapIndices.length
314
+ function siftDown(heapIndices, heapPriorities, startIndex, heapSize) {
313
315
  let i = startIndex
314
316
  const idxVal = heapIndices[i]
315
317
  const prVal = heapPriorities[i]
@@ -158,3 +158,22 @@ export const INV_POWER_OF_TWO = (() => {
158
158
  }
159
159
  return table
160
160
  })()
161
+
162
+ export const WORD_LENGTH_DELTA_BITS = (() => {
163
+ const table = new Int32Array(MAX_WORD_LENGTH_INDEX)
164
+ for (let i = 0; i < MAX_WORD_LENGTH_INDEX; i++) {
165
+ table[i] = (WORD_LENGTH_BITS[i + 1] - WORD_LENGTH_BITS[i]) | 0
166
+ }
167
+ return table
168
+ })()
169
+
170
+ export const DISTORTION_DELTA_FACTORS = (() => {
171
+ const table = new Float64Array(MAX_WORD_LENGTH_INDEX)
172
+ table[0] = 2.0 - INV_POWER_OF_TWO[WORD_LENGTH_BITS[1]]
173
+ for (let i = 1; i < MAX_WORD_LENGTH_INDEX; i++) {
174
+ const bits1 = WORD_LENGTH_BITS[i] | 0
175
+ const bits2 = WORD_LENGTH_BITS[i + 1] | 0
176
+ table[i] = INV_POWER_OF_TWO[bits1] - INV_POWER_OF_TWO[bits2]
177
+ }
178
+ return table
179
+ })()
@@ -52,7 +52,7 @@ class EncoderOptions {
52
52
  name: 'Bit allocation bias',
53
53
  description:
54
54
  'Controls how bits are distributed across the frequency spectrum during allocation. Higher values focus more bits on loud spectral components, while lower values spread bits more evenly across frequencies.',
55
- range: [0.5, 3.0],
55
+ range: [0.0, 5.0],
56
56
  step: 0.01,
57
57
  },
58
58
  fixedBlockModes: {
package/codec/index.js CHANGED
@@ -14,7 +14,7 @@ import { qmfAnalysisStage, mdctStage } from './pipeline/encoder.js'
14
14
  import { pipe } from './utils.js'
15
15
  import { BufferPool } from './core/buffers.js'
16
16
  import { EncoderOptions } from './core/options.js'
17
- import { AudioProcessor } from './io/processor.js'
17
+ import { AudioProcessor, decodeAeaPcm, encodeAeaPcm } from './io/processor.js'
18
18
  import { FFT } from './transforms/fft.js'
19
19
  import {
20
20
  WORD_LENGTH_BITS,
@@ -37,6 +37,8 @@ export {
37
37
  BufferPool,
38
38
  EncoderOptions,
39
39
  AudioProcessor,
40
+ decodeAeaPcm,
41
+ encodeAeaPcm,
40
42
  FFT,
41
43
  WORD_LENGTH_BITS,
42
44
  SPECS_PER_BFU,
@@ -52,7 +52,7 @@ export function unpackBits(buffer, bitPosition, bitCount) {
52
52
  let bitOffset = bitPosition % 8
53
53
  let value = 0
54
54
 
55
- for (let bitsRead = 0; bitsRead < bitCount && byteIndex < buffer.length; ) {
55
+ for (let bitsRead = 0; bitsRead < bitCount && byteIndex < buffer.length;) {
56
56
  const bitsAvailable = 8 - bitOffset
57
57
  const bitsToRead = Math.min(bitCount - bitsRead, bitsAvailable)
58
58