PS1 homebrew, running in your browser. This player runs the compiled PS1 executable in the PSoXide emulator.
Focus the game to use the keyboard. Arrows: D-pad. F / G / H / X: Cross / Circle / Square / Triangle. Enter: Start. Sound starts muted; use the speaker button in the game. No touch controls.
Open player separately · Download EXE · Matching source · Builds and credits
Build and try it
With the SDK tools installed:
make disc EXAMPLE=hello-audio
Open build/examples/mipsel-sony-psx/release/hello-audio.cue. The example embeds its cooked .psau samples, so it needs no extra audio tracks. In the browser, start the player, enable sound with its speaker button, and focus the screen. F triggers jump, G coin, H punch and X swoosh. Arrow keys and Enter trigger other effects.
Load samples once
Before the render loop, the example initializes the SPU and parses each embedded Audio asset. It copies the asset's ADPCM payload into sound RAM, starting at byte address 0x1010:
let addr = SpuAddr::new(next_addr);
spu_driver.upload_adpcm(addr, audio.adpcm_bytes());
ch.voice.configure_sample(
addr, audio.sample_rate_hz(), ch.volume, Adsr::sample(),
);
next_addr += audio.adpcm_bytes().len() as u32;
Each channel gets a voice, sample address, sample rate, volume and envelope. Keep allocations aligned to the SPU's sample-address requirements and within its 512 KB sound RAM. A sound's bytes must remain available there while its voice plays.
Detect the press edge
Polling a held button every frame would restart a sound every frame. Instead, compare the current and previous button states:
let now = pad.is_held(ch.button);
let was = prev_pad.is_held(ch.button);
if now && !was {
on_mask |= ch.voice.mask();
}
Once the channels have been checked, start all newly triggered voices together:
if on_mask != 0 {
Voice::start(on_mask);
}
prev_pad = pad;
The controller guide explains this distinction between held input and a new press. In this example, releasing the button does not immediately cut a one-shot sample off. Pressing it again retriggers the same voice.
Use the screen to debug
The example flashes the background and a PLAYING label for a short fixed number of frames when a channel is triggered. The flash is trigger feedback; it doesn't measure whether the SPU voice is still audible. A working flash with no sound points to mute, output-device or SPU setup instead of the input path.
Try a change
Change a channel's Volume::linear ratio to reduce its level, then rebuild. Swap the button assignments for jump and coin. Hold a button: it should trigger once. Release and press again: it should retrigger. Press two mapped buttons together to exercise overlapping voices.
Credits and browser limits
The nine effects are from Kronbits Free SFX, recorded as CC0-1.0 in the repository's audio import report. The report lists source files and hashes for the cooked assets.
Browser audio can require a click inside the player before it starts. Check its mute button and your device's output volume. This player does not download the full demo disc or play CD music. Check the final mix on original hardware before treating emulator playback as a hardware result.