mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 11:51:49 +09:00
TAD: working kotlin decoder
This commit is contained in:
361
assets/disk0/tvdos/bin/playtad.js
Normal file
361
assets/disk0/tvdos/bin/playtad.js
Normal 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
|
||||
@@ -1352,7 +1352,6 @@ try {
|
||||
|
||||
if (!tadInitialised) {
|
||||
tadInitialised = true
|
||||
audio.tadSetQuality(header.qualityLevel)
|
||||
}
|
||||
|
||||
seqread.readBytes(payloadLen, SND_MEM_ADDR - 262144)
|
||||
|
||||
@@ -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`),
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user