I’m not sure I would have taken the trouble of implementing sound in the simulator version of Quinti-Maze if I’d not already done it in 2020. I used it then to emulate the hardware for a Simon-style game I was implementing to experiment with a system for escape room props using MQTT. That experiment might make for an interesting future post.

To start, I defined the notes to play as an array of tuples, with elements for frequency, duration and delay.

pub const NOTES: &[(u32, u64, u64)] = &[
    (1000, 256, 0),
    (1000, 128, 10),
    (1000, 128, 10),
    (1333, 169, 10),
    (1000, 169, 10),
    (1333, 169, 10),
    (1667, 653, 10),
];

In order to bring some structure to the bits of Quinti-Maze that are different between the simulator and actual hardware I’ve introduced a PlatformSpecific trait. I’ll talk about that design more in a future post, but below is a simplified version of it and the simulator implementation.

pub trait PlatformSpecific: Default {
    fn play_victory_notes(&mut self);
}

struct SimPlatform {
    stream: OutputStream,
    stream_handle: OutputStreamHandle,
}

impl Default for SimPlatform {
    fn default() -> Self {
        let (stream, stream_handle) = OutputStream::try_default().expect("default sound output");
        Self {
            stream,
            stream_handle,
        }
    }
}

impl PlatformSpecific for SimPlatform {
    fn play_victory_notes(&mut self) {
        let sink = Sink::try_new(&self.stream_handle).expect("new sink");
        for (freq, duration, delay) in NOTES {
            let source = SineWave::new(*freq)
                .take_duration(Duration::from_millis(*duration))
                .amplify(0.20)
                .delay(Duration::from_millis(*delay));
            sink.append(source);
        }
        sink.sleep_until_end();
    }
}

Simulator playback depends on the rodio audio playback library.

Clicking the Apple II speaker at a particular frequency produces a square wave, which has that traditional early game machine sound. Rodio doesn’t have a square wave generator, so I’ve used the sin wave source as the next closest thing.

The rest is something that Rodio handles well; adding a source of frequency and duration to a sink. The delay was needed due to an interest wrinkle; there’s no inherent delay between sounds sent to a Rodio sink so the first three notes sounded like one long note. I guess it was the time it took to go back to the Applesoft interpreter and evaluate the next expression that provided the audible gap in 1982.