Multi-file instrument combining and export (untested)

master
Ashley N. 2023-09-01 13:55:10 -04:00
parent 62790d4c9c
commit 2d9fcb8e34
8 changed files with 469 additions and 319 deletions

View File

@ -15,4 +15,5 @@ samplerate = "0.2.4"
hound = "3.5.0" hound = "3.5.0"
pitch_shift = "1.0.0" pitch_shift = "1.0.0"
linked_hash_set = "0.1.4" linked_hash_set = "0.1.4"
linked-hash-map = "0.5.6"
convert_case = "0.6.0" convert_case = "0.6.0"

View File

@ -1,6 +1,6 @@
use std::{error::Error, fs::File, io::Write}; use std::{error::Error, fs::File, io::Write, path::Path};
use clap::Parser; use clap::Parser;
use crate::reskit::{tileset, soundtrack::{formats::dmf::DmfModule, engines::echo::engine::EchoFormat}, utility::print_good}; use crate::reskit::{tileset, soundtrack::{formats::dmf::DmfModule, engines::echo::engine::{EchoFormat, EchoArtifact}}, utility::print_good};
use super::settings::{Args, Tools, TileOutputFormat, TileOrder}; use super::settings::{Args, Tools, TileOutputFormat, TileOrder};
pub fn run_command() -> Result<(), Box<dyn Error>> { pub fn run_command() -> Result<(), Box<dyn Error>> {
@ -20,22 +20,43 @@ pub fn run_command() -> Result<(), Box<dyn Error>> {
TileOrder::Sprite => "sprite" TileOrder::Sprite => "sprite"
} }
), ),
Tools::Soundtrack { input_file, output_file, input_format: _, output_format: _, source_file_format: _, artifact_output_directory, soundtrack_label, instrument_list_label } => { Tools::Soundtrack { input_file, output_directory, input_format: _, output_format: _, source_file_format: _, artifact_output_directory } => {
let result: Vec<DmfModule> = input_file.iter().map( | filename | Ok( DmfModule::from_file( &filename )? ) ).collect::<Result<Vec<DmfModule>, Box<dyn Error>>>()?; let output_directory = if output_directory.ends_with( "/" ) { output_directory.to_string() } else { format!( "{}/", output_directory ) };
// very temporary! let artifact_output_directory = if artifact_output_directory.ends_with( "/" ) { artifact_output_directory.to_string() } else { format!( "{}/", artifact_output_directory ) };
let result: &DmfModule = result.first().ok_or( "must provide input_file" )?;
for ( filename, data ) in result.get_artifacts( 0, &soundtrack_label, &output_file, &artifact_output_directory, &instrument_list_label )? { let modules: Vec<DmfModule> = input_file.iter().map( | filename | Ok( DmfModule::from_file( &filename )? ) ).collect::<Result<Vec<DmfModule>, Box<dyn Error>>>()?;
println!( "Creating artifact file {}{}", artifact_output_directory, filename );
let mut file = File::create( format!( "{}{}", artifact_output_directory, filename ) )?; let mut combined_asset_list = DmfModule::get_combined_assets_list( artifact_output_directory.clone(), &modules )?;
file.write_all( &data )? if combined_asset_list.instruments.len() > 255 {
return Err( "combined instrument set exceeds 255 instruments; try converting fewer modules or modules with fewer instruments" )?;
}
if combined_asset_list.samples.len() > 255 {
return Err( "combined sample set exceeds 255 samples; try converting fewer modules or modules with fewer samples" )?;
} }
println!( "Creating ESF output file {}", output_file ); // Output the ESF sequences
let mut file = File::create( &output_file )?; for i in 0..modules.len() {
file.write_all( &result.get_esf()? )?; let module = &modules[ i ];
print_good( &format!( "successfully compiled soundtrack for {}", output_file ) ); let path = Path::new( &module.path );
let original_filename_stem = path.file_stem().ok_or( "internal error: unable to parse filename" )?.to_string_lossy();
let output_file = format!( "{}{}.esf", output_directory, original_filename_stem );
combined_asset_list.sequences.push( output_file.clone() );
println!( "Creating ESF output file {}", output_file );
let mut file = File::create( &output_file )?;
file.write_all( &module.get_esf( &combined_asset_list )? )?;
print_good( &format!( "successfully compiled soundtrack for {}", output_file ) );
}
// Output the shared artifacts used by all ESF sequences
println!( "Writing sequence artifacts..." );
let shared_artifacts = combined_asset_list.to_bytes()?;
println!( "Writing sequence artifact {}", format!( "{}music.asm", artifact_output_directory ) );
let mut file = File::create( format!( "{}music.asm", artifact_output_directory ) )?;
file.write_all( &shared_artifacts )?;
} }
}; };

View File

@ -64,13 +64,13 @@ pub enum Tools {
#[command(name = "soundtrack")] #[command(name = "soundtrack")]
#[command(about = "Generate a console-compatible soundtrack from a sequence file.")] #[command(about = "Generate a console-compatible soundtrack from a sequence file.")]
Soundtrack { Soundtrack {
/// Input filename /// Input filename(s)
#[arg(short, long)] #[arg(short, long)]
input_file: Vec<String>, input_file: Vec<String>,
/// Output filename (if multiple input_files are provided, used as output parent directory) /// Output directory
#[arg(short, long)] #[arg(short, long, default_value_t=String::from("./"))]
output_file: String, output_directory: String,
/// Input sequence file format (the kind of tracker used to compose the track) /// Input sequence file format (the kind of tracker used to compose the track)
#[arg(long, value_enum, default_value_t=SequenceFormat::Dmf)] #[arg(long, value_enum, default_value_t=SequenceFormat::Dmf)]
@ -86,14 +86,6 @@ pub enum Tools {
/// Directory to output artifacts (instruments and samples) /// Directory to output artifacts (instruments and samples)
#[arg(long, default_value_t=String::from( "./" ))] #[arg(long, default_value_t=String::from( "./" ))]
artifact_output_directory: String, artifact_output_directory: String
/// Identifier used for the soundtrack in the generated source file
#[arg(long, default_value_t=String::from( "MuzPlaceholder1" ))]
soundtrack_label: String,
/// Identifier used for the soundtrack's artifacts in the generated source file
#[arg(long, default_value_t=String::from( "MuzInstrumentList" ))]
instrument_list_label: String
} }
} }

View File

