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"
|
bitflags = "1"
|
||||||
bitvec = "0.17.4"
|
bitvec = "0.17.4"
|
||||||
byteorder = "1.3"
|
byteorder = "1.3"
|
||||||
|
cpal = "0.12.1"
|
||||||
directories = "2"
|
directories = "2"
|
||||||
gfx = "0.18"
|
gfx = "0.18"
|
||||||
gfx_core = "0.9"
|
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.fnt", include_bytes!("builtin/builtin_font.fnt")),
|
||||||
FSNode::File("builtin_font_0.png", include_bytes!("builtin/builtin_font_0.png")),
|
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("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::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::string::FromUtf8Error;
|
use std::string::FromUtf8Error;
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, PoisonError};
|
||||||
|
|
||||||
/// An enum containing all kinds of game framework errors.
|
/// An enum containing all kinds of game framework errors.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -232,3 +232,31 @@ impl From<strum::ParseError> for GameError {
|
||||||
GameError::ParseError(errstr)
|
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),
|
texture_set: TextureSet::new(base_path),
|
||||||
base_path: str!(base_path),
|
base_path: str!(base_path),
|
||||||
stages: Vec::with_capacity(96),
|
stages: Vec::with_capacity(96),
|
||||||
sound_manager: SoundManager::new(ctx),
|
sound_manager: SoundManager::new(ctx)?,
|
||||||
constants,
|
constants,
|
||||||
scale,
|
scale,
|
||||||
screen_size,
|
screen_size,
|
||||||
|
|
205
src/sound/mod.rs
205
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;
|
pub mod pixtone;
|
||||||
|
mod wave_bank;
|
||||||
|
mod organya;
|
||||||
|
mod playback;
|
||||||
|
mod stuff;
|
||||||
|
mod wav;
|
||||||
|
|
||||||
pub struct SoundManager {
|
pub struct SoundManager {
|
||||||
intro: Vec<u8>,
|
tx: Sender<PlaybackMessage>,
|
||||||
sloop: Vec<u8>,
|
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 {
|
impl SoundManager {
|
||||||
pub fn new(ctx: &mut Context) -> SoundManager {
|
pub fn new(ctx: &mut Context) -> GameResult<SoundManager> {
|
||||||
SoundManager {
|
let (tx, rx): (Sender<PlaybackMessage>, Receiver<PlaybackMessage>) = mpsc::channel();
|
||||||
intro: Vec::new(),
|
|
||||||
sloop: Vec::new(),
|
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 {
|
pub fn play_song(&mut self, song_id: usize, constants: &EngineConstants, ctx: &mut Context) -> GameResult {
|
||||||
/*self.intro.clear();
|
if self.current_song_id == song_id {
|
||||||
self.sloop.clear();
|
return Ok(());
|
||||||
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)?;
|
|
||||||
|
|
||||||
let sink = Sink::new(ctx.audio_context.device());
|
if let Some(song_name) = SONGS.get(song_id) {
|
||||||
sink.append(rodio::Decoder::new(Cursor::new(self.intro.clone()))?);
|
let org = organya::Song::load_from(filesystem::open(ctx, ["/base/Org/", &song_name.to_lowercase(), ".org"].join(""))?)?;
|
||||||
sink.append(rodio::Decoder::new(Cursor::new(self.sloop.clone()))?);
|
log::info!("Playing song: {}", song_name);
|
||||||
sink.detach();*/
|
|
||||||
|
|
||||||
|
self.current_song_id = song_id;
|
||||||
|
self.tx.send(PlaybackMessage::PlaySong(org));
|
||||||
|
}
|
||||||
Ok(())
|
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