#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
use std::fs;
pub mod textout;
const CLOCK_TICKS_PERS_SECOND: f32 = 3579545.0;
static VIBRATO_TABLE: [ i32; 64] = [0,24,49,74,97,120,141,161, 180,197,212,224,235,244,250,253,255,253,250,244,235,224,212,197,180,161,141,120,97,74,49,24,
-0,-24,-49,-74,-97,-120,-141,-161, -180,-197,-212,-224,-235,-244,-250,-253,-255,-253,-250,-244,-235,-224,-212,-197,-180,-161,-141,-120,-97,-74,-49,-24];
static FREQUENCY_TABLE: [u32; 60] = [
57, 60, 64, 67, 71, 76, 80, 85, 90, 95, 101, 107,
113, 120, 127, 135, 143, 151, 160, 170, 180, 190, 202, 214,
226, 240, 254, 269, 285, 302, 320, 339, 360, 381, 404, 428,
453, 480, 508, 538, 570, 604, 640, 678, 720, 762, 808, 856,
907, 961, 1017, 1077, 1141, 1209, 1281, 1357, 1440, 1525, 1616, 1712
];
pub struct Sample {
name: String,
size: u32,
volume: u8,
fine_tune: u8,
repeat_offset: u32,
repeat_size: u32,
samples: Vec<i8>,
}
impl Sample{
fn new( sample_info : &[u8] ) -> Sample {
let sample_name = String::from_utf8_lossy(&sample_info[0..22]);
let sample_size: u32 = ((sample_info[23] as u32) + (sample_info[22] as u32) * 256) * 2;
let fine_tune = sample_info[24];
let volume = sample_info[25];
let mut repeat_offset: u32 = ((sample_info[27] as u32) + (sample_info[26] as u32) * 256 )*2;
let repeat_size: u32 = ((sample_info[29] as u32) + (sample_info[28] as u32) * 256 )*2;
if sample_size > 0 {
if repeat_offset + repeat_size > sample_size {
repeat_offset -= ( repeat_offset + repeat_size ) - sample_size;
}
}
Sample {
name: String::from(sample_name),
size: sample_size,
volume: volume,
fine_tune: fine_tune,
repeat_offset: repeat_offset,
repeat_size: repeat_size,
samples: Vec::new(),
}
}
}
enum Effect{
None,
Arpeggio{ chord_offset_1 : u8, chord_offset_2 : u8 },
SlideUp{ speed : u8 },
SlideDown{ speed: u8 },
TonePortamento{ speed: u8 },
Vibrato{ speed : u8, amplitude : u8 },
TonePortamentoVolumeSlide{ volume_change : i8 },
VibratoVolumeSlide{ volume_change : i8 },
SetSampleOffset{ offset : u8 },
VolumeSlide{ volume_change : i8 },
PositionJump{ next_pattern : u8 },
SetVolume{ volume : u8 },
PatternBreak{ next_pattern_pos : u8 },
SetSpeed{ speed : u8 },
SetHardwareFilter{ new_state: u8 },
FinePortaUp{ period_change : u8 },
FinePortaDown{ period_change : u8 },
PatternLoop{ arg: u8 },
RetriggerSample{ retrigger_delay : u8 },
FineVolumeSlideUp{ volume_change : u8 },
FineVolumeSlideDown{ volume_change : u8 },
DelayedSample{ delay_ticks : u8 },
SetVibratoWave{ wave : u8 }
}
impl Effect{
fn new( effect_number : u8, effect_argument : i8 ) -> Effect {
match effect_number {
0 => match effect_argument {
0 => Effect::None,
_ => Effect::Arpeggio{ chord_offset_1 : effect_argument as u8 >> 4, chord_offset_2 : effect_argument as u8 & 0x0f },
},
1 => Effect::SlideUp{ speed : effect_argument as u8 },
2 => Effect::SlideDown{ speed : effect_argument as u8 },
3 => Effect::TonePortamento{ speed : effect_argument as u8 },
4 => Effect::Vibrato{ speed : effect_argument as u8 >> 4, amplitude : effect_argument as u8 & 0x0f },
5 => {
if (effect_argument as u8 & 0xf0) != 0 {
Effect::TonePortamentoVolumeSlide{ volume_change : effect_argument >> 4 }
} else {
Effect::TonePortamentoVolumeSlide{ volume_change : -effect_argument }
}
},
6 => {
if (effect_argument as u8 & 0xf0) != 0 {
Effect::VibratoVolumeSlide{ volume_change : effect_argument >> 4 }
} else {
Effect::VibratoVolumeSlide{ volume_change : -effect_argument }
}
},
9 => Effect::SetSampleOffset{ offset : effect_argument as u8 },
10 => {
if (effect_argument as u8 & 0xf0) != 0 {
Effect::VolumeSlide{ volume_change : effect_argument >> 4 }
} else {
Effect::VolumeSlide{ volume_change : -effect_argument }
}
}
11 => Effect::PositionJump{ next_pattern : effect_argument as u8 },
12 => Effect::SetVolume{ volume : effect_argument as u8 },
13 => Effect::PatternBreak{ next_pattern_pos : ((0xf0&( effect_argument as u32 ))*10 + ( effect_argument as u32 & 0x0f)) as u8 },
14 => {
let extended_effect = ( effect_argument as u8 ) >> 4;
let extended_argument = ( effect_argument as u8 ) &0x0f;
match extended_effect {
0 => Effect::SetHardwareFilter{ new_state : extended_argument as u8 },
1 => Effect::FinePortaUp{ period_change : extended_argument as u8 },
2 => Effect::FinePortaDown{ period_change : extended_argument as u8 },
6 => {
match extended_argument {
0 => Effect::PatternLoop{ arg: 0},
_ => panic!( "unhandled PATTERN LOOp Trigger" )
}
}
9 => Effect::RetriggerSample{ retrigger_delay : extended_argument as u8 },
10 => Effect::FineVolumeSlideUp{ volume_change : extended_argument as u8 },
11 => Effect::FineVolumeSlideDown{ volume_change : extended_argument as u8 },
13 => Effect::DelayedSample{ delay_ticks : extended_argument as u8 },
_ => panic!( format!( "unhandled extended effect number: {}", extended_effect ) )
}
},
15 => Effect::SetSpeed{ speed : effect_argument as u8 },
_ => panic!( format!( "unhandled effect number: {}", effect_number ) )
}
}
}
pub struct Note{
sample_number: u8,
period: u32,
effect: Effect,
}
fn change_note( current_period : u32, change : i32 ) -> u32 {
let mut result = current_period as i32 + change;
if result > 856 { result = 856; }
if result < 113 { result = 113; }
result as u32
}
impl Note{
fn new( note_data : &[u8], format_description : &FormatDescription) -> Note {
let mut sample_number = ( (note_data[2] & 0xf0) >> 4 ) + ( note_data[ 0 ] &0xf0);
if format_description.num_samples == 15 {
sample_number = sample_number & 0x0f;
} else {
sample_number = sample_number & 0x1f;
}
let period = ((note_data[0] & 0x0f) as u32) * 256 + (note_data[1] as u32);
let effect_argument = note_data[3] as i8;
let effect_number = note_data[ 2] & 0x0f;
let effect = Effect::new(effect_number, effect_argument);
Note{
sample_number, period, effect
}
}
}
pub struct Pattern {
lines: Vec<Vec<Note>>
}
impl Pattern{
fn new( ) -> Pattern {
let mut lines : Vec<Vec<Note>> = Vec::new();
for _line in 0..64 {
lines.push( Vec::new() );
}
Pattern{ lines }
}
}
pub struct FormatDescription{
pub num_channels : u32,
pub num_samples : u32,
pub has_tag : bool
}
pub struct Song {
pub name: String,
pub format : FormatDescription,
pub samples: Vec<Sample>,
pub patterns: Vec<Pattern>,
pub pattern_table: Vec<u8>,
pub num_used_patterns : u32,
pub end_position : u32,
}
struct ChannelInfo {
sample_num: u8,
sample_pos: f32,
period : u32,
size : u32,
volume: f32,
volume_change: f32,
note_change : i32,
period_target : u32,
base_period : u32,
vibrato_pos : u32,
vibrato_speed : u32,
vibrato_depth : i32,
retrigger_delay : u32,
retrigger_counter : u32,
arpeggio_counter : u32,
arpeggio_offsets : [u32;2],
}
impl ChannelInfo{
fn new() -> ChannelInfo {
ChannelInfo {
sample_num: 0,
sample_pos: 0.0,
period : 0,
size : 0,
volume: 0.0,
volume_change: 0.0,
note_change : 0,
period_target : 0,
base_period : 0,
vibrato_pos : 0,
vibrato_speed : 0,
vibrato_depth : 0,
retrigger_delay : 0,
retrigger_counter : 0,
arpeggio_counter : 0,
arpeggio_offsets : [ 0, 0] ,
}
}
}
pub struct PlayerState{
channels: Vec<ChannelInfo>,
song_pattern_position: u32,
pub current_line: u32,
pub song_has_ended : bool,
pub has_looped : bool,
device_sample_rate : u32,
song_speed: u32,
current_vblank : u32,
samples_per_vblank: u32,
clock_ticks_per_device_sample : f32,
current_vblank_sample : u32,
next_pattern_pos : i32,
next_position : i32,
}
impl PlayerState{
pub fn new( num_channels : u32, device_sample_rate : u32 ) -> PlayerState {
let mut channels = Vec::new();
for _channel in 0..num_channels {
channels.push(ChannelInfo::new())
}
PlayerState{
channels,
song_pattern_position : 0,
current_line: 0,
current_vblank : 0,
current_vblank_sample : 0,
device_sample_rate : device_sample_rate,
song_speed: 6,
samples_per_vblank: device_sample_rate / 50,
clock_ticks_per_device_sample : CLOCK_TICKS_PERS_SECOND / device_sample_rate as f32,
next_pattern_pos : -1,
next_position : -1,
song_has_ended : false,
has_looped :false
}
}
pub fn get_song_line<'a>( &self, song : &'a Song ) -> &'a Vec<Note> {
let pattern_idx = song.pattern_table[self.song_pattern_position as usize];
let pattern = &song.patterns[ pattern_idx as usize];
let line = &pattern.lines[ self.current_line as usize ];
line
}
}
fn play_note(note: &Note, player_state: &mut PlayerState, channel_num: usize, song: &Song) {
let old_period = player_state.channels[ channel_num ].period;
let old_period_target = player_state.channels[ channel_num ].period_target;
let old_vibrato_pos = player_state.channels[channel_num].vibrato_pos;
let old_vibrato_speed = player_state.channels[channel_num].vibrato_speed;
let old_vibrato_depth = player_state.channels[channel_num].vibrato_depth;
let old_note_change = player_state.channels[channel_num].note_change;
if note.sample_number > 0 {
let current_sample: &Sample = &song.samples[(note.sample_number - 1) as usize];
player_state.channels[channel_num].volume = current_sample.volume as f32;
player_state.channels[channel_num].size = song.samples[(note.sample_number-1) as usize].size;
player_state.channels[channel_num].sample_num = note.sample_number;
}
player_state.channels[channel_num].volume_change = 0.0;
player_state.channels[channel_num].note_change = 0;
player_state.channels[channel_num].retrigger_delay = 0;
player_state.channels[channel_num].vibrato_pos = 0;
player_state.channels[channel_num].vibrato_speed = 0;
player_state.channels[channel_num].vibrato_depth = 0;
player_state.channels[channel_num].arpeggio_offsets[ 0 ] = 0;
player_state.channels[channel_num].arpeggio_offsets[ 1 ] = 0;
if note.period != 0 {
player_state.channels[channel_num].period = note.period as u32;
player_state.channels[channel_num].sample_pos = 0.0;
}
match note.effect {
Effect::SetSpeed{ speed } => {
if speed <= 31 {
player_state.song_speed = speed as u32;
} else {
let vblanks_per_sec = speed as f32 * 0.4;
player_state.samples_per_vblank = ( player_state.device_sample_rate as f32 / vblanks_per_sec ) as u32
}
}
Effect::Arpeggio{ chord_offset_1, chord_offset_2 } => {
player_state.channels[channel_num].base_period = player_state.channels[channel_num].period;
player_state.channels[channel_num].arpeggio_offsets[ 0 ] = chord_offset_1 as u32;
player_state.channels[channel_num].arpeggio_offsets[ 1 ] = chord_offset_2 as u32;
player_state.channels[channel_num].arpeggio_counter = 0;
}
Effect::SlideUp{ speed } => {
player_state.channels[channel_num].note_change = -(speed as i32);
}
Effect::SlideDown{ speed } => {
player_state.channels[channel_num].note_change = speed as i32;
}
Effect::TonePortamento{ speed } => {
player_state.channels[channel_num].period = old_period;
if note.period != 0 {
player_state.channels[channel_num].period_target = note.period;
} else {
player_state.channels[channel_num].period_target = old_period_target;
}
if speed != 0 {
player_state.channels[channel_num].note_change = speed as i32;
} else {
player_state.channels[channel_num].note_change = old_note_change;
}
}
Effect::Vibrato{ speed, amplitude } => {
player_state.channels[channel_num].base_period = player_state.channels[channel_num].period;
player_state.channels[channel_num].vibrato_speed = speed as u32;
player_state.channels[channel_num].vibrato_depth = amplitude as i32;
}
Effect::TonePortamentoVolumeSlide{ volume_change } => {
player_state.channels[channel_num].volume_change = volume_change as f32;
player_state.channels[channel_num].period_target = old_period_target;
player_state.channels[channel_num].period = old_period;
player_state.channels[channel_num].note_change = old_note_change;
}
Effect::VibratoVolumeSlide{ volume_change } => {
player_state.channels[channel_num].volume_change = volume_change as f32;
player_state.channels[channel_num].vibrato_pos = old_vibrato_pos as u32;
player_state.channels[channel_num].vibrato_speed = old_vibrato_speed as u32;
player_state.channels[channel_num].vibrato_depth = old_vibrato_depth as i32;
}
Effect::SetSampleOffset{ offset } => {
player_state.channels[channel_num].sample_pos = ( offset as f32 )* 256.0;
}
Effect::VolumeSlide{ volume_change } => {
player_state.channels[channel_num].volume_change = volume_change as f32;
}
Effect::SetVolume{ volume } => {
player_state.channels[channel_num].volume = volume as f32;
}
Effect::PatternBreak{ next_pattern_pos } => {
player_state.next_pattern_pos = next_pattern_pos as i32;
}
Effect::PositionJump{ next_pattern } => {
if next_pattern as u32 <= player_state.song_pattern_position {
player_state.has_looped = true;
}
player_state.next_position = next_pattern as i32;
}
Effect::FinePortaUp{ period_change } => {
player_state.channels[channel_num].period = change_note(player_state.channels[channel_num].period, -( period_change as i32 ) );
}
Effect::FinePortaDown{ period_change } => {
player_state.channels[channel_num].period = change_note(player_state.channels[channel_num].period, period_change as i32 );
}
Effect::RetriggerSample{ retrigger_delay } => {
player_state.channels[ channel_num ].retrigger_delay = retrigger_delay as u32;
player_state.channels[ channel_num ].retrigger_counter = 0;
}
Effect::FineVolumeSlideUp{ volume_change } => {
player_state.channels[channel_num].volume = player_state.channels[channel_num].volume + volume_change as f32;
if player_state.channels[channel_num].volume > 64.0 {
player_state.channels[channel_num].volume = 64.0;
}
}
Effect::FineVolumeSlideDown{ volume_change } => {
player_state.channels[channel_num].volume = player_state.channels[channel_num].volume - volume_change as f32;
if player_state.channels[channel_num].volume < 0.0 {
player_state.channels[channel_num].volume = 0.0;
}
}
Effect::SetHardwareFilter{ new_state : _ } => {
}
Effect::None => {}
_ => {
println!("Unhandled effect" );
}
}
}
fn play_line(song: &Song, player_state: &mut PlayerState ) {
if player_state.next_pattern_pos != -1 {
player_state.song_pattern_position += 1;
player_state.current_line = player_state.next_pattern_pos as u32;
player_state.next_pattern_pos = -1;
} else if player_state.next_position != -1 {
player_state.song_pattern_position = player_state.next_position as u32;
player_state.current_line = 0;
player_state.next_position = -1;
}
let line = player_state.get_song_line( song );
for channel_number in 0..line.len(){
play_note(&line[ channel_number as usize ], player_state, channel_number, song);
}
player_state.current_line += 1;
if player_state.current_line >= 64 {
if player_state.song_pattern_position == song.num_used_patterns {
player_state.song_has_ended = true;
}
player_state.song_pattern_position += 1;
player_state.current_line = 0;
}
}
fn update_effects( player_state: &mut PlayerState ){
for channel in &mut player_state.channels {
if channel.sample_num != 0 {
if channel.retrigger_delay > 0 {
channel.retrigger_counter +=1;
if channel.retrigger_delay == channel.retrigger_counter {
channel.sample_pos = 0.0;
channel.retrigger_counter = 0;
}
}
channel.volume += channel.volume_change;
if channel.volume < 0.0 { channel.volume = 0.0 }
if channel.volume > 64.0 { channel.volume = 64.0 }
if channel.arpeggio_offsets[ 0] != 0 || channel.arpeggio_offsets[ 1 ] != 0 {
let index : u32 = FREQUENCY_TABLE.binary_search( &channel.base_period ).expect( "Unexpected period value") as u32;
if channel.arpeggio_counter > 0 {
let note_offset = ( index + channel.arpeggio_offsets[ channel.arpeggio_counter as usize]) as usize;
channel.period = FREQUENCY_TABLE[ note_offset-1 ];
} else {
channel.period = channel.base_period;
}
channel.arpeggio_counter += 1;
if channel.arpeggio_counter >= 2 {
channel.arpeggio_counter = 0;
}
}
if channel.vibrato_depth > 0 {
channel.period = ( ( channel.base_period as i32 ) + ( VIBRATO_TABLE[(channel.vibrato_pos&63) as usize] * channel.vibrato_depth ) / 32 ) as u32;
channel.vibrato_pos += channel.vibrato_speed;
}
else if channel.note_change != 0 {
if channel.period_target != 0 {
if channel.period_target > channel.period {
channel.period = change_note(channel.period, channel.note_change);
if channel.period >= channel.period_target {
channel.period = channel.period_target;
channel.note_change = 0;
}
} else {
channel.period = change_note(channel.period, -channel.note_change);
if channel.period <= channel.period_target {
channel.period = channel.period_target;
channel.note_change = 0;
}
}
} else {
channel.period = change_note(channel.period, channel.note_change);
}
}
}
}
}
pub fn next_sample(song: &Song, player_state: &mut PlayerState) -> (f32, f32) {
let mut left = 0.0;
let mut right = 0.0;
if player_state.current_vblank_sample >= player_state.samples_per_vblank {
player_state.current_vblank_sample = 0;
update_effects(player_state);
if player_state.current_vblank >= player_state.song_speed {
player_state.current_vblank = 0;
play_line( song, player_state );
}
player_state.current_vblank += 1;
}
player_state.current_vblank_sample += 1;
for channel_number in 0..player_state.channels.len() {
let channel_info: &mut ChannelInfo = &mut player_state.channels[channel_number];
if channel_info.size > 2 {
let current_sample: &Sample = &song.samples[(channel_info.sample_num - 1) as usize];
if channel_info.sample_pos as u32 == 4356 && current_sample.samples.len() == 3900 {
println!("FFFFFFF");
}
let mut channel_value: f32 = current_sample.samples[(channel_info.sample_pos as u32) as usize] as f32;
channel_value *= channel_info.volume / (128.0*64.0);
channel_info.sample_pos += player_state.clock_ticks_per_device_sample / channel_info.period as f32;
if channel_info.sample_pos >= channel_info.size as f32 {
let overflow : f32 = channel_info.sample_pos - channel_info.size as f32;
channel_info.sample_pos = current_sample.repeat_offset as f32 + overflow;
channel_info.size = current_sample.repeat_size + current_sample.repeat_offset;
}
let channel_selector = ( channel_number as u8 ) & 0x0003;
if channel_selector == 0 || channel_number as u32 == 0 || channel_number == 3 {
left += channel_value;
} else {
right += channel_value;
}
}
}
(left, right )
}
fn get_format(file_data: &Vec<u8> ) -> FormatDescription {
let format_tag = String::from_utf8_lossy(&file_data[1080..1084]);
match format_tag.as_ref() {
"M.K." | "FLT4" | "M!K!" | "4CHN" => FormatDescription{ num_channels : 4, num_samples : 31, has_tag : true },
_ => FormatDescription{ num_channels : 4, num_samples : 15, has_tag : false }
}
}
pub fn read_mod_file(file_name: &str) -> Song {
let file_data: Vec<u8> = fs::read(file_name).expect( &format!( "Cant open file {}", &file_name ) );
let song_name = String::from_utf8_lossy(&file_data[0..20]);
let format = get_format(&file_data);
let mut samples: Vec<Sample> = Vec::new();
let mut offset : usize = 20;
for _sample_num in 0..format.num_samples {
samples.push(Sample::new( &file_data[ offset .. ( offset + 30 ) as usize ]));
offset += 30;
}
let num_used_patterns: u8 = file_data[offset];
let end_position: u8 = file_data[offset + 1];
offset += 2;
let pattern_table: Vec<u8> = file_data[offset..(offset + 128)].to_vec();
offset += 128;
if format.has_tag { offset += 4; }
let mut total_sample_size = 0;
for sample in &mut samples {
total_sample_size = total_sample_size + sample.size;
}
let total_pattern_size = file_data.len() as u32 - (offset as u32) - total_sample_size;
let single_pattern_size = format.num_channels * 4 * 64;
let num_patterns = total_pattern_size / single_pattern_size;
if total_pattern_size % single_pattern_size != 0 {
panic!( "Unrecognized file format. Pattern space does not match expected size")
}
let mut patterns: Vec<Pattern> = Vec::new();
for _pattern_number in 0..num_patterns {
let mut pattern = Pattern::new();
for line in 0..64 {
for _channel in 0..format.num_channels {
let note = Note::new( &file_data[ offset..(offset+4)], &format);
pattern.lines[ line ].push( note );
offset += 4;
}
}
patterns.push(pattern);
}
for sample_number in 0..samples.len() {
let length = samples[sample_number].size;
for _idx in 0..length {
samples[sample_number].samples.push(file_data[offset] as i8);
offset += 1;
}
}
Song {
name: String::from(song_name),
format : format,
samples: samples,
patterns: patterns,
pattern_table: pattern_table,
num_used_patterns : num_used_patterns as u32,
end_position: end_position as u32
}
}