diff --git a/src/reskit/soundtrack/engines/echo/dmf.rs b/src/reskit/soundtrack/engines/echo/dmf.rs index 3e85991..5b44830 100644 --- a/src/reskit/soundtrack/engines/echo/dmf.rs +++ b/src/reskit/soundtrack/engines/echo/dmf.rs @@ -2,7 +2,7 @@ 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, print_info}}; +use crate::reskit::{soundtrack::{formats::dmf::{DmfModule, ECHO_EWF_SAMPLE_RATE}, types::{InstrumentType, SampleFormat, Channel, PatternRow, Effect, DcsgChannelMode, NoiseType}}, utility::{print_warning, Ring, print_info}}; 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}; diff --git a/src/reskit/soundtrack/engines/echo/engine.rs b/src/reskit/soundtrack/engines/echo/engine.rs index 7d24099..7217847 100644 --- a/src/reskit/soundtrack/engines/echo/engine.rs +++ b/src/reskit/soundtrack/engines/echo/engine.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, error::Error, cmp::{min, max}}; use linked_hash_set::LinkedHashSet; -use crate::reskit::{soundtrack::types::{PatternRow, Note, Channel, OctaveFrequency, Effect, Instrument}, utility::print_warning}; +use crate::reskit::{soundtrack::types::{PatternRow, Note, Channel, OctaveFrequency, Effect, Instrument, NoiseType, DcsgChannelMode}, utility::print_warning}; // https://github.com/sikthehedgehog/Echo/blob/master/doc/esf.txt const ESF_NOTE_ON: u8 = 0x00; @@ -32,6 +32,15 @@ pub const ESF_PSG_2: u8 = 0x09; pub const ESF_PSG_3: u8 = 0x0A; pub const ESF_PSG_4: u8 = 0x0B; +pub const ESF_NOISE_PERIODIC_HIGH: u8 = 0x00; +pub const ESF_NOISE_PERIODIC_MED: u8 = 0x01; +pub const ESF_NOISE_PERIODIC_LOW: u8 = 0x02; +pub const ESF_NOISE_PERIODIC_PSG_3: u8 = 0x03; +pub const ESF_NOISE_WHITE_HIGH: u8 = 0x04; +pub const ESF_NOISE_WHITE_MED: u8 = 0x05; +pub const ESF_NOISE_WHITE_LOW: u8 = 0x06; +pub const ESF_NOISE_WHITE_PSG_3: u8 = 0x07; + pub const ESF_FM_6_PCM: u8 = 0x0C; const ESF_FM_PARAM_LEFT_SPEAKER: u8 = 0x80; @@ -180,6 +189,45 @@ pub fn get_psg_octave_from_frequency( frequency: i16 ) -> u8 { } } +/** + * Get the setting for the noise generator based on the active noise mode effect. + */ +pub fn get_noise_generator_setting( note: &Note, noise_mode: Option<&Effect> ) -> Result> { + Ok( + if let Some( noise_mode ) = noise_mode { + if let Effect::DcsgNoiseMode { mode, noise_type } = noise_mode { + match noise_type { + NoiseType::PeriodicNoise => match mode { + DcsgChannelMode::Ch3Frequency => ESF_NOISE_PERIODIC_PSG_3, + DcsgChannelMode::FixedFrequency => match note { + Note::NoteOff => return Err( "internal error: attempted to get noise generator setting for NoteOff" )?, + Note::C(_) => ESF_NOISE_PERIODIC_LOW, + Note::CSharp(_) => ESF_NOISE_PERIODIC_MED, + Note::D(_) | _ => ESF_NOISE_PERIODIC_HIGH + } + }, + NoiseType::WhiteNoise => match mode { + DcsgChannelMode::Ch3Frequency => ESF_NOISE_WHITE_PSG_3, + DcsgChannelMode::FixedFrequency => match note { + Note::NoteOff => return Err( "internal error: attempted to get noise generator setting for NoteOff" )?, + Note::C(_) => ESF_NOISE_WHITE_LOW, + Note::CSharp(_) => ESF_NOISE_WHITE_MED, + Note::D(_) | _ => ESF_NOISE_WHITE_HIGH + } + } + } + } else { + return Err( "internal error: non-DcsgNoiseMode effect passed to get_noise_generator_setting" )? + } + } else { + match note { + Note::NoteOff => return Err( "internal error: attempted to get noise generator setting for NoteOff" )?, + _ => ESF_NOISE_WHITE_PSG_3 + } + } + ) +} + pub fn get_fm_note_byte( octave: u8, semitone: u8 ) -> u8 { 32 * octave + 2 * semitone + 1 } @@ -243,9 +291,9 @@ pub fn get_events_for_channel( channel: &mut Channel, row: &PatternRow, pcm_offs channel.active_instrument = Some( *instrument ); } } else { - // This is SN1, SN2, or SN3 with no active instrument + // This is SN1, SN2, SN3, or SN4 with no active instrument // Check if the channel needs "__reskit_default_psg_instrument" set before a note is set - if channel.active_instrument.is_none() && ( ESF_PSG_1..=ESF_PSG_3 ).contains( &channel.id ) { + if channel.active_instrument.is_none() && ( ESF_PSG_1..=ESF_PSG_4 ).contains( &channel.id ) { if row.note.is_some() { // Set "__reskit_default_psg_instrument" let instrument = default_psg_instr_index.ok_or( "internal error: no default psg instrument to apply" )?; @@ -267,6 +315,11 @@ pub fn get_events_for_channel( channel: &mut Channel, row: &PatternRow, pcm_offs octave: note.get_octave()?, frequency: if ( ESF_PSG_1..=ESF_PSG_3 ).contains( &channel.id ) { get_psg_semitone_frequency( note.get_octave()?, get_semitone( note )? )? + } else if channel.id == ESF_PSG_4 { + // probably doesn't matter a damn what we put here, the OctaveFrequency is used only for portamentos. + // and you can't portamento on PSG_4 (you can, however, portamento the frequency set on PSG_3, but + // we'll get to cross-channel generated events in a bit.) + 0 } else { get_fm_semitone_frequency( get_semitone( note )? )? } @@ -275,6 +328,10 @@ pub fn get_events_for_channel( channel: &mut Channel, row: &PatternRow, pcm_offs if channel.id == ESF_FM_6_PCM { events.push( vec![ ESF_NOTE_ON | channel.id, get_pcm_index( note )? + pcm_offset ] ); + } else if channel.id == ESF_PSG_4 { + // Find the single (and it should only ever be a single) Effect::DcsgNoiseMode attached to the channel + let dcsg_noise_mode = channel.active_effects.iter().find( | effect | matches!( effect, Effect::DcsgNoiseMode { mode: _, noise_type: _ } ) ); + events.push( vec![ ESF_NOTE_ON | channel.id, get_noise_generator_setting( note, dcsg_noise_mode )? ] ); } else { let note = get_note( note, channel.id )?; events.push( vec![ ESF_NOTE_ON | channel.id, note ] ); @@ -395,6 +452,22 @@ pub fn apply_effects_to_channel( channel: &mut Channel, effects: &LinkedHashSet< } } }, + Effect::DcsgNoiseMode { mode, noise_type } => { + let mode = *mode; + let noise_type = *noise_type; + + if channel.id == ESF_PSG_4 { + channel.active_effects = channel.active_effects + .clone() + .into_iter() + .filter( | effect | !( matches!( effect, Effect::DcsgNoiseMode { mode: _, noise_type: _ } ) ) ) + .collect(); + + channel.active_effects.insert( Effect::DcsgNoiseMode { mode, noise_type } ); + } else { + print_warning( "Effect:DcsgNoiseMode can only be applied to SN4. ignoring..." ); + } + }, unsupported_effect => print_warning( &format!( "effect unsupported: {:?}. your soundtrack may sound different than expected.", unsupported_effect ) ) } } @@ -465,9 +538,9 @@ pub fn compact_delays( events: Vec ) -> Result, Box Result> { Ok( - if channel.id == ESF_FM_6_PCM { + if channel.id == ESF_FM_6_PCM || channel.id == ESF_PSG_4 { // Generate no events if channel is set to pcm - print_warning( "attempted to portamento on FM6 when it is set to pcm. this makes no sense and does nothing" ); + print_warning( "attempted to portamento on FM6 when it is set to pcm, or on SN4. this makes no sense and does nothing" ); vec![] } else if let Some( octave_frequency ) = channel.active_note { let ( octave, frequency ) = {