carta1 1.1.9 → 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.
Files changed (44) hide show
  1. package/README.md +149 -75
  2. package/bin/cli.js +8 -4
  3. package/codec/{coding/bitallocation.js → allocation/solve.js} +131 -97
  4. package/codec/allocation/source.js +69 -0
  5. package/codec/allocation/stage.js +38 -0
  6. package/codec/analysis/bands.js +49 -0
  7. package/codec/analysis/{transient.js → blocks.js} +90 -47
  8. package/codec/analysis/spectrum.js +160 -0
  9. package/codec/analysis/stage.js +32 -0
  10. package/codec/{browser/interface.js → boundary/browser.js} +1 -1
  11. package/codec/boundary/container.js +96 -0
  12. package/codec/boundary/pcm.js +193 -0
  13. package/codec/boundary/processor.js +208 -0
  14. package/codec/{io/readers.js → boundary/reader.js} +2 -1
  15. package/codec/boundary/stream.js +361 -0
  16. package/codec/{browser → boundary}/worker.js +45 -42
  17. package/codec/core/constants.js +9 -131
  18. package/codec/core/geometry.js +25 -0
  19. package/codec/core/tables.js +117 -0
  20. package/codec/dequantization.js +54 -0
  21. package/codec/index.js +12 -11
  22. package/codec/pipeline/decoder.js +10 -399
  23. package/codec/pipeline/encoder.js +12 -434
  24. package/codec/quantization/spectrum.js +67 -0
  25. package/codec/quantization/stage.js +45 -0
  26. package/codec/{transforms/qmf.js → signal/bands.js} +8 -5
  27. package/codec/{transforms → signal}/fft.js +1 -1
  28. package/codec/{transforms/mdct.js → signal/spectrum.js} +16 -1
  29. package/codec/{core/buffers.js → state.js} +46 -35
  30. package/codec/{io → syntax}/bitstream.js +28 -9
  31. package/codec/{io/serialization.js → syntax/frame.js} +5 -101
  32. package/codec/synthesis/bands.js +201 -0
  33. package/codec/synthesis/pcm.js +50 -0
  34. package/codec/synthesis/stage.js +17 -0
  35. package/codec/utils.js +9 -52
  36. package/dist/carta1-worker-interface.min.js +1 -1
  37. package/dist/carta1-worker-interface.min.js.map +1 -1
  38. package/dist/carta1-worker.min.js +1 -1
  39. package/dist/carta1-worker.min.js.map +1 -1
  40. package/dist/carta1.min.js +1 -1
  41. package/dist/carta1.min.js.map +1 -1
  42. package/package.json +16 -16
  43. package/codec/coding/quantization.js +0 -149
  44. package/codec/io/processor.js +0 -558
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Carta1 PCM boundary conversions.
3
+ */
4
+
5
+ import {
6
+ SAMPLE_RATE,
7
+ SAMPLES_PER_FRAME,
8
+ WAV_BITS_PER_SAMPLE,
9
+ WAV_BYTES_PER_SAMPLE,
10
+ WAV_DATA_OFFSET,
11
+ WAV_HEADER_SIZE,
12
+ WAV_PCM_MAX_NEGATIVE,
13
+ WAV_PCM_MAX_POSITIVE,
14
+ } from '../core/constants.js'
15
+
16
+ /**
17
+ * Write a four-character code.
18
+ *
19
+ * @param {DataView} output
20
+ * @param {number} offset
21
+ * @param {string} value
22
+ */
23
+ function writeFourCc(output, offset, value) {
24
+ for (let i = 0; i < value.length; i++) {
25
+ output.setUint8(offset + i, value.charCodeAt(i))
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Join complete PCM frames into one channel.
31
+ *
32
+ * @param {Float32Array[]} frames
33
+ * @returns {Float32Array}
34
+ */
35
+ export function joinChannelFrames(frames) {
36
+ const sampleCount = frames.reduce((total, frame) => total + frame.length, 0)
37
+ const channel = new Float32Array(sampleCount)
38
+ let offset = 0
39
+
40
+ for (const frame of frames) {
41
+ channel.set(frame, offset)
42
+ offset += frame.length
43
+ }
44
+ return channel
45
+ }
46
+
47
+ /**
48
+ * Split planar PCM buffers into complete zero-padded codec frames.
49
+ *
50
+ * @param {Float32Array[]} buffers
51
+ * @param {number} [frameSize=SAMPLES_PER_FRAME]
52
+ * @returns {Generator<Float32Array>|Generator<[Float32Array, Float32Array]>}
53
+ * @throws {Error} If the channel count is unsupported
54
+ */
55
+ export function* frameBufferToFrames(buffers, frameSize = SAMPLES_PER_FRAME) {
56
+ if (
57
+ !Array.isArray(buffers) ||
58
+ (buffers.length !== 1 && buffers.length !== 2) ||
59
+ !buffers.every(
60
+ (buffer) =>
61
+ buffer instanceof Float32Array && buffer.length === buffers[0].length
62
+ ) ||
63
+ !Number.isInteger(frameSize) ||
64
+ frameSize < 1
65
+ ) {
66
+ throw new RangeError(
67
+ 'ATRAC1 framing requires one or two equally sized Float32 channels and a positive integer frame size'
68
+ )
69
+ }
70
+ if (buffers.length === 1) {
71
+ const [left] = buffers
72
+ for (let offset = 0; offset < left.length; offset += frameSize) {
73
+ const frame = new Float32Array(frameSize)
74
+ frame.set(left.subarray(offset, offset + frameSize))
75
+ yield frame
76
+ }
77
+ return
78
+ }
79
+
80
+ if (buffers.length === 2) {
81
+ const [left, right] = buffers
82
+ const sampleCount = left.length
83
+ for (let offset = 0; offset < sampleCount; offset += frameSize) {
84
+ const leftFrame = new Float32Array(frameSize)
85
+ const rightFrame = new Float32Array(frameSize)
86
+ leftFrame.set(left.subarray(offset, offset + frameSize))
87
+ rightFrame.set(right.subarray(offset, offset + frameSize))
88
+ yield [leftFrame, rightFrame]
89
+ }
90
+ return
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Convert one normalized sample to signed PCM16.
96
+ *
97
+ * @param {number} sample
98
+ * @returns {number}
99
+ */
100
+ export function floatToPcm16(sample) {
101
+ const clipped = Math.max(-1, Math.min(1, sample))
102
+ return clipped < 0
103
+ ? clipped * WAV_PCM_MAX_NEGATIVE
104
+ : clipped * WAV_PCM_MAX_POSITIVE
105
+ }
106
+
107
+ /**
108
+ * Create a PCM WAVE header.
109
+ *
110
+ * @param {Object} options
111
+ * @param {number} options.sampleCount
112
+ * @param {number} options.sampleRate
113
+ * @param {number} options.channels
114
+ * @returns {Uint8Array}
115
+ */
116
+ export function createPcmWaveHeader({ sampleCount, sampleRate, channels }) {
117
+ const byteRate = sampleRate * channels * WAV_BYTES_PER_SAMPLE
118
+ const blockAlign = channels * WAV_BYTES_PER_SAMPLE
119
+ const dataSize = sampleCount * blockAlign
120
+ const output = new Uint8Array(WAV_HEADER_SIZE)
121
+ const view = new DataView(output.buffer)
122
+
123
+ writeFourCc(view, 0, 'RIFF')
124
+ view.setUint32(4, WAV_DATA_OFFSET + dataSize, true)
125
+ writeFourCc(view, 8, 'WAVE')
126
+ writeFourCc(view, 12, 'fmt ')
127
+ view.setUint32(16, 16, true)
128
+ view.setUint16(20, 1, true)
129
+ view.setUint16(22, channels, true)
130
+ view.setUint32(24, sampleRate, true)
131
+ view.setUint32(28, byteRate, true)
132
+ view.setUint16(32, blockAlign, true)
133
+ view.setUint16(34, WAV_BITS_PER_SAMPLE, true)
134
+ writeFourCc(view, 36, 'data')
135
+ view.setUint32(40, dataSize, true)
136
+ return output
137
+ }
138
+
139
+ /**
140
+ * Interleave planar normalized channels as PCM16.
141
+ *
142
+ * @param {Float32Array[]} channels
143
+ * @returns {Uint8Array}
144
+ */
145
+ export function interleavePcm16(channels) {
146
+ if (
147
+ !Array.isArray(channels) ||
148
+ channels.length < 1 ||
149
+ !channels.every(
150
+ (channel) =>
151
+ channel instanceof Float32Array && channel.length === channels[0].length
152
+ )
153
+ ) {
154
+ throw new RangeError('PCM must contain equally sized Float32 channels')
155
+ }
156
+ const sampleCount = channels[0].length
157
+ const output = new Uint8Array(
158
+ sampleCount * channels.length * WAV_BYTES_PER_SAMPLE
159
+ )
160
+ const view = new DataView(output.buffer)
161
+ let offset = 0
162
+
163
+ for (let sample = 0; sample < sampleCount; sample++) {
164
+ for (const channel of channels) {
165
+ view.setInt16(offset, floatToPcm16(channel[sample]), true)
166
+ offset += WAV_BYTES_PER_SAMPLE
167
+ }
168
+ }
169
+ return output
170
+ }
171
+
172
+ /**
173
+ * Create a complete PCM WAVE byte image from planar channels.
174
+ *
175
+ * @param {Float32Array[]} channels
176
+ * @param {Object} [options]
177
+ * @param {number} [options.sampleRate=SAMPLE_RATE]
178
+ * @returns {Uint8Array}
179
+ */
180
+ export function createPcmWave(channels, options = {}) {
181
+ const { sampleRate = SAMPLE_RATE } = options
182
+ const pcm = interleavePcm16(channels)
183
+ const sampleCount = channels[0].length
184
+ const header = createPcmWaveHeader({
185
+ sampleCount,
186
+ sampleRate,
187
+ channels: channels.length,
188
+ })
189
+ const output = new Uint8Array(header.length + pcm.length)
190
+ output.set(header)
191
+ output.set(pcm, header.length)
192
+ return output
193
+ }
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Carta1 public processing facade.
3
+ */
4
+
5
+ import {
6
+ AEA_HEADER_SIZE,
7
+ SAMPLE_RATE,
8
+ SAMPLES_PER_FRAME,
9
+ SOUND_UNIT_SIZE,
10
+ STREAM_FLUSH_TAIL_FRAMES,
11
+ STREAM_PRIMING_SAMPLES,
12
+ } from '../core/constants.js'
13
+ import { deserializeFrame, serializeFrame } from '../syntax/frame.js'
14
+ import { AeaFile } from './container.js'
15
+ import { createPcmWave, frameBufferToFrames, joinChannelFrames } from './pcm.js'
16
+ import {
17
+ createAeaStreamingDecoder,
18
+ createAeaStreamingEncoder,
19
+ decodeAeaPcm,
20
+ encodeAeaPcm,
21
+ } from './stream.js'
22
+
23
+ /**
24
+ * High-level audio processing facade.
25
+ */
26
+ export class AudioProcessor {
27
+ /**
28
+ * @param {Float32Array[]} channels
29
+ * @param {object} [options]
30
+ * @returns {Promise<Uint8Array>}
31
+ */
32
+ static encodeAeaPcm(channels, options = {}) {
33
+ return encodeAeaPcm(channels, options)
34
+ }
35
+
36
+ /**
37
+ * @param {Uint8Array|ArrayBuffer|Blob} input
38
+ * @returns {Promise<Float32Array[]>}
39
+ */
40
+ static decodeAeaPcm(input) {
41
+ return decodeAeaPcm(input)
42
+ }
43
+
44
+ /**
45
+ * @param {AsyncIterable<object>} audioFrames
46
+ * @param {object} [options]
47
+ * @returns {AsyncGenerator<object>}
48
+ */
49
+ static async *encodeStream(audioFrames, options = {}) {
50
+ const encoder = createAeaStreamingEncoder(options)
51
+ yield* encoder.frames(audioFrames)
52
+ yield* encoder.finish()
53
+ }
54
+
55
+ /**
56
+ * @param {AsyncIterable<object>} encodedFrames
57
+ * @param {object} [options]
58
+ * @returns {AsyncGenerator<object>}
59
+ */
60
+ static async *decodeStream(encodedFrames, options = {}) {
61
+ const decoder = createAeaStreamingDecoder(options)
62
+ yield* decoder.frames(encodedFrames)
63
+ yield* decoder.finish()
64
+ }
65
+
66
+ /**
67
+ * @param {Float32Array[]} buffers
68
+ * @param {number} [frameSize]
69
+ * @returns {Generator<Float32Array>|Generator<[Float32Array, Float32Array]>}
70
+ */
71
+ static *frameBufferToFrames(buffers, frameSize) {
72
+ yield* frameBufferToFrames(buffers, frameSize)
73
+ }
74
+
75
+ /**
76
+ * @param {AsyncIterable|Iterable} frameStream
77
+ * @returns {Promise<Array>}
78
+ */
79
+ static async collectFrames(frameStream) {
80
+ const frames = []
81
+ for await (const frame of frameStream) {
82
+ frames.push(frame)
83
+ }
84
+ return frames
85
+ }
86
+
87
+ /**
88
+ * @param {AsyncIterable<object>} encodedFrames
89
+ * @param {object} [options]
90
+ * @returns {Promise<Blob>}
91
+ */
92
+ static async createAeaBlob(encodedFrames, options = {}) {
93
+ const { title = 'encoded by atrac1.js', channelCount = 1 } = options
94
+ const frames = []
95
+ for await (const frame of encodedFrames) {
96
+ frames.push(serializeFrame(frame))
97
+ }
98
+
99
+ const timelineFrames = frames.length / channelCount
100
+ const sampleCount =
101
+ options.sampleCount ??
102
+ Math.max(0, timelineFrames - STREAM_FLUSH_TAIL_FRAMES) * SAMPLES_PER_FRAME
103
+ const header = AeaFile.createHeader(
104
+ title,
105
+ frames.length,
106
+ channelCount,
107
+ sampleCount,
108
+ STREAM_PRIMING_SAMPLES
109
+ )
110
+ return new Blob([header, ...frames], { type: 'application/octet-stream' })
111
+ }
112
+
113
+ /**
114
+ * @param {Blob} blob
115
+ * @returns {Promise<object>} Parsed AEA data.
116
+ */
117
+ static async parseAeaBlob(blob) {
118
+ const buffer = await blob.arrayBuffer()
119
+ const header = new Uint8Array(buffer.slice(0, AEA_HEADER_SIZE))
120
+ const info = AeaFile.parseHeader(header)
121
+ const frameData = []
122
+
123
+ for (let offset = AEA_HEADER_SIZE; offset < buffer.byteLength;) {
124
+ const frame = new Uint8Array(
125
+ buffer.slice(offset, offset + SOUND_UNIT_SIZE)
126
+ )
127
+ if (frame.length === SOUND_UNIT_SIZE) {
128
+ frameData.push(frame)
129
+ }
130
+ offset += SOUND_UNIT_SIZE
131
+ }
132
+ return { info, frameData }
133
+ }
134
+
135
+ /**
136
+ * @param {Uint8Array[]} frameData
137
+ * @returns {Generator<object>}
138
+ */
139
+ static *deserializedFrameStream(frameData) {
140
+ for (const frame of frameData) {
141
+ yield deserializeFrame(frame)
142
+ }
143
+ }
144
+
145
+ /**
146
+ * @param {Float32Array[]|[Float32Array, Float32Array][]} pcmFrames
147
+ * @param {number} [channelCount=1]
148
+ * @param {number} [sampleRate=SAMPLE_RATE]
149
+ * @returns {Blob}
150
+ */
151
+ static createPcmWaveBlob(
152
+ pcmFrames,
153
+ channelCount = 1,
154
+ sampleRate = SAMPLE_RATE
155
+ ) {
156
+ let channels
157
+ if (channelCount === 1) {
158
+ channels = [joinChannelFrames(pcmFrames)]
159
+ } else if (channelCount === 2) {
160
+ channels = [
161
+ joinChannelFrames(pcmFrames.map(([left]) => left)),
162
+ joinChannelFrames(pcmFrames.map(([, right]) => right)),
163
+ ]
164
+ } else {
165
+ throw new Error(`Unsupported channel count: ${channelCount}`)
166
+ }
167
+ return new Blob([createPcmWave(channels, { sampleRate })], {
168
+ type: 'audio/wav',
169
+ })
170
+ }
171
+
172
+ /**
173
+ * Preserve the supported WAVE facade name.
174
+ *
175
+ * @param {Float32Array[]|[Float32Array, Float32Array][]} pcmFrames
176
+ * @param {number} [channelCount=1]
177
+ * @param {number} [sampleRate=SAMPLE_RATE]
178
+ * @returns {Blob}
179
+ */
180
+ static createWavBlob(pcmFrames, channelCount = 1, sampleRate = SAMPLE_RATE) {
181
+ return AudioProcessor.createPcmWaveBlob(pcmFrames, channelCount, sampleRate)
182
+ }
183
+
184
+ /**
185
+ * Assemble PCM frames using the supported interleaved result shape.
186
+ *
187
+ * @param {Float32Array[]|[Float32Array, Float32Array][]} pcmFrames
188
+ * @param {number} channelCount
189
+ * @returns {Float32Array}
190
+ */
191
+ static assemblePcmFrames(pcmFrames, channelCount) {
192
+ if (channelCount === 1) {
193
+ return joinChannelFrames(pcmFrames)
194
+ }
195
+ if (channelCount !== 2) {
196
+ throw new Error(`Unsupported channel count: ${channelCount}`)
197
+ }
198
+
199
+ const left = joinChannelFrames(pcmFrames.map(([channel]) => channel))
200
+ const right = joinChannelFrames(pcmFrames.map(([, channel]) => channel))
201
+ const pcm = new Float32Array(Math.max(left.length, right.length) * 2)
202
+ for (let i = 0; i < pcm.length / 2; i++) {
203
+ pcm[i * 2] = left[i] ?? 0
204
+ pcm[i * 2 + 1] = right[i] ?? 0
205
+ }
206
+ return pcm
207
+ }
208
+ }
@@ -6,7 +6,8 @@
6
6
  */
7
7
 
8
8
  import fs from 'fs'
9
- import { AeaFile, deserializeFrame } from './serialization.js'
9
+ import { AeaFile } from './container.js'
10
+ import { deserializeFrame } from '../syntax/frame.js'
10
11
  import { AEA_HEADER_SIZE, SOUND_UNIT_SIZE } from '../core/constants.js'
11
12
 
12
13
  /**