9 Commits

18 changed files with 313 additions and 100 deletions

4
Cargo.lock generated
View File

@@ -599,7 +599,7 @@ checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
[[package]] [[package]]
name = "curlywas" name = "curlywas"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/exoticorn/curlywas.git?rev=aac7bbd#aac7bbd8786a26da0dcbe8320b1afefaf6086464" source = "git+https://github.com/exoticorn/curlywas.git?rev=c22297e#c22297ea82977ac06373d629fb273a795322d788"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"ariadne", "ariadne",
@@ -2637,7 +2637,7 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]] [[package]]
name = "uw8" name = "uw8"
version = "0.2.0-rc1" version = "0.2.0-rc3"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"anyhow", "anyhow",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "uw8" name = "uw8"
version = "0.2.0-rc1" 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
@@ -16,7 +16,7 @@ anyhow = "1"
minifb = { version = "0.22", default-features = false, features = ["x11"], optional = true } minifb = { version = "0.22", default-features = false, features = ["x11"], optional = true }
notify = "4" notify = "4"
pico-args = "0.4" pico-args = "0.4"
curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "aac7bbd" } curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "c22297e" }
wat = "1" wat = "1"
uw8-tool = { path = "uw8-tool" } uw8-tool = { path = "uw8-tool" }
same-file = "1" same-file = "1"

View 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
)
}

2
platform/Cargo.lock generated
View File

@@ -146,7 +146,7 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]] [[package]]
name = "curlywas" name = "curlywas"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/exoticorn/curlywas.git?rev=aac7bbd#aac7bbd8786a26da0dcbe8320b1afefaf6086464" source = "git+https://github.com/exoticorn/curlywas.git?rev=c22297e#c22297ea82977ac06373d629fb273a795322d788"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"ariadne", "ariadne",

View File

@@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
curlywas = { git="https://github.com/exoticorn/curlywas.git", rev="aac7bbd" } curlywas = { git="https://github.com/exoticorn/curlywas.git", rev="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.

View File

@@ -4,6 +4,7 @@ import "env.sin" fn sin(f32) -> f32;
import "env.cos" fn cos(f32) -> f32; import "env.cos" fn cos(f32) -> f32;
import "env.pow" fn pow(f32, f32) -> f32; import "env.pow" fn pow(f32, f32) -> f32;
import "env.exp" fn exp(f32) -> f32; import "env.exp" fn exp(f32) -> f32;
import "env.logChar" fn logChar(i32);
export fn time() -> f32 { export fn time() -> f32 {
(0!64) as f32 / 1000 as f32 (0!64) as f32 / 1000 as f32
@@ -32,11 +33,9 @@ export fn random() -> i32 {
} }
export fn random64() -> i64 { export fn random64() -> i64 {
let state: i64; let lazy state = randomState ^ (randomState #>> 12i64);
randomState = (state := ( let lazy state = state ^ (state << 25i64);
state := randomState ^ (randomState #>> 12i64) randomState = state ^ (state #>> 27i64);
) ^ (state << 25i64)
) ^ (state #>> 27i64);
randomState * 0x2545f4914f6cdd1di64 randomState * 0x2545f4914f6cdd1di64
} }
@@ -62,7 +61,7 @@ export fn cls(col: i32) {
let i: i32; let i: i32;
textCursorX = 0; textCursorX = 0;
textCursorY = 0; textCursorY = 0;
graphicsText = 0; outputChannel = 0;
col = (col & 255) * 0x1010101; col = (col & 255) * 0x1010101;
loop pixels { loop pixels {
i!120 = col; i!120 = col;
@@ -307,7 +306,7 @@ global mut textCursorX = 0;
global mut textCursorY = 0; global mut textCursorY = 0;
global mut textColor = 15; global mut textColor = 15;
global mut bgColor = 0; global mut bgColor = 0;
global mut graphicsText = 0; global mut outputChannel = 0;
export fn printChar(char: i32) { export fn printChar(char: i32) {
loop chars { loop chars {
@@ -319,6 +318,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;
@@ -332,13 +343,6 @@ fn printSingleChar(char: i32) {
return; return;
} }
if char == 4 | char == 5 {
graphicsText = char == 5;
textCursorX = 0;
textCursorY = 0;
return;
}
if char == 7 { if char == 7 {
80?0 = 80?0 ^ 2; 80?0 = 80?0 ^ 2;
return; return;
@@ -346,7 +350,7 @@ fn printSingleChar(char: i32) {
if char == 8 { if char == 8 {
textCursorX = textCursorX - 8; textCursorX = textCursorX - 8;
if !graphicsText & textCursorX < 0 { if !outputChannel & textCursorX < 0 {
textCursorX = 320-8; textCursorX = 320-8;
printSingleChar(11); printSingleChar(11);
} }
@@ -354,7 +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;
@@ -363,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 {
@@ -377,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 {
@@ -417,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;
} }
@@ -443,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);
} }
@@ -451,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);
@@ -503,7 +507,7 @@ export fn setBackgroundColor(col: i32) {
} }
export fn setCursorPosition(x: i32, y: i32) { export fn setCursorPosition(x: i32, y: i32) {
let lazy scale = select(graphicsText, 1, 8); let lazy scale = select(outputChannel, 1, 8);
textCursorX = x * scale; textCursorX = x * scale;
textCursorY = y * scale; textCursorY = y * scale;
} }

View File

@@ -29,9 +29,41 @@ Examplers for older versions:
## Versions ## Versions
### v0.2.0-rc3
* [Web runtime](v0.2.0-rc3)
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc3/microw8-0.2.0-rc3-linux.tgz)
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc3/microw8-0.2.0-rc3-macos.tgz)
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc3/microw8-0.2.0-rc3-windows.zip)
Changes:
* improve timing stability some more. essentially now guaranteeing that "frame = time_ms * 6 / 100" returns
consecutive frame numbers, provided the module can be run at 60 fps
* add support to redirect text output to the console for debugging using control code 6
* update curlywas:
* * add support for `else if`
* * add support for escape sequences in strings
* * add support for char literals
* * add support for binop-assignment, eg. `+=`, `^=`, `<<=` etc. (also support for the tee operator: `+:=`)
### v0.2.0-rc2
* [Web runtime](v0.2.0-rc2)
* [Linux](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc2/microw8-0.2.0-rc2-linux.tgz)
* [MacOS](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc2/microw8-0.2.0-rc2-macos.tgz)
* [Windows](https://github.com/exoticorn/microw8/releases/download/v0.2.0-rc2/microw8-0.2.0-rc2-windows.zip)
Changes:
* fix timing issues of sound playback, especially on systems with large sound buffers
### v0.2.0-rc1 ### v0.2.0-rc1
* [Web runtime](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: Changes:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -37,6 +37,7 @@ struct UW8Instance {
end_frame: TypedFunc<(), ()>, end_frame: TypedFunc<(), ()>,
update: Option<TypedFunc<(), ()>>, update: Option<TypedFunc<(), ()>>,
start_time: Instant, start_time: Instant,
next_frame: Instant,
module: Vec<u8>, module: Vec<u8>,
watchdog: Arc<Mutex<UW8WatchDog>>, watchdog: Arc<Mutex<UW8WatchDog>>,
sound: Option<Uw8Sound>, sound: Option<Uw8Sound>,
@@ -69,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,
@@ -186,6 +186,7 @@ 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, sound,
@@ -198,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
@@ -232,7 +245,10 @@ impl super::Runtime for MicroW8 {
let mut sound_regs = [0u8; 32]; let mut sound_regs = [0u8; 32];
sound_regs.copy_from_slice(&memory[80..112]); sound_regs.copy_from_slice(&memory[80..112]);
if let Some(ref sound) = instance.sound { if let Some(ref sound) = instance.sound {
sound.tx.send(sound_regs)?; sound.tx.send(RegisterUpdate {
time,
data: sound_regs,
})?;
} }
let framebuffer = &memory[120..(120 + 320 * 240)]; let framebuffer = &memory[120..(120 + 320 * 240)];
@@ -277,6 +293,16 @@ fn add_native_functions(
for i in 10..64 { for i in 10..64 {
linker.func_wrap("env", &format!("reserved{}", i), || {})?; 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 { for i in 0..16 {
linker.define( linker.define(
"env", "env",
@@ -312,9 +338,14 @@ fn instantiate_platform(
Ok(platform_instance) Ok(platform_instance)
} }
struct RegisterUpdate {
time: i32,
data: [u8; 32],
}
struct Uw8Sound { struct Uw8Sound {
stream: cpal::Stream, stream: cpal::Stream,
tx: mpsc::SyncSender<[u8; 32]>, tx: mpsc::SyncSender<RegisterUpdate>,
} }
fn init_sound( fn init_sound(
@@ -368,7 +399,7 @@ fn init_sound(
let buffer_size = match *config.buffer_size() { let buffer_size = match *config.buffer_size() {
cpal::SupportedBufferSize::Unknown => cpal::BufferSize::Default, cpal::SupportedBufferSize::Unknown => cpal::BufferSize::Default,
cpal::SupportedBufferSize::Range { min, max } => { cpal::SupportedBufferSize::Range { min, max } => {
cpal::BufferSize::Fixed(256.max(min).min(max)) cpal::BufferSize::Fixed(65536.max(min).min(max))
} }
}; };
let config = cpal::StreamConfig { let config = cpal::StreamConfig {
@@ -378,9 +409,7 @@ fn init_sound(
let sample_rate = config.sample_rate.0 as usize; let sample_rate = config.sample_rate.0 as usize;
let (tx, rx) = mpsc::sync_channel::<[u8; 32]>(1); let (tx, rx) = mpsc::sync_channel::<RegisterUpdate>(30);
let start_time = Instant::now();
struct Resampler { struct Resampler {
resampler: rubato::FftFixedIn<f32>, resampler: rubato::FftFixedIn<f32>,
@@ -403,60 +432,91 @@ fn init_sound(
}; };
let mut sample_index = 0; 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( let stream = device.build_output_stream(
&config, &config,
move |mut buffer: &mut [f32], _| { move |mut outer_buffer: &mut [f32], _| {
if let Ok(regs) = rx.try_recv() { let mut first_update = true;
memory.write(&mut store, 80, &regs).unwrap(); 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() {
let time = start_time.elapsed().as_millis() as i32; while pending_updates
let mem = memory.data_mut(&mut store); .first()
mem[64..68].copy_from_slice(&time.to_le_bytes()); .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();
}
if let Some(ref mut resampler) = resampler { let duration = if let Some(update) = pending_updates.first() {
while !buffer.is_empty() { ((update.time.wrapping_sub(current_time) as usize) * sample_rate + 999) / 1000
let copy_size = resampler.output_buffers[0] } else {
.len() outer_buffer.len()
.saturating_sub(resampler.output_index) };
.min(buffer.len() / 2); let step_size = (duration.max(64) * 2).min(outer_buffer.len());
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 let mut buffer = &mut outer_buffer[..step_size];
.resampler
.process_into_buffer( {
&resampler.input_buffers, let mem = memory.data_mut(&mut store);
&mut resampler.output_buffers, mem[64..68].copy_from_slice(&current_time.to_le_bytes());
None, }
)
.unwrap(); if let Some(ref mut resampler) = resampler {
resampler.output_index = 0; while !buffer.is_empty() {
} else { let copy_size = resampler.output_buffers[0]
for i in 0..copy_size { .len()
buffer[i * 2] = resampler.output_buffers[0][resampler.output_index + i]; .saturating_sub(resampler.output_index)
buffer[i * 2 + 1] = .min(buffer.len() / 2);
resampler.output_buffers[1][resampler.output_index + i]; 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..];
} }
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);
} }
} }
} else {
for v in buffer { outer_buffer = &mut outer_buffer[step_size..];
*v = snd.call(&mut store, (sample_index,)).unwrap_or(0.0); current_time =
sample_index = sample_index.wrapping_add(1); current_time.wrapping_add((step_size * 500 / sample_rate).max(1) as i32);
}
} }
}, },
move |err| { move |err| {

20
test/frame_time.cwa Normal file
View File

@@ -0,0 +1,20 @@
include "../examples/include/microw8-api.cwa"
global mut pos = 0;
global mut next = 0;
export fn upd() {
let lazy t = 32!32;
let lazy tick = t * 6 / 100;
let lazy rel = t - tick * 100 / 6;
setBackgroundColor(select(tick == next, 0, select(tick < next, 0x35, 0x55)));
setCursorPosition(pos % 13 * 3, pos / 13 % 30);
if rel < 10 {
printChar(32);
}
printInt(rel);
pos = pos + 1;
next = tick + 1;
}

7
test/log.cwa Normal file
View File

@@ -0,0 +1,7 @@
include "../examples/include/microw8-api.cwa"
export fn upd() {
printChar('\06f: ');
printInt(32!32 * 6 / 100);
printChar('\n\4');
}

View File

@@ -3,13 +3,17 @@ class APU extends AudioWorkletProcessor {
constructor() { constructor() {
super(); super();
this.sampleIndex = 0; this.sampleIndex = 0;
this.currentTime = 0;
this.isFirstMessage = true;
this.pendingUpdates = [];
this.port.onmessage = (ev) => { this.port.onmessage = (ev) => {
if(this.memory) { if(this.memory) {
if(isNaN(ev.data)) { if(this.isFirstMessage)
U8(this.memory.buffer, 80, 32).set(U8(ev.data)); {
} else { this.currentTime += (ev.data.t - this.currentTime) / 8;
this.startTime = ev.data; this.isFirstMessage = false;
} }
this.pendingUpdates.push(ev.data);
} else { } else {
this.load(ev.data[0], ev.data[1]); this.load(ev.data[0], ev.data[1]);
} }
@@ -33,6 +37,16 @@ class APU extends AudioWorkletProcessor {
importObject.env['reserved' + i] = () => { }; importObject.env['reserved' + i] = () => { };
} }
let logLine = '';
importObject.env['logChar'] = (c) => {
if(c == 10) {
console.log(logLine);
logLine = '';
} else {
logLine += String.fromCharCode(c);
}
};
for (let i = 0; i < 16; ++i) { for (let i = 0; i < 16; ++i) {
importObject.env['g_reserved' + i] = 0; importObject.env['g_reserved' + i] = 0;
} }
@@ -55,9 +69,13 @@ class APU extends AudioWorkletProcessor {
} }
process(inputs, outputs, parameters) { process(inputs, outputs, parameters) {
if(this.snd && this.startTime) { this.isFirstMessage = true;
if(this.snd) {
while(this.pendingUpdates.length > 0 && this.pendingUpdates[0].t <= this.currentTime) {
U8(this.memory.buffer, 80, 32).set(U8(this.pendingUpdates.shift().r));
}
let u32Mem = new Uint32Array(this.memory.buffer); let u32Mem = new Uint32Array(this.memory.buffer);
u32Mem[16] = Date.now() - this.startTime; u32Mem[16] = this.currentTime;
let channels = outputs[0]; let channels = outputs[0];
let index = this.sampleIndex; let index = this.sampleIndex;
let numSamples = channels[0].length; let numSamples = channels[0].length;
@@ -66,6 +84,7 @@ class APU extends AudioWorkletProcessor {
channels[1][i] = this.snd(index++); channels[1][i] = this.snd(index++);
} }
this.sampleIndex = index & 0xffffffff; this.sampleIndex = index & 0xffffffff;
this.currentTime += numSamples / 44.1;
} }
return true; return true;

View File

@@ -10,7 +10,7 @@
</head> </head>
<body> <body>
<div id="uw8"> <div id="uw8">
<a href="https://exoticorn.github.io/microw8">MicroW8</a> 0.2.0-rc1 <a href="https://exoticorn.github.io/microw8">MicroW8</a> 0.2.0-rc3
</div> </div>
<div id="centered"> <div id="centered">
<canvas class="screen" id="screen" width="320" height="240"> <canvas class="screen" id="screen" width="320" height="240">

View File

@@ -107,7 +107,7 @@ export default function MicroW8(screen, config = {}) {
audioContext.close(); audioContext.close();
keepRunning = false; keepRunning = false;
abortController.abort(); abortController.abort();
} };
let cartridgeSize = data.byteLength; let cartridgeSize = data.byteLength;
@@ -206,6 +206,16 @@ export default function MicroW8(screen, config = {}) {
importObject.env['reserved' + i] = () => { }; importObject.env['reserved' + i] = () => { };
} }
let logLine = '';
importObject.env['logChar'] = (c) => {
if(c == 10) {
console.log(logLine);
logLine = '';
} else {
logLine += String.fromCharCode(c);
}
};
for (let i = 0; i < 16; ++i) { for (let i = 0; i < 16; ++i) {
importObject.env['g_reserved' + i] = 0; importObject.env['g_reserved' + i] = 0;
} }
@@ -232,7 +242,6 @@ export default function MicroW8(screen, config = {}) {
let startTime = Date.now(); let startTime = Date.now();
const timePerFrame = 1000 / 60; const timePerFrame = 1000 / 60;
let nextFrame = startTime;
audioNode.connect(audioContext.destination); audioNode.connect(audioContext.destination);
@@ -244,18 +253,16 @@ export default function MicroW8(screen, config = {}) {
isPaused = false; isPaused = false;
audioContext.resume(); audioContext.resume();
startTime += now - pauseTime; startTime += now - pauseTime;
audioNode.port.postMessage(startTime);
} else { } else {
isPaused = true; isPaused = true;
audioContext.suspend(); audioContext.suspend();
pauseTime = now; pauseTime = now;
audioNode.port.postMessage(0);
} }
}; };
window.addEventListener('focus', () => updateVisibility(true), { signal: abortController.signal }); window.addEventListener('focus', () => updateVisibility(true), { signal: abortController.signal });
window.addEventListener('blur', () => updateVisibility(false), { signal: abortController.signal }); window.addEventListener('blur', () => updateVisibility(false), { signal: abortController.signal });
updateVisibility(document.hasFocus()); updateVisibility(document.hasFocus());
function mainloop() { function mainloop() {
if (!keepRunning) { if (!keepRunning) {
return; return;
@@ -263,6 +270,7 @@ export default function MicroW8(screen, config = {}) {
try { try {
let restart = false; let restart = false;
let thisFrame;
if (!isPaused) { if (!isPaused) {
let gamepads = navigator.getGamepads(); let gamepads = navigator.getGamepads();
let gamepad = 0; let gamepad = 0;
@@ -291,7 +299,8 @@ export default function MicroW8(screen, config = {}) {
} }
let u32Mem = U32(memory.buffer); let u32Mem = U32(memory.buffer);
u32Mem[16] = Date.now() - startTime; let time = Date.now() - startTime;
u32Mem[16] = time;
u32Mem[17] = pad | gamepad; u32Mem[17] = pad | gamepad;
if(instance.exports.upd) { if(instance.exports.upd) {
instance.exports.upd(); instance.exports.upd();
@@ -300,21 +309,26 @@ export default function MicroW8(screen, config = {}) {
let soundRegisters = new ArrayBuffer(32); let soundRegisters = new ArrayBuffer(32);
U8(soundRegisters).set(U8(memory.buffer, 80, 32)); U8(soundRegisters).set(U8(memory.buffer, 80, 32));
audioNode.port.postMessage(soundRegisters, [soundRegisters]); audioNode.port.postMessage({t: time, r: soundRegisters}, [soundRegisters]);
let palette = U32(memory.buffer, 0x13000, 1024); let palette = U32(memory.buffer, 0x13000, 1024);
for (let i = 0; i < 320 * 240; ++i) { for (let i = 0; i < 320 * 240; ++i) {
buffer[i] = palette[memU8[i + 120]] | 0xff000000; buffer[i] = palette[memU8[i + 120]] | 0xff000000;
} }
canvasCtx.putImageData(imageData, 0, 0); canvasCtx.putImageData(imageData, 0, 0);
let timeOffset = ((time * 6) % 100 - 50) / 6;
thisFrame = startTime + time - timeOffset / 8;
} else {
thisFrame = Date.now();
} }
let now = Date.now(); let now = Date.now();
nextFrame = Math.max(nextFrame + timePerFrame, now); let nextFrame = Math.max(thisFrame + timePerFrame, now);
if (restart) { if (restart) {
runModule(currentData); runModule(currentData);
} else { } else {
window.setTimeout(mainloop, Math.round(nextFrame - now)) window.setTimeout(mainloop, nextFrame - now)
} }
} catch (err) { } catch (err) {
config.setMessage(cartridgeSize, err.toString()); config.setMessage(cartridgeSize, err.toString());