Refactor Echo instrument generation
parent
b4fffa8ea9
commit
546376cc02
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "reskit"
|
name = "reskit"
|
||||||
version = "0.1.0"
|
version = "0.0.2"
|
||||||
authors = ["ne0ndrag0n <ne0ndrag0n@users.noreply.github.com>"]
|
authors = ["ne0ndrag0n <ne0ndrag0n@users.noreply.github.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
@ -16,3 +16,4 @@ hound = "3.5.0"
|
||||||
pitch_shift = "1.0.0"
|
pitch_shift = "1.0.0"
|
||||||
uuid = { version = "1.4.1", features = [ "v4" ] }
|
uuid = { version = "1.4.1", features = [ "v4" ] }
|
||||||
linked_hash_set = "0.1.4"
|
linked_hash_set = "0.1.4"
|
||||||
|
convert_case = "0.6.0"
|
24
src/main.rs
24
src/main.rs
|
@ -17,9 +17,9 @@ use crate::reskit::utility::print_good;
|
||||||
|
|
||||||
fn run() -> Result<(), Box<dyn Error>> {
|
fn run() -> Result<(), Box<dyn Error>> {
|
||||||
let matches = App::new( "reskit" )
|
let matches = App::new( "reskit" )
|
||||||
.version( "0.0.2a" )
|
.version( env!( "CARGO_PKG_VERSION" ) )
|
||||||
.author( "(c) 2021-2023 Ashley N. <ne0ndrag0n@ne0ndrag0n.com>" )
|
.author( "(c) 2021-2023 Ashley N. <ne0ndrag0n@ne0ndrag0n.com>" )
|
||||||
.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(
|
||||||
SubCommand::with_name( "tileset" )
|
SubCommand::with_name( "tileset" )
|
||||||
.about( "Generate a Sega Megadrive VDP tileset + palette from a 15-colour image" )
|
.about( "Generate a Sega Megadrive VDP tileset + palette from a 15-colour image" )
|
||||||
|
@ -49,7 +49,6 @@ fn run() -> Result<(), Box<dyn Error>> {
|
||||||
.arg_from_usage( "-o, --output=<FILE> 'Specify output filename (may output multiple files depending on format)'")
|
.arg_from_usage( "-o, --output=<FILE> 'Specify output filename (may output multiple files depending on format)'")
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name( "TRACKER_FORMAT" )
|
Arg::with_name( "TRACKER_FORMAT" )
|
||||||
.short( "tf" )
|
|
||||||
.long( "tracker-format" )
|
.long( "tracker-format" )
|
||||||
.help( "Specify tracker module format (valid options: dmf (DefleMask Tracker))")
|
.help( "Specify tracker module format (valid options: dmf (DefleMask Tracker))")
|
||||||
.default_value( "dmf" )
|
.default_value( "dmf" )
|
||||||
|
@ -57,12 +56,25 @@ fn run() -> Result<(), Box<dyn Error>> {
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name( "DRIVER_FORMAT" )
|
Arg::with_name( "DRIVER_FORMAT" )
|
||||||
.short( "df" )
|
|
||||||
.long( "driver-format" )
|
.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" )
|
.default_value( "esf" )
|
||||||
.takes_value( true )
|
.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();
|
.get_matches();
|
||||||
|
|
||||||
|
@ -84,7 +96,7 @@ fn run() -> Result<(), Box<dyn Error>> {
|
||||||
|
|
||||||
let result = DmfModule::from_file( &input_filename )?;
|
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 );
|
println!( "Creating instrument file {}", filename );
|
||||||
let mut file = File::create( filename )?;
|
let mut file = File::create( filename )?;
|
||||||
file.write_all( &data )?
|
file.write_all( &data )?
|
||||||
|
|
|
@ -55,7 +55,7 @@ pub type EchoEvent = Vec<u8>;
|
||||||
|
|
||||||
pub trait EchoFormat {
|
pub trait EchoFormat {
|
||||||
|
|
||||||
fn get_instruments( &self ) -> Result<HashMap<String, Vec<u8>>, Box<dyn Error>>;
|
fn get_instruments( &self, 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>>;
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ use flate2::read::ZlibDecoder;
|
||||||
use linked_hash_set::LinkedHashSet;
|
use linked_hash_set::LinkedHashSet;
|
||||||
use samplerate::convert;
|
use samplerate::convert;
|
||||||
use uuid::Uuid;
|
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}}};
|
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.";
|
const DMF_MAGIC_NUMBER: &'static str = ".DelekDefleMask.";
|
||||||
|
@ -30,6 +31,7 @@ pub enum FrameMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DmfModule {
|
pub struct DmfModule {
|
||||||
|
path: String,
|
||||||
platform: u8,
|
platform: u8,
|
||||||
version: u8,
|
version: u8,
|
||||||
time_base: u8,
|
time_base: u8,
|
||||||
|
@ -621,7 +623,7 @@ impl DmfModule {
|
||||||
|
|
||||||
Ok(
|
Ok(
|
||||||
DmfModule {
|
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
|
pattern_matrix, channel_patterns, instruments, samples
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -631,12 +633,16 @@ impl DmfModule {
|
||||||
|
|
||||||
impl EchoFormat for DmfModule {
|
impl EchoFormat for DmfModule {
|
||||||
|
|
||||||
fn get_instruments( &self ) -> Result<HashMap<String, Vec<u8>>, Box<dyn Error>> {
|
fn get_instruments( &self, 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
|
||||||
|
let mut instrument_filenames: Vec<String> = Vec::new();
|
||||||
|
|
||||||
for instrument in &self.instruments {
|
for instrument in &self.instruments {
|
||||||
match &instrument.instrument_type {
|
match &instrument.instrument_type {
|
||||||
InstrumentType::Fm2612( settings ) => {
|
InstrumentType::Fm2612( settings ) => {
|
||||||
|
instrument_filenames.push( format!( "{}.eif", instrument.name ) );
|
||||||
|
|
||||||
// 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;
|
||||||
|
|
||||||
|
@ -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 );
|
files.insert( format!( "{}.eif", if instrument.name.is_empty() { Uuid::new_v4().to_string() } else { instrument.name.to_owned() } ), eif );
|
||||||
},
|
},
|
||||||
InstrumentType::PsgDcsg( settings ) => {
|
InstrumentType::PsgDcsg( settings ) => {
|
||||||
|
instrument_filenames.push( format!( "{}.eef", instrument.name ) );
|
||||||
|
|
||||||
// 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.
|
||||||
|
@ -815,6 +823,8 @@ impl EchoFormat for DmfModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
for sample in &self.samples {
|
for sample in &self.samples {
|
||||||
|
instrument_filenames.push( format!( "{}.ewf", sample.name ) );
|
||||||
|
|
||||||
// Amplify data in original sample
|
// Amplify data in original sample
|
||||||
let data: Vec<i16> = sample.data
|
let data: Vec<i16> = sample.data
|
||||||
.iter()
|
.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 );
|
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 )
|
Ok( files )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue