Refactor Echo instrument generation

master
Ashley N. 2023-08-25 23:20:15 -04:00
parent b4fffa8ea9
commit 546376cc02
4 changed files with 51 additions and 11 deletions

View File

@ -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"

View File

@ -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 )?

View File

@ -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>>;

View File

@ -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 )
}