6 Commits

Author SHA1 Message Date
4c82f4ad02 add 0.2.0rc1 web runtime 2022-04-24 23:32:28 +02:00
4dd8c3b029 add audio track to recorded video 2022-04-24 23:17:58 +02:00
2bf8938183 remove back-channel from audio thread for now
It needs some more thought before committing to it.
2022-04-24 15:52:19 +02:00
491bf88ade add first version of sound doku 2022-04-24 00:01:54 +02:00
e05701300c implement backchannel from audio thread 2022-04-22 00:28:19 +02:00
df0c169d54 use the last byte 2022-04-20 23:19:26 +02:00
12 changed files with 867 additions and 3955 deletions

View File

@@ -20,10 +20,10 @@ data 0x20000 {
0x44, 0x0, 0x0, 0x47, 0x49, 0x47, 0x45, 0x41,
0x44, 0x0, 0x0, 0x0, 0x42, 0x0, 0x0, 0x0,
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0,
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0,
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0,
0x25, 0, 0x49, 0x25, 0x25, 0, 0x49, 0,
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,

Binary file not shown.

View File

@@ -16,7 +16,7 @@ export fn gesSnd(t: i32) -> f32 {
let i: i32;
loop clearLoop {
(baseAddr + i)!GesBufferOffset = 0;
branch_if (i := i + 4) < 128*8: clearLoop;
branch_if (i := i + 4) < 128*4: clearLoop;
}
let ch: i32;

View File

@@ -43,27 +43,6 @@ Changes:
* CurlyWas: implement support for constants
* fix crash when trying to draw zero sized line
### v0.1.1
### Older versions
* [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)
[Find older versions here.](versions)

View File

@@ -23,9 +23,7 @@ The memory has to be imported as `env` `memory` and has a maximum size of 256kb
00070-00078: reserved
00078-12c78: frame buffer
12c78-12c7c: sound registers/work area base address (for sndGes function)
12c7c-12c80: reserved
12c80-12ca0: sound data (synced from sound thread)
12ca0-13000: reserved
12c7c-13000: reserved
13000-13400: palette
13400-13c00: font
13c00-14000: reserved
@@ -234,7 +232,8 @@ Avoid the reserved control chars, they are currently NOPs but their behavior can
| 2-3 | - | Reserved |
| 4 | - | Switch to normal mode |
| 5 | - | Switch to graphics mode |
| 6-7 | - | Reserved |
| 6 | - | Reserved |
| 7 | - | Bell / trigger sound channel 0 |
| 8 | - | Move cursor left |
| 9 | - | Move cursor right |
| 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
### 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
bit 0: note on flag
bit 1: note trigger
bit 2,3: filter 0,1 send
bit 6,7: wave form (rect, saw, tri, noise)
01 : PULS - pulse width
02 : FINE - fine tuning
03 : NOTE - note
04 : ENVA - attack, decay
05 : ENVR - sustain, release
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
50-56: channel 0
56-5b: channel 1
5c-61: channel 2
62-67: channel 3
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)
68: VO01 - volumes channel 0&1
69: VO23 - volumes channel 2&3
02 - FINE0
08 - FINE1
0e - FINE2
14 - FINE3
Fractional note
6a : FCTR 0 - type, resonance
6b : FCTR 1 - type, resonance
6c : FFIN 0 - cutoff fine tuning
6d : FNOT 0 - cutoff note
6e : FFIN 1 - cutoff fine tuning
6f : FNOT 1 - cutoff 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

42
site/content/versions.md Normal file
View 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)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -261,6 +261,8 @@ impl super::Runtime for MicroW8 {
let (tx, rx) = mpsc::sync_channel::<[u8; 32]>(1);
let start_time = Instant::now();
let mut sample_index = 0;
let stream = {
device.build_output_stream(
@@ -269,6 +271,13 @@ impl super::Runtime for MicroW8 {
if let Ok(regs) = rx.try_recv() {
memory.write(&mut store, 80, &regs).unwrap();
}
{
let time = start_time.elapsed().as_millis() as i32;
let mem = memory.data_mut(&mut store);
mem[64..68].copy_from_slice(&time.to_le_bytes());
}
for v in buffer {
*v = snd.call(&mut store, (sample_index,)).unwrap_or(0.0);
sample_index = sample_index.wrapping_add(1);

View File

@@ -5,7 +5,11 @@ class APU extends AudioWorkletProcessor {
this.sampleIndex = 0;
this.port.onmessage = (ev) => {
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 {
this.load(ev.data[0], ev.data[1]);
}
@@ -51,7 +55,9 @@ class APU extends AudioWorkletProcessor {
}
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 index = this.sampleIndex;
let numSamples = channels[0].length;

View File

@@ -90,6 +90,9 @@ export default function MicroW8(screen, config = {}) {
keyboardElement.onkeydown = keyHandler;
keyboardElement.onkeyup = keyHandler;
}
let audioContext;
let audioNode;
async function runModule(data, keepUrl) {
if (cancelFunction) {
@@ -97,7 +100,7 @@ export default function MicroW8(screen, config = {}) {
cancelFunction = null;
}
let audioContext = new AudioContext({sampleRate: 44100});
audioContext = new AudioContext({sampleRate: 44100});
let keepRunning = true;
let abortController = new AbortController();
cancelFunction = () => {
@@ -114,7 +117,7 @@ export default function MicroW8(screen, config = {}) {
}
await audioContext.audioWorklet.addModule(audioWorkletUrl);
let audioNode = new AudioNode(audioContext);
audioNode = new AudioNode(audioContext);
let audioReadyFlags = 0;
let audioReadyResolve;
@@ -126,7 +129,6 @@ export default function MicroW8(screen, config = {}) {
audioReadyResolve = null;
}
};
audioNode.port.onmessage = (e) => updateAudioReady(e.data);
let audioStateChange = () => {
if(audioContext.state == 'suspended') {
if(config.startButton) {
@@ -211,6 +213,8 @@ export default function MicroW8(screen, config = {}) {
data = loadModuleData(data);
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);
@@ -240,10 +244,12 @@ export default function MicroW8(screen, config = {}) {
isPaused = false;
audioContext.resume();
startTime += now - pauseTime;
audioNode.port.postMessage(startTime);
} else {
isPaused = true;
audioContext.suspend();
pauseTime = now;
audioNode.port.postMessage(0);
}
};
window.addEventListener('focus', () => updateVisibility(true), { signal: abortController.signal });
@@ -331,14 +337,25 @@ export default function MicroW8(screen, config = {}) {
let videoRecorder;
let videoStartTime;
let videoAudioSourceNode;
let videoAudioStreamNode;
function recordVideo() {
if(videoRecorder) {
videoRecorder.stop();
videoRecorder = null;
videoAudioSourceNode.disconnect(videoAudioStreamNode);
videoAudioSourceNode = null;
videoAudioStreamNode = null;
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',
videoBitsPerSecond: 25000000
});

File diff suppressed because it is too large Load Diff