/
hard_clipper.jsfx
154 lines (129 loc) · 4.19 KB
/
hard_clipper.jsfx
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
// Hard Clipper
//
// Simple hard clipping that will rigorously "chop off" any
// signal peaks that shoot above the set ceiling.
//
// This clipper has hardcoded (but optional) 4x oversampling
// so it should not cause a very hefty CPU hit while active.
// It doesn't "save" the signal, just makes things a little
// smoother, but it comes at the cost of losing true 0 dBfs
// peak safety from all the filtering. If you need reliable
// 0 dBfs peak safety, then load another clipper after this,
// or just simply disable oversampling.
//
// Since hard clipping causes lots of aliasing, I've added
// a DC Blocker, can't hurt to have it ready, just in case.
//
// author: chokehold
// url: https://github.com/chkhld/jsfx/
// tags: processing gain amplitude clipper distortion saturation
//
desc: Hard Clipper
slider1:dBCeil=0<-48, 0,0.01> Ceiling dBfs
slider2:dBGain=0< 0,48,0.01> Boost dB
slider3:ovs=0<0,1,{Off,On}> Oversampling
slider4:blocker=0<0,1,{Enabled,Disabled}> DC Blocker
in_pin:left input
in_pin:right input
out_pin:left output
out_pin:right output
@init
// Buffer for upsampled samples
ups = 100000;
// Decibel to gain factor conversion
function dBToGain (decibels) (10.0 ^ (decibels / 20.0));
// Hard clipping function with ceiling
function hardclip ()
(
this = max(-ceiling, min(ceiling, this))
);
// DC Blocker to remove near-static frequency content
// that would otherwise "offset" the waveform.
function dcBlocker () instance (stateIn, stateOut)
(
stateOut *= 0.99988487;
stateOut += this - stateIn;
stateIn = this;
this = stateOut;
);
// Filter used for up- and downsampling
function bwLP (Hz, SR, order, memOffset) instance (a, d1, d2, w0, w1, w2, stack) local (a1, a2, ro4, step, r, ar, ar2, s2, rs2)
(
a = memOffset; d1 = a+order; d2 = d1+order; w0 = d2+order; w1 = w0+order; w2 = w1+order;
stack = order; a1 = tan($PI * (Hz / SR)); a2 = sqr(a1); ro4 = 1.0 / (4.0 * order);
step = 0; while (step < order)
(
r = sin($PI * (2.0 * step + 1.0) * ro4); ar2 = 2.0 * a1 * r; s2 = a2 + ar2 + 1.0; rs2 = 1.0 / s2;
a[step] = a2 * rs2; d1[step] = 2.0 * (1.0 - a2) * rs2; d2[step] = -(a2 - ar2 + 1.0) * rs2; step += 1;
);
);
function bwTick (sample) instance (a, d1, d2, w0, w1, w2, stack) local (output, step)
(
output = sample; step = 0;
while (step < stack)
(
w0[step] = d1[step] * w1[step] + d2[step] * w2[step] + output;
output = a[step] * (w0[step] + 2.0 * w1[step] + w2[step]);
w2[step] = w1[step]; w1[step] = w0[step]; step += 1;
);
output;
);
// Fixed 4x oversampling
ovsRate = srate * 4;
upFilterL.bwLP(22050, ovsRate, 2, 200000);
upFilterR.bwLP(22050, ovsRate, 2, 201000);
dnFilterL.bwLP(22000, ovsRate, 4, 202000);
dnFilterR.bwLP(22000, ovsRate, 4, 203000);
@slider
ceiling = dBToGain(dBCeil);
gain = dBToGain(dBGain);
@sample
spl0 *= gain;
spl1 *= gain;
// Do the following only if oversampling is happening
ovs == 1 ?
(
spl0 = upFilterL.bwTick(spl0 * 4);
spl1 = upFilterR.bwTick(spl1 * 4);
counter = 0;
while (counter < 3)
(
ups[counter] = upFilterL.bwTick(0);
ups[counter+4] = upFilterR.bwTick(0);
counter += 1;
);
);
spl0.hardclip();
spl1.hardclip();
// And yet another block of stuff that has to be processed when
// oversamplign is activated
ovs == 1 ?
(
counter = 0;
while (counter < 3)
(
orly1 = ups[counter];
orly1.hardclip();
ups[counter] = orly1;
orly2 = ups[counter+4];
orly2.hardclip();
ups[counter+4] = orly2;
counter += 1;
);
spl0 = dnFilterL.bwTick(spl0);
spl1 = dnFilterR.bwTick(spl1);
counter = 0;
while (counter < 3)
(
ups[counter] = dnFilterL.bwTick(ups[counter]);
ups[counter+4] = dnFilterR.bwTick(ups[counter+4]);
counter += 1;
);
);
// If DC blocking enabled
blocker == 0 ?
(
// Run the DC blocker on each sample
spl0.dcBlocker();
spl1.dcBlocker();
);