use serde::{Deserialize, Serialize};
use crate::daw::automation::AutomationLane;
use crate::daw::clip::Clip;
use crate::daw::sorted_clips::SortedClips;
use crate::daw::timeline::TimelineMap;
use crate::daw::track::Track;
use crate::fixed::units::{ChannelVolume, Panning, Period, Volume};
use crate::instrument::Instrument;
use crate::mix_plugin::MixPlugins;
use crate::period_helper::FrequencyType;
use crate::prelude::TrackUnit;
use alloc::string::String;
use alloc::string::ToString;
use alloc::{vec, vec::Vec};
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ModuleFormat {
#[default]
Unknown,
Mod,
S3m,
Xm,
It,
}
impl ModuleFormat {
#[inline]
pub fn is_xm(&self) -> bool {
matches!(self, ModuleFormat::Xm)
}
#[inline]
pub fn is_s3m(&self) -> bool {
matches!(self, ModuleFormat::S3m)
}
#[inline]
pub fn is_it(&self) -> bool {
matches!(self, ModuleFormat::It)
}
#[inline]
pub fn is_mod(&self) -> bool {
matches!(self, ModuleFormat::Mod)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Default)]
pub struct PlaybackQuirks {
pub ft2_pitch_slide_overflow: bool,
pub period_clamp: Option<(Period, Period)>,
pub allow_zero_period: bool,
pub ft2_arpeggio_lut: bool,
pub ft2_arpeggio_note_clamp: bool,
pub keyoff_cuts_without_vol_env: bool,
pub k00_eats_note: bool,
pub volcol_b_advances_vibrato: bool,
pub e60_leaks_to_next_pattern: bool,
pub pattern_loop_resumes: bool,
pub tremor_state_persists: bool,
pub it_old_effects: bool,
pub it_link_gxx_memory: bool,
pub pan_reset_policy: PanResetPolicy,
pub it_vibrato_ticks_at_row_zero: bool,
pub sample_volume_is_static_gain: bool,
pub speed_zero_ends_song: bool,
}
#[derive(Default, Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
pub enum PanResetPolicy {
#[default]
Always,
OnInstrumentChange,
Never,
}
#[derive(Default, Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
pub struct ChannelDefault {
pub panning: Option<Panning>,
pub volume: Option<ChannelVolume>,
pub muted: bool,
pub surround: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
pub struct PatternHighlight {
pub beat: u8,
pub measure: u8,
}
impl Default for PatternHighlight {
fn default() -> Self {
Self {
beat: 4,
measure: 16,
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Module {
pub name: String,
pub comment: String,
pub profile: crate::compatibility_profile::CompatibilityProfile,
pub frequency_type: FrequencyType,
pub default_tempo: usize,
pub default_bpm: usize,
pub channel_names: Vec<String>,
pub channel_defaults: Vec<ChannelDefault>,
pub instrument: Vec<Instrument>,
pub midi_macros: Option<MidiMacros>,
pub pattern_highlight: PatternHighlight,
pub mix_plugins: Option<MixPlugins>,
pub pitch_wheel_depth: u8,
pub mix_volume: Volume,
#[serde(default)]
pub tracks: Vec<Track>,
#[serde(default)]
pub clips: SortedClips,
#[serde(default)]
pub automation: Vec<AutomationLane>,
#[serde(default)]
pub timeline_map: TimelineMap,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct MidiMacros {
pub global: Vec<Vec<u8>>,
pub parametric: Vec<Vec<u8>>,
pub fixed: Vec<Vec<u8>>,
}
impl Default for Module {
fn default() -> Self {
Module {
name: "".to_string(),
comment: "".to_string(),
profile: crate::compatibility_profile::CompatibilityProfile::default(),
frequency_type: FrequencyType::LinearFrequencies,
default_tempo: 6,
default_bpm: 125,
channel_names: vec![],
channel_defaults: vec![],
instrument: vec![],
midi_macros: None,
pattern_highlight: PatternHighlight::default(),
mix_plugins: None,
pitch_wheel_depth: 2,
mix_volume: Volume::FULL,
tracks: vec![],
clips: SortedClips::new(),
automation: vec![],
timeline_map: TimelineMap::default(),
}
}
}
impl Module {
pub fn get_num_channels(&self) -> usize {
self.clips
.iter()
.map(|c| c.target_channel as usize + 1)
.max()
.unwrap_or(0)
}
pub fn song_length(&self, song: usize) -> usize {
self.timeline_map.order_count(song)
}
pub fn order_pattern_idx(&self, song: usize, order_idx: usize) -> Option<usize> {
self.timeline_map
.find_order_entry(song, order_idx)
.map(|e| e.pattern_idx as usize)
}
pub fn pattern_row_count(&self, pat_idx: usize) -> usize {
self.timeline_map.row_count_in_pattern(pat_idx)
}
pub fn find_overlapping_clip(&self, candidate: &Clip, ignore: &[u32]) -> Option<u32> {
let range = self
.clips
.lane_range(candidate.song, candidate.target_channel);
let lane_start = range.start;
let cand_end = candidate.end_tick(self);
for (offset, existing) in self.clips.as_slice()[range].iter().enumerate() {
let global_idx = (lane_start + offset) as u32;
if ignore.contains(&global_idx) {
continue;
}
let existing_end = existing.end_tick(self);
let disjoint =
cand_end <= existing.position_tick || existing_end <= candidate.position_tick;
if !disjoint {
return Some(global_idx);
}
}
None
}
pub fn row_at(
&self,
song: usize,
pat_idx: usize,
row_idx: usize,
) -> alloc::vec::Vec<TrackUnit> {
let num_channels = self.get_num_channels();
let mut out: alloc::vec::Vec<TrackUnit> =
(0..num_channels).map(|_| TrackUnit::default()).collect();
let abs_tick = match self.timeline_map.find_entry(song, pat_idx, row_idx) {
Some(e) => e.tick,
None => return out,
};
let song_u16 = song as u16;
let row_u32 = row_idx as u32;
for (ch_idx, out_cell) in out.iter_mut().enumerate() {
let Some((_, clip)) = self.clips.active_at(song_u16, ch_idx as u8, abs_tick) else {
continue;
};
if abs_tick >= clip.end_tick {
continue;
}
if row_u32 < clip.source_start_row {
continue;
}
let row_in_track = row_u32 - clip.source_start_row;
if row_in_track < clip.track_row_offset {
continue;
}
let track = match clip.track_in(self) {
Some(t) => t,
None => continue,
};
if let Some(crate::instrument::InstrumentType::Euclidean(ekn)) =
self.instrument.get(track.instrument).map(|i| &i.instr_type)
{
let steps = ekn.steps.max(1) as u32;
let pos = (row_in_track) % steps;
let rot = (ekn.rotation as u32) % steps;
let bjork_idx = ((pos + steps - rot) % steps) as usize;
let canonical = crate::daw::euclidean::euclidean_pattern(ekn.events, ekn.steps);
if canonical.get(bjork_idx).copied().unwrap_or(false) {
if let Some(proto) = track.rows.first() {
*out_cell = proto.clone();
}
}
continue;
}
if (row_in_track as usize) >= track.rows.len() {
continue;
}
*out_cell = track.rows[row_in_track as usize].clone();
}
out
}
pub fn verify_layers_consistent(&self) -> Result<(), LayerInconsistency> {
for w in self.clips.as_slice().windows(2) {
let a = (w[0].song, w[0].target_channel, w[0].position_tick);
let b = (w[1].song, w[1].target_channel, w[1].position_tick);
if a > b {
return Err(LayerInconsistency::ClipsUnsorted);
}
}
for (i, clip) in self.clips.iter().enumerate() {
if clip.track as usize >= self.tracks.len() {
return Err(LayerInconsistency::ClipTrackOutOfRange {
clip_idx: i,
track: clip.track,
});
}
}
for i in 0..self.clips.len() {
let ci = &self.clips[i];
let end_i = ci.end_tick(self);
for j in (i + 1)..self.clips.len() {
let cj = &self.clips[j];
if cj.song != ci.song || cj.target_channel != ci.target_channel {
continue;
}
if cj.position_tick >= end_i {
break;
}
return Err(LayerInconsistency::ClipsOverlap {
clip_a: i,
clip_b: j,
});
}
}
for (i, t) in self.tracks.iter().enumerate() {
if t.instrument == crate::daw::build_timeline::EFFECT_ONLY_INSTRUMENT {
for (r, cell) in t.rows.iter().enumerate() {
if cell.instrument.is_some() {
return Err(LayerInconsistency::TrackInstrumentMismatch {
track_idx: i,
row: r,
});
}
}
continue;
}
if t.instrument >= self.instrument.len() && !self.instrument.is_empty() {
return Err(LayerInconsistency::TrackInstrumentMismatch {
track_idx: i,
row: 0,
});
}
for (r, cell) in t.rows.iter().enumerate() {
if let Some(instr) = cell.instrument {
if instr != t.instrument {
return Err(LayerInconsistency::TrackInstrumentMismatch {
track_idx: i,
row: r,
});
}
}
}
}
for (i, lane) in self.automation.iter().enumerate() {
let mut last_tick: Option<u32> = None;
for (j, p) in lane.points.iter().enumerate() {
if !p.value.is_compatible_with(lane.target) {
return Err(LayerInconsistency::AutomationTypeMismatch {
lane_idx: i,
point_idx: j,
});
}
if let Some(lt) = last_tick {
if p.tick < lt {
return Err(LayerInconsistency::AutomationPointsUnsorted { lane_idx: i });
}
}
last_tick = Some(p.tick);
}
}
let mut last_song: u16 = 0;
let mut last_tick: u32 = 0;
let mut first = true;
for (i, e) in self.timeline_map.entries.iter().enumerate() {
if first || e.song != last_song {
first = false;
last_song = e.song;
last_tick = e.tick;
continue;
}
if e.tick < last_tick {
return Err(LayerInconsistency::TimelineMapMismatch { entry_idx: i });
}
last_tick = e.tick;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LayerInconsistency {
ClipsUnsorted,
ClipTrackOutOfRange { clip_idx: usize, track: u32 },
ClipsOverlap { clip_a: usize, clip_b: usize },
TimelineMapMismatch { entry_idx: usize },
AutomationTypeMismatch { lane_idx: usize, point_idx: usize },
AutomationPointsUnsorted { lane_idx: usize },
TrackInstrumentMismatch { track_idx: usize, row: usize },
}