Refactor into separate modules
parent
8459028858
commit
a1eb158292
|
@ -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;
|
||||||
|
|
|
@ -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" )?
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
|
@ -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 )?;
|
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod dmf;
|
||||||
|
pub mod engine;
|
|
@ -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" )?
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
Loading…
Reference in New Issue