diff --git a/Cargo.toml b/Cargo.toml index 4746fac..7badb56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ edition = "2018" [dependencies] image = "^0.23" colour = "^0.5" -clap = "^2.33" +clap = { version = "^4.4.1", features = [ "derive" ] } flate2 = "1.0.26" samplerate = "0.2.4" hound = "3.5.0" diff --git a/src/main.rs b/src/main.rs index 2f18a48..e3c1835 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,134 +4,11 @@ extern crate clap; extern crate colour; mod reskit; -use std::error::Error; -use std::fs::File; -use std::io::Write; -use clap::{App, Arg, SubCommand}; -use reskit::soundtrack::engines::echo::engine::EchoFormat; -use reskit::soundtrack::formats::dmf::DmfModule; +use reskit::cli::evaluator::run_command; use reskit::utility; -use reskit::tileset; - -use crate::reskit::utility::print_good; - -fn run() -> Result<(), Box> { - let matches = App::new( "reskit" ) - .version( env!( "CARGO_PKG_VERSION" ) ) - .author( "(c) 2021-2023 Ashley N. " ) - .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" ) - .arg_from_usage( "-i, --input= 'Specify input image'" ) - .arg_from_usage( "-o, --output= 'Specify output file'") - .arg( - Arg::with_name( "FORMAT" ) - .short( "f" ) - .long( "format" ) - .help( "Specify output format for tileset (valid options: bin, inc)") - .default_value( "bin" ) - .takes_value( true ) - ) - .arg( - Arg::with_name( "TILEORDER" ) - .short( "to" ) - .long( "tile-order" ) - .help( "Specify tile order for tileset (valid options: tile, sprite" ) - .default_value( "tile" ) - .takes_value( true ) - ) - ) - .subcommand( - SubCommand::with_name( "soundtrack" ) - .about( "Generate an on-console soundtrack from a chiptune tracker module" ) - .arg_from_usage( "-i, --input= 'Specify input module'" ) - .arg_from_usage( "-o, --output= 'Specify output filename (may output multiple files depending on format)'") - .arg( - Arg::with_name( "ARTIFACT_OUTPUT_DIRECTORY" ) - .long( "artifact-output-directory" ) - .help( "Specify directory to output secondary artifacts (e.g. instruments)" ) - .default_value( "./" ) - .takes_value( true ) - ) - .arg( - Arg::with_name( "SOUNDTRACK_LABEL" ) - .long( "soundtrack-label" ) - .help( "In sound drivers supporting generated boilerplate, specify label to use for soundtrack" ) - .default_value( "MuzPlaceholder1" ) - .takes_value( true ) - ) - .arg( - Arg::with_name( "TRACKER_FORMAT" ) - .long( "tracker-format" ) - .help( "Specify tracker module format (valid options: dmf (DefleMask Tracker))") - .default_value( "dmf" ) - .takes_value( true ) - ) - .arg( - Arg::with_name( "DRIVER_FORMAT" ) - .long( "driver-format" ) - .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 boilerplate, 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 boilerplate, specify output format for instrument list" ) - .default_value( "asmx" ) - .takes_value( true ) - ) - ) - .get_matches(); - - if let Some( matches ) = matches.subcommand_matches( "tileset" ) { - let input_filename = matches.value_of( "input" ).ok_or( "expected: input filename (-i,--input)" )?; - let output_filename = matches.value_of( "output" ).ok_or( "expected: output_filename (-o,--output)" )?; - - tileset::generate( - input_filename, - output_filename, - matches.value_of( "FORMAT" ).unwrap(), - matches.value_of( "TILEORDER" ).unwrap() - ); - - Ok( () ) - } else if let Some( matches ) = matches.subcommand_matches( "soundtrack" ) { - let input_filename = matches.value_of( "input" ).ok_or( "expected: input filename (-i,--input)" )?; - let output_filename = matches.value_of( "output" ).ok_or( "expected: output_filename (-o,--output)" )?; - - let result = DmfModule::from_file( &input_filename )?; - - let artifact_path = matches.value_of( "ARTIFACT_OUTPUT_DIRECTORY" ).unwrap_or( "./" ); - let soundtrack_label = matches.value_of( "SOUNDTRACK_LABEL" ).unwrap_or( "MuzPlaceholder1" ); - for ( filename, data ) in result.get_artifacts( soundtrack_label, output_filename, artifact_path, matches.value_of( "EXPORT_INSTRUMENT_LIST_LABEL" ).ok_or( "internal error" )? )? { - println!( "Creating artifact file {}{}", artifact_path, filename ); - let mut file = File::create( format!( "{}{}", artifact_path, filename ) )?; - file.write_all( &data )? - } - - println!( "Creating ESF output file {}", output_filename ); - let mut file = File::create( output_filename )?; - file.write_all( &result.get_esf()? )?; - - print_good( &format!( "successfully compiled soundtrack for {}", output_filename ) ); - - Ok( () ) - } else { - Err( "no tool provided (try --help)" )? - } -} fn main() { - if let Err( error ) = run() { + if let Err( error ) = run_command() { utility::print_error( &format!( "{}", error ) ); } } \ No newline at end of file diff --git a/src/reskit/cli/evaluator.rs b/src/reskit/cli/evaluator.rs new file mode 100644 index 0000000..0c04f81 --- /dev/null +++ b/src/reskit/cli/evaluator.rs @@ -0,0 +1,41 @@ +use std::{error::Error, fs::File, io::Write}; +use clap::Parser; +use crate::reskit::{tileset, soundtrack::{formats::dmf::DmfModule, engines::echo::engine::EchoFormat}, utility::print_good}; +use super::settings::{Args, Tools, TileOutputFormat, TileOrder}; + +pub fn run_command() -> Result<(), Box> { + let cli = Args::parse(); + + match cli.tool { + Tools::Tileset { input_file, output_file, format, tile_order } => + tileset::generate( + &input_file, + &output_file, + match format { + TileOutputFormat::Bin => "bin", + TileOutputFormat::Inc => "inc" + }, + match tile_order { + TileOrder::Tile => "tile", + TileOrder::Sprite => "sprite" + } + ), + 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 )?; + + for ( filename, data ) in result.get_artifacts( &soundtrack_label, &output_file, &artifact_output_directory, &instrument_list_label )? { + println!( "Creating artifact file {}{}", artifact_output_directory, filename ); + let mut file = File::create( format!( "{}{}", artifact_output_directory, filename ) )?; + file.write_all( &data )? + } + + println!( "Creating ESF output file {}", output_file ); + let mut file = File::create( &output_file )?; + file.write_all( &result.get_esf()? )?; + + print_good( &format!( "successfully compiled soundtrack for {}", output_file ) ); + } + }; + + Ok( () ) +} \ No newline at end of file diff --git a/src/reskit/cli/mod.rs b/src/reskit/cli/mod.rs new file mode 100644 index 0000000..9c92770 --- /dev/null +++ b/src/reskit/cli/mod.rs @@ -0,0 +1,2 @@ +pub mod evaluator; +pub mod settings; \ No newline at end of file diff --git a/src/reskit/cli/settings.rs b/src/reskit/cli/settings.rs new file mode 100644 index 0000000..b7cb176 --- /dev/null +++ b/src/reskit/cli/settings.rs @@ -0,0 +1,99 @@ +use clap::{Parser, Subcommand, ValueEnum}; + +#[derive(Parser)] +#[command(name = "reskit")] +#[command(version = env!("CARGO_PKG_VERSION"))] +#[command(long_about = "reskit\nThe Retro Entertainment Software Toolkit - Utilities for authoring software for legacy video game systems\n(c) 2023 Ashley N. (ne0ndrag0n)")] +pub struct Args { + + #[command(subcommand)] + pub tool: Tools + +} + +#[derive(Clone, ValueEnum)] +pub enum TileOrder { + Tile, + Sprite +} + +#[derive(Clone, ValueEnum)] +pub enum TileOutputFormat { + Bin, + Inc +} + +#[derive(Clone, ValueEnum)] +pub enum SequenceFormat { + Dmf +} + +#[derive(Clone, ValueEnum)] +pub enum DriverFormat { + Esf +} + +#[derive(Clone, ValueEnum)] +pub enum ArtifactListFormat { + Asmx +} + +#[derive(Subcommand)] +pub enum Tools { + + #[command(name = "tileset")] + #[command(about = "Generate a set of tiles from an input image. Supports Sega Mega Drive VDP output format.")] + Tileset { + /// Input filename + #[arg(short, long)] + input_file: String, + + /// Output filename + #[arg(short, long)] + output_file: String, + + /// Output format + #[arg(short, long, value_enum, default_value_t=TileOutputFormat::Bin)] + format: TileOutputFormat, + + /// Tile order + #[arg(short, long, value_enum, default_value_t=TileOrder::Tile)] + tile_order: TileOrder + }, + + #[command(name = "soundtrack")] + #[command(about = "Generate a console-compatible soundtrack from a sequence file.")] + Soundtrack { + /// Input filename + #[arg(short, long)] + input_file: String, + + /// Output filename + #[arg(short, long)] + output_file: String, + + /// Input sequence file format (the kind of tracker used to compose the track) + #[arg(long, value_enum, default_value_t=SequenceFormat::Dmf)] + input_format: SequenceFormat, + + /// Output console file format (the sound driver your console uses) + #[arg(long, value_enum, default_value_t=DriverFormat::Esf)] + output_format: DriverFormat, + + /// Output format for the generated source file linking soundtracks and instruments + #[arg(long, value_enum, default_value_t=ArtifactListFormat::Asmx)] + source_file_format: ArtifactListFormat, + + /// Directory to output artifacts (instruments and samples) + #[arg(long, default_value_t=String::from( "./" ))] + 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 + } +} \ No newline at end of file diff --git a/src/reskit/mod.rs b/src/reskit/mod.rs index bcc13ab..5ca7637 100644 --- a/src/reskit/mod.rs +++ b/src/reskit/mod.rs @@ -1,3 +1,4 @@ +pub mod cli; pub mod soundtrack; pub mod tileset; pub mod utility; \ No newline at end of file