mirror of
https://github.com/exoticorn/microw8.git
synced 2026-01-20 19:26:43 +01:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e7a00dd9c6 | |||
| a02243d98c | |||
| 599873890a | |||
| 8e9bb002bc | |||
| b2b990333e | |||
| d1556f7be8 | |||
| 9f548cd6f0 | |||
| 7cea4eebd3 | |||
| 3f67e92c5c | |||
| a2714f25e4 | |||
| 7e203d93e6 | |||
| e44c87d1f6 | |||
| 614b7cf358 | |||
| c42a484adb | |||
| 3a5f2bf865 | |||
| 2dee1b30a4 | |||
| 42f7887ab2 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: sudo apt-get install -y libxkbcommon-dev
|
run: sudo apt-get install -y libxkbcommon-dev libasound2-dev
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|||||||
557
Cargo.lock
generated
557
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
12
Cargo.toml
@@ -1,22 +1,22 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "uw8"
|
name = "uw8"
|
||||||
version = "0.1.2"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["native", "browser"]
|
default = ["native", "browser"]
|
||||||
native = ["wasmtime"]
|
native = ["wasmtime", "minifb", "cpal", "rubato", "winapi" ]
|
||||||
browser = ["warp", "tokio", "tokio-stream", "webbrowser"]
|
browser = ["warp", "tokio", "tokio-stream", "webbrowser"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasmtime = { version = "0.35.3", optional = true }
|
wasmtime = { version = "0.35.3", optional = true }
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
minifb = { version = "0.22", default-features = false, features = ["x11"] }
|
minifb = { version = "0.22", default-features = false, features = ["x11"], optional = true }
|
||||||
notify = "4"
|
notify = "4"
|
||||||
pico-args = "0.4"
|
pico-args = "0.4"
|
||||||
curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "aac7bbd" }
|
curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "0a0d90c" }
|
||||||
wat = "1"
|
wat = "1"
|
||||||
uw8-tool = { path = "uw8-tool" }
|
uw8-tool = { path = "uw8-tool" }
|
||||||
same-file = "1"
|
same-file = "1"
|
||||||
@@ -25,4 +25,6 @@ tokio = { version = "1.17.0", features = ["sync", "rt"], optional = true }
|
|||||||
tokio-stream = { version = "0.1.8", features = ["sync"], optional = true }
|
tokio-stream = { version = "0.1.8", features = ["sync"], optional = true }
|
||||||
webbrowser = { version = "0.6.0", optional = true }
|
webbrowser = { version = "0.6.0", optional = true }
|
||||||
ansi_term = "0.12.1"
|
ansi_term = "0.12.1"
|
||||||
cpal = "0.13.5"
|
cpal = { version = "0.13.5", optional = true }
|
||||||
|
rubato = { version = "0.11.0", optional = true }
|
||||||
|
winapi = { version = "0.3.9", features = ["timeapi"], optional = true }
|
||||||
55
examples/curlywas/cracklebass.cwa
Normal file
55
examples/curlywas/cracklebass.cwa
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
// port of cracklebass by pestis (originally on TIC-80)
|
||||||
|
|
||||||
|
include "../include/microw8-api.cwa"
|
||||||
|
|
||||||
|
const MUSIC_DATA = 0x20000;
|
||||||
|
|
||||||
|
export fn upd() {
|
||||||
|
let inline t = 32!32 * 6 / 100;
|
||||||
|
let inline p = t / 1024;
|
||||||
|
|
||||||
|
let channel:i32;
|
||||||
|
|
||||||
|
loop channels {
|
||||||
|
let inline e = t * channel?MUSIC_DATA / 8;
|
||||||
|
let lazy pattern = (8 * channel + p)?(MUSIC_DATA + 56);
|
||||||
|
let lazy n = !!pattern * (8 * pattern + e / 16 % 8)?MUSIC_DATA;
|
||||||
|
let inline prev_ctrl = (channel * 6)?80;
|
||||||
|
(channel * 6)?80 = if n {
|
||||||
|
let inline base_note = 12 + 12 * channel?(MUSIC_DATA + 4) + n;
|
||||||
|
let inline pitch_drop = e % 16 * channel?(MUSIC_DATA + 94);
|
||||||
|
let inline key_pattern = p?(MUSIC_DATA + 8*4 + 56);
|
||||||
|
let inline key = select(key_pattern, (8 * key_pattern + t / 128 % 8)?MUSIC_DATA, 1);
|
||||||
|
(channel * 6)?83 = base_note - pitch_drop / 4 + key;
|
||||||
|
prev_ctrl & 0xfc | (e / 8 & 2) | 1
|
||||||
|
} else {
|
||||||
|
prev_ctrl & 0xfe
|
||||||
|
};
|
||||||
|
|
||||||
|
branch_if (channel := channel + 1) < 4: channels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data 80 {
|
||||||
|
i8(
|
||||||
|
0x44, 0, 0, 0, 0x50, 0x40,
|
||||||
|
0x4, 0x50, 0, 0, 0x80, 0x80,
|
||||||
|
0x40, 0x80, 0, 0, 0x40, 0x40,
|
||||||
|
0, 0, 0, 0, 0x50, 0x50
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data MUSIC_DATA {
|
||||||
|
i8(
|
||||||
|
16, 2, 8, 8, 1, 2, 2, 3, 1, 0,
|
||||||
|
1,13,16, 0, 1, 8, 1, 0, 1,13,
|
||||||
|
16, 1, 1, 8, 1, 0, 8,13,13, 0,
|
||||||
|
16,13, 1, 0, 1, 0, 1, 0, 1, 1,
|
||||||
|
1, 0, 0, 0, 1, 0,13, 1, 1, 1,
|
||||||
|
6, 8, 1, 1, 6, 8, 1, 1, 2, 1,
|
||||||
|
2, 1, 2, 0, 0, 0, 0, 3, 3, 3,
|
||||||
|
5, 0, 0, 2, 1, 2, 1, 2, 1, 2,
|
||||||
|
0, 4, 4, 0, 4, 4, 4, 4, 0, 0,
|
||||||
|
0, 0, 6, 6, 0, 0, 0, 8
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ global mut f: f32 = 2.0;
|
|||||||
|
|
||||||
export fn upd() {
|
export fn upd() {
|
||||||
let y: i32;
|
let y: i32;
|
||||||
let inline zero = 0.0;
|
let inline zero = 0_f;
|
||||||
|
|
||||||
let lazy control_speed = 0.03125;
|
let lazy control_speed = 0.03125;
|
||||||
s = s + 0.1875 - (f + control_speed) * isButtonPressed(4 <| cls(4)) as f32;
|
s = s + 0.1875 - (f + control_speed) * isButtonPressed(4 <| cls(4)) as f32;
|
||||||
@@ -30,6 +30,8 @@ export fn upd() {
|
|||||||
|
|
||||||
if y == 180 & py > zero {
|
if y == 180 & py > zero {
|
||||||
if x > w | x < zero {
|
if x > w | x < zero {
|
||||||
|
0?80 = 0xc3;
|
||||||
|
3?80 = 32;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
py = zero;
|
py = zero;
|
||||||
@@ -43,6 +45,9 @@ export fn upd() {
|
|||||||
circle(160 as f32, 160 as f32 + py, 22 as f32, -28);
|
circle(160 as f32, 160 as f32 + py, 22 as f32, -28);
|
||||||
circle((160 - 6) as f32, (160 - 6) as f32 + py, 6 as f32, -26);
|
circle((160 - 6) as f32, (160 - 6) as f32 + py, 6 as f32, -26);
|
||||||
|
|
||||||
|
0?86 = py < zero;
|
||||||
|
3?86 = 32 - py as i32;
|
||||||
|
|
||||||
px = px + (isButtonPressed(3) - isButtonPressed(2)) as f32 * control_speed;
|
px = px + (isButtonPressed(3) - isButtonPressed(2)) as f32 * control_speed;
|
||||||
py = py + s;
|
py = py + s;
|
||||||
pz = pz + 1;
|
pz = pz + 1;
|
||||||
|
|||||||
2
platform/Cargo.lock
generated
2
platform/Cargo.lock
generated
@@ -146,7 +146,7 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "curlywas"
|
name = "curlywas"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/exoticorn/curlywas.git?rev=aac7bbd#aac7bbd8786a26da0dcbe8320b1afefaf6086464"
|
source = "git+https://github.com/exoticorn/curlywas.git?rev=0a0d90c#0a0d90c8013f1d7856a9b71a19d3d006300f31f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"ariadne",
|
"ariadne",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
curlywas = { git="https://github.com/exoticorn/curlywas.git", rev="aac7bbd" }
|
curlywas = { git="https://github.com/exoticorn/curlywas.git", rev="0a0d90c" }
|
||||||
uw8-tool = { path="../uw8-tool" }
|
uw8-tool = { path="../uw8-tool" }
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
lodepng = "3.4"
|
lodepng = "3.4"
|
||||||
Binary file not shown.
@@ -4,6 +4,7 @@ import "env.sin" fn sin(f32) -> f32;
|
|||||||
import "env.cos" fn cos(f32) -> f32;
|
import "env.cos" fn cos(f32) -> f32;
|
||||||
import "env.pow" fn pow(f32, f32) -> f32;
|
import "env.pow" fn pow(f32, f32) -> f32;
|
||||||
import "env.exp" fn exp(f32) -> f32;
|
import "env.exp" fn exp(f32) -> f32;
|
||||||
|
import "env.logChar" fn logChar(i32);
|
||||||
|
|
||||||
export fn time() -> f32 {
|
export fn time() -> f32 {
|
||||||
(0!64) as f32 / 1000 as f32
|
(0!64) as f32 / 1000 as f32
|
||||||
@@ -32,11 +33,9 @@ export fn random() -> i32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export fn random64() -> i64 {
|
export fn random64() -> i64 {
|
||||||
let state: i64;
|
let lazy state = randomState ^ (randomState #>> 12i64);
|
||||||
randomState = (state := (
|
let lazy state = state ^ (state << 25i64);
|
||||||
state := randomState ^ (randomState #>> 12i64)
|
randomState = state ^ (state #>> 27i64);
|
||||||
) ^ (state << 25i64)
|
|
||||||
) ^ (state #>> 27i64);
|
|
||||||
randomState * 0x2545f4914f6cdd1di64
|
randomState * 0x2545f4914f6cdd1di64
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +61,7 @@ export fn cls(col: i32) {
|
|||||||
let i: i32;
|
let i: i32;
|
||||||
textCursorX = 0;
|
textCursorX = 0;
|
||||||
textCursorY = 0;
|
textCursorY = 0;
|
||||||
graphicsText = 0;
|
outputChannel = 0;
|
||||||
col = (col & 255) * 0x1010101;
|
col = (col & 255) * 0x1010101;
|
||||||
loop pixels {
|
loop pixels {
|
||||||
i!120 = col;
|
i!120 = col;
|
||||||
@@ -307,7 +306,7 @@ global mut textCursorX = 0;
|
|||||||
global mut textCursorY = 0;
|
global mut textCursorY = 0;
|
||||||
global mut textColor = 15;
|
global mut textColor = 15;
|
||||||
global mut bgColor = 0;
|
global mut bgColor = 0;
|
||||||
global mut graphicsText = 0;
|
global mut outputChannel = 0;
|
||||||
|
|
||||||
export fn printChar(char: i32) {
|
export fn printChar(char: i32) {
|
||||||
loop chars {
|
loop chars {
|
||||||
@@ -319,6 +318,20 @@ export fn printChar(char: i32) {
|
|||||||
global mut controlCodeLength = 0;
|
global mut controlCodeLength = 0;
|
||||||
|
|
||||||
fn printSingleChar(char: i32) {
|
fn printSingleChar(char: i32) {
|
||||||
|
if char >= 4 & char <= 6 {
|
||||||
|
outputChannel = char - 4;
|
||||||
|
if !outputChannel {
|
||||||
|
textCursorX = 0;
|
||||||
|
textCursorY = 0;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if outputChannel >= 2 {
|
||||||
|
logChar(char);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
controlCodeLength?0x12d20 = char;
|
controlCodeLength?0x12d20 = char;
|
||||||
controlCodeLength = controlCodeLength + 1;
|
controlCodeLength = controlCodeLength + 1;
|
||||||
char = 0x12d20?0;
|
char = 0x12d20?0;
|
||||||
@@ -332,13 +345,6 @@ fn printSingleChar(char: i32) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if char == 4 | char == 5 {
|
|
||||||
graphicsText = char == 5;
|
|
||||||
textCursorX = 0;
|
|
||||||
textCursorY = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if char == 7 {
|
if char == 7 {
|
||||||
80?0 = 80?0 ^ 2;
|
80?0 = 80?0 ^ 2;
|
||||||
return;
|
return;
|
||||||
@@ -346,7 +352,7 @@ fn printSingleChar(char: i32) {
|
|||||||
|
|
||||||
if char == 8 {
|
if char == 8 {
|
||||||
textCursorX = textCursorX - 8;
|
textCursorX = textCursorX - 8;
|
||||||
if !graphicsText & textCursorX < 0 {
|
if !outputChannel & textCursorX < 0 {
|
||||||
textCursorX = 320-8;
|
textCursorX = 320-8;
|
||||||
printSingleChar(11);
|
printSingleChar(11);
|
||||||
}
|
}
|
||||||
@@ -354,7 +360,7 @@ fn printSingleChar(char: i32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if char == 9 {
|
if char == 9 {
|
||||||
if !graphicsText & textCursorX >= 320 {
|
if !outputChannel & textCursorX >= 320 {
|
||||||
printChar(0xd0a);
|
printChar(0xd0a);
|
||||||
}
|
}
|
||||||
textCursorX = textCursorX + 8;
|
textCursorX = textCursorX + 8;
|
||||||
@@ -363,7 +369,7 @@ fn printSingleChar(char: i32) {
|
|||||||
|
|
||||||
if char == 10 {
|
if char == 10 {
|
||||||
textCursorY = textCursorY + 8;
|
textCursorY = textCursorY + 8;
|
||||||
if !graphicsText & textCursorY >= 240 {
|
if !outputChannel & textCursorY >= 240 {
|
||||||
textCursorY = 240 - 8;
|
textCursorY = 240 - 8;
|
||||||
let i: i32;
|
let i: i32;
|
||||||
loop scroll_copy {
|
loop scroll_copy {
|
||||||
@@ -377,7 +383,7 @@ fn printSingleChar(char: i32) {
|
|||||||
|
|
||||||
if char == 11 {
|
if char == 11 {
|
||||||
textCursorY = textCursorY - 8;
|
textCursorY = textCursorY - 8;
|
||||||
if !graphicsText & textCursorY < 0 {
|
if !outputChannel & textCursorY < 0 {
|
||||||
textCursorY = 0;
|
textCursorY = 0;
|
||||||
let i = 320 * (240 - 8);
|
let i = 320 * (240 - 8);
|
||||||
loop scroll_copy {
|
loop scroll_copy {
|
||||||
@@ -417,8 +423,8 @@ fn printSingleChar(char: i32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if char == 31 {
|
if char == 31 {
|
||||||
textCursorX = 0x12d20?1 * (8 - graphicsText * 6);
|
textCursorX = 0x12d20?1 * (8 - outputChannel * 6);
|
||||||
textCursorY = 0x12d20?2 * (8 - graphicsText * 7);
|
textCursorY = 0x12d20?2 * (8 - outputChannel * 7);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,7 +449,7 @@ data(0x12d00) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn drawChar(char: i32) {
|
fn drawChar(char: i32) {
|
||||||
if !graphicsText & textCursorX >= 320 {
|
if !outputChannel & textCursorX >= 320 {
|
||||||
printChar(0xd0a);
|
printChar(0xd0a);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,7 +457,7 @@ fn drawChar(char: i32) {
|
|||||||
loop rows {
|
loop rows {
|
||||||
let bits = (char * 8 + y)?0x13400;
|
let bits = (char * 8 + y)?0x13400;
|
||||||
let x = 0;
|
let x = 0;
|
||||||
if graphicsText {
|
if outputChannel {
|
||||||
loop pixels {
|
loop pixels {
|
||||||
if (bits := bits << 1) & 256 {
|
if (bits := bits << 1) & 256 {
|
||||||
setPixel(textCursorX + x, textCursorY + y, textColor);
|
setPixel(textCursorX + x, textCursorY + y, textColor);
|
||||||
@@ -503,7 +509,7 @@ export fn setBackgroundColor(col: i32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export fn setCursorPosition(x: i32, y: i32) {
|
export fn setCursorPosition(x: i32, y: i32) {
|
||||||
let lazy scale = select(graphicsText, 1, 8);
|
let lazy scale = select(outputChannel, 1, 8);
|
||||||
textCursorX = x * scale;
|
textCursorX = x * scale;
|
||||||
textCursorY = y * scale;
|
textCursorY = y * scale;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ The initial motivation behind MicroW8 was to explore whether there was a way to
|
|||||||
* Gamepad input (D-Pad + 4 Buttons)
|
* Gamepad input (D-Pad + 4 Buttons)
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
* [Fireworks](v0.1.2#AgwvgP+M59snqjl4CMKw5sqm1Zw9yJCbSviMjeLUdHus2a3yl/a99+uiBeqZgP/2jqSjrLjRk73COMM6OSLpsxK8ugT1kuk/q4hQUqqPpGozHoa0laulzGGcahzdfdJsYaK1sIdeIYS9M5PnJx/Wk9H+PvWEPy2Zvv7I6IW7Fg==) (127 bytes): Some fireworks to welcome 2022.
|
* [Skip Ahead](v0.2.0#AgVfq24KI2Ok2o8qVtPYj27fSuGnfeSKgbOkIOsaEQMov8TDYQ6UjdjwkZrYcM1i9alo4/+Bhm1PRFEa0YHJlJAk/PGoc2K41rejv9ZSqJqIHNjr7cappqhOR2jT+jk+0b0+U6hO+geRCTP2aufWs7L+f/Z27NFY8LKlqPSv+C6Rd6+ohoKi6sYl5Kcrlf1cyTinV7jTTnmbcXWVDBA5rRKxAGMUTDS8rHxqSztRITOaQVP1pSdYgi/BDdOJOxSOIkeaId84S+Ycls5na7EgwSfVIpgqF+tcfkUecb8t2mQrXA7pyKrh/wzHn5N6Oe5aOgmzY2YpTIct) (249 bytes): A port of my [TIC-80 256byte game](http://tic80.com/play?cart=1735) from LoveByte'21, now with sound
|
||||||
* [Skip Ahead](v0.1.2#AgyfpZ80wkW28kiUZ9VIK4v+RPnVxqjK1dz2BcDoNyQPsS2g4OgEzkTe6jyoAfFOmqKrS8SM2aRljBal9mjNn8i4fP9eBK+RehQKxxGtJa9FqftvqEnh3ez1YaYxqj7jgTdzJ/WAYVmKMovBT1myrX3FamqKSOgMsNedLhVTLAhQup3sNcYEjGNo8b0HZ5+AgMgCwYRGCe//XQOMAaAAzqDILgmpEZ/43RKHcQpHEQwbURfNQJpadJe2sz3q5FlQnTGXQ9oSMokidhlC+aR/IpNHieuBGLhFZ2GfnwVQ0geBbQpTPA==) (229 bytes): A port of my [TIC-80 256byte game](http://tic80.com/play?cart=1735) from LoveByte'21
|
* [Fireworks](v0.2.0#AgwvgP+M59snqjl4CMKw5sqm1Zw9yJCbSviMjeLUdHus2a3yl/a99+uiBeqZgP/2jqSjrLjRk73COMM6OSLpsxK8ugT1kuk/q4hQUqqPpGozHoa0laulzGGcahzdfdJsYaK1sIdeIYS9M5PnJx/Wk9H+PvWEPy2Zvv7I6IW7Fg==) (127 bytes): Some fireworks to welcome 2022.
|
||||||
* [OhNoAnotherTunnel](v0.1.2#AgPP1oEFvPzY/rBZwTumtYn37zeMFgpir1Bkn91jsNcp26VzoUpkAOOJTtnzVBfW+/dGnnIdbq/irBUJztY5wuua80DORTYZndgdwZHcSk15ajc4nyO0g1A6kGWyW56oZk0iPYJA9WtUmoj0Plvy1CGwIZrMe57X7QZcdqc3u6zjTA41Tpiqi9vnO3xbhi8o594Vx0XPXwVzpYq1ZCTYenfAGaXKkDmAFJqiVIsiCg==) (175 bytes): A port of my [entry](http://tic80.com/play?cart=1871) in the Outline'21 bytebattle final
|
* [OhNoAnotherTunnel](v0.2.0#AgPP1oEFvPzY/rBZwTumtYn37zeMFgpir1Bkn91jsNcp26VzoUpkAOOJTtnzVBfW+/dGnnIdbq/irBUJztY5wuua80DORTYZndgdwZHcSk15ajc4nyO0g1A6kGWyW56oZk0iPYJA9WtUmoj0Plvy1CGwIZrMe57X7QZcdqc3u6zjTA41Tpiqi9vnO3xbhi8o594Vx0XPXwVzpYq1ZCTYenfAGaXKkDmAFJqiVIsiCg==) (175 bytes): A port of my [entry](http://tic80.com/play?cart=1871) in the Outline'21 bytebattle final
|
||||||
* [Technotunnel](v0.1.2#AhPXpq894LaUhp5+HQf39f39/Jc8g5zUrBSc0uyKh36ivskczhY84h55zL8gWpkdvKuRQI+KIt80isKzh8jkM8nILcx0RUvyk8yjE8TgNsgkcORVI0RY5k3qE4ySjaycxa2DVZH61UWZuLsCouuwT7I80TbmmetQSbMywJ/avrrCZIAH0UzQfvOiCJNG48NI0FFY1vjB7a7dcp8Uqg==) (157 bytes): A port of my [entry](https://tic80.com/play?cart=1873) in the Outline'21 bytebattle quater final
|
* [Technotunnel](v0.2.0#AhPXpq894LaUhp5+HQf39f39/Jc8g5zUrBSc0uyKh36ivskczhY84h55zL8gWpkdvKuRQI+KIt80isKzh8jkM8nILcx0RUvyk8yjE8TgNsgkcORVI0RY5k3qE4ySjaycxa2DVZH61UWZuLsCouuwT7I80TbmmetQSbMywJ/avrrCZIAH0UzQfvOiCJNG48NI0FFY1vjB7a7dcp8Uqg==) (157 bytes): A port of my [entry](https://tic80.com/play?cart=1873) in the Outline'21 bytebattle quater final
|
||||||
* [Font & Palette](v0.1.2#At/p39+IBnj6ry1TRe7jzVy2A4tXgBvmoW2itzoyF2aM28pGy5QDiKxqrk8l9sbWZLtnAb+jgOfU+9QhpuyCAkhN6gPOU481IUL/df96vNe3h288Dqwhd3sfFpothIVFsMwRK72kW2hiR7zWsaXyy5pNmjR6BJk4piWx9ApT1ZwoUajhk6/zij6itq/FD1U3jj/J3MOwqZ2ef8Bv6ZPQlJIYVf62icGa69wS6SI1qBpIFiF14F8PcztRVbKIxLpT4ArCS6nz6FPnyUkqATGSBNPJ): Just a simple viewer for the default font and palette.
|
* [Font & Palette](v0.2.0#At/p39+IBnj6ry1TRe7jzVy2A4tXgBvmoW2itzoyF2aM28pGy5QDiKxqrk8l9sbWZLtnAb+jgOfU+9QhpuyCAkhN6gPOU481IUL/df96vNe3h288Dqwhd3sfFpothIVFsMwRK72kW2hiR7zWsaXyy5pNmjR6BJk4piWx9ApT1ZwoUajhk6/zij6itq/FD1U3jj/J3MOwqZ2ef8Bv6ZPQlJIYVf62icGa69wS6SI1qBpIFiF14F8PcztRVbKIxLpT4ArCS6nz6FPnyUkqATGSBNPJ): Just a simple viewer for the default font and palette.
|
||||||
|
|
||||||
Examplers for older versions:
|
Examplers for older versions:
|
||||||
|
|
||||||
@@ -29,19 +29,23 @@ Examplers for older versions:
|
|||||||
|
|
||||||
## Versions
|
## Versions
|
||||||
|
|
||||||
### v0.1.2
|
### v0.2.0
|
||||||
|
|
||||||
* [Web runtime](v0.1.2)
|
* [Web runtime](v0.2.0)
|
||||||
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-linux.tgz)
|
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.0/microw8-0.2.0-linux.tgz)
|
||||||
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-macos.tgz)
|
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.0/microw8-0.2.0-macos.tgz)
|
||||||
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-windows.zip)
|
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.0/microw8-0.2.0-windows.zip)
|
||||||
|
|
||||||
Changes:
|
Changes:
|
||||||
|
|
||||||
* add option to `uw8 run` to run the cart in the browser using the web runtime
|
* [add sound support!](docs#sound)
|
||||||
* CurlyWas: implement `include` support
|
* add support to redirect text output to the console for debugging using control code 6
|
||||||
* CurlyWas: implement support for constants
|
* update curlywas:
|
||||||
* fix crash when trying to draw zero sized line
|
* * add support for `else if`
|
||||||
|
* * add support for escape sequences in strings
|
||||||
|
* * add support for char literals
|
||||||
|
* * add support for binop-assignment, eg. `+=`, `^=`, `<<=` etc. (also support for the tee operator: `+:=`)
|
||||||
|
* * "integer constant cast to float" literal syntax in CurlyWas (ex. `1_f` is equivalent to `1 as f32`)
|
||||||
|
|
||||||
### Older versions
|
### Older versions
|
||||||
|
|
||||||
|
|||||||
@@ -225,30 +225,45 @@ Moving/printing past any border does not cause any special operation, the cursor
|
|||||||
Characters 0-31 are control characters and don't print by default. They take the next 0-2 following characters as parameters.
|
Characters 0-31 are control characters and don't print by default. They take the next 0-2 following characters as parameters.
|
||||||
Avoid the reserved control chars, they are currently NOPs but their behavior can change in later MicroW8 versions.
|
Avoid the reserved control chars, they are currently NOPs but their behavior can change in later MicroW8 versions.
|
||||||
|
|
||||||
| Code | Parameters | Operation |
|
| Code | Parameters | Operation |
|
||||||
| ----- | ---------- | ------------------------------------ |
|
| ----- | ---------- | ------------------------------------------ |
|
||||||
| 0 | - | Nop |
|
| 0 | - | Nop |
|
||||||
| 1 | char | Print char (including control chars) |
|
| 1 | char | Print char (including control chars) |
|
||||||
| 2-3 | - | Reserved |
|
| 2-3 | - | Reserved |
|
||||||
| 4 | - | Switch to normal mode |
|
| 4 | - | Switch to normal mode, reset cursor to 0,0 |
|
||||||
| 5 | - | Switch to graphics mode |
|
| 5 | - | Switch to graphics mode |
|
||||||
| 6 | - | Reserved |
|
| 6 | - | Switch output to (debug) console |
|
||||||
| 7 | - | Bell / trigger sound channel 0 |
|
| 7 | - | Bell / trigger sound channel 0 |
|
||||||
| 8 | - | Move cursor left |
|
| 8 | - | Move cursor left |
|
||||||
| 9 | - | Move cursor right |
|
| 9 | - | Move cursor right |
|
||||||
| 10 | - | Move cursor down |
|
| 10 | - | Move cursor down |
|
||||||
| 11 | - | Move cursor up |
|
| 11 | - | Move cursor up |
|
||||||
| 12 | - | do `cls(background_color)` |
|
| 12 | - | do `cls(background_color)` |
|
||||||
| 13 | - | Move cursor to the left border |
|
| 13 | - | Move cursor to the left border |
|
||||||
| 14 | color | Set the background color |
|
| 14 | color | Set the background color |
|
||||||
| 15 | color | Set the text color |
|
| 15 | color | Set the text color |
|
||||||
| 16-23 | - | Reserved |
|
| 16-23 | - | Reserved |
|
||||||
| 24 | - | Swap text/background colors |
|
| 24 | - | Swap text/background colors |
|
||||||
| 25-30 | - | Reserved |
|
| 25-30 | - | Reserved |
|
||||||
| 31 | x, y | Set cursor position (*) |
|
| 31 | x, y | Set cursor position (*) |
|
||||||
|
|
||||||
(*) In graphics mode, the x coordinate is doubled when using control char 31 to be able to cover the whole screen with one byte.
|
(*) In graphics mode, the x coordinate is doubled when using control char 31 to be able to cover the whole screen with one byte.
|
||||||
|
|
||||||
|
#### Debug output
|
||||||
|
|
||||||
|
Control code 6 switches all text output (except codes 4 and 5 to switch output back to the screen) to the console. Where exactly this ends
|
||||||
|
up (if at all) is an implementation detail of the runtimes. The native dev-runtime writes the debug output to `stdout`, the web runtime to
|
||||||
|
the debug console using `console.log`. Both implementation buffer the output until they encounter a newline character (10) in the output stream.
|
||||||
|
|
||||||
|
There may be future runtimes that ignore the debug output completely.
|
||||||
|
|
||||||
|
In CurlyWas, a simple way to log some value might look like this:
|
||||||
|
```
|
||||||
|
printChar('\06V: '); // switch to console out, print some prefix
|
||||||
|
printInt(some_value);
|
||||||
|
printChar('\n\4'); // newline and switch back to screen
|
||||||
|
```
|
||||||
|
|
||||||
### fn printChar(char: i32)
|
### fn printChar(char: i32)
|
||||||
|
|
||||||
Prints the character in the lower 8 bits of `char`. If the upper 24 bits are non-zero, right-shifts `char` by 8 bits and loops back to the beginning.
|
Prints the character in the lower 8 bits of `char`. If the upper 24 bits are non-zero, right-shifts `char` by 8 bits and loops back to the beginning.
|
||||||
@@ -286,6 +301,12 @@ As the only means of communication, 32 bytes starting at address 0x00050 are cop
|
|||||||
By default, the `sndGes` function generates sound based on the 32 bytes at 0x00050. This means that in the default configuration those 32 bytes act
|
By default, the `sndGes` function generates sound based on the 32 bytes at 0x00050. This means that in the default configuration those 32 bytes act
|
||||||
as sound registers. See the `sndGes` function for the meaning of those registers.
|
as sound registers. See the `sndGes` function for the meaning of those registers.
|
||||||
|
|
||||||
|
### export fn snd(sampleIndex: i32) -> f32
|
||||||
|
|
||||||
|
If the module exports a `snd` function, it is called 88200 times per second to provide PCM sample data for playback (44.1kHz stereo).
|
||||||
|
The `sampleIndex` will start at 0 and increments by 1 for each call. On even indices the function is expected to return a sample value for
|
||||||
|
the left channel, on odd indices for the right channel.
|
||||||
|
|
||||||
### fn playNote(channel: i32, note: i32)
|
### fn playNote(channel: i32, note: i32)
|
||||||
|
|
||||||
Triggers a note (1-127) on the given channel (0-3). Notes are semitones with 69 being A4 (same as MIDI). A note value of 0 stops the
|
Triggers a note (1-127) on the given channel (0-3). Notes are semitones with 69 being A4 (same as MIDI). A note value of 0 stops the
|
||||||
|
|||||||
@@ -2,6 +2,69 @@
|
|||||||
description = "Versions"
|
description = "Versions"
|
||||||
+++
|
+++
|
||||||
|
|
||||||
|
### v0.2.0
|
||||||
|
|
||||||
|
* [Web runtime](v0.2.0)
|
||||||
|
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.0/microw8-0.2.0-linux.tgz)
|
||||||
|
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.0/microw8-0.2.0-macos.tgz)
|
||||||
|
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.0/microw8-0.2.0-windows.zip)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
* [add sound support!](docs#sound)
|
||||||
|
* add support to redirect text output to the console for debugging using control code 6
|
||||||
|
* update curlywas:
|
||||||
|
* * add support for `else if`
|
||||||
|
* * add support for escape sequences in strings
|
||||||
|
* * add support for char literals
|
||||||
|
* * add support for binop-assignment, eg. `+=`, `^=`, `<<=` etc. (also support for the tee operator: `+:=`)
|
||||||
|
* * "integer constant cast to float" literal syntax in CurlyWas (ex. `1_f` is equivalent to `1 as f32`)
|
||||||
|
|
||||||
|
### v0.2.0-rc3
|
||||||
|
|
||||||
|
* [Web runtime](v0.2.0-rc3)
|
||||||
|
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc3/microw8-0.2.0-rc3-linux.tgz)
|
||||||
|
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc3/microw8-0.2.0-rc3-macos.tgz)
|
||||||
|
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc3/microw8-0.2.0-rc3-windows.zip)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
* improve timing stability some more. essentially now guaranteeing that "frame = time_ms * 6 / 100" returns
|
||||||
|
consecutive frame numbers, provided the module can be run at 60 fps
|
||||||
|
* add support to redirect text output to the console for debugging using control code 6
|
||||||
|
* update curlywas:
|
||||||
|
* * add support for `else if`
|
||||||
|
* * add support for escape sequences in strings
|
||||||
|
* * add support for char literals
|
||||||
|
* * add support for binop-assignment, eg. `+=`, `^=`, `<<=` etc. (also support for the tee operator: `+:=`)
|
||||||
|
|
||||||
|
### v0.2.0-rc2
|
||||||
|
|
||||||
|
* [Web runtime](v0.2.0-rc2)
|
||||||
|
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc2/microw8-0.2.0-rc2-linux.tgz)
|
||||||
|
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc2/microw8-0.2.0-rc2-macos.tgz)
|
||||||
|
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc2/microw8-0.2.0-rc2-windows.zip)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
* fix timing issues of sound playback, especially on systems with large sound buffers
|
||||||
|
|
||||||
|
### v0.2.0-rc1
|
||||||
|
|
||||||
|
* [Web runtime](v0.2.0-rc1)
|
||||||
|
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc1/microw8-0.2.0-rc1-linux.tgz)
|
||||||
|
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc1/microw8-0.2.0-rc1-macos.tgz)
|
||||||
|
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc1/microw8-0.2.0-rc1-windows.zip)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
* [add sound support](docs#sound)
|
||||||
|
* "integer constant cast to float" literal syntax in CurlyWas (ex. `1_f` is equivalent to `1 as f32`)
|
||||||
|
|
||||||
|
Known issues:
|
||||||
|
|
||||||
|
* timing accuracy/update frequency of sound support currently depends on sound buffer size
|
||||||
|
|
||||||
### v0.1.2
|
### v0.1.2
|
||||||
|
|
||||||
* [Web runtime](v0.1.2)
|
* [Web runtime](v0.1.2)
|
||||||
|
|||||||
1
site/static/v0.2.0-rc1/index.html
Normal file
1
site/static/v0.2.0-rc1/index.html
Normal file
File diff suppressed because one or more lines are too long
1
site/static/v0.2.0-rc2/index.html
Normal file
1
site/static/v0.2.0-rc2/index.html
Normal file
File diff suppressed because one or more lines are too long
1
site/static/v0.2.0-rc3/index.html
Normal file
1
site/static/v0.2.0-rc3/index.html
Normal file
File diff suppressed because one or more lines are too long
1
site/static/v0.2.0/index.html
Normal file
1
site/static/v0.2.0/index.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -4,7 +4,7 @@
|
|||||||
<section>
|
<section>
|
||||||
<h1 class="text-center heading-text">A WebAssembly based fantasy console</h1>
|
<h1 class="text-center heading-text">A WebAssembly based fantasy console</h1>
|
||||||
</section>
|
</section>
|
||||||
<a href="v0.1.2">
|
<a href="v0.2.0">
|
||||||
<img class="demonstration-gif" style="width:640px;height:480px;image-rendering:pixelated" src="img/technotunnel.png"></img>
|
<img class="demonstration-gif" style="width:640px;height:480px;image-rendering:pixelated" src="img/technotunnel.png"></img>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
12
src/main.rs
12
src/main.rs
@@ -16,7 +16,7 @@ fn main() -> Result<()> {
|
|||||||
let mut args = Arguments::from_env();
|
let mut args = Arguments::from_env();
|
||||||
|
|
||||||
// try to enable ansi support in win10 cmd shell
|
// try to enable ansi support in win10 cmd shell
|
||||||
#[cfg(target_os="windows")]
|
#[cfg(target_os = "windows")]
|
||||||
let _ = ansi_term::enable_ansi_support();
|
let _ = ansi_term::enable_ansi_support();
|
||||||
|
|
||||||
match args.subcommand()?.as_deref() {
|
match args.subcommand()?.as_deref() {
|
||||||
@@ -79,6 +79,8 @@ fn run(mut args: Arguments) -> Result<()> {
|
|||||||
#[cfg(not(feature = "native"))]
|
#[cfg(not(feature = "native"))]
|
||||||
let run_browser = args.contains(["-b", "--browser"]) || true;
|
let run_browser = args.contains(["-b", "--browser"]) || true;
|
||||||
|
|
||||||
|
let disable_audio = args.contains(["-m", "--disable-audio"]);
|
||||||
|
|
||||||
let filename = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
let filename = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
||||||
|
|
||||||
let mut watcher = uw8::FileWatcher::new()?;
|
let mut watcher = uw8::FileWatcher::new()?;
|
||||||
@@ -89,7 +91,13 @@ fn run(mut args: Arguments) -> Result<()> {
|
|||||||
#[cfg(not(feature = "native"))]
|
#[cfg(not(feature = "native"))]
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
#[cfg(feature = "native")]
|
#[cfg(feature = "native")]
|
||||||
Box::new(MicroW8::new()?)
|
{
|
||||||
|
let mut microw8 = MicroW8::new()?;
|
||||||
|
if disable_audio {
|
||||||
|
microw8.disable_audio();
|
||||||
|
}
|
||||||
|
Box::new(microw8)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
#[cfg(not(feature = "browser"))]
|
#[cfg(not(feature = "browser"))]
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -5,6 +5,7 @@ use std::{thread, time::Instant};
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use cpal::traits::*;
|
use cpal::traits::*;
|
||||||
use minifb::{Key, Window, WindowOptions};
|
use minifb::{Key, Window, WindowOptions};
|
||||||
|
use rubato::Resampler;
|
||||||
use wasmtime::{
|
use wasmtime::{
|
||||||
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
|
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
|
||||||
};
|
};
|
||||||
@@ -27,6 +28,7 @@ pub struct MicroW8 {
|
|||||||
window_buffer: Vec<u32>,
|
window_buffer: Vec<u32>,
|
||||||
instance: Option<UW8Instance>,
|
instance: Option<UW8Instance>,
|
||||||
timeout: u32,
|
timeout: u32,
|
||||||
|
disable_audio: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UW8Instance {
|
struct UW8Instance {
|
||||||
@@ -35,9 +37,10 @@ struct UW8Instance {
|
|||||||
end_frame: TypedFunc<(), ()>,
|
end_frame: TypedFunc<(), ()>,
|
||||||
update: Option<TypedFunc<(), ()>>,
|
update: Option<TypedFunc<(), ()>>,
|
||||||
start_time: Instant,
|
start_time: Instant,
|
||||||
|
next_frame: Instant,
|
||||||
module: Vec<u8>,
|
module: Vec<u8>,
|
||||||
watchdog: Arc<Mutex<UW8WatchDog>>,
|
watchdog: Arc<Mutex<UW8WatchDog>>,
|
||||||
sound: Uw8Sound,
|
sound: Option<Uw8Sound>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for UW8Instance {
|
impl Drop for UW8Instance {
|
||||||
@@ -56,6 +59,9 @@ struct UW8WatchDog {
|
|||||||
|
|
||||||
impl MicroW8 {
|
impl MicroW8 {
|
||||||
pub fn new() -> Result<MicroW8> {
|
pub fn new() -> Result<MicroW8> {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
unsafe { winapi::um::timeapi::timeBeginPeriod(1); }
|
||||||
|
|
||||||
let engine = wasmtime::Engine::new(wasmtime::Config::new().interruptable(true))?;
|
let engine = wasmtime::Engine::new(wasmtime::Config::new().interruptable(true))?;
|
||||||
|
|
||||||
let loader_module =
|
let loader_module =
|
||||||
@@ -67,8 +73,7 @@ impl MicroW8 {
|
|||||||
resize: true,
|
resize: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let mut window = Window::new("MicroW8", 320, 240, options)?;
|
let window = Window::new("MicroW8", 320, 240, options)?;
|
||||||
window.limit_update_rate(Some(std::time::Duration::from_micros(16666)));
|
|
||||||
|
|
||||||
Ok(MicroW8 {
|
Ok(MicroW8 {
|
||||||
engine,
|
engine,
|
||||||
@@ -77,6 +82,7 @@ impl MicroW8 {
|
|||||||
window_buffer: vec![0u32; 320 * 240],
|
window_buffer: vec![0u32; 320 * 240],
|
||||||
instance: None,
|
instance: None,
|
||||||
timeout: 30,
|
timeout: 30,
|
||||||
|
disable_audio: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,11 +92,10 @@ impl MicroW8 {
|
|||||||
*v = 0;
|
*v = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
struct Uw8Sound {
|
pub fn disable_audio(&mut self) {
|
||||||
stream: cpal::Stream,
|
self.disable_audio = true;
|
||||||
tx: mpsc::SyncSender<[u8; 32]>,
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::Runtime for MicroW8 {
|
impl super::Runtime for MicroW8 {
|
||||||
@@ -126,60 +131,8 @@ impl super::Runtime for MicroW8 {
|
|||||||
let module_length = load_uw8.call(&mut store, module_data.len() as i32)? as u32 as usize;
|
let module_length = load_uw8.call(&mut store, module_data.len() as i32)? as u32 as usize;
|
||||||
let module = wasmtime::Module::new(&self.engine, &memory.data(&store)[..module_length])?;
|
let module = wasmtime::Module::new(&self.engine, &memory.data(&store)[..module_length])?;
|
||||||
|
|
||||||
fn add_native_functions(
|
|
||||||
linker: &mut wasmtime::Linker<()>,
|
|
||||||
store: &mut wasmtime::Store<()>,
|
|
||||||
) -> Result<()> {
|
|
||||||
linker.func_wrap("env", "acos", |v: f32| v.acos())?;
|
|
||||||
linker.func_wrap("env", "asin", |v: f32| v.asin())?;
|
|
||||||
linker.func_wrap("env", "atan", |v: f32| v.atan())?;
|
|
||||||
linker.func_wrap("env", "atan2", |x: f32, y: f32| x.atan2(y))?;
|
|
||||||
linker.func_wrap("env", "cos", |v: f32| v.cos())?;
|
|
||||||
linker.func_wrap("env", "exp", |v: f32| v.exp())?;
|
|
||||||
linker.func_wrap("env", "log", |v: f32| v.ln())?;
|
|
||||||
linker.func_wrap("env", "sin", |v: f32| v.sin())?;
|
|
||||||
linker.func_wrap("env", "tan", |v: f32| v.tan())?;
|
|
||||||
linker.func_wrap("env", "pow", |a: f32, b: f32| a.powf(b))?;
|
|
||||||
for i in 10..64 {
|
|
||||||
linker.func_wrap("env", &format!("reserved{}", i), || {})?;
|
|
||||||
}
|
|
||||||
for i in 0..16 {
|
|
||||||
linker.define(
|
|
||||||
"env",
|
|
||||||
&format!("g_reserved{}", i),
|
|
||||||
wasmtime::Global::new(
|
|
||||||
&mut *store,
|
|
||||||
GlobalType::new(ValType::I32, Mutability::Const),
|
|
||||||
0.into(),
|
|
||||||
)?,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
add_native_functions(&mut linker, &mut store)?;
|
add_native_functions(&mut linker, &mut store)?;
|
||||||
|
|
||||||
fn instantiate_platform(
|
|
||||||
linker: &mut wasmtime::Linker<()>,
|
|
||||||
store: &mut wasmtime::Store<()>,
|
|
||||||
platform_module: &wasmtime::Module,
|
|
||||||
) -> Result<wasmtime::Instance> {
|
|
||||||
let platform_instance = linker.instantiate(&mut *store, &platform_module)?;
|
|
||||||
|
|
||||||
for export in platform_instance.exports(&mut *store) {
|
|
||||||
linker.define(
|
|
||||||
"env",
|
|
||||||
export.name(),
|
|
||||||
export
|
|
||||||
.into_func()
|
|
||||||
.expect("platform surely only exports functions"),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(platform_instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
let platform_instance = instantiate_platform(&mut linker, &mut store, &platform_module)?;
|
let platform_instance = instantiate_platform(&mut linker, &mut store, &platform_module)?;
|
||||||
|
|
||||||
let watchdog = Arc::new(Mutex::new(UW8WatchDog {
|
let watchdog = Arc::new(Mutex::new(UW8WatchDog {
|
||||||
@@ -215,91 +168,28 @@ impl super::Runtime for MicroW8 {
|
|||||||
let end_frame = platform_instance.get_typed_func::<(), (), _>(&mut store, "endFrame")?;
|
let end_frame = platform_instance.get_typed_func::<(), (), _>(&mut store, "endFrame")?;
|
||||||
let update = instance.get_typed_func::<(), (), _>(&mut store, "upd").ok();
|
let update = instance.get_typed_func::<(), (), _>(&mut store, "upd").ok();
|
||||||
|
|
||||||
let sound = {
|
let sound = if self.disable_audio {
|
||||||
let mut store = wasmtime::Store::new(&self.engine, ());
|
None
|
||||||
|
} else {
|
||||||
let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
|
match init_sound(&self.engine, &platform_module, &module) {
|
||||||
|
Ok(sound) => {
|
||||||
let mut linker = wasmtime::Linker::new(&self.engine);
|
sound.stream.play()?;
|
||||||
linker.define("env", "memory", memory)?;
|
Some(sound)
|
||||||
add_native_functions(&mut linker, &mut store)?;
|
|
||||||
|
|
||||||
let platform_instance =
|
|
||||||
instantiate_platform(&mut linker, &mut store, &platform_module)?;
|
|
||||||
let instance = linker.instantiate(&mut store, &module)?;
|
|
||||||
|
|
||||||
let snd = instance
|
|
||||||
.get_typed_func::<(i32,), f32, _>(&mut store, "snd")
|
|
||||||
.or_else(|_| {
|
|
||||||
platform_instance.get_typed_func::<(i32,), f32, _>(&mut store, "gesSnd")
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let host = cpal::default_host();
|
|
||||||
let device = host
|
|
||||||
.default_output_device()
|
|
||||||
.ok_or_else(|| anyhow!("No audio output device available"))?;
|
|
||||||
let config = device
|
|
||||||
.supported_output_configs()?
|
|
||||||
.find(|config| {
|
|
||||||
config.min_sample_rate().0 <= 44100
|
|
||||||
&& config.max_sample_rate().0 >= 44100
|
|
||||||
&& config.channels() == 2
|
|
||||||
&& config.sample_format() == cpal::SampleFormat::F32
|
|
||||||
})
|
|
||||||
.ok_or_else(|| anyhow!("Could not find 44.1kHz float config"))?;
|
|
||||||
let config = config.with_sample_rate(cpal::SampleRate(44100));
|
|
||||||
let buffer_size = match *config.buffer_size() {
|
|
||||||
cpal::SupportedBufferSize::Unknown => cpal::BufferSize::Default,
|
|
||||||
cpal::SupportedBufferSize::Range { min, max } => {
|
|
||||||
cpal::BufferSize::Fixed(256.max(min).min(max))
|
|
||||||
}
|
}
|
||||||
};
|
Err(err) => {
|
||||||
let config = cpal::StreamConfig {
|
eprintln!("Failed to init sound: {}", err);
|
||||||
buffer_size,
|
None
|
||||||
..config.config()
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
let (tx, rx) = mpsc::sync_channel::<[u8; 32]>(1);
|
|
||||||
|
|
||||||
let start_time = Instant::now();
|
|
||||||
|
|
||||||
let mut sample_index = 0;
|
|
||||||
let stream = {
|
|
||||||
device.build_output_stream(
|
|
||||||
&config,
|
|
||||||
move |buffer: &mut [f32], _| {
|
|
||||||
if let Ok(regs) = rx.try_recv() {
|
|
||||||
memory.write(&mut store, 80, ®s).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let time = start_time.elapsed().as_millis() as i32;
|
|
||||||
let mem = memory.data_mut(&mut store);
|
|
||||||
mem[64..68].copy_from_slice(&time.to_le_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
for v in buffer {
|
|
||||||
*v = snd.call(&mut store, (sample_index,)).unwrap_or(0.0);
|
|
||||||
sample_index = sample_index.wrapping_add(1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
move |err| {
|
|
||||||
dbg!(err);
|
|
||||||
},
|
|
||||||
)?
|
|
||||||
};
|
|
||||||
|
|
||||||
Uw8Sound { stream, tx }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
sound.stream.play()?;
|
|
||||||
|
|
||||||
self.instance = Some(UW8Instance {
|
self.instance = Some(UW8Instance {
|
||||||
store,
|
store,
|
||||||
memory,
|
memory,
|
||||||
end_frame,
|
end_frame,
|
||||||
update,
|
update,
|
||||||
start_time: Instant::now(),
|
start_time: Instant::now(),
|
||||||
|
next_frame: Instant::now(),
|
||||||
module: module_data.into(),
|
module: module_data.into(),
|
||||||
watchdog,
|
watchdog,
|
||||||
sound,
|
sound,
|
||||||
@@ -312,7 +202,19 @@ impl super::Runtime for MicroW8 {
|
|||||||
let mut result = Ok(());
|
let mut result = Ok(());
|
||||||
if let Some(mut instance) = self.instance.take() {
|
if let Some(mut instance) = self.instance.take() {
|
||||||
{
|
{
|
||||||
let time = instance.start_time.elapsed().as_millis() as i32;
|
if let Some(sleep) = instance.next_frame.checked_duration_since(Instant::now()) {
|
||||||
|
std::thread::sleep(sleep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let now = Instant::now();
|
||||||
|
let time = (now - instance.start_time).as_millis() as i32;
|
||||||
|
{
|
||||||
|
let offset = ((time as u32 as i64 * 6) % 100 - 50) / 6;
|
||||||
|
instance.next_frame = now + Duration::from_millis((16 - offset) as u64);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
let mut gamepad: u32 = 0;
|
let mut gamepad: u32 = 0;
|
||||||
for key in self.window.get_keys() {
|
for key in self.window.get_keys() {
|
||||||
if let Some(index) = GAMEPAD_KEYS
|
if let Some(index) = GAMEPAD_KEYS
|
||||||
@@ -345,7 +247,12 @@ impl super::Runtime for MicroW8 {
|
|||||||
|
|
||||||
let mut sound_regs = [0u8; 32];
|
let mut sound_regs = [0u8; 32];
|
||||||
sound_regs.copy_from_slice(&memory[80..112]);
|
sound_regs.copy_from_slice(&memory[80..112]);
|
||||||
instance.sound.tx.send(sound_regs)?;
|
if let Some(ref sound) = instance.sound {
|
||||||
|
sound.tx.send(RegisterUpdate {
|
||||||
|
time,
|
||||||
|
data: sound_regs,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
let framebuffer = &memory[120..(120 + 320 * 240)];
|
let framebuffer = &memory[120..(120 + 320 * 240)];
|
||||||
let palette = &memory[0x13000..];
|
let palette = &memory[0x13000..];
|
||||||
@@ -371,3 +278,254 @@ impl super::Runtime for MicroW8 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_native_functions(
|
||||||
|
linker: &mut wasmtime::Linker<()>,
|
||||||
|
store: &mut wasmtime::Store<()>,
|
||||||
|
) -> Result<()> {
|
||||||
|
linker.func_wrap("env", "acos", |v: f32| v.acos())?;
|
||||||
|
linker.func_wrap("env", "asin", |v: f32| v.asin())?;
|
||||||
|
linker.func_wrap("env", "atan", |v: f32| v.atan())?;
|
||||||
|
linker.func_wrap("env", "atan2", |x: f32, y: f32| x.atan2(y))?;
|
||||||
|
linker.func_wrap("env", "cos", |v: f32| v.cos())?;
|
||||||
|
linker.func_wrap("env", "exp", |v: f32| v.exp())?;
|
||||||
|
linker.func_wrap("env", "log", |v: f32| v.ln())?;
|
||||||
|
linker.func_wrap("env", "sin", |v: f32| v.sin())?;
|
||||||
|
linker.func_wrap("env", "tan", |v: f32| v.tan())?;
|
||||||
|
linker.func_wrap("env", "pow", |a: f32, b: f32| a.powf(b))?;
|
||||||
|
for i in 10..64 {
|
||||||
|
linker.func_wrap("env", &format!("reserved{}", i), || {})?;
|
||||||
|
}
|
||||||
|
let log_line = std::sync::Mutex::new(String::new());
|
||||||
|
linker.func_wrap("env", "logChar", move |c: i32| {
|
||||||
|
let mut log_line = log_line.lock().unwrap();
|
||||||
|
if c == 10 {
|
||||||
|
println!("{}", log_line);
|
||||||
|
log_line.clear();
|
||||||
|
} else {
|
||||||
|
log_line.push(c as u8 as char);
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
for i in 0..16 {
|
||||||
|
linker.define(
|
||||||
|
"env",
|
||||||
|
&format!("g_reserved{}", i),
|
||||||
|
wasmtime::Global::new(
|
||||||
|
&mut *store,
|
||||||
|
GlobalType::new(ValType::I32, Mutability::Const),
|
||||||
|
0.into(),
|
||||||
|
)?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instantiate_platform(
|
||||||
|
linker: &mut wasmtime::Linker<()>,
|
||||||
|
store: &mut wasmtime::Store<()>,
|
||||||
|
platform_module: &wasmtime::Module,
|
||||||
|
) -> Result<wasmtime::Instance> {
|
||||||
|
let platform_instance = linker.instantiate(&mut *store, &platform_module)?;
|
||||||
|
|
||||||
|
for export in platform_instance.exports(&mut *store) {
|
||||||
|
linker.define(
|
||||||
|
"env",
|
||||||
|
export.name(),
|
||||||
|
export
|
||||||
|
.into_func()
|
||||||
|
.expect("platform surely only exports functions"),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(platform_instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RegisterUpdate {
|
||||||
|
time: i32,
|
||||||
|
data: [u8; 32],
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Uw8Sound {
|
||||||
|
stream: cpal::Stream,
|
||||||
|
tx: mpsc::SyncSender<RegisterUpdate>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_sound(
|
||||||
|
engine: &wasmtime::Engine,
|
||||||
|
platform_module: &wasmtime::Module,
|
||||||
|
module: &wasmtime::Module,
|
||||||
|
) -> Result<Uw8Sound> {
|
||||||
|
let mut store = wasmtime::Store::new(engine, ());
|
||||||
|
|
||||||
|
let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
|
||||||
|
|
||||||
|
let mut linker = wasmtime::Linker::new(engine);
|
||||||
|
linker.define("env", "memory", memory)?;
|
||||||
|
add_native_functions(&mut linker, &mut store)?;
|
||||||
|
|
||||||
|
let platform_instance = instantiate_platform(&mut linker, &mut store, platform_module)?;
|
||||||
|
let instance = linker.instantiate(&mut store, module)?;
|
||||||
|
|
||||||
|
let snd = instance
|
||||||
|
.get_typed_func::<(i32,), f32, _>(&mut store, "snd")
|
||||||
|
.or_else(|_| platform_instance.get_typed_func::<(i32,), f32, _>(&mut store, "gesSnd"))?;
|
||||||
|
|
||||||
|
let host = cpal::default_host();
|
||||||
|
let device = host
|
||||||
|
.default_output_device()
|
||||||
|
.ok_or_else(|| anyhow!("No audio output device available"))?;
|
||||||
|
let mut configs: Vec<_> = device
|
||||||
|
.supported_output_configs()?
|
||||||
|
.filter(|config| {
|
||||||
|
config.channels() == 2 && config.sample_format() == cpal::SampleFormat::F32
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
configs.sort_by_key(|config| {
|
||||||
|
let rate = 44100
|
||||||
|
.max(config.min_sample_rate().0)
|
||||||
|
.min(config.max_sample_rate().0);
|
||||||
|
if rate >= 44100 {
|
||||||
|
rate - 44100
|
||||||
|
} else {
|
||||||
|
(44100 - rate) * 1000
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let config = configs
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| anyhow!("Could not find float output config"))?;
|
||||||
|
let sample_rate = cpal::SampleRate(44100)
|
||||||
|
.max(config.min_sample_rate())
|
||||||
|
.max(config.max_sample_rate());
|
||||||
|
let config = config.with_sample_rate(sample_rate);
|
||||||
|
let buffer_size = match *config.buffer_size() {
|
||||||
|
cpal::SupportedBufferSize::Unknown => cpal::BufferSize::Default,
|
||||||
|
cpal::SupportedBufferSize::Range { min, max } => {
|
||||||
|
cpal::BufferSize::Fixed(256.max(min).min(max))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let config = cpal::StreamConfig {
|
||||||
|
buffer_size,
|
||||||
|
..config.config()
|
||||||
|
};
|
||||||
|
|
||||||
|
let sample_rate = config.sample_rate.0 as usize;
|
||||||
|
|
||||||
|
let (tx, rx) = mpsc::sync_channel::<RegisterUpdate>(30);
|
||||||
|
|
||||||
|
struct Resampler {
|
||||||
|
resampler: rubato::FftFixedIn<f32>,
|
||||||
|
input_buffers: Vec<Vec<f32>>,
|
||||||
|
output_buffers: Vec<Vec<f32>>,
|
||||||
|
output_index: usize,
|
||||||
|
}
|
||||||
|
let mut resampler: Option<Resampler> = if sample_rate == 44100 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let rs = rubato::FftFixedIn::new(44100, sample_rate, 128, 1, 2)?;
|
||||||
|
let input_buffers = rs.input_buffer_allocate();
|
||||||
|
let output_buffers = rs.output_buffer_allocate();
|
||||||
|
Some(Resampler {
|
||||||
|
resampler: rs,
|
||||||
|
input_buffers,
|
||||||
|
output_buffers,
|
||||||
|
output_index: usize::MAX,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut sample_index = 0;
|
||||||
|
let mut pending_updates: Vec<RegisterUpdate> = Vec::with_capacity(30);
|
||||||
|
let mut current_time = 0;
|
||||||
|
let stream = device.build_output_stream(
|
||||||
|
&config,
|
||||||
|
move |mut outer_buffer: &mut [f32], _| {
|
||||||
|
let mut first_update = true;
|
||||||
|
while let Ok(update) = rx.try_recv() {
|
||||||
|
if first_update {
|
||||||
|
current_time += update.time.wrapping_sub(current_time) / 8;
|
||||||
|
first_update = false;
|
||||||
|
}
|
||||||
|
pending_updates.push(update);
|
||||||
|
}
|
||||||
|
|
||||||
|
while !outer_buffer.is_empty() {
|
||||||
|
while pending_updates
|
||||||
|
.first()
|
||||||
|
.into_iter()
|
||||||
|
.any(|u| u.time.wrapping_sub(current_time) <= 0)
|
||||||
|
{
|
||||||
|
let update = pending_updates.remove(0);
|
||||||
|
memory.write(&mut store, 80, &update.data).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let duration = if let Some(update) = pending_updates.first() {
|
||||||
|
((update.time.wrapping_sub(current_time) as usize) * sample_rate + 999) / 1000
|
||||||
|
} else {
|
||||||
|
outer_buffer.len()
|
||||||
|
};
|
||||||
|
let step_size = (duration.max(64) * 2).min(outer_buffer.len());
|
||||||
|
|
||||||
|
let mut buffer = &mut outer_buffer[..step_size];
|
||||||
|
|
||||||
|
{
|
||||||
|
let mem = memory.data_mut(&mut store);
|
||||||
|
mem[64..68].copy_from_slice(¤t_time.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref mut resampler) = resampler {
|
||||||
|
while !buffer.is_empty() {
|
||||||
|
let copy_size = resampler.output_buffers[0]
|
||||||
|
.len()
|
||||||
|
.saturating_sub(resampler.output_index)
|
||||||
|
.min(buffer.len() / 2);
|
||||||
|
if copy_size == 0 {
|
||||||
|
resampler.input_buffers[0].clear();
|
||||||
|
resampler.input_buffers[1].clear();
|
||||||
|
for _ in 0..resampler.resampler.input_frames_next() {
|
||||||
|
resampler.input_buffers[0]
|
||||||
|
.push(snd.call(&mut store, (sample_index,)).unwrap_or(0.0));
|
||||||
|
resampler.input_buffers[1]
|
||||||
|
.push(snd.call(&mut store, (sample_index + 1,)).unwrap_or(0.0));
|
||||||
|
sample_index = sample_index.wrapping_add(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
resampler
|
||||||
|
.resampler
|
||||||
|
.process_into_buffer(
|
||||||
|
&resampler.input_buffers,
|
||||||
|
&mut resampler.output_buffers,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
resampler.output_index = 0;
|
||||||
|
} else {
|
||||||
|
for i in 0..copy_size {
|
||||||
|
buffer[i * 2] =
|
||||||
|
resampler.output_buffers[0][resampler.output_index + i];
|
||||||
|
buffer[i * 2 + 1] =
|
||||||
|
resampler.output_buffers[1][resampler.output_index + i];
|
||||||
|
}
|
||||||
|
resampler.output_index += copy_size;
|
||||||
|
buffer = &mut buffer[copy_size * 2..];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for v in buffer {
|
||||||
|
*v = snd.call(&mut store, (sample_index,)).unwrap_or(0.0);
|
||||||
|
sample_index = sample_index.wrapping_add(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outer_buffer = &mut outer_buffer[step_size..];
|
||||||
|
current_time =
|
||||||
|
current_time.wrapping_add((step_size * 500 / sample_rate).max(1) as i32);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
move |err| {
|
||||||
|
dbg!(err);
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Uw8Sound { stream, tx })
|
||||||
|
}
|
||||||
|
|||||||
20
test/frame_time.cwa
Normal file
20
test/frame_time.cwa
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
include "../examples/include/microw8-api.cwa"
|
||||||
|
|
||||||
|
global mut pos = 0;
|
||||||
|
global mut next = 0;
|
||||||
|
|
||||||
|
export fn upd() {
|
||||||
|
let lazy t = 32!32;
|
||||||
|
let lazy tick = t * 6 / 100;
|
||||||
|
let lazy rel = t - tick * 100 / 6;
|
||||||
|
|
||||||
|
setBackgroundColor(select(tick == next, 0, select(tick < next, 0x35, 0x55)));
|
||||||
|
setCursorPosition(pos % 13 * 3, pos / 13 % 30);
|
||||||
|
if rel < 10 {
|
||||||
|
printChar(32);
|
||||||
|
}
|
||||||
|
printInt(rel);
|
||||||
|
|
||||||
|
pos = pos + 1;
|
||||||
|
next = tick + 1;
|
||||||
|
}
|
||||||
7
test/log.cwa
Normal file
7
test/log.cwa
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
include "../examples/include/microw8-api.cwa"
|
||||||
|
|
||||||
|
export fn upd() {
|
||||||
|
printChar('\06f: ');
|
||||||
|
printInt(32!32 * 6 / 100);
|
||||||
|
printChar('\n\4');
|
||||||
|
}
|
||||||
@@ -3,13 +3,17 @@ class APU extends AudioWorkletProcessor {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.sampleIndex = 0;
|
this.sampleIndex = 0;
|
||||||
|
this.currentTime = 0;
|
||||||
|
this.isFirstMessage = true;
|
||||||
|
this.pendingUpdates = [];
|
||||||
this.port.onmessage = (ev) => {
|
this.port.onmessage = (ev) => {
|
||||||
if(this.memory) {
|
if(this.memory) {
|
||||||
if(isNaN(ev.data)) {
|
if(this.isFirstMessage)
|
||||||
U8(this.memory.buffer, 80, 32).set(U8(ev.data));
|
{
|
||||||
} else {
|
this.currentTime += (ev.data.t - this.currentTime) / 8;
|
||||||
this.startTime = ev.data;
|
this.isFirstMessage = false;
|
||||||
}
|
}
|
||||||
|
this.pendingUpdates.push(ev.data);
|
||||||
} else {
|
} else {
|
||||||
this.load(ev.data[0], ev.data[1]);
|
this.load(ev.data[0], ev.data[1]);
|
||||||
}
|
}
|
||||||
@@ -33,6 +37,16 @@ class APU extends AudioWorkletProcessor {
|
|||||||
importObject.env['reserved' + i] = () => { };
|
importObject.env['reserved' + i] = () => { };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let logLine = '';
|
||||||
|
importObject.env['logChar'] = (c) => {
|
||||||
|
if(c == 10) {
|
||||||
|
console.log(logLine);
|
||||||
|
logLine = '';
|
||||||
|
} else {
|
||||||
|
logLine += String.fromCharCode(c);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for (let i = 0; i < 16; ++i) {
|
for (let i = 0; i < 16; ++i) {
|
||||||
importObject.env['g_reserved' + i] = 0;
|
importObject.env['g_reserved' + i] = 0;
|
||||||
}
|
}
|
||||||
@@ -55,9 +69,13 @@ class APU extends AudioWorkletProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
process(inputs, outputs, parameters) {
|
process(inputs, outputs, parameters) {
|
||||||
if(this.snd && this.startTime) {
|
this.isFirstMessage = true;
|
||||||
|
if(this.snd) {
|
||||||
|
while(this.pendingUpdates.length > 0 && this.pendingUpdates[0].t <= this.currentTime) {
|
||||||
|
U8(this.memory.buffer, 80, 32).set(U8(this.pendingUpdates.shift().r));
|
||||||
|
}
|
||||||
let u32Mem = new Uint32Array(this.memory.buffer);
|
let u32Mem = new Uint32Array(this.memory.buffer);
|
||||||
u32Mem[16] = Date.now() - this.startTime;
|
u32Mem[16] = this.currentTime;
|
||||||
let channels = outputs[0];
|
let channels = outputs[0];
|
||||||
let index = this.sampleIndex;
|
let index = this.sampleIndex;
|
||||||
let numSamples = channels[0].length;
|
let numSamples = channels[0].length;
|
||||||
@@ -66,6 +84,7 @@ class APU extends AudioWorkletProcessor {
|
|||||||
channels[1][i] = this.snd(index++);
|
channels[1][i] = this.snd(index++);
|
||||||
}
|
}
|
||||||
this.sampleIndex = index & 0xffffffff;
|
this.sampleIndex = index & 0xffffffff;
|
||||||
|
this.currentTime += numSamples / 44.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="uw8">
|
<div id="uw8">
|
||||||
<a href="https://exoticorn.github.io/microw8">MicroW8</a> 0.1.2
|
<a href="https://exoticorn.github.io/microw8">MicroW8</a> 0.2.0
|
||||||
</div>
|
</div>
|
||||||
<div id="centered">
|
<div id="centered">
|
||||||
<canvas class="screen" id="screen" width="320" height="240">
|
<canvas class="screen" id="screen" width="320" height="240">
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
audioContext.close();
|
audioContext.close();
|
||||||
keepRunning = false;
|
keepRunning = false;
|
||||||
abortController.abort();
|
abortController.abort();
|
||||||
}
|
};
|
||||||
|
|
||||||
let cartridgeSize = data.byteLength;
|
let cartridgeSize = data.byteLength;
|
||||||
|
|
||||||
@@ -206,6 +206,16 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
importObject.env['reserved' + i] = () => { };
|
importObject.env['reserved' + i] = () => { };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let logLine = '';
|
||||||
|
importObject.env['logChar'] = (c) => {
|
||||||
|
if(c == 10) {
|
||||||
|
console.log(logLine);
|
||||||
|
logLine = '';
|
||||||
|
} else {
|
||||||
|
logLine += String.fromCharCode(c);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for (let i = 0; i < 16; ++i) {
|
for (let i = 0; i < 16; ++i) {
|
||||||
importObject.env['g_reserved' + i] = 0;
|
importObject.env['g_reserved' + i] = 0;
|
||||||
}
|
}
|
||||||
@@ -232,7 +242,6 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
let startTime = Date.now();
|
let startTime = Date.now();
|
||||||
|
|
||||||
const timePerFrame = 1000 / 60;
|
const timePerFrame = 1000 / 60;
|
||||||
let nextFrame = startTime;
|
|
||||||
|
|
||||||
audioNode.connect(audioContext.destination);
|
audioNode.connect(audioContext.destination);
|
||||||
|
|
||||||
@@ -244,12 +253,10 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
isPaused = false;
|
isPaused = false;
|
||||||
audioContext.resume();
|
audioContext.resume();
|
||||||
startTime += now - pauseTime;
|
startTime += now - pauseTime;
|
||||||
audioNode.port.postMessage(startTime);
|
|
||||||
} else {
|
} else {
|
||||||
isPaused = true;
|
isPaused = true;
|
||||||
audioContext.suspend();
|
audioContext.suspend();
|
||||||
pauseTime = now;
|
pauseTime = now;
|
||||||
audioNode.port.postMessage(0);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
window.addEventListener('focus', () => updateVisibility(true), { signal: abortController.signal });
|
window.addEventListener('focus', () => updateVisibility(true), { signal: abortController.signal });
|
||||||
@@ -263,6 +270,7 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
let restart = false;
|
let restart = false;
|
||||||
|
let thisFrame;
|
||||||
if (!isPaused) {
|
if (!isPaused) {
|
||||||
let gamepads = navigator.getGamepads();
|
let gamepads = navigator.getGamepads();
|
||||||
let gamepad = 0;
|
let gamepad = 0;
|
||||||
@@ -291,7 +299,8 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let u32Mem = U32(memory.buffer);
|
let u32Mem = U32(memory.buffer);
|
||||||
u32Mem[16] = Date.now() - startTime;
|
let time = Date.now() - startTime;
|
||||||
|
u32Mem[16] = time;
|
||||||
u32Mem[17] = pad | gamepad;
|
u32Mem[17] = pad | gamepad;
|
||||||
if(instance.exports.upd) {
|
if(instance.exports.upd) {
|
||||||
instance.exports.upd();
|
instance.exports.upd();
|
||||||
@@ -300,21 +309,26 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
|
|
||||||
let soundRegisters = new ArrayBuffer(32);
|
let soundRegisters = new ArrayBuffer(32);
|
||||||
U8(soundRegisters).set(U8(memory.buffer, 80, 32));
|
U8(soundRegisters).set(U8(memory.buffer, 80, 32));
|
||||||
audioNode.port.postMessage(soundRegisters, [soundRegisters]);
|
audioNode.port.postMessage({t: time, r: soundRegisters}, [soundRegisters]);
|
||||||
|
|
||||||
let palette = U32(memory.buffer, 0x13000, 1024);
|
let palette = U32(memory.buffer, 0x13000, 1024);
|
||||||
for (let i = 0; i < 320 * 240; ++i) {
|
for (let i = 0; i < 320 * 240; ++i) {
|
||||||
buffer[i] = palette[memU8[i + 120]] | 0xff000000;
|
buffer[i] = palette[memU8[i + 120]] | 0xff000000;
|
||||||
}
|
}
|
||||||
canvasCtx.putImageData(imageData, 0, 0);
|
canvasCtx.putImageData(imageData, 0, 0);
|
||||||
|
|
||||||
|
let timeOffset = ((time * 6) % 100 - 50) / 6;
|
||||||
|
thisFrame = startTime + time - timeOffset / 8;
|
||||||
|
} else {
|
||||||
|
thisFrame = Date.now();
|
||||||
}
|
}
|
||||||
let now = Date.now();
|
let now = Date.now();
|
||||||
nextFrame = Math.max(nextFrame + timePerFrame, now);
|
let nextFrame = Math.max(thisFrame + timePerFrame, now);
|
||||||
|
|
||||||
if (restart) {
|
if (restart) {
|
||||||
runModule(currentData);
|
runModule(currentData);
|
||||||
} else {
|
} else {
|
||||||
window.setTimeout(mainloop, Math.round(nextFrame - now))
|
window.setTimeout(mainloop, nextFrame - now)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
config.setMessage(cartridgeSize, err.toString());
|
config.setMessage(cartridgeSize, err.toString());
|
||||||
|
|||||||
Reference in New Issue
Block a user