Echo ESF events generation

master
Ashley N. 2023-08-22 01:40:06 -04:00
parent 8326f10617
commit 8e6e0d9fb8
3 changed files with 207 additions and 33 deletions

View File

@ -1,4 +1,52 @@
use std::{collections::HashMap, error::Error}; use std::{collections::HashMap, error::Error};
use crate::reskit::soundtrack::{types::{PatternRow, Note}, formats::dmf::ChannelState};
// https://github.com/sikthehedgehog/Echo/blob/master/doc/esf.txt
const ESF_NOTE_ON: u8 = 0x00;
const ESF_NOTE_OFF: u8 = 0x10;
const ESF_SET_VOLUME: u8 = 0x20;
const ESF_SET_FREQUENCY: u8 = 0x30;
const ESF_SET_INSTRUMENT: u8 = 0x40;
const ESF_DELAY_SHORT: u8 = 0xD0;
const ESF_SFX_LOCK: u8 = 0xE0;
const ESF_SET_FM_PARAMS: u8 = 0xF0;
const ESF_SET_FM_DIRECT_0: u8 = 0xF8;
const ESF_SET_FM_DIRECT_1: u8 = 0xF9;
const ESF_SET_FLAGS: u8 = 0xFA;
const ESF_CLEAR_FLAGS: u8 = 0xFB;
const ESF_GOTO: u8 = 0xFC;
const ESF_LOOP_SET: u8 = 0xFD;
const ESF_DELAY_LONG: u8 = 0xFE;
const ESF_STOP: u8 = 0xFF;
pub const ESF_FM_1: u8 = 0x00;
pub const ESF_FM_2: u8 = 0x01;
pub const ESF_FM_3: u8 = 0x02;
pub const ESF_FM_4: u8 = 0x04;
pub const ESF_FM_5: u8 = 0x05;
pub const ESF_FM_6: u8 = 0x06;
pub const ESF_PSG_1: u8 = 0x08;
pub const ESF_PSG_2: u8 = 0x09;
pub const ESF_PSG_3: u8 = 0x0A;
pub const ESF_PSG_4: u8 = 0x0B;
pub const ESF_FM_6_PCM: u8 = 0x0C;
const ESF_SEMITONE_C: u8 = 0;
const ESF_SEMITONE_C_SHARP: u8 = 1;
const ESF_SEMITONE_D: u8 = 2;
const ESF_SEMITONE_D_SHARP: u8 = 3;
const ESF_SEMITONE_E: u8 = 4;
const ESF_SEMITONE_F: u8 = 5;
const ESF_SEMITONE_F_SHARP: u8 = 6;
const ESF_SEMITONE_G: u8 = 7;
const ESF_SEMITONE_G_SHARP: u8 = 8;
const ESF_SEMITONE_A: u8 = 9;
const ESF_SEMITONE_A_SHARP: u8 = 10;
const ESF_SEMITONE_B: u8 = 11;
pub type EchoEvent = Vec<u8>;
pub trait EchoFormat { pub trait EchoFormat {
@ -8,4 +56,74 @@ pub trait EchoFormat {
fn get_ewfs( &self ) -> Result<HashMap<String, Vec<u8>>, Box<dyn Error>>; fn get_ewfs( &self ) -> Result<HashMap<String, Vec<u8>>, Box<dyn Error>>;
fn get_esf( &self ) -> Result<Vec<u8>, Box<dyn Error>>;
}
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,
Note::CSharp(_) => ESF_SEMITONE_C_SHARP,
Note::D(_) => ESF_SEMITONE_D,
Note::DSharp(_) => ESF_SEMITONE_D_SHARP,
Note::E(_) => ESF_SEMITONE_E,
Note::F(_) => ESF_SEMITONE_F,
Note::FSharp(_) => ESF_SEMITONE_F_SHARP,
Note::G(_) => ESF_SEMITONE_G,
Note::GSharp(_) => ESF_SEMITONE_G_SHARP,
Note::A(_) => ESF_SEMITONE_A,
Note::ASharp(_) => ESF_SEMITONE_A_SHARP,
Note::B(_) => ESF_SEMITONE_B
} )
}
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 {
24 * octave + 2 * semitone
}
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 )? ),
_ => return Err( "internal error: invalid channel" )?
} )
}
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,
_ => return Err( "internal error: invalid channel" )?
} )
}
pub fn get_events( row: &PatternRow, channel: u8, channel_state: &mut ChannelState ) -> Result<Vec<EchoEvent>, Box<dyn Error>> {
let mut events: Vec<EchoEvent> = Vec::new();
if let Some( note ) = &row.note {
if let Note::NoteOff = note {
events.push( vec![ ESF_NOTE_OFF | channel ] );
} else {
events.push( vec![ ESF_NOTE_ON | channel, get_note( note, channel )? ] );
}
}
if let Some( volume ) = &row.volume {
// https://github.com/sikthehedgehog/Echo/blob/master/doc/esf.txt#L206-L207
events.push( vec![ ESF_SET_VOLUME | channel, get_volume( channel, *volume )? ] );
}
if let Some( instrument ) = &row.instrument_index {
// Do not set the same instrument if it was already set before
if &row.instrument_index != &channel_state.current_instrument {
events.push( vec![ ESF_SET_INSTRUMENT | channel, *instrument as u8 ] );
channel_state.current_instrument = Some( *instrument );
}
}
Ok( events )
} }

View File

@ -2,7 +2,7 @@ use std::{error::Error, fs::File, io::Read, convert::TryInto, collections::HashM
use flate2::read::ZlibDecoder; use flate2::read::ZlibDecoder;
use samplerate::convert; use samplerate::convert;
use uuid::Uuid; use uuid::Uuid;
use crate::reskit::{utility::{get_string, get_u8, skip, get_u32, get_i8, get_i32, get_u16, get_i16, Ring, print_warning}, soundtrack::{types::{SampleFormat, PsgEnvelope, Note, Sample, PsgSettings}, engines::echo::EchoFormat}}; use crate::reskit::{utility::{get_string, get_u8, skip, get_u32, get_i8, get_i32, get_u16, get_i16, Ring, print_warning}, soundtrack::{types::{SampleFormat, PsgEnvelope, Note, Sample, PsgSettings, PatternRow, Effect}, engines::echo::{EchoFormat, EchoEvent}}};
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;
@ -54,21 +54,6 @@ pub struct Instrument {
instrument_type: InstrumentType instrument_type: InstrumentType
} }
#[derive(Debug)]
pub struct Effect {
effect_code: i16,
effect_value: Option<i16>
}
#[derive(Debug)]
pub struct PatternRow {
note: Option<Note>,
volume: Option<i16>,
effects: Vec<Effect>,
instrument_index: Option<u16>
}
pub struct DmfModule { pub struct DmfModule {
platform: u8, platform: u8,
version: u8, version: u8,
@ -83,6 +68,12 @@ pub struct DmfModule {
samples: Vec<Sample> samples: Vec<Sample>
} }
#[derive(Default, Debug)]
pub struct ChannelState {
pub current_instrument: Option<u16>,
pub current_volume: Option<u8>
}
impl DmfModule { impl DmfModule {
pub fn from_file( path: &str ) -> Result<DmfModule, Box<dyn Error>> { pub fn from_file( path: &str ) -> Result<DmfModule, Box<dyn Error>> {
@ -110,7 +101,7 @@ impl DmfModule {
let platform = get_u8( bytes.by_ref() )?; let platform = get_u8( bytes.by_ref() )?;
match platform { match platform {
DMF_MD => println!( "Mode:\tSega Megadrive" ), DMF_MD => println!( "Mode:\tSega Megadrive" ),
DMF_MD_ENHANCED_CH3 => println!( "Mode:\t\tSega Megadrive (Ext. Ch. 3 Mode)" ), DMF_MD_ENHANCED_CH3 => return Err( "Extended Ch. 3 mode not yet supported (coming soon!) - if your sequence does not use Extended Ch. 3 mode, try re-exporting as standard mode." )?,
_ => return Err( "invalid file: invalid console format" )? _ => return Err( "invalid file: invalid console format" )?
}; };
@ -498,7 +489,7 @@ impl DmfModule {
for _ in 0..patterns_count { for _ in 0..patterns_count {
for row_id in 0..rows_per_pattern { for row_id in 0..rows_per_pattern {
let note = get_u16( bytes.by_ref() )?; let note = get_u16( bytes.by_ref() )?;
let octave = get_u16( bytes.by_ref() )?; let octave = get_u16( bytes.by_ref() )? as u8;
let note: Option<Note> = if note == 0 && octave == 0 { let note: Option<Note> = if note == 0 && octave == 0 {
None None
@ -524,10 +515,10 @@ impl DmfModule {
}; };
let volume = get_i16( bytes.by_ref() )?; let volume = get_i16( bytes.by_ref() )?;
let volume: Option<i16> = if volume == -1 { let volume: Option<u8> = if volume == -1 {
None None
} else { } else {
Some( volume ) Some( volume as u8 )
}; };
let mut effects: Vec<Effect> = Vec::new(); let mut effects: Vec<Effect> = Vec::new();
@ -866,6 +857,40 @@ impl EchoFormat for DmfModule {
Ok( ewfs ) Ok( ewfs )
} }
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 channel_settings: [ChannelState; 10] = [
ChannelState::default(), ChannelState::default(), ChannelState::default(), ChannelState::default(), ChannelState::default(), ChannelState::default(),
ChannelState::default(), ChannelState::default(), ChannelState::default(), ChannelState::default()
];
// Iterate for each row, for each channel
// Recall items are stored as self.channel_patterns[ channel ][ row_number ]
let mut delay: usize = 0;
for row_number in 0..self.rows_per_pattern {
let mut events_this_row: Vec<EchoEvent> = Vec::new();
for channel in 0..self.channel_patterns.len() {
// TODO !!
}
}
Ok( esf )
}
} }
fn get_eef_volume( dmf_volume: u8 ) -> Result<u8, Box<dyn Error>> { fn get_eef_volume( dmf_volume: u8 ) -> Result<u8, Box<dyn Error>> {

View File

@ -1,4 +1,4 @@
use std::collections::HashMap; use std::{collections::HashMap, error::Error};
#[derive(Debug)] #[derive(Debug)]
pub struct PsgEnvelope< DataFormat > { pub struct PsgEnvelope< DataFormat > {
@ -34,23 +34,54 @@ pub struct Sample {
#[derive(Debug)] #[derive(Debug)]
pub enum Note { pub enum Note {
NoteOff, NoteOff,
CSharp( u16 ), CSharp( u8 ),
D( u16 ), D( u8 ),
DSharp( u16 ), DSharp( u8 ),
E( u16 ), E( u8 ),
F( u16 ), F( u8 ),
FSharp( u16 ), FSharp( u8 ),
G( u16 ), G( u8 ),
GSharp( u16 ), GSharp( u8 ),
A( u16 ), A( u8 ),
ASharp( u16 ), ASharp( u8 ),
B( u16 ), B( u8 ),
C( u16 ) C( u8 )
} }
#[derive(Debug)]
pub struct Effect {
pub effect_code: i16,
pub effect_value: Option<i16>
}
#[derive(Debug)]
pub struct PatternRow {
pub note: Option<Note>,
pub volume: Option<u8>,
pub effects: Vec<Effect>,
pub instrument_index: Option<u16>
}
impl Note { impl Note {
pub fn get_octave( &self ) -> Result<u8, Box<dyn Error>> {
Ok( match self {
Note::NoteOff => return Err( "internal error: attempted to get octave for Note::NoteOff" )?,
Note::CSharp( octave ) => *octave,
Note::D( octave ) => *octave,
Note::DSharp( octave ) => *octave,
Note::E( octave ) => *octave,
Note::F( octave ) => *octave,
Note::FSharp( octave ) => *octave,
Note::G( octave ) => *octave,
Note::GSharp( octave ) => *octave,
Note::A( octave ) => *octave,
Note::ASharp( octave ) => *octave,
Note::B( octave ) => *octave,
Note::C( octave ) => *octave
} )
}
pub fn get_freq( &self ) -> Option<f32> { pub fn get_freq( &self ) -> Option<f32> {
match self { match self {
Note::NoteOff => None, Note::NoteOff => None,