Refactor and prepare for multiple files

master
Ashley N. 2023-08-31 00:06:07 -04:00
parent 714b28f1aa
commit 62790d4c9c
6 changed files with 30 additions and 28 deletions

View File

@ -14,6 +14,5 @@ flate2 = "1.0.26"
samplerate = "0.2.4" samplerate = "0.2.4"
hound = "3.5.0" hound = "3.5.0"
pitch_shift = "1.0.0" pitch_shift = "1.0.0"
uuid = { version = "1.4.1", features = [ "v4" ] }
linked_hash_set = "0.1.4" linked_hash_set = "0.1.4"
convert_case = "0.6.0" convert_case = "0.6.0"

View File

@ -21,9 +21,11 @@ pub fn run_command() -> Result<(), Box<dyn Error>> {
} }
), ),
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_file, input_format: _, output_format: _, source_file_format: _, artifact_output_directory, soundtrack_label, instrument_list_label } => {
let result = DmfModule::from_file( &input_file )?; let result: Vec<DmfModule> = input_file.iter().map( | filename | Ok( DmfModule::from_file( &filename )? ) ).collect::<Result<Vec<DmfModule>, Box<dyn Error>>>()?;
// very temporary!
let result: &DmfModule = result.first().ok_or( "must provide input_file" )?;
for ( filename, data ) in result.get_artifacts( &soundtrack_label, &output_file, &artifact_output_directory, &instrument_list_label )? { for ( filename, data ) in result.get_artifacts( 0, &soundtrack_label, &output_file, &artifact_output_directory, &instrument_list_label )? {
println!( "Creating artifact file {}{}", artifact_output_directory, filename ); println!( "Creating artifact file {}{}", artifact_output_directory, filename );
let mut file = File::create( format!( "{}{}", artifact_output_directory, filename ) )?; let mut file = File::create( format!( "{}{}", artifact_output_directory, filename ) )?;
file.write_all( &data )? file.write_all( &data )?

View File

@ -66,9 +66,9 @@ pub enum Tools {
Soundtrack { Soundtrack {
/// Input filename /// Input filename
#[arg(short, long)] #[arg(short, long)]
input_file: String, input_file: Vec<String>,
/// Output filename /// Output filename (if multiple input_files are provided, used as output parent directory)
#[arg(short, long)] #[arg(short, long)]
output_file: String, output_file: String,

View File

@ -1,24 +1,20 @@
use std::{collections::HashMap, cmp::{max, min}, error::Error}; use std::{collections::HashMap, cmp::{max, min}, error::Error};
use convert_case::{Case, Casing}; use convert_case::{Case, Casing};
use samplerate::convert; use samplerate::convert;
use uuid::Uuid; 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}}, utility::{print_warning, Ring, print_info}};
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};
impl EchoFormat for DmfModule { impl EchoFormat for DmfModule {
fn get_artifacts( &self, soundtrack_label: &str, soundtrack_path: &str, artifact_path: &str, instr_list_label: &str ) -> Result<HashMap<String, Vec<u8>>, Box<dyn Error>> { 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>> {
let mut files: HashMap<String, Vec<u8>> = HashMap::new(); let mut files: HashMap<String, Vec<u8>> = HashMap::new();
// This next list preserves order of filenames to generate the echo .asm file // This next list preserves order of filenames to generate the echo .asm file
let mut instrument_filenames: Vec<String> = Vec::new(); let mut instrument_filenames: Vec<String> = Vec::new();
let mut index = 0;
for instrument in &self.instruments { for instrument in &self.instruments {
match &instrument.instrument_type { match &instrument.instrument_type {
InstrumentType::Fm2612( settings ) => { InstrumentType::Fm2612( settings ) => {
let uuid = Uuid::new_v4().to_string();
instrument_filenames.push( format!( "{}.eif", if instrument.name.is_empty() { format!( "fm_{}", uuid ) } else { instrument.name.to_owned() } ) );
// Create feedback + algorithm byte // Create feedback + algorithm byte
let alg_fb: u8 = ( settings.fb << 3 ) | settings.alg; let alg_fb: u8 = ( settings.fb << 3 ) | settings.alg;
@ -71,12 +67,11 @@ impl EchoFormat for DmfModule {
eif.extend( rr_sl ); eif.extend( rr_sl );
eif.extend( ssg_eg ); eif.extend( ssg_eg );
files.insert( format!( "{}.eif", if instrument.name.is_empty() { format!( "fm_{}", uuid ) } else { instrument.name.to_owned() } ), eif ); let filename = format!( "file{}_ins{}_{}.eif", file_index, index, instrument.name );
instrument_filenames.push( filename.clone() );
files.insert( filename, eif );
}, },
InstrumentType::PsgDcsg( settings ) => { InstrumentType::PsgDcsg( settings ) => {
let uuid = Uuid::new_v4().to_string();
instrument_filenames.push( format!( "{}.eef", if instrument.name.is_empty() { format!( "psg_{}", uuid ) } else { instrument.name.to_owned() } ) );
// Echo uses volume and arpeggio envelopes // Echo uses volume and arpeggio envelopes
// Noise envelopes are usable but cannot be articulated in an instrument. // 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. // For use of the noise envelope, use ESF $0Bnn/$3Bnn and $3Ann in the stream.
@ -204,15 +199,17 @@ impl EchoFormat for DmfModule {
// Echo end loop command // Echo end loop command
envelope.push( 0xFF ); envelope.push( 0xFF );
files.insert( format!( "{}.eef", if instrument.name.is_empty() { format!( "psg_{}", uuid ) } else { instrument.name.to_owned() } ), envelope ); let filename = format!( "file{}_ins{}_{}.eef", file_index, index, instrument.name );
instrument_filenames.push( filename.clone() );
files.insert( filename, envelope );
} }
} }
index += 1;
} }
index = 0;
for sample in &self.samples { for sample in &self.samples {
let uuid = Uuid::new_v4().to_string();
instrument_filenames.push( format!( "{}.ewf", if sample.name.is_empty() { format!( "sample_{}", uuid ) } else { sample.name.to_owned() } ) );
// Amplify data in original sample // Amplify data in original sample
let data: Vec<i16> = sample.data let data: Vec<i16> = sample.data
.iter() .iter()
@ -262,7 +259,11 @@ impl EchoFormat for DmfModule {
// Terminate stream // Terminate stream
data.push( 0xFF ); data.push( 0xFF );
files.insert( format!( "{}.ewf", if sample.name.is_empty() { format!( "sample_{}", uuid ) } else { sample.name.to_owned() } ), data ); let filename = format!( "file{}_sample{}_{}.ewf", file_index, index, sample.name );
instrument_filenames.push( filename.clone() );
files.insert( filename, data );
index += 1;
} }
// Write Echo ASM file that includes all the instruments // Write Echo ASM file that includes all the instruments

View File

@ -69,7 +69,7 @@ pub type EchoEvent = Vec<u8>;
pub trait EchoFormat { pub trait EchoFormat {
fn get_artifacts( &self, soundtrack_label: &str, soundtrack_path: &str, artifact_path: &str, instrument_list_label: &str ) -> Result<HashMap<String, Vec<u8>>, Box<dyn Error>>; 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 ) -> Result<Vec<u8>, Box<dyn Error>>; fn get_esf( &self ) -> Result<Vec<u8>, Box<dyn Error>>;

View File

@ -2,14 +2,14 @@ use std::{collections::HashMap, error::Error};
use linked_hash_set::LinkedHashSet; use linked_hash_set::LinkedHashSet;
#[derive(Debug)] #[derive(Debug, PartialEq)]
pub struct PsgEnvelope< DataFormat > { pub struct PsgEnvelope< DataFormat: PartialEq > {
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)] #[derive(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 +17,7 @@ pub struct PsgSettings {
pub wavetable: PsgEnvelope< u32 > pub wavetable: PsgEnvelope< u32 >
} }
#[derive(Debug, Default)] #[derive(Debug, Default, PartialEq)]
pub struct Fm2612Operator { pub struct Fm2612Operator {
pub am: u8, pub am: u8,
pub ar: u8, pub ar: u8,
@ -33,7 +33,7 @@ pub struct Fm2612Operator {
pub ssg_mode: u8 pub ssg_mode: u8
} }
#[derive(Debug)] #[derive(Debug, PartialEq)]
pub struct Fm2612Settings { pub struct Fm2612Settings {
pub alg: u8, pub alg: u8,
pub fb: u8, pub fb: u8,
@ -42,7 +42,7 @@ pub struct Fm2612Settings {
pub operators: [Fm2612Operator; 4] pub operators: [Fm2612Operator; 4]
} }
#[derive(Debug)] #[derive(Debug, PartialEq)]
pub enum InstrumentType { pub enum InstrumentType {
Fm2612( Fm2612Settings ), Fm2612( Fm2612Settings ),
PsgDcsg( PsgSettings ) PsgDcsg( PsgSettings )