I’m currently at the Recurse Center in New York, basically having fun and learning a lot…ok
A few of us have been hacking at the Apple ][ in the space, and decided to make a web/rust/audio selfie studio maker, here’s what it looks like:
The Apple ][ can accept audio as data to write directly to memory. It also has a ‘high graphics mode’ which we can write directly to using the audio method. I basically just wanted a quick way to make drawings on my computer and put them onto the Apple ][… The memory mapping is crazy, idk what to say
Kevan Hollbach, also at RC, recently published bas2wav, a rust crate which converts BASIC programs to wav files to run on the Apple ][. It wasn’t too much of a leap for us to adapt the code to translate images and other arbitrary data to a wav file. We followed a wasm tutorial, and surprisingly it just worked - we could pass data from javascript to the rust code and get audio back, all client-side!!
Here’s a snippet from Kevan’s crate:
/// Three-quarters of the maximum voltage value, assuming each sample is i8.
const AMPLITUDE: f64 = i8::MAX as f64 * 3. / 4.;
fn sine_wave(wav: &mut Wav, freq: f64, dur: Duration, invert: bool) -> Result<()> {
let num_samples = dur.as_secs_f64() * wav.spec().sample_rate as f64;
for i in 0..num_samples as u32 {
let time = i as f64 / wav.spec().sample_rate as f64; // (in seconds)
let sign = if invert { -1. } else { 1. };
let sample = (time * freq * 2. * PI).sin() * sign; // [-1, 1]
wav.write_sample((sample * AMPLITUDE) as i8)?; // [i8::MIN, i8::MAX]
}
Ok(())
}
First we made a little pixel editor with html, envisioning a sort of MS Paint that exports to wav files for the Apple ][. If you upload an image, we convert the data to HSL, pick some lightness threshold with a slider, and use that to convert to black and white.
Whatever is on the canvas is mapped in an array to the correct Apple ][ graphics memory layout, then the most significant bit is discarded, then the 7 bit-memory-mapped pixel data is fed into the rust crate, and the audio that comes out of that is finally played using an <audio> element.
// I told you the memory mapping's crazy
function pixelsToMemory(pixelBytes) {
let mem = new Array(8192).fill(0);
let i = 0;
for (let band = 0; band < 3; band++) {
for (let block_row = 0; block_row < 8; block_row++) {
for (let pixel_row = 0; pixel_row < 8; pixel_row++) {
for (let block_column = 0; block_column < 40; block_column++) {
let mem_offset = band * 0x28 + block_row * 0x80 + pixel_row * 0x400 + block_column;
mem[mem_offset] = pixelBytes[i];
i++;
}
}
}
}
return mem;
}
Upload a picture, have fun! Make a drawing :) Oh yeah if you have access to an Apple ][, here are the steps: