mod io;
use crate::{
Key, NATIVE_SAMPLE_RATE, SampleRate,
event::DEFAULT_BASICKEY,
noise_builder::{NoiseTable, noise_to_pcm},
point::EnvPt,
pulse_oscillator::{OsciArgs, coord, overtone},
voice_data::{
noise::NoiseData,
oggv::OggVData,
pcm::PcmData,
wave::{WaveData, WaveDataPoints},
},
};
#[derive(Clone)]
#[expect(clippy::large_enum_variant)]
pub enum VoiceData {
Noise(NoiseData),
Pcm(PcmData),
Wave(WaveData),
OggV(OggVData),
}
#[derive(Default, Clone)]
pub struct VoiceInstance {
pub num_samples: u32,
pub sample_buf: Vec<u8>,
pub env: Vec<u8>,
pub env_release: u32,
}
impl VoiceInstance {
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub fn recalc_envelope(&mut self, out_sps: SampleRate, envelope: &EnvelopeSrc) {
let Some((prepared, head)) = envelope.to_prepared(out_sps) else {
self.env = Vec::new();
return;
};
self.env = prepared;
if head < envelope.points.len() {
let release_f64 = f64::from((envelope.points[head]).x) * f64::from(out_sps)
/ f64::from(envelope.seconds_per_point);
let clamped = release_f64.clamp(0.0, 1_000_000.0);
self.env_release = clamped as u32;
} else {
self.env_release = 0;
}
}
pub fn recalc_wave_data(&mut self, wave: &WaveDataPoints, volume: i16, pan: i16) {
self.num_samples = 400;
let size = self.num_samples * 2 * 2;
self.sample_buf = vec![0; size as usize];
update_wave_ptv(wave, self, volume, pan);
}
}
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn to_absolute(envelope: &EnvelopeSrc, head: usize, out_sps: SampleRate) -> (Vec<(u32, u8)>, u32) {
let mut points: Vec<(u32, u8)> = vec![(0, 0); head];
let mut offset: u32 = 0;
let mut head_num: u32 = 0;
for (e, pt) in points.iter_mut().enumerate().take(head) {
if e == 0 || (envelope.points[e]).x != 0 || (envelope.points[e]).y != 0 {
offset += (f64::from((envelope.points[e]).x) * f64::from(out_sps)
/ f64::from(envelope.seconds_per_point)) as u32;
pt.0 = offset;
pt.1 = envelope.points[e].y;
head_num += 1;
}
}
(points, head_num)
}
#[expect(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap
)]
fn to_prepared_envelope(dst: &mut [u8], abs_points: &[(u32, u8)], head_num: u32) {
let mut e = 0;
let mut start: (u32, i32) = (0, 0);
for (i, out) in dst.iter_mut().enumerate() {
while (e as u32) < head_num && i as u32 >= abs_points[e].0 {
start.0 = abs_points[e].0;
start.1 = i32::from(abs_points[e].1);
e += 1;
}
*out = if (e as u32) < head_num {
(start.1
+ (i32::from(abs_points[e].1) - start.1) * (i as i32 - start.0 as i32)
/ (abs_points[e].0 as i32 - start.0 as i32)) as u8
} else {
start.1 as u8
}
}
}
#[derive(Clone, Default)]
pub struct EnvelopeSrc {
pub seconds_per_point: u32,
pub points: Vec<EnvPt>,
}
impl EnvelopeSrc {
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn to_prepared(&self, out_sps: SampleRate) -> Option<(Vec<u8>, usize)> {
if self.points.is_empty() {
return None;
}
let mut size: u32 = 0;
let head = self.points.len().saturating_sub(1);
for e in 0..head {
size += u32::from(self.points[e].x);
}
let env_samples_per_second = size * u32::from(out_sps);
let mut env_size =
(f64::from(env_samples_per_second) / f64::from(self.seconds_per_point)) as usize;
if env_size == 0 {
env_size = 1;
}
if env_size > ENV_SIZE_SAFETY_LIMIT {
eprintln!("EnvelopeSrc::to_prepared: env_size too large ({env_size}).");
return None;
}
let (abs_points, head_num) = to_absolute(self, head, out_sps);
let mut prepared = vec![0; env_size];
to_prepared_envelope(&mut prepared, &abs_points, head_num);
Some((prepared, head))
}
}
#[derive(Clone)]
pub struct VoiceUnit {
pub basic_key: Key,
pub tuning: f32,
pub flags: VoiceFlags,
}
impl Default for VoiceUnit {
fn default() -> Self {
Self {
basic_key: DEFAULT_BASICKEY.cast_signed(),
tuning: 1.0,
flags: VoiceFlags::SMOOTH,
}
}
}
bitflags::bitflags! {
#[derive(Clone, Copy, Default, bytemuck::AnyBitPattern, bytemuck::NoUninit, Debug)]
#[repr(C)]
pub struct VoiceFlags: u32 {
const WAVE_LOOP = 0b001;
const SMOOTH = 0b010;
const BEAT_FIT = 0b100;
}
}
#[derive(Default, Clone)]
pub struct VoiceTone {
pub smp_pos: f64,
pub offset_freq: f32,
pub env_volume: u8,
pub life_count: i32,
pub on_count: i32,
pub env_start: u8,
pub env_pos: usize,
pub env_release_clock: u32,
}
#[derive(Clone)]
pub struct Voice {
pub base: VoiceSlot,
pub extra: Option<VoiceSlot>,
pub name: String,
}
#[derive(Clone)]
pub struct VoiceSlot {
pub unit: VoiceUnit,
pub data: VoiceData,
pub inst: VoiceInstance,
}
impl VoiceSlot {
fn from_unit_and_data(unit: VoiceUnit, data: VoiceData) -> Self {
Self {
unit,
data,
inst: VoiceInstance::default(),
}
}
}
impl Voice {
#[must_use]
pub fn from_unit_and_data(unit: VoiceUnit, data: VoiceData) -> Self {
Self::from_slot(VoiceSlot::from_unit_and_data(unit, data))
}
#[must_use]
pub fn from_data(data: VoiceData) -> Self {
Self::from_unit_and_data(VoiceUnit::default(), data)
}
fn from_slot(slot: VoiceSlot) -> Self {
Self {
base: slot,
extra: None,
name: "<no name>".into(),
}
}
pub(crate) fn tone_ready_sample(&mut self, ptn_bldr: &NoiseTable) {
for VoiceSlot { inst, data, .. } in self.slots_mut() {
inst.num_samples = 0;
match data {
VoiceData::Pcm(pcm) => {
let (body, buf) = pcm.to_converted(NATIVE_SAMPLE_RATE);
inst.num_samples = body;
inst.sample_buf = buf;
}
VoiceData::Noise(ptn) => {
inst.sample_buf = noise_to_pcm(ptn, ptn_bldr).into_sample_buf();
inst.num_samples = ptn.smp_num_44k;
}
VoiceData::Wave(data) => {
inst.recalc_wave_data(&data.points, data.volume, data.pan);
}
VoiceData::OggV(ogg_vdata) => {
#[cfg(feature = "oggv")]
match crate::voice_data::oggv::decode_oggv(&ogg_vdata.raw_bytes) {
Some(pcm) => {
let (body, buf) = pcm.to_converted(NATIVE_SAMPLE_RATE);
inst.num_samples = body;
inst.sample_buf = buf;
}
None => {
eprintln!("Failed to decode Ogg/Vorbis data");
}
}
#[cfg(not(feature = "oggv"))]
panic!("Ogg/Vorbis support was disabled.");
}
}
}
}
pub(crate) fn tone_ready_envelopes(&mut self, sps: SampleRate) {
for VoiceSlot { inst, data, .. } in self.slots_mut() {
if let VoiceData::Wave(data) = data {
inst.recalc_envelope(sps, &data.envelope);
}
}
}
pub fn recalculate(&mut self, noise_tbl: &NoiseTable, out_sps: SampleRate) {
self.tone_ready_sample(noise_tbl);
self.tone_ready_envelopes(out_sps);
}
pub fn slots(&self) -> impl Iterator<Item = &VoiceSlot> {
std::iter::once(&self.base).chain(&self.extra)
}
pub fn slots_mut(&mut self) -> impl Iterator<Item = &mut VoiceSlot> {
std::iter::once(&mut self.base).chain(&mut self.extra)
}
}
const ENV_SIZE_SAFETY_LIMIT: usize = 1_048_576;
fn update_wave_ptv(wave: &WaveDataPoints, inst: &mut VoiceInstance, volume: i16, pan: i16) {
let mut pan_volume: [i16; 2] = [64, 64];
if pan > 64 {
pan_volume[0] = 128 - pan;
}
if pan < 64 {
pan_volume[1] = pan;
}
let osci = OsciArgs {
volume,
sample_num: inst.num_samples,
};
let smp_buf_16: &mut [i16] = bytemuck::cast_slice_mut(&mut inst.sample_buf[..]);
for s in 0..inst.num_samples {
let osc = match wave {
WaveDataPoints::Coord {
points: coordinates,
resolution,
} => coord(osci, coordinates, s.try_into().unwrap(), *resolution),
WaveDataPoints::Overtone {
points: coordinates,
} => overtone(osci, coordinates, s.try_into().unwrap()),
};
for c in 0..2 {
let mut work = osc * f64::from(pan_volume[c]) / 64.;
work = work.clamp(-1.0, 1.0);
#[expect(clippy::cast_possible_truncation)]
(smp_buf_16[s as usize * 2 + c] = (work * 32767.) as i16);
}
}
}