use crate::import::patternslot::PatternSlot;
use crate::import::track_import_effect::TrackImportEffect;
use crate::import::track_import_unit::TrackImportUnit;
use crate::prelude::*;
use alloc::vec;
use alloc::vec::Vec;
pub struct ModXmEffect;
impl ModXmEffect {
fn mod_xm_volume_slide(param: u8) -> Option<f32> {
let upper_nibble = param & 0xF0;
let lower_nibble = param & 0x0F;
match (upper_nibble, lower_nibble) {
(f, 0) => Some((f >> 4) as f32 / 64.0), (0, f) => Some(-(f as f32) / 64.0), _ => None,
}
}
fn mod_xm_walk_effect(
freq_type: FrequencyType,
current: &PatternSlot,
) -> Option<Vec<TrackImportEffect>> {
match current.effect_type {
0x00 => {
let param = current.effect_parameter;
if param > 0 {
let v1 = (param >> 4) as usize;
let v2 = (param & 0x0F) as usize;
Some(vec![TrackImportEffect::Arpeggio(v1, v2)])
} else {
None
}
}
0x01 => Some(vec![TrackImportEffect::PortamentoUp(
-4.0 * current.effect_parameter as f32,
)]),
0x02 => Some(vec![TrackImportEffect::PortamentoDown(
4.0 * current.effect_parameter as f32,
)]),
0x03 => {
let param = current.effect_parameter as f32;
let speed = match freq_type {
FrequencyType::LinearFrequencies => 4.0 * param,
FrequencyType::AmigaFrequencies => param,
};
Some(vec![TrackImportEffect::TonePortamento(speed)])
}
0x04 => {
let param = current.effect_parameter;
let speed = ((param & 0xF0) >> 4) as f32 / 64.0;
let depth = (param & 0x0F) as f32 / 16.0;
Some(vec![TrackImportEffect::Vibrato(speed, depth)])
}
0x05 => {
let vs = Self::mod_xm_volume_slide(current.effect_parameter);
if let Some(vste) = vs {
Some(vec![
TrackImportEffect::TonePortamento(0.0),
TrackImportEffect::VolumeSlideN(vste),
])
} else {
Some(vec![
TrackImportEffect::TonePortamento(0.0),
TrackImportEffect::VolumeSlideN(0.0),
])
}
}
0x06 => {
if let Some(vste) = Self::mod_xm_volume_slide(current.effect_parameter) {
Some(vec![
TrackImportEffect::Vibrato(0.0, 0.0),
TrackImportEffect::VolumeSlideN(vste),
])
} else {
Some(vec![
TrackImportEffect::Vibrato(0.0, 0.0),
TrackImportEffect::VolumeSlideN(0.0),
])
}
}
0x07 => {
let param = current.effect_parameter;
let speed = ((param & 0xF0) >> 4) as f32 / 64.0;
let depth = (param & 0x0F) as f32 / 16.0;
Some(vec![TrackImportEffect::Tremolo(speed, depth)])
}
0x08 => Some(vec![TrackImportEffect::Panning(
(current.effect_parameter as f32) / 255.0,
)]),
0x09 => Some(vec![TrackImportEffect::InstrumentSampleOffset(
current.effect_parameter as usize * 256,
)]),
0x0A => Self::mod_xm_volume_slide(current.effect_parameter)
.map(|vste| vec![TrackImportEffect::VolumeSlideN(vste)]),
0x0C => Some(vec![TrackImportEffect::Volume(
(current.effect_parameter.min(64) as f32) / 64.0,
0,
)]),
0x0E => {
let param = current.effect_parameter & 0x0F;
match current.effect_parameter >> 4 {
0x1 => (param != 0)
.then(|| vec![TrackImportEffect::PortamentoFineUp(-4.0 * param as f32)]),
0x2 => (param != 0)
.then(|| vec![TrackImportEffect::PortamentoFineDown(4.0 * param as f32)]),
0x3 => Some(vec![TrackImportEffect::Glissando(
current.effect_parameter != 0,
)]),
0x4 => {
let waveform = match param & 0b0000_0011 {
1 => Waveform::RampDown,
2 => Waveform::Square,
_ => Waveform::Sine,
};
let retrig = (param & 0b0000_0100) == 0;
Some(vec![TrackImportEffect::VibratoWaveform(waveform, retrig)])
}
0x5 => Some(vec![TrackImportEffect::InstrumentFineTune(
param as f32 / 8.0 - 1.0,
)]),
0x7 => {
let waveform = match param & 0b0000_0011 {
1 => Waveform::RampDown,
2 => Waveform::Square,
_ => Waveform::Sine,
};
let retrig = (param & 0b0000_0100) == 0;
Some(vec![TrackImportEffect::TremoloWaveform(waveform, retrig)])
}
0x9 => Some(vec![TrackImportEffect::NoteRetrig(param as usize)]),
0xA => Some(vec![TrackImportEffect::VolumeSlide0((param as f32) / 64.0)]),
0xB => Some(vec![TrackImportEffect::VolumeSlide0(
-(param as f32) / 64.0,
)]),
0xC => Some(vec![TrackImportEffect::NoteCut(param as usize, false)]),
0xD => Some(vec![TrackImportEffect::NoteDelay(param as usize)]),
_ => None,
}
}
0x14 => Some(vec![TrackImportEffect::NoteOff(
current.effect_parameter as usize,
false,
)]),
0x15 => Some(vec![TrackImportEffect::InstrumentVolumeEnvelopePosition(
current.effect_parameter as usize,
)]),
0x19 => {
let param = current.effect_parameter;
let upper_nibble = param & 0xF0;
let lower_nibble = param & 0x0F;
match (upper_nibble, lower_nibble) {
(f, 0) => Some(vec![TrackImportEffect::PanningSlideN(
(f >> 4) as f32 / 16.0,
)]), (0, f) => Some(vec![TrackImportEffect::PanningSlideN(-(f as f32) / 16.0)]), _ => None,
}
}
0x1B => {
let param = current.effect_parameter;
let vol = param >> 4;
let speed = param & 0x0F;
Some(vec![TrackImportEffect::NoteRetrigExtended(
speed as usize,
vol as usize,
)])
}
0x1D => {
let param = current.effect_parameter;
let on_time = param >> 4;
let off_time = param & 0x0F;
Some(vec![TrackImportEffect::Tremor(
on_time as usize,
off_time as usize,
)])
}
0x21 => {
let effect_case = current.effect_parameter >> 4;
let param = current.effect_parameter & 0x0F;
match effect_case {
1 => {
Some(vec![TrackImportEffect::PortamentoExtraFineUp(
-(param as f32),
)])
}
2 => {
Some(vec![TrackImportEffect::PortamentoExtraFineDown(
param as f32,
)])
}
_ => None,
}
}
_ => None,
}
}
fn mod_xm_walk_volume(
freq_type: FrequencyType,
current: &PatternSlot,
) -> Option<TrackImportEffect> {
match current.volume >> 4 {
0x0 => None,
0x1..=0x4 => Some(TrackImportEffect::Volume(
(current.volume - 0x10) as f32 / 64.0,
0,
)),
0x5 => None,
0x6 => Some(TrackImportEffect::VolumeSlide0(
-((current.volume & 0x0F) as f32) / 64.0,
)),
0x7 => Some(TrackImportEffect::VolumeSlide0(
((current.volume & 0x0F) as f32) / 64.0,
)),
0x8 => Some(TrackImportEffect::VolumeSlideN(
-((current.volume & 0x0F) as f32) / 64.0,
)),
0x9 => Some(TrackImportEffect::VolumeSlideN(
((current.volume & 0x0F) as f32) / 64.0,
)),
0xA => {
let s = current.volume & 0x0F;
if s != 0 {
Some(TrackImportEffect::VibratoSpeed((s as f32) / 64.0))
} else {
None
}
}
0xB => {
let d = current.volume & 0x0F;
if d != 0 {
Some(TrackImportEffect::VibratoDepthFxVol((d as f32) / 16.0))
} else {
None
}
}
0xC => Some(TrackImportEffect::Panning(
((current.volume & 0x0F) as f32) / 16.0,
)),
0xD => Some(TrackImportEffect::PanningSlideN(
-((current.volume & 0x0F) as f32) / 16.0,
)),
0xE => Some(TrackImportEffect::PanningSlideN(
((current.volume & 0x0F) as f32) / 16.0,
)),
0xF => {
let speed = (current.volume & 0x0F) as f32 * 16.0;
let speed = match freq_type {
FrequencyType::LinearFrequencies => 4.0 * speed,
FrequencyType::AmigaFrequencies => speed,
};
Some(TrackImportEffect::TonePortamentoFxVol(speed))
}
_ => None,
}
}
fn mod_xm_walk_global_effect(current: &PatternSlot) -> Option<GlobalEffect> {
match current.effect_type {
0x0B => Some(GlobalEffect::PositionJump(
current.effect_parameter as usize,
)),
0x0D => {
let param = current.effect_parameter;
let ten = (param >> 4) as usize * 10;
let unit = (param & 0x0F) as usize;
let total = ten + unit;
Some(GlobalEffect::PatternBreak(total))
}
0x0E => {
let param = current.effect_parameter & 0x0F;
match current.effect_parameter >> 4 {
0x6 => Some(GlobalEffect::PatternLoop(param as usize)),
0xE => Some(GlobalEffect::PatternDelay {
quantity: param as usize,
tempo: true,
}),
_ => None,
}
}
0x0F => {
let param = current.effect_parameter;
if param < 32 {
Some(GlobalEffect::Speed(param as usize))
} else {
Some(GlobalEffect::Bpm(param as usize))
}
}
0x10 => Some(GlobalEffect::Volume(
(current.effect_parameter.min(64) as f32) / 64.0,
)),
0x11 => {
let param = current.effect_parameter;
let upper_nibble = param & 0xF0;
let lower_nibble = param & 0x0F;
match (upper_nibble, lower_nibble) {
(0, f) => Some(GlobalEffect::VolumeSlide {
speed: -(f as f32) / 64.0,
fine: false,
}), (f, 0) => Some(GlobalEffect::VolumeSlide {
speed: (f >> 4) as f32 / 64.0,
fine: false,
}), _ => None,
}
}
_ => None,
}
}
pub fn mod_xm_unpack(freq_type: FrequencyType, current: &PatternSlot) -> TrackImportUnit {
let te = Self::mod_xm_walk_effect(freq_type, current);
let ve = Self::mod_xm_walk_volume(freq_type, current);
let cc = Self::mod_xm_walk_global_effect(current);
let mut tiu = TrackImportUnit::default();
tiu.note = current.note;
tiu.instrument = current.instrument;
if let Some(e) = ve {
tiu.effects.push(e);
}
if let Some(e) = te {
tiu.effects.extend(e)
}
if let Some(c) = cc {
tiu.global_effects.push(c);
}
tiu
}
pub fn mod_xm_unpack_row(
freq_type: FrequencyType,
row: &[PatternSlot],
) -> Vec<TrackImportUnit> {
row.iter()
.map(|pattern_slot| ModXmEffect::mod_xm_unpack(freq_type, pattern_slot))
.collect()
}
pub fn mod_xm_unpack_pattern(
freq_type: FrequencyType,
pattern: &[Vec<PatternSlot>],
) -> Vec<Vec<TrackImportUnit>> {
pattern
.iter()
.map(|row| Self::mod_xm_unpack_row(freq_type, row))
.collect()
}
}