EWF generation

master
Ashley N. 2023-08-20 16:06:29 -04:00
parent e55f9e3fe8
commit 34e4bbb3db
5 changed files with 84 additions and 23 deletions

View File

@ -11,3 +11,6 @@ image = "^0.23"
colour = "^0.5" colour = "^0.5"
clap = "^2.33" clap = "^2.33"
flate2 = "1.0.26" flate2 = "1.0.26"
samplerate = "0.2.4"
hound = "3.5.0"
pitch_shift = "1.0.0"

View File

@ -82,20 +82,24 @@ fn run() -> Result<(), Box<dyn Error>> {
let result = DmfModule::from_file( &input_filename )?; let result = DmfModule::from_file( &input_filename )?;
let eef_files = result.get_eefs()?; for ( filename, data ) in result.get_eefs()? {
for ( filename, data ) in eef_files {
println!( "Creating EEF instrument file {}", filename ); println!( "Creating EEF instrument file {}", filename );
let mut file = File::create( filename )?; let mut file = File::create( filename )?;
file.write_all( &data )? file.write_all( &data )?
} }
let eif_files = result.get_eifs()?; for ( filename, data ) in result.get_eifs()? {
for ( filename, data ) in eif_files {
println!( "Creating EIF instrument file {}", filename ); println!( "Creating EIF instrument file {}", filename );
let mut file = File::create( filename )?; let mut file = File::create( filename )?;
file.write_all( &data )? 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 !! // TODO !!
Ok( () ) Ok( () )

View File

@ -6,4 +6,6 @@ pub trait EchoFormat {
fn get_eifs( &self ) -> Result<HashMap<String, Vec<u8>>, Box<dyn Error>>; fn get_eifs( &self ) -> Result<HashMap<String, Vec<u8>>, Box<dyn Error>>;
fn get_ewfs( &self ) -> Result<HashMap<String, Vec<u8>>, Box<dyn Error>>;
} }

View File

@ -1,11 +1,13 @@
use std::{error::Error, fs::File, io::Read, convert::TryInto, collections::HashMap, cmp::{min, max}}; use std::{error::Error, fs::File, io::Read, convert::TryInto, collections::HashMap, cmp::{min, max}};
use flate2::read::ZlibDecoder; 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_MAGIC_NUMBER: &'static str = ".DelekDefleMask.";
const DMF_SUPPORTED_VERSION: u8 = 27; const DMF_SUPPORTED_VERSION: u8 = 27;
const DMF_MD: u8 = 0x02; const DMF_MD: u8 = 0x02;
const DMF_MD_ENHANCED_CH3: u8 = 0x42; const DMF_MD_ENHANCED_CH3: u8 = 0x42;
const ECHO_EWF_SAMPLE_RATE: u32 = 10650;
#[derive(Debug)] #[derive(Debug)]
pub enum FrameMode { pub enum FrameMode {
@ -566,11 +568,11 @@ impl DmfModule {
let sample_name_chars = get_u8( bytes.by_ref() )?; let sample_name_chars = get_u8( bytes.by_ref() )?;
let name = get_string( bytes.by_ref(), sample_name_chars.into() )?; let name = get_string( bytes.by_ref(), sample_name_chars.into() )?;
let rate = match get_u8( bytes.by_ref() )? { let rate = match get_u8( bytes.by_ref() )? {
1 => SampleRate::Hz8000, 1 => 8000,
2 => SampleRate::Hz11025, 2 => 11025,
3 => SampleRate::Hz16000, 3 => 16000,
4 => SampleRate::Hz22050, 4 => 22050,
5 => SampleRate::Hz32000, 5 => 32000,
invalid => return Err( format!( "invalid file: invalid sample rate {}", invalid ) )? invalid => return Err( format!( "invalid file: invalid sample rate {}", invalid ) )?
}; };
@ -592,7 +594,7 @@ impl DmfModule {
println!( "{}:\t{:?}", i, sample ); println!( "{}:\t{:?}", i, sample );
for _ in 0..sample_size { for _ in 0..sample_size {
sample.data.push( get_u16( bytes.by_ref() )? ); sample.data.push( get_i16( bytes.by_ref() )? );
} }
samples.push( sample ); samples.push( sample );
@ -803,6 +805,65 @@ impl EchoFormat for DmfModule {
Ok( eifs ) Ok( eifs )
} }
fn get_ewfs( &self ) -> Result<HashMap<String, Vec<u8>>, Box<dyn Error>> {
let mut ewfs: HashMap<String, Vec<u8>> = HashMap::new();
for sample in &self.samples {
// Amplify data in original sample
let data: Vec<i16> = 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<f32> = 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<f32> = 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<u8> = 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<u8, Box<dyn Error>> { fn get_eef_volume( dmf_volume: u8 ) -> Result<u8, Box<dyn Error>> {

View File

@ -15,15 +15,6 @@ pub struct PsgSettings {
pub wavetable: PsgEnvelope< u32 > pub wavetable: PsgEnvelope< u32 >
} }
#[derive(Debug)]
pub enum SampleRate {
Hz8000,
Hz11025,
Hz16000,
Hz22050,
Hz32000
}
#[derive(Debug)] #[derive(Debug)]
pub enum SampleFormat { pub enum SampleFormat {
Bits8, Bits8,
@ -33,11 +24,11 @@ pub enum SampleFormat {
#[derive(Debug)] #[derive(Debug)]
pub struct Sample { pub struct Sample {
pub name: String, pub name: String,
pub rate: SampleRate, pub rate: u32,
pub pitch: i8, pub pitch: i8,
pub amp: u8, pub amp: u8,
pub bitrate: SampleFormat, pub bitrate: SampleFormat,
pub data: Vec<u16> pub data: Vec<i16>
} }
#[derive(Debug)] #[derive(Debug)]