mirror of
https://github.com/exoticorn/microw8.git
synced 2026-01-20 19:26:43 +01:00
Compare commits
65 Commits
browser-ru
...
v0.2.0-rc3
| Author | SHA1 | Date | |
|---|---|---|---|
| b2b990333e | |||
| d1556f7be8 | |||
| 9f548cd6f0 | |||
| 7cea4eebd3 | |||
| 3f67e92c5c | |||
| a2714f25e4 | |||
| 7e203d93e6 | |||
| e44c87d1f6 | |||
| 614b7cf358 | |||
| c42a484adb | |||
| 3a5f2bf865 | |||
| 2dee1b30a4 | |||
| 42f7887ab2 | |||
| 4c82f4ad02 | |||
| 4dd8c3b029 | |||
| 2bf8938183 | |||
| 491bf88ade | |||
| e05701300c | |||
| df0c169d54 | |||
| 61941bceeb | |||
| 8fa64519e4 | |||
| 7a52ce4e4c | |||
| 6f20d303c8 | |||
| a5edeb21d8 | |||
| 2839fe5be4 | |||
| 893158e136 | |||
| 7c5f43f152 | |||
| f32b0762b0 | |||
| 9ebb6b6d34 | |||
| 8a10b99eeb | |||
| 6c064a1dd8 | |||
| 37f12f5a2c | |||
| 8ad2885a55 | |||
| 1917057b81 | |||
| 82c1ddb867 | |||
| 8713aa8930 | |||
| 0f82e6e711 | |||
| 0ade24ebf6 | |||
| 29186c806f | |||
| b626d2609a | |||
| 39ead8220f | |||
| ce18a8a162 | |||
| a15e796489 | |||
| f178076b86 | |||
| 81adcf0198 | |||
| 780caf965a | |||
| 2033f9a172 | |||
| 0d514c7dd3 | |||
| a8eb3bda27 | |||
| 8b765a5742 | |||
| 7197c11586 | |||
| 99a423619e | |||
| 9063e872d3 | |||
| 85240599e8 | |||
| 35ec5fdb59 | |||
| a6a82ff5a1 | |||
| 973814a629 | |||
| 00d21b4745 | |||
| d11b46576a | |||
| add49a1f8b | |||
| 8815a8e02e | |||
| eb7c33d412 | |||
| 47ad3b4f30 | |||
| 0f668fb6e9 | |||
| f876f59e80 |
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
|
||||||
|
|||||||
581
Cargo.lock
generated
581
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
15
Cargo.toml
15
Cargo.toml
@@ -1,26 +1,29 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "uw8"
|
name = "uw8"
|
||||||
version = "0.1.1"
|
version = "0.2.0-rc3"
|
||||||
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"]
|
||||||
browser = ["warp", "tokio", "tokio-stream", "webbrowser"]
|
browser = ["warp", "tokio", "tokio-stream", "webbrowser"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasmtime = { version = "0.30", optional = true }
|
wasmtime = { version = "0.35.3", optional = true }
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
minifb = { version = "0.20", 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 = "196719b" }
|
curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "c22297e" }
|
||||||
wat = "1"
|
wat = "1"
|
||||||
uw8-tool = { path = "uw8-tool" }
|
uw8-tool = { path = "uw8-tool" }
|
||||||
same-file = "1"
|
same-file = "1"
|
||||||
warp = { version = "0.3.2", optional = true }
|
warp = { version = "0.3.2", optional = true }
|
||||||
tokio = { version = "1.17.0", features = ["sync", "rt"], optional = true }
|
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"
|
||||||
|
cpal = { version = "0.13.5", optional = true }
|
||||||
|
rubato = { version = "0.11.0", optional = true }
|
||||||
@@ -15,9 +15,9 @@ See [here](https://exoticorn.github.io/microw8/) for more information and docs.
|
|||||||
|
|
||||||
## Downloads
|
## Downloads
|
||||||
|
|
||||||
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-linux.tgz)
|
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-linux.tgz)
|
||||||
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-macos.tgz)
|
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-macos.tgz)
|
||||||
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-windows.zip)
|
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-windows.zip)
|
||||||
|
|
||||||
The download includes
|
The download includes
|
||||||
|
|
||||||
@@ -35,6 +35,7 @@ Runs <file> which can be a binary WebAssembly module, an `.uw8` cart, a wat (Web
|
|||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
|
-b, --browser : Run in browser instead of using native runtime
|
||||||
-t, --timeout FRAMES : Sets the timeout in frames (1/60s)
|
-t, --timeout FRAMES : Sets the timeout in frames (1/60s)
|
||||||
-w, --watch : Reloads the given file every time it changes on disk.
|
-w, --watch : Reloads the given file every time it changes on disk.
|
||||||
-p, --pack : Pack the file into an .uw8 cart before running it and print the resulting size.
|
-p, --pack : Pack the file into an .uw8 cart before running it and print the resulting size.
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import "env.memory" memory(4);
|
include "../include/microw8-api.cwa"
|
||||||
import "env.printString" fn printString(i32);
|
|
||||||
|
|
||||||
export fn upd() {
|
export fn upd() {
|
||||||
printString(0x20000);
|
printString(0x20000);
|
||||||
|
|||||||
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
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,11 +1,4 @@
|
|||||||
import "env.time" fn time() -> f32;
|
include "../include/microw8-api.cwa"
|
||||||
import "env.circle" fn circle(f32, f32, f32, i32);
|
|
||||||
import "env.cls" fn cls(i32);
|
|
||||||
import "env.randomSeed" fn seed(i32);
|
|
||||||
import "env.randomf" fn randomf() -> f32;
|
|
||||||
import "env.sin" fn sin(f32) -> f32;
|
|
||||||
import "env.cos" fn cos(f32) -> f32;
|
|
||||||
import "env.fmod" fn fmod(f32, f32) -> f32;
|
|
||||||
|
|
||||||
export fn upd() {
|
export fn upd() {
|
||||||
cls(0);
|
cls(0);
|
||||||
@@ -15,10 +8,10 @@ export fn upd() {
|
|||||||
let inline rocket = i #>> 9;
|
let inline rocket = i #>> 9;
|
||||||
let lazy local_time = fmod(time() + rocket as f32 / 5 as f32, 2 as f32);
|
let lazy local_time = fmod(time() + rocket as f32 / 5 as f32, 2 as f32);
|
||||||
let lazy rocket = rocket + nearest(time() - local_time) as i32 * 10;
|
let lazy rocket = rocket + nearest(time() - local_time) as i32 * 10;
|
||||||
seed(rocket);
|
randomSeed(rocket);
|
||||||
let inline x = randomf() * 645 as f32;
|
let inline x = randomf() * 645 as f32;
|
||||||
let y = randomf() * 133 as f32;
|
let y = randomf() * 133 as f32;
|
||||||
let lazy angle = { seed(i); randomf() } * 44 as f32;
|
let lazy angle = { randomSeed(i); randomf() } * 44 as f32;
|
||||||
let inline dx = sin(angle);
|
let inline dx = sin(angle);
|
||||||
let inline dy = cos(angle);
|
let inline dy = cos(angle);
|
||||||
let lazy dist = local_time * (randomf() * 44 as f32);
|
let lazy dist = local_time * (randomf() * 44 as f32);
|
||||||
|
|||||||
@@ -1,38 +1,30 @@
|
|||||||
import "env.memory" memory(4);
|
include "../include/microw8-api.cwa"
|
||||||
|
|
||||||
import "env.cls" fn cls(i32);
|
|
||||||
import "env.printString" fn printString(i32);
|
|
||||||
import "env.printChar" fn printChar(i32);
|
|
||||||
import "env.setCursorPosition" fn setCursor(i32, i32);
|
|
||||||
import "env.setTextColor" fn setTextColor(i32);
|
|
||||||
import "env.line" fn line(f32, f32, f32, f32, i32);
|
|
||||||
import "env.isButtonTriggered" fn triggered(i32) -> i32;
|
|
||||||
|
|
||||||
global mut mode: i32 = 0;
|
global mut mode: i32 = 0;
|
||||||
|
|
||||||
export fn upd() {
|
export fn upd() {
|
||||||
cls(0);
|
cls(0);
|
||||||
|
|
||||||
if triggered(4) {
|
if isButtonTriggered(BUTTON_A) {
|
||||||
mode = !mode;
|
mode = !mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTextColor(15);
|
setTextColor(15);
|
||||||
printString(mode * 0x20000);
|
printString(mode * USER_MEM);
|
||||||
|
|
||||||
let y: i32;
|
let y: i32;
|
||||||
loop y {
|
loop y {
|
||||||
line(0 as f32, (y * 9 + 39) as f32, (14+16*9) as f32, (y * 9 + 39) as f32, 1);
|
line(0 as f32, (y * 9 + 39) as f32, (14+16*9) as f32, (y * 9 + 39) as f32, 1);
|
||||||
line((y * 9 + 15) as f32, 24 as f32, (y * 9 + 15) as f32, (38+16*9) as f32, 1);
|
line((y * 9 + 15) as f32, 24 as f32, (y * 9 + 15) as f32, (38+16*9) as f32, 1);
|
||||||
setTextColor(15);
|
setTextColor(15);
|
||||||
setCursor(y * 9 + 16, 24);
|
setCursorPosition(y * 9 + 16, 24);
|
||||||
let lazy hexChar = select(y < 10, y + 48, y + 87);
|
let lazy hexChar = select(y < 10, y + 48, y + 87);
|
||||||
printChar(hexChar);
|
printChar(hexChar);
|
||||||
setCursor(0, y * 9 + 24+16);
|
setCursorPosition(0, y * 9 + 24+16);
|
||||||
printChar(hexChar);
|
printChar(hexChar);
|
||||||
let x = 0;
|
let x = 0;
|
||||||
loop x {
|
loop x {
|
||||||
setCursor(x * 9 + 16, y * 9 + 24+16);
|
setCursorPosition(x * 9 + 16, y * 9 + 24+16);
|
||||||
setTextColor(select(mode, x + y * 16, -9));
|
setTextColor(select(mode, x + y * 16, -9));
|
||||||
if y >= 2 | mode {
|
if y >= 2 | mode {
|
||||||
printChar(select(mode, 0xa4, x + y * 16));
|
printChar(select(mode, 0xa4, x + y * 16));
|
||||||
@@ -47,6 +39,6 @@ data 0 {
|
|||||||
"Default font: (press " i8(0xcc) " for palette)" i8(5, 0)
|
"Default font: (press " i8(0xcc) " for palette)" i8(5, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
data 0x20000 {
|
data USER_MEM {
|
||||||
"Default palette: (press " i8(0xcc) " for font)" i8(5, 0)
|
"Default palette: (press " i8(0xcc) " for font)" i8(5, 0)
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,4 @@
|
|||||||
import "env.memory" memory(4);
|
include "../include/microw8-api.cwa"
|
||||||
|
|
||||||
import "env.cls" fn cls(i32);
|
|
||||||
import "env.time" fn time() -> f32;
|
|
||||||
import "env.line" fn line(f32, f32, f32, f32, i32);
|
|
||||||
import "env.sin" fn sin(f32) -> f32;
|
|
||||||
import "env.cos" fn cos(f32) -> f32;
|
|
||||||
|
|
||||||
export fn upd() {
|
export fn upd() {
|
||||||
cls(0);
|
cls(0);
|
||||||
|
|||||||
38
examples/curlywas/simple_music.cwa
Normal file
38
examples/curlywas/simple_music.cwa
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
include "../include/microw8-api.cwa"
|
||||||
|
|
||||||
|
global mut frame = 0;
|
||||||
|
|
||||||
|
export fn upd() {
|
||||||
|
if frame % 16 == 0 {
|
||||||
|
let ch: i32;
|
||||||
|
loop channels {
|
||||||
|
playNote(ch, (ch * 32 + (frame / 16) % 32)?0x20000);
|
||||||
|
branch_if ch := (ch + 1) % 4: channels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frame = frame + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
data 0x20000 {
|
||||||
|
i8(
|
||||||
|
0x4e, 0x0, 0x0, 0x4c, 0x49, 0x0, 0x45, 0x47,
|
||||||
|
0x49, 0x47, 0x45, 0x44, 0x42, 0x0, 0x3d, 0x41,
|
||||||
|
0x44, 0x0, 0x0, 0x47, 0x49, 0x47, 0x45, 0x41,
|
||||||
|
0x44, 0x0, 0x0, 0x0, 0x42, 0x0, 0x0, 0x0,
|
||||||
|
|
||||||
|
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0x38,
|
||||||
|
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0x38,
|
||||||
|
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0x38,
|
||||||
|
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0x38,
|
||||||
|
|
||||||
|
0x2a, 0x0, 0x0, 0x0, 0x2d, 0x0, 0x0, 0x0,
|
||||||
|
0x2c, 0x0, 0x28, 0x0, 0x2a, 0x0, 0x0, 0x0,
|
||||||
|
0x25, 0x0, 0x0, 0x0, 0x29, 0x0, 0x0, 0x0,
|
||||||
|
0x2c, 0x0, 0x2d, 0x0, 0x2a, 0x0, 0x25, 0x0,
|
||||||
|
|
||||||
|
0x0, 0x0, 0x31, 0x0, 0x34, 0x0, 0x0, 0x36,
|
||||||
|
0x38, 0x39, 0x38, 0x34, 0x36, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x3d, 0x3b, 0x39, 0x38, 0x0, 0x0, 0x0,
|
||||||
|
0x0, 0x39, 0x38, 0x39, 0x38, 0x0, 0x36, 0x0
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,12 +1,4 @@
|
|||||||
import "env.memory" memory(4);
|
include "../include/microw8-api.cwa"
|
||||||
|
|
||||||
import "env.rectangle" fn rect(f32, f32, f32, f32, i32);
|
|
||||||
import "env.circle" fn circle(f32, f32, f32, i32);
|
|
||||||
import "env.isButtonPressed" fn btn(i32) -> i32;
|
|
||||||
import "env.random" fn random() -> i32;
|
|
||||||
import "env.randomSeed" fn randomSeed(i32);
|
|
||||||
import "env.cls" fn cls(i32);
|
|
||||||
import "env.printInt" fn printInt(i32);
|
|
||||||
|
|
||||||
global mut pz: i32 = 4;
|
global mut pz: i32 = 4;
|
||||||
global mut px: f32 = 2.0;
|
global mut px: f32 = 2.0;
|
||||||
@@ -19,7 +11,7 @@ export fn upd() {
|
|||||||
let inline zero = 0.0;
|
let inline zero = 0.0;
|
||||||
|
|
||||||
let lazy control_speed = 0.03125;
|
let lazy control_speed = 0.03125;
|
||||||
s = s + 0.1875 - (f + control_speed) * btn(4 <| cls(4)) as f32;
|
s = s + 0.1875 - (f + control_speed) * isButtonPressed(4 <| cls(4)) as f32;
|
||||||
f = f * 0.5625;
|
f = f * 0.5625;
|
||||||
|
|
||||||
printInt(pz);
|
printInt(pz);
|
||||||
@@ -33,8 +25,8 @@ export fn upd() {
|
|||||||
|
|
||||||
let inline c = (z & 1) * -2;
|
let inline c = (z & 1) * -2;
|
||||||
let inline yf = y as f32;
|
let inline yf = y as f32;
|
||||||
rect(rx, yf, rw, yf / 6 as f32, c + 1);
|
rectangle(rx, yf, rw, yf / 6 as f32, c + 1);
|
||||||
rect(rx, yf, rw, 1 as f32, c - 4);
|
rectangle(rx, yf, rw, 1 as f32, c - 4);
|
||||||
|
|
||||||
if y == 180 & py > zero {
|
if y == 180 & py > zero {
|
||||||
if x > w | x < zero {
|
if x > w | x < zero {
|
||||||
@@ -51,7 +43,7 @@ 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);
|
||||||
|
|
||||||
px = px + (btn(3) - btn(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;
|
||||||
}
|
}
|
||||||
|
|||||||
38
examples/curlywas/steadyon.cwa
Normal file
38
examples/curlywas/steadyon.cwa
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// Steady On Tim, It's Only A Budget Game
|
||||||
|
// by Gasman / Hooy-Program
|
||||||
|
// ported to MicroW8 by exoticorn/icebird
|
||||||
|
|
||||||
|
include "../include/microw8-api.cwa"
|
||||||
|
|
||||||
|
fn melody(t: i32, T: i32) -> i32 {
|
||||||
|
let inline riff_pos = abs(((T&31) - 16) as f32) as i32;
|
||||||
|
let lazy shift = ((1-((T>>5)&3))%2-1) as f32 / 6 as f32;
|
||||||
|
|
||||||
|
let inline note_count = 5 - (T >= 512);
|
||||||
|
let inline octave = (riff_pos/5) as f32;
|
||||||
|
let inline riff_note = 5514 >> (riff_pos % note_count * 4) & 15;
|
||||||
|
let inline melody_freq = pow(2 as f32, shift + octave - (riff_note as f32 / 12 as f32));
|
||||||
|
let inline melody = (t as f32 * melody_freq) as i32 & 128;
|
||||||
|
|
||||||
|
let inline arp_note = ((0x85>>((t>>12)%3*4)) & 15) - 1;
|
||||||
|
let inline arp_freq = pow(2 as f32, shift + (arp_note as f32 / 12 as f32));
|
||||||
|
let inline arp_vol = (T >= 256) * (12-T%12);
|
||||||
|
let inline arpeggio = ((t as f32 * arp_freq) as i32 & 128) * arp_vol / 12;
|
||||||
|
|
||||||
|
melody + arpeggio
|
||||||
|
}
|
||||||
|
|
||||||
|
export fn snd(t: i32) -> f32 {
|
||||||
|
let lazy T = t/10000;
|
||||||
|
|
||||||
|
let inline mel_arp = melody(t, T)/3 + melody(t, T-3)/5;
|
||||||
|
|
||||||
|
let inline bass_vol = (T >= 128) & (197 >> (T % 8));
|
||||||
|
let inline bass_freq = pow(2 as f32, (((T & 4) * ((T & 7) - 1)) as f32 / 24 as f32 - 5 as f32));
|
||||||
|
let inline bass = ((t as f32 * bass_freq) as i32 & 63) * bass_vol;
|
||||||
|
|
||||||
|
let inline snare_ish = (random() & 31) * (8 - (T + 4) % 8) / 8;
|
||||||
|
|
||||||
|
let inline sample = mel_arp + bass + snare_ish;
|
||||||
|
sample as f32 / 255 as f32
|
||||||
|
}
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
import "env.memory" memory(4);
|
include "../include/microw8-api.cwa"
|
||||||
import "env.sin" fn sin(f32) -> f32;
|
|
||||||
import "env.time" fn time() -> f32;
|
|
||||||
import "env.setPixel" fn setPixel(i32, i32, i32);
|
|
||||||
|
|
||||||
export fn upd() {
|
export fn upd() {
|
||||||
let x: i32;
|
let x: i32;
|
||||||
|
|||||||
64
examples/curlywas/tim_ges.cwa
Normal file
64
examples/curlywas/tim_ges.cwa
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
// Steady On Tim, It's Only A Budget Game
|
||||||
|
// original bytebeat by Gasman / Hooy-Program
|
||||||
|
// ported to MicroW8/GES by exoticorn/icebird
|
||||||
|
|
||||||
|
import "env.memory" memory(4);
|
||||||
|
|
||||||
|
fn melody(ch: i32, t: i32, T: i32) {
|
||||||
|
let lazy riff_pos = abs(((T&31) - 16) as f32) as i32;
|
||||||
|
let lazy shift = ((1-((T>>5)&3))%2-1) * 2;
|
||||||
|
|
||||||
|
let inline note_count = 5 - (T >= 512);
|
||||||
|
let inline octave = (riff_pos/5) * 12;
|
||||||
|
let inline riff_note = 5514 >> (riff_pos % note_count * 4) & 15;
|
||||||
|
let inline melody_note = shift + octave - riff_note;
|
||||||
|
|
||||||
|
ch?1 = 230 - riff_pos * 14;
|
||||||
|
ch?3 = melody_note + 64;
|
||||||
|
|
||||||
|
let inline arp_note = shift + ((0x85>>((t/2)%3*4)) & 15) - 1;
|
||||||
|
80?3 = arp_note + 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
export fn upd() {
|
||||||
|
let lazy t = 32!32 / (1000/60);
|
||||||
|
let lazy T = t / 7;
|
||||||
|
melody(98, t, T - 3);
|
||||||
|
melody(92, t, T);
|
||||||
|
|
||||||
|
80?0 = ((T >= 256) & (T/12+(T-3)/12)) * 2 | 0x48; // arp trigger
|
||||||
|
|
||||||
|
if T >= 128 {
|
||||||
|
let inline bass_step = T % 8;
|
||||||
|
86?3 = if bass_step / 2 == 2 {
|
||||||
|
86?0 = 0xd6;
|
||||||
|
81
|
||||||
|
} else {
|
||||||
|
86?0 = ((197 >> bass_step) & 1) | 0x48;
|
||||||
|
((T & 4) * ((T & 7) - 1)) / 2 + 28
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data 80 {
|
||||||
|
i8(
|
||||||
|
0, 0x90, 0, 0, 0, 0x90,
|
||||||
|
0, 0x4c, 0, 0, 0, 0x4c,
|
||||||
|
0x19, 0, 0, 0, 0, 0x4c,
|
||||||
|
0x19, 0, 0, 0, 0, 0x4c,
|
||||||
|
0xfa, 0x84,
|
||||||
|
0xc1, 0xc1, 0, 107, 0, 0x4c
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
include "../../platform/src/ges.cwa"
|
||||||
|
|
||||||
|
import "env.pow" fn pow(f32, f32) -> f32;
|
||||||
|
import "env.exp" fn exp(f32) -> f32;
|
||||||
|
import "env.sin" fn sin(f32) -> f32;
|
||||||
|
|
||||||
|
export fn snd(t: i32) -> f32 {
|
||||||
|
gesSnd(t)
|
||||||
|
}
|
||||||
|
*/
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
import "env.memory" memory(2);
|
include "../include/microw8-api.cwa"
|
||||||
|
|
||||||
import "env.fmod" fn fmod(f32, f32) -> f32;
|
|
||||||
import "env.time" fn time() -> f32;
|
|
||||||
|
|
||||||
export fn upd() {
|
export fn upd() {
|
||||||
let i: i32;
|
let i: i32;
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
import "env.memory" memory(4);
|
include "../include/microw8-api.cwa"
|
||||||
|
|
||||||
import "env.atan2" fn atan2(f32, f32) -> f32;
|
|
||||||
import "env.time" fn time() -> f32;
|
|
||||||
|
|
||||||
export fn upd() {
|
export fn upd() {
|
||||||
let i: i32;
|
let i: i32;
|
||||||
@@ -12,7 +9,7 @@ export fn upd() {
|
|||||||
let inline d = 40000 as f32 / sqrt(x * x + y * y);
|
let inline d = 40000 as f32 / sqrt(x * x + y * y);
|
||||||
let inline u = atan2(x, y) * (512.0 / 3.141);
|
let inline u = atan2(x, y) * (512.0 / 3.141);
|
||||||
let inline c = ((i32.trunc_sat_f32_s(d + t * 2 as f32) ^ i32.trunc_sat_f32_s(u + t)) & 255) >> 4;
|
let inline c = ((i32.trunc_sat_f32_s(d + t * 2 as f32) ^ i32.trunc_sat_f32_s(u + t)) & 255) >> 4;
|
||||||
i?120 = c;
|
i?FRAMEBUFFER = c;
|
||||||
|
|
||||||
branch_if (i := i + 1) < 320*240: pixels;
|
branch_if (i := i + 1) < 320*240: pixels;
|
||||||
}
|
}
|
||||||
|
|||||||
51
examples/include/microw8-api.cwa
Normal file
51
examples/include/microw8-api.cwa
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// MicroW8 APIs, to be `include`d in CurlyWas sources
|
||||||
|
import "env.memory" memory(4);
|
||||||
|
|
||||||
|
import "env.sin" fn sin(f32) -> f32;
|
||||||
|
import "env.cos" fn cos(f32) -> f32;
|
||||||
|
import "env.tan" fn tan(f32) -> f32;
|
||||||
|
import "env.asin" fn asin(f32) -> f32;
|
||||||
|
import "env.acos" fn acos(f32) -> f32;
|
||||||
|
import "env.atan" fn atan(f32) -> f32;
|
||||||
|
import "env.atan2" fn atan2(f32, f32) -> f32;
|
||||||
|
import "env.pow" fn pow(f32, f32) -> f32;
|
||||||
|
import "env.log" fn log(f32) -> f32;
|
||||||
|
import "env.fmod" fn fmod(f32, f32) -> f32;
|
||||||
|
import "env.random" fn random() -> i32;
|
||||||
|
import "env.randomf" fn randomf() -> f32;
|
||||||
|
import "env.randomSeed" fn randomSeed(i32);
|
||||||
|
import "env.cls" fn cls(i32);
|
||||||
|
import "env.setPixel" fn setPixel(i32, i32, i32);
|
||||||
|
import "env.getPixel" fn getPixel(i32, i32) -> i32;
|
||||||
|
import "env.hline" fn hline(i32, i32, i32, i32);
|
||||||
|
import "env.rectangle" fn rectangle(f32, f32, f32, f32, i32);
|
||||||
|
import "env.circle" fn circle(f32, f32, f32, i32);
|
||||||
|
import "env.line" fn line(f32, f32, f32, f32, i32);
|
||||||
|
import "env.time" fn time() -> f32;
|
||||||
|
import "env.isButtonPressed" fn isButtonPressed(i32) -> i32;
|
||||||
|
import "env.isButtonTriggered" fn isButtonTriggered(i32) -> i32;
|
||||||
|
import "env.printChar" fn printChar(i32);
|
||||||
|
import "env.printString" fn printString(i32);
|
||||||
|
import "env.printInt" fn printInt(i32);
|
||||||
|
import "env.setTextColor" fn setTextColor(i32);
|
||||||
|
import "env.setBackgroundColor" fn setBackgroundColor(i32);
|
||||||
|
import "env.setCursorPosition" fn setCursorPosition(i32, i32);
|
||||||
|
import "env.rectangle_outline" fn rectangle_outline(f32, f32, f32, f32, i32);
|
||||||
|
import "env.circle_outline" fn circle_outline(f32, f32, f32, i32);
|
||||||
|
import "env.exp" fn exp(f32) -> f32;
|
||||||
|
import "env.playNote" fn playNote(i32, i32);
|
||||||
|
|
||||||
|
const TIME_MS = 0x40;
|
||||||
|
const GAMEPAD = 0x44;
|
||||||
|
const FRAMEBUFFER = 0x78;
|
||||||
|
const PALETTE = 0x13000;
|
||||||
|
const FONT = 0x13400;
|
||||||
|
const USER_MEM = 0x14000;
|
||||||
|
const BUTTON_UP = 0x0;
|
||||||
|
const BUTTON_DOWN = 0x1;
|
||||||
|
const BUTTON_LEFT = 0x2;
|
||||||
|
const BUTTON_RIGHT = 0x3;
|
||||||
|
const BUTTON_A = 0x4;
|
||||||
|
const BUTTON_B = 0x5;
|
||||||
|
const BUTTON_X = 0x6;
|
||||||
|
const BUTTON_Y = 0x7;
|
||||||
53
examples/include/microw8-api.wat
Normal file
53
examples/include/microw8-api.wat
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
;; MicroW8 APIs, in WAT (Wasm Text) format
|
||||||
|
(import "env" "memory" (memory 4))
|
||||||
|
|
||||||
|
(import "env" "sin" (func $sin (param f32) (result f32)))
|
||||||
|
(import "env" "cos" (func $cos (param f32) (result f32)))
|
||||||
|
(import "env" "tan" (func $tan (param f32) (result f32)))
|
||||||
|
(import "env" "asin" (func $asin (param f32) (result f32)))
|
||||||
|
(import "env" "acos" (func $acos (param f32) (result f32)))
|
||||||
|
(import "env" "atan" (func $atan (param f32) (result f32)))
|
||||||
|
(import "env" "atan2" (func $atan2 (param f32) (param f32) (result f32)))
|
||||||
|
(import "env" "pow" (func $pow (param f32) (param f32) (result f32)))
|
||||||
|
(import "env" "log" (func $log (param f32) (result f32)))
|
||||||
|
(import "env" "fmod" (func $fmod (param f32) (param f32) (result f32)))
|
||||||
|
(import "env" "random" (func $random (result i32)))
|
||||||
|
(import "env" "randomf" (func $randomf (result f32)))
|
||||||
|
(import "env" "randomSeed" (func $randomSeed (param i32)))
|
||||||
|
(import "env" "cls" (func $cls (param i32)))
|
||||||
|
(import "env" "setPixel" (func $setPixel (param i32) (param i32) (param i32)))
|
||||||
|
(import "env" "getPixel" (func $getPixel (param i32) (param i32) (result i32)))
|
||||||
|
(import "env" "hline" (func $hline (param i32) (param i32) (param i32) (param i32)))
|
||||||
|
(import "env" "rectangle" (func $rectangle (param f32) (param f32) (param f32) (param f32) (param i32)))
|
||||||
|
(import "env" "circle" (func $circle (param f32) (param f32) (param f32) (param i32)))
|
||||||
|
(import "env" "line" (func $line (param f32) (param f32) (param f32) (param f32) (param i32)))
|
||||||
|
(import "env" "time" (func $time (result f32)))
|
||||||
|
(import "env" "isButtonPressed" (func $isButtonPressed (param i32) (result i32)))
|
||||||
|
(import "env" "isButtonTriggered" (func $isButtonTriggered (param i32) (result i32)))
|
||||||
|
(import "env" "printChar" (func $printChar (param i32)))
|
||||||
|
(import "env" "printString" (func $printString (param i32)))
|
||||||
|
(import "env" "printInt" (func $printInt (param i32)))
|
||||||
|
(import "env" "setTextColor" (func $setTextColor (param i32)))
|
||||||
|
(import "env" "setBackgroundColor" (func $setBackgroundColor (param i32)))
|
||||||
|
(import "env" "setCursorPosition" (func $setCursorPosition (param i32) (param i32)))
|
||||||
|
(import "env" "rectangle_outline" (func $rectangle_outline (param f32) (param f32) (param f32) (param f32) (param i32)))
|
||||||
|
(import "env" "circle_outline" (func $circle_outline (param f32) (param f32) (param f32) (param i32)))
|
||||||
|
(import "env" "exp" (func $exp (param f32) (result f32)))
|
||||||
|
(import "env" "playNote" (func $playNote (param i32) (param i32)))
|
||||||
|
|
||||||
|
;; to use defines, include this file with a preprocessor
|
||||||
|
;; like gpp (https://logological.org/gpp).
|
||||||
|
#define TIME_MS 0x40;
|
||||||
|
#define GAMEPAD 0x44;
|
||||||
|
#define FRAMEBUFFER 0x78;
|
||||||
|
#define PALETTE 0x13000;
|
||||||
|
#define FONT 0x13400;
|
||||||
|
#define USER_MEM 0x14000;
|
||||||
|
#define BUTTON_UP 0x0;
|
||||||
|
#define BUTTON_DOWN 0x1;
|
||||||
|
#define BUTTON_LEFT 0x2;
|
||||||
|
#define BUTTON_RIGHT 0x3;
|
||||||
|
#define BUTTON_A 0x4;
|
||||||
|
#define BUTTON_B 0x5;
|
||||||
|
#define BUTTON_X 0x6;
|
||||||
|
#define BUTTON_Y 0x7;
|
||||||
127
platform/Cargo.lock
generated
127
platform/Cargo.lock
generated
@@ -79,9 +79,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chumsky"
|
name = "chumsky"
|
||||||
version = "0.5.0"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2d3efff85e8572b1c3fa0127706af58c4fff8458f8d9436d54b1e97573c7a3f"
|
checksum = "8d02796e4586c6c41aeb68eae9bfb4558a522c35f1430c14b40136c3706e09e4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.3.8",
|
"ahash 0.3.8",
|
||||||
]
|
]
|
||||||
@@ -146,14 +146,14 @@ 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=196719b#196719b35ef377cb7e001554b27ac5de013dcf2b"
|
source = "git+https://github.com/exoticorn/curlywas.git?rev=c22297e#c22297ea82977ac06373d629fb273a795322d788"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"ariadne",
|
"ariadne",
|
||||||
"chumsky",
|
"chumsky",
|
||||||
"pico-args",
|
"pico-args",
|
||||||
"wasm-encoder",
|
"wasm-encoder 0.10.0",
|
||||||
"wasmparser",
|
"wasmparser 0.83.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -197,6 +197,21 @@ dependencies = [
|
|||||||
"ahash 0.7.6",
|
"ahash 0.7.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "id-arena"
|
||||||
|
version = "2.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@@ -227,6 +242,15 @@ dependencies = [
|
|||||||
"rgb",
|
"rgb",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
@@ -286,6 +310,24 @@ version = "0.5.19"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.36"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rgb"
|
name = "rgb"
|
||||||
version = "0.8.31"
|
version = "0.8.31"
|
||||||
@@ -304,6 +346,17 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.86"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.1.43"
|
version = "0.1.43"
|
||||||
@@ -323,6 +376,18 @@ dependencies = [
|
|||||||
"crunchy",
|
"crunchy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "upkr"
|
name = "upkr"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -342,8 +407,9 @@ dependencies = [
|
|||||||
"pbr",
|
"pbr",
|
||||||
"pico-args",
|
"pico-args",
|
||||||
"upkr",
|
"upkr",
|
||||||
"wasm-encoder",
|
"walrus",
|
||||||
"wasmparser",
|
"wasm-encoder 0.8.0",
|
||||||
|
"wasmparser 0.81.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -352,6 +418,32 @@ version = "0.9.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walrus"
|
||||||
|
version = "0.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4eb08e48cde54c05f363d984bb54ce374f49e242def9468d2e1b6c2372d291f8"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"id-arena",
|
||||||
|
"leb128",
|
||||||
|
"log",
|
||||||
|
"walrus-macro",
|
||||||
|
"wasmparser 0.77.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walrus-macro"
|
||||||
|
version = "0.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0a6e5bd22c71e77d60140b0bd5be56155a37e5bd14e24f5f87298040d0cc40d7"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.10.2+wasi-snapshot-preview1"
|
version = "0.10.2+wasi-snapshot-preview1"
|
||||||
@@ -367,12 +459,33 @@ dependencies = [
|
|||||||
"leb128",
|
"leb128",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-encoder"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aa9d9bf45fc46f71c407837c9b30b1e874197f2dc357588430b21e5017d290ab"
|
||||||
|
dependencies = [
|
||||||
|
"leb128",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmparser"
|
||||||
|
version = "0.77.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b35c86d22e720a07d954ebbed772d01180501afe7d03d464f413bb5f8914a8d6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasmparser"
|
name = "wasmparser"
|
||||||
version = "0.81.0"
|
version = "0.81.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "98930446519f63d00a836efdc22f67766ceae8dbcc1571379f2bcabc6b2b9abc"
|
checksum = "98930446519f63d00a836efdc22f67766ceae8dbcc1571379f2bcabc6b2b9abc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasmparser"
|
||||||
|
version = "0.83.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|||||||
@@ -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="196719b" }
|
curlywas = { git="https://github.com/exoticorn/curlywas.git", rev="c22297e" }
|
||||||
uw8-tool = { path="../uw8-tool" }
|
uw8-tool = { path="../uw8-tool" }
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
lodepng = "3.4"
|
lodepng = "3.4"
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
222
platform/src/ges.cwa
Normal file
222
platform/src/ges.cwa
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
const GesChannelState.Trigger = 0;
|
||||||
|
const GesChannelState.EnvState = 1;
|
||||||
|
const GesChannelState.EnvVol = 2;
|
||||||
|
const GesChannelState.Phase = 4;
|
||||||
|
const GesChannelState.Size = 8;
|
||||||
|
|
||||||
|
const GesState.Filter = GesChannelState.Size * 4;
|
||||||
|
const GesState.Size = GesState.Filter + 8*4;
|
||||||
|
|
||||||
|
const GesStateOffset = 32;
|
||||||
|
const GesBufferOffset = 32 + GesState.Size;
|
||||||
|
|
||||||
|
export fn gesSnd(t: i32) -> f32 {
|
||||||
|
let baseAddr = 0!0x12c78;
|
||||||
|
if !(t & 127) {
|
||||||
|
let i: i32;
|
||||||
|
loop clearLoop {
|
||||||
|
(baseAddr + i)!GesBufferOffset = 0;
|
||||||
|
branch_if (i := i + 4) < 128*4: clearLoop;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ch: i32;
|
||||||
|
loop channelLoop {
|
||||||
|
let lazy channelState = baseAddr + GesStateOffset + ch * GesChannelState.Size;
|
||||||
|
let lazy channelReg = baseAddr + ch * 6;
|
||||||
|
let envState = channelState?GesChannelState.EnvState;
|
||||||
|
let envVol = i32.load16_u(channelState, GesChannelState.EnvVol);
|
||||||
|
|
||||||
|
let lazy oldTrigger = channelState?GesChannelState.Trigger;
|
||||||
|
let lazy ctrl = channelReg?0;
|
||||||
|
if (oldTrigger ^ ctrl) & (ctrl | 2) & 3 {
|
||||||
|
envState = 1;
|
||||||
|
envVol = 0;
|
||||||
|
}
|
||||||
|
channelState?GesChannelState.Trigger = ctrl;
|
||||||
|
|
||||||
|
if envState {
|
||||||
|
let lazy attack = channelReg?4 & 15;
|
||||||
|
envVol = envVol + 12 * pow(1.675, (15 - attack) as f32) as i32;
|
||||||
|
if envVol >= 65535 {
|
||||||
|
envVol = 65535;
|
||||||
|
envState = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let inline decay = (channelReg - (ctrl & 1))?5 >> 4;
|
||||||
|
let inline dec = 8 * pow(1.5625, (15 - decay) as f32) as i32;
|
||||||
|
envVol = envVol - ((dec * (envVol + 8192)) >> 16);
|
||||||
|
let inline sustain = (channelReg?5 & 15) << 12;
|
||||||
|
let lazy targetVol = (ctrl & 1) * sustain;
|
||||||
|
if envVol < targetVol {
|
||||||
|
envVol = targetVol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
channelState?GesChannelState.EnvState = envState;
|
||||||
|
|
||||||
|
i32.store16(envVol, channelState, GesChannelState.EnvVol);
|
||||||
|
|
||||||
|
let inline note = i32.load16_u(channelReg, 2);
|
||||||
|
let lazy freq = 440 as f32 * pow(2.0, (note - 69*256) as f32 / (12*256) as f32);
|
||||||
|
let phaseInc = (freq * (65536.0 / 44100.0)) as i32;
|
||||||
|
|
||||||
|
let phase = channelState!GesChannelState.Phase;
|
||||||
|
|
||||||
|
let inline pulseWidth = channelReg?1;
|
||||||
|
let phaseShift = (pulseWidth - 128) * 255;
|
||||||
|
let invPhaseInc = 1 as f32 / phaseInc as f32;
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
let wave = ctrl >> 6;
|
||||||
|
if wave < 2 {
|
||||||
|
if wave {
|
||||||
|
let pulsePhase1 = pulseWidth << 23;
|
||||||
|
let pulsePhase2 = (511 - pulseWidth) << 23;
|
||||||
|
loop sawLoop {
|
||||||
|
let p = (phase ^ 32768) << 16;
|
||||||
|
let saw = (p >> 16) - polyBlep(phase, invPhaseInc, -32767);
|
||||||
|
let saw2 = select(p #>= pulsePhase1 & p #< pulsePhase2, -saw, saw);
|
||||||
|
let saw2 = saw2 -
|
||||||
|
polyBlep((p - pulsePhase1) >> 16, invPhaseInc, -saw) -
|
||||||
|
polyBlep((p - pulsePhase2) >> 16, invPhaseInc, saw);
|
||||||
|
(baseAddr + i)!(GesBufferOffset + 128*4) = saw2;
|
||||||
|
phase = phase + phaseInc;
|
||||||
|
branch_if (i := i + 4) < 64*4: sawLoop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
let pulsePhase = 32768 + pulseWidth * 128;
|
||||||
|
loop rectLoop {
|
||||||
|
(baseAddr + i)!(GesBufferOffset + 128*4) = select((phase & 65535) < pulsePhase, -32768, 32767) -
|
||||||
|
polyBlep(phase, invPhaseInc, -32767) -
|
||||||
|
polyBlep(phase - pulsePhase, invPhaseInc, 32767);
|
||||||
|
phase = phase + phaseInc;
|
||||||
|
branch_if (i := i + 4) < 64*4: rectLoop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if wave == 2 {
|
||||||
|
let scale = pulseWidth + 256;
|
||||||
|
loop triLoop {
|
||||||
|
let s = phase << 16;
|
||||||
|
s = (s ^ (s >> 31));
|
||||||
|
s = (s >> 8) * scale;
|
||||||
|
s = (s ^ (s >> 31));
|
||||||
|
(baseAddr + i)!(GesBufferOffset + 128*4) = (s >> 15) - 32768;
|
||||||
|
phase = phase + phaseInc;
|
||||||
|
branch_if (i := i + 4) < 64*4: triLoop;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loop noiseLoop {
|
||||||
|
let s = phase >> 12;
|
||||||
|
let inline pulse = ((phase >> 8) & 255) >= pulseWidth;
|
||||||
|
s = s * 0x6746ba73;
|
||||||
|
s = s ^ (s >> 15) * pulse;
|
||||||
|
(baseAddr + i)!(GesBufferOffset + 128*4) = (s * 0x835776c7) >> 16;
|
||||||
|
phase = phase + phaseInc;
|
||||||
|
branch_if (i := i + 4) < 64*4: noiseLoop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
channelState!GesChannelState.Phase = phase;
|
||||||
|
|
||||||
|
if ctrl & 32 {
|
||||||
|
let lazy modSrc = (ch - 1) & 3;
|
||||||
|
let inline channelState = baseAddr + GesStateOffset + modSrc * GesChannelState.Size;
|
||||||
|
let inline channelReg = baseAddr + modSrc * 6;
|
||||||
|
|
||||||
|
let inline note = i32.load16_u(channelReg, 2);
|
||||||
|
let inline freq = 440 as f32 * pow(2.0, (note - 69*256) as f32 / (12*256) as f32);
|
||||||
|
let phaseInc = (freq * (65536.0 / 44100.0)) as i32;
|
||||||
|
|
||||||
|
let phase = channelState!GesChannelState.Phase;
|
||||||
|
if modSrc > ch {
|
||||||
|
phase = phase - (phaseInc << 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
loop ringLoop {
|
||||||
|
let s = phase << 16;
|
||||||
|
s = (s ^ (s >> 31));
|
||||||
|
(baseAddr + i)!(GesBufferOffset + 128*4) = ((baseAddr + i)!(GesBufferOffset + 128*4) * ((s >> 15) - 32768)) >> 15;
|
||||||
|
phase = phase + phaseInc;
|
||||||
|
branch_if (i := i + 4) < 64*4: ringLoop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let channelVol = ((baseAddr + (ch >> 1))?24 >> ((ch & 1) * 4)) & 15;
|
||||||
|
envVol = envVol * channelVol / 15;
|
||||||
|
|
||||||
|
let leftVol = (select(ctrl & 16, 0x3d5b, 0x6a79) >> (ch * 4)) & 15;
|
||||||
|
let rightVol = 16 - leftVol;
|
||||||
|
|
||||||
|
let lazy filter = (ctrl >> 2) & 3;
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
if filter < 2 {
|
||||||
|
if filter {
|
||||||
|
let f = (4096 as f32 - min(4096 as f32, 4096 as f32 * exp(freq * (-8.0 * 3.141 / 44100.0)))) as i32;
|
||||||
|
let low = (baseAddr + ch * 8)!(GesStateOffset + GesState.Filter);
|
||||||
|
loop filterLoop {
|
||||||
|
let in = ((baseAddr + i)!(GesBufferOffset + 128*4) * envVol) >> 18;
|
||||||
|
low = low + (((in - low) * f) >> 12);
|
||||||
|
(baseAddr + i * 2)!GesBufferOffset = (baseAddr + i * 2)!GesBufferOffset + ((low * leftVol) >> 4);
|
||||||
|
(baseAddr + i * 2)!(GesBufferOffset + 4) = (baseAddr + i * 2)!(GesBufferOffset + 4) + ((low * rightVol) >> 4);
|
||||||
|
branch_if (i := i + 4) < 64*4: filterLoop;
|
||||||
|
(baseAddr + ch * 8)!(GesStateOffset + GesState.Filter) = low;
|
||||||
|
(baseAddr + ch * 8)!(GesStateOffset + GesState.Filter + 4) = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loop mixLoop {
|
||||||
|
let sample = ((baseAddr + i)!(GesBufferOffset + 128*4) * envVol) >> 18;
|
||||||
|
(baseAddr + i * 2)!GesBufferOffset = (baseAddr + i * 2)!GesBufferOffset + ((sample * leftVol) >> 4);
|
||||||
|
(baseAddr + i * 2)!(GesBufferOffset + 4) = (baseAddr + i * 2)!(GesBufferOffset + 4) + ((sample * rightVol) >> 4);
|
||||||
|
branch_if (i := i + 4) < 64*4: mixLoop;
|
||||||
|
(baseAddr + ch * 8)!(GesStateOffset + GesState.Filter) = sample;
|
||||||
|
(baseAddr + ch * 8)!(GesStateOffset + GesState.Filter + 4) = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filter = filter - 2;
|
||||||
|
let ctrl = (baseAddr + filter)?26;
|
||||||
|
let note = i32.load16_u(baseAddr + filter * 2, 28);
|
||||||
|
let inline freq = 440 as f32 * pow(2.0, (note - 69*256) as f32 / (12*256) as f32);
|
||||||
|
let F = (8192 as f32 * sin(min(0.25, freq / 44100 as f32) * 3.1415)) as i32;
|
||||||
|
let Q = 8192 - (ctrl >> 4) * (7000/15);
|
||||||
|
let Qlimit = (8192*4096/F - F/2) * 3 / 4;
|
||||||
|
if Q > Qlimit {
|
||||||
|
Q = Qlimit;
|
||||||
|
}
|
||||||
|
let low_out = ctrl & 1;
|
||||||
|
let high_out = (ctrl >> 1) & 1;
|
||||||
|
let band_out = (ctrl >> 2) & 1;
|
||||||
|
let low = (baseAddr + ch * 8)!(GesStateOffset + GesState.Filter);
|
||||||
|
let band = (baseAddr + ch * 8)!(GesStateOffset + GesState.Filter + 4);
|
||||||
|
loop filterLoop {
|
||||||
|
let in = ((baseAddr + i)!(GesBufferOffset + 128*4) * envVol) >> 18;
|
||||||
|
|
||||||
|
let high = in - low - ((band * Q) >> 12);
|
||||||
|
band = band + ((F * high) >> 12);
|
||||||
|
low = low + ((F * band) >> 12);
|
||||||
|
|
||||||
|
let sample = low * low_out + high * high_out + band * band_out;
|
||||||
|
(baseAddr + i * 2)!GesBufferOffset = (baseAddr + i * 2)!GesBufferOffset + ((sample * leftVol) >> 4);
|
||||||
|
(baseAddr + i * 2)!(GesBufferOffset + 4) = (baseAddr + i * 2)!(GesBufferOffset + 4) + ((sample * rightVol) >> 4);
|
||||||
|
branch_if (i := i + 4) < 64*4: filterLoop;
|
||||||
|
(baseAddr + ch * 8)!(GesStateOffset + GesState.Filter) = low;
|
||||||
|
(baseAddr + ch * 8)!(GesStateOffset + GesState.Filter + 4) = band;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
branch_if (ch := ch + 1) < 4: channelLoop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
((baseAddr + (t & 127) * 4)!GesBufferOffset) as f32 / 32768 as f32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn polyBlep(transientPhase: i32, invPhaseInc: f32, magnitude: i32) -> i32 {
|
||||||
|
let lazy t = ((transientPhase << 16) >> 16) as f32 * invPhaseInc;
|
||||||
|
let lazy x = max(0 as f32, 1 as f32 - abs(t));
|
||||||
|
(f32.copysign(x * x, t) * magnitude as f32) as i32
|
||||||
|
}
|
||||||
6
platform/src/ges_only.cwa
Normal file
6
platform/src/ges_only.cwa
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import "env.memory" memory(1);
|
||||||
|
import "env.sin" fn sin(f32) -> f32;
|
||||||
|
import "env.pow" fn pow(f32, f32) -> f32;
|
||||||
|
import "env.exp" fn exp(f32) -> f32;
|
||||||
|
|
||||||
|
include "ges.cwa"
|
||||||
@@ -11,13 +11,13 @@ fn main() -> Result<()> {
|
|||||||
convert_font()?;
|
convert_font()?;
|
||||||
|
|
||||||
println!("Compiling loader module");
|
println!("Compiling loader module");
|
||||||
let loader = curlywas::compile_file("src/loader.cwa", curlywas::Options::default())?;
|
let loader = curlywas::compile_file("src/loader.cwa", curlywas::Options::default()).0?;
|
||||||
File::create("bin/loader.wasm")?.write_all(&loader)?;
|
File::create("bin/loader.wasm")?.write_all(&loader)?;
|
||||||
|
|
||||||
println!("Loader (including base module): {} bytes", loader.len());
|
println!("Loader (including base module): {} bytes", loader.len());
|
||||||
|
|
||||||
println!("Compiling platform module");
|
println!("Compiling platform module");
|
||||||
let platform = curlywas::compile_file("src/platform.cwa", curlywas::Options::default())?;
|
let platform = curlywas::compile_file("src/platform.cwa", curlywas::Options::default()).0?;
|
||||||
println!("Compressing platform module");
|
println!("Compressing platform module");
|
||||||
let platform = uw8_tool::pack(
|
let platform = uw8_tool::pack(
|
||||||
&platform,
|
&platform,
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import "env.memory" memory(4);
|
import "env.memory" memory(4);
|
||||||
|
|
||||||
|
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.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
|
||||||
@@ -29,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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,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;
|
||||||
@@ -256,6 +258,11 @@ export fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
|
|||||||
p = y1;
|
p = y1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if max_axis == 0 as f32 {
|
||||||
|
setPixel(x1 as i32, y1 as i32, col);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let steps = floor(p + max_axis) as i32 - floor(p) as i32;
|
let steps = floor(p + max_axis) as i32 - floor(p) as i32;
|
||||||
p = floor(p) + 0.5 - p;
|
p = floor(p) + 0.5 - p;
|
||||||
if max_axis < 0 as f32 {
|
if max_axis < 0 as f32 {
|
||||||
@@ -267,7 +274,7 @@ export fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
|
|||||||
dy = dy / max_axis;
|
dy = dy / max_axis;
|
||||||
|
|
||||||
let f = min(max_axis, max(0 as f32, p));
|
let f = min(max_axis, max(0 as f32, p));
|
||||||
setPixel((x1 + f * dx) as i32, (y1 + f * dy) as i32, col);
|
setPixel(i32.trunc_sat_f32_s(x1 + f * dx), i32.trunc_sat_f32_s(y1 + f * dy), col);
|
||||||
|
|
||||||
if !steps {
|
if !steps {
|
||||||
return;
|
return;
|
||||||
@@ -280,7 +287,7 @@ export fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
|
|||||||
|
|
||||||
loop pixels {
|
loop pixels {
|
||||||
if steps := steps - 1 {
|
if steps := steps - 1 {
|
||||||
setPixel(x1 as i32, y1 as i32, col);
|
setPixel(i32.trunc_sat_f32_s(x1), i32.trunc_sat_f32_s(y1), col);
|
||||||
x1 = x1 + dx;
|
x1 = x1 + dx;
|
||||||
y1 = y1 + dy;
|
y1 = y1 + dy;
|
||||||
branch pixels;
|
branch pixels;
|
||||||
@@ -288,7 +295,7 @@ export fn line(x1: f32, y1: f32, x2: f32, y2: f32, col: i32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
f = min(max_axis, p) - p;
|
f = min(max_axis, p) - p;
|
||||||
setPixel((x1 + f * dx) as i32, (y1 + f * dy) as i32, col);
|
setPixel(i32.trunc_sat_f32_s(x1 + f * dx), i32.trunc_sat_f32_s(y1 + f * dy), col);
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////
|
//////////
|
||||||
@@ -299,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 {
|
||||||
@@ -311,6 +318,18 @@ 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;
|
||||||
|
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;
|
||||||
@@ -324,16 +343,14 @@ fn printSingleChar(char: i32) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if char == 4 | char == 5 {
|
if char == 7 {
|
||||||
graphicsText = char == 5;
|
80?0 = 80?0 ^ 2;
|
||||||
textCursorX = 0;
|
|
||||||
textCursorY = 0;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -341,7 +358,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;
|
||||||
@@ -350,7 +367,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 {
|
||||||
@@ -364,7 +381,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 {
|
||||||
@@ -404,8 +421,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -430,7 +447,7 @@ data(0x12d00) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn drawChar(char: i32) {
|
fn drawChar(char: i32) {
|
||||||
if !graphicsText & textCursorX >= 320 {
|
if !outputChannel & textCursorX >= 320 {
|
||||||
printChar(0xd0a);
|
printChar(0xd0a);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,7 +455,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);
|
||||||
@@ -490,11 +507,37 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////
|
||||||
|
// SOUND //
|
||||||
|
///////////
|
||||||
|
|
||||||
|
include "ges.cwa"
|
||||||
|
|
||||||
|
export fn playNote(channel: i32, note: i32) {
|
||||||
|
(channel * 6)?80 = (channel * 6)?80 & 0xfe ^ if note {
|
||||||
|
(channel * 6)?83 = note & 127;
|
||||||
|
2 | !(note >> 7)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
data 80 {
|
||||||
|
i8(
|
||||||
|
0x80, 0xc0, 0, 81, 0xa0, 0x50,
|
||||||
|
0xc4, 0, 0, 69, 0x60, 0x40,
|
||||||
|
0x44, 0xb0, 0, 69, 0x90, 0x43,
|
||||||
|
0x4, 0xf0, 0, 69, 0xa4, 0x44,
|
||||||
|
0xff, 0xff,
|
||||||
|
1, 1, 0, 100, 0, 100
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
///////////
|
///////////
|
||||||
// SETUP //
|
// SETUP //
|
||||||
///////////
|
///////////
|
||||||
@@ -503,6 +546,13 @@ export fn endFrame() {
|
|||||||
68!4 = 68!0;
|
68!4 = 68!0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn memclr(base: i32, size: i32) {
|
||||||
|
loop bytes {
|
||||||
|
(base + (size := size - 1))?0 = 0;
|
||||||
|
branch_if size: bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
start fn setup() {
|
start fn setup() {
|
||||||
let i: i32 = 12*16*3-1;
|
let i: i32 = 12*16*3-1;
|
||||||
let avg: f32;
|
let avg: f32;
|
||||||
@@ -535,10 +585,19 @@ start fn setup() {
|
|||||||
branch_if (i := i - 1) >= 0: expand_sweetie;
|
branch_if (i := i - 1) >= 0: expand_sweetie;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
memclr(0, 64);
|
||||||
|
memclr(112, 8);
|
||||||
|
memclr(0x14000, 0x2c000);
|
||||||
|
|
||||||
|
|
||||||
cls(0);
|
cls(0);
|
||||||
randomSeed(random());
|
randomSeed(random());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data 0x12c78 {
|
||||||
|
i32(80)
|
||||||
|
}
|
||||||
|
|
||||||
data 0x13000+192*4 {
|
data 0x13000+192*4 {
|
||||||
i32(
|
i32(
|
||||||
0x2c1c1a,
|
0x2c1c1a,
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ for dir in build/*; do
|
|||||||
cp $example $dir/examples
|
cp $example $dir/examples
|
||||||
done
|
done
|
||||||
|
|
||||||
|
cp -r ../examples/include $dir/include
|
||||||
|
|
||||||
mkdir $dir/carts
|
mkdir $dir/carts
|
||||||
for example in $dir/examples/*; do
|
for example in $dir/examples/*; do
|
||||||
build/microw8-linux/uw8 pack -l 9 $example $dir/carts/$(basename ${example%.*}).uw8
|
build/microw8-linux/uw8 pack -l 9 $example $dir/carts/$(basename ${example%.*}).uw8
|
||||||
|
|||||||
@@ -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.1#AgwvgP+M59snqjl4CMKw5sqm1Zw9yJCbSviMjeLUdHus2a3yl/a99+uiBeqZgP/2jqSjrLjRk73COMM6OSLpsxK8ugT1kuk/q4hQUqqPpGozHoa0laulzGGcahzdfdJsYaK1sIdeIYS9M5PnJx/Wk9H+PvWEPy2Zvv7I6IW7Fg==) (127 bytes): Some fireworks to welcome 2022.
|
* [Fireworks](v0.1.2#AgwvgP+M59snqjl4CMKw5sqm1Zw9yJCbSviMjeLUdHus2a3yl/a99+uiBeqZgP/2jqSjrLjRk73COMM6OSLpsxK8ugT1kuk/q4hQUqqPpGozHoa0laulzGGcahzdfdJsYaK1sIdeIYS9M5PnJx/Wk9H+PvWEPy2Zvv7I6IW7Fg==) (127 bytes): Some fireworks to welcome 2022.
|
||||||
* [Skip Ahead](v0.1.1#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
|
* [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
|
||||||
* [OhNoAnotherTunnel](v0.1.1#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.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
|
||||||
* [Technotunnel](v0.1.1#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.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
|
||||||
* [Font & Palette](v0.1.1#At/p39+IBnj6ry1TRe7jzVy2A4tXgBvmoW2itzoyF2aM28pGy5QDiKxqrk8l9sbWZLtnAb+jgOfU+9QhpuyCAkhN6gPOU481IUL/df96vNe3h288Dqwhd3sfFpothIVFsMwRK72kW2hiR7zWsaXyy5pNmjR6BJk4piWx9ApT1ZwoUajhk6/zij6itq/FD1U3jj/J3MOwqZ2ef8Bv6ZPQlJIYVf62icGa69wS6SI1qBpIFiF14F8PcztRVbKIxLpT4ArCS6nz6FPnyUkqATGSBNPJ): Just a simple viewer for the default font and palette.
|
* [Font & Palette](v0.1.2#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,27 +29,65 @@ Examplers for older versions:
|
|||||||
|
|
||||||
## Versions
|
## Versions
|
||||||
|
|
||||||
### v0.1.1
|
### v0.2.0-rc3
|
||||||
|
|
||||||
* [Web runtime](v0.1.1)
|
* [Web runtime](v0.2.0-rc3)
|
||||||
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.1/microw8-0.1.1-linux.tgz)
|
* [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.1.1/microw8-0.1.1-macos.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.1.1/microw8-0.1.1-windows.zip)
|
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc3/microw8-0.2.0-rc3-windows.zip)
|
||||||
|
|
||||||
Changes:
|
Changes:
|
||||||
|
|
||||||
* implement more robust file watcher
|
* improve timing stability some more. essentially now guaranteeing that "frame = time_ms * 6 / 100" returns
|
||||||
* add basic video recording on F10 in web runtime
|
consecutive frame numbers, provided the module can be run at 60 fps
|
||||||
* add screenshot on F9
|
* add support to redirect text output to the console for debugging using control code 6
|
||||||
* add watchdog to interrupt hanging update in native runtime
|
* update curlywas:
|
||||||
* add devkit mode to web runtime
|
* * add support for `else if`
|
||||||
* add unpack and compile commands to uw8
|
* * add support for escape sequences in strings
|
||||||
* add support for table/element section in pack command
|
* * add support for char literals
|
||||||
* disable wayland support (caused missing window decorations in gnome)
|
* * add support for binop-assignment, eg. `+=`, `^=`, `<<=` etc. (also support for the tee operator: `+:=`)
|
||||||
|
|
||||||
### v0.1.0
|
### v0.2.0-rc2
|
||||||
|
|
||||||
* [Web runtime](v0.1.0)
|
* [Web runtime](v0.2.0-rc2)
|
||||||
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-linux.tgz)
|
* [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.1.0/microw8-0.1.0-macos.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.1.0/microw8-0.1.0-windows.zip)
|
* [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
|
||||||
|
|
||||||
|
* [Web runtime](v0.1.2)
|
||||||
|
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-linux.tgz)
|
||||||
|
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-macos.tgz)
|
||||||
|
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-windows.zip)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
* add option to `uw8 run` to run the cart in the browser using the web runtime
|
||||||
|
* CurlyWas: implement `include` support
|
||||||
|
* CurlyWas: implement support for constants
|
||||||
|
* fix crash when trying to draw zero sized line
|
||||||
|
|
||||||
|
### Older versions
|
||||||
|
|
||||||
|
[Find older versions here.](versions)
|
||||||
@@ -18,9 +18,12 @@ The memory has to be imported as `env` `memory` and has a maximum size of 256kb
|
|||||||
00000-00040: user memory
|
00000-00040: user memory
|
||||||
00040-00044: time since module start in ms
|
00040-00044: time since module start in ms
|
||||||
00044-0004c: gamepad state
|
00044-0004c: gamepad state
|
||||||
0004c-00078: reserved
|
0004c-00050: reserved
|
||||||
|
00050-00070: sound data (synced to sound thread)
|
||||||
|
00070-00078: reserved
|
||||||
00078-12c78: frame buffer
|
00078-12c78: frame buffer
|
||||||
12c78-13000: reserved
|
12c78-12c7c: sound registers/work area base address (for sndGes function)
|
||||||
|
12c7c-13000: reserved
|
||||||
13000-13400: palette
|
13000-13400: palette
|
||||||
13400-13c00: font
|
13400-13c00: font
|
||||||
13c00-14000: reserved
|
13c00-14000: reserved
|
||||||
@@ -47,7 +50,7 @@ Returns the arccosine of `x`.
|
|||||||
|
|
||||||
Returns the arctangent of `x`.
|
Returns the arctangent of `x`.
|
||||||
|
|
||||||
### fn atan2(y: f32, y: f32) -> f32
|
### fn atan2(y: f32, x: f32) -> f32
|
||||||
|
|
||||||
Returns the angle between the point `(x, y)` and the positive x-axis.
|
Returns the angle between the point `(x, y)` and the positive x-axis.
|
||||||
|
|
||||||
@@ -229,7 +232,8 @@ Avoid the reserved control chars, they are currently NOPs but their behavior can
|
|||||||
| 2-3 | - | Reserved |
|
| 2-3 | - | Reserved |
|
||||||
| 4 | - | Switch to normal mode |
|
| 4 | - | Switch to normal mode |
|
||||||
| 5 | - | Switch to graphics mode |
|
| 5 | - | Switch to graphics mode |
|
||||||
| 6-7 | - | Reserved |
|
| 6 | - | Reserved |
|
||||||
|
| 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 |
|
||||||
@@ -269,6 +273,130 @@ Sets the background color.
|
|||||||
|
|
||||||
Sets the cursor position. In normal mode `x` and `y` are multiplied by 8 to get the pixel position, in graphics mode they are used as is.
|
Sets the cursor position. In normal mode `x` and `y` are multiplied by 8 to get the pixel position, in graphics mode they are used as is.
|
||||||
|
|
||||||
|
## Sound
|
||||||
|
|
||||||
|
### Low level operation
|
||||||
|
|
||||||
|
MicroW8 actually runs two instances of your module. On the first instance, it calls `upd` and displays the framebuffer found in its memory. On the
|
||||||
|
second instance, it calls `snd` instead with an incrementing sample index and expects that function to return sound samples for the left and right
|
||||||
|
channel at 44100 Hz. If your module does not export a `snd` function, it calls the api function `sndGes` instead.
|
||||||
|
|
||||||
|
As the only means of communication, 32 bytes starting at address 0x00050 are copied from main to sound memory after `upd` returns.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### 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)
|
||||||
|
|
||||||
|
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
|
||||||
|
sound playing on that channel. A note value 128-255 will trigger note-128 and immediately stop it (playing attack+release parts of envelope).
|
||||||
|
|
||||||
|
This function assumes the default setup, with the `sndGes` registers located at 0x00050.
|
||||||
|
|
||||||
|
### fn sndGes(sampleIndex: i32) -> f32
|
||||||
|
|
||||||
|
This implements a sound chip, generating sound based on 32 bytes of sound registers.
|
||||||
|
|
||||||
|
The spec of this sound chip are:
|
||||||
|
|
||||||
|
- 4 channels with individual volume control (0-15)
|
||||||
|
- rect, saw, tri, noise wave forms selectable per channel
|
||||||
|
- each wave form supports some kind of pulse width modulation
|
||||||
|
- each channel has an optional automatic low pass filter, or can be sent to one of two manually controllable filters
|
||||||
|
- each channel can select between a narrow and a wide stereo positioning. The two stereo positions of each channel are fixed.
|
||||||
|
- optional ring modulation
|
||||||
|
|
||||||
|
This function requires 1024 bytes of working memory, the first 32 bytes of which are interpreted as the sound registers.
|
||||||
|
The base address of its working memory can be configured by writing the address to 0x12c78. It defaults to 0x00050.
|
||||||
|
|
||||||
|
Here is a short description of the 32 sound registers.
|
||||||
|
|
||||||
|
```
|
||||||
|
00 - CTRL0
|
||||||
|
06 - CTRL1
|
||||||
|
0c - CTRL2
|
||||||
|
12 - CTRL3
|
||||||
|
| 7 6 | 5 | 4 | 3 2 | 1 | 0 |
|
||||||
|
| wave | ring | wide | filter | trigger | note on |
|
||||||
|
|
||||||
|
note on: stay in decay/sustain part of envelope
|
||||||
|
trigger: the attack part of the envlope is triggered when either this changes
|
||||||
|
or note on is changed from 0 to 1.
|
||||||
|
filter : 0 - no filter
|
||||||
|
1 - fixed 6db 1-pole filter with cutoff two octaves above note
|
||||||
|
2 - programmable filter 0
|
||||||
|
3 - programmable filter 1
|
||||||
|
wide : use wide stereo panning
|
||||||
|
ring : ring modulate with triangle wave at frequency of previous channel
|
||||||
|
wave : 0 - rectangle
|
||||||
|
1 - saw
|
||||||
|
2 - triangle
|
||||||
|
3 - noise
|
||||||
|
|
||||||
|
01 - PULS0
|
||||||
|
07 - PULS1
|
||||||
|
0d - PULS2
|
||||||
|
13 - PULS3
|
||||||
|
Pulse width 0-255, with 0 being the plain version of each wave form.
|
||||||
|
rectangle - 50%-100% pulse width
|
||||||
|
saw - inverts 0%-100% of the saw wave form around the center
|
||||||
|
triangle - morphs into an octave up triangle wave
|
||||||
|
noise - blends into a decimated saw wave (just try it out)
|
||||||
|
|
||||||
|
02 - FINE0
|
||||||
|
08 - FINE1
|
||||||
|
0e - FINE2
|
||||||
|
14 - FINE3
|
||||||
|
Fractional note
|
||||||
|
|
||||||
|
03 - NOTE0
|
||||||
|
09 - NOTE1
|
||||||
|
0f - NOTE2
|
||||||
|
15 - NOTE3
|
||||||
|
Note, 69 = A4
|
||||||
|
|
||||||
|
04 - ENVA0
|
||||||
|
0a - ENVA1
|
||||||
|
10 - ENVA2
|
||||||
|
16 - ENVA3
|
||||||
|
| 7 6 5 4 | 3 2 1 0 |
|
||||||
|
| decay | attack |
|
||||||
|
|
||||||
|
05 - ENVB0
|
||||||
|
0b - ENVB1
|
||||||
|
11 - ENVB2
|
||||||
|
17 - ENVB3
|
||||||
|
| 7 6 5 4 | 3 2 1 0 |
|
||||||
|
| release | sustain |
|
||||||
|
|
||||||
|
18 - VO01
|
||||||
|
| 7 6 5 4 | 3 2 1 0 |
|
||||||
|
| volume 1 | volume 0 |
|
||||||
|
|
||||||
|
19 - VO23
|
||||||
|
| 7 6 5 4 | 3 2 1 0 |
|
||||||
|
| volume 3 | volume 2 |
|
||||||
|
|
||||||
|
1a - FCTR0
|
||||||
|
1b - FCTR1
|
||||||
|
| 7 6 5 4 | 3 | 2 | 1 | 0 |
|
||||||
|
| resonance | 0 | band | high | low |
|
||||||
|
|
||||||
|
1c - FFIN0
|
||||||
|
1e - FFIN1
|
||||||
|
cutoff frequency - fractional note
|
||||||
|
|
||||||
|
1d - FNOT0
|
||||||
|
1f - FNOT1
|
||||||
|
cutoff frequency - note
|
||||||
|
```
|
||||||
|
|
||||||
# The `uw8` tool
|
# The `uw8` tool
|
||||||
|
|
||||||
The `uw8` tool included in the MicroW8 download includes a number of useful tools for developing MicroW8 carts. For small productions written in
|
The `uw8` tool included in the MicroW8 download includes a number of useful tools for developing MicroW8 carts. For small productions written in
|
||||||
@@ -284,7 +412,8 @@ Runs `<file>` which can be a binary WebAssembly module, an `.uw8` cart, a wat (W
|
|||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
* `-t FRAMES`, `--timeout FRAMES` : Sets the timeout in frames (1/60s). If the start or update function runs longer than this it is forcibly interupted
|
* `-b`, `--browser`: Run in browser instead of using native runtime
|
||||||
|
* `-t FRAMES`, `--timeout FRAMES`: Sets the timeout in frames (1/60s). If the start or update function runs longer than this it is forcibly interupted
|
||||||
and execution of the cart is stopped. Defaults to 30 (0.5s)
|
and execution of the cart is stopped. Defaults to 30 (0.5s)
|
||||||
* `-w`, `--watch`: Reloads the given file every time it changes on disk.
|
* `-w`, `--watch`: Reloads the given file every time it changes on disk.
|
||||||
* `-p`, `--pack`: Pack the file into an `.uw8` cart before running it and print the resulting size.
|
* `-p`, `--pack`: Pack the file into an `.uw8` cart before running it and print the resulting size.
|
||||||
|
|||||||
42
site/content/versions.md
Normal file
42
site/content/versions.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
+++
|
||||||
|
description = "Versions"
|
||||||
|
+++
|
||||||
|
|
||||||
|
### v0.1.2
|
||||||
|
|
||||||
|
* [Web runtime](v0.1.2)
|
||||||
|
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-linux.tgz)
|
||||||
|
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-macos.tgz)
|
||||||
|
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.2/microw8-0.1.2-windows.zip)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
* add option to `uw8 run` to run the cart in the browser using the web runtime
|
||||||
|
* CurlyWas: implement `include` support
|
||||||
|
* CurlyWas: implement support for constants
|
||||||
|
* fix crash when trying to draw zero sized line
|
||||||
|
|
||||||
|
### v0.1.1
|
||||||
|
|
||||||
|
* [Web runtime](v0.1.1)
|
||||||
|
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.1/microw8-0.1.1-linux.tgz)
|
||||||
|
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.1/microw8-0.1.1-macos.tgz)
|
||||||
|
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.1/microw8-0.1.1-windows.zip)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
* implement more robust file watcher
|
||||||
|
* add basic video recording on F10 in web runtime
|
||||||
|
* add screenshot on F9
|
||||||
|
* add watchdog to interrupt hanging update in native runtime
|
||||||
|
* add devkit mode to web runtime
|
||||||
|
* add unpack and compile commands to uw8
|
||||||
|
* add support for table/element section in pack command
|
||||||
|
* disable wayland support (caused missing window decorations in gnome)
|
||||||
|
|
||||||
|
### v0.1.0
|
||||||
|
|
||||||
|
* [Web runtime](v0.1.0)
|
||||||
|
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-linux.tgz)
|
||||||
|
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-macos.tgz)
|
||||||
|
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.1.0/microw8-0.1.0-windows.zip)
|
||||||
1
site/static/v0.1.2/index.html
Normal file
1
site/static/v0.1.2/index.html
Normal file
File diff suppressed because one or more lines are too long
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
@@ -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.1">
|
<a href="v0.1.2">
|
||||||
<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>
|
||||||
|
|||||||
@@ -1,23 +1,38 @@
|
|||||||
use anyhow::{bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use notify::{DebouncedEvent, Watcher, RecommendedWatcher};
|
use notify::{DebouncedEvent, RecommendedWatcher, Watcher};
|
||||||
use std::{
|
use std::{collections::BTreeSet, path::PathBuf, sync::mpsc, time::Duration};
|
||||||
collections::BTreeSet,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
sync::mpsc,
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct FileWatcher {
|
pub struct FileWatcher {
|
||||||
_watcher: RecommendedWatcher,
|
watcher: RecommendedWatcher,
|
||||||
watched_files: BTreeSet<PathBuf>,
|
watched_files: BTreeSet<PathBuf>,
|
||||||
|
directories: BTreeSet<PathBuf>,
|
||||||
rx: mpsc::Receiver<DebouncedEvent>,
|
rx: mpsc::Receiver<DebouncedEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FileWatcherBuilder(BTreeSet<PathBuf>);
|
|
||||||
|
|
||||||
impl FileWatcher {
|
impl FileWatcher {
|
||||||
pub fn builder() -> FileWatcherBuilder {
|
pub fn new() -> Result<FileWatcher> {
|
||||||
FileWatcherBuilder(BTreeSet::new())
|
let (tx, rx) = mpsc::channel();
|
||||||
|
let watcher = notify::watcher(tx, Duration::from_millis(100))?;
|
||||||
|
Ok(FileWatcher {
|
||||||
|
watcher,
|
||||||
|
watched_files: BTreeSet::new(),
|
||||||
|
directories: BTreeSet::new(),
|
||||||
|
rx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_file<P: Into<PathBuf>>(&mut self, path: P) -> Result<()> {
|
||||||
|
let path = path.into();
|
||||||
|
let parent = path.parent().ok_or_else(|| anyhow!("File has no parent"))?;
|
||||||
|
|
||||||
|
if !self.directories.contains(parent) {
|
||||||
|
self.watcher
|
||||||
|
.watch(parent, notify::RecursiveMode::NonRecursive)?;
|
||||||
|
self.directories.insert(parent.to_path_buf());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.watched_files.insert(path);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn poll_changed_file(&self) -> Result<Option<PathBuf>> {
|
pub fn poll_changed_file(&self) -> Result<Option<PathBuf>> {
|
||||||
@@ -38,33 +53,3 @@ impl FileWatcher {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileWatcherBuilder {
|
|
||||||
pub fn add_file<P: Into<PathBuf>>(&mut self, path: P) -> &mut Self {
|
|
||||||
self.0.insert(path.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(self) -> Result<FileWatcher> {
|
|
||||||
let mut directories: BTreeSet<&Path> = BTreeSet::new();
|
|
||||||
|
|
||||||
for file in &self.0 {
|
|
||||||
if let Some(directory) = file.parent() {
|
|
||||||
directories.insert(directory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel();
|
|
||||||
let mut watcher = notify::watcher(tx, Duration::from_millis(100))?;
|
|
||||||
|
|
||||||
for directory in directories {
|
|
||||||
watcher.watch(directory, notify::RecursiveMode::NonRecursive)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(FileWatcher {
|
|
||||||
_watcher: watcher,
|
|
||||||
watched_files: self.0,
|
|
||||||
rx,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
151
src/main.rs
151
src/main.rs
@@ -15,6 +15,10 @@ use uw8::Runtime;
|
|||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let mut args = Arguments::from_env();
|
let mut args = Arguments::from_env();
|
||||||
|
|
||||||
|
// try to enable ansi support in win10 cmd shell
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let _ = ansi_term::enable_ansi_support();
|
||||||
|
|
||||||
match args.subcommand()?.as_deref() {
|
match args.subcommand()?.as_deref() {
|
||||||
Some("version") => {
|
Some("version") => {
|
||||||
println!("{}", env!("CARGO_PKG_VERSION"));
|
println!("{}", env!("CARGO_PKG_VERSION"));
|
||||||
@@ -75,15 +79,11 @@ 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::builder();
|
let mut watcher = uw8::FileWatcher::new()?;
|
||||||
|
|
||||||
if watch_mode {
|
|
||||||
watcher.add_file(&filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
let watcher = watcher.build()?;
|
|
||||||
|
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
@@ -91,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!();
|
||||||
@@ -103,18 +109,23 @@ fn run(mut args: Arguments) -> Result<()> {
|
|||||||
runtime.set_timeout(timeout);
|
runtime.set_timeout(timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = start_cart(&filename, &mut *runtime, &config) {
|
let mut first_run = true;
|
||||||
eprintln!("Load error: {}", err);
|
|
||||||
if !watch_mode {
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while runtime.is_open() {
|
while runtime.is_open() {
|
||||||
if watcher.poll_changed_file()?.is_some() {
|
if first_run || watcher.poll_changed_file()?.is_some() {
|
||||||
if let Err(err) = start_cart(&filename, &mut *runtime, &config) {
|
let (result, dependencies) = start_cart(&filename, &mut *runtime, &config);
|
||||||
eprintln!("Load error: {}", err);
|
if watch_mode {
|
||||||
|
for dep in dependencies {
|
||||||
|
watcher.add_file(dep)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if let Err(err) = result {
|
||||||
|
eprintln!("Load error: {}", err);
|
||||||
|
if !watch_mode {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
first_run = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = runtime.run_frame() {
|
if let Err(err) = runtime.run_frame() {
|
||||||
@@ -134,40 +145,99 @@ struct Config {
|
|||||||
output_path: Option<PathBuf>,
|
output_path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_cart(filename: &Path, config: &Config) -> Result<Vec<u8>> {
|
fn load_cart(filename: &Path, config: &Config) -> (Result<Vec<u8>>, Vec<PathBuf>) {
|
||||||
let mut cart = vec![];
|
let mut dependencies = Vec::new();
|
||||||
File::open(filename)?.read_to_end(&mut cart)?;
|
fn inner(filename: &Path, config: &Config, dependencies: &mut Vec<PathBuf>) -> Result<Vec<u8>> {
|
||||||
|
let mut cart = match SourceType::of_file(filename)? {
|
||||||
if cart[0] >= 10 {
|
SourceType::Binary => {
|
||||||
let src = String::from_utf8(cart)?;
|
let mut cart = vec![];
|
||||||
cart = if src.chars().find(|c| !c.is_whitespace()) == Some('(') {
|
File::open(filename)?.read_to_end(&mut cart)?;
|
||||||
wat::parse_str(src)?
|
cart
|
||||||
} else {
|
}
|
||||||
curlywas::compile_str(&src, filename, curlywas::Options::default())?
|
SourceType::Wat => {
|
||||||
|
let cart = wat::parse_file(filename)?;
|
||||||
|
cart
|
||||||
|
}
|
||||||
|
SourceType::CurlyWas => {
|
||||||
|
let (module, deps) = curlywas::compile_file(filename, curlywas::Options::default());
|
||||||
|
*dependencies = deps;
|
||||||
|
module?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(ref pack_config) = config.pack {
|
||||||
|
cart = uw8_tool::pack(&cart, pack_config)?;
|
||||||
|
println!("packed size: {} bytes", cart.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref path) = config.output_path {
|
||||||
|
File::create(path)?.write_all(&cart)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(cart)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref pack_config) = config.pack {
|
let result = inner(filename, config, &mut dependencies);
|
||||||
cart = uw8_tool::pack(&cart, pack_config)?;
|
|
||||||
println!("packed size: {} bytes", cart.len());
|
if dependencies.is_empty() {
|
||||||
|
dependencies.push(filename.to_path_buf());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref path) = config.output_path {
|
(result, dependencies)
|
||||||
File::create(path)?.write_all(&cart)?;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(cart)
|
enum SourceType {
|
||||||
|
Binary,
|
||||||
|
Wat,
|
||||||
|
CurlyWas,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceType {
|
||||||
|
fn of_file(filename: &Path) -> Result<SourceType> {
|
||||||
|
if let Some(extension) = filename.extension() {
|
||||||
|
if extension == "uw8" || extension == "wasm" {
|
||||||
|
return Ok(SourceType::Binary);
|
||||||
|
} else if extension == "wat" || extension == "wast" {
|
||||||
|
return Ok(SourceType::Wat);
|
||||||
|
} else if extension == "cwa" {
|
||||||
|
return Ok(SourceType::CurlyWas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cart = vec![];
|
||||||
|
File::open(filename)?.read_to_end(&mut cart)?;
|
||||||
|
|
||||||
|
let ty = if cart[0] < 10 {
|
||||||
|
SourceType::Binary
|
||||||
|
} else {
|
||||||
|
let src = String::from_utf8(cart)?;
|
||||||
|
if src.chars().find(|&c| !c.is_whitespace() && c != ';') == Some('(') {
|
||||||
|
SourceType::Wat
|
||||||
|
} else {
|
||||||
|
SourceType::CurlyWas
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(ty)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "native", feature = "browser"))]
|
#[cfg(any(feature = "native", feature = "browser"))]
|
||||||
fn start_cart(filename: &Path, runtime: &mut dyn Runtime, config: &Config) -> Result<()> {
|
fn start_cart(
|
||||||
let cart = load_cart(filename, config)?;
|
filename: &Path,
|
||||||
|
runtime: &mut dyn Runtime,
|
||||||
|
config: &Config,
|
||||||
|
) -> (Result<()>, Vec<PathBuf>) {
|
||||||
|
let (cart, dependencies) = load_cart(filename, config);
|
||||||
|
let cart = match cart {
|
||||||
|
Ok(cart) => cart,
|
||||||
|
Err(err) => return (Err(err), dependencies),
|
||||||
|
};
|
||||||
|
|
||||||
if let Err(err) = runtime.load(&cart) {
|
if let Err(err) = runtime.load(&cart) {
|
||||||
eprintln!("Load error: {}", err);
|
eprintln!("Load error: {}", err);
|
||||||
Err(err)
|
(Err(err), dependencies)
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
(Ok(()), dependencies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +262,8 @@ fn pack(mut args: Arguments) -> Result<()> {
|
|||||||
pack: Some(pack_config),
|
pack: Some(pack_config),
|
||||||
output_path: None,
|
output_path: None,
|
||||||
},
|
},
|
||||||
)?;
|
)
|
||||||
|
.0?;
|
||||||
|
|
||||||
File::create(out_file)?.write_all(&cart)?;
|
File::create(out_file)?.write_all(&cart)?;
|
||||||
|
|
||||||
@@ -215,7 +286,7 @@ fn compile(mut args: Arguments) -> Result<()> {
|
|||||||
let in_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
let in_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
||||||
let out_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
let out_file = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
||||||
|
|
||||||
let module = curlywas::compile_file(in_file, options)?;
|
let module = curlywas::compile_file(in_file, options).0?;
|
||||||
File::create(out_file)?.write_all(&module)?;
|
File::create(out_file)?.write_all(&module)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,9 +1,11 @@
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{mpsc, Arc, Mutex};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{thread, time::Instant};
|
use std::{thread, time::Instant};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{anyhow, Result};
|
||||||
|
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,
|
||||||
};
|
};
|
||||||
@@ -26,16 +28,19 @@ 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 {
|
||||||
store: Store<()>,
|
store: Store<()>,
|
||||||
memory: Memory,
|
memory: Memory,
|
||||||
end_frame: TypedFunc<(), ()>,
|
end_frame: TypedFunc<(), ()>,
|
||||||
update: 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: Option<Uw8Sound>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for UW8Instance {
|
impl Drop for UW8Instance {
|
||||||
@@ -65,8 +70,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,
|
||||||
@@ -75,6 +79,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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +89,10 @@ impl MicroW8 {
|
|||||||
*v = 0;
|
*v = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn disable_audio(&mut self) {
|
||||||
|
self.disable_audio = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::Runtime for MicroW8 {
|
impl super::Runtime for MicroW8 {
|
||||||
@@ -119,42 +128,9 @@ 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])?;
|
||||||
|
|
||||||
linker.func_wrap("env", "acos", |v: f32| v.acos())?;
|
add_native_functions(&mut linker, &mut store)?;
|
||||||
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 9..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(),
|
|
||||||
)?,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let platform_instance = linker.instantiate(&mut store, &platform_module)?;
|
let platform_instance = instantiate_platform(&mut linker, &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"),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let watchdog = Arc::new(Mutex::new(UW8WatchDog {
|
let watchdog = Arc::new(Mutex::new(UW8WatchDog {
|
||||||
interupt: store.interrupt_handle()?,
|
interupt: store.interrupt_handle()?,
|
||||||
@@ -187,7 +163,22 @@ impl super::Runtime for MicroW8 {
|
|||||||
watchdog.timeout = 0;
|
watchdog.timeout = 0;
|
||||||
}
|
}
|
||||||
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")?;
|
let update = instance.get_typed_func::<(), (), _>(&mut store, "upd").ok();
|
||||||
|
|
||||||
|
let sound = if self.disable_audio {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
match init_sound(&self.engine, &platform_module, &module) {
|
||||||
|
Ok(sound) => {
|
||||||
|
sound.stream.play()?;
|
||||||
|
Some(sound)
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Failed to init sound: {}", err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
self.instance = Some(UW8Instance {
|
self.instance = Some(UW8Instance {
|
||||||
store,
|
store,
|
||||||
@@ -195,8 +186,10 @@ impl super::Runtime for MicroW8 {
|
|||||||
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,
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -206,7 +199,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
|
||||||
@@ -227,13 +232,25 @@ impl super::Runtime for MicroW8 {
|
|||||||
if let Ok(mut watchdog) = instance.watchdog.lock() {
|
if let Ok(mut watchdog) = instance.watchdog.lock() {
|
||||||
watchdog.timeout = self.timeout;
|
watchdog.timeout = self.timeout;
|
||||||
}
|
}
|
||||||
result = instance.update.call(&mut instance.store, ());
|
if let Some(ref update) = instance.update {
|
||||||
|
result = update.call(&mut instance.store, ());
|
||||||
|
}
|
||||||
if let Ok(mut watchdog) = instance.watchdog.lock() {
|
if let Ok(mut watchdog) = instance.watchdog.lock() {
|
||||||
watchdog.timeout = 0;
|
watchdog.timeout = 0;
|
||||||
}
|
}
|
||||||
instance.end_frame.call(&mut instance.store, ())?;
|
instance.end_frame.call(&mut instance.store, ())?;
|
||||||
|
|
||||||
let memory = instance.memory.data(&instance.store);
|
let memory = instance.memory.data(&instance.store);
|
||||||
|
|
||||||
|
let mut sound_regs = [0u8; 32];
|
||||||
|
sound_regs.copy_from_slice(&memory[80..112]);
|
||||||
|
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..];
|
||||||
for (i, &color_index) in framebuffer.iter().enumerate() {
|
for (i, &color_index) in framebuffer.iter().enumerate() {
|
||||||
@@ -258,3 +275,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(65536.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;
|
||||||
|
}
|
||||||
30
test/ges_test.cwa
Normal file
30
test/ges_test.cwa
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import "env.memory" memory(4);
|
||||||
|
import "env.pow" fn pow(f32, f32) -> f32;
|
||||||
|
import "env.sin" fn sin(f32) -> f32;
|
||||||
|
import "env.cls" fn cls(i32);
|
||||||
|
import "env.rectangle" fn rectangle(f32, f32, f32, f32, i32);
|
||||||
|
|
||||||
|
include "../platform/src/ges.cwa"
|
||||||
|
|
||||||
|
export fn snd(t: i32) -> f32 {
|
||||||
|
gesSnd(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
export fn upd() {
|
||||||
|
80?0 = 32!32 / 200 & 2 | 0x41;
|
||||||
|
80?3 = (32!32 / 400)%7*12/7 + 40;
|
||||||
|
let pulse = (32!32 * 256 / 2000) & 511;
|
||||||
|
if pulse >= 256 {
|
||||||
|
pulse = 511 - pulse;
|
||||||
|
}
|
||||||
|
80?1 = pulse;
|
||||||
|
|
||||||
|
cls(0);
|
||||||
|
rectangle(0.0, 100.0, (pulse * 320 / 256) as f32, 16.0, 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
data 80 {
|
||||||
|
i8(
|
||||||
|
0x41, 0, 0, 80, 0x70, 0
|
||||||
|
)
|
||||||
|
}
|
||||||
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');
|
||||||
|
}
|
||||||
59
test/plot_ges.cwa
Normal file
59
test/plot_ges.cwa
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
include "../examples/include/microw8-api.cwa"
|
||||||
|
|
||||||
|
export fn upd() {
|
||||||
|
80?0 = (32!32 >> 11 << 6) | 5;
|
||||||
|
80?1 = (sin(time() * 6 as f32) * 95 as f32) as i32 + 128;
|
||||||
|
plotGes();
|
||||||
|
}
|
||||||
|
|
||||||
|
data 80 { i8 (
|
||||||
|
1, 128, 0, 69, 0, 15,
|
||||||
|
0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0,
|
||||||
|
0xff, 0xff,
|
||||||
|
0xc1, 0, 0, 110, 0, 0
|
||||||
|
) }
|
||||||
|
|
||||||
|
//import "env.gesSnd" fn gesSnd(i32) -> f32;
|
||||||
|
|
||||||
|
include "../platform/src/ges.cwa"
|
||||||
|
|
||||||
|
export fn snd(t: i32) -> f32 {
|
||||||
|
gesSnd(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
global mut samplePos: i32 = 0;
|
||||||
|
|
||||||
|
const SoundBuffer = 0x30000;
|
||||||
|
|
||||||
|
fn plotGes() {
|
||||||
|
rectangle(0 as f32, 10 as f32, 320 as f32, 320 as f32, 0);
|
||||||
|
let count = (time() * 44100 as f32) as i32 * 2 - samplePos;
|
||||||
|
let i: i32;
|
||||||
|
loop genLoop {
|
||||||
|
(i*4)$SoundBuffer = gesSnd(samplePos + i);
|
||||||
|
branch_if (i := i + 1) < count: genLoop;
|
||||||
|
}
|
||||||
|
samplePos = samplePos + count;
|
||||||
|
|
||||||
|
let ch: i32;
|
||||||
|
loop channelLoop {
|
||||||
|
let offset = 159;
|
||||||
|
i = 0;
|
||||||
|
|
||||||
|
loop searchLoop {
|
||||||
|
offset = offset + 1;
|
||||||
|
branch_if (offset * 8 + ch - 8)$SoundBuffer < 0 as f32 | (offset * 8 + ch)$SoundBuffer >= 0 as f32 & offset + 160 < count: searchLoop;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = ch + (offset - 160) * 8;
|
||||||
|
i = 0;
|
||||||
|
loop plotLoop {
|
||||||
|
setPixel(i, floor((i * 8 + offset)$SoundBuffer * 127 as f32) as i32 + 60 + ch * (120/8), 15);
|
||||||
|
branch_if (i := i + 1) < 320: plotLoop;
|
||||||
|
}
|
||||||
|
|
||||||
|
branch_if (ch := ch + 8) < 16: channelLoop;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -71,26 +71,102 @@ impl BaseModule {
|
|||||||
add_function(&mut functions, &type_map, "randomSeed", &[I32], None);
|
add_function(&mut functions, &type_map, "randomSeed", &[I32], None);
|
||||||
|
|
||||||
add_function(&mut functions, &type_map, "cls", &[I32], None);
|
add_function(&mut functions, &type_map, "cls", &[I32], None);
|
||||||
add_function(&mut functions, &type_map, "setPixel", &[I32, I32, I32], None);
|
add_function(
|
||||||
add_function(&mut functions, &type_map, "getPixel", &[I32, I32], Some(I32));
|
&mut functions,
|
||||||
add_function(&mut functions, &type_map, "hline", &[I32, I32, I32, I32], None);
|
&type_map,
|
||||||
add_function(&mut functions, &type_map, "rectangle", &[F32, F32, F32, F32, I32], None);
|
"setPixel",
|
||||||
add_function(&mut functions, &type_map, "circle", &[F32, F32, F32, I32], None);
|
&[I32, I32, I32],
|
||||||
add_function(&mut functions, &type_map, "line", &[F32, F32, F32, F32, I32], None);
|
None,
|
||||||
|
);
|
||||||
|
add_function(
|
||||||
|
&mut functions,
|
||||||
|
&type_map,
|
||||||
|
"getPixel",
|
||||||
|
&[I32, I32],
|
||||||
|
Some(I32),
|
||||||
|
);
|
||||||
|
add_function(
|
||||||
|
&mut functions,
|
||||||
|
&type_map,
|
||||||
|
"hline",
|
||||||
|
&[I32, I32, I32, I32],
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
add_function(
|
||||||
|
&mut functions,
|
||||||
|
&type_map,
|
||||||
|
"rectangle",
|
||||||
|
&[F32, F32, F32, F32, I32],
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
add_function(
|
||||||
|
&mut functions,
|
||||||
|
&type_map,
|
||||||
|
"circle",
|
||||||
|
&[F32, F32, F32, I32],
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
add_function(
|
||||||
|
&mut functions,
|
||||||
|
&type_map,
|
||||||
|
"line",
|
||||||
|
&[F32, F32, F32, F32, I32],
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
add_function(&mut functions, &type_map, "time", &[], Some(F32));
|
add_function(&mut functions, &type_map, "time", &[], Some(F32));
|
||||||
add_function(&mut functions, &type_map, "isButtonPressed", &[I32], Some(I32));
|
add_function(
|
||||||
add_function(&mut functions, &type_map, "isButtonTriggered", &[I32], Some(I32));
|
&mut functions,
|
||||||
|
&type_map,
|
||||||
|
"isButtonPressed",
|
||||||
|
&[I32],
|
||||||
|
Some(I32),
|
||||||
|
);
|
||||||
|
add_function(
|
||||||
|
&mut functions,
|
||||||
|
&type_map,
|
||||||
|
"isButtonTriggered",
|
||||||
|
&[I32],
|
||||||
|
Some(I32),
|
||||||
|
);
|
||||||
|
|
||||||
add_function(&mut functions, &type_map, "printChar", &[I32], None);
|
add_function(&mut functions, &type_map, "printChar", &[I32], None);
|
||||||
add_function(&mut functions, &type_map, "printString", &[I32], None);
|
add_function(&mut functions, &type_map, "printString", &[I32], None);
|
||||||
add_function(&mut functions, &type_map, "printInt", &[I32], None);
|
add_function(&mut functions, &type_map, "printInt", &[I32], None);
|
||||||
add_function(&mut functions, &type_map, "setTextColor", &[I32], None);
|
add_function(&mut functions, &type_map, "setTextColor", &[I32], None);
|
||||||
add_function(&mut functions, &type_map, "setBackgroundColor", &[I32], None);
|
add_function(
|
||||||
add_function(&mut functions, &type_map, "setCursorPosition", &[I32, I32], None);
|
&mut functions,
|
||||||
|
&type_map,
|
||||||
|
"setBackgroundColor",
|
||||||
|
&[I32],
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
add_function(
|
||||||
|
&mut functions,
|
||||||
|
&type_map,
|
||||||
|
"setCursorPosition",
|
||||||
|
&[I32, I32],
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
add_function(&mut functions, &type_map, "rectangle_outline", &[F32, F32, F32, F32, I32], None);
|
add_function(
|
||||||
add_function(&mut functions, &type_map, "circle_outline", &[F32, F32, F32, I32], None);
|
&mut functions,
|
||||||
|
&type_map,
|
||||||
|
"rectangle_outline",
|
||||||
|
&[F32, F32, F32, F32, I32],
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
add_function(
|
||||||
|
&mut functions,
|
||||||
|
&type_map,
|
||||||
|
"circle_outline",
|
||||||
|
&[F32, F32, F32, I32],
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
add_function(&mut functions, &type_map, "exp", &[F32], Some(F32));
|
||||||
|
|
||||||
|
add_function(&mut functions, &type_map, "playNote", &[I32, I32], None);
|
||||||
|
|
||||||
for i in functions.len()..64 {
|
for i in functions.len()..64 {
|
||||||
add_function(
|
add_function(
|
||||||
@@ -214,6 +290,71 @@ impl BaseModule {
|
|||||||
File::create(path)?.write_all(&data)?;
|
File::create(path)?.write_all(&data)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write_as_cwa<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
||||||
|
fn inner(mut file: File, base: &BaseModule) -> Result<()> {
|
||||||
|
writeln!(
|
||||||
|
file,
|
||||||
|
"// MicroW8 APIs, to be `include`d in CurlyWas sources"
|
||||||
|
)?;
|
||||||
|
writeln!(file, "import \"env.memory\" memory({});", base.memory)?;
|
||||||
|
writeln!(file)?;
|
||||||
|
for &(module, ref name, type_id) in &base.function_imports {
|
||||||
|
if !name.contains("reserved") {
|
||||||
|
let ty = &base.types[type_id as usize];
|
||||||
|
let params: Vec<&str> = ty.params.iter().copied().map(type_to_str).collect();
|
||||||
|
write!(
|
||||||
|
file,
|
||||||
|
"import \"{}.{}\" fn {}({})",
|
||||||
|
module,
|
||||||
|
name,
|
||||||
|
name,
|
||||||
|
params.join(", ")
|
||||||
|
)?;
|
||||||
|
if let Some(result) = ty.result {
|
||||||
|
write!(file, " -> {}", type_to_str(result))?;
|
||||||
|
}
|
||||||
|
writeln!(file, ";")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeln!(file)?;
|
||||||
|
for &(name, value) in CONSTANTS {
|
||||||
|
writeln!(file, "const {} = 0x{:x};", name, value)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
inner(File::create(path)?, self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_as_wat<P: AsRef<Path>>(&self, path: P) -> Result<()> {
|
||||||
|
fn inner(mut file: File, base: &BaseModule) -> Result<()> {
|
||||||
|
writeln!(file, ";; MicroW8 APIs, in WAT (Wasm Text) format")?;
|
||||||
|
writeln!(file, "(import \"env\" \"memory\" (memory {}))", base.memory)?;
|
||||||
|
writeln!(file)?;
|
||||||
|
for &(module, ref name, type_id) in &base.function_imports {
|
||||||
|
if !name.contains("reserved") {
|
||||||
|
let ty = &base.types[type_id as usize];
|
||||||
|
write!(file, "(import \"{}\" \"{}\" (func ${}", module, name, name)?;
|
||||||
|
for ¶m in &ty.params {
|
||||||
|
write!(file, " (param {})", type_to_str(param))?;
|
||||||
|
}
|
||||||
|
if let Some(result) = ty.result {
|
||||||
|
write!(file, " (result {})", type_to_str(result))?;
|
||||||
|
}
|
||||||
|
writeln!(file, "))")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeln!(file)?;
|
||||||
|
writeln!(file, ";; to use defines, include this file with a preprocessor\n;; like gpp (https://logological.org/gpp).")?;
|
||||||
|
for &(name, value) in CONSTANTS {
|
||||||
|
writeln!(file, "#define {} 0x{:x};", name, value)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
inner(File::create(path)?, self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_function(
|
fn add_function(
|
||||||
@@ -241,3 +382,30 @@ fn lookup_type(
|
|||||||
};
|
};
|
||||||
*type_map.get(&key).unwrap()
|
*type_map.get(&key).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn type_to_str(ty: ValType) -> &'static str {
|
||||||
|
match ty {
|
||||||
|
ValType::I32 => "i32",
|
||||||
|
ValType::I64 => "i64",
|
||||||
|
ValType::F32 => "f32",
|
||||||
|
ValType::F64 => "f64",
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CONSTANTS: &[(&str, u32)] = &[
|
||||||
|
("TIME_MS", 0x40),
|
||||||
|
("GAMEPAD", 0x44),
|
||||||
|
("FRAMEBUFFER", 0x78),
|
||||||
|
("PALETTE", 0x13000),
|
||||||
|
("FONT", 0x13400),
|
||||||
|
("USER_MEM", 0x14000),
|
||||||
|
("BUTTON_UP", 0),
|
||||||
|
("BUTTON_DOWN", 1),
|
||||||
|
("BUTTON_LEFT", 2),
|
||||||
|
("BUTTON_RIGHT", 3),
|
||||||
|
("BUTTON_A", 4),
|
||||||
|
("BUTTON_B", 5),
|
||||||
|
("BUTTON_X", 6),
|
||||||
|
("BUTTON_Y", 7),
|
||||||
|
];
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use uw8_tool::BaseModule;
|
|
||||||
use pico_args::Arguments;
|
use pico_args::Arguments;
|
||||||
|
use uw8_tool::BaseModule;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let mut args = Arguments::from_env();
|
let mut args = Arguments::from_env();
|
||||||
@@ -32,6 +32,14 @@ fn main() -> Result<()> {
|
|||||||
let dest: PathBuf = args.free_from_str()?;
|
let dest: PathBuf = args.free_from_str()?;
|
||||||
uw8_tool::filter_exports(&source, &dest)?;
|
uw8_tool::filter_exports(&source, &dest)?;
|
||||||
}
|
}
|
||||||
|
"base-cwa" => {
|
||||||
|
let path: PathBuf = args.free_from_str()?;
|
||||||
|
BaseModule::for_format_version(1)?.write_as_cwa(path)?;
|
||||||
|
}
|
||||||
|
"base-wat" => {
|
||||||
|
let path: PathBuf = args.free_from_str()?;
|
||||||
|
BaseModule::for_format_version(1)?.write_as_wat(path)?;
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
eprintln!("Unknown subcommand '{}'", cmd);
|
eprintln!("Unknown subcommand '{}'", cmd);
|
||||||
print_help();
|
print_help();
|
||||||
|
|||||||
2
web/run
Executable file
2
web/run
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
rm -rf .parcel-cache && yarn parcel src/index.html
|
||||||
94
web/src/audiolet.js
Normal file
94
web/src/audiolet.js
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
let U8 = (...a) => new Uint8Array(...a);
|
||||||
|
class APU extends AudioWorkletProcessor {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.sampleIndex = 0;
|
||||||
|
this.currentTime = 0;
|
||||||
|
this.isFirstMessage = true;
|
||||||
|
this.pendingUpdates = [];
|
||||||
|
this.port.onmessage = (ev) => {
|
||||||
|
if(this.memory) {
|
||||||
|
if(this.isFirstMessage)
|
||||||
|
{
|
||||||
|
this.currentTime += (ev.data.t - this.currentTime) / 8;
|
||||||
|
this.isFirstMessage = false;
|
||||||
|
}
|
||||||
|
this.pendingUpdates.push(ev.data);
|
||||||
|
} else {
|
||||||
|
this.load(ev.data[0], ev.data[1]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async load(platform_data, data) {
|
||||||
|
let memory = new WebAssembly.Memory({ initial: 4, maximum: 4 });
|
||||||
|
|
||||||
|
let importObject = {
|
||||||
|
env: {
|
||||||
|
memory
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let n of ['acos', 'asin', 'atan', 'atan2', 'cos', 'exp', 'log', 'sin', 'tan', 'pow']) {
|
||||||
|
importObject.env[n] = Math[n];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 9; i < 64; ++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) {
|
||||||
|
importObject.env['g_reserved' + i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let instantiate = async (data) => (await WebAssembly.instantiate(data, importObject)).instance;
|
||||||
|
|
||||||
|
let platform_instance = await instantiate(platform_data);
|
||||||
|
|
||||||
|
for (let name in platform_instance.exports) {
|
||||||
|
importObject.env[name] = platform_instance.exports[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
let instance = await instantiate(data);
|
||||||
|
|
||||||
|
this.memory = memory;
|
||||||
|
|
||||||
|
this.snd = instance.exports.snd || platform_instance.exports.gesSnd;
|
||||||
|
|
||||||
|
this.port.postMessage(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
process(inputs, outputs, parameters) {
|
||||||
|
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);
|
||||||
|
u32Mem[16] = this.currentTime;
|
||||||
|
let channels = outputs[0];
|
||||||
|
let index = this.sampleIndex;
|
||||||
|
let numSamples = channels[0].length;
|
||||||
|
for(let i = 0; i < numSamples; ++i) {
|
||||||
|
channels[0][i] = this.snd(index++);
|
||||||
|
channels[1][i] = this.snd(index++);
|
||||||
|
}
|
||||||
|
this.sampleIndex = index & 0xffffffff;
|
||||||
|
this.currentTime += numSamples / 44.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerProcessor('apu', APU);
|
||||||
@@ -10,13 +10,14 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="uw8">
|
<div id="uw8">
|
||||||
<a href="https://exoticorn.github.io/microw8">MicroW8</a> 0.1.1
|
<a href="https://exoticorn.github.io/microw8">MicroW8</a> 0.2.0-rc3
|
||||||
</div>
|
</div>
|
||||||
<div id="centered">
|
<div id="centered">
|
||||||
<canvas id="screen" width="320" height="240">
|
<canvas class="screen" id="screen" width="320" height="240">
|
||||||
</canvas>
|
</canvas>
|
||||||
<div id="timer" hidden="true"></div>
|
<button class="screen" id="start" style="display:none">Click to start</button>
|
||||||
<div id="message"></div>
|
<div id="timer" hidden="true"></div>
|
||||||
|
<div id="message"></div>
|
||||||
<button id="cartButton" style="visibility:hidden">Load cart...</button>
|
<button id="cartButton" style="visibility:hidden">Load cart...</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="footer">
|
<div id="footer">
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ let uw8 = MicroW8(document.getElementById('screen'), {
|
|||||||
setMessage,
|
setMessage,
|
||||||
keyboardElement: window,
|
keyboardElement: window,
|
||||||
timerElement: document.getElementById("timer"),
|
timerElement: document.getElementById("timer"),
|
||||||
|
startButton: document.getElementById("start")
|
||||||
});
|
});
|
||||||
|
|
||||||
function runModuleFromHash() {
|
function runModuleFromHash() {
|
||||||
@@ -79,7 +80,9 @@ if(location.hash.length != 0) {
|
|||||||
url += 'cart.uw8';
|
url += 'cart.uw8';
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await uw8.runModuleFromURL(url, true);
|
if(!await uw8.runModuleFromURL(url, true)) {
|
||||||
|
setupLoad();
|
||||||
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
setupLoad();
|
setupLoad();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
import loaderUrl from "data-url:../../platform/bin/loader.wasm";
|
import loaderUrl from "data-url:../../platform/bin/loader.wasm";
|
||||||
import platformUrl from "data-url:../../platform/bin/platform.uw8";
|
import platformUrl from "data-url:../../platform/bin/platform.uw8";
|
||||||
|
import audioWorkletUrl from "data-url:./audiolet.js";
|
||||||
|
|
||||||
|
class AudioNode extends AudioWorkletNode {
|
||||||
|
constructor(context) {
|
||||||
|
super(context, 'apu', {outputChannelCount: [2]});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let U8 = (...a) => new Uint8Array(...a);
|
||||||
|
let U32 = (...a) => new Uint32Array(...a);
|
||||||
|
|
||||||
export default function MicroW8(screen, config = {}) {
|
export default function MicroW8(screen, config = {}) {
|
||||||
if(!config.setMessage) {
|
if(!config.setMessage) {
|
||||||
@@ -18,9 +28,6 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
|
|
||||||
let currentData;
|
let currentData;
|
||||||
|
|
||||||
let U8 = (d) => new Uint8Array(d);
|
|
||||||
let U32 = (d) => new Uint32Array(d);
|
|
||||||
|
|
||||||
let pad = 0;
|
let pad = 0;
|
||||||
let keyboardElement = config.keyboardElement == undefined ? screen : config.keyboardElement;
|
let keyboardElement = config.keyboardElement == undefined ? screen : config.keyboardElement;
|
||||||
if(keyboardElement) {
|
if(keyboardElement) {
|
||||||
@@ -83,6 +90,9 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
keyboardElement.onkeydown = keyHandler;
|
keyboardElement.onkeydown = keyHandler;
|
||||||
keyboardElement.onkeyup = keyHandler;
|
keyboardElement.onkeyup = keyHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let audioContext;
|
||||||
|
let audioNode;
|
||||||
|
|
||||||
async function runModule(data, keepUrl) {
|
async function runModule(data, keepUrl) {
|
||||||
if (cancelFunction) {
|
if (cancelFunction) {
|
||||||
@@ -90,6 +100,15 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
cancelFunction = null;
|
cancelFunction = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
audioContext = new AudioContext({sampleRate: 44100});
|
||||||
|
let keepRunning = true;
|
||||||
|
let abortController = new AbortController();
|
||||||
|
cancelFunction = () => {
|
||||||
|
audioContext.close();
|
||||||
|
keepRunning = false;
|
||||||
|
abortController.abort();
|
||||||
|
};
|
||||||
|
|
||||||
let cartridgeSize = data.byteLength;
|
let cartridgeSize = data.byteLength;
|
||||||
|
|
||||||
config.setMessage(cartridgeSize);
|
config.setMessage(cartridgeSize);
|
||||||
@@ -97,6 +116,39 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await audioContext.audioWorklet.addModule(audioWorkletUrl);
|
||||||
|
audioNode = new AudioNode(audioContext);
|
||||||
|
|
||||||
|
let audioReadyFlags = 0;
|
||||||
|
let audioReadyResolve;
|
||||||
|
let audioReadyPromise = new Promise(resolve => audioReadyResolve = resolve);
|
||||||
|
let updateAudioReady = (f) => {
|
||||||
|
audioReadyFlags |= f;
|
||||||
|
if(audioReadyFlags == 3 && audioReadyResolve) {
|
||||||
|
audioReadyResolve(true);
|
||||||
|
audioReadyResolve = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let audioStateChange = () => {
|
||||||
|
if(audioContext.state == 'suspended') {
|
||||||
|
if(config.startButton) {
|
||||||
|
config.startButton.style = '';
|
||||||
|
screen.style = 'display:none';
|
||||||
|
}
|
||||||
|
(config.startButton || screen).onclick = () => {
|
||||||
|
audioContext.resume();
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
if(config.startButton) {
|
||||||
|
config.startButton.style = 'display:none';
|
||||||
|
screen.style = '';
|
||||||
|
}
|
||||||
|
updateAudioReady(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
audioContext.onstatechange = audioStateChange;
|
||||||
|
audioStateChange();
|
||||||
|
|
||||||
currentData = data;
|
currentData = data;
|
||||||
|
|
||||||
let newURL = window.location.pathname;
|
let newURL = window.location.pathname;
|
||||||
@@ -119,7 +171,7 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
if(!devkitMode) {
|
if(!devkitMode) {
|
||||||
memSize.maximum = 4;
|
memSize.maximum = 4;
|
||||||
}
|
}
|
||||||
let memory = new WebAssembly.Memory({ initial: 4, maximum: devkitMode ? 16 : 4 });
|
let memory = new WebAssembly.Memory(memSize);
|
||||||
let memU8 = U8(memory.buffer);
|
let memU8 = U8(memory.buffer);
|
||||||
|
|
||||||
let importObject = {
|
let importObject = {
|
||||||
@@ -142,9 +194,9 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
|
|
||||||
let instantiate = async (data) => (await WebAssembly.instantiate(data, importObject)).instance;
|
let instantiate = async (data) => (await WebAssembly.instantiate(data, importObject)).instance;
|
||||||
|
|
||||||
let loadModuleURL = async (url) => instantiate(loadModuleData(await (await fetch(url)).arrayBuffer()));
|
let loadModuleURL = async (url) => loadModuleData(await (await fetch(url)).arrayBuffer());
|
||||||
|
|
||||||
loader = await loadModuleURL(loaderUrl);
|
loader = await instantiate(await loadModuleURL(loaderUrl));
|
||||||
|
|
||||||
for (let n of ['acos', 'asin', 'atan', 'atan2', 'cos', 'exp', 'log', 'sin', 'tan', 'pow']) {
|
for (let n of ['acos', 'asin', 'atan', 'atan2', 'cos', 'exp', 'log', 'sin', 'tan', 'pow']) {
|
||||||
importObject.env[n] = Math[n];
|
importObject.env[n] = Math[n];
|
||||||
@@ -154,13 +206,28 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
data = loadModuleData(data);
|
data = loadModuleData(data);
|
||||||
|
|
||||||
let platform_instance = await loadModuleURL(platformUrl);
|
let platform_data = await loadModuleURL(platformUrl);
|
||||||
|
|
||||||
|
audioNode.port.onmessage = (e) => updateAudioReady(e.data);
|
||||||
|
audioNode.port.postMessage([platform_data, data]);
|
||||||
|
|
||||||
|
let platform_instance = await instantiate(platform_data);
|
||||||
|
|
||||||
for (let name in platform_instance.exports) {
|
for (let name in platform_instance.exports) {
|
||||||
importObject.env[name] = platform_instance.exports[name]
|
importObject.env[name] = platform_instance.exports[name]
|
||||||
@@ -169,24 +236,42 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
let instance = await instantiate(data);
|
let instance = await instantiate(data);
|
||||||
|
|
||||||
let buffer = U32(imageData.data.buffer);
|
let buffer = U32(imageData.data.buffer);
|
||||||
|
|
||||||
|
await audioReadyPromise;
|
||||||
|
|
||||||
let startTime = Date.now();
|
let startTime = Date.now();
|
||||||
|
|
||||||
let keepRunning = true;
|
|
||||||
cancelFunction = () => keepRunning = false;
|
|
||||||
|
|
||||||
const timePerFrame = 1000 / 60;
|
const timePerFrame = 1000 / 60;
|
||||||
let nextFrame = startTime;
|
|
||||||
|
audioNode.connect(audioContext.destination);
|
||||||
|
|
||||||
|
let isPaused = false;
|
||||||
|
let pauseTime = startTime;
|
||||||
|
let updateVisibility = isVisible => {
|
||||||
|
let now = Date.now();
|
||||||
|
if(isVisible) {
|
||||||
|
isPaused = false;
|
||||||
|
audioContext.resume();
|
||||||
|
startTime += now - pauseTime;
|
||||||
|
} else {
|
||||||
|
isPaused = true;
|
||||||
|
audioContext.suspend();
|
||||||
|
pauseTime = now;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('focus', () => updateVisibility(true), { signal: abortController.signal });
|
||||||
|
window.addEventListener('blur', () => updateVisibility(false), { signal: abortController.signal });
|
||||||
|
updateVisibility(document.hasFocus());
|
||||||
|
|
||||||
function mainloop() {
|
function mainloop() {
|
||||||
if (!keepRunning) {
|
if (!keepRunning) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let now = Date.now();
|
|
||||||
let restart = false;
|
let restart = false;
|
||||||
if (now >= nextFrame) {
|
let thisFrame;
|
||||||
|
if (!isPaused) {
|
||||||
let gamepads = navigator.getGamepads();
|
let gamepads = navigator.getGamepads();
|
||||||
let gamepad = 0;
|
let gamepad = 0;
|
||||||
for (let i = 0; i < 4; ++i) {
|
for (let i = 0; i < 4; ++i) {
|
||||||
@@ -214,23 +299,36 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let u32Mem = U32(memory.buffer);
|
let u32Mem = U32(memory.buffer);
|
||||||
u32Mem[16] = now - startTime;
|
let time = Date.now() - startTime;
|
||||||
|
u32Mem[16] = time;
|
||||||
u32Mem[17] = pad | gamepad;
|
u32Mem[17] = pad | gamepad;
|
||||||
instance.exports.upd();
|
if(instance.exports.upd) {
|
||||||
|
instance.exports.upd();
|
||||||
|
}
|
||||||
platform_instance.exports.endFrame();
|
platform_instance.exports.endFrame();
|
||||||
|
|
||||||
|
let soundRegisters = new ArrayBuffer(32);
|
||||||
|
U8(soundRegisters).set(U8(memory.buffer, 80, 32));
|
||||||
|
audioNode.port.postMessage({t: time, r: soundRegisters}, [soundRegisters]);
|
||||||
|
|
||||||
let palette = U32(memory.buffer.slice(0x13000, 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);
|
||||||
nextFrame = Math.max(nextFrame + timePerFrame, now);
|
|
||||||
|
let timeOffset = ((time * 6) % 100 - 50) / 6;
|
||||||
|
thisFrame = startTime + time - timeOffset / 8;
|
||||||
|
} else {
|
||||||
|
thisFrame = Date.now();
|
||||||
}
|
}
|
||||||
|
let now = Date.now();
|
||||||
|
let nextFrame = Math.max(thisFrame + timePerFrame, now);
|
||||||
|
|
||||||
if (restart) {
|
if (restart) {
|
||||||
runModule(currentData);
|
runModule(currentData);
|
||||||
} else {
|
} else {
|
||||||
window.requestAnimationFrame(mainloop);
|
window.setTimeout(mainloop, nextFrame - now)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
config.setMessage(cartridgeSize, err.toString());
|
config.setMessage(cartridgeSize, err.toString());
|
||||||
@@ -253,14 +351,25 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
|
|
||||||
let videoRecorder;
|
let videoRecorder;
|
||||||
let videoStartTime;
|
let videoStartTime;
|
||||||
|
let videoAudioSourceNode;
|
||||||
|
let videoAudioStreamNode;
|
||||||
function recordVideo() {
|
function recordVideo() {
|
||||||
if(videoRecorder) {
|
if(videoRecorder) {
|
||||||
videoRecorder.stop();
|
videoRecorder.stop();
|
||||||
videoRecorder = null;
|
videoRecorder = null;
|
||||||
|
videoAudioSourceNode.disconnect(videoAudioStreamNode);
|
||||||
|
videoAudioSourceNode = null;
|
||||||
|
videoAudioStreamNode = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let stream = screen.captureStream();
|
||||||
|
videoAudioStreamNode = audioContext.createMediaStreamDestination();
|
||||||
|
videoAudioSourceNode = audioNode;
|
||||||
|
audioNode.connect(videoAudioStreamNode);
|
||||||
|
stream.addTrack(videoAudioStreamNode.stream.getAudioTracks()[0]);
|
||||||
|
|
||||||
videoRecorder = new MediaRecorder(screen.captureStream(), {
|
videoRecorder = new MediaRecorder(stream, {
|
||||||
mimeType: 'video/webm',
|
mimeType: 'video/webm',
|
||||||
videoBitsPerSecond: 25000000
|
videoBitsPerSecond: 25000000
|
||||||
});
|
});
|
||||||
@@ -305,10 +414,11 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
async function runModuleFromURL(url, keepUrl) {
|
async function runModuleFromURL(url, keepUrl) {
|
||||||
let response = await fetch(url);
|
let response = await fetch(url);
|
||||||
let type = response.headers.get('Content-Type');
|
let type = response.headers.get('Content-Type');
|
||||||
if(type && type.includes('html')) {
|
if((type && type.includes('html')) || response.status != 200) {
|
||||||
throw false;
|
return false;
|
||||||
}
|
}
|
||||||
runModule(await response.arrayBuffer(), keepUrl || devkitMode);
|
runModule(await response.arrayBuffer(), keepUrl || devkitMode);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ a:hover {
|
|||||||
color: #405040;
|
color: #405040;
|
||||||
}
|
}
|
||||||
|
|
||||||
#screen {
|
.screen {
|
||||||
width: 320px;
|
width: 320px;
|
||||||
height: 240px;
|
height: 240px;
|
||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
@@ -45,9 +45,16 @@ a:hover {
|
|||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
border: 4px solid #303040;
|
border: 4px solid #303040;
|
||||||
box-shadow: 5px 5px 20px black;
|
box-shadow: 5px 5px 20px black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#screen {
|
||||||
cursor: none;
|
cursor: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#start {
|
||||||
|
font-size: 150%;
|
||||||
|
}
|
||||||
|
|
||||||
#timer::before {
|
#timer::before {
|
||||||
content: '';
|
content: '';
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -84,21 +91,21 @@ button:active {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 680px) and (min-height: 620px) {
|
@media (min-width: 680px) and (min-height: 620px) {
|
||||||
#screen {
|
.screen {
|
||||||
width: 640px;
|
width: 640px;
|
||||||
height: 480px;
|
height: 480px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1000px) and (min-height: 800px) {
|
@media (min-width: 1000px) and (min-height: 800px) {
|
||||||
#screen {
|
.screen {
|
||||||
width: 960px;
|
width: 960px;
|
||||||
height: 720px;
|
height: 720px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width:640px) and (height:480px) {
|
@media (width:640px) and (height:480px) {
|
||||||
#screen {
|
.screen {
|
||||||
width: 640px;
|
width: 640px;
|
||||||
height: 480px;
|
height: 480px;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|||||||
4559
web/yarn.lock
4559
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user