@ -1,295 +1,359 @@
use std::{collections::HashMap, cmp::{max, min}, error::Error}; use std::{cmp::{max, min}, error::Error, fs::File, io::Write};
use convert_case::{Case, Casing};
use samplerate::convert; use samplerate::convert;
use crate::reskit::{soundtrack::{formats::dmf::{DmfModule, ECHO_EWF_SAMPLE_RATE}, types::{InstrumentType, SampleFormat, Channel, PatternRow, Effect}}, utility::{print_warning, Ring}}; use crate::reskit::{soundtrack::{formats::dmf::{DmfModule, ECHO_EWF_SAMPLE_RATE}, types::{InstrumentType, SampleFormat, Channel, PatternRow, Effect, CombinedAssets, Instrument, Sample}}, utility::{print_warning, Ring, symbol_to_pascal}};
use super::engine::{EchoFormat, ESF_FM_1, ESF_FM_2, ESF_FM_3, ESF_FM_4, ESF_FM_5, ESF_FM_6, ESF_PSG_1, ESF_PSG_2, ESF_PSG_3, ESF_PSG_4, EchoEvent, get_events_for_row, compact_delays, ESF_SET_LOOP, ESF_GO_TO_LOOP, ESF_STOP}; use super::engine::{EchoFormat, ESF_FM_1, ESF_FM_2, ESF_FM_3, ESF_FM_4, ESF_FM_5, ESF_FM_6, ESF_PSG_1, ESF_PSG_2, ESF_PSG_3, ESF_PSG_4, EchoEvent, get_events_for_row, compact_delays, ESF_SET_LOOP, ESF_GO_TO_LOOP, ESF_STOP, EchoArtifact};
impl EchoFormat for DmfModule { impl EchoArtifact for Instrument {
fn get_artifacts( &self, file_index: u32, soundtrack_label: &str, soundtrack_path: &str, artifact_path: &str, instr_list_label: &str ) -> Result<HashMap<String, Vec<u8>>, Box<dyn Error>> { fn to_bytes( &self ) -> Result<Vec<u8>, Box<dyn Error>> {
let mut files: HashMap<String, Vec<u8>> = HashMap::new(); let mut data: Vec<u8> = Vec::new();
// This next list preserves order of filenames to generate the echo .asm file
let mut instrument_filenames: Vec<String> = Vec::new();
let mut index = 0; match &self.instrument_type {
for instrument in &self.instruments { InstrumentType::Fm2612( settings ) => {
match &instrument.instrument_type { // Create feedback + algorithm byte
InstrumentType::Fm2612( settings ) => { let alg_fb: u8 = ( settings.fb << 3 ) | settings.alg;
// Create feedback + algorithm byte
let alg_fb: u8 = ( settings.fb << 3 ) | settings.alg;
// The operators are laid out as FmOperator objects in order 1 -> 3 -> 2 -> 4 // The operators are laid out as FmOperator objects in order 1 -> 3 -> 2 -> 4
// EIF instrument layout below // EIF instrument layout below
let mut mult_dt: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ]; let mut mult_dt: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
let mut tl: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ]; let mut tl: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
let mut ar_rs: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ]; let mut ar_rs: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
let mut dr_am: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ]; let mut dr_am: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
let mut sr: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ]; let mut sr: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
let mut rr_sl: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ]; let mut rr_sl: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
let mut ssg_eg: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ]; let mut ssg_eg: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
// Likewise, "operators" is laid out 1 -> 3 -> 2 -> 4 // Likewise, "operators" is laid out 1 -> 3 -> 2 -> 4
for i in 0..4 { for i in 0..4 {
let dt: u8 = match settings.operators[ i ].dt { let dt: u8 = match settings.operators[ i ].dt {
0 => 0b000, 0 => 0b000,
1 => 0b001, 1 => 0b001,
2 => 0b010, 2 => 0b010,
3 => 0b011, 3 => 0b011,
-1 => 0b101, -1 => 0b101,
-2 => 0b110, -2 => 0b110,
-3 => 0b111, -3 => 0b111,
_ => return Err( "invalid file: invalid value given for dt" )? _ => return Err( "invalid file: invalid value given for dt" )?
}; };
// https://plutiedev.com/ym2612-registers // https://plutiedev.com/ym2612-registers
// https://github.com/sikthehedgehog/Echo/blob/master/doc/eif.txt // https://github.com/sikthehedgehog/Echo/blob/master/doc/eif.txt
mult_dt[ i ] = ( dt << 4 ) | settings.operators[ i ].mult; mult_dt[ i ] = ( dt << 4 ) | settings.operators[ i ].mult;
tl[ i ] = settings.operators[ i ].tl; tl[ i ] = settings.operators[ i ].tl;
ar_rs[ i ] = ( settings.operators[ i ].rs << 6 ) | settings.operators[ i ].ar; ar_rs[ i ] = ( settings.operators[ i ].rs << 6 ) | settings.operators[ i ].ar;
dr_am[ i ] = ( settings.operators[ i ].am << 7 ) | settings.operators[ i ].dr; dr_am[ i ] = ( settings.operators[ i ].am << 7 ) | settings.operators[ i ].dr;
sr[ i ] = settings.operators[ i ].d2r; // "Sometimes also called 'second decay rate' (D2R)." sr[ i ] = settings.operators[ i ].d2r; // "Sometimes also called 'second decay rate' (D2R)."
rr_sl[ i ] = ( settings.operators[ i ].sl << 4 ) | settings.operators[ i ].rr; rr_sl[ i ] = ( settings.operators[ i ].sl << 4 ) | settings.operators[ i ].rr;
if settings.operators[ i ].ssg_mode > 0 { if settings.operators[ i ].ssg_mode > 0 {
print_warning( &format!( "SSG-EG mode set on instrument {}, operator {}. this operator may not work on clone hardware or certain emulators", instrument.name, i ) ); print_warning( &format!( "SSG-EG mode set on instrument {}, operator {}. this operator may not work on clone hardware or certain emulators", self.name, i ) );
}
ssg_eg[ i ] = settings.operators[ i ].ssg_mode;
} }
let mut eif: Vec<u8> = Vec::new(); ssg_eg[ i ] = settings.operators[ i ].ssg_mode;
eif.push( alg_fb );
eif.extend( mult_dt );
eif.extend( tl );
eif.extend( ar_rs );
eif.extend( dr_am );
eif.extend( sr );
eif.extend( rr_sl );
eif.extend( ssg_eg );
let filename = format!( "file{}_ins{}_{}.eif", file_index, index, instrument.name );
instrument_filenames.push( filename.clone() );
files.insert( filename, eif );
},
InstrumentType::PsgDcsg( settings ) => {
// 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.
// comments using example: 20 vol values, 6 arp values, both repeat from the beginning
// generate the initial portion (always). this is just an expansion
// of the envelopes, without loops, into two Vec<u8>'s.
// 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
// 01 02 03 04 05 06 -- -- -- -- -- -- -- -- -- -- -- -- -- --
let mut volume_envelope: Vec<u8> = settings.volume.envelope.iter().map( | long | *long as u8 ).collect();
let mut arpeggio_envelope: Vec<i8> = settings.arpeggio.envelope.iter().map( | long | *long as i8 ).collect();
// Validations (this makes my life easier when dmf format changes again)
if volume_envelope.is_empty() {
// Push one max volume if there is no volume defined in this instrument
volume_envelope.push( 0x0F );
}
if arpeggio_envelope.is_empty() {
// Push one byte of no arpeggio shift if no arpeggio is defined in this instrument
arpeggio_envelope.push( 0x00 );
}
// now take a slice of the repeatable portion of the smaller array,
// and use it to expand the smaller array to the same size as the
// larger array. if there is no repeatable portion, take a one-element
// slice of the last element in the array.
// 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
// 01 02 03 04 05 06 01 02 03 04 05 06 01 02 03 04 05 06 01 02
let mut volume_repeat_pattern: Ring<u8> = Ring::create(
if let Some( loop_at ) = settings.volume.loop_at {
Vec::from( &volume_envelope[ loop_at.. ] )
} else {
vec![ volume_envelope[ volume_envelope.len() - 1 ] ]
},
None
);
let mut arpeggio_repeat_pattern: Ring<i8> = Ring::create(
if let Some( loop_at ) = settings.arpeggio.loop_at {
Vec::from( &arpeggio_envelope[ loop_at.. ] )
} else {
vec![ arpeggio_envelope[ arpeggio_envelope.len() - 1 ] ]
},
None
);
if volume_envelope.len() < arpeggio_envelope.len() {
let difference = arpeggio_envelope.len() - volume_envelope.len();
for _ in 0..difference {
volume_envelope.push(
volume_repeat_pattern.next().expect( "fatal: internal error (volume_repeat_pattern)" )
);
}
} else if arpeggio_envelope.len() < volume_envelope.len() {
let difference = volume_envelope.len() - arpeggio_envelope.len();
for _ in 0..difference {
arpeggio_envelope.push(
arpeggio_repeat_pattern.next().expect( "fatal: internal error (arpeggio_repeat_pattern)" )
);
}
}
let interval_size = max( volume_repeat_pattern.len(), arpeggio_repeat_pattern.len() );
// now, you generate the repeating portion using the two repeat_pattern iterators.
// each segment at a time is generated by the size of the larger array.
// generate it until the first repeat pattern is regenerated.
// 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 .. (volume repeats from beginning)
// 03 04 05 06 01 02 03 04 05 06 01 02 03 04 05 06 01 02 03 04 .. (arpeggio repeats up from where it left off)
// 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 ..
// 05 06 01 02 03 04 05 06 01 02 03 04 05 06 01 02 03 04 05 06 ..
// 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 ..
// 01 02 03 04 05 06 01 02 03 04 05 06 01 02 03 04 05 06 01 02 ..
let mut volume_repeat: Vec<u8> = Vec::new();
let mut arpeggio_repeat: Vec<i8> = Vec::new();
let mut initial_volume_repeat: Vec<u8> = Vec::new();
let mut initial_arpeggio_repeat: Vec<i8> = Vec::new();
for _ in 0..interval_size {
initial_volume_repeat.push( volume_repeat_pattern.next().expect( "fatal: internal error (volume_repeat_pattern)" ) );
initial_arpeggio_repeat.push( arpeggio_repeat_pattern.next().expect( "fatal: internal error (arpeggio_repeat_pattern)" ) );
}
volume_repeat.extend( &initial_volume_repeat );
arpeggio_repeat.extend( &initial_arpeggio_repeat );
loop {
// Guard against OOM
if volume_repeat.len() > 65535 {
return Err( "eef error: psg instrument repeat period too long (try modifying repeating envelopes to have evenly-divisible parameters)" )?
}
let mut volume_repeat_period: Vec<u8> = Vec::new();
let mut arpeggio_repeat_period: Vec<i8> = Vec::new();
for _ in 0..interval_size {
volume_repeat_period.push( volume_repeat_pattern.next().expect( "fatal: internal error (volume_repeat_pattern)" ) );
arpeggio_repeat_period.push( arpeggio_repeat_pattern.next().expect( "fatal: internal error (arpeggio_repeat_pattern)" ) );
}
// End loop once the pattern begins repeating
if initial_volume_repeat == volume_repeat_period && initial_arpeggio_repeat == arpeggio_repeat_period {
break;
} else {
// If it doesn't repeat, append and generate another period
volume_repeat.append( &mut volume_repeat_period );
arpeggio_repeat.append( &mut arpeggio_repeat_period );
}
}
// Now generate the Echo EEF envelope from volume_envelope/volume_repeat + arpeggio_envelope/arpeggio_repeat
let mut envelope: Vec<u8> = vec![];
for i in 0..volume_envelope.len() {
envelope.push( get_eef_volume( volume_envelope[ i ] )? | get_eef_shift( arpeggio_envelope[ i ] )? );
}
// Echo begin loop command
envelope.push( 0xFE );
for i in 0..volume_repeat.len() {
envelope.push( get_eef_volume( volume_repeat[ i ] )? | get_eef_shift( arpeggio_repeat[ i ] )? );
}
// Echo end loop command
envelope.push( 0xFF );
let filename = format!( "file{}_ins{}_{}.eef", file_index, index, instrument.name );
instrument_filenames.push( filename.clone() );
files.insert( filename, envelope );
} }
}
index += 1; data.push( alg_fb );
data.extend( mult_dt );
data.extend( tl );
data.extend( ar_rs );
data.extend( dr_am );
data.extend( sr );
data.extend( rr_sl );
data.extend( ssg_eg );
}
InstrumentType::PsgDcsg( settings ) => {
// 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.
// comments using example: 20 vol values, 6 arp values, both repeat from the beginning
// generate the initial portion (always). this is just an expansion
// of the envelopes, without loops, into two Vec<u8>'s.
// 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
// 01 02 03 04 05 06 -- -- -- -- -- -- -- -- -- -- -- -- -- --
let mut volume_envelope: Vec<u8> = settings.volume.envelope.iter().map( | long | *long as u8 ).collect();
let mut arpeggio_envelope: Vec<i8> = settings.arpeggio.envelope.iter().map( | long | *long as i8 ).collect();
// Validations (this makes my life easier when dmf format changes again)
if volume_envelope.is_empty() {
// Push one max volume if there is no volume defined in this instrument
volume_envelope.push( 0x0F );
}
if arpeggio_envelope.is_empty() {
// Push one byte of no arpeggio shift if no arpeggio is defined in this instrument
arpeggio_envelope.push( 0x00 );
}
// now take a slice of the repeatable portion of the smaller array,
// and use it to expand the smaller array to the same size as the
// larger array. if there is no repeatable portion, take a one-element
// slice of the last element in the array.
// 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20
// 01 02 03 04 05 06 01 02 03 04 05 06 01 02 03 04 05 06 01 02
let mut volume_repeat_pattern: Ring<u8> = Ring::create(
if let Some( loop_at ) = settings.volume.loop_at {
Vec::from( &volume_envelope[ loop_at.. ] )
} else {
vec![ volume_envelope[ volume_envelope.len() - 1 ] ]
},
None
);
let mut arpeggio_repeat_pattern: Ring<i8> = Ring::create(
if let Some( loop_at ) = settings.arpeggio.loop_at {
Vec::from( &arpeggio_envelope[ loop_at.. ] )
} else {
vec![ arpeggio_envelope[ arpeggio_envelope.len() - 1 ] ]
},
None
);
if volume_envelope.len() < arpeggio_envelope.len() {
let difference = arpeggio_envelope.len() - volume_envelope.len();
for _ in 0..difference {
volume_envelope.push(
volume_repeat_pattern.next().expect( "fatal: internal error (volume_repeat_pattern)" )
);
}
} else if arpeggio_envelope.len() < volume_envelope.len() {
let difference = volume_envelope.len() - arpeggio_envelope.len();
for _ in 0..difference {
arpeggio_envelope.push(
arpeggio_repeat_pattern.next().expect( "fatal: internal error (arpeggio_repeat_pattern)" )
);
}
}
let interval_size = max( volume_repeat_pattern.len(), arpeggio_repeat_pattern.len() );
// now, you generate the repeating portion using the two repeat_pattern iterators.
// each segment at a time is generated by the size of the larger array.
// generate it until the first repeat pattern is regenerated.
// 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 .. (volume repeats from beginning)
// 03 04 05 06 01 02 03 04 05 06 01 02 03 04 05 06 01 02 03 04 .. (arpeggio repeats up from where it left off)
// 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 ..
// 05 06 01 02 03 04 05 06 01 02 03 04 05 06 01 02 03 04 05 06 ..
// 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 ..
// 01 02 03 04 05 06 01 02 03 04 05 06 01 02 03 04 05 06 01 02 ..
let mut volume_repeat: Vec<u8> = Vec::new();
let mut arpeggio_repeat: Vec<i8> = Vec::new();
let mut initial_volume_repeat: Vec<u8> = Vec::new();
let mut initial_arpeggio_repeat: Vec<i8> = Vec::new();
for _ in 0..interval_size {
initial_volume_repeat.push( volume_repeat_pattern.next().expect( "fatal: internal error (volume_repeat_pattern)" ) );
initial_arpeggio_repeat.push( arpeggio_repeat_pattern.next().expect( "fatal: internal error (arpeggio_repeat_pattern)" ) );
}
volume_repeat.extend( &initial_volume_repeat );
arpeggio_repeat.extend( &initial_arpeggio_repeat );
loop {
// Guard against OOM
if volume_repeat.len() > 65535 {
return Err( "eef error: psg instrument repeat period too long (try modifying repeating envelopes to have evenly-divisible parameters)" )?
}
let mut volume_repeat_period: Vec<u8> = Vec::new();
let mut arpeggio_repeat_period: Vec<i8> = Vec::new();
for _ in 0..interval_size {
volume_repeat_period.push( volume_repeat_pattern.next().expect( "fatal: internal error (volume_repeat_pattern)" ) );
arpeggio_repeat_period.push( arpeggio_repeat_pattern.next().expect( "fatal: internal error (arpeggio_repeat_pattern)" ) );
}
// End loop once the pattern begins repeating
if initial_volume_repeat == volume_repeat_period && initial_arpeggio_repeat == arpeggio_repeat_period {
break;
} else {
// If it doesn't repeat, append and generate another period
volume_repeat.append( &mut volume_repeat_period );
arpeggio_repeat.append( &mut arpeggio_repeat_period );
}
}
// Now generate the Echo EEF envelope from volume_envelope/volume_repeat + arpeggio_envelope/arpeggio_repeat
let mut envelope: Vec<u8> = vec![];
for i in 0..volume_envelope.len() {
envelope.push( get_eef_volume( volume_envelope[ i ] )? | get_eef_shift( arpeggio_envelope[ i ] )? );
}
// Echo begin loop command
envelope.push( 0xFE );
for i in 0..volume_repeat.len() {
envelope.push( get_eef_volume( volume_repeat[ i ] )? | get_eef_shift( arpeggio_repeat[ i ] )? );
}
// Echo end loop command
envelope.push( 0xFF );
data.extend( envelope.into_iter() );
}
} }
index = 0; Ok( data )
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 impl EchoArtifact for Sample {
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 fn to_bytes( &self ) -> Result<Vec<u8>, Box<dyn Error>> {
if sample.pitch > 0 { // Amplify data in original sample
print_warning( "pitch shift not yet implemented for pcm samples. your drums/voice samples/etc may sound a bit funny" ); let data: Vec<i16> = self.data
} .iter()
.map( | pcm_sample | {
let big_pcm_sample: i32 = min( ( *pcm_sample as i32 ) + self.amp as i32, i16::MAX as i32 );
// Use libsamplerate to resample from whatever it is now to 10650Hz, the sample rate big_pcm_sample as i16
// required by Echo sound engine EWF samples. } )
let data: Vec<f32> = convert( sample.rate, ECHO_EWF_SAMPLE_RATE, 1, samplerate::ConverterType::SincBestQuality, &data )?; .collect();
// Convert from f32 back to i16, then downconvert to u8 // Convert to f32 for various library operations
let mut data: Vec<u8> = data let data: Vec<f32> = data
.into_iter() .into_iter()
.map( | pcm_sample | { .map( | pcm_sample | {
let pcm_sample = ( ( pcm_sample + 1.0 ) * 128.0 ) as u8; if let SampleFormat::Bits8 = self.bitrate {
( pcm_sample as u8 ) as f32 / 255.0
} else {
pcm_sample as f32 / 32767.0
}
} )
.collect();
// Do not end stream prematurely (EWF format uses 0xFF to end stream) // TODO: Adjust pitch using crate pitch_shift
if pcm_sample == 0xFF { if self.pitch > 0 {
0xFE print_warning( "pitch shift not yet implemented for pcm samples. your drums/voice samples/etc may sound a bit funny" );
} else {
pcm_sample
}
} )
.collect();
// Terminate stream
data.push( 0xFF );
let filename = format!( "file{}_sample{}_{}.ewf", file_index, index, sample.name );
instrument_filenames.push( filename.clone() );
files.insert( filename, data );
index += 1;
} }
// 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( self.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 );
Ok( data )
}
}
impl EchoArtifact for CombinedAssets {
fn to_bytes( &self ) -> Result<Vec<u8>, Box<dyn Error>> {
// Write Echo ASM file that includes all the instruments // 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" ) ); let mut echo_asm: String = format!( "; Echo (esf) instruments and sequences source file\n; Generated by reskit v{}\n\n", env!( "CARGO_PKG_VERSION" ) );
let mut index = 0;
echo_asm += &format!( "{}:\n", soundtrack_label ); for soundtrack_path in &self.sequences {
echo_asm += &format!( "\tincbin '{}'\n\n", soundtrack_path ); echo_asm += &format!( "{}:\n", symbol_to_pascal( soundtrack_path ) );
echo_asm += &format!( "\tincbin '{}'\n\n", soundtrack_path );
}
echo_asm += &format!( "{}:\n", instr_list_label ); echo_asm += &format!( "Instr_List:\n" );
for filename in &instrument_filenames {
echo_asm += &format!( "\tEcho_ListEntry Instr_{}\n", filename.replace( ".", "_" ).replace( "-", "_" ).to_case( Case::Pascal ) ); let instrument_paths: Vec<String> = self.instruments.iter()
.map( | instrument |
format!(
"Instrument{}_{}.{}",
if instrument.name.is_empty() {
format!( "_NoName" )
} else {
format!( "_{}", &instrument.name )
},
{
let given = index;
index += 1;
given
},
match &instrument.instrument_type {
InstrumentType::Fm2612(_) => "eif",
InstrumentType::PsgDcsg(_) => "eef"
}
)
)
.collect();
let sample_paths: Vec<String> = self.samples.iter()
.map( | sample |
format!(
"Sample{}_{}.ewf",
if sample.name.is_empty() {
format!( "_NoName" )
} else {
format!( "_{}", &sample.name )
},
{
let given = index;
index += 1;
given
}
)
)
.collect();
for instrument_path in &instrument_paths {
echo_asm += &format!( "\tEcho_ListEntry Instr_{}\n", symbol_to_pascal( instrument_path ) );
}
for sample_path in &sample_paths {
echo_asm += &format!( "\tEcho_ListEntry Sample_{}\n", symbol_to_pascal( sample_path ) );
} }
echo_asm += &format!( "\tEcho_ListEnd\n\n" ); echo_asm += &format!( "\tEcho_ListEnd\n\n" );
for filename in &instrument_filenames { for i in 0..self.instruments.len() {
echo_asm += &format!( "Instr_{}:\n", filename.replace( ".", "_" ).replace( "-", "_" ).to_case( Case::Pascal ) ); let instrument = &self.instruments[ i ];
echo_asm += &format!( "\tincbin '{}{}'\n\n", artifact_path, filename ); let instrument_path = &instrument_paths[ i ];
let instrument_total_path = format!( "{}{}", self.output_prefix, instrument_path );
echo_asm += &format!( "Instr_{}:\n", symbol_to_pascal( instrument_path ) );
echo_asm += &format!( "\tincbin '{}'\n\n", instrument_total_path );
println!( "Writing sequence artifact {}", instrument_total_path );
let instrument_data = instrument.to_bytes()?;
let mut file = File::create( instrument_total_path )?;
file.write_all( &instrument_data )?;
} }
files.insert( format!( "music.asm" ), echo_asm.into_bytes() ); for i in 0..self.samples.len() {
let sample = &self.samples[ i ];
let sample_path = &sample_paths[ i ];
let sample_total_path = format!( "{}{}", self.output_prefix, sample_path );
Ok( files ) echo_asm += &format!( "Sample_{}:\n", symbol_to_pascal( sample_path ) );
echo_asm += &format!( "\tincbin '{}'\n\n", sample_total_path );
println!( "Writing sequence artifact {}", sample_total_path );
let sample_data = sample.to_bytes()?;
let mut file = File::create( sample_total_path )?;
file.write_all( &sample_data )?;
}
Ok( echo_asm.into_bytes() )
} }
fn get_esf( &self ) -> Result<Vec<u8>, Box<dyn Error>> { }
impl EchoFormat for DmfModule {
fn get_esf( &self, combined_assets: &CombinedAssets ) -> Result<Vec<u8>, Box<dyn Error>> {
let mut esf: Vec<u8> = Vec::new(); let mut esf: Vec<u8> = Vec::new();
// Write deflemask rows one at a time. // Write deflemask rows one at a time.
@ -327,13 +391,14 @@ impl EchoFormat for DmfModule {
let events_this_row: Vec<EchoEvent> = get_events_for_row( let events_this_row: Vec<EchoEvent> = get_events_for_row(
&mut channels, &mut channels,
&self.instruments, &self.instruments,
&self.samples,
&combined_assets,
columns.clone(), columns.clone(),
if row_number % 2 == 0 { if row_number % 2 == 0 {
self.speed_a * self.time_base self.speed_a * self.time_base
} else { } else {
self.speed_b * self.time_base self.speed_b * self.time_base
}, }
self.instruments.len() as u8
)?; )?;
// Is this the pattern matrix row that we repeat to? // Is this the pattern matrix row that we repeat to?

View File

@ -1,6 +1,7 @@
use std::{collections::{HashMap, HashSet}, error::Error, cmp::{min, max}}; use std::{collections::{HashMap, HashSet}, error::Error, cmp::{min, max}};
use linked_hash_map::LinkedHashMap;
use linked_hash_set::LinkedHashSet; use linked_hash_set::LinkedHashSet;
use crate::reskit::{soundtrack::types::{PatternRow, Note, Channel, OctaveFrequency, Effect, Instrument, NoiseType, DcsgChannelMode, InstrumentType}, utility::print_warning}; use crate::reskit::{soundtrack::types::{PatternRow, Note, Channel, OctaveFrequency, Effect, Instrument, NoiseType, DcsgChannelMode, InstrumentType, CombinedAssets, Sample}, utility::print_warning};
// https://github.com/sikthehedgehog/Echo/blob/master/doc/esf.txt // https://github.com/sikthehedgehog/Echo/blob/master/doc/esf.txt
const ESF_NOTE_ON: u8 = 0x00; const ESF_NOTE_ON: u8 = 0x00;
@ -59,9 +60,13 @@ const ESF_SEMITONE_A: u8 = 9;
const ESF_SEMITONE_A_SHARP: u8 = 10; const ESF_SEMITONE_A_SHARP: u8 = 10;
const ESF_SEMITONE_B: u8 = 11; const ESF_SEMITONE_B: u8 = 11;
struct InstrumentSet { struct InstrumentSet<'a> {
fm_ids: HashSet<u16>, fm_ids: HashSet<u16>,
psg_ids: HashSet<u16>, psg_ids: HashSet<u16>,
module_set: &'a Vec<Instrument>,
total_set: &'a Vec<Instrument>,
module_samples: &'a Vec<Sample>,
total_samples: &'a Vec<Sample>,
default_psg_id: Option<u16> default_psg_id: Option<u16>
} }
@ -69,9 +74,13 @@ pub type EchoEvent = Vec<u8>;
pub trait EchoFormat { pub trait EchoFormat {
fn get_artifacts( &self, file_index: u32, soundtrack_label: &str, soundtrack_path: &str, artifact_path: &str, instrument_list_label: &str ) -> Result<HashMap<String, Vec<u8>>, Box<dyn Error>>; fn get_esf( &self, combined_assets: &CombinedAssets ) -> Result<Vec<u8>, Box<dyn Error>>;
fn get_esf( &self ) -> Result<Vec<u8>, Box<dyn Error>>; }
pub trait EchoArtifact {
fn to_bytes( &self ) -> Result<Vec<u8>, Box<dyn Error>>;
} }
@ -280,7 +289,7 @@ fn get_delay( delay: u8 ) -> Result<EchoEvent, Box<dyn Error>> {
* For a specific row and channel, get the events this row and channel contribute to the stream. * For a specific row and channel, get the events this row and channel contribute to the stream.
* While doing so, update the state of the channel for future event generation. * While doing so, update the state of the channel for future event generation.
*/ */
fn get_events_for_channel( channels: &mut [Channel], active_channel: usize, row: &PatternRow, pcm_offset: u8, instrument_set: &InstrumentSet ) -> Result<Vec<EchoEvent>, Box<dyn Error>> { fn get_events_for_channel( channels: &mut [Channel], active_channel: usize, row: &PatternRow, asset_set: &InstrumentSet ) -> Result<Vec<EchoEvent>, Box<dyn Error>> {
let mut events: Vec<EchoEvent> = Vec::new(); let mut events: Vec<EchoEvent> = Vec::new();
// Adjust volume // Adjust volume
@ -295,20 +304,25 @@ fn get_events_for_channel( channels: &mut [Channel], active_channel: usize, row:
if let Some( instrument ) = &row.instrument_index { if let Some( instrument ) = &row.instrument_index {
// Validate the instrument is applicable to the current channel // Validate the instrument is applicable to the current channel
let correct_type = match channels[ active_channel ].id { let correct_type = match channels[ active_channel ].id {
ESF_FM_1..=ESF_FM_3 | ESF_FM_4..=ESF_FM_6 => instrument_set.fm_ids.contains( instrument ), ESF_FM_1..=ESF_FM_3 | ESF_FM_4..=ESF_FM_6 => asset_set.fm_ids.contains( instrument ),
ESF_FM_6_PCM => { ESF_FM_6_PCM => {
print_warning( "attempted to set instrument on FM6 when it is in pcm mode. this is valid, but it won't do anything until the next time it is set back to fm mode." ); print_warning( "attempted to set instrument on FM6 when it is in pcm mode. this is valid, but it won't do anything until the next time it is set back to fm mode." );
instrument_set.fm_ids.contains( instrument ) asset_set.fm_ids.contains( instrument )
}, },
ESF_PSG_1..=ESF_PSG_4 => instrument_set.psg_ids.contains( instrument ), ESF_PSG_1..=ESF_PSG_4 => asset_set.psg_ids.contains( instrument ),
invalid_channel => return Err( format!( "internal error: get_events_for_channel: invalid channel {:#04X}", invalid_channel ) )? invalid_channel => return Err( format!( "internal error: get_events_for_channel: invalid channel {:#04X}", invalid_channel ) )?
}; };
// Hop one layer of indirection from `instrument` by getting it from instrument_set.module_set,
// then finding the index of that instrument in instrument_set.total_set.
let original_instrument = &asset_set.module_set[ *instrument as usize ];
let instrument = asset_set.total_set.iter().position( | instrument | instrument == original_instrument ).ok_or( "internal error: could not locate instrument in combined instrument set" )?;
// Do not set the same instrument if it was already set before // Do not set the same instrument if it was already set before
if &row.instrument_index != &channels[ active_channel ].active_instrument { if &channels[ active_channel ].active_instrument != &Some( instrument as u16 ) {
if correct_type { if correct_type {
events.push( vec![ ESF_SET_INSTRUMENT | channels[ active_channel ].id, *instrument as u8 ] ); events.push( vec![ ESF_SET_INSTRUMENT | channels[ active_channel ].id, instrument as u8 ] );
channels[ active_channel ].active_instrument = Some( *instrument ); channels[ active_channel ].active_instrument = Some( instrument as u16 );
} else { } else {
print_warning( "attempted to set an fm instrument on a psg channel, or vice versa. the instrument was not set - your soundtrack may sound different than expected." ); print_warning( "attempted to set an fm instrument on a psg channel, or vice versa. the instrument was not set - your soundtrack may sound different than expected." );
// Now if current channel is PSG1 through PSG4, it's gonna need an instrument set on it. // Now if current channel is PSG1 through PSG4, it's gonna need an instrument set on it.
@ -316,7 +330,7 @@ fn get_events_for_channel( channels: &mut [Channel], active_channel: usize, row:
print_warning( "this is a psg channel, seting the default psg instrument so your note can still play..." ); print_warning( "this is a psg channel, seting the default psg instrument so your note can still play..." );
if row.note.is_some() { if row.note.is_some() {
// Set "__reskit_default_psg_instrument" // Set "__reskit_default_psg_instrument"
let instrument = instrument_set.default_psg_id.ok_or( "internal error: no default psg instrument to apply" )?; let instrument = asset_set.default_psg_id.ok_or( "internal error: no default psg instrument to apply" )?;
events.push( vec![ ESF_SET_INSTRUMENT | channels[ active_channel ].id, instrument as u8 ] ); events.push( vec![ ESF_SET_INSTRUMENT | channels[ active_channel ].id, instrument as u8 ] );
channels[ active_channel ].active_instrument = Some( instrument ); channels[ active_channel ].active_instrument = Some( instrument );
@ -330,7 +344,7 @@ fn get_events_for_channel( channels: &mut [Channel], active_channel: usize, row:
if channels[ active_channel ].active_instrument.is_none() && ( ESF_PSG_1..=ESF_PSG_4 ).contains( &channels[ active_channel ].id ) { if channels[ active_channel ].active_instrument.is_none() && ( ESF_PSG_1..=ESF_PSG_4 ).contains( &channels[ active_channel ].id ) {
if row.note.is_some() { if row.note.is_some() {
// Set "__reskit_default_psg_instrument" // Set "__reskit_default_psg_instrument"
let instrument = instrument_set.default_psg_id.ok_or( "internal error: no default psg instrument to apply" )?; let instrument = asset_set.default_psg_id.ok_or( "internal error: no default psg instrument to apply" )?;
events.push( vec![ ESF_SET_INSTRUMENT | channels[ active_channel ].id, instrument as u8 ] ); events.push( vec![ ESF_SET_INSTRUMENT | channels[ active_channel ].id, instrument as u8 ] );
channels[ active_channel ].active_instrument = Some( instrument ); channels[ active_channel ].active_instrument = Some( instrument );
@ -368,7 +382,17 @@ fn get_events_for_channel( channels: &mut [Channel], active_channel: usize, row:
); );
if channels[ active_channel ].id == ESF_FM_6_PCM { if channels[ active_channel ].id == ESF_FM_6_PCM {
events.push( vec![ ESF_NOTE_ON | channels[ active_channel ].id, get_pcm_index( note )? + pcm_offset ] ); let module_pcm_index = get_pcm_index( note )?;
if asset_set.module_samples.get( module_pcm_index as usize ).is_none() {
print_warning( "FM6 is in PCM mode but you specified a note that is not linked to a sample. this has no effect, skipping..." );
} else {
let sample = &asset_set.module_samples[ module_pcm_index as usize ];
let global_sample_index = asset_set.total_samples.iter().position( | global_sample | global_sample == sample ).ok_or( "internal error: could not locate sample in combined sample set" )?;
// Remember, instruments will always be listed first in the output source file, followed by pointers to samples
// So the minimum sample offset is the number of instruments
events.push( vec![ ESF_NOTE_ON | channels[ active_channel ].id, global_sample_index as u8 + asset_set.total_set.len() as u8 ] );
}
} else if channels[ active_channel ].id == ESF_PSG_4 { } else if channels[ active_channel ].id == ESF_PSG_4 {
// Find the single (and it should only ever be a single) Effect::DcsgNoiseMode attached to the channel // Find the single (and it should only ever be a single) Effect::DcsgNoiseMode attached to the channel
let dcsg_noise_mode = channels[ active_channel ].active_effects.iter().find( | effect | matches!( effect, Effect::DcsgNoiseMode { mode: _, noise_type: _ } ) ); let dcsg_noise_mode = channels[ active_channel ].active_effects.iter().find( | effect | matches!( effect, Effect::DcsgNoiseMode { mode: _, noise_type: _ } ) );
@ -805,13 +829,13 @@ fn get_delays( events: Vec<EchoEvent>, channels: &mut [Channel], ticks_to_wait:
* For an entire row across all channels, generate the events that apply to the row as a whole. This usually means applying * For an entire row across all channels, generate the events that apply to the row as a whole. This usually means applying
* the waits so that the row can be flushed to Echo and played - ESF ticks are until the nearest wait or stop event. * the waits so that the row can be flushed to Echo and played - ESF ticks are until the nearest wait or stop event.
*/ */
pub fn get_events_for_row( channels: &mut [Channel], instruments: &Vec<Instrument>, subrows: Vec<&PatternRow>, ticks_to_wait: u8, pcm_offset: u8 ) -> Result<Vec<EchoEvent>, Box<dyn Error>> { pub fn get_events_for_row( channels: &mut [Channel], module_instruments: &Vec<Instrument>, module_samples: &Vec<Sample>, asset_set: &CombinedAssets, subrows: Vec<&PatternRow>, ticks_to_wait: u8 ) -> Result<Vec<EchoEvent>, Box<dyn Error>> {
let mut events: Vec<EchoEvent> = Vec::new(); let mut events: Vec<EchoEvent> = Vec::new();
let mut index = 0; let mut index = 0;
// Find the index of the default psg instrument, if it exists // Find the index of the default psg instrument, if it exists
let default_psg_id: Option<u16> = instruments.iter().find_map( | item | { let default_psg_id: Option<u16> = module_instruments.iter().find_map( | item | {
if item.name == "__reskit_default_psg_instrument" { if item.name == "__reskit_default_psg_instrument" {
Some( index ) Some( index )
} else { } else {
@ -822,7 +846,7 @@ pub fn get_events_for_row( channels: &mut [Channel], instruments: &Vec<Instrumen
// Generate InstrumentSet which is used to validate when instruments are set on channels // Generate InstrumentSet which is used to validate when instruments are set on channels
index = 0; index = 0;
let fm_ids = instruments.iter() let fm_ids = module_instruments.iter()
.filter_map( | instrument | if matches!( instrument.instrument_type, InstrumentType::Fm2612( _ ) ) { .filter_map( | instrument | if matches!( instrument.instrument_type, InstrumentType::Fm2612( _ ) ) {
let result = index; let result = index;
index += 1; index += 1;
@ -834,7 +858,7 @@ pub fn get_events_for_row( channels: &mut [Channel], instruments: &Vec<Instrumen
.collect(); .collect();
index = 0; index = 0;
let psg_ids = instruments.iter() let psg_ids = module_instruments.iter()
.filter_map( | instrument | if matches!( instrument.instrument_type, InstrumentType::PsgDcsg( _ ) ) { .filter_map( | instrument | if matches!( instrument.instrument_type, InstrumentType::PsgDcsg( _ ) ) {
let result = index; let result = index;
index += 1; index += 1;
@ -848,6 +872,10 @@ pub fn get_events_for_row( channels: &mut [Channel], instruments: &Vec<Instrumen
let instrument_set = InstrumentSet { let instrument_set = InstrumentSet {
fm_ids, fm_ids,
psg_ids, psg_ids,
module_set: module_instruments,
total_set: &asset_set.instruments,
module_samples,
total_samples: &asset_set.samples,
default_psg_id default_psg_id
}; };
@ -857,7 +885,7 @@ pub fn get_events_for_row( channels: &mut [Channel], instruments: &Vec<Instrumen
events.extend( apply_effects_to_channel( &mut channels[ i ], &subrows[ i ].effects )? ); events.extend( apply_effects_to_channel( &mut channels[ i ], &subrows[ i ].effects )? );
// Get the ESF events for this channel's part of the row // Get the ESF events for this channel's part of the row
events.extend( get_events_for_channel( channels, i, subrows[ i ], pcm_offset, &instrument_set )? ); events.extend( get_events_for_channel( channels, i, subrows[ i ], &instrument_set )? );
} }
Ok( get_delays( events, channels, ticks_to_wait )? ) Ok( get_delays( events, channels, ticks_to_wait )? )

View File

@ -1,7 +1,7 @@
use std::{error::Error, fs::File, io::Read, convert::TryInto, collections::HashMap}; use std::{error::Error, fs::File, io::Read, convert::TryInto, collections::HashMap};
use flate2::read::ZlibDecoder; use flate2::read::ZlibDecoder;
use linked_hash_set::LinkedHashSet; use linked_hash_set::LinkedHashSet;
use crate::reskit::{utility::{get_string, get_u8, skip, get_u32, get_i8, get_i32, get_u16, get_i16, print_info}, soundtrack::types::{SampleFormat, PsgEnvelope, Note, Sample, PsgSettings, PatternRow, Effect, DcsgChannelMode, NoiseType, Instrument, InstrumentType, Fm2612Operator, Fm2612Settings, Channel}}; use crate::reskit::{utility::{get_string, get_u8, skip, get_u32, get_i8, get_i32, get_u16, get_i16, print_info}, soundtrack::types::{SampleFormat, PsgEnvelope, Note, Sample, PsgSettings, PatternRow, Effect, DcsgChannelMode, NoiseType, Instrument, InstrumentType, Fm2612Operator, Fm2612Settings, Channel, CombinedAssets}};
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;
@ -28,7 +28,7 @@ pub enum FrameMode {
} }
pub struct DmfModule { pub struct DmfModule {
path: String, pub path: String,
platform: u8, platform: u8,
version: u8, version: u8,
pub time_base: u8, pub time_base: u8,
@ -45,6 +45,32 @@ pub struct DmfModule {
impl DmfModule { impl DmfModule {
/**
* Build the combined instrument list across a list of Deflemask modules. When assigning instruments to tracks,
* there will be a two step process. First, get the index into the module's list of instruments. Then, find that
* instrument in the combined instrument list (by simple equality, even) and use _that_ index instead.
*/
pub fn get_combined_assets_list( output_prefix: String, modules: &Vec<DmfModule> ) -> Result<CombinedAssets, Box<dyn Error>> {
let mut instruments: Vec<Instrument> = Vec::new();
let mut samples: Vec<Sample> = Vec::new();
for module in modules {
for instrument in &module.instruments {
if !instruments.contains( instrument ) {
instruments.push( instrument.clone() );
}
}
for sample in &module.samples {
if !samples.contains( sample ) {
samples.push( sample.clone() );
}
}
}
Ok( CombinedAssets { output_prefix, sequences: Vec::new(), instruments, samples } )
}
pub fn from_file( path: &str ) -> Result<DmfModule, Box<dyn Error>> { pub fn from_file( path: &str ) -> Result<DmfModule, Box<dyn Error>> {
let mut file = File::open( path )?; let mut file = File::open( path )?;
let mut compressed: Vec<u8> = Vec::new(); let mut compressed: Vec<u8> = Vec::new();

View File

@ -1,15 +1,14 @@
use std::{collections::HashMap, error::Error}; use std::{collections::HashMap, error::Error};
use linked_hash_set::LinkedHashSet; use linked_hash_set::LinkedHashSet;
#[derive(Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct PsgEnvelope< DataFormat: PartialEq > { pub struct PsgEnvelope< DataFormat: PartialEq + Clone > {
pub envelope: Vec<DataFormat>, pub envelope: Vec<DataFormat>,
pub loop_at: Option<usize>, pub loop_at: Option<usize>,
pub settings: HashMap<&'static str, bool> pub settings: HashMap<&'static str, bool>
} }
#[derive(Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct PsgSettings { pub struct PsgSettings {
pub volume: PsgEnvelope< u32 >, pub volume: PsgEnvelope< u32 >,
pub arpeggio: PsgEnvelope< i32 >, pub arpeggio: PsgEnvelope< i32 >,
@ -17,7 +16,7 @@ pub struct PsgSettings {
pub wavetable: PsgEnvelope< u32 > pub wavetable: PsgEnvelope< u32 >
} }
#[derive(Debug, Default, PartialEq)] #[derive(Clone, Debug, Default, PartialEq)]
pub struct Fm2612Operator { pub struct Fm2612Operator {
pub am: u8, pub am: u8,
pub ar: u8, pub ar: u8,
@ -33,7 +32,7 @@ pub struct Fm2612Operator {
pub ssg_mode: u8 pub ssg_mode: u8
} }
#[derive(Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Fm2612Settings { pub struct Fm2612Settings {
pub alg: u8, pub alg: u8,
pub fb: u8, pub fb: u8,
@ -42,25 +41,32 @@ pub struct Fm2612Settings {
pub operators: [Fm2612Operator; 4] pub operators: [Fm2612Operator; 4]
} }
#[derive(Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum InstrumentType { pub enum InstrumentType {
Fm2612( Fm2612Settings ), Fm2612( Fm2612Settings ),
PsgDcsg( PsgSettings ) PsgDcsg( PsgSettings )
} }
#[derive(Debug)] #[derive(Clone, PartialEq, Debug)]
pub struct Instrument { pub struct Instrument {
pub name: String, pub name: String,
pub instrument_type: InstrumentType pub instrument_type: InstrumentType
} }
#[derive(Debug)] pub struct CombinedAssets {
pub output_prefix: String,
pub sequences: Vec<String>,
pub instruments: Vec<Instrument>,
pub samples: Vec<Sample>
}
#[derive(Clone, PartialEq, Debug)]
pub enum SampleFormat { pub enum SampleFormat {
Bits8, Bits8,
Bits16 Bits16
} }
#[derive(Debug)] #[derive(Clone, PartialEq, Debug)]
pub struct Sample { pub struct Sample {
pub name: String, pub name: String,
pub rate: u32, pub rate: u32,
@ -173,7 +179,7 @@ impl Note {
} ) } )
} }
pub fn get_freq( &self ) -> Option<f32> { pub fn _get_freq( &self ) -> Option<f32> {
match self { match self {
Note::NoteOff => None, Note::NoteOff => None,
Note::CSharp( octave ) => match octave { Note::CSharp( octave ) => match octave {

View File

@ -1,5 +1,7 @@
use std::{slice::Iter, error::Error, str::from_utf8, convert::TryInto}; use std::{slice::Iter, error::Error, str::from_utf8, convert::TryInto};
use convert_case::{Casing, Case};
pub struct Ring< Type: Copy >{ pub struct Ring< Type: Copy >{
data: Vec< Type >, data: Vec< Type >,
position: usize position: usize
@ -37,6 +39,15 @@ impl< Type: Copy > Iterator for Ring< Type > {
} }
pub fn symbol_to_pascal( filename: &str ) -> String {
filename
.replace( "/", "_" )
.replace( "\\", "_" )
.replace( ".", "_" )
.replace( "-", "_" )
.to_case( Case::Pascal )
}
pub fn print_error( error_msg: &str ) { pub fn print_error( error_msg: &str ) {
red!( "fatal: " ); println!( "{}", error_msg ); red!( "fatal: " ); println!( "{}", error_msg );
} }