support for overriding pixtone samples
This commit is contained in:
parent
3fe8e132e5
commit
752ecac3ee
|
@ -7,6 +7,8 @@ use crate::case_insensitive_hashmap;
|
||||||
use crate::common::{BulletFlag, Color, Rect};
|
use crate::common::{BulletFlag, Color, Rect};
|
||||||
use crate::engine_constants::npcs::NPCConsts;
|
use crate::engine_constants::npcs::NPCConsts;
|
||||||
use crate::player::ControlMode;
|
use crate::player::ControlMode;
|
||||||
|
use crate::sound::pixtone::{Channel, PixToneParameters, Waveform, Envelope};
|
||||||
|
use crate::sound::SoundManager;
|
||||||
use crate::str;
|
use crate::str;
|
||||||
use crate::text_script::TextScriptEncoding;
|
use crate::text_script::TextScriptEncoding;
|
||||||
|
|
||||||
|
@ -1395,7 +1397,7 @@ impl EngineConstants {
|
||||||
inventory_item_count_x: 6,
|
inventory_item_count_x: 6,
|
||||||
text_shadow: false,
|
text_shadow: false,
|
||||||
text_speed_normal: 4,
|
text_speed_normal: 4,
|
||||||
text_speed_fast: 1
|
text_speed_fast: 1,
|
||||||
},
|
},
|
||||||
title: TitleConsts {
|
title: TitleConsts {
|
||||||
intro_text: "Studio Pixel presents".to_string(),
|
intro_text: "Studio Pixel presents".to_string(),
|
||||||
|
@ -1468,7 +1470,7 @@ impl EngineConstants {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_csplus_patches(&mut self) {
|
pub fn apply_csplus_patches(&mut self, sound_manager: &SoundManager) {
|
||||||
info!("Applying Cave Story+ constants patches...");
|
info!("Applying Cave Story+ constants patches...");
|
||||||
|
|
||||||
self.is_cs_plus = true;
|
self.is_cs_plus = true;
|
||||||
|
@ -1482,6 +1484,33 @@ impl EngineConstants {
|
||||||
self.font_space_offset = 2.0;
|
self.font_space_offset = 2.0;
|
||||||
self.soundtracks.insert("Remastered".to_string(), "/base/Ogg11/".to_string());
|
self.soundtracks.insert("Remastered".to_string(), "/base/Ogg11/".to_string());
|
||||||
self.soundtracks.insert("New".to_string(), "/base/Ogg/".to_string());
|
self.soundtracks.insert("New".to_string(), "/base/Ogg/".to_string());
|
||||||
|
|
||||||
|
let typewriter_sample = PixToneParameters {
|
||||||
|
// fx2 (CS+)
|
||||||
|
channels: [
|
||||||
|
Channel {
|
||||||
|
enabled: true,
|
||||||
|
length: 2000,
|
||||||
|
carrier: Waveform { waveform_type: 0, pitch: 92.000000, level: 32, offset: 0 },
|
||||||
|
frequency: Waveform { waveform_type: 0, pitch: 3.000000, level: 44, offset: 0 },
|
||||||
|
amplitude: Waveform { waveform_type: 0, pitch: 0.000000, level: 32, offset: 0 },
|
||||||
|
envelope: Envelope {
|
||||||
|
initial: 7,
|
||||||
|
time_a: 2,
|
||||||
|
value_a: 18,
|
||||||
|
time_b: 128,
|
||||||
|
value_b: 0,
|
||||||
|
time_c: 255,
|
||||||
|
value_c: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Channel::disabled(),
|
||||||
|
Channel::disabled(),
|
||||||
|
Channel::disabled(),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
sound_manager.set_sample_params(2, typewriter_sample);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_csplus_nx_patches(&mut self) {
|
pub fn apply_csplus_nx_patches(&mut self) {
|
||||||
|
|
|
@ -130,16 +130,17 @@ pub struct SharedGameState {
|
||||||
impl SharedGameState {
|
impl SharedGameState {
|
||||||
pub fn new(ctx: &mut Context) -> GameResult<SharedGameState> {
|
pub fn new(ctx: &mut Context) -> GameResult<SharedGameState> {
|
||||||
let mut constants = EngineConstants::defaults();
|
let mut constants = EngineConstants::defaults();
|
||||||
|
let sound_manager = SoundManager::new(ctx)?;
|
||||||
let mut base_path = "/";
|
let mut base_path = "/";
|
||||||
let settings = Settings::load(ctx)?;
|
let settings = Settings::load(ctx)?;
|
||||||
|
|
||||||
if filesystem::exists(ctx, "/base/Nicalis.bmp") {
|
if filesystem::exists(ctx, "/base/Nicalis.bmp") {
|
||||||
info!("Cave Story+ (PC) data files detected.");
|
info!("Cave Story+ (PC) data files detected.");
|
||||||
constants.apply_csplus_patches();
|
constants.apply_csplus_patches(&sound_manager);
|
||||||
base_path = "/base/";
|
base_path = "/base/";
|
||||||
} else if filesystem::exists(ctx, "/base/lighting.tbl") {
|
} else if filesystem::exists(ctx, "/base/lighting.tbl") {
|
||||||
info!("Cave Story+ (Switch) data files detected.");
|
info!("Cave Story+ (Switch) data files detected.");
|
||||||
constants.apply_csplus_patches();
|
constants.apply_csplus_patches(&sound_manager);
|
||||||
constants.apply_csplus_nx_patches();
|
constants.apply_csplus_nx_patches();
|
||||||
base_path = "/base/";
|
base_path = "/base/";
|
||||||
} else if filesystem::exists(ctx, "/mrmap.bin") {
|
} else if filesystem::exists(ctx, "/mrmap.bin") {
|
||||||
|
@ -157,6 +158,32 @@ impl SharedGameState {
|
||||||
texture_set.apply_seasonal_content(season, &settings);
|
texture_set.apply_seasonal_content(season, &settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for i in 0..0xffu8 {
|
||||||
|
let path = format!("{}/pxt/fx{:02x}.pxt", base_path, i);
|
||||||
|
if let Ok(file) = filesystem::open(ctx, path) {
|
||||||
|
sound_manager.set_sample_params_from_file(i, file)?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = format!("/pxt/fx{:02x}.pxt", i);
|
||||||
|
if let Ok(file) = filesystem::open(ctx, path) {
|
||||||
|
sound_manager.set_sample_params_from_file(i, file)?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = format!("{}/PixTone/{:03}.pxt", base_path, i);
|
||||||
|
if let Ok(file) = filesystem::open(ctx, path) {
|
||||||
|
sound_manager.set_sample_params_from_file(i, file)?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = format!("/PixTone/{:03}.pxt", i);
|
||||||
|
if let Ok(file) = filesystem::open(ctx, path) {
|
||||||
|
sound_manager.set_sample_params_from_file(i, file)?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
println!("lookup path: {:#?}", texture_set.paths);
|
println!("lookup path: {:#?}", texture_set.paths);
|
||||||
|
|
||||||
#[cfg(feature = "hooks")]
|
#[cfg(feature = "hooks")]
|
||||||
|
@ -195,7 +222,7 @@ impl SharedGameState {
|
||||||
texture_set,
|
texture_set,
|
||||||
#[cfg(feature = "scripting")]
|
#[cfg(feature = "scripting")]
|
||||||
lua: LuaScriptingState::new(),
|
lua: LuaScriptingState::new(),
|
||||||
sound_manager: SoundManager::new(ctx)?,
|
sound_manager,
|
||||||
settings,
|
settings,
|
||||||
shutdown: false,
|
shutdown: false,
|
||||||
})
|
})
|
||||||
|
|
169
src/sound/mod.rs
169
src/sound/mod.rs
|
@ -1,17 +1,18 @@
|
||||||
|
use std::io;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::sync::mpsc::{Receiver, Sender};
|
use std::sync::mpsc::{Receiver, Sender, TryRecvError};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use cpal::Sample;
|
|
||||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||||
|
use cpal::Sample;
|
||||||
#[cfg(feature = "ogg-playback")]
|
#[cfg(feature = "ogg-playback")]
|
||||||
use lewton::inside_ogg::OggStreamReader;
|
use lewton::inside_ogg::OggStreamReader;
|
||||||
use num_traits::clamp;
|
use num_traits::clamp;
|
||||||
|
|
||||||
use crate::engine_constants::EngineConstants;
|
use crate::engine_constants::EngineConstants;
|
||||||
use crate::framework::context::Context;
|
use crate::framework::context::Context;
|
||||||
use crate::framework::error::{GameError, GameResult};
|
|
||||||
use crate::framework::error::GameError::{AudioError, InvalidValue};
|
use crate::framework::error::GameError::{AudioError, InvalidValue};
|
||||||
|
use crate::framework::error::{GameError, GameResult};
|
||||||
use crate::framework::filesystem;
|
use crate::framework::filesystem;
|
||||||
use crate::framework::filesystem::File;
|
use crate::framework::filesystem::File;
|
||||||
use crate::settings::Settings;
|
use crate::settings::Settings;
|
||||||
|
@ -19,15 +20,17 @@ use crate::settings::Settings;
|
||||||
use crate::sound::ogg_playback::{OggPlaybackEngine, SavedOggPlaybackState};
|
use crate::sound::ogg_playback::{OggPlaybackEngine, SavedOggPlaybackState};
|
||||||
use crate::sound::org_playback::{OrgPlaybackEngine, SavedOrganyaPlaybackState};
|
use crate::sound::org_playback::{OrgPlaybackEngine, SavedOrganyaPlaybackState};
|
||||||
use crate::sound::organya::Song;
|
use crate::sound::organya::Song;
|
||||||
use crate::sound::pixtone::PixTonePlayback;
|
use crate::sound::pixtone::{PixToneParameters, PixTonePlayback};
|
||||||
use crate::sound::wave_bank::SoundBank;
|
use crate::sound::wave_bank::SoundBank;
|
||||||
use crate::str;
|
use crate::str;
|
||||||
|
use std::io::{BufReader, BufRead, Lines};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[cfg(feature = "ogg-playback")]
|
#[cfg(feature = "ogg-playback")]
|
||||||
mod ogg_playback;
|
mod ogg_playback;
|
||||||
mod org_playback;
|
mod org_playback;
|
||||||
mod organya;
|
mod organya;
|
||||||
mod pixtone;
|
pub mod pixtone;
|
||||||
mod pixtone_sfx;
|
mod pixtone_sfx;
|
||||||
mod stuff;
|
mod stuff;
|
||||||
mod wav;
|
mod wav;
|
||||||
|
@ -52,7 +55,8 @@ impl SoundManager {
|
||||||
let (tx, rx): (Sender<PlaybackMessage>, Receiver<PlaybackMessage>) = mpsc::channel();
|
let (tx, rx): (Sender<PlaybackMessage>, Receiver<PlaybackMessage>) = mpsc::channel();
|
||||||
|
|
||||||
let host = cpal::default_host();
|
let host = cpal::default_host();
|
||||||
let device = host.default_output_device().ok_or_else(|| AudioError(str!("Error initializing audio device.")))?;
|
let device =
|
||||||
|
host.default_output_device().ok_or_else(|| AudioError(str!("Error initializing audio device.")))?;
|
||||||
let config = device.default_output_config()?;
|
let config = device.default_output_config()?;
|
||||||
|
|
||||||
let bnk = wave_bank::SoundBank::load_from(filesystem::open(ctx, "/builtin/organya-wavetable-doukutsu.bin")?)?;
|
let bnk = wave_bank::SoundBank::load_from(filesystem::open(ctx, "/builtin/organya-wavetable-doukutsu.bin")?)?;
|
||||||
|
@ -70,11 +74,17 @@ impl SoundManager {
|
||||||
Ok(SoundManager { tx: tx.clone(), prev_song_id: 0, current_song_id: 0 })
|
Ok(SoundManager { tx: tx.clone(), prev_song_id: 0, current_song_id: 0 })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn play_sfx(&mut self, id: u8) {
|
pub fn play_sfx(&self, id: u8) {
|
||||||
let _ = self.tx.send(PlaybackMessage::PlaySample(id));
|
let _ = self.tx.send(PlaybackMessage::PlaySample(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn play_song(&mut self, song_id: usize, constants: &EngineConstants, settings: &Settings, ctx: &mut Context) -> GameResult {
|
pub fn play_song(
|
||||||
|
&mut self,
|
||||||
|
song_id: usize,
|
||||||
|
constants: &EngineConstants,
|
||||||
|
settings: &Settings,
|
||||||
|
ctx: &mut Context,
|
||||||
|
) -> GameResult {
|
||||||
if self.current_song_id == song_id {
|
if self.current_song_id == song_id {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -98,16 +108,21 @@ impl SoundManager {
|
||||||
|
|
||||||
let songs_paths = paths.iter().map(|prefix| {
|
let songs_paths = paths.iter().map(|prefix| {
|
||||||
[
|
[
|
||||||
#[cfg(feature = "ogg-playback")]
|
#[cfg(feature = "ogg-playback")]
|
||||||
(SongFormat::OggMultiPart, vec![format!("{}{}_intro.ogg", prefix, song_name), format!("{}{}_loop.ogg", prefix, song_name)]),
|
(
|
||||||
#[cfg(feature = "ogg-playback")]
|
SongFormat::OggMultiPart,
|
||||||
|
vec![format!("{}{}_intro.ogg", prefix, song_name), format!("{}{}_loop.ogg", prefix, song_name)],
|
||||||
|
),
|
||||||
|
#[cfg(feature = "ogg-playback")]
|
||||||
(SongFormat::OggSinglePart, vec![format!("{}{}.ogg", prefix, song_name)]),
|
(SongFormat::OggSinglePart, vec![format!("{}{}.ogg", prefix, song_name)]),
|
||||||
(SongFormat::Organya, vec![format!("{}{}.org", prefix, song_name)]),
|
(SongFormat::Organya, vec![format!("{}{}.org", prefix, song_name)]),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
for songs in songs_paths {
|
for songs in songs_paths {
|
||||||
for (format, paths) in songs.iter().filter(|(_, paths)| paths.iter().all(|path| filesystem::exists(ctx, path))) {
|
for (format, paths) in
|
||||||
|
songs.iter().filter(|(_, paths)| paths.iter().all(|path| filesystem::exists(ctx, path)))
|
||||||
|
{
|
||||||
match format {
|
match format {
|
||||||
SongFormat::Organya => {
|
SongFormat::Organya => {
|
||||||
// we're sure that there's one element
|
// we're sure that there's one element
|
||||||
|
@ -134,7 +149,9 @@ impl SoundManager {
|
||||||
// we're sure that there's one element
|
// we're sure that there's one element
|
||||||
let path = unsafe { paths.get_unchecked(0) };
|
let path = unsafe { paths.get_unchecked(0) };
|
||||||
|
|
||||||
match filesystem::open(ctx, path).map(|f| OggStreamReader::new(f).map_err(|e| GameError::ResourceLoadError(e.to_string()))) {
|
match filesystem::open(ctx, path).map(|f| {
|
||||||
|
OggStreamReader::new(f).map_err(|e| GameError::ResourceLoadError(e.to_string()))
|
||||||
|
}) {
|
||||||
Ok(Ok(song)) => {
|
Ok(Ok(song)) => {
|
||||||
log::info!("Playing single part Ogg BGM: {} {}", song_id, path);
|
log::info!("Playing single part Ogg BGM: {} {}", song_id, path);
|
||||||
|
|
||||||
|
@ -157,16 +174,28 @@ impl SoundManager {
|
||||||
let path_loop = unsafe { paths.get_unchecked(1) };
|
let path_loop = unsafe { paths.get_unchecked(1) };
|
||||||
|
|
||||||
match (
|
match (
|
||||||
filesystem::open(ctx, path_intro).map(|f| OggStreamReader::new(f).map_err(|e| GameError::ResourceLoadError(e.to_string()))),
|
filesystem::open(ctx, path_intro).map(|f| {
|
||||||
filesystem::open(ctx, path_loop).map(|f| OggStreamReader::new(f).map_err(|e| GameError::ResourceLoadError(e.to_string()))),
|
OggStreamReader::new(f).map_err(|e| GameError::ResourceLoadError(e.to_string()))
|
||||||
|
}),
|
||||||
|
filesystem::open(ctx, path_loop).map(|f| {
|
||||||
|
OggStreamReader::new(f).map_err(|e| GameError::ResourceLoadError(e.to_string()))
|
||||||
|
}),
|
||||||
) {
|
) {
|
||||||
(Ok(Ok(song_intro)), Ok(Ok(song_loop))) => {
|
(Ok(Ok(song_intro)), Ok(Ok(song_loop))) => {
|
||||||
log::info!("Playing multi part Ogg BGM: {} {} + {}", song_id, path_intro, path_loop);
|
log::info!(
|
||||||
|
"Playing multi part Ogg BGM: {} {} + {}",
|
||||||
|
song_id,
|
||||||
|
path_intro,
|
||||||
|
path_loop
|
||||||
|
);
|
||||||
|
|
||||||
self.prev_song_id = self.current_song_id;
|
self.prev_song_id = self.current_song_id;
|
||||||
self.current_song_id = song_id;
|
self.current_song_id = song_id;
|
||||||
self.tx.send(PlaybackMessage::SaveState)?;
|
self.tx.send(PlaybackMessage::SaveState)?;
|
||||||
self.tx.send(PlaybackMessage::PlayOggSongMultiPart(Box::new(song_intro), Box::new(song_loop)))?;
|
self.tx.send(PlaybackMessage::PlayOggSongMultiPart(
|
||||||
|
Box::new(song_intro),
|
||||||
|
Box::new(song_loop),
|
||||||
|
))?;
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -197,7 +226,7 @@ impl SoundManager {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_speed(&mut self, speed: f32) -> GameResult {
|
pub fn set_speed(&self, speed: f32) -> GameResult {
|
||||||
if speed <= 0.0 {
|
if speed <= 0.0 {
|
||||||
return Err(InvalidValue(str!("Speed must be bigger than 0.0!")));
|
return Err(InvalidValue(str!("Speed must be bigger than 0.0!")));
|
||||||
}
|
}
|
||||||
|
@ -209,6 +238,72 @@ impl SoundManager {
|
||||||
pub fn current_song(&self) -> usize {
|
pub fn current_song(&self) -> usize {
|
||||||
self.current_song_id
|
self.current_song_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_sample_params_from_file<R: io::Read>(&self, id: u8, data: R) -> GameResult {
|
||||||
|
let mut reader = BufReader::new(data).lines();
|
||||||
|
let mut params = PixToneParameters::empty();
|
||||||
|
|
||||||
|
fn next_string<T: FromStr, R: io::Read>(reader: &mut Lines<BufReader<R>>) -> GameResult<T> {
|
||||||
|
loop {
|
||||||
|
if let Some(Ok(str)) = reader.next() {
|
||||||
|
let str = str.trim();
|
||||||
|
if str == "" || str.starts_with("#") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut splits = str.split(":");
|
||||||
|
|
||||||
|
let _ = splits.next();
|
||||||
|
if let Some(str) = splits.next() {
|
||||||
|
println!("{}", str);
|
||||||
|
return str.trim().parse::<T>().map_err(|_| GameError::ParseError("failed to parse the value as specified type.".to_string()))
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Err(GameError::ParseError("unexpected end.".to_string()))
|
||||||
|
};
|
||||||
|
|
||||||
|
for channel in params.channels.iter_mut() {
|
||||||
|
channel.enabled = next_string::<u8, R>(&mut reader)? != 0;
|
||||||
|
channel.length = next_string::<u32, R>(&mut reader)?;
|
||||||
|
|
||||||
|
channel.carrier.waveform_type = next_string::<u8, R>(&mut reader)?;
|
||||||
|
channel.carrier.pitch = next_string::<f32, R>(&mut reader)?;
|
||||||
|
channel.carrier.level = next_string::<i32, R>(&mut reader)?;
|
||||||
|
channel.carrier.offset = next_string::<i32, R>(&mut reader)?;
|
||||||
|
|
||||||
|
channel.frequency.waveform_type = next_string::<u8, R>(&mut reader)?;
|
||||||
|
channel.frequency.pitch = next_string::<f32, R>(&mut reader)?;
|
||||||
|
channel.frequency.level = next_string::<i32, R>(&mut reader)?;
|
||||||
|
channel.frequency.offset = next_string::<i32, R>(&mut reader)?;
|
||||||
|
|
||||||
|
channel.amplitude.waveform_type = next_string::<u8, R>(&mut reader)?;
|
||||||
|
channel.amplitude.pitch = next_string::<f32, R>(&mut reader)?;
|
||||||
|
channel.amplitude.level = next_string::<i32, R>(&mut reader)?;
|
||||||
|
channel.amplitude.offset = next_string::<i32, R>(&mut reader)?;
|
||||||
|
|
||||||
|
channel.envelope.initial = next_string::<i32, R>(&mut reader)?;
|
||||||
|
channel.envelope.time_a = next_string::<i32, R>(&mut reader)?;
|
||||||
|
channel.envelope.value_a = next_string::<i32, R>(&mut reader)?;
|
||||||
|
channel.envelope.time_b = next_string::<i32, R>(&mut reader)?;
|
||||||
|
channel.envelope.value_b = next_string::<i32, R>(&mut reader)?;
|
||||||
|
channel.envelope.time_c = next_string::<i32, R>(&mut reader)?;
|
||||||
|
channel.envelope.value_c = next_string::<i32, R>(&mut reader)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.set_sample_params(id, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_sample_params(&self, id: u8, params: PixToneParameters) -> GameResult {
|
||||||
|
self.tx.send(PlaybackMessage::SetSampleParams(id, params))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PlaybackMessage {
|
enum PlaybackMessage {
|
||||||
|
@ -222,6 +317,7 @@ enum PlaybackMessage {
|
||||||
SetSpeed(f32),
|
SetSpeed(f32),
|
||||||
SaveState,
|
SaveState,
|
||||||
RestoreState,
|
RestoreState,
|
||||||
|
SetSampleParams(u8, PixToneParameters),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq)]
|
#[derive(PartialEq, Eq)]
|
||||||
|
@ -239,7 +335,12 @@ enum PlaybackStateType {
|
||||||
Ogg(SavedOggPlaybackState),
|
Ogg(SavedOggPlaybackState),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run<T>(rx: Receiver<PlaybackMessage>, bank: SoundBank, device: &cpal::Device, config: &cpal::StreamConfig) -> GameResult
|
fn run<T>(
|
||||||
|
rx: Receiver<PlaybackMessage>,
|
||||||
|
bank: SoundBank,
|
||||||
|
device: &cpal::Device,
|
||||||
|
config: &cpal::StreamConfig,
|
||||||
|
) -> GameResult
|
||||||
where
|
where
|
||||||
T: cpal::Sample,
|
T: cpal::Sample,
|
||||||
{
|
{
|
||||||
|
@ -257,10 +358,10 @@ where
|
||||||
log::info!("Audio format: {} {}", sample_rate, channels);
|
log::info!("Audio format: {} {}", sample_rate, channels);
|
||||||
org_engine.set_sample_rate(sample_rate as usize);
|
org_engine.set_sample_rate(sample_rate as usize);
|
||||||
#[cfg(feature = "ogg-playback")]
|
#[cfg(feature = "ogg-playback")]
|
||||||
{
|
{
|
||||||
org_engine.loops = usize::MAX;
|
org_engine.loops = usize::MAX;
|
||||||
ogg_engine.set_sample_rate(sample_rate as usize);
|
ogg_engine.set_sample_rate(sample_rate as usize);
|
||||||
}
|
}
|
||||||
|
|
||||||
let buf_size = sample_rate as usize * 10 / 1000;
|
let buf_size = sample_rate as usize * 10 / 1000;
|
||||||
let mut bgm_buf = vec![0x8080; buf_size * 2];
|
let mut bgm_buf = vec![0x8080; buf_size * 2];
|
||||||
|
@ -388,6 +489,9 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(PlaybackMessage::SetSampleParams(id, params)) => {
|
||||||
|
pixtone.set_sample_parameters(id, params);
|
||||||
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -449,16 +553,25 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
if frame.len() >= 2 {
|
if frame.len() >= 2 {
|
||||||
let sample_l =
|
let sample_l = clamp(
|
||||||
clamp((((bgm_sample_l ^ 0x8000) as i16) as isize) + (((pxt_sample ^ 0x8000) as i16) as isize), -0x7fff, 0x7fff) as u16 ^ 0x8000;
|
(((bgm_sample_l ^ 0x8000) as i16) as isize) + (((pxt_sample ^ 0x8000) as i16) as isize),
|
||||||
let sample_r =
|
-0x7fff,
|
||||||
clamp((((bgm_sample_r ^ 0x8000) as i16) as isize) + (((pxt_sample ^ 0x8000) as i16) as isize), -0x7fff, 0x7fff) as u16 ^ 0x8000;
|
0x7fff,
|
||||||
|
) as u16
|
||||||
|
^ 0x8000;
|
||||||
|
let sample_r = clamp(
|
||||||
|
(((bgm_sample_r ^ 0x8000) as i16) as isize) + (((pxt_sample ^ 0x8000) as i16) as isize),
|
||||||
|
-0x7fff,
|
||||||
|
0x7fff,
|
||||||
|
) as u16
|
||||||
|
^ 0x8000;
|
||||||
|
|
||||||
frame[0] = Sample::from::<u16>(&sample_l);
|
frame[0] = Sample::from::<u16>(&sample_l);
|
||||||
frame[1] = Sample::from::<u16>(&sample_r);
|
frame[1] = Sample::from::<u16>(&sample_r);
|
||||||
} else {
|
} else {
|
||||||
let sample = clamp(
|
let sample = clamp(
|
||||||
((((bgm_sample_l ^ 0x8000) as i16) + ((bgm_sample_r ^ 0x8000) as i16)) / 2) as isize + (((pxt_sample ^ 0x8000) as i16) as isize),
|
((((bgm_sample_l ^ 0x8000) as i16) + ((bgm_sample_r ^ 0x8000) as i16)) / 2) as isize
|
||||||
|
+ (((pxt_sample ^ 0x8000) as i16) as isize),
|
||||||
-0x7fff,
|
-0x7fff,
|
||||||
0x7fff,
|
0x7fff,
|
||||||
) as u16
|
) as u16
|
||||||
|
|
|
@ -4,7 +4,7 @@ use lazy_static::lazy_static;
|
||||||
use num_traits::clamp;
|
use num_traits::clamp;
|
||||||
use vec_mut_scan::VecMutScan;
|
use vec_mut_scan::VecMutScan;
|
||||||
|
|
||||||
use crate::sound::pixtone_sfx::PIXTONE_TABLE;
|
use crate::sound::pixtone_sfx::{DEFAULT_PIXTONE_TABLE};
|
||||||
use crate::sound::stuff::cubic_interp;
|
use crate::sound::stuff::cubic_interp;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
@ -31,17 +31,7 @@ lazy_static! {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*#[test]
|
#[derive(Copy, Clone)]
|
||||||
fn test_waveforms() {
|
|
||||||
let reference = include_bytes!("pixtone_ref.dat");
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
pub struct Waveform {
|
pub struct Waveform {
|
||||||
pub waveform_type: u8,
|
pub waveform_type: u8,
|
||||||
pub pitch: f32,
|
pub pitch: f32,
|
||||||
|
@ -55,6 +45,7 @@ impl Waveform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
pub struct Envelope {
|
pub struct Envelope {
|
||||||
pub initial: i32,
|
pub initial: i32,
|
||||||
pub time_a: i32,
|
pub time_a: i32,
|
||||||
|
@ -104,6 +95,7 @@ impl Envelope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
pub struct Channel {
|
pub struct Channel {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub length: u32,
|
pub length: u32,
|
||||||
|
@ -149,6 +141,7 @@ impl Channel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
pub struct PixToneParameters {
|
pub struct PixToneParameters {
|
||||||
pub channels: [Channel; 4],
|
pub channels: [Channel; 4],
|
||||||
}
|
}
|
||||||
|
@ -204,23 +197,39 @@ pub struct PlaybackState(u8, f32, u32);
|
||||||
pub struct PixTonePlayback {
|
pub struct PixTonePlayback {
|
||||||
pub samples: HashMap<u8, Vec<i16>>,
|
pub samples: HashMap<u8, Vec<i16>>,
|
||||||
pub playback_state: Vec<PlaybackState>,
|
pub playback_state: Vec<PlaybackState>,
|
||||||
|
pub table: [PixToneParameters; 256],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
impl PixTonePlayback {
|
impl PixTonePlayback {
|
||||||
pub fn new() -> PixTonePlayback {
|
pub fn new() -> PixTonePlayback {
|
||||||
|
let mut table = [PixToneParameters::empty(); 256];
|
||||||
|
|
||||||
|
for (i, params) in DEFAULT_PIXTONE_TABLE.iter().enumerate() {
|
||||||
|
table[i] = *params;
|
||||||
|
}
|
||||||
|
|
||||||
PixTonePlayback {
|
PixTonePlayback {
|
||||||
samples: HashMap::new(),
|
samples: HashMap::new(),
|
||||||
playback_state: vec![],
|
playback_state: vec![],
|
||||||
|
table,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_samples(&mut self) {
|
pub fn create_samples(&mut self) {
|
||||||
for (i, params) in PIXTONE_TABLE.iter().enumerate() {
|
for (i, params) in self.table.iter().enumerate() {
|
||||||
self.samples.insert(i as u8, params.synth());
|
self.samples.insert(i as u8, params.synth());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_sample_parameters(&mut self, id: u8, params: PixToneParameters) {
|
||||||
|
self.table[id as usize] = params;
|
||||||
|
self.samples.insert(id, params.synth());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_sample_data(&mut self, id: u8, data: Vec<i16>) {
|
||||||
|
self.samples.insert(id, data);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn play_sfx(&mut self, id: u8) {
|
pub fn play_sfx(&mut self, id: u8) {
|
||||||
for state in self.playback_state.iter_mut() {
|
for state in self.playback_state.iter_mut() {
|
||||||
if state.0 == id && state.2 == 0 {
|
if state.0 == id && state.2 == 0 {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::sound::pixtone::{Channel, Envelope, PixToneParameters, Waveform};
|
use crate::sound::pixtone::{Channel, Envelope, PixToneParameters, Waveform};
|
||||||
|
|
||||||
pub static PIXTONE_TABLE: [PixToneParameters; 160] = [
|
pub static DEFAULT_PIXTONE_TABLE: [PixToneParameters; 160] = [
|
||||||
PixToneParameters::empty(), // fx0
|
PixToneParameters::empty(), // fx0
|
||||||
PixToneParameters { // fx1
|
PixToneParameters { // fx1
|
||||||
channels: [
|
channels: [
|
||||||
|
@ -40,21 +40,21 @@ pub static PIXTONE_TABLE: [PixToneParameters; 160] = [
|
||||||
Channel::disabled(),
|
Channel::disabled(),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
PixToneParameters { // fx2 (CS+)
|
PixToneParameters { // fx2
|
||||||
channels: [
|
channels: [
|
||||||
Channel {
|
Channel {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
length: 2000,
|
length: 4000,
|
||||||
carrier: Waveform {
|
carrier: Waveform {
|
||||||
waveform_type: 0,
|
waveform_type: 1,
|
||||||
pitch: 92.000000,
|
pitch: 54.000000,
|
||||||
level: 32,
|
level: 32,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
},
|
},
|
||||||
frequency: Waveform {
|
frequency: Waveform {
|
||||||
waveform_type: 0,
|
waveform_type: 5,
|
||||||
pitch: 3.000000,
|
pitch: 0.100000,
|
||||||
level: 44,
|
level: 33,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
},
|
},
|
||||||
amplitude: Waveform {
|
amplitude: Waveform {
|
||||||
|
@ -64,11 +64,11 @@ pub static PIXTONE_TABLE: [PixToneParameters; 160] = [
|
||||||
offset: 0,
|
offset: 0,
|
||||||
},
|
},
|
||||||
envelope: Envelope {
|
envelope: Envelope {
|
||||||
initial: 7,
|
initial: 53,
|
||||||
time_a: 2,
|
time_a: 57,
|
||||||
value_a: 18,
|
value_a: 44,
|
||||||
time_b: 128,
|
time_b: 128,
|
||||||
value_b: 0,
|
value_b: 24,
|
||||||
time_c: 255,
|
time_c: 255,
|
||||||
value_c: 0,
|
value_c: 0,
|
||||||
},
|
},
|
||||||
|
@ -78,44 +78,6 @@ pub static PIXTONE_TABLE: [PixToneParameters; 160] = [
|
||||||
Channel::disabled(),
|
Channel::disabled(),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
// PixToneParameters { // fx2
|
|
||||||
// channels: [
|
|
||||||
// Channel {
|
|
||||||
// enabled: true,
|
|
||||||
// length: 4000,
|
|
||||||
// carrier: Waveform {
|
|
||||||
// waveform_type: 1,
|
|
||||||
// pitch: 54.000000,
|
|
||||||
// level: 32,
|
|
||||||
// offset: 0,
|
|
||||||
// },
|
|
||||||
// frequency: Waveform {
|
|
||||||
// waveform_type: 5,
|
|
||||||
// pitch: 0.100000,
|
|
||||||
// level: 33,
|
|
||||||
// offset: 0,
|
|
||||||
// },
|
|
||||||
// amplitude: Waveform {
|
|
||||||
// waveform_type: 0,
|
|
||||||
// pitch: 0.000000,
|
|
||||||
// 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 { // fx3
|
PixToneParameters { // fx3
|
||||||
channels: [
|
channels: [
|
||||||
Channel {
|
Channel {
|
||||||
|
|
Loading…
Reference in New Issue