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
type shape = Hard | Tanh | Cubic | Asymmetric
let shapes = [ Hard; Tanh; Cubic; Asymmetric ]
let name = function Hard -> "hard" | Tanh -> "tanh" | Cubic -> "cubic" | Asymmetric -> "asymmetric"
let bias = 0.3
let curve (shape : shape) (x : float) : float =
match shape with
| Hard -> Float.max (-1.) (Float.min 1. x)
| Tanh -> tanh x
| Cubic ->
let x = Float.max (-1.) (Float.min 1. x) in
1.5 *. (x -. (x *. x *. x /. 3.))
| Asymmetric -> tanh (x +. bias) -. tanh bias
let butterworth_qs = [| 0.50980; 0.60134; 0.89998; 2.56292 |]
let cutoff = 18000.
type t = {
oversampling : int;
filter : Filter.biquad array;
up : Filter.memory array;
down : Filter.memory array;
mutable dc : float;
mutable last_drive : float;
mutable last_mix : float;
}
let create ~(oversampling : int) () : t =
let rate = float_of_int (oversampling * Signal.rate) in
{
oversampling;
filter = Array.map (fun q -> Filter.biquad ~rate Low_pass ~cutoff ~q) butterworth_qs;
up = Array.init 4 (fun _ -> Filter.silence ());
down = Array.init 4 (fun _ -> Filter.silence ());
dc = 0.;
last_drive = Float.nan;
last_mix = Float.nan;
}
let chain (t : t) (memories : Filter.memory array) (x : float) : float =
let y = ref x in
for k = 0 to 3 do
y := Filter.step t.filter.(k) memories.(k) !y
done;
!y
let dc_coefficient = Filter.one_pole_coefficient 10.
let process (t : t) (shape : shape) ~(drive : float) ~(mix : float) (s : Signal.t) : unit =
let l = t.oversampling and n = Array.length s in
let from_drive = if Float.is_nan t.last_drive then drive else t.last_drive
and from_mix = if Float.is_nan t.last_mix then mix else t.last_mix in
Array.iteri
(fun i x ->
let g = Mix.of_decibels (Effect.ramp from_drive drive i n) and mix = Effect.ramp from_mix mix i n in
let wet =
if l = 1 then curve shape (g *. x)
else begin
let out = ref 0. in
for k = 0 to l - 1 do
let up = chain t t.up (if k = 0 then float_of_int l *. x else 0.) in
let y = chain t t.down (curve shape (g *. up)) in
if k = 0 then out := y
done;
!out
end
in
t.dc <- t.dc +. (dc_coefficient *. (wet -. t.dc));
s.(i) <- ((1. -. mix) *. x) +. (mix *. (wet -. t.dc)))
s;
t.last_drive <- drive;
t.last_mix <- mix
let knobs : Effect.knob list =
[
{ name = "shape"; control = Selector (List.map name shapes); initial = 1. };
{ name = "gain"; control = Knob (0., 36.); initial = 12. };
{ name = "oversampling"; control = Switch; initial = 1. };
]
let fx () : Effect.t =
let plain = (create ~oversampling:1 (), create ~oversampling:1 ()) and oversampled = (create ~oversampling:4 (), create ~oversampling:4 ()) in
let shape = ref Tanh and gain = ref 0. and x4 = ref false in
let set (knob : string) (x : float) =
match knob with
| "shape" -> shape := List.nth shapes (max 0 (min (List.length shapes - 1) (Control.index x)))
| "gain" -> gain := x
| "oversampling" -> x4 := Control.on x
| _ -> ()
in
List.iter (fun (k : Effect.knob) -> set k.name k.initial) knobs;
let process (s : Signal.stereo) =
let left, right = if !x4 then oversampled else plain in
process left !shape ~drive:!gain ~mix:1. s.left;
process right !shape ~drive:!gain ~mix:1. s.right
in
{ name = "drive"; knobs; set; process; meters = (fun () -> []) }