use crate::effect::MidiMacroType;
use crate::fixed::fixed::{Q15, Q8_8};
use crate::fixed::from_tracker::{
it_panbrello_depth_from_nibble_4, it_vibrato_depth_from_nibble_4, lfo_depth_from_nibble_16,
lfo_speed_from_nibble_64, vibrato_depth_from_nibble_16,
};
use crate::fixed::units::{ChannelVolume, Panning, PitchDelta, Volume};
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 ItEffect;
impl ItEffect {
fn it_volume_slide(fx: u8) -> Option<TrackImportEffect> {
let nibble_high = (fx & 0xF0) >> 4;
let nibble_low = fx & 0x0F;
match (nibble_high, nibble_low) {
(f, 0) => Some(TrackImportEffect::VolumeSlideN(Q15::from_ratio(
f as i32, 64,
))),
(0, f) => Some(TrackImportEffect::VolumeSlideN(Q15::from_ratio(
-(f as i32),
64,
))),
(f, 0xF) => Some(TrackImportEffect::VolumeSlide0(Q15::from_ratio(
f as i32, 64,
))),
(0xF, f) => Some(TrackImportEffect::VolumeSlide0(Q15::from_ratio(
-(f as i32),
64,
))),
_ => None,
}
}
pub fn parse_it_effect(
freq_type: FrequencyType,
current: &PatternSlot,
) -> Option<Vec<TrackImportEffect>> {
match current.effect_type {
0x04 => Self::it_volume_slide(current.effect_parameter).map(|tie| vec![tie]),
0x05 => {
let fx = current.effect_parameter;
let nibble_high = (fx & 0xF0) >> 4;
let nibble_low = fx & 0x0F;
match (nibble_high, nibble_low) {
(0xE, low) => {
Some(vec![TrackImportEffect::PortamentoExtraFineDown(low as i16)])
}
(0xF, low) => Some(vec![TrackImportEffect::PortamentoFineDown(4 * low as i16)]),
(_high, _low) => Some(vec![TrackImportEffect::PortamentoDown(4 * fx as i16)]),
}
}
0x06 => {
let fx = current.effect_parameter;
let nibble_high = (fx & 0xF0) >> 4;
let nibble_low = fx & 0x0F;
match (nibble_high, nibble_low) {
(0xE, low) => Some(vec![TrackImportEffect::PortamentoExtraFineUp(
-(low as i16),
)]),
(0xF, low) => Some(vec![TrackImportEffect::PortamentoFineUp(-4 * low as i16)]),
(_high, _low) => Some(vec![TrackImportEffect::PortamentoUp(-4 * fx as i16)]),
}
}
0x07 => {
let param = current.effect_parameter as i16;
let speed: i16 = match freq_type {
FrequencyType::LinearFrequencies => 4 * param,
FrequencyType::AmigaFrequencies => param,
};
Some(vec![TrackImportEffect::TonePortamento(speed)])
}
0x08 => {
let param = current.effect_parameter;
let speed = lfo_speed_from_nibble_64((param & 0xF0) >> 4);
let depth = it_vibrato_depth_from_nibble_4(param & 0x0F);
Some(vec![TrackImportEffect::Vibrato(speed, depth)])
}
0x09 => {
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,
)])
}
0x0A => {
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
}
}
0x0B => {
if let Some(vste) = Self::it_volume_slide(current.effect_parameter) {
Some(vec![
TrackImportEffect::Vibrato(Q8_8::ZERO, PitchDelta::ZERO),
vste,
])
} else {
Some(vec![
TrackImportEffect::Vibrato(Q8_8::ZERO, PitchDelta::ZERO),
TrackImportEffect::VolumeSlideN(Q15::ZERO),
])
}
}
0x0C => {
if let Some(vste) = Self::it_volume_slide(current.effect_parameter) {
Some(vec![TrackImportEffect::TonePortamento(0), vste])
} else {
Some(vec![
TrackImportEffect::TonePortamento(0),
TrackImportEffect::VolumeSlideN(Q15::ZERO),
])
}
}
0x0D => {
let volume = ChannelVolume::from_byte_64(current.effect_parameter.min(64));
Some(vec![TrackImportEffect::ChannelVolume(volume)])
}
0x0E => {
let fx = current.effect_parameter;
let nibble_high = (fx & 0xF0) >> 4;
let nibble_low = fx & 0x0F;
match (nibble_high, nibble_low) {
(f, 0) => Some(vec![TrackImportEffect::ChannelVolumeSlideN(
Q15::from_ratio(f as i32, 64),
)]),
(0, low) => Some(vec![TrackImportEffect::ChannelVolumeSlideN(
Q15::from_ratio(-(low as i32), 64),
)]),
(f, 0xF) => Some(vec![TrackImportEffect::ChannelVolumeSlide0(
Q15::from_ratio(f as i32, 64),
)]),
(0xF, low) => Some(vec![TrackImportEffect::ChannelVolumeSlide0(
Q15::from_ratio(-(low as i32), 64),
)]),
_ => None,
}
}
0x0F => Some(vec![TrackImportEffect::InstrumentSampleOffset(
current.effect_parameter as usize * 256,
)]),
0x10 => {
let fx = current.effect_parameter;
let nibble_high = (fx & 0xF0) >> 4;
let nibble_low = fx & 0x0F;
match (nibble_high, nibble_low) {
(f, 0) => Some(vec![TrackImportEffect::PanningSlideN(Q15::from_ratio(
f as i32, 16,
))]),
(0, f) => Some(vec![TrackImportEffect::PanningSlideN(Q15::from_ratio(
-(f as i32),
16,
))]),
(f, 0xF) => Some(vec![TrackImportEffect::PanningSlide0(Q15::from_ratio(
f as i32, 16,
))]),
(0xF, f) => Some(vec![TrackImportEffect::PanningSlide0(Q15::from_ratio(
-(f as i32),
16,
))]),
_ => None,
}
}
0x11 => {
let param = current.effect_parameter;
let vol = param >> 4;
let speed = param & 0x0F;
Some(vec![TrackImportEffect::NoteRetrigExtendedS3m(
speed as usize,
vol as usize,
)])
}
0x12 => {
let param = current.effect_parameter;
let speed = lfo_speed_from_nibble_64((param & 0xF0) >> 4);
let depth = lfo_depth_from_nibble_16(param & 0x0F);
Some(vec![TrackImportEffect::Tremolo(speed, depth)])
}
0x13 => {
let fx = current.effect_parameter >> 4;
let param = current.effect_parameter & 0xF;
match fx {
0x3 => {
let waveform = match param & 0b0000_0011 {
1 => Waveform::RampDown,
2 => Waveform::Square,
3 => Waveform::Random,
_ => Waveform::Sine,
};
Some(vec![TrackImportEffect::VibratoWaveform(waveform, true)])
}
0x4 => {
let waveform = match param & 0b0000_0011 {
1 => Waveform::RampDown,
2 => Waveform::Square,
3 => Waveform::Random,
_ => Waveform::Sine,
};
Some(vec![TrackImportEffect::TremoloWaveform(waveform, true)])
}
0x5 => {
let waveform = match param & 0b0000_0011 {
1 => Waveform::RampDown,
2 => Waveform::Square,
3 => Waveform::Random,
_ => Waveform::Sine,
};
Some(vec![TrackImportEffect::PanbrelloWaveform(waveform, true)])
}
0x7 => {
match param {
0x0 => Some(vec![TrackImportEffect::NoteCut(0, true)]),
0x1 => Some(vec![TrackImportEffect::NoteOff(0, true)]),
0x2 => Some(vec![TrackImportEffect::NoteFadeOut(0, true)]),
0x3 => Some(vec![TrackImportEffect::InstrumentNewNoteAction(
NewNoteAction::NoteCut,
)]),
0x4 => Some(vec![TrackImportEffect::InstrumentNewNoteAction(
NewNoteAction::Continue,
)]),
0x5 => Some(vec![TrackImportEffect::InstrumentNewNoteAction(
NewNoteAction::NoteOff,
)]),
0x6 => Some(vec![TrackImportEffect::InstrumentNewNoteAction(
NewNoteAction::NoteFadeOut,
)]),
0x7 => Some(vec![TrackImportEffect::InstrumentVolumeEnvelope(true)]),
0x8 => Some(vec![TrackImportEffect::InstrumentVolumeEnvelope(false)]),
0x9 => Some(vec![TrackImportEffect::InstrumentPanningEnvelope(true)]),
0xA => Some(vec![TrackImportEffect::InstrumentPanningEnvelope(false)]),
0xB => Some(vec![TrackImportEffect::InstrumentPitchEnvelope(true)]),
0xC => Some(vec![TrackImportEffect::InstrumentPitchEnvelope(false)]),
_ => None,
}
}
0x8 => Some(vec![TrackImportEffect::Panning(Panning::from_ratio(
param as i32,
15,
))]),
0x9 => Some(vec![TrackImportEffect::InstrumentSurround(param == 1)]),
0xA => Some(vec![TrackImportEffect::InstrumentSampleOffsetAddHigh(
param as usize * 65536,
)]),
0xC => Some(vec![TrackImportEffect::NoteCut(param as usize, false)]),
0xD => Some(vec![TrackImportEffect::NoteDelay(param as usize)]),
_ => None,
}
}
0x15 => {
let param = current.effect_parameter;
let speed = lfo_speed_from_nibble_64((param & 0xF0) >> 4);
let depth = vibrato_depth_from_nibble_16(param & 0x0F);
Some(vec![TrackImportEffect::Vibrato(speed, depth)])
}
0x18 => Some(vec![TrackImportEffect::Panning(Panning::from_byte_255(
current.effect_parameter,
))]),
0x19 => {
let param = current.effect_parameter;
let speed = Q8_8::from_raw(((param & 0xF0) >> 4) as i16);
let depth = it_panbrello_depth_from_nibble_4(param & 0x0F);
Some(vec![TrackImportEffect::Panbrello(speed, depth)])
}
_ => None,
}
}
pub fn parse_global_it_effect(current: &PatternSlot) -> Option<GlobalEffect> {
match current.effect_type {
0x01 => {
let speed = current.effect_parameter as usize;
if speed == 0 {
None
} else {
Some(GlobalEffect::Speed(speed))
}
}
0x02 => Some(GlobalEffect::PositionJump(
current.effect_parameter as usize,
)),
0x03 => Some(GlobalEffect::PatternBreak(
current.effect_parameter as usize,
)),
0x13 => {
let fx = current.effect_parameter >> 4;
let param = current.effect_parameter & 0xF;
match fx {
0x6 => Some(GlobalEffect::PatternDelay {
quantity: param as usize,
tempo: false,
}),
0xB => Some(GlobalEffect::PatternLoop(param as usize)),
0xE => Some(GlobalEffect::PatternDelay {
quantity: param as usize,
tempo: true,
}),
0xF => Some(GlobalEffect::MidiMacro(MidiMacroType::SelectParametric(
param as usize,
))),
_ => None,
}
}
0x14 => {
let param = current.effect_parameter;
let upper_nibble = param >> 4;
let lower_nibble = param & 0x0F;
match (upper_nibble, lower_nibble) {
(0, f) => Some(GlobalEffect::BpmSlide(-(f as isize))),
(1, f) => Some(GlobalEffect::BpmSlide(f as isize)),
_ => Some(GlobalEffect::Bpm(param as usize)),
}
}
0x16 => Some(GlobalEffect::Volume(Volume::from_ratio(
current.effect_parameter.min(128) as i32,
128,
))),
0x17 => {
let fx = current.effect_parameter;
let nibble_high = (fx & 0xF0) >> 4;
let nibble_low = fx & 0x0F;
match (nibble_high, nibble_low) {
(f, 0) => Some(GlobalEffect::VolumeSlide {
speed: Q15::from_ratio(f as i32, 128),
fine: false,
}),
(0, f) => Some(GlobalEffect::VolumeSlide {
speed: Q15::from_ratio(-(f as i32), 128),
fine: false,
}),
(f, 0xF) => Some(GlobalEffect::VolumeSlide {
speed: Q15::from_ratio(f as i32, 128),
fine: true,
}),
(0xF, f) => Some(GlobalEffect::VolumeSlide {
speed: Q15::from_ratio(-(f as i32), 128),
fine: true,
}),
_ => None,
}
}
0x1A => {
let param = current.effect_parameter;
if param & 0x80 == 0 {
Some(GlobalEffect::MidiMacro(MidiMacroType::Parametric(param)))
} else {
Some(GlobalEffect::MidiMacro(MidiMacroType::Fixed {
idx: (param - 0x80) as usize,
z: param,
}))
}
}
_ => None,
}
}
fn parse_it_volume(freq_type: FrequencyType, slot: &PatternSlot) -> Option<TrackImportEffect> {
if slot.volume > 212 {
return None;
}
if slot.volume <= 64 {
return Some(TrackImportEffect::Volume(
Volume::from_byte_64(slot.volume),
0,
));
}
if (slot.volume & 0x7F) <= 64 {
let p = Panning::from_byte_64(slot.volume & 0x7F);
return Some(TrackImportEffect::Panning(p));
}
let temp = if slot.volume & 0x80 == 0 {
slot.volume - b'A'
} else {
(slot.volume & 0x7F) - 5
};
let fx = temp / 10;
let param = temp % 10;
match fx {
0x0 => {
return Some(TrackImportEffect::VolumeSlide0(Q15::from_ratio(
param as i32,
64,
)))
} 0x1 => {
return Some(TrackImportEffect::VolumeSlide0(Q15::from_ratio(
-(param as i32),
64,
)))
} 0x2 => {
return Some(TrackImportEffect::VolumeSlideN(Q15::from_ratio(
param as i32,
64,
)))
} 0x3 => {
return Some(TrackImportEffect::VolumeSlideN(Q15::from_ratio(
-(param as i32),
64,
)))
}
0x4 => return Some(TrackImportEffect::PortamentoDown(16 * param as i16)), 0x5 => return Some(TrackImportEffect::PortamentoUp(-16 * param as i16)), 0x6 => {
let param: i16 = match param {
1 => 1,
2 => 4,
3 => 8,
4 => 16,
5 => 32,
6 => 64,
7 => 96,
8 => 128,
9 => 255,
_ => 0,
};
let speed: i16 = match freq_type {
FrequencyType::LinearFrequencies => 4 * param,
FrequencyType::AmigaFrequencies => param,
};
return Some(TrackImportEffect::TonePortamento(speed));
}
0x7 => {
let depth = it_vibrato_depth_from_nibble_4(param & 0x0F);
return Some(TrackImportEffect::Vibrato(Q8_8::ZERO, depth));
}
_ => {}
}
None
}
pub fn it_unpack_pattern(
freq_type: FrequencyType,
pattern: &[Vec<PatternSlot>],
) -> Vec<Vec<TrackImportUnit>> {
pattern
.iter()
.map(|channel| {
channel
.iter()
.map(|slot| {
let te = Self::parse_it_effect(freq_type, slot);
let ve = Self::parse_it_volume(freq_type, slot);
let cc = Self::parse_global_it_effect(slot);
let mut tiu = TrackImportUnit::default();
tiu.note = slot.note;
tiu.instrument = slot.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
})
.collect::<Vec<TrackImportUnit>>() })
.collect::<Vec<Vec<TrackImportUnit>>>() }
}