Refactor into separate modules

master
Ashley N. 2023-08-27 11:09:36 -04:00
parent 8459028858
commit a1eb158292
5 changed files with 390 additions and 385 deletions

View File

@ -8,7 +8,7 @@ use std::error::Error;
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
use clap::{App, Arg, SubCommand}; use clap::{App, Arg, SubCommand};
use reskit::soundtrack::engines::echo::EchoFormat; use reskit::soundtrack::engines::echo::engine::EchoFormat;
use reskit::soundtrack::formats::dmf::DmfModule; use reskit::soundtrack::formats::dmf::DmfModule;
use reskit::utility; use reskit::utility;
use reskit::tileset; use reskit::tileset;

View File

@ -0,0 +1,360 @@
use std::{collections::HashMap, cmp::{max, min}, error::Error};
use convert_case::{Case, Casing};
use samplerate::convert;
use uuid::Uuid;
use crate::reskit::{soundtrack::{formats::dmf::{DmfModule, ECHO_EWF_SAMPLE_RATE}, types::{InstrumentType, SampleFormat, Channel, PatternRow}}, utility::{print_warning, Ring}};
use super::engine::{EchoFormat, ESF_FM_1, ESF_FM_2, ESF_FM_3, ESF_FM_4, ESF_FM_5, ESF_FM_6, ESF_PSG_1, ESF_PSG_2, ESF_PSG_3, ESF_PSG_4, EchoEvent, get_events_for_row, compact_delays};
impl EchoFormat for DmfModule {
fn get_artifacts( &self, soundtrack_label: &str, soundtrack_path: &str, artifact_path: &str, 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;
// The operators are laid out as FmOperator objects in order 1 -> 3 -> 2 -> 4
// EIF instrument layout below
let mut mult_dt: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
let mut tl: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
let mut ar_rs: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
let mut dr_am: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
let mut sr: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
let mut rr_sl: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
let mut ssg_eg: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
// Likewise, "operators" is laid out 1 -> 3 -> 2 -> 4
for i in 0..4 {
let dt: u8 = match settings.operators[ i ].dt {
0 => 0b000,
1 => 0b001,
2 => 0b010,
3 => 0b011,
-1 => 0b101,
-2 => 0b110,
-3 => 0b111,
_ => return Err( "invalid file: invalid value given for dt" )?
};
// https://plutiedev.com/ym2612-registers
// https://github.com/sikthehedgehog/Echo/blob/master/doc/eif.txt
mult_dt[ i ] = ( dt << 4 ) | settings.operators[ i ].mult;
tl[ i ] = settings.operators[ i ].tl;
ar_rs[ i ] = ( settings.operators[ i ].rs << 6 ) | settings.operators[ i ].ar;
dr_am[ i ] = ( settings.operators[ i ].am << 7 ) | settings.operators[ i ].dr;
sr[ i ] = settings.operators[ i ].d2r; // "Sometimes also called 'second decay rate' (D2R)."
rr_sl[ i ] = ( settings.operators[ i ].sl << 4 ) | settings.operators[ i ].rr;
if settings.operators[ i ].ssg_mode > 0 {
print_warning( &format!( "SSG-EG mode set on instrument {}, operator {}. this operator may not work on clone hardware or certain emulators", instrument.name, i ) );
}
ssg_eg[ i ] = settings.operators[ i ].ssg_mode;
}
let mut eif: Vec<u8> = Vec::new();
eif.push( alg_fb );
eif.extend( mult_dt );
eif.extend( tl );
eif.extend( ar_rs );
eif.extend( dr_am );
eif.extend( sr );
eif.extend( rr_sl );
eif.extend( ssg_eg );
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.
// 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<u8>'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<u8> = settings.volume.envelope.iter().map( | long | *long as u8 ).collect();
let mut arpeggio_envelope: Vec<i8> = 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<u8> = 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<i8> = 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<u8> = Vec::new();
let mut arpeggio_repeat: Vec<i8> = Vec::new();
let mut initial_volume_repeat: Vec<u8> = Vec::new();
let mut initial_arpeggio_repeat: Vec<i8> = 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.extend( &initial_volume_repeat );
arpeggio_repeat.extend( &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<u8> = Vec::new();
let mut arpeggio_repeat_period: Vec<i8> = 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 initial_volume_repeat == volume_repeat_period && 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<u8> = vec![ 0x00 ];
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 );
files.insert( format!( "{}.eef", if instrument.name.is_empty() { Uuid::new_v4().to_string() } else { instrument.name.to_owned() } ), envelope );
}
}
}
for sample in &self.samples {
instrument_filenames.push( format!( "{}.ewf", sample.name ) );
// Amplify data in original sample
let data: Vec<i16> = sample.data
.iter()
.map( | pcm_sample | {
let big_pcm_sample: i32 = min( ( *pcm_sample as i32 ) + sample.amp as i32, i16::MAX as i32 );
big_pcm_sample as i16
} )
.collect();
// Convert to f32 for various library operations
let data: Vec<f32> = data
.into_iter()
.map( | pcm_sample | {
if let SampleFormat::Bits8 = sample.bitrate {
( pcm_sample as u8 ) as f32 / 255.0
} else {
pcm_sample as f32 / 32767.0
}
} )
.collect();
// TODO: Adjust pitch using crate pitch_shift
if sample.pitch > 0 {
print_warning( "pitch shift not yet implemented for pcm samples. your drums/voice samples/etc may sound a bit funny" );
}
// Use libsamplerate to resample from whatever it is now to 10650Hz, the sample rate
// required by Echo sound engine EWF samples.
let data: Vec<f32> = convert( sample.rate, ECHO_EWF_SAMPLE_RATE, 1, samplerate::ConverterType::SincBestQuality, &data )?;
// Convert from f32 back to i16, then downconvert to u8
let mut data: Vec<u8> = data
.into_iter()
.map( | pcm_sample | {
let pcm_sample = ( ( pcm_sample + 1.0 ) * 128.0 ) as u8;
// Do not end stream prematurely (EWF format uses 0xFF to end stream)
if pcm_sample == 0xFF {
0xFE
} else {
pcm_sample
}
} )
.collect();
// Terminate stream
data.push( 0xFF );
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", soundtrack_label );
echo_asm += &format!( "\tincbin '{}'\n\n", soundtrack_path );
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", artifact_path, filename );
}
files.insert( format!( "music.asm" ), echo_asm.into_bytes() );
Ok( files )
}
fn get_esf( &self ) -> Result<Vec<u8>, Box<dyn Error>> {
let mut esf: Vec<u8> = Vec::new();
// Write deflemask rows one at a time.
// Final pass will combine delays into single delay events
// to save space on the cartridge.
// ESF ticks are not the same as Deflemask ticks!
// A series of events fit into a single ESF tick, which ends
// upon the next delay event or stop event.
// Use instrument changes wisely, as these are expensive register writes.
// PCM playback stalls the stream pipeline as well so use wisely.
// FM1 -> SN4 channel states (used to compute effects and to aid in not duplicating instrument sets)
// Since trackers like to set the same instrument each time a note on is played...
let mut channels: [Channel; 10] = [
Channel::new( ESF_FM_1 ), Channel::new( ESF_FM_2 ), Channel::new( ESF_FM_3 ), Channel::new( ESF_FM_4 ), Channel::new( ESF_FM_5 ), Channel::new( ESF_FM_6 ),
Channel::new( ESF_PSG_1 ), Channel::new( ESF_PSG_2 ), Channel::new( ESF_PSG_3 ), Channel::new( ESF_PSG_4 )
];
// Iterate for each row, for each channel
// Recall items are stored as self.channel_patterns[ channel ][ row_number ]
let mut all_events: Vec<EchoEvent> = Vec::new();
for row_number in 0..self.rows_per_pattern {
let events_this_row: Vec<EchoEvent> = get_events_for_row(
&mut channels,
{
let mut columns: Vec<&PatternRow> = Vec::new();
for channel in 0..10 {
columns.push( &self.channel_patterns[ channel ][ row_number as usize ] );
}
columns
},
if row_number % 2 == 0 {
self.speed_a * self.time_base
} else {
self.speed_b * self.time_base
},
self.instruments.len() as u8
)?;
// Transfer ESF events to the main stream
all_events.extend( events_this_row );
}
// Compact sequences of delays to save rom space
let all_events = compact_delays( all_events )?;
for event in all_events {
esf.extend( event );
}
esf.push( 0xFF ); // Terminate the stream
Ok( esf )
}
}
fn get_eef_volume( dmf_volume: u8 ) -> Result<u8, Box<dyn Error>> {
Ok( min( 0x0F - dmf_volume, 0x0F ) )
}
fn get_eef_shift( dmf_shift: i8 ) -> Result<u8, Box<dyn Error>> {
// 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" )?
}
)
}

View File

@ -1,7 +1,6 @@
use std::{collections::HashMap, error::Error}; use std::{collections::HashMap, error::Error};
use linked_hash_set::LinkedHashSet; use linked_hash_set::LinkedHashSet;
use crate::reskit::{soundtrack::types::{PatternRow, Note, Channel, OctaveFrequency, Effect}, utility::print_warning};
use crate::reskit::{soundtrack::{types::{PatternRow, Note, Channel, OctaveFrequency, Effect}, formats::dmf::DmfModule}, utility::print_warning};
// https://github.com/sikthehedgehog/Echo/blob/master/doc/esf.txt // https://github.com/sikthehedgehog/Echo/blob/master/doc/esf.txt
const ESF_NOTE_ON: u8 = 0x00; const ESF_NOTE_ON: u8 = 0x00;
@ -61,7 +60,7 @@ pub trait EchoFormat {
} }
fn get_semitone( note: &Note ) -> Result<u8, Box<dyn Error>> { pub fn get_semitone( note: &Note ) -> Result<u8, Box<dyn Error>> {
Ok( match note { Ok( match note {
Note::NoteOff => return Err( "internal error: attempted to get semitone for a Note::NoteOff" )?, Note::NoteOff => return Err( "internal error: attempted to get semitone for a Note::NoteOff" )?,
Note::C(_) => ESF_SEMITONE_C, Note::C(_) => ESF_SEMITONE_C,
@ -82,7 +81,7 @@ fn get_semitone( note: &Note ) -> Result<u8, Box<dyn Error>> {
/** /**
* Notes are used to index PCM sample playback. C = 1, etc. * Notes are used to index PCM sample playback. C = 1, etc.
*/ */
fn get_pcm_index( note: &Note ) -> Result<u8, Box<dyn Error>> { pub fn get_pcm_index( note: &Note ) -> Result<u8, Box<dyn Error>> {
Ok( match note { Ok( match note {
Note::NoteOff => return Err( "internal error: attempted to get pcm index for a Note::NoteOff" )?, Note::NoteOff => return Err( "internal error: attempted to get pcm index for a Note::NoteOff" )?,
Note::C(_) => 0, Note::C(_) => 0,
@ -108,7 +107,7 @@ fn get_pcm_index( note: &Note ) -> Result<u8, Box<dyn Error>> {
D - 722 | F# - 910 | A# - 1146 D - 722 | F# - 910 | A# - 1146
D# - 765 | G - 964 | B - 1214 D# - 765 | G - 964 | B - 1214
*/ */
fn get_semitone_frequency( semitone: u8 ) -> Result<u16, Box<dyn Error>> { pub fn get_semitone_frequency( semitone: u8 ) -> Result<u16, Box<dyn Error>> {
Ok( match semitone { Ok( match semitone {
ESF_SEMITONE_C => 644, ESF_SEMITONE_C => 644,
ESF_SEMITONE_C_SHARP => 681, ESF_SEMITONE_C_SHARP => 681,
@ -126,15 +125,15 @@ fn get_semitone_frequency( semitone: u8 ) -> Result<u16, Box<dyn Error>> {
} ) } )
} }
fn get_fm_note_byte( octave: u8, semitone: u8 ) -> u8 { pub fn get_fm_note_byte( octave: u8, semitone: u8 ) -> u8 {
32 * octave + 2 * semitone + 1 32 * octave + 2 * semitone + 1
} }
fn get_psg_note_byte( octave: u8, semitone: u8 ) -> u8 { pub fn get_psg_note_byte( octave: u8, semitone: u8 ) -> u8 {
24 * octave + 2 * semitone 24 * octave + 2 * semitone
} }
fn get_note( note: &Note, channel: u8 ) -> Result<u8, Box<dyn Error>> { pub fn get_note( note: &Note, channel: u8 ) -> Result<u8, Box<dyn Error>> {
Ok( match channel { Ok( match channel {
ESF_FM_1..=ESF_FM_3 | ESF_FM_4..=ESF_FM_6 => get_fm_note_byte( note.get_octave()?, get_semitone( note )? ), ESF_FM_1..=ESF_FM_3 | ESF_FM_4..=ESF_FM_6 => get_fm_note_byte( note.get_octave()?, get_semitone( note )? ),
ESF_PSG_1..=ESF_PSG_4 => get_psg_note_byte( note.get_octave()?, get_semitone( note )? ), ESF_PSG_1..=ESF_PSG_4 => get_psg_note_byte( note.get_octave()?, get_semitone( note )? ),
@ -142,7 +141,7 @@ fn get_note( note: &Note, channel: u8 ) -> Result<u8, Box<dyn Error>> {
} ) } )
} }
fn get_volume( channel: u8, volume: u8 ) -> Result<u8, Box<dyn Error>> { pub fn get_volume( channel: u8, volume: u8 ) -> Result<u8, Box<dyn Error>> {
Ok( match channel { Ok( match channel {
ESF_FM_1..=ESF_FM_3 | ESF_FM_4..=ESF_FM_6 => 0x7F - volume, ESF_FM_1..=ESF_FM_3 | ESF_FM_4..=ESF_FM_6 => 0x7F - volume,
ESF_PSG_1..=ESF_PSG_4 => 0x0F - volume, ESF_PSG_1..=ESF_PSG_4 => 0x0F - volume,
@ -153,7 +152,7 @@ fn get_volume( channel: u8, volume: u8 ) -> Result<u8, Box<dyn Error>> {
/** /**
* Generate an echo delay event. "delay" is the amount to wait, in 1/60 of a second ticks. * Generate an echo delay event. "delay" is the amount to wait, in 1/60 of a second ticks.
*/ */
fn get_delay( delay: u8 ) -> Result<EchoEvent, Box<dyn Error>> { pub fn get_delay( delay: u8 ) -> Result<EchoEvent, Box<dyn Error>> {
let events: EchoEvent = if delay == 0 { let events: EchoEvent = if delay == 0 {
// No delay to generate! Probably should be an error // No delay to generate! Probably should be an error
vec![] vec![]
@ -170,7 +169,7 @@ fn get_delay( delay: u8 ) -> Result<EchoEvent, Box<dyn Error>> {
* For a specific row and channel, get the events this row and channel contribute to the stream. * For a specific row and channel, get the events this row and channel contribute to the stream.
* While doing so, update the state of the channel for future event generation. * While doing so, update the state of the channel for future event generation.
*/ */
fn get_events_for_channel( channel: &mut Channel, row: &PatternRow, pcm_offset: u8 ) -> Result<Vec<EchoEvent>, Box<dyn Error>> { pub fn get_events_for_channel( channel: &mut Channel, row: &PatternRow, pcm_offset: u8 ) -> Result<Vec<EchoEvent>, Box<dyn Error>> {
let mut events: Vec<EchoEvent> = Vec::new(); let mut events: Vec<EchoEvent> = Vec::new();
// Adjust volume // Adjust volume
@ -219,7 +218,7 @@ fn get_events_for_channel( channel: &mut Channel, row: &PatternRow, pcm_offset:
* Add effects from a given row to a particular channel and perform activities due on insert. This may generate Echo * Add effects from a given row to a particular channel and perform activities due on insert. This may generate Echo
* events that should be applied to the stream. * events that should be applied to the stream.
*/ */
fn apply_effects_to_channel( channel: &mut Channel, effects: &LinkedHashSet<Effect> ) -> Result<Vec<EchoEvent>, Box<dyn Error>> { pub fn apply_effects_to_channel( channel: &mut Channel, effects: &LinkedHashSet<Effect> ) -> Result<Vec<EchoEvent>, Box<dyn Error>> {
let mut events: Vec<EchoEvent> = Vec::new(); let mut events: Vec<EchoEvent> = Vec::new();
for effect in effects { for effect in effects {
@ -332,7 +331,7 @@ fn apply_effects_to_channel( channel: &mut Channel, effects: &LinkedHashSet<Effe
Ok( events ) Ok( events )
} }
fn sequence_to_compacted_delay( current_sequence: &Vec<EchoEvent> ) -> Result<Vec<EchoEvent>, Box<dyn Error>> { pub fn sequence_to_compacted_delay( current_sequence: &Vec<EchoEvent> ) -> Result<Vec<EchoEvent>, Box<dyn Error>> {
let mut new_events: Vec<EchoEvent> = Vec::new(); let mut new_events: Vec<EchoEvent> = Vec::new();
let mut cumulative_delay: u16 = 0; let mut cumulative_delay: u16 = 0;
@ -393,7 +392,7 @@ pub fn compact_delays( events: Vec<EchoEvent> ) -> Result<Vec<EchoEvent>, Box<dy
* Generate an Echo frequency shift up event for a given channel, updating active_note to be ready * Generate an Echo frequency shift up event for a given channel, updating active_note to be ready
* for the next event. * for the next event.
*/ */
fn get_portamento( channel: &mut Channel, portamento_effect: &Effect ) -> Result<EchoEvent, Box<dyn Error>> { pub fn get_portamento( channel: &mut Channel, portamento_effect: &Effect ) -> Result<EchoEvent, Box<dyn Error>> {
Ok( Ok(
if channel.id == ESF_FM_6_PCM { if channel.id == ESF_FM_6_PCM {
// Generate no events if channel is set to pcm // Generate no events if channel is set to pcm
@ -452,7 +451,7 @@ fn get_portamento( channel: &mut Channel, portamento_effect: &Effect ) -> Result
/** /**
* Generate a note cut effect for this channel and modify channel state to reflect it * Generate a note cut effect for this channel and modify channel state to reflect it
*/ */
fn get_note_cut( channel: &mut Channel, note_cut_effect: &Effect, tick: u8 ) -> Result<EchoEvent, Box<dyn Error>> { pub fn get_note_cut( channel: &mut Channel, note_cut_effect: &Effect, tick: u8 ) -> Result<EchoEvent, Box<dyn Error>> {
let after_ticks = match note_cut_effect { let after_ticks = match note_cut_effect {
Effect::NoteCut { after_ticks } => *after_ticks, Effect::NoteCut { after_ticks } => *after_ticks,
_ => return Err( "internal error: provided effect is not the note cut effect" )? _ => return Err( "internal error: provided effect is not the note cut effect" )?
@ -478,7 +477,7 @@ fn get_note_cut( channel: &mut Channel, note_cut_effect: &Effect, tick: u8 ) ->
/** /**
* Get effects that require a specific action when computing the event flushes to Echo. * Get effects that require a specific action when computing the event flushes to Echo.
*/ */
fn get_actionable_effects( channels: &mut [Channel] ) -> Result<Vec<(usize, Effect)>, Box<dyn Error>> { pub fn get_actionable_effects( channels: &mut [Channel] ) -> Result<Vec<(usize, Effect)>, Box<dyn Error>> {
// All portamento effects deploy per tick, not per row. So we need to aggregate all portamentos across all // All portamento effects deploy per tick, not per row. So we need to aggregate all portamentos across all
// channels for this row, then flush them once per `ticks_to_wait` for this row. // channels for this row, then flush them once per `ticks_to_wait` for this row.
let mut actionable_effects: Vec<(usize, Effect)> = Vec::new(); let mut actionable_effects: Vec<(usize, Effect)> = Vec::new();
@ -507,7 +506,7 @@ fn get_actionable_effects( channels: &mut [Channel] ) -> Result<Vec<(usize, Effe
* -are- effects generated by this function, you should not apply ticks_to_wait, since this function will spend out * -are- effects generated by this function, you should not apply ticks_to_wait, since this function will spend out
* the tick budget for that row instead. * the tick budget for that row instead.
*/ */
fn get_actionable_effect_sequence( channels: &mut [Channel], ticks_to_wait: u8 ) -> Result<Vec<EchoEvent>, Box<dyn Error>> { pub fn get_actionable_effect_sequence( channels: &mut [Channel], ticks_to_wait: u8 ) -> Result<Vec<EchoEvent>, Box<dyn Error>> {
let mut events: Vec<EchoEvent> = Vec::new(); let mut events: Vec<EchoEvent> = Vec::new();
let actionable_effects = get_actionable_effects( channels )?; let actionable_effects = get_actionable_effects( channels )?;
@ -537,7 +536,7 @@ fn get_actionable_effect_sequence( channels: &mut [Channel], ticks_to_wait: u8 )
/** /**
* Get the delays due at the end of a row. These delays are what flushes the tick to Echo so that it can play. * Get the delays due at the end of a row. These delays are what flushes the tick to Echo so that it can play.
*/ */
fn get_delays( events: Vec<EchoEvent>, channels: &mut [Channel], ticks_to_wait: u8 ) -> Result<Vec<EchoEvent>, Box<dyn Error>> { pub fn get_delays( events: Vec<EchoEvent>, channels: &mut [Channel], ticks_to_wait: u8 ) -> Result<Vec<EchoEvent>, Box<dyn Error>> {
let mut events: Vec<EchoEvent> = events; let mut events: Vec<EchoEvent> = events;
let applied_effects = get_actionable_effect_sequence( channels, ticks_to_wait )?; let applied_effects = get_actionable_effect_sequence( channels, ticks_to_wait )?;

View File

@ -0,0 +1,2 @@
pub mod dmf;
pub mod engine;

View File

@ -1,16 +1,13 @@
use std::{error::Error, fs::File, io::Read, convert::TryInto, collections::HashMap, cmp::{min, max}}; use std::{error::Error, fs::File, io::Read, convert::TryInto, collections::HashMap};
use flate2::read::ZlibDecoder; use flate2::read::ZlibDecoder;
use linked_hash_set::LinkedHashSet; use linked_hash_set::LinkedHashSet;
use samplerate::convert; use crate::reskit::{utility::{get_string, get_u8, skip, get_u32, get_i8, get_i32, get_u16, get_i16, print_info}, soundtrack::types::{SampleFormat, PsgEnvelope, Note, Sample, PsgSettings, PatternRow, Effect, DcsgChannelMode, NoiseType, Instrument, InstrumentType, Fm2612Operator, Fm2612Settings}};
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."; const DMF_MAGIC_NUMBER: &'static str = ".DelekDefleMask.";
const DMF_SUPPORTED_VERSION: u8 = 27; const DMF_SUPPORTED_VERSION: u8 = 27;
const DMF_MD: u8 = 0x02; const DMF_MD: u8 = 0x02;
const DMF_MD_ENHANCED_CH3: u8 = 0x42; const DMF_MD_ENHANCED_CH3: u8 = 0x42;
const ECHO_EWF_SAMPLE_RATE: u32 = 10650; pub const ECHO_EWF_SAMPLE_RATE: u32 = 10650;
const DMF_EFFECT_ARPEGGIO: u8 = 0x00; const DMF_EFFECT_ARPEGGIO: u8 = 0x00;
const DMF_EFFECT_PORTAMENTO_UP: u8 = 0x01; const DMF_EFFECT_PORTAMENTO_UP: u8 = 0x01;
@ -34,15 +31,15 @@ pub struct DmfModule {
path: String, path: String,
platform: u8, platform: u8,
version: u8, version: u8,
time_base: u8, pub time_base: u8,
speed_a: u8, pub speed_a: u8,
speed_b: u8, pub speed_b: u8,
frame_mode: FrameMode, frame_mode: FrameMode,
rows_per_pattern: u32, pub rows_per_pattern: u32,
pattern_matrix: Vec<Vec<u16>>, pattern_matrix: Vec<Vec<u16>>,
channel_patterns: Vec<Vec<PatternRow>>, pub channel_patterns: Vec<Vec<PatternRow>>,
instruments: Vec<Instrument>, pub instruments: Vec<Instrument>,
samples: Vec<Sample> pub samples: Vec<Sample>
} }
impl DmfModule { impl DmfModule {
@ -633,356 +630,3 @@ impl DmfModule {
} }
} }
impl EchoFormat for DmfModule {
fn get_artifacts( &self, soundtrack_label: &str, soundtrack_path: &str, artifact_path: &str, 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;
// The operators are laid out as FmOperator objects in order 1 -> 3 -> 2 -> 4
// EIF instrument layout below
let mut mult_dt: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
let mut tl: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
let mut ar_rs: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
let mut dr_am: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
let mut sr: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
let mut rr_sl: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
let mut ssg_eg: [u8; 4] = [ 0x00, 0x00, 0x00, 0x00 ];
// Likewise, "operators" is laid out 1 -> 3 -> 2 -> 4
for i in 0..4 {
let dt: u8 = match settings.operators[ i ].dt {
0 => 0b000,
1 => 0b001,
2 => 0b010,
3 => 0b011,
-1 => 0b101,
-2 => 0b110,
-3 => 0b111,
_ => return Err( "invalid file: invalid value given for dt" )?
};
// https://plutiedev.com/ym2612-registers
// https://github.com/sikthehedgehog/Echo/blob/master/doc/eif.txt
mult_dt[ i ] = ( dt << 4 ) | settings.operators[ i ].mult;
tl[ i ] = settings.operators[ i ].tl;
ar_rs[ i ] = ( settings.operators[ i ].rs << 6 ) | settings.operators[ i ].ar;
dr_am[ i ] = ( settings.operators[ i ].am << 7 ) | settings.operators[ i ].dr;
sr[ i ] = settings.operators[ i ].d2r; // "Sometimes also called 'second decay rate' (D2R)."
rr_sl[ i ] = ( settings.operators[ i ].sl << 4 ) | settings.operators[ i ].rr;
if settings.operators[ i ].ssg_mode > 0 {
print_warning( &format!( "SSG-EG mode set on instrument {}, operator {}. this operator may not work on clone hardware or certain emulators", instrument.name, i ) );
}
ssg_eg[ i ] = settings.operators[ i ].ssg_mode;
}
let mut eif: Vec<u8> = Vec::new();
eif.push( alg_fb );
eif.extend( mult_dt );
eif.extend( tl );
eif.extend( ar_rs );
eif.extend( dr_am );
eif.extend( sr );
eif.extend( rr_sl );
eif.extend( ssg_eg );
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.
// 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<u8>'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<u8> = settings.volume.envelope.iter().map( | long | *long as u8 ).collect();
let mut arpeggio_envelope: Vec<i8> = 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<u8> = 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<i8> = 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<u8> = Vec::new();
let mut arpeggio_repeat: Vec<i8> = Vec::new();
let mut initial_volume_repeat: Vec<u8> = Vec::new();
let mut initial_arpeggio_repeat: Vec<i8> = 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.extend( &initial_volume_repeat );
arpeggio_repeat.extend( &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<u8> = Vec::new();
let mut arpeggio_repeat_period: Vec<i8> = 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 initial_volume_repeat == volume_repeat_period && 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<u8> = vec![ 0x00 ];
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 );
files.insert( format!( "{}.eef", if instrument.name.is_empty() { Uuid::new_v4().to_string() } else { instrument.name.to_owned() } ), envelope );
}
}
}
for sample in &self.samples {
instrument_filenames.push( format!( "{}.ewf", sample.name ) );
// Amplify data in original sample
let data: Vec<i16> = sample.data
.iter()
.map( | pcm_sample | {
let big_pcm_sample: i32 = min( ( *pcm_sample as i32 ) + sample.amp as i32, i16::MAX as i32 );
big_pcm_sample as i16
} )
.collect();
// Convert to f32 for various library operations
let data: Vec<f32> = data
.into_iter()
.map( | pcm_sample | {
if let SampleFormat::Bits8 = sample.bitrate {
( pcm_sample as u8 ) as f32 / 255.0
} else {
pcm_sample as f32 / 32767.0
}
} )
.collect();
// TODO: Adjust pitch using crate pitch_shift
if sample.pitch > 0 {
print_warning( "pitch shift not yet implemented for pcm samples. your drums/voice samples/etc may sound a bit funny" );
}
// Use libsamplerate to resample from whatever it is now to 10650Hz, the sample rate
// required by Echo sound engine EWF samples.
let data: Vec<f32> = convert( sample.rate, ECHO_EWF_SAMPLE_RATE, 1, samplerate::ConverterType::SincBestQuality, &data )?;
// Convert from f32 back to i16, then downconvert to u8
let mut data: Vec<u8> = data
.into_iter()
.map( | pcm_sample | {
let pcm_sample = ( ( pcm_sample + 1.0 ) * 128.0 ) as u8;
// Do not end stream prematurely (EWF format uses 0xFF to end stream)
if pcm_sample == 0xFF {
0xFE
} else {
pcm_sample
}
} )
.collect();
// Terminate stream
data.push( 0xFF );
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", soundtrack_label );
echo_asm += &format!( "\tincbin '{}'\n\n", soundtrack_path );
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", artifact_path, filename );
}
files.insert( format!( "music.asm" ), echo_asm.into_bytes() );
Ok( files )
}
fn get_esf( &self ) -> Result<Vec<u8>, Box<dyn Error>> {
let mut esf: Vec<u8> = Vec::new();
// Write deflemask rows one at a time.
// Final pass will combine delays into single delay events
// to save space on the cartridge.
// ESF ticks are not the same as Deflemask ticks!
// A series of events fit into a single ESF tick, which ends
// upon the next delay event or stop event.
// Use instrument changes wisely, as these are expensive register writes.
// PCM playback stalls the stream pipeline as well so use wisely.
// FM1 -> SN4 channel states (used to compute effects and to aid in not duplicating instrument sets)
// Since trackers like to set the same instrument each time a note on is played...
let mut channels: [Channel; 10] = [
Channel::new( ESF_FM_1 ), Channel::new( ESF_FM_2 ), Channel::new( ESF_FM_3 ), Channel::new( ESF_FM_4 ), Channel::new( ESF_FM_5 ), Channel::new( ESF_FM_6 ),
Channel::new( ESF_PSG_1 ), Channel::new( ESF_PSG_2 ), Channel::new( ESF_PSG_3 ), Channel::new( ESF_PSG_4 )
];
// Iterate for each row, for each channel
// Recall items are stored as self.channel_patterns[ channel ][ row_number ]
let mut all_events: Vec<EchoEvent> = Vec::new();
for row_number in 0..self.rows_per_pattern {
let events_this_row: Vec<EchoEvent> = get_events_for_row(
&mut channels,
{
let mut columns: Vec<&PatternRow> = Vec::new();
for channel in 0..10 {
columns.push( &self.channel_patterns[ channel ][ row_number as usize ] );
}
columns
},
if row_number % 2 == 0 {
self.speed_a * self.time_base
} else {
self.speed_b * self.time_base
},
self.instruments.len() as u8
)?;
// Transfer ESF events to the main stream
all_events.extend( events_this_row );
}
// Compact sequences of delays to save rom space
let all_events = compact_delays( all_events )?;
for event in all_events {
esf.extend( event );
}
esf.push( 0xFF ); // Terminate the stream
Ok( esf )
}
}
fn get_eef_volume( dmf_volume: u8 ) -> Result<u8, Box<dyn Error>> {
Ok( min( 0x0F - dmf_volume, 0x0F ) )
}
fn get_eef_shift( dmf_shift: i8 ) -> Result<u8, Box<dyn Error>> {
// 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" )?
}
)
}