Refactor into separate modules
parent
8459028858
commit
a1eb158292
|
@ -8,7 +8,7 @@ use std::error::Error;
|
|||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
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::utility;
|
||||
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 linked_hash_set::LinkedHashSet;
|
||||
|
||||
use crate::reskit::{soundtrack::{types::{PatternRow, Note, Channel, OctaveFrequency, Effect}, formats::dmf::DmfModule}, utility::print_warning};
|
||||
use crate::reskit::{soundtrack::types::{PatternRow, Note, Channel, OctaveFrequency, Effect}, utility::print_warning};
|
||||
|
||||
// https://github.com/sikthehedgehog/Echo/blob/master/doc/esf.txt
|
||||
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 {
|
||||
Note::NoteOff => return Err( "internal error: attempted to get semitone for a Note::NoteOff" )?,
|
||||
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.
|
||||
*/
|
||||
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 {
|
||||
Note::NoteOff => return Err( "internal error: attempted to get pcm index for a Note::NoteOff" )?,
|
||||
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# - 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 {
|
||||
ESF_SEMITONE_C => 644,
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
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 )? ),
|
||||
|
@ -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 {
|
||||
ESF_FM_1..=ESF_FM_3 | ESF_FM_4..=ESF_FM_6 => 0x7F - 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.
|
||||
*/
|
||||
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 {
|
||||
// No delay to generate! Probably should be an error
|
||||
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.
|
||||
* 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();
|
||||
|
||||
// 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
|
||||
* 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();
|
||||
|
||||
for effect in effects {
|
||||
|
@ -332,7 +331,7 @@ fn apply_effects_to_channel( channel: &mut Channel, effects: &LinkedHashSet<Effe
|
|||
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 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
|
||||
* 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(
|
||||
if channel.id == ESF_FM_6_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
|
||||
*/
|
||||
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 {
|
||||
Effect::NoteCut { after_ticks } => *after_ticks,
|
||||
_ => 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.
|
||||
*/
|
||||
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
|
||||
// channels for this row, then flush them once per `ticks_to_wait` for this row.
|
||||
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
|
||||
* 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 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.
|
||||
*/
|
||||
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 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 linked_hash_set::LinkedHashSet;
|
||||
use samplerate::convert;
|
||||
use uuid::Uuid;
|
||||
use convert_case::{Case, Casing};
|
||||
use crate::reskit::{utility::{get_string, get_u8, skip, get_u32, get_i8, get_i32, get_u16, get_i16, Ring, print_warning, print_info}, soundtrack::{types::{SampleFormat, PsgEnvelope, Note, Sample, PsgSettings, PatternRow, Effect, DcsgChannelMode, NoiseType, Channel, Instrument, InstrumentType, Fm2612Operator, Fm2612Settings}, engines::echo::{EchoFormat, EchoEvent, ESF_FM_1, ESF_FM_2, ESF_FM_3, ESF_FM_4, ESF_FM_5, ESF_PSG_1, ESF_FM_6, ESF_PSG_2, ESF_PSG_3, ESF_PSG_4, get_events_for_row, compact_delays}}};
|
||||
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}};
|
||||
|
||||
const DMF_MAGIC_NUMBER: &'static str = ".DelekDefleMask.";
|
||||
const DMF_SUPPORTED_VERSION: u8 = 27;
|
||||
const DMF_MD: u8 = 0x02;
|
||||
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_PORTAMENTO_UP: u8 = 0x01;
|
||||
|
@ -34,15 +31,15 @@ pub struct DmfModule {
|
|||
path: String,
|
||||
platform: u8,
|
||||
version: u8,
|
||||
time_base: u8,
|
||||
speed_a: u8,
|
||||
speed_b: u8,
|
||||
pub time_base: u8,
|
||||
pub speed_a: u8,
|
||||
pub speed_b: u8,
|
||||
frame_mode: FrameMode,
|
||||
rows_per_pattern: u32,
|
||||
pub rows_per_pattern: u32,
|
||||
pattern_matrix: Vec<Vec<u16>>,
|
||||
channel_patterns: Vec<Vec<PatternRow>>,
|
||||
instruments: Vec<Instrument>,
|
||||
samples: Vec<Sample>
|
||||
pub channel_patterns: Vec<Vec<PatternRow>>,
|
||||
pub instruments: Vec<Instrument>,
|
||||
pub samples: Vec<Sample>
|
||||
}
|
||||
|
||||
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