Refactor Echo instrument generation
parent
b4fffa8ea9
commit
546376cc02
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "reskit"
|
||||
version = "0.1.0"
|
||||
version = "0.0.2"
|
||||
authors = ["ne0ndrag0n <ne0ndrag0n@users.noreply.github.com>"]
|
||||
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"
|
||||
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>> {
|
||||
let matches = App::new( "reskit" )
|
||||
.version( "0.0.2a" )
|
||||
.version( env!( "CARGO_PKG_VERSION" ) )
|
||||
.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::with_name( "tileset" )
|
||||
.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(
|
||||
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<dyn Error>> {
|
|||
)
|
||||
.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<dyn Error>> {
|
|||
|
||||
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 )?
|
||||
|
|
|
@ -55,7 +55,7 @@ pub type EchoEvent = Vec<u8>;
|
|||
|
||||
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>>;
|
||||
|
||||
|
|
|
@ -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<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();
|
||||
// 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 {
|
||||
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<i16> = 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 )
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue