mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-13 08:04:03 +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
|
Sound Adapter
|
||||||
|
|
||||||
Endianness: little
|
Endianness: little
|
||||||
@@ -1092,6 +1127,44 @@ Play Head Flags
|
|||||||
|
|
||||||
65536..131071 RW: PCM Sample buffer
|
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
|
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_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_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_MULT_CHROMA = floatArrayOf(6.6f, 5.5f, 4.4f, 3.3f, 2.2f, 1.1f)
|
||||||
var ANISOTROPY_BIAS_CHROMA = floatArrayOf(0.4f, 0.3f, 0.2f, 0.1f, 0.0f, 0.0f)
|
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 {
|
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 {
|
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
|
// LUMA CHANNEL: Based on statistical analysis from real video content
|
||||||
|
|
||||||
// LL subband - contains most image energy, preserve carefully
|
// 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)
|
// 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
|
if (subbandType == 1) return LH
|
||||||
|
|
||||||
// HL subband - vertical details
|
// 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)
|
if (subbandType == 2) return HL * (if (level == 2) TWO_PIXEL_DETAILER else if (level == 3) FOUR_PIXEL_DETAILER else 1f)
|
||||||
|
|
||||||
// HH subband - diagonal details
|
// 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_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_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_MULT_CHROMA[] = {6.6f, 5.5f, 4.4f, 3.3f, 2.2f, 1.1f};
|
||||||
static const float ANISOTROPY_BIAS_CHROMA[] = {0.4f, 0.3f, 0.2f, 0.1f, 0.0f, 0.0f};
|
static const float ANISOTROPY_BIAS_CHROMA[] = {1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0.0f};
|
||||||
|
|
||||||
// DWT coefficient structure for each subband
|
// DWT coefficient structure for each subband
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -821,7 +821,7 @@ static float perceptual_model3_HL(int quality, float LH) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static float perceptual_model3_HH(float LH, float HL) {
|
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) {
|
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) {
|
if (!is_chroma) {
|
||||||
// LL subband - contains most image energy, preserve carefully
|
// LL subband - contains most image energy, preserve carefully
|
||||||
if (subband_type == 0)
|
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)
|
// 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)
|
if (subband_type == 1)
|
||||||
return LH;
|
return LH;
|
||||||
|
|
||||||
// HL subband - vertical details
|
// 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)
|
if (subband_type == 2)
|
||||||
return HL * (level == 2 ? TWO_PIXEL_DETAILER : level == 3 ? FOUR_PIXEL_DETAILER : 1.0f);
|
return HL * (level == 2 ? TWO_PIXEL_DETAILER : level == 3 ? FOUR_PIXEL_DETAILER : 1.0f);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user