SDK example · full source

hello-cdda

Playing a disc audio track

On this page

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

View this file on GitHub

[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

View this file on GitHub

//! `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)
}