#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
mod loader;
pub use loader::read_mod_file;
mod static_tables;
pub mod textout;
const CLOCK_TICKS_PERS_SECOND: f32 = 3579545.0;
fn fine_tune_period(period: u32, fine_tune: u32, use_fine_tune_table: bool) -> u32 {
if use_fine_tune_table {
let index: i32 = static_tables::FREQUENCY_TABLE
.binary_search(&period)
.expect("Unexpected period value") as i32;
return static_tables::FINE_TUNE_TABLE[fine_tune as usize][index as usize];
} else {
return (period as f32 * static_tables::SCALE_FINE_TUNE[fine_tune as usize]) as u32;
}
}
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,
}, Tremolo {
speed: u8,
amplitude: u8,
}, Pan {
position: u8,
}, 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,
}, Glissando {
use_smooth_slide: bool,
}, PatternLoop {
arg: u8,
}, TremoloWaveform {
wave: u8,
}, CoarsePan {
pan_pos: u8,
}, RetriggerSample {
retrigger_delay: u8,
}, FineVolumeSlideUp {
volume_change: u8,
}, FineVolumeSlideDown {
volume_change: u8,
}, CutNote {
delay: u8,
}, DelayedSample {
delay_ticks: u8,
}, DelayedLine {
delay_ticks: u8,
}, InvertLoop {
loop_position: u8,
}, SetVibratoWave {
wave: u8,
},
SetFineTune {
fine_tune: 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,
}
}
}
7 => Effect::Tremolo {
speed: effect_argument as u8 >> 4,
amplitude: effect_argument as u8 & 0x0f,
},
8 => Effect::Pan {
position: effect_argument as u8,
},
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)) >> 4) * 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,
},
3 => Effect::Glissando {
use_smooth_slide: extended_argument != 0,
},
4 => Effect::SetVibratoWave {
wave: extended_argument,
},
5 => Effect::SetFineTune {
fine_tune: extended_argument,
},
6 => Effect::PatternLoop {
arg: extended_argument as u8,
},
7 => Effect::TremoloWaveform {
wave: extended_argument as u8,
},
8 => Effect::CoarsePan {
pan_pos: extended_argument as u8,
},
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,
},
12 => Effect::CutNote {
delay: extended_argument as u8,
},
13 => Effect::DelayedSample {
delay_ticks: extended_argument as u8,
},
14 => Effect::DelayedLine {
delay_ticks: extended_argument as u8,
},
15 => Effect::InvertLoop {
loop_position: 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,
pub has_standard_notes: bool,
}
struct ChannelInfo {
sample_num: u8, sample_pos: f32,
period: u32, fine_tune: u32,
size: u32,
volume: f32, volume_change: f32, note_change: i32,
period_target: u32, last_porta_speed: i32, last_porta_target: u32,
base_period: u32, vibrato_pos: u32,
vibrato_speed: u32,
vibrato_depth: i32,
tremolo_pos: u32,
tremolo_speed: u32,
tremolo_depth: i32,
retrigger_delay: u32,
retrigger_counter: u32,
cut_note_delay: u32,
arpeggio_counter: u32,
arpeggio_offsets: [u32; 2],
}
impl ChannelInfo {
fn new() -> ChannelInfo {
ChannelInfo {
sample_num: 0,
sample_pos: 0.0,
period: 0,
fine_tune: 0,
size: 0,
volume: 0.0,
volume_change: 0.0,
note_change: 0,
period_target: 0,
last_porta_speed: 0,
last_porta_target: 0,
base_period: 0,
vibrato_pos: 0,
vibrato_speed: 0,
vibrato_depth: 0,
tremolo_pos: 0,
tremolo_speed: 0,
tremolo_depth: 0,
retrigger_delay: 0,
retrigger_counter: 0,
cut_note_delay: 0,
arpeggio_counter: 0,
arpeggio_offsets: [0, 0],
}
}
}
pub struct PlayerState {
channels: Vec<ChannelInfo>,
pub 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, delay_line: u32,
pattern_loop_position: Option<u32>, pattern_loop: i32,
set_pattern_position: bool, }
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,
delay_line: 0,
song_has_ended: false,
has_looped: false,
pattern_loop_position: None,
pattern_loop: 0,
set_pattern_position: 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 channel = &mut player_state.channels[channel_num];
let old_period = channel.period;
let old_vibrato_pos = channel.vibrato_pos;
let old_vibrato_speed = channel.vibrato_speed;
let old_vibrato_depth = channel.vibrato_depth;
let old_tremolo_speed = channel.tremolo_speed;
let old_tremolo_depth = channel.tremolo_depth;
let old_sample_pos = channel.sample_pos;
let old_sample_num = channel.sample_num;
if note.sample_number > 0 {
let current_sample: &Sample = &song.samples[(note.sample_number - 1) as usize];
channel.volume = current_sample.volume as f32; channel.size = current_sample.size;
channel.sample_num = note.sample_number;
channel.fine_tune = current_sample.fine_tune as u32;
}
channel.volume_change = 0.0;
channel.note_change = 0;
channel.retrigger_delay = 0;
channel.vibrato_speed = 0;
channel.vibrato_depth = 0;
channel.tremolo_speed = 0;
channel.tremolo_depth = 0;
channel.arpeggio_counter = 0;
channel.arpeggio_offsets[0] = 0;
channel.arpeggio_offsets[1] = 0;
if note.period != 0 {
channel.period = fine_tune_period(note.period, channel.fine_tune, song.has_standard_notes);
channel.base_period = note.period;
channel.sample_pos = 0.0;
if channel.sample_num > 0 {
let current_sample: &Sample = &song.samples[(channel.sample_num - 1) as usize];
channel.size = current_sample.size;
}
}
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,
} => {
channel.arpeggio_offsets[0] = chord_offset_1 as u32;
channel.arpeggio_offsets[1] = chord_offset_2 as u32;
channel.arpeggio_counter = 0;
}
Effect::SlideUp { speed } => {
channel.note_change = -(speed as i32);
}
Effect::SlideDown { speed } => {
channel.note_change = speed as i32;
}
Effect::TonePortamento { speed } => {
if note.period != 0 {
channel.period_target = channel.period; } else {
if channel.last_porta_target != 0 {
channel.period_target = channel.last_porta_target;
} else {
channel.period_target = old_period;
}
}
channel.period = old_period; if speed != 0 {
channel.note_change = speed as i32;
} else {
channel.note_change = channel.last_porta_speed;
}
channel.last_porta_speed = channel.note_change;
channel.last_porta_target = channel.period_target;
if old_sample_num == channel.sample_num {
channel.sample_pos = old_sample_pos;
}
}
Effect::Vibrato { speed, amplitude } => {
if speed == 0 {
channel.vibrato_speed = old_vibrato_speed;
}
if amplitude == 0 {
channel.vibrato_depth = old_vibrato_depth;
}
}
Effect::TonePortamentoVolumeSlide { volume_change } => {
channel.volume_change = volume_change as f32;
if note.period != 0 {
channel.period_target = channel.period;
} else {
channel.period_target = channel.last_porta_target;
}
channel.period = old_period;
channel.sample_pos = old_sample_pos;
channel.last_porta_target = channel.period_target;
channel.note_change = channel.last_porta_speed;
}
Effect::VibratoVolumeSlide { volume_change } => {
channel.volume_change = volume_change as f32;
channel.vibrato_pos = old_vibrato_pos as u32;
channel.vibrato_speed = old_vibrato_speed as u32;
channel.vibrato_depth = old_vibrato_depth as i32;
}
Effect::Tremolo { speed, amplitude } => {
if speed == 0 && amplitude == 0 {
channel.tremolo_depth = old_tremolo_depth;
channel.tremolo_speed = old_tremolo_speed;
} else {
channel.tremolo_depth = amplitude as i32;
channel.tremolo_speed = speed as u32;
}
}
Effect::SetSampleOffset { offset } => {
if note.period != 0 && channel.sample_num > 0 {
channel.sample_pos = (offset as f32) * 256.0;
let current_sample: &Sample = &song.samples[(channel.sample_num - 1) as usize];
if channel.sample_pos as u32 > current_sample.size {
channel.sample_pos = (channel.sample_pos as u32 % current_sample.size) as f32
}
}
}
Effect::VolumeSlide { volume_change } => {
channel.volume_change = volume_change as f32;
}
Effect::SetVolume { volume } => {
channel.volume = volume as f32;
}
Effect::PatternBreak { next_pattern_pos } => {
player_state.next_pattern_pos = next_pattern_pos as i32;
if player_state.next_pattern_pos > 63 {
player_state.next_pattern_pos = 0;
}
}
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 } => {
channel.period = change_note(channel.period, -(period_change as i32));
}
Effect::FinePortaDown { period_change } => {
channel.period = change_note(channel.period, period_change as i32);
}
Effect::PatternLoop { arg } => {
if arg == 0 {
player_state.pattern_loop_position = Some(player_state.current_line);
} else {
if player_state.pattern_loop == 0 {
player_state.pattern_loop = arg as i32;
} else {
player_state.pattern_loop -= 1;
}
if player_state.pattern_loop > 0 && player_state.pattern_loop_position.is_some() {
player_state.set_pattern_position = true;
} else {
player_state.pattern_loop_position = None;
}
}
}
Effect::TremoloWaveform { wave: _ } => {
}
Effect::CoarsePan { pan_pos: _ } => {
}
Effect::RetriggerSample { retrigger_delay } => {
channel.retrigger_delay = retrigger_delay as u32;
channel.retrigger_counter = 0;
}
Effect::FineVolumeSlideUp { volume_change } => {
channel.volume = channel.volume + volume_change as f32;
if channel.volume > 64.0 {
channel.volume = 64.0;
}
}
Effect::FineVolumeSlideDown { volume_change } => {
channel.volume = channel.volume - volume_change as f32;
if channel.volume < 0.0 {
channel.volume = 0.0;
}
}
Effect::CutNote { delay } => {
channel.cut_note_delay = delay as u32;
}
Effect::SetHardwareFilter { new_state: _ } => {
}
Effect::DelayedLine { delay_ticks } => {
player_state.delay_line = delay_ticks as u32;
}
Effect::InvertLoop { loop_position: _ } => {
}
Effect::None => {}
_ => {
}
}
}
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;
}
if player_state.song_pattern_position >= song.num_used_patterns {
if song.end_position < song.num_used_patterns {
player_state.song_pattern_position = song.end_position;
player_state.has_looped = true;
} else {
player_state.song_has_ended = true;
}
}
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,
);
}
if player_state.set_pattern_position && player_state.pattern_loop_position.is_some() {
player_state.set_pattern_position = false;
player_state.current_line = player_state.pattern_loop_position.unwrap();
} else {
player_state.current_line += 1;
if player_state.current_line >= 64 {
player_state.song_pattern_position += 1;
if player_state.song_pattern_position >= song.num_used_patterns {
player_state.song_has_ended = true;
}
player_state.current_line = 0;
}
}
}
fn update_effects(player_state: &mut PlayerState, song: &Song) {
for channel in &mut player_state.channels {
if channel.sample_num != 0 {
if channel.cut_note_delay > 0 {
channel.cut_note_delay -= 1;
if channel.cut_note_delay == 0 {
channel.cut_note_delay = 0;
channel.size = 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.tremolo_depth > 0 {
let base_volume = song.samples[(channel.sample_num - 1) as usize].volume as i32;
let tremolo_size: i32 = (static_tables::VIBRATO_TABLE
[(channel.tremolo_pos & 63) as usize]
* channel.tremolo_depth)
/ 64;
let volume = base_volume + tremolo_size;
channel.tremolo_pos += channel.tremolo_speed;
channel.volume = volume as f32;
}
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 new_period: u32;
let index = static_tables::FREQUENCY_TABLE
.binary_search(&channel.base_period)
.expect(&format!(
"Unexpected period value at arpeggio {}, {}:{}",
channel.base_period,
player_state.song_pattern_position,
player_state.current_line
)) as i32;
if channel.arpeggio_counter > 0 {
let mut note_offset = index
- channel.arpeggio_offsets[(channel.arpeggio_counter - 1) as usize] as i32;
if note_offset < 0 {
note_offset = 0;
}
new_period = static_tables::FREQUENCY_TABLE[note_offset as usize];
} else {
new_period = channel.base_period;
}
channel.period =
fine_tune_period(new_period, channel.fine_tune, song.has_standard_notes);
channel.arpeggio_counter += 1;
if channel.arpeggio_counter >= 3 {
channel.arpeggio_counter = 0;
}
}
if channel.vibrato_depth > 0 {
let period = fine_tune_period(
channel.base_period,
channel.fine_tune,
song.has_standard_notes,
);
channel.period = ((period as i32)
+ (static_tables::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;
}
} else {
channel.period = change_note(channel.period, -channel.note_change);
if channel.period <= channel.period_target {
channel.period = channel.period_target;
}
}
} 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, song);
if player_state.current_vblank >= player_state.song_speed {
if player_state.delay_line > 0 {
player_state.delay_line -= 1;
} else {
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 >= 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;
if channel_info.size <= 2 {
continue;
}
}
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;
let channel_selector = (channel_number as u8) & 0x0003;
if channel_selector == 0 || channel_selector == 3 {
left += channel_value;
} else {
right += channel_value;
}
}
}
(left, right)
}