use serde::{Deserialize, Serialize};
use crate::core::cell::Cell;
use crate::core::compatibility::PlaybackQuirks;
use crate::core::daw::automation::AutomationLane;
use crate::core::daw::clip::Clip;
use crate::core::daw::sorted_clips::SortedClips;
use crate::core::daw::timeline::TimelineMap;
use crate::core::daw::track::Track;
use crate::core::fixed::units::{ChannelVolume, Panning, Volume};
use crate::core::instrument::Instrument;
#[cfg(feature = "import_it")]
use crate::tracker::mix_plugin::MixPlugins;
use crate::tracker::period::FrequencyType;
use alloc::string::String;
use alloc::string::ToString;
use alloc::{vec, vec::Vec};
#[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 quirks: PlaybackQuirks,
#[cfg(any(
feature = "import_mod",
feature = "import_xm",
feature = "import_s3m",
feature = "import_it",
feature = "import_sid",
))]
pub origin: Option<crate::tracker::format::ModuleFormat>,
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,
#[cfg(feature = "import_it")]
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,
#[serde(default)]
pub song_loop_to: Option<u32>,
}
#[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(),
quirks: PlaybackQuirks::default(),
#[cfg(any(
feature = "import_mod",
feature = "import_xm",
feature = "import_s3m",
feature = "import_it",
feature = "import_sid",
))]
origin: None,
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(),
#[cfg(feature = "import_it")]
mix_plugins: None,
pitch_wheel_depth: 2,
mix_volume: Volume::FULL,
tracks: vec![],
clips: SortedClips::new(),
automation: vec![],
timeline_map: TimelineMap::default(),
song_loop_to: None,
}
}
}
impl Module {
pub fn copy_block(
&self,
tracks: &[u32],
row_start: u32,
row_end: u32,
) -> alloc::vec::Vec<alloc::vec::Vec<Cell>> {
use alloc::vec::Vec;
if row_end <= row_start {
return Vec::new();
}
let len = (row_end - row_start) as usize;
tracks
.iter()
.map(|&t| {
let mut col: Vec<Cell> = Vec::with_capacity(len);
let track = self.tracks.get(t as usize);
for r in row_start..row_end {
let cell = track.map(|tr| tr.cell_at(r)).unwrap_or_default();
col.push(cell);
}
col
})
.collect()
}
pub fn lane_for(
&self,
target: crate::core::daw::automation::AutomationTarget,
) -> Option<&AutomationLane> {
self.automation.iter().find(|l| l.target == target)
}
pub fn lanes_for<'a>(
&'a self,
target: crate::core::daw::automation::AutomationTarget,
) -> impl Iterator<Item = &'a AutomationLane> + 'a {
self.automation.iter().filter(move |l| l.target == target)
}
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<(Cell, Option<usize>)> {
let num_channels = self.get_num_channels();
let mut out: alloc::vec::Vec<(Cell, Option<usize>)> =
(0..num_channels).map(|_| (Cell::default(), None)).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, slot) 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,
};
let track_instr_idx = track.instrument();
let track_instr =
if track_instr_idx == crate::tracker::import::build::EFFECT_ONLY_INSTRUMENT {
None
} else {
Some(track_instr_idx)
};
match track {
Track::Notes { rows, .. } => {
if (row_in_track as usize) >= rows.len() {
continue;
}
slot.0 = rows[row_in_track as usize].clone();
slot.1 = track_instr;
}
Track::Euclidean {
events,
steps,
rotation,
prototype,
..
} => {
let steps_u32 = (*steps).max(1) as u32;
let pos = row_in_track % steps_u32;
let rot = (*rotation as u32) % steps_u32;
let bjork_idx = ((pos + steps_u32 - rot) % steps_u32) as usize;
let canonical = crate::core::daw::euclidean::euclidean_pattern(*events, *steps);
if canonical.get(bjork_idx).copied().unwrap_or(false) {
slot.0 = prototype.clone();
slot.1 = track_instr;
}
}
}
}
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() {
let instr = t.instrument();
if instr == crate::tracker::import::build::EFFECT_ONLY_INSTRUMENT {
continue;
}
if instr >= self.instrument.len() {
return Err(LayerInconsistency::TrackInstrumentMismatch {
track_idx: i,
row: 0,
});
}
}
for (i, lane) in self.automation.iter().enumerate() {
if let Some(points) = lane.points() {
let mut last_tick: Option<u32> = None;
for (j, p) in 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,
},
}