mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2024-10-31 19:44:20 +00:00
initial organya implementation
This commit is contained in:
parent
ff2f87133f
commit
a4f0d8dfa4
|
@ -16,6 +16,7 @@ approx = "0.3"
|
|||
bitflags = "1"
|
||||
bitvec = "0.17.4"
|
||||
byteorder = "1.3"
|
||||
cpal = "0.12.1"
|
||||
directories = "2"
|
||||
gfx = "0.18"
|
||||
gfx_core = "0.9"
|
||||
|
|
BIN
src/builtin/pixtone.pcm
Normal file
BIN
src/builtin/pixtone.pcm
Normal file
Binary file not shown.
|
@ -110,6 +110,7 @@ impl BuiltinFS {
|
|||
FSNode::File("builtin_font.fnt", include_bytes!("builtin/builtin_font.fnt")),
|
||||
FSNode::File("builtin_font_0.png", include_bytes!("builtin/builtin_font_0.png")),
|
||||
FSNode::File("builtin_font_1.png", include_bytes!("builtin/builtin_font_1.png")),
|
||||
FSNode::File("pixtone.pcm", include_bytes!("builtin/pixtone.pcm")),
|
||||
])
|
||||
],
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::string::FromUtf8Error;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, PoisonError};
|
||||
|
||||
/// An enum containing all kinds of game framework errors.
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -232,3 +232,31 @@ impl From<strum::ParseError> for GameError {
|
|||
GameError::ParseError(errstr)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cpal::DefaultStreamConfigError> for GameError {
|
||||
fn from(s: cpal::DefaultStreamConfigError) -> GameError {
|
||||
let errstr = format!("Default stream config error: {}", s);
|
||||
GameError::AudioError(errstr)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cpal::PlayStreamError> for GameError {
|
||||
fn from(s: cpal::PlayStreamError) -> GameError {
|
||||
let errstr = format!("Play stream error: {}", s);
|
||||
GameError::AudioError(errstr)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cpal::BuildStreamError> for GameError {
|
||||
fn from(s: cpal::BuildStreamError) -> GameError {
|
||||
let errstr = format!("Build stream error: {}", s);
|
||||
GameError::AudioError(errstr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<PoisonError<T>> for GameError {
|
||||
fn from(s: PoisonError<T>) -> GameError {
|
||||
let errstr = format!("Poison error: {}", s);
|
||||
GameError::EventLoopError(errstr)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -187,7 +187,7 @@ impl Game {
|
|||
texture_set: TextureSet::new(base_path),
|
||||
base_path: str!(base_path),
|
||||
stages: Vec::with_capacity(96),
|
||||
sound_manager: SoundManager::new(ctx),
|
||||
sound_manager: SoundManager::new(ctx)?,
|
||||
constants,
|
||||
scale,
|
||||
screen_size,
|
||||
|
|
207
src/sound/mod.rs
207
src/sound/mod.rs
|
@ -1,33 +1,204 @@
|
|||
use crate::ggez::{Context, GameResult};
|
||||
use std::sync::{mpsc, RwLock};
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
|
||||
use bitflags::_core::time::Duration;
|
||||
use cpal::Sample;
|
||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
|
||||
use crate::engine_constants::EngineConstants;
|
||||
use crate::ggez::{Context, filesystem, GameResult};
|
||||
use crate::ggez::GameError::AudioError;
|
||||
use crate::sound::organya::Song;
|
||||
use crate::sound::playback::PlaybackEngine;
|
||||
use crate::sound::wave_bank::SoundBank;
|
||||
use crate::str;
|
||||
|
||||
pub mod pixtone;
|
||||
mod wave_bank;
|
||||
mod organya;
|
||||
mod playback;
|
||||
mod stuff;
|
||||
mod wav;
|
||||
|
||||
pub struct SoundManager {
|
||||
intro: Vec<u8>,
|
||||
sloop: Vec<u8>,
|
||||
tx: Sender<PlaybackMessage>,
|
||||
current_song_id: usize,
|
||||
}
|
||||
|
||||
//unsafe impl Send for SoundManager {}
|
||||
static SONGS: [&'static str; 42] = [
|
||||
"XXXX",
|
||||
"WANPAKU",
|
||||
"ANZEN",
|
||||
"GAMEOVER",
|
||||
"GRAVITY",
|
||||
"WEED",
|
||||
"MDOWN2",
|
||||
"FIREEYE",
|
||||
"VIVI",
|
||||
"MURA",
|
||||
"FANFALE1",
|
||||
"GINSUKE",
|
||||
"CEMETERY",
|
||||
"PLANT",
|
||||
"KODOU",
|
||||
"FANFALE3",
|
||||
"FANFALE2",
|
||||
"DR",
|
||||
"ESCAPE",
|
||||
"JENKA",
|
||||
"MAZE",
|
||||
"ACCESS",
|
||||
"IRONH",
|
||||
"GRAND",
|
||||
"Curly",
|
||||
"OSIDE",
|
||||
"REQUIEM",
|
||||
"WANPAK2",
|
||||
"QUIET",
|
||||
"LASTCAVE",
|
||||
"BALCONY",
|
||||
"LASTBTL",
|
||||
"LASTBT3",
|
||||
"ENDING",
|
||||
"ZONBIE",
|
||||
"BDOWN",
|
||||
"HELL",
|
||||
"JENKA2",
|
||||
"MARINE",
|
||||
"BALLOS",
|
||||
"TOROKO",
|
||||
"WHITE"
|
||||
];
|
||||
|
||||
impl SoundManager {
|
||||
pub fn new(ctx: &mut Context) -> SoundManager {
|
||||
SoundManager {
|
||||
intro: Vec::new(),
|
||||
sloop: Vec::new(),
|
||||
}
|
||||
pub fn new(ctx: &mut Context) -> GameResult<SoundManager> {
|
||||
let (tx, rx): (Sender<PlaybackMessage>, Receiver<PlaybackMessage>) = mpsc::channel();
|
||||
|
||||
let host = cpal::default_host();
|
||||
let device = host.default_output_device().ok_or_else(|| AudioError(str!("Error initializing audio device.")))?;
|
||||
let config = device.default_output_config()?;
|
||||
|
||||
let bnk = wave_bank::SoundBank::load_from(filesystem::open(ctx, "/builtin/pixtone.pcm")?)?;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if let Err(err) = match config.sample_format() {
|
||||
cpal::SampleFormat::F32 => run::<f32>(rx, bnk, &device, &config.into()),
|
||||
cpal::SampleFormat::I16 => run::<i16>(rx, bnk, &device, &config.into()),
|
||||
cpal::SampleFormat::U16 => run::<u16>(rx, bnk, &device, &config.into()),
|
||||
} {
|
||||
log::error!("Something went wrong in audio thread: {}", err);
|
||||
}
|
||||
});
|
||||
|
||||
Ok(SoundManager {
|
||||
tx: tx.clone(),
|
||||
current_song_id: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn play_song(&mut self, ctx: &mut Context) -> GameResult {
|
||||
/*self.intro.clear();
|
||||
self.sloop.clear();
|
||||
ggez::filesystem::open(ctx, "/base/Ogg11/curly_intro.ogg")?.read_to_end(&mut self.intro)?;
|
||||
ggez::filesystem::open(ctx, "/base/Ogg11/curly_loop.ogg")?.read_to_end(&mut self.sloop)?;
|
||||
pub fn play_song(&mut self, song_id: usize, constants: &EngineConstants, ctx: &mut Context) -> GameResult {
|
||||
if self.current_song_id == song_id {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let sink = Sink::new(ctx.audio_context.device());
|
||||
sink.append(rodio::Decoder::new(Cursor::new(self.intro.clone()))?);
|
||||
sink.append(rodio::Decoder::new(Cursor::new(self.sloop.clone()))?);
|
||||
sink.detach();*/
|
||||
if let Some(song_name) = SONGS.get(song_id) {
|
||||
let org = organya::Song::load_from(filesystem::open(ctx, ["/base/Org/", &song_name.to_lowercase(), ".org"].join(""))?)?;
|
||||
log::info!("Playing song: {}", song_name);
|
||||
|
||||
self.current_song_id = song_id;
|
||||
self.tx.send(PlaybackMessage::PlaySong(org));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
enum PlaybackMessage {
|
||||
Stop,
|
||||
PlaySong(Song),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
enum PlaybackState {
|
||||
Stopped,
|
||||
Playing,
|
||||
}
|
||||
|
||||
fn run<T>(rx: Receiver<PlaybackMessage>, bank: SoundBank,
|
||||
device: &cpal::Device, config: &cpal::StreamConfig) -> GameResult where
|
||||
T: cpal::Sample,
|
||||
{
|
||||
let sample_rate = config.sample_rate.0 as f32;
|
||||
let channels = config.channels as usize;
|
||||
let mut state = PlaybackState::Stopped;
|
||||
let mut new_song: Option<Song> = None;
|
||||
let mut engine = PlaybackEngine::new(Song::empty(), &bank);
|
||||
|
||||
log::info!("Audio format: {} {}", sample_rate, channels);
|
||||
engine.set_sample_rate(sample_rate as usize);
|
||||
engine.loops = usize::MAX;
|
||||
|
||||
let mut buf = vec![0x8080; 441];
|
||||
let mut index = 0;
|
||||
let mut frames = engine.render_to(&mut buf);
|
||||
|
||||
let err_fn = |err| eprintln!("an error occurred on stream: {}", err);
|
||||
|
||||
let stream = device.build_output_stream(
|
||||
config,
|
||||
move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
|
||||
match rx.try_recv() {
|
||||
Ok(PlaybackMessage::PlaySong(song)) => {
|
||||
engine.start_song(song, &bank);
|
||||
|
||||
for i in &mut buf[0..frames] { *i = 0x8080 };
|
||||
frames = engine.render_to(&mut buf);
|
||||
index = 0;
|
||||
|
||||
state = PlaybackState::Playing;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
for frame in data.chunks_mut(channels) {
|
||||
let sample: u16 = {
|
||||
if state == PlaybackState::Stopped {
|
||||
0x8000
|
||||
} else if index < frames {
|
||||
let sample = buf[index];
|
||||
index += 1;
|
||||
if index & 1 == 0 { (sample & 0xff) << 8 } else { sample & 0xff00 }
|
||||
} else {
|
||||
for i in &mut buf[0..frames] { *i = 0x8080 };
|
||||
frames = engine.render_to(&mut buf);
|
||||
index = 0;
|
||||
let sample = buf[0];
|
||||
(sample & 0xff) << 8
|
||||
}
|
||||
};
|
||||
|
||||
let value: T = cpal::Sample::from::<u16>(&sample);
|
||||
for sample in frame.iter_mut() {
|
||||
*sample = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
err_fn,
|
||||
)?;
|
||||
stream.play()?;
|
||||
|
||||
loop {
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
}
|
||||
|
||||
fn write_data<T>(output: &mut [T], channels: usize, next_sample: &mut dyn FnMut() -> u16)
|
||||
where
|
||||
T: cpal::Sample,
|
||||
{
|
||||
for frame in output.chunks_mut(channels) {
|
||||
let value: T = cpal::Sample::from::<u16>(&next_sample());
|
||||
for sample in frame.iter_mut() {
|
||||
*sample = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
225
src/sound/organya.rs
Normal file
225
src/sound/organya.rs
Normal file
|
@ -0,0 +1,225 @@
|
|||
#[repr(u8)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Version {
|
||||
// Can't find any files with this signature,
|
||||
// But apparently these files had no Pi flag.
|
||||
Beta = b'1',
|
||||
Main = b'2',
|
||||
// OrgMaker 2.05 Extended Drums
|
||||
Extended = b'3'
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct LoopRange {
|
||||
// inclusive
|
||||
pub start: i32,
|
||||
// exclusive
|
||||
pub end: i32
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Display {
|
||||
pub beats: u8,
|
||||
pub steps: u8
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Timing {
|
||||
pub wait: u16,
|
||||
pub loop_range: LoopRange
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct Instrument {
|
||||
pub freq: u16,
|
||||
pub inst: u8,
|
||||
pub pipi: u8,
|
||||
pub notes: u16
|
||||
}
|
||||
|
||||
pub struct Track {
|
||||
pub inst: Instrument,
|
||||
pub notes: Vec<Note>
|
||||
}
|
||||
|
||||
impl Clone for Track {
|
||||
fn clone(&self) -> Track {
|
||||
Track {
|
||||
inst: self.inst,
|
||||
notes: self.notes.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Track {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
self.inst.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Note {
|
||||
pub pos: i32,
|
||||
pub key: u8,
|
||||
pub len: u8,
|
||||
pub vol: u8,
|
||||
pub pan: u8
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Song {
|
||||
pub version: Version,
|
||||
pub time: Timing,
|
||||
pub tracks: [Track; 16]
|
||||
}
|
||||
|
||||
impl Clone for Song {
|
||||
fn clone(&self) -> Song {
|
||||
Song {
|
||||
version: self.version,
|
||||
time: self.time,
|
||||
tracks: self.tracks.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use byteorder::{LE, ReadBytesExt};
|
||||
use std::io;
|
||||
|
||||
impl Song {
|
||||
pub fn empty() -> Song {
|
||||
Song {
|
||||
version: Version::Main,
|
||||
time: Timing { wait: 8, loop_range: LoopRange { start: 0, end: 1 } },
|
||||
tracks: [
|
||||
Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] },
|
||||
Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] },
|
||||
Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] },
|
||||
Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] },
|
||||
Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] },
|
||||
Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] },
|
||||
Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] },
|
||||
Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] },
|
||||
Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] },
|
||||
Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] },
|
||||
Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] },
|
||||
Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] },
|
||||
Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] },
|
||||
Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] },
|
||||
Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] },
|
||||
Track { inst: Instrument {freq: 1000, inst: 0, pipi: 0, notes: 0}, notes: vec![] },
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_from<R: io::Read>(mut f: R) -> io::Result<Song> {
|
||||
let mut magic = [0; 6];
|
||||
|
||||
f.read_exact(&mut magic)?;
|
||||
|
||||
let version =
|
||||
match &magic {
|
||||
b"Org-01" => Version::Beta,
|
||||
b"Org-02" => Version::Main,
|
||||
b"Org-03" => Version::Extended,
|
||||
_ => return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid magic number"))
|
||||
};
|
||||
|
||||
let wait = f.read_u16::<LE>()?;
|
||||
let _bpm = f.read_u8()?;
|
||||
let _spb = f.read_u8()?;
|
||||
let start = f.read_i32::<LE>()?;
|
||||
let end = f.read_i32::<LE>()?;
|
||||
|
||||
use std::mem::MaybeUninit as Mu;
|
||||
|
||||
let mut insts: [Mu<Instrument>; 16] = unsafe {
|
||||
Mu::uninit().assume_init()
|
||||
};
|
||||
|
||||
for i in insts.iter_mut() {
|
||||
let freq = f.read_u16::<LE>()?;
|
||||
let inst = f.read_u8()?;
|
||||
let pipi = f.read_u8()?;
|
||||
let notes = f.read_u16::<LE>()?;
|
||||
|
||||
*i = Mu::new(Instrument {
|
||||
freq,
|
||||
inst,
|
||||
pipi,
|
||||
notes
|
||||
});
|
||||
}
|
||||
|
||||
let insts: [Instrument; 16] = unsafe {
|
||||
std::mem::transmute(insts)
|
||||
};
|
||||
|
||||
let mut tracks: [Mu<Track>; 16] = unsafe {
|
||||
Mu::uninit().assume_init()
|
||||
};
|
||||
|
||||
for (i, t) in tracks.iter_mut().enumerate() {
|
||||
let count = insts[i].notes as usize;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
struct UninitNote {
|
||||
pos: Mu<i32>,
|
||||
key: Mu<u8>,
|
||||
len: Mu<u8>,
|
||||
vol: Mu<u8>,
|
||||
pan: Mu<u8>
|
||||
}
|
||||
|
||||
let mut notes: Vec<UninitNote> = unsafe {
|
||||
vec![Mu::uninit().assume_init(); count]
|
||||
};
|
||||
|
||||
for note in notes.iter_mut() {
|
||||
note.pos = Mu::new(f.read_i32::<LE>()?);
|
||||
}
|
||||
|
||||
for note in notes.iter_mut() {
|
||||
note.key = Mu::new(f.read_u8()?);
|
||||
}
|
||||
|
||||
for note in notes.iter_mut() {
|
||||
note.len = Mu::new(f.read_u8()?);
|
||||
}
|
||||
|
||||
for note in notes.iter_mut() {
|
||||
note.vol = Mu::new(f.read_u8()?);
|
||||
}
|
||||
|
||||
for note in notes.iter_mut() {
|
||||
note.pan = Mu::new(f.read_u8()?);
|
||||
}
|
||||
|
||||
*t = Mu::new(Track {
|
||||
inst: insts[i],
|
||||
notes: unsafe { std::mem::transmute(notes) }
|
||||
});
|
||||
}
|
||||
|
||||
let tracks = unsafe {
|
||||
std::mem::transmute(tracks)
|
||||
};
|
||||
|
||||
let song = Song {
|
||||
version,
|
||||
time: Timing {
|
||||
wait,
|
||||
loop_range: LoopRange {
|
||||
start,
|
||||
end
|
||||
}
|
||||
},
|
||||
tracks
|
||||
};
|
||||
|
||||
Ok(song)
|
||||
}
|
||||
}
|
512
src/sound/playback.rs
Normal file
512
src/sound/playback.rs
Normal file
|
@ -0,0 +1,512 @@
|
|||
use std::mem::MaybeUninit;
|
||||
|
||||
use crate::sound::organya::Song as Organya;
|
||||
use crate::sound::stuff::*;
|
||||
use crate::sound::wav::*;
|
||||
use crate::sound::wave_bank::SoundBank;
|
||||
|
||||
pub struct PlaybackEngine {
|
||||
song: Organya,
|
||||
lengths: [u8; 8],
|
||||
swaps: [usize; 8],
|
||||
keys: [u8; 8],
|
||||
track_buffers: [RenderBuffer; 136],
|
||||
output_format: WavFormat,
|
||||
play_pos: i32,
|
||||
frames_this_tick: usize,
|
||||
frames_per_tick: usize,
|
||||
pub loops: usize,
|
||||
}
|
||||
|
||||
pub struct PlaybackState {
|
||||
song: Organya,
|
||||
play_pos: i32,
|
||||
}
|
||||
|
||||
impl PlaybackEngine {
|
||||
pub fn new(song: Organya, samples: &SoundBank) -> Self {
|
||||
|
||||
// Octave 0 Track 0 Swap 0
|
||||
// Octave 0 Track 1 Swap 0
|
||||
// ...
|
||||
// Octave 1 Track 0 Swap 0
|
||||
// ...
|
||||
// Octave 0 Track 0 Swap 1
|
||||
// octave * 8 + track + swap
|
||||
// 128..136: Drum Tracks
|
||||
let mut buffers: [MaybeUninit<RenderBuffer>; 136] = unsafe {
|
||||
MaybeUninit::uninit().assume_init()
|
||||
};
|
||||
|
||||
// track
|
||||
for i in 0..8 {
|
||||
let sound_index = song.tracks[i].inst.inst as usize;
|
||||
|
||||
// WAVE100 uses 8-bit signed audio, but wav audio wants 8-bit unsigned.
|
||||
// On 2s complement system, we can simply flip the top bit
|
||||
// No need to cast to u8 here because the sound bank data is one big &[u8].
|
||||
let sound = samples.get_wave(sound_index)
|
||||
.iter()
|
||||
.map(|&x| x ^ 128)
|
||||
.collect();
|
||||
|
||||
let format = WavFormat { channels: 1, sample_rate: 22050, bit_depth: 8 };
|
||||
|
||||
let rbuf = RenderBuffer::new_organya(WavSample { format, data: sound });
|
||||
|
||||
// octave
|
||||
for j in 0..8 {
|
||||
// swap
|
||||
for &k in &[0, 64] {
|
||||
buffers[i + (j * 8) + k] = MaybeUninit::new(rbuf.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (inst, buf) in song.tracks[8..].iter().zip(buffers[128..].iter_mut()) {
|
||||
*buf =
|
||||
MaybeUninit::new(
|
||||
RenderBuffer::new(
|
||||
// FIXME: *frustrated screaming*
|
||||
samples.samples[inst.inst.inst as usize].clone()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
let frames_per_tick = (44100 / 1000) * song.time.wait as usize;
|
||||
|
||||
PlaybackEngine {
|
||||
song,
|
||||
lengths: [0; 8],
|
||||
swaps: [0; 8],
|
||||
keys: [255; 8],
|
||||
track_buffers: unsafe { std::mem::transmute(buffers) },
|
||||
play_pos: 0,
|
||||
output_format: WavFormat {
|
||||
channels: 2,
|
||||
sample_rate: 44100,
|
||||
bit_depth: 16,
|
||||
},
|
||||
frames_this_tick: 0,
|
||||
frames_per_tick,
|
||||
loops: 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_sample_rate(&mut self, sample_rate: usize) {
|
||||
self.output_format.sample_rate = sample_rate as u32;
|
||||
self.frames_per_tick = (sample_rate / 1000) * self.song.time.wait as usize;
|
||||
}
|
||||
|
||||
pub fn get_state(&self) -> PlaybackState {
|
||||
PlaybackState {
|
||||
song: self.song.clone(),
|
||||
play_pos: self.play_pos,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_state(&mut self, state: PlaybackState, samples: &SoundBank) {
|
||||
self.start_song(state.song, samples);
|
||||
self.play_pos = state.play_pos;
|
||||
}
|
||||
|
||||
pub fn start_song(&mut self, song: Organya, samples: &SoundBank) {
|
||||
for i in 0..8 {
|
||||
let sound_index = song.tracks[i].inst.inst as usize;
|
||||
let sound = samples.get_wave(sound_index)
|
||||
.iter()
|
||||
.map(|&x| x ^ 128)
|
||||
.collect();
|
||||
|
||||
let format = WavFormat { channels: 1, sample_rate: 22050, bit_depth: 8 };
|
||||
|
||||
let rbuf = RenderBuffer::new_organya(WavSample { format, data: sound });
|
||||
|
||||
for j in 0..8 {
|
||||
for &k in &[0, 64] {
|
||||
self.track_buffers[i + (j * 8) + k] = rbuf.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (inst, buf) in song.tracks[8..].iter().zip(self.track_buffers[128..].iter_mut()) {
|
||||
*buf = RenderBuffer::new(samples.samples[inst.inst.inst as usize].clone());
|
||||
}
|
||||
|
||||
self.song = song;
|
||||
self.play_pos = 0;
|
||||
self.frames_per_tick = (self.output_format.sample_rate as usize / 1000) * self.song.time.wait as usize;
|
||||
self.frames_this_tick = 0;
|
||||
for i in self.lengths.iter_mut() { *i = 0 };
|
||||
for i in self.swaps.iter_mut() { *i = 0 };
|
||||
for i in self.keys.iter_mut() { *i = 255 };
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn set_position(&mut self, position: i32) {
|
||||
self.play_pos = position;
|
||||
}
|
||||
|
||||
pub fn get_total_samples(&self) -> u32 {
|
||||
let ticks_intro = self.song.time.loop_range.start;
|
||||
let ticks_loop = self.song.time.loop_range.end - self.song.time.loop_range.start;
|
||||
let ticks_total = ticks_intro + ticks_loop + (ticks_loop * self.loops as i32);
|
||||
|
||||
self.frames_per_tick as u32 * ticks_total as u32
|
||||
}
|
||||
|
||||
fn update_play_state(&mut self) {
|
||||
for track in 0..8 {
|
||||
if let Some(note) =
|
||||
self.song.tracks[track].notes.iter().find(|x| x.pos == self.play_pos) {
|
||||
// New note
|
||||
//eprintln!("{:?}", &self.keys);
|
||||
if note.key != 255 {
|
||||
if self.keys[track] == 255 { // New
|
||||
let octave = (note.key / 12) * 8;
|
||||
let j = octave as usize + track + self.swaps[track];
|
||||
for k in 0..16 {
|
||||
let swap = if k >= 8 { 64 } else { 0 };
|
||||
let key = note.key % 12;
|
||||
let p_oct = k % 8;
|
||||
|
||||
let freq = org_key_to_freq(key + p_oct * 12, self.song.tracks[track].inst.freq as i16);
|
||||
|
||||
let l = p_oct as usize * 8 + track + swap;
|
||||
self.track_buffers[l].set_frequency(freq as u32);
|
||||
self.track_buffers[l].organya_select_octave(p_oct as usize, self.song.tracks[track].inst.pipi != 0);
|
||||
}
|
||||
self.track_buffers[j].looping = true;
|
||||
self.track_buffers[j].playing = true;
|
||||
// last playing key
|
||||
self.keys[track] = note.key;
|
||||
} else if self.keys[track] == note.key { // Same
|
||||
//assert!(self.lengths[track] == 0);
|
||||
let octave = (self.keys[track] / 12) * 8;
|
||||
let j = octave as usize + track + self.swaps[track];
|
||||
if self.song.tracks[track].inst.pipi == 0 {
|
||||
self.track_buffers[j].looping = false;
|
||||
}
|
||||
self.swaps[track] += 64;
|
||||
self.swaps[track] %= 128;
|
||||
let j = octave as usize + track + self.swaps[track];
|
||||
self.track_buffers[j].organya_select_octave(note.key as usize / 12, self.song.tracks[track].inst.pipi != 0);
|
||||
self.track_buffers[j].looping = true;
|
||||
self.track_buffers[j].playing = true;
|
||||
} else { // change
|
||||
let octave = (self.keys[track] / 12) * 8;
|
||||
let j = octave as usize + track + self.swaps[track];
|
||||
if self.song.tracks[track].inst.pipi == 0 {
|
||||
self.track_buffers[j].looping = false;
|
||||
}
|
||||
self.swaps[track] += 64;
|
||||
self.swaps[track] %= 128;
|
||||
let octave = (note.key / 12) * 8;
|
||||
let j = octave as usize + track + self.swaps[track];
|
||||
for k in 0..16 {
|
||||
let swap = if k >= 8 { 64 } else { 0 };
|
||||
let key = note.key % 12;
|
||||
let p_oct = k % 8;
|
||||
|
||||
let freq = org_key_to_freq(key + p_oct * 12, self.song.tracks[track].inst.freq as i16);
|
||||
let l = p_oct as usize * 8 + track + swap;
|
||||
self.track_buffers[l].set_frequency(freq as u32);
|
||||
self.track_buffers[l].organya_select_octave(p_oct as usize, self.song.tracks[track].inst.pipi != 0);
|
||||
}
|
||||
self.track_buffers[j].looping = true;
|
||||
self.track_buffers[j].playing = true;
|
||||
self.keys[track] = note.key;
|
||||
}
|
||||
|
||||
self.lengths[track] = note.len;
|
||||
}
|
||||
|
||||
if self.keys[track] != 255 {
|
||||
let octave = (self.keys[track] / 12) * 8;
|
||||
let j = octave as usize + track + self.swaps[track];
|
||||
|
||||
if note.vol != 255 {
|
||||
let vol = org_vol_to_vol(note.vol);
|
||||
self.track_buffers[j].set_volume(vol);
|
||||
}
|
||||
|
||||
if note.pan != 255 {
|
||||
let pan = org_pan_to_pan(note.pan);
|
||||
self.track_buffers[j].set_pan(pan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.lengths[track] == 0 {
|
||||
if self.keys[track] != 255 {
|
||||
let octave = (self.keys[track] / 12) * 8;
|
||||
let j = octave as usize + track + self.swaps[track];
|
||||
if self.song.tracks[track].inst.pipi == 0 {
|
||||
self.track_buffers[j].looping = false;
|
||||
}
|
||||
self.keys[track] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
self.lengths[track] = self.lengths[track].saturating_sub(1);
|
||||
}
|
||||
|
||||
for i in 8..16 {
|
||||
let j = i + 120;
|
||||
|
||||
let notes = &self.song.tracks[i].notes;
|
||||
|
||||
// start a new note
|
||||
// note (hah) that drums are unaffected by length and pi values. This is the only case we have to handle.
|
||||
if let Some(note) =
|
||||
notes.iter().find(|x| x.pos == self.play_pos) {
|
||||
|
||||
// FIXME: Add constants for dummy values
|
||||
if note.key != 255 {
|
||||
let freq = org_key_to_drum_freq(note.key);
|
||||
self.track_buffers[j].set_frequency(freq as u32);
|
||||
self.track_buffers[j].set_position(0);
|
||||
self.track_buffers[j].playing = true;
|
||||
}
|
||||
|
||||
if note.vol != 255 {
|
||||
let vol = org_vol_to_vol(note.vol);
|
||||
self.track_buffers[j].set_volume(vol);
|
||||
}
|
||||
|
||||
if note.pan != 255 {
|
||||
let pan = org_pan_to_pan(note.pan);
|
||||
self.track_buffers[j].set_pan(pan);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_to(&mut self, buf: &mut [u16]) -> usize {
|
||||
for (i, frame) in buf.iter_mut().enumerate() {
|
||||
if self.frames_this_tick == 0 {
|
||||
self.update_play_state()
|
||||
}
|
||||
|
||||
mix(std::slice::from_mut(frame), self.output_format, &mut self.track_buffers);
|
||||
|
||||
self.frames_this_tick += 1;
|
||||
|
||||
if self.frames_this_tick == self.frames_per_tick {
|
||||
self.play_pos += 1;
|
||||
|
||||
if self.play_pos == self.song.time.loop_range.end {
|
||||
self.play_pos = self.song.time.loop_range.start;
|
||||
|
||||
if self.loops == 0 {
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
self.loops -= 1;
|
||||
}
|
||||
|
||||
self.frames_this_tick = 0;
|
||||
}
|
||||
}
|
||||
|
||||
buf.len()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Create a MixingBuffer or something...
|
||||
fn mix(dst: &mut [u16], dst_fmt: WavFormat, srcs: &mut [RenderBuffer]) {
|
||||
let freq = dst_fmt.sample_rate as f64;
|
||||
|
||||
for buf in srcs {
|
||||
if buf.playing {
|
||||
// index into sound samples
|
||||
let advance = buf.frequency as f64 / freq;
|
||||
|
||||
let vol = centibel_to_scale(buf.volume);
|
||||
|
||||
let (pan_l, pan_r) =
|
||||
match buf.pan.signum() {
|
||||
0 => (1.0, 1.0),
|
||||
1 => (centibel_to_scale(-buf.pan), 1.0),
|
||||
-1 => (1.0, centibel_to_scale(buf.pan)),
|
||||
_ => unsafe { std::hint::unreachable_unchecked() }
|
||||
};
|
||||
|
||||
fn clamp<T: Ord>(v: T, limit: T) -> T {
|
||||
if v > limit {
|
||||
limit
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
// s1: sample 1
|
||||
// s2: sample 2
|
||||
// sp: previous sample (before s1)
|
||||
// sn: next sample (after s2)
|
||||
// mu: position to interpolate for
|
||||
fn cubic_interp(s1: f32, s2: f32, sp: f32, sn: f32, mu: f32) -> f32 {
|
||||
let mu2 = mu * mu;
|
||||
let a0 = sn - s2 - sp + s1;
|
||||
let a1 = sp - s1 - a0;
|
||||
let a2 = s2 - sp;
|
||||
let a3 = s1;
|
||||
|
||||
a0 * mu * mu2 + a1 * mu2 + a2 * mu + a3
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
|
||||
for frame in dst.iter_mut() {
|
||||
let pos = buf.position as usize + buf.base_pos;
|
||||
// -1..1
|
||||
let s1 = (buf.sample.data[pos] as f32 - 128.0) / 128.0;
|
||||
let s2 = (buf.sample.data[clamp(pos + 1, buf.base_pos + buf.len - 1)] as f32 - 128.0) / 128.0;
|
||||
let s3 = (buf.sample.data[clamp(pos + 2, buf.base_pos + buf.len - 1)] as f32 - 128.0) / 128.0;
|
||||
let s4 = (buf.sample.data[pos.saturating_sub(1)] as f32 - 128.0) / 128.0;
|
||||
|
||||
use std::f32::consts::PI;
|
||||
|
||||
let r1 = buf.position.fract() as f32;
|
||||
let r2 = (1.0 - f32::cos(r1 * PI)) / 2.0;
|
||||
|
||||
//let s = s1; // No interp
|
||||
//let s = s1 + (s2 - s1) * r1; // Linear interp
|
||||
//let s = s1 * (1.0 - r2) + s2 * r2; // Cosine interp
|
||||
let s = cubic_interp(s1, s2, s4, s3, r1); // Cubic interp
|
||||
// Ideally we want sinc/lanczos interpolation, since that's what DirectSound appears to use.
|
||||
|
||||
// -128..128
|
||||
let sl = s * pan_l * vol * 128.0;
|
||||
let sr = s * pan_r * vol * 128.0;
|
||||
|
||||
buf.position += advance;
|
||||
|
||||
if buf.position as usize >= buf.len {
|
||||
if buf.looping && buf.nloops != 1 {
|
||||
buf.position %= buf.len as f64;
|
||||
if buf.nloops != -1 {
|
||||
buf.nloops -= 1;
|
||||
}
|
||||
} else {
|
||||
buf.position = 0.0;
|
||||
buf.playing = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let [mut l, mut r] = frame.to_le_bytes();
|
||||
// -128..127
|
||||
let xl = (l ^ 128) as i8;
|
||||
let xr = (r ^ 128) as i8;
|
||||
|
||||
// 0..255
|
||||
l = xl.saturating_add(sl as i8) as u8 ^ 128;
|
||||
r = xr.saturating_add(sr as i8) as u8 ^ 128;
|
||||
|
||||
*frame = u16::from_le_bytes([l, r]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn centibel_to_scale(a: i32) -> f32 {
|
||||
f32::powf(10.0, a as f32 / 2000.0)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RenderBuffer {
|
||||
pub position: f64,
|
||||
pub frequency: u32,
|
||||
pub volume: i32,
|
||||
pub pan: i32,
|
||||
pub sample: WavSample,
|
||||
pub playing: bool,
|
||||
pub looping: bool,
|
||||
pub base_pos: usize,
|
||||
pub len: usize,
|
||||
// -1 = infinite
|
||||
pub nloops: i32,
|
||||
}
|
||||
|
||||
impl RenderBuffer {
|
||||
pub fn new(sample: WavSample) -> RenderBuffer {
|
||||
RenderBuffer {
|
||||
position: 0.0,
|
||||
frequency: sample.format.sample_rate,
|
||||
volume: 0,
|
||||
pan: 0,
|
||||
len: sample.data.len(),
|
||||
sample,
|
||||
playing: false,
|
||||
looping: false,
|
||||
base_pos: 0,
|
||||
nloops: -1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_organya(mut sample: WavSample) -> RenderBuffer {
|
||||
let wave = sample.data.clone();
|
||||
sample.data.clear();
|
||||
|
||||
for size in &[256_usize, 256, 128, 128, 64, 32, 16, 8] {
|
||||
let step = 256 / size;
|
||||
let mut acc = 0;
|
||||
|
||||
for _ in 0..*size {
|
||||
sample.data.push(wave[acc]);
|
||||
acc += step;
|
||||
|
||||
if acc >= 256 {
|
||||
acc = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RenderBuffer::new(sample)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn organya_select_octave(&mut self, octave: usize, pipi: bool) {
|
||||
const OFFS: &[usize] = &[0x000, 0x100,
|
||||
0x200, 0x280,
|
||||
0x300, 0x340,
|
||||
0x360, 0x370];
|
||||
const LENS: &[usize] = &[256_usize, 256, 128, 128, 64, 32, 16, 8];
|
||||
self.base_pos = OFFS[octave];
|
||||
self.len = LENS[octave];
|
||||
self.position %= self.len as f64;
|
||||
if pipi && !self.playing {
|
||||
self.nloops = ((octave + 1) * 4) as i32;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_frequency(&mut self, frequency: u32) {
|
||||
//assert!(frequency >= 100 && frequency <= 100000);
|
||||
//dbg!(frequency);
|
||||
self.frequency = frequency;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_volume(&mut self, volume: i32) {
|
||||
assert!(volume >= -10000 && volume <= 0);
|
||||
|
||||
self.volume = volume;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_pan(&mut self, pan: i32) {
|
||||
assert!(pan >= -10000 && pan <= 10000);
|
||||
|
||||
self.pan = pan;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(unused)]
|
||||
pub fn set_position(&mut self, position: u32) {
|
||||
assert!(position < self.sample.data.len() as u32 / self.sample.format.bit_depth as u32);
|
||||
|
||||
self.position = position as f64;
|
||||
}
|
||||
}
|
36
src/sound/stuff.rs
Normal file
36
src/sound/stuff.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
pub const FRQ_TBL: [i16; 12] = [
|
||||
261,278,294,311,329,349,371,391,414,440,466,494
|
||||
];
|
||||
|
||||
pub const PAN_TBL: [i16; 13] = [
|
||||
0,43,86,129,172,215,256,297,340,383,426,469,512
|
||||
];
|
||||
|
||||
pub const OCT_TBL: [i16; 8] = [
|
||||
32,64,64,128,128,128,128,128
|
||||
];
|
||||
|
||||
pub fn org_key_to_freq(key: u8, a: i16) -> i32 {
|
||||
let (oct, pitch) = org_key_to_oct_pitch(key);
|
||||
|
||||
let freq = FRQ_TBL[pitch as usize] as f32;
|
||||
let oct = OCT_TBL[oct as usize] as f32;
|
||||
|
||||
(freq * oct) as i32 + (a as i32 - 1000)
|
||||
}
|
||||
|
||||
pub fn org_key_to_drum_freq(key: u8) -> i32 {
|
||||
key as i32 * 800 + 100
|
||||
}
|
||||
|
||||
pub fn org_pan_to_pan(pan: u8) -> i32 {
|
||||
(PAN_TBL[pan as usize] as i32 - 256) * 10
|
||||
}
|
||||
|
||||
pub fn org_vol_to_vol(vol: u8) -> i32 {
|
||||
(vol as i32 - 255) * 8
|
||||
}
|
||||
|
||||
pub fn org_key_to_oct_pitch(key: u8) -> (u8, u8) {
|
||||
(key/12, key%12)
|
||||
}
|
120
src/sound/wav.rs
Normal file
120
src/sound/wav.rs
Normal file
|
@ -0,0 +1,120 @@
|
|||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub struct RiffChunk {
|
||||
id: [u8; 4],
|
||||
length: u32
|
||||
}
|
||||
|
||||
use std::fmt;
|
||||
|
||||
impl fmt::Display for RiffChunk {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use std::ascii::escape_default as esc;
|
||||
|
||||
write!(f, "chunk \"{}{}{}{}\", length: {}",
|
||||
esc(self.id[0]),
|
||||
esc(self.id[1]),
|
||||
esc(self.id[2]),
|
||||
esc(self.id[3]),
|
||||
self.length
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub struct WavFormat {
|
||||
pub channels: u16,
|
||||
pub sample_rate: u32,
|
||||
pub bit_depth: u16
|
||||
}
|
||||
|
||||
impl fmt::Display for WavFormat {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{} channels, {} Hz, {}-bit",
|
||||
self.channels,
|
||||
self.sample_rate,
|
||||
self.bit_depth
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WavSample {
|
||||
pub format: WavFormat,
|
||||
pub data: Vec<u8>
|
||||
}
|
||||
|
||||
impl fmt::Display for WavSample {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}, {} samples",
|
||||
self.format,
|
||||
// num_bytes / bytes_per_sample
|
||||
self.data.len() / ((self.format.bit_depth / 8) * self.format.channels) as usize
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
use byteorder::{LE, ReadBytesExt};
|
||||
use std::io;
|
||||
|
||||
impl RiffChunk {
|
||||
pub fn read_from<R: io::Read>(mut f: R) -> io::Result<RiffChunk> {
|
||||
let mut id = [0; 4];
|
||||
|
||||
f.read_exact(&mut id)?;
|
||||
let length = f.read_u32::<LE>()?;
|
||||
|
||||
Ok(RiffChunk { id, length })
|
||||
}
|
||||
}
|
||||
|
||||
impl WavSample {
|
||||
pub fn read_from<R: io::Read>(mut f: R) -> io::Result<WavSample> {
|
||||
let riff = RiffChunk::read_from(&mut f)?;
|
||||
|
||||
match &riff.id {
|
||||
b"RIFF" => {},
|
||||
b"RIFX" => panic!("Cannot handle RIFX data!"),
|
||||
_ => panic!("Expected RIFF signature, found {}", riff)
|
||||
}
|
||||
|
||||
let mut rfmt = [0; 4];
|
||||
|
||||
f.read_exact(&mut rfmt)?;
|
||||
|
||||
assert_eq!(rfmt, *b"WAVE");
|
||||
|
||||
let fmt = RiffChunk::read_from(&mut f)?;
|
||||
|
||||
assert_eq!(fmt.id, *b"fmt ");
|
||||
//assert_eq!(fmt.length, 16);
|
||||
|
||||
let afmt = f.read_u16::<LE>()?;
|
||||
|
||||
debug_assert!(afmt == 1);
|
||||
|
||||
let channels = f.read_u16::<LE>()?;
|
||||
let samples = f.read_u32::<LE>()?;
|
||||
let _brate = f.read_u32::<LE>()?;
|
||||
let _balgn = f.read_u16::<LE>()?;
|
||||
let bits = f.read_u16::<LE>()?;
|
||||
|
||||
let data = RiffChunk::read_from(&mut f)?;
|
||||
|
||||
assert_eq!(data.id, *b"data");
|
||||
|
||||
let mut buf = vec![0; data.length as usize];
|
||||
|
||||
f.read_exact(&mut buf)?;
|
||||
|
||||
Ok(
|
||||
WavSample {
|
||||
format: WavFormat {
|
||||
channels,
|
||||
sample_rate: samples,
|
||||
bit_depth: bits
|
||||
},
|
||||
data: buf
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
45
src/sound/wave_bank.rs
Normal file
45
src/sound/wave_bank.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
use crate::sound::wav;
|
||||
|
||||
use std::io;
|
||||
use std::fmt;
|
||||
|
||||
pub struct SoundBank {
|
||||
// FIXME: would prefer Box<[u8; 25600]>
|
||||
pub wave100: Box<[u8]>,
|
||||
|
||||
pub samples: Vec<wav::WavSample>
|
||||
}
|
||||
|
||||
impl fmt::Display for SoundBank {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f, "WAVE100: {:2X?}...", &self.wave100[..8])?;
|
||||
|
||||
for sample in self.samples.iter() {
|
||||
writeln!(f, "{}", sample)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SoundBank {
|
||||
pub fn load_from<R: io::Read>(mut f: R) -> io::Result<SoundBank> {
|
||||
// no box [0; 25600] yet
|
||||
let mut wave100 = vec![0; 25600].into_boxed_slice();
|
||||
|
||||
f.read_exact(&mut *wave100)?;
|
||||
|
||||
let mut samples = Vec::with_capacity(16);
|
||||
|
||||
loop {
|
||||
match wav::WavSample::read_from(&mut f) {
|
||||
Ok(sample) => samples.push(sample),
|
||||
Err(_) => return Ok(SoundBank { wave100, samples })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_wave(&self, index: usize) -> &[u8] {
|
||||
&self.wave100[index*256..(index+1)*256]
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue