1use super::prepared::BeatClock;
3use super::{Duty, NoiseMode, Oscillator, Pitch, Waveform};
4use crate::{Error, Limits, Result};
5
6pub const MAX_MACRO_VALUES: usize = 64;
7pub const MAX_SAMPLE_VALUES: usize = 65_536;
8pub const NTSC_NOISE_PERIODS: [u16; 16] = [
10 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068,
11];
12pub const NTSC_CLOCK: u32 = 1_789_773;
14
15#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
18pub enum MacroStep {
19 Frames { frames: u16, hz: u16 },
21 Ticks(u16),
23}
24impl Default for MacroStep {
25 fn default() -> Self {
26 Self::Frames { frames: 1, hz: 60 }
27 }
28}
29
30#[derive(Clone, Debug, Eq, Hash, PartialEq)]
35pub struct Macro {
36 pub values: Vec<i16>,
37 pub step: MacroStep,
38 pub loop_start: Option<usize>,
39}
40impl Macro {
41 pub fn new(values: Vec<i16>, step: MacroStep) -> Result<Self> {
42 let m = Self {
43 values,
44 step,
45 loop_start: None,
46 };
47 m.validate()?;
48 Ok(m)
49 }
50 pub fn validate(&self) -> Result<()> {
51 if self.values.is_empty()
52 || self.values.len() > MAX_MACRO_VALUES
53 || self.loop_start.is_some_and(|i| i >= self.values.len())
54 {
55 return Err(Error::invalid(
56 "A macro needs 1..=64 values and an in-range loop start",
57 ));
58 }
59 let valid = match self.step {
60 MacroStep::Frames { frames, hz } => {
61 (1..=1000).contains(&frames) && (1..=1000).contains(&hz)
62 }
63 MacroStep::Ticks(t) => (1..=960).contains(&t),
64 };
65 if !valid {
66 return Err(Error::invalid("Invalid macro clock interval"));
67 }
68 Ok(())
69 }
70 pub fn value_at(
74 &self,
75 frame: u64,
76 sample_rate: u32,
77 tempo: u16,
78 ticks_per_quarter: u16,
79 ) -> Result<i16> {
80 self.validate()?;
81 Limits::default().check(0, 1, sample_rate)?;
82 if !(1..=999).contains(&tempo) || !(1..=960).contains(&ticks_per_quarter) {
83 return Err(Error::invalid("Invalid macro score clock"));
84 }
85 Ok(self.value(frame, sample_rate, tempo, ticks_per_quarter))
86 }
87 fn value(&self, age: u64, rate: u32, tempo: u16, ticks: u16) -> i16 {
88 self.value_clock(age, rate, tempo, ticks, None)
89 }
90 fn value_clock(
91 &self,
92 age: u64,
93 rate: u32,
94 tempo: u16,
95 ticks: u16,
96 clock: Option<BeatClock>,
97 ) -> i16 {
98 let index = match self.step {
99 MacroStep::Frames { frames, hz } => {
100 age as u128 * hz as u128 / (rate as u128 * frames as u128)
101 }
102 MacroStep::Ticks(step) => clock.map_or_else(
103 || age as u128 * tempo as u128 * ticks as u128 / (rate as u128 * 60 * step as u128),
104 |c| c.numerator * ticks as u128 / (c.denominator * step as u128),
105 ),
106 };
107 let n = self.values.len();
108 let index = if index < n as u128 {
109 index as usize
110 } else if let Some(start) = self.loop_start {
111 start + ((index - start as u128) % (n - start) as u128) as usize
112 } else {
113 n - 1
114 };
115 self.values[index]
116 }
117}
118
119#[derive(Clone, Debug, Eq, Hash, PartialEq)]
122pub struct Wavetable {
123 pub values: Vec<u8>,
124}
125impl Wavetable {
126 pub fn new(values: Vec<u8>) -> Result<Self> {
127 let table = Self { values };
128 table.validate()?;
129 Ok(table)
130 }
131 pub fn validate(&self) -> Result<()> {
132 if !(2..=256).contains(&self.values.len()) || self.values.iter().any(|&v| v > 15) {
133 return Err(Error::invalid("A wavetable needs 2..=256 four-bit values"));
134 }
135 Ok(())
136 }
137}
138
139#[derive(Clone, Debug, Eq, Hash, PartialEq)]
143pub struct ChipSample {
144 pub values: Vec<u8>,
145 pub bits: u8,
146 pub sample_rate: u32,
148 pub root: Pitch,
149 pub looping: bool,
150}
151impl ChipSample {
152 pub fn new(values: Vec<u8>, bits: u8, sample_rate: u32) -> Result<Self> {
153 let sample = Self {
154 values,
155 bits,
156 sample_rate,
157 root: Pitch::C4,
158 looping: false,
159 };
160 sample.validate()?;
161 Ok(sample)
162 }
163 pub fn validate(&self) -> Result<()> {
164 if self.values.is_empty()
165 || self.values.len() > MAX_SAMPLE_VALUES
166 || !(2..=8).contains(&self.bits)
167 || !(100..=32_000).contains(&self.sample_rate)
168 {
169 return Err(Error::invalid(
170 "PCM needs 1..=65536 values, 2..=8 bits and 100..=32000 Hz",
171 ));
172 }
173 let maximum = (1u16 << self.bits) - 1;
174 if self.values.iter().any(|&v| v as u16 > maximum) {
175 return Err(Error::invalid("PCM value exceeds its bit depth"));
176 }
177 Ok(())
178 }
179}
180
181#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
185pub enum PitchTable {
186 NtscPulse,
187 NtscTriangle,
188}
189impl PitchTable {
190 pub fn timer(self, frequency: f64) -> Result<u16> {
191 if !frequency.is_finite() || frequency <= 0.0 {
192 return Err(Error::invalid(
193 "Pitch-table frequency must be finite and positive",
194 ));
195 }
196 let (divider, minimum) = self.bounds();
197 Ok((NTSC_CLOCK as f64 / (divider * frequency) - 1.0)
198 .round()
199 .clamp(minimum, 2047.0) as u16)
200 }
201 pub fn frequency(self, timer: u16) -> Result<f64> {
202 let (divider, minimum) = self.bounds();
203 if !(minimum as u16..=2047).contains(&timer) {
204 return Err(Error::invalid("Timer is outside the selected pitch table"));
205 }
206 Ok(NTSC_CLOCK as f64 / (divider * (timer as f64 + 1.0)))
207 }
208 fn bounds(self) -> (f64, f64) {
209 match self {
210 Self::NtscPulse => (16.0, 8.0),
211 Self::NtscTriangle => (32.0, 0.0),
212 }
213 }
214 fn quantize(self, frequency: f64) -> f64 {
215 if !frequency.is_finite() || frequency <= 0.0 {
216 return 0.0;
217 }
218 let (divider, minimum) = self.bounds();
219 let timer = (NTSC_CLOCK as f64 / (divider * frequency) - 1.0)
220 .round()
221 .clamp(minimum, 2047.0);
222 NTSC_CLOCK as f64 / (divider * (timer + 1.0))
223 }
224}
225
226#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
227pub enum ChipSource {
228 #[default]
229 Waveform,
230 Wavetable(Wavetable),
231 Sample(ChipSample),
232 Noise {
234 period: u8,
235 },
236}
237
238#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
242pub struct ChipInstrument {
243 pub source: ChipSource,
244 pub volume: Option<Macro>,
245 pub duty: Option<Macro>,
246 pub arpeggio: Option<Macro>,
247 pub pitch: Option<Macro>,
248 pub volume_4bit: bool,
249 pub pitch_table: Option<PitchTable>,
250}
251impl ChipInstrument {
252 pub(super) fn validate(&self, waveform: Waveform) -> Result<()> {
253 for (m, lo, hi) in [
254 (&self.volume, 0, 15),
255 (&self.duty, 0, 3),
256 (&self.arpeggio, -48, 48),
257 (&self.pitch, -1200, 1200),
258 ] {
259 if let Some(m) = m {
260 m.validate()?;
261 if m.values.iter().any(|&v| !(lo..=hi).contains(&v)) {
262 return Err(Error::invalid("Macro value is outside its lane's range"));
263 }
264 }
265 }
266 match &self.source {
267 ChipSource::Wavetable(w) => w.validate()?,
268 ChipSource::Sample(s) => s.validate()?,
269 ChipSource::Noise { period } => {
270 if *period > 15 || !matches!(waveform, Waveform::Noise(_)) {
271 return Err(Error::invalid(
272 "Period noise needs wave=noise and period 0..=15",
273 ));
274 }
275 if self.arpeggio.is_some() || self.pitch.is_some() || self.pitch_table.is_some() {
276 return Err(Error::invalid(
277 "Fixed-period noise does not take pitch controls",
278 ));
279 }
280 }
281 _ => (),
282 }
283 if matches!(
284 self.source,
285 ChipSource::Wavetable(_) | ChipSource::Sample(_)
286 ) && waveform != Waveform::default()
287 {
288 return Err(Error::invalid(
289 "Custom chip sources do not use a melodic waveform",
290 ));
291 }
292 if self.duty.is_some()
293 && (!matches!(self.source, ChipSource::Waveform)
294 || !matches!(waveform, Waveform::Pulse(_)))
295 {
296 return Err(Error::invalid("Duty macros need a pulse waveform"));
297 }
298 if let Some(table) = self.pitch_table {
299 let valid = matches!(self.source, ChipSource::Waveform)
300 && match table {
301 PitchTable::NtscPulse => matches!(waveform, Waveform::Pulse(_)),
302 PitchTable::NtscTriangle => waveform == Waveform::Triangle,
303 };
304 if !valid {
305 return Err(Error::invalid(
306 "Pitch table does not match its waveform source",
307 ));
308 }
309 }
310 Ok(())
311 }
312}
313
314pub fn noise_clock(period: u8) -> Result<f64> {
316 NTSC_NOISE_PERIODS
317 .get(period as usize)
318 .map(|&p| NTSC_CLOCK as f64 / p as f64)
319 .ok_or_else(|| Error::invalid("Noise period must be 0..=15"))
320}
321
322#[derive(Clone, Debug)]
323pub(super) struct ChipPlan {
324 settings: ChipInstrument,
325 rate: u32,
326 tempo: u16,
327 ticks: u16,
328 root_hz: f64,
329}
330impl ChipPlan {
331 pub(super) fn new(settings: &ChipInstrument, rate: u32, tempo: u16, ticks: u16) -> Self {
332 Self {
333 settings: settings.clone(),
334 root_hz: if let ChipSource::Sample(sample) = &settings.source {
335 sample.root.frequency()
336 } else {
337 1.0
338 },
339 rate,
340 tempo,
341 ticks,
342 }
343 }
344 fn lane(&self, lane: &Option<Macro>, age: u64, default: i16, clock: Option<BeatClock>) -> i16 {
345 lane.as_ref().map_or(default, |m| {
346 m.value_clock(age, self.rate, self.tempo, self.ticks, clock)
347 })
348 }
349 pub(super) fn frequency(&self, frequency: f64, age: u64, clock: Option<BeatClock>) -> f64 {
350 let cents = self.lane(&self.settings.arpeggio, age, 0, clock) as f64 * 100.0
351 + self.lane(&self.settings.pitch, age, 0, clock) as f64;
352 let frequency = if cents == 0.0 {
353 frequency
354 } else {
355 frequency * crate::math::exp2(cents / 1200.0)
356 };
357 self.settings
358 .pitch_table
359 .map_or(frequency, |table| table.quantize(frequency))
360 }
361 pub(super) fn gain(&self, level: f64, age: u64, clock: Option<BeatClock>) -> f64 {
362 let gain = level * self.lane(&self.settings.volume, age, 15, clock) as f64 / 15.0;
363 if self.settings.volume_4bit {
364 (gain.clamp(0.0, 1.0) * 15.0).round() / 15.0
365 } else {
366 gain
367 }
368 }
369}
370
371const ONE: u64 = 1 << 32;
372#[derive(Clone, Debug)]
373pub(super) struct ChipVoice {
374 oscillator: Oscillator,
375 aliasing: super::Aliasing,
376 phase: u64,
377}
378impl ChipVoice {
379 pub(super) fn new(aliasing: super::Aliasing) -> Self {
380 Self {
381 oscillator: Oscillator::new(aliasing),
382 aliasing,
383 phase: 0,
384 }
385 }
386 pub(super) fn reset(&mut self) {
387 *self = Self::new(self.aliasing);
388 }
389 pub(super) fn sample(
392 &mut self,
393 plan: &ChipPlan,
394 waveform: Waveform,
395 frequency: f64,
396 age: u64,
397 clock: Option<BeatClock>,
398 ) -> (f64, bool) {
399 let frequency = if frequency.is_finite() {
400 frequency.max(0.0)
401 } else {
402 0.0
403 };
404 match &plan.settings.source {
405 ChipSource::Waveform => {
406 let waveform = if plan.settings.duty.is_some() {
407 Waveform::Pulse(match plan.lane(&plan.settings.duty, age, 2, clock) {
408 0 => Duty::Eighth,
409 1 => Duty::Quarter,
410 2 => Duty::Half,
411 _ => Duty::ThreeQuarters,
412 })
413 } else {
414 waveform
415 };
416 (self.oscillator.next(waveform, frequency, plan.rate), false)
417 }
418 ChipSource::Wavetable(table) => {
419 let index = ((self.phase as u128 * table.values.len() as u128) >> 32) as usize;
420 let value = table.values[index] as f64 * 2.0 / 15.0 - 1.0;
421 let frequency = frequency.min(plan.rate as f64 / 2.0);
422 self.phase =
423 (self.phase + (frequency / plan.rate as f64 * ONE as f64).round() as u64) % ONE;
424 (value, false)
425 }
426 ChipSource::Sample(sample) => {
427 let n = sample.values.len() as u64;
428 let index = (self.phase >> 32).min(n - 1) as usize;
429 let value =
430 sample.values[index] as f64 * 2.0 / ((1u16 << sample.bits) - 1) as f64 - 1.0;
431 let step = sample.sample_rate as f64 / plan.rate as f64 * frequency / plan.root_hz;
432 let end = n * ONE;
433 let increment = (step * ONE as f64).round() as u64;
434 self.phase = if sample.looping {
435 (self.phase + increment % end) % end
436 } else {
437 self.phase.saturating_add(increment)
438 };
439 let finished = self.phase >= end && !sample.looping;
440 (value, finished)
441 }
442 ChipSource::Noise { period } => {
443 let mode = if let Waveform::Noise(mode) = waveform {
444 mode
445 } else {
446 NoiseMode::Long
447 };
448 let clock = NTSC_CLOCK as f64 / NTSC_NOISE_PERIODS[*period as usize] as f64;
449 (self.oscillator.clocked_noise(mode, clock, plan.rate), false)
450 }
451 }
452 }
453}