What this program does
Play CD-DA track 2 and expose playback controls through the pad. This requires a mixed-mode disc with an audio track; a bare EXE cannot supply that audio.
Build and run
Use the documented SDK checkout and tool setup. Run these commands from the repository root.
Build the executable, then master a mixed-mode image with your own sector-aligned raw CD-DA audio. Replace /path/to/track.raw with a prepared audio file; the packer does not convert a WAV or MP3 in this command.
make example EXAMPLE=hello-cdda
cargo run --locked --release -p mkisopsx -- \
--exe build/examples/mipsel-sony-psx/release/hello-cdda.exe \
--out build/examples/mipsel-sony-psx/release/hello-cdda.bin \
--cdda-track /path/to/track.raw
Open the resulting CUE with the BIN beside it. A generic make disc image has no audio track. See the audio cooker's source and formats when preparing your source audio.
For original hardware, read the burned-disc requirements and warning.
Complete source
Complete, unabridged files from SDK revision f2fec1c301f1. Build inside the repository checkout: the manifest uses relative SDK paths, and some programs embed assets from elsewhere in the repository.
Runtime dependencies: psx-font, psx-gpu, psx-hw, psx-io, psx-pad, psx-rt, psx-spu, psx-vram.
Cargo.toml · 26 lines
[package]
name = "hello-cdda"
version = "0.1.0"
edition = "2021"
license = "GPL-2.0-or-later"
[workspace]
[[bin]]
name = "hello-cdda"
path = "src/main.rs"
[profile.release]
panic = "abort"
codegen-units = 1
lto = true
[dependencies]
psx-font = { path = "../../crates/psx-font" }
psx-gpu = { path = "../../crates/psx-gpu" }
psx-hw = { path = "../../../crates/psx-hw" }
psx-io = { path = "../../crates/psx-io" }
psx-pad = { path = "../../crates/psx-pad" }
psx-rt = { path = "../../crates/psx-rt" }
psx-spu = { path = "../../crates/psx-spu" }
psx-vram = { path = "../../crates/psx-vram" }
src/main.rs · 117 lines
//! `hello-cdda` -- play Red Book audio from track 2.
//!
//! Build a mixed-mode disc with `mkisopsx --cdda-track`; this demo
//! assumes track 2 is `GONCHAROV`.
#![no_std]
#![no_main]
extern crate psx_rt;
use psx_font::{fonts::BASIC, FontAtlas};
use psx_gpu::display::{DisplayConfig, DoubleBuffer, Resolution, VideoMode};
use psx_gpu::Gpu;
use psx_pad::{button, poll_on, ButtonState, Port};
use psx_spu::{self as spu, CdVolume, Spu, Volume};
use psx_vram::{Clut, TextureDepth, TexturePage};
const FONT_TPAGE: TexturePage = TexturePage::new(320, 0, TextureDepth::Bit4);
const FONT_CLUT: Clut = Clut::new(320, 256);
const TRACK_GONCHAROV: u8 = 2;
#[derive(Copy, Clone, PartialEq, Eq)]
enum Playback {
Playing,
Paused,
Stopped,
Muted,
}
#[no_mangle]
fn main() {
let Some(peripherals) = psx_rt::Peripherals::take() else {
return;
};
let mut gpu = Gpu::new(
peripherals.gpu_dma,
DisplayConfig::new(VideoMode::Ntsc, Resolution::R320X240),
);
let mut fb = DoubleBuffer::new(Resolution::R320X240);
gpu.set_draw_area((0, 0), (319, 239));
gpu.set_draw_offset((0, 0));
let _spu = Spu::new(peripherals.spu_dma);
spu::set_main_volume(Volume::MAX, Volume::MAX);
spu::set_cd_volume(CdVolume::MAX, CdVolume::MAX);
spu::enable_cd_audio(true);
let mut cd = peripherals.cd;
let mut port = peripherals.controller_port;
let _ = cd.set_mode(psx_hw::cd::MODE_DOUBLE_SPEED | psx_hw::cd::MODE_CDDA);
let _ = cd.unmute();
let _ = cd.play_track(TRACK_GONCHAROV);
let font = FontAtlas::upload(&BASIC, FONT_TPAGE, FONT_CLUT);
let mut prev_pad = ButtonState::NONE;
let mut playback = Playback::Playing;
loop {
let pad = poll_on(&mut port, Port::One).buttons;
if pressed(pad, prev_pad, button::START) {
let _ = cd.unmute();
let _ = cd.play_track(TRACK_GONCHAROV);
playback = Playback::Playing;
}
if pressed(pad, prev_pad, button::CROSS) {
let _ = cd.pause();
playback = Playback::Paused;
}
if pressed(pad, prev_pad, button::SQUARE) {
let _ = cd.stop();
playback = Playback::Stopped;
}
if pressed(pad, prev_pad, button::TRIANGLE) {
let _ = cd.mute();
playback = Playback::Muted;
}
if pressed(pad, prev_pad, button::CIRCLE) {
let _ = cd.unmute();
playback = Playback::Playing;
}
prev_pad = pad;
let bg = match playback {
Playback::Playing => (10, 24, 34),
Playback::Paused => (28, 24, 12),
Playback::Stopped => (24, 12, 16),
Playback::Muted => (18, 16, 28),
};
fb.clear(&mut gpu, (bg.0, bg.1, bg.2));
font.draw_text(4, 4, "hello-cdda", (200, 200, 200));
font.draw_text(4, 18, "TRACK 02: GONCHAROV", (120, 190, 230));
font.draw_text(4, 42, "START play from top", (130, 130, 130));
font.draw_text(4, 54, "CROSS pause", (130, 130, 130));
font.draw_text(4, 66, "SQUARE stop", (130, 130, 130));
font.draw_text(4, 78, "TRIANGLE mute", (130, 130, 130));
font.draw_text(4, 90, "CIRCLE demute", (130, 130, 130));
let (status, tint) = match playback {
Playback::Playing => ("PLAYING", (80, 220, 150)),
Playback::Paused => ("PAUSED ", (230, 190, 80)),
Playback::Stopped => ("STOPPED", (230, 100, 100)),
Playback::Muted => ("MUTED ", (170, 140, 230)),
};
font.draw_text(4, 122, "CDDA:", (160, 160, 160));
font.draw_text(56, 122, status, tint);
font.draw_text(4, 220, "mixed-mode disc audio stream", (110, 110, 110));
gpu.wait_idle();
psx_rt::interrupts::wait_vblank();
fb.swap(&mut gpu);
}
}
fn pressed(now: ButtonState, prev: ButtonState, button: u16) -> bool {
now.is_held(button) && !prev.is_held(button)
}