1use rill_core::{
4 math::vector::scalar::ScalarVector4,
5 math::vector::traits::Vector,
6 math::Transcendental,
7 traits::{Node, NodeCategory, NodeMetadata, NodeState, Processor},
8 NodeId, ParamValue, ParameterId, Port, ProcessError, ProcessResult, RenderContext,
9};
10
11#[derive(Debug, Clone, Copy, PartialEq)]
13pub enum DistortionType {
14 HardClip,
16 SoftClip,
18 Tube,
20 Fuzz,
22}
23
24impl DistortionType {
25 pub fn names() -> Vec<&'static str> {
27 vec!["hard_clip", "soft_clip", "tube", "fuzz"]
28 }
29
30 #[allow(clippy::should_implement_trait)]
32 pub fn from_str(s: &str) -> Option<Self> {
33 match s {
34 "hard_clip" => Some(DistortionType::HardClip),
35 "soft_clip" => Some(DistortionType::SoftClip),
36 "tube" => Some(DistortionType::Tube),
37 "fuzz" => Some(DistortionType::Fuzz),
38 _ => None,
39 }
40 }
41
42 pub fn as_str(&self) -> &'static str {
44 match self {
45 DistortionType::HardClip => "hard_clip",
46 DistortionType::SoftClip => "soft_clip",
47 DistortionType::Tube => "tube",
48 DistortionType::Fuzz => "fuzz",
49 }
50 }
51}
52
53pub struct Distortion<T: Transcendental, const BUF_SIZE: usize> {
60 id: NodeId,
62 metadata: NodeMetadata,
64 inputs: Vec<Port<T, BUF_SIZE>>,
66 outputs: Vec<Port<T, BUF_SIZE>>,
68 controls: Vec<Port<T, BUF_SIZE>>,
70 state: NodeState<T, BUF_SIZE>,
72 pub distortion_type: DistortionType,
74 pub drive: f32,
76 pub output_gain: f32,
78 sample_rate: f32,
80}
81
82impl<T: Transcendental, const BUF_SIZE: usize> Distortion<T, BUF_SIZE> {
83 pub fn new(sample_rate: f32) -> Self {
85 let metadata = NodeMetadata::new("Distortion", NodeCategory::Processor);
86
87 let mut inputs = Vec::new();
88 let mut outputs = Vec::new();
89
90 inputs.push(Port::input(NodeId(0), 0, "signal_in"));
92 outputs.push(Port::output(NodeId(0), 0, "signal_out"));
93
94 Self {
95 id: NodeId(0),
96 metadata,
97 inputs,
98 outputs,
99 controls: Vec::new(),
100 state: NodeState::new(sample_rate),
101 distortion_type: DistortionType::SoftClip,
102 drive: 1.0,
103 output_gain: 1.0,
104 sample_rate,
105 }
106 }
107
108 pub fn with_params(
110 sample_rate: f32,
111 distortion_type: DistortionType,
112 drive: f32,
113 output_gain: f32,
114 ) -> Self {
115 let mut instance = Self::new(sample_rate);
116 instance.set_type(distortion_type);
117 instance.set_drive(drive);
118 instance.set_output_gain(output_gain);
119 instance
120 }
121
122 pub fn set_type(&mut self, distortion_type: DistortionType) {
124 self.distortion_type = distortion_type;
125 }
126
127 pub fn set_drive(&mut self, drive: f32) {
129 self.drive = drive.clamp(1.0, 100.0);
130 }
131
132 pub fn set_output_gain(&mut self, gain: f32) {
134 self.output_gain = gain.clamp(0.0, 2.0);
135 }
136
137 pub fn process_sample(&self, input: T) -> T {
139 let driven = input.mul(T::from_f32(self.drive));
140
141 let distorted = match self.distortion_type {
142 DistortionType::HardClip => driven.clamp(T::MIN, T::MAX),
143 DistortionType::SoftClip => T::from_f32(driven.to_f32().tanh()),
144 DistortionType::Tube => {
145 if driven > T::ZERO {
147 T::ONE - (-driven).exp()
148 } else {
149 -T::ONE + driven.exp()
150 }
151 }
152 DistortionType::Fuzz => {
153 if driven > T::ZERO {
155 T::ONE - T::ONE.div(T::ONE + driven)
156 } else {
157 driven
158 }
159 }
160 };
161
162 distorted.mul(T::from_f32(self.output_gain))
163 }
164}
165
166impl<T: Transcendental, const BUF_SIZE: usize> Node<T, BUF_SIZE> for Distortion<T, BUF_SIZE> {
167 fn node_type_id(&self) -> rill_core::NodeTypeId
168 where
169 Self: 'static + Sized,
170 {
171 rill_core::NodeTypeId::of::<Self>()
172 }
173
174 fn id(&self) -> NodeId {
175 self.id
176 }
177
178 fn set_id(&mut self, id: NodeId) {
179 self.id = id;
180 }
181
182 fn metadata(&self) -> NodeMetadata {
183 self.metadata.clone()
184 }
185
186 fn init(&mut self, sample_rate: f32) {
187 self.sample_rate = sample_rate;
188 }
189
190 fn reset(&mut self) {
191 self.state.sample_pos = 0;
192 self.state.blocks_processed = 0;
193 }
194
195 fn get_parameter(&self, id: &ParameterId) -> Option<ParamValue> {
196 let name = id.as_str();
197 match name {
198 "type" => Some(ParamValue::Choice(
199 self.distortion_type.as_str().to_string(),
200 )),
201 "drive" => Some(ParamValue::Float(self.drive)),
202 "output_gain" => Some(ParamValue::Float(self.output_gain)),
203 _ => None,
204 }
205 }
206
207 fn set_parameter(&mut self, id: &ParameterId, value: ParamValue) -> ProcessResult<()> {
208 let name = id.as_str();
209 match name {
210 "type" => {
211 if let ParamValue::Choice(t) = value {
212 if let Some(dt) = DistortionType::from_str(&t) {
213 self.set_type(dt);
214 Ok(())
215 } else {
216 Err(ProcessError::parameter("unknown distortion type"))
217 }
218 } else {
219 Err(ProcessError::parameter("expected Choice value"))
220 }
221 }
222 "drive" => {
223 if let Some(v) = value.as_f32() {
224 self.set_drive(v);
225 Ok(())
226 } else {
227 Err(ProcessError::parameter("expected float value"))
228 }
229 }
230 "output_gain" => {
231 if let Some(v) = value.as_f32() {
232 self.set_output_gain(v);
233 Ok(())
234 } else {
235 Err(ProcessError::parameter("expected float value"))
236 }
237 }
238 _ => Err(ProcessError::parameter("unknown parameter")),
239 }
240 }
241
242 fn input_port(&self, index: usize) -> Option<&Port<T, BUF_SIZE>> {
243 self.inputs.get(index)
244 }
245
246 fn input_port_mut(&mut self, index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
247 self.inputs.get_mut(index)
248 }
249
250 fn output_port(&self, index: usize) -> Option<&Port<T, BUF_SIZE>> {
251 self.outputs.get(index)
252 }
253
254 fn output_port_mut(&mut self, index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
255 self.outputs.get_mut(index)
256 }
257
258 fn control_port(&self, index: usize) -> Option<&Port<T, BUF_SIZE>> {
259 self.controls.get(index)
260 }
261
262 fn control_port_mut(&mut self, index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
263 self.controls.get_mut(index)
264 }
265
266 fn num_inputs(&self) -> usize {
267 self.inputs.len()
268 }
269
270 fn num_outputs(&self) -> usize {
271 self.outputs.len()
272 }
273
274 fn num_signal_inputs(&self) -> usize {
275 self.inputs.len()
276 }
277
278 fn num_signal_outputs(&self) -> usize {
279 self.outputs.len()
280 }
281
282 fn state(&self) -> &NodeState<T, BUF_SIZE> {
283 &self.state
284 }
285
286 fn state_mut(&mut self) -> &mut NodeState<T, BUF_SIZE> {
287 &mut self.state
288 }
289}
290
291impl<T: Transcendental, const BUF_SIZE: usize> Processor<T, BUF_SIZE> for Distortion<T, BUF_SIZE> {
292 fn process(
293 &mut self,
294 _ctx: &RenderContext,
295 _signal_inputs: &[&[T; BUF_SIZE]],
296 _control_inputs: &[T],
297 _clock_inputs: &[RenderContext],
298 _feedback_inputs: &[&[T; BUF_SIZE]],
299 ) -> ProcessResult<()> {
300 let inp = self.inputs[0].read();
301 let out = self.outputs[0].write();
302 let drive_t = T::from_f32(self.drive);
303 let gain_t = T::from_f32(self.output_gain);
304 let chunks = BUF_SIZE / 4;
305
306 match self.distortion_type {
307 DistortionType::HardClip => {
308 let min = ScalarVector4::splat(T::MIN);
309 let max = ScalarVector4::splat(T::MAX);
310 for chunk in 0..chunks {
311 let o = chunk * 4;
312 let x = ScalarVector4::load(&inp[o..o + 4]);
313 let d = x.mul(&ScalarVector4::splat(drive_t));
314 let r = d.clamp(&min, &max);
315 r.mul(&ScalarVector4::splat(gain_t))
316 .store(&mut out[o..o + 4]);
317 }
318 }
319 DistortionType::SoftClip => {
320 for chunk in 0..chunks {
321 let o = chunk * 4;
322 let vals: [T; 4] = std::array::from_fn(|k| {
323 let d = inp[o + k].mul(drive_t);
324 let s = T::from_f32(d.to_f32().tanh());
325 s.mul(gain_t)
326 });
327 out[o..o + 4].copy_from_slice(&vals);
328 }
329 }
330 DistortionType::Tube => {
331 for chunk in 0..chunks {
332 let o = chunk * 4;
333 let d_v = ScalarVector4::load(&[
334 inp[o].mul(drive_t),
335 inp[o + 1].mul(drive_t),
336 inp[o + 2].mul(drive_t),
337 inp[o + 3].mul(drive_t),
338 ]);
339 let vals = ScalarVector4::from_fn(|i| {
340 let d = d_v.extract(i);
341 if d > T::ZERO {
342 T::ONE - (-d).exp()
343 } else {
344 -T::ONE + d.exp()
345 }
346 });
347 vals.mul(&ScalarVector4::splat(gain_t))
348 .store(&mut out[o..o + 4]);
349 }
350 }
351 DistortionType::Fuzz => {
352 for chunk in 0..chunks {
353 let o = chunk * 4;
354 let out_arr: [T; 4] = std::array::from_fn(|k| {
355 let d = inp[o + k].mul(drive_t);
356 let f = if d > T::ZERO {
357 T::ONE - T::ONE.div(T::ONE + d)
358 } else {
359 d
360 };
361 f.mul(gain_t)
362 });
363 out[o..o + 4].copy_from_slice(&out_arr);
364 }
365 }
366 }
367
368 let dt = self.distortion_type;
370 let dr = self.drive;
371 let og = self.output_gain;
372 for i in chunks * 4..BUF_SIZE {
373 let driven = inp[i].mul(T::from_f32(dr));
374 out[i] = match dt {
375 DistortionType::HardClip => driven.clamp(T::MIN, T::MAX),
376 DistortionType::SoftClip => T::from_f32(driven.to_f32().tanh()),
377 DistortionType::Tube => {
378 if driven > T::ZERO {
379 T::ONE - (-driven).exp()
380 } else {
381 -T::ONE + driven.exp()
382 }
383 }
384 DistortionType::Fuzz => {
385 if driven > T::ZERO {
386 T::ONE - T::ONE.div(T::ONE + driven)
387 } else {
388 driven
389 }
390 }
391 }
392 .mul(T::from_f32(og));
393 }
394
395 self.state.advance();
396 Ok(())
397 }
398
399 fn latency(&self) -> usize {
400 0
401 }
402}