From c7c703ef65fc13e8e3276df56a0949071fdfe349 Mon Sep 17 00:00:00 2001 From: ashley Date: Sun, 27 Aug 2023 16:56:58 -0400 Subject: [PATCH] Implement portamento up and down for psg channels --- src/reskit/soundtrack/engines/echo/engine.rs | 135 +++++++++++++++---- 1 file changed, 107 insertions(+), 28 deletions(-) diff --git a/src/reskit/soundtrack/engines/echo/engine.rs b/src/reskit/soundtrack/engines/echo/engine.rs index fd1148e..7d24099 100644 --- a/src/reskit/soundtrack/engines/echo/engine.rs +++ b/src/reskit/soundtrack/engines/echo/engine.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, error::Error}; +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}; @@ -107,7 +107,7 @@ pub fn get_pcm_index( note: &Note ) -> Result> { D - 722 | F# - 910 | A# - 1146 D# - 765 | G - 964 | B - 1214 */ -pub fn get_semitone_frequency( semitone: u8 ) -> Result> { +pub fn get_fm_semitone_frequency( semitone: u8 ) -> Result> { Ok( match semitone { ESF_SEMITONE_C => 644, ESF_SEMITONE_C_SHARP => 681, @@ -121,10 +121,65 @@ pub fn get_semitone_frequency( semitone: u8 ) -> Result> { ESF_SEMITONE_A => 1081, ESF_SEMITONE_A_SHARP => 1146, ESF_SEMITONE_B => 1214, - _ => return Err( "internal error: invalid semitone value provided to get_semitone_frequency" )? + _ => return Err( "internal error: invalid semitone value provided to get_fm_semitone_frequency" )? } ) } +/** + * Gets the semitone frequency used by Echo (PSG). + https://github.com/sikthehedgehog/Echo/blob/master/doc/esf.txt#L273 + |Oct.0|Oct.1|Oct.2|Oct.3|Oct.4|Oct.5 + ---|-----|-----|-----|-----|-----|----- + C | 851 | 425 | 212 | 106 | 53 | 26 + C# | 803 | 401 | 200 | 100 | 50 | 25 + D | 758 | 379 | 189 | 94 | 47 | 23 + D# | 715 | 357 | 178 | 89 | 44 | 22 + E | 675 | 337 | 168 | 84 | 42 | 21 + F | 637 | 318 | 159 | 79 | 39 | 19 + F# | 601 | 300 | 150 | 75 | 37 | 18 + G | 568 | 284 | 142 | 71 | 35 | 17 + G# | 536 | 268 | 134 | 67 | 33 | 16 + A | 506 | 253 | 126 | 63 | 31 | 15 + A# | 477 | 238 | 119 | 59 | 29 | 14 + B | 450 | 225 | 112 | 56 | 28 | 14 + */ +pub fn get_psg_semitone_frequency( octave: u8, semitone: u8 ) -> Result> { + Ok( match semitone { + ESF_SEMITONE_C => match octave { 0 => 851, 1 => 425, 2 => 212, 3 => 106, 4 => 53, 5 | _ => 26 }, + ESF_SEMITONE_C_SHARP => match octave { 0 => 803, 1 => 401, 2 => 200, 3 => 100, 4 => 50, 5 | _ => 25 }, + ESF_SEMITONE_D => match octave { 0 => 758, 1 => 379, 2 => 189, 3 => 94, 4 => 47, 5 | _ => 23 }, + ESF_SEMITONE_D_SHARP => match octave { 0 => 715, 1 => 357, 2 => 178, 3 => 89, 4 => 44, 5 | _ => 22 }, + ESF_SEMITONE_E => match octave { 0 => 675, 1 => 337, 2 => 168, 3 => 84, 4 => 42, 5 | _ => 21 }, + ESF_SEMITONE_F => match octave { 0 => 637, 1 => 318, 2 => 159, 3 => 79, 4 => 39, 5 | _ => 19 }, + ESF_SEMITONE_F_SHARP => match octave { 0 => 601, 1 => 300, 2 => 150, 3 => 75, 4 => 37, 5 | _ => 18 }, + ESF_SEMITONE_G => match octave { 0 => 568, 1 => 284, 2 => 142, 3 => 71, 4 => 35, 5 | _ => 17 }, + ESF_SEMITONE_G_SHARP => match octave { 0 => 536, 1 => 268, 2 => 134, 3 => 67, 4 => 33, 5 | _ => 16 }, + ESF_SEMITONE_A => match octave { 0 => 506, 1 => 253, 2 => 126, 3 => 63, 4 => 31, 5 | _ => 15 }, + ESF_SEMITONE_A_SHARP => match octave { 0 => 477, 1 => 238, 2 => 119, 3 => 59, 4 => 29, 5 | _ => 14 }, + ESF_SEMITONE_B => match octave { 0 => 450, 1 => 225, 2 => 112, 3 => 56, 4 => 28, 5 | _ => 14 }, + _ => return Err( "internal error: invalid semitone value provided to get_psg_semitone_frequency" )? + } ) +} + +/** + * Convenience method to get the octave of a given PSG frequency + */ +pub fn get_psg_octave_from_frequency( frequency: i16 ) -> u8 { + if frequency > 425 { + 0 + } else if frequency > 212 && frequency <= 425 { + 1 + } else if frequency > 106 && frequency <= 212 { + 2 + } else if frequency > 53 && frequency <= 106 { + 3 + } else if frequency > 26 && frequency <= 53 { + 4 + } else { + 5 + } +} + pub fn get_fm_note_byte( octave: u8, semitone: u8 ) -> u8 { 32 * octave + 2 * semitone + 1 } @@ -190,7 +245,7 @@ pub fn get_events_for_channel( channel: &mut Channel, row: &PatternRow, pcm_offs } else { // This is SN1, SN2, or SN3 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() && ( 6..=8 ).contains( &channel.id ) { + if channel.active_instrument.is_none() && ( ESF_PSG_1..=ESF_PSG_3 ).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" )?; @@ -210,7 +265,11 @@ pub fn get_events_for_channel( channel: &mut Channel, row: &PatternRow, pcm_offs channel.active_note = Some( OctaveFrequency { octave: note.get_octave()?, - frequency: get_semitone_frequency( get_semitone( note )? )? + frequency: if ( ESF_PSG_1..=ESF_PSG_3 ).contains( &channel.id ) { + get_psg_semitone_frequency( note.get_octave()?, get_semitone( note )? )? + } else { + get_fm_semitone_frequency( get_semitone( note )? )? + } } ); @@ -418,28 +477,40 @@ pub fn get_portamento( channel: &mut Channel, portamento_effect: &Effect ) -> Re _ => 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 ) - } + if ( ESF_PSG_1..=ESF_PSG_3 ).contains( &channel.id ) { + // Frequency note shifts are backward in PSG + let new_frequency: i16 = octave_frequency.frequency as i16 + ( by_amount * -1 ); + + // Clamp frequency + let new_frequency = max( min( new_frequency, 1024 ), 0 ); + + // Get psg octave from frequency (which will clamp octave to 0-5) and return the new frequency + ( get_psg_octave_from_frequency( new_frequency ), new_frequency as u16 ) } else { - // Move within the same octave - ( octave_frequency.octave, new_frequency as u16 ) + let new_frequency: i16 = octave_frequency.frequency as i16 + by_amount; + + if new_frequency > get_fm_semitone_frequency( ESF_SEMITONE_B )? as i16 { + if octave_frequency.octave == 7 { + // Nowhere else to go up + ( 7, get_fm_semitone_frequency( ESF_SEMITONE_B )? ) + } else { + // Go up an octave then add the difference to middle C + let difference = new_frequency - get_fm_semitone_frequency( ESF_SEMITONE_B )? as i16; + ( octave_frequency.octave + 1, get_fm_semitone_frequency( ESF_SEMITONE_C )? + difference as u16 ) + } + } else if new_frequency < get_fm_semitone_frequency( ESF_SEMITONE_C )? as i16 { + if octave_frequency.octave == 0 { + // Nowhere else to go down + ( 0, get_fm_semitone_frequency( ESF_SEMITONE_C )? ) + } else { + // Go down an octave then subtract the overshoot of C from B + let difference = get_fm_semitone_frequency( ESF_SEMITONE_C )? as i16 - new_frequency; + ( octave_frequency.octave - 1, get_fm_semitone_frequency( ESF_SEMITONE_B )? - difference as u16 ) + } + } else { + // Move within the same octave + ( octave_frequency.octave, new_frequency as u16 ) + } } }; @@ -450,8 +521,16 @@ pub fn get_portamento( channel: &mut Channel, portamento_effect: &Effect ) -> Re // 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 + if ( ESF_PSG_1..=ESF_PSG_3 ).contains( &channel.id ) { + ( 0x000F & frequency ) as u8 + } else { + octave << 3 | ( ( ( 0x0700 & frequency ) >> 8 ) as u8 ) + }, + if ( ESF_PSG_1..=ESF_PSG_3 ).contains( &channel.id ) { + ( ( 0x3F0 & frequency ) >> 4 ) as u8 + } else { + ( 0x00FF & frequency ) as u8 + } ] } else { // No active note, nothing to generate