Skip to content

Instantly share code, notes, and snippets.

@yupferris
Last active February 25, 2017 19:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yupferris/99238041bde343d20ce2aae6afe3a1ef to your computer and use it in GitHub Desktop.
Save yupferris/99238041bde343d20ce2aae6afe3a1ef to your computer and use it in GitHub Desktop.
Sample extraction for Galactic Pinball
use std::io::{self, Write, Read, BufWriter};
use std::fs::File;
use std::path::Path;
const NUM_CHANNELS: usize = 1;
const BITS_PER_SAMPLE: usize = 16;
fn main() {
let rom_file_name = "C:\\msys64\\home\\ferris\\dev\\projects\\vb-rs-corpus\\Galactic Pinball (Japan, USA).vb";
let buf = load(rom_file_name).unwrap();
let src = 0xfff1a15a; // Welcome to space world!
//let src = 0xfff24d30; // Cosmic
let output = decompress(src, &buf);
let output_file_name = "space-world-20khz.wav";
//let output_file_name = "cosmic-20khz.wav";
let file = File::create(output_file_name).unwrap();
let mut writer = BufWriter::new(file);
// Sample rate is not exactly 20000; it's very close but there's still some phasing.
let sample_rate = 20000;
let num_frames = output.len();
let data_chunk_size = num_frames * NUM_CHANNELS * BITS_PER_SAMPLE / 8;
// RIFF header
write_str(&mut writer, "RIFF").unwrap();
write_u32(&mut writer, (data_chunk_size + 36) as _).unwrap();
write_str(&mut writer, "WAVE").unwrap();
// Format sub-chunk
write_str(&mut writer, "fmt ").unwrap();
write_u32(&mut writer, 16).unwrap();
write_u16(&mut writer, 1).unwrap(); // WAVE_FORMAT_PCM
write_u16(&mut writer, NUM_CHANNELS as _).unwrap();
write_u32(&mut writer, sample_rate as _).unwrap();
write_u32(&mut writer, (sample_rate * NUM_CHANNELS * BITS_PER_SAMPLE / 8) as _).unwrap();
write_u16(&mut writer, (NUM_CHANNELS * BITS_PER_SAMPLE / 8) as _).unwrap();
write_u16(&mut writer, BITS_PER_SAMPLE as _).unwrap();
// Data sub-chunk
write_str(&mut writer, "data").unwrap();
write_u32(&mut writer, data_chunk_size as _).unwrap();
for byte in output {
let sample = (byte as u16) << 7;
write_u16(&mut writer, sample as _).unwrap();
}
println!("Hello, world!");
}
pub fn load<P: AsRef<Path>>(file_name: P) -> io::Result<Box<[u8]>> {
let mut file = File::open(file_name)?;
let mut vec = Vec::new();
file.read_to_end(&mut vec)?;
Ok(vec.into_boxed_slice())
}
fn read_byte(addr: u32, buf: &[u8]) -> u8 {
//println!("Read 0x{:08x}", addr);
let offset = (((addr & 0x07ffffff) - 0x07000000) as usize) & (buf.len() - 1);
buf[offset]
}
fn decompress(mut stream_ptr: u32, buf: &[u8]) -> Vec<u8> {
let mut output = Vec::new();
let mut nybble = 0;
loop {
let mut data_byte = read_byte(stream_ptr, buf);
if data_byte == 0x0f {
let next_data_byte = read_byte(stream_ptr + 1, buf);
if next_data_byte == 0x0f {
return output;
}
println!("Set voice volume: 0x{:02x}", next_data_byte);
stream_ptr += 2;
// Technically this should read data_byte again and then jump to the compare
// to 0x0f, but this should be equivalent.
continue;
}
if nybble == 0 {
nybble = 1;
} else {
data_byte <<= 4;
stream_ptr += 1;
nybble = 0;
}
//println!("Set envelope data: 0x{:02x}", data_byte);
output.push(data_byte);
}
}
fn write_str<B: Write>(writer: &mut BufWriter<B>, value: &str) -> io::Result<()> {
writer.write_all(value.as_bytes())?;
Ok(())
}
fn write_u16<B: Write>(writer: &mut BufWriter<B>, value: u16) -> io::Result<()> {
let buf = [value as u8, (value >> 8) as u8];
writer.write_all(&buf)?;
Ok(())
}
fn write_u32<B: Write>(writer: &mut BufWriter<B>, value: u32) -> io::Result<()> {
let buf = [value as u8, (value >> 8) as u8, (value >> 16) as u8, (value >> 24) as u8];
writer.write_all(&buf)?;
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment