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