From d7735901e28694f30548d8417fcaf3f17b522b8b Mon Sep 17 00:00:00 2001 From: ashley Date: Sat, 26 Aug 2023 14:52:36 -0400 Subject: [PATCH] Refactor into concept of 'actionable effects' --- src/reskit/soundtrack/engines/echo.rs | 200 +++++++++++++++----------- 1 file changed, 120 insertions(+), 80 deletions(-) diff --git a/src/reskit/soundtrack/engines/echo.rs b/src/reskit/soundtrack/engines/echo.rs index b566042..1dcb758 100644 --- a/src/reskit/soundtrack/engines/echo.rs +++ b/src/reskit/soundtrack/engines/echo.rs @@ -166,66 +166,6 @@ fn get_delay( delay: u8 ) -> Result> { Ok( events ) } -/** - * 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> { - Ok( - if channel.id == ESF_FM_6_PCM { - // Generate no events if channel is set to pcm - print_warning( "attempted to portamento up on FM6 when it is set to pcm. this makes no sense and does nothing" ); - vec![] - } else if let Some( octave_frequency ) = channel.active_note { - let ( octave, frequency ) = { - let by_amount: i16 = match portamento_effect { - Effect::PortamentoUp { speed } => *speed as i16, - Effect::PortamentoDown { speed } => -( *speed as i16 ), - _ => return Err( "internal error: provided effect is not a supported portamento effect" )? - }; - - let new_frequency: i16 = octave_frequency.frequency as i16 + by_amount; - if new_frequency > get_semitone_frequency( ESF_SEMITONE_B )? as i16 { - if octave_frequency.octave == 7 { - // Nowhere else to go up - ( 7, get_semitone_frequency( ESF_SEMITONE_B )? ) - } else { - // Go up an octave then add the difference to middle C - let difference = new_frequency - get_semitone_frequency( ESF_SEMITONE_B )? as i16; - ( octave_frequency.octave + 1, get_semitone_frequency( ESF_SEMITONE_C )? + difference as u16 ) - } - } else if new_frequency < get_semitone_frequency( ESF_SEMITONE_C )? as i16 { - if octave_frequency.octave == 0 { - // Nowhere else to go down - ( 0, get_semitone_frequency( ESF_SEMITONE_C )? ) - } else { - // Go down an octave then subtract the overshoot of C from B - let difference = get_semitone_frequency( ESF_SEMITONE_C )? as i16 - new_frequency; - ( octave_frequency.octave - 1, get_semitone_frequency( ESF_SEMITONE_B )? - difference as u16 ) - } - } else { - // Move within the same octave - ( octave_frequency.octave, new_frequency as u16 ) - } - }; - - // Set the new OctaveFrequency on the channel - channel.active_note = Some( OctaveFrequency { octave, frequency } ); - - // Generate the note slide ESF events - // YM2612 register format for frequency: https://plutiedev.com/ym2612-registers#reg-A0 - vec![ - ESF_SET_FREQUENCY | channel.id, - octave << 3 | ( ( ( 0x0700 & frequency ) >> 8 ) as u8 ), - ( 0x00FF & frequency ) as u8 - ] - } else { - // No active note, nothing to generate - vec![] - } - ) -} - /** * 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. @@ -442,34 +382,134 @@ pub fn compact_delays( events: Vec ) -> Result, Box Result> { + Ok( + if channel.id == ESF_FM_6_PCM { + // 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" ); + vec![] + } else if let Some( octave_frequency ) = channel.active_note { + let ( octave, frequency ) = { + let by_amount: i16 = match portamento_effect { + Effect::PortamentoUp { speed } => *speed as i16, + Effect::PortamentoDown { speed } => -( *speed as i16 ), + _ => return Err( "internal error: provided effect is not a supported portamento effect" )? + }; + + let new_frequency: i16 = octave_frequency.frequency as i16 + by_amount; + if new_frequency > get_semitone_frequency( ESF_SEMITONE_B )? as i16 { + if octave_frequency.octave == 7 { + // Nowhere else to go up + ( 7, get_semitone_frequency( ESF_SEMITONE_B )? ) + } else { + // Go up an octave then add the difference to middle C + let difference = new_frequency - get_semitone_frequency( ESF_SEMITONE_B )? as i16; + ( octave_frequency.octave + 1, get_semitone_frequency( ESF_SEMITONE_C )? + difference as u16 ) + } + } else if new_frequency < get_semitone_frequency( ESF_SEMITONE_C )? as i16 { + if octave_frequency.octave == 0 { + // Nowhere else to go down + ( 0, get_semitone_frequency( ESF_SEMITONE_C )? ) + } else { + // Go down an octave then subtract the overshoot of C from B + let difference = get_semitone_frequency( ESF_SEMITONE_C )? as i16 - new_frequency; + ( octave_frequency.octave - 1, get_semitone_frequency( ESF_SEMITONE_B )? - difference as u16 ) + } + } else { + // Move within the same octave + ( octave_frequency.octave, new_frequency as u16 ) + } + }; + + // Set the new OctaveFrequency on the channel + channel.active_note = Some( OctaveFrequency { octave, frequency } ); + + // Generate the note slide ESF events + // YM2612 register format for frequency: https://plutiedev.com/ym2612-registers#reg-A0 + vec![ + ESF_SET_FREQUENCY | channel.id, + octave << 3 | ( ( ( 0x0700 & frequency ) >> 8 ) as u8 ), + ( 0x00FF & frequency ) as u8 + ] + } else { + // No active note, nothing to generate + vec![] + } + ) +} + +/** + * 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> { + 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" )? + }; + + if tick >= after_ticks { + channel.active_note = None; + return Ok( vec![ ESF_NOTE_OFF | channel.id ] ); + } + + // Nothing to generate this tick + Ok( vec![] ) +} + +/** + * Get effects that require a specific action when computing the event flushes to Echo. + */ +fn get_actionable_effects( channels: &mut [Channel] ) -> Result, Box> { + // 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(); + for channel_id in 0..channels.len() { + let channel = &channels[ channel_id ]; + + for effect in &channel.active_effects { + let is_actionable = match effect { + Effect::PortamentoUp { speed: _ } | Effect::PortamentoDown { speed: _ } => true, + Effect::NoteCut { after_ticks: _ } => true, + _ => false // effect is not an actionable effect + }; + + if is_actionable { + actionable_effects.push( ( channel_id, *effect ) ); + } + } + } + + Ok( actionable_effects ) +} + /** * Calculate delays generated before and after effects applied across all channels. If there are no effects for this row, * an empty vec will be returned. This means you should just apply ticks_to_wait as the delay for that row. If there * -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_delays_for_effects( channels: &mut [Channel], ticks_to_wait: u8 ) -> Result, Box> { +fn get_actionable_effect_sequence( channels: &mut [Channel], ticks_to_wait: u8 ) -> Result, Box> { let mut events: Vec = Vec::new(); - // 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 active_portamentos: Vec<(usize, Effect)> = Vec::new(); - for channel_id in 0..channels.len() { - let channel = &channels[ channel_id ]; + let actionable_effects = get_actionable_effects( channels )?; + if !actionable_effects.is_empty() { + for tick in 0..ticks_to_wait { + for ( channel_id, actionable_effect ) in &actionable_effects { + let event_for_tick = match actionable_effect { + Effect::PortamentoUp { speed: _ } | Effect::PortamentoDown { speed: _ } => Some( get_portamento( &mut channels[ *channel_id ], &actionable_effect )? ), + Effect::NoteCut { after_ticks: _ } => Some( get_note_cut( &mut channels[ *channel_id ], &actionable_effect, tick )? ), + _ => None + }; - for effect in &channel.active_effects { - if matches!( effect, Effect::PortamentoDown { speed: _ } ) || - matches!( effect, Effect::PortamentoUp { speed: _ } ) { - active_portamentos.push( ( channel_id, *effect ) ); - } - } - } - - if !active_portamentos.is_empty() { - for _tick in 0..ticks_to_wait { - for ( channel_id, portamento ) in &active_portamentos { - let portamento_event: EchoEvent = get_portamento( &mut channels[ *channel_id ], &portamento )?; - events.push( portamento_event ); + if let Some( event_for_tick ) = event_for_tick { + if !event_for_tick.is_empty() { + events.push( event_for_tick ); + } + } } events.push( get_delay( 1 )? ); @@ -485,7 +525,7 @@ fn get_delays_for_effects( channels: &mut [Channel], ticks_to_wait: u8 ) -> Resu fn get_delays( events: Vec, channels: &mut [Channel], ticks_to_wait: u8 ) -> Result, Box> { let mut events: Vec = events; - let applied_effects = get_delays_for_effects( channels, ticks_to_wait )?; + let applied_effects = get_actionable_effect_sequence( channels, ticks_to_wait )?; if applied_effects.is_empty() { // Push the amount of ticks to wait for this row. ticks_to_wait is speed_a or speed_b, times base_speed. events.push( get_delay( ticks_to_wait )? );