1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//! Multi-component mechanical sound mixer.
//!
//! Combines multiple mechanical synthesizers into a single output
//! with independent gain and pan per component.
use alloc::string::String;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
use crate::traits::Synthesizer;
/// A named component in the mixer with gain and pan.
#[derive(Debug, Serialize, Deserialize)]
pub struct MixerChannel {
/// Channel name (e.g., "engine", "exhaust", "turbo").
pub name: String,
/// Linear gain (0.0 = silent, 1.0 = unity).
pub gain: f32,
/// Stereo pan (-1.0 = full left, 0.0 = center, 1.0 = full right).
pub pan: f32,
/// Mute flag.
pub muted: bool,
/// The synthesizer for this channel.
#[serde(skip)]
synth: Option<Box<dyn Synthesizer>>,
/// Scratch buffer for per-channel processing.
#[serde(skip)]
scratch: Vec<f32>,
}
impl MixerChannel {
fn new(name: String, synth: Box<dyn Synthesizer>) -> Self {
Self {
name,
gain: 1.0,
pan: 0.0,
muted: false,
synth: Some(synth),
scratch: Vec::new(),
}
}
}
/// Multi-component mechanical sound mixer.
///
/// Owns multiple `Synthesizer` instances and mixes them with
/// independent gain, pan, and mute per channel.
#[derive(Debug, Serialize, Deserialize)]
pub struct MechanicalMixer {
channels: Vec<MixerChannel>,
master_gain: f32,
}
impl MechanicalMixer {
/// Creates a new empty mixer.
#[must_use]
pub fn new() -> Self {
Self {
channels: Vec::new(),
master_gain: 1.0,
}
}
/// Adds a synthesizer channel. Returns the channel index.
pub fn add_channel(&mut self, name: String, synth: Box<dyn Synthesizer>) -> usize {
let idx = self.channels.len();
self.channels.push(MixerChannel::new(name, synth));
idx
}
/// Sets gain on a channel (0.0 = silent, 1.0 = unity).
pub fn set_channel_gain(&mut self, index: usize, gain: f32) {
if let Some(ch) = self.channels.get_mut(index) {
ch.gain = gain.max(0.0);
}
}
/// Sets pan on a channel (-1.0 left, 0.0 center, 1.0 right).
pub fn set_channel_pan(&mut self, index: usize, pan: f32) {
if let Some(ch) = self.channels.get_mut(index) {
ch.pan = pan.clamp(-1.0, 1.0);
}
}
/// Mutes or unmutes a channel.
pub fn set_channel_muted(&mut self, index: usize, muted: bool) {
if let Some(ch) = self.channels.get_mut(index) {
ch.muted = muted;
}
}
/// Sets the master output gain.
pub fn set_master_gain(&mut self, gain: f32) {
self.master_gain = gain.max(0.0);
}
/// Sets RPM on all channels.
pub fn set_rpm(&mut self, rpm: f32) {
for ch in &mut self.channels {
if let Some(synth) = &mut ch.synth {
synth.set_rpm(rpm);
}
}
}
/// Returns the number of channels.
#[must_use]
pub fn channel_count(&self) -> usize {
self.channels.len()
}
/// Processes all channels and mixes into a mono output.
pub fn process_block(&mut self, output: &mut [f32]) {
let len = output.len();
for s in output.iter_mut() {
*s = 0.0;
}
for ch in &mut self.channels {
if ch.muted {
continue;
}
if let Some(synth) = &mut ch.synth {
// Ensure scratch buffer is large enough
if ch.scratch.len() < len {
ch.scratch.resize(len, 0.0);
}
let scratch = &mut ch.scratch[..len];
for s in scratch.iter_mut() {
*s = 0.0;
}
synth.process_block(scratch);
let gain = ch.gain * self.master_gain;
for (out, &src) in output.iter_mut().zip(scratch.iter()) {
*out += src * gain;
}
}
}
}
/// Processes all channels into stereo output with panning.
pub fn process_block_stereo(&mut self, left: &mut [f32], right: &mut [f32]) {
let len = left.len().min(right.len());
for s in left[..len].iter_mut() {
*s = 0.0;
}
for s in right[..len].iter_mut() {
*s = 0.0;
}
for ch in &mut self.channels {
if ch.muted {
continue;
}
if let Some(synth) = &mut ch.synth {
if ch.scratch.len() < len {
ch.scratch.resize(len, 0.0);
}
let scratch = &mut ch.scratch[..len];
for s in scratch.iter_mut() {
*s = 0.0;
}
synth.process_block(scratch);
// Equal-power pan law
let pan_norm = (ch.pan + 1.0) * 0.5; // 0..1
let angle = pan_norm * core::f32::consts::FRAC_PI_2;
let (pan_sin, pan_cos) = (libm::sinf(angle), libm::cosf(angle));
let gain_l = pan_cos * ch.gain * self.master_gain;
let gain_r = pan_sin * ch.gain * self.master_gain;
for i in 0..len {
left[i] += scratch[i] * gain_l;
right[i] += scratch[i] * gain_r;
}
}
}
}
}
impl Default for MechanicalMixer {
fn default() -> Self {
Self::new()
}
}