From 95bfaae1da944e117c49ae414bff5519a3d32c71 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Tue, 12 Apr 2022 16:44:14 +0900 Subject: [PATCH] tsvm mov encoder and decoder --- assets/disk0/decodemov.js | 54 ++++++++++++ assets/disk0/encodemov.js | 64 ++++++++++++++ assets/disk0/tvdos/TVDOS.SYS | 2 +- terranmon.txt | 30 ++++++- .../net/torvald/tsvm/CompressorDelegate.kt | 87 ++++++++++++------- .../src/net/torvald/tsvm/VMRunnerFactory.kt | 2 +- .../torvald/tsvm/peripheral/TestDiskDrive.kt | 15 +++- .../torvald/tsvm/peripheral/VMProgramRom.kt | 2 +- 8 files changed, 219 insertions(+), 37 deletions(-) create mode 100644 assets/disk0/decodemov.js create mode 100644 assets/disk0/encodemov.js diff --git a/assets/disk0/decodemov.js b/assets/disk0/decodemov.js new file mode 100644 index 0000000..ec88edc --- /dev/null +++ b/assets/disk0/decodemov.js @@ -0,0 +1,54 @@ + +let filename = exec_args[1] +const FBUF_SIZE = 560*448 + +let status = filesystem.open("A", filename, "R") +if (status) return status + +println("Reading...") + +let bytes = filesystem.readAllBytes("A") + +con.clear() + +let readCount = 0 + +function readBytes(length) { + let ret = new Int8Array(length) + for (let k = 0; k < length; k++) { + ret[k] = bytes[readCount] + readCount += 1 + } + return ret +} + +function readInt() { + let b = readBytes(4) + return (b[0] & 255) | ((b[1] & 255) << 8) | ((b[2] & 255) << 16) | ((b[3] & 255) << 24) +} + +function readShort() { + let b = readBytes(2) + return (b[0] & 255) | ((b[1] & 255) << 8) +} + + +let magic = readBytes(8) + +if (String.fromCharCode.apply(null, magic) != '\x1fTSVMMOV') return 1 + +let width = readShort() +let height = readShort() +let fps = readShort() +let frameCount = readInt() % 16777216 + +let fbuf = sys.malloc(FBUF_SIZE) + +for (let f = 0; f < frameCount; f++) { + let payloadLen = readInt() + let gzipped = readBytes(payloadLen) + gzip.decompTo(gzipped, fbuf) + dma.ramToFrame(fbuf, 0, FBUF_SIZE) +} + +sys.free(fbuf) \ No newline at end of file diff --git a/assets/disk0/encodemov.js b/assets/disk0/encodemov.js new file mode 100644 index 0000000..207c6f5 --- /dev/null +++ b/assets/disk0/encodemov.js @@ -0,0 +1,64 @@ + +const FBUF_SIZE = 560*448 +let infile = sys.malloc(120000) // somewhat arbitrary +let imagearea = sys.malloc(FBUF_SIZE*3) +let decodearea = sys.malloc(FBUF_SIZE) +let gzippedImage = sys.malloc(180000) // somewhat arbitrary + +let outfilename = exec_args[1] + +if (!outfilename) return 1 + +function appendToOutfile(bytes) { + filesystem.open("A", outfilename, "A") + filesystem.writeBytes("A", bytes) +} + +function appendToOutfilePtr(ptr, len) { + filesystem.open("A", outfilename, "A") + dma.ramToCom(ptr, 0, len) +} + +// write header to the file +let headerBytes = [ + 0x1F, 0x54, 0x53, 0x56, 0x4D, 0x4D, 0x4F, 0x56, + 0x30, 0x02, + 0xC0, 0x01, + 0x1E, 0x00, + 0x34, 0x00, 0x00, 0x7C +] + +filesystem.open("A", outfilename, "W") +filesystem.writeBytes("A", headerBytes) + +for (let f = 1; f <=52; f++) { + let fname = `/movtestimg/${(''+f).padStart(3,'0')}.jpg` + filesystem.open("A", fname, "R") + let fileLen = filesystem.getFileLen("A") + dma.comToRam(0, 0, infile, fileLen) + + graphics.decodeImageTo(infile, fileLen, imagearea) + + print(`Encoding frame ${f}...`) + + graphics.imageToDisplayableFormat(imagearea, decodearea, 560, 448, 3, 1) + + let gzlen = gzip.compFromTo(decodearea, FBUF_SIZE, gzippedImage) + + let frameSize = [ + (gzlen >>> 0) & 255, + (gzlen >>> 8) & 255, + (gzlen >>> 16) & 255, + (gzlen >>> 24) & 255 + ] + + appendToOutfile(frameSize) + appendToOutfilePtr(gzippedImage, gzlen) + + print(` ${gzlen} bytes\n`) +} + +sys.free(infile) +sys.free(imagearea) +sys.free(decodearea) +sys.free(gzippedImage) \ No newline at end of file diff --git a/assets/disk0/tvdos/TVDOS.SYS b/assets/disk0/tvdos/TVDOS.SYS index 630129b..47cc1f5 100644 --- a/assets/disk0/tvdos/TVDOS.SYS +++ b/assets/disk0/tvdos/TVDOS.SYS @@ -141,7 +141,7 @@ filesystem.write = (driveLetter, string) => { filesystem._flush(port[0]); filesystem._close(port[0]); }; filesystem.writeBytes = (driveLetter, bytes) => { - var string = String.fromCharCode(...bytes); + var string = String.fromCharCode.apply(null, bytes); // no spreading: has length limit filesystem.write(driveLetter, string); }; filesystem.isDirectory = (driveLetter) => { diff --git a/terranmon.txt b/terranmon.txt index 09fff24..73a58a1 100644 --- a/terranmon.txt +++ b/terranmon.txt @@ -269,4 +269,32 @@ SETPAL 5 (15 2 8 15) SETBG (15 2 8 15) D0·00·F2 8F (0xF28F: RGBA colour) END (pseudocommand of WAITFOR) - 80·FF FF FF \ No newline at end of file + 80·FF FF FF + +-------------------------------------------------------------------------------- + +TSVM MOV file format + +Endianness: Little + +\x1F T S V M M O V +[METADATA] +[FRAME0] +[FRAME1] +[FRAME2] +... + + +where: + +METADATA - + uint16 WIDTH + uint16 HEIGHT + uint16 FPS (0: play as fast as can) + uint24 NUMBER OF FRAMES + \x7C + +FRAME - + uint32 SIZE OF FRAMEDATA + * FRAMEDATA COMPRESSED IN GZIP + diff --git a/tsvm_core/src/net/torvald/tsvm/CompressorDelegate.kt b/tsvm_core/src/net/torvald/tsvm/CompressorDelegate.kt index 92a824e..6797339 100644 --- a/tsvm_core/src/net/torvald/tsvm/CompressorDelegate.kt +++ b/tsvm_core/src/net/torvald/tsvm/CompressorDelegate.kt @@ -6,41 +6,70 @@ import java.io.ByteArrayOutputStream import java.util.zip.GZIPInputStream import java.util.zip.GZIPOutputStream -object CompressorDelegate { +class CompressorDelegate(val vm: VM) { - /*fun comp(ba: ByteArray): ByteArray { - val bin = ByteArrayInputStream(ba) - val bout = ByteArrayOutputStream(256) - Lzma.compress(bin, bout) - return bout.toByteArray() + fun comp(str: String) = Companion.comp(str) + fun comp(ba: ByteArray) = Companion.comp(ba) + + fun compFromTo(input: Int, len: Int, output: Int): Int { + val inbytes = ByteArray(len) { vm.peek(input.toLong() + it)!! } + comp(inbytes).let { + it.forEachIndexed { index, byte -> + vm.poke(output.toLong() + index, byte) + } + return it.size + } } - fun decomp(ba: ByteArray): ByteArray { - val bin = ByteArrayInputStream(ba) - val bout = ByteArrayOutputStream(256) - Lzma.decompress(bin, bout) - return bout.toByteArray() - }*/ - - fun comp(str: String) = comp(str.toByteArray(VM.CHARSET)) - - fun comp(ba: ByteArray): ByteArray { - val baos = ByteArrayOutputStream() - val gz = GZIPOutputStream(baos) - gz.write(ba); gz.flush(); gz.finish() - baos.flush(); baos.close() - return baos.toByteArray() + fun compTo(ba: ByteArray, output: Int): Int { + comp(ba).let { + it.forEachIndexed { index, byte -> + vm.poke(output.toLong() + index, byte) + } + return it.size + } } - fun decomp(str: String) = decomp(str.toByteArray(VM.CHARSET)) - fun decomp(ba: ByteArray): ByteArray { - val bais = ByteArrayInputStream(ba) - val gz = GZIPInputStream(bais) - val ret = gz.readBytes() - gz.close(); bais.close() - return ret + fun decomp(str: String) = Companion.decomp(str) + fun decomp(ba: ByteArray) = Companion.decomp(ba) + + fun decompTo(str: String, pointer: Int) { + val bytes = decomp(str) + bytes.forEachIndexed { index, byte -> + vm.poke(pointer.toLong() + index, byte) + } } - val GZIP_HEADER = byteArrayOf(31,-117,8) // .gz in DEFLATE + fun decompTo(ba: ByteArray, pointer: Int) { + val bytes = decomp(ba) + bytes.forEachIndexed { index, byte -> + vm.poke(pointer.toLong() + index, byte) + } + } + + companion object { + val GZIP_HEADER = byteArrayOf(31, -117, 8) // .gz in DEFLATE + + fun comp(str: String) = comp(str.toByteArray(VM.CHARSET)) + + fun comp(ba: ByteArray): ByteArray { + val baos = ByteArrayOutputStream() + val gz = GZIPOutputStream(baos) + gz.write(ba); gz.flush(); gz.finish() + baos.flush(); baos.close() + return baos.toByteArray() + } + + + fun decomp(str: String) = decomp(str.toByteArray(VM.CHARSET)) + + fun decomp(ba: ByteArray): ByteArray { + val bais = ByteArrayInputStream(ba) + val gz = GZIPInputStream(bais) + val ret = gz.readBytes() + gz.close(); bais.close() + return ret + } + } } \ No newline at end of file diff --git a/tsvm_core/src/net/torvald/tsvm/VMRunnerFactory.kt b/tsvm_core/src/net/torvald/tsvm/VMRunnerFactory.kt index 521a736..ae7b2b3 100644 --- a/tsvm_core/src/net/torvald/tsvm/VMRunnerFactory.kt +++ b/tsvm_core/src/net/torvald/tsvm/VMRunnerFactory.kt @@ -65,7 +65,7 @@ object VMRunnerFactory { bind.putMember("sys", VMJSR223Delegate(vm)) // TODO use delegator class to access peripheral (do not expose VM itself) bind.putMember("graphics", GraphicsJSR223Delegate(vm)) bind.putMember("serial", VMSerialDebugger(vm)) - bind.putMember("gzip", CompressorDelegate) + bind.putMember("gzip", CompressorDelegate(vm)) bind.putMember("base64", Base64Delegate) bind.putMember("com", SerialHelperDelegate(vm)) bind.putMember("dma", DMADelegate(vm)) diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt index ca07596..5ba1a57 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/TestDiskDrive.kt @@ -68,6 +68,7 @@ class TestDiskDrive(private val vm: VM, private val driveNum: Int, theRootPath: private var file = File(rootPath.toURI()) //private var readModeLength = -1 // always 4096 private var writeMode = false + private var appendMode = false private var writeModeLength = -1 private val messageComposeBuffer = ByteArrayOutputStream(BLOCK_SIZE) // always use this and don't alter blockSendBuffer please @@ -127,7 +128,7 @@ class TestDiskDrive(private val vm: VM, private val driveNum: Int, theRootPath: * Disk drive must create desired side effects in accordance with the input message. */ override fun writeoutImpl(inputData: ByteArray) { - if (writeMode) { + if (writeMode || appendMode) { //println("[DiskDrive] writeout with inputdata length of ${inputData.size}") //println("[DiskDriveMsg] ${inputData.toString(Charsets.UTF_8)}") @@ -137,9 +138,14 @@ class TestDiskDrive(private val vm: VM, private val driveNum: Int, theRootPath: writeBufferUsage += inputData.size if (writeBufferUsage >= writeModeLength) { - writeMode = false // commit to the disk - file.writeBytes(writeBuffer) + if (appendMode) + file.appendBytes(writeBuffer) + else if (writeMode) + file.writeBytes(writeBuffer) + + writeMode = false + appendMode = false } } else { @@ -385,7 +391,8 @@ class TestDiskDrive(private val vm: VM, private val driveNum: Int, theRootPath: statusCode = STATE_CODE_OPERATION_NOT_PERMITTED return } - writeMode = true + if (fileOpenMode == 1) { writeMode = true; appendMode = false } + else if (fileOpenMode == 2) { writeMode = false; appendMode = true } writeModeLength = inputString.substring(5, inputString.length).toInt() writeBuffer = ByteArray(writeModeLength) writeBufferUsage = 0 diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/VMProgramRom.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/VMProgramRom.kt index 3e4d9b8..0ba4666 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/VMProgramRom.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/VMProgramRom.kt @@ -1,7 +1,7 @@ package net.torvald.tsvm.peripheral import net.torvald.tsvm.CompressorDelegate -import net.torvald.tsvm.CompressorDelegate.GZIP_HEADER +import net.torvald.tsvm.CompressorDelegate.Companion.GZIP_HEADER import net.torvald.tsvm.VM import java.io.File