use pixel8_runtime::{assets::Assets, audio::Synth, pico8};
use std::path::Path;
const SR: f32 = 44100.0;
const MAX_SECS: f32 = 3.0;
const GAP_SECS: f32 = 0.2;
const MUSIC_SECS: f32 = 45.0;
fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() < 3 {
eprintln!("usage: render_audio <cart> <out.wav> [sfx_index|music]");
std::process::exit(2);
}
let assets = pico8::parse_file(Path::new(&args[1])).expect("parse cart");
let arg = args.get(3).map(String::as_str);
let samples = if arg == Some("music") {
render_music(&assets)
} else {
render_sfx(&assets, arg.and_then(|s| s.parse().ok()))
};
write_wav(Path::new(&args[2]), &samples, SR as u32).expect("write wav");
eprintln!(
"wrote {} ({:.2}s) to {}",
samples.len(),
samples.len() as f32 / SR,
args[2]
);
}
fn render_music(assets: &Assets) -> Vec<f32> {
let mut synth = Synth::new(SR);
synth.load(assets.sfx.clone(), assets.music.clone());
synth.play_music(0, 0, 0, 0);
let mut samples = Vec::new();
let mut last = None;
for _ in 0..(MUSIC_SECS * SR) as usize {
let p = synth.playing_pattern();
if p != last {
let chans = synth.channel_sfx();
let active: Vec<_> = chans.iter().filter_map(|c| *c).collect();
eprintln!(
"pattern {p:?} @ {:.2}s channels={chans:?} active={active:?}",
samples.len() as f32 / SR
);
last = p;
}
samples.push(synth.next_sample());
if synth.playing_pattern().is_none() {
break;
}
}
samples
}
fn render_sfx(assets: &Assets, only: Option<usize>) -> Vec<f32> {
let mut samples: Vec<f32> = Vec::new();
let gap = (GAP_SECS * SR) as usize;
for (idx, sfx) in assets.sfx.iter().enumerate() {
if let Some(want) = only {
if idx != want {
continue;
}
} else if sfx.is_empty() {
continue;
}
eprintln!(
"sfx {idx:02} @ {:.2}s speed={} noiz={} buzz={} detune={} reverb={} dampen={}",
samples.len() as f32 / SR,
sfx.speed,
sfx.noiz,
sfx.buzz,
sfx.detune,
sfx.reverb,
sfx.dampen
);
let mut synth = Synth::new(SR);
synth.load(assets.sfx.clone(), assets.music.clone());
synth.play_sfx(idx as i32, 0);
for _ in 0..(MAX_SECS * SR) as usize {
samples.push(synth.next_sample());
if synth.channel_sfx()[0].is_none() {
break;
}
}
samples.resize(samples.len() + gap, 0.0);
}
samples
}
fn write_wav(path: &Path, samples: &[f32], sr: u32) -> std::io::Result<()> {
let mut buf = Vec::with_capacity(44 + samples.len() * 2);
let data_len = (samples.len() * 2) as u32;
let byte_rate = sr * 2;
buf.extend_from_slice(b"RIFF");
buf.extend_from_slice(&(36 + data_len).to_le_bytes());
buf.extend_from_slice(b"WAVE");
buf.extend_from_slice(b"fmt ");
buf.extend_from_slice(&16u32.to_le_bytes());
buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&sr.to_le_bytes());
buf.extend_from_slice(&byte_rate.to_le_bytes());
buf.extend_from_slice(&2u16.to_le_bytes()); buf.extend_from_slice(&16u16.to_le_bytes()); buf.extend_from_slice(b"data");
buf.extend_from_slice(&data_len.to_le_bytes());
for &s in samples {
let v = (s.clamp(-1.0, 1.0) * 32767.0) as i16;
buf.extend_from_slice(&v.to_le_bytes());
}
std::fs::write(path, buf)
}