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
@@ -1,558 +0,0 @@
1
- /**
2
- * Carta1 Audio Codec - Audio Processing Module
3
- *
4
- * High-level audio processing interface that provides stream-based encoding and decoding
5
- * for ATRAC1 audio format. Handles both mono and stereo audio processing with proper
6
- * delay compensation and frame management.
7
- *
8
- * Key features:
9
- * - Stream-based processing for large audio files
10
- * - Automatic delay compensation for codec processing
11
- * - Support for mono and stereo audio
12
- * - Progress tracking callbacks
13
- * - AEA file format creation and parsing
14
- * - WAV file generation from decoded audio
15
- */
16
-
17
- import { encode } from '../pipeline/encoder.js'
18
- import { decode } from '../pipeline/decoder.js'
19
- import { EncoderOptions } from '../core/options.js'
20
- import {
21
- SAMPLES_PER_FRAME,
22
- SAMPLE_RATE,
23
- AEA_HEADER_SIZE,
24
- SOUND_UNIT_SIZE,
25
- WAV_HEADER_SIZE,
26
- WAV_BYTES_PER_SAMPLE,
27
- WAV_BITS_PER_SAMPLE,
28
- WAV_PCM_MAX_POSITIVE,
29
- WAV_PCM_MAX_NEGATIVE,
30
- WAV_DATA_OFFSET,
31
- } from '../core/constants.js'
32
- import { serializeFrame, deserializeFrame, AeaFile } from './serialization.js'
33
-
34
- /**
35
- * Audio processing utility class providing high-level encoding and decoding operations
36
- */
37
- export class AudioProcessor {
38
- /**
39
- * Encodes a stream of audio frames to ATRAC1 format
40
- * @param {AsyncIterable<Float32Array>|AsyncIterable<[Float32Array, Float32Array]>} audioFrames - Audio frame stream
41
- * @param {Object} [options={}] - Encoding options
42
- * @param {number} [options.channelCount=1] - Number of audio channels (1 or 2)
43
- * @param {Function} [options.onProgress] - Progress callback function
44
- * @param {EncoderOptions} [options.encoderOptions] - Encoder configuration options
45
- * @returns {AsyncGenerator<Object>} Stream of encoded frame data
46
- * @throws {Error} If unsupported channel count is provided
47
- */
48
- static async *encodeStream(audioFrames, options = {}) {
49
- const { channelCount = 1, onProgress, encoderOptions } = options
50
-
51
- if (channelCount === 1) {
52
- yield* AudioProcessor._encodeMonoStream(
53
- audioFrames,
54
- onProgress,
55
- encoderOptions
56
- )
57
- } else if (channelCount === 2) {
58
- yield* AudioProcessor._encodeStereoStream(
59
- audioFrames,
60
- onProgress,
61
- encoderOptions
62
- )
63
- } else {
64
- throw new Error(`Unsupported channel count: ${channelCount}`)
65
- }
66
- }
67
-
68
- /**
69
- * Internal method for encoding mono audio streams
70
- * @param {AsyncIterable<Float32Array>} audioFrames - Mono audio frame stream
71
- * @param {Function} onProgress - Progress callback function
72
- * @param {EncoderOptions} encoderOptions - Encoder configuration
73
- * @returns {AsyncGenerator<Object>} Stream of encoded frame data
74
- * @private
75
- */
76
- static async *_encodeMonoStream(audioFrames, onProgress, encoderOptions) {
77
- const options = encoderOptions || new EncoderOptions()
78
- const encoder = encode(options)
79
- let frameIndex = 0
80
- for await (const frame of audioFrames) {
81
- const result = encoder(frame)
82
- yield result
83
-
84
- if (onProgress) {
85
- onProgress(frameIndex++)
86
- }
87
- }
88
- }
89
-
90
- /**
91
- * Internal method for encoding stereo audio streams
92
- * @param {AsyncIterable<[Float32Array, Float32Array]>} audioFrames - Stereo audio frame stream
93
- * @param {Function} onProgress - Progress callback function
94
- * @param {EncoderOptions} encoderOptions - Encoder configuration
95
- * @returns {AsyncGenerator<Object>} Stream of encoded frame data (left then right)
96
- * @private
97
- */
98
- static async *_encodeStereoStream(audioFrames, onProgress, encoderOptions) {
99
- const options = encoderOptions || new EncoderOptions()
100
- const leftEncoder = encode(options)
101
- const rightEncoder = encode(options)
102
- let frameIndex = 0
103
-
104
- for await (const [leftFrame, rightFrame] of audioFrames) {
105
- const leftResult = leftEncoder(leftFrame)
106
- const rightResult = rightEncoder(rightFrame)
107
-
108
- yield leftResult
109
- yield rightResult
110
-
111
- if (onProgress) {
112
- onProgress(frameIndex++)
113
- }
114
- }
115
- }
116
-
117
- /**
118
- * Decodes a stream of ATRAC1 encoded frames back to PCM audio
119
- * @param {AsyncIterable<Object>} encodedFrames - Stream of encoded frame data
120
- * @param {Object} [options={}] - Decoding options
121
- * @param {number} [options.channelCount=1] - Number of audio channels (1 or 2)
122
- * @param {Function} [options.onProgress] - Progress callback function
123
- * @returns {AsyncGenerator<Float32Array>|AsyncGenerator<[Float32Array, Float32Array]>} Stream of decoded PCM frames
124
- * @throws {Error} If unsupported channel count is provided
125
- */
126
- static async *decodeStream(encodedFrames, options = {}) {
127
- const { channelCount = 1, onProgress } = options
128
-
129
- if (channelCount === 1) {
130
- yield* AudioProcessor._decodeMonoStream(encodedFrames, onProgress)
131
- } else if (channelCount === 2) {
132
- yield* AudioProcessor._decodeStereoStream(encodedFrames, onProgress)
133
- } else {
134
- throw new Error(`Unsupported channel count: ${channelCount}`)
135
- }
136
- }
137
-
138
- /**
139
- * Internal method for decoding mono audio streams
140
- * @param {AsyncIterable<Object>} encodedFrames - Mono encoded frame stream
141
- * @param {Function} onProgress - Progress callback function
142
- * @returns {AsyncGenerator<Float32Array>} Stream of decoded mono PCM frames
143
- * @private
144
- */
145
- static async *_decodeMonoStream(encodedFrames, onProgress) {
146
- const decoder = decode()
147
- let frameIndex = 0
148
-
149
- // Create raw PCM frame stream
150
- const rawPcmFrames = async function* () {
151
- for await (const frame of encodedFrames) {
152
- const result = decoder(frame)
153
- yield result
154
-
155
- if (onProgress) {
156
- onProgress(frameIndex++)
157
- }
158
- }
159
- }
160
-
161
- // Always apply delay compensation
162
- yield* rawPcmFrames()
163
- }
164
-
165
- /**
166
- * Internal method for decoding stereo audio streams
167
- * @param {AsyncIterable<Object>} encodedFrames - Stereo encoded frame stream (interleaved left/right)
168
- * @param {Function} onProgress - Progress callback function
169
- * @returns {AsyncGenerator<[Float32Array, Float32Array]>} Stream of decoded stereo PCM frame pairs
170
- * @private
171
- */
172
- static async *_decodeStereoStream(encodedFrames, onProgress) {
173
- const leftDecoder = decode()
174
- const rightDecoder = decode()
175
- let frameIndex = 0
176
-
177
- const rawPcmFrames = async function* () {
178
- let leftFrame = null
179
- let isLeftChannel = true
180
-
181
- for await (const frame of encodedFrames) {
182
- if (isLeftChannel) {
183
- leftFrame = frame
184
- } else {
185
- const rightFrame = frame
186
-
187
- // Decode and yield the pair
188
- const leftResult = leftDecoder(leftFrame)
189
- const rightResult = rightDecoder(rightFrame)
190
-
191
- yield [leftResult, rightResult]
192
-
193
- if (onProgress) {
194
- onProgress(frameIndex++)
195
- }
196
- }
197
-
198
- isLeftChannel = !isLeftChannel
199
- }
200
-
201
- if (!isLeftChannel && leftFrame) {
202
- const rightFrame = AudioProcessor._createDummyFrame()
203
- const leftResult = leftDecoder(leftFrame)
204
- const rightResult = rightDecoder(rightFrame)
205
-
206
- yield [leftResult, rightResult]
207
-
208
- if (onProgress) {
209
- onProgress(frameIndex++)
210
- }
211
- }
212
- }
213
-
214
- // Always apply stereo delay compensation
215
- yield* rawPcmFrames()
216
- }
217
-
218
- /**
219
- * Converts channel buffer arrays to frame-based stream format
220
- * @param {Float32Array[]} buffers - Array of channel buffers (1 for mono, 2 for stereo)
221
- * @param {number} [frameSize=SAMPLES_PER_FRAME] - Frame size in samples
222
- * @returns {Generator<Float32Array>|Generator<[Float32Array, Float32Array]>} Stream of audio frames
223
- * @throws {Error} If unsupported channel count is provided
224
- */
225
- static *frameBufferToFrames(buffers, frameSize = SAMPLES_PER_FRAME) {
226
- const channelCount = buffers.length
227
-
228
- if (channelCount === 1) {
229
- const [left] = buffers
230
- for (let i = 0; i < left.length; i += frameSize) {
231
- const frame = new Float32Array(frameSize)
232
- const end = Math.min(i + frameSize, left.length)
233
- for (let j = 0; j < end - i; j++) {
234
- frame[j] = left[i + j]
235
- }
236
- yield frame
237
- }
238
- } else if (channelCount === 2) {
239
- const [left, right] = buffers
240
- const maxLength = Math.max(left.length, right.length)
241
-
242
- for (let i = 0; i < maxLength; i += frameSize) {
243
- const leftFrame = new Float32Array(frameSize)
244
- const rightFrame = new Float32Array(frameSize)
245
-
246
- const end = Math.min(i + frameSize, maxLength)
247
-
248
- for (let j = 0; j < end - i; j++) {
249
- leftFrame[j] = i + j < left.length ? left[i + j] : 0
250
- rightFrame[j] = i + j < right.length ? right[i + j] : 0
251
- }
252
-
253
- yield [leftFrame, rightFrame]
254
- }
255
- } else {
256
- throw new Error(`Unsupported channel count: ${channelCount}`)
257
- }
258
- }
259
-
260
- /**
261
- * Collects all frames from a stream into an array
262
- * @param {AsyncIterable} frameStream - Stream of frames to collect
263
- * @returns {Promise<Array>} Array of all frames from the stream
264
- */
265
- static async collectFrames(frameStream) {
266
- const frames = []
267
- for await (const frame of frameStream) {
268
- frames.push(frame)
269
- }
270
- return frames
271
- }
272
-
273
- /**
274
- * Creates a dummy frame for padding purposes
275
- * @returns {Object} Empty frame structure
276
- * @private
277
- */
278
- static _createDummyFrame() {
279
- return {
280
- nBfu: 0,
281
- blockModes: [0, 0, 0],
282
- scaleFactorIndices: new Int32Array(0),
283
- wordLengthIndices: new Int32Array(0),
284
- quantizedCoefficients: [],
285
- }
286
- }
287
-
288
- /**
289
- * Creates an AEA file blob from encoded frames
290
- * @param {AsyncIterable<Object>} encodedFrames - Stream of encoded frame data
291
- * @param {Object} [options={}] - AEA file options
292
- * @param {string} [options.title='encoded by atrac1.js'] - File title
293
- * @param {number} [options.channelCount=1] - Number of audio channels
294
- * @returns {Promise<Blob>} AEA file blob
295
- */
296
- static async createAeaBlob(encodedFrames, options = {}) {
297
- const { title = 'encoded by atrac1.js', channelCount = 1 } = options
298
-
299
- const frames = []
300
- for await (const frame of encodedFrames) {
301
- frames.push(serializeFrame(frame))
302
- }
303
-
304
- const header = AeaFile.createHeader(title, frames.length, channelCount)
305
- const totalSize =
306
- header.length + frames.reduce((sum, frame) => sum + frame.length, 0)
307
-
308
- const buffer = new Uint8Array(totalSize)
309
- buffer.set(header, 0)
310
-
311
- let offset = header.length
312
- for (const frame of frames) {
313
- buffer.set(frame, offset)
314
- offset += frame.length
315
- }
316
-
317
- return new Blob([buffer], { type: 'application/octet-stream' })
318
- }
319
-
320
- /**
321
- * Creates a WAV file blob from PCM audio frames
322
- * @param {Float32Array[]|Float32Array|[Float32Array, Float32Array][]} pcmFrames - PCM audio frames
323
- * @param {number} [channelCount=1] - Number of audio channels
324
- * @param {number} [sampleRate=SAMPLE_RATE] - Audio sample rate
325
- * @returns {Blob} WAV file blob
326
- * @throws {Error} If unsupported channel count is provided
327
- */
328
- static createWavBlob(pcmFrames, channelCount = 1, sampleRate = SAMPLE_RATE) {
329
- const frames = Array.isArray(pcmFrames) ? pcmFrames : [pcmFrames]
330
-
331
- if (channelCount === 1) {
332
- return AudioProcessor._createMonoWavBlob(frames, sampleRate)
333
- } else if (channelCount === 2) {
334
- return AudioProcessor._createStereoWavBlob(frames, sampleRate)
335
- } else {
336
- throw new Error(`Unsupported channel count: ${channelCount}`)
337
- }
338
- }
339
-
340
- /**
341
- * Creates a mono WAV file blob from PCM frames
342
- * @param {Float32Array[]} frames - Array of mono PCM frames
343
- * @param {number} sampleRate - Audio sample rate
344
- * @returns {Blob} Mono WAV file blob
345
- * @private
346
- */
347
- static _createMonoWavBlob(frames, sampleRate) {
348
- const totalSamples = frames.reduce((sum, frame) => sum + frame.length, 0)
349
- const buffer = new ArrayBuffer(
350
- WAV_HEADER_SIZE + totalSamples * WAV_BYTES_PER_SAMPLE
351
- )
352
- const view = new DataView(buffer)
353
-
354
- // WAV header
355
- AudioProcessor._writeWavHeader(view, totalSamples, 1, sampleRate)
356
-
357
- // PCM data using DataView for consistency
358
- let offset = WAV_HEADER_SIZE
359
- for (const frame of frames) {
360
- for (let i = 0; i < frame.length; i++) {
361
- const sample = Math.max(-1, Math.min(1, frame[i]))
362
- view.setInt16(
363
- offset,
364
- sample < 0
365
- ? sample * WAV_PCM_MAX_NEGATIVE
366
- : sample * WAV_PCM_MAX_POSITIVE,
367
- true
368
- )
369
- offset += WAV_BYTES_PER_SAMPLE
370
- }
371
- }
372
-
373
- return new Blob([buffer], { type: 'audio/wav' })
374
- }
375
-
376
- /**
377
- * Creates a stereo WAV file blob from PCM frame pairs
378
- * @param {[Float32Array, Float32Array][]} framePairs - Array of stereo PCM frame pairs
379
- * @param {number} sampleRate - Audio sample rate
380
- * @returns {Blob} Stereo WAV file blob
381
- * @private
382
- */
383
- static _createStereoWavBlob(framePairs, sampleRate) {
384
- const totalSamples = framePairs.reduce(
385
- (sum, [left, right]) => sum + Math.max(left.length, right.length),
386
- 0
387
- )
388
-
389
- const buffer = new ArrayBuffer(
390
- WAV_HEADER_SIZE + totalSamples * WAV_BYTES_PER_SAMPLE * 2
391
- )
392
- const view = new DataView(buffer)
393
-
394
- // WAV header
395
- AudioProcessor._writeWavHeader(view, totalSamples, 2, sampleRate)
396
-
397
- // Interleaved PCM data using DataView for consistency
398
- let offset = WAV_HEADER_SIZE
399
- for (const [leftFrame, rightFrame] of framePairs) {
400
- const frameLength = Math.max(leftFrame.length, rightFrame.length)
401
- for (let i = 0; i < frameLength; i++) {
402
- const leftSample = i < leftFrame.length ? leftFrame[i] : 0
403
- const rightSample = i < rightFrame.length ? rightFrame[i] : 0
404
- const leftClipped = Math.max(-1, Math.min(1, leftSample))
405
- const rightClipped = Math.max(-1, Math.min(1, rightSample))
406
- view.setInt16(
407
- offset,
408
- leftClipped < 0
409
- ? leftClipped * WAV_PCM_MAX_NEGATIVE
410
- : leftClipped * WAV_PCM_MAX_POSITIVE,
411
- true
412
- )
413
- offset += WAV_BYTES_PER_SAMPLE
414
- view.setInt16(
415
- offset,
416
- rightClipped < 0
417
- ? rightClipped * WAV_PCM_MAX_NEGATIVE
418
- : rightClipped * WAV_PCM_MAX_POSITIVE,
419
- true
420
- )
421
- offset += WAV_BYTES_PER_SAMPLE
422
- }
423
- }
424
-
425
- return new Blob([buffer], { type: 'audio/wav' })
426
- }
427
-
428
- /**
429
- * Writes WAV file header to DataView
430
- * @param {DataView} view - DataView to write to
431
- * @param {number} totalSamples - Total number of samples
432
- * @param {number} channelCount - Number of audio channels
433
- * @param {number} sampleRate - Audio sample rate
434
- * @private
435
- */
436
- static _writeWavHeader(view, totalSamples, channelCount, sampleRate) {
437
- const byteRate = sampleRate * channelCount * WAV_BYTES_PER_SAMPLE
438
- const blockAlign = channelCount * WAV_BYTES_PER_SAMPLE
439
- const dataSize = totalSamples * blockAlign
440
- const fileSize = WAV_DATA_OFFSET + dataSize
441
-
442
- let pos = 0
443
-
444
- // Helper function to write ASCII strings
445
- const writeString = (offset, string) => {
446
- for (let i = 0; i < string.length; i++) {
447
- view.setUint8(offset + i, string.charCodeAt(i))
448
- }
449
- }
450
-
451
- // RIFF header
452
- writeString(pos, 'RIFF')
453
- pos += 4
454
- view.setUint32(pos, fileSize, true)
455
- pos += 4
456
- writeString(pos, 'WAVE')
457
- pos += 4
458
-
459
- // fmt chunk
460
- writeString(pos, 'fmt ')
461
- pos += 4
462
- view.setUint32(pos, 16, true) // chunk size
463
- pos += 4
464
- view.setUint16(pos, 1, true) // PCM format
465
- pos += 2
466
- view.setUint16(pos, channelCount, true)
467
- pos += 2
468
- view.setUint32(pos, sampleRate, true)
469
- pos += 4
470
- view.setUint32(pos, byteRate, true)
471
- pos += 4
472
- view.setUint16(pos, blockAlign, true)
473
- pos += 2
474
- view.setUint16(pos, WAV_BITS_PER_SAMPLE, true) // bits per sample
475
- pos += 2
476
-
477
- // data chunk
478
- writeString(pos, 'data')
479
- pos += 4
480
- view.setUint32(pos, dataSize, true)
481
- }
482
-
483
- /**
484
- * Parses an AEA file blob to extract header info and frame data
485
- * @param {Blob} blob - AEA file blob to parse
486
- * @returns {Promise<Object>} Parsed AEA file data
487
- * @returns {Object} returns.info - AEA file header information
488
- * @returns {Uint8Array[]} returns.frameData - Array of frame data buffers
489
- */
490
- static async parseAeaBlob(blob) {
491
- const buffer = await blob.arrayBuffer()
492
- const header = new Uint8Array(buffer.slice(0, AEA_HEADER_SIZE))
493
- const info = AeaFile.parseHeader(header)
494
-
495
- const frameData = []
496
- for (let i = AEA_HEADER_SIZE; i < buffer.byteLength; i += SOUND_UNIT_SIZE) {
497
- const frame = new Uint8Array(buffer.slice(i, i + SOUND_UNIT_SIZE))
498
- if (frame.length === SOUND_UNIT_SIZE) {
499
- frameData.push(frame)
500
- }
501
- }
502
-
503
- return { info, frameData }
504
- }
505
-
506
- /**
507
- * Creates a stream of deserialized frames from binary frame data
508
- * @param {Uint8Array[]} frameData - Array of binary frame data
509
- * @returns {Generator<Object>} Stream of deserialized frame objects
510
- */
511
- static *deserializedFrameStream(frameData) {
512
- for (const frame of frameData) {
513
- yield deserializeFrame(frame)
514
- }
515
- }
516
-
517
- /**
518
- * Assembles PCM frames into a continuous buffer
519
- * @param {Float32Array[]|[Float32Array, Float32Array][]} pcmFrames - Array of PCM frames
520
- * @param {number} channelCount - Number of audio channels
521
- * @returns {Float32Array} Continuous PCM buffer (interleaved for stereo)
522
- * @throws {Error} If unsupported channel count is provided
523
- */
524
- static assemblePcmFrames(pcmFrames, channelCount) {
525
- if (channelCount === 1) {
526
- const totalSamples = pcmFrames.reduce(
527
- (sum, frame) => sum + frame.length,
528
- 0
529
- )
530
- const pcm = new Float32Array(totalSamples)
531
- let offset = 0
532
- for (const frame of pcmFrames) {
533
- pcm.set(frame, offset)
534
- offset += frame.length
535
- }
536
- return pcm
537
- } else if (channelCount === 2) {
538
- const totalSamples = pcmFrames.reduce(
539
- (sum, [left, right]) => sum + Math.max(left.length, right.length),
540
- 0
541
- )
542
- const pcm = new Float32Array(totalSamples * 2)
543
-
544
- let pcmOffset = 0
545
- for (const [leftFrame, rightFrame] of pcmFrames) {
546
- const frameLength = Math.max(leftFrame.length, rightFrame.length)
547
- for (let j = 0; j < frameLength; j++) {
548
- pcm[pcmOffset * 2] = j < leftFrame.length ? leftFrame[j] : 0
549
- pcm[pcmOffset * 2 + 1] = j < rightFrame.length ? rightFrame[j] : 0
550
- pcmOffset++
551
- }
552
- }
553
- return pcm
554
- } else {
555
- throw new Error(`Unsupported channel count: ${channelCount}`)
556
- }
557
- }
558
- }