diff --git a/Cargo.toml b/Cargo.toml index 2243fb3..7a6c677 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,6 @@ lazy_static = "1.4.0" log = "0.4" lru = "0.6.0" lyon = "0.13" -maplit = "1.0.2" mint = "0.5" nalgebra = {version = "0.18", features = ["mint"] } num-derive = "0.3.2" @@ -51,4 +50,6 @@ strum = "0.18.0" strum_macros = "0.18.0" toml = "0.5" varint = "0.9.0" +# remove and replace when drain_filter is in stable +vec_mut_scan = "0.3.0" winit = { version = "0.19.3" } diff --git a/src/engine_constants.rs b/src/engine_constants.rs index e547b65..aa038df 100644 --- a/src/engine_constants.rs +++ b/src/engine_constants.rs @@ -34,8 +34,8 @@ pub struct BoosterConsts { pub struct MyCharConsts { pub display_bounds: Rect, pub hit_bounds: Rect, - pub life: usize, - pub max_life: usize, + pub life: u16, + pub max_life: u16, pub control_mode: ControlMode, pub air_physics: PhysicsConsts, pub water_physics: PhysicsConsts, diff --git a/src/player.rs b/src/player.rs index 7d9a0d0..2652bbe 100644 --- a/src/player.rs +++ b/src/player.rs @@ -27,8 +27,8 @@ pub struct Player { pub vel_y: isize, pub target_x: isize, pub target_y: isize, - pub life: usize, - pub max_life: usize, + pub life: u16, + pub max_life: u16, pub cond: Condition, pub flags: Flag, pub equip: Equipment, @@ -111,7 +111,7 @@ impl Player { self.question = false; if self.flags.head_bounced() { 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); } @@ -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() { 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 } - // todo: sound if state.key_trigger.jump() || self.booster_fuel % 3 == 1 { if self.direction == Direction::Left || self.direction == Direction::Right { 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 => { self.vel_y -= 0x20; - // todo: sound if state.key_trigger.jump() || self.booster_fuel % 3 == 1 { 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 => { 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 { 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 @@ -417,7 +414,7 @@ impl Player { Ok(()) } - fn tick_animation(&mut self, state: &SharedGameState) { + fn tick_animation(&mut self, state: &mut SharedGameState) { if self.cond.hidden() { return; } @@ -434,7 +431,7 @@ impl Player { self.anim_num += 1; 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; 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() { if self.cond.fallen() { - // PlaySoundObject(24, SOUND_MODE_PLAY); todo + state.sound_manager.play_sfx(24); } self.cond.set_fallen(false); self.anim_num = 5; } else { if self.cond.fallen() { - // PlaySoundObject(24, SOUND_MODE_PLAY); todo + state.sound_manager.play_sfx(24); } self.cond.set_fallen(false); @@ -518,7 +515,7 @@ impl Player { return; } - // todo play sound 16 + state.sound_manager.play_sfx(16); self.shock_counter = 128; self.cond.set_interacted(false); @@ -526,14 +523,14 @@ impl Player { 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 { self.stars -= 1; } if self.life == 0 { - // todo play sound 17 + state.sound_manager.play_sfx(17); self.cond.0 = 0; state.textscript_vm.start_script(40); } diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs index 6165e62..a14ec98 100644 --- a/src/scene/game_scene.rs +++ b/src/scene/game_scene.rs @@ -126,9 +126,7 @@ impl GameScene { if max_level { batch.add_rect(weap_x + 24.0, 32.0, &Rect::::new_size(40, 72, 40, 8)); - } - - if max_xp > 0 { + } else if max_xp > 0 { // xp bar 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() { 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.npc_flags.show_damage() { diff --git a/src/sound/mod.rs b/src/sound/mod.rs index 8098c5e..f3f7857 100644 --- a/src/sound/mod.rs +++ b/src/sound/mod.rs @@ -2,16 +2,17 @@ use std::sync::mpsc; 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, InvalidValue, ResourceLoadError}; use crate::sound::organya::Song; +use crate::sound::pixtone::PixTonePlayback; use crate::sound::playback::{PlaybackEngine, SavedPlaybackState}; use crate::sound::wave_bank::SoundBank; use crate::str; -use cpal::Sample; pub mod pixtone; 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 { if self.current_song_id == song_id { return Ok(()); @@ -157,6 +162,7 @@ impl SoundManager { enum PlaybackMessage { Stop, PlaySong(Box), + PlaySample(u8), SetSpeed(f32), SaveState, RestoreState, @@ -176,76 +182,100 @@ fn run(rx: Receiver, bank: SoundBank, let channels = config.channels as usize; let mut state = PlaybackState::Stopped; let mut saved_state: Option = None; + let mut speed = 1.0; let mut engine = PlaybackEngine::new(Song::empty(), &bank); + let mut pixtone = PixTonePlayback::new(); + pixtone.create_samples(); + 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 mut org_buf = vec![0x8080; 441]; + let mut pxt_buf = vec![0x8000; 441]; + 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 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); + loop { + 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; - } - 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; + for i in &mut org_buf[0..frames] { *i = 0x8080 }; + frames = engine.render_to(&mut org_buf); + org_index = 0; 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) { - let sample: u16 = { + let org_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 if org_index < frames { + let sample = org_buf[org_index]; + org_index += 1; + if org_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]; + for i in &mut org_buf[0..frames] { *i = 0x8080 }; + frames = engine.render_to(&mut org_buf); + org_index = 0; + let sample = org_buf[0]; (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::(&sample); for sample in frame.iter_mut() { @@ -258,6 +288,6 @@ fn run(rx: Receiver, bank: SoundBank, stream.play()?; loop { - std::thread::sleep(Duration::from_millis(10)); + std::thread::sleep(Duration::from_millis(4)); } } diff --git a/src/sound/pixtone.rs b/src/sound/pixtone.rs index 7b7d380..8c92b1f 100644 --- a/src/sound/pixtone.rs +++ b/src/sound/pixtone.rs @@ -1,8 +1,11 @@ +use std::collections::HashMap; + +use vec_mut_scan::VecMutScan; + use lazy_static::lazy_static; lazy_static! { static ref WAVEFORMS: [[i8; 0x100]; 6] = { - let mut seed = 0; let mut sine = [0i8; 0x100]; let mut triangle = [0i8; 0x100]; let mut saw_up = [0i8; 0x100]; @@ -10,22 +13,438 @@ lazy_static! { let mut square = [0i8; 0x100]; let mut random = [0i8; 0x100]; - for i in 0..255 { - seed = (seed * 214013) + 2531011; - sine[i] = (64.0 * (i as f32 * std::f32::consts::PI).sin()) as i8; - triangle[i] = (if (0x40 + i as isize) & 0x80 != 0 { 0x80 - i as isize } else { i as isize }) as i8; - saw_up[i] = (-0x40 + i as isize / 2) as i8; - saw_down[i] = (0x40 - i as isize / 2) as i8; - square[i] = (0x40 - (i as isize & 0x80)) as i8; - random[i] = ((seed >> 16) / 2) as i8; + let ref_data = include_bytes!("pixtone_ref.dat"); + unsafe { + sine.copy_from_slice(&*(&ref_data[0..0x100] as *const [u8] as *const [i8])); + triangle.copy_from_slice(&*(&ref_data[0x100..0x200] as *const [u8] as *const [i8])); + saw_up.copy_from_slice(&*(&ref_data[0x200..0x300] as *const [u8] as *const [i8])); + saw_down.copy_from_slice(&*(&ref_data[0x300..0x400] as *const [u8] as *const [i8])); + square.copy_from_slice(&*(&ref_data[0x400..0x500] as *const [u8] as *const [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] }; } -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 { + 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>, + pub playback_state: Vec, +} + +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); + } + } + } + } +} diff --git a/src/sound/pixtone_ref.dat b/src/sound/pixtone_ref.dat new file mode 100644 index 0000000..f584982 Binary files /dev/null and b/src/sound/pixtone_ref.dat differ diff --git a/src/text_script.rs b/src/text_script.rs index f1051c9..7c118c4 100644 --- a/src/text_script.rs +++ b/src/text_script.rs @@ -403,6 +403,7 @@ impl TextScriptVM { if remaining > 1 { 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); } else { 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); } 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.max_life += life; @@ -959,7 +960,7 @@ impl TextScriptVM { exec_state = TextScriptExecutionState::Running(event, cursor.position() as u32); } 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);