diff --git a/assets/disk0/tvdos/bin/playmp3.js b/assets/disk0/tvdos/bin/playmp3.js deleted file mode 100644 index 4ff1cd4..0000000 --- a/assets/disk0/tvdos/bin/playmp3.js +++ /dev/null @@ -1,236 +0,0 @@ -println("DEPRECATION NOTICE: MP3 Playback function will be removed for following reason") -println("\tMP3 does not really fit in the time TSVM targets to emulate") -return 1 - - -const Mp3 = require('mp3dec') -const pcm = require("pcm") -const interactive = exec_args[2] && exec_args[2].toLowerCase() == "-i" - -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) - } - - /*readFull(n) { - throw Error() - let ptr = this.seq.readBytes(n) - return ptr - }*/ - - 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 - } - - readByteNumbers(n) { - let ptr = this.seq.readBytes(n) - try { - let s = [] - for (let i = 0; i < n; i++) { - if (i >= this.length) break - s.push(sys.peek(ptr + i)) - } - sys.free(ptr) - return s - } - catch (e) { - println(`n: ${n}; ptr: ${ptr}`) - println(e) - } - } - - 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 remaining() { - return this.length - this.getReadCount() - }*/ -} - - - -con.curs_set(0) -let [cy, cx] = con.getyx() -let [__, CONSOLE_WIDTH] = con.getmaxyx() -let paintWidth = CONSOLE_WIDTH - 16 -if (interactive) { - println("Decoding...") -} - - -printdbg("pre-decode...") -let filebuf = new SequentialFileBuffer(_G.shell.resolvePathInput(exec_args[1]).full) -const FILE_SIZE = filebuf.length -let decoder = Mp3.newDecoder(filebuf) -if (decoder === null) throw Error("decoder is null") - -const HEADER_SIZE = decoder.headerSize + 3 -const FRAME_SIZE = decoder.frameSize // only works reliably for CBR - -//serial.println(`header size: ${HEADER_SIZE}`) -//serial.println(`frame size: ${FRAME_SIZE}`) - -audio.resetParams(0) -audio.purgeQueue(0) -audio.setPcmMode(0) -audio.setPcmQueueCapacityIndex(0, 5) // queue size is now 24 -const QUEUE_MAX = audio.getPcmQueueCapacity(0) -audio.setMasterVolume(0, 255) -audio.play(0) - -let decodedLength = 0 -let readPtr = sys.malloc(8000) -let decodePtr = sys.malloc(12000) - -function bytesToSec(i) { - return i / (FRAME_SIZE * 1000 / bufRealTimeLen) -} -function secToReadable(n) { - let mins = ''+((n/60)|0) - let secs = ''+(n % 60) - return `${mins.padStart(2,'0')}:${secs.padStart(2,'0')}` -} -function decodeAndResample(inPtr, outPtr, inputLen) { - // TODO resample - for (let k = 0; k < inputLen / 2; k+=2) { - let sample = [ - pcm.u16Tos16(sys.peek(inPtr + k*2 + 0) | (sys.peek(inPtr + k*2 + 1) << 8)), - pcm.u16Tos16(sys.peek(inPtr + k*2 + 2) | (sys.peek(inPtr + k*2 + 3) << 8)) - ] - sys.poke(outPtr + k, pcm.s16Tou8(sample[0])) - sys.poke(outPtr + k + 1, pcm.s16Tou8(sample[1])) - // soothing visualiser(????) -// printvis(`${sampleToVisual(sample[0])} | ${sampleToVisual(sample[1])}`) - } -} - - -function printPlayBar() { -} - -let stopPlay = false -con.curs_set(0) -if (interactive) { - con.move(cy, cy) - println("Push and hold Backspace to exit") -} -[cy, cx] = con.getyx() -function printPlayBar(currently) { - if (interactive) { -// let currently = decodedLength - let total = FILE_SIZE - HEADER_SIZE - - let currentlySec = Math.round(bytesToSec(currently)) - let totalSec = Math.round(bytesToSec(total)) - - con.move(cy, 1) - print(' '.repeat(15)) - con.move(cy, 1) - - print(`${secToReadable(currentlySec)} / ${secToReadable(totalSec)}`) - - con.move(cy, 15) - print(' ') - let progressbar = '\x84205u'.repeat(paintWidth + 1) - print(progressbar) - - con.mvaddch(cy, 16 + Math.round(paintWidth * (currently / total)), 0xDB) - } -} -let t1 = sys.nanoTime() -let errorlevel = 0 -let bufRealTimeLen = 36 -try { - decoder.decode((ptr, len, pos)=>{ - - if (interactive) { - sys.poke(-40, 1) - if (sys.peek(-41) == 67) { - stopPlay = true - throw "STOP" - } - } - - printPlayBar(pos) - - let t2 = sys.nanoTime() - - decodedLength += len - -// serial.println(`Audio queue size: ${audio.getPosition(0)}/${QUEUE_MAX}`) - - if (audio.getPosition(0) >= QUEUE_MAX) { - while (audio.getPosition(0) >= (QUEUE_MAX >>> 1)) { - printdbg(`Queue full, waiting until the queue has some space (${audio.getPosition(0)}/${QUEUE_MAX})`) -// serial.println(`Queue full, waiting until the queue has some space (${audio.getPosition(0)}/${QUEUE_MAX})`) - sys.sleep(bufRealTimeLen) - } - } - - - - decodeAndResample(ptr, decodePtr, len) - - audio.putPcmDataByPtr(decodePtr, len >> 1, 0) - audio.setSampleUploadLength(0, len >> 1) - audio.startSampleUpload(0) - - - let decodingTime = (t2 - t1) / 1000000.0 - bufRealTimeLen = (len >> 1) / 64000.0 * 1000 - t1 = t2 - - printdbg(`Decoded ${decodedLength} bytes; target: ${bufRealTimeLen} ms, lag: ${decodingTime - bufRealTimeLen} ms`) - - - }) // now you got decoded PCM data -} -catch (e) { - if (e != "STOP") { - printerrln(e) - errorlevel = 1 - } -} -finally { - //audio.stop(0) - sys.free(readPtr) - sys.free(decodePtr) -} - -return errorlevel \ No newline at end of file diff --git a/assets/disk0/tvdos/bin/playucf.js b/assets/disk0/tvdos/bin/playucf.js new file mode 100644 index 0000000..ba96080 --- /dev/null +++ b/assets/disk0/tvdos/bin/playucf.js @@ -0,0 +1,357 @@ +// TSVM Universal Cue Format (UCF) Player +// Created by Claude on 2025-09-22 +// Usage: playucf cuefile.ucf [options] +// Options: -i (interactive mode) + +if (!exec_args[1]) { + serial.println("Usage: playucf cuefile.ucf [options]") + serial.println("Options: -i (interactive mode)") + return 1 +} + +const interactive = exec_args[2] && exec_args[2].toLowerCase() == "-i" +const fullFilePath = _G.shell.resolvePathInput(exec_args[1]) + +if (!files.exists(fullFilePath.full)) { + serial.println(`Error: File not found: ${fullFilePath.full}`) + return 2 +} + +// UCF Format constants +const UCF_MAGIC = [0x1F, 0x54, 0x53, 0x56, 0x4D, 0x55, 0x43, 0x46] // "\x1FTSVM UCF" +const UCF_VERSION = 1 +const ADDRESSING_EXTERNAL = 0x01 +const ADDRESSING_INTERNAL = 0x02 + +// Media player mappings based on file extensions +const PLAYER_MAP = { + 'mp2': 'playmp2', + 'wav': 'playwav', + 'pcm': 'playpcm', + 'mv1': 'playmv1', + 'mv2': 'playtev', + 'mv3': 'playtav' +} + +// Helper class for UCF file reading with internal addressing support +class UCFSequentialReader { + constructor(path, baseOffset = 0) { + this.path = path + this.baseOffset = baseOffset + this.currentOffset = 0 + + // Detect if this is a TAPE device path + if (path.startsWith("$:/TAPE") || path.startsWith("$:\\TAPE")) { + this.seq = require("seqreadtape") + } else { + this.seq = require("seqread") + } + + this.seq.prepare(path) + + // Skip to the base offset for internal addressing + if (baseOffset > 0) { + this.seq.skip(baseOffset) + this.currentOffset = baseOffset + } + } + + readBytes(length) { + this.currentOffset += length + return this.seq.readBytes(length) + } + + readOneByte() { + this.currentOffset += 1 + return this.seq.readOneByte() + } + + readShort() { + this.currentOffset += 2 + return this.seq.readShort() + } + + readString(length) { + this.currentOffset += length + return this.seq.readString(length) + } + + skip(n) { + this.currentOffset += n + this.seq.skip(n) + } + + // Skip to absolute position from base offset + seekTo(position) { + let targetOffset = this.baseOffset + position + if (targetOffset < this.currentOffset) { + // Need to rewind and seek forward + this.seq.prepare(this.path) + this.currentOffset = 0 + if (targetOffset > 0) { + this.seq.skip(targetOffset) + this.currentOffset = targetOffset + } + } else if (targetOffset > this.currentOffset) { + // Skip forward + let skipAmount = targetOffset - this.currentOffset + this.seq.skip(skipAmount) + this.currentOffset = targetOffset + } + } + + getPosition() { + return this.currentOffset - this.baseOffset + } +} + +// Parse UCF file +serial.println(`Playing UCF: ${fullFilePath.full}`) + +let reader = new UCFSequentialReader(fullFilePath.full) + +// Read and validate magic +let magic = [] +for (let i = 0; i < 8; i++) { + magic.push(reader.readOneByte()) +} + +let magicValid = true +for (let i = 0; i < 8; i++) { + if (magic[i] !== UCF_MAGIC[i]) { + magicValid = false + break + } +} + +if (!magicValid) { + serial.println("Error: Invalid UCF magic signature") + return 3 +} + +// Read header +let version = reader.readOneByte() +if (version !== UCF_VERSION) { + serial.println(`Error: Unsupported UCF version: ${version} (expected ${UCF_VERSION})`) + return 4 +} + +let numElements = reader.readShort() +// Skip reserved bytes (5 bytes) +reader.skip(5) + +serial.println(`UCF Version: ${version}, Elements: ${numElements}`) + +// Parse cue elements +let cueElements = [] +for (let i = 0; i < numElements; i++) { + let element = {} + + element.addressingMode = reader.readOneByte() + let nameLength = reader.readShort() + element.name = reader.readString(nameLength) + + if (element.addressingMode === ADDRESSING_EXTERNAL) { + let pathLength = reader.readShort() + element.path = reader.readString(pathLength) + serial.println(`Element ${i + 1}: ${element.name} -> ${element.path} (external)`) + } else if (element.addressingMode === ADDRESSING_INTERNAL) { + // Read 48-bit offset (6 bytes, little endian) + let offsetBytes = [] + for (let j = 0; j < 6; j++) { + offsetBytes.push(reader.readOneByte()) + } + + element.offset = 0 + for (let j = 0; j < 6; j++) { + element.offset |= (offsetBytes[j] << (j * 8)) + } + + serial.println(`Element ${i + 1}: ${element.name} -> offset ${element.offset} (internal)`) + } else { + serial.println(`Error: Unknown addressing mode: ${element.addressingMode}`) + return 5 + } + + cueElements.push(element) +} + +// Function to get file extension +function getFileExtension(filename) { + let lastDot = filename.lastIndexOf('.') + if (lastDot === -1) return '' + return filename.substring(lastDot + 1).toLowerCase() +} + +// Function to determine player for a file +function getPlayerForFile(filename) { + let ext = getFileExtension(filename) + return PLAYER_MAP[ext] || null +} + +// Function to create a temporary file for internal addressing +function createTempFileForInternal(element, ucfPath) { + // Create a unique temporary filename + let tempFilename = `$:\\TMP\\temp_ucf_${Date.now()}_${element.name.replace(/[^a-zA-Z0-9]/g, '_')}` + + // For internal addressing, we abuse seqread by creating a "virtual" file view + // We'll return a special path that our modified exec environment can handle + return { + isTemporary: true, + path: tempFilename, + ucfPath: ucfPath, + offset: element.offset, + name: element.name + } +} + +// Play each cue element in sequence +for (let i = 0; i < cueElements.length; i++) { + let element = cueElements[i] + + serial.println(`\nPlaying element ${i + 1}/${numElements}: ${element.name}`) + + if (interactive && i > 0) { + serial.print("Press ENTER to continue, 'q' to quit: ") + let input = serial.readLine() + if (input && input.toLowerCase().startsWith('q')) { + serial.println("Playback stopped by user") + break + } + } + + let playerFile = null + let targetPath = null + + if (element.addressingMode === ADDRESSING_EXTERNAL) { + // External addressing - resolve relative path + let elementPath = element.path + if (!elementPath.startsWith('A:\\') && !elementPath.startsWith('A:/')) { + // Relative path - resolve relative to UCF file location + let ucfDir = fullFilePath.full.substring(0, fullFilePath.full.lastIndexOf('\\')) + targetPath = ucfDir + '\\' + elementPath.replace(/\//g, '\\') + } else { + targetPath = elementPath + } + + if (!files.exists(targetPath)) { + serial.println(`Warning: External file not found: ${targetPath}`) + continue + } + + playerFile = getPlayerForFile(element.name) + } else if (element.addressingMode === ADDRESSING_INTERNAL) { + // Internal addressing - create temporary file reference + let tempFile = createTempFileForInternal(element, fullFilePath.full) + targetPath = tempFile.path + playerFile = getPlayerForFile(element.name) + + // For internal addressing, we need to extract the data to a temporary location + // or use a specialized player that can handle offset-based reading + // Since we can't easily create temp files, we'll modify the exec_args for the player + + // Create a new UCF reader positioned at the file offset + let fileReader = new UCFSequentialReader(fullFilePath.full, element.offset) + + // We need to somehow pass this to the player... + // The most elegant solution is to create a wrapper that temporarily modifies + // the file system view or uses a custom SequentialFileBuffer + + // For now, let's use a simpler approach: save exec_args and restore them + let originalExecArgs = [...exec_args] + + // Modify the global environment to provide the offset reader + let originalFilesOpen = files.open + + files.open = function(path) { + if (path === targetPath || path.endsWith(targetPath)) { + // Return a mock file object that uses our offset reader + return { + exists: true, + size: 2147483648, // Arbitrary large size + path: path, + _ucfReader: fileReader + } + } + return originalFilesOpen.call(this, path) + } + + // Also modify seqread require to use our reader + let originalRequire = require + require = function(moduleName) { + if (moduleName === "seqread" || moduleName === "seqreadtape") { + return { + prepare: function(path) { + if (path === targetPath || path.endsWith(targetPath)) { + // Already prepared in fileReader + return 0 + } + return fileReader.seq.prepare(path) + }, + readBytes: function(length, ptr) { return fileReader.readBytes(length, ptr) }, + readOneByte: function() { return fileReader.readOneByte() }, + readShort: function() { return fileReader.readShort() }, + readInt: function() { return fileReader.seq.readInt() }, + readFourCC: function() { return fileReader.seq.readFourCC() }, + readString: function(length) { return fileReader.readString(length) }, + skip: function(n) { return fileReader.skip(n) }, + getReadCount: function() { return fileReader.getPosition() }, + fileHeader: fileReader.seq.fileHeader + } + } + return originalRequire.call(this, moduleName) + } + + try { + // Execute the player with modified environment + exec_args[1] = targetPath + if (playerFile) { + let playerPath = `A:\\tvdos\\bin\\${playerFile}.js` + if (files.exists(playerPath)) { + eval(files.readText(playerPath)) + } else { + serial.println(`Warning: Player not found: ${playerFile}`) + } + } else { + serial.println(`Warning: No player found for file type: ${element.name}`) + } + } catch (e) { + serial.println(`Error playing ${element.name}: ${e.message}`) + } finally { + // Restore original environment + files.open = originalFilesOpen + require = originalRequire + exec_args = originalExecArgs + } + + continue + } + + if (!playerFile) { + serial.println(`Warning: No player found for file type: ${element.name}`) + continue + } + + // Execute the appropriate player + let playerPath = `A:\\tvdos\\bin\\${playerFile}.js` + if (!files.exists(playerPath)) { + serial.println(`Warning: Player script not found: ${playerPath}`) + continue + } + + // Save and modify exec_args for the player + let originalExecArgs = [...exec_args] + exec_args[1] = targetPath + + try { + eval(files.readText(playerPath)) + } catch (e) { + serial.println(`Error playing ${element.name}: ${e.message}`) + } finally { + // Restore original exec_args + exec_args = originalExecArgs + } +} + +serial.println("\nUCF playback completed") +return 0 \ No newline at end of file diff --git a/terranmon.txt b/terranmon.txt index d8f52e9..47d1080 100644 --- a/terranmon.txt +++ b/terranmon.txt @@ -954,6 +954,41 @@ Unlike the TEV format, TAV encoder emits extra sync packet for every 1000th fram -------------------------------------------------------------------------------- +TSVM Universal Cue format +Created by CuriousTorvald on 2025-09-22 + +A universal, simle cue designed to work as both playlist to cue up external files and lookup table for internal bytes. + +# File Structure +\x1F T S V M U C F +[HEADER] +[CUE ELEMENT 0] +[CUE ELEMENT 1] +[CUE ELEMENT 2] +... + +## Header (16 bytes) + uint8 Magic[8]: "\x1F TSVM UCF" + uint8 Version: 1 + uint16 Number of cue elements + unit8 Reserved[5] + +## Cue Element + uint8 Addressing Mode + - 0x01: External + - 0x02: Internal + uint16 String Length for name + * Name of the element in UTF-8 + + + uint16 String Length for relative path + * Relative path + + + uint48 Offset to the file + +-------------------------------------------------------------------------------- + Sound Adapter Endianness: little @@ -1092,6 +1127,44 @@ Play Head Flags 65536..131071 RW: PCM Sample buffer +Table of 3.5 Minifloat values (CSV) + ,000,001,010,011,100,101,110,111,MSB +00000,0,1,2,4,8,16,32,64 +00001,0.03125,1.03125,2.0625,4.125,8.25,16.5,33,66 +00010,0.0625,1.0625,2.125,4.25,8.5,17,34,68 +00011,0.09375,1.09375,2.1875,4.375,8.75,17.5,35,70 +00100,0.125,1.125,2.25,4.5,9,18,36,72 +00101,0.15625,1.15625,2.3125,4.625,9.25,18.5,37,74 +00110,0.1875,1.1875,2.375,4.75,9.5,19,38,76 +00111,0.21875,1.21875,2.4375,4.875,9.75,19.5,39,78 +01000,0.25,1.25,2.5,5,10,20,40,80 +01001,0.28125,1.28125,2.5625,5.125,10.25,20.5,41,82 +01010,0.3125,1.3125,2.625,5.25,10.5,21,42,84 +01011,0.34375,1.34375,2.6875,5.375,10.75,21.5,43,86 +01100,0.375,1.375,2.75,5.5,11,22,44,88 +01101,0.40625,1.40625,2.8125,5.625,11.25,22.5,45,90 +01110,0.4375,1.4375,2.875,5.75,11.5,23,46,92 +01111,0.46875,1.46875,2.9375,5.875,11.75,23.5,47,94 +10000,0.5,1.5,3,6,12,24,48,96 +10001,0.53125,1.53125,3.0625,6.125,12.25,24.5,49,98 +10010,0.5625,1.5625,3.125,6.25,12.5,25,50,100 +10011,0.59375,1.59375,3.1875,6.375,12.75,25.5,51,102 +10100,0.625,1.625,3.25,6.5,13,26,52,104 +10101,0.65625,1.65625,3.3125,6.625,13.25,26.5,53,106 +10110,0.6875,1.6875,3.375,6.75,13.5,27,54,108 +10111,0.71875,1.71875,3.4375,6.875,13.75,27.5,55,110 +11000,0.75,1.75,3.5,7,14,28,56,112 +11001,0.78125,1.78125,3.5625,7.125,14.25,28.5,57,114 +11010,0.8125,1.8125,3.625,7.25,14.5,29,58,116 +11011,0.84375,1.84375,3.6875,7.375,14.75,29.5,59,118 +11100,0.875,1.875,3.75,7.5,15,30,60,120 +11101,0.90625,1.90625,3.8125,7.625,15.25,30.5,61,122 +11110,0.9375,1.9375,3.875,7.75,15.5,31,62,124 +11111,0.96875,1.96875,3.9375,7.875,15.75,31.5,63,126 +LSB + + + -------------------------------------------------------------------------------- RomBank / RamBank diff --git a/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt b/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt index 8b3509c..c6974dc 100644 --- a/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt +++ b/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt @@ -4091,8 +4091,8 @@ class GraphicsJSR223Delegate(private val vm: VM) { var ANISOTROPY_MULT = floatArrayOf(1.8f, 1.6f, 1.4f, 1.2f, 1.0f, 1.0f) var ANISOTROPY_BIAS = floatArrayOf(0.2f, 0.1f, 0.0f, 0.0f, 0.0f, 0.0f) - var ANISOTROPY_MULT_CHROMA = floatArrayOf(2.4f, 2.2f, 2.0f, 1.7f, 1.4f, 1.0f) - var ANISOTROPY_BIAS_CHROMA = floatArrayOf(0.4f, 0.3f, 0.2f, 0.1f, 0.0f, 0.0f) + var ANISOTROPY_MULT_CHROMA = floatArrayOf(6.6f, 5.5f, 4.4f, 3.3f, 2.2f, 1.1f) + var ANISOTROPY_BIAS_CHROMA = floatArrayOf(1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0.0f) @@ -4111,7 +4111,7 @@ class GraphicsJSR223Delegate(private val vm: VM) { } private fun perceptual_model3_HH(LH: Float, HL: Float): Float { - return 2f * (LH + HL) / 3f + return (HL / LH) * 1.44f; } fun perceptual_model3_LL(quality: Int, level: Int): Float { @@ -4144,14 +4144,14 @@ class GraphicsJSR223Delegate(private val vm: VM) { // LUMA CHANNEL: Based on statistical analysis from real video content // LL subband - contains most image energy, preserve carefully - if (subbandType == 0) return perceptual_model3_LL(qualityLevel, level + 1) + if (subbandType == 0) return perceptual_model3_LL(qualityLevel, level) // LH subband - horizontal details (human eyes more sensitive) - val LH: Float = perceptual_model3_LH(qualityLevel, level + 1) + val LH: Float = perceptual_model3_LH(qualityLevel, level) if (subbandType == 1) return LH // HL subband - vertical details - val HL: Float = perceptual_model3_HL(qualityLevel, LH + 1) + val HL: Float = perceptual_model3_HL(qualityLevel, LH) if (subbandType == 2) return HL * (if (level == 2) TWO_PIXEL_DETAILER else if (level == 3) FOUR_PIXEL_DETAILER else 1f) // HH subband - diagonal details diff --git a/video_encoder/encoder_tav.c b/video_encoder/encoder_tav.c index 3013ea3..93a3a39 100644 --- a/video_encoder/encoder_tav.c +++ b/video_encoder/encoder_tav.c @@ -152,8 +152,8 @@ static const int QUALITY_CG[] = {240, 180, 120, 60, 30, 5}; static const float ANISOTROPY_MULT[] = {1.8f, 1.6f, 1.4f, 1.2f, 1.0f, 1.0f}; static const float ANISOTROPY_BIAS[] = {0.2f, 0.1f, 0.0f, 0.0f, 0.0f, 0.0f}; -static const float ANISOTROPY_MULT_CHROMA[] = {2.4f, 2.2f, 2.0f, 1.7f, 1.4f, 1.0f}; -static const float ANISOTROPY_BIAS_CHROMA[] = {0.4f, 0.3f, 0.2f, 0.1f, 0.0f, 0.0f}; +static const float ANISOTROPY_MULT_CHROMA[] = {6.6f, 5.5f, 4.4f, 3.3f, 2.2f, 1.1f}; +static const float ANISOTROPY_BIAS_CHROMA[] = {1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0.0f}; // DWT coefficient structure for each subband typedef struct { @@ -821,7 +821,7 @@ static float perceptual_model3_HL(int quality, float LH) { } static float perceptual_model3_HH(float LH, float HL) { - return 2.f * (LH + HL) / 3.f; + return (HL / LH) * 1.44f; } static float perceptual_model3_LL(int quality, int level) { @@ -913,15 +913,15 @@ static float get_perceptual_weight(tav_encoder_t *enc, int level, int subband_ty if (!is_chroma) { // LL subband - contains most image energy, preserve carefully if (subband_type == 0) - return perceptual_model3_LL(enc->quality_level, level + 1); + return perceptual_model3_LL(enc->quality_level, level); // LH subband - horizontal details (human eyes more sensitive) - float LH = perceptual_model3_LH(enc->quality_level, level + 1); + float LH = perceptual_model3_LH(enc->quality_level, level); if (subband_type == 1) return LH; // HL subband - vertical details - float HL = perceptual_model3_HL(enc->quality_level, LH + 1); + float HL = perceptual_model3_HL(enc->quality_level, LH); if (subband_type == 2) return HL * (level == 2 ? TWO_PIXEL_DETAILER : level == 3 ? FOUR_PIXEL_DETAILER : 1.0f);