From 873a08dc00afc4d3c50a710750f338fafd68e5fa Mon Sep 17 00:00:00 2001 From: ashley Date: Fri, 18 Aug 2023 00:55:24 -0400 Subject: [PATCH] EEF instrument generation --- src/main.rs | 2 +- src/reskit/soundtrack/dmf.rs | 172 +++++++++++++++++++++++++++++++-- src/reskit/soundtrack/types.rs | 14 ++- src/reskit/utility.rs | 54 ++++++++++- 4 files changed, 222 insertions(+), 20 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8bf6546..613b517 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,7 +41,7 @@ fn run() -> Result<(), Box> { 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 file'") + .arg_from_usage( "-o, --output= 'Specify output filename (may output multiple files depending on format)'") .arg( Arg::with_name( "TRACKER_FORMAT" ) .short( "tf" ) diff --git a/src/reskit/soundtrack/dmf.rs b/src/reskit/soundtrack/dmf.rs index 58da7d9..2796e63 100644 --- a/src/reskit/soundtrack/dmf.rs +++ b/src/reskit/soundtrack/dmf.rs @@ -1,6 +1,6 @@ -use std::{error::Error, fs::File, io::Read, convert::TryInto, collections::HashMap}; +use std::{error::Error, fs::File, io::Read, convert::TryInto, collections::HashMap, cmp::{min, max}}; use flate2::read::ZlibDecoder; -use crate::reskit::{utility::{Either, get_string, get_u8, skip, get_u32, get_i8, get_i32, get_u16, get_i16}, soundtrack::types::{SampleRate, SampleFormat, PsgEnvelope}}; +use crate::reskit::{utility::{get_string, get_u8, skip, get_u32, get_i8, get_i32, get_u16, get_i16, Ring, arrays_equal}, soundtrack::types::{SampleRate, SampleFormat, PsgEnvelope}}; use super::types::{Sample, Note, PsgSettings}; @@ -223,9 +223,10 @@ impl DmfModule { }; println!( "Volume Envelope Size:\t{}", envelope.len() ); + println!( "Loop at:\t{:?}", loop_at ); PsgEnvelope { - envelope: Either::Type1( envelope ), + envelope, loop_at, settings: HashMap::new() } @@ -257,9 +258,10 @@ impl DmfModule { println!( "Arpeggio Envelope Size:\t{}", envelope.len() ); println!( "Arpeggio Macro Fixed:\t{}", settings[ "arpeggio_macro_fixed" ] ); + println!( "Loop at:\t{:?}", loop_at ); PsgEnvelope { - envelope: Either::Type2( envelope ), + envelope, loop_at, settings } @@ -286,9 +288,10 @@ impl DmfModule { }; println!( "Noise Envelope Size:\t{}", envelope.len() ); + println!( "Loop at:\t{:?}", loop_at ); PsgEnvelope { - envelope: Either::Type1( envelope ), + envelope, loop_at, settings: HashMap::new() } @@ -315,9 +318,10 @@ impl DmfModule { }; println!( "WavTbl Envelope Size:\t{}\n", envelope.len() ); + println!( "Loop at:\t{:?}", loop_at ); PsgEnvelope { - envelope: Either::Type1( envelope ), + envelope, loop_at, settings: HashMap::new() } @@ -592,9 +596,163 @@ impl DmfModule { ) } - pub fn to_esf( self ) -> Result, Box> { + pub fn get_eefs( &self ) -> Result>, Box> { + let mut eefs: HashMap> = HashMap::new(); + + for instrument in &self.instruments { + if let InstrumentType::Psg( settings ) = &instrument.instrument_type { + // 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. + + // comments using example: 20 vol values, 6 arp values, both repeat from the beginning + + // generate the initial portion (always). this is just an expansion + // of the envelopes, without loops, into two Vec's. + // 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 + // 01 02 03 04 05 06 -- -- -- -- -- -- -- -- -- -- -- -- -- -- + let mut volume_envelope: Vec = settings.volume.envelope.iter().map( | long | *long as u8 ).collect(); + let mut arpeggio_envelope: Vec = settings.arpeggio.envelope.iter().map( | long | *long as i8 ).collect(); + + // now take a slice of the repeatable portion of the smaller array, + // and use it to expand the smaller array to the same size as the + // larger array. if there is no repeatable portion, take a one-element + // slice of the last element in the array. + // 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 + // 01 02 03 04 05 06 01 02 03 04 05 06 01 02 03 04 05 06 01 02 + let mut volume_repeat_pattern: Ring = Ring::create( + if let Some( loop_at ) = settings.volume.loop_at { + Vec::from( &volume_envelope[ loop_at.. ] ) + } else { + vec![ volume_envelope[ volume_envelope.len() - 1 ] ] + }, + None + ); + let mut arpeggio_repeat_pattern: Ring = Ring::create( + if let Some( loop_at ) = settings.arpeggio.loop_at { + Vec::from( &arpeggio_envelope[ loop_at.. ] ) + } else { + vec![ arpeggio_envelope[ arpeggio_envelope.len() - 1 ] ] + }, + None + ); + + if volume_envelope.len() < arpeggio_envelope.len() { + let difference = arpeggio_envelope.len() - volume_envelope.len(); + for _ in 0..difference { + volume_envelope.push( + volume_repeat_pattern.next().expect( "fatal: internal error (volume_repeat_pattern)" ) + ); + } + } else if arpeggio_envelope.len() < volume_envelope.len() { + let difference = volume_envelope.len() - arpeggio_envelope.len(); + for _ in 0..difference { + arpeggio_envelope.push( + arpeggio_repeat_pattern.next().expect( "fatal: internal error (arpeggio_repeat_pattern)" ) + ); + } + } + + let interval_size = max( volume_repeat_pattern.len(), arpeggio_repeat_pattern.len() ); + + // now, you generate the repeating portion using the two repeat_pattern iterators. + // each segment at a time is generated by the size of the larger array. + // generate it until the first repeat pattern is regenerated. + // 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 .. (volume repeats from beginning) + // 03 04 05 06 01 02 03 04 05 06 01 02 03 04 05 06 01 02 03 04 .. (arpeggio repeats up from where it left off) + + // 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 .. + // 05 06 01 02 03 04 05 06 01 02 03 04 05 06 01 02 03 04 05 06 .. + + // 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 .. + // 01 02 03 04 05 06 01 02 03 04 05 06 01 02 03 04 05 06 01 02 .. + let mut volume_repeat: Vec = Vec::new(); + let mut arpeggio_repeat: Vec = Vec::new(); + + let mut initial_volume_repeat: Vec = Vec::new(); + let mut initial_arpeggio_repeat: Vec = Vec::new(); + for _ in 0..interval_size { + initial_volume_repeat.push( volume_repeat_pattern.next().expect( "fatal: internal error (volume_repeat_pattern)" ) ); + initial_arpeggio_repeat.push( arpeggio_repeat_pattern.next().expect( "fatal: internal error (arpeggio_repeat_pattern)" ) ); + } + + volume_repeat.append( &mut initial_volume_repeat ); + arpeggio_repeat.append( &mut initial_arpeggio_repeat ); + + loop { + // Guard against OOM + if volume_repeat.len() > 65535 { + return Err( "eef error: psg instrument repeat period too long (try modifying repeating envelopes to have evenly-divisible parameters)" )? + } + + let mut volume_repeat_period: Vec = Vec::new(); + let mut arpeggio_repeat_period: Vec = Vec::new(); + for _ in 0..interval_size { + volume_repeat_period.push( volume_repeat_pattern.next().expect( "fatal: internal error (volume_repeat_pattern)" ) ); + arpeggio_repeat_period.push( arpeggio_repeat_pattern.next().expect( "fatal: internal error (arpeggio_repeat_pattern)" ) ); + } + + // End loop once the pattern begins repeating + if arrays_equal( &initial_volume_repeat, &volume_repeat_period ) && arrays_equal( &initial_arpeggio_repeat, &arpeggio_repeat_period ) { + break; + } else { + // If it doesn't repeat, append and generate another period + volume_repeat.append( &mut volume_repeat_period ); + arpeggio_repeat.append( &mut arpeggio_repeat_period ); + } + } + + // Now generate the Echo EEF envelope from volume_envelope/volume_repeat + arpeggio_envelope/arpeggio_repeat + let mut envelope: Vec = Vec::new(); + for i in 0..volume_envelope.len() { + envelope.push( get_eef_volume( volume_envelope[ i ] )? | get_eef_shift( arpeggio_envelope[ i ] )? ); + } + + // Echo begin loop command + envelope.push( 0xFE ); + + for i in 0..volume_repeat.len() { + envelope.push( get_eef_volume( volume_repeat[ i ] )? | get_eef_shift( arpeggio_repeat[ i ] )? ); + } + + // Echo end loop command + envelope.push( 0xFF ); + + eefs.insert( instrument.name.clone(), envelope ); + } + } + + Ok( eefs ) + } + + pub fn get_esf( &self ) -> Result, Box> { // TODO !! todo!() } +} + +fn get_eef_volume( dmf_volume: u8 ) -> Result> { + Ok( max( dmf_volume, 0x0F ) ) +} + +fn get_eef_shift( dmf_shift: i8 ) -> Result> { + // Clamp to [-12, 12] + let as_i8: i8 = max( -12, min( dmf_shift, 12 ) ); + + Ok( + match as_i8 { + 0 => 0x00, + + 1 => 0x10, -1 => 0x80, + 2 => 0x20, -2 => 0x90, + 3 => 0x30, -3 => 0xA0, + 4 => 0x40, -4 => 0xB0, + 5 | 6 => 0x50, -5 | -6 => 0xC0, + 7 | 8 => 0x60, -7 | -8 => 0xD0, + 9 | 10 | 11 | 12 => 0x70, -9 | -10 | -11 | -12 => 0xE0, + + _ => return Err( "internal error: arpeggio shift out of defined range" )? + } + ) } \ No newline at end of file diff --git a/src/reskit/soundtrack/types.rs b/src/reskit/soundtrack/types.rs index 8df4728..c053df7 100644 --- a/src/reskit/soundtrack/types.rs +++ b/src/reskit/soundtrack/types.rs @@ -1,22 +1,20 @@ use std::collections::HashMap; -use crate::reskit::utility::Either; #[derive(Debug)] -pub struct PsgEnvelope { - pub envelope: Either, Vec>, +pub struct PsgEnvelope< DataFormat > { + pub envelope: Vec, pub loop_at: Option, pub settings: HashMap<&'static str, bool> } #[derive(Debug)] pub struct PsgSettings { - pub volume: PsgEnvelope, - pub arpeggio: PsgEnvelope, - pub noise: PsgEnvelope, - pub wavetable: PsgEnvelope + pub volume: PsgEnvelope< u32 >, + pub arpeggio: PsgEnvelope< i32 >, + pub noise: PsgEnvelope< u32 >, + pub wavetable: PsgEnvelope< u32 > } - #[derive(Debug)] pub enum SampleRate { Hz8000, diff --git a/src/reskit/utility.rs b/src/reskit/utility.rs index d266e17..6f385fe 100644 --- a/src/reskit/utility.rs +++ b/src/reskit/utility.rs @@ -1,10 +1,56 @@ use std::{slice::Iter, error::Error, str::from_utf8, convert::TryInto}; +pub struct Ring< Type: Copy >{ + data: Vec< Type >, + position: usize +} -#[derive(Debug)] -pub enum Either< Type1, Type2 > { - Type1( Type1 ), - Type2( Type2 ) +impl< Type: Copy > Ring< Type > { + + pub fn create( data: Vec< Type >, start_position: Option ) -> Ring< Type > { + Ring { + data, + position: if let Some( position ) = start_position { + position + } else { + 0 + } + } + } + + pub fn len( &self ) -> usize { + self.data.len() + } + +} + +impl< Type: Copy > Iterator for Ring< Type > { + type Item = Type; + + fn next( &mut self ) -> Option< Self::Item > { + let item = self.data[ self.position % self.data.len() ]; + + self.position += 1; + + Some( item ) + } + +} + +pub fn arrays_equal< Type: PartialEq >( array1: &Vec, array2: &Vec ) -> bool { + if array1.len() != array2.len() { + return false + } + + for i1 in 0..array1.len() { + for i2 in 0..array2.len() { + if array1[ i1 ] != array2[ i2 ] { + return false + } + } + } + + true } pub fn print_error( error_msg: &str ) {