1
0
Fork 0
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:
Alula 2020-09-03 00:58:11 +02:00
parent ff2f87133f
commit a4f0d8dfa4
No known key found for this signature in database
GPG key ID: 3E00485503A1D8BA
11 changed files with 1159 additions and 20 deletions

View file

@ -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

Binary file not shown.

View file

@ -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")),
])
],
}

View file

@ -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)
}
}

View file

@ -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,

View file

@ -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
View 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
View 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
View 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
View 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
View 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]
}
}