use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
use crate::core::instr_sid::SidVoice;
#[derive(Default, Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
pub struct WaveShape {
pub noise: bool,
pub pulse: bool,
pub sawtooth: bool,
pub triangle: bool,
pub test: bool,
pub ring: bool,
pub sync: bool,
pub gate: bool,
}
impl WaveShape {
pub fn from_ctrl(b: u8) -> Self {
Self {
noise: b & 0x80 != 0,
pulse: b & 0x40 != 0,
sawtooth: b & 0x20 != 0,
triangle: b & 0x10 != 0,
test: b & 0x08 != 0,
ring: b & 0x04 != 0,
sync: b & 0x02 != 0,
gate: b & 0x01 != 0,
}
}
pub fn is_silent(self) -> bool {
!(self.noise || self.pulse || self.sawtooth || self.triangle)
}
pub fn to_ctrl(self) -> u8 {
(self.noise as u8) << 7
| (self.pulse as u8) << 6
| (self.sawtooth as u8) << 5
| (self.triangle as u8) << 4
| (self.test as u8) << 3
| (self.ring as u8) << 2
| (self.sync as u8) << 1
| self.gate as u8
}
}
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
pub enum PitchAction {
Bend(i16),
Fixed(u8),
}
impl Default for PitchAction {
fn default() -> Self {
Self::Bend(0)
}
}
#[derive(Default, Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
pub struct WaveStep {
pub shape: WaveShape,
pub pitch: PitchAction,
}
#[derive(Default, Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
pub enum ProgramEnd {
#[default]
Hold,
Loop { step: u8 },
}
#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct VoiceProgram {
pub steps: Vec<WaveStep>,
pub end: ProgramEnd,
}
#[derive(Default, Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
pub enum VibratoMode {
#[default]
None,
Upward {
depth: u8,
reg_step: u16,
},
Bipolar { depth: u8, div: u8 },
Semitone {
half_depth: u8,
shift: u8,
delay: u8,
flat: u8,
},
}
#[derive(Default, Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
pub struct BounceRange {
pub lo: u8,
pub hi: u8,
}
#[derive(Default, Serialize, Deserialize, Copy, Clone, Debug)]
pub struct PulseSweep {
pub enable: bool,
pub speed: i8,
pub delay: u16,
#[serde(default)]
pub bounce: Option<BounceRange>,
#[serde(default)]
pub low_byte_mode: bool,
#[serde(default)]
pub reseed_on_note: bool,
}
#[derive(Default, Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
pub enum ArpPhase {
#[default]
Global,
PerNote,
}
#[derive(Default, Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
pub enum ArpMode {
#[default]
None,
Octave,
TwoNoteFixed(u16),
Cycle {
steps: [i8; 3],
len: u8,
phase: ArpPhase,
follow_climb: bool,
},
}
#[derive(Default, Serialize, Deserialize, Copy, Clone, Debug)]
pub struct Drum {
pub enable: bool,
#[serde(default)]
pub no_freq_slide: bool,
}
#[derive(Default, Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
pub enum SkydiveMode {
#[default]
Climb,
Add(i16),
}
#[derive(Default, Serialize, Deserialize, Copy, Clone, Debug)]
pub struct Skydive {
pub enable: bool,
pub length_gate: u8,
pub mode: SkydiveMode,
}
#[derive(Default, Serialize, Deserialize, Copy, Clone, Debug)]
pub struct TwoPhase {
#[serde(default)]
pub attack_shape: WaveShape,
#[serde(default)]
pub attack_frames: u8,
#[serde(default)]
pub attack_note: Option<u8>,
}
#[derive(Default, Serialize, Deserialize, Copy, Clone, Debug)]
pub struct WaveAlt {
#[serde(default)]
pub alt_shape: WaveShape,
}
#[derive(Default, Serialize, Deserialize, Copy, Clone, Debug)]
pub struct Filter {
pub enable: bool,
#[serde(default)]
pub resonance: u8,
#[serde(default)]
pub routing: FilterRouting,
#[serde(default)]
pub cutoff_seed: u16,
#[serde(default)]
pub sweep: CutoffSweep,
#[serde(default)]
pub mode: FilterMode,
#[serde(default)]
pub reseed_each_note: bool,
}
#[derive(Default, Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
pub struct FilterRouting {
pub voice1: bool,
pub voice2: bool,
pub voice3: bool,
pub external: bool,
}
impl FilterRouting {
pub fn from_bits(b: u8) -> Self {
Self {
voice1: b & 0x01 != 0,
voice2: b & 0x02 != 0,
voice3: b & 0x04 != 0,
external: b & 0x08 != 0,
}
}
pub fn to_bits(self) -> u8 {
self.voice1 as u8
| (self.voice2 as u8) << 1
| (self.voice3 as u8) << 2
| (self.external as u8) << 3
}
}
#[derive(Default, Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
pub struct FilterMode {
pub low_pass: bool,
pub band_pass: bool,
pub high_pass: bool,
}
impl FilterMode {
pub fn from_bits(b: u8) -> Self {
Self {
low_pass: b & 0x01 != 0,
band_pass: b & 0x02 != 0,
high_pass: b & 0x04 != 0,
}
}
pub fn to_bits(self) -> u8 {
self.low_pass as u8 | (self.band_pass as u8) << 1 | (self.high_pass as u8) << 2
}
pub fn low() -> Self {
Self {
low_pass: true,
..Default::default()
}
}
}
#[derive(Default, Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
pub enum CutoffSweep {
#[default]
Static,
Wrap { step: i8 },
Bounce {
step: u16,
min: u8,
max: u8,
up: bool,
},
}
#[derive(Default, Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq)]
pub enum ReleaseMode {
#[default]
Gate,
HardCut,
Ramp { ad: u8, sr: u8 },
}
#[derive(Default, Serialize, Deserialize, Copy, Clone, Debug)]
pub struct RobEffects {
pub vibrato: VibratoMode,
pub pulse_sweep: PulseSweep,
pub arpeggio: ArpMode,
pub drum: Drum,
pub skydive: Skydive,
pub two_phase: TwoPhase,
pub wave_alt: WaveAlt,
pub filter: Filter,
#[serde(default)]
pub release: ReleaseMode,
}
#[derive(Default, Serialize, Deserialize, Debug, Clone)]
pub struct InstrRobSid {
pub voice: SidVoice,
#[serde(default)]
pub program: Option<VoiceProgram>,
pub fx: RobEffects,
}
#[cfg(test)]
mod frozen_shape {
use super::*;
#[test]
fn field_names_and_types_are_frozen() {
let InstrRobSid { voice, program, fx } = InstrRobSid::default();
let _: SidVoice = voice;
let _: Option<VoiceProgram> = program;
let RobEffects {
vibrato,
pulse_sweep,
arpeggio,
drum,
skydive,
two_phase,
wave_alt,
filter,
release,
} = fx;
let _: VibratoMode = vibrato;
let _: ArpMode = arpeggio;
let _: ReleaseMode = release;
let TwoPhase {
attack_shape,
attack_frames,
attack_note,
} = two_phase;
let _: WaveShape = attack_shape;
let _: u8 = attack_frames;
let _: Option<u8> = attack_note;
let WaveAlt { alt_shape } = wave_alt;
let _: WaveShape = alt_shape;
let Filter {
enable,
resonance,
routing,
cutoff_seed,
sweep,
mode,
reseed_each_note,
} = filter;
let _: bool = enable;
let _: u8 = resonance;
let _: FilterRouting = routing;
let _: u16 = cutoff_seed;
let _: CutoffSweep = sweep;
let _: FilterMode = mode;
let _: bool = reseed_each_note;
let Skydive {
enable,
length_gate,
mode,
} = skydive;
let _: bool = enable;
let _: u8 = length_gate;
let _: SkydiveMode = mode;
let PulseSweep {
enable,
speed,
delay,
bounce,
low_byte_mode,
reseed_on_note,
} = pulse_sweep;
let _: bool = enable;
let _: i8 = speed;
let _: u16 = delay;
let _: Option<BounceRange> = bounce;
let _: bool = low_byte_mode;
let _: bool = reseed_on_note;
let Drum {
enable,
no_freq_slide,
} = drum;
let _: bool = enable;
let _: bool = no_freq_slide;
let WaveShape {
noise,
pulse,
sawtooth,
triangle,
test,
ring,
sync,
gate,
} = WaveShape::default();
let _: [bool; 8] = [noise, pulse, sawtooth, triangle, test, ring, sync, gate];
let FilterMode {
low_pass,
band_pass,
high_pass,
} = FilterMode::default();
let _: [bool; 3] = [low_pass, band_pass, high_pass];
let FilterRouting {
voice1,
voice2,
voice3,
external,
} = FilterRouting::default();
let _: [bool; 4] = [voice1, voice2, voice3, external];
}
}
#[cfg(all(test, feature = "format"))]
mod freeze {
use super::*;
use alloc::vec::Vec;
use ciborium::value::Value;
#[test]
fn serialised_field_names_are_frozen() {
let mut buf = Vec::new();
ciborium::into_writer(&InstrRobSid::default(), &mut buf).expect("encode");
let v: Value = ciborium::from_reader(&buf[..]).expect("decode");
let keys = |v: &Value| -> Vec<alloc::string::String> {
match v {
Value::Map(m) => m
.iter()
.filter_map(|(k, _)| match k {
Value::Text(t) => Some(t.clone()),
_ => None,
})
.collect(),
_ => panic!("expected a map"),
}
};
assert_eq!(keys(&v), ["voice", "program", "fx"]);
let fx = match &v {
Value::Map(m) => m
.iter()
.find(|(k, _)| k == &Value::Text("fx".into()))
.unwrap()
.1
.clone(),
_ => unreachable!(),
};
assert_eq!(
keys(&fx),
[
"vibrato",
"pulse_sweep",
"arpeggio",
"drum",
"skydive",
"two_phase",
"wave_alt",
"filter",
"release",
]
);
let group = |name: &str| -> Value {
match &fx {
Value::Map(m) => m
.iter()
.find(|(k, _)| k == &Value::Text(name.into()))
.unwrap()
.1
.clone(),
_ => unreachable!(),
}
};
assert_eq!(
keys(&group("two_phase")),
["attack_shape", "attack_frames", "attack_note"]
);
assert_eq!(keys(&group("wave_alt")), ["alt_shape"]);
assert_eq!(
keys(&group("filter")),
[
"enable",
"resonance",
"routing",
"cutoff_seed",
"sweep",
"mode",
"reseed_each_note"
]
);
assert_eq!(keys(&group("skydive")), ["enable", "length_gate", "mode"]);
}
}