mirror of
https://github.com/doukutsu-rs/doukutsu-rs
synced 2024-11-22 05:33:02 +00:00
pixtone!
This commit is contained in:
parent
82a51dda88
commit
2a28c24538
|
@ -36,7 +36,6 @@ lazy_static = "1.4.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
lru = "0.6.0"
|
lru = "0.6.0"
|
||||||
lyon = "0.13"
|
lyon = "0.13"
|
||||||
maplit = "1.0.2"
|
|
||||||
mint = "0.5"
|
mint = "0.5"
|
||||||
nalgebra = {version = "0.18", features = ["mint"] }
|
nalgebra = {version = "0.18", features = ["mint"] }
|
||||||
num-derive = "0.3.2"
|
num-derive = "0.3.2"
|
||||||
|
@ -51,4 +50,6 @@ strum = "0.18.0"
|
||||||
strum_macros = "0.18.0"
|
strum_macros = "0.18.0"
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
varint = "0.9.0"
|
varint = "0.9.0"
|
||||||
|
# remove and replace when drain_filter is in stable
|
||||||
|
vec_mut_scan = "0.3.0"
|
||||||
winit = { version = "0.19.3" }
|
winit = { version = "0.19.3" }
|
||||||
|
|
|
@ -34,8 +34,8 @@ pub struct BoosterConsts {
|
||||||
pub struct MyCharConsts {
|
pub struct MyCharConsts {
|
||||||
pub display_bounds: Rect<usize>,
|
pub display_bounds: Rect<usize>,
|
||||||
pub hit_bounds: Rect<usize>,
|
pub hit_bounds: Rect<usize>,
|
||||||
pub life: usize,
|
pub life: u16,
|
||||||
pub max_life: usize,
|
pub max_life: u16,
|
||||||
pub control_mode: ControlMode,
|
pub control_mode: ControlMode,
|
||||||
pub air_physics: PhysicsConsts,
|
pub air_physics: PhysicsConsts,
|
||||||
pub water_physics: PhysicsConsts,
|
pub water_physics: PhysicsConsts,
|
||||||
|
|
|
@ -27,8 +27,8 @@ pub struct Player {
|
||||||
pub vel_y: isize,
|
pub vel_y: isize,
|
||||||
pub target_x: isize,
|
pub target_x: isize,
|
||||||
pub target_y: isize,
|
pub target_y: isize,
|
||||||
pub life: usize,
|
pub life: u16,
|
||||||
pub max_life: usize,
|
pub max_life: u16,
|
||||||
pub cond: Condition,
|
pub cond: Condition,
|
||||||
pub flags: Flag,
|
pub flags: Flag,
|
||||||
pub equip: Equipment,
|
pub equip: Equipment,
|
||||||
|
@ -111,7 +111,7 @@ impl Player {
|
||||||
self.question = false;
|
self.question = false;
|
||||||
if self.flags.head_bounced() {
|
if self.flags.head_bounced() {
|
||||||
self.flags.set_head_bounced(false);
|
self.flags.set_head_bounced(false);
|
||||||
// todo: PlaySoundObject(3, SOUND_MODE_PLAY);
|
state.sound_manager.play_sfx(3);
|
||||||
state.create_caret(self.x, self.y - self.hit_bounds.top as isize, CaretType::LittleParticles, Direction::Left);
|
state.create_caret(self.x, self.y - self.hit_bounds.top as isize, CaretType::LittleParticles, Direction::Left);
|
||||||
state.create_caret(self.x, self.y - self.hit_bounds.top as isize, CaretType::LittleParticles, Direction::Left);
|
state.create_caret(self.x, self.y - self.hit_bounds.top as isize, CaretType::LittleParticles, Direction::Left);
|
||||||
}
|
}
|
||||||
|
@ -242,7 +242,7 @@ impl Player {
|
||||||
|
|
||||||
if state.key_trigger.jump() && (self.flags.hit_bottom_wall() || self.flags.hit_right_slope() || self.flags.hit_left_slope()) && !self.flags.force_up() {
|
if state.key_trigger.jump() && (self.flags.hit_bottom_wall() || self.flags.hit_right_slope() || self.flags.hit_left_slope()) && !self.flags.force_up() {
|
||||||
self.vel_y = -physics.jump;
|
self.vel_y = -physics.jump;
|
||||||
// todo: PlaySoundObject(15, SOUND_MODE_PLAY);
|
state.sound_manager.play_sfx(15);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,27 +285,24 @@ impl Player {
|
||||||
self.vel_x += 0x20; // 0.1fix9
|
self.vel_x += 0x20; // 0.1fix9
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: sound
|
|
||||||
if state.key_trigger.jump() || self.booster_fuel % 3 == 1 {
|
if state.key_trigger.jump() || self.booster_fuel % 3 == 1 {
|
||||||
if self.direction == Direction::Left || self.direction == Direction::Right {
|
if self.direction == Direction::Left || self.direction == Direction::Right {
|
||||||
state.create_caret(self.x + 0x400, self.y + 0x400, CaretType::Exhaust, self.direction.opposite());
|
state.create_caret(self.x + 0x400, self.y + 0x400, CaretType::Exhaust, self.direction.opposite());
|
||||||
}
|
}
|
||||||
// PlaySoundObject(113, SOUND_MODE_PLAY);
|
state.sound_manager.play_sfx(113);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
2 => {
|
2 => {
|
||||||
self.vel_y -= 0x20;
|
self.vel_y -= 0x20;
|
||||||
|
|
||||||
// todo: sound
|
|
||||||
if state.key_trigger.jump() || self.booster_fuel % 3 == 1 {
|
if state.key_trigger.jump() || self.booster_fuel % 3 == 1 {
|
||||||
state.create_caret(self.x, self.y + 6 * 0x200, CaretType::Exhaust, Direction::Bottom);
|
state.create_caret(self.x, self.y + 6 * 0x200, CaretType::Exhaust, Direction::Bottom);
|
||||||
// PlaySoundObject(113, SOUND_MODE_PLAY);
|
state.sound_manager.play_sfx(113);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// todo: sound
|
|
||||||
3 if state.key_trigger.jump() || self.booster_fuel % 3 == 1 => {
|
3 if state.key_trigger.jump() || self.booster_fuel % 3 == 1 => {
|
||||||
state.create_caret(self.x, self.y + 6 * 0x200, CaretType::Exhaust, Direction::Up);
|
state.create_caret(self.x, self.y + 6 * 0x200, CaretType::Exhaust, Direction::Up);
|
||||||
// PlaySoundObject(113, SOUND_MODE_PLAY);
|
state.sound_manager.play_sfx(113);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
@ -316,7 +313,7 @@ impl Player {
|
||||||
|
|
||||||
if self.booster_fuel % 3 == 0 {
|
if self.booster_fuel % 3 == 0 {
|
||||||
state.create_caret(self.x, self.y + self.hit_bounds.bottom as isize / 2, CaretType::Exhaust, Direction::Bottom);
|
state.create_caret(self.x, self.y + self.hit_bounds.bottom as isize / 2, CaretType::Exhaust, Direction::Bottom);
|
||||||
// PlaySoundObject(113, SOUND_MODE_PLAY);
|
state.sound_manager.play_sfx(113);
|
||||||
}
|
}
|
||||||
|
|
||||||
// bounce off of ceiling
|
// bounce off of ceiling
|
||||||
|
@ -417,7 +414,7 @@ impl Player {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick_animation(&mut self, state: &SharedGameState) {
|
fn tick_animation(&mut self, state: &mut SharedGameState) {
|
||||||
if self.cond.hidden() {
|
if self.cond.hidden() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -434,7 +431,7 @@ impl Player {
|
||||||
|
|
||||||
self.anim_num += 1;
|
self.anim_num += 1;
|
||||||
if self.anim_num == 7 || self.anim_num == 9 {
|
if self.anim_num == 7 || self.anim_num == 9 {
|
||||||
// PlaySoundObject(24, SOUND_MODE_PLAY); todo
|
state.sound_manager.play_sfx(24);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -450,7 +447,7 @@ impl Player {
|
||||||
|
|
||||||
self.anim_num += 1;
|
self.anim_num += 1;
|
||||||
if self.anim_num == 2 || self.anim_num == 4 {
|
if self.anim_num == 2 || self.anim_num == 4 {
|
||||||
// PlaySoundObject(24, SOUND_MODE_PLAY); todo
|
state.sound_manager.play_sfx(24);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -459,14 +456,14 @@ impl Player {
|
||||||
}
|
}
|
||||||
} else if state.control_flags.control_enabled() && state.key_state.up() {
|
} else if state.control_flags.control_enabled() && state.key_state.up() {
|
||||||
if self.cond.fallen() {
|
if self.cond.fallen() {
|
||||||
// PlaySoundObject(24, SOUND_MODE_PLAY); todo
|
state.sound_manager.play_sfx(24);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.cond.set_fallen(false);
|
self.cond.set_fallen(false);
|
||||||
self.anim_num = 5;
|
self.anim_num = 5;
|
||||||
} else {
|
} else {
|
||||||
if self.cond.fallen() {
|
if self.cond.fallen() {
|
||||||
// PlaySoundObject(24, SOUND_MODE_PLAY); todo
|
state.sound_manager.play_sfx(24);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.cond.set_fallen(false);
|
self.cond.set_fallen(false);
|
||||||
|
@ -518,7 +515,7 @@ impl Player {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo play sound 16
|
state.sound_manager.play_sfx(16);
|
||||||
self.shock_counter = 128;
|
self.shock_counter = 128;
|
||||||
self.cond.set_interacted(false);
|
self.cond.set_interacted(false);
|
||||||
|
|
||||||
|
@ -526,14 +523,14 @@ impl Player {
|
||||||
self.vel_y = -0x400; // -2.0fix9
|
self.vel_y = -0x400; // -2.0fix9
|
||||||
}
|
}
|
||||||
|
|
||||||
self.life = if hp >= self.life as isize { 0 } else { (self.life as isize - hp) as usize };
|
self.life = self.life.saturating_sub(hp as u16);
|
||||||
|
|
||||||
if self.equip.has_whimsical_star() && self.stars > 0 {
|
if self.equip.has_whimsical_star() && self.stars > 0 {
|
||||||
self.stars -= 1;
|
self.stars -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.life == 0 {
|
if self.life == 0 {
|
||||||
// todo play sound 17
|
state.sound_manager.play_sfx(17);
|
||||||
self.cond.0 = 0;
|
self.cond.0 = 0;
|
||||||
state.textscript_vm.start_script(40);
|
state.textscript_vm.start_script(40);
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,9 +126,7 @@ impl GameScene {
|
||||||
if max_level {
|
if max_level {
|
||||||
batch.add_rect(weap_x + 24.0, 32.0,
|
batch.add_rect(weap_x + 24.0, 32.0,
|
||||||
&Rect::<usize>::new_size(40, 72, 40, 8));
|
&Rect::<usize>::new_size(40, 72, 40, 8));
|
||||||
}
|
} else if max_xp > 0 {
|
||||||
|
|
||||||
if max_xp > 0 {
|
|
||||||
// xp bar
|
// xp bar
|
||||||
let bar_width = (xp as f32 / max_xp as f32 * 40.0) as usize;
|
let bar_width = (xp as f32 / max_xp as f32 * 40.0) as usize;
|
||||||
|
|
||||||
|
@ -615,7 +613,7 @@ impl GameScene {
|
||||||
|
|
||||||
if npc.npc_flags.shootable() {
|
if npc.npc_flags.shootable() {
|
||||||
log::info!("damage: {} {}", npc.life, -(bullet.damage.min(npc.life) as isize));
|
log::info!("damage: {} {}", npc.life, -(bullet.damage.min(npc.life) as isize));
|
||||||
npc.life -= bullet.damage.min(npc.life);
|
npc.life = npc.life.saturating_sub(bullet.damage);
|
||||||
|
|
||||||
if npc.life == 0 {
|
if npc.life == 0 {
|
||||||
if npc.npc_flags.show_damage() {
|
if npc.npc_flags.show_damage() {
|
||||||
|
|
122
src/sound/mod.rs
122
src/sound/mod.rs
|
@ -2,16 +2,17 @@ use std::sync::mpsc;
|
||||||
use std::sync::mpsc::{Receiver, Sender};
|
use std::sync::mpsc::{Receiver, Sender};
|
||||||
|
|
||||||
use bitflags::_core::time::Duration;
|
use bitflags::_core::time::Duration;
|
||||||
|
use cpal::Sample;
|
||||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||||
|
|
||||||
use crate::engine_constants::EngineConstants;
|
use crate::engine_constants::EngineConstants;
|
||||||
use crate::ggez::{Context, filesystem, GameResult};
|
use crate::ggez::{Context, filesystem, GameResult};
|
||||||
use crate::ggez::GameError::{AudioError, InvalidValue, ResourceLoadError};
|
use crate::ggez::GameError::{AudioError, InvalidValue, ResourceLoadError};
|
||||||
use crate::sound::organya::Song;
|
use crate::sound::organya::Song;
|
||||||
|
use crate::sound::pixtone::PixTonePlayback;
|
||||||
use crate::sound::playback::{PlaybackEngine, SavedPlaybackState};
|
use crate::sound::playback::{PlaybackEngine, SavedPlaybackState};
|
||||||
use crate::sound::wave_bank::SoundBank;
|
use crate::sound::wave_bank::SoundBank;
|
||||||
use crate::str;
|
use crate::str;
|
||||||
use cpal::Sample;
|
|
||||||
|
|
||||||
pub mod pixtone;
|
pub mod pixtone;
|
||||||
mod wave_bank;
|
mod wave_bank;
|
||||||
|
@ -99,6 +100,10 @@ impl SoundManager {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn play_sfx(&mut self, id: u8) {
|
||||||
|
self.tx.send(PlaybackMessage::PlaySample(id));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn play_song(&mut self, song_id: usize, constants: &EngineConstants, ctx: &mut Context) -> GameResult {
|
pub fn play_song(&mut self, song_id: usize, constants: &EngineConstants, ctx: &mut Context) -> GameResult {
|
||||||
if self.current_song_id == song_id {
|
if self.current_song_id == song_id {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
@ -157,6 +162,7 @@ impl SoundManager {
|
||||||
enum PlaybackMessage {
|
enum PlaybackMessage {
|
||||||
Stop,
|
Stop,
|
||||||
PlaySong(Box<Song>),
|
PlaySong(Box<Song>),
|
||||||
|
PlaySample(u8),
|
||||||
SetSpeed(f32),
|
SetSpeed(f32),
|
||||||
SaveState,
|
SaveState,
|
||||||
RestoreState,
|
RestoreState,
|
||||||
|
@ -176,76 +182,100 @@ fn run<T>(rx: Receiver<PlaybackMessage>, bank: SoundBank,
|
||||||
let channels = config.channels as usize;
|
let channels = config.channels as usize;
|
||||||
let mut state = PlaybackState::Stopped;
|
let mut state = PlaybackState::Stopped;
|
||||||
let mut saved_state: Option<SavedPlaybackState> = None;
|
let mut saved_state: Option<SavedPlaybackState> = None;
|
||||||
|
let mut speed = 1.0;
|
||||||
let mut engine = PlaybackEngine::new(Song::empty(), &bank);
|
let mut engine = PlaybackEngine::new(Song::empty(), &bank);
|
||||||
|
let mut pixtone = PixTonePlayback::new();
|
||||||
|
pixtone.create_samples();
|
||||||
|
|
||||||
|
|
||||||
log::info!("Audio format: {} {}", sample_rate, channels);
|
log::info!("Audio format: {} {}", sample_rate, channels);
|
||||||
engine.set_sample_rate(sample_rate as usize);
|
engine.set_sample_rate(sample_rate as usize);
|
||||||
engine.loops = usize::MAX;
|
engine.loops = usize::MAX;
|
||||||
|
|
||||||
let mut buf = vec![0x8080; 441];
|
let mut org_buf = vec![0x8080; 441];
|
||||||
let mut index = 0;
|
let mut pxt_buf = vec![0x8000; 441];
|
||||||
let mut frames = engine.render_to(&mut buf);
|
let mut org_index = 0;
|
||||||
|
let mut pxt_index = 0;
|
||||||
|
let mut frames = engine.render_to(&mut org_buf);
|
||||||
|
pixtone.mix(&mut pxt_buf, sample_rate);
|
||||||
|
|
||||||
let err_fn = |err| eprintln!("an error occurred on stream: {}", err);
|
let err_fn = |err| eprintln!("an error occurred on stream: {}", err);
|
||||||
|
|
||||||
let stream = device.build_output_stream(
|
let stream = device.build_output_stream(
|
||||||
config,
|
config,
|
||||||
move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
|
move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
|
||||||
match rx.try_recv() {
|
loop {
|
||||||
Ok(PlaybackMessage::PlaySong(song)) => {
|
match rx.try_recv() {
|
||||||
engine.start_song(*song, &bank);
|
Ok(PlaybackMessage::PlaySong(song)) => {
|
||||||
|
engine.start_song(*song, &bank);
|
||||||
|
|
||||||
for i in &mut buf[0..frames] { *i = 0x8080 };
|
for i in &mut org_buf[0..frames] { *i = 0x8080 };
|
||||||
frames = engine.render_to(&mut buf);
|
frames = engine.render_to(&mut org_buf);
|
||||||
index = 0;
|
org_index = 0;
|
||||||
|
|
||||||
state = PlaybackState::Playing;
|
|
||||||
}
|
|
||||||
Ok(PlaybackMessage::Stop) => {
|
|
||||||
state = PlaybackState::Stopped;
|
|
||||||
}
|
|
||||||
Ok(PlaybackMessage::SetSpeed(speed)) => {
|
|
||||||
assert!(speed > 0.0);
|
|
||||||
engine.set_sample_rate((sample_rate / speed) as usize);
|
|
||||||
}
|
|
||||||
Ok(PlaybackMessage::SaveState) => {
|
|
||||||
saved_state = Some(engine.get_state());
|
|
||||||
}
|
|
||||||
Ok(PlaybackMessage::RestoreState) => {
|
|
||||||
if saved_state.is_some() {
|
|
||||||
engine.set_state(saved_state.clone().unwrap(), &bank);
|
|
||||||
saved_state = None;
|
|
||||||
|
|
||||||
if state == PlaybackState::Stopped {
|
|
||||||
engine.set_position(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in &mut buf[0..frames] { *i = 0x8080 };
|
|
||||||
frames = engine.render_to(&mut buf);
|
|
||||||
index = 0;
|
|
||||||
|
|
||||||
state = PlaybackState::Playing;
|
state = PlaybackState::Playing;
|
||||||
}
|
}
|
||||||
|
Ok(PlaybackMessage::PlaySample(id)) => {
|
||||||
|
pixtone.play_sfx(id);
|
||||||
|
}
|
||||||
|
Ok(PlaybackMessage::Stop) => {
|
||||||
|
state = PlaybackState::Stopped;
|
||||||
|
}
|
||||||
|
Ok(PlaybackMessage::SetSpeed(new_speed)) => {
|
||||||
|
assert!(new_speed > 0.0);
|
||||||
|
speed = new_speed;
|
||||||
|
engine.set_sample_rate((sample_rate / new_speed) as usize);
|
||||||
|
}
|
||||||
|
Ok(PlaybackMessage::SaveState) => {
|
||||||
|
saved_state = Some(engine.get_state());
|
||||||
|
}
|
||||||
|
Ok(PlaybackMessage::RestoreState) => {
|
||||||
|
if saved_state.is_some() {
|
||||||
|
engine.set_state(saved_state.clone().unwrap(), &bank);
|
||||||
|
saved_state = None;
|
||||||
|
|
||||||
|
if state == PlaybackState::Stopped {
|
||||||
|
engine.set_position(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in &mut org_buf[0..frames] { *i = 0x8080 };
|
||||||
|
frames = engine.render_to(&mut org_buf);
|
||||||
|
org_index = 0;
|
||||||
|
|
||||||
|
state = PlaybackState::Playing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => { break; }
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for frame in data.chunks_mut(channels) {
|
for frame in data.chunks_mut(channels) {
|
||||||
let sample: u16 = {
|
let org_sample: u16 = {
|
||||||
if state == PlaybackState::Stopped {
|
if state == PlaybackState::Stopped {
|
||||||
0x8000
|
0x8000
|
||||||
} else if index < frames {
|
} else if org_index < frames {
|
||||||
let sample = buf[index];
|
let sample = org_buf[org_index];
|
||||||
index += 1;
|
org_index += 1;
|
||||||
if index & 1 == 0 { (sample & 0xff) << 8 } else { sample & 0xff00 }
|
if org_index & 1 == 0 { (sample & 0xff) << 8 } else { sample & 0xff00 }
|
||||||
} else {
|
} else {
|
||||||
for i in &mut buf[0..frames] { *i = 0x8080 };
|
for i in &mut org_buf[0..frames] { *i = 0x8080 };
|
||||||
frames = engine.render_to(&mut buf);
|
frames = engine.render_to(&mut org_buf);
|
||||||
index = 0;
|
org_index = 0;
|
||||||
let sample = buf[0];
|
let sample = org_buf[0];
|
||||||
(sample & 0xff) << 8
|
(sample & 0xff) << 8
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let pxt_sample: u16 = pxt_buf[pxt_index] ^ 0x8000;
|
||||||
|
|
||||||
|
if pxt_index < (pxt_buf.len() - 1) {
|
||||||
|
pxt_index += 1;
|
||||||
|
} else {
|
||||||
|
pxt_index = 0;
|
||||||
|
for i in pxt_buf.iter_mut() { *i = 0x8000 };
|
||||||
|
pixtone.mix(&mut pxt_buf, sample_rate / speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sample = org_sample.wrapping_add(pxt_sample);
|
||||||
|
|
||||||
let value: T = Sample::from::<u16>(&sample);
|
let value: T = Sample::from::<u16>(&sample);
|
||||||
for sample in frame.iter_mut() {
|
for sample in frame.iter_mut() {
|
||||||
|
@ -258,6 +288,6 @@ fn run<T>(rx: Receiver<PlaybackMessage>, bank: SoundBank,
|
||||||
stream.play()?;
|
stream.play()?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
std::thread::sleep(Duration::from_millis(10));
|
std::thread::sleep(Duration::from_millis(4));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use vec_mut_scan::VecMutScan;
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref WAVEFORMS: [[i8; 0x100]; 6] = {
|
static ref WAVEFORMS: [[i8; 0x100]; 6] = {
|
||||||
let mut seed = 0;
|
|
||||||
let mut sine = [0i8; 0x100];
|
let mut sine = [0i8; 0x100];
|
||||||
let mut triangle = [0i8; 0x100];
|
let mut triangle = [0i8; 0x100];
|
||||||
let mut saw_up = [0i8; 0x100];
|
let mut saw_up = [0i8; 0x100];
|
||||||
|
@ -10,22 +13,438 @@ lazy_static! {
|
||||||
let mut square = [0i8; 0x100];
|
let mut square = [0i8; 0x100];
|
||||||
let mut random = [0i8; 0x100];
|
let mut random = [0i8; 0x100];
|
||||||
|
|
||||||
for i in 0..255 {
|
let ref_data = include_bytes!("pixtone_ref.dat");
|
||||||
seed = (seed * 214013) + 2531011;
|
unsafe {
|
||||||
sine[i] = (64.0 * (i as f32 * std::f32::consts::PI).sin()) as i8;
|
sine.copy_from_slice(&*(&ref_data[0..0x100] as *const [u8] as *const [i8]));
|
||||||
triangle[i] = (if (0x40 + i as isize) & 0x80 != 0 { 0x80 - i as isize } else { i as isize }) as i8;
|
triangle.copy_from_slice(&*(&ref_data[0x100..0x200] as *const [u8] as *const [i8]));
|
||||||
saw_up[i] = (-0x40 + i as isize / 2) as i8;
|
saw_up.copy_from_slice(&*(&ref_data[0x200..0x300] as *const [u8] as *const [i8]));
|
||||||
saw_down[i] = (0x40 - i as isize / 2) as i8;
|
saw_down.copy_from_slice(&*(&ref_data[0x300..0x400] as *const [u8] as *const [i8]));
|
||||||
square[i] = (0x40 - (i as isize & 0x80)) as i8;
|
square.copy_from_slice(&*(&ref_data[0x400..0x500] as *const [u8] as *const [i8]));
|
||||||
random[i] = ((seed >> 16) / 2) as i8;
|
random.copy_from_slice(&*(&ref_data[0x500..0x600] as *const [u8] as *const [i8]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo i can't get this shit right
|
||||||
|
/*
|
||||||
|
let mut seed = 0i32;
|
||||||
|
|
||||||
|
for i in 0..255 {
|
||||||
|
seed = seed.wrapping_mul(214013).wrapping_add(2531011);
|
||||||
|
sine[i] = (64.0 * (i as f64 * std::f64::consts::PI).sin()) as i8;
|
||||||
|
triangle[i] = (if (0x40i32.wrapping_add(i as i32)) & 0x80 != 0 { 0x80i32.wrapping_sub(i as i32) } else { i as i32 }) as i8;
|
||||||
|
saw_up[i] = (-0x40i32).wrapping_add(i as i32 / 2) as i8;
|
||||||
|
saw_down[i] = (0x40i32.wrapping_sub(i as i32 / 2)) as i8;
|
||||||
|
square[i] = (0x40i32.wrapping_sub(i as i32 & 0x80)) as i8;
|
||||||
|
random[i] = ((seed >> 16) / 2) as i8;
|
||||||
|
}*/
|
||||||
|
|
||||||
[sine, triangle, saw_up, saw_down, square, random]
|
[sine, triangle, saw_up, saw_down, square, random]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PixToneData {}
|
#[test]
|
||||||
|
fn test_waveforms() {
|
||||||
|
let reference = include_bytes!("pixtone_ref.dat");
|
||||||
|
|
||||||
pub struct PixTone {}
|
for n in 1..(WAVEFORMS.len()) {
|
||||||
|
for (i, &val) in WAVEFORMS[n].iter().enumerate() {
|
||||||
|
assert_eq!((val as u8, i, n), (reference[n as usize * 256 + i], i, n));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PixTone {}
|
static PIXTONE_TABLE: [PixToneParameters; 17] = [
|
||||||
|
PixToneParameters::empty(),
|
||||||
|
PixToneParameters {
|
||||||
|
channels: [
|
||||||
|
Channel {
|
||||||
|
enabled: true,
|
||||||
|
length: 3000,
|
||||||
|
carrier: Waveform {
|
||||||
|
waveform_type: 0,
|
||||||
|
pitch: 99.0,
|
||||||
|
level: 32,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
frequency: Waveform {
|
||||||
|
waveform_type: 2,
|
||||||
|
pitch: 1.0,
|
||||||
|
level: 55,
|
||||||
|
offset: 197,
|
||||||
|
},
|
||||||
|
amplitude: Waveform {
|
||||||
|
waveform_type: 5,
|
||||||
|
pitch: 0.0,
|
||||||
|
level: 0,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
envelope: Envelope {
|
||||||
|
initial: 63,
|
||||||
|
time_a: 0,
|
||||||
|
value_a: 63,
|
||||||
|
time_b: 164,
|
||||||
|
value_b: 28,
|
||||||
|
time_c: 255,
|
||||||
|
value_c: 0,
|
||||||
|
},
|
||||||
|
}, Channel::disabled(), Channel::disabled(), Channel::disabled()
|
||||||
|
]
|
||||||
|
},
|
||||||
|
PixToneParameters {
|
||||||
|
channels: [
|
||||||
|
Channel {
|
||||||
|
enabled: true,
|
||||||
|
length: 4000,
|
||||||
|
carrier: Waveform {
|
||||||
|
waveform_type: 1,
|
||||||
|
pitch: 54.0,
|
||||||
|
level: 32,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
frequency: Waveform {
|
||||||
|
waveform_type: 5,
|
||||||
|
pitch: 0.1,
|
||||||
|
level: 33,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
amplitude: Waveform {
|
||||||
|
waveform_type: 0,
|
||||||
|
pitch: 0.0,
|
||||||
|
level: 32,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
envelope: Envelope {
|
||||||
|
initial: 53,
|
||||||
|
time_a: 57,
|
||||||
|
value_a: 44,
|
||||||
|
time_b: 128,
|
||||||
|
value_b: 24,
|
||||||
|
time_c: 255,
|
||||||
|
value_c: 0,
|
||||||
|
},
|
||||||
|
}, Channel::disabled(), Channel::disabled(), Channel::disabled()
|
||||||
|
]
|
||||||
|
},
|
||||||
|
PixToneParameters::empty(),
|
||||||
|
PixToneParameters::empty(),
|
||||||
|
PixToneParameters::empty(),
|
||||||
|
PixToneParameters::empty(),
|
||||||
|
PixToneParameters::empty(),
|
||||||
|
PixToneParameters::empty(),
|
||||||
|
PixToneParameters::empty(),
|
||||||
|
PixToneParameters::empty(),
|
||||||
|
PixToneParameters::empty(),
|
||||||
|
PixToneParameters::empty(),
|
||||||
|
PixToneParameters::empty(),
|
||||||
|
PixToneParameters::empty(),
|
||||||
|
PixToneParameters {
|
||||||
|
channels: [Channel {
|
||||||
|
enabled: true,
|
||||||
|
length: 1000,
|
||||||
|
carrier: Waveform {
|
||||||
|
waveform_type: 5,
|
||||||
|
pitch: 1.0,
|
||||||
|
level: 32,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
frequency: Waveform {
|
||||||
|
waveform_type: 3,
|
||||||
|
pitch: 1.0,
|
||||||
|
level: 63,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
amplitude: Waveform {
|
||||||
|
waveform_type: 0,
|
||||||
|
pitch: 0.0,
|
||||||
|
level: 63,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
envelope: Envelope {
|
||||||
|
initial: 0,
|
||||||
|
time_a: 28,
|
||||||
|
value_a: 63,
|
||||||
|
time_b: 53,
|
||||||
|
value_b: 31,
|
||||||
|
time_c: 210,
|
||||||
|
value_c: 31,
|
||||||
|
},
|
||||||
|
}, Channel::disabled(), Channel::disabled(), Channel::disabled()]
|
||||||
|
},
|
||||||
|
PixToneParameters {
|
||||||
|
channels: [Channel {
|
||||||
|
enabled: true,
|
||||||
|
length: 5000,
|
||||||
|
carrier: Waveform {
|
||||||
|
waveform_type: 2,
|
||||||
|
pitch: 50.0,
|
||||||
|
level: 39,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
frequency: Waveform {
|
||||||
|
waveform_type: 3,
|
||||||
|
pitch: 0.5,
|
||||||
|
level: 40,
|
||||||
|
offset: 217,
|
||||||
|
},
|
||||||
|
amplitude: Waveform {
|
||||||
|
waveform_type: 1,
|
||||||
|
pitch: 0.0,
|
||||||
|
level: 32,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
envelope: Envelope {
|
||||||
|
initial: 63,
|
||||||
|
time_a: 64,
|
||||||
|
value_a: 63,
|
||||||
|
time_b: 128,
|
||||||
|
value_b: 34,
|
||||||
|
time_c: 198,
|
||||||
|
value_c: 32,
|
||||||
|
},
|
||||||
|
}, Channel {
|
||||||
|
enabled: true,
|
||||||
|
length: 5000,
|
||||||
|
carrier: Waveform {
|
||||||
|
waveform_type: 5,
|
||||||
|
pitch: 10.0,
|
||||||
|
level: 39,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
frequency: Waveform {
|
||||||
|
waveform_type: 3,
|
||||||
|
pitch: 0.5,
|
||||||
|
level: 24,
|
||||||
|
offset: 217,
|
||||||
|
},
|
||||||
|
amplitude: Waveform {
|
||||||
|
waveform_type: 1,
|
||||||
|
pitch: 4.0,
|
||||||
|
level: 32,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
envelope: Envelope {
|
||||||
|
initial: 0,
|
||||||
|
time_a: 4,
|
||||||
|
value_a: 63,
|
||||||
|
time_b: 128,
|
||||||
|
value_b: 34,
|
||||||
|
time_c: 198,
|
||||||
|
value_c: 32,
|
||||||
|
},
|
||||||
|
}, Channel::disabled(), Channel::disabled()]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
pub struct Waveform {
|
||||||
|
waveform_type: u8,
|
||||||
|
pitch: f32,
|
||||||
|
level: i32,
|
||||||
|
offset: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Waveform {
|
||||||
|
pub fn get_waveform(&self) -> &[i8; 0x100] {
|
||||||
|
&WAVEFORMS[self.waveform_type as usize % WAVEFORMS.len()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Envelope {
|
||||||
|
initial: i32,
|
||||||
|
time_a: i32,
|
||||||
|
value_a: i32,
|
||||||
|
time_b: i32,
|
||||||
|
value_b: i32,
|
||||||
|
time_c: i32,
|
||||||
|
value_c: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Envelope {
|
||||||
|
pub fn evaluate(&self, i: i32) -> i32 {
|
||||||
|
let (prev_time, prev_val) = {
|
||||||
|
if i >= self.time_a {
|
||||||
|
(self.time_a, self.value_a)
|
||||||
|
} else if i >= self.time_b {
|
||||||
|
(self.time_b, self.value_b)
|
||||||
|
} else if i >= self.time_c {
|
||||||
|
(self.time_c, self.value_c)
|
||||||
|
} else {
|
||||||
|
(0, self.initial)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let (next_time, next_val) = {
|
||||||
|
if i < self.time_c {
|
||||||
|
(self.time_c, self.value_c)
|
||||||
|
} else if i < self.time_b {
|
||||||
|
(self.time_b, self.value_b)
|
||||||
|
} else if i < self.time_a {
|
||||||
|
(self.time_a, self.value_a)
|
||||||
|
} else {
|
||||||
|
(256, 0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if next_time <= prev_time {
|
||||||
|
return prev_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
(i - prev_time) * (next_val - prev_val) / (next_time - prev_time) + prev_val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Channel {
|
||||||
|
enabled: bool,
|
||||||
|
length: u32,
|
||||||
|
carrier: Waveform,
|
||||||
|
frequency: Waveform,
|
||||||
|
amplitude: Waveform,
|
||||||
|
envelope: Envelope,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Channel {
|
||||||
|
pub const fn disabled() -> Channel {
|
||||||
|
Channel {
|
||||||
|
enabled: false,
|
||||||
|
length: 0,
|
||||||
|
carrier: Waveform {
|
||||||
|
waveform_type: 0,
|
||||||
|
pitch: 0.0,
|
||||||
|
level: 0,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
frequency: Waveform {
|
||||||
|
waveform_type: 0,
|
||||||
|
pitch: 0.0,
|
||||||
|
level: 0,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
amplitude: Waveform {
|
||||||
|
waveform_type: 0,
|
||||||
|
pitch: 0.0,
|
||||||
|
level: 0,
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
envelope: Envelope {
|
||||||
|
initial: 0,
|
||||||
|
time_a: 0,
|
||||||
|
value_a: 0,
|
||||||
|
time_b: 0,
|
||||||
|
value_b: 0,
|
||||||
|
time_c: 0,
|
||||||
|
value_c: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PixToneParameters {
|
||||||
|
channels: [Channel; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PixToneParameters {
|
||||||
|
pub const fn empty() -> PixToneParameters {
|
||||||
|
PixToneParameters {
|
||||||
|
channels: [Channel::disabled(), Channel::disabled(), Channel::disabled(), Channel::disabled()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn synth(&self) -> Vec<i16> {
|
||||||
|
let length = self.channels.iter().map(|c| c.length as usize).max().unwrap_or(0);
|
||||||
|
if length == 0 {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut samples = vec![0; length];
|
||||||
|
|
||||||
|
for channel in self.channels.iter() {
|
||||||
|
if !channel.enabled { continue; }
|
||||||
|
|
||||||
|
fn s(p: f32, i: usize, length: u32) -> f32 {
|
||||||
|
256.0 * p * i as f32 / length as f32
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut phase = channel.carrier.offset as f32;
|
||||||
|
let delta = 256.0 * channel.carrier.pitch as f32 / channel.length as f32;
|
||||||
|
let carrier_wave = channel.carrier.get_waveform();
|
||||||
|
let frequency_wave = channel.frequency.get_waveform();
|
||||||
|
let amplitude_wave = channel.amplitude.get_waveform();
|
||||||
|
|
||||||
|
for (i, result) in samples.iter_mut().enumerate() {
|
||||||
|
let carrier = carrier_wave[0xff & phase as usize] as i32 * channel.carrier.level;
|
||||||
|
let freq = frequency_wave[0xff & (channel.frequency.offset as f32 + s(channel.frequency.pitch, i, channel.length)) as usize] as i32 * channel.frequency.level;
|
||||||
|
let amp = amplitude_wave[0xff & (channel.amplitude.offset as f32 + s(channel.amplitude.pitch, i, channel.length)) as usize] as i32 * channel.amplitude.level;
|
||||||
|
|
||||||
|
*result += ((carrier * (amp + 4096) / 4096 * channel.envelope.evaluate(s(1.0, i, channel.length) as i32) / 4096) * 256) as i16;
|
||||||
|
|
||||||
|
phase += delta * (1.0 + (freq as f32 / (if freq < 0 { 8192.0 } else { 2048.0 })));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
samples
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
|
pub struct PlaybackState(u8, f32);
|
||||||
|
|
||||||
|
pub struct PixTonePlayback {
|
||||||
|
pub samples: HashMap<u8, Vec<i16>>,
|
||||||
|
pub playback_state: Vec<PlaybackState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PixTonePlayback {
|
||||||
|
pub fn new() -> PixTonePlayback {
|
||||||
|
PixTonePlayback {
|
||||||
|
samples: HashMap::new(),
|
||||||
|
playback_state: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_samples(&mut self) {
|
||||||
|
for (i, params) in PIXTONE_TABLE.iter().enumerate() {
|
||||||
|
self.samples.insert(i as u8, params.synth());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play_sfx(&mut self, id: u8) {
|
||||||
|
for state in self.playback_state.iter_mut() {
|
||||||
|
if state.0 == id {
|
||||||
|
state.1 = 0.0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.playback_state.push(PlaybackState(id, 0.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mix(&mut self, dst: &mut [u16], sample_rate: f32) {
|
||||||
|
let mut scan = VecMutScan::new(&mut self.playback_state);
|
||||||
|
let delta = 22050.0 / sample_rate;
|
||||||
|
|
||||||
|
while let Some(item) = scan.next() {
|
||||||
|
let mut state = *item;
|
||||||
|
let mut remove = false;
|
||||||
|
|
||||||
|
if let Some(sample) = self.samples.get(&state.0) {
|
||||||
|
if sample.is_empty() {
|
||||||
|
item.remove();
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
for result in dst.iter_mut() {
|
||||||
|
if state.1 >= sample.len() as f32 {
|
||||||
|
remove = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
*result = ((*result as u16 ^ 0x8000u16) as i16).saturating_add(sample[state.1 as usize]) as u16 ^ 0x8000u16;
|
||||||
|
state.1 += delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if remove {
|
||||||
|
item.remove();
|
||||||
|
} else {
|
||||||
|
item.replace(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
BIN
src/sound/pixtone_ref.dat
Normal file
BIN
src/sound/pixtone_ref.dat
Normal file
Binary file not shown.
|
@ -403,6 +403,7 @@ impl TextScriptVM {
|
||||||
|
|
||||||
if remaining > 1 {
|
if remaining > 1 {
|
||||||
let ticks = if state.key_state.jump() || state.key_state.fire() { 1 } else { 4 };
|
let ticks = if state.key_state.jump() || state.key_state.fire() { 1 } else { 4 };
|
||||||
|
state.sound_manager.play_sfx(2);
|
||||||
state.textscript_vm.state = TextScriptExecutionState::Msg(event, cursor.position() as u32, remaining - 1, ticks);
|
state.textscript_vm.state = TextScriptExecutionState::Msg(event, cursor.position() as u32, remaining - 1, ticks);
|
||||||
} else {
|
} else {
|
||||||
state.textscript_vm.state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
state.textscript_vm.state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||||
|
@ -658,7 +659,7 @@ impl TextScriptVM {
|
||||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||||
}
|
}
|
||||||
OpCode::MLp => {
|
OpCode::MLp => {
|
||||||
let life = read_cur_varint(&mut cursor)? as usize;
|
let life = read_cur_varint(&mut cursor)? as u16;
|
||||||
game_scene.player.life += life;
|
game_scene.player.life += life;
|
||||||
game_scene.player.max_life += life;
|
game_scene.player.max_life += life;
|
||||||
|
|
||||||
|
@ -959,7 +960,7 @@ impl TextScriptVM {
|
||||||
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32);
|
||||||
}
|
}
|
||||||
OpCode::LIp => {
|
OpCode::LIp => {
|
||||||
let life = read_cur_varint(&mut cursor)? as usize;
|
let life = read_cur_varint(&mut cursor)? as u16;
|
||||||
|
|
||||||
game_scene.player.life = clamp(game_scene.player.life + life, 0, game_scene.player.max_life);
|
game_scene.player.life = clamp(game_scene.player.life + life, 0, game_scene.player.max_life);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue