mirror of
https://github.com/exoticorn/microw8.git
synced 2026-01-21 03:36:41 +01:00
Compare commits
4 Commits
e05701300c
...
sound
| Author | SHA1 | Date | |
|---|---|---|---|
| 4c82f4ad02 | |||
| 4dd8c3b029 | |||
| 2bf8938183 | |||
| 491bf88ade |
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;
|
||||||
|
|||||||
@@ -43,27 +43,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,120 @@ 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.
|
||||||
|
|
||||||
|
### 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.0rc1/index.html
Normal file
1
site/static/v0.2.0rc1/index.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -91,7 +91,6 @@ impl MicroW8 {
|
|||||||
struct Uw8Sound {
|
struct Uw8Sound {
|
||||||
stream: cpal::Stream,
|
stream: cpal::Stream,
|
||||||
tx: mpsc::SyncSender<[u8; 32]>,
|
tx: mpsc::SyncSender<[u8; 32]>,
|
||||||
rx: mpsc::Receiver<[u8; 32]>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::Runtime for MicroW8 {
|
impl super::Runtime for MicroW8 {
|
||||||
@@ -261,7 +260,6 @@ impl super::Runtime for MicroW8 {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let (tx, rx) = mpsc::sync_channel::<[u8; 32]>(1);
|
let (tx, rx) = mpsc::sync_channel::<[u8; 32]>(1);
|
||||||
let (back_tx, back_rx) = mpsc::sync_channel::<[u8; 32]>(2);
|
|
||||||
|
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
|
|
||||||
@@ -270,10 +268,8 @@ impl super::Runtime for MicroW8 {
|
|||||||
device.build_output_stream(
|
device.build_output_stream(
|
||||||
&config,
|
&config,
|
||||||
move |buffer: &mut [f32], _| {
|
move |buffer: &mut [f32], _| {
|
||||||
if let Ok(mut regs) = rx.try_recv() {
|
if let Ok(regs) = rx.try_recv() {
|
||||||
memory.write(&mut store, 80, ®s).unwrap();
|
memory.write(&mut store, 80, ®s).unwrap();
|
||||||
memory.read(&mut store, 0x12c80, &mut regs).unwrap();
|
|
||||||
back_tx.send(regs).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -293,11 +289,7 @@ impl super::Runtime for MicroW8 {
|
|||||||
)?
|
)?
|
||||||
};
|
};
|
||||||
|
|
||||||
Uw8Sound {
|
Uw8Sound { stream, tx }
|
||||||
stream,
|
|
||||||
tx,
|
|
||||||
rx: back_rx,
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
sound.stream.play()?;
|
sound.stream.play()?;
|
||||||
@@ -319,13 +311,6 @@ impl super::Runtime for MicroW8 {
|
|||||||
fn run_frame(&mut self) -> Result<()> {
|
fn run_frame(&mut self) -> Result<()> {
|
||||||
let mut result = Ok(());
|
let mut result = Ok(());
|
||||||
if let Some(mut instance) = self.instance.take() {
|
if let Some(mut instance) = self.instance.take() {
|
||||||
while let Ok(regs) = instance.sound.rx.try_recv() {
|
|
||||||
instance
|
|
||||||
.memory
|
|
||||||
.write(&mut instance.store, 0x12c80, ®s)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let time = instance.start_time.elapsed().as_millis() as i32;
|
let time = instance.start_time.elapsed().as_millis() as i32;
|
||||||
let mut gamepad: u32 = 0;
|
let mut gamepad: u32 = 0;
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ class APU extends AudioWorkletProcessor {
|
|||||||
this.port.onmessage = (ev) => {
|
this.port.onmessage = (ev) => {
|
||||||
if(this.memory) {
|
if(this.memory) {
|
||||||
if(isNaN(ev.data)) {
|
if(isNaN(ev.data)) {
|
||||||
let data = U8(ev.data);
|
U8(this.memory.buffer, 80, 32).set(U8(ev.data));
|
||||||
U8(this.memory.buffer, 80, 32).set(data);
|
|
||||||
data.set(U8(this.memory.buffer, 0x12c80, 32));
|
|
||||||
this.port.postMessage(ev.data);
|
|
||||||
} else {
|
} else {
|
||||||
this.startTime = ev.data;
|
this.startTime = ev.data;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -211,13 +214,7 @@ export default function MicroW8(screen, config = {}) {
|
|||||||
|
|
||||||
let platform_data = await loadModuleURL(platformUrl);
|
let platform_data = await loadModuleURL(platformUrl);
|
||||||
|
|
||||||
audioNode.port.onmessage = (e) => {
|
audioNode.port.onmessage = (e) => updateAudioReady(e.data);
|
||||||
if(isNaN(e.data)) {
|
|
||||||
U8(memory.buffer, 0x12c80, 32).set(U8(e.data));
|
|
||||||
} else {
|
|
||||||
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);
|
||||||
@@ -340,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