From 546376cc02535216fb7ed27bf9637eed22b82a7c Mon Sep 17 00:00:00 2001 From: ashley Date: Fri, 25 Aug 2023 23:20:15 -0400 Subject: [PATCH] Refactor Echo instrument generation --- Cargo.toml | 5 +++-- src/main.rs | 24 +++++++++++++++------ src/reskit/soundtrack/engines/echo.rs | 2 +- src/reskit/soundtrack/formats/dmf.rs | 31 +++++++++++++++++++++++++-- 4 files changed, 51 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5fad463..4746fac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reskit" -version = "0.1.0" +version = "0.0.2" authors = ["ne0ndrag0n "] edition = "2018" @@ -15,4 +15,5 @@ samplerate = "0.2.4" hound = "3.5.0" pitch_shift = "1.0.0" uuid = { version = "1.4.1", features = [ "v4" ] } -linked_hash_set = "0.1.4" \ No newline at end of file +linked_hash_set = "0.1.4" +convert_case = "0.6.0" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index dd86448..b6aca27 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,9 +17,9 @@ use crate::reskit::utility::print_good; fn run() -> Result<(), Box> { let matches = App::new( "reskit" ) - .version( "0.0.2a" ) + .version( env!( "CARGO_PKG_VERSION" ) ) .author( "(c) 2021-2023 Ashley N. " ) - .about( "Sega Megadrive resource kit and format converter" ) + .about( "The Retro Entertainment Software Toolkit - Tools for authoring software for legacy video game systems" ) .subcommand( SubCommand::with_name( "tileset" ) .about( "Generate a Sega Megadrive VDP tileset + palette from a 15-colour image" ) @@ -49,7 +49,6 @@ fn run() -> Result<(), Box> { .arg_from_usage( "-o, --output= 'Specify output filename (may output multiple files depending on format)'") .arg( Arg::with_name( "TRACKER_FORMAT" ) - .short( "tf" ) .long( "tracker-format" ) .help( "Specify tracker module format (valid options: dmf (DefleMask Tracker))") .default_value( "dmf" ) @@ -57,12 +56,25 @@ fn run() -> Result<(), Box> { ) .arg( Arg::with_name( "DRIVER_FORMAT" ) - .short( "df" ) .long( "driver-format" ) - .help( "Specify console sound driver format (valid options: esf (Echo))") + .help( "Specify on-console sound driver format (valid options: esf (Echo))") .default_value( "esf" ) .takes_value( true ) ) + .arg( + Arg::with_name( "EXPORT_INSTRUMENT_LIST_LABEL" ) + .long( "instrument-list-label" ) + .help( "In sound drivers supporting generated-code instrument lists, specify label to use for instrument list" ) + .default_value( "MuzInstrumentList" ) + .takes_value( true ) + ) + .arg( + Arg::with_name( "EXPORT_INSTRUMENT_LIST_FORMAT" ) + .long( "instrument-list-format" ) + .help( "In sound drivers supporting generated-code instrument lists, specify output format for instrument list" ) + .default_value( "asmx" ) + .takes_value( true ) + ) ) .get_matches(); @@ -84,7 +96,7 @@ fn run() -> Result<(), Box> { let result = DmfModule::from_file( &input_filename )?; - for ( filename, data ) in result.get_instruments()? { + for ( filename, data ) in result.get_instruments( matches.value_of( "EXPORT_INSTRUMENT_LIST_LABEL" ).ok_or( "internal error" )? )? { println!( "Creating instrument file {}", filename ); let mut file = File::create( filename )?; file.write_all( &data )? diff --git a/src/reskit/soundtrack/engines/echo.rs b/src/reskit/soundtrack/engines/echo.rs index 42d1250..673e7d5 100644 --- a/src/reskit/soundtrack/engines/echo.rs +++ b/src/reskit/soundtrack/engines/echo.rs @@ -55,7 +55,7 @@ pub type EchoEvent = Vec; pub trait EchoFormat { - fn get_instruments( &self ) -> Result>, Box>; + fn get_instruments( &self, instrument_list_label: &str ) -> Result>, Box>; fn get_esf( &self ) -> Result, Box>; diff --git a/src/reskit/soundtrack/formats/dmf.rs b/src/reskit/soundtrack/formats/dmf.rs index be30475..cf5f5ef 100644 --- a/src/reskit/soundtrack/formats/dmf.rs +++ b/src/reskit/soundtrack/formats/dmf.rs @@ -3,6 +3,7 @@ use flate2::read::ZlibDecoder; use linked_hash_set::LinkedHashSet; use samplerate::convert; use uuid::Uuid; +use convert_case::{Case, Casing}; use crate::reskit::{utility::{get_string, get_u8, skip, get_u32, get_i8, get_i32, get_u16, get_i16, Ring, print_warning, print_info}, soundtrack::{types::{SampleFormat, PsgEnvelope, Note, Sample, PsgSettings, PatternRow, Effect, DcsgChannelMode, NoiseType, Channel, Instrument, InstrumentType, Fm2612Operator, Fm2612Settings}, engines::echo::{EchoFormat, EchoEvent, ESF_FM_1, ESF_FM_2, ESF_FM_3, ESF_FM_4, ESF_FM_5, ESF_PSG_1, ESF_FM_6, ESF_PSG_2, ESF_PSG_3, ESF_PSG_4, get_events_for_row, compact_delays}}}; const DMF_MAGIC_NUMBER: &'static str = ".DelekDefleMask."; @@ -30,6 +31,7 @@ pub enum FrameMode { } pub struct DmfModule { + path: String, platform: u8, version: u8, time_base: u8, @@ -621,7 +623,7 @@ impl DmfModule { Ok( DmfModule { - platform, version, time_base, speed_a, speed_b, frame_mode, rows_per_pattern, + path: path.to_string(), platform, version, time_base, speed_a, speed_b, frame_mode, rows_per_pattern, pattern_matrix, channel_patterns, instruments, samples } ) @@ -631,12 +633,16 @@ impl DmfModule { impl EchoFormat for DmfModule { - fn get_instruments( &self ) -> Result>, Box> { + fn get_instruments( &self, instr_list_label: &str ) -> Result>, Box> { let mut files: HashMap> = HashMap::new(); + // This next list preserves order of filenames to generate the echo .asm file + let mut instrument_filenames: Vec = Vec::new(); for instrument in &self.instruments { match &instrument.instrument_type { InstrumentType::Fm2612( settings ) => { + instrument_filenames.push( format!( "{}.eif", instrument.name ) ); + // Create feedback + algorithm byte let alg_fb: u8 = ( settings.fb << 3 ) | settings.alg; @@ -692,6 +698,8 @@ impl EchoFormat for DmfModule { files.insert( format!( "{}.eif", if instrument.name.is_empty() { Uuid::new_v4().to_string() } else { instrument.name.to_owned() } ), eif ); }, InstrumentType::PsgDcsg( settings ) => { + instrument_filenames.push( format!( "{}.eef", instrument.name ) ); + // Echo uses volume and arpeggio envelopes // Noise envelopes are usable but cannot be articulated in an instrument. // For use of the noise envelope, use ESF $0Bnn/$3Bnn and $3Ann in the stream. @@ -815,6 +823,8 @@ impl EchoFormat for DmfModule { } for sample in &self.samples { + instrument_filenames.push( format!( "{}.ewf", sample.name ) ); + // Amplify data in original sample let data: Vec = sample.data .iter() @@ -867,6 +877,23 @@ impl EchoFormat for DmfModule { files.insert( format!( "{}.ewf", if sample.name.is_empty() { Uuid::new_v4().to_string() } else { sample.name.to_owned() } ), data ); } + // Write Echo ASM file that includes all the instruments + let mut echo_asm: String = format!( "; Echo instrument definitions file\n; Generated by reskit v{}\n\n", env!( "CARGO_PKG_VERSION" ) ); + + echo_asm += &format!( "{}:\n", instr_list_label ); + for filename in &instrument_filenames { + echo_asm += &format!( "\tEcho_ListEntry Instr_{}\n", filename.replace( ".", "_" ).to_case( Case::Pascal ) ); + } + + echo_asm += &format!( "\tEcho_ListEnd\n\n" ); + + for filename in &instrument_filenames { + echo_asm += &format!( "Instr_{}:\n", filename.replace( ".", "_" ).to_case( Case::Pascal ) ); + echo_asm += &format!( "\tincbin '{}'\n\n", filename ); + } + + files.insert( format!( "{}_instruments.asm", self.path ), echo_asm.into_bytes() ); + Ok( files ) }