torque_tracker_engine/project/
song.rs

1use std::array;
2use std::fmt::{Debug, Formatter};
3use std::num::NonZero;
4
5use super::pattern::{Pattern, PatternOperation};
6use crate::channel::Pan;
7use crate::file::impulse_format;
8use crate::file::impulse_format::header::PatternOrder;
9use crate::manager::Collector;
10use crate::sample::{Sample, SampleMetaData};
11
12#[derive(Clone, Debug)]
13pub struct Song {
14    pub global_volume: u8,
15    pub mix_volume: u8,
16    /// Speed specifies how many ticks are in one row. This reduces tempo, but increases resolution of some effects.
17    pub initial_speed: NonZero<u8>,
18    /// Tempo determines how many ticks are in one second with the following formula: tempo/2 = ticks per second.
19    pub initial_tempo: NonZero<u8>,
20    pub pan_separation: u8,
21    pub pitch_wheel_depth: u8,
22
23    pub patterns: [Pattern; Song::MAX_PATTERNS],
24    pub pattern_order: [PatternOrder; Song::MAX_ORDERS],
25    pub volume: [u8; Song::MAX_CHANNELS],
26    pub pan: [Pan; Song::MAX_CHANNELS],
27    pub samples: [Option<(SampleMetaData, Sample)>; Song::MAX_SAMPLES_INSTR],
28}
29
30impl Song {
31    pub const MAX_ORDERS: usize = 256;
32    pub const MAX_PATTERNS: usize = 240;
33    pub const MAX_SAMPLES_INSTR: usize = 236;
34    pub const MAX_CHANNELS: usize = 64;
35
36    /// order value shouldn't be modified outside of this function.
37    /// This moves it forward correctly and returns the pattern to be played
38    pub fn next_pattern(&self, order: &mut u16) -> Option<u8> {
39        loop {
40            match self.get_order(*order) {
41                PatternOrder::Number(pattern) => break Some(pattern),
42                PatternOrder::EndOfSong => break None,
43                PatternOrder::SkipOrder => (),
44            }
45            *order += 1;
46        }
47    }
48
49    /// out of bounds is EndOfSong
50    pub(crate) fn get_order(&self, order: u16) -> PatternOrder {
51        self.pattern_order
52            .get(usize::from(order))
53            .copied()
54            .unwrap_or_default()
55    }
56
57    /// takes the values that are included in Song from the header and write them to the song.
58    ///
59    /// Mostly applies to metadata about the song.
60    /// Samples, patterns, instruments are not filled as they are not included in the header
61    pub fn copy_values_from_header(&mut self, header: &impulse_format::header::ImpulseHeader) {
62        self.global_volume = header.global_volume;
63        // TODO: figure out if i want to error here or when parsing the header
64        self.initial_speed = NonZero::new(header.initial_speed).unwrap();
65        self.initial_tempo = NonZero::new(header.initial_tempo).unwrap();
66        self.mix_volume = header.mix_volume;
67        self.pan_separation = header.pan_separation;
68        self.pitch_wheel_depth = header.pitch_wheel_depth;
69
70        self.pan = header.channel_pan;
71        self.volume = header.channel_volume;
72
73        for (idx, order) in header.orders.iter().enumerate() {
74            self.pattern_order[idx] = *order;
75        }
76    }
77
78    /// debug like impl which isn't as long by cutting down a lot of information
79    pub fn dbg_relevant(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
80        write!(f, "global_volume: {}, ", self.global_volume)?;
81        write!(f, "mix_volume: {}, ", self.mix_volume)?;
82        write!(f, "initial_speed: {}, ", self.initial_speed)?;
83        write!(f, "initial_tempo: {}, ", self.initial_tempo)?;
84        write!(f, "pan_seperation: {}, ", self.pan_separation)?;
85        write!(f, "pitch_wheel_depth: {}, ", self.pitch_wheel_depth)?;
86        write!(
87            f,
88            "{} not empty patterns, ",
89            self.patterns.iter().filter(|p| !p.is_empty()).count()
90        )?;
91        write!(
92            f,
93            "{} orders, ",
94            self.pattern_order
95                .iter()
96                .filter(|o| **o != PatternOrder::EndOfSong)
97                .count()
98        )?;
99        Ok(())
100    }
101}
102
103impl Default for Song {
104    fn default() -> Self {
105        Self {
106            global_volume: 128,
107            mix_volume: Default::default(),
108            initial_speed: NonZero::new(6).unwrap(),
109            initial_tempo: NonZero::new(125).unwrap(),
110            pan_separation: 128,
111            pitch_wheel_depth: Default::default(),
112            patterns: array::from_fn(|_| Pattern::default()),
113            pattern_order: array::from_fn(|_| PatternOrder::default()),
114            volume: array::from_fn(|_| 64),
115            pan: array::from_fn(|_| Pan::default()),
116            samples: array::from_fn(|_| None),
117        }
118    }
119}
120
121// On change: also change ValidOperation
122#[derive(Clone, Debug)]
123pub enum SongOperation {
124    SetVolume(u8, u8),
125    SetPan(u8, Pan),
126    SetSample(u8, SampleMetaData, Sample),
127    RemoveSample(u8),
128    PatternOperation(u8, PatternOperation),
129    SetOrder(u16, PatternOrder),
130    SetInitialSpeed(NonZero<u8>),
131    SetInitialTempo(NonZero<u8>),
132    SetGlobalVol(u8),
133}
134
135/// keep in sync with SongOperation
136#[derive(Clone, Debug)]
137pub(crate) enum ValidOperation {
138    SetVolume(u8, u8),
139    SetPan(u8, Pan),
140    SetSample(u8, SampleMetaData, Sample),
141    RemoveSample(u8),
142    PatternOperation(u8, PatternOperation),
143    SetOrder(u16, PatternOrder),
144    SetInitialSpeed(NonZero<u8>),
145    SetInitialTempo(NonZero<u8>),
146    SetGlobalVol(u8),
147}
148
149impl ValidOperation {
150    pub(crate) fn new(
151        op: SongOperation,
152        handle: &mut Collector,
153        song: &Song,
154    ) -> Result<ValidOperation, SongOperation> {
155        let valid = match op {
156            SongOperation::SetVolume(c, _) => usize::from(c) < Song::MAX_CHANNELS,
157            SongOperation::SetPan(c, _) => usize::from(c) < Song::MAX_CHANNELS,
158            SongOperation::SetSample(idx, _, _) => usize::from(idx) < Song::MAX_SAMPLES_INSTR,
159            SongOperation::RemoveSample(idx) => usize::from(idx) < Song::MAX_SAMPLES_INSTR,
160            SongOperation::PatternOperation(idx, op) => match song.patterns.get(usize::from(idx)) {
161                Some(pattern) => pattern.operation_is_valid(&op),
162                None => false,
163            },
164            SongOperation::SetOrder(idx, _) => usize::from(idx) < Song::MAX_ORDERS,
165            SongOperation::SetInitialSpeed(_) => true,
166            SongOperation::SetInitialTempo(_) => true,
167            SongOperation::SetGlobalVol(_) => true,
168        };
169
170        if valid {
171            Ok(match op {
172                SongOperation::SetVolume(c, v) => Self::SetVolume(c, v),
173                SongOperation::SetPan(c, pan) => Self::SetPan(c, pan),
174                SongOperation::SetSample(i, meta_data, sample) => {
175                    handle.add_sample(sample.clone());
176                    Self::SetSample(i, meta_data, sample)
177                }
178                SongOperation::RemoveSample(i) => Self::RemoveSample(i),
179                SongOperation::PatternOperation(i, pattern_operation) => {
180                    Self::PatternOperation(i, pattern_operation)
181                }
182                SongOperation::SetOrder(i, pattern_order) => Self::SetOrder(i, pattern_order),
183                SongOperation::SetInitialSpeed(s) => Self::SetInitialSpeed(s),
184                SongOperation::SetInitialTempo(t) => Self::SetInitialTempo(t),
185                SongOperation::SetGlobalVol(v) => Self::SetGlobalVol(v),
186            })
187        } else {
188            Err(op)
189        }
190    }
191}
192
193impl simple_left_right::Absorb<ValidOperation> for Song {
194    fn absorb(&mut self, operation: ValidOperation) {
195        match operation {
196            ValidOperation::SetVolume(i, val) => self.volume[usize::from(i)] = val,
197            ValidOperation::SetPan(i, val) => self.pan[usize::from(i)] = val,
198            ValidOperation::SetSample(i, meta, sample) => {
199                self.samples[usize::from(i)] = Some((meta, sample))
200            }
201            ValidOperation::RemoveSample(i) => self.samples[usize::from(i)] = None,
202            ValidOperation::PatternOperation(i, op) => {
203                self.patterns[usize::from(i)].apply_operation(op)
204            }
205            ValidOperation::SetOrder(i, order) => self.pattern_order[usize::from(i)] = order,
206            ValidOperation::SetInitialSpeed(s) => self.initial_speed = s,
207            ValidOperation::SetInitialTempo(t) => self.initial_tempo = t,
208            ValidOperation::SetGlobalVol(v) => self.global_volume = v,
209        }
210    }
211}