mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-06 13:38:30 +09:00
Compare commits
142 Commits
libtav
...
d058f11329
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d058f11329 | ||
|
|
60b07a325a | ||
|
|
1e482e32a8 | ||
|
|
4ff48bba1c | ||
|
|
2dcdff83c8 | ||
|
|
89d3c5d776 | ||
|
|
517d0ad9a7 | ||
|
|
9524bf36e0 | ||
|
|
8e17256224 | ||
|
|
ac409bf961 | ||
|
|
94e3ce55ce | ||
|
|
9a9893b9a3 | ||
|
|
789c78f1e7 | ||
|
|
c7e7ee650d | ||
|
|
5d968fecf5 | ||
|
|
aaf3cc28b2 | ||
|
|
24375727db | ||
|
|
6a7ef670d9 | ||
|
|
1bbf0de381 | ||
|
|
5e6ac17146 | ||
|
|
d2b1e792b9 | ||
|
|
219ca1e475 | ||
|
|
902ab00132 | ||
|
|
5dc87a80be | ||
|
|
2b91251d6e | ||
|
|
f84d317f95 | ||
|
|
f295223f15 | ||
|
|
6de9476c4f | ||
|
|
e317d79a21 | ||
|
|
fe59df18f7 | ||
|
|
a4adc428d0 | ||
|
|
31e46b78ce | ||
|
|
ac94a52329 | ||
|
|
01ff4b1d47 | ||
|
|
50802186ce | ||
|
|
7184392521 | ||
|
|
018b9f5eb3 | ||
|
|
bb0810798d | ||
|
|
909f970d60 | ||
|
|
80c26c6b35 | ||
|
|
515e0268e6 | ||
|
|
606fa736af | ||
|
|
89effb5b24 | ||
|
|
376c3c4766 | ||
|
|
0a247897e4 | ||
|
|
b838b35525 | ||
|
|
1148454fb3 | ||
|
|
cfb7b97bf0 | ||
|
|
176aa824fe | ||
|
|
d33484c3c8 | ||
|
|
737b1cebe7 | ||
|
|
176766b793 | ||
|
|
191c733913 | ||
|
|
895f1b27ef | ||
|
|
538d718568 | ||
|
|
b3c5719e3a | ||
|
|
27e4bc1ae5 | ||
|
|
2282e0c10b | ||
|
|
e7287fae37 | ||
|
|
65d89db9c6 | ||
|
|
53173a359c | ||
|
|
8d7d534bc8 | ||
|
|
dc3c73252e | ||
|
|
2053526dfa | ||
|
|
6bc49e3f0b | ||
|
|
02c4f9590c | ||
|
|
34afa95137 | ||
|
|
284108f07f | ||
|
|
76011d4fa9 | ||
|
|
b44d9c6b68 | ||
|
|
e47e9e1259 | ||
|
|
c5789ec28b | ||
|
|
93f7f436a3 | ||
|
|
3ca31e57a1 | ||
|
|
1f630aee62 | ||
|
|
3f3644d165 | ||
|
|
e29f9c3032 | ||
|
|
85b8586a3a | ||
|
|
92b9984ef8 | ||
|
|
3f98d25828 | ||
|
|
d4ea9b2d29 | ||
|
|
a1b62f3155 | ||
|
|
4802e10dfc | ||
|
|
6d70960e5c | ||
|
|
3d99568359 | ||
|
|
1fe966ca09 | ||
|
|
037b2c1a16 | ||
|
|
ea09065802 | ||
|
|
8d28bde119 | ||
|
|
755afb7df4 | ||
|
|
539df453ec | ||
|
|
5e3ffea6d3 | ||
|
|
4bda55d511 | ||
|
|
852c0e6e80 | ||
|
|
25309cf5b6 | ||
|
|
44f11120d8 | ||
|
|
bc16ffabb4 | ||
|
|
ad5e5b62bc | ||
|
|
887c2fbfba | ||
|
|
e58eb2c12b | ||
|
|
3a91edb379 | ||
|
|
74d94b350c | ||
|
|
4559e4f3f6 | ||
|
|
c5bc0d6526 | ||
|
|
a92727862e | ||
|
|
31d25d940b | ||
|
|
460046c4ed | ||
|
|
6eeccb4baa | ||
|
|
a7c44bd05f | ||
|
|
4e647f9fe1 | ||
|
|
b2faab9377 | ||
|
|
e833d75b2c | ||
|
|
f84ea5e68a | ||
|
|
5374ca43c3 | ||
|
|
bef85f6e2f | ||
|
|
f02ad1de79 | ||
|
|
7f0ff3e653 | ||
|
|
8702104bfe | ||
|
|
7d899936e2 | ||
|
|
6aa2542bb8 | ||
|
|
2ac084acd7 | ||
|
|
1208690c4f | ||
|
|
ca977b074d | ||
|
|
102801d8b0 | ||
|
|
10351bafb1 | ||
|
|
9310885260 | ||
|
|
b10d5d3a34 | ||
|
|
86b44565e0 | ||
|
|
54b61fb436 | ||
|
|
4afe3816c7 | ||
|
|
f09dd66185 | ||
|
|
b590415231 | ||
|
|
237d3d6fd2 | ||
|
|
3421d71012 | ||
|
|
5d5576c077 | ||
|
|
64026be133 | ||
|
|
96d697e158 | ||
|
|
1680137b7d | ||
|
|
c71920b95d | ||
|
|
629ed5fb12 | ||
|
|
4f6efbe000 | ||
|
|
4362610c70 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -66,3 +66,7 @@ assets/disk0/home/basic/*
|
|||||||
assets/disk0/movtestimg/*.jpg
|
assets/disk0/movtestimg/*.jpg
|
||||||
assets/disk0/*.mov
|
assets/disk0/*.mov
|
||||||
assets/diskMediabin/*
|
assets/diskMediabin/*
|
||||||
|
|
||||||
|
video_encoder/*
|
||||||
|
|
||||||
|
assets/disk0/tvdos/bin/tautfont.png
|
||||||
|
|||||||
54
.idea/artifacts/TerranBASIC.xml
generated
54
.idea/artifacts/TerranBASIC.xml
generated
@@ -21,36 +21,54 @@
|
|||||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.8.21/kotlin-reflect-1.8.21.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.8.21/kotlin-reflect-1.8.21.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-test/1.8.21/kotlin-test-1.8.21.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-test/1.8.21/kotlin-test-1.8.21.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.21/kotlin-stdlib-common-1.8.21.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.21/kotlin-stdlib-common-1.8.21.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/graal-sdk-22.3.1.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/polyglot-23.1.10.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/icu4j-71.1.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/js-language-23.1.10.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/js-22.3.1-edit.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/js-scriptengine-23.1.10.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/js-scriptengine-22.3.1.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/truffle-api-23.1.10.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/regex-22.3.1-edit.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/truffle-runtime-23.1.10.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/truffle-api-22.3.1.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/truffle-compiler-23.1.10.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/compiler-23.1.10.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/compiler-management-23.1.10.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/word-23.1.10.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/regex-23.1.10.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/icu4j-23.1.10.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/collections-23.1.10.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/nativeimage-23.1.10.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/jniutils-23.1.10.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/gdx-1.12.1.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/gdx-1.12.1.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-3.3.3.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-3.3.3.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-stb-3.3.3.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-stb-3.3.3.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-glfw-3.3.3.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-glfw-3.3.3.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/js-22.3.1-javadoc.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/js-language-23.1.10-javadoc.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/js-22.3.1-sources.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/js-language-23.1.10-sources.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/gdx-1.12.1-javadoc.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/gdx-1.12.1-javadoc.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/gdx-1.12.1-sources.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/gdx-1.12.1-sources.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/icu4j-71.1-javadoc.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/icu4j-23.1.10-javadoc.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/icu4j-71.1-sources.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/icu4j-23.1.10-sources.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-openal-3.3.3.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-openal-3.3.3.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-opengl-3.3.3.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-opengl-3.3.3.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-3.3.3-javadoc.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-3.3.3-javadoc.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-3.3.3-sources.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-3.3.3-sources.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-jemalloc-3.3.3.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-jemalloc-3.3.3.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/regex-22.3.1-javadoc.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/regex-23.1.10-javadoc.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/regex-22.3.1-sources.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/regex-23.1.10-sources.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/TerranVirtualDisk-src.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/TerranVirtualDisk-src.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/jorbis-0.0.17-javadoc.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/jorbis-0.0.17-javadoc.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/jorbis-0.0.17-sources.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/jorbis-0.0.17-sources.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-stb-3.3.3-javadoc.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-stb-3.3.3-javadoc.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-stb-3.3.3-sources.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-stb-3.3.3-sources.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/graal-sdk-22.3.1-javadoc.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/polyglot-23.1.10-javadoc.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/graal-sdk-22.3.1-sources.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/polyglot-23.1.10-sources.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/truffle-api-23.1.10-javadoc.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/truffle-api-23.1.10-sources.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/truffle-runtime-23.1.10-javadoc.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/truffle-runtime-23.1.10-sources.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/collections-23.1.10-javadoc.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/collections-23.1.10-sources.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/nativeimage-23.1.10-javadoc.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/nativeimage-23.1.10-sources.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/jniutils-23.1.10-javadoc.jar" path-in-jar="/" />
|
||||||
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/jniutils-23.1.10-sources.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/jlayer-1.0.1-gdx-javadoc.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/jlayer-1.0.1-gdx-javadoc.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/jlayer-1.0.1-gdx-sources.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/jlayer-1.0.1-gdx-sources.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-glfw-3.3.3-javadoc.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-glfw-3.3.3-javadoc.jar" path-in-jar="/" />
|
||||||
@@ -62,15 +80,15 @@
|
|||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-openal-3.3.3-sources.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-openal-3.3.3-sources.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-opengl-3.3.3-javadoc.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-opengl-3.3.3-javadoc.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-opengl-3.3.3-sources.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-opengl-3.3.3-sources.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/truffle-api-22.3.1-javadoc.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/regex-23.1.10-javadoc.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/truffle-api-22.3.1-sources.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/regex-23.1.10-sources.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-3.3.3-natives-windows.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-3.3.3-natives-windows.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-jemalloc-3.3.3-javadoc.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-jemalloc-3.3.3-javadoc.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-jemalloc-3.3.3-sources.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-jemalloc-3.3.3-sources.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-stb-3.3.3-natives-linux.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-stb-3.3.3-natives-linux.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-stb-3.3.3-natives-macos.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-stb-3.3.3-natives-macos.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/js-scriptengine-22.3.1-javadoc.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/js-scriptengine-23.1.10-javadoc.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/js-scriptengine-22.3.1-sources.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/js-scriptengine-23.1.10-sources.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-glfw-3.3.3-natives-linux.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-glfw-3.3.3-natives-linux.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-glfw-3.3.3-natives-macos.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/lwjgl-glfw-3.3.3-natives-macos.jar" path-in-jar="/" />
|
||||||
<element id="extracted-dir" path="$PROJECT_DIR$/lib/gdx-jnigen-loader-2.3.1-javadoc.jar" path-in-jar="/" />
|
<element id="extracted-dir" path="$PROJECT_DIR$/lib/gdx-jnigen-loader-2.3.1-javadoc.jar" path-in-jar="/" />
|
||||||
|
|||||||
133
.idea/cody_history.xml
generated
Normal file
133
.idea/cody_history.xml
generated
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ChatHistory">
|
||||||
|
<accountData>
|
||||||
|
<list>
|
||||||
|
<AccountData>
|
||||||
|
<accountId value="VXNlcjo1NTUxMzY=" />
|
||||||
|
<chats>
|
||||||
|
<list>
|
||||||
|
<chat>
|
||||||
|
<internalId value="8a32c880-d8ec-4d84-b5f7-a62b2edf63c3" />
|
||||||
|
<llm>
|
||||||
|
<llm>
|
||||||
|
<model value="anthropic/claude-3-5-sonnet-20240620" />
|
||||||
|
<provider value="Anthropic" />
|
||||||
|
<title value="Claude 3.5 Sonnet" />
|
||||||
|
<usage>
|
||||||
|
<list>
|
||||||
|
<option value="chat" />
|
||||||
|
<option value="edit" />
|
||||||
|
</list>
|
||||||
|
</usage>
|
||||||
|
</llm>
|
||||||
|
</llm>
|
||||||
|
</chat>
|
||||||
|
<chat>
|
||||||
|
<internalId value="f4e7338b-57d3-44e0-b1d9-04aa2dfb4bae" />
|
||||||
|
<llm>
|
||||||
|
<llm>
|
||||||
|
<model value="anthropic/claude-3-5-sonnet-20240620" />
|
||||||
|
<provider value="Anthropic" />
|
||||||
|
<title value="Claude 3.5 Sonnet" />
|
||||||
|
<usage>
|
||||||
|
<list>
|
||||||
|
<option value="chat" />
|
||||||
|
<option value="edit" />
|
||||||
|
</list>
|
||||||
|
</usage>
|
||||||
|
</llm>
|
||||||
|
</llm>
|
||||||
|
</chat>
|
||||||
|
<chat>
|
||||||
|
<internalId value="26ac815b-5db9-488b-be0b-ab174fbc5646" />
|
||||||
|
<llm>
|
||||||
|
<llm>
|
||||||
|
<model value="anthropic/claude-3-5-sonnet-20240620" />
|
||||||
|
<provider value="Anthropic" />
|
||||||
|
<title value="Claude 3.5 Sonnet" />
|
||||||
|
<usage>
|
||||||
|
<list>
|
||||||
|
<option value="chat" />
|
||||||
|
<option value="edit" />
|
||||||
|
</list>
|
||||||
|
</usage>
|
||||||
|
</llm>
|
||||||
|
</llm>
|
||||||
|
</chat>
|
||||||
|
<chat>
|
||||||
|
<internalId value="8ab39b26-cdda-4256-878f-e0416e66bbea" />
|
||||||
|
<llm>
|
||||||
|
<llm>
|
||||||
|
<model value="anthropic/claude-3-5-sonnet-20240620" />
|
||||||
|
<provider value="Anthropic" />
|
||||||
|
<title value="Claude 3.5 Sonnet" />
|
||||||
|
<usage>
|
||||||
|
<list>
|
||||||
|
<option value="chat" />
|
||||||
|
<option value="edit" />
|
||||||
|
</list>
|
||||||
|
</usage>
|
||||||
|
</llm>
|
||||||
|
</llm>
|
||||||
|
</chat>
|
||||||
|
<chat>
|
||||||
|
<internalId value="f79a288a-adc5-4d45-a069-4bf024d90236" />
|
||||||
|
<llm>
|
||||||
|
<llm>
|
||||||
|
<model value="anthropic/claude-3-5-sonnet-20240620" />
|
||||||
|
<provider value="Anthropic" />
|
||||||
|
<title value="Claude 3.5 Sonnet" />
|
||||||
|
<usage>
|
||||||
|
<list>
|
||||||
|
<option value="chat" />
|
||||||
|
<option value="edit" />
|
||||||
|
</list>
|
||||||
|
</usage>
|
||||||
|
</llm>
|
||||||
|
</llm>
|
||||||
|
</chat>
|
||||||
|
<chat>
|
||||||
|
<internalId value="20bc02fd-c6b5-4590-a00b-b7012a630ef4" />
|
||||||
|
<llm>
|
||||||
|
<llm>
|
||||||
|
<model value="anthropic/claude-3-5-sonnet-20240620" />
|
||||||
|
<provider value="Anthropic" />
|
||||||
|
<title value="Claude 3.5 Sonnet" />
|
||||||
|
<usage>
|
||||||
|
<list>
|
||||||
|
<option value="chat" />
|
||||||
|
<option value="edit" />
|
||||||
|
</list>
|
||||||
|
</usage>
|
||||||
|
</llm>
|
||||||
|
</llm>
|
||||||
|
</chat>
|
||||||
|
</list>
|
||||||
|
</chats>
|
||||||
|
<defaultLlm>
|
||||||
|
<llm>
|
||||||
|
<model value="anthropic/claude-3-5-sonnet-20240620" />
|
||||||
|
<provider value="Anthropic" />
|
||||||
|
<tags>
|
||||||
|
<list>
|
||||||
|
<option value="gateway" />
|
||||||
|
<option value="accuracy" />
|
||||||
|
<option value="recommended" />
|
||||||
|
<option value="free" />
|
||||||
|
</list>
|
||||||
|
</tags>
|
||||||
|
<title value="Claude 3.5 Sonnet" />
|
||||||
|
<usage>
|
||||||
|
<list>
|
||||||
|
<option value="chat" />
|
||||||
|
<option value="edit" />
|
||||||
|
</list>
|
||||||
|
</usage>
|
||||||
|
</llm>
|
||||||
|
</defaultLlm>
|
||||||
|
</AccountData>
|
||||||
|
</list>
|
||||||
|
</accountData>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
9
.idea/markdown.xml
generated
Normal file
9
.idea/markdown.xml
generated
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="MarkdownSettings">
|
||||||
|
<option name="previewPanelProviderInfo">
|
||||||
|
<ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
|
||||||
|
</option>
|
||||||
|
<option name="splitLayout" value="SHOW_EDITOR" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
17
.idea/runConfigurations/AppLoader.xml
generated
Normal file
17
.idea/runConfigurations/AppLoader.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="AppLoader" type="Application" factoryName="Application" nameIsGenerated="true">
|
||||||
|
<option name="ALTERNATIVE_JRE_PATH" value="21" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="net.torvald.tsvm.AppLoader" />
|
||||||
|
<module name="tsvm_executable" />
|
||||||
|
<option name="VM_PARAMETERS" value="-ea --upgrade-module-path=lib/compiler-23.1.10.jar:lib/compiler-management-23.1.10.jar:lib/truffle-compiler-23.1.10.jar:lib/truffle-api-23.1.10.jar:lib/truffle-runtime-23.1.10.jar:lib/polyglot-23.1.10.jar:lib/collections-23.1.10.jar:lib/word-23.1.10.jar:lib/nativeimage-23.1.10.jar:lib/jniutils-23.1.10.jar -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseZGC -XX:+UseDynamicNumberOfGCThreads --add-exports=java.base/jdk.internal.misc=jdk.internal.vm.compiler" />
|
||||||
|
<extension name="coverage">
|
||||||
|
<pattern>
|
||||||
|
<option name="PATTERN" value="net.torvald.tsvm.*" />
|
||||||
|
<option name="ENABLED" value="true" />
|
||||||
|
</pattern>
|
||||||
|
</extension>
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
16
.idea/runConfigurations/TerranBASIC.xml
generated
Normal file
16
.idea/runConfigurations/TerranBASIC.xml
generated
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="TerranBASIC" type="Application" factoryName="Application" nameIsGenerated="true">
|
||||||
|
<option name="MAIN_CLASS_NAME" value="net.torvald.tsvm.TerranBASIC" />
|
||||||
|
<module name="TerranBASICexecutable" />
|
||||||
|
<option name="VM_PARAMETERS" value="--upgrade-module-path=lib/compiler-23.1.10.jar:lib/compiler-management-23.1.10.jar:lib/truffle-compiler-23.1.10.jar:lib/truffle-api-23.1.10.jar:lib/truffle-runtime-23.1.10.jar:lib/polyglot-23.1.10.jar:lib/collections-23.1.10.jar:lib/word-23.1.10.jar:lib/nativeimage-23.1.10.jar:lib/jniutils-23.1.10.jar -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI --add-exports=java.base/jdk.internal.misc=jdk.internal.vm.compiler" />
|
||||||
|
<extension name="coverage">
|
||||||
|
<pattern>
|
||||||
|
<option name="PATTERN" value="net.torvald.tsvm.*" />
|
||||||
|
<option name="ENABLED" value="true" />
|
||||||
|
</pattern>
|
||||||
|
</extension>
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
16
.idea/runConfigurations/TsvmEmulator.xml
generated
Normal file
16
.idea/runConfigurations/TsvmEmulator.xml
generated
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="TsvmEmulator" type="Application" factoryName="Application" nameIsGenerated="true">
|
||||||
|
<option name="MAIN_CLASS_NAME" value="net.torvald.tsvm.TsvmEmulator" />
|
||||||
|
<module name="tsvm_executable" />
|
||||||
|
<option name="VM_PARAMETERS" value="-ea --upgrade-module-path=lib/compiler-23.1.10.jar:lib/compiler-management-23.1.10.jar:lib/truffle-compiler-23.1.10.jar:lib/truffle-api-23.1.10.jar:lib/truffle-runtime-23.1.10.jar:lib/polyglot-23.1.10.jar:lib/collections-23.1.10.jar:lib/word-23.1.10.jar:lib/nativeimage-23.1.10.jar:lib/jniutils-23.1.10.jar -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseZGC -XX:+UseDynamicNumberOfGCThreads --add-exports=java.base/jdk.internal.misc=jdk.internal.vm.compiler" />
|
||||||
|
<extension name="coverage">
|
||||||
|
<pattern>
|
||||||
|
<option name="PATTERN" value="net.torvald.tsvm.*" />
|
||||||
|
<option name="ENABLED" value="true" />
|
||||||
|
</pattern>
|
||||||
|
</extension>
|
||||||
|
<method v="2">
|
||||||
|
<option name="Make" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
2
.idea/vcs.xml
generated
2
.idea/vcs.xml
generated
@@ -2,5 +2,7 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/assets/disk0/home/tetrino" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/assets/disk0/home/tvnes" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
8
2taud.sh
Executable file
8
2taud.sh
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env fish
|
||||||
|
|
||||||
|
for f in *.mod; python3 mod2taud.py $f assets/disk0/(basename $f .mod).taud; end
|
||||||
|
for f in *.s3m; python3 s3m2taud.py $f assets/disk0/(basename $f .s3m).taud; end
|
||||||
|
for f in *.it; python3 it2taud.py $f assets/disk0/(basename $f .it).taud; end
|
||||||
|
for f in *.xm; python3 xm2taud.py $f assets/disk0/(basename $f .xm).taud; end
|
||||||
|
for f in *.mon; python3 mon2taud.py $f assets/disk0/(basename $f .mon).taud; end
|
||||||
|
for f in *.MON; python3 mon2taud.py $f assets/disk0/(basename $f .MON).taud; end
|
||||||
42
CLAUDE.md
42
CLAUDE.md
@@ -12,6 +12,36 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
- TerranBASIC integration
|
- TerranBASIC integration
|
||||||
- Multiple platform build system
|
- Multiple platform build system
|
||||||
|
|
||||||
|
## Documentations
|
||||||
|
|
||||||
|
Documentation for TSVM and TVDOS are available on `./doc/*.tex` as machine-readable format.
|
||||||
|
|
||||||
|
Documentatino for TSVM architecture is available on `terranmon.txt`
|
||||||
|
|
||||||
|
## Reference Materials
|
||||||
|
|
||||||
|
Third-party source-code references that inform TSVM implementations live in
|
||||||
|
`reference_materials/<topic>/`. Each topic folder has a `README.md` that
|
||||||
|
summarises the takeaway and points back into the verbatim source files.
|
||||||
|
**Consult these before reimplementing tracker / codec / DSP behaviour from
|
||||||
|
memory** — TSVM aims to match the audible behaviour of the originals.
|
||||||
|
|
||||||
|
Current topics:
|
||||||
|
|
||||||
|
- `reference_materials/tracker_filter/` — Impulse Tracker / OpenMPT / Schism
|
||||||
|
Tracker resonant low-pass filter source. Defines the cutoff formula, the
|
||||||
|
resonance damping curve, and the **IIR-only 2-pole topology** (NOT a
|
||||||
|
biquad — no feedforward x[n−1] / x[n−2] terms) that `AudioAdapter.kt` uses
|
||||||
|
for Taud playback.
|
||||||
|
- `reference_materials/ft2-clone` — Modernised clone for the original FastTracker 2
|
||||||
|
- `reference_materials/impulse-tracker` — The original source code for ImpulseTracker
|
||||||
|
- `reference_materials/MilkyTracker` — FastTracker 2 compatible tracker
|
||||||
|
- `reference_materials/schismtracker` — Open-source re-implementation of ImpulseTracker
|
||||||
|
|
||||||
|
When fetching new references, copy the relevant upstream files verbatim into
|
||||||
|
a topic folder, write a `README.md` summarising the relevant maths /
|
||||||
|
algorithms with file:line citations, and add an entry here.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
### Core Components
|
### Core Components
|
||||||
@@ -62,12 +92,12 @@ Use the build scripts in `buildapp/`:
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
1. Download JDK 17 runtimes to `~/Documents/openjdk/*` with specific naming:
|
1. Download JDK 21 runtimes to `~/Documents/openjdk/*` with specific naming:
|
||||||
- `jdk-17.0.1-x86` (Linux AMD64)
|
- `jdk-21.0.1-x86` (Linux AMD64)
|
||||||
- `jdk-17.0.1-arm` (Linux Aarch64)
|
- `jdk-21.0.1-arm` (Linux Aarch64)
|
||||||
- `jdk-17.0.1-windows` (Windows AMD64)
|
- `jdk-21.0.1-windows` (Windows AMD64)
|
||||||
- `jdk-17.0.1.jdk-arm` (macOS Apple Silicon)
|
- `jdk-21.0.1.jdk-arm` (macOS Apple Silicon)
|
||||||
- `jdk-17.0.1.jdk-x86` (macOS Intel)
|
- `jdk-21.0.1.jdk-x86` (macOS Intel)
|
||||||
|
|
||||||
2. Run `jlink` commands to create custom Java runtimes in `out/runtime-*` directories
|
2. Run `jlink` commands to create custom Java runtimes in `out/runtime-*` directories
|
||||||
|
|
||||||
|
|||||||
224
README.md
224
README.md
@@ -1,8 +1,222 @@
|
|||||||

|

|
||||||
|
|
||||||
**tsvm** /tiː.ɛs.viː.ɛm/ is a virtual machine with the architecture that mimics the 8-bit era of
|
# tsvm
|
||||||
computers, and runs programs written in Javascript.
|
|
||||||
|
|
||||||
**tsvm** repository includes the virtual machine itself, the reference BIOS
|
**tsvm** /tiː.ɛs.viː.ɛm/ is a fantasy computer platform: a virtual machine whose
|
||||||
implementation and a DOS; BASIC is provided by the [TerranBASIC](https://github.com/curioustorvald/TerranBASIC)
|
architecture is inspired by the 8-bit and early 16-bit home computers, built
|
||||||
repository.
|
from the ground up around running JavaScript as its native machine code.
|
||||||
|
|
||||||
|
What started as "an 8-bit-flavoured VM that runs JS" has grown into a complete,
|
||||||
|
self-hosted retro computing ecosystem — with its own BIOS, operating system,
|
||||||
|
filesystem, video and audio codecs, video display coprocessor with its own
|
||||||
|
assembly language, tracker music format, and a stack of userland tools that
|
||||||
|
together come closer to a small alternate-history computer line than a
|
||||||
|
single-binary emulator.
|
||||||
|
|
||||||
|
This repository contains the virtual machine core, the reference BIOS
|
||||||
|
implementations, the **TVDOS** operating system, the **Videotron2K** video
|
||||||
|
display controller, hardware-accelerated codec backends for the **TEV / TAV /
|
||||||
|
TAD** media formats, and the multi-platform packaging scripts. The
|
||||||
|
[TerranBASIC](https://github.com/curioustorvald/TerranBASIC) repository
|
||||||
|
provides the matching BASIC dialect that ships on the system disk.
|
||||||
|
|
||||||
|
## What's actually in here
|
||||||
|
|
||||||
|
### The virtual machine
|
||||||
|
|
||||||
|
- **VM core** (`tsvm_core/`) — memory model, peripheral bus, MMIO, JS
|
||||||
|
sandboxing through GraalVM, watchdog, DMA engine, and cooperative scheduling.
|
||||||
|
Up to 8 hot-pluggable peripheral slots, each with a dedicated MMIO window
|
||||||
|
and memory-space window mapped into the VM's negative address range.
|
||||||
|
- **Multiple BIOS implementations** (`assets/bios/`) — including the reference
|
||||||
|
`tsvmbios.js`, an OpenBIOS variant, the TBM-BIOS for TerranBASIC machines,
|
||||||
|
and the Pip-Boy-style `pipboot.rom`. BIOSes are first-class swappable
|
||||||
|
components, not a fixed boot blob.
|
||||||
|
- **Reference monitor / debugger** (`mon.js`) for poking at memory and
|
||||||
|
peripherals from a running machine.
|
||||||
|
- **Multi-platform packaging** (`buildapp/`) — scripts to produce Linux x86_64
|
||||||
|
/ ARM64 AppImages, macOS Intel / Apple Silicon bundles, and Windows builds,
|
||||||
|
each with its own `jlink`-trimmed JDK 21 runtime.
|
||||||
|
|
||||||
|
### Peripherals (the "hardware")
|
||||||
|
|
||||||
|
Living under `tsvm_core/src/net/torvald/tsvm/peripheral/`:
|
||||||
|
|
||||||
|
- **Graphics adapters** — the standard `GraphicsAdapter`, plus `TexticsAdapter`
|
||||||
|
for text-mode framebuffers, `ExtDisp` for external displays, and a
|
||||||
|
`RemoteGraphicsAdapter` for networked rendering.
|
||||||
|
- **Audio devices** — `AudioAdapter` (the main programmable sound chip with
|
||||||
|
PCM channels, an Impulse Tracker-style resonant low-pass filter, and a
|
||||||
|
hardware-accelerated **TAD** decoder), `OpenALBufferedAudioDevice`, and the
|
||||||
|
`MP2Env` MPEG audio environment.
|
||||||
|
- **Disk drives** — `TevdDiskDrive` (TEVD custom filesystem),
|
||||||
|
`ClusteredDiskDrive`, `TestDiskDrive`, and a latency-simulator script for
|
||||||
|
testing slow-storage behaviour.
|
||||||
|
- **Networking and serial** — `HttpModem`, `HSDPA` / `HostFileHSDPA` for
|
||||||
|
high-speed packet I/O, `SerialStdioHost`, `BlockTransferInterface` /
|
||||||
|
`BlockTransferPort`.
|
||||||
|
- **Terminals and displays** — `TTY`, `GlassTty`, `TermSim`, and a
|
||||||
|
`CharacterLCDdisplay` for HD44780-flavoured projects.
|
||||||
|
- **Memory expansion** — `RamBank` for bank-switched memory, plus a
|
||||||
|
programmable `TestFunctionGenerator`.
|
||||||
|
|
||||||
|
### Videotron2K — the video coprocessor
|
||||||
|
|
||||||
|
Videotron2K is a programmable video display controller with its **own
|
||||||
|
assembly-like language**, six general registers (`r1`–`r6`), special registers
|
||||||
|
(`tmr`, `frm`, `px`, `py`, `c1`–`c6`), a scene-based programming model, and
|
||||||
|
conditional postfixes (`zr`, `nz`, `gt`, `ls`, `ge`, `le`). Programs declare
|
||||||
|
`SCENE` blocks and dispatch them with `perform`. Drawing primitives include
|
||||||
|
`plot`, `fillin`, `fillscr`, and `goto`. See `Videotron2K.md` and the VDC
|
||||||
|
implementation under `tsvm_core/.../vdc/`.
|
||||||
|
|
||||||
|
### TVDOS — the operating system
|
||||||
|
|
||||||
|
`assets/disk0/tvdos/` is a complete DOS-style userland:
|
||||||
|
|
||||||
|
- **Kernel and drivers** — `TVDOS.SYS`, `HSDPADRV.SYS`, `hyve.SYS`,
|
||||||
|
installable drivers under `moviedev/` and `tuidev/`.
|
||||||
|
- **Custom filesystem** — TEVD, with the on-disk format documented in
|
||||||
|
`tvdos/filesystem.md`.
|
||||||
|
- **Internationalisation** — Colemak / Dvorak / QWERTY keymaps and an `i18n/`
|
||||||
|
resource tree.
|
||||||
|
- **Userland binaries** (`tvdos/bin/`) — a shell (`command.js`), file tools
|
||||||
|
(`hexdump`, `less`, `tee`, `touch`, `printfile`, `writeto`, `defrag`,
|
||||||
|
`lfs`, `drives`), an editor (`edit.js`), a file manager (`zfm.js`), a
|
||||||
|
network fetcher (`geturl`), gzip/Zstd helpers, palette tools, and a battery
|
||||||
|
of media players (`playmp2`, `playpcm`, `playwav`, `playmv1`, `playtev`,
|
||||||
|
`playtav`, `playtad`, `playucf`).
|
||||||
|
- **Taut tracker** — a full in-VM tracker (`taut.js`,
|
||||||
|
`taut_instredit.js`, `taut_sampleedit.js`, `taut_notationedit.js`,
|
||||||
|
`taut_fileop.js`) with its own font and chrome assets.
|
||||||
|
|
||||||
|
### Codecs and media formats
|
||||||
|
|
||||||
|
tsvm ships a small but serious codec lab. Encoders are written in C and live
|
||||||
|
in `video_encoder/`; decoders are split between JavaScript players in TVDOS
|
||||||
|
and hardware-accelerated Kotlin backends in the VM core.
|
||||||
|
|
||||||
|
- **iPF (Type 1 / 2 / 1-delta)** — picture and legacy movie format. Encoders:
|
||||||
|
`encodeipf.js`, `encodemov.js`, `encodemov2.js`. Documented in
|
||||||
|
`terranmon.txt`.
|
||||||
|
- **TEV (TSVM Enhanced Video)** — modern DCT codec with motion compensation,
|
||||||
|
16×16 blocks, YCoCg-R 4:2:0, and either quality-mode or bitrate-mode rate
|
||||||
|
control. Encoder: `video_encoder/encoder_tev.c`. Decoder: `playtev.js`,
|
||||||
|
with `tevDecode` / `tevIdct8x8` / `tevMotionCopy8x8` accelerated in
|
||||||
|
`GraphicsJSR223Delegate.kt`.
|
||||||
|
- **TAV (TSVM Advanced Video)** — successor to TEV based on the Discrete
|
||||||
|
Wavelet Transform. Five wavelet types (5/3 reversible, 9/7 irreversible,
|
||||||
|
CDF 13/7, DD-4, Haar), 6-level decomposition, EZBC sparsity coding,
|
||||||
|
perceptual quantisation, and an optional **3D temporal DWT** that encodes
|
||||||
|
whole groups of pictures as one unified wavelet tree. Includes a packet
|
||||||
|
inspector (`tav_inspector.c`) and coefficient visualiser
|
||||||
|
(`tav_visualise_coefficients.c`).
|
||||||
|
- **TAD (TSVM Advanced Audio)** — perceptual audio codec at 32 kHz stereo,
|
||||||
|
using CDF 9/7 wavelets, M/S decorrelation, gamma compression, pre-emphasis,
|
||||||
|
EZBC, and Zstd. Achieves ~2.5:1 compression vs. PCMu8 at quality 3 while
|
||||||
|
preserving the full 0–16 kHz band. Designed to be embeddable inside TAV so
|
||||||
|
audio chunks can align with video GOP boundaries.
|
||||||
|
- **Taud** — tracker module format with conversion tools from
|
||||||
|
the major formats: `it2taud.py` (Impulse Tracker), `mod2taud.py`
|
||||||
|
(ProTracker / FastTracker), `s3m2taud.py` (Scream Tracker 3), plus
|
||||||
|
`2taud.sh` and shared helpers in `taud_common.py`. Note effects are
|
||||||
|
documented in `TAUD_NOTE_EFFECTS.md`. The `AudioAdapter` runs the same
|
||||||
|
IIR-only 2-pole resonant low-pass topology used by Impulse Tracker /
|
||||||
|
OpenMPT / Schism.
|
||||||
|
- **MP2** — reference MPEG-1 Layer II environment via `MP2Env.kt` and
|
||||||
|
`playmp2.js`.
|
||||||
|
|
||||||
|
### Languages and runtimes
|
||||||
|
|
||||||
|
- **JavaScript** is the VM's native code, executed by GraalVM in a sandboxed
|
||||||
|
context with a curated set of host bindings (graphics, audio, filesystem,
|
||||||
|
DMA, compression, networking, low-level peek/poke).
|
||||||
|
- **TerranBASIC** is provided by the
|
||||||
|
[TerranBASIC](https://github.com/curioustorvald/TerranBASIC) repository and
|
||||||
|
shipped as `tbas` on the system disk. The `TerranBASICexecutable/` subproject
|
||||||
|
packages a BASIC-only flavour of the machine.
|
||||||
|
- **Videotron2K assembly** for VDC programs.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- `terranmon.txt` — the architecture reference (memory map, peripheral
|
||||||
|
protocol, codec bitstreams).
|
||||||
|
- `doc/*.tex` — machine-readable LaTeX sources for the TSVM and TVDOS manuals,
|
||||||
|
built with `doc/makepdf.sh`.
|
||||||
|
- `Videotron2K.md` — VDC programming guide.
|
||||||
|
- `TAUD_NOTE_EFFECTS.md` — tracker effect reference.
|
||||||
|
- `CLAUDE.md` — a condensed map of the project for collaborators (and
|
||||||
|
language-model assistants) working in the tree.
|
||||||
|
|
||||||
|
## Building and running
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
JDK 21 runtimes laid out under `~/Documents/openjdk/` with platform-specific
|
||||||
|
names:
|
||||||
|
|
||||||
|
- `jdk-21.0.1-x86` — Linux AMD64
|
||||||
|
- `jdk-21.0.1-arm` — Linux Aarch64
|
||||||
|
- `jdk-21.0.1-windows` — Windows AMD64
|
||||||
|
- `jdk-21.0.1.jdk-x86` — macOS Intel
|
||||||
|
- `jdk-21.0.1.jdk-arm` — macOS Apple Silicon
|
||||||
|
|
||||||
|
`jlink` is then used to produce trimmed runtimes under `out/runtime-*`.
|
||||||
|
|
||||||
|
### Common entry points
|
||||||
|
|
||||||
|
- **Run the emulator** — `TsvmEmulator.java` (in `tsvm_executable/`).
|
||||||
|
- **Run TerranBASIC-only build** — `TerranBASIC.java` (in
|
||||||
|
`TerranBASICexecutable/`).
|
||||||
|
- **Package an installable bundle** — pick the right script in `buildapp/`:
|
||||||
|
- `build_app_linux_x86.sh`
|
||||||
|
- `build_app_linux_arm.sh`
|
||||||
|
- `build_app_mac_x86.sh`
|
||||||
|
- `build_app_mac_arm.sh`
|
||||||
|
- `build_app_windows_x86.sh`
|
||||||
|
- **Build C encoders** — in `video_encoder/`: `make` (TEV), `make tav`,
|
||||||
|
`make tad`.
|
||||||
|
|
||||||
|
### Encoding sample media
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Quality-mode TEV encode
|
||||||
|
./encoder_tev -i input.mp4 -o clip.tev -q 3
|
||||||
|
|
||||||
|
# TAV with 9/7 wavelet, quality 4
|
||||||
|
./encoder_tav -i input.mp4 -w 1 -q 4 -o clip.tav
|
||||||
|
|
||||||
|
# TAV with 3D temporal DWT (GOP-unified encoding)
|
||||||
|
./encoder_tav -i input.mp4 --temporal-dwt -o clip.tav
|
||||||
|
|
||||||
|
# TAD audio at the highest quality
|
||||||
|
./encoder_tad -i input.mp4 -o track.tad -q 5
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, inside TVDOS:
|
||||||
|
|
||||||
|
```
|
||||||
|
A:\> playtev clip.tev
|
||||||
|
A:\> playtav clip.tav
|
||||||
|
A:\> playtad track.tad
|
||||||
|
```
|
||||||
|
|
||||||
|
## Repository layout
|
||||||
|
|
||||||
|
```
|
||||||
|
tsvm_core/ VM core, peripherals, VDC, JS bindings (Kotlin)
|
||||||
|
tsvm_executable/ Main emulator GUI (LibGDX)
|
||||||
|
TerranBASICexecutable/ For creatingTerranBASIC executable
|
||||||
|
assets/bios/ BIOS ROMs and source
|
||||||
|
assets/disk0/ Boot disk image, including all of TVDOS
|
||||||
|
video_encoder/ C encoders, decoder libs, inspectors (TEV / TAV / TAD)
|
||||||
|
ipf_encoder/ Reference iPF encoder
|
||||||
|
doc/ LaTeX sources for the TSVM / TVDOS manuals
|
||||||
|
buildapp/ Per-platform packaging scripts
|
||||||
|
My_BASIC_Programs/ Example BASIC programs
|
||||||
|
*.py, *.sh, *.kts Conversion tools and ad-hoc utilities
|
||||||
|
```
|
||||||
|
|
||||||
|
## Licence
|
||||||
|
|
||||||
|
See `COPYING`.
|
||||||
|
|||||||
1059
TAUD_NOTE_EFFECTS.md
Normal file
1059
TAUD_NOTE_EFFECTS.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -122,8 +122,39 @@ class VMGUI(val loaderInfo: EmulInstance, val viewportWidth: Int, val viewportHe
|
|||||||
private var rebootRequested = false
|
private var rebootRequested = false
|
||||||
|
|
||||||
private fun reboot() {
|
private fun reboot() {
|
||||||
vmRunner.close()
|
// Order is critical: stop ALL execution first, then dispose peripherals
|
||||||
coroutineJob.interrupt()
|
// before re-initialising. Without this, the old JS thread races the new
|
||||||
|
// one on shared VM memory / IO state and can SIGSEGV on disposed peripherals.
|
||||||
|
|
||||||
|
// 1. Stop parallel/child contexts. park() interrupts and joins them.
|
||||||
|
vm.park()
|
||||||
|
vm.poke(-90L, -128)
|
||||||
|
|
||||||
|
// 2. Interrupt the main runner thread and cancel the GraalVM context.
|
||||||
|
if (::coroutineJob.isInitialized) coroutineJob.interrupt()
|
||||||
|
try { if (::vmRunner.isInitialized) vmRunner.close() } catch (_: Throwable) {}
|
||||||
|
|
||||||
|
// 3. Wait for the main runner thread to actually finish.
|
||||||
|
if (::coroutineJob.isInitialized && coroutineJob !== Thread.currentThread()) {
|
||||||
|
try {
|
||||||
|
coroutineJob.join(2000L)
|
||||||
|
if (coroutineJob.isAlive) {
|
||||||
|
System.err.println("[VMGUI] runner ${vm.id} did not exit within 2s; proceeding anyway")
|
||||||
|
coroutineJob.interrupt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (_: InterruptedException) {
|
||||||
|
Thread.currentThread().interrupt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Now it's safe to release native resources held by peripherals.
|
||||||
|
for (i in 1 until vm.peripheralTable.size) {
|
||||||
|
try {
|
||||||
|
vm.peripheralTable[i].peripheral?.dispose()
|
||||||
|
}
|
||||||
|
catch (_: Throwable) {}
|
||||||
|
}
|
||||||
|
|
||||||
vm.init()
|
vm.init()
|
||||||
init()
|
init()
|
||||||
|
|||||||
Binary file not shown.
@@ -1,5 +1,5 @@
|
|||||||
con.reset_graphics();con.curs_set(0);con.clear();
|
con.reset_graphics();con.curs_set(0);con.clear();
|
||||||
graphics.resetPalette();graphics.setBackground(0,0,0);
|
graphics.resetPalette();graphics.setPalette(0, 0, 0, 0, 15);graphics.setBackground(0,0,0);
|
||||||
|
|
||||||
let logo = gzip.decomp(base64.atob("H4sICJoBTGECA3Rzdm1sb2dvLnJhdwDtneu2nCoQhPf7v6xLEMUL5lxyVk6yhxm7mmZGpfqnK7uC+gkN1TA/fhTFF+Ni8eOjwedPXsgLeSEvDPLCIC8M8sIgL+SFvJAX8kJeGOSFQV4Y5IVBXsgLeSEv5IW8MMgLow1e1i4XfH/kJR8deSEvcl48eSEvAC+RvJAXgJedvJAXOS9DR17Ii5yXSF7IC8DLTl7Ii5yX0JEX8iLnZSUv5EXOy7Nsl7yQF6h7IS/kBcheyAt5eYx+Jy/kRc7L0pEX8iLmZezIC3kR8zJ05IW8iHnxO3khL2JeDnAhL+Tlj8HoABfyQl6kqS55IS9/rrssHXkhL1Jewt6RF/Ii5GVYO4vYctouxGVLe2cXXvHg3TeN3eeu6rR9lRafl5ewGr3I6RHEOXXmMSse/PeSwTV7Vac9V2nxSXkZotmnv/ffvulYAZZ//h8HP/f+e0tC9qpK2+01WnxSXtZq372bu1oxwc/9u+mesld12lOVFp+Ul65SXtHHrl5s8HNfs+9vNdHeqrT4/rz8/kxC6mrGUJiR/hwfvIn2UKXFDfAyIhlgWSyFGenyopWo9lKlxffn5f9s122VcUHzx4casCF7VaXt9hotboCX+OsJpq56ROipj9mRczTRjlVa3AAvTmhym0QqykjHl3kqpp2qtPj+vKxY/1waoSAj/TlyDibaoUqLG+AlvG8w+h1PTUY6H+SpiPZapcX35yX18sWIN5tIDz2eP+oH5dq+Sosb4GV6z0RaY8lM2Q99MtGeq7S4AV4cOJqbm1XyjDQc5qli7X6v0uL787J8PfHv6sVobh3h2mOVFjfAi4fWIt5qIq3ZhZDVRHur0uL787J95auPTmAiPSwHOckikUx7qNLiBngZ35zsApZMzP5VNNFeqrT4/rz8zOTe3L3ILBnIOgK14aVJ3ES6Jy/z+7OX3+bwmHXUy/JUifZUpcUN8OIhJ+WtJhJmHWHaqUqL78/Lqkr+3mIi+ezI6U20Q5UWN8BL+ES2K7Nk5uzIOZtor1VafH9e/rOO0vt56RyakXp5nnqoXaXFDfAyfWLx5fe1N3lGugF5agQn6jYtboCXt1tHj664NCMdgZ7wQFvpfaS+dV6Wr8/MpgWWzJB9WYOJ9lilxQ3wMujWOt9hIi3ZwWAx0d6qtPj+vGyFz89k6UeY7TpsVdYbFUrJVS+wfxrBp2DxalIUf0gwXMytI5n2Ujp+t87LbrsQLk0TXlkye3adSG76vNAuqGqHTKT78vL6L3stL4cvZpIXSvXoPG4ytI503w55QeNoLTaJh7IJzrOSoXWkM5E4HqFxmFgO5tbRsXaZVzaQl2r57rFNswo7pkXhcq2G1pHKRLovL2Xz6T1tSwxOZQM7WaGUhwv6n2qXeh+OvNis16V5wBfeo6xQSrUqGw2tI42JdF9erPyAFB2onLdkZIVSq0b7kOBN1eK2eDH0G2eH9f5BkJHm99jvXqN9eKuDRrUxXkzrGWKPDHWr2jqKKu2jTmlRqTbGi229VArI7NVrC6W8Rlsww1eoNseLcT3mDKA4H2ZT69OruLZkBRFXbY4X63rvzYlX3x93ssv22AeNdi9xKPAWN8eLeQFvcmoTSWYd/XsV1j5EwZXZXs3wYl5ht3vpELAdZKTTi6uo9iYaalDVBnmxr/j+Zf2DJpLPLqjmr6LawlRWbXu1w0uFHUi/hiSsbEpWKLWotBdhx1FS6NUILxW2lGzS6mr3KiMdnl9FtQ/vcdSotslLjT0CMzApwayjDZrwwFO13iTjvTcvNc4jC7iJJLOORo1BBZifOturKV5qbFr777ECRo/QOurlC7ZBfoNeo9osLzU23Ue0bEp2PPOsKslCire0hV4t8VJjG5LDvmyxdfSF9xpQnwH0Re3yUuE8+BkzkWTHM6/Q0vSsKj43MJFuz0uN35tw0MxEbh3Bsx5wzmNgIt2flwq/ZxNlII7ZbDe/x/7b5ESoDW6eE6o2zov9kJSQlVXZ8cwRrD7eVGu20rXgtnmx/z2+QebcDLn1V/f19CriCg3SfwSrkpdatVOSzxuzjuTzukXVXRSbSI3wYvx7wklmyfydPz6svw7ZVdnhcPtJThtPRwSq5OXnVMLUS3LS6cmYJW18Oe2VaiumO8UmUjO8/J0zGA5KQbj80cv22E+KITT1muWUY1Xy8j8x0WpUisLl1Sk7wfWvp71C7cMO02tUA3n5Y4YwmyCzCC2ZlP3kZ9G66pH20dCymp4W0Cgv//QyIS5bKlvE25T+t3++897cWw86VUde8OgnoS+TFJhNwlWysp4wKVUjedHEa2B2XQXfUaGUZXVgVKq+znjJy7MeRvY/O/wHWQfpmkeRU/r0FMMyE+navPQf5wU6ZubZHvtnUXKEzaJWXa/MS61T6KzGI2jXrc9aR77Kjt5Br+ovzEu1U+iM8l2kgO/5Hnv74sCtQHW+MC8fOtUdeB3yk29D1joK6k5O2/OWlE2dnZflnLwsgCXzZ58UhNNeTBvyDUtMpLPzEs/JS1TUSrzaY29dhzEXqW7X5SWck5eAWDKwdQRrQylr0d77s/PizsmLw3Os/PHMS5X8bStUXS7Ly0d+tRNca5edoft6j/2z0P1q2lio+rzXOz0v8xl5mfGs9GCPvWnGe1gld6gaL8vLcEZeBjwpx6yjsoQ/Fqumy/JyxgEp4UkWaB2VJXCuXDVclpcTzqgjWoQk2WP/LPCfHlkNVNfL8nLCGZLDZ/2odVSyohAMVHd/VV7Ol/E+9gqHpdcpuxAvOoUdPvNIdO5Pr9x7fwFe3Om7F6ElA1lHehNpMlF9klpdgJezZTBRw/SIWkf678XZqI6X5aU/1RQp391LtqauAvDKPdfFSHW7LC/nMpGC1pIBrSOtieStVIfL8nKmlHdWWzJR2RFgJtJmprpcl5fzlE1takvGJ8n3W2wijWaq2f7vIry4k6QwyaktmUXdESAm0t7bqU7X5aXGKXQaI8/ZjZnyjgDRng1V04V5qXAKnQIXb1fatCOV6nJtb6kaLszLCYak5AyNHqQjkGuvpqrrlXmxP4UOTXWd5azfQ/cu1Q6mqpnh90K8fHhafdghQMuKG3bnQu3U26rGa/NifAodNBYJvlzE6Angncu0J2PVxyTrWrwYn0IHeEaSDxcwenZ0X6ZM21mrjhfnxfYUOvFQJHwPcqMnwvct0V7MVbfL82J5Cp1sJIrir1Zca7w7+K4l2oO9qr8+L19mp9AJYJmhdyCdwa2Kez7W3iqozrfg5cvmFLpXPUDalhjQbkBq9ATFDR9rjxVUv/eEl+WF8ZEgLwzywiAvDPLC509eyAt5IS8M8sIgLwzywiAv5IW8kBfyQl4Y5IVBXhjkhUFeyAt5IS/khbwwyAuDvDDIC+OWvPwFgd7gz8BmAQA="));
|
let logo = gzip.decomp(base64.atob("H4sICJoBTGECA3Rzdm1sb2dvLnJhdwDtneu2nCoQhPf7v6xLEMUL5lxyVk6yhxm7mmZGpfqnK7uC+gkN1TA/fhTFF+Ni8eOjwedPXsgLeSEvDPLCIC8M8sIgL+SFvJAX8kJeGOSFQV4Y5IVBXsgLeSEv5IW8MMgLow1e1i4XfH/kJR8deSEvcl48eSEvAC+RvJAXgJedvJAXOS9DR17Ii5yXSF7IC8DLTl7Ii5yX0JEX8iLnZSUv5EXOy7Nsl7yQF6h7IS/kBcheyAt5eYx+Jy/kRc7L0pEX8iLmZezIC3kR8zJ05IW8iHnxO3khL2JeDnAhL+Tlj8HoABfyQl6kqS55IS9/rrssHXkhL1Jewt6RF/Ii5GVYO4vYctouxGVLe2cXXvHg3TeN3eeu6rR9lRafl5ewGr3I6RHEOXXmMSse/PeSwTV7Vac9V2nxSXkZotmnv/ffvulYAZZ//h8HP/f+e0tC9qpK2+01WnxSXtZq372bu1oxwc/9u+mesld12lOVFp+Ul65SXtHHrl5s8HNfs+9vNdHeqrT4/rz8/kxC6mrGUJiR/hwfvIn2UKXFDfAyIhlgWSyFGenyopWo9lKlxffn5f9s122VcUHzx4casCF7VaXt9hotboCX+OsJpq56ROipj9mRczTRjlVa3AAvTmhym0QqykjHl3kqpp2qtPj+vKxY/1waoSAj/TlyDibaoUqLG+AlvG8w+h1PTUY6H+SpiPZapcX35yX18sWIN5tIDz2eP+oH5dq+Sosb4GV6z0RaY8lM2Q99MtGeq7S4AV4cOJqbm1XyjDQc5qli7X6v0uL787J8PfHv6sVobh3h2mOVFjfAi4fWIt5qIq3ZhZDVRHur0uL787J95auPTmAiPSwHOckikUx7qNLiBngZ35zsApZMzP5VNNFeqrT4/rz8zOTe3L3ILBnIOgK14aVJ3ES6Jy/z+7OX3+bwmHXUy/JUifZUpcUN8OIhJ+WtJhJmHWHaqUqL78/Lqkr+3mIi+ezI6U20Q5UWN8BL+ES2K7Nk5uzIOZtor1VafH9e/rOO0vt56RyakXp5nnqoXaXFDfAyfWLx5fe1N3lGugF5agQn6jYtboCXt1tHj664NCMdgZ7wQFvpfaS+dV6Wr8/MpgWWzJB9WYOJ9lilxQ3wMujWOt9hIi3ZwWAx0d6qtPj+vGyFz89k6UeY7TpsVdYbFUrJVS+wfxrBp2DxalIUf0gwXMytI5n2Ujp+t87LbrsQLk0TXlkye3adSG76vNAuqGqHTKT78vL6L3stL4cvZpIXSvXoPG4ytI503w55QeNoLTaJh7IJzrOSoXWkM5E4HqFxmFgO5tbRsXaZVzaQl2r57rFNswo7pkXhcq2G1pHKRLovL2Xz6T1tSwxOZQM7WaGUhwv6n2qXeh+OvNis16V5wBfeo6xQSrUqGw2tI42JdF9erPyAFB2onLdkZIVSq0b7kOBN1eK2eDH0G2eH9f5BkJHm99jvXqN9eKuDRrUxXkzrGWKPDHWr2jqKKu2jTmlRqTbGi229VArI7NVrC6W8Rlsww1eoNseLcT3mDKA4H2ZT69OruLZkBRFXbY4X63rvzYlX3x93ssv22AeNdi9xKPAWN8eLeQFvcmoTSWYd/XsV1j5EwZXZXs3wYl5ht3vpELAdZKTTi6uo9iYaalDVBnmxr/j+Zf2DJpLPLqjmr6LawlRWbXu1w0uFHUi/hiSsbEpWKLWotBdhx1FS6NUILxW2lGzS6mr3KiMdnl9FtQ/vcdSotslLjT0CMzApwayjDZrwwFO13iTjvTcvNc4jC7iJJLOORo1BBZifOturKV5qbFr777ECRo/QOurlC7ZBfoNeo9osLzU23Ue0bEp2PPOsKslCire0hV4t8VJjG5LDvmyxdfSF9xpQnwH0Re3yUuE8+BkzkWTHM6/Q0vSsKj43MJFuz0uN35tw0MxEbh3Bsx5wzmNgIt2flwq/ZxNlII7ZbDe/x/7b5ESoDW6eE6o2zov9kJSQlVXZ8cwRrD7eVGu20rXgtnmx/z2+QebcDLn1V/f19CriCg3SfwSrkpdatVOSzxuzjuTzukXVXRSbSI3wYvx7wklmyfydPz6svw7ZVdnhcPtJThtPRwSq5OXnVMLUS3LS6cmYJW18Oe2VaiumO8UmUjO8/J0zGA5KQbj80cv22E+KITT1muWUY1Xy8j8x0WpUisLl1Sk7wfWvp71C7cMO02tUA3n5Y4YwmyCzCC2ZlP3kZ9G66pH20dCymp4W0Cgv//QyIS5bKlvE25T+t3++897cWw86VUde8OgnoS+TFJhNwlWysp4wKVUjedHEa2B2XQXfUaGUZXVgVKq+znjJy7MeRvY/O/wHWQfpmkeRU/r0FMMyE+navPQf5wU6ZubZHvtnUXKEzaJWXa/MS61T6KzGI2jXrc9aR77Kjt5Br+ovzEu1U+iM8l2kgO/5Hnv74sCtQHW+MC8fOtUdeB3yk29D1joK6k5O2/OWlE2dnZflnLwsgCXzZ58UhNNeTBvyDUtMpLPzEs/JS1TUSrzaY29dhzEXqW7X5SWck5eAWDKwdQRrQylr0d77s/PizsmLw3Os/PHMS5X8bStUXS7Ly0d+tRNca5edoft6j/2z0P1q2lio+rzXOz0v8xl5mfGs9GCPvWnGe1gld6gaL8vLcEZeBjwpx6yjsoQ/Fqumy/JyxgEp4UkWaB2VJXCuXDVclpcTzqgjWoQk2WP/LPCfHlkNVNfL8nLCGZLDZ/2odVSyohAMVHd/VV7Ol/E+9gqHpdcpuxAvOoUdPvNIdO5Pr9x7fwFe3Om7F6ElA1lHehNpMlF9klpdgJezZTBRw/SIWkf678XZqI6X5aU/1RQp391LtqauAvDKPdfFSHW7LC/nMpGC1pIBrSOtieStVIfL8nKmlHdWWzJR2RFgJtJmprpcl5fzlE1takvGJ8n3W2wijWaq2f7vIry4k6QwyaktmUXdESAm0t7bqU7X5aXGKXQaI8/ZjZnyjgDRng1V04V5qXAKnQIXb1fatCOV6nJtb6kaLszLCYak5AyNHqQjkGuvpqrrlXmxP4UOTXWd5azfQ/cu1Q6mqpnh90K8fHhafdghQMuKG3bnQu3U26rGa/NifAodNBYJvlzE6Angncu0J2PVxyTrWrwYn0IHeEaSDxcwenZ0X6ZM21mrjhfnxfYUOvFQJHwPcqMnwvct0V7MVbfL82J5Cp1sJIrir1Zca7w7+K4l2oO9qr8+L19mp9AJYJmhdyCdwa2Kez7W3iqozrfg5cvmFLpXPUDalhjQbkBq9ATFDR9rjxVUv/eEl+WF8ZEgLwzywiAvDPLC509eyAt5IS8M8sIgLwzywiAv5IW8kBfyQl4Y5IVBXhjkhUFeyAt5IS/khbwwyAuDvDDIC+OWvPwFgd7gz8BmAQA="));
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ tmr = sys.nanoTime();
|
|||||||
while (sys.nanoTime() - tmr < 2147483648) sys.spin();
|
while (sys.nanoTime() - tmr < 2147483648) sys.spin();
|
||||||
// clear screen
|
// clear screen
|
||||||
graphics.clearPixels(255);con.color_pair(239,255);
|
graphics.clearPixels(255);con.color_pair(239,255);
|
||||||
con.clear();con.move(1,1);
|
con.clear();con.move(1,1);graphics.resetPalette();
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ set PATH=\tvdos\installer;\tvdos\tuidev;$PATH
|
|||||||
set KEYBOARD=us_colemak
|
set KEYBOARD=us_colemak
|
||||||
|
|
||||||
rem this line specifies which shell to be presented after the boot precess:
|
rem this line specifies which shell to be presented after the boot precess:
|
||||||
|
tvdos/i18n/korean
|
||||||
zfm
|
zfm
|
||||||
command -fancy
|
command -fancy
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
if (exec_args[1] === undefined) {
|
if (exec_args[1] === undefined) {
|
||||||
println("Usage: compile -le/-lo myfile.js")
|
println("Usage: compile -le/-lo myfile.js")
|
||||||
println(" The compiled and linked file will be myfile.out")
|
println(" The compiled and linked file will be myfile.exc")
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ if (exec_args[2]) {
|
|||||||
_G.shell.execute(`rm ${tempFilename}.gz`)
|
_G.shell.execute(`rm ${tempFilename}.gz`)
|
||||||
|
|
||||||
_G.shell.execute(`link -${exec_args[1][2]} ${tempFilename}.bin`)
|
_G.shell.execute(`link -${exec_args[1][2]} ${tempFilename}.bin`)
|
||||||
_G.shell.execute(`mv ${tempFilename}.out ${filenameWithoutExt}.out`)
|
_G.shell.execute(`mv ${tempFilename}.exc ${filenameWithoutExt}.exc`)
|
||||||
_G.shell.execute(`rm ${tempFilename}.bin`)
|
_G.shell.execute(`rm ${tempFilename}.bin`)
|
||||||
}
|
}
|
||||||
// with no linking
|
// with no linking
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
if (exec_args[1] === undefined) {
|
if (exec_args[1] === undefined) {
|
||||||
println("Usage: decompile myfile.bin")
|
println("Usage: decompile myfile.exc")
|
||||||
println("The compiled file will be myfile.bin.js")
|
println("The compiled file will be myfile.exc.js")
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
_G.shell.execute(`enc ${exec_args[1]} ${exec_args[1]}.gz`)
|
_G.shell.execute(`enc ${exec_args[1]} ${exec_args[1]}.gz`)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// a simple, symmetric obfuscator with infinite-length key
|
||||||
|
|
||||||
function seq(s) {
|
function seq(s) {
|
||||||
let out = ""
|
let out = ""
|
||||||
let cnt = 0
|
let cnt = 0
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ let infile = files.open(infilePath)
|
|||||||
|
|
||||||
if (!infile.exists) throw Error("No such file: " + infilePath)
|
if (!infile.exists) throw Error("No such file: " + infilePath)
|
||||||
|
|
||||||
let outfile = files.open(infilePath.substringBeforeLast(".") + ".out")
|
|
||||||
let outMode = exec_args[1].toLowerCase()
|
let outMode = exec_args[1].toLowerCase()
|
||||||
|
|
||||||
let type = {
|
let type = {
|
||||||
@@ -21,6 +20,13 @@ let type = {
|
|||||||
"-c": "\x04"
|
"-c": "\x04"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let ext = {
|
||||||
|
"-r": ".exc", // executable
|
||||||
|
"-e": ".exc", // executable
|
||||||
|
"-o": ".lib", // library
|
||||||
|
"-c": ".cob" // core object
|
||||||
|
}
|
||||||
|
|
||||||
function toI32(num) {
|
function toI32(num) {
|
||||||
const buffer = new ArrayBuffer(4)
|
const buffer = new ArrayBuffer(4)
|
||||||
const view = new DataView(buffer)
|
const view = new DataView(buffer)
|
||||||
@@ -40,6 +46,7 @@ let addr = 0
|
|||||||
if (exec_args[3] !== undefined && exec_args[3].toLowerCase() == "-a" && exec_args[4] !== undefined)
|
if (exec_args[3] !== undefined && exec_args[3].toLowerCase() == "-a" && exec_args[4] !== undefined)
|
||||||
addr = parseInt(exec_args[4], 16)
|
addr = parseInt(exec_args[4], 16)
|
||||||
|
|
||||||
|
let outfile = files.open(infilePath.substringBeforeLast(".") + ext[exec_args[3].toLowerCase()])
|
||||||
outfile.sappend("\x20\xC0\xCC\x0A")
|
outfile.sappend("\x20\xC0\xCC\x0A")
|
||||||
outfile.sappend(type[outMode] || "\x00")
|
outfile.sappend(type[outMode] || "\x00")
|
||||||
outfile.bappend(toI24(addr))
|
outfile.bappend(toI24(addr))
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
if (exec_args[1] === undefined) {
|
if (exec_args[1] === undefined) {
|
||||||
println("Usage: load myfile.out")
|
println("Usage: load myfile.exc")
|
||||||
println(" This will load the binary image onto the Core Memory")
|
println(" This will load the binary image onto the Core Memory")
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ music.pread(samples, 65534)
|
|||||||
|
|
||||||
audio.setPcmMode(0)
|
audio.setPcmMode(0)
|
||||||
audio.setMasterVolume(0, 255)
|
audio.setMasterVolume(0, 255)
|
||||||
audio.putPcmDataByPtr(samples, 65534, 0)
|
audio.putPcmDataByPtr(0, samples, 65534, 0)
|
||||||
audio.setLoopPoint(0, 65534)
|
audio.setLoopPoint(0, 65534)
|
||||||
audio.play(0)*/
|
audio.play(0)*/
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ while (sampleSize > 0) {
|
|||||||
let readLength = (sampleSize < BLOCK_SIZE) ? sampleSize : BLOCK_SIZE
|
let readLength = (sampleSize < BLOCK_SIZE) ? sampleSize : BLOCK_SIZE
|
||||||
readBytes(readLength, decodePtr)
|
readBytes(readLength, decodePtr)
|
||||||
|
|
||||||
audio.putPcmDataByPtr(decodePtr, readLength, 0)
|
audio.putPcmDataByPtr(0, decodePtr, readLength, 0)
|
||||||
audio.setSampleUploadLength(0, readLength)
|
audio.setSampleUploadLength(0, readLength)
|
||||||
audio.startSampleUpload(0)
|
audio.startSampleUpload(0)
|
||||||
|
|
||||||
|
|||||||
21
assets/disk0/tracker_play.js
Normal file
21
assets/disk0/tracker_play.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
const taud = require("taud")
|
||||||
|
|
||||||
|
const fullFilePath = _G.shell.resolvePathInput(exec_args[1])
|
||||||
|
if (fullFilePath === undefined) {
|
||||||
|
println(`Usage: ${exec_args[0]} path_to.taud`)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const PLAYHEAD = 0
|
||||||
|
|
||||||
|
println("Playing "+fullFilePath.full)
|
||||||
|
|
||||||
|
audio.resetParams(PLAYHEAD)
|
||||||
|
audio.purgeQueue(PLAYHEAD)
|
||||||
|
audio.stop(PLAYHEAD)
|
||||||
|
|
||||||
|
taud.uploadTaudFile(fullFilePath.full, 0, PLAYHEAD)
|
||||||
|
audio.setMasterVolume(PLAYHEAD, 255)
|
||||||
|
audio.setMasterPan(PLAYHEAD, 128)
|
||||||
|
audio.setCuePosition(PLAYHEAD, 0)
|
||||||
|
audio.play(PLAYHEAD)
|
||||||
186
assets/disk0/tracker_test.js
Normal file
186
assets/disk0/tracker_test.js
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
// Tracker Mode — Bach's Prelude in C Major (BWV 846)
|
||||||
|
// Run from the TVDOS shell: js tracker_test.js
|
||||||
|
// Uploads ~92 patterns on startup (takes a moment).
|
||||||
|
|
||||||
|
// -- Note table (12-TET, 4096-TET encoding) ------------------------------------
|
||||||
|
// C3 = 0x4000; each semitone = 4096/12 ≈ 341.33 steps; each octave = 4096 steps.
|
||||||
|
// Sharp suffix: s (e.g. Cs3); flat aliases also provided (e.g. Db3 = Cs3).
|
||||||
|
// Special values: Note.OFF = key-off, Note.CUT = note cut, Note.NOP = no-op.
|
||||||
|
var Note = (function() {
|
||||||
|
var SEMITONE = 4096 / 12;
|
||||||
|
var C3 = 0x4000;
|
||||||
|
function n(oct, semi) { return Math.round(C3 + (oct - 3) * 4096 + semi * SEMITONE) & 0xFFFF; }
|
||||||
|
var t = {};
|
||||||
|
var names = ['C','Cs','D','Ds','E','F','Fs','G','Gs','A','As','B'];
|
||||||
|
var flats = ['C','Db','D','Eb','E','F','Gb','G','Ab','A','Bb','B'];
|
||||||
|
for (var oct = 0; oct <= 9; oct++) {
|
||||||
|
for (var s = 0; s < 12; s++) {
|
||||||
|
t[names[s] + oct] = n(oct, s);
|
||||||
|
if (flats[s] !== names[s]) t[flats[s] + oct] = n(oct, s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.OFF = 0x0000; // key-off
|
||||||
|
t.CUT = 0xFFFE; // note cut (immediate)
|
||||||
|
t.NOP = 0xFFFF; // no-op (empty row)
|
||||||
|
return t;
|
||||||
|
}());
|
||||||
|
|
||||||
|
var PLAYHEAD = 0;
|
||||||
|
|
||||||
|
// -- 1. Sample: triangle wave (256 samples @ C3) --------------------------------
|
||||||
|
var SAMPLE_LEN = 256;
|
||||||
|
var sampleBytes = new Array(SAMPLE_LEN);
|
||||||
|
for (var i = 0; i < SAMPLE_LEN; i++) {
|
||||||
|
var phase = (i / SAMPLE_LEN) * 2.0;
|
||||||
|
var val_ = phase < 1.0 ? phase : 2.0 - phase;
|
||||||
|
sampleBytes[i] = Math.round(val_ * 254) & 0xFF;
|
||||||
|
}
|
||||||
|
var memBase = audio.getMemAddr();
|
||||||
|
for (var i = 0; i < SAMPLE_LEN; i++) {
|
||||||
|
sys.poke(memBase - i, sampleBytes[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- 2. Instrument 0 -----------------------------------------------------------
|
||||||
|
var instBytes = new Array(64).fill(0);
|
||||||
|
instBytes[2] = 0; instBytes[3] = 1; // sampleLength = 256
|
||||||
|
instBytes[4] = 0x00; instBytes[5] = 0x7D; // samplingRate = 32000
|
||||||
|
instBytes[10] = 0x00; instBytes[11] = 0x01; // sampleLoopEnd = 256 (whole sample)
|
||||||
|
instBytes[12] = 1; // loopMode = 1 (forward)
|
||||||
|
instBytes[16] = 255; instBytes[17] = 0; // envelope: vol=255, hold
|
||||||
|
audio.uploadInstrument(1, instBytes);
|
||||||
|
|
||||||
|
// -- 3. Piano-roll builder -----------------------------------------------------
|
||||||
|
// Source convention: C1=0, C2=12, C3=24, C4=36 (i.e. C3=24, octave every 12).
|
||||||
|
function midiToTsvm(n) {
|
||||||
|
var oct = Math.floor(n / 12) + 1;
|
||||||
|
return Math.round(0x3000 + oct * 4096 + (n % 12) * (4096 / 12)) & 0xFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
var noteMap = {}; // absRow → TSVM note value
|
||||||
|
var rowCursor = 0;
|
||||||
|
|
||||||
|
function seq(notes, lens) {
|
||||||
|
for (var i = 0; i < notes.length; i++) {
|
||||||
|
noteMap[rowCursor] = midiToTsvm(notes[i]);
|
||||||
|
rowCursor += lens[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var TD = 3; // rows per note step (= source TICK_DIVISOR)
|
||||||
|
|
||||||
|
function prel(n1, n2, n3, n4, n5) {
|
||||||
|
seq([n1, n2, n3, n4, n5, n3, n4, n5, n1, n2, n3, n4, n5, n3, n4, n5],
|
||||||
|
[TD+2, TD, TD, TD-1, TD, TD, TD, TD, TD, TD, TD, TD-1, TD, TD, TD, TD]);
|
||||||
|
}
|
||||||
|
function end1(n1,n2,n3,n4,n5,n6,n7,n8,n9) {
|
||||||
|
seq([n1, n2, n3, n4, n5, n6, n5, n4, n5, n7, n8, n7, n8, n9, n8, n9],
|
||||||
|
[TD+2, TD, TD, TD-1, TD, TD, TD, TD, TD, TD, TD, TD-1, TD, TD, TD, TD]);
|
||||||
|
}
|
||||||
|
function end2(n1,n2,n3,n4,n5,n6,n7,n8,n9) {
|
||||||
|
seq([n1, n2, n3, n4, n5, n6, n5, n4, n5, n4, n3, n4, n7, n8, n9, n7],
|
||||||
|
[TD+2, TD+1, TD+1, TD+1, TD+1, TD+2, TD+2, TD+2,
|
||||||
|
TD+3, TD+3, TD+4, TD+4, TD+6, TD+8, TD+12, TD+24]);
|
||||||
|
}
|
||||||
|
function end3(ns) {
|
||||||
|
for (var i = 0; i < ns.length; i++) {
|
||||||
|
noteMap[rowCursor] = midiToTsvm(ns[i]);
|
||||||
|
rowCursor += 1;
|
||||||
|
}
|
||||||
|
for (var i = 0; i < TD*2; i++) {
|
||||||
|
noteMap[rowCursor] = Note.NOP
|
||||||
|
rowCursor += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- 4. Build the piece --------------------------------------------------------
|
||||||
|
rowCursor = 16 * TD; // 160-row intro silence
|
||||||
|
|
||||||
|
prel(24,28,31,36,40);
|
||||||
|
prel(24,26,33,38,41);
|
||||||
|
prel(23,26,31,38,41);
|
||||||
|
prel(24,28,31,36,40);
|
||||||
|
prel(24,28,33,40,45);
|
||||||
|
prel(24,26,30,33,38);
|
||||||
|
prel(23,26,31,38,43);
|
||||||
|
prel(23,24,28,31,36);
|
||||||
|
prel(21,24,28,31,36);
|
||||||
|
prel(14,21,26,30,36);
|
||||||
|
prel(19,23,26,31,35);
|
||||||
|
prel(19,22,28,31,37);
|
||||||
|
prel(17,21,26,33,38);
|
||||||
|
prel(17,20,26,29,35);
|
||||||
|
prel(16,19,24,31,36);
|
||||||
|
prel(16,17,21,24,29);
|
||||||
|
prel(14,17,21,24,29);
|
||||||
|
prel( 7,14,19,23,29);
|
||||||
|
prel(12,16,19,24,28);
|
||||||
|
prel(12,19,22,24,28);
|
||||||
|
prel( 5,17,21,24,28);
|
||||||
|
prel( 6,12,21,24,27);
|
||||||
|
prel( 8,17,23,24,26);
|
||||||
|
prel( 7,17,19,23,26);
|
||||||
|
prel( 7,16,19,24,28);
|
||||||
|
prel( 7,14,19,24,29);
|
||||||
|
prel( 7,14,19,23,29);
|
||||||
|
prel( 7,15,21,24,30);
|
||||||
|
prel( 7,16,19,24,31);
|
||||||
|
prel( 7,14,19,24,29);
|
||||||
|
prel( 7,14,19,23,29);
|
||||||
|
prel( 0,12,19,22,28);
|
||||||
|
end1( 0,12,17,21,24,29,21,17,14);
|
||||||
|
end2( 0,11,31,35,38,41,26,29,28);
|
||||||
|
end3([0,12,28,31,36]);
|
||||||
|
|
||||||
|
noteMap[rowCursor] = Note.OFF; // key-off at start of final silence
|
||||||
|
rowCursor += 16 * TD - 5; // 155 more rows of silence
|
||||||
|
|
||||||
|
var totalRows = rowCursor; // 5836
|
||||||
|
var NUM_ROWS = 64;
|
||||||
|
var numPatterns = Math.ceil(totalRows / NUM_ROWS); // 92
|
||||||
|
|
||||||
|
// -- 5. Build and upload patterns ----------------------------------------------
|
||||||
|
for (var p = 0; p < numPatterns; p++) {
|
||||||
|
var patBytes = new Array(512).fill(0);
|
||||||
|
for (var r = 0; r < NUM_ROWS; r++) {
|
||||||
|
var absRow = p * NUM_ROWS + r;
|
||||||
|
var noteVal = (noteMap[absRow] !== undefined) ? noteMap[absRow] : Note.NOP;
|
||||||
|
var isOn = (noteVal !== Note.NOP && noteVal !== Note.OFF && noteVal !== Note.CUT);
|
||||||
|
var off = r * 8;
|
||||||
|
patBytes[off] = noteVal & 0xFF;
|
||||||
|
patBytes[off + 1] = (noteVal >> 8) & 0xFF;
|
||||||
|
patBytes[off + 2] = 1; // instrument 1
|
||||||
|
patBytes[off + 3] = 63; // volume
|
||||||
|
patBytes[off + 4] = 31; // pan (centre)
|
||||||
|
}
|
||||||
|
audio.uploadPattern(p, patBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- 6. Cue sheet: one entry per pattern, last halts -------------------------
|
||||||
|
// Cue format: 32 bytes, 20 voices with 12-bit pattern numbers packed as:
|
||||||
|
// bytes 0-9: low nybbles (byte i = voice i*2 in hi-nybble, voice i*2+1 in lo-nybble)
|
||||||
|
// bytes 10-19: mid nybbles (same packing)
|
||||||
|
// bytes 20-29: high nybbles (same packing)
|
||||||
|
// byte 30: instruction (0=NOP, 1=Halt)
|
||||||
|
// Voice 0 plays pattern c; voices 1-19 are disabled (0xFFF).
|
||||||
|
for (var c = 0; c < numPatterns; c++) {
|
||||||
|
var cueBytes = new Array(32).fill(0xFF);
|
||||||
|
// voice 0 = c (12-bit), voice 1 = 0xFFF → byte0=(c&0xF)<<4|0xF
|
||||||
|
cueBytes[0] = ((c & 0xF) << 4) | 0xF; // lo nybbles v0,v1
|
||||||
|
cueBytes[10] = (((c >> 4) & 0xF) << 4) | 0xF; // mid nybbles v0,v1
|
||||||
|
cueBytes[20] = (((c >> 8) & 0xF) << 4) | 0xF; // hi nybbles v0,v1
|
||||||
|
cueBytes[30] = (c === numPatterns - 1) ? 0x01 : 0;
|
||||||
|
audio.uploadCue(c, cueBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- 7. Playback ---------------------------------------------------------------
|
||||||
|
// BPM=500, tickRate=1: 1 row = 5 ms; 10 rows/step × 16 steps/bar ≈ 75 bars/min.
|
||||||
|
audio.setTrackerMode(PLAYHEAD);
|
||||||
|
audio.setBPM(PLAYHEAD, 250);
|
||||||
|
audio.setTickRate(PLAYHEAD, 6);
|
||||||
|
audio.setMasterVolume(PLAYHEAD, 255);
|
||||||
|
audio.setMasterPan(PLAYHEAD, 128);
|
||||||
|
audio.setCuePosition(PLAYHEAD, 0);
|
||||||
|
audio.play(PLAYHEAD);
|
||||||
|
|
||||||
|
println("Bach's Prelude in C Major -- " + numPatterns + " patterns loaded.");
|
||||||
|
println("Stop: audio.stop(" + PLAYHEAD + ")");
|
||||||
@@ -147,7 +147,7 @@ _TVDOS.variables = {
|
|||||||
LANG: "EN",
|
LANG: "EN",
|
||||||
KEYBOARD: "us_qwerty",
|
KEYBOARD: "us_qwerty",
|
||||||
PATH: "\\tvdos\\bin;\\home",
|
PATH: "\\tvdos\\bin;\\home",
|
||||||
PATHEXT: ".com;.bat;.app;.js",
|
PATHEXT: ".com;.bat;.app;.js;.alias",
|
||||||
HELPPATH: "\\tvdos\\help",
|
HELPPATH: "\\tvdos\\help",
|
||||||
OS_NAME: "TSVM Disk Operating System",
|
OS_NAME: "TSVM Disk Operating System",
|
||||||
OS_VERSION: _TVDOS.VERSION
|
OS_VERSION: _TVDOS.VERSION
|
||||||
@@ -225,8 +225,9 @@ class TVDOSFileDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** reads the file bytewise and puts it to the specified memory address
|
/** reads the file bytewise and puts it to the specified memory address
|
||||||
* @param count optional -- how many bytes to read
|
* @param ptr -- where the bytes should be dumped
|
||||||
* @param offset optional -- how many bytes to skip initially
|
* @param count -- how many bytes to read
|
||||||
|
* @param offset -- how many bytes to skip initially from the file
|
||||||
*/
|
*/
|
||||||
pread(ptr, count, offset) {
|
pread(ptr, count, offset) {
|
||||||
this.driver.pread(this, ptr, count, offset)
|
this.driver.pread(this, ptr, count, offset)
|
||||||
@@ -241,7 +242,9 @@ class TVDOSFileDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** writes the bytes stored in the memory[ptr .. ptr+count-1] to file[offset .. offset+count-1]
|
/** writes the bytes stored in the memory[ptr .. ptr+count-1] to file[offset .. offset+count-1]
|
||||||
* - @param offset is optional
|
* @param ptr -- where the bytes are
|
||||||
|
* @param count -- how many bytes to write
|
||||||
|
* @param offset -- position in the file
|
||||||
*/
|
*/
|
||||||
pwrite(ptr, count, offset) {
|
pwrite(ptr, count, offset) {
|
||||||
this.driver.pwrite(this, ptr, count, offset)
|
this.driver.pwrite(this, ptr, count, offset)
|
||||||
@@ -420,11 +423,11 @@ _TVDOS.DRV.FS.SERIAL.sread = (fd) => {
|
|||||||
}
|
}
|
||||||
_TVDOS.DRV.FS.SERIAL.bread = (fd) => {
|
_TVDOS.DRV.FS.SERIAL.bread = (fd) => {
|
||||||
let str = _TVDOS.DRV.FS.SERIAL.sread(fd)
|
let str = _TVDOS.DRV.FS.SERIAL.sread(fd)
|
||||||
let bytes = new Int8Array(str.length)
|
let bytes = []//new Int8Array(str.length)
|
||||||
for (let i = 0; i < str.length; i++) {
|
for (let i = 0; i < str.length; i++) {
|
||||||
// let p = str.charCodeAt(i)
|
// let p = str.charCodeAt(i)
|
||||||
// bytes[i] = (p > 127) ? p - 255 : p
|
// bytes[i] = (p > 127) ? p - 255 : p
|
||||||
bytes[i] = str.charCodeAt(i)
|
bytes.push(str.charCodeAt(i))
|
||||||
}
|
}
|
||||||
return bytes
|
return bytes
|
||||||
}
|
}
|
||||||
@@ -870,11 +873,21 @@ Object.freeze(_TVDOS.DRV.FS.DEVPT)
|
|||||||
_TVDOS.DRV.FS.DEVFBIPF = {}
|
_TVDOS.DRV.FS.DEVFBIPF = {}
|
||||||
|
|
||||||
_TVDOS.DRV.FS.DEVFBIPF.pwrite = (fd, infilePtr, count, _2) => {
|
_TVDOS.DRV.FS.DEVFBIPF.pwrite = (fd, infilePtr, count, _2) => {
|
||||||
let decodefun = ([graphics.decodeIpf1, graphics.decodeIpf2])[sys.peek(infilePtr + 13)]
|
let flags = sys.peek(infilePtr+12)
|
||||||
|
let ipfType = sys.peek(infilePtr+13)
|
||||||
|
let isProgressive = (flags & 0x80) != 0
|
||||||
|
let hasAlpha = (flags & 0x01) != 0
|
||||||
|
|
||||||
|
// Select decode function based on type and progressive flag
|
||||||
|
let decodefun
|
||||||
|
if (isProgressive) {
|
||||||
|
decodefun = ([graphics.decodeIpf1Progressive, graphics.decodeIpf2Progressive])[ipfType]
|
||||||
|
} else {
|
||||||
|
decodefun = ([graphics.decodeIpf1, graphics.decodeIpf2])[ipfType]
|
||||||
|
}
|
||||||
|
|
||||||
let width = sys.peek(infilePtr+8) | (sys.peek(infilePtr+9) << 8)
|
let width = sys.peek(infilePtr+8) | (sys.peek(infilePtr+9) << 8)
|
||||||
let height = sys.peek(infilePtr+10) | (sys.peek(infilePtr+11) << 8)
|
let height = sys.peek(infilePtr+10) | (sys.peek(infilePtr+11) << 8)
|
||||||
let hasAlpha = (sys.peek(infilePtr+12) != 0)
|
|
||||||
let ipfType = sys.peek(infilePtr+13)
|
|
||||||
let imgLen = sys.peek(infilePtr+24) | (sys.peek(infilePtr+25) << 8) | (sys.peek(infilePtr+26) << 16) | (sys.peek(infilePtr+27) << 24)
|
let imgLen = sys.peek(infilePtr+24) | (sys.peek(infilePtr+25) << 8) | (sys.peek(infilePtr+26) << 16) | (sys.peek(infilePtr+27) << 24)
|
||||||
|
|
||||||
let ipfbuf = sys.malloc(imgLen)
|
let ipfbuf = sys.malloc(imgLen)
|
||||||
@@ -1014,136 +1027,6 @@ _TVDOS.DRV.FS.NET.exists = (fd) => {
|
|||||||
return (0 == com.getStatusCode(port[0]))
|
return (0 == com.getStatusCode(port[0]))
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
|
|
||||||
// Legacy Serial filesystem, !!pending for removal!!
|
|
||||||
|
|
||||||
|
|
||||||
/*const filesystem = {};
|
|
||||||
|
|
||||||
filesystem._toPorts = (driveLetter) => {
|
|
||||||
if (driveLetter.toUpperCase === undefined) {
|
|
||||||
throw Error("'"+driveLetter+"' (type: "+typeof driveLetter+") is not a valid drive letter");
|
|
||||||
}
|
|
||||||
var port = _TVDOS.DRIVES[driveLetter.toUpperCase()];
|
|
||||||
if (port === undefined) {
|
|
||||||
throw Error("Drive letter '" + driveLetter.toUpperCase() + "' does not exist");
|
|
||||||
}
|
|
||||||
return port
|
|
||||||
};
|
|
||||||
filesystem._close = (portNo) => {
|
|
||||||
com.sendMessage(portNo, "CLOSE")
|
|
||||||
}
|
|
||||||
filesystem._flush = (portNo) => {
|
|
||||||
com.sendMessage(portNo, "FLUSH")
|
|
||||||
}
|
|
||||||
|
|
||||||
// @return disk status code (0 for successful operation)
|
|
||||||
// throws if:
|
|
||||||
// - java.lang.NullPointerException if path is null
|
|
||||||
// - Error if operation mode is not "R", "W" nor "A"
|
|
||||||
filesystem.open = (driveLetter, path, operationMode) => {
|
|
||||||
var port = filesystem._toPorts(driveLetter);
|
|
||||||
|
|
||||||
filesystem._flush(port[0]); filesystem._close(port[0]);
|
|
||||||
|
|
||||||
var mode = operationMode.toUpperCase();
|
|
||||||
if (mode != "R" && mode != "W" && mode != "A") {
|
|
||||||
throw Error("Unknown file opening mode: " + mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
com.sendMessage(port[0], "OPEN"+mode+'"'+path+'",'+port[1]);
|
|
||||||
return com.getStatusCode(port[0]);
|
|
||||||
};
|
|
||||||
filesystem.getFileLen = (driveLetter) => {
|
|
||||||
var port = filesystem._toPorts(driveLetter);
|
|
||||||
com.sendMessage(port[0], "GETLEN");
|
|
||||||
var response = com.getStatusCode(port[0]);
|
|
||||||
if (135 == response) {
|
|
||||||
throw Error("File not opened");
|
|
||||||
}
|
|
||||||
if (response < 0 || response >= 128) {
|
|
||||||
throw Error("Reading a file failed with "+response);
|
|
||||||
}
|
|
||||||
return Number(com.pullMessage(port[0]));
|
|
||||||
};
|
|
||||||
// @return the entire contents of the file in String
|
|
||||||
filesystem.readAll = (driveLetter) => {
|
|
||||||
var port = filesystem._toPorts(driveLetter);
|
|
||||||
com.sendMessage(port[0], "READ");
|
|
||||||
var response = com.getStatusCode(port[0]);
|
|
||||||
if (135 == response) {
|
|
||||||
throw Error("File not opened");
|
|
||||||
}
|
|
||||||
if (response < 0 || response >= 128) {
|
|
||||||
throw Error("Reading a file failed with "+response);
|
|
||||||
}
|
|
||||||
return com.pullMessage(port[0]);
|
|
||||||
};
|
|
||||||
filesystem.readAllBytes = (driveLetter) => {
|
|
||||||
var str = filesystem.readAll(driveLetter);
|
|
||||||
var bytes = new Uint8Array(str.length);
|
|
||||||
for (let i = 0; i < str.length; i++) {
|
|
||||||
bytes[i] = str.charCodeAt(i);
|
|
||||||
}
|
|
||||||
return bytes;
|
|
||||||
};
|
|
||||||
filesystem.write = (driveLetter, string) => {
|
|
||||||
var port = filesystem._toPorts(driveLetter);
|
|
||||||
com.sendMessage(port[0], "WRITE"+string.length);
|
|
||||||
var response = com.getStatusCode(port[0]);
|
|
||||||
if (135 == response) {
|
|
||||||
throw Error("File not opened");
|
|
||||||
}
|
|
||||||
if (response < 0 || response >= 128) {
|
|
||||||
throw Error("Writing a file failed with "+response);
|
|
||||||
}
|
|
||||||
com.sendMessage(port[0], string);
|
|
||||||
filesystem._flush(port[0]); filesystem._close(port[0]);
|
|
||||||
};
|
|
||||||
filesystem.writeBytes = (driveLetter, bytes) => {
|
|
||||||
var string = btostr(bytes); // no spreading: has length limit
|
|
||||||
filesystem.write(driveLetter, string);
|
|
||||||
};
|
|
||||||
filesystem.isDirectory = (driveLetter) => {
|
|
||||||
var port = filesystem._toPorts(driveLetter);
|
|
||||||
com.sendMessage(port[0], "LISTFILES");
|
|
||||||
var response = com.getStatusCode(port[0]);
|
|
||||||
return (response === 0);
|
|
||||||
};
|
|
||||||
filesystem.mkDir = (driveLetter) => {
|
|
||||||
var port = filesystem._toPorts(driveLetter);
|
|
||||||
com.sendMessage(port[0], "MKDIR");
|
|
||||||
var response = com.getStatusCode(port[0]);
|
|
||||||
|
|
||||||
if (response < 0 || response >= 128) {
|
|
||||||
var status = com.getDeviceStatus(port[0]);
|
|
||||||
throw Error("Creating a directory failed with ("+response+"): "+status.message+"\n");
|
|
||||||
}
|
|
||||||
return (response === 0); // possible status codes: 0 (success), 1 (fail)
|
|
||||||
};
|
|
||||||
filesystem.touch = (driveLetter) => {
|
|
||||||
var port = filesystem._toPorts(driveLetter);
|
|
||||||
com.sendMessage(port[0], "TOUCH");
|
|
||||||
var response = com.getStatusCode(port[0]);
|
|
||||||
return (response === 0);
|
|
||||||
};
|
|
||||||
filesystem.mkFile = (driveLetter) => {
|
|
||||||
var port = filesystem._toPorts(driveLetter);
|
|
||||||
com.sendMessage(port[0], "MKFILE");
|
|
||||||
var response = com.getStatusCode(port[0]);
|
|
||||||
return (response === 0);
|
|
||||||
};
|
|
||||||
filesystem.remove = (driveLetter) => {
|
|
||||||
var port = filesystem._toPorts(driveLetter);
|
|
||||||
com.sendMessage(port[0], "DELETE");
|
|
||||||
var response = com.getStatusCode(port[0]);
|
|
||||||
return (response === 0);
|
|
||||||
};
|
|
||||||
Object.freeze(filesystem);*/
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
const files = {}
|
const files = {}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ function print_prompt_text() {
|
|||||||
print(" "+CURRENT_DRIVE+":")
|
print(" "+CURRENT_DRIVE+":")
|
||||||
con.color_pair(161,253)
|
con.color_pair(161,253)
|
||||||
con.addch(16);con.curs_right()
|
con.addch(16);con.curs_right()
|
||||||
con.color_pair(0,253)
|
con.color_pair(240,253)
|
||||||
print(" \\"+shell_pwd.join("\\").substring(1)+" ")
|
print(" \\"+shell_pwd.join("\\").substring(1)+" ")
|
||||||
if (errorlevel != 0 && errorlevel != "undefined" && errorlevel != undefined) {
|
if (errorlevel != 0 && errorlevel != "undefined" && errorlevel != undefined) {
|
||||||
con.color_pair(166,253)
|
con.color_pair(166,253)
|
||||||
@@ -61,7 +61,7 @@ function greet() {
|
|||||||
con.clear()
|
con.clear()
|
||||||
con.color_pair(253,255)
|
con.color_pair(253,255)
|
||||||
print(' ');con.addch(17);con.curs_right()
|
print(' ');con.addch(17);con.curs_right()
|
||||||
con.color_pair(0,253)
|
con.color_pair(240,253)
|
||||||
print(" ".repeat(greetLeftPad)+welcome_text+" ".repeat(greetRightPad))
|
print(" ".repeat(greetLeftPad)+welcome_text+" ".repeat(greetRightPad))
|
||||||
con.color_pair(253,255)
|
con.color_pair(253,255)
|
||||||
con.addch(16);con.curs_right();print(' ')
|
con.addch(16);con.curs_right();print(' ')
|
||||||
@@ -753,6 +753,25 @@ shell.execute = function(line) {
|
|||||||
shell.execute(line)
|
shell.execute(line)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
else if ("ALIAS" == extension) {
|
||||||
|
// parse alias
|
||||||
|
// $0: all arguments
|
||||||
|
// $1..9: specific arguments
|
||||||
|
var lines = programCode.split('\n').filter(function(it) { return it.length > 0 }) // this return is not shell's return!
|
||||||
|
lines.forEach(function(line) {
|
||||||
|
var newLine = line
|
||||||
|
|
||||||
|
// replace $1..$9
|
||||||
|
for (let j = 1; j < 9; j++) {
|
||||||
|
newLine = newLine.replaceAll('$'+j, tokens[j])
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace $0
|
||||||
|
newLine = newLine.replaceAll('$0', tokens.slice(1).join(' '))
|
||||||
|
|
||||||
|
shell.execute(newLine)
|
||||||
|
})
|
||||||
|
}
|
||||||
else if ("APP" == extension) {
|
else if ("APP" == extension) {
|
||||||
let appexec = `A:${_TVDOS.variables.DOSDIR}\\sbin\\appexec.js`
|
let appexec = `A:${_TVDOS.variables.DOSDIR}\\sbin\\appexec.js`
|
||||||
let foundFile = searchFile.fullPath
|
let foundFile = searchFile.fullPath
|
||||||
|
|||||||
5
assets/disk0/tvdos/bin/hopper.js
Normal file
5
assets/disk0/tvdos/bin/hopper.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/**
|
||||||
|
* Hopper is a package manager for TSVM
|
||||||
|
* Created by CuriousTorvald on 2026-04-16
|
||||||
|
*/
|
||||||
|
|
||||||
1
assets/disk0/tvdos/bin/microtone.alias
Normal file
1
assets/disk0/tvdos/bin/microtone.alias
Normal file
@@ -0,0 +1 @@
|
|||||||
|
taut $0
|
||||||
@@ -326,7 +326,7 @@ while (!stopPlay && seqread.getReadCount() < FILE_LENGTH) {
|
|||||||
// RAW PCM packets (decode on the fly)
|
// RAW PCM packets (decode on the fly)
|
||||||
else if (packetType == 0x1000 || packetType == 0x1001) {
|
else if (packetType == 0x1000 || packetType == 0x1001) {
|
||||||
let frame = seqread.readBytes(readLength)
|
let frame = seqread.readBytes(readLength)
|
||||||
audio.putPcmDataByPtr(frame, readLength, 0)
|
audio.putPcmDataByPtr(0, frame, readLength, 0)
|
||||||
audio.setSampleUploadLength(0, readLength)
|
audio.setSampleUploadLength(0, readLength)
|
||||||
audio.startSampleUpload(0)
|
audio.startSampleUpload(0)
|
||||||
sys.free(frame)
|
sys.free(frame)
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ while (!stopPlay && seqread.getReadCount() < FILE_SIZE && readLength > 0) {
|
|||||||
|
|
||||||
seqread.readBytes(readLength, readPtr)
|
seqread.readBytes(readLength, readPtr)
|
||||||
|
|
||||||
audio.putPcmDataByPtr(readPtr, readLength, 0)
|
audio.putPcmDataByPtr(0, readPtr, readLength, 0)
|
||||||
audio.setSampleUploadLength(0, readLength)
|
audio.setSampleUploadLength(0, readLength)
|
||||||
audio.startSampleUpload(0)
|
audio.startSampleUpload(0)
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const MAXMEM = sys.maxmem()
|
|||||||
const WIDTH = 560
|
const WIDTH = 560
|
||||||
const HEIGHT = 448
|
const HEIGHT = 448
|
||||||
const TAV_MAGIC = [0x1F, 0x54, 0x53, 0x56, 0x4D, 0x54, 0x41, 0x56] // "\x1FTSVM TAV"
|
const TAV_MAGIC = [0x1F, 0x54, 0x53, 0x56, 0x4D, 0x54, 0x41, 0x56] // "\x1FTSVM TAV"
|
||||||
|
const TAP_MAGIC = [0x1F, 0x54, 0x53, 0x56, 0x4D, 0x54, 0x41, 0x50] // "\x1FTSVM TAP"
|
||||||
const UCF_MAGIC = [0x1F, 0x54, 0x53, 0x56, 0x4D, 0x55, 0x43, 0x46] // "\x1FTSVM UCF"
|
const UCF_MAGIC = [0x1F, 0x54, 0x53, 0x56, 0x4D, 0x55, 0x43, 0x46] // "\x1FTSVM UCF"
|
||||||
const TAV_VERSION = 1 // Initial DWT version
|
const TAV_VERSION = 1 // Initial DWT version
|
||||||
const UCF_VERSION = 1
|
const UCF_VERSION = 1
|
||||||
@@ -17,6 +18,7 @@ const ADDRESSING_INTERNAL = 0x02
|
|||||||
const SND_BASE_ADDR = audio.getBaseAddr()
|
const SND_BASE_ADDR = audio.getBaseAddr()
|
||||||
const SND_MEM_ADDR = audio.getMemAddr()
|
const SND_MEM_ADDR = audio.getMemAddr()
|
||||||
const pcm = require("pcm")
|
const pcm = require("pcm")
|
||||||
|
const AUDIO_DEVICE = 0
|
||||||
const MP2_FRAME_SIZE = [144,216,252,288,360,432,504,576,720,864,1008,1152,1440,1728]
|
const MP2_FRAME_SIZE = [144,216,252,288,360,432,504,576,720,864,1008,1152,1440,1728]
|
||||||
const TAV_TEMPORAL_LEVELS = 2
|
const TAV_TEMPORAL_LEVELS = 2
|
||||||
|
|
||||||
@@ -151,13 +153,10 @@ graphics.clearPixels4(0)
|
|||||||
const gpuGraphicsMode = graphics.getGraphicsMode()
|
const gpuGraphicsMode = graphics.getGraphicsMode()
|
||||||
|
|
||||||
// Initialize audio
|
// Initialize audio
|
||||||
audio.resetParams(0)
|
audio.resetParams(AUDIO_DEVICE)
|
||||||
audio.purgeQueue(0)
|
audio.purgeQueue(AUDIO_DEVICE)
|
||||||
audio.setPcmMode(0)
|
audio.setPcmMode(AUDIO_DEVICE)
|
||||||
audio.setMasterVolume(0, 255)
|
audio.setMasterVolume(AUDIO_DEVICE, 255)
|
||||||
|
|
||||||
// set colour zero as half-opaque black
|
|
||||||
graphics.setPalette(0, 0, 0, 0, 7)
|
|
||||||
|
|
||||||
// Parse SSF-TC subtitle packet and add to event buffer (0x31)
|
// Parse SSF-TC subtitle packet and add to event buffer (0x31)
|
||||||
function parseSubtitlePacketTC(packetSize) {
|
function parseSubtitlePacketTC(packetSize) {
|
||||||
@@ -367,6 +366,8 @@ let header = {
|
|||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
fps: 0,
|
fps: 0,
|
||||||
|
fps_num: 0, // Fractional FPS numerator (from XFPS or derived from fps)
|
||||||
|
fps_den: 1, // Fractional FPS denominator (from XFPS, default 1)
|
||||||
totalFrames: 0,
|
totalFrames: 0,
|
||||||
waveletFilter: 0, // TAV-specific: wavelet filter type
|
waveletFilter: 0, // TAV-specific: wavelet filter type
|
||||||
decompLevels: 0, // TAV-specific: decomposition levels
|
decompLevels: 0, // TAV-specific: decomposition levels
|
||||||
@@ -381,6 +382,22 @@ let header = {
|
|||||||
fileRole: 0
|
fileRole: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to parse XFPS string ("num/den" format) and update header
|
||||||
|
function parseXFPS(xfpsStr) {
|
||||||
|
let parts = xfpsStr.split("/")
|
||||||
|
if (parts.length === 2) {
|
||||||
|
let num = parseInt(parts[0], 10)
|
||||||
|
let den = parseInt(parts[1], 10)
|
||||||
|
if (!isNaN(num) && !isNaN(den) && den > 0) {
|
||||||
|
header.fps_num = num
|
||||||
|
header.fps_den = den
|
||||||
|
header.fps = num / den
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Read and validate header
|
// Read and validate header
|
||||||
for (let i = 0; i < 8; i++) {
|
for (let i = 0; i < 8; i++) {
|
||||||
header.magic[i] = seqread.readOneByte()
|
header.magic[i] = seqread.readOneByte()
|
||||||
@@ -389,7 +406,7 @@ for (let i = 0; i < 8; i++) {
|
|||||||
// Validate magic number
|
// Validate magic number
|
||||||
let magicValid = true
|
let magicValid = true
|
||||||
for (let i = 0; i < 8; i++) {
|
for (let i = 0; i < 8; i++) {
|
||||||
if (header.magic[i] !== TAV_MAGIC[i]) {
|
if (header.magic[i] !== TAV_MAGIC[i] &&header.magic[i] !== TAP_MAGIC[i] ) {
|
||||||
magicValid = false
|
magicValid = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -401,10 +418,16 @@ if (!magicValid) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this is a TAP still image file (magic ends with 'P' instead of 'V')
|
||||||
|
const isTapFile = (header.magic[7] === TAP_MAGIC[7])
|
||||||
|
|
||||||
header.version = seqread.readOneByte()
|
header.version = seqread.readOneByte()
|
||||||
header.width = seqread.readShort()
|
header.width = seqread.readShort()
|
||||||
header.height = seqread.readShort()
|
header.height = seqread.readShort()
|
||||||
header.fps = seqread.readOneByte()
|
header.fps = seqread.readOneByte()
|
||||||
|
// Set default fractional fps (will be overridden by XFPS if present)
|
||||||
|
header.fps_num = header.fps
|
||||||
|
header.fps_den = 1
|
||||||
header.totalFrames = seqread.readInt()
|
header.totalFrames = seqread.readInt()
|
||||||
header.waveletFilter = seqread.readOneByte()
|
header.waveletFilter = seqread.readOneByte()
|
||||||
header.decompLevels = seqread.readOneByte()
|
header.decompLevels = seqread.readOneByte()
|
||||||
@@ -457,7 +480,7 @@ const isLossless = (header.videoFlags & 0x04) !== 0
|
|||||||
|
|
||||||
console.log(`TAV Decoder`)
|
console.log(`TAV Decoder`)
|
||||||
console.log(`Resolution: ${header.width}x${header.height}`)
|
console.log(`Resolution: ${header.width}x${header.height}`)
|
||||||
console.log(`FPS: ${header.fps}`)
|
console.log(`FPS: ${header.fps === 255 ? "(see XFPS)" : header.fps}`)
|
||||||
console.log(`Total frames: ${header.totalFrames}`)
|
console.log(`Total frames: ${header.totalFrames}`)
|
||||||
console.log(`Wavelet filter: ${header.waveletFilter === WAVELET_5_3_REVERSIBLE ? "5/3 reversible" : header.waveletFilter === WAVELET_9_7_IRREVERSIBLE ? "9/7 irreversible" : header.waveletFilter === WAVELET_BIORTHOGONAL_13_7 ? "Biorthogonal 13/7" : header.waveletFilter === WAVELET_DD4 ? "DD-4" : header.waveletFilter === WAVELET_HAAR ? "Haar" : "unknown"}`)
|
console.log(`Wavelet filter: ${header.waveletFilter === WAVELET_5_3_REVERSIBLE ? "5/3 reversible" : header.waveletFilter === WAVELET_9_7_IRREVERSIBLE ? "9/7 irreversible" : header.waveletFilter === WAVELET_BIORTHOGONAL_13_7 ? "Biorthogonal 13/7" : header.waveletFilter === WAVELET_DD4 ? "DD-4" : header.waveletFilter === WAVELET_HAAR ? "Haar" : "unknown"}`)
|
||||||
console.log(`Decomposition levels: ${header.decompLevels}`)
|
console.log(`Decomposition levels: ${header.decompLevels}`)
|
||||||
@@ -469,6 +492,135 @@ console.log(`Features: ${hasAudio ? "Audio " : ""}${hasSubtitles ? "Subtitles "
|
|||||||
console.log(`Video flags raw: 0x${header.videoFlags.toString(16)}`)
|
console.log(`Video flags raw: 0x${header.videoFlags.toString(16)}`)
|
||||||
console.log(`Scan type: ${isInterlaced ? "Interlaced" : "Progressive"}`)
|
console.log(`Scan type: ${isInterlaced ? "Interlaced" : "Progressive"}`)
|
||||||
|
|
||||||
|
// Handle TAP still image file
|
||||||
|
if (isTapFile) {
|
||||||
|
console.log("TAP still image detected")
|
||||||
|
|
||||||
|
// Allocate single frame buffer for still image
|
||||||
|
const FRAME_PIXELS = header.width * header.height
|
||||||
|
const FRAME_SIZE = FRAME_PIXELS * 3
|
||||||
|
|
||||||
|
const RGB_BUFFER = sys.malloc(FRAME_SIZE)
|
||||||
|
const PREV_RGB_BUFFER = sys.malloc(FRAME_SIZE)
|
||||||
|
sys.memset(RGB_BUFFER, 0, FRAME_SIZE)
|
||||||
|
sys.memset(PREV_RGB_BUFFER, 0, FRAME_SIZE)
|
||||||
|
|
||||||
|
// Read the image packet (should be I-frame)
|
||||||
|
let packetType = seqread.readOneByte()
|
||||||
|
|
||||||
|
// Skip non-video packets until we find the image data
|
||||||
|
while (packetType !== TAV_PACKET_IFRAME) {
|
||||||
|
if (packetType === TAV_PACKET_EXTENDED_HDR) {
|
||||||
|
// Parse extended header - look for XFPS
|
||||||
|
let numPairs = seqread.readShort()
|
||||||
|
for (let i = 0; i < numPairs; i++) {
|
||||||
|
// Read key (4 bytes)
|
||||||
|
let keyBytes = seqread.readBytes(4)
|
||||||
|
let key = ""
|
||||||
|
for (let j = 0; j < 4; j++) {
|
||||||
|
key += String.fromCharCode(sys.peek(keyBytes + j))
|
||||||
|
}
|
||||||
|
sys.free(keyBytes)
|
||||||
|
|
||||||
|
// Read value type and value
|
||||||
|
let valueType = seqread.readOneByte()
|
||||||
|
if (valueType === 0x04) { // Uint64 - 8 bytes
|
||||||
|
seqread.skip(8)
|
||||||
|
} else if (valueType === 0x10) { // Bytes - length-prefixed
|
||||||
|
let length = seqread.readShort()
|
||||||
|
let dataBytes = seqread.readBytes(length)
|
||||||
|
// Check for XFPS key
|
||||||
|
if (key === "XFPS") {
|
||||||
|
let xfpsStr = ""
|
||||||
|
for (let j = 0; j < length; j++) {
|
||||||
|
xfpsStr += String.fromCharCode(sys.peek(dataBytes + j))
|
||||||
|
}
|
||||||
|
parseXFPS(xfpsStr)
|
||||||
|
}
|
||||||
|
sys.free(dataBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (packetType === TAV_PACKET_SCREEN_MASK) {
|
||||||
|
// Skip screen mask packet - single entry: frame_num(4) + top(2) + right(2) + bottom(2) + left(2)
|
||||||
|
seqread.skip(12)
|
||||||
|
} else if (packetType === TAV_PACKET_TIMECODE) {
|
||||||
|
seqread.skip(8)
|
||||||
|
} else {
|
||||||
|
console.log(`got unknown packet type 0x${packetType.toString(16)}`)
|
||||||
|
let size = seqread.readInt()
|
||||||
|
seqread.skip(size)
|
||||||
|
}
|
||||||
|
packetType = seqread.readOneByte()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packetType === TAV_PACKET_IFRAME) {
|
||||||
|
// Read and decode I-frame
|
||||||
|
const compressedSize = seqread.readInt()
|
||||||
|
const compressedPtr = seqread.readBytes(compressedSize)
|
||||||
|
|
||||||
|
// Decode using TAV hardware decoder
|
||||||
|
graphics.tavDecodeCompressed(
|
||||||
|
compressedPtr, compressedSize,
|
||||||
|
RGB_BUFFER, PREV_RGB_BUFFER,
|
||||||
|
header.width, header.height,
|
||||||
|
header.qualityLevel,
|
||||||
|
QLUT[header.qualityY], QLUT[header.qualityCo], QLUT[header.qualityCg],
|
||||||
|
header.channelLayout, 0, header.waveletFilter, header.decompLevels,
|
||||||
|
isLossless, header.version, header.entropyCoder, 2
|
||||||
|
)
|
||||||
|
sys.free(compressedPtr)
|
||||||
|
|
||||||
|
// Upload to framebuffer
|
||||||
|
graphics.uploadRGBToFramebuffer(RGB_BUFFER, header.width, header.height, 0, false)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free buffers
|
||||||
|
sys.free(RGB_BUFFER)
|
||||||
|
sys.free(PREV_RGB_BUFFER)
|
||||||
|
|
||||||
|
// Show "backspace to exit" message
|
||||||
|
con.clear()
|
||||||
|
con.curs_set(0)
|
||||||
|
con.move(1, 1)
|
||||||
|
println("Push and hold Backspace to exit")
|
||||||
|
|
||||||
|
// Wait loop for still image viewing (similar to decodeipf.js)
|
||||||
|
let wait = true
|
||||||
|
let t1 = sys.nanoTime()
|
||||||
|
let tapNotifHideTimer = 0
|
||||||
|
const TAP_NOTIF_SHOWUPTIME = 3000000000 // 3 seconds
|
||||||
|
|
||||||
|
while (wait) {
|
||||||
|
sys.poke(-40, 1)
|
||||||
|
if (sys.peek(-41) == 67) { // Backspace
|
||||||
|
wait = false
|
||||||
|
con.curs_set(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
sys.sleep(50)
|
||||||
|
|
||||||
|
let t2 = sys.nanoTime()
|
||||||
|
tapNotifHideTimer += (t2 - t1)
|
||||||
|
if (tapNotifHideTimer > TAP_NOTIF_SHOWUPTIME) {
|
||||||
|
con.clear()
|
||||||
|
}
|
||||||
|
t1 = t2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up and exit (matching normal video playback cleanup)
|
||||||
|
con.clear()
|
||||||
|
con.curs_set(1)
|
||||||
|
|
||||||
|
// Reset font ROM
|
||||||
|
sys.poke(-1299460, 20)
|
||||||
|
sys.poke(-1299460, 21)
|
||||||
|
|
||||||
|
graphics.setPalette(0, 0, 0, 0, 0)
|
||||||
|
con.move(cy, cx) // restore cursor
|
||||||
|
return errorlevel
|
||||||
|
}
|
||||||
|
|
||||||
// Adjust decode height for interlaced content
|
// Adjust decode height for interlaced content
|
||||||
// For interlaced: header.height is display height (448)
|
// For interlaced: header.height is display height (448)
|
||||||
// Each field is half of display height (448/2 = 224)
|
// Each field is half of display height (448/2 = 224)
|
||||||
@@ -998,10 +1150,10 @@ try {
|
|||||||
else if (keyCode == 62) { // SPACE - pause/resume
|
else if (keyCode == 62) { // SPACE - pause/resume
|
||||||
paused = !paused
|
paused = !paused
|
||||||
if (paused) {
|
if (paused) {
|
||||||
audio.stop(0)
|
audio.stop(AUDIO_DEVICE)
|
||||||
serial.println(`Paused at frame ${frameCount}`)
|
serial.println(`Paused at frame ${frameCount}`)
|
||||||
} else {
|
} else {
|
||||||
audio.play(0)
|
audio.play(AUDIO_DEVICE)
|
||||||
serial.println(`Resumed`)
|
serial.println(`Resumed`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1022,10 +1174,10 @@ try {
|
|||||||
baseTimecodeFrameCount = 0
|
baseTimecodeFrameCount = 0
|
||||||
currentTimecodeNs = 0
|
currentTimecodeNs = 0
|
||||||
nextSubtitleEventIndex = 0 // Reset subtitle event processing
|
nextSubtitleEventIndex = 0 // Reset subtitle event processing
|
||||||
audio.purgeQueue(0)
|
audio.purgeQueue(AUDIO_DEVICE)
|
||||||
if (paused) {
|
if (paused) {
|
||||||
audio.play(0)
|
audio.play(AUDIO_DEVICE)
|
||||||
audio.stop(0)
|
audio.stop(AUDIO_DEVICE)
|
||||||
}
|
}
|
||||||
skipped = true
|
skipped = true
|
||||||
}
|
}
|
||||||
@@ -1047,10 +1199,10 @@ try {
|
|||||||
baseTimecodeFrameCount = 0
|
baseTimecodeFrameCount = 0
|
||||||
currentTimecodeNs = 0
|
currentTimecodeNs = 0
|
||||||
nextSubtitleEventIndex = 0 // Reset subtitle event processing
|
nextSubtitleEventIndex = 0 // Reset subtitle event processing
|
||||||
audio.purgeQueue(0)
|
audio.purgeQueue(AUDIO_DEVICE)
|
||||||
if (paused) {
|
if (paused) {
|
||||||
audio.play(0)
|
audio.play(AUDIO_DEVICE)
|
||||||
audio.stop(0)
|
audio.stop(AUDIO_DEVICE)
|
||||||
}
|
}
|
||||||
skipped = true
|
skipped = true
|
||||||
}
|
}
|
||||||
@@ -1078,10 +1230,10 @@ try {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
audio.purgeQueue(0)
|
audio.purgeQueue(AUDIO_DEVICE)
|
||||||
if (paused) {
|
if (paused) {
|
||||||
audio.play(0)
|
audio.play(AUDIO_DEVICE)
|
||||||
audio.stop(0)
|
audio.stop(AUDIO_DEVICE)
|
||||||
}
|
}
|
||||||
skipped = true
|
skipped = true
|
||||||
}
|
}
|
||||||
@@ -1117,10 +1269,10 @@ try {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
audio.purgeQueue(0)
|
audio.purgeQueue(AUDIO_DEVICE)
|
||||||
if (paused) {
|
if (paused) {
|
||||||
audio.play(0)
|
audio.play(AUDIO_DEVICE)
|
||||||
audio.stop(0)
|
audio.stop(AUDIO_DEVICE)
|
||||||
}
|
}
|
||||||
skipped = true
|
skipped = true
|
||||||
} else if (!seekTarget) {
|
} else if (!seekTarget) {
|
||||||
@@ -1159,7 +1311,7 @@ try {
|
|||||||
baseTimecodeFrameCount = 0
|
baseTimecodeFrameCount = 0
|
||||||
currentTimecodeNs = 0
|
currentTimecodeNs = 0
|
||||||
nextSubtitleEventIndex = 0 // Reset subtitle event processing
|
nextSubtitleEventIndex = 0 // Reset subtitle event processing
|
||||||
audio.purgeQueue(0)
|
audio.purgeQueue(AUDIO_DEVICE)
|
||||||
currentFileIndex++
|
currentFileIndex++
|
||||||
if (skipped) {
|
if (skipped) {
|
||||||
skipped = false
|
skipped = false
|
||||||
@@ -1170,7 +1322,7 @@ try {
|
|||||||
|
|
||||||
console.log(`\nStarting file ${currentFileIndex}:`)
|
console.log(`\nStarting file ${currentFileIndex}:`)
|
||||||
console.log(`Resolution: ${header.width}x${header.height}`)
|
console.log(`Resolution: ${header.width}x${header.height}`)
|
||||||
console.log(`FPS: ${header.fps}`)
|
console.log(`FPS: ${header.fps === 255 ? "(see XFPS)" : header.fps}`)
|
||||||
console.log(`Total frames: ${header.totalFrames}`)
|
console.log(`Total frames: ${header.totalFrames}`)
|
||||||
console.log(`Wavelet filter: ${header.waveletFilter === WAVELET_5_3_REVERSIBLE ? "5/3 reversible" : header.waveletFilter === WAVELET_9_7_IRREVERSIBLE ? "9/7 irreversible" : header.waveletFilter === WAVELET_BIORTHOGONAL_13_7 ? "Biorthogonal 13/7" : header.waveletFilter === WAVELET_DD4 ? "DD-4" : header.waveletFilter === WAVELET_HAAR ? "Haar" : "unknown"}`)
|
console.log(`Wavelet filter: ${header.waveletFilter === WAVELET_5_3_REVERSIBLE ? "5/3 reversible" : header.waveletFilter === WAVELET_9_7_IRREVERSIBLE ? "9/7 irreversible" : header.waveletFilter === WAVELET_BIORTHOGONAL_13_7 ? "Biorthogonal 13/7" : header.waveletFilter === WAVELET_DD4 ? "DD-4" : header.waveletFilter === WAVELET_HAAR ? "Haar" : "unknown"}`)
|
||||||
console.log(`Quality: Y=${header.qualityY}, Co=${header.qualityCo}, Cg=${header.qualityCg}`)
|
console.log(`Quality: Y=${header.qualityY}, Co=${header.qualityCo}, Cg=${header.qualityCg}`)
|
||||||
@@ -1583,7 +1735,7 @@ try {
|
|||||||
|
|
||||||
seqread.readBytes(audioLen, SND_BASE_ADDR - 2368)
|
seqread.readBytes(audioLen, SND_BASE_ADDR - 2368)
|
||||||
audio.mp2Decode()
|
audio.mp2Decode()
|
||||||
audio.mp2UploadDecoded(0)
|
audio.mp2UploadDecoded(AUDIO_DEVICE)
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (packetType === TAV_PACKET_AUDIO_TAD) {
|
else if (packetType === TAV_PACKET_AUDIO_TAD) {
|
||||||
@@ -1596,7 +1748,7 @@ try {
|
|||||||
|
|
||||||
seqread.readBytes(payloadLen, SND_MEM_ADDR - 262144)
|
seqread.readBytes(payloadLen, SND_MEM_ADDR - 262144)
|
||||||
audio.tadDecode()
|
audio.tadDecode()
|
||||||
audio.tadUploadDecoded(0, sampleLen)
|
audio.tadUploadDecoded(AUDIO_DEVICE, sampleLen)
|
||||||
}
|
}
|
||||||
else if (packetType === TAV_PACKET_AUDIO_NATIVE) {
|
else if (packetType === TAV_PACKET_AUDIO_NATIVE) {
|
||||||
// PCM length must not exceed 65536 bytes!
|
// PCM length must not exceed 65536 bytes!
|
||||||
@@ -1608,10 +1760,10 @@ try {
|
|||||||
let pcmLen = gzip.decompFromTo(zstdPtr, zstdLen, pcmPtr) // <- segfaults!
|
let pcmLen = gzip.decompFromTo(zstdPtr, zstdLen, pcmPtr) // <- segfaults!
|
||||||
if (pcmLen > 65536) throw Error(`PCM data too long -- got ${pcmLen} bytes`)
|
if (pcmLen > 65536) throw Error(`PCM data too long -- got ${pcmLen} bytes`)
|
||||||
|
|
||||||
audio.putPcmDataByPtr(pcmPtr, pcmLen, 0)
|
audio.putPcmDataByPtr(AUDIO_DEVICE, pcmPtr, pcmLen, 0)
|
||||||
|
|
||||||
audio.setSampleUploadLength(0, pcmLen)
|
audio.setSampleUploadLength(AUDIO_DEVICE, pcmLen)
|
||||||
audio.startSampleUpload(0)
|
audio.startSampleUpload(AUDIO_DEVICE)
|
||||||
sys.free(zstdPtr)
|
sys.free(zstdPtr)
|
||||||
|
|
||||||
sys.free(pcmPtr)
|
sys.free(pcmPtr)
|
||||||
@@ -1704,7 +1856,19 @@ try {
|
|||||||
}
|
}
|
||||||
sys.free(dataBytes)
|
sys.free(dataBytes)
|
||||||
|
|
||||||
if (interactive) {
|
// Parse XFPS if present (always try, not just when fps=255)
|
||||||
|
if (key === "XFPS") {
|
||||||
|
if (parseXFPS(dataStr)) {
|
||||||
|
// Update frame timing with new fps
|
||||||
|
frametime = 1000000000.0 / header.fps
|
||||||
|
FRAME_TIME = 1.0 / header.fps
|
||||||
|
if (interactive) {
|
||||||
|
serial.println(` ${key}: ${dataStr} -> ${header.fps.toFixed(3)} fps`)
|
||||||
|
}
|
||||||
|
} else if (interactive) {
|
||||||
|
serial.println(` ${key}: "${dataStr}" (parse failed)`)
|
||||||
|
}
|
||||||
|
} else if (interactive) {
|
||||||
serial.println(` ${key}: "${dataStr}"`)
|
serial.println(` ${key}: "${dataStr}"`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1883,7 +2047,7 @@ try {
|
|||||||
|
|
||||||
// Fire audio on first frame
|
// Fire audio on first frame
|
||||||
if (!audioFired) {
|
if (!audioFired) {
|
||||||
audio.play(0)
|
audio.play(AUDIO_DEVICE)
|
||||||
audioFired = true
|
audioFired = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1971,7 +2135,7 @@ try {
|
|||||||
|
|
||||||
// Fire audio on first frame
|
// Fire audio on first frame
|
||||||
if (!audioFired) {
|
if (!audioFired) {
|
||||||
audio.play(0)
|
audio.play(AUDIO_DEVICE)
|
||||||
audioFired = true
|
audioFired = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2007,8 +2171,8 @@ try {
|
|||||||
sys.memcpy(predecodedPcmBuffer + predecodedPcmOffset, SND_BASE_ADDR, uploadSize)
|
sys.memcpy(predecodedPcmBuffer + predecodedPcmOffset, SND_BASE_ADDR, uploadSize)
|
||||||
|
|
||||||
// Set upload parameters and trigger upload to queue
|
// Set upload parameters and trigger upload to queue
|
||||||
audio.setSampleUploadLength(0, uploadSize)
|
audio.setSampleUploadLength(AUDIO_DEVICE, uploadSize)
|
||||||
audio.startSampleUpload(0)
|
audio.startSampleUpload(AUDIO_DEVICE)
|
||||||
|
|
||||||
predecodedPcmOffset += uploadSize
|
predecodedPcmOffset += uploadSize
|
||||||
}
|
}
|
||||||
@@ -2292,10 +2456,10 @@ finally {
|
|||||||
sys.poke(-1299460, 20)
|
sys.poke(-1299460, 20)
|
||||||
sys.poke(-1299460, 21)
|
sys.poke(-1299460, 21)
|
||||||
|
|
||||||
audio.stop(0)
|
audio.stop(AUDIO_DEVICE)
|
||||||
audio.purgeQueue(0)
|
audio.purgeQueue(AUDIO_DEVICE)
|
||||||
}
|
}
|
||||||
|
|
||||||
graphics.setPalette(0, 0, 0, 0, 0)
|
graphics.resetPalette()
|
||||||
con.move(cy, cx) // restore cursor
|
con.move(cy, cx) // restore cursor
|
||||||
return errorlevel
|
return errorlevel
|
||||||
@@ -289,7 +289,7 @@ while (!stopPlay && seqread.getReadCount() < FILE_SIZE - 8) {
|
|||||||
let decodedSampleLength = decodeInfilePcm(readPtr, decodePtr, readLength)
|
let decodedSampleLength = decodeInfilePcm(readPtr, decodePtr, readLength)
|
||||||
printdbg(` decodedSampleLength: ${decodedSampleLength}`)
|
printdbg(` decodedSampleLength: ${decodedSampleLength}`)
|
||||||
|
|
||||||
audio.putPcmDataByPtr(decodePtr, decodedSampleLength, 0)
|
audio.putPcmDataByPtr(0, decodePtr, decodedSampleLength, 0)
|
||||||
audio.setSampleUploadLength(0, decodedSampleLength)
|
audio.setSampleUploadLength(0, decodedSampleLength)
|
||||||
audio.startSampleUpload(0)
|
audio.startSampleUpload(0)
|
||||||
|
|
||||||
|
|||||||
2645
assets/disk0/tvdos/bin/taut.js
Normal file
2645
assets/disk0/tvdos/bin/taut.js
Normal file
File diff suppressed because it is too large
Load Diff
77
assets/disk0/tvdos/bin/taut_fileop.js
Normal file
77
assets/disk0/tvdos/bin/taut_fileop.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* TAUT File Operations
|
||||||
|
* Sub-program launched by taut.js when the File tab is active.
|
||||||
|
* Rows 1-3 are owned by the parent; this program draws rows 4+.
|
||||||
|
*
|
||||||
|
* exec_args[1] = path to .taud file
|
||||||
|
* Sets _G.taut_nextPanel before returning to request a panel switch.
|
||||||
|
*
|
||||||
|
* Created by minjaesong on 2026-04-27
|
||||||
|
*/
|
||||||
|
|
||||||
|
const win = require("wintex")
|
||||||
|
|
||||||
|
const PANEL_COUNT = 7
|
||||||
|
const MY_PANEL = 6 // VIEW_FILE
|
||||||
|
|
||||||
|
const [SCRH, SCRW] = con.getmaxyx()
|
||||||
|
const PANEL_Y = 4
|
||||||
|
const PANEL_H = SCRH - PANEL_Y
|
||||||
|
|
||||||
|
const colStatus = 253
|
||||||
|
const colContent = 240
|
||||||
|
const colHdr = 230
|
||||||
|
|
||||||
|
function drawFileOpContents(wo) {
|
||||||
|
for (let y = PANEL_Y; y < SCRH; y++) {
|
||||||
|
con.move(y, 1)
|
||||||
|
con.color_pair(colContent, 255)
|
||||||
|
print(' '.repeat(SCRW))
|
||||||
|
}
|
||||||
|
con.move(PANEL_Y + 1, 3)
|
||||||
|
con.color_pair(colHdr, 255)
|
||||||
|
print('[ File ]')
|
||||||
|
con.move(PANEL_Y + 3, 3)
|
||||||
|
con.color_pair(colStatus, 255)
|
||||||
|
print('placeholder — not yet implemented')
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawHints() {
|
||||||
|
con.move(SCRH, 1)
|
||||||
|
con.color_pair(colStatus, 255)
|
||||||
|
print(' '.repeat(SCRW - 1))
|
||||||
|
con.move(SCRH, 1)
|
||||||
|
con.color_pair(colHdr, 255); print('Tab ')
|
||||||
|
con.color_pair(colStatus, 255); print('Panel')
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileOpInput(wo, event) {
|
||||||
|
// placeholder — no interaction yet
|
||||||
|
}
|
||||||
|
|
||||||
|
const panel = new win.WindowObject(1, PANEL_Y, SCRW, PANEL_H, fileOpInput, drawFileOpContents, undefined, ()=>{})
|
||||||
|
|
||||||
|
panel.drawContents()
|
||||||
|
drawHints()
|
||||||
|
|
||||||
|
let done = false
|
||||||
|
while (!done) {
|
||||||
|
input.withEvent(event => {
|
||||||
|
if (event[0] !== 'key_down') return
|
||||||
|
const keysym = event[1]
|
||||||
|
const keyJustHit = (1 == event[2])
|
||||||
|
const shiftDown = (event.includes(59) || event.includes(60))
|
||||||
|
|
||||||
|
if (!keyJustHit) return
|
||||||
|
|
||||||
|
if (keysym === '<TAB>') {
|
||||||
|
_G.taut_nextPanel = (MY_PANEL + (shiftDown ? -1 : 1) + PANEL_COUNT) % PANEL_COUNT
|
||||||
|
done = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.processInput(event)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
77
assets/disk0/tvdos/bin/taut_instredit.js
Normal file
77
assets/disk0/tvdos/bin/taut_instredit.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* TAUT Instrument Editor
|
||||||
|
* Sub-program launched by taut.js when the Instrmnt tab is active.
|
||||||
|
* Rows 1-3 are owned by the parent; this program draws rows 4+.
|
||||||
|
*
|
||||||
|
* exec_args[1] = path to .taud file
|
||||||
|
* Sets _G.taut_nextPanel before returning to request a panel switch.
|
||||||
|
*
|
||||||
|
* Created by minjaesong on 2026-04-27
|
||||||
|
*/
|
||||||
|
|
||||||
|
const win = require("wintex")
|
||||||
|
|
||||||
|
const PANEL_COUNT = 7
|
||||||
|
const MY_PANEL = 4 // VIEW_INSTRMNT
|
||||||
|
|
||||||
|
const [SCRH, SCRW] = con.getmaxyx()
|
||||||
|
const PANEL_Y = 4
|
||||||
|
const PANEL_H = SCRH - PANEL_Y
|
||||||
|
|
||||||
|
const colStatus = 253
|
||||||
|
const colContent = 240
|
||||||
|
const colHdr = 230
|
||||||
|
|
||||||
|
function drawInstEditContents(wo) {
|
||||||
|
for (let y = PANEL_Y; y < SCRH; y++) {
|
||||||
|
con.move(y, 1)
|
||||||
|
con.color_pair(colContent, 255)
|
||||||
|
print(' '.repeat(SCRW))
|
||||||
|
}
|
||||||
|
con.move(PANEL_Y + 1, 3)
|
||||||
|
con.color_pair(colHdr, 255)
|
||||||
|
print('[ Instrument Editor ]')
|
||||||
|
con.move(PANEL_Y + 3, 3)
|
||||||
|
con.color_pair(colStatus, 255)
|
||||||
|
print('placeholder — not yet implemented')
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawHints() {
|
||||||
|
con.move(SCRH, 1)
|
||||||
|
con.color_pair(colStatus, 255)
|
||||||
|
print(' '.repeat(SCRW - 1))
|
||||||
|
con.move(SCRH, 1)
|
||||||
|
con.color_pair(colHdr, 255); print('Tab ')
|
||||||
|
con.color_pair(colStatus, 255); print('Panel')
|
||||||
|
}
|
||||||
|
|
||||||
|
function instEditInput(wo, event) {
|
||||||
|
// placeholder — no interaction yet
|
||||||
|
}
|
||||||
|
|
||||||
|
const panel = new win.WindowObject(1, PANEL_Y, SCRW, PANEL_H, instEditInput, drawInstEditContents, undefined, ()=>{})
|
||||||
|
|
||||||
|
panel.drawContents()
|
||||||
|
drawHints()
|
||||||
|
|
||||||
|
let done = false
|
||||||
|
while (!done) {
|
||||||
|
input.withEvent(event => {
|
||||||
|
if (event[0] !== 'key_down') return
|
||||||
|
const keysym = event[1]
|
||||||
|
const keyJustHit = (1 == event[2])
|
||||||
|
const shiftDown = (event.includes(59) || event.includes(60))
|
||||||
|
|
||||||
|
if (!keyJustHit) return
|
||||||
|
|
||||||
|
if (keysym === '<TAB>') {
|
||||||
|
_G.taut_nextPanel = (MY_PANEL + (shiftDown ? -1 : 1) + PANEL_COUNT) % PANEL_COUNT
|
||||||
|
done = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.processInput(event)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
0
assets/disk0/tvdos/bin/taut_notationedit.js
Normal file
0
assets/disk0/tvdos/bin/taut_notationedit.js
Normal file
77
assets/disk0/tvdos/bin/taut_sampleedit.js
Normal file
77
assets/disk0/tvdos/bin/taut_sampleedit.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* TAUT Sample Editor
|
||||||
|
* Sub-program launched by taut.js when the Samples tab is active.
|
||||||
|
* Rows 1-3 are owned by the parent; this program draws rows 4+.
|
||||||
|
*
|
||||||
|
* exec_args[1] = path to .taud file
|
||||||
|
* Sets _G.taut_nextPanel before returning to request a panel switch.
|
||||||
|
*
|
||||||
|
* Created by minjaesong on 2026-04-27
|
||||||
|
*/
|
||||||
|
|
||||||
|
const win = require("wintex")
|
||||||
|
|
||||||
|
const PANEL_COUNT = 7
|
||||||
|
const MY_PANEL = 3 // VIEW_SAMPLES
|
||||||
|
|
||||||
|
const [SCRH, SCRW] = con.getmaxyx()
|
||||||
|
const PANEL_Y = 4
|
||||||
|
const PANEL_H = SCRH - PANEL_Y
|
||||||
|
|
||||||
|
const colStatus = 253
|
||||||
|
const colContent = 240
|
||||||
|
const colHdr = 230
|
||||||
|
|
||||||
|
function drawSampleEditContents(wo) {
|
||||||
|
for (let y = PANEL_Y; y < SCRH; y++) {
|
||||||
|
con.move(y, 1)
|
||||||
|
con.color_pair(colContent, 255)
|
||||||
|
print(' '.repeat(SCRW))
|
||||||
|
}
|
||||||
|
con.move(PANEL_Y + 1, 3)
|
||||||
|
con.color_pair(colHdr, 255)
|
||||||
|
print('[ Sample Editor ]')
|
||||||
|
con.move(PANEL_Y + 3, 3)
|
||||||
|
con.color_pair(colStatus, 255)
|
||||||
|
print('placeholder — not yet implemented')
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawHints() {
|
||||||
|
con.move(SCRH, 1)
|
||||||
|
con.color_pair(colStatus, 255)
|
||||||
|
print(' '.repeat(SCRW - 1))
|
||||||
|
con.move(SCRH, 1)
|
||||||
|
con.color_pair(colHdr, 255); print('Tab ')
|
||||||
|
con.color_pair(colStatus, 255); print('Panel')
|
||||||
|
}
|
||||||
|
|
||||||
|
function sampleEditInput(wo, event) {
|
||||||
|
// placeholder — no interaction yet
|
||||||
|
}
|
||||||
|
|
||||||
|
const panel = new win.WindowObject(1, PANEL_Y, SCRW, PANEL_H, sampleEditInput, drawSampleEditContents, undefined, ()=>{})
|
||||||
|
|
||||||
|
panel.drawContents()
|
||||||
|
drawHints()
|
||||||
|
|
||||||
|
let done = false
|
||||||
|
while (!done) {
|
||||||
|
input.withEvent(event => {
|
||||||
|
if (event[0] !== 'key_down') return
|
||||||
|
const keysym = event[1]
|
||||||
|
const keyJustHit = (1 == event[2])
|
||||||
|
const shiftDown = (event.includes(59) || event.includes(60))
|
||||||
|
|
||||||
|
if (!keyJustHit) return
|
||||||
|
|
||||||
|
if (keysym === '<TAB>') {
|
||||||
|
_G.taut_nextPanel = (MY_PANEL + (shiftDown ? -1 : 1) + PANEL_COUNT) % PANEL_COUNT
|
||||||
|
done = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.processInput(event)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
BIN
assets/disk0/tvdos/bin/tautbtn.png
Normal file
BIN
assets/disk0/tvdos/bin/tautbtn.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 518 B |
1
assets/disk0/tvdos/bin/tautbtn.r8
Normal file
1
assets/disk0/tvdos/bin/tautbtn.r8
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<EFBFBD><EFBFBD>洄洄洄洄洄泚<EFBFBD>泚<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD>_
|
||||||
1
assets/disk0/tvdos/bin/tautbtn0.r8
Normal file
1
assets/disk0/tvdos/bin/tautbtn0.r8
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
BIN
assets/disk0/tvdos/bin/tautfont.kra
LFS
Normal file
BIN
assets/disk0/tvdos/bin/tautfont.kra
LFS
Normal file
Binary file not shown.
BIN
assets/disk0/tvdos/bin/tautfont_high.chr
Normal file
BIN
assets/disk0/tvdos/bin/tautfont_high.chr
Normal file
Binary file not shown.
BIN
assets/disk0/tvdos/bin/tautfont_low.chr
Normal file
BIN
assets/disk0/tvdos/bin/tautfont_low.chr
Normal file
Binary file not shown.
BIN
assets/disk0/tvdos/bin/tauthdr.png
Normal file
BIN
assets/disk0/tvdos/bin/tauthdr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 490 B |
BIN
assets/disk0/tvdos/bin/tauthdr.r8
Normal file
BIN
assets/disk0/tvdos/bin/tauthdr.r8
Normal file
Binary file not shown.
@@ -1,4 +1,6 @@
|
|||||||
const win = require("wintex")
|
const win = require("wintex")
|
||||||
|
const keys = require("keysym")
|
||||||
|
|
||||||
const COL_TEXT = 253
|
const COL_TEXT = 253
|
||||||
const COL_BACK = 255
|
const COL_BACK = 255
|
||||||
const COL_BACK_SEL = 81
|
const COL_BACK_SEL = 81
|
||||||
@@ -26,37 +28,45 @@ const COL_HL_EXT = {
|
|||||||
"wav": 31,
|
"wav": 31,
|
||||||
"adpcm": 31,
|
"adpcm": 31,
|
||||||
"pcm": 32,
|
"pcm": 32,
|
||||||
"mp3": 33,
|
// "mp3": 33,
|
||||||
"tad": 33,
|
"tad": 33,
|
||||||
"mp2": 34,
|
"mp2": 34,
|
||||||
"mv1": 213,
|
"mv1": 213,
|
||||||
"mv2": 213,
|
"mv2": 213,
|
||||||
"mv3": 213,
|
"mv3": 213,
|
||||||
"tav": 213,
|
"tav": 213,
|
||||||
|
"ipf": 190,
|
||||||
"ipf1": 190,
|
"ipf1": 190,
|
||||||
"ipf2": 190,
|
"ipf2": 190,
|
||||||
|
"im3": 190,
|
||||||
|
"tap": 190,
|
||||||
"txt": 223,
|
"txt": 223,
|
||||||
"md": 223,
|
"md": 223,
|
||||||
"log": 223
|
"log": 223,
|
||||||
|
"taud":109,
|
||||||
}
|
}
|
||||||
|
|
||||||
const EXEC_FUNS = {
|
const EXEC_FUNS = {
|
||||||
"wav": (f) => _G.shell.execute(`playwav "${f}" -i`),
|
"wav": (f) => _G.shell.execute(`playwav "${f}" -i`),
|
||||||
"adpcm": (f) => _G.shell.execute(`playwav "${f}" -i`),
|
"adpcm": (f) => _G.shell.execute(`playwav "${f}" -i`),
|
||||||
"mp3": (f) => _G.shell.execute(`playmp3 "${f}" -i`),
|
// "mp3": (f) => _G.shell.execute(`playmp3 "${f}" -i`),
|
||||||
"mp2": (f) => _G.shell.execute(`playmp2 "${f}" -i`),
|
"mp2": (f) => _G.shell.execute(`playmp2 "${f}" -i`),
|
||||||
"mv1": (f) => _G.shell.execute(`playmv1 "${f}" -i`),
|
"mv1": (f) => _G.shell.execute(`playmv1 "${f}" -i`),
|
||||||
"mv2": (f) => _G.shell.execute(`playtev "${f}" -i`),
|
"mv2": (f) => _G.shell.execute(`playtev "${f}" -i`),
|
||||||
"mv3": (f) => _G.shell.execute(`playtav "${f}" -i`),
|
"mv3": (f) => _G.shell.execute(`playtav "${f}" -i`),
|
||||||
"tav": (f) => _G.shell.execute(`playtav "${f}" -i`),
|
"tav": (f) => _G.shell.execute(`playtav "${f}" -i`),
|
||||||
|
"im3": (f) => _G.shell.execute(`playtav "${f}" -i`),
|
||||||
|
"tap": (f) => _G.shell.execute(`playtav "${f}" -i`),
|
||||||
"tad": (f) => _G.shell.execute(`playtad "${f}" -i`),
|
"tad": (f) => _G.shell.execute(`playtad "${f}" -i`),
|
||||||
"pcm": (f) => _G.shell.execute(`playpcm "${f}" -i`),
|
"pcm": (f) => _G.shell.execute(`playpcm "${f}" -i`),
|
||||||
|
"ipf": (f) => _G.shell.execute(`decodeipf "${f}" -i`),
|
||||||
"ipf1": (f) => _G.shell.execute(`decodeipf "${f}" -i`),
|
"ipf1": (f) => _G.shell.execute(`decodeipf "${f}" -i`),
|
||||||
"ipf2": (f) => _G.shell.execute(`decodeipf "${f}" -i`),
|
"ipf2": (f) => _G.shell.execute(`decodeipf "${f}" -i`),
|
||||||
"bas": (f) => _G.shell.execute(`basic "${f}"`),
|
"bas": (f) => _G.shell.execute(`basic "${f}"`),
|
||||||
"txt": (f) => _G.shell.execute(`less "${f}"`),
|
"txt": (f) => _G.shell.execute(`less "${f}"`),
|
||||||
"md": (f) => _G.shell.execute(`less "${f}"`),
|
"md": (f) => _G.shell.execute(`less "${f}"`),
|
||||||
"log": (f) => _G.shell.execute(`less "${f}"`)
|
"log": (f) => _G.shell.execute(`less "${f}"`),
|
||||||
|
"taud": (f) => _G.shell.execute(`taut "${f}"`),
|
||||||
}
|
}
|
||||||
|
|
||||||
let windowMode = 0 // 0 == left, 1 == right
|
let windowMode = 0 // 0 == left, 1 == right
|
||||||
@@ -412,6 +422,8 @@ let filenavOninput = (window, event) => {
|
|||||||
let keycodes = [event[3],event[4],event[5],event[6],event[7],event[8],event[9],event[10]]
|
let keycodes = [event[3],event[4],event[5],event[6],event[7],event[8],event[9],event[10]]
|
||||||
let keycode = keycodes[0]
|
let keycode = keycodes[0]
|
||||||
|
|
||||||
|
let scrollPeek = (LIST_HEIGHT / 3)|0
|
||||||
|
|
||||||
if (keyJustHit && keysym == "q") {
|
if (keyJustHit && keysym == "q") {
|
||||||
exit = true
|
exit = true
|
||||||
}
|
}
|
||||||
@@ -420,19 +432,19 @@ let filenavOninput = (window, event) => {
|
|||||||
redraw() // this would double-redraw (hence no panel switching) or something if redraw() is not merely a request to do so
|
redraw() // this would double-redraw (hence no panel switching) or something if redraw() is not merely a request to do so
|
||||||
}
|
}
|
||||||
else if (keysym == "<UP>") {
|
else if (keysym == "<UP>") {
|
||||||
[cursor[windowMode], scroll[windowMode]] = win.scrollVert(-1, dirFileList[windowMode].length, LIST_HEIGHT, cursor[windowMode], scroll[windowMode], 1)
|
[cursor[windowMode], scroll[windowMode]] = win.scrollVert(-1, dirFileList[windowMode].length, LIST_HEIGHT, cursor[windowMode], scroll[windowMode], scrollPeek)
|
||||||
drawFilePanel()
|
drawFilePanel()
|
||||||
}
|
}
|
||||||
else if (keysym == "<DOWN>") {
|
else if (keysym == "<DOWN>") {
|
||||||
[cursor[windowMode], scroll[windowMode]] = win.scrollVert(+1, dirFileList[windowMode].length, LIST_HEIGHT, cursor[windowMode], scroll[windowMode], 1)
|
[cursor[windowMode], scroll[windowMode]] = win.scrollVert(+1, dirFileList[windowMode].length, LIST_HEIGHT, cursor[windowMode], scroll[windowMode], scrollPeek)
|
||||||
drawFilePanel()
|
drawFilePanel()
|
||||||
}
|
}
|
||||||
else if (keysym == "<PAGE_UP>") {
|
else if (keysym == "<PAGE_UP>") {
|
||||||
[cursor[windowMode], scroll[windowMode]] = win.scrollVert(-LIST_HEIGHT, dirFileList[windowMode].length, LIST_HEIGHT, cursor[windowMode], scroll[windowMode], 1)
|
[cursor[windowMode], scroll[windowMode]] = win.scrollVert(-LIST_HEIGHT, dirFileList[windowMode].length, LIST_HEIGHT, cursor[windowMode], scroll[windowMode], scrollPeek)
|
||||||
drawFilePanel()
|
drawFilePanel()
|
||||||
}
|
}
|
||||||
else if (keysym == "<PAGE_DOWN>") {
|
else if (keysym == "<PAGE_DOWN>") {
|
||||||
[cursor[windowMode], scroll[windowMode]] = win.scrollVert(+LIST_HEIGHT, dirFileList[windowMode].length, LIST_HEIGHT, cursor[windowMode], scroll[windowMode], 1)
|
[cursor[windowMode], scroll[windowMode]] = win.scrollVert(+LIST_HEIGHT, dirFileList[windowMode].length, LIST_HEIGHT, cursor[windowMode], scroll[windowMode], scrollPeek)
|
||||||
drawFilePanel()
|
drawFilePanel()
|
||||||
}
|
}
|
||||||
else if (keyJustHit && keycode == 66) { // enter
|
else if (keyJustHit && keycode == 66) { // enter
|
||||||
@@ -665,7 +677,7 @@ while (!exit) {
|
|||||||
let keysym = event[1]
|
let keysym = event[1]
|
||||||
let keyJustHit = (1 == event[2])
|
let keyJustHit = (1 == event[2])
|
||||||
|
|
||||||
if (keyJustHit && event[3] != 66) { // release the latch right away if the key is not Return
|
if (keyJustHit && event[3] != keys.ENTER) { // release the latch right away if the key is not Return
|
||||||
firstRunLatch = false
|
firstRunLatch = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
31
assets/disk0/tvdos/include/font.mjs
Normal file
31
assets/disk0/tvdos/include/font.mjs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
function setHighRom(fullPath) {
|
||||||
|
const fontFile = files.open(fullPath)
|
||||||
|
|
||||||
|
// upload font
|
||||||
|
const fontData = fontFile.bread()
|
||||||
|
for (let i = 0; i < 1920; i++) sys.poke(-133121 - i, fontData[i])
|
||||||
|
sys.poke(-1299460, 19) // write to high rom
|
||||||
|
|
||||||
|
fontFile.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLowRom(fullPath) {
|
||||||
|
const fontFile = files.open(fullPath)
|
||||||
|
|
||||||
|
// upload font
|
||||||
|
const fontData = fontFile.bread()
|
||||||
|
for (let i = 0; i < 1920; i++) sys.poke(-133121 - i, fontData[i])
|
||||||
|
sys.poke(-1299460, 18) // write to low rom
|
||||||
|
|
||||||
|
fontFile.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetHighRom() {
|
||||||
|
sys.poke(-1299460, 21)
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetLowRom() {
|
||||||
|
sys.poke(-1299460, 20)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports = { setHighRom, setLowRom, resetHighRom, resetLowRom }
|
||||||
305
assets/disk0/tvdos/include/getopt.mjs
Normal file
305
assets/disk0/tvdos/include/getopt.mjs
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
/*
|
||||||
|
* getopt.js: node.js implementation of POSIX getopt() (and then some)
|
||||||
|
*
|
||||||
|
* Copyright 2011 David Pacheco. All rights reserved.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var ASSERT = require('assert').ok;
|
||||||
|
|
||||||
|
function goError(msg)
|
||||||
|
{
|
||||||
|
return (new Error('getopt: ' + msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The BasicParser is our primary interface to the outside world. The
|
||||||
|
* documentation for this object and its public methods is contained in
|
||||||
|
* the included README.md.
|
||||||
|
*/
|
||||||
|
function goBasicParser(optstring, argv, optind)
|
||||||
|
{
|
||||||
|
var ii;
|
||||||
|
|
||||||
|
ASSERT(optstring || optstring === '', 'optstring is required');
|
||||||
|
ASSERT(optstring.constructor === String, 'optstring must be a string');
|
||||||
|
ASSERT(argv, 'argv is required');
|
||||||
|
ASSERT(argv.constructor === Array, 'argv must be an array');
|
||||||
|
|
||||||
|
this.gop_argv = new Array(argv.length);
|
||||||
|
this.gop_options = {};
|
||||||
|
this.gop_aliases = {};
|
||||||
|
this.gop_optind = optind !== undefined ? optind : 2;
|
||||||
|
this.gop_subind = 0;
|
||||||
|
|
||||||
|
for (ii = 0; ii < argv.length; ii++) {
|
||||||
|
ASSERT(argv[ii].constructor === String,
|
||||||
|
'argv must be string array');
|
||||||
|
this.gop_argv[ii] = argv[ii];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.parseOptstr(optstring);
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.BasicParser = goBasicParser;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parse the option string and update the following fields:
|
||||||
|
*
|
||||||
|
* gop_silent Whether to log errors to stderr. Silent mode is
|
||||||
|
* indicated by a leading ':' in the option string.
|
||||||
|
*
|
||||||
|
* gop_options Maps valid single-letter-options to booleans indicating
|
||||||
|
* whether each option is required.
|
||||||
|
*
|
||||||
|
* gop_aliases Maps valid long options to the corresponding
|
||||||
|
* single-letter short option.
|
||||||
|
*/
|
||||||
|
goBasicParser.prototype.parseOptstr = function (optstr)
|
||||||
|
{
|
||||||
|
var chr, cp, alias, arg, ii;
|
||||||
|
|
||||||
|
ii = 0;
|
||||||
|
if (optstr.length > 0 && optstr[0] == ':') {
|
||||||
|
this.gop_silent = true;
|
||||||
|
ii++;
|
||||||
|
} else {
|
||||||
|
this.gop_silent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (ii < optstr.length) {
|
||||||
|
chr = optstr[ii];
|
||||||
|
arg = false;
|
||||||
|
|
||||||
|
if (!/^[\w\d\u1000-\u1100]$/.test(chr))
|
||||||
|
throw (goError('invalid optstring: only alphanumeric ' +
|
||||||
|
'characters and unicode characters between ' +
|
||||||
|
'\\u1000-\\u1100 may be used as options: ' + chr));
|
||||||
|
|
||||||
|
if (ii + 1 < optstr.length && optstr[ii + 1] == ':') {
|
||||||
|
arg = true;
|
||||||
|
ii++;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.gop_options[chr] = arg;
|
||||||
|
|
||||||
|
while (ii + 1 < optstr.length && optstr[ii + 1] == '(') {
|
||||||
|
ii++;
|
||||||
|
cp = optstr.indexOf(')', ii + 1);
|
||||||
|
if (cp == -1)
|
||||||
|
throw (goError('invalid optstring: missing ' +
|
||||||
|
'")" to match "(" at char ' + ii));
|
||||||
|
|
||||||
|
alias = optstr.substring(ii + 1, cp);
|
||||||
|
this.gop_aliases[alias] = chr;
|
||||||
|
ii = cp;
|
||||||
|
}
|
||||||
|
|
||||||
|
ii++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
goBasicParser.prototype.optind = function ()
|
||||||
|
{
|
||||||
|
return (this.gop_optind);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For documentation on what getopt() does, see README.md. The following
|
||||||
|
* implementation invariants are maintained by getopt() and its helper methods:
|
||||||
|
*
|
||||||
|
* this.gop_optind Refers to the element of gop_argv that contains
|
||||||
|
* the next argument to be processed. This may
|
||||||
|
* exceed gop_argv, in which case the end of input
|
||||||
|
* has been reached.
|
||||||
|
*
|
||||||
|
* this.gop_subind Refers to the character inside
|
||||||
|
* this.gop_options[this.gop_optind] which begins
|
||||||
|
* the next option to be processed. This may never
|
||||||
|
* exceed the length of gop_argv[gop_optind], so
|
||||||
|
* when incrementing this value we must always
|
||||||
|
* check if we should instead increment optind and
|
||||||
|
* reset subind to 0.
|
||||||
|
*
|
||||||
|
* That is, when any of these functions is entered, the above indices' values
|
||||||
|
* are as described above. getopt() itself and getoptArgument() may both be
|
||||||
|
* called at the end of the input, so they check whether optind exceeds
|
||||||
|
* argv.length. getoptShort() and getoptLong() are called only when the indices
|
||||||
|
* already point to a valid short or long option, respectively.
|
||||||
|
*
|
||||||
|
* getopt() processes the next option as follows:
|
||||||
|
*
|
||||||
|
* o If gop_optind > gop_argv.length, then we already parsed all arguments.
|
||||||
|
*
|
||||||
|
* o If gop_subind == 0, then we're looking at the start of an argument:
|
||||||
|
*
|
||||||
|
* o Check for special cases like '-', '--', and non-option arguments.
|
||||||
|
* If present, update the indices and return the appropriate value.
|
||||||
|
*
|
||||||
|
* o Check for a long-form option (beginning with '--'). If present,
|
||||||
|
* delegate to getoptLong() and return the result.
|
||||||
|
*
|
||||||
|
* o Otherwise, advance subind past the argument's leading '-' and
|
||||||
|
* continue as though gop_subind != 0 (since that's now the case).
|
||||||
|
*
|
||||||
|
* o Delegate to getoptShort() and return the result.
|
||||||
|
*/
|
||||||
|
goBasicParser.prototype.getopt = function ()
|
||||||
|
{
|
||||||
|
if (this.gop_optind >= this.gop_argv.length)
|
||||||
|
/* end of input */
|
||||||
|
return (undefined);
|
||||||
|
|
||||||
|
var arg = this.gop_argv[this.gop_optind];
|
||||||
|
|
||||||
|
if (this.gop_subind === 0) {
|
||||||
|
if (arg == '-' || arg === '' || arg[0] != '-')
|
||||||
|
return (undefined);
|
||||||
|
|
||||||
|
if (arg == '--') {
|
||||||
|
this.gop_optind++;
|
||||||
|
this.gop_subind = 0;
|
||||||
|
return (undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg[1] == '-')
|
||||||
|
return (this.getoptLong());
|
||||||
|
|
||||||
|
this.gop_subind++;
|
||||||
|
ASSERT(this.gop_subind < arg.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (this.getoptShort());
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Implements getopt() for the case where optind/subind point to a short option.
|
||||||
|
*/
|
||||||
|
goBasicParser.prototype.getoptShort = function ()
|
||||||
|
{
|
||||||
|
var arg, chr;
|
||||||
|
|
||||||
|
ASSERT(this.gop_optind < this.gop_argv.length);
|
||||||
|
arg = this.gop_argv[this.gop_optind];
|
||||||
|
ASSERT(this.gop_subind < arg.length);
|
||||||
|
chr = arg[this.gop_subind];
|
||||||
|
|
||||||
|
if (++this.gop_subind >= arg.length) {
|
||||||
|
this.gop_optind++;
|
||||||
|
this.gop_subind = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(chr in this.gop_options))
|
||||||
|
return (this.errInvalidOption(chr));
|
||||||
|
|
||||||
|
if (!this.gop_options[chr])
|
||||||
|
return ({ option: chr });
|
||||||
|
|
||||||
|
return (this.getoptArgument(chr));
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Implements getopt() for the case where optind/subind point to a long option.
|
||||||
|
*/
|
||||||
|
goBasicParser.prototype.getoptLong = function ()
|
||||||
|
{
|
||||||
|
var arg, alias, chr, eq;
|
||||||
|
|
||||||
|
ASSERT(this.gop_subind === 0);
|
||||||
|
ASSERT(this.gop_optind < this.gop_argv.length);
|
||||||
|
arg = this.gop_argv[this.gop_optind];
|
||||||
|
ASSERT(arg.length > 2 && arg[0] == '-' && arg[1] == '-');
|
||||||
|
|
||||||
|
eq = arg.indexOf('=');
|
||||||
|
alias = arg.substring(2, eq == -1 ? arg.length : eq);
|
||||||
|
if (!(alias in this.gop_aliases))
|
||||||
|
return (this.errInvalidOption(alias));
|
||||||
|
|
||||||
|
chr = this.gop_aliases[alias];
|
||||||
|
ASSERT(chr in this.gop_options);
|
||||||
|
|
||||||
|
if (!this.gop_options[chr]) {
|
||||||
|
if (eq != -1)
|
||||||
|
return (this.errExtraArg(alias));
|
||||||
|
|
||||||
|
this.gop_optind++; /* eat this argument */
|
||||||
|
return ({ option: chr });
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Advance optind/subind for the argument value and retrieve it.
|
||||||
|
*/
|
||||||
|
if (eq == -1)
|
||||||
|
this.gop_optind++;
|
||||||
|
else
|
||||||
|
this.gop_subind = eq + 1;
|
||||||
|
|
||||||
|
return (this.getoptArgument(chr));
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For the given option letter 'chr' that takes an argument, assumes that
|
||||||
|
* optind/subind point to the argument (or denote the end of input) and return
|
||||||
|
* the appropriate getopt() return value for this option and argument (or return
|
||||||
|
* the appropriate error).
|
||||||
|
*/
|
||||||
|
goBasicParser.prototype.getoptArgument = function (chr)
|
||||||
|
{
|
||||||
|
var arg;
|
||||||
|
|
||||||
|
if (this.gop_optind >= this.gop_argv.length)
|
||||||
|
return (this.errMissingArg(chr));
|
||||||
|
|
||||||
|
arg = this.gop_argv[this.gop_optind].substring(this.gop_subind);
|
||||||
|
this.gop_optind++;
|
||||||
|
this.gop_subind = 0;
|
||||||
|
return ({ option: chr, optarg: arg });
|
||||||
|
};
|
||||||
|
|
||||||
|
goBasicParser.prototype.errMissingArg = function (chr)
|
||||||
|
{
|
||||||
|
if (this.gop_silent)
|
||||||
|
return ({ option: ':', optopt: chr });
|
||||||
|
|
||||||
|
process.stderr.write('option requires an argument -- ' + chr + '\n');
|
||||||
|
return ({ option: '?', optopt: chr, error: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
goBasicParser.prototype.errInvalidOption = function (chr)
|
||||||
|
{
|
||||||
|
if (!this.gop_silent)
|
||||||
|
process.stderr.write('illegal option -- ' + chr + '\n');
|
||||||
|
|
||||||
|
return ({ option: '?', optopt: chr, error: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This error is not specified by POSIX, but neither is the notion of specifying
|
||||||
|
* long option arguments using "=" in the same argv-argument, but it's common
|
||||||
|
* practice and pretty convenient.
|
||||||
|
*/
|
||||||
|
goBasicParser.prototype.errExtraArg = function (chr)
|
||||||
|
{
|
||||||
|
if (!this.gop_silent)
|
||||||
|
process.stderr.write('option expects no argument -- ' +
|
||||||
|
chr + '\n');
|
||||||
|
|
||||||
|
return ({ option: '?', optopt: chr, error: true });
|
||||||
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/**
|
||||||
TVDOS Graphics Library
|
* LibGL — TVDOS Graphics Library
|
||||||
|
* Has no affiliation with OpenGL by Khronos Group
|
||||||
Has no affiliation with OpenGL by Khronos Group
|
* @author CuriousTorvald
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ exports.SpriteSheet = function(tilew, tileh, tex) {
|
|||||||
return ty;
|
return ty;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
exports.drawTexPattern = function(texture, x, y, width, height, framebuffer, fgcol, bgcol) {
|
exports.drawTexPattern = function(texture, x, y, width = texture.width, height = texture.height, framebuffer = false, fgcol, bgcol) {
|
||||||
if (!(texture instanceof exports.Texture) && !(texture instanceof exports.MonoTex)) throw Error("Texture is not a GL Texture types");
|
if (!(texture instanceof exports.Texture) && !(texture instanceof exports.MonoTex)) throw Error("Texture is not a GL Texture types");
|
||||||
|
|
||||||
let paint = (!framebuffer) ? graphics.plotPixel : graphics.plotPixel2
|
let paint = (!framebuffer) ? graphics.plotPixel : graphics.plotPixel2
|
||||||
|
|||||||
80
assets/disk0/tvdos/include/keysym.mjs
Normal file
80
assets/disk0/tvdos/include/keysym.mjs
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* These are key symbols returned by `input.withEvent`, NOT `con.getch()`
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports = {
|
||||||
|
NUM_0:7,
|
||||||
|
NUM_1:8,
|
||||||
|
NUM_2:9,
|
||||||
|
NUM_3:10,
|
||||||
|
NUM_4:11,
|
||||||
|
NUM_5:12,
|
||||||
|
NUM_6:13,
|
||||||
|
NUM_7:14,
|
||||||
|
NUM_8:15,
|
||||||
|
NUM_9:16,
|
||||||
|
A:29,
|
||||||
|
ALT_LEFT:57,
|
||||||
|
ALT_RIGHT:58,
|
||||||
|
APOSTROPHE:75,
|
||||||
|
AT:77,
|
||||||
|
B:30,
|
||||||
|
BACK:4,
|
||||||
|
BACKSLASH:73,
|
||||||
|
C:31,
|
||||||
|
CAPS_LOCK:115,
|
||||||
|
COMMA:55,
|
||||||
|
D:32,
|
||||||
|
DEL:67,
|
||||||
|
BACKSPACE:67,
|
||||||
|
FORWARD_DEL:112,
|
||||||
|
DOWN:20,
|
||||||
|
LEFT:21,
|
||||||
|
RIGHT:22,
|
||||||
|
UP:19,
|
||||||
|
E:33,
|
||||||
|
ENTER:66,
|
||||||
|
EQUALS:70,
|
||||||
|
F:34,
|
||||||
|
G:35,
|
||||||
|
GRAVE:68,
|
||||||
|
H:36,
|
||||||
|
HOME:3,
|
||||||
|
I:37,
|
||||||
|
J:38,
|
||||||
|
K:39,
|
||||||
|
L:40,
|
||||||
|
LEFT_BRACKET:71,
|
||||||
|
M:41,
|
||||||
|
MINUS:69,
|
||||||
|
N:42,
|
||||||
|
O:43,
|
||||||
|
P:44,
|
||||||
|
PERIOD:56,
|
||||||
|
PLUS:81,
|
||||||
|
Q:45,
|
||||||
|
R:46,
|
||||||
|
RIGHT_BRACKET:72,
|
||||||
|
S:47,
|
||||||
|
SEMICOLON:74,
|
||||||
|
SHIFT_LEFT:59,
|
||||||
|
SHIFT_RIGHT:60,
|
||||||
|
SLASH:76,
|
||||||
|
SPACE:62,
|
||||||
|
SYM:63, // on MacOS, this is Command (⌘)
|
||||||
|
T:48,
|
||||||
|
TAB:61,
|
||||||
|
U:49,
|
||||||
|
V:50,
|
||||||
|
W:51,
|
||||||
|
X:52,
|
||||||
|
Y:53,
|
||||||
|
Z:54,
|
||||||
|
CONTROL_LEFT:129,
|
||||||
|
CONTROL_RIGHT:130,
|
||||||
|
ESCAPE:111,
|
||||||
|
END:123,
|
||||||
|
INSERT:124,
|
||||||
|
PAGE_UP:92,
|
||||||
|
PAGE_DOWN:93,
|
||||||
|
}
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* LibPCM — PCM decoder for TSVM
|
||||||
|
* @author CuriousTorvald
|
||||||
|
*/
|
||||||
|
|
||||||
const HW_SAMPLING_RATE = 32000
|
const HW_SAMPLING_RATE = 32000
|
||||||
function printdbg(s) { if (0) serial.println(s) }
|
function printdbg(s) { if (0) serial.println(s) }
|
||||||
function printvis(s) { if (0) println(s) }
|
function printvis(s) { if (0) println(s) }
|
||||||
@@ -29,7 +34,7 @@ function s16Tou8(i) {
|
|||||||
}
|
}
|
||||||
function u16Tos16(i) { return (i > 32767) ? i - 65536 : i }
|
function u16Tos16(i) { return (i > 32767) ? i - 65536 : i }
|
||||||
function randomRound(k) {
|
function randomRound(k) {
|
||||||
let rnd = (Math.random() + Math.random()) / 2.0 // this produces triangular distribution
|
let rnd = Math.random() // note to self: no triangular here
|
||||||
return (rnd < (k - (k|0))) ? Math.ceil(k) : Math.floor(k)
|
return (rnd < (k - (k|0))) ? Math.ceil(k) : Math.floor(k)
|
||||||
}
|
}
|
||||||
function lerp(start, end, x) {
|
function lerp(start, end, x) {
|
||||||
|
|||||||
414
assets/disk0/tvdos/include/psg.mjs
Normal file
414
assets/disk0/tvdos/include/psg.mjs
Normal file
@@ -0,0 +1,414 @@
|
|||||||
|
/**
|
||||||
|
* LibPSG — PSG emulator and mixer for TSVM
|
||||||
|
* Software-mixes various PSG channels and sends them to sound device as PCM
|
||||||
|
* @author CuriousTorvald
|
||||||
|
*/
|
||||||
|
|
||||||
|
const HW_SAMPLING_RATE = 32000
|
||||||
|
|
||||||
|
function clamp(val, low, hi) { return (val < low) ? low : (val > hi) ? hi : val }
|
||||||
|
function clampS16(i) { return clamp(i, -32768, 32767) }
|
||||||
|
const uNybToSnyb = [0,1,2,3,4,5,6,7,-8,-7,-6,-5,-4,-3,-2,-1]
|
||||||
|
// returns: [unsigned high, unsigned low, signed high, signed low]
|
||||||
|
function getNybbles(b) { return [b >> 4, b & 15, uNybToSnyb[b >> 4], uNybToSnyb[b & 15]] }
|
||||||
|
function s8Tou8(i) { return i + 128 }
|
||||||
|
function s16Tou8(i) {
|
||||||
|
// return s8Tou8((i >> 8) & 255)
|
||||||
|
// apply dithering
|
||||||
|
let ufval = (i / 65536.0) + 0.5
|
||||||
|
let ival = randomRound(ufval * 255.0)
|
||||||
|
return ival|0
|
||||||
|
}
|
||||||
|
function u16Tos16(i) { return (i > 32767) ? i - 65536 : i }
|
||||||
|
function randomRound(k) {
|
||||||
|
let rnd = Math.random() // note to self: no triangular here
|
||||||
|
return (rnd < (k - (k|0))) ? Math.ceil(k) : Math.floor(k)
|
||||||
|
}
|
||||||
|
function lerp(start, end, x) {
|
||||||
|
return (1 - x) * start + x * end
|
||||||
|
}
|
||||||
|
function lerpAndRound(start, end, x) {
|
||||||
|
return Math.round(lerp(start, end, x))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// output format: immediately uploadable into TSVM audio adapter
|
||||||
|
|
||||||
|
// ── Internal helpers ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function secToSamples(sec) { return Math.round(HW_SAMPLING_RATE * sec) }
|
||||||
|
|
||||||
|
function isNative(buf) { return buf.native }
|
||||||
|
|
||||||
|
function readU8(buf, ch, i) {
|
||||||
|
return isNative(buf) ? (sys.peek(buf[ch] + i) & 255) : buf[ch][i]
|
||||||
|
}
|
||||||
|
function writeU8(buf, ch, i, v) {
|
||||||
|
if (isNative(buf)) sys.poke(buf[ch] + i, v)
|
||||||
|
else buf[ch][i] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Buffer management ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function makeBuffer(length) {
|
||||||
|
// returns [Uint8Array, Uint8Array] (stereo) that will be used to collect samples made by LibPSG.
|
||||||
|
// Length: seconds. Number of elements: round(HW_SAMPLING_RATE * length)
|
||||||
|
const n = secToSamples(length)
|
||||||
|
const L = new Uint8Array(n)
|
||||||
|
const R = new Uint8Array(n)
|
||||||
|
L.fill(128)
|
||||||
|
R.fill(128)
|
||||||
|
return { 0: L, 1: R, samples: n, native: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeBufferNative(length) {
|
||||||
|
// returns native buffer object (stereo) that will be used to collect samples made by LibPSG.
|
||||||
|
// Length: seconds. Number of elements: round(HW_SAMPLING_RATE * length)
|
||||||
|
// Free with freeBufferNative() when done.
|
||||||
|
const n = secToSamples(length)
|
||||||
|
const L = sys.malloc(n); sys.memset(L, 128, n)
|
||||||
|
const R = sys.malloc(n); sys.memset(R, 128, n)
|
||||||
|
return { 0: L, 1: R, samples: n, native: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
function freeBufferNative(buf) {
|
||||||
|
sys.free(buf[0])
|
||||||
|
sys.free(buf[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearBuffer(buf, offsetSec, lengthSec) {
|
||||||
|
// Re-silence a buffer region (fill with 128) for re-use across frames.
|
||||||
|
const start = (offsetSec != null) ? secToSamples(offsetSec) : 0
|
||||||
|
const total = (lengthSec != null) ? secToSamples(lengthSec) : (buf.samples - start)
|
||||||
|
if (!buf.native) {
|
||||||
|
buf[0].fill(128, start, start + total)
|
||||||
|
buf[1].fill(128, start, start + total)
|
||||||
|
} else {
|
||||||
|
sys.memset(buf[0] + start, 128, total)
|
||||||
|
sys.memset(buf[1] + start, 128, total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Shared mix core ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// sampleFn(i) must return a float in [-1, 1].
|
||||||
|
// Mixing maths: decode u8 → s16, apply op, clamp, dither back to u8.
|
||||||
|
function mixInto(buf, lengthSec, offsetSec, op, amp, pan, sampleFn) {
|
||||||
|
const startIdx = secToSamples(offsetSec)
|
||||||
|
const n = secToSamples(lengthSec)
|
||||||
|
// Linear pan law: centre (pan=0) → both channels at full amp
|
||||||
|
const gainL = Math.max(0, Math.min(1, 1.0 - pan))
|
||||||
|
const gainR = Math.max(0, Math.min(1, 1.0 + pan))
|
||||||
|
const opCode = (op === 'sub') ? 1 : (op === 'mul') ? 2 : 0 // default: add
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const v = sampleFn(i) // oscillator value in [-1, 1]
|
||||||
|
const oscBase = v * amp * 32767
|
||||||
|
const oscL = Math.round(oscBase * gainL) | 0
|
||||||
|
const oscR = Math.round(oscBase * gainR) | 0
|
||||||
|
for (let ch = 0; ch < 2; ch++) {
|
||||||
|
const osc = (ch === 0) ? oscL : oscR
|
||||||
|
const cur = (readU8(buf, ch, startIdx + i) - 128) << 8
|
||||||
|
let out
|
||||||
|
switch (opCode) {
|
||||||
|
case 0: out = cur + osc; break
|
||||||
|
case 1: out = cur - osc; break
|
||||||
|
case 2: out = (cur * osc) >> 15; break
|
||||||
|
}
|
||||||
|
writeU8(buf, ch, startIdx + i, s16Tou8(clampS16(out)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Waveform generators ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function makeSquare(buf, length, offset, freq, duty, op, amp, pan, phaseOffset) {
|
||||||
|
// buffer: [Uint8Array, Uint8Array] or native buffer
|
||||||
|
// length: in seconds
|
||||||
|
// offset: in seconds
|
||||||
|
// duty: 0.0 to 1.0. default 0.5 (fraction of period where output is +1)
|
||||||
|
// freq: Hz
|
||||||
|
// op: add / mul / sub; default: add
|
||||||
|
// amp: 0.0 to 1.0; default: 0.5
|
||||||
|
// pan: -1.0 to 1.0; default: 0.0
|
||||||
|
// phaseOffset: optional absolute-time base (seconds) added to phase calc only,
|
||||||
|
// not to the buffer write position — use to ensure phase continuity
|
||||||
|
// across successive calls (e.g. frame boundaries).
|
||||||
|
if (duty == null) duty = 0.5
|
||||||
|
if (op == null) op = 'add'
|
||||||
|
if (amp == null) amp = 0.5
|
||||||
|
if (pan == null) pan = 0.0
|
||||||
|
const tBase = (phaseOffset || 0) + offset
|
||||||
|
mixInto(buf, length, offset, op, amp, pan, function(i) {
|
||||||
|
const phase = ((tBase + i / HW_SAMPLING_RATE) * freq) % 1
|
||||||
|
return (phase < duty) ? 1.0 : -1.0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeTriangle(buf, length, offset, freq, duty, op, amp, pan, phaseOffset) {
|
||||||
|
// buffer: [Uint8Array, Uint8Array] or native buffer
|
||||||
|
// length: in seconds
|
||||||
|
// offset: in seconds
|
||||||
|
// duty: skew. -1.0 = falling sawtooth, 0.0 = symmetric triangle, 1.0 = rising sawtooth
|
||||||
|
// freq: Hz
|
||||||
|
// op: add / mul / sub; default: add
|
||||||
|
// amp: 0.0 to 1.0; default: 0.5
|
||||||
|
// pan: -1.0 to 1.0; default: 0.0
|
||||||
|
// phaseOffset: optional absolute-time base (seconds) added to phase calc only —
|
||||||
|
// see makeSquare for details.
|
||||||
|
if (duty == null) duty = 0.0
|
||||||
|
if (op == null) op = 'add'
|
||||||
|
if (amp == null) amp = 0.5
|
||||||
|
if (pan == null) pan = 0.0
|
||||||
|
// riseFrac: fraction of period spent rising from -1 to +1
|
||||||
|
// 0.0 → falling saw, 0.5 → symmetric triangle, 1.0 → rising saw
|
||||||
|
const riseFrac = (duty + 1.0) * 0.5
|
||||||
|
const tBase = (phaseOffset || 0) + offset
|
||||||
|
mixInto(buf, length, offset, op, amp, pan, function(i) {
|
||||||
|
const phase = ((tBase + i / HW_SAMPLING_RATE) * freq) % 1
|
||||||
|
if (riseFrac <= 0) {
|
||||||
|
return 1.0 - 2.0 * phase // falling saw
|
||||||
|
} else if (riseFrac >= 1) {
|
||||||
|
return -1.0 + 2.0 * phase // rising saw
|
||||||
|
} else if (phase < riseFrac) {
|
||||||
|
return -1.0 + 2.0 * (phase / riseFrac) // rising slope
|
||||||
|
} else {
|
||||||
|
return 1.0 - 2.0 * ((phase - riseFrac) / (1.0 - riseFrac)) // falling slope
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeAliasedTriangle(buf, length, offset, freq, duty, op, amp, pan, phaseOffset) {
|
||||||
|
// buffer: [Uint8Array, Uint8Array] or native buffer
|
||||||
|
// Famicom-style triangle — output is quantised to 16 DAC levels (4-bit, NES APU style).
|
||||||
|
// The staircase quantisation introduces harmonics that mimic NES character.
|
||||||
|
// length: in seconds
|
||||||
|
// offset: in seconds
|
||||||
|
// duty: skew. -1.0 = falling sawtooth, 0.0 = symmetric triangle, 1.0 = rising sawtooth
|
||||||
|
// freq: Hz
|
||||||
|
// op: add / mul / sub; default: add
|
||||||
|
// amp: 0.0 to 1.0; default: 0.5
|
||||||
|
// pan: -1.0 to 1.0; default: 0.0
|
||||||
|
// phaseOffset: optional absolute-time base (seconds) added to phase calc only —
|
||||||
|
// see makeSquare for details.
|
||||||
|
if (duty == null) duty = 0.0
|
||||||
|
if (op == null) op = 'add'
|
||||||
|
if (amp == null) amp = 0.5
|
||||||
|
if (pan == null) pan = 0.0
|
||||||
|
const riseFrac = (duty + 1.0) * 0.5
|
||||||
|
const tBase = (phaseOffset || 0) + offset
|
||||||
|
mixInto(buf, length, offset, op, amp, pan, function(i) {
|
||||||
|
const phase = ((tBase + i / HW_SAMPLING_RATE) * freq) % 1
|
||||||
|
let v
|
||||||
|
if (riseFrac <= 0) {
|
||||||
|
v = 1.0 - 2.0 * phase
|
||||||
|
} else if (riseFrac >= 1) {
|
||||||
|
v = -1.0 + 2.0 * phase
|
||||||
|
} else if (phase < riseFrac) {
|
||||||
|
v = -1.0 + 2.0 * (phase / riseFrac)
|
||||||
|
} else {
|
||||||
|
v = 1.0 - 2.0 * ((phase - riseFrac) / (1.0 - riseFrac))
|
||||||
|
}
|
||||||
|
// Quantise to 16 levels (NES triangle 4-bit DAC: 0..15 → -1..+1)
|
||||||
|
const level = Math.max(0, Math.min(15, Math.round((v + 1.0) * 7.5)))
|
||||||
|
return level / 7.5 - 1.0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── LFSR helpers (for noise types 1 and 2) ─────────────────────────────────
|
||||||
|
|
||||||
|
function lfsrStep(state, mode) {
|
||||||
|
// mode 0 (long/NES mode 0): feedback tap at bit 1; period 32767
|
||||||
|
// mode 1 (short/NES mode 1): feedback tap at bit 6; period 93 (metallic/tonal)
|
||||||
|
const bit0 = state & 1
|
||||||
|
const bitTap = (mode === 0) ? (state >> 1) & 1 : (state >> 6) & 1
|
||||||
|
const feed = bit0 ^ bitTap
|
||||||
|
return ((feed << 14) | (state >> 1)) & 0x7FFF
|
||||||
|
}
|
||||||
|
|
||||||
|
function lfsrAdvance(state, steps, mode) {
|
||||||
|
for (let k = 0; k < steps; k++) state = lfsrStep(state, mode)
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
// NES APU documented LFSR periods
|
||||||
|
const LFSR_PERIOD_LONG = 32767 // mode 0
|
||||||
|
const LFSR_PERIOD_SHORT = 93 // mode 1
|
||||||
|
|
||||||
|
function makeNoise(buf, length, offset, freq, type, op, amp, pan, phaseOffset) {
|
||||||
|
// buffer: [Uint8Array, Uint8Array] or native buffer
|
||||||
|
// length: in seconds
|
||||||
|
// offset: in seconds
|
||||||
|
// type:
|
||||||
|
// -1: 8-bit white noise (random float per period, sample-and-hold)
|
||||||
|
// 0: 1-bit white noise (random ±1 per period, sample-and-hold)
|
||||||
|
// 1: 1-bit LFSR long mode — NES mode 0, tap=bit0^bit1, period 32767 (full-spectrum)
|
||||||
|
// 2: 1-bit LFSR short mode — NES mode 1, tap=bit0^bit6, period 93 (metallic/tonal)
|
||||||
|
// freq: Hz (clock rate of the noise generator)
|
||||||
|
// op: add / mul / sub; default: add
|
||||||
|
// amp: 0.0 to 1.0; default: 0.5
|
||||||
|
// pan: -1.0 to 1.0; default: 0.0
|
||||||
|
// phaseOffset: optional absolute-time base (seconds) added to phase/LFSR calc only —
|
||||||
|
// see makeSquare for details.
|
||||||
|
//
|
||||||
|
// LFSR types (1 and 2) are deterministic given (phaseOffset+offset, freq): calling
|
||||||
|
// with monotonically advancing phaseOffset+offset produces a seamless noise stream
|
||||||
|
// across frames. White noise types (-1, 0) are random per call.
|
||||||
|
if (op == null) op = 'add'
|
||||||
|
if (amp == null) amp = 0.5
|
||||||
|
if (pan == null) pan = 0.0
|
||||||
|
const tBase = (phaseOffset || 0) + offset
|
||||||
|
|
||||||
|
if (type === -1) {
|
||||||
|
// 8-bit white: new random float in [-1, 1] each clock period
|
||||||
|
let prevClock = -1
|
||||||
|
let noiseVal = 0.0
|
||||||
|
mixInto(buf, length, offset, op, amp, pan, function(i) {
|
||||||
|
const currentClock = Math.floor((tBase + i / HW_SAMPLING_RATE) * freq) | 0
|
||||||
|
if (currentClock !== prevClock) {
|
||||||
|
prevClock = currentClock
|
||||||
|
noiseVal = Math.random() * 2.0 - 1.0
|
||||||
|
}
|
||||||
|
return noiseVal
|
||||||
|
})
|
||||||
|
} else if (type === 0) {
|
||||||
|
// 1-bit white: random ±1 each clock period
|
||||||
|
let prevClock = -1
|
||||||
|
let noiseVal = 1.0
|
||||||
|
mixInto(buf, length, offset, op, amp, pan, function(i) {
|
||||||
|
const currentClock = Math.floor((tBase + i / HW_SAMPLING_RATE) * freq) | 0
|
||||||
|
if (currentClock !== prevClock) {
|
||||||
|
prevClock = currentClock
|
||||||
|
noiseVal = (Math.random() >= 0.5) ? 1.0 : -1.0
|
||||||
|
}
|
||||||
|
return noiseVal
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// LFSR-based noise (types 1 and 2)
|
||||||
|
const mode = (type === 2) ? 1 : 0
|
||||||
|
const period = (mode === 0) ? LFSR_PERIOD_LONG : LFSR_PERIOD_SHORT
|
||||||
|
// Advance to deterministic position for this tBase so consecutive frame
|
||||||
|
// calls with monotonically advancing phaseOffset produce a seamless noise stream.
|
||||||
|
const startClock = Math.floor(tBase * freq) | 0
|
||||||
|
let lfsr = lfsrAdvance(1, startClock % period, mode)
|
||||||
|
let prevClock = startClock
|
||||||
|
mixInto(buf, length, offset, op, amp, pan, function(i) {
|
||||||
|
const currentClock = Math.floor((tBase + i / HW_SAMPLING_RATE) * freq) | 0
|
||||||
|
const delta = currentClock - prevClock
|
||||||
|
if (delta > 0) {
|
||||||
|
const steps = delta % period
|
||||||
|
if (steps > 0) lfsr = lfsrAdvance(lfsr, steps, mode)
|
||||||
|
prevClock = currentClock
|
||||||
|
}
|
||||||
|
return (lfsr & 1) ? 1.0 : -1.0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeAliasedTriangleNES(buf, length, offset, freq, duty, op, amp, pan, phaseOffset) {
|
||||||
|
// NES APU triangle — quantised to the authentic 32-step, 4-bit (0..15) staircase.
|
||||||
|
// The 32-step sequence is: 15,14,...,1,0, 0,1,...,14,15 (descending then ascending).
|
||||||
|
// This mirrors the real NES triangle DAC which has 32 equal-height steps per period.
|
||||||
|
// duty parameter is accepted for API symmetry but ignored (NES triangle is always symmetric).
|
||||||
|
// phaseOffset: optional absolute-time base (seconds) — see makeSquare for details.
|
||||||
|
if (op == null) op = 'add'
|
||||||
|
if (amp == null) amp = 0.5
|
||||||
|
if (pan == null) pan = 0.0
|
||||||
|
const tBase = (phaseOffset || 0) + offset
|
||||||
|
mixInto(buf, length, offset, op, amp, pan, function(i) {
|
||||||
|
const phase = ((tBase + i / HW_SAMPLING_RATE) * freq) % 1
|
||||||
|
const step32 = Math.floor(phase * 32) | 0 // 0..31
|
||||||
|
// step 0..15: descend from 15 to 0; step 16..31: ascend from 0 to 15
|
||||||
|
const level = (step32 < 16) ? (15 - step32) : (step32 - 16)
|
||||||
|
return level / 7.5 - 1.0 // map 0..15 → -1..+1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Send to audio hardware ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function sendBuffer(buf, playhead, offsetSec, lengthSec, stagingPtr) {
|
||||||
|
// Interleaves the L and R channels into a staging region (LRLRLR…) and uploads
|
||||||
|
// to the audio adapter pcmBin via the standard putPcmDataByPtr pipeline.
|
||||||
|
//
|
||||||
|
// offsetSec: start of region to send (default: 0)
|
||||||
|
// lengthSec: duration to send (default: entire buffer from offsetSec)
|
||||||
|
// stagingPtr: optional caller-owned native buffer (≥ min(chunk, 32768) * 2 bytes).
|
||||||
|
// Pass a pre-allocated pointer to avoid malloc/free per call —
|
||||||
|
// useful for the per-frame tvnes pattern.
|
||||||
|
//
|
||||||
|
// The function auto-chunks at 32768 stereo samples (pcmBin capacity).
|
||||||
|
// Blocks briefly if the audio queue is saturated (queue depth > 2).
|
||||||
|
const start = (offsetSec != null) ? secToSamples(offsetSec) : 0
|
||||||
|
const total = (lengthSec != null) ? secToSamples(lengthSec) : (buf.samples - start)
|
||||||
|
const MAX_CHUNK = 32768 // pcmBin = 65536 bytes; stereo → max 32768 samples per upload
|
||||||
|
const ownsStaging = (stagingPtr == null)
|
||||||
|
if (ownsStaging) stagingPtr = sys.malloc(Math.min(total, MAX_CHUNK) * 2)
|
||||||
|
|
||||||
|
let remaining = total
|
||||||
|
let cursor = start
|
||||||
|
while (remaining > 0) {
|
||||||
|
const take = Math.min(remaining, MAX_CHUNK)
|
||||||
|
// Interleave L, R into staging buffer
|
||||||
|
for (let i = 0; i < take; i++) {
|
||||||
|
sys.poke(stagingPtr + 2 * i, readU8(buf, 0, cursor + i))
|
||||||
|
sys.poke(stagingPtr + 2 * i + 1, readU8(buf, 1, cursor + i))
|
||||||
|
}
|
||||||
|
// Wait for room in the playback queue (mirrors playwav.js idiom)
|
||||||
|
// while (audio.getPosition(playhead) > 2) sys.sleep(2)
|
||||||
|
audio.putPcmDataByPtr(playhead, stagingPtr, take * 2, 0)
|
||||||
|
audio.setSampleUploadLength(playhead, take * 2)
|
||||||
|
audio.startSampleUpload(playhead)
|
||||||
|
remaining -= take
|
||||||
|
cursor += take
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ownsStaging) sys.free(stagingPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lazily-allocated JS-side interleave scratch; shared across sendBufferFast calls.
|
||||||
|
let _sendFastScratch = null
|
||||||
|
|
||||||
|
function sendBufferFast(buf, playhead, offsetSec, lengthSec, stagingPtr) {
|
||||||
|
// Like sendBuffer but interleaves L/R via a JS Uint8Array + one sys.pokeBytes per chunk,
|
||||||
|
// instead of ~2n sys.poke calls. Requires a non-native (JS-backed) buffer.
|
||||||
|
// Falls back to sendBuffer for native buffers.
|
||||||
|
if (isNative(buf)) { sendBuffer(buf, playhead, offsetSec, lengthSec, stagingPtr); return }
|
||||||
|
|
||||||
|
const start = (offsetSec != null) ? secToSamples(offsetSec) : 0
|
||||||
|
const total = (lengthSec != null) ? secToSamples(lengthSec) : (buf.samples - start)
|
||||||
|
const MAX_CHUNK = 32768
|
||||||
|
const ownsStaging = (stagingPtr == null)
|
||||||
|
if (ownsStaging) stagingPtr = sys.malloc(Math.min(total, MAX_CHUNK) * 2)
|
||||||
|
|
||||||
|
const scratchNeeded = Math.min(total, MAX_CHUNK) * 2
|
||||||
|
if (_sendFastScratch == null || _sendFastScratch.length < scratchNeeded) {
|
||||||
|
_sendFastScratch = new Uint8Array(scratchNeeded)
|
||||||
|
}
|
||||||
|
|
||||||
|
let remaining = total
|
||||||
|
let cursor = start
|
||||||
|
while (remaining > 0) {
|
||||||
|
const take = Math.min(remaining, MAX_CHUNK)
|
||||||
|
const L = buf[0], R = buf[1], sc = _sendFastScratch
|
||||||
|
for (let i = 0; i < take; i++) {
|
||||||
|
sc[2 * i] = L[cursor + i]
|
||||||
|
sc[2 * i + 1] = R[cursor + i]
|
||||||
|
}
|
||||||
|
sys.pokeBytes(stagingPtr, sc.subarray(0, take * 2), take * 2)
|
||||||
|
// while (audio.getPosition(playhead) > 2) sys.sleep(2)
|
||||||
|
audio.putPcmDataByPtr(playhead, stagingPtr, take * 2, 0)
|
||||||
|
audio.setSampleUploadLength(playhead, take * 2)
|
||||||
|
audio.startSampleUpload(playhead)
|
||||||
|
remaining -= take
|
||||||
|
cursor += take
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ownsStaging) sys.free(stagingPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports = {
|
||||||
|
HW_SAMPLING_RATE,
|
||||||
|
makeBuffer, makeBufferNative, freeBufferNative, clearBuffer,
|
||||||
|
makeSquare, makeTriangle, makeAliasedTriangle, makeAliasedTriangleNES, makeNoise,
|
||||||
|
sendBuffer, sendBufferFast
|
||||||
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* LibSeqread — sequentially read files from disk drive
|
||||||
|
* @author CuriousTorvald
|
||||||
|
*/
|
||||||
let readCount = 0
|
let readCount = 0
|
||||||
let port = undefined
|
let port = undefined
|
||||||
let fileHeader = new Uint8Array(4096)
|
let fileHeader = new Uint8Array(4096)
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* LibSeqread extension for Tape Drive — sequentially read tape
|
||||||
|
* @author CuriousTorvald
|
||||||
|
*/
|
||||||
|
|
||||||
// Sequential reader for HSDPA TAPE devices
|
// Sequential reader for HSDPA TAPE devices
|
||||||
// Unlike seqread.mjs which is limited to 4096 bytes per read due to serial communication,
|
// Unlike seqread.mjs which is limited to 4096 bytes per read due to serial communication,
|
||||||
// this module can read larger chunks efficiently from HSDPA devices.
|
// this module can read larger chunks efficiently from HSDPA devices.
|
||||||
|
|||||||
312
assets/disk0/tvdos/include/taud.mjs
Normal file
312
assets/disk0/tvdos/include/taud.mjs
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
/*
|
||||||
|
* LibTaud — Helper functions for interaction between Taud format and TSVM Tracker
|
||||||
|
* Requires TVDOS to function.
|
||||||
|
* @author CuriousTorvald
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ── Format constants ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const TAUD_MAGIC = [0x1F,0x54,0x53,0x56,0x4D,0x61,0x75,0x64] // \x1F TSVMaud
|
||||||
|
const TAUD_VERSION = 1
|
||||||
|
const TAUD_HEADER_SIZE = 32 // magic(8) + version(1) + numSongs(1) + compSize(4) + projOff(4) + sig(14)
|
||||||
|
const TAUD_SONG_ENTRY = 32 // see encodeSongEntry / decodeSongEntry below
|
||||||
|
const SAMPLEINST_SIZE = 786432 // 737280 sample + 49152 instrument (256 × 192)
|
||||||
|
const PATTERN_SIZE = 512 // bytes per pattern (64 rows × 8 bytes)
|
||||||
|
const NUM_PATTERNS_MAX = 256
|
||||||
|
const NUM_CUES = 1024
|
||||||
|
const CUE_SIZE = 32 // bytes per cue entry (packed 12-bit×20 voices + instruction + pad)
|
||||||
|
|
||||||
|
// Signature written into the file (14 bytes, space-padded)
|
||||||
|
const CAPTURE_SIGNATURE = "LibTaud/TSVM "
|
||||||
|
|
||||||
|
// ── Internal helpers ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function _peekU32LE(ptr, off) {
|
||||||
|
return ((sys.peek(ptr+off) & 0xFF) ) |
|
||||||
|
((sys.peek(ptr+off+1) & 0xFF) << 8 ) |
|
||||||
|
((sys.peek(ptr+off+2) & 0xFF) << 16 ) |
|
||||||
|
((sys.peek(ptr+off+3) & 0xFF) * 0x1000000) // avoid sign-extend
|
||||||
|
}
|
||||||
|
|
||||||
|
function _pokeU32LE(ptr, off, v) {
|
||||||
|
sys.poke(ptr+off, (v ) & 0xFF)
|
||||||
|
sys.poke(ptr+off+1, (v >>> 8) & 0xFF)
|
||||||
|
sys.poke(ptr+off+2, (v >>> 16) & 0xFF)
|
||||||
|
sys.poke(ptr+off+3, (v >>> 24) & 0xFF)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── uploadTaudFile ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load one song from a Taud file into the tracker hardware and configure the
|
||||||
|
* given playhead ready to play.
|
||||||
|
*
|
||||||
|
* @param inFile Full path with drive letter, e.g. "A:/music/song.taud"
|
||||||
|
* @param songIndex 0-based index of the song in the SONG TABLE
|
||||||
|
* @param playhead Playhead number (0-3) to configure
|
||||||
|
*/
|
||||||
|
function uploadTaudFile(inFile, songIndex, playhead) {
|
||||||
|
const drive = inFile[0].toUpperCase()
|
||||||
|
const diskPath = inFile.substring(2)
|
||||||
|
|
||||||
|
const memBase = audio.getMemAddr()
|
||||||
|
|
||||||
|
// -- 1. Read whole file into VM memory ------------------------------------
|
||||||
|
const fileHandle = files.open(inFile)
|
||||||
|
|
||||||
|
if (!fileHandle.exists) {
|
||||||
|
throw Error("taud: file not exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileSize = fileHandle.size
|
||||||
|
const filePtr = sys.malloc(fileSize)
|
||||||
|
fileHandle.pread(filePtr, fileSize, 0)
|
||||||
|
|
||||||
|
let pos = 0
|
||||||
|
|
||||||
|
// -- 2. Verify magic ------------------------------------------------------
|
||||||
|
for (let i = 0; i < 8; i++) {
|
||||||
|
let magicc = sys.peek(filePtr + i)
|
||||||
|
if (magicc !== TAUD_MAGIC[i]) {
|
||||||
|
sys.free(filePtr)
|
||||||
|
throw Error("taud: bad magic byte " + magicc.toString(16) + " at index " + i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos = 8
|
||||||
|
|
||||||
|
// -- 3. Parse header ------------------------------------------------------
|
||||||
|
// version(1) + numSongs(1) + compressedSize(4) + rsvd(2) + signature(16) = 24 bytes
|
||||||
|
let version = sys.peek(filePtr + pos) & 0xFF; pos++
|
||||||
|
let numSongs = sys.peek(filePtr + pos) & 0xFF; pos++
|
||||||
|
let compressedSize = _peekU32LE(filePtr, pos); pos += 4
|
||||||
|
pos += 18 // skip reserved(2) + signature(16)
|
||||||
|
// pos == 32 == TAUD_HEADER_SIZE
|
||||||
|
|
||||||
|
if (songIndex < 0 || songIndex >= numSongs) {
|
||||||
|
sys.free(filePtr)
|
||||||
|
throw Error("taud: songIndex " + songIndex + " out of range (numSongs=" + numSongs + ")")
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- 4. Decompress and upload sample+instrument bin -----------------------
|
||||||
|
let decompPtr = sys.malloc(SAMPLEINST_SIZE)
|
||||||
|
gzip.decompFromTo(filePtr + pos, compressedSize, decompPtr)
|
||||||
|
pos += compressedSize
|
||||||
|
|
||||||
|
// Write decompressed data to peripheral memory (backwards addressing:
|
||||||
|
// peripheral byte k lives at memBase - k).
|
||||||
|
for (let i = 0; i < SAMPLEINST_SIZE; i++) {
|
||||||
|
// TODO use sys.memcpy
|
||||||
|
sys.poke(memBase - i, sys.peek(decompPtr + i))
|
||||||
|
}
|
||||||
|
sys.free(decompPtr)
|
||||||
|
|
||||||
|
// -- 5. Parse song-table entry for the requested song --------------------
|
||||||
|
let entryOff = pos + songIndex * TAUD_SONG_ENTRY
|
||||||
|
let songOffset = _peekU32LE(filePtr, entryOff)
|
||||||
|
let numVoices = sys.peek(filePtr + entryOff + 4) & 0xFF
|
||||||
|
let numPatsLo = sys.peek(filePtr + entryOff + 5) & 0xFF
|
||||||
|
let numPatsHi = sys.peek(filePtr + entryOff + 6) & 0xFF
|
||||||
|
let bpmStored = sys.peek(filePtr + entryOff + 7) & 0xFF
|
||||||
|
let tickRate = sys.peek(filePtr + entryOff + 8) & 0xFF
|
||||||
|
let mixerflags = sys.peek(filePtr + entryOff + 15) & 0xFF
|
||||||
|
let songGlobalVolume = sys.peek(filePtr + entryOff + 16) & 0xFF
|
||||||
|
let songMixingVolume = sys.peek(filePtr + entryOff + 17) & 0xFF
|
||||||
|
let patBinCompSize = _peekU32LE(filePtr, entryOff + 18)
|
||||||
|
let cueSheetCompSize = _peekU32LE(filePtr, entryOff + 22)
|
||||||
|
|
||||||
|
let bpm = bpmStored + 24
|
||||||
|
let patsToLoad = numPatsLo | (numPatsHi << 8)
|
||||||
|
|
||||||
|
// -- 6. Decompress + upload patterns --------------------------------------
|
||||||
|
let patBinSize = patsToLoad * PATTERN_SIZE
|
||||||
|
let patBinPtr = sys.malloc(patBinSize)
|
||||||
|
gzip.decompFromTo(filePtr + songOffset, patBinCompSize, patBinPtr)
|
||||||
|
|
||||||
|
let patBytes = new Array(PATTERN_SIZE)
|
||||||
|
for (let p = 0; p < patsToLoad; p++) {
|
||||||
|
for (let k = 0; k < PATTERN_SIZE; k++)
|
||||||
|
patBytes[k] = sys.peek(patBinPtr + p * PATTERN_SIZE + k) & 0xFF
|
||||||
|
audio.uploadPattern(p, patBytes)
|
||||||
|
}
|
||||||
|
sys.free(patBinPtr)
|
||||||
|
|
||||||
|
// -- 7. Decompress + upload cue sheet -------------------------------------
|
||||||
|
let cueSheetSize = NUM_CUES * CUE_SIZE
|
||||||
|
let cueSheetPtr = sys.malloc(cueSheetSize)
|
||||||
|
gzip.decompFromTo(filePtr + songOffset + patBinCompSize, cueSheetCompSize, cueSheetPtr)
|
||||||
|
|
||||||
|
let cueBytes = new Array(CUE_SIZE)
|
||||||
|
for (let c = 0; c < NUM_CUES; c++) {
|
||||||
|
for (let k = 0; k < CUE_SIZE; k++)
|
||||||
|
cueBytes[k] = sys.peek(cueSheetPtr + c * CUE_SIZE + k) & 0xFF
|
||||||
|
audio.uploadCue(c, cueBytes)
|
||||||
|
}
|
||||||
|
sys.free(cueSheetPtr)
|
||||||
|
|
||||||
|
// -- 8. Configure playhead ------------------------------------------------
|
||||||
|
audio.setTrackerMode(playhead)
|
||||||
|
audio.setBPM(playhead, bpm)
|
||||||
|
audio.setTickRate(playhead, tickRate > 0 ? tickRate : 6)
|
||||||
|
audio.setTrackerMixerFlags(playhead, mixerflags)
|
||||||
|
audio.setSongGlobalVolume(playhead, songGlobalVolume)
|
||||||
|
audio.setSongMixingVolume(playhead, songMixingVolume)
|
||||||
|
|
||||||
|
|
||||||
|
fileHandle.close()
|
||||||
|
sys.free(filePtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── captureTrackerDataToFile ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dump the current tracker hardware state (sample bin, instruments, patterns
|
||||||
|
* in bank 0, cue sheet) to a single-song Taud file. BPM and tick-rate are
|
||||||
|
* taken from playhead 0.
|
||||||
|
*
|
||||||
|
* @param outFile Full path with drive letter, e.g. "A:/music/out.taud"
|
||||||
|
*/
|
||||||
|
function captureTrackerDataToFile(outFile) {
|
||||||
|
const drive = outFile[0].toUpperCase()
|
||||||
|
const diskPath = outFile.substring(2)
|
||||||
|
|
||||||
|
const memBase = audio.getMemAddr()
|
||||||
|
const baseAddr = audio.getBaseAddr()
|
||||||
|
|
||||||
|
// -- 1. Compress sample+instrument bin ------------------------------------
|
||||||
|
// sys.memcpy(negative_src, positive_dst, len) copies peripheral byte k from
|
||||||
|
// (memBase - k) into (sampleInstBuf + k).
|
||||||
|
let sampleInstBuf = sys.malloc(SAMPLEINST_SIZE)
|
||||||
|
sys.memcpy(memBase, sampleInstBuf, SAMPLEINST_SIZE)
|
||||||
|
|
||||||
|
let compBuf = sys.malloc(SAMPLEINST_SIZE + 4096) // headroom for incompressible data
|
||||||
|
let compressedSize = gzip.compFromTo(sampleInstBuf, SAMPLEINST_SIZE, compBuf)
|
||||||
|
sys.free(sampleInstBuf)
|
||||||
|
|
||||||
|
// -- 2. Find last non-empty pattern in bank 0 (all-zero = uninitialized) --
|
||||||
|
let numPatsActual = 0
|
||||||
|
outer: for (let p = NUM_PATTERNS_MAX - 1; p >= 0; p--) {
|
||||||
|
let patBase = 131072 + p * PATTERN_SIZE // offset within peripheral memory space
|
||||||
|
for (let k = 0; k < PATTERN_SIZE; k++) {
|
||||||
|
if ((sys.peek(memBase - (patBase + k)) & 0xFF) !== 0) {
|
||||||
|
numPatsActual = p + 1
|
||||||
|
break outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (numPatsActual === 0) numPatsActual = 1 // always emit at least one pattern slot
|
||||||
|
|
||||||
|
let numPats = numPatsActual // Uint16, 1-65535
|
||||||
|
let patsToSave = numPatsActual
|
||||||
|
|
||||||
|
// -- 3. BPM / tick-rate / volumes from playhead 0 -------------------------
|
||||||
|
let bpm = audio.getBPM(0) || 125
|
||||||
|
let tickRate = audio.getTickRate(0) || 6
|
||||||
|
let bpmStored = (bpm - 24) & 0xFF
|
||||||
|
let songGlobalVolume = audio.getSongGlobalVolume(0)
|
||||||
|
let songMixingVolume = audio.getSongMixingVolume(0)
|
||||||
|
if (songGlobalVolume === undefined || songGlobalVolume === null) songGlobalVolume = 0x80
|
||||||
|
if (songMixingVolume === undefined || songMixingVolume === null) songMixingVolume = 0x80
|
||||||
|
|
||||||
|
// -- 4. Compress pattern bin ----------------------------------------------
|
||||||
|
let patBinSize = patsToSave * PATTERN_SIZE
|
||||||
|
let patBuf = sys.malloc(patBinSize)
|
||||||
|
sys.memcpy(memBase - 131072, patBuf, patBinSize)
|
||||||
|
|
||||||
|
let patCompBuf = sys.malloc(patBinSize + 4096)
|
||||||
|
let patCompSize = gzip.compFromTo(patBuf, patBinSize, patCompBuf)
|
||||||
|
sys.free(patBuf)
|
||||||
|
|
||||||
|
// -- 5. Compress cue sheet ------------------------------------------------
|
||||||
|
// Cue entry c, byte k is at MMIO address 32768 + c*32 + k,
|
||||||
|
// accessed as sys.peek(baseAddr − (32768 + c*32 + k)).
|
||||||
|
let cueSheetSize = NUM_CUES * CUE_SIZE
|
||||||
|
let cueBuf = sys.malloc(cueSheetSize)
|
||||||
|
for (let c = 0; c < NUM_CUES; c++) {
|
||||||
|
let cueOff = 32768 + c * CUE_SIZE
|
||||||
|
for (let k = 0; k < CUE_SIZE; k++)
|
||||||
|
sys.poke(cueBuf + c * CUE_SIZE + k,
|
||||||
|
sys.peek(baseAddr - (cueOff + k)) & 0xFF)
|
||||||
|
}
|
||||||
|
|
||||||
|
let cueCompBuf = sys.malloc(cueSheetSize + 4096)
|
||||||
|
let cueCompSize = gzip.compFromTo(cueBuf, cueSheetSize, cueCompBuf)
|
||||||
|
sys.free(cueBuf)
|
||||||
|
|
||||||
|
// -- 6. Compute song offset (absolute from file start) --------------------
|
||||||
|
// Layout: header(32) + compressed(compressedSize) + songTable(1 × TAUD_SONG_ENTRY)
|
||||||
|
let songOffset = TAUD_HEADER_SIZE + compressedSize + 1 * TAUD_SONG_ENTRY
|
||||||
|
|
||||||
|
// -- 7. Build header byte array (32 bytes) --------------------------------
|
||||||
|
let sigBytes = new Array(14)
|
||||||
|
for (let i = 0; i < 14; i++)
|
||||||
|
sigBytes[i] = i < CAPTURE_SIGNATURE.length ? CAPTURE_SIGNATURE.charCodeAt(i) : 0
|
||||||
|
|
||||||
|
let header = [
|
||||||
|
// Magic (8)
|
||||||
|
0x1F, 0x54, 0x53, 0x56, 0x4D, 0x61, 0x75, 0x64,
|
||||||
|
// version, numSongs
|
||||||
|
TAUD_VERSION, 1,
|
||||||
|
// compressedSize uint32 LE (4) -- sample+inst bin
|
||||||
|
(compressedSize ) & 0xFF,
|
||||||
|
(compressedSize >>> 8) & 0xFF,
|
||||||
|
(compressedSize >>> 16) & 0xFF,
|
||||||
|
(compressedSize >>> 24) & 0xFF,
|
||||||
|
// project data offset (4) -- not emitted
|
||||||
|
0x00, 0x00, 0x00, 0x00,
|
||||||
|
].concat(sigBytes) // 8 + 2 + 4 + 4 + 14 = 32 bytes
|
||||||
|
|
||||||
|
// -- 8. Build song-table row (32 bytes) -----------------------------------
|
||||||
|
let songTable = [
|
||||||
|
(songOffset ) & 0xFF,
|
||||||
|
(songOffset >>> 8) & 0xFF,
|
||||||
|
(songOffset >>> 16) & 0xFF,
|
||||||
|
(songOffset >>> 24) & 0xFF,
|
||||||
|
20, // numVoices
|
||||||
|
numPats & 0xFF, (numPats >>> 8) & 0xFF, // numPatterns Uint16 LE
|
||||||
|
bpmStored, // BPM with −24 bias
|
||||||
|
tickRate, // initial tick-rate
|
||||||
|
0x00,0xA0, // basenote (0xA000 -- C9)
|
||||||
|
0x00,0xAC,0x02,0x46, // basefreq (8363 Hz)
|
||||||
|
sys.peek(baseAddr - 7), // mixer flags
|
||||||
|
songGlobalVolume & 0xFF, // global volume
|
||||||
|
songMixingVolume & 0xFF, // mixing volume
|
||||||
|
// pattern bin compressed size (4)
|
||||||
|
(patCompSize ) & 0xFF,
|
||||||
|
(patCompSize >>> 8) & 0xFF,
|
||||||
|
(patCompSize >>> 16) & 0xFF,
|
||||||
|
(patCompSize >>> 24) & 0xFF,
|
||||||
|
// cue sheet compressed size (4)
|
||||||
|
(cueCompSize ) & 0xFF,
|
||||||
|
(cueCompSize >>> 8) & 0xFF,
|
||||||
|
(cueCompSize >>> 16) & 0xFF,
|
||||||
|
(cueCompSize >>> 24) & 0xFF,
|
||||||
|
// reserved (6)
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
]
|
||||||
|
|
||||||
|
// -- 9. Write header (creates / truncates file) ---------------------------
|
||||||
|
const fileHandle = files.open(outFile)
|
||||||
|
fileHandle.bwrite(header)
|
||||||
|
|
||||||
|
// -- 10. Append compressed sample+inst bin --------------------------------
|
||||||
|
fileHandle.pwrite(compBuf, compressedSize, TAUD_HEADER_SIZE)
|
||||||
|
sys.free(compBuf)
|
||||||
|
|
||||||
|
// -- 11. Write song table -------------------------------------------------
|
||||||
|
fileHandle.bwrite(songTable)
|
||||||
|
|
||||||
|
// -- 12. Append compressed pattern bin ------------------------------------
|
||||||
|
fileHandle.pwrite(patCompBuf, patCompSize,
|
||||||
|
TAUD_HEADER_SIZE + compressedSize + songTable.length)
|
||||||
|
sys.free(patCompBuf)
|
||||||
|
|
||||||
|
// -- 13. Append compressed cue sheet --------------------------------------
|
||||||
|
fileHandle.pwrite(cueCompBuf, cueCompSize,
|
||||||
|
TAUD_HEADER_SIZE + compressedSize + songTable.length + patCompSize)
|
||||||
|
sys.free(cueCompBuf)
|
||||||
|
|
||||||
|
|
||||||
|
fileHandle.flush(); fileHandle.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
exports = { uploadTaudFile, captureTrackerDataToFile }
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* WinTex — TUI window management and renderer
|
||||||
|
* @author CuriousTorvald
|
||||||
|
*/
|
||||||
|
|
||||||
class WindowObject {
|
class WindowObject {
|
||||||
|
|
||||||
constructor(x, y, w, h, inputProcessor, drawContents, title, drawFrame) {
|
constructor(x, y, w, h, inputProcessor, drawContents, title, drawFrame) {
|
||||||
@@ -91,8 +96,6 @@ class WindowObject {
|
|||||||
* @return [new cursor pos, new scroll pos]
|
* @return [new cursor pos, new scroll pos]
|
||||||
*/
|
*/
|
||||||
function scrollVert(dy, listSize, listHeight, currentCursorPos, currentScrollPos, scrollPeek) {
|
function scrollVert(dy, listSize, listHeight, currentCursorPos, currentScrollPos, scrollPeek) {
|
||||||
let peek = 1
|
|
||||||
|
|
||||||
// clamp dy
|
// clamp dy
|
||||||
if (currentCursorPos + dy > listSize - 1)
|
if (currentCursorPos + dy > listSize - 1)
|
||||||
dy = (listSize - 1) - currentCursorPos
|
dy = (listSize - 1) - currentCursorPos
|
||||||
@@ -103,13 +106,13 @@ function scrollVert(dy, listSize, listHeight, currentCursorPos, currentScrollPos
|
|||||||
|
|
||||||
// update vertical scroll stats
|
// update vertical scroll stats
|
||||||
if (dy != 0) {
|
if (dy != 0) {
|
||||||
let visible = listHeight - 1 - peek
|
let visible = listHeight - 1 - scrollPeek
|
||||||
|
|
||||||
if (nextRow - currentScrollPos > visible) {
|
if (nextRow - currentScrollPos > visible) {
|
||||||
currentScrollPos = nextRow - visible
|
currentScrollPos = nextRow - visible
|
||||||
}
|
}
|
||||||
else if (nextRow - currentScrollPos < 0 + peek) {
|
else if (nextRow - currentScrollPos < 0 + scrollPeek) {
|
||||||
currentScrollPos = nextRow - peek // nextRow is less than zero
|
currentScrollPos = nextRow - scrollPeek // nextRow is less than zero
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: future-proofing here -- scroll clamping is moved outside of go-up/go-down
|
// NOTE: future-proofing here -- scroll clamping is moved outside of go-up/go-down
|
||||||
@@ -140,8 +143,6 @@ function scrollVert(dy, listSize, listHeight, currentCursorPos, currentScrollPos
|
|||||||
* @return [new cursor pos, new scroll pos]
|
* @return [new cursor pos, new scroll pos]
|
||||||
*/
|
*/
|
||||||
function scrollHorz(dx, stringSize, stringViewSize, currentCursorPos, currentScrollPos, scrollPeek) {
|
function scrollHorz(dx, stringSize, stringViewSize, currentCursorPos, currentScrollPos, scrollPeek) {
|
||||||
let peek = 1
|
|
||||||
|
|
||||||
// clamp dx
|
// clamp dx
|
||||||
if (currentCursorPos + dx > stringSize - 1)
|
if (currentCursorPos + dx > stringSize - 1)
|
||||||
dx = (stringSize - 1) - currentCursorPos
|
dx = (stringSize - 1) - currentCursorPos
|
||||||
@@ -152,13 +153,13 @@ function scrollHorz(dx, stringSize, stringViewSize, currentCursorPos, currentScr
|
|||||||
|
|
||||||
// update vertical scroll stats
|
// update vertical scroll stats
|
||||||
if (dx != 0) {
|
if (dx != 0) {
|
||||||
let visible = stringViewSize - 1 - peek
|
let visible = stringViewSize - 1 - scrollPeek
|
||||||
|
|
||||||
if (nextCol - currentScrollPos > visible) {
|
if (nextCol - currentScrollPos > visible) {
|
||||||
currentScrollPos = nextCol - visible
|
currentScrollPos = nextCol - visible
|
||||||
}
|
}
|
||||||
else if (nextCol - currentScrollPos < 0 + peek) {
|
else if (nextCol - currentScrollPos < 0 + scrollPeek) {
|
||||||
currentScrollPos = nextCol - peek // nextCol is less than zero
|
currentScrollPos = nextCol - scrollPeek // nextCol is less than zero
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: future-proofing here -- scroll clamping is moved outside of go-up/go-down
|
// NOTE: future-proofing here -- scroll clamping is moved outside of go-up/go-down
|
||||||
|
|||||||
6
assets/disk0/tvdos/tuidev/Makefile
Normal file
6
assets/disk0/tvdos/tuidev/Makefile
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
CC = gcc
|
||||||
|
CFLAGS = -std=c99 -O3 -Wall -Wextra -Ofast -D_GNU_SOURCE
|
||||||
|
|
||||||
|
font_rom_builder:
|
||||||
|
rm -f font_rom_builder
|
||||||
|
$(CC) $(CFLAGS) font_rom_builder.c -o font_rom_builder
|
||||||
202
assets/disk0/tvdos/tuidev/font_rom_builder.c
Normal file
202
assets/disk0/tvdos/tuidev/font_rom_builder.c
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
/*
|
||||||
|
* font_rom_builder.c
|
||||||
|
* Build TSVM 7x14 font ROM from human-readable images (.png, .tga)
|
||||||
|
*
|
||||||
|
* Input: Image with no gaps between characters (7x14 pixels per glyph)
|
||||||
|
* Output: TSVM-compatible font ROM file(s) padded to 1920 bytes
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* gcc -O2 -std=c99 -Wall font_rom_builder.c -o font_rom_builder
|
||||||
|
* ./font_rom_builder <input.png|tga> <output_prefix>
|
||||||
|
*
|
||||||
|
* For 128-char images: outputs <output_prefix>_high.chr
|
||||||
|
* For 256-char images: outputs <output_prefix>_low.chr and <output_prefix>_high.chr
|
||||||
|
*
|
||||||
|
* Image layout:
|
||||||
|
* - 128 chars: 16 columns × 8 rows = 112×112 pixels
|
||||||
|
* - 256 chars: 16 columns × 16 rows = 112×224 pixels
|
||||||
|
* or 32 columns × 8 rows = 224×112 pixels
|
||||||
|
*
|
||||||
|
* ROM format:
|
||||||
|
* - Each glyph: 14 bytes (one byte per row)
|
||||||
|
* - Bit 6 = leftmost pixel, Bit 0 = rightmost pixel
|
||||||
|
* - Each ROM padded to 1920 bytes
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define GLYPH_W 7
|
||||||
|
#define GLYPH_H 14
|
||||||
|
#define GLYPH_BYTES 14
|
||||||
|
#define ROM_PADDED_SIZE 1920
|
||||||
|
|
||||||
|
static void die(const char *msg) {
|
||||||
|
fprintf(stderr, "Error: %s\n", msg);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void write_rom(const char *filename, const uint8_t *glyphs, int glyph_count) {
|
||||||
|
FILE *out = fopen(filename, "wb");
|
||||||
|
if (!out) {
|
||||||
|
fprintf(stderr, "Failed to open output file: %s\n", filename);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write glyph data
|
||||||
|
size_t data_size = glyph_count * GLYPH_BYTES;
|
||||||
|
fwrite(glyphs, 1, data_size, out);
|
||||||
|
|
||||||
|
// Pad to 1920 bytes
|
||||||
|
if (data_size < ROM_PADDED_SIZE) {
|
||||||
|
size_t padding = ROM_PADDED_SIZE - data_size;
|
||||||
|
uint8_t *pad = calloc(padding, 1);
|
||||||
|
fwrite(pad, 1, padding, out);
|
||||||
|
free(pad);
|
||||||
|
fprintf(stderr, " Wrote %zu bytes + %zu bytes padding = %d bytes total\n",
|
||||||
|
data_size, padding, ROM_PADDED_SIZE);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, " Wrote %zu bytes (no padding needed)\n", data_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(out);
|
||||||
|
fprintf(stderr, " Output: %s\n", filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
if (argc < 3) {
|
||||||
|
fprintf(stderr, "Usage: %s <input.png|tga> <output_prefix>\n", argv[0]);
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
fprintf(stderr, "Converts human-readable font images to TSVM font ROM format.\n");
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
fprintf(stderr, "Input requirements:\n");
|
||||||
|
fprintf(stderr, " - Image with no gaps between characters\n");
|
||||||
|
fprintf(stderr, " - Each character is 7x14 pixels\n");
|
||||||
|
fprintf(stderr, " - 128 chars: typically 112x112 (16 cols × 8 rows)\n");
|
||||||
|
fprintf(stderr, " - 256 chars: typically 112x224 (16 cols × 16 rows)\n");
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
fprintf(stderr, "Output:\n");
|
||||||
|
fprintf(stderr, " - 128 chars: <prefix>_high.chr (high ROM only)\n");
|
||||||
|
fprintf(stderr, " - 256 chars: <prefix>_low.chr + <prefix>_high.chr\n");
|
||||||
|
fprintf(stderr, " - Each ROM padded to 1920 bytes\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *input_path = argv[1];
|
||||||
|
const char *output_prefix = argv[2];
|
||||||
|
|
||||||
|
// Get image dimensions using ImageMagick identify
|
||||||
|
char cmd[1024];
|
||||||
|
snprintf(cmd, sizeof(cmd), "identify -format '%%w %%h' \"%s\" 2>/dev/null", input_path);
|
||||||
|
|
||||||
|
FILE *pipe = popen(cmd, "r");
|
||||||
|
if (!pipe) die("Failed to run 'identify' command (ImageMagick required)");
|
||||||
|
|
||||||
|
int img_w = 0, img_h = 0;
|
||||||
|
if (fscanf(pipe, "%d %d", &img_w, &img_h) != 2) {
|
||||||
|
pclose(pipe);
|
||||||
|
die("Failed to read image dimensions (is ImageMagick installed?)");
|
||||||
|
}
|
||||||
|
pclose(pipe);
|
||||||
|
|
||||||
|
fprintf(stderr, "Input: %s (%dx%d)\n", input_path, img_w, img_h);
|
||||||
|
|
||||||
|
// Calculate grid dimensions
|
||||||
|
int cols = img_w / GLYPH_W;
|
||||||
|
int rows = img_h / GLYPH_H;
|
||||||
|
int total_chars = cols * rows;
|
||||||
|
|
||||||
|
if (img_w % GLYPH_W != 0 || img_h % GLYPH_H != 0) {
|
||||||
|
fprintf(stderr, "Warning: Image dimensions not evenly divisible by %dx%d\n",
|
||||||
|
GLYPH_W, GLYPH_H);
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "Grid: %d columns × %d rows = %d characters\n", cols, rows, total_chars);
|
||||||
|
|
||||||
|
// Validate character count
|
||||||
|
if (total_chars != 128 && total_chars != 256) {
|
||||||
|
fprintf(stderr, "Error: Expected 128 or 256 characters, got %d\n", total_chars);
|
||||||
|
fprintf(stderr, " For 128 chars: use 112x112 (16×8) or similar layout\n");
|
||||||
|
fprintf(stderr, " For 256 chars: use 112x224 (16×16) or 224x112 (32×8)\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read image as grayscale using ImageMagick convert
|
||||||
|
// IMPORTANT: Flatten alpha onto black background first, so transparent pixels become black
|
||||||
|
size_t img_size = img_w * img_h;
|
||||||
|
uint8_t *img_data = malloc(img_size);
|
||||||
|
if (!img_data) die("Memory allocation failed");
|
||||||
|
|
||||||
|
snprintf(cmd, sizeof(cmd),
|
||||||
|
"convert \"%s\" -background black -alpha remove -colorspace Gray -depth 8 gray:- 2>/dev/null",
|
||||||
|
input_path);
|
||||||
|
|
||||||
|
pipe = popen(cmd, "r");
|
||||||
|
if (!pipe) die("Failed to run 'convert' command (ImageMagick required)");
|
||||||
|
|
||||||
|
if (fread(img_data, 1, img_size, pipe) != img_size) {
|
||||||
|
pclose(pipe);
|
||||||
|
die("Failed to read image data from ImageMagick");
|
||||||
|
}
|
||||||
|
pclose(pipe);
|
||||||
|
|
||||||
|
fprintf(stderr, "Read %zu bytes of grayscale data\n", img_size);
|
||||||
|
|
||||||
|
// Extract glyphs
|
||||||
|
uint8_t *glyphs = calloc(total_chars, GLYPH_BYTES);
|
||||||
|
if (!glyphs) die("Memory allocation failed");
|
||||||
|
|
||||||
|
for (int gy = 0; gy < rows; gy++) {
|
||||||
|
for (int gx = 0; gx < cols; gx++) {
|
||||||
|
int glyph_idx = gy * cols + gx;
|
||||||
|
uint8_t *glyph = &glyphs[glyph_idx * GLYPH_BYTES];
|
||||||
|
|
||||||
|
for (int row = 0; row < GLYPH_H; row++) {
|
||||||
|
uint8_t byte = 0;
|
||||||
|
for (int col = 0; col < GLYPH_W; col++) {
|
||||||
|
int px = gx * GLYPH_W + col;
|
||||||
|
int py = gy * GLYPH_H + row;
|
||||||
|
uint8_t pixel = img_data[py * img_w + px];
|
||||||
|
|
||||||
|
// Threshold: >= 128 is foreground (white/lit)
|
||||||
|
int is_set = (pixel >= 128) ? 1 : 0;
|
||||||
|
|
||||||
|
// Pack: bit 6 = leftmost, bit 0 = rightmost
|
||||||
|
if (is_set) {
|
||||||
|
byte |= (1u << (6 - col));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
glyph[row] = byte;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(img_data);
|
||||||
|
fprintf(stderr, "Extracted %d glyphs\n", total_chars);
|
||||||
|
|
||||||
|
// Write output ROM file(s)
|
||||||
|
char out_path[1024];
|
||||||
|
|
||||||
|
if (total_chars == 128) {
|
||||||
|
// High ROM only (chars 128-255)
|
||||||
|
snprintf(out_path, sizeof(out_path), "%s.chr", output_prefix);
|
||||||
|
fprintf(stderr, "\nWriting high ROM (128 chars):\n");
|
||||||
|
write_rom(out_path, glyphs, 128);
|
||||||
|
} else {
|
||||||
|
// 256 chars: low ROM (0-127) and high ROM (128-255)
|
||||||
|
snprintf(out_path, sizeof(out_path), "%s_low.chr", output_prefix);
|
||||||
|
fprintf(stderr, "\nWriting low ROM (chars 0-127):\n");
|
||||||
|
write_rom(out_path, glyphs, 128);
|
||||||
|
|
||||||
|
snprintf(out_path, sizeof(out_path), "%s_high.chr", output_prefix);
|
||||||
|
fprintf(stderr, "\nWriting high ROM (chars 128-255):\n");
|
||||||
|
write_rom(out_path, &glyphs[128 * GLYPH_BYTES], 128);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(glyphs);
|
||||||
|
fprintf(stderr, "\nDone.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ cp -r "../out/$RUNTIME" $DESTDIR/
|
|||||||
|
|
||||||
# Copy over all the assets and a jarfile
|
# Copy over all the assets and a jarfile
|
||||||
cp -r "../out/TerranBASIC.jar" $DESTDIR/
|
cp -r "../out/TerranBASIC.jar" $DESTDIR/
|
||||||
|
cp "../lib/compiler-23.1.10.jar" "../lib/compiler-management-23.1.10.jar" "../lib/truffle-compiler-23.1.10.jar" "../lib/truffle-api-23.1.10.jar" "../lib/truffle-runtime-23.1.10.jar" "../lib/polyglot-23.1.10.jar" "../lib/collections-23.1.10.jar" "../lib/word-23.1.10.jar" "../lib/nativeimage-23.1.10.jar" "../lib/jniutils-23.1.10.jar" $DESTDIR/
|
||||||
|
|
||||||
# Pack everything to AppImage
|
# Pack everything to AppImage
|
||||||
ARCH=arm_aarch64 "./$APPIMAGETOOL" $DESTDIR "out/$DESTDIR.AppImage" || { echo 'Building AppImage failed' >&2; exit 1; }
|
ARCH=arm_aarch64 "./$APPIMAGETOOL" $DESTDIR "out/$DESTDIR.AppImage" || { echo 'Building AppImage failed' >&2; exit 1; }
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ cp -r "../out/$RUNTIME" $DESTDIR/
|
|||||||
|
|
||||||
# Copy over all the assets and a jarfile
|
# Copy over all the assets and a jarfile
|
||||||
cp -r "../out/TerranBASIC.jar" $DESTDIR/
|
cp -r "../out/TerranBASIC.jar" $DESTDIR/
|
||||||
|
cp "../lib/compiler-23.1.10.jar" "../lib/compiler-management-23.1.10.jar" "../lib/truffle-compiler-23.1.10.jar" "../lib/truffle-api-23.1.10.jar" "../lib/truffle-runtime-23.1.10.jar" "../lib/polyglot-23.1.10.jar" "../lib/collections-23.1.10.jar" "../lib/word-23.1.10.jar" "../lib/nativeimage-23.1.10.jar" "../lib/jniutils-23.1.10.jar" $DESTDIR/
|
||||||
|
|
||||||
# Pack everything to AppImage
|
# Pack everything to AppImage
|
||||||
"./$APPIMAGETOOL" $DESTDIR "out/$DESTDIR.AppImage" || { echo 'Building AppImage failed' >&2; exit 1; }
|
"./$APPIMAGETOOL" $DESTDIR "out/$DESTDIR.AppImage" || { echo 'Building AppImage failed' >&2; exit 1; }
|
||||||
|
|||||||
@@ -23,5 +23,6 @@ cp -r "../out/$RUNTIME" $DESTDIR/Contents/MacOS/
|
|||||||
|
|
||||||
# Copy over all the assets and a jarfile
|
# Copy over all the assets and a jarfile
|
||||||
cp -r "../out/TerranBASIC.jar" $DESTDIR/Contents/MacOS/
|
cp -r "../out/TerranBASIC.jar" $DESTDIR/Contents/MacOS/
|
||||||
|
cp "../lib/compiler-23.1.10.jar" "../lib/compiler-management-23.1.10.jar" "../lib/truffle-compiler-23.1.10.jar" "../lib/truffle-api-23.1.10.jar" "../lib/truffle-runtime-23.1.10.jar" "../lib/polyglot-23.1.10.jar" "../lib/collections-23.1.10.jar" "../lib/word-23.1.10.jar" "../lib/nativeimage-23.1.10.jar" "../lib/jniutils-23.1.10.jar" $DESTDIR/Contents/MacOS/
|
||||||
|
|
||||||
echo "Build successful: $DESTDIR"
|
echo "Build successful: $DESTDIR"
|
||||||
|
|||||||
@@ -23,5 +23,6 @@ cp -r "../out/$RUNTIME" $DESTDIR/Contents/MacOS/
|
|||||||
|
|
||||||
# Copy over all the assets and a jarfile
|
# Copy over all the assets and a jarfile
|
||||||
cp -r "../out/TerranBASIC.jar" $DESTDIR/Contents/MacOS/
|
cp -r "../out/TerranBASIC.jar" $DESTDIR/Contents/MacOS/
|
||||||
|
cp "../lib/compiler-23.1.10.jar" "../lib/compiler-management-23.1.10.jar" "../lib/truffle-compiler-23.1.10.jar" "../lib/truffle-api-23.1.10.jar" "../lib/truffle-runtime-23.1.10.jar" "../lib/polyglot-23.1.10.jar" "../lib/collections-23.1.10.jar" "../lib/word-23.1.10.jar" "../lib/nativeimage-23.1.10.jar" "../lib/jniutils-23.1.10.jar" $DESTDIR/Contents/MacOS/
|
||||||
|
|
||||||
echo "Build successful: $DESTDIR"
|
echo "Build successful: $DESTDIR"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ cp -r "../out/$RUNTIME" $DESTDIR/
|
|||||||
|
|
||||||
# Copy over all the assets and a jarfile
|
# Copy over all the assets and a jarfile
|
||||||
cp -r "../out/TerranBASIC.jar" $DESTDIR/
|
cp -r "../out/TerranBASIC.jar" $DESTDIR/
|
||||||
|
cp "../lib/compiler-23.1.10.jar" "../lib/compiler-management-23.1.10.jar" "../lib/truffle-compiler-23.1.10.jar" "../lib/truffle-api-23.1.10.jar" "../lib/truffle-runtime-23.1.10.jar" "../lib/polyglot-23.1.10.jar" "../lib/collections-23.1.10.jar" "../lib/word-23.1.10.jar" "../lib/nativeimage-23.1.10.jar" "../lib/jniutils-23.1.10.jar" $DESTDIR/
|
||||||
|
|
||||||
# Temporary solution: zip everything
|
# Temporary solution: zip everything
|
||||||
zip -r -9 -l "out/$DESTDIR.zip" $DESTDIR
|
zip -r -9 -l "out/$DESTDIR.zip" $DESTDIR
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
cd "${0%/*}"
|
cd "${0%/*}"
|
||||||
./runtime-linux-arm/bin/java -Xms128M -Xmx2G -Dswing.aatext=true -Dawt.useSystemAAFontSettings=lcd -jar ./TerranBASIC.jar
|
GRAAL_MODULE_PATH=compiler-23.1.10.jar:compiler-management-23.1.10.jar:truffle-compiler-23.1.10.jar:truffle-api-23.1.10.jar:truffle-runtime-23.1.10.jar:polyglot-23.1.10.jar:collections-23.1.10.jar:word-23.1.10.jar:nativeimage-23.1.10.jar:jniutils-23.1.10.jar
|
||||||
|
./runtime-linux-arm/bin/java --upgrade-module-path=$GRAAL_MODULE_PATH -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI --add-exports=java.base/jdk.internal.misc=jdk.internal.vm.compiler -Xms128M -Xmx2G -Dswing.aatext=true -Dawt.useSystemAAFontSettings=lcd -jar ./TerranBASIC.jar
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
cd "${0%/*}"
|
cd "${0%/*}"
|
||||||
./runtime-linux-x86/bin/java -Xms128M -Xmx2G -Dswing.aatext=true -Dawt.useSystemAAFontSettings=lcd -jar ./TerranBASIC.jar
|
GRAAL_MODULE_PATH=compiler-23.1.10.jar:compiler-management-23.1.10.jar:truffle-compiler-23.1.10.jar:truffle-api-23.1.10.jar:truffle-runtime-23.1.10.jar:polyglot-23.1.10.jar:collections-23.1.10.jar:word-23.1.10.jar:nativeimage-23.1.10.jar:jniutils-23.1.10.jar
|
||||||
|
./runtime-linux-x86/bin/java --upgrade-module-path=$GRAAL_MODULE_PATH -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI --add-exports=java.base/jdk.internal.misc=jdk.internal.vm.compiler -Xms128M -Xmx2G -Dswing.aatext=true -Dawt.useSystemAAFontSettings=lcd -jar ./TerranBASIC.jar
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
cd "${0%/*}"
|
cd "${0%/*}"
|
||||||
./runtime-osx-arm/bin/java -XstartOnFirstThread -Xms128M -Xmx2G -jar ./TerranBASIC.jar
|
GRAAL_MODULE_PATH=compiler-23.1.10.jar:compiler-management-23.1.10.jar:truffle-compiler-23.1.10.jar:truffle-api-23.1.10.jar:truffle-runtime-23.1.10.jar:polyglot-23.1.10.jar:collections-23.1.10.jar:word-23.1.10.jar:nativeimage-23.1.10.jar:jniutils-23.1.10.jar
|
||||||
|
./runtime-osx-arm/bin/java --upgrade-module-path=$GRAAL_MODULE_PATH -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI --add-exports=java.base/jdk.internal.misc=jdk.internal.vm.compiler -XstartOnFirstThread -Xms128M -Xmx2G -jar ./TerranBASIC.jar
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
cd "${0%/*}"
|
cd "${0%/*}"
|
||||||
./runtime-osx-x86/bin/java -XstartOnFirstThread -Xms128M -Xmx2G -jar ./TerranBASIC.jar
|
GRAAL_MODULE_PATH=compiler-23.1.10.jar:compiler-management-23.1.10.jar:truffle-compiler-23.1.10.jar:truffle-api-23.1.10.jar:truffle-runtime-23.1.10.jar:polyglot-23.1.10.jar:collections-23.1.10.jar:word-23.1.10.jar:nativeimage-23.1.10.jar:jniutils-23.1.10.jar
|
||||||
|
./runtime-osx-x86/bin/java --upgrade-module-path=$GRAAL_MODULE_PATH -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI --add-exports=java.base/jdk.internal.misc=jdk.internal.vm.compiler -XstartOnFirstThread -Xms128M -Xmx2G -jar ./TerranBASIC.jar
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
cd /D "%~dp0"
|
cd /D "%~dp0"
|
||||||
.\runtime-windows-x86\bin\java -Xms128M -Xmx2G -jar .\TerranBASIC.jar
|
set GRAAL_MODULE_PATH=compiler-23.1.10.jar;compiler-management-23.1.10.jar;truffle-compiler-23.1.10.jar;truffle-api-23.1.10.jar;truffle-runtime-23.1.10.jar;polyglot-23.1.10.jar;collections-23.1.10.jar;word-23.1.10.jar;nativeimage-23.1.10.jar;jniutils-23.1.10.jar
|
||||||
|
.\runtime-windows-x86\bin\java --upgrade-module-path=%GRAAL_MODULE_PATH% -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI --add-exports=java.base/jdk.internal.misc=jdk.internal.vm.compiler -Xms128M -Xmx2G -jar .\TerranBASIC.jar
|
||||||
|
|||||||
@@ -662,7 +662,7 @@ TODO
|
|||||||
\endlastfoot
|
\endlastfoot
|
||||||
\centering
|
\centering
|
||||||
\begin{tabulary}{\textwidth}{rl}
|
\begin{tabulary}{\textwidth}{rl}
|
||||||
{\ttfamily 0} & {\ttfamily \#000F} \\
|
{\ttfamily 0} & {\ttfamily \#0007} \\
|
||||||
{\ttfamily 1} & {\ttfamily \#004F} \\
|
{\ttfamily 1} & {\ttfamily \#004F} \\
|
||||||
{\ttfamily 2} & {\ttfamily \#008F} \\
|
{\ttfamily 2} & {\ttfamily \#008F} \\
|
||||||
{\ttfamily 3} & {\ttfamily \#00BF} \\
|
{\ttfamily 3} & {\ttfamily \#00BF} \\
|
||||||
|
|||||||
BIN
doc/tsvmpal.png
BIN
doc/tsvmpal.png
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 21 KiB |
81
ipf_encoder/Makefile
Normal file
81
ipf_encoder/Makefile
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# Makefile for iPF (TSVM Interchangeable Picture Format) Encoder
|
||||||
|
# Created by CuriousTorvald and Claude on 2025-12-19.
|
||||||
|
|
||||||
|
CC = gcc
|
||||||
|
CFLAGS = -std=c99 -Wall -Wextra -O2 -D_GNU_SOURCE
|
||||||
|
DBGFLAGS =
|
||||||
|
PREFIX = /usr/local
|
||||||
|
|
||||||
|
# Zstd flags (use pkg-config if available, fallback for cross-platform compatibility)
|
||||||
|
ZSTD_CFLAGS = $(shell pkg-config --cflags libzstd 2>/dev/null || echo "")
|
||||||
|
ZSTD_LIBS = $(shell pkg-config --libs libzstd 2>/dev/null || echo "-lzstd")
|
||||||
|
LIBS = -lm $(ZSTD_LIBS)
|
||||||
|
|
||||||
|
# Targets
|
||||||
|
TARGETS = encoder_ipf decoder_ipf
|
||||||
|
|
||||||
|
# Build all (default)
|
||||||
|
all: $(TARGETS)
|
||||||
|
|
||||||
|
encoder_ipf: encoder_ipf.c
|
||||||
|
rm -f encoder_ipf
|
||||||
|
$(CC) $(CFLAGS) $(ZSTD_CFLAGS) -o encoder_ipf encoder_ipf.c $(LIBS)
|
||||||
|
@echo "iPF encoder built: encoder_ipf"
|
||||||
|
|
||||||
|
decoder_ipf: decoder_ipf.c
|
||||||
|
rm -f decoder_ipf
|
||||||
|
$(CC) $(CFLAGS) $(ZSTD_CFLAGS) -o decoder_ipf decoder_ipf.c $(LIBS)
|
||||||
|
@echo "iPF decoder built: decoder_ipf"
|
||||||
|
|
||||||
|
# Build with debug symbols
|
||||||
|
debug: CFLAGS += -g -DDEBUG -fsanitize=address -fno-omit-frame-pointer
|
||||||
|
debug: DBGFLAGS += -fsanitize=address -fno-omit-frame-pointer
|
||||||
|
debug: clean $(TARGETS)
|
||||||
|
|
||||||
|
# Build with optimizations
|
||||||
|
release: CFLAGS = -std=c99 -Wall -Wextra -O3 -D_GNU_SOURCE -march=native
|
||||||
|
release: clean $(TARGETS)
|
||||||
|
|
||||||
|
# Clean build artifacts
|
||||||
|
clean:
|
||||||
|
rm -f $(TARGETS) *.o
|
||||||
|
|
||||||
|
# Install
|
||||||
|
install: $(TARGETS)
|
||||||
|
cp encoder_ipf $(PREFIX)/bin/
|
||||||
|
cp decoder_ipf $(PREFIX)/bin/
|
||||||
|
|
||||||
|
# Check for required dependencies
|
||||||
|
check-deps:
|
||||||
|
@echo "Checking dependencies..."
|
||||||
|
@pkg-config --exists libzstd || (echo "Error: libzstd-dev not found. Install libzstd-dev or equivalent" && exit 1)
|
||||||
|
@which ffmpeg >/dev/null 2>&1 || (echo "Error: ffmpeg not found in PATH" && exit 1)
|
||||||
|
@which ffprobe >/dev/null 2>&1 || (echo "Error: ffprobe not found in PATH" && exit 1)
|
||||||
|
@echo "All dependencies found."
|
||||||
|
|
||||||
|
# Help
|
||||||
|
help:
|
||||||
|
@echo "iPF (TSVM Interchangeable Picture Format) Tools"
|
||||||
|
@echo ""
|
||||||
|
@echo "Targets:"
|
||||||
|
@echo " all - Build encoder and decoder (default)"
|
||||||
|
@echo " encoder_ipf - Build encoder only"
|
||||||
|
@echo " decoder_ipf - Build decoder only"
|
||||||
|
@echo " debug - Build with debug symbols and AddressSanitizer"
|
||||||
|
@echo " release - Build with full optimizations"
|
||||||
|
@echo " clean - Remove build artifacts"
|
||||||
|
@echo " install - Install to /usr/local/bin"
|
||||||
|
@echo " check-deps - Check for required dependencies"
|
||||||
|
@echo " help - Show this help"
|
||||||
|
@echo ""
|
||||||
|
@echo "Requirements:"
|
||||||
|
@echo " - GCC with C99 support"
|
||||||
|
@echo " - libzstd-dev (Zstd compression library)"
|
||||||
|
@echo " - FFmpeg (for image encoding/decoding)"
|
||||||
|
@echo ""
|
||||||
|
@echo "Usage:"
|
||||||
|
@echo " make # Build all"
|
||||||
|
@echo " ./encoder_ipf -i input.png -o output.ipf # Encode"
|
||||||
|
@echo " ./decoder_ipf -i output.ipf -o decoded.png # Decode"
|
||||||
|
|
||||||
|
.PHONY: all clean install check-deps help debug release
|
||||||
592
ipf_encoder/decoder_ipf.c
Normal file
592
ipf_encoder/decoder_ipf.c
Normal file
@@ -0,0 +1,592 @@
|
|||||||
|
/**
|
||||||
|
* iPF Decoder - TSVM Interchangeable Picture Format Decoder
|
||||||
|
*
|
||||||
|
* Decodes iPF format (Type 1 or Type 2) images to standard formats via FFmpeg.
|
||||||
|
*
|
||||||
|
* Created by CuriousTorvald and Claude on 2025-12-19.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <zstd.h>
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Constants
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
#define IPF_MAGIC "\x1F\x54\x53\x56\x4D\x69\x50\x46" // "\x1FTSVMiPF"
|
||||||
|
#define IPF_HEADER_SIZE 28 // 8 magic + 2 width + 2 height + 1 flags + 1 type + 10 reserved + 4 uncompressed
|
||||||
|
|
||||||
|
#define IPF_TYPE_1 0 // 4:2:0 chroma subsampling
|
||||||
|
#define IPF_TYPE_2 1 // 4:2:2 chroma subsampling
|
||||||
|
|
||||||
|
#define IPF_FLAG_ALPHA 0x01
|
||||||
|
#define IPF_FLAG_ZSTD 0x10
|
||||||
|
#define IPF_FLAG_PROGRESSIVE 0x80
|
||||||
|
|
||||||
|
#define MAX_PATH 4096
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Structures
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint16_t width;
|
||||||
|
uint16_t height;
|
||||||
|
uint8_t flags;
|
||||||
|
uint8_t type;
|
||||||
|
uint32_t uncompressed_size;
|
||||||
|
} ipf_header_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *input_file;
|
||||||
|
char *output_file;
|
||||||
|
int verbose;
|
||||||
|
int raw_output; // Output raw RGB instead of using FFmpeg
|
||||||
|
} decoder_config_t;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Utility Functions
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
static void print_usage(const char *program) {
|
||||||
|
printf("iPF Decoder - TSVM Interchangeable Picture Format\n");
|
||||||
|
printf("\nUsage: %s -i input.ipf -o output.png [options]\n\n", program);
|
||||||
|
printf("Required:\n");
|
||||||
|
printf(" -i, --input FILE Input iPF file\n");
|
||||||
|
printf(" -o, --output FILE Output image file (any format FFmpeg supports)\n");
|
||||||
|
printf("\nOptions:\n");
|
||||||
|
printf(" --raw Output raw RGB24/RGBA data instead of image file\n");
|
||||||
|
printf(" -v, --verbose Verbose output\n");
|
||||||
|
printf(" -h, --help Show this help\n");
|
||||||
|
printf("\nExamples:\n");
|
||||||
|
printf(" %s -i photo.ipf -o photo.png\n", program);
|
||||||
|
printf(" %s -i logo.ipf -o logo.jpg -v\n", program);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float clampf(float v, float lo, float hi) {
|
||||||
|
return v < lo ? lo : (v > hi ? hi : v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// iPF File Reading
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
static int read_ipf_header(FILE *fp, ipf_header_t *header) {
|
||||||
|
uint8_t magic[8];
|
||||||
|
|
||||||
|
if (fread(magic, 1, 8, fp) != 8) {
|
||||||
|
fprintf(stderr, "Error: Failed to read magic\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp(magic, IPF_MAGIC, 8) != 0) {
|
||||||
|
fprintf(stderr, "Error: Invalid iPF magic\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read width (uint16 LE)
|
||||||
|
if (fread(&header->width, 2, 1, fp) != 1) return -1;
|
||||||
|
|
||||||
|
// Read height (uint16 LE)
|
||||||
|
if (fread(&header->height, 2, 1, fp) != 1) return -1;
|
||||||
|
|
||||||
|
// Read flags
|
||||||
|
if (fread(&header->flags, 1, 1, fp) != 1) return -1;
|
||||||
|
|
||||||
|
// Read type
|
||||||
|
if (fread(&header->type, 1, 1, fp) != 1) return -1;
|
||||||
|
|
||||||
|
// Skip reserved (10 bytes)
|
||||||
|
fseek(fp, 10, SEEK_CUR);
|
||||||
|
|
||||||
|
// Read uncompressed size (uint32 LE)
|
||||||
|
if (fread(&header->uncompressed_size, 4, 1, fp) != 1) return -1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// YCoCg to RGB Conversion
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert YCoCg to RGB for 4 pixels sharing the same chroma.
|
||||||
|
* y_values: 4 Y values packed as nibbles (Y0|Y1 in low byte, Y2|Y3 in high byte style)
|
||||||
|
* a_values: 4 alpha values packed similarly
|
||||||
|
* co, cg: 4-bit chroma values [0..15]
|
||||||
|
*
|
||||||
|
* Output: fills rgb array with R,G,B[,A] values for 4 pixels
|
||||||
|
*/
|
||||||
|
static void ycocg_to_rgb_quad(int co, int cg, int y0, int y1, int y2, int y3,
|
||||||
|
int a0, int a1, int a2, int a3,
|
||||||
|
int has_alpha, uint8_t *rgb) {
|
||||||
|
// Convert chroma from [0..15] to [-1..1]
|
||||||
|
float co_f = (co - 7) / 8.0f;
|
||||||
|
float cg_f = (cg - 7) / 8.0f;
|
||||||
|
|
||||||
|
int ys[4] = {y0, y1, y2, y3};
|
||||||
|
int as[4] = {a0, a1, a2, a3};
|
||||||
|
|
||||||
|
int stride = has_alpha ? 4 : 3;
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
float y = ys[i] / 15.0f;
|
||||||
|
|
||||||
|
// YCoCg to RGB conversion
|
||||||
|
float tmp = y - cg_f / 2.0f;
|
||||||
|
float g = clampf(cg_f + tmp, 0.0f, 1.0f);
|
||||||
|
float b = clampf(tmp - co_f / 2.0f, 0.0f, 1.0f);
|
||||||
|
float r = clampf(b + co_f, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
rgb[i * stride + 0] = (uint8_t)(r * 255.0f + 0.5f);
|
||||||
|
rgb[i * stride + 1] = (uint8_t)(g * 255.0f + 0.5f);
|
||||||
|
rgb[i * stride + 2] = (uint8_t)(b * 255.0f + 0.5f);
|
||||||
|
|
||||||
|
if (has_alpha) {
|
||||||
|
rgb[i * stride + 3] = (uint8_t)(as[i] * 17); // Scale 0-15 to 0-255
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode iPF1 block (4:2:0 chroma subsampling).
|
||||||
|
* Input: 12 bytes (or 20 with alpha)
|
||||||
|
* Output: 16 pixels in RGB24/RGBA format
|
||||||
|
*/
|
||||||
|
static void decode_ipf1_block(const uint8_t *block, int has_alpha, uint8_t *pixels, int stride) {
|
||||||
|
// Read chroma (4 values for 2x2 regions)
|
||||||
|
int co1 = block[0] & 0x0F;
|
||||||
|
int co2 = (block[0] >> 4) & 0x0F;
|
||||||
|
int co3 = block[1] & 0x0F;
|
||||||
|
int co4 = (block[1] >> 4) & 0x0F;
|
||||||
|
|
||||||
|
int cg1 = block[2] & 0x0F;
|
||||||
|
int cg2 = (block[2] >> 4) & 0x0F;
|
||||||
|
int cg3 = block[3] & 0x0F;
|
||||||
|
int cg4 = (block[3] >> 4) & 0x0F;
|
||||||
|
|
||||||
|
// Read Y values (16 values)
|
||||||
|
// Layout: [Y1|Y0|Y5|Y4], [Y3|Y2|Y7|Y6], [Y9|Y8|YD|YC], [YB|YA|YF|YE]
|
||||||
|
int Y[16];
|
||||||
|
Y[0] = block[4] & 0x0F;
|
||||||
|
Y[1] = (block[4] >> 4) & 0x0F;
|
||||||
|
Y[4] = block[5] & 0x0F;
|
||||||
|
Y[5] = (block[5] >> 4) & 0x0F;
|
||||||
|
Y[2] = block[6] & 0x0F;
|
||||||
|
Y[3] = (block[6] >> 4) & 0x0F;
|
||||||
|
Y[6] = block[7] & 0x0F;
|
||||||
|
Y[7] = (block[7] >> 4) & 0x0F;
|
||||||
|
Y[8] = block[8] & 0x0F;
|
||||||
|
Y[9] = (block[8] >> 4) & 0x0F;
|
||||||
|
Y[12] = block[9] & 0x0F;
|
||||||
|
Y[13] = (block[9] >> 4) & 0x0F;
|
||||||
|
Y[10] = block[10] & 0x0F;
|
||||||
|
Y[11] = (block[10] >> 4) & 0x0F;
|
||||||
|
Y[14] = block[11] & 0x0F;
|
||||||
|
Y[15] = (block[11] >> 4) & 0x0F;
|
||||||
|
|
||||||
|
// Read alpha values if present
|
||||||
|
int A[16];
|
||||||
|
if (has_alpha) {
|
||||||
|
A[0] = block[12] & 0x0F;
|
||||||
|
A[1] = (block[12] >> 4) & 0x0F;
|
||||||
|
A[4] = block[13] & 0x0F;
|
||||||
|
A[5] = (block[13] >> 4) & 0x0F;
|
||||||
|
A[2] = block[14] & 0x0F;
|
||||||
|
A[3] = (block[14] >> 4) & 0x0F;
|
||||||
|
A[6] = block[15] & 0x0F;
|
||||||
|
A[7] = (block[15] >> 4) & 0x0F;
|
||||||
|
A[8] = block[16] & 0x0F;
|
||||||
|
A[9] = (block[16] >> 4) & 0x0F;
|
||||||
|
A[12] = block[17] & 0x0F;
|
||||||
|
A[13] = (block[17] >> 4) & 0x0F;
|
||||||
|
A[10] = block[18] & 0x0F;
|
||||||
|
A[11] = (block[18] >> 4) & 0x0F;
|
||||||
|
A[14] = block[19] & 0x0F;
|
||||||
|
A[15] = (block[19] >> 4) & 0x0F;
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < 16; i++) A[i] = 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
int channels = has_alpha ? 4 : 3;
|
||||||
|
uint8_t quad[16]; // 4 pixels max
|
||||||
|
|
||||||
|
// Decode 4 quads (2x2 regions), each sharing one chroma pair
|
||||||
|
// Top-left quad (pixels 0,1,4,5) uses co1/cg1
|
||||||
|
ycocg_to_rgb_quad(co1, cg1, Y[0], Y[1], Y[4], Y[5], A[0], A[1], A[4], A[5], has_alpha, quad);
|
||||||
|
memcpy(pixels + 0 * stride + 0 * channels, quad + 0 * channels, channels);
|
||||||
|
memcpy(pixels + 0 * stride + 1 * channels, quad + 1 * channels, channels);
|
||||||
|
memcpy(pixels + 1 * stride + 0 * channels, quad + 2 * channels, channels);
|
||||||
|
memcpy(pixels + 1 * stride + 1 * channels, quad + 3 * channels, channels);
|
||||||
|
|
||||||
|
// Top-right quad (pixels 2,3,6,7) uses co2/cg2
|
||||||
|
ycocg_to_rgb_quad(co2, cg2, Y[2], Y[3], Y[6], Y[7], A[2], A[3], A[6], A[7], has_alpha, quad);
|
||||||
|
memcpy(pixels + 0 * stride + 2 * channels, quad + 0 * channels, channels);
|
||||||
|
memcpy(pixels + 0 * stride + 3 * channels, quad + 1 * channels, channels);
|
||||||
|
memcpy(pixels + 1 * stride + 2 * channels, quad + 2 * channels, channels);
|
||||||
|
memcpy(pixels + 1 * stride + 3 * channels, quad + 3 * channels, channels);
|
||||||
|
|
||||||
|
// Bottom-left quad (pixels 8,9,12,13) uses co3/cg3
|
||||||
|
ycocg_to_rgb_quad(co3, cg3, Y[8], Y[9], Y[12], Y[13], A[8], A[9], A[12], A[13], has_alpha, quad);
|
||||||
|
memcpy(pixels + 2 * stride + 0 * channels, quad + 0 * channels, channels);
|
||||||
|
memcpy(pixels + 2 * stride + 1 * channels, quad + 1 * channels, channels);
|
||||||
|
memcpy(pixels + 3 * stride + 0 * channels, quad + 2 * channels, channels);
|
||||||
|
memcpy(pixels + 3 * stride + 1 * channels, quad + 3 * channels, channels);
|
||||||
|
|
||||||
|
// Bottom-right quad (pixels 10,11,14,15) uses co4/cg4
|
||||||
|
ycocg_to_rgb_quad(co4, cg4, Y[10], Y[11], Y[14], Y[15], A[10], A[11], A[14], A[15], has_alpha, quad);
|
||||||
|
memcpy(pixels + 2 * stride + 2 * channels, quad + 0 * channels, channels);
|
||||||
|
memcpy(pixels + 2 * stride + 3 * channels, quad + 1 * channels, channels);
|
||||||
|
memcpy(pixels + 3 * stride + 2 * channels, quad + 2 * channels, channels);
|
||||||
|
memcpy(pixels + 3 * stride + 3 * channels, quad + 3 * channels, channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode iPF2 block (4:2:2 chroma subsampling).
|
||||||
|
* Input: 16 bytes (or 24 with alpha)
|
||||||
|
* Output: 16 pixels in RGB24/RGBA format
|
||||||
|
*/
|
||||||
|
static void decode_ipf2_block(const uint8_t *block, int has_alpha, uint8_t *pixels, int stride) {
|
||||||
|
// Read chroma (8 values for horizontal pairs)
|
||||||
|
int co[8], cg[8];
|
||||||
|
co[0] = block[0] & 0x0F;
|
||||||
|
co[1] = (block[0] >> 4) & 0x0F;
|
||||||
|
co[2] = block[1] & 0x0F;
|
||||||
|
co[3] = (block[1] >> 4) & 0x0F;
|
||||||
|
co[4] = block[2] & 0x0F;
|
||||||
|
co[5] = (block[2] >> 4) & 0x0F;
|
||||||
|
co[6] = block[3] & 0x0F;
|
||||||
|
co[7] = (block[3] >> 4) & 0x0F;
|
||||||
|
|
||||||
|
cg[0] = block[4] & 0x0F;
|
||||||
|
cg[1] = (block[4] >> 4) & 0x0F;
|
||||||
|
cg[2] = block[5] & 0x0F;
|
||||||
|
cg[3] = (block[5] >> 4) & 0x0F;
|
||||||
|
cg[4] = block[6] & 0x0F;
|
||||||
|
cg[5] = (block[6] >> 4) & 0x0F;
|
||||||
|
cg[6] = block[7] & 0x0F;
|
||||||
|
cg[7] = (block[7] >> 4) & 0x0F;
|
||||||
|
|
||||||
|
// Read Y values (16 values) - same layout as iPF1
|
||||||
|
int Y[16];
|
||||||
|
Y[0] = block[8] & 0x0F;
|
||||||
|
Y[1] = (block[8] >> 4) & 0x0F;
|
||||||
|
Y[4] = block[9] & 0x0F;
|
||||||
|
Y[5] = (block[9] >> 4) & 0x0F;
|
||||||
|
Y[2] = block[10] & 0x0F;
|
||||||
|
Y[3] = (block[10] >> 4) & 0x0F;
|
||||||
|
Y[6] = block[11] & 0x0F;
|
||||||
|
Y[7] = (block[11] >> 4) & 0x0F;
|
||||||
|
Y[8] = block[12] & 0x0F;
|
||||||
|
Y[9] = (block[12] >> 4) & 0x0F;
|
||||||
|
Y[12] = block[13] & 0x0F;
|
||||||
|
Y[13] = (block[13] >> 4) & 0x0F;
|
||||||
|
Y[10] = block[14] & 0x0F;
|
||||||
|
Y[11] = (block[14] >> 4) & 0x0F;
|
||||||
|
Y[14] = block[15] & 0x0F;
|
||||||
|
Y[15] = (block[15] >> 4) & 0x0F;
|
||||||
|
|
||||||
|
// Read alpha values if present
|
||||||
|
int A[16];
|
||||||
|
if (has_alpha) {
|
||||||
|
A[0] = block[16] & 0x0F;
|
||||||
|
A[1] = (block[16] >> 4) & 0x0F;
|
||||||
|
A[4] = block[17] & 0x0F;
|
||||||
|
A[5] = (block[17] >> 4) & 0x0F;
|
||||||
|
A[2] = block[18] & 0x0F;
|
||||||
|
A[3] = (block[18] >> 4) & 0x0F;
|
||||||
|
A[6] = block[19] & 0x0F;
|
||||||
|
A[7] = (block[19] >> 4) & 0x0F;
|
||||||
|
A[8] = block[20] & 0x0F;
|
||||||
|
A[9] = (block[20] >> 4) & 0x0F;
|
||||||
|
A[12] = block[21] & 0x0F;
|
||||||
|
A[13] = (block[21] >> 4) & 0x0F;
|
||||||
|
A[10] = block[22] & 0x0F;
|
||||||
|
A[11] = (block[22] >> 4) & 0x0F;
|
||||||
|
A[14] = block[23] & 0x0F;
|
||||||
|
A[15] = (block[23] >> 4) & 0x0F;
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < 16; i++) A[i] = 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
int channels = has_alpha ? 4 : 3;
|
||||||
|
|
||||||
|
// iPF2: 4:2:2 - each horizontal pair shares chroma
|
||||||
|
// Row 0: pixels 0,1 share co[0]/cg[0], pixels 2,3 share co[1]/cg[1]
|
||||||
|
// Row 1: pixels 4,5 share co[2]/cg[2], pixels 6,7 share co[3]/cg[3]
|
||||||
|
// Row 2: pixels 8,9 share co[4]/cg[4], pixels 10,11 share co[5]/cg[5]
|
||||||
|
// Row 3: pixels 12,13 share co[6]/cg[6], pixels 14,15 share co[7]/cg[7]
|
||||||
|
|
||||||
|
int pixel_map[8][4] = {
|
||||||
|
{0, 1, 0, 1}, // co/cg index 0: pixels 0,1
|
||||||
|
{2, 3, 2, 3}, // co/cg index 1: pixels 2,3
|
||||||
|
{4, 5, 4, 5}, // co/cg index 2: pixels 4,5
|
||||||
|
{6, 7, 6, 7}, // co/cg index 3: pixels 6,7
|
||||||
|
{8, 9, 8, 9}, // co/cg index 4: pixels 8,9
|
||||||
|
{10, 11, 10, 11}, // co/cg index 5: pixels 10,11
|
||||||
|
{12, 13, 12, 13}, // co/cg index 6: pixels 12,13
|
||||||
|
{14, 15, 14, 15} // co/cg index 7: pixels 14,15
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int ci = 0; ci < 8; ci++) {
|
||||||
|
int p0 = pixel_map[ci][0];
|
||||||
|
int p1 = pixel_map[ci][1];
|
||||||
|
|
||||||
|
uint8_t quad[16]; // 4 pixels max (ycocg_to_rgb_quad writes 4 pixels)
|
||||||
|
ycocg_to_rgb_quad(co[ci], cg[ci], Y[p0], Y[p1], Y[p0], Y[p1],
|
||||||
|
A[p0], A[p1], A[p0], A[p1], has_alpha, quad);
|
||||||
|
|
||||||
|
int row = p0 / 4;
|
||||||
|
int col0 = p0 % 4;
|
||||||
|
int col1 = p1 % 4;
|
||||||
|
|
||||||
|
memcpy(pixels + row * stride + col0 * channels, quad + 0 * channels, channels);
|
||||||
|
memcpy(pixels + row * stride + col1 * channels, quad + 1 * channels, channels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Main Decoding
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
static int decode_ipf(const decoder_config_t *cfg) {
|
||||||
|
FILE *fp = fopen(cfg->input_file, "rb");
|
||||||
|
if (!fp) {
|
||||||
|
fprintf(stderr, "Error: Failed to open input file: %s\n", cfg->input_file);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read header
|
||||||
|
ipf_header_t header;
|
||||||
|
if (read_ipf_header(fp, &header) < 0) {
|
||||||
|
fclose(fp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int has_alpha = (header.flags & IPF_FLAG_ALPHA) != 0;
|
||||||
|
int use_zstd = (header.flags & IPF_FLAG_ZSTD) != 0;
|
||||||
|
int progressive = (header.flags & IPF_FLAG_PROGRESSIVE) != 0;
|
||||||
|
|
||||||
|
if (cfg->verbose) {
|
||||||
|
printf("iPF Header:\n");
|
||||||
|
printf(" Size: %dx%d\n", header.width, header.height);
|
||||||
|
printf(" Type: iPF%d (%s)\n", header.type + 1,
|
||||||
|
header.type == 0 ? "4:2:0" : "4:2:2");
|
||||||
|
printf(" Flags: %s%s%s\n",
|
||||||
|
has_alpha ? "alpha " : "",
|
||||||
|
use_zstd ? "zstd " : "",
|
||||||
|
progressive ? "progressive " : "");
|
||||||
|
printf(" Uncompressed size: %u bytes\n", header.uncompressed_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progressive) {
|
||||||
|
fprintf(stderr, "Warning: Progressive mode not implemented, decoding as sequential\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read compressed/raw block data
|
||||||
|
fseek(fp, 0, SEEK_END);
|
||||||
|
long file_size = ftell(fp);
|
||||||
|
fseek(fp, IPF_HEADER_SIZE, SEEK_SET);
|
||||||
|
|
||||||
|
size_t compressed_size = file_size - IPF_HEADER_SIZE;
|
||||||
|
uint8_t *compressed_data = malloc(compressed_size);
|
||||||
|
if (!compressed_data) {
|
||||||
|
fclose(fp);
|
||||||
|
fprintf(stderr, "Error: Failed to allocate memory\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fread(compressed_data, 1, compressed_size, fp) != compressed_size) {
|
||||||
|
free(compressed_data);
|
||||||
|
fclose(fp);
|
||||||
|
fprintf(stderr, "Error: Failed to read block data\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
// Decompress if needed
|
||||||
|
uint8_t *block_data;
|
||||||
|
size_t block_data_size;
|
||||||
|
|
||||||
|
if (use_zstd) {
|
||||||
|
block_data_size = header.uncompressed_size;
|
||||||
|
block_data = malloc(block_data_size);
|
||||||
|
if (!block_data) {
|
||||||
|
free(compressed_data);
|
||||||
|
fprintf(stderr, "Error: Failed to allocate decompression buffer\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t result = ZSTD_decompress(block_data, block_data_size,
|
||||||
|
compressed_data, compressed_size);
|
||||||
|
if (ZSTD_isError(result)) {
|
||||||
|
fprintf(stderr, "Error: Zstd decompression failed: %s\n",
|
||||||
|
ZSTD_getErrorName(result));
|
||||||
|
free(block_data);
|
||||||
|
free(compressed_data);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cfg->verbose) {
|
||||||
|
printf("Decompressed: %zu -> %zu bytes\n", compressed_size, block_data_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(compressed_data);
|
||||||
|
} else {
|
||||||
|
block_data = compressed_data;
|
||||||
|
block_data_size = compressed_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate output image
|
||||||
|
int channels = has_alpha ? 4 : 3;
|
||||||
|
size_t image_size = (size_t)header.width * header.height * channels;
|
||||||
|
uint8_t *image = malloc(image_size);
|
||||||
|
if (!image) {
|
||||||
|
free(block_data);
|
||||||
|
fprintf(stderr, "Error: Failed to allocate image buffer\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode blocks
|
||||||
|
int blocks_x = (header.width + 3) / 4;
|
||||||
|
int blocks_y = (header.height + 3) / 4;
|
||||||
|
int block_size = (header.type == IPF_TYPE_1) ? (has_alpha ? 20 : 12) : (has_alpha ? 24 : 16);
|
||||||
|
int row_stride = header.width * channels;
|
||||||
|
int block_stride = 4 * channels; // 4 pixels per block row
|
||||||
|
|
||||||
|
size_t block_offset = 0;
|
||||||
|
for (int by = 0; by < blocks_y; by++) {
|
||||||
|
for (int bx = 0; bx < blocks_x; bx++) {
|
||||||
|
// Calculate output position
|
||||||
|
uint8_t *block_pixels = image + by * 4 * row_stride + bx * block_stride;
|
||||||
|
|
||||||
|
if (header.type == IPF_TYPE_1) {
|
||||||
|
decode_ipf1_block(block_data + block_offset, has_alpha, block_pixels, row_stride);
|
||||||
|
} else {
|
||||||
|
decode_ipf2_block(block_data + block_offset, has_alpha, block_pixels, row_stride);
|
||||||
|
}
|
||||||
|
|
||||||
|
block_offset += block_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(block_data);
|
||||||
|
|
||||||
|
if (cfg->verbose) {
|
||||||
|
printf("Decoded %d blocks (%dx%d)\n", blocks_x * blocks_y, blocks_x, blocks_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output image
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
if (cfg->raw_output) {
|
||||||
|
// Write raw RGB/RGBA data
|
||||||
|
FILE *out = fopen(cfg->output_file, "wb");
|
||||||
|
if (!out) {
|
||||||
|
fprintf(stderr, "Error: Failed to open output file: %s\n", cfg->output_file);
|
||||||
|
result = -1;
|
||||||
|
} else {
|
||||||
|
fwrite(image, 1, image_size, out);
|
||||||
|
fclose(out);
|
||||||
|
if (cfg->verbose) {
|
||||||
|
printf("Wrote %zu bytes raw %s data\n", image_size, has_alpha ? "RGBA" : "RGB24");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Use FFmpeg to write output image
|
||||||
|
char cmd[MAX_PATH * 2];
|
||||||
|
const char *pix_fmt = has_alpha ? "rgba" : "rgb24";
|
||||||
|
|
||||||
|
snprintf(cmd, sizeof(cmd),
|
||||||
|
"ffmpeg -hide_banner -v quiet -y -f rawvideo -pix_fmt %s -s %dx%d "
|
||||||
|
"-i - \"%s\"",
|
||||||
|
pix_fmt, header.width, header.height, cfg->output_file);
|
||||||
|
|
||||||
|
if (cfg->verbose) {
|
||||||
|
printf("FFmpeg command: %s\n", cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *pipe = popen(cmd, "w");
|
||||||
|
if (!pipe) {
|
||||||
|
fprintf(stderr, "Error: Failed to start FFmpeg\n");
|
||||||
|
result = -1;
|
||||||
|
} else {
|
||||||
|
fwrite(image, 1, image_size, pipe);
|
||||||
|
int status = pclose(pipe);
|
||||||
|
if (status != 0) {
|
||||||
|
fprintf(stderr, "Error: FFmpeg failed with status %d\n", status);
|
||||||
|
result = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(image);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Main Entry Point
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
decoder_config_t cfg = {
|
||||||
|
.input_file = NULL,
|
||||||
|
.output_file = NULL,
|
||||||
|
.verbose = 0,
|
||||||
|
.raw_output = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct option long_options[] = {
|
||||||
|
{"input", required_argument, 0, 'i'},
|
||||||
|
{"output", required_argument, 0, 'o'},
|
||||||
|
{"raw", no_argument, 0, 'R'},
|
||||||
|
{"verbose", no_argument, 0, 'v'},
|
||||||
|
{"help", no_argument, 0, 'h'},
|
||||||
|
{0, 0, 0, 0}
|
||||||
|
};
|
||||||
|
|
||||||
|
int opt;
|
||||||
|
while ((opt = getopt_long(argc, argv, "i:o:vh", long_options, NULL)) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 'i':
|
||||||
|
cfg.input_file = optarg;
|
||||||
|
break;
|
||||||
|
case 'o':
|
||||||
|
cfg.output_file = optarg;
|
||||||
|
break;
|
||||||
|
case 'R':
|
||||||
|
cfg.raw_output = 1;
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
cfg.verbose = 1;
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
print_usage(argv[0]);
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
print_usage(argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate required arguments
|
||||||
|
if (!cfg.input_file || !cfg.output_file) {
|
||||||
|
fprintf(stderr, "Error: Input and output files are required\n\n");
|
||||||
|
print_usage(argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = decode_ipf(&cfg);
|
||||||
|
|
||||||
|
if (result == 0) {
|
||||||
|
printf("Successfully decoded: %s\n", cfg.output_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
787
ipf_encoder/encoder_ipf.c
Normal file
787
ipf_encoder/encoder_ipf.c
Normal file
@@ -0,0 +1,787 @@
|
|||||||
|
/**
|
||||||
|
* iPF Encoder - TSVM Interchangeable Picture Format Encoder
|
||||||
|
*
|
||||||
|
* Encodes images to iPF format (Type 1 or Type 2) with:
|
||||||
|
* - YCoCg colour space with chroma subsampling
|
||||||
|
* - 4x4 block encoding
|
||||||
|
* - Optional Zstd compression
|
||||||
|
* - Optional alpha channel
|
||||||
|
* - Optional Adam7 progressive ordering
|
||||||
|
*
|
||||||
|
* Created by CuriousTorvald and Claude on 2025-12-19.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <zstd.h>
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Constants
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
#define IPF_MAGIC "\x1F\x54\x53\x56\x4D\x69\x50\x46" // "\x1FTSVMiPF"
|
||||||
|
#define IPF_HEADER_SIZE 28 // 8 magic + 2 width + 2 height + 1 flags + 1 type + 10 reserved + 4 uncompressed size
|
||||||
|
|
||||||
|
#define DEFAULT_WIDTH 560
|
||||||
|
#define DEFAULT_HEIGHT 448
|
||||||
|
|
||||||
|
#define IPF_TYPE_1 0 // 4:2:0 chroma subsampling (12 bytes per block, +8 with alpha)
|
||||||
|
#define IPF_TYPE_2 1 // 4:2:2 chroma subsampling (16 bytes per block, +8 with alpha)
|
||||||
|
|
||||||
|
#define IPF_FLAG_ALPHA 0x01 // Has alpha channel
|
||||||
|
#define IPF_FLAG_ZSTD 0x10 // Zstd compressed
|
||||||
|
#define IPF_FLAG_PROGRESSIVE 0x80 // Adam7 progressive ordering
|
||||||
|
|
||||||
|
#define MAX_PATH 4096
|
||||||
|
|
||||||
|
// Bayer dithering kernel (4x4)
|
||||||
|
static const float BAYER_4X4[16] = {
|
||||||
|
0.0f/16.0f, 8.0f/16.0f, 2.0f/16.0f, 10.0f/16.0f,
|
||||||
|
12.0f/16.0f, 4.0f/16.0f, 14.0f/16.0f, 6.0f/16.0f,
|
||||||
|
3.0f/16.0f, 11.0f/16.0f, 1.0f/16.0f, 9.0f/16.0f,
|
||||||
|
15.0f/16.0f, 7.0f/16.0f, 13.0f/16.0f, 5.0f/16.0f
|
||||||
|
};
|
||||||
|
|
||||||
|
// Adam7 interlace pattern - pass number (1-7) for each pixel in 8x8 block
|
||||||
|
// 0 = not in this standard pattern, we'll adapt for 4x4 blocks
|
||||||
|
static const int ADAM7_PASS[8][8] = {
|
||||||
|
{1, 6, 4, 6, 2, 6, 4, 6},
|
||||||
|
{7, 7, 7, 7, 7, 7, 7, 7},
|
||||||
|
{5, 6, 5, 6, 5, 6, 5, 6},
|
||||||
|
{7, 7, 7, 7, 7, 7, 7, 7},
|
||||||
|
{3, 6, 4, 6, 3, 6, 4, 6},
|
||||||
|
{7, 7, 7, 7, 7, 7, 7, 7},
|
||||||
|
{5, 6, 5, 6, 5, 6, 5, 6},
|
||||||
|
{7, 7, 7, 7, 7, 7, 7, 7}
|
||||||
|
};
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Structures
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *input_file;
|
||||||
|
char *output_file;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
int ipf_type; // 0 = iPF1, 1 = iPF2
|
||||||
|
int use_zstd; // 1 = compress with Zstd
|
||||||
|
int force_alpha; // 1 = force alpha channel in output
|
||||||
|
int no_alpha; // 1 = strip alpha even if present in input
|
||||||
|
int progressive; // 1 = Adam7 progressive ordering
|
||||||
|
int dither; // Bayer dither pattern index (-1 = no dithering)
|
||||||
|
int verbose;
|
||||||
|
} encoder_config_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t *data; // RGB or RGBA data
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
int channels; // 3 = RGB, 4 = RGBA
|
||||||
|
int has_alpha; // 1 if input image has meaningful alpha
|
||||||
|
} image_t;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Utility Functions
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
static void print_usage(const char *program) {
|
||||||
|
printf("iPF Encoder - TSVM Interchangeable Picture Format\n");
|
||||||
|
printf("\nUsage: %s -i input.png -o output.ipf [options]\n\n", program);
|
||||||
|
printf("Required:\n");
|
||||||
|
printf(" -i, --input FILE Input image file (any format FFmpeg supports)\n");
|
||||||
|
printf(" -o, --output FILE Output iPF file\n");
|
||||||
|
printf("\nOptions:\n");
|
||||||
|
printf(" -s, --size WxH Output size (default: %dx%d)\n", DEFAULT_WIDTH, DEFAULT_HEIGHT);
|
||||||
|
printf(" -t, --type N iPF type: 1 (4:2:0, default) or 2 (4:2:2)\n");
|
||||||
|
printf(" --no-zstd Disable Zstd compression (default: enabled)\n");
|
||||||
|
printf(" --alpha Force alpha channel in output\n");
|
||||||
|
printf(" --no-alpha Strip alpha channel from input\n");
|
||||||
|
printf(" -p, --progressive Use Adam7 progressive ordering\n");
|
||||||
|
printf(" -d, --dither N Bayer dither pattern (0=4x4, -1=none, default: 0)\n");
|
||||||
|
printf(" -v, --verbose Verbose output\n");
|
||||||
|
printf(" -h, --help Show this help\n");
|
||||||
|
printf("\nExamples:\n");
|
||||||
|
printf(" %s -i photo.jpg -o photo.ipf\n", program);
|
||||||
|
printf(" %s -i logo.png -o logo.ipf --alpha\n", program);
|
||||||
|
printf(" %s -i image.png -o image.ipf -s 280x224 -t 2\n", program);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int clampi(int v, int lo, int hi) {
|
||||||
|
return v < lo ? lo : (v > hi ? hi : v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert chroma value [-1..1] to 4-bit [0..15]
|
||||||
|
static int chroma_to_four_bits(float f) {
|
||||||
|
return clampi((int)roundf(f * 8.0f) + 7, 0, 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Image Loading via FFmpeg
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Probe input image dimensions using FFmpeg.
|
||||||
|
* Returns 0 on success, -1 on error.
|
||||||
|
*/
|
||||||
|
static int probe_image_dimensions(const char *input_file, int *width, int *height, int *has_alpha) {
|
||||||
|
char cmd[MAX_PATH * 2];
|
||||||
|
|
||||||
|
// Use ffprobe to get dimensions and pixel format
|
||||||
|
snprintf(cmd, sizeof(cmd),
|
||||||
|
"ffprobe -v quiet -select_streams v:0 -show_entries stream=width,height,pix_fmt "
|
||||||
|
"-of csv=p=0:s=x \"%s\" 2>/dev/null",
|
||||||
|
input_file);
|
||||||
|
|
||||||
|
FILE *fp = popen(cmd, "r");
|
||||||
|
if (!fp) {
|
||||||
|
fprintf(stderr, "Error: Failed to run ffprobe\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buffer[256];
|
||||||
|
if (fgets(buffer, sizeof(buffer), fp) == NULL) {
|
||||||
|
pclose(fp);
|
||||||
|
fprintf(stderr, "Error: Failed to read image info\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
pclose(fp);
|
||||||
|
|
||||||
|
// Parse "width x height x pix_fmt"
|
||||||
|
char pix_fmt[64] = "";
|
||||||
|
if (sscanf(buffer, "%dx%dx%63s", width, height, pix_fmt) < 2) {
|
||||||
|
// Try alternate format without pix_fmt
|
||||||
|
if (sscanf(buffer, "%dx%d", width, height) != 2) {
|
||||||
|
fprintf(stderr, "Error: Failed to parse image dimensions\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if pixel format indicates alpha
|
||||||
|
*has_alpha = (strstr(pix_fmt, "rgba") != NULL ||
|
||||||
|
strstr(pix_fmt, "argb") != NULL ||
|
||||||
|
strstr(pix_fmt, "bgra") != NULL ||
|
||||||
|
strstr(pix_fmt, "abgr") != NULL ||
|
||||||
|
strstr(pix_fmt, "ya") != NULL ||
|
||||||
|
strstr(pix_fmt, "pal8") != NULL || // palette may have alpha
|
||||||
|
strstr(pix_fmt, "yuva") != NULL);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load and resize image using FFmpeg.
|
||||||
|
* Maintains aspect ratio and crops to target size.
|
||||||
|
* Returns image data or NULL on error.
|
||||||
|
*/
|
||||||
|
static image_t* load_image(const char *input_file, int target_width, int target_height,
|
||||||
|
int want_alpha, int verbose) {
|
||||||
|
int src_width, src_height, src_has_alpha;
|
||||||
|
|
||||||
|
// Probe source dimensions
|
||||||
|
if (probe_image_dimensions(input_file, &src_width, &src_height, &src_has_alpha) < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
printf("Source image: %dx%d, alpha: %s\n",
|
||||||
|
src_width, src_height, src_has_alpha ? "yes" : "no");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if we need alpha channel
|
||||||
|
int use_alpha = want_alpha || src_has_alpha;
|
||||||
|
int channels = use_alpha ? 4 : 3;
|
||||||
|
const char *pix_fmt = use_alpha ? "rgba" : "rgb24";
|
||||||
|
|
||||||
|
// Build FFmpeg command with scale and crop filter
|
||||||
|
char cmd[MAX_PATH * 2];
|
||||||
|
snprintf(cmd, sizeof(cmd),
|
||||||
|
"ffmpeg -hide_banner -v quiet -i \"%s\" -f rawvideo -pix_fmt %s -vf "
|
||||||
|
"\"scale=%d:%d:force_original_aspect_ratio=increase,crop=%d:%d\" -frames:v 1 -",
|
||||||
|
input_file, pix_fmt, target_width, target_height, target_width, target_height);
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
printf("FFmpeg command: %s\n", cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *fp = popen(cmd, "r");
|
||||||
|
if (!fp) {
|
||||||
|
fprintf(stderr, "Error: Failed to start FFmpeg\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate image
|
||||||
|
image_t *img = malloc(sizeof(image_t));
|
||||||
|
if (!img) {
|
||||||
|
pclose(fp);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t data_size = (size_t)target_width * target_height * channels;
|
||||||
|
img->data = malloc(data_size);
|
||||||
|
if (!img->data) {
|
||||||
|
free(img);
|
||||||
|
pclose(fp);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
img->width = target_width;
|
||||||
|
img->height = target_height;
|
||||||
|
img->channels = channels;
|
||||||
|
img->has_alpha = use_alpha;
|
||||||
|
|
||||||
|
// Read image data
|
||||||
|
size_t bytes_read = fread(img->data, 1, data_size, fp);
|
||||||
|
pclose(fp);
|
||||||
|
|
||||||
|
if (bytes_read != data_size) {
|
||||||
|
fprintf(stderr, "Error: Expected %zu bytes, got %zu\n", data_size, bytes_read);
|
||||||
|
free(img->data);
|
||||||
|
free(img);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
printf("Loaded %dx%d image, %d channels, %zu bytes\n",
|
||||||
|
img->width, img->height, img->channels, data_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_image(image_t *img) {
|
||||||
|
if (img) {
|
||||||
|
free(img->data);
|
||||||
|
free(img);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// iPF Block Encoding
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a 4x4 block to YCoCg with dithering.
|
||||||
|
* Returns arrays of Y (16 values), A (16 values), Co (16 values), Cg (16 values).
|
||||||
|
*/
|
||||||
|
static void encode_block_to_ycocg(const image_t *img, int block_x, int block_y,
|
||||||
|
int dither_pattern,
|
||||||
|
int *Y_out, int *A_out, float *Co_out, float *Cg_out) {
|
||||||
|
for (int py = 0; py < 4; py++) {
|
||||||
|
for (int px = 0; px < 4; px++) {
|
||||||
|
int ox = block_x * 4 + px;
|
||||||
|
int oy = block_y * 4 + py;
|
||||||
|
|
||||||
|
// Handle out-of-bounds (extend edge pixels)
|
||||||
|
ox = clampi(ox, 0, img->width - 1);
|
||||||
|
oy = clampi(oy, 0, img->height - 1);
|
||||||
|
|
||||||
|
// Get dither threshold
|
||||||
|
float t = 0.0f;
|
||||||
|
if (dither_pattern >= 0) {
|
||||||
|
t = BAYER_4X4[(py % 4) * 4 + (px % 4)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read pixel
|
||||||
|
int offset = (oy * img->width + ox) * img->channels;
|
||||||
|
float r0 = img->data[offset + 0] / 255.0f;
|
||||||
|
float g0 = (img->channels >= 3) ? img->data[offset + 1] / 255.0f : r0;
|
||||||
|
float b0 = (img->channels >= 3) ? img->data[offset + 2] / 255.0f : r0;
|
||||||
|
float a0 = (img->channels == 4) ? img->data[offset + 3] / 255.0f : 1.0f;
|
||||||
|
|
||||||
|
// Apply dithering
|
||||||
|
float r = floorf((t / 15.0f + r0) * 15.0f) / 15.0f;
|
||||||
|
float g = floorf((t / 15.0f + g0) * 15.0f) / 15.0f;
|
||||||
|
float b = floorf((t / 15.0f + b0) * 15.0f) / 15.0f;
|
||||||
|
float a = floorf((t / 15.0f + a0) * 15.0f) / 15.0f;
|
||||||
|
|
||||||
|
// Convert to YCoCg
|
||||||
|
float co = r - b; // [-1..1]
|
||||||
|
float tmp = b + co / 2.0f;
|
||||||
|
float cg = g - tmp; // [-1..1]
|
||||||
|
float y = tmp + cg / 2.0f; // [0..1]
|
||||||
|
|
||||||
|
int index = py * 4 + px;
|
||||||
|
Y_out[index] = (int)roundf(y * 15.0f);
|
||||||
|
A_out[index] = (int)roundf(a * 15.0f);
|
||||||
|
Co_out[index] = co;
|
||||||
|
Cg_out[index] = cg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode iPF1 block (4:2:0 chroma subsampling).
|
||||||
|
* Returns 12 bytes (or 20 with alpha).
|
||||||
|
*/
|
||||||
|
static int encode_ipf1_block(const int *Ys, const int *As, const float *COs, const float *CGs,
|
||||||
|
int has_alpha, uint8_t *out) {
|
||||||
|
// Subsample Co/Cg by averaging 2x2 regions (4:2:0)
|
||||||
|
int cos1 = chroma_to_four_bits((COs[0] + COs[1] + COs[4] + COs[5]) / 4.0f);
|
||||||
|
int cos2 = chroma_to_four_bits((COs[2] + COs[3] + COs[6] + COs[7]) / 4.0f);
|
||||||
|
int cos3 = chroma_to_four_bits((COs[8] + COs[9] + COs[12] + COs[13]) / 4.0f);
|
||||||
|
int cos4 = chroma_to_four_bits((COs[10] + COs[11] + COs[14] + COs[15]) / 4.0f);
|
||||||
|
|
||||||
|
int cgs1 = chroma_to_four_bits((CGs[0] + CGs[1] + CGs[4] + CGs[5]) / 4.0f);
|
||||||
|
int cgs2 = chroma_to_four_bits((CGs[2] + CGs[3] + CGs[6] + CGs[7]) / 4.0f);
|
||||||
|
int cgs3 = chroma_to_four_bits((CGs[8] + CGs[9] + CGs[12] + CGs[13]) / 4.0f);
|
||||||
|
int cgs4 = chroma_to_four_bits((CGs[10] + CGs[11] + CGs[14] + CGs[15]) / 4.0f);
|
||||||
|
|
||||||
|
// Pack according to iPF1 format
|
||||||
|
// uint16 [Co4 | Co3 | Co2 | Co1]
|
||||||
|
out[0] = (cos2 << 4) | cos1;
|
||||||
|
out[1] = (cos4 << 4) | cos3;
|
||||||
|
// uint16 [Cg4 | Cg3 | Cg2 | Cg1]
|
||||||
|
out[2] = (cgs2 << 4) | cgs1;
|
||||||
|
out[3] = (cgs4 << 4) | cgs3;
|
||||||
|
// Y values: [Y1|Y0|Y5|Y4], [Y3|Y2|Y7|Y6], [Y9|Y8|YD|YC], [YB|YA|YF|YE]
|
||||||
|
out[4] = (Ys[1] << 4) | Ys[0];
|
||||||
|
out[5] = (Ys[5] << 4) | Ys[4];
|
||||||
|
out[6] = (Ys[3] << 4) | Ys[2];
|
||||||
|
out[7] = (Ys[7] << 4) | Ys[6];
|
||||||
|
out[8] = (Ys[9] << 4) | Ys[8];
|
||||||
|
out[9] = (Ys[13] << 4) | Ys[12];
|
||||||
|
out[10] = (Ys[11] << 4) | Ys[10];
|
||||||
|
out[11] = (Ys[15] << 4) | Ys[14];
|
||||||
|
|
||||||
|
int block_size = 12;
|
||||||
|
|
||||||
|
if (has_alpha) {
|
||||||
|
// Alpha values: same layout as Y
|
||||||
|
out[12] = (As[1] << 4) | As[0];
|
||||||
|
out[13] = (As[5] << 4) | As[4];
|
||||||
|
out[14] = (As[3] << 4) | As[2];
|
||||||
|
out[15] = (As[7] << 4) | As[6];
|
||||||
|
out[16] = (As[9] << 4) | As[8];
|
||||||
|
out[17] = (As[13] << 4) | As[12];
|
||||||
|
out[18] = (As[11] << 4) | As[10];
|
||||||
|
out[19] = (As[15] << 4) | As[14];
|
||||||
|
block_size = 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
return block_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode iPF2 block (4:2:2 chroma subsampling).
|
||||||
|
* Returns 16 bytes (or 24 with alpha).
|
||||||
|
*/
|
||||||
|
static int encode_ipf2_block(const int *Ys, const int *As, const float *COs, const float *CGs,
|
||||||
|
int has_alpha, uint8_t *out) {
|
||||||
|
// Subsample Co/Cg horizontally only (4:2:2) - 8 values each
|
||||||
|
int cos1 = chroma_to_four_bits((COs[0] + COs[1]) / 2.0f);
|
||||||
|
int cos2 = chroma_to_four_bits((COs[2] + COs[3]) / 2.0f);
|
||||||
|
int cos3 = chroma_to_four_bits((COs[4] + COs[5]) / 2.0f);
|
||||||
|
int cos4 = chroma_to_four_bits((COs[6] + COs[7]) / 2.0f);
|
||||||
|
int cos5 = chroma_to_four_bits((COs[8] + COs[9]) / 2.0f);
|
||||||
|
int cos6 = chroma_to_four_bits((COs[10] + COs[11]) / 2.0f);
|
||||||
|
int cos7 = chroma_to_four_bits((COs[12] + COs[13]) / 2.0f);
|
||||||
|
int cos8 = chroma_to_four_bits((COs[14] + COs[15]) / 2.0f);
|
||||||
|
|
||||||
|
int cgs1 = chroma_to_four_bits((CGs[0] + CGs[1]) / 2.0f);
|
||||||
|
int cgs2 = chroma_to_four_bits((CGs[2] + CGs[3]) / 2.0f);
|
||||||
|
int cgs3 = chroma_to_four_bits((CGs[4] + CGs[5]) / 2.0f);
|
||||||
|
int cgs4 = chroma_to_four_bits((CGs[6] + CGs[7]) / 2.0f);
|
||||||
|
int cgs5 = chroma_to_four_bits((CGs[8] + CGs[9]) / 2.0f);
|
||||||
|
int cgs6 = chroma_to_four_bits((CGs[10] + CGs[11]) / 2.0f);
|
||||||
|
int cgs7 = chroma_to_four_bits((CGs[12] + CGs[13]) / 2.0f);
|
||||||
|
int cgs8 = chroma_to_four_bits((CGs[14] + CGs[15]) / 2.0f);
|
||||||
|
|
||||||
|
// Pack according to iPF2 format
|
||||||
|
// uint32 [Co8 | Co7 | Co6 | Co5 | Co4 | Co3 | Co2 | Co1]
|
||||||
|
out[0] = (cos2 << 4) | cos1;
|
||||||
|
out[1] = (cos4 << 4) | cos3;
|
||||||
|
out[2] = (cos6 << 4) | cos5;
|
||||||
|
out[3] = (cos8 << 4) | cos7;
|
||||||
|
// uint32 [Cg8 | Cg7 | Cg6 | Cg5 | Cg4 | Cg3 | Cg2 | Cg1]
|
||||||
|
out[4] = (cgs2 << 4) | cgs1;
|
||||||
|
out[5] = (cgs4 << 4) | cgs3;
|
||||||
|
out[6] = (cgs6 << 4) | cgs5;
|
||||||
|
out[7] = (cgs8 << 4) | cgs7;
|
||||||
|
// Y values: same as iPF1
|
||||||
|
out[8] = (Ys[1] << 4) | Ys[0];
|
||||||
|
out[9] = (Ys[5] << 4) | Ys[4];
|
||||||
|
out[10] = (Ys[3] << 4) | Ys[2];
|
||||||
|
out[11] = (Ys[7] << 4) | Ys[6];
|
||||||
|
out[12] = (Ys[9] << 4) | Ys[8];
|
||||||
|
out[13] = (Ys[13] << 4) | Ys[12];
|
||||||
|
out[14] = (Ys[11] << 4) | Ys[10];
|
||||||
|
out[15] = (Ys[15] << 4) | Ys[14];
|
||||||
|
|
||||||
|
int block_size = 16;
|
||||||
|
|
||||||
|
if (has_alpha) {
|
||||||
|
// Alpha values: same layout as Y
|
||||||
|
out[16] = (As[1] << 4) | As[0];
|
||||||
|
out[17] = (As[5] << 4) | As[4];
|
||||||
|
out[18] = (As[3] << 4) | As[2];
|
||||||
|
out[19] = (As[7] << 4) | As[6];
|
||||||
|
out[20] = (As[9] << 4) | As[8];
|
||||||
|
out[21] = (As[13] << 4) | As[12];
|
||||||
|
out[22] = (As[11] << 4) | As[10];
|
||||||
|
out[23] = (As[15] << 4) | As[14];
|
||||||
|
block_size = 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
return block_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Adam7 Progressive Ordering
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Adam7 pass number for a block at (block_x, block_y).
|
||||||
|
* For blocks, we use a simplified version based on block position.
|
||||||
|
*/
|
||||||
|
static int get_adam7_pass(int block_x, int block_y) {
|
||||||
|
// Use Adam7 pattern for 8x8 blocks, but adapt for 4x4 block indices
|
||||||
|
int px = (block_x * 4) % 8;
|
||||||
|
int py = (block_y * 4) % 8;
|
||||||
|
return ADAM7_PASS[py][px];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode blocks in Adam7 progressive order.
|
||||||
|
* Returns the encoded block data in progressive order.
|
||||||
|
*/
|
||||||
|
static uint8_t* encode_progressive(const image_t *img, const encoder_config_t *cfg,
|
||||||
|
int has_alpha, size_t *out_size) {
|
||||||
|
int blocks_x = (img->width + 3) / 4;
|
||||||
|
int blocks_y = (img->height + 3) / 4;
|
||||||
|
int total_blocks = blocks_x * blocks_y;
|
||||||
|
|
||||||
|
int block_size = (cfg->ipf_type == IPF_TYPE_1) ? (has_alpha ? 20 : 12) : (has_alpha ? 24 : 16);
|
||||||
|
size_t max_size = (size_t)total_blocks * block_size;
|
||||||
|
|
||||||
|
uint8_t *output = malloc(max_size);
|
||||||
|
if (!output) return NULL;
|
||||||
|
|
||||||
|
// Temporary storage for all encoded blocks
|
||||||
|
uint8_t *all_blocks = malloc(max_size);
|
||||||
|
if (!all_blocks) {
|
||||||
|
free(output);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode all blocks first
|
||||||
|
size_t offset = 0;
|
||||||
|
for (int by = 0; by < blocks_y; by++) {
|
||||||
|
for (int bx = 0; bx < blocks_x; bx++) {
|
||||||
|
int Ys[16], As[16];
|
||||||
|
float COs[16], CGs[16];
|
||||||
|
|
||||||
|
encode_block_to_ycocg(img, bx, by, cfg->dither, Ys, As, COs, CGs);
|
||||||
|
|
||||||
|
if (cfg->ipf_type == IPF_TYPE_1) {
|
||||||
|
encode_ipf1_block(Ys, As, COs, CGs, has_alpha, all_blocks + offset);
|
||||||
|
} else {
|
||||||
|
encode_ipf2_block(Ys, As, COs, CGs, has_alpha, all_blocks + offset);
|
||||||
|
}
|
||||||
|
offset += block_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reorder blocks according to Adam7 progressive order (7 passes)
|
||||||
|
size_t out_offset = 0;
|
||||||
|
for (int pass = 1; pass <= 7; pass++) {
|
||||||
|
for (int by = 0; by < blocks_y; by++) {
|
||||||
|
for (int bx = 0; bx < blocks_x; bx++) {
|
||||||
|
if (get_adam7_pass(bx, by) == pass) {
|
||||||
|
int block_idx = by * blocks_x + bx;
|
||||||
|
memcpy(output + out_offset, all_blocks + block_idx * block_size, block_size);
|
||||||
|
out_offset += block_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(all_blocks);
|
||||||
|
*out_size = out_offset;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode blocks in sequential (raster) order.
|
||||||
|
*/
|
||||||
|
static uint8_t* encode_sequential(const image_t *img, const encoder_config_t *cfg,
|
||||||
|
int has_alpha, size_t *out_size) {
|
||||||
|
int blocks_x = (img->width + 3) / 4;
|
||||||
|
int blocks_y = (img->height + 3) / 4;
|
||||||
|
int total_blocks = blocks_x * blocks_y;
|
||||||
|
|
||||||
|
int block_size = (cfg->ipf_type == IPF_TYPE_1) ? (has_alpha ? 20 : 12) : (has_alpha ? 24 : 16);
|
||||||
|
size_t max_size = (size_t)total_blocks * block_size;
|
||||||
|
|
||||||
|
uint8_t *output = malloc(max_size);
|
||||||
|
if (!output) return NULL;
|
||||||
|
|
||||||
|
size_t offset = 0;
|
||||||
|
for (int by = 0; by < blocks_y; by++) {
|
||||||
|
for (int bx = 0; bx < blocks_x; bx++) {
|
||||||
|
int Ys[16], As[16];
|
||||||
|
float COs[16], CGs[16];
|
||||||
|
|
||||||
|
encode_block_to_ycocg(img, bx, by, cfg->dither, Ys, As, COs, CGs);
|
||||||
|
|
||||||
|
if (cfg->ipf_type == IPF_TYPE_1) {
|
||||||
|
offset += encode_ipf1_block(Ys, As, COs, CGs, has_alpha, output + offset);
|
||||||
|
} else {
|
||||||
|
offset += encode_ipf2_block(Ys, As, COs, CGs, has_alpha, output + offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_size = offset;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// iPF File Writing
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
static int write_ipf_file(const char *output_file, const encoder_config_t *cfg,
|
||||||
|
const image_t *img, int verbose) {
|
||||||
|
// Determine if we use alpha
|
||||||
|
int has_alpha = 0;
|
||||||
|
if (cfg->force_alpha) {
|
||||||
|
has_alpha = 1;
|
||||||
|
} else if (!cfg->no_alpha && img->has_alpha) {
|
||||||
|
has_alpha = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode blocks
|
||||||
|
size_t block_data_size;
|
||||||
|
uint8_t *block_data;
|
||||||
|
|
||||||
|
if (cfg->progressive) {
|
||||||
|
block_data = encode_progressive(img, cfg, has_alpha, &block_data_size);
|
||||||
|
} else {
|
||||||
|
block_data = encode_sequential(img, cfg, has_alpha, &block_data_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!block_data) {
|
||||||
|
fprintf(stderr, "Error: Failed to encode image blocks\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
printf("Encoded %zu bytes of block data\n", block_data_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare output data (may be compressed)
|
||||||
|
uint8_t *output_data = block_data;
|
||||||
|
size_t output_size = block_data_size;
|
||||||
|
uint8_t *compressed_data = NULL;
|
||||||
|
|
||||||
|
if (cfg->use_zstd) {
|
||||||
|
size_t max_compressed = ZSTD_compressBound(block_data_size);
|
||||||
|
compressed_data = malloc(max_compressed);
|
||||||
|
if (!compressed_data) {
|
||||||
|
free(block_data);
|
||||||
|
fprintf(stderr, "Error: Failed to allocate compression buffer\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
output_size = ZSTD_compress(compressed_data, max_compressed,
|
||||||
|
block_data, block_data_size, 7);
|
||||||
|
if (ZSTD_isError(output_size)) {
|
||||||
|
fprintf(stderr, "Error: Zstd compression failed: %s\n",
|
||||||
|
ZSTD_getErrorName(output_size));
|
||||||
|
free(block_data);
|
||||||
|
free(compressed_data);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
output_data = compressed_data;
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
printf("Compressed: %zu -> %zu bytes (%.1f%%)\n",
|
||||||
|
block_data_size, output_size,
|
||||||
|
100.0 * output_size / block_data_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open output file
|
||||||
|
FILE *fp = fopen(output_file, "wb");
|
||||||
|
if (!fp) {
|
||||||
|
fprintf(stderr, "Error: Failed to open output file: %s\n", output_file);
|
||||||
|
free(block_data);
|
||||||
|
if (compressed_data) free(compressed_data);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build flags byte
|
||||||
|
uint8_t flags = 0;
|
||||||
|
if (has_alpha) flags |= IPF_FLAG_ALPHA;
|
||||||
|
if (cfg->use_zstd) flags |= IPF_FLAG_ZSTD;
|
||||||
|
if (cfg->progressive) flags |= IPF_FLAG_PROGRESSIVE | IPF_FLAG_ZSTD; // Progressive always sets zstd flag
|
||||||
|
|
||||||
|
// Write header
|
||||||
|
// Magic: "\x1FTSVMiPF" (8 bytes)
|
||||||
|
fwrite(IPF_MAGIC, 1, 8, fp);
|
||||||
|
|
||||||
|
// Width (uint16 LE)
|
||||||
|
uint16_t width_le = (uint16_t)cfg->width;
|
||||||
|
fwrite(&width_le, 2, 1, fp);
|
||||||
|
|
||||||
|
// Height (uint16 LE)
|
||||||
|
uint16_t height_le = (uint16_t)cfg->height;
|
||||||
|
fwrite(&height_le, 2, 1, fp);
|
||||||
|
|
||||||
|
// Flags (uint8)
|
||||||
|
fwrite(&flags, 1, 1, fp);
|
||||||
|
|
||||||
|
// Type (uint8)
|
||||||
|
uint8_t type_byte = (uint8_t)cfg->ipf_type;
|
||||||
|
fwrite(&type_byte, 1, 1, fp);
|
||||||
|
|
||||||
|
// Reserved (10 bytes)
|
||||||
|
uint8_t reserved[10] = {0};
|
||||||
|
fwrite(reserved, 1, 10, fp);
|
||||||
|
|
||||||
|
// Uncompressed size (uint32 LE)
|
||||||
|
uint32_t uncompressed_size_le = (uint32_t)block_data_size;
|
||||||
|
fwrite(&uncompressed_size_le, 4, 1, fp);
|
||||||
|
|
||||||
|
// Write block data
|
||||||
|
fwrite(output_data, 1, output_size, fp);
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
printf("Wrote %zu bytes to %s\n", IPF_HEADER_SIZE + output_size, output_file);
|
||||||
|
printf(" Format: iPF%d, %dx%d\n", cfg->ipf_type + 1, cfg->width, cfg->height);
|
||||||
|
printf(" Flags: %s%s%s\n",
|
||||||
|
has_alpha ? "alpha " : "",
|
||||||
|
cfg->use_zstd ? "zstd " : "",
|
||||||
|
cfg->progressive ? "progressive " : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
free(block_data);
|
||||||
|
if (compressed_data) free(compressed_data);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Main Entry Point
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
static int parse_size(const char *arg, int *width, int *height) {
|
||||||
|
return sscanf(arg, "%dx%d", width, height) == 2 ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
encoder_config_t cfg = {
|
||||||
|
.input_file = NULL,
|
||||||
|
.output_file = NULL,
|
||||||
|
.width = DEFAULT_WIDTH,
|
||||||
|
.height = DEFAULT_HEIGHT,
|
||||||
|
.ipf_type = IPF_TYPE_1,
|
||||||
|
.use_zstd = 1,
|
||||||
|
.force_alpha = 0,
|
||||||
|
.no_alpha = 0,
|
||||||
|
.progressive = 0,
|
||||||
|
.dither = 0,
|
||||||
|
.verbose = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct option long_options[] = {
|
||||||
|
{"input", required_argument, 0, 'i'},
|
||||||
|
{"output", required_argument, 0, 'o'},
|
||||||
|
{"size", required_argument, 0, 's'},
|
||||||
|
{"type", required_argument, 0, 't'},
|
||||||
|
{"no-zstd", no_argument, 0, 'Z'},
|
||||||
|
{"alpha", no_argument, 0, 'A'},
|
||||||
|
{"no-alpha", no_argument, 0, 'N'},
|
||||||
|
{"progressive", no_argument, 0, 'p'},
|
||||||
|
{"dither", required_argument, 0, 'd'},
|
||||||
|
{"verbose", no_argument, 0, 'v'},
|
||||||
|
{"help", no_argument, 0, 'h'},
|
||||||
|
{0, 0, 0, 0}
|
||||||
|
};
|
||||||
|
|
||||||
|
int opt;
|
||||||
|
while ((opt = getopt_long(argc, argv, "i:o:s:t:pd:vh", long_options, NULL)) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 'i':
|
||||||
|
cfg.input_file = optarg;
|
||||||
|
break;
|
||||||
|
case 'o':
|
||||||
|
cfg.output_file = optarg;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
if (parse_size(optarg, &cfg.width, &cfg.height) != 0) {
|
||||||
|
fprintf(stderr, "Error: Invalid size format (use WxH)\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 't':
|
||||||
|
cfg.ipf_type = atoi(optarg) - 1; // User specifies 1 or 2
|
||||||
|
if (cfg.ipf_type < 0 || cfg.ipf_type > 1) {
|
||||||
|
fprintf(stderr, "Error: Invalid iPF type (use 1 or 2)\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'Z':
|
||||||
|
cfg.use_zstd = 0;
|
||||||
|
break;
|
||||||
|
case 'A':
|
||||||
|
cfg.force_alpha = 1;
|
||||||
|
break;
|
||||||
|
case 'N':
|
||||||
|
cfg.no_alpha = 1;
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
cfg.progressive = 1;
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
cfg.dither = atoi(optarg);
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
cfg.verbose = 1;
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
print_usage(argv[0]);
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
print_usage(argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate required arguments
|
||||||
|
if (!cfg.input_file || !cfg.output_file) {
|
||||||
|
fprintf(stderr, "Error: Input and output files are required\n\n");
|
||||||
|
print_usage(argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load image
|
||||||
|
if (cfg.verbose) {
|
||||||
|
printf("Loading image: %s\n", cfg.input_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
image_t *img = load_image(cfg.input_file, cfg.width, cfg.height,
|
||||||
|
cfg.force_alpha, cfg.verbose);
|
||||||
|
if (!img) {
|
||||||
|
fprintf(stderr, "Error: Failed to load image\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode and write iPF file
|
||||||
|
int result = write_ipf_file(cfg.output_file, &cfg, img, cfg.verbose);
|
||||||
|
|
||||||
|
free_image(img);
|
||||||
|
|
||||||
|
if (result == 0) {
|
||||||
|
printf("Successfully encoded: %s\n", cfg.output_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
1820
it2taud.py
Normal file
1820
it2taud.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,20 +1,8 @@
|
|||||||
## How To Edit the Graaljs Jars
|
## GraalJS JAR Editing (OBSOLETE)
|
||||||
|
|
||||||
0. Download following from Maven:
|
The META-INF/services cross-registration hack was needed for GraalJS 22.3.1 where
|
||||||
|
`js` and `regex` JARs each needed the other's `TruffleLanguage$Provider` registered.
|
||||||
|
|
||||||
org.graalvm.js:js:00.0.0
|
As of GraalJS 24.1.2, the service discovery mechanism changed to
|
||||||
org.graalvm.js:js-scriptengine:00.0.0
|
`TruffleLanguageProvider` and each JAR registers its own provider independently.
|
||||||
|
No JAR editing is required.
|
||||||
1. grab `js-00.0.0.jar`
|
|
||||||
2. on `META-INF/services/com.oracle.truffle.api.TruffleLanguage$Provider`, edit as shown:
|
|
||||||
|
|
||||||
com.oracle.truffle.js.lang.JavaScriptLanguageProvider (existing line)
|
|
||||||
com.oracle.truffle.regex.RegexLanguageProvider (<< add this line)
|
|
||||||
|
|
||||||
3. grab `regex-00.0.0.jar`
|
|
||||||
4. on `META-INF/services/com.oracle.truffle.api.TruffleLanguage$Provider`, edit as shown:
|
|
||||||
|
|
||||||
com.oracle.truffle.regex.RegexLanguageProvider (existing line)
|
|
||||||
com.oracle.truffle.js.lang.JavaScriptLanguageProvider (<< add this line)
|
|
||||||
|
|
||||||
5. Re-zip two files
|
|
||||||
|
|||||||
BIN
lib/collections-23.1.10-javadoc.jar
Normal file
BIN
lib/collections-23.1.10-javadoc.jar
Normal file
Binary file not shown.
BIN
lib/collections-23.1.10-sources.jar
Normal file
BIN
lib/collections-23.1.10-sources.jar
Normal file
Binary file not shown.
BIN
lib/collections-23.1.10.jar
Normal file
BIN
lib/collections-23.1.10.jar
Normal file
Binary file not shown.
Binary file not shown.
BIN
lib/compiler-23.1.10-sources.jar
Normal file
BIN
lib/compiler-23.1.10-sources.jar
Normal file
Binary file not shown.
BIN
lib/compiler-23.1.10.jar
Normal file
BIN
lib/compiler-23.1.10.jar
Normal file
Binary file not shown.
BIN
lib/compiler-management-23.1.10-javadoc.jar
Normal file
BIN
lib/compiler-management-23.1.10-javadoc.jar
Normal file
Binary file not shown.
BIN
lib/compiler-management-23.1.10-sources.jar
Normal file
BIN
lib/compiler-management-23.1.10-sources.jar
Normal file
Binary file not shown.
BIN
lib/compiler-management-23.1.10.jar
Normal file
BIN
lib/compiler-management-23.1.10.jar
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
lib/icu4j-23.1.10-javadoc.jar
Normal file
BIN
lib/icu4j-23.1.10-javadoc.jar
Normal file
Binary file not shown.
BIN
lib/icu4j-23.1.10-sources.jar
Normal file
BIN
lib/icu4j-23.1.10-sources.jar
Normal file
Binary file not shown.
BIN
lib/icu4j-23.1.10.jar
Normal file
BIN
lib/icu4j-23.1.10.jar
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
lib/jniutils-23.1.10-javadoc.jar
Normal file
BIN
lib/jniutils-23.1.10-javadoc.jar
Normal file
Binary file not shown.
BIN
lib/jniutils-23.1.10-sources.jar
Normal file
BIN
lib/jniutils-23.1.10-sources.jar
Normal file
Binary file not shown.
BIN
lib/jniutils-23.1.10.jar
Normal file
BIN
lib/jniutils-23.1.10.jar
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
lib/js-language-23.1.10-sources.jar
Normal file
BIN
lib/js-language-23.1.10-sources.jar
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user