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::engine_constants::npcs::NPCConsts;
|
||||
use crate::player::ControlMode;
|
||||
use crate::sound::pixtone::{Channel, PixToneParameters, Waveform, Envelope};
|
||||
use crate::sound::SoundManager;
|
||||
use crate::str;
|
||||
use crate::text_script::TextScriptEncoding;
|
||||
|
||||
|
@ -1395,7 +1397,7 @@ impl EngineConstants {
|
|||
inventory_item_count_x: 6,
|
||||
text_shadow: false,
|
||||
text_speed_normal: 4,
|
||||
text_speed_fast: 1
|
||||
text_speed_fast: 1,
|
||||
},
|
||||
title: TitleConsts {
|
||||
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...");
|
||||
|
||||
self.is_cs_plus = true;
|
||||
|
@ -1482,6 +1484,33 @@ impl EngineConstants {
|
|||
self.font_space_offset = 2.0;
|
||||
self.soundtracks.insert("Remastered".to_string(), "/base/Ogg11/".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) {
|
||||
|
|
|
@ -130,16 +130,17 @@ pub struct SharedGameState {
|
|||
impl SharedGameState {
|
||||
pub fn new(ctx: &mut Context) -> GameResult<SharedGameState> {
|
||||
let mut constants = EngineConstants::defaults();
|
||||
let sound_manager = SoundManager::new(ctx)?;
|
||||
let mut base_path = "/";
|
||||
let settings = Settings::load(ctx)?;
|
||||
|
||||
if filesystem::exists(ctx, "/base/Nicalis.bmp") {
|
||||
info!("Cave Story+ (PC) data files detected.");
|
||||
constants.apply_csplus_patches();
|
||||
constants.apply_csplus_patches(&sound_manager);
|
||||
base_path = "/base/";
|
||||
} else if filesystem::exists(ctx, "/base/lighting.tbl") {
|
||||
info!("Cave Story+ (Switch) data files detected.");
|
||||
constants.apply_csplus_patches();
|
||||
constants.apply_csplus_patches(&sound_manager);
|
||||
constants.apply_csplus_nx_patches();
|
||||
base_path = "/base/";
|
||||
} else if filesystem::exists(ctx, "/mrmap.bin") {
|
||||
|
@ -157,6 +158,32 @@ impl SharedGameState {
|
|||
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);
|
||||
|
||||
#[cfg(feature = "hooks")]
|
||||
|
@ -195,7 +222,7 @@ impl SharedGameState {
|
|||
texture_set,
|
||||
#[cfg(feature = "scripting")]
|
||||
lua: LuaScriptingState::new(),
|
||||
sound_manager: SoundManager::new(ctx)?,
|
||||
sound_manager,
|
||||
settings,
|
||||
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::{Receiver, Sender};
|
||||
use std::sync::mpsc::{Receiver, Sender, TryRecvError};
|
||||
use std::time::Duration;
|
||||
|
||||
use cpal::Sample;
|
||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
use cpal::Sample;
|
||||
#[cfg(feature = "ogg-playback")]
|
||||
use lewton::inside_ogg::OggStreamReader;
|
||||
use num_traits::clamp;
|
||||
|
||||
use crate::engine_constants::EngineConstants;
|
||||
use crate::framework::context::Context;
|
||||
use crate::framework::error::{GameError, GameResult};
|
||||
use crate::framework::error::GameError::{AudioError, InvalidValue};
|
||||
use crate::framework::error::{GameError, GameResult};
|
||||
use crate::framework::filesystem;
|
||||
use crate::framework::filesystem::File;
|
||||
use crate::settings::Settings;
|
||||
|
@ -19,15 +20,17 @@ use crate::settings::Settings;
|
|||
use crate::sound::ogg_playback::{OggPlaybackEngine, SavedOggPlaybackState};
|
||||
use crate::sound::org_playback::{OrgPlaybackEngine, SavedOrganyaPlaybackState};
|
||||
use crate::sound::organya::Song;
|
||||
use crate::sound::pixtone::PixTonePlayback;
|
||||
use crate::sound::pixtone::{PixToneParameters, PixTonePlayback};
|
||||
use crate::sound::wave_bank::SoundBank;
|
||||
use crate::str;
|
||||
use std::io::{BufReader, BufRead, Lines};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[cfg(feature = "ogg-playback")]
|
||||
mod ogg_playback;
|
||||
mod org_playback;
|
||||
mod organya;
|
||||
mod pixtone;
|
||||
pub mod pixtone;
|
||||
mod pixtone_sfx;
|
||||
mod stuff;
|
||||
mod wav;
|
||||
|
@ -52,7 +55,8 @@ impl 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 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/organya-wavetable-doukutsu.bin")?)?;
|
||||
|
@ -70,11 +74,17 @@ impl SoundManager {
|
|||
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));
|
||||
}
|
||||
|
||||
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 {
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -98,16 +108,21 @@ impl SoundManager {
|
|||
|
||||
let songs_paths = paths.iter().map(|prefix| {
|
||||
[
|
||||
#[cfg(feature = "ogg-playback")]
|
||||
(SongFormat::OggMultiPart, vec![format!("{}{}_intro.ogg", prefix, song_name), format!("{}{}_loop.ogg", prefix, song_name)]),
|
||||
#[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::OggSinglePart, vec![format!("{}{}.ogg", prefix, song_name)]),
|
||||
(SongFormat::Organya, vec![format!("{}{}.org", prefix, song_name)]),
|
||||
]
|
||||
});
|
||||
|
||||
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 {
|
||||
SongFormat::Organya => {
|
||||
// we're sure that there's one element
|
||||
|
@ -134,7 +149,9 @@ impl SoundManager {
|
|||
// we're sure that there's one element
|
||||
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)) => {
|
||||
log::info!("Playing single part Ogg BGM: {} {}", song_id, path);
|
||||
|
||||
|
@ -157,16 +174,28 @@ impl SoundManager {
|
|||
let path_loop = unsafe { paths.get_unchecked(1) };
|
||||
|
||||
match (
|
||||
filesystem::open(ctx, path_intro).map(|f| 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()))),
|
||||
filesystem::open(ctx, path_intro).map(|f| {
|
||||
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))) => {
|
||||
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.current_song_id = song_id;
|
||||
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(());
|
||||
}
|
||||
|
@ -197,7 +226,7 @@ impl SoundManager {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_speed(&mut self, speed: f32) -> GameResult {
|
||||
pub fn set_speed(&self, speed: f32) -> GameResult {
|
||||
if speed <= 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 {
|
||||
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 {
|
||||
|
@ -222,6 +317,7 @@ enum PlaybackMessage {
|
|||
SetSpeed(f32),
|
||||
SaveState,
|
||||
RestoreState,
|
||||
SetSampleParams(u8, PixToneParameters),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
|
@ -239,7 +335,12 @@ enum PlaybackStateType {
|
|||
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
|
||||
T: cpal::Sample,
|
||||
{
|
||||
|
@ -257,10 +358,10 @@ where
|
|||
log::info!("Audio format: {} {}", sample_rate, channels);
|
||||
org_engine.set_sample_rate(sample_rate as usize);
|
||||
#[cfg(feature = "ogg-playback")]
|
||||
{
|
||||
org_engine.loops = usize::MAX;
|
||||
ogg_engine.set_sample_rate(sample_rate as usize);
|
||||
}
|
||||
{
|
||||
org_engine.loops = usize::MAX;
|
||||
ogg_engine.set_sample_rate(sample_rate as usize);
|
||||
}
|
||||
|
||||
let buf_size = sample_rate as usize * 10 / 1000;
|
||||
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(_) => {
|
||||
break;
|
||||
}
|
||||
|
@ -449,16 +553,25 @@ where
|
|||
}
|
||||
|
||||
if frame.len() >= 2 {
|
||||
let sample_l =
|
||||
clamp((((bgm_sample_l ^ 0x8000) as i16) as isize) + (((pxt_sample ^ 0x8000) as i16) as isize), -0x7fff, 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;
|
||||
let sample_l = clamp(
|
||||
(((bgm_sample_l ^ 0x8000) as i16) as isize) + (((pxt_sample ^ 0x8000) as i16) as isize),
|
||||
-0x7fff,
|
||||
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[1] = Sample::from::<u16>(&sample_r);
|
||||
} else {
|
||||
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,
|
||||
) as u16
|
||||
|
|
|
@ -4,7 +4,7 @@ use lazy_static::lazy_static;
|
|||
use num_traits::clamp;
|
||||
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;
|
||||
|
||||
lazy_static! {
|
||||
|
@ -31,17 +31,7 @@ lazy_static! {
|
|||
};
|
||||
}
|
||||
|
||||
/*#[test]
|
||||
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));
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Waveform {
|
||||
pub waveform_type: u8,
|
||||
pub pitch: f32,
|
||||
|
@ -55,6 +45,7 @@ impl Waveform {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Envelope {
|
||||
pub initial: i32,
|
||||
pub time_a: i32,
|
||||
|
@ -104,6 +95,7 @@ impl Envelope {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Channel {
|
||||
pub enabled: bool,
|
||||
pub length: u32,
|
||||
|
@ -149,6 +141,7 @@ impl Channel {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct PixToneParameters {
|
||||
pub channels: [Channel; 4],
|
||||
}
|
||||
|
@ -204,23 +197,39 @@ pub struct PlaybackState(u8, f32, u32);
|
|||
pub struct PixTonePlayback {
|
||||
pub samples: HashMap<u8, Vec<i16>>,
|
||||
pub playback_state: Vec<PlaybackState>,
|
||||
pub table: [PixToneParameters; 256],
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl PixTonePlayback {
|
||||
pub fn new() -> PixTonePlayback {
|
||||
let mut table = [PixToneParameters::empty(); 256];
|
||||
|
||||
for (i, params) in DEFAULT_PIXTONE_TABLE.iter().enumerate() {
|
||||
table[i] = *params;
|
||||
}
|
||||
|
||||
PixTonePlayback {
|
||||
samples: HashMap::new(),
|
||||
playback_state: vec![],
|
||||
table,
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
for state in self.playback_state.iter_mut() {
|
||||
if state.0 == id && state.2 == 0 {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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 { // fx1
|
||||
channels: [
|
||||
|
@ -40,21 +40,21 @@ pub static PIXTONE_TABLE: [PixToneParameters; 160] = [
|
|||
Channel::disabled(),
|
||||
],
|
||||
},
|
||||
PixToneParameters { // fx2 (CS+)
|
||||
PixToneParameters { // fx2
|
||||
channels: [
|
||||
Channel {
|
||||
enabled: true,
|
||||
length: 2000,
|
||||
length: 4000,
|
||||
carrier: Waveform {
|
||||
waveform_type: 0,
|
||||
pitch: 92.000000,
|
||||
waveform_type: 1,
|
||||
pitch: 54.000000,
|
||||
level: 32,
|
||||
offset: 0,
|
||||
},
|
||||
frequency: Waveform {
|
||||
waveform_type: 0,
|
||||
pitch: 3.000000,
|
||||
level: 44,
|
||||
waveform_type: 5,
|
||||
pitch: 0.100000,
|
||||
level: 33,
|
||||
offset: 0,
|
||||
},
|
||||
amplitude: Waveform {
|
||||
|
@ -64,11 +64,11 @@ pub static PIXTONE_TABLE: [PixToneParameters; 160] = [
|
|||
offset: 0,
|
||||
},
|
||||
envelope: Envelope {
|
||||
initial: 7,
|
||||
time_a: 2,
|
||||
value_a: 18,
|
||||
initial: 53,
|
||||
time_a: 57,
|
||||
value_a: 44,
|
||||
time_b: 128,
|
||||
value_b: 0,
|
||||
value_b: 24,
|
||||
time_c: 255,
|
||||
value_c: 0,
|
||||
},
|
||||
|
@ -78,44 +78,6 @@ pub static PIXTONE_TABLE: [PixToneParameters; 160] = [
|
|||
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
|
||||
channels: [
|
||||
Channel {
|
||||
|
|
Loading…
Reference in New Issue