TAD: working kotlin decoder

This commit is contained in:
minjaesong
2025-10-30 17:08:42 +09:00
parent 480d2d8538
commit 991d035bcc
9 changed files with 405 additions and 134 deletions

View File

@@ -0,0 +1,361 @@
const SND_BASE_ADDR = audio.getBaseAddr()
const SND_MEM_ADDR = audio.getMemAddr()
const TAD_INPUT_ADDR = SND_MEM_ADDR - 262144 // TAD input buffer (matches TAV packet 0x24)
const TAD_DECODED_ADDR = SND_MEM_ADDR - 262144 + 65536 // TAD decoded buffer
if (!SND_BASE_ADDR) return 10
// Check for help flag or missing arguments
if (!exec_args[1] || exec_args[1] == "-h" || exec_args[1] == "--help") {
serial.println("Usage: playtad <file.tad> [-i | -d] [quality]")
serial.println(" -i Interactive mode (progress bar, press Backspace to exit)")
serial.println(" -d Dump mode (show first 3 chunks with payload hex and decoded samples)")
serial.println("")
serial.println("Examples:")
serial.println(" playtad audio.tad -i # Play with progress bar")
serial.println(" playtad audio.tad -d # Dump first 3 chunks for debugging")
return 0
}
const pcm = require("pcm")
const interactive = exec_args[2] && exec_args[2].toLowerCase() == "-i"
const dumpCoeffs = exec_args[2] && exec_args[2].toLowerCase() == "-d"
function printdbg(s) { if (0) serial.println(s) }
class SequentialFileBuffer {
constructor(path, offset, length) {
if (Array.isArray(path)) throw Error("arg #1 is path(string), not array")
this.path = path
this.file = files.open(path)
this.offset = offset || 0
this.originalOffset = offset
this.length = length || this.file.size
this.seq = require("seqread")
this.seq.prepare(path)
}
readBytes(size, ptr) {
return this.seq.readBytes(size, ptr)
}
readByte() {
let ptr = this.seq.readBytes(1)
let val = sys.peek(ptr)
sys.free(ptr)
return val
}
readShort() {
let ptr = this.seq.readBytes(2)
let val = sys.peek(ptr) | (sys.peek(ptr + 1) << 8)
sys.free(ptr)
return val
}
readInt() {
let ptr = this.seq.readBytes(4)
let val = sys.peek(ptr) | (sys.peek(ptr + 1) << 8) | (sys.peek(ptr + 2) << 16) | (sys.peek(ptr + 3) << 24)
sys.free(ptr)
return val
}
readStr(n) {
let ptr = this.seq.readBytes(n)
let s = ''
for (let i = 0; i < n; i++) {
if (i >= this.length) break
s += String.fromCharCode(sys.peek(ptr + i))
}
sys.free(ptr)
return s
}
unread(diff) {
let newSkipLen = this.seq.getReadCount() - diff
this.seq.prepare(this.path)
this.seq.skip(newSkipLen)
}
rewind() {
this.seq.prepare(this.path)
}
seek(p) {
this.seq.prepare(this.path)
this.seq.skip(p)
}
get byteLength() {
return this.length
}
get fileHeader() {
return this.seq.fileHeader
}
getReadCount() {
return this.seq.getReadCount()
}
}
// Read TAD chunk header to determine format
let filebuf = new SequentialFileBuffer(_G.shell.resolvePathInput(exec_args[1]).full)
const FILE_SIZE = filebuf.length
if (FILE_SIZE < 7) {
serial.println(`ERROR: File too small (${FILE_SIZE} bytes). Expected TAD format.`)
return 1
}
// Read first chunk header (standalone TAD format: no TAV wrapper)
let firstSampleCount = filebuf.readShort()
let firstMaxIndex = filebuf.readByte()
let firstPayloadSize = filebuf.readInt()
// Validate first chunk
if (firstSampleCount < 0 || firstSampleCount > 65536) {
serial.println(`ERROR: Invalid sample count ${firstSampleCount}. File may be corrupted.`)
return 1
}
if (firstMaxIndex < 0 || firstMaxIndex > 255) {
serial.println(`ERROR: Invalid max index ${firstMaxIndex}. File may be corrupted.`)
return 1
}
if (firstPayloadSize < 1 || firstPayloadSize > 65536) {
serial.println(`ERROR: Invalid payload size ${firstPayloadSize}. File may be corrupted.`)
return 1
}
// Rewind to start
filebuf.rewind()
// Calculate approximate frame info
const AVG_CHUNK_SIZE = 7 + firstPayloadSize // TAD header (2+1+4) + payload
const SAMPLE_RATE = 32000
const bufRealTimeLen = Math.floor((firstSampleCount / SAMPLE_RATE) * 1000) // milliseconds per chunk
if (dumpCoeffs) {
serial.println(`TAD Coefficient Dump Mode`)
serial.println(`File: ${filebuf.file.name}`)
serial.println(`First chunk header:`)
serial.println(` Sample Count: ${firstSampleCount}`)
serial.println(` Max Index: ${firstMaxIndex}`)
serial.println(` Payload Size: ${firstPayloadSize} bytes`)
serial.println(`Chunk Duration: ${bufRealTimeLen} ms`)
serial.println(``)
}
let bytes_left = FILE_SIZE
let decodedLength = 0
let chunkNumber = 0
con.curs_set(0)
let [__, CONSOLE_WIDTH] = con.getmaxyx()
if (interactive) {
let [cy, cx] = con.getyx()
// file name
con.mvaddch(cy, 1)
con.prnch(0xC9);con.prnch(0xCD);con.prnch(0xB5)
print(filebuf.file.name)
con.prnch(0xC6);con.prnch(0xCD)
print("\x84205u".repeat(CONSOLE_WIDTH - 26 - filebuf.file.name.length))
con.prnch(0xB5)
print("Hold Bksp to Exit")
con.prnch(0xC6);con.prnch(0xCD);con.prnch(0xBB)
// L R pillar
con.prnch(0xBA)
con.mvaddch(cy+1, CONSOLE_WIDTH, 0xBA)
// media info
let mediaInfoStr = `TAD Q${firstMaxIndex} ${SAMPLE_RATE/1000}kHz`
con.move(cy+2,1)
con.prnch(0xC8)
print("\x84205u".repeat(CONSOLE_WIDTH - 5 - mediaInfoStr.length))
con.prnch(0xB5)
print(mediaInfoStr)
con.prnch(0xC6);con.prnch(0xCD);con.prnch(0xBC)
con.move(cy+1, 2)
}
let [cy, cx] = con.getyx()
let paintWidth = CONSOLE_WIDTH - 20
function bytesToSec(i) {
// Approximate: use first chunk's ratio
return Math.round((i / FILE_SIZE) * (FILE_SIZE / AVG_CHUNK_SIZE) * (bufRealTimeLen / 1000))
}
function secToReadable(n) {
let mins = ''+((n/60)|0)
let secs = ''+(n % 60)
return `${mins.padStart(2,'0')}:${secs.padStart(2,'0')}`
}
function printPlayBar() {
if (interactive) {
let currently = decodedLength
let total = FILE_SIZE
let currentlySec = bytesToSec(currently)
let totalSec = bytesToSec(total)
con.move(cy, 3)
print(' '.repeat(15))
con.move(cy, 3)
print(`${secToReadable(currentlySec)} / ${secToReadable(totalSec)}`)
con.move(cy, 17)
print(' ')
let progressbar = '\x84196u'.repeat(paintWidth + 1)
print(progressbar)
con.mvaddch(cy, 18 + Math.round(paintWidth * (currently / total)), 0xDB)
}
}
audio.resetParams(0)
audio.purgeQueue(0)
audio.setPcmMode(0)
audio.setPcmQueueCapacityIndex(0, 2) // queue size is now 8
const QUEUE_MAX = audio.getPcmQueueCapacity(0)
audio.setMasterVolume(0, 255)
audio.play(0)
let stopPlay = false
let errorlevel = 0
try {
while (bytes_left > 0 && !stopPlay) {
if (interactive) {
sys.poke(-40, 1)
if (sys.peek(-41) == 67) { // Backspace key
stopPlay = true
}
}
printPlayBar()
// Read TAD chunk header (standalone TAD format)
// Format: [sample_count][max_index][payload_size][payload]
let sampleCount = filebuf.readShort()
let maxIndex = filebuf.readByte()
let payloadSize = filebuf.readInt()
// Validate every chunk (not just first one)
if (sampleCount < 0 || sampleCount > 65536) {
serial.println(`ERROR: Chunk ${chunkNumber}: Invalid sample count ${sampleCount}. File may be corrupted.`)
errorlevel = 1
break
}
if (maxIndex < 0 || maxIndex > 255) {
serial.println(`ERROR: Chunk ${chunkNumber}: Invalid max index ${maxIndex}. File may be corrupted.`)
errorlevel = 1
break
}
if (payloadSize < 1 || payloadSize > 65536) {
serial.println(`ERROR: Chunk ${chunkNumber}: Invalid payload size ${payloadSize}. File may be corrupted.`)
errorlevel = 1
break
}
if (payloadSize + 7 > bytes_left) {
serial.println(`ERROR: Chunk ${chunkNumber}: Chunk size ${payloadSize + 7} exceeds remaining file size ${bytes_left}`)
errorlevel = 1
break
}
if (dumpCoeffs && chunkNumber < 3) {
serial.println(`=== Chunk ${chunkNumber} ===`)
serial.println(` Sample Count: ${sampleCount}`)
serial.println(` Max Index: ${maxIndex}`)
serial.println(` Payload Size: ${payloadSize} bytes`)
serial.println(` Bytes remaining in file: ${bytes_left}`)
}
// Rewind 7 bytes to re-read the header along with payload
// This allows reading the complete chunk (header + payload) in one call
filebuf.unread(7)
// Read entire chunk (header + payload) to TAD input buffer
// This matches TAV's approach for packet 0x24
let totalChunkSize = 7 + payloadSize
filebuf.readBytes(totalChunkSize, TAD_INPUT_ADDR)
if (dumpCoeffs && chunkNumber < 3) {
// Dump first 32 bytes of compressed payload (skip 7-byte header)
serial.print(` Compressed data (first 32 bytes): `)
for (let i = 0; i < Math.min(32, payloadSize); i++) {
let b = sys.peek(TAD_INPUT_ADDR + 7 + i)
serial.print(`${(b & 0xFF).toString(16).padStart(2, '0')} `)
}
serial.println('')
}
// Decode TAD chunk
audio.tadDecode()
if (dumpCoeffs && chunkNumber < 3) {
// After decoding, the decoded PCMu8 samples are in tadDecodedBin
serial.println(` Decoded ${sampleCount} samples`)
// Dump first 16 decoded samples (PCMu8 stereo interleaved)
serial.print(` Decoded (first 16 L samples): `)
for (let i = 0; i < 16; i++) {
serial.print(`${sys.peek(TAD_DECODED_ADDR + i * 2) & 0xFF} `)
}
serial.println('')
serial.print(` Decoded (first 16 R samples): `)
for (let i = 0; i < 16; i++) {
serial.print(`${sys.peek(TAD_DECODED_ADDR + i * 2 + 1) & 0xFF} `)
}
serial.println('')
serial.println('')
}
// Upload decoded audio to queue
audio.tadUploadDecoded(0, sampleCount)
if (!dumpCoeffs) {
// Sleep for the duration of the audio chunk to pace playback
// This prevents uploading everything at once
sys.sleep(bufRealTimeLen)
}
// Chunk size = header (7 bytes) + payload
let chunkSize = 7 + payloadSize
bytes_left -= chunkSize
decodedLength += chunkSize
chunkNumber++
// Limit coefficient dump to first 3 chunks
if (dumpCoeffs && chunkNumber >= 3) {
serial.println(`... (remaining chunks omitted)`)
// Keep playing but don't dump more
}
}
}
catch (e) {
printerrln(e)
errorlevel = 1
}
finally {
if (interactive) {
con.move(cy + 3, 1)
con.curs_set(1)
}
}
return errorlevel

View File

@@ -1352,7 +1352,6 @@ try {
if (!tadInitialised) {
tadInitialised = true
audio.tadSetQuality(header.qualityLevel)
}
seqread.readBytes(payloadLen, SND_MEM_ADDR - 262144)

View File

@@ -27,6 +27,7 @@ const COL_HL_EXT = {
"adpcm": 31,
"pcm": 32,
"mp3": 33,
"tad": 33,
"mp2": 34,
"mv1": 213,
"mv2": 213,
@@ -48,6 +49,7 @@ const EXEC_FUNS = {
"mv2": (f) => _G.shell.execute(`playtev "${f}" -i`),
"mv3": (f) => _G.shell.execute(`playtav "${f}" -i`),
"tav": (f) => _G.shell.execute(`playtav "${f}" -i`),
"tad": (f) => _G.shell.execute(`playtad "${f}" -i`),
"pcm": (f) => _G.shell.execute(`playpcm "${f}" -i`),
"ipf1": (f) => _G.shell.execute(`decodeipf "${f}" -i`),
"ipf2": (f) => _G.shell.execute(`decodeipf "${f}" -i`),

View File

@@ -94,13 +94,6 @@ class AudioJSR223Delegate(private val vm: VM) {
}
}
// TAD (Terrarum Advanced Audio) decoder functions
fun tadSetQuality(quality: Int) {
getFirstSnd()?.mmio_write(43L, quality.toByte())
}
fun tadGetQuality() = getFirstSnd()?.mmio_read(43L)?.toInt()
fun tadDecode() {
getFirstSnd()?.mmio_write(42L, 1)
}

View File

@@ -372,9 +372,9 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
// Lambda-based decompanding decoder (inverse of Laplacian CDF-based encoder)
// Converts quantized index back to normalized float in [-1, 1]
private fun lambdaDecompanding(quantVal: Short, maxIndex: Int): Float {
private fun lambdaDecompanding(quantVal: Byte, maxIndex: Int): Float {
// Handle zero
if (quantVal == 0.toShort()) {
if (quantVal == 0.toByte()) {
return 0.0f
}
@@ -477,17 +477,19 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
var offset = 0L
val sampleCount = (
(tadInputBin[offset++].toInt() and 0xFF) or
((tadInputBin[offset++].toInt() and 0xFF) shl 8)
(tadInputBin[offset++].toUint()) or
((tadInputBin[offset++].toUint()) shl 8)
)
val maxIndex = tadInputBin[offset++].toInt() and 0xFF
val maxIndex = tadInputBin[offset++].toUint()
val payloadSize = (
(tadInputBin[offset++].toInt() and 0xFF) or
((tadInputBin[offset++].toInt() and 0xFF) shl 8) or
((tadInputBin[offset++].toInt() and 0xFF) shl 16) or
((tadInputBin[offset++].toInt() and 0xFF) shl 24)
(tadInputBin[offset++].toUint()) or
((tadInputBin[offset++].toUint()) shl 8) or
((tadInputBin[offset++].toUint()) shl 16) or
((tadInputBin[offset++].toUint()) shl 24)
)
// println("Q$maxIndex, SampleCount: $sampleCount, payloadSize: $payloadSize")
// Decompress payload
val compressed = ByteArray(payloadSize)
UnsafeHelper.memcpyRaw(null, tadInputBin.ptr + offset, compressed, UnsafeHelper.getArrayOffset(compressed), payloadSize.toLong())
@@ -501,15 +503,9 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
return
}
// Decode significance maps
val quantMid = ShortArray(sampleCount)
val quantSide = ShortArray(sampleCount)
var payloadOffset = 0
val midBytes = decodeSigmap2bit(payload, payloadOffset, quantMid, sampleCount)
payloadOffset += midBytes
val sideBytes = decodeSigmap2bit(payload, payloadOffset, quantSide, sampleCount)
// Decode raw int8_t storage (no significance map - encoder uses raw format)
val quantMid = payload.sliceArray(0 until sampleCount)
val quantSide = payload.sliceArray(sampleCount until sampleCount*2)
// Calculate DWT levels from sample count
val dwtLevels = calculateDwtLevels(sampleCount)
@@ -542,42 +538,6 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
}
}
private fun decodeSigmap2bit(input: ByteArray, offset: Int, values: ShortArray, count: Int): Int {
val mapBytes = (count * 2 + 7) / 8
var readPtr = offset + mapBytes
var otherIdx = 0
for (i in 0 until count) {
val bitPos = i * 2
val byteIdx = offset + bitPos / 8
val bitOffset = bitPos % 8
var code = ((input[byteIdx].toInt() and 0xFF) shr bitOffset) and 0x03
// Handle bit spillover
if (bitOffset == 7) {
code = ((input[byteIdx].toInt() and 0xFF) shr 7) or
(((input[byteIdx + 1].toInt() and 0xFF) and 0x01) shl 1)
}
values[i] = when (code) {
0 -> 0
1 -> 1
2 -> (-1).toShort()
3 -> {
val v = ((input[readPtr].toInt() and 0xFF) or
((input[readPtr + 1].toInt() and 0xFF) shl 8)).toShort()
readPtr += 2
otherIdx++
v
}
else -> 0
}
}
return mapBytes + otherIdx * 2
}
private fun calculateDwtLevels(chunkSize: Int): Int {
// Hard-coded to 9 levels to match C decoder
return 9
@@ -628,7 +588,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
}
}
private fun dequantizeDwtCoefficients(quantized: ShortArray, coeffs: FloatArray, count: Int,
private fun dequantizeDwtCoefficients(quantized: ByteArray, coeffs: FloatArray, count: Int,
maxIndex: Int, dwtLevels: Int) {
// Calculate sideband boundaries dynamically
val firstBandSize = count shr dwtLevels

View File

@@ -312,7 +312,7 @@ static void dwt_dd4_inverse_1d(float *data, int length) {
free(temp);
}
static void dwt_haar_inverse_multilevel(float *data, int length, int levels) {
static void dwt_inverse_multilevel(float *data, int length, int levels) {
// Calculate the length at the deepest level (size of low-pass after all forward DWTs)
int current_length = length;
for (int level = 0; level < levels; level++) {
@@ -588,8 +588,8 @@ static int decode_chunk(const uint8_t *input, size_t input_size, uint8_t *pcmu8_
dequantize_dwt_coefficients(quant_side, dwt_side, sample_count, sample_count, dwt_levels, max_index, quantiser_scale);
// Inverse DWT
dwt_haar_inverse_multilevel(dwt_mid, sample_count, dwt_levels);
dwt_haar_inverse_multilevel(dwt_side, sample_count, dwt_levels);
dwt_inverse_multilevel(dwt_mid, sample_count, dwt_levels);
dwt_inverse_multilevel(dwt_side, sample_count, dwt_levels);
float err[2][2] = {{0,0},{0,0}};

View File

@@ -36,9 +36,8 @@ static const float BASE_QUANTISER_WEIGHTS[] = {
// Forward declarations for internal functions
static void dwt_dd4_forward_1d(float *data, int length);
static void dwt_dd4_forward_multilevel(float *data, int length, int levels);
static void dwt_forward_multilevel(float *data, int length, int levels);
static void quantize_dwt_coefficients(const float *coeffs, int8_t *quantized, size_t count, int apply_deadzone, int chunk_size, int dwt_levels, int quant_bits, int *current_subband_index, float quantiser_scale);
static size_t encode_twobitmap(const int8_t *values, size_t count, uint8_t *output);
static inline float FCLAMP(float x, float min, float max) {
return x < min ? min : (x > max ? max : x);
@@ -190,7 +189,7 @@ static void dwt_97_forward_1d(float *data, int length) {
}
// Apply multi-level DWT (using DD-4 wavelet)
static void dwt_dd4_forward_multilevel(float *data, int length, int levels) {
static void dwt_forward_multilevel(float *data, int length, int levels) {
int current_length = length;
for (int level = 0; level < levels; level++) {
// dwt_dd4_forward_1d(data, current_length);
@@ -315,61 +314,6 @@ static void quantize_dwt_coefficients(const float *coeffs, int8_t *quantized, si
free(sideband_starts);
}
//=============================================================================
// Twobit-map Significance Map Encoding
//=============================================================================
// Twobit-map encoding: 2 bits per coefficient for common values
// 00 = 0
// 01 = +1
// 10 = -1
// 11 = other value (followed by int8_t in separate array)
static size_t encode_twobitmap(const int8_t *values, size_t count, uint8_t *output) {
// Calculate size needed for twobit map
size_t map_bytes = (count * 2 + 7) / 8; // 2 bits per coefficient
// First pass: create significance map and count "other" values
uint8_t *map = output;
memset(map, 0, map_bytes);
size_t other_count = 0;
for (size_t i = 0; i < count; i++) {
int8_t val = values[i];
uint8_t code;
if (val == 0) {
code = 0; // 00
} else if (val == 1) {
code = 1; // 01
} else if (val == -1) {
code = 2; // 10
} else {
code = 3; // 11
other_count++;
}
// Write 2-bit code into map
size_t bit_offset = i * 2;
size_t byte_idx = bit_offset / 8;
size_t bit_in_byte = bit_offset % 8;
map[byte_idx] |= (code << bit_in_byte);
}
// Second pass: write "other" values
int8_t *other_values = (int8_t*)(output + map_bytes);
size_t other_idx = 0;
for (size_t i = 0; i < count; i++) {
int8_t val = values[i];
if (val != 0 && val != 1 && val != -1) {
other_values[other_idx++] = val;
}
}
return map_bytes + other_count;
}
//=============================================================================
// Coefficient Statistics
//=============================================================================
@@ -818,8 +762,8 @@ size_t tad32_encode_chunk(const float *pcm32_stereo, size_t num_samples,
dwt_side[i] = pcm32_side[i];
}
dwt_dd4_forward_multilevel(dwt_mid, num_samples, dwt_levels);
dwt_dd4_forward_multilevel(dwt_side, num_samples, dwt_levels);
dwt_forward_multilevel(dwt_mid, num_samples, dwt_levels);
dwt_forward_multilevel(dwt_side, num_samples, dwt_levels);
// Step 3.5: Accumulate coefficient statistics if enabled
static int stats_enabled = -1;

View File

@@ -48,8 +48,13 @@ static void print_usage(const char *prog_name) {
printf("Options:\n");
printf(" -i <file> Input audio file (any format supported by FFmpeg)\n");
printf(" -o <file> Output TAD32 file (optional, auto-generated as input.qN.tad)\n");
printf(" -q <bits> Positive side quantization steps (default: 47, range: up to 127)\n");
printf(" Higher = more precision, larger files\n");
printf(" -q <level> Quality level (0-5, default: %d)\n", TAD32_QUALITY_DEFAULT);
printf(" 0 = lowest quality/smallest (max_index=31)\n");
printf(" 1 = low quality (max_index=35)\n");
printf(" 2 = medium quality (max_index=39)\n");
printf(" 3 = good quality (max_index=47) [DEFAULT]\n");
printf(" 4 = high quality (max_index=56)\n");
printf(" 5 = very high quality/largest (max_index=89)\n");
printf(" -s <scale> Quantiser scaling factor (default: 1.0, range: 0.5-4.0)\n");
printf(" Higher = more aggressive quantization, smaller files\n");
printf(" 2.0 = quantize 2x coarser than baseline\n");
@@ -65,7 +70,7 @@ int main(int argc, char *argv[]) {
char *input_file = NULL;
char *output_file = NULL;
int max_index = 47; // Default QUANT_BITS
int quality = TAD32_QUALITY_DEFAULT; // Default quality level (0-5)
float quantiser_scale = 1.0f; // Default quantiser scaling
int verbose = 0;
@@ -86,7 +91,11 @@ int main(int argc, char *argv[]) {
output_file = optarg;
break;
case 'q':
max_index = atoi(optarg);
quality = atoi(optarg);
if (quality < TAD32_QUALITY_MIN || quality > TAD32_QUALITY_MAX) {
fprintf(stderr, "Error: Quality must be in range %d-%d\n", TAD32_QUALITY_MIN, TAD32_QUALITY_MAX);
return 1;
}
break;
case 's':
quantiser_scale = atof(optarg);
@@ -113,6 +122,9 @@ int main(int argc, char *argv[]) {
return 1;
}
// Convert quality (0-5) to max_index for quantization
int max_index = tad32_quality_to_max_index(quality);
// Generate output filename if not provided
if (!output_file) {
// Allocate space for output filename
@@ -140,8 +152,8 @@ int main(int argc, char *argv[]) {
strcpy(output_file + dir_len, basename_start);
}
// Append .qNN.tad
sprintf(output_file + strlen(output_file), ".q%d.tad", max_index);
// Append .qNN.tad (use quality level for filename)
sprintf(output_file + strlen(output_file), ".q%d.tad", quality);
if (verbose) {
printf("Auto-generated output path: %s\n", output_file);
@@ -152,7 +164,7 @@ int main(int argc, char *argv[]) {
printf("%s\n", ENCODER_VENDOR_STRING);
printf("Input: %s\n", input_file);
printf("Output: %s\n", output_file);
printf("Quant bits: %d\n", max_index);
printf("Quality level: %d (max_index=%d)\n", quality, max_index);
printf("Quantiser scale: %.2f\n", quantiser_scale);
}

View File

@@ -9041,7 +9041,7 @@ static int write_tad_packet_samples(tav_encoder_t *enc, FILE *output, int sample
if (enc->verbose) {
printf("TAD32 packet: %d samples, %u bytes compressed (Q%d)\n",
sample_count, tad_payload_size, tad_quality);
sample_count, tad_payload_size, quant_size);
}
// Cleanup