mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 11:51:49 +09:00
more perceptual optimisation
This commit is contained in:
@@ -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
|
||||
357
assets/disk0/tvdos/bin/playucf.js
Normal file
357
assets/disk0/tvdos/bin/playucf.js
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
<if external addressing mode>
|
||||
uint16 String Length for relative path
|
||||
* Relative path
|
||||
|
||||
<if internal addressing mode>
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user