Implement portamento up and down for psg channels

master
Ashley N. 2023-08-27 16:56:58 -04:00
parent e01675fcad
commit c7c703ef65
1 changed files with 107 additions and 28 deletions

View File

@ -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<u8, Box<dyn Error>> {
D - 722 | F# - 910 | A# - 1146
D# - 765 | G - 964 | B - 1214
*/
pub fn get_semitone_frequency( semitone: u8 ) -> Result<u16, Box<dyn Error>> {
pub fn get_fm_semitone_frequency( semitone: u8 ) -> Result<u16, Box<dyn Error>> {
Ok( match semitone {
ESF_SEMITONE_C => 644,
ESF_SEMITONE_C_SHARP => 681,
@ -121,10 +121,65 @@ pub fn get_semitone_frequency( semitone: u8 ) -> Result<u16, Box<dyn Error>> {
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<u16, Box<dyn Error>> {
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,29 +477,41 @@ pub fn get_portamento( channel: &mut Channel, portamento_effect: &Effect ) -> Re
_ => return Err( "internal error: provided effect is not a supported portamento effect" )?
};
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 {
let new_frequency: i16 = octave_frequency.frequency as i16 + by_amount;
if new_frequency > get_semitone_frequency( ESF_SEMITONE_B )? as i16 {
if new_frequency > get_fm_semitone_frequency( ESF_SEMITONE_B )? as i16 {
if octave_frequency.octave == 7 {
// Nowhere else to go up
( 7, get_semitone_frequency( ESF_SEMITONE_B )? )
( 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_semitone_frequency( ESF_SEMITONE_B )? as i16;
( octave_frequency.octave + 1, get_semitone_frequency( ESF_SEMITONE_C )? + difference as u16 )
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_semitone_frequency( ESF_SEMITONE_C )? as i16 {
} 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_semitone_frequency( ESF_SEMITONE_C )? )
( 0, get_fm_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 )
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 )
}
}
};
// Set the new OctaveFrequency on the channel
@ -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 ),
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