carta1 1.1.10 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +8 -4
- package/codec/{coding/bitallocation.js → allocation/solve.js} +131 -97
- package/codec/allocation/source.js +69 -0
- package/codec/allocation/stage.js +38 -0
- package/codec/analysis/bands.js +49 -0
- package/codec/analysis/{transient.js → blocks.js} +90 -47
- package/codec/analysis/spectrum.js +160 -0
- package/codec/analysis/stage.js +32 -0
- package/codec/{browser/interface.js → boundary/browser.js} +1 -1
- package/codec/boundary/container.js +96 -0
- package/codec/boundary/pcm.js +193 -0
- package/codec/boundary/processor.js +208 -0
- package/codec/{io/readers.js → boundary/reader.js} +2 -1
- package/codec/boundary/stream.js +361 -0
- package/codec/{browser → boundary}/worker.js +45 -42
- package/codec/core/constants.js +9 -131
- package/codec/core/geometry.js +25 -0
- package/codec/core/tables.js +117 -0
- package/codec/dequantization.js +54 -0
- package/codec/index.js +10 -11
- package/codec/pipeline/decoder.js +10 -399
- package/codec/pipeline/encoder.js +12 -434
- package/codec/quantization/spectrum.js +67 -0
- package/codec/quantization/stage.js +45 -0
- package/codec/{transforms/qmf.js → signal/bands.js} +8 -5
- package/codec/{transforms → signal}/fft.js +1 -1
- package/codec/{transforms/mdct.js → signal/spectrum.js} +16 -1
- package/codec/{core/buffers.js → state.js} +46 -35
- package/codec/{io → syntax}/bitstream.js +27 -8
- package/codec/{io/serialization.js → syntax/frame.js} +5 -101
- package/codec/synthesis/bands.js +201 -0
- package/codec/synthesis/pcm.js +50 -0
- package/codec/synthesis/stage.js +17 -0
- package/codec/utils.js +9 -52
- package/dist/carta1-worker-interface.min.js.map +1 -1
- 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
- package/codec/coding/quantization.js +0 -149
- package/codec/io/processor.js +0 -671
|
@@ -1,12 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Carta1
|
|
2
|
+
* Carta1 stream state and reusable scratch.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { QMF_DELAY, QMF_HIGH_BAND_DELAY } from './constants.js'
|
|
5
|
+
import { QMF_DELAY, QMF_HIGH_BAND_DELAY } from './core/constants.js'
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Values with meaning across chronological frames.
|
|
9
|
+
*/
|
|
10
|
+
class History {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.qmfDelays = {
|
|
13
|
+
lowBand: new Float32Array(QMF_DELAY),
|
|
14
|
+
midBand: new Float32Array(QMF_DELAY),
|
|
15
|
+
highBand: new Float32Array(QMF_HIGH_BAND_DELAY),
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
this.transientDetection = [
|
|
19
|
+
new Float32Array(64),
|
|
20
|
+
new Float32Array(64),
|
|
21
|
+
new Float32Array(128),
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
this.mdctOverlap = [
|
|
25
|
+
new Float32Array(32),
|
|
26
|
+
new Float32Array(32),
|
|
27
|
+
new Float32Array(32),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
this.imdctOverlap = [
|
|
31
|
+
new Float32Array(256),
|
|
32
|
+
new Float32Array(256),
|
|
33
|
+
new Float32Array(512),
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Reusable storage with no meaning between operations.
|
|
40
|
+
*/
|
|
41
|
+
class Scratch {
|
|
8
42
|
constructor() {
|
|
9
|
-
// Transform buffers
|
|
10
43
|
this.transformBuffers = {
|
|
11
44
|
64: new Float32Array(64),
|
|
12
45
|
128: new Float32Array(128),
|
|
@@ -14,7 +47,6 @@ export class BufferPool {
|
|
|
14
47
|
512: new Float32Array(512),
|
|
15
48
|
}
|
|
16
49
|
|
|
17
|
-
// QMF work buffers
|
|
18
50
|
this.qmfWorkBuffers = {
|
|
19
51
|
delay: {
|
|
20
52
|
128: new Float32Array(QMF_DELAY + 128),
|
|
@@ -27,21 +59,6 @@ export class BufferPool {
|
|
|
27
59
|
},
|
|
28
60
|
}
|
|
29
61
|
|
|
30
|
-
// QMF delay lines
|
|
31
|
-
this.qmfDelays = {
|
|
32
|
-
lowBand: new Float32Array(QMF_DELAY),
|
|
33
|
-
midBand: new Float32Array(QMF_DELAY),
|
|
34
|
-
highBand: new Float32Array(QMF_HIGH_BAND_DELAY),
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Transient detector
|
|
38
|
-
this.transientDetection = [
|
|
39
|
-
new Float32Array(64),
|
|
40
|
-
new Float32Array(64),
|
|
41
|
-
new Float32Array(128),
|
|
42
|
-
]
|
|
43
|
-
|
|
44
|
-
// MDCT/IMDCT work buffers
|
|
45
62
|
this.mdctBuffers = {
|
|
46
63
|
16: {
|
|
47
64
|
real: new Float32Array(16),
|
|
@@ -57,21 +74,6 @@ export class BufferPool {
|
|
|
57
74
|
},
|
|
58
75
|
}
|
|
59
76
|
|
|
60
|
-
// MDCT overlap buffers (encoder)
|
|
61
|
-
this.mdctOverlap = [
|
|
62
|
-
new Float32Array(32),
|
|
63
|
-
new Float32Array(32),
|
|
64
|
-
new Float32Array(32),
|
|
65
|
-
]
|
|
66
|
-
|
|
67
|
-
// IMDCT overlap buffers (decoder)
|
|
68
|
-
this.imdctOverlap = [
|
|
69
|
-
new Float32Array(256),
|
|
70
|
-
new Float32Array(256),
|
|
71
|
-
new Float32Array(512),
|
|
72
|
-
]
|
|
73
|
-
|
|
74
|
-
// Spectrum reversal buffers
|
|
75
77
|
this.reversalBuffers = {
|
|
76
78
|
32: new Float32Array(32),
|
|
77
79
|
128: new Float32Array(128),
|
|
@@ -79,3 +81,12 @@ export class BufferPool {
|
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
83
|
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Own one chronological stream's history and reusable storage.
|
|
87
|
+
*/
|
|
88
|
+
export class BufferPool {
|
|
89
|
+
constructor() {
|
|
90
|
+
Object.assign(this, new History(), new Scratch())
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -1,24 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Carta1
|
|
2
|
+
* Carta1 bitstream syntax operators.
|
|
3
3
|
*
|
|
4
4
|
* This module provides low-level bitstream packing and unpacking operations
|
|
5
5
|
* for reading and writing ATRAC1 frame data with bit-level precision.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Reject field widths outside the shared bitstream primitive's safe range.
|
|
10
|
+
*
|
|
11
|
+
* @param {number} bitCount
|
|
12
|
+
* @returns {void}
|
|
13
|
+
*/
|
|
14
|
+
function assertBitCount(bitCount) {
|
|
15
|
+
if (!Number.isInteger(bitCount) || bitCount < 0 || bitCount > 32) {
|
|
16
|
+
throw new RangeError(
|
|
17
|
+
`Bit count must be an integer in 0..32, got ${bitCount}`
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
8
22
|
/**
|
|
9
23
|
* Pack bits into a buffer at a specific bit position
|
|
10
24
|
* @param {Uint8Array} buffer - Destination buffer
|
|
11
25
|
* @param {number} bitPosition - Bit position to start writing at
|
|
12
26
|
* @param {number} value - Value to pack
|
|
13
27
|
* @param {number} bitCount - Number of bits to pack
|
|
28
|
+
* @returns {void}
|
|
14
29
|
*/
|
|
15
30
|
export function packBits(buffer, bitPosition, value, bitCount) {
|
|
31
|
+
assertBitCount(bitCount)
|
|
16
32
|
if (bitCount === 0) return
|
|
17
33
|
|
|
18
34
|
let byteIndex = Math.floor(bitPosition / 8)
|
|
19
35
|
let bitOffset = bitPosition % 8
|
|
20
36
|
|
|
21
|
-
|
|
37
|
+
const unsignedValue = Number(value) >>> 0
|
|
22
38
|
|
|
23
39
|
let bitsWritten = 0
|
|
24
40
|
while (bitsWritten < bitCount && byteIndex < buffer.length) {
|
|
@@ -26,9 +42,9 @@ export function packBits(buffer, bitPosition, value, bitCount) {
|
|
|
26
42
|
const bitsToWrite = Math.min(bitCount - bitsWritten, bitsAvailable)
|
|
27
43
|
|
|
28
44
|
const shift = bitCount - bitsWritten - bitsToWrite
|
|
29
|
-
const valueBits = (
|
|
45
|
+
const valueBits = (unsignedValue >>> shift) & (2 ** bitsToWrite - 1)
|
|
30
46
|
|
|
31
|
-
const mask = (
|
|
47
|
+
const mask = (2 ** bitsToWrite - 1) << (bitsAvailable - bitsToWrite)
|
|
32
48
|
buffer[byteIndex] =
|
|
33
49
|
(buffer[byteIndex] & ~mask) | (valueBits << (bitsAvailable - bitsToWrite))
|
|
34
50
|
|
|
@@ -46,6 +62,7 @@ export function packBits(buffer, bitPosition, value, bitCount) {
|
|
|
46
62
|
* @returns {number} Unpacked unsigned value
|
|
47
63
|
*/
|
|
48
64
|
export function unpackBits(buffer, bitPosition, bitCount) {
|
|
65
|
+
assertBitCount(bitCount)
|
|
49
66
|
if (bitCount === 0) return 0
|
|
50
67
|
|
|
51
68
|
let byteIndex = Math.floor(bitPosition / 8)
|
|
@@ -56,10 +73,10 @@ export function unpackBits(buffer, bitPosition, bitCount) {
|
|
|
56
73
|
const bitsAvailable = 8 - bitOffset
|
|
57
74
|
const bitsToRead = Math.min(bitCount - bitsRead, bitsAvailable)
|
|
58
75
|
|
|
59
|
-
const mask =
|
|
76
|
+
const mask = 2 ** bitsToRead - 1
|
|
60
77
|
const bits = (buffer[byteIndex] >> (bitsAvailable - bitsToRead)) & mask
|
|
61
78
|
|
|
62
|
-
value = (value
|
|
79
|
+
value = (value * 2 ** bitsToRead + bits) >>> 0
|
|
63
80
|
bitsRead += bitsToRead
|
|
64
81
|
byteIndex++
|
|
65
82
|
bitOffset = 0
|
|
@@ -76,7 +93,9 @@ export function unpackBits(buffer, bitPosition, bitCount) {
|
|
|
76
93
|
* @returns {number} Unpacked signed value (two's complement)
|
|
77
94
|
*/
|
|
78
95
|
export function unpackSignedBits(buffer, bitPosition, bitCount) {
|
|
96
|
+
assertBitCount(bitCount)
|
|
97
|
+
if (bitCount === 0) return 0
|
|
79
98
|
const value = unpackBits(buffer, bitPosition, bitCount)
|
|
80
|
-
const signBit =
|
|
81
|
-
return value >= signBit ? value -
|
|
99
|
+
const signBit = 2 ** (bitCount - 1)
|
|
100
|
+
return value >= signBit ? value - 2 ** bitCount : value
|
|
82
101
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Carta1
|
|
2
|
+
* Carta1 structured-frame syntax.
|
|
3
3
|
*
|
|
4
|
-
* Handles serialization and deserialization of ATRAC1 audio frames
|
|
4
|
+
* Handles serialization and deserialization of ATRAC1 audio frames.
|
|
5
5
|
* Provides functions for packing encoded frame data into binary format and unpacking
|
|
6
6
|
* it back into structured data for processing.
|
|
7
7
|
*
|
|
@@ -13,29 +13,16 @@
|
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
15
|
SOUND_UNIT_SIZE,
|
|
16
|
-
BFU_AMOUNTS,
|
|
17
|
-
SPECS_PER_BFU,
|
|
18
|
-
AEA_HEADER_SIZE,
|
|
19
|
-
AEA_MAGIC,
|
|
20
|
-
AEA_TITLE_OFFSET,
|
|
21
|
-
AEA_TITLE_SIZE,
|
|
22
|
-
AEA_FRAME_COUNT_OFFSET,
|
|
23
|
-
AEA_CHANNEL_COUNT_OFFSET,
|
|
24
|
-
WORD_LENGTH_BITS,
|
|
25
16
|
FRAME_HEADER_BITS,
|
|
26
17
|
FRAME_WORD_LENGTH_BITS,
|
|
27
18
|
FRAME_SCALE_FACTOR_BITS,
|
|
28
19
|
} from '../core/constants.js'
|
|
20
|
+
import { BFU_AMOUNTS, SPECS_PER_BFU, WORD_LENGTH_BITS } from '../core/tables.js'
|
|
29
21
|
import { packBits, unpackBits, unpackSignedBits } from './bitstream.js'
|
|
30
22
|
|
|
31
23
|
/**
|
|
32
24
|
* Serializes encoded frame data into binary format
|
|
33
|
-
* @param {
|
|
34
|
-
* @param {number} frameData.nBfu - Number of block floating units
|
|
35
|
-
* @param {number[]} frameData.blockModes - Block modes for each band [0-2]
|
|
36
|
-
* @param {Int32Array} frameData.scaleFactorIndices - Scale factor indices for each BFU
|
|
37
|
-
* @param {Int32Array} frameData.wordLengthIndices - Word length indices for each BFU
|
|
38
|
-
* @param {Int32Array[]} frameData.quantizedCoefficients - Quantized spectral coefficients
|
|
25
|
+
* @param {object} frameData - The encoded frame data to serialize
|
|
39
26
|
* @returns {Uint8Array} Serialized frame data buffer
|
|
40
27
|
*/
|
|
41
28
|
export function serializeFrame(frameData) {
|
|
@@ -100,12 +87,7 @@ export function serializeFrame(frameData) {
|
|
|
100
87
|
/**
|
|
101
88
|
* Deserializes binary frame data back into structured format
|
|
102
89
|
* @param {Uint8Array} buffer - Binary frame data buffer
|
|
103
|
-
* @returns {
|
|
104
|
-
* @returns {number} returns.nBfu - Number of block floating units
|
|
105
|
-
* @returns {number[]} returns.blockModes - Block modes for each band
|
|
106
|
-
* @returns {Int32Array} returns.scaleFactorIndices - Scale factor indices for each BFU
|
|
107
|
-
* @returns {Int32Array} returns.wordLengthIndices - Word length indices for each BFU
|
|
108
|
-
* @returns {Int32Array[]} returns.quantizedCoefficients - Quantized spectral coefficients
|
|
90
|
+
* @returns {object} Deserialized frame data
|
|
109
91
|
* @throws {Error} If buffer size is invalid
|
|
110
92
|
*/
|
|
111
93
|
export function deserializeFrame(buffer) {
|
|
@@ -174,81 +156,3 @@ export function deserializeFrame(buffer) {
|
|
|
174
156
|
blockModes,
|
|
175
157
|
}
|
|
176
158
|
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* AEA file format handler for ATRAC1 audio files
|
|
180
|
-
* Manages creation and parsing of AEA file headers
|
|
181
|
-
*/
|
|
182
|
-
export class AeaFile {
|
|
183
|
-
/**
|
|
184
|
-
* Creates an AEA file header with the specified metadata
|
|
185
|
-
* @param {string} [title=''] - Audio file title (max 255 characters)
|
|
186
|
-
* @param {number} [frameCount=0] - Number of audio frames in the file
|
|
187
|
-
* @param {number} [channelCount=1] - Number of audio channels (1 or 2)
|
|
188
|
-
* @returns {Uint8Array} AEA file header buffer
|
|
189
|
-
*/
|
|
190
|
-
static createHeader(title = '', frameCount = 0, channelCount = 1) {
|
|
191
|
-
const header = new Uint8Array(AEA_HEADER_SIZE)
|
|
192
|
-
const view = new DataView(header.buffer)
|
|
193
|
-
|
|
194
|
-
// Magic number
|
|
195
|
-
header.set(AEA_MAGIC, 0)
|
|
196
|
-
|
|
197
|
-
// Title (null-terminated string)
|
|
198
|
-
const titleBytes = new TextEncoder().encode(title)
|
|
199
|
-
header.set(
|
|
200
|
-
titleBytes.subarray(0, Math.min(titleBytes.length, AEA_TITLE_SIZE - 1)),
|
|
201
|
-
AEA_TITLE_OFFSET
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
// Frame count
|
|
205
|
-
view.setUint32(AEA_FRAME_COUNT_OFFSET, frameCount, true)
|
|
206
|
-
|
|
207
|
-
// Channel count
|
|
208
|
-
header[AEA_CHANNEL_COUNT_OFFSET] = channelCount
|
|
209
|
-
|
|
210
|
-
return header
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Parses an AEA file header to extract metadata
|
|
215
|
-
* @param {Uint8Array} header - AEA file header buffer
|
|
216
|
-
* @returns {Object} Parsed header information
|
|
217
|
-
* @returns {string} returns.title - Audio file title
|
|
218
|
-
* @returns {number} returns.frameCount - Number of audio frames
|
|
219
|
-
* @returns {number} returns.channelCount - Number of audio channels
|
|
220
|
-
* @throws {Error} If header size is invalid or magic number doesn't match
|
|
221
|
-
*/
|
|
222
|
-
static parseHeader(header) {
|
|
223
|
-
if (header.length !== AEA_HEADER_SIZE) {
|
|
224
|
-
throw new Error(`Header must be ${AEA_HEADER_SIZE} bytes`)
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Check magic number
|
|
228
|
-
for (let i = 0; i < AEA_MAGIC.length; i++) {
|
|
229
|
-
if (header[i] !== AEA_MAGIC[i]) {
|
|
230
|
-
throw new Error('Invalid AEA file')
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const view = new DataView(
|
|
235
|
-
header.buffer,
|
|
236
|
-
header.byteOffset,
|
|
237
|
-
header.byteLength
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
// Extract title
|
|
241
|
-
const titleEnd = header.indexOf(0, AEA_TITLE_OFFSET)
|
|
242
|
-
const titleLength =
|
|
243
|
-
titleEnd === -1 ? AEA_TITLE_SIZE : titleEnd - AEA_TITLE_OFFSET
|
|
244
|
-
const title = new TextDecoder().decode(
|
|
245
|
-
header.subarray(AEA_TITLE_OFFSET, AEA_TITLE_OFFSET + titleLength)
|
|
246
|
-
)
|
|
247
|
-
|
|
248
|
-
// Extract metadata
|
|
249
|
-
const frameCount = view.getUint32(AEA_FRAME_COUNT_OFFSET, true)
|
|
250
|
-
const channelCount = header[AEA_CHANNEL_COUNT_OFFSET]
|
|
251
|
-
|
|
252
|
-
return { title, frameCount, channelCount }
|
|
253
|
-
}
|
|
254
|
-
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Carta1 spectral synthesis application.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
MDCT_SHORT_BLOCK_SIZE,
|
|
7
|
+
MDCT_SIZE_LONG,
|
|
8
|
+
MDCT_TAIL_WINDOW_SIZE,
|
|
9
|
+
} from '../core/constants.js'
|
|
10
|
+
import { extractBandCoefficients } from '../core/geometry.js'
|
|
11
|
+
import { MDCT_BAND_CONFIGS, WINDOW_SHORT } from '../core/tables.js'
|
|
12
|
+
import {
|
|
13
|
+
imdct64,
|
|
14
|
+
imdct256,
|
|
15
|
+
imdct512,
|
|
16
|
+
overlapAdd,
|
|
17
|
+
reverseSpectrum,
|
|
18
|
+
} from '../signal/spectrum.js'
|
|
19
|
+
import { throwError } from '../utils.js'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Synthesize time-domain bands from the ATRAC1 spectrum.
|
|
23
|
+
*
|
|
24
|
+
* @param {object} context
|
|
25
|
+
* @returns {Function}
|
|
26
|
+
* @throws {Error} If the buffer pool is missing
|
|
27
|
+
*/
|
|
28
|
+
export function synthesizeSpectrum(context) {
|
|
29
|
+
const bufferPool =
|
|
30
|
+
context?.bufferPool ??
|
|
31
|
+
throwError('synthesizeSpectrum: bufferPool is required')
|
|
32
|
+
const overlapBuffers = bufferPool.imdctOverlap
|
|
33
|
+
const transformFunctions = [imdct256, imdct256, imdct512]
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {Float32Array} coefficients
|
|
37
|
+
* @param {number} bandIndex
|
|
38
|
+
* @param {number} blockMode
|
|
39
|
+
* @param {Float32Array} overlapBuffer
|
|
40
|
+
* @param {Object} config
|
|
41
|
+
* @returns {Float32Array}
|
|
42
|
+
*/
|
|
43
|
+
function transformBand(
|
|
44
|
+
coefficients,
|
|
45
|
+
bandIndex,
|
|
46
|
+
blockMode,
|
|
47
|
+
overlapBuffer,
|
|
48
|
+
config
|
|
49
|
+
) {
|
|
50
|
+
if (blockMode === 0) {
|
|
51
|
+
return inverseLongBlock(
|
|
52
|
+
coefficients,
|
|
53
|
+
bandIndex,
|
|
54
|
+
overlapBuffer,
|
|
55
|
+
config,
|
|
56
|
+
transformFunctions[bandIndex]
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return inverseShortBlocks(coefficients, bandIndex, overlapBuffer, config)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {Float32Array} coefficients
|
|
65
|
+
* @param {number} bandIndex
|
|
66
|
+
* @param {Float32Array} overlapBuffer
|
|
67
|
+
* @param {Object} config
|
|
68
|
+
* @param {Object} transformFunction
|
|
69
|
+
* @returns {Float32Array}
|
|
70
|
+
*/
|
|
71
|
+
function inverseLongBlock(
|
|
72
|
+
coefficients,
|
|
73
|
+
bandIndex,
|
|
74
|
+
overlapBuffer,
|
|
75
|
+
config,
|
|
76
|
+
transformFunction
|
|
77
|
+
) {
|
|
78
|
+
let blockSpectrum = coefficients
|
|
79
|
+
if (bandIndex > 0) {
|
|
80
|
+
blockSpectrum = reverseSpectrum(coefficients, bufferPool.reversalBuffers)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const inverse = transformFunction.transform(
|
|
84
|
+
blockSpectrum,
|
|
85
|
+
bufferPool.mdctBuffers
|
|
86
|
+
)
|
|
87
|
+
const inverseStart = inverse.length / 4
|
|
88
|
+
const inverseBuffer = bufferPool.transformBuffers[MDCT_SIZE_LONG]
|
|
89
|
+
inverseBuffer.fill(0)
|
|
90
|
+
|
|
91
|
+
for (let i = 0; i < config.size; i++) {
|
|
92
|
+
inverseBuffer[i] = inverse[inverseStart + i]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const previous = overlapBuffer.slice(
|
|
96
|
+
config.size * 2 - MDCT_TAIL_WINDOW_SIZE,
|
|
97
|
+
config.size * 2
|
|
98
|
+
)
|
|
99
|
+
const overlap = overlapAdd(
|
|
100
|
+
previous.slice(0, MDCT_TAIL_WINDOW_SIZE),
|
|
101
|
+
inverseBuffer.slice(0, MDCT_TAIL_WINDOW_SIZE),
|
|
102
|
+
WINDOW_SHORT
|
|
103
|
+
)
|
|
104
|
+
overlapBuffer.set(overlap, 0)
|
|
105
|
+
|
|
106
|
+
for (let i = 0; i < MDCT_TAIL_WINDOW_SIZE; i++) {
|
|
107
|
+
previous[i] = inverseBuffer[MDCT_TAIL_WINDOW_SIZE + i]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const copyLength = bandIndex === 2 ? 240 : 112
|
|
111
|
+
for (let i = 0; i < copyLength; i++) {
|
|
112
|
+
overlapBuffer[32 + i] = inverseBuffer[MDCT_TAIL_WINDOW_SIZE + i]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (let i = 0; i < MDCT_TAIL_WINDOW_SIZE; i++) {
|
|
116
|
+
overlapBuffer[config.size * 2 - MDCT_TAIL_WINDOW_SIZE + i] =
|
|
117
|
+
inverseBuffer[config.size - MDCT_TAIL_WINDOW_SIZE + i]
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return overlapBuffer.slice(0, config.size)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @param {Float32Array} coefficients
|
|
125
|
+
* @param {number} bandIndex
|
|
126
|
+
* @param {Float32Array} overlapBuffer
|
|
127
|
+
* @param {Object} config
|
|
128
|
+
* @returns {Float32Array}
|
|
129
|
+
*/
|
|
130
|
+
function inverseShortBlocks(coefficients, bandIndex, overlapBuffer, config) {
|
|
131
|
+
const numBlocks = 1 << (config.size === 256 ? 3 : 2)
|
|
132
|
+
const inverseBuffer = bufferPool.transformBuffers[MDCT_SIZE_LONG]
|
|
133
|
+
inverseBuffer.fill(0)
|
|
134
|
+
|
|
135
|
+
const previous = overlapBuffer.slice(
|
|
136
|
+
config.size * 2 - MDCT_TAIL_WINDOW_SIZE,
|
|
137
|
+
config.size * 2
|
|
138
|
+
)
|
|
139
|
+
let start = 0
|
|
140
|
+
let position = 0
|
|
141
|
+
|
|
142
|
+
for (let block = 0; block < numBlocks; block++) {
|
|
143
|
+
let blockSpectrum = coefficients.slice(
|
|
144
|
+
position,
|
|
145
|
+
position + MDCT_SHORT_BLOCK_SIZE
|
|
146
|
+
)
|
|
147
|
+
if (bandIndex > 0) {
|
|
148
|
+
blockSpectrum = reverseSpectrum(
|
|
149
|
+
blockSpectrum,
|
|
150
|
+
bufferPool.reversalBuffers
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const inverse = imdct64.transform(blockSpectrum, bufferPool.mdctBuffers)
|
|
155
|
+
const inverseStart = inverse.length / 4
|
|
156
|
+
for (let i = 0; i < MDCT_SHORT_BLOCK_SIZE; i++) {
|
|
157
|
+
inverseBuffer[start + i] = inverse[inverseStart + i]
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const overlap = overlapAdd(
|
|
161
|
+
previous.slice(0, MDCT_TAIL_WINDOW_SIZE),
|
|
162
|
+
inverseBuffer.slice(start, start + MDCT_TAIL_WINDOW_SIZE),
|
|
163
|
+
WINDOW_SHORT
|
|
164
|
+
)
|
|
165
|
+
overlapBuffer.set(overlap, start)
|
|
166
|
+
|
|
167
|
+
for (let i = 0; i < MDCT_TAIL_WINDOW_SIZE; i++) {
|
|
168
|
+
previous[i] = inverseBuffer[start + MDCT_TAIL_WINDOW_SIZE + i]
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
start += MDCT_SHORT_BLOCK_SIZE
|
|
172
|
+
position += MDCT_SHORT_BLOCK_SIZE
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (let i = 0; i < MDCT_TAIL_WINDOW_SIZE; i++) {
|
|
176
|
+
overlapBuffer[config.size * 2 - MDCT_TAIL_WINDOW_SIZE + i] =
|
|
177
|
+
inverseBuffer[config.size - MDCT_TAIL_WINDOW_SIZE + i]
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return overlapBuffer.slice(0, config.size)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* @param {object} input
|
|
185
|
+
* @returns {Array<Float32Array>}
|
|
186
|
+
*/
|
|
187
|
+
return (input) => {
|
|
188
|
+
const { coefficients, blockModes } = input
|
|
189
|
+
|
|
190
|
+
return MDCT_BAND_CONFIGS.map((config, bandIndex) => {
|
|
191
|
+
const bandCoefficients = extractBandCoefficients(coefficients, bandIndex)
|
|
192
|
+
return transformBand(
|
|
193
|
+
bandCoefficients,
|
|
194
|
+
bandIndex,
|
|
195
|
+
blockModes[bandIndex],
|
|
196
|
+
overlapBuffers[bandIndex],
|
|
197
|
+
config
|
|
198
|
+
)
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Carta1 PCM synthesis application.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { synthesizeQmfPair } from '../signal/bands.js'
|
|
6
|
+
import { throwError } from '../utils.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Synthesize full-spectrum PCM from frequency bands.
|
|
10
|
+
*
|
|
11
|
+
* @param {object} context
|
|
12
|
+
* @returns {Function}
|
|
13
|
+
* @throws {Error} If the buffer pool is missing
|
|
14
|
+
*/
|
|
15
|
+
export function synthesizePcm(context) {
|
|
16
|
+
const bufferPool =
|
|
17
|
+
context?.bufferPool ?? throwError('synthesizePcm: bufferPool is required')
|
|
18
|
+
const delays = bufferPool.qmfDelays
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {Array<Float32Array>} bands
|
|
22
|
+
* @returns {Float32Array}
|
|
23
|
+
*/
|
|
24
|
+
return (bands) => {
|
|
25
|
+
const delayedHigh = bufferPool.qmfWorkBuffers.highBandDelay[bands[2].length]
|
|
26
|
+
delayedHigh.set(delays.highBand)
|
|
27
|
+
delayedHigh.set(bands[2], delays.highBand.length)
|
|
28
|
+
|
|
29
|
+
const highBand = delayedHigh.slice(0, bands[0].length * 2)
|
|
30
|
+
delays.highBand = delayedHigh.slice(bands[0].length * 2)
|
|
31
|
+
|
|
32
|
+
const stage2 = synthesizeQmfPair(
|
|
33
|
+
bands[0],
|
|
34
|
+
bands[1],
|
|
35
|
+
delays.midBand,
|
|
36
|
+
bufferPool.qmfWorkBuffers
|
|
37
|
+
)
|
|
38
|
+
delays.midBand = stage2.newDelay
|
|
39
|
+
|
|
40
|
+
const stage1 = synthesizeQmfPair(
|
|
41
|
+
stage2.output,
|
|
42
|
+
highBand,
|
|
43
|
+
delays.lowBand,
|
|
44
|
+
bufferPool.qmfWorkBuffers
|
|
45
|
+
)
|
|
46
|
+
delays.lowBand = stage1.newDelay
|
|
47
|
+
|
|
48
|
+
return stage1.output
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Carta1 synthesis stage.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { pipe } from '../utils.js'
|
|
6
|
+
import { synthesizeSpectrum } from './bands.js'
|
|
7
|
+
import { synthesizePcm } from './pcm.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Synthesize PCM from the dequantized ATRAC1 spectrum.
|
|
11
|
+
*
|
|
12
|
+
* @param {object} context
|
|
13
|
+
* @returns {Function}
|
|
14
|
+
*/
|
|
15
|
+
export function synthesize(context) {
|
|
16
|
+
return pipe(context, synthesizeSpectrum, synthesizePcm)
|
|
17
|
+
}
|
package/codec/utils.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Carta1 Audio Codec - Utilities
|
|
3
3
|
*
|
|
4
|
-
* This module provides common utility functions for the ATRAC1 codec
|
|
5
|
-
* pipeline composition, error handling, and audio stream processing utilities
|
|
6
|
-
* for delay compensation and frame padding.
|
|
4
|
+
* This module provides common utility functions for the ATRAC1 codec.
|
|
7
5
|
*/
|
|
8
6
|
|
|
9
7
|
/**
|
|
@@ -16,55 +14,14 @@ export function throwError(msg) {
|
|
|
16
14
|
}
|
|
17
15
|
|
|
18
16
|
/**
|
|
19
|
-
*
|
|
20
|
-
* @param {Object} context - Shared context passed to all stages
|
|
21
|
-
* @param {...Function} stages - Stage functions to compose
|
|
22
|
-
* @returns {Function} Composed pipeline function
|
|
23
|
-
*/
|
|
24
|
-
export function pipe(context, ...stages) {
|
|
25
|
-
const functions = stages.map((stage) => stage(context))
|
|
26
|
-
|
|
27
|
-
return (input) => {
|
|
28
|
-
return functions.reduce((value, fn) => fn(value), input)
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Reverses the spectral order of coefficients for mid/high band processing
|
|
34
|
-
*
|
|
35
|
-
* Used in both MDCT and IMDCT stages to handle spectral reversal required
|
|
36
|
-
* by the ATRAC1 format for mid and high frequency bands.
|
|
37
|
-
*
|
|
38
|
-
* @param {Float32Array} spectrum - Input spectrum coefficients
|
|
39
|
-
* @param {Object} reversalBuffers - Pre-allocated buffers for different spectrum sizes
|
|
40
|
-
* @returns {Float32Array} Spectrum with reversed coefficient order
|
|
41
|
-
*/
|
|
42
|
-
export function reverseSpectrum(spectrum, reversalBuffers) {
|
|
43
|
-
const reversed = reversalBuffers[spectrum.length]
|
|
44
|
-
for (let i = 0; i < spectrum.length; i++) {
|
|
45
|
-
reversed[i] = spectrum[spectrum.length - 1 - i]
|
|
46
|
-
}
|
|
47
|
-
return reversed
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Calculates the starting offset for a frequency band in the coefficient array
|
|
17
|
+
* Compose stateful stage factories once and return the reusable frame path.
|
|
52
18
|
*
|
|
53
|
-
* @param {
|
|
54
|
-
* @
|
|
19
|
+
* @param {*} context Shared stage ownership and persistent state.
|
|
20
|
+
* @param {...Function} stages Ordered stage factories.
|
|
21
|
+
* @returns {Function} Reusable composed frame operation.
|
|
55
22
|
*/
|
|
56
|
-
export function
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Extracts coefficients for a specific frequency band from the full coefficient array
|
|
62
|
-
*
|
|
63
|
-
* @param {Float32Array} coefficients - Full 512-sample coefficient array
|
|
64
|
-
* @param {number} bandIndex - Band index (0=low, 1=mid, 2=high)
|
|
65
|
-
* @returns {Float32Array} Subarray containing coefficients for the specified band
|
|
66
|
-
*/
|
|
67
|
-
export function extractBandCoefficients(coefficients, bandIndex) {
|
|
68
|
-
const offsets = [0, 128, 256, 512]
|
|
69
|
-
return coefficients.subarray(offsets[bandIndex], offsets[bandIndex + 1])
|
|
23
|
+
export function pipe(context, ...stages) {
|
|
24
|
+
const operations = stages.map((stage) => stage(context))
|
|
25
|
+
return (input) =>
|
|
26
|
+
operations.reduce((value, operation) => operation(value), input)
|
|
70
27
|
}
|