mirror of
https://github.com/exoticorn/microw8.git
synced 2026-01-20 19:26:43 +01:00
Compare commits
10 Commits
61941bceeb
...
v0.2.0-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
| c42a484adb | |||
| 3a5f2bf865 | |||
| 2dee1b30a4 | |||
| 42f7887ab2 | |||
| 4c82f4ad02 | |||
| 4dd8c3b029 | |||
| 2bf8938183 | |||
| 491bf88ade | |||
| e05701300c | |||
| df0c169d54 |
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
|
||||||
|
|||||||
554
Cargo.lock
generated
554
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,19 +1,19 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "uw8"
|
name = "uw8"
|
||||||
version = "0.1.2"
|
version = "0.2.0-rc1"
|
||||||
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.35.3", optional = true }
|
wasmtime = { version = "0.35.3", optional = true }
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
minifb = { version = "0.22", default-features = false, features = ["x11"] }
|
minifb = { version = "0.22", default-features = false, features = ["x11"], optional = true }
|
||||||
notify = "4"
|
notify = "4"
|
||||||
pico-args = "0.4"
|
pico-args = "0.4"
|
||||||
curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "aac7bbd" }
|
curlywas = { git = "https://github.com/exoticorn/curlywas.git", rev = "aac7bbd" }
|
||||||
@@ -25,4 +25,5 @@ tokio = { version = "1.17.0", features = ["sync", "rt"], optional = true }
|
|||||||
tokio-stream = { version = "0.1.8", features = ["sync"], optional = true }
|
tokio-stream = { version = "0.1.8", features = ["sync"], optional = true }
|
||||||
webbrowser = { version = "0.6.0", optional = true }
|
webbrowser = { version = "0.6.0", optional = true }
|
||||||
ansi_term = "0.12.1"
|
ansi_term = "0.12.1"
|
||||||
cpal = "0.13.5"
|
cpal = { version = "0.13.5", optional = true }
|
||||||
|
rubato = { version = "0.11.0", optional = true }
|
||||||
@@ -20,10 +20,10 @@ data 0x20000 {
|
|||||||
0x44, 0x0, 0x0, 0x47, 0x49, 0x47, 0x45, 0x41,
|
0x44, 0x0, 0x0, 0x47, 0x49, 0x47, 0x45, 0x41,
|
||||||
0x44, 0x0, 0x0, 0x0, 0x42, 0x0, 0x0, 0x0,
|
0x44, 0x0, 0x0, 0x0, 0x42, 0x0, 0x0, 0x0,
|
||||||
|
|
||||||
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0,
|
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0x38,
|
||||||
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0,
|
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0x38,
|
||||||
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0,
|
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0x38,
|
||||||
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0,
|
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0x38,
|
||||||
|
|
||||||
0x2a, 0x0, 0x0, 0x0, 0x2d, 0x0, 0x0, 0x0,
|
0x2a, 0x0, 0x0, 0x0, 0x2d, 0x0, 0x0, 0x0,
|
||||||
0x2c, 0x0, 0x28, 0x0, 0x2a, 0x0, 0x0, 0x0,
|
0x2c, 0x0, 0x28, 0x0, 0x2a, 0x0, 0x0, 0x0,
|
||||||
|
|||||||
Binary file not shown.
@@ -16,7 +16,7 @@ export fn gesSnd(t: i32) -> f32 {
|
|||||||
let i: i32;
|
let i: i32;
|
||||||
loop clearLoop {
|
loop clearLoop {
|
||||||
(baseAddr + i)!GesBufferOffset = 0;
|
(baseAddr + i)!GesBufferOffset = 0;
|
||||||
branch_if (i := i + 4) < 128*8: clearLoop;
|
branch_if (i := i + 4) < 128*4: clearLoop;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ch: i32;
|
let ch: i32;
|
||||||
|
|||||||
@@ -29,6 +29,19 @@ Examplers for older versions:
|
|||||||
|
|
||||||
## Versions
|
## Versions
|
||||||
|
|
||||||
|
### v0.2.0-rc1
|
||||||
|
|
||||||
|
* [Web runtime](v0.2.0-rc1)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
* [add sound support](docs#sound)
|
||||||
|
* "integer constant cast to float" literal syntax in CurlyWas (ex. `1_f` is equivalent to `1 as f32`)
|
||||||
|
|
||||||
|
Known issues:
|
||||||
|
|
||||||
|
* timing accuracy/update frequency of sound support currently depends on sound buffer size
|
||||||
|
|
||||||
### v0.1.2
|
### v0.1.2
|
||||||
|
|
||||||
* [Web runtime](v0.1.2)
|
* [Web runtime](v0.1.2)
|
||||||
@@ -43,27 +56,6 @@ Changes:
|
|||||||
* CurlyWas: implement support for constants
|
* CurlyWas: implement support for constants
|
||||||
* fix crash when trying to draw zero sized line
|
* fix crash when trying to draw zero sized line
|
||||||
|
|
||||||
### v0.1.1
|
### Older versions
|
||||||
|
|
||||||
* [Web runtime](v0.1.1)
|
[Find older versions here.](versions)
|
||||||
* [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)
|
|
||||||
@@ -23,9 +23,7 @@ The memory has to be imported as `env` `memory` and has a maximum size of 256kb
|
|||||||
00070-00078: reserved
|
00070-00078: reserved
|
||||||
00078-12c78: frame buffer
|
00078-12c78: frame buffer
|
||||||
12c78-12c7c: sound registers/work area base address (for sndGes function)
|
12c78-12c7c: sound registers/work area base address (for sndGes function)
|
||||||
12c7c-12c80: reserved
|
12c7c-13000: reserved
|
||||||
12c80-12ca0: sound data (synced from sound thread)
|
|
||||||
12ca0-13000: reserved
|
|
||||||
13000-13400: palette
|
13000-13400: palette
|
||||||
13400-13c00: font
|
13400-13c00: font
|
||||||
13c00-14000: reserved
|
13c00-14000: reserved
|
||||||
@@ -234,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 |
|
||||||
@@ -276,34 +275,126 @@ Sets the cursor position. In normal mode `x` and `y` are multiplied by 8 to get
|
|||||||
|
|
||||||
## Sound
|
## 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.
|
||||||
|
|
||||||
```
|
```
|
||||||
Per channel:
|
00 - CTRL0
|
||||||
|
06 - CTRL1
|
||||||
|
0c - CTRL2
|
||||||
|
12 - CTRL3
|
||||||
|
| 7 6 | 5 | 4 | 3 2 | 1 | 0 |
|
||||||
|
| wave | ring | wide | filter | trigger | note on |
|
||||||
|
|
||||||
00 : CTRL - wave form, ring, sync, filter send, trigger
|
note on: stay in decay/sustain part of envelope
|
||||||
bit 0: note on flag
|
trigger: the attack part of the envlope is triggered when either this changes
|
||||||
bit 1: note trigger
|
or note on is changed from 0 to 1.
|
||||||
bit 2,3: filter 0,1 send
|
filter : 0 - no filter
|
||||||
bit 6,7: wave form (rect, saw, tri, noise)
|
1 - fixed 6db 1-pole filter with cutoff two octaves above note
|
||||||
01 : PULS - pulse width
|
2 - programmable filter 0
|
||||||
02 : FINE - fine tuning
|
3 - programmable filter 1
|
||||||
03 : NOTE - note
|
wide : use wide stereo panning
|
||||||
04 : ENVA - attack, decay
|
ring : ring modulate with triangle wave at frequency of previous channel
|
||||||
05 : ENVR - sustain, release
|
wave : 0 - rectangle
|
||||||
|
1 - saw
|
||||||
|
2 - triangle
|
||||||
|
3 - noise
|
||||||
|
|
||||||
50-56: channel 0
|
01 - PULS0
|
||||||
56-5b: channel 1
|
07 - PULS1
|
||||||
5c-61: channel 2
|
0d - PULS2
|
||||||
62-67: channel 3
|
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)
|
||||||
|
|
||||||
68: VO01 - volumes channel 0&1
|
02 - FINE0
|
||||||
69: VO23 - volumes channel 2&3
|
08 - FINE1
|
||||||
|
0e - FINE2
|
||||||
|
14 - FINE3
|
||||||
|
Fractional note
|
||||||
|
|
||||||
6a : FCTR 0 - type, resonance
|
03 - NOTE0
|
||||||
6b : FCTR 1 - type, resonance
|
09 - NOTE1
|
||||||
6c : FFIN 0 - cutoff fine tuning
|
0f - NOTE2
|
||||||
6d : FNOT 0 - cutoff note
|
15 - NOTE3
|
||||||
6e : FFIN 1 - cutoff fine tuning
|
Note, 69 = A4
|
||||||
6f : FNOT 1 - cutoff note
|
|
||||||
|
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
|
||||||
|
|||||||
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.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
12
src/main.rs
12
src/main.rs
@@ -16,7 +16,7 @@ fn main() -> Result<()> {
|
|||||||
let mut args = Arguments::from_env();
|
let mut args = Arguments::from_env();
|
||||||
|
|
||||||
// try to enable ansi support in win10 cmd shell
|
// try to enable ansi support in win10 cmd shell
|
||||||
#[cfg(target_os="windows")]
|
#[cfg(target_os = "windows")]
|
||||||
let _ = ansi_term::enable_ansi_support();
|
let _ = ansi_term::enable_ansi_support();
|
||||||
|
|
||||||
match args.subcommand()?.as_deref() {
|
match args.subcommand()?.as_deref() {
|
||||||
@@ -79,6 +79,8 @@ fn run(mut args: Arguments) -> Result<()> {
|
|||||||
#[cfg(not(feature = "native"))]
|
#[cfg(not(feature = "native"))]
|
||||||
let run_browser = args.contains(["-b", "--browser"]) || true;
|
let run_browser = args.contains(["-b", "--browser"]) || true;
|
||||||
|
|
||||||
|
let disable_audio = args.contains(["-m", "--disable-audio"]);
|
||||||
|
|
||||||
let filename = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
let filename = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
||||||
|
|
||||||
let mut watcher = uw8::FileWatcher::new()?;
|
let mut watcher = uw8::FileWatcher::new()?;
|
||||||
@@ -89,7 +91,13 @@ fn run(mut args: Arguments) -> Result<()> {
|
|||||||
#[cfg(not(feature = "native"))]
|
#[cfg(not(feature = "native"))]
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
#[cfg(feature = "native")]
|
#[cfg(feature = "native")]
|
||||||
Box::new(MicroW8::new()?)
|
{
|
||||||
|
let mut microw8 = MicroW8::new()?;
|
||||||
|
if disable_audio {
|
||||||
|
microw8.disable_audio();
|
||||||
|
}
|
||||||
|
Box::new(microw8)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
#[cfg(not(feature = "browser"))]
|
#[cfg(not(feature = "browser"))]
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -5,6 +5,7 @@ use std::{thread, time::Instant};
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use cpal::traits::*;
|
use cpal::traits::*;
|
||||||
use minifb::{Key, Window, WindowOptions};
|
use minifb::{Key, Window, WindowOptions};
|
||||||
|
use rubato::Resampler;
|
||||||
use wasmtime::{
|
use wasmtime::{
|
||||||
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
|
Engine, GlobalType, Memory, MemoryType, Module, Mutability, Store, TypedFunc, ValType,
|
||||||
};
|
};
|
||||||
@@ -27,6 +28,7 @@ pub struct MicroW8 {
|
|||||||
window_buffer: Vec<u32>,
|
window_buffer: Vec<u32>,
|
||||||
instance: Option<UW8Instance>,
|
instance: Option<UW8Instance>,
|
||||||
timeout: u32,
|
timeout: u32,
|
||||||
|
disable_audio: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UW8Instance {
|
struct UW8Instance {
|
||||||
@@ -37,7 +39,7 @@ struct UW8Instance {
|
|||||||
start_time: Instant,
|
start_time: Instant,
|
||||||
module: Vec<u8>,
|
module: Vec<u8>,
|
||||||
watchdog: Arc<Mutex<UW8WatchDog>>,
|
watchdog: Arc<Mutex<UW8WatchDog>>,
|
||||||
sound: Uw8Sound,
|
sound: Option<Uw8Sound>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for UW8Instance {
|
impl Drop for UW8Instance {
|
||||||
@@ -77,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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,11 +89,10 @@ impl MicroW8 {
|
|||||||
*v = 0;
|
*v = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
struct Uw8Sound {
|
pub fn disable_audio(&mut self) {
|
||||||
stream: cpal::Stream,
|
self.disable_audio = true;
|
||||||
tx: mpsc::SyncSender<[u8; 32]>,
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::Runtime for MicroW8 {
|
impl super::Runtime for MicroW8 {
|
||||||
@@ -126,60 +128,8 @@ impl super::Runtime for MicroW8 {
|
|||||||
let module_length = load_uw8.call(&mut store, module_data.len() as i32)? as u32 as usize;
|
let module_length = load_uw8.call(&mut store, module_data.len() as i32)? as u32 as usize;
|
||||||
let module = wasmtime::Module::new(&self.engine, &memory.data(&store)[..module_length])?;
|
let module = wasmtime::Module::new(&self.engine, &memory.data(&store)[..module_length])?;
|
||||||
|
|
||||||
fn add_native_functions(
|
|
||||||
linker: &mut wasmtime::Linker<()>,
|
|
||||||
store: &mut wasmtime::Store<()>,
|
|
||||||
) -> Result<()> {
|
|
||||||
linker.func_wrap("env", "acos", |v: f32| v.acos())?;
|
|
||||||
linker.func_wrap("env", "asin", |v: f32| v.asin())?;
|
|
||||||
linker.func_wrap("env", "atan", |v: f32| v.atan())?;
|
|
||||||
linker.func_wrap("env", "atan2", |x: f32, y: f32| x.atan2(y))?;
|
|
||||||
linker.func_wrap("env", "cos", |v: f32| v.cos())?;
|
|
||||||
linker.func_wrap("env", "exp", |v: f32| v.exp())?;
|
|
||||||
linker.func_wrap("env", "log", |v: f32| v.ln())?;
|
|
||||||
linker.func_wrap("env", "sin", |v: f32| v.sin())?;
|
|
||||||
linker.func_wrap("env", "tan", |v: f32| v.tan())?;
|
|
||||||
linker.func_wrap("env", "pow", |a: f32, b: f32| a.powf(b))?;
|
|
||||||
for i in 10..64 {
|
|
||||||
linker.func_wrap("env", &format!("reserved{}", i), || {})?;
|
|
||||||
}
|
|
||||||
for i in 0..16 {
|
|
||||||
linker.define(
|
|
||||||
"env",
|
|
||||||
&format!("g_reserved{}", i),
|
|
||||||
wasmtime::Global::new(
|
|
||||||
&mut *store,
|
|
||||||
GlobalType::new(ValType::I32, Mutability::Const),
|
|
||||||
0.into(),
|
|
||||||
)?,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
add_native_functions(&mut linker, &mut store)?;
|
add_native_functions(&mut linker, &mut store)?;
|
||||||
|
|
||||||
fn instantiate_platform(
|
|
||||||
linker: &mut wasmtime::Linker<()>,
|
|
||||||
store: &mut wasmtime::Store<()>,
|
|
||||||
platform_module: &wasmtime::Module,
|
|
||||||
) -> Result<wasmtime::Instance> {
|
|
||||||
let platform_instance = linker.instantiate(&mut *store, &platform_module)?;
|
|
||||||
|
|
||||||
for export in platform_instance.exports(&mut *store) {
|
|
||||||
linker.define(
|
|
||||||
"env",
|
|
||||||
export.name(),
|
|
||||||
export
|
|
||||||
.into_func()
|
|
||||||
.expect("platform surely only exports functions"),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(platform_instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
let platform_instance = instantiate_platform(&mut linker, &mut store, &platform_module)?;
|
let platform_instance = instantiate_platform(&mut linker, &mut store, &platform_module)?;
|
||||||
|
|
||||||
let watchdog = Arc::new(Mutex::new(UW8WatchDog {
|
let watchdog = Arc::new(Mutex::new(UW8WatchDog {
|
||||||
@@ -215,76 +165,21 @@ impl super::Runtime for MicroW8 {
|
|||||||
let end_frame = platform_instance.get_typed_func::<(), (), _>(&mut store, "endFrame")?;
|
let end_frame = platform_instance.get_typed_func::<(), (), _>(&mut store, "endFrame")?;
|
||||||
let update = instance.get_typed_func::<(), (), _>(&mut store, "upd").ok();
|
let update = instance.get_typed_func::<(), (), _>(&mut store, "upd").ok();
|
||||||
|
|
||||||
let sound = {
|
let sound = if self.disable_audio {
|
||||||
let mut store = wasmtime::Store::new(&self.engine, ());
|
None
|
||||||
|
} else {
|
||||||
let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
|
match init_sound(&self.engine, &platform_module, &module) {
|
||||||
|
Ok(sound) => {
|
||||||
let mut linker = wasmtime::Linker::new(&self.engine);
|
sound.stream.play()?;
|
||||||
linker.define("env", "memory", memory)?;
|
Some(sound)
|
||||||
add_native_functions(&mut linker, &mut store)?;
|
|
||||||
|
|
||||||
let platform_instance =
|
|
||||||
instantiate_platform(&mut linker, &mut store, &platform_module)?;
|
|
||||||
let instance = linker.instantiate(&mut store, &module)?;
|
|
||||||
|
|
||||||
let snd = instance
|
|
||||||
.get_typed_func::<(i32,), f32, _>(&mut store, "snd")
|
|
||||||
.or_else(|_| {
|
|
||||||
platform_instance.get_typed_func::<(i32,), f32, _>(&mut store, "gesSnd")
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let host = cpal::default_host();
|
|
||||||
let device = host
|
|
||||||
.default_output_device()
|
|
||||||
.ok_or_else(|| anyhow!("No audio output device available"))?;
|
|
||||||
let config = device
|
|
||||||
.supported_output_configs()?
|
|
||||||
.find(|config| {
|
|
||||||
config.min_sample_rate().0 <= 44100
|
|
||||||
&& config.max_sample_rate().0 >= 44100
|
|
||||||
&& config.channels() == 2
|
|
||||||
&& config.sample_format() == cpal::SampleFormat::F32
|
|
||||||
})
|
|
||||||
.ok_or_else(|| anyhow!("Could not find 44.1kHz float config"))?;
|
|
||||||
let config = config.with_sample_rate(cpal::SampleRate(44100));
|
|
||||||
let buffer_size = match *config.buffer_size() {
|
|
||||||
cpal::SupportedBufferSize::Unknown => cpal::BufferSize::Default,
|
|
||||||
cpal::SupportedBufferSize::Range { min, max } => {
|
|
||||||
cpal::BufferSize::Fixed(256.max(min).min(max))
|
|
||||||
}
|
}
|
||||||
};
|
Err(err) => {
|
||||||
let config = cpal::StreamConfig {
|
eprintln!("Failed to init sound: {}", err);
|
||||||
buffer_size,
|
None
|
||||||
..config.config()
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
let (tx, rx) = mpsc::sync_channel::<[u8; 32]>(1);
|
|
||||||
|
|
||||||
let mut sample_index = 0;
|
|
||||||
let stream = {
|
|
||||||
device.build_output_stream(
|
|
||||||
&config,
|
|
||||||
move |buffer: &mut [f32], _| {
|
|
||||||
if let Ok(regs) = rx.try_recv() {
|
|
||||||
memory.write(&mut store, 80, ®s).unwrap();
|
|
||||||
}
|
|
||||||
for v in buffer {
|
|
||||||
*v = snd.call(&mut store, (sample_index,)).unwrap_or(0.0);
|
|
||||||
sample_index = sample_index.wrapping_add(1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
move |err| {
|
|
||||||
dbg!(err);
|
|
||||||
},
|
|
||||||
)?
|
|
||||||
};
|
|
||||||
|
|
||||||
Uw8Sound { stream, tx }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
sound.stream.play()?;
|
|
||||||
|
|
||||||
self.instance = Some(UW8Instance {
|
self.instance = Some(UW8Instance {
|
||||||
store,
|
store,
|
||||||
memory,
|
memory,
|
||||||
@@ -336,7 +231,9 @@ impl super::Runtime for MicroW8 {
|
|||||||
|
|
||||||
let mut sound_regs = [0u8; 32];
|
let mut sound_regs = [0u8; 32];
|
||||||
sound_regs.copy_from_slice(&memory[80..112]);
|
sound_regs.copy_from_slice(&memory[80..112]);
|
||||||
instance.sound.tx.send(sound_regs)?;
|
if let Some(ref sound) = instance.sound {
|
||||||
|
sound.tx.send(sound_regs)?;
|
||||||
|
}
|
||||||
|
|
||||||
let framebuffer = &memory[120..(120 + 320 * 240)];
|
let framebuffer = &memory[120..(120 + 320 * 240)];
|
||||||
let palette = &memory[0x13000..];
|
let palette = &memory[0x13000..];
|
||||||
@@ -362,3 +259,210 @@ 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), || {})?;
|
||||||
|
}
|
||||||
|
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 Uw8Sound {
|
||||||
|
stream: cpal::Stream,
|
||||||
|
tx: mpsc::SyncSender<[u8; 32]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_sound(
|
||||||
|
engine: &wasmtime::Engine,
|
||||||
|
platform_module: &wasmtime::Module,
|
||||||
|
module: &wasmtime::Module,
|
||||||
|
) -> Result<Uw8Sound> {
|
||||||
|
let mut store = wasmtime::Store::new(engine, ());
|
||||||
|
|
||||||
|
let memory = wasmtime::Memory::new(&mut store, MemoryType::new(4, Some(4)))?;
|
||||||
|
|
||||||
|
let mut linker = wasmtime::Linker::new(engine);
|
||||||
|
linker.define("env", "memory", memory)?;
|
||||||
|
add_native_functions(&mut linker, &mut store)?;
|
||||||
|
|
||||||
|
let platform_instance = instantiate_platform(&mut linker, &mut store, platform_module)?;
|
||||||
|
let instance = linker.instantiate(&mut store, module)?;
|
||||||
|
|
||||||
|
let snd = instance
|
||||||
|
.get_typed_func::<(i32,), f32, _>(&mut store, "snd")
|
||||||
|
.or_else(|_| platform_instance.get_typed_func::<(i32,), f32, _>(&mut store, "gesSnd"))?;
|
||||||
|
|
||||||
|
let host = cpal::default_host();
|
||||||
|
let device = host
|
||||||
|
.default_output_device()
|
||||||
|
.ok_or_else(|| anyhow!("No audio output device available"))?;
|
||||||
|
let mut configs: Vec<_> = device
|
||||||
|
.supported_output_configs()?
|
||||||
|
.filter(|config| {
|
||||||
|
config.channels() == 2 && config.sample_format() == cpal::SampleFormat::F32
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
configs.sort_by_key(|config| {
|
||||||
|
let rate = 44100
|
||||||
|
.max(config.min_sample_rate().0)
|
||||||
|
.min(config.max_sample_rate().0);
|
||||||
|
if rate >= 44100 {
|
||||||
|
rate - 44100
|
||||||
|
} else {
|
||||||
|
(44100 - rate) * 1000
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let config = configs
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| anyhow!("Could not find float output config"))?;
|
||||||
|
let sample_rate = cpal::SampleRate(44100)
|
||||||
|
.max(config.min_sample_rate())
|
||||||
|
.max(config.max_sample_rate());
|
||||||
|
let config = config.with_sample_rate(sample_rate);
|
||||||
|
let buffer_size = match *config.buffer_size() {
|
||||||
|
cpal::SupportedBufferSize::Unknown => cpal::BufferSize::Default,
|
||||||
|
cpal::SupportedBufferSize::Range { min, max } => {
|
||||||
|
cpal::BufferSize::Fixed(256.max(min).min(max))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let config = cpal::StreamConfig {
|
||||||
|
buffer_size,
|
||||||
|
..config.config()
|
||||||
|
};
|
||||||
|
|
||||||
|
let sample_rate = config.sample_rate.0 as usize;
|
||||||
|
|
||||||
|
let (tx, rx) = mpsc::sync_channel::<[u8; 32]>(1);
|
||||||
|
|
||||||
|
let start_time = Instant::now();
|
||||||
|
|
||||||
|
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 stream = device.build_output_stream(
|
||||||
|
&config,
|
||||||
|
move |mut buffer: &mut [f32], _| {
|
||||||
|
if let Ok(regs) = rx.try_recv() {
|
||||||
|
memory.write(&mut store, 80, ®s).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let time = start_time.elapsed().as_millis() as i32;
|
||||||
|
let mem = memory.data_mut(&mut store);
|
||||||
|
mem[64..68].copy_from_slice(&time.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
move |err| {
|
||||||
|
dbg!(err);
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Uw8Sound { stream, tx })
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ class APU extends AudioWorkletProcessor {
|
|||||||
this.sampleIndex = 0;
|
this.sampleIndex = 0;
|
||||||
this.port.onmessage = (ev) => {
|
this.port.onmessage = (ev) => {
|
||||||
if(this.memory) {
|
if(this.memory) {
|
||||||
U8(this.memory.buffer, 80, 32).set(U8(ev.data));
|
if(isNaN(ev.data)) {
|
||||||
|
U8(this.memory.buffer, 80, 32).set(U8(ev.data));
|
||||||
|
} else {
|
||||||
|
this.startTime = ev.data;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.load(ev.data[0], ev.data[1]);
|
this.load(ev.data[0], ev.data[1]);
|
||||||
}
|
}
|
||||||
@@ -51,7 +55,9 @@ class APU extends AudioWorkletProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
process(inputs, outputs, parameters) {
|
process(inputs, outputs, parameters) {
|
||||||
if(this.snd) {
|
if(this.snd && this.startTime) {
|
||||||
|
let u32Mem = new Uint32Array(this.memory.buffer);
|
||||||
|
u32Mem[16] = Date.now() - this.startTime;
|
||||||
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;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="uw8">
|
<div id="uw8">
|
||||||
<a href="https://exoticorn.github.io/microw8">MicroW8</a> 0.1.2
|
<a href="https://exoticorn.github.io/microw8">MicroW8</a> 0.2.0-rc1
|
||||||
</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">
|
||||||
|
|||||||
@@ -90,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) {
|
||||||
@@ -97,7 +100,7 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
cancelFunction = null;
|
cancelFunction = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let audioContext = new AudioContext({sampleRate: 44100});
|
audioContext = new AudioContext({sampleRate: 44100});
|
||||||
let keepRunning = true;
|
let keepRunning = true;
|
||||||
let abortController = new AbortController();
|
let abortController = new AbortController();
|
||||||
cancelFunction = () => {
|
cancelFunction = () => {
|
||||||
@@ -114,7 +117,7 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await audioContext.audioWorklet.addModule(audioWorkletUrl);
|
await audioContext.audioWorklet.addModule(audioWorkletUrl);
|
||||||
let audioNode = new AudioNode(audioContext);
|
audioNode = new AudioNode(audioContext);
|
||||||
|
|
||||||
let audioReadyFlags = 0;
|
let audioReadyFlags = 0;
|
||||||
let audioReadyResolve;
|
let audioReadyResolve;
|
||||||
@@ -126,7 +129,6 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
audioReadyResolve = null;
|
audioReadyResolve = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
audioNode.port.onmessage = (e) => updateAudioReady(e.data);
|
|
||||||
let audioStateChange = () => {
|
let audioStateChange = () => {
|
||||||
if(audioContext.state == 'suspended') {
|
if(audioContext.state == 'suspended') {
|
||||||
if(config.startButton) {
|
if(config.startButton) {
|
||||||
@@ -211,6 +213,8 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
data = loadModuleData(data);
|
data = loadModuleData(data);
|
||||||
|
|
||||||
let platform_data = await loadModuleURL(platformUrl);
|
let platform_data = await loadModuleURL(platformUrl);
|
||||||
|
|
||||||
|
audioNode.port.onmessage = (e) => updateAudioReady(e.data);
|
||||||
audioNode.port.postMessage([platform_data, data]);
|
audioNode.port.postMessage([platform_data, data]);
|
||||||
|
|
||||||
let platform_instance = await instantiate(platform_data);
|
let platform_instance = await instantiate(platform_data);
|
||||||
@@ -240,10 +244,12 @@ 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 });
|
||||||
@@ -331,14 +337,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
|
||||||
});
|
});
|
||||||
|
|||||||
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