diff --git a/Cargo.toml b/Cargo.toml index 4b9f3ee..9389907 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,7 @@ edition = "2018" image = "^0.23" colour = "^0.5" clap = "^2.33" -flate2 = "1.0.26" \ No newline at end of file +flate2 = "1.0.26" +samplerate = "0.2.4" +hound = "3.5.0" +pitch_shift = "1.0.0" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 9244676..7325b6e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -82,20 +82,24 @@ fn run() -> Result<(), Box> { let result = DmfModule::from_file( &input_filename )?; - let eef_files = result.get_eefs()?; - for ( filename, data ) in eef_files { + for ( filename, data ) in result.get_eefs()? { println!( "Creating EEF instrument file {}", filename ); let mut file = File::create( filename )?; file.write_all( &data )? } - let eif_files = result.get_eifs()?; - for ( filename, data ) in eif_files { + for ( filename, data ) in result.get_eifs()? { println!( "Creating EIF instrument file {}", filename ); let mut file = File::create( filename )?; file.write_all( &data )? } + for ( filename, data ) in result.get_ewfs()? { + println!( "Creating EWF audio file {}", filename ); + let mut file = File::create( filename )?; + file.write_all( &data )? + } + // TODO !! Ok( () ) diff --git a/src/reskit/soundtrack/engines/echo.rs b/src/reskit/soundtrack/engines/echo.rs index 2566efe..d806168 100644 --- a/src/reskit/soundtrack/engines/echo.rs +++ b/src/reskit/soundtrack/engines/echo.rs @@ -6,4 +6,6 @@ pub trait EchoFormat { fn get_eifs( &self ) -> Result>, Box>; + fn get_ewfs( &self ) -> Result>, Box>; + } \ No newline at end of file diff --git a/src/reskit/soundtrack/formats/dmf.rs b/src/reskit/soundtrack/formats/dmf.rs index 4de4038..ee5e56e 100644 --- a/src/reskit/soundtrack/formats/dmf.rs +++ b/src/reskit/soundtrack/formats/dmf.rs @@ -1,11 +1,13 @@ use std::{error::Error, fs::File, io::Read, convert::TryInto, collections::HashMap, cmp::{min, max}}; use flate2::read::ZlibDecoder; -use crate::reskit::{utility::{get_string, get_u8, skip, get_u32, get_i8, get_i32, get_u16, get_i16, Ring, print_warning}, soundtrack::{types::{SampleRate, SampleFormat, PsgEnvelope, Note, Sample, PsgSettings}, engines::echo::EchoFormat}}; +use samplerate::convert; +use crate::reskit::{utility::{get_string, get_u8, skip, get_u32, get_i8, get_i32, get_u16, get_i16, Ring, print_warning}, soundtrack::{types::{SampleFormat, PsgEnvelope, Note, Sample, PsgSettings}, engines::echo::EchoFormat}}; const DMF_MAGIC_NUMBER: &'static str = ".DelekDefleMask."; const DMF_SUPPORTED_VERSION: u8 = 27; const DMF_MD: u8 = 0x02; const DMF_MD_ENHANCED_CH3: u8 = 0x42; +const ECHO_EWF_SAMPLE_RATE: u32 = 10650; #[derive(Debug)] pub enum FrameMode { @@ -566,11 +568,11 @@ impl DmfModule { let sample_name_chars = get_u8( bytes.by_ref() )?; let name = get_string( bytes.by_ref(), sample_name_chars.into() )?; let rate = match get_u8( bytes.by_ref() )? { - 1 => SampleRate::Hz8000, - 2 => SampleRate::Hz11025, - 3 => SampleRate::Hz16000, - 4 => SampleRate::Hz22050, - 5 => SampleRate::Hz32000, + 1 => 8000, + 2 => 11025, + 3 => 16000, + 4 => 22050, + 5 => 32000, invalid => return Err( format!( "invalid file: invalid sample rate {}", invalid ) )? }; @@ -592,7 +594,7 @@ impl DmfModule { println!( "{}:\t{:?}", i, sample ); for _ in 0..sample_size { - sample.data.push( get_u16( bytes.by_ref() )? ); + sample.data.push( get_i16( bytes.by_ref() )? ); } samples.push( sample ); @@ -803,6 +805,65 @@ impl EchoFormat for DmfModule { Ok( eifs ) } + fn get_ewfs( &self ) -> Result>, Box> { + let mut ewfs: HashMap> = HashMap::new(); + + for sample in &self.samples { + // Amplify data in original sample + let data: Vec = sample.data + .iter() + .map( | pcm_sample | { + let big_pcm_sample: i32 = min( ( *pcm_sample as i32 ) + sample.amp as i32, i16::MAX as i32 ); + + big_pcm_sample as i16 + } ) + .collect(); + + // Convert to f32 for various library operations + let data: Vec = data + .into_iter() + .map( | pcm_sample | { + if let SampleFormat::Bits8 = sample.bitrate { + ( pcm_sample as u8 ) as f32 / 255.0 + } else { + pcm_sample as f32 / 32767.0 + } + } ) + .collect(); + + // TODO: Adjust pitch using crate pitch_shift + if sample.pitch > 0 { + print_warning( "pitch shift not yet implemented for pcm samples. your drums/voice samples/etc may sound a bit funny" ); + } + + // Use libsamplerate to resample from whatever it is now to 10650Hz, the sample rate + // required by Echo sound engine EWF samples. + let data: Vec = convert( sample.rate, ECHO_EWF_SAMPLE_RATE, 1, samplerate::ConverterType::SincBestQuality, &data )?; + + // Convert from f32 back to i16, then downconvert to u8 + let mut data: Vec = data + .into_iter() + .map( | pcm_sample | { + let pcm_sample = ( ( pcm_sample + 1.0 ) * 128.0 ) as u8; + + // Do not end stream prematurely (EWF format uses 0xFF to end stream) + if pcm_sample == 0xFF { + 0xFE + } else { + pcm_sample + } + } ) + .collect(); + + // Terminate stream + data.push( 0xFF ); + + ewfs.insert( format!( "{}.ewf", sample.name ), data ); + } + + Ok( ewfs ) + } + } fn get_eef_volume( dmf_volume: u8 ) -> Result> { diff --git a/src/reskit/soundtrack/types.rs b/src/reskit/soundtrack/types.rs index c053df7..289715c 100644 --- a/src/reskit/soundtrack/types.rs +++ b/src/reskit/soundtrack/types.rs @@ -15,15 +15,6 @@ pub struct PsgSettings { pub wavetable: PsgEnvelope< u32 > } -#[derive(Debug)] -pub enum SampleRate { - Hz8000, - Hz11025, - Hz16000, - Hz22050, - Hz32000 -} - #[derive(Debug)] pub enum SampleFormat { Bits8, @@ -33,11 +24,11 @@ pub enum SampleFormat { #[derive(Debug)] pub struct Sample { pub name: String, - pub rate: SampleRate, + pub rate: u32, pub pitch: i8, pub amp: u8, pub bitrate: SampleFormat, - pub data: Vec + pub data: Vec } #[derive(Debug)]