├── AUTHORS
├── Clipper
├── clipping_algorithm_comparison.jsfx
├── hard_clipper.jsfx
├── knee_clipper.jsfx
├── loud_clipper.jsfx
├── sine_clipper.jsfx
├── soft_clipper.jsfx
└── staging_clipper.jsfx
├── Distortion
└── foldback_distortion.jsfx
├── Dynamics
├── bus_comp.jsfx
├── consolidator.jsfx
├── gate_expander.jsfx
└── track_comp.jsfx
├── Equalizer
└── eq_560.jsfx
├── FX
├── filthy_delay.jsfx
└── ring_mod.jsfx
├── Filter
└── dc_filter.jsfx
├── Generator
└── test_signals.jsfx
├── Instrument FX
├── amp_sim.jsfx
├── bass_squeezer.jsfx
├── cabinet_sim.jsfx
├── chug_thug.jsfx
└── mic_combiner.jsfx
├── LICENSE
├── Lo-Fi
├── signal_crusher.jsfx
└── telephone.jsfx
├── MIDI
└── midi_chord_trigger.jsfx
├── Metering
├── correlation_meter.jsfx
├── phase_scope.jsfx
├── stereo_checker.jsfx
└── wave_scope.jsfx
├── Noise
├── interpolated_noise.jsfx
└── reference_noise.jsfx
├── PLUGINS.md
├── README.md
├── Stereo
├── m-s_fader.jsfx
├── stereo_bleed_remover.jsfx
└── stereo_pan.jsfx
├── Utility
├── dc_offset.jsfx
├── impulse_generator.jsfx
├── string_tuning_calculator.jsfx
├── volume_range_trim.jsfx
└── volume_trim.jsfx
├── assets
├── reapack-index command.txt
├── screenshots
│ ├── amp_sim.png
│ ├── bass_squeezer.png
│ ├── bus_comp.png
│ ├── cabinet_sim.png
│ ├── chug_thug.png
│ ├── clipping_algorithm_comparison.png
│ ├── consolidator.png
│ ├── correlation_meter.png
│ ├── dc_offset.png
│ ├── eq_560.png
│ ├── filthy_delay.png
│ ├── foldback_distortion.png
│ ├── gate_expander.png
│ ├── hard_clipper.png
│ ├── impulse_generator.png
│ ├── interpolated_noise.png
│ ├── knee_clipper.png
│ ├── loud_clipper.png
│ ├── m-s_fader.png
│ ├── mic_combiner.png
│ ├── midi_chord_trigger.png
│ ├── phase_scope.png
│ ├── reference_noise.png
│ ├── ring_mod.png
│ ├── signal_crusher.png
│ ├── sine_clipper.png
│ ├── soft_clipper.png
│ ├── staging_clipper.png
│ ├── stereo_bleed_remover.png
│ ├── stereo_checker.png
│ ├── stereo_pan.png
│ ├── string_tuning_calculator.png
│ ├── telephone.png
│ ├── test_signals.png
│ ├── track_comp.png
│ ├── volume_range_trim.png
│ ├── volume_trim.png
│ └── wave_scope.png
└── thumbnails
│ ├── amp_sim.jpg
│ ├── bass_squeezer.jpg
│ ├── bus_comp.jpg
│ ├── cabinet_sim.jpg
│ ├── chug_thug.jpg
│ ├── clipping_algorithm_comparison.jpg
│ ├── consolidator.jpg
│ ├── correlation_meter.jpg
│ ├── dc_offset.jpg
│ ├── eq_560.jpg
│ ├── filthy_delay.jpg
│ ├── foldback_distortion.jpg
│ ├── gate_expander.jpg
│ ├── hard_clipper.jpg
│ ├── impulse_generator.jpg
│ ├── interpolated_noise.jpg
│ ├── knee_clipper.jpg
│ ├── loud_clipper.jpg
│ ├── m-s_fader.jpg
│ ├── mic_combiner.jpg
│ ├── midi_chord_trigger.jpg
│ ├── phase_scope.jpg
│ ├── reference_noise.jpg
│ ├── ring_mod.jpg
│ ├── signal_crusher.jpg
│ ├── sine_clipper.jpg
│ ├── soft_clipper.jpg
│ ├── staging_clipper.jpg
│ ├── stereo_bleed_remover.jpg
│ ├── stereo_checker.jpg
│ ├── stereo_pan.jpg
│ ├── string_tuning_calculator.jpg
│ ├── telephone.jpg
│ ├── test_signals.jpg
│ ├── track_comp.jpg
│ ├── volume_range_trim.jpg
│ ├── volume_trim.jpg
│ └── wave_scope.jpg
└── index.xml
/AUTHORS:
--------------------------------------------------------------------------------
1 | chokehold
--------------------------------------------------------------------------------
/Clipper/hard_clipper.jsfx:
--------------------------------------------------------------------------------
1 | desc: Hard Clipper
2 | version: 1.8.3
3 | author: chokehold
4 | tags: processing gain amplitude clipper distortion saturation
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/hard_clipper.png
7 | about:
8 | # Hard Clipper
9 |
10 | Simple hard clipping that will rigorously "chop off" any
11 | signal peaks that shoot above the set ceiling.
12 |
13 | This clipper has hardcoded (but optional) 4x oversampling
14 | so it should not cause a very hefty CPU hit while active.
15 | It doesn't "save" the signal, just makes things a little
16 | smoother, but it comes at the cost of losing true 0 dBfs
17 | peak safety from all the filtering. If you need reliable
18 | 0 dBfs peak safety, then load another clipper after this,
19 | or just simply deactivate oversampling.
20 |
21 | Since hard clipping causes lots of aliasing, I've added
22 | a DC Blocker, can't hurt to have it ready, just in case.
23 |
24 | // ----------------------------------------------------------------------------
25 | slider1:dBCeil=0<-48, 0,0.01> Ceiling dBfs
26 | slider2:dBGain=0< 0,48,0.01> Boost dB
27 | slider3:ovs=0<0,1,{Off,On}> Oversampling
28 | slider4:blocker=0<0,1,{Activated,Deactivated}> DC Blocker
29 |
30 | in_pin:left input
31 | in_pin:right input
32 | out_pin:left output
33 | out_pin:right output
34 |
35 | @init
36 |
37 | // Buffer for upsampled samples
38 | ups = 100000;
39 |
40 | // Decibel to gain factor conversion
41 | function dBToGain (decibels) (pow(10, decibels * 0.05));
42 |
43 | // Hard clipping function with ceiling
44 | function hardclip ()
45 | (
46 | this = max(-ceiling, min(ceiling, this))
47 | );
48 |
49 | // DC Blocker to remove near-static frequency content
50 | // that would otherwise "offset" the waveform.
51 | function dcBlocker () instance (stateIn, stateOut)
52 | (
53 | stateOut *= 0.99988487;
54 | stateOut += this - stateIn;
55 | stateIn = this;
56 | this = stateOut;
57 | );
58 |
59 | // Filter used for up- and downsampling
60 | function bwLP (Hz, SR, order, memOffset) instance (a, d1, d2, w0, w1, w2, stack) local (a1, a2, ro4, step, r, ar, ar2, s2, rs2)
61 | (
62 | a = memOffset; d1 = a+order; d2 = d1+order; w0 = d2+order; w1 = w0+order; w2 = w1+order;
63 | stack = order; a1 = tan($PI * (Hz / SR)); a2 = sqr(a1); ro4 = 1.0 / (4.0 * order);
64 | step = 0; while (step < order)
65 | (
66 | r = sin($PI * (2.0 * step + 1.0) * ro4); ar2 = 2.0 * a1 * r; s2 = a2 + ar2 + 1.0; rs2 = 1.0 / s2;
67 | a[step] = a2 * rs2; d1[step] = 2.0 * (1.0 - a2) * rs2; d2[step] = -(a2 - ar2 + 1.0) * rs2; step += 1;
68 | );
69 | );
70 |
71 | function bwTick (sample) instance (a, d1, d2, w0, w1, w2, stack) local (output, step)
72 | (
73 | output = sample; step = 0;
74 | while (step < stack)
75 | (
76 | w0[step] = d1[step] * w1[step] + d2[step] * w2[step] + output;
77 | output = a[step] * (w0[step] + 2.0 * w1[step] + w2[step]);
78 | w2[step] = w1[step]; w1[step] = w0[step]; step += 1;
79 | );
80 | output;
81 | );
82 |
83 | // Fixed 4x oversampling
84 | ovsRate = srate * 4;
85 |
86 | upFilterL.bwLP(22050, ovsRate, 2, 200000);
87 | upFilterR.bwLP(22050, ovsRate, 2, 201000);
88 |
89 | dnFilterL.bwLP(22000, ovsRate, 4, 202000);
90 | dnFilterR.bwLP(22000, ovsRate, 4, 203000);
91 |
92 | @slider
93 |
94 | ceiling = dBToGain(dBCeil);
95 | gain = dBToGain(dBGain);
96 |
97 | @sample
98 |
99 | spl0 *= gain;
100 | spl1 *= gain;
101 |
102 | // Do the following only if oversampling is happening
103 | ovs == 1 ?
104 | (
105 | spl0 = upFilterL.bwTick(spl0 * 4);
106 | spl1 = upFilterR.bwTick(spl1 * 4);
107 |
108 | counter = 0;
109 | while (counter < 3)
110 | (
111 | ups[counter] = upFilterL.bwTick(0);
112 | ups[counter+4] = upFilterR.bwTick(0);
113 | counter += 1;
114 | );
115 | );
116 |
117 | spl0.hardclip();
118 | spl1.hardclip();
119 |
120 | // And yet another block of stuff that has to be processed when
121 | // oversamplign is activated
122 | ovs == 1 ?
123 | (
124 | counter = 0;
125 | while (counter < 3)
126 | (
127 | orly1 = ups[counter];
128 | orly1.hardclip();
129 | ups[counter] = orly1;
130 |
131 | orly2 = ups[counter+4];
132 | orly2.hardclip();
133 | ups[counter+4] = orly2;
134 |
135 | counter += 1;
136 | );
137 |
138 | spl0 = dnFilterL.bwTick(spl0);
139 | spl1 = dnFilterR.bwTick(spl1);
140 |
141 | counter = 0;
142 | while (counter < 3)
143 | (
144 | ups[counter] = dnFilterL.bwTick(ups[counter]);
145 | ups[counter+4] = dnFilterR.bwTick(ups[counter+4]);
146 | counter += 1;
147 | );
148 | );
149 |
150 | // If DC blocking activated
151 | blocker == 0 ?
152 | (
153 | // Run the DC blocker on each sample
154 | spl0.dcBlocker();
155 | spl1.dcBlocker();
156 | );
157 |
--------------------------------------------------------------------------------
/Clipper/loud_clipper.jsfx:
--------------------------------------------------------------------------------
1 | desc: Loud Clipper
2 | version: 1.8.3
3 | author: chokehold
4 | tags: processing gain amplitude clipper distortion saturation
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/loud_clipper.png
7 | about:
8 | # Loud Clipper
9 |
10 | JSFX Implementation of the original "Loud" algorithm from my "ClipMax" plugin,
11 | both nowadays long defunct and abandoned: https://free.chokehold.net/clipmax/
12 |
13 | Additionally comes with the slightly louder "Loud 2" algorithm, and the so far
14 | loudest variant, "Louder".
15 |
16 | The "Louder" algo has an additional "Intensity" parameter, it's effectively an
17 | accelerator that distorts the transfer curve into "fast first, slow later" and
18 | lifts low-volume signal parts above the linear curve and adds a sort-of static
19 | upward compression. If you have a decently loud signal, try turning "Intensity"
20 | up instead of input Gain, maybe even dialing Gain back, and see how that fares.
21 |
22 | Most other aspects of this plugin are straight-forward: the Gain is input gain,
23 | the Trim is output volume, the Ceiling is the maximum output level.
24 |
25 | Careful with the Ceiling, it's like a reverse input Gain, the two sum together.
26 | If you bring down the Ceiling by a few dB, it's like raising the input Gain by
27 | a few dB. So to not always have to re-visit the parameter, best make your mind
28 | up early which one you prefer. :)
29 |
30 | // ----------------------------------------------------------------------------
31 | slider1:dBGain=0<-48,48,0.01>Gain [dB]
32 | slider2
33 | slider3:method=1<0,2,{Loud (legacy),Loud 2,Louder}>Algorithm
34 | slider4:pctIntensity=25<0,100,0.1:log=25>-Intensity [%]
35 | slider5
36 | slider6:dBCeil=0<-48,0,0.01:log=-6>Ceiling [dBfs]
37 | slider7:dBTrim=0<-48,48,0.01>Trim [dB]
38 |
39 | // By not having any in_pin and out_pin assignments, this plugin will
40 | // automatically adapt to the number of channels of the track.
41 |
42 | @init
43 |
44 | // Converts dB values to float amplitude values
45 | function dBToAmp (decibels) (pow(10, decibels * 0.05));
46 |
47 | // HYPERBOLIC TANGENT APPROXIMATION -----------------------------------------
48 | //
49 | // Implemented after code posted by Antto on KVR
50 | // https://www.kvraudio.com/forum/viewtopic.php?p=3781279#p3781279
51 | //
52 | function tanh (number) local (xa, x2, x3, x4, x7, res)
53 | (
54 | xa = abs(number); x2 = xa * xa; x3 = x2 * xa; x4 = x2 * x2; x7 = x4 * x3;
55 | res = (1.0 - 1.0 / (1.0 + xa + x2 + 0.58576695 * x3 + 0.55442112 * x4 + 0.057481508 * x7));
56 | sign(number) * res;
57 | );
58 |
59 | // HYPERBOLIC SINE FUNCTION -------------------------------------------------
60 | //
61 | // Implemented after NumPy
62 | // https://numpy.org/doc/stable/reference/generated/numpy.sinh.html
63 | //
64 | function sinh (number)
65 | (
66 | 0.5 * (exp(number) - exp(-number));
67 | );
68 |
69 | // ERROR FUNCTION -----------------------------------------------------------
70 | //
71 | // Implemented after Stack Overflow
72 | // https://stackoverflow.com/a/457805
73 | //
74 | function erf (x) local (polarity, absolute, a1, a2, a3, a4, a5, p, t, y)
75 | (
76 | (x != 0) * // To avoid DC offset - if (x==0) {return 0;}
77 | (
78 | polarity = sign(x);
79 | absolute = abs(x);
80 |
81 | a1 = 0.254829592;
82 | a2 = -0.284496736;
83 | a3 = 1.421413741;
84 | a4 = -1.453152027;
85 | a5 = 1.061405429;
86 | p = 0.3275911;
87 | t = 1.0 / (1.0 + p * absolute);
88 |
89 | polarity * (1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * exp(-absolute * absolute));
90 | );
91 | );
92 |
93 | // LOUD CLIPPING ------------------------------------------------------------
94 | //
95 | // Sigmoid curve distortion that stays close to the linear signal very long.
96 | // Used in original VST2/AU ClipMax plugin.
97 | //
98 | function loudClip (sample)
99 | (
100 | tanh(0.55 * sinh(1.75 * sample));
101 | );
102 |
103 | // LOUD2 CLIPPING -----------------------------------------------------------
104 | //
105 | // Sigmoid curve distortion that stays close to the linear signal very long.
106 | // Successor to the original ClipMax plugin "Loud" algorithm.
107 | //
108 | function loudClip2 (sample)
109 | (
110 | erf(0.54 * sinh(1.6 * sample));
111 | );
112 |
113 | // LOUDER CLIPPING
114 | //
115 | // Super simple, super loud. By "accelerating" the input sample the transfer
116 | // curve can be "boosted" over the linear, in contrast to most other methods
117 | // that usually stay inside of the linear. A little bit of this is also part
118 | // of the older loud1/loud2 algorithms, but always tamed with the countering
119 | // hyperbolic sine curve again. It's effectively like adding a bit of static
120 | // upward compression.
121 | //
122 | function louderClip (sample, factor)
123 | (
124 | erf(sample * factor);
125 | );
126 |
127 | @slider
128 |
129 | // Turns slider dB values into amplitude values
130 | gain = dBToAmp(dBGain);
131 | wall = dBToAmp(dBCeil);
132 | trim = dBToAmp(dBTrim);
133 |
134 | // Louder Clipping allows a boost factor parameter, other methods don't.
135 | slider_show(pctIntensity, (method == 2));
136 |
137 | // Boost factor for the Louder Clipping algorithm.
138 | // Ranges from 1/4π to ~2 = 0.6366197724π = (0.25+0.3866197724)π.
139 | louder_factor = $pi * (0.25 + pctIntensity * 0.003866197724);
140 |
141 | // Pre-clip boost factor to fake ceiling with soft clipping functions
142 | wallBoost = 1.0 / wall;
143 |
144 | @sample
145 |
146 | // Louder Clipping allows a boost factor parameter, other methods don't.
147 | // Not reliable enough to do this only in @slider, sadly.
148 | slider_show(pctIntensity, (method == 2));
149 |
150 | // Set channel counter to 0 to start with first input channel
151 | channel = 0;
152 |
153 | // Cycle through all of the plugin's input channels
154 | while (channel < num_ch)
155 | (
156 | // Fetch the channel's sample and apply fake ceiling boost and input gain
157 | channelSample = spl(channel) * wallBoost * gain;
158 |
159 | // Loud (legacy; tanh+sinh)
160 | (method == 0) ? (channelClipped = loudClip(channelSample))
161 | :
162 | // Loud 2 (erf+sinh)
163 | (method == 1) ? (channelClipped = loudClip2(channelSample))
164 | :
165 | // Louder (erf)
166 | (method == 2) ? (channelClipped = louderClip(channelSample, louder_factor));
167 |
168 | // Apply fake ceiling compensation and output Trim, then write to output sample
169 | spl(channel) = channelClipped * wall * trim;
170 |
171 | // Increment counter to next channel
172 | channel += 1;
173 | );
174 |
175 |
--------------------------------------------------------------------------------
/Clipper/sine_clipper.jsfx:
--------------------------------------------------------------------------------
1 | desc: Sine Clipper
2 | version: 1.8.3
3 | author: chokehold
4 | tags: processing gain amplitude clipper distortion saturation
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/sine_clipper.png
7 | about:
8 | # Sine Clipper
9 |
10 | Clipper that uses the smoothness of the sine wave to tame loud signal peaks.
11 |
12 | In floating point audio, the sample values -1.0 and +1.0 are "as loud as it
13 | could possibly be". A sample value beyond -1.0 or +1.0 would overdrive your
14 | D/A converter and lead to distortion. A value of 0.0 means total silence.
15 |
16 | The usual way to clip a signal is to either hard-restrict it into the range
17 | between -1 and +1, i.e. "clip" every louder sample to the -1/+1 maximum, or
18 | to apply something like a sigmoid function to it, that gradually attenuates
19 | the input, thereby also restricting sample values to inside the -1/+1 range.
20 |
21 | But hard clipping causes nasty and harsh harmonics, and soft clipping tends
22 | to sound buzzy and quieten the signal. This clipper makes use of both those
23 | principles, but with a twist that makes it much better.
24 |
25 | The sine function is not a sigmoid, so its output will not keep approaching
26 | the -1/+1 limit without ever reaching it. At 1/2π the sine function outputs
27 | exactly 1.0, also for negative polarity. The value of 1/2π is ~1.5708 which
28 | as a sample value is roughly +3.922 dBfs, that's well above the 0 dBfs mark.
29 | In other words: the sine function can soft-clip very loud signals down from
30 | +3.922 dBfs to 0 dBfs -- that is, as long as the incoming samples are below
31 | that magic 1/2π value.
32 |
33 | After the magical 1/2π point, the output of the sine function will start to
34 | gradually approach 0 again, which is of little use for clipping, because it
35 | means louder samples don't just get restricted to the -1/+1 limit, but they
36 | create quieter output the louder they become. So something needs to be done
37 | to stop this from happening.
38 |
39 | And that's what this clipper does. Samples values outside [-1/2π,+1/2π] are
40 | just hard-clipped, everything else inside is soft-clipped using the sine.
41 |
42 | Or to put it into real-world relation by throwing them numbers around again:
43 | signal levels of up to ~ +3.922 dBfs (well above 0 dBfs) will NOT merely be
44 | brutishly hard-clipped, instead they are shaped and stay hard-clipping-free.
45 | So the incoming signal could be nearly +4 dBfs above the point at which the
46 | level meters usually start freaking out - and there still won't be any hard
47 | clipping, also no overs on your track.
48 |
49 | A great thing about the sine function is that its output values stay closer
50 | to the input value, especially at larger values, than many other comparable
51 | functions like e.g. the beloved hyperbolic tangent or arctangent. Therefore
52 | it introduces way less distortion harmonics while letting more of the input
53 | signal survive, and there's much less attenuation in the lower volumes than
54 | those aforementioned sigmoids tend to cause. The transition into distortion
55 | also happens noticeably smoother.
56 |
57 | TL;DR: More headroom, louder signal, fewer artefacts, softer clipping onset,
58 | less "buzz". There's no reason to keep using regular sigmoid-based clippers
59 | anymore like a basic boomer. ;)
60 |
61 | // ----------------------------------------------------------------------------
62 | slider1:dBCeil=0<-48,0,0.01>Ceiling [dBfs]
63 | slider2:dBGain=0<-48,48,0.01>Boost [dB]
64 |
65 | // By not having any in_pin and out_pin assignments, this plugin will
66 | // automatically adapt to the number of channels of the track.
67 |
68 | @init
69 |
70 | // Convenience variables for CPU economy during processing
71 | halfPi = $pi * 0.5;
72 |
73 | // Converts dB values to float gain factors
74 | function dBToGain (decibels) (pow(10, decibels * 0.05));
75 |
76 | // SINE CLIPPING -------------------------------------------------------------
77 | //
78 | // The output of the sine function is within the range [-1,+1] as long as its
79 | // input stays inside the range [-1/2π,+1/2π]. This is perfect to create soft
80 | // overdrive, with less distortion than common hyperbolic tangent saturation.
81 | //
82 | // This type of clipping doesn't use a ceiling, if you want one then you have
83 | // to boost the signal into this function first, and attenuate it later on by
84 | // the factor 1/boost.
85 | //
86 | function sineClip (sample) local (inside)
87 | (
88 | // Evaluate whether the sample is outside of range [-1/2π,+1/2π]
89 | inside = (abs(sample) < halfPi);
90 |
91 | // If the sample is outside [-1/2π,+1/2π] then output the sample's polarity,
92 | // which will always be either -1 or +1, making it a perfect 0 dBfs ceiling
93 | // value for hard clipping. If the sample is inside [-1/2π,+1/2π], then run
94 | // it through the sin() function, which returns values in range [-1,+1], so
95 | // the signal is gently scaled until well over the usual 0 dBfs hard limit.
96 | //
97 | inside * sin(sample) + !inside * sign(sample);
98 | );
99 |
100 | @slider
101 |
102 | // Turns slider dB values into float gain factors
103 | gain = dBToGain(dBGain);
104 | wall = dBToGain(dBCeil);
105 |
106 | // Ceiling-related boost factor
107 | wallBoost = 1.0 / wall;
108 |
109 | @sample
110 |
111 | // Set this to 0 to start with first input channel
112 | channel = 0;
113 |
114 | // Cycle through all of the plugin's input channels
115 | while (channel < num_ch)
116 | (
117 | // Repetitive spl(n) addressing is slow
118 | channelSample = spl(channel);
119 |
120 | // Apply boost/gain to input sample
121 | channelSample *= gain;
122 |
123 | // To get a 'real' ceiling, the input needs to be boosted first,
124 | // then the signal clipped, and finally the boosted and clipped
125 | // output has to be attenuated again.
126 | //
127 | channelSample = sineClip(wallBoost * channelSample) * wall;
128 |
129 | // Write the processed sample to the channel output
130 | spl(channel) = channelSample;
131 |
132 | // Increment counter to next channel
133 | channel += 1;
134 | );
135 |
136 |
--------------------------------------------------------------------------------
/Clipper/soft_clipper.jsfx:
--------------------------------------------------------------------------------
1 | desc: Soft Clipper
2 | version: 1.8.3
3 | author: chokehold
4 | tags: processing gain amplitude clipper distortion saturation
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/soft_clipper.png
7 | about:
8 | # Soft Clipper
9 |
10 | Based on hyperbolic tangent clipping, it's what some would
11 | sell you as gentle tube distortion with warm harmonics. :)
12 |
13 | The basic idea is to have it both ways round: you can boost a
14 | signal into the ceiling, or lower the ceiling onto the signal.
15 |
16 | Either way, the closer the signal approaches the ceiling, the
17 | more it will be pushed down and squashed. The signal will not
18 | reach 0 dBfs (*) however hard you push it. This is an instant
19 | effect, so there are no attack or release times.
20 |
21 | Since saturation and soft-clipping can introduce a DC offset,
22 | there's an optional DC blocker included that can filter 0 Hz
23 | frequency content.
24 |
25 | Saturation and soft-clipping can also introduce evil aliasing,
26 | that's when newly generated harmonics are so high in frequency
27 | that they shoot past 22 kHz and could not possibly be handled
28 | anymore by the sample rate so they "fold back" into the lower
29 | part of the frequency spectrum where they can cause havoc and
30 | destruction by becoming audible, sitting in unharmonic spaces
31 | and even cancelling out signal that you actually want to hear.
32 |
33 | To battle this, I've built in a crude oversampling mechanism.
34 | Pick an oversampling factor and a filtering intensity, that's
35 | it. Bear in mind that oversampling adds new samples that were
36 | not part of the signal before, so your CPU will have to munch
37 | through exponentially more samples than without oversampling.
38 | Higher oversampling amount = higher CPU load.
39 |
40 | Processing more samples also means that the filtering that is
41 | so essential to oversampling has to run through more samples,
42 | which means that even the "relaxed" filtering intensity could
43 | become quite heavy to handle at very high oversampling rates
44 | on older or weaker machines.
45 |
46 | The trick is to find a balance between the oversampling ratio
47 | and the filtering. Checking with just a sine wave, a ratio of
48 | 8x and "normal" or "heavy" filtering would take care of just
49 | about any aliasing here in my test setup. But obviously, real
50 | music is immensely more complex than a mere sine wave, so you
51 | might find that higher ratios or intensities work better in
52 | your specific case/s, or that you can maybe get away with far
53 | lower settings than me.
54 |
55 | (*) While the idea of a clipper is to, well, clip the tops off
56 | a signal at a fixed ceiling, unfortunately filtering will
57 | inherently come with an undesirable side-effect: it screws
58 | with the volume. So if you activate oversampling or the DC
59 | blocker, there will be no 0 dBfs ceiling "and not higher"
60 | guarantee anymore, i.e. a hot signal may still shoot above
61 | 0 dBfs.
62 |
63 | To counter this, set the ceiling level low, so you hardly ever
64 | see any spikes over 0 dBfs, then activate the hard clipper. As
65 | the hard clipper is NOT oversampled, and located behind all of
66 | the filters, it will always guarantee true 0 dBfs peak safety.
67 |
68 | However, it will also cause nasty aliasing and pretty quickly,
69 | so make sure you don't boost into it too much. Unless you want
70 | to, in that case go right ahead and knock yourself out. :)
71 |
72 | // ----------------------------------------------------------------------------
73 | slider1:dBCeil=0<-48, 0,0.01> Ceiling dBfs
74 | slider2:dBGain=0< 0,48,0.01> Boost dBfs
75 | slider3:ovs=1<0,4,{Off,2x,4x,8x,16x}> Oversampling
76 | slider4:filter=1<0,3,{Relaxed,Normal,Heavy,Insane}> Filtering
77 | slider5:blocker=1<0,0,{Deactivated,Activated}> DC blocker
78 | slider6:hard=0<0,1,{Deactivated,Activated}> Hard clipping
79 |
80 | in_pin:left input
81 | in_pin:right input
82 | out_pin:left output
83 | out_pin:right output
84 |
85 | @init
86 |
87 | // Buffer for upsampled samples
88 | ups = 100000;
89 |
90 | // Order of up-/downsampling filters. These values should NOT
91 | // be updated immediately after the setting changes on the UI,
92 | // otherwise the filter parameters may change mid-processing,
93 | // which could have nasty side-effects.
94 | orderUp = 1;
95 | orderDn = 1;
96 |
97 | // Decibel to gain factor conversion
98 | function dBToGain (decibels) (pow(10, decibels * 0.05));
99 |
100 | // Hyperbolic tangent approximation, used for soft clipping.
101 | //
102 | // Implemented after code posted by Antto on KVR
103 | // https://www.kvraudio.com/forum/viewtopic.php?p=3781279#p3781279
104 | //
105 | function tanh (number) local (xa, x2, x3, x4, x7, res)
106 | (
107 | xa = abs(number);
108 | x2 = xa * xa;
109 | x3 = x2 * xa;
110 | x4 = x2 * x2;
111 | x7 = x4 * x3;
112 | res = (1.0 - 1.0 / (1.0 + xa + x2 + 0.58576695 * x3 + 0.55442112 * x4 + 0.057481508 * x7));
113 |
114 | sign(number) * res;
115 | );
116 |
117 | // Soft clipping function with ceiling
118 | function softclip ()
119 | (
120 | // Tricky to do, because highest result of soft clipping
121 | // is always 1.0 (ceiling). To get a 'real' ceiling make
122 | // the input louder first, then clip the tops off, later
123 | // attenuate it again.
124 | //
125 | // 1) first get a ceiling <= 1.0
126 | // 2) the amount of boost = 1 / ceiling --> 1 / 0.5 = 2
127 | // 3) clip: input * boost
128 | // 4) attenuate to ceiling level again
129 | //
130 | vCeiling = min(roof, 1.0);
131 | vUpscale = gain / vCeiling;
132 |
133 | this = tanh(this * vUpscale);
134 | this *= vCeiling;
135 | );
136 |
137 | // Clamping a.k.a. hard clipping,
138 | // restricts samples to range [-1/+1]
139 | function hardclip ()
140 | (
141 | this = max(-1, min(1, this))
142 | );
143 |
144 | // DC Blocker to remove near-static frequency content
145 | // that would otherwise "offset" the waveform.
146 | function dcBlocker () instance (stateIn, stateOut)
147 | (
148 | stateOut *= 0.99988487;
149 | stateOut += this - stateIn;
150 | stateIn = this;
151 | this = stateOut;
152 | );
153 |
154 | // Filter used for up- and downsampling
155 | function bwLP (Hz, SR, order, memOffset) instance (a, d1, d2, w0, w1, w2, stack) local (a1, a2, ro4, step, r, ar, ar2, s2, rs2)
156 | (
157 | a = memOffset; d1 = a+order; d2 = d1+order; w0 = d2+order; w1 = w0+order; w2 = w1+order;
158 | stack = order; a1 = tan($PI * (Hz / SR)); a2 = sqr(a1); ro4 = 1.0 / (4.0 * order);
159 | step = 0; while (step < order)
160 | (
161 | r = sin($PI * (2.0 * step + 1.0) * ro4); ar2 = 2.0 * a1 * r; s2 = a2 + ar2 + 1.0; rs2 = 1.0 / s2;
162 | a[step] = a2 * rs2; d1[step] = 2.0 * (1.0 - a2) * rs2; d2[step] = -(a2 - ar2 + 1.0) * rs2; step += 1;
163 | );
164 | );
165 |
166 | function bwTick (sample) instance (a, d1, d2, w0, w1, w2, stack) local (output, step)
167 | (
168 | output = sample; step = 0;
169 | while (step < stack)
170 | (
171 | w0[step] = d1[step] * w1[step] + d2[step] * w2[step] + output;
172 | output = a[step] * (w0[step] + 2.0 * w1[step] + w2[step]);
173 | w2[step] = w1[step]; w1[step] = w0[step]; step += 1;
174 | );
175 | output;
176 | );
177 |
178 | // If the oversampling parameters were updated immediately when
179 | // the sliders/dropdowns change, that could cause undesirable
180 | // side effects if happens in the middle of a calculation run.
181 | //
182 | // Instead of altering the variables right away, check first if
183 | // the new values have changed at all, and only trigger updates
184 | // if that is the case. This saves CPU from calculating filter
185 | // coefficients less frequently, and it guarantees that values
186 | // will only change when this function is called, and only then.
187 | //
188 | function updateOversamplingX () local (newX, newUp, newDn)
189 | (
190 | // Calculate new values, "just in case" and to compare
191 | newX = pow(2, ovs); // 0,1,2,3 -> 1,2,4,8
192 | newUp = 2^(filter+1);
193 | newDn = 2^(filter+2);
194 |
195 | // Check if the new values have in any way changed from the
196 | // previous values, and only if that is the case...
197 | ((newX != ovsX) || (newUp != orderUp) || (newDn != orderDn)) ?
198 | (
199 | // Update the variables that are used in code with the new
200 | // values, because when this function is called, it's safe
201 | // to do so.
202 | ovsX = newX;
203 | orderUp = newUp;
204 | orderDn = newDn;
205 |
206 | // Update the filter instances with the new settings.
207 | // Since these operate at oversampled sample rate, it
208 | // should be fine to have them set really high. In my
209 | // tests these values worked fine, although a filter
210 | // at 22 kHz needs to be REALLY steep to cut off all
211 | // nasties above 22.05 kHz. :)
212 | upFilterL.bwLP(22050, srate*ovsX, orderUp, 200000);
213 | upFilterR.bwLP(22050, srate*ovsX, orderUp, 201000);
214 |
215 | dnFilterL.bwLP(22000, srate*ovsX, orderDn, 202000);
216 | dnFilterR.bwLP(22000, srate*ovsX, orderDn, 203000);
217 | );
218 | );
219 |
220 | @slider
221 |
222 | // Simple "slider dB value to gain factor" conversions
223 | roof = dBToGain(dBCeil); // ceiling
224 | gain = dBToGain(dBGain); // boost
225 |
226 | @sample
227 |
228 | // Before any processing starts, check if new parameter values were
229 | // set on the UI, and "import" them to their targets if so.
230 | updateOversamplingX();
231 |
232 | // Do the following only if oversampling is happening
233 | ovsX > 1 ?
234 | (
235 | // Oversampling is achieved by stuffing an array of samples with
236 | // zeroes, and then running a filter over them. By adding "dead"
237 | // values to the signal and filtering it, the overall volume of
238 | // the zero-stuffed signal will drop by half for every step. To
239 | // counter this, it's enough to multiply the incoming sample by
240 | // the oversampling factor before filtering, this will keep the
241 | // signal level consistent.
242 | spl0 = upFilterL.bwTick(spl0 * ovsX);
243 | spl1 = upFilterR.bwTick(spl1 * ovsX);
244 |
245 | // After filtering the original input samples, it's time to also
246 | // filter all the "dead" zero-value samples that are now part of
247 | // the signal. This is necessary to keep filters' states in sync
248 | // with what is going on, but unfortunately adds to the CPU load
249 | // significantly. For every oversampling step, it's necessary to
250 | // process one "dead" sample with the upsampling filter as well.
251 | // Minus one round, that is, since the filter already ran on the
252 | // input sample.
253 | counter = 0;
254 | while (counter < ovsX-1)
255 | (
256 | ups[counter] = upFilterL.bwTick(0);
257 | ups[counter+ovsX] = upFilterR.bwTick(0);
258 | counter += 1;
259 | );
260 | );
261 |
262 | // Oversampled or not, this is the place where the magic happens,
263 | // i.e. this is where the signal is clipped.
264 | spl0.softclip();
265 | spl1.softclip();
266 |
267 | // And yet another block of stuff that has to be processed when
268 | // oversamplign is activated
269 | ovsX > 1 ?
270 | (
271 | // Unfortunately, even though they'll be discarded without second
272 | // thought later on, it's necessary to process the "dead" samples
273 | // as well, just so the filters have signal to work on & can stay
274 | // in sync with the rest of the process. Omitting this step would
275 | // save CPU cycles, but it would not sound right. Sad.
276 | counter = 0;
277 | while (counter < ovsX-1)
278 | (
279 | orly1 = ups[counter];
280 | orly1.softclip();
281 | ups[counter] = orly1;
282 |
283 | orly2 = ups[counter+ovsX];
284 | orly2.softclip();
285 | ups[counter+ovsX] = orly2;
286 |
287 | counter += 1;
288 | );
289 |
290 | // Filtering the actual signal samples that we really want to keep.
291 | // These downsampling filters should be really steep, so that they
292 | // cut away all the frequency content above 22 kHz that would fold
293 | // into the audible signal and cause aliasing.
294 | spl0 = dnFilterL.bwTick(spl0);
295 | spl1 = dnFilterR.bwTick(spl1);
296 |
297 | // And yet another loop to let the downsampling filters process
298 | // dead samples. Although these samples are practically irrelevant
299 | // after this step, it's still necessary to run them through the
300 | // filters so that they run at oversampled sample rate and so they
301 | // are in the correct state for when the next real sample arrives.
302 | counter = 0;
303 | while (counter < ovsX-1)
304 | (
305 | ups[counter] = dnFilterL.bwTick(ups[counter]);
306 | ups[counter+ovsX] = dnFilterR.bwTick(ups[counter+ovsX]);
307 | counter += 1;
308 | );
309 | );
310 |
311 | // If DC blocking activated
312 | blocker == 1 ?
313 | (
314 | // Run the DC blocker on each sample
315 | spl0.dcBlocker();
316 | spl1.dcBlocker();
317 | );
318 |
319 | // If hard clipping is activated
320 | hard == 1 ?
321 | (
322 | // Hard-clip the output to avoid any distortion outside of this
323 | // plugin. Careful, this is not oversampled, so once it clips,
324 | // it will alias. Try to avoid using this, unless of course you
325 | // want to.
326 | spl0.hardclip();
327 | spl1.hardclip();
328 | );
329 |
--------------------------------------------------------------------------------
/Clipper/staging_clipper.jsfx:
--------------------------------------------------------------------------------
1 | desc:Staging Clipper
2 | version: 1.8.3
3 | author: chokehold
4 | tags: processing gain amplitude clipper distortion saturation
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/staging_clipper.png
7 | about:
8 | # Staging Clipper
9 |
10 | This clipper is intended for signal leveling rather than distortion.
11 |
12 | The idea was that the ceiling can be at an arbitrary point, even way above 0.0 dBfs,
13 | but wherever the ceiling sits, what comes out of the plugin will never exceed those
14 | 0.0 dBfs. Could be useful to create consistency between multiple tracks that vastly
15 | vary in level.
16 |
17 | The ceiling value you set will be followed by the plugin, but also compensated by an
18 | internal gain stage to always have the output clip at 0.0 dBfs. You can then use the
19 | output trim as yet another gain adjustment, to offset the signal volume to wherever
20 | you need it to sit.
21 |
22 | If you set the ceiling to -12 dBfs, then the plugin will clip at -12 dBfs as instructed,
23 | but it will also boost the output by +12 dB so the signal coming out of the plugin will
24 | always appear clipped at 0 dBfs.
25 |
26 | The input boost parameter lets you, well, boost the signal into the clipping stage.
27 |
28 | The output trim will offset the output volume away from the 0 dBfs ceiling.
29 |
30 | So... if you want nothing to come out of the clipper past -12 dBfs, you'll need to set
31 | the ceiling to -12 dBfs to have the signal clipped there, and the output trim to -12 dB
32 | so the 0 dBfs compensated output is offset down to the desired -12 dBfs ceiling.
33 |
34 | And if your signal tends to shoot past +6 dBfs and you want to keep most of its detail,
35 | you can set the ceiling to +6 dBfs. The plugin will output a signal clipped at +6 dBfs
36 | but leveled to 0 dBfs, you can then use the output trim to adjust the output volume.
37 |
38 | Easy, right? Ah, you'll get it. :)
39 |
40 | // ----------------------------------------------------------------------------
41 | slider1:dBboost=0<-24,24,0.01>Input boost [dB]
42 | slider2
43 | slider3:type=0<0,2,1{Clean (sine based),Soft (tanh based),Hard}>Clipping type
44 | slider4:dBfsCeil=0<-24,24,0.01>Ceiling [dBfs]
45 | slider5
46 | slider6:dBtrim=0<-24,24,0.01>Output trim [dB]
47 |
48 | // By not having any in_pin and out_pin assignments, this plugin will
49 | // automatically adapt to the number of channels of the track.
50 |
51 | @init
52 |
53 | // Converts signed Decibels to float amplitude value
54 | function dBToAmp (decibels) (pow(10, decibels * 0.05));
55 |
56 | // HARD CLIPPING -------------------------------------------------------------
57 | //
58 | // Most basic "if larger than ceiling, set to ceiling" clamping.
59 | //
60 | function hardClip (sample)
61 | (
62 | sign(sample) * min(abs(sample), ceiling);
63 | );
64 |
65 | // SOFT CLIPPING -------------------------------------------------------------
66 | //
67 | // Hyperbolic Tangent implemented after code by Antto on KVR
68 | // https://www.kvraudio.com/forum/viewtopic.php?p=3781279#p3781279
69 | //
70 | // This type of clipping doesn't use a ceiling, if you want one then you have
71 | // to boost the signal into this function first, and attenuate it later on by
72 | // the factor 1/boost.
73 | //
74 | function softClip (sample) local (expPos, expNeg)
75 | (
76 | // Fake a non 0 dBfs ceiling by boosting the signal into the 0 dBfs ceiling
77 | sample *= softBoost;
78 |
79 | // Hyperbolic tangent soft clipping below
80 | expPos = exp(sample);
81 | expNeg = 1.0 / (expPos); // exp(-number) = 1/exp(number)
82 | sample = (expPos - expNeg) / (expPos + expNeg);
83 |
84 | // Compensate the initial fake ceiling boost by attenuating by the same factor,
85 | // then return the soft clipped sample.
86 | sample * softAtten;
87 | );
88 |
89 | // SINE CLIPPING -------------------------------------------------------------
90 | //
91 | // The output of the sine function is within the range [-1,+1] as long as its
92 | // input stays inside the range [-1/2π,+1/2π]. This is perfect to create soft
93 | // overdrive, with less distortion than common hyperbolic tangent saturation.
94 | //
95 | // Because sample values can reach +/- 1/2π before the hard clipping sets in,
96 | // there are about +3.92 dB of soft clipping headroom above 0 dBfs.
97 | //
98 | // This type of clipping doesn't use a ceiling, if you want one then you have
99 | // to boost the signal into this function first, and attenuate it later on by
100 | // the factor 1/boost.
101 | //
102 | halfPi = $pi * 0.5;
103 | //
104 | function sineClip (sample) local (inside)
105 | (
106 | // Fake a non 0 dBfs ceiling by boosting the signal into the 0 dBfs ceiling
107 | sample *= softBoost;
108 |
109 | // Evaluate if the sample is inside the range [-1/2π,+1/2π]
110 | inside = abs(sample) < halfPi;
111 |
112 | // If the sample is outside [-1/2π,+1/2π] then output the sample's polarity,
113 | // which will always be either -1 or +1, making it a perfect 0 dBfs ceiling
114 | // value for hard clipping. If the sample is inside [-1/2π,+1/2π], then run
115 | // it through the sin() function, which returns values in range [-1,+1] for
116 | // peak sample values up to 1/2π, so well over the usual 0 dBfs hard limit.
117 | //
118 | sample = inside * sin(sample) + !inside * sign(sample);
119 |
120 | // Compensate the initial fake ceiling boost by attenuating by the same
121 | // factor, then return the soft clipped sample.
122 | sample * softAtten;
123 | );
124 |
125 | // The actual clipping function.
126 | //
127 | // This could be optimised into a single line, but I'll leave it
128 | // spread over several lines in order to explain what's going on.
129 | function clip (sample) local (polarity)
130 | (
131 | // Store the +/- polarity of the incoming sample
132 | polarity = sign(sample);
133 |
134 | // Apply input boost to the sample
135 | sample *= gain;
136 |
137 | // Clip the boosted sample to stay below the ceiling.
138 | //
139 | (type == 0) ? (sample = sineClip(sample))
140 | :
141 | (type == 1) ? (sample = softClip(sample))
142 | :
143 | (type == 2) ? (sample = hardClip(sample));
144 |
145 | // Apply the make-up gain that will shift the output level up or down
146 | // based on where the ceiling is, so that the output ceiling always
147 | // sits at 0.0 dBfs.
148 | sample *= makeup;
149 |
150 | // Apply the output trim to offset the 0.0 dBfs ceiling to wherever
151 | // the signal's level should sit, then return.
152 | sample * trim;
153 | );
154 |
155 | @slider
156 |
157 | // Calculate the gain stages
158 | gain = dBToAmp(dBboost);
159 | ceiling = dBToAmp(dBfsCeil);
160 | makeup = 1.0 / ceiling; // Factor to shift output ceiling to 0 dBfs
161 | trim = dBToAmp(dBtrim);
162 |
163 | // Boost and attenuation for fake ceiling used in soft clipping modes
164 | softBoost = 1.0 / ceiling;
165 | softAtten = 1.0 / softBoost;
166 |
167 | @sample
168 |
169 | // Set this to 0 to start with first audio channel
170 | channel = 0;
171 |
172 | // Cycle through all of the plugin's audio channels
173 | while (channel < num_ch)
174 | (
175 | // Apply volume adjustments and clipping to this channel's sample
176 | spl(channel) = clip(spl(channel));
177 |
178 | // Increment counter to next channel
179 | channel += 1;
180 | );
181 |
--------------------------------------------------------------------------------
/Distortion/foldback_distortion.jsfx:
--------------------------------------------------------------------------------
1 | desc: Foldback Distortion
2 | version: 1.8.3
3 | author: chokehold
4 | tags: processing distortion foldback
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/foldback_distortion.png
7 | about:
8 | # Foldback Distortion
9 |
10 | When the signal reaches the set ceiling or 0, it will be
11 | "folded" into the opposite direction. Which means values
12 | (ceiling + overshoot) will become (ceiling - overshoot).
13 |
14 | This will continue folding between ceiling and 0 until
15 | no more overshoot is found, so it will grow rather CPU
16 | demanding with increasing amounts of folds/distortion.
17 |
18 | This is not oversampled, i.e. it will create aliasing. I
19 | tried adding oversampling to this, and all that happened
20 | was that the distorted waveforms lost the new harmonics
21 | and with every additional oversampling step, the output
22 | gradually went back to its original form. So, kinda meh.
23 |
24 | But I added a DC blocker in, with all the aliasing can't
25 | really hurt to have that around.
26 |
27 | // ----------------------------------------------------------------------------
28 | slider1:dBCeiling=0<-48, 0,0.01> Ceiling dBfs
29 | slider2:dBBoost=0< 0,48,0.01> Boost dB
30 | slider5:blocker=0<0,1,{Activated,Deactivated}> DC Blocker
31 |
32 | in_pin:left input
33 | in_pin:right input
34 | out_pin:left output
35 | out_pin:right output
36 |
37 | @init
38 |
39 | // Decibel to gain factor conversion
40 | function dBToGain (decibels) (pow(10, decibels * 0.05));
41 |
42 | function foldback () instance (signum)
43 | (
44 | signum = sign(this);
45 |
46 | while ((this > ceiling) || (this < 0))
47 | (
48 | this < 0 ? this = abs(this);
49 |
50 | (this > ceiling) ? this = ceiling - (this - ceiling);
51 | );
52 |
53 | this *= signum;
54 | );
55 |
56 | // DC Blocker to remove near-static frequency content
57 | // that would otherwise "offset" the waveform.
58 | function dcBlocker () instance (stateIn, stateOut)
59 | (
60 | stateOut *= 0.99988487;
61 | stateOut += this - stateIn;
62 | stateIn = this;
63 | this = stateOut;
64 | );
65 |
66 | @slider
67 |
68 | ceiling = dBToGain(dBCeiling);
69 | gain = dBToGain(dBBoost);
70 |
71 | @sample
72 |
73 | // Input boost
74 | spl0 *= gain;
75 | spl1 *= gain;
76 |
77 | // Finally, the actual processing
78 | spl0.foldback();
79 | spl1.foldback();
80 |
81 | // If DC blocking activated
82 | blocker == 0 ?
83 | (
84 | // Run the DC blocker on each sample
85 | spl0.dcBlocker();
86 | spl1.dcBlocker();
87 | );
88 |
--------------------------------------------------------------------------------
/Dynamics/consolidator.jsfx:
--------------------------------------------------------------------------------
1 | desc: Consolidator
2 | version: 1.8.3
3 | author: chokehold
4 | tags: processing compressor dynamics gain limiter maximizer
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/consolidator.png
7 | about:
8 | # Consolidator
9 |
10 | The idea is simple: boost a dynamic signal into this processor at ridiculous
11 | amounts of gain, compensate with the makeup gain, turn the mix knob down.
12 |
13 | This is a set of 3 compressors chained in a row, each with its own character.
14 | The compressor thresholds are adjusted in relation to increasing input boost,
15 | so don't be afraid to turn up that boost. Things will become louder at first
16 | but don't be scared, keep pushing that input boost for some serious squash.
17 |
18 | The sidechain high pass filter is applied to every compressor stage. This is
19 | only a sidechain filter that processes the key signal, it will not alter the
20 | program material path.
21 |
22 | Stereo linking is calculated at each compressor stage independently. Linking
23 | L+R channels in the detector makes the stereo image more stable, but it robs
24 | the compressors of possible gain reduction. Rule of thumb: to squash signals
25 | as much as possible ("maximise"), use low or no stereo linking. To help keep
26 | a signal's stereo field intact, use high or full stereo linking.
27 |
28 | Makeup gain is just a simple volume adjustment. Don't get scared when things
29 | get loud, just bring down the makeup gain in the end.
30 |
31 | The dry/wet parameter mixes the unprocessed input signal with the output. If
32 | things get too squashy and flattened for your liking, just turn down the wet
33 | amount and mix in some of the dry signal. Instant parallel/NY compression.
34 |
35 | // ----------------------------------------------------------------------------
36 | slider1:operation=2<0, 2, {Gentle,Dynamic,Hard}>Operation
37 | slider2:dBGain=0<0, 24, 0.01>Input boost [dB]
38 | slider3:scFreq=75<20, 500, 1>SC high pass [Hz]
39 | slider4:midSide=0<0,1,{Stereo (L+R),Mid/Side (M+S)}>Channel routing
40 | slider5:linkAmount=0<0, 100, 1>Stereo link [%]
41 | slider6:dBTrim=0<-12, 12, 0.01>Makeup gain [dB]
42 | slider7:pctMix=100<0,100,0.01>Dry/wet mix [%]
43 |
44 | in_pin:Input L
45 | in_pin:Input R
46 | out_pin:Output L
47 | out_pin:Output R
48 |
49 | @init
50 |
51 | // Stop Reaper from re-initialising the plugin every time playback is reset
52 | ext_noinit = 1;
53 |
54 | // Various convenience constants
55 | M_LN10_20 = 8.68588963806503655302257837833210164588794011607333;
56 | floatFloor = 0.0000000630957; // dBToGain --> ~ -144 dBfs
57 | halfPi = $PI * 0.5;
58 | rcpSqrt2 = 0.7071067812; // Reciprocal constant 1.0 / sqrt(2.0)
59 |
60 | // Converts dB values to float gain factors
61 | function dBToGain (decibels) (pow(10, decibels * 0.05));
62 |
63 | // Converts float gain factors to dB values
64 | function gainTodB (float) local (below)
65 | (
66 | float = abs(float);
67 | below = float < floatFloor;
68 | float = below * floatFloor + (!below) * float;
69 | (log(float) * M_LN10_20);
70 | );
71 |
72 | // Stereo L+R and Mid/Side M+S conversion functions
73 | //
74 | function lrToM (sampleLeft, sampleRight) ((sampleLeft + sampleRight) * rcpSqrt2);
75 | function lrToS (sampleLeft, sampleRight) ((sampleLeft - sampleRight) * rcpSqrt2);
76 | function msToL (sampleMid, sampleSide) ((sampleMid + sampleSide) * rcpSqrt2);
77 | function msToR (sampleMid, sampleSide) ((sampleMid - sampleSide) * rcpSqrt2);
78 |
79 | // SIDECHAIN FILTER
80 | //
81 | // Simple Biquad HP filters used in the sidechain circuit.
82 | // Implemented after Andrew Simper's State Variable Filter paper.
83 | // https://cytomic.com/files/dsp/SvfLinearTrapOptimised2.pdf
84 | //
85 | function eqHP (Hz, Q) instance (a1, a2, a3, m0, m1, m2) local (g, k)
86 | (
87 | g = tan(halfPi * (Hz / srate)); k = 1.0 / Q;
88 | a1 = 1.0 / (1.0 + g * (g + k)); a2 = a1 * g; a3 = a2 * g;
89 | m0 = 1.0; m1 = -k; m2 = -1.0;
90 | );
91 | //
92 | function eqTick (sample) instance (v1, v2, v3, ic1eq, ic2eq)
93 | (
94 | v3 = sample - ic2eq; v1 = this.a1 * ic1eq + this.a2 * v3;
95 | v2 = ic2eq + this.a2 * ic1eq + this.a3 * v3;
96 | ic1eq = 2.0 * v1 - ic1eq; ic2eq = 2.0 * v2 - ic2eq;
97 | (this.m0 * sample + this.m1 * v1 + this.m2 * v2);
98 | );
99 |
100 | // ATTACK / RELEASE ENVELOPE
101 | //
102 | // This will turn a variable into a full envelope container that
103 | // holds an envelope state as well as two time coefficients used
104 | // for separate attack and release timings.
105 | //
106 | function attRelSetup (msAttack, msRelease) instance (coeffAtt, coeffRel) local ()
107 | (
108 | // Set attack and release time coefficients
109 | coeffAtt = exp(-1000 / (msAttack * srate));
110 | coeffRel = exp(-1000 / (msRelease * srate));
111 | );
112 | //
113 | // This calculates the new envelope state for the current sample.
114 | // If the current sample is above the current envelope state, let
115 | // the attack envelope run. And if the current sample is below the
116 | // the current envelope state, then let the release envelope run.
117 | //
118 | // The sample should already be abs()-ed by here to dBfs
119 | //
120 | function attRelTick (dBsample) instance (envelope, coeffAtt, coeffRel) local (above, change)
121 | (
122 | above = (dBsample > envelope);
123 | change = envelope - dBsample;
124 |
125 | // If above, calculate attack + if not above, calculate release
126 | envelope = (above * (dBsample + coeffAtt * change)) + (!above * (dBsample + coeffRel * change));
127 | );
128 |
129 | // GAIN CALCULATOR
130 | //
131 | // From all the various levels, this will calculate more
132 | // values required to calculate with later on.
133 | //
134 | function gainCalcSetup (dBThreshold, fullRatio, dBKnee) instance (threshold, ratio, knee, kneeWidth, kneeUpper, kneeLower) local ()
135 | (
136 | threshold = dBThreshold; // signed dBfs
137 | ratio = 1.0 / fullRatio; // 1/x --> compression < 1, expansion > 1
138 | knee = dBKnee;
139 | kneeWidth = knee * 0.5;
140 | kneeUpper = threshold + kneeWidth;
141 | kneeLower = threshold - kneeWidth;
142 | );
143 | //
144 | function gainCalcTick (dBsample) instance (ratio, knee, kneeLower, kneeUpper, threshold) local (dBReduction, slope)
145 | (
146 | dBReduction = dBsample;
147 | slope = 1.0 - ratio;
148 |
149 | // If the signal is inside the confies of the set Soft Knee,
150 | // calculate the appropriate "soft" reduction here.
151 | (knee > 0.0) && (dBsample > kneeLower) && (dBsample < kneeUpper) ?
152 | (
153 | slope *= ((dBsample - kneeLower) / knee) * 0.5;
154 | dBReduction = slope * (kneeLower - dBsample);
155 | ):(
156 | dBReduction = min(0.0, slope * (threshold - dBsample));
157 | );
158 |
159 | // Return the gain reduction float factor
160 | dBToGain(dBReduction);
161 | );
162 |
163 | // COMPRESSOR
164 | //
165 | // Finally, now all the individual components created
166 | // earlier are combined into a single big compressor.
167 | //
168 | // Full ratio: >1 for compression, <1 for expansion
169 | // dBThreshold: signed dBfs
170 | // dBKnee: absolute/positive dB
171 | //
172 | function compSetup (msAttack, msRelease, dBThreshold, fullRatio, dBKnee) instance (attRel, calc) local ()
173 | (
174 | attRel.attRelSetup(msAttack, msRelease);
175 | calc.gainCalcSetup(dbThreshold, fullRatio, dBKnee);
176 | );
177 | //
178 | function compTick (sample) instance (GR, attRel, calc) local ()
179 | (
180 | // Turn the key sample [-1;1] into a dBfs value and send it
181 | // into the envelope follower.
182 | attRel.attRelTick(gainTodB(sample));
183 |
184 | // Calculate the required gain reduction for this input
185 | // sample based on user-specified parameters. This will
186 | // output the GR value as a float gain factor, NOT in dB.
187 | GR = calc.gainCalcTick(attRel.envelope);
188 |
189 | // This return value is the float factor gain adjustment
190 | // that needs to be applied to the signal sample, it is
191 | // NOT an actual sample value.
192 | GR;
193 | );
194 |
195 | @slider
196 |
197 | // Calculate amount of stereo-linking in the key signals
198 | lnkMix = linkAmount * 0.01;
199 | splMix = 1.0 - lnkMix;
200 |
201 | // Configure the sidechain high-pass filters
202 | filter1L.eqHP(scFreq, 1.5);
203 | filter1R.eqHP(scFreq, 1.5);
204 | filter2L.eqHP(scFreq, 1.5);
205 | filter2R.eqHP(scFreq, 1.5);
206 | filter3L.eqHP(scFreq, 1.5);
207 | filter3R.eqHP(scFreq, 1.5);
208 |
209 | // Turn input/output dB gain values into float factors
210 | gainIn = dBToGain(dBGain);
211 | gainOut = dBToGain(dBTrim);
212 |
213 | // The amount of dry and processed signal to blend
214 | wetDry = pctMix * 0.01;
215 | dryWet = 1.0 - wetDry;
216 |
217 | // Operation: Gentle
218 | operation == 0 ?
219 | (
220 | attack1 = 100;
221 | release1 = 1000;
222 | threshold1 = min(-(dBGain * 2),0);
223 | ratio1 = 1.5;
224 | knee1 = 12;
225 |
226 | attack2 = 25;
227 | release2 = 1000;
228 | threshold2 = min(-(dBGain * 0.5),0);
229 | ratio2 = 2;
230 | knee2 = 12;
231 |
232 | attack3 = 1;
233 | release3 = 1000;
234 | threshold3 = min(-(dBGain * 0.25),0);
235 | ratio3 = 3;
236 | knee3 = 6;
237 | );
238 |
239 | // Operation: Normal
240 | operation == 1 ?
241 | (
242 | attack1 = 200;
243 | release1 = 200;
244 | threshold1 = min(-(dBGain),0);
245 | ratio1 = 1.5;
246 | knee1 = 6;
247 |
248 | attack2 = 20;
249 | release2 = 50;
250 | threshold2 = min(-(dBGain),0);
251 | ratio2 = 2;
252 | knee2 = 12;
253 |
254 | attack3 = 1;
255 | release3 = 1000;
256 | threshold3 = min(-(dBGain * 0.75),0);
257 | ratio3 = 2;
258 | knee3 = 3;
259 | );
260 |
261 | // Operation: Hard
262 | operation == 2 ?
263 | (
264 | attack1 = 150;
265 | release1 = 10;
266 | threshold1 = min(-(dBGain * 2),0);
267 | ratio1 = 2;
268 | knee1 = 12;
269 |
270 | attack2 = 20;
271 | release2 = 10;
272 | threshold2 = min(-(dBGain),0);
273 | ratio2 = 4;
274 | knee2 = 12;
275 |
276 | attack3 = 1;
277 | release3 = 500;
278 | threshold3 = min(-(dBGain * 0.5),0);
279 | ratio3 = 8;
280 | knee3 = 3;
281 | );
282 |
283 | // Set up stage 1 compressors
284 | comp1L.compSetup(attack1, release1, threshold1, ratio1, knee1);
285 | comp1R.compSetup(attack1, release1, threshold1, ratio1, knee1);
286 |
287 | // Set up stage 2 compressors
288 | comp2L.compSetup(attack2, release2, threshold2, ratio2, knee2);
289 | comp2R.compSetup(attack2, release2, threshold2, ratio2, knee2);
290 |
291 | // Set up stage 3 compressors
292 | comp3L.compSetup(attack3, release3, threshold3, ratio3, knee3);
293 | comp3R.compSetup(attack3, release3, threshold3, ratio3, knee3);
294 |
295 | @sample
296 |
297 | // Storing dry samples here for later dry/wet mixing
298 | dryL = spl0;
299 | dryR = spl1;
300 |
301 | // Input gain. Branching is slow, so it's faster to
302 | // just do this multiplication instead of running
303 | // a check to see if it's needed, i.e. dBGain != 0
304 | //
305 | spl0 *= gainIn;
306 | spl1 *= gainIn;
307 |
308 | // If Mid/Side mode is set, convert L+R samples to M+S.
309 | // This will turn the left sample into the mid sample,
310 | // and the right sample into the side sample.
311 | midSide == 1 ?
312 | (
313 | // Can mis-use these as buffers here, since they're
314 | // not yet required by other parts of the process.
315 | keyL = lrToM(spl0, spl1);
316 | keyR = lrToS(spl0, spl1);
317 |
318 | spl0 = keyL;
319 | spl1 = keyR;
320 | );
321 |
322 | //
323 | // COMPRESSOR STAGE #1
324 | //
325 |
326 | // Use channel 1+2 inputs as sidechain key
327 | keyL = spl0;
328 | keyR = spl1;
329 |
330 | // Filter sidechain samples and make them positive.
331 | keyL = abs(filter1L.eqTick(keyL));
332 | keyR = abs(filter1R.eqTick(keyR));
333 |
334 | // Stereo-link the detector signal?
335 | lnkMix > 0 ?
336 | (
337 | // If stereo-linked, take average of both channels
338 | linked = sqrt(sqr(keyL) + sqr(keyR)) * lnkMix;
339 |
340 | // Adjust mix volume of un-linked samples
341 | keyL *= splMix;
342 | keyR *= splMix;
343 |
344 | // Add linked samples on top
345 | keyL += linked;
346 | keyR += linked;
347 | );
348 |
349 | // Run the comp calculations and apply gain reduction
350 | spl0 *= comp1L.compTick(keyL);
351 | spl1 *= comp1R.compTick(keyR);
352 |
353 | //
354 | // COMPRESSOR STAGE #2
355 | //
356 |
357 | // Use channel 1+2 inputs as sidechain key
358 | keyL = spl0;
359 | keyR = spl1;
360 |
361 | // Filter sidechain samples and make them positive.
362 | keyL = abs(filter2L.eqTick(keyL));
363 | keyR = abs(filter2R.eqTick(keyR));
364 |
365 | // Stereo-link the detector signal?
366 | lnkMix > 0 ?
367 | (
368 | // If stereo-linked, take average of both channels
369 | linked = sqrt(sqr(keyL) + sqr(keyR)) * lnkMix;
370 |
371 | // Adjust mix volume of un-linked samples
372 | keyL *= splMix;
373 | keyR *= splMix;
374 |
375 | // Add linked samples on top
376 | keyL += linked;
377 | keyR += linked;
378 | );
379 |
380 | // Run the comp calculations and apply gain reduction
381 | spl0 *= comp2L.compTick(keyL);
382 | spl1 *= comp2R.compTick(keyR);
383 |
384 | //
385 | // COMPRESSOR STAGE #3
386 | //
387 |
388 | // Use channel 1+2 inputs as sidechain key
389 | keyL = spl0;
390 | keyR = spl1;
391 |
392 | // Filter sidechain samples and make them positive.
393 | keyL = abs(filter3L.eqTick(keyL));
394 | keyR = abs(filter3R.eqTick(keyR));
395 |
396 | // Stereo-link the detector signal?
397 | lnkMix > 0 ?
398 | (
399 | // If stereo-linked, take average of both channels
400 | linked = sqrt(sqr(keyL) + sqr(keyR)) * lnkMix;
401 |
402 | // Adjust mix volume of un-linked samples
403 | keyL *= splMix;
404 | keyR *= splMix;
405 |
406 | // Add linked samples on top
407 | keyL += linked;
408 | keyR += linked;
409 | );
410 |
411 | // Run the comp calculations and apply gain reduction
412 | spl0 *= comp3L.compTick(keyL);
413 | spl1 *= comp3R.compTick(keyR);
414 |
415 | // If Mid/Side processing is selected
416 | midSide == 1 ?
417 | (
418 | // Can mis-use these as buffers here, since they're
419 | // no longer required by other parts of the process.
420 | keyL = spl0;
421 | keyR = spl1;
422 |
423 | // Convert the M+S samples back to L+R samples
424 | spl0 = msToL(keyL, keyR);
425 | spl1 = msToR(keyL, keyR);
426 | );
427 |
428 | // Makeup gain. Probably faster to just multiply than
429 | // doing conditional branching.
430 | spl0 *= gainOut;
431 | spl1 *= gainOut;
432 |
433 | // Dry/wet mix
434 | //
435 | // Mixes unprocessed and processed signals this way:
436 | // - 0.0: 100% dry
437 | // - 0.5: 50% dry + 50% wet
438 | // - 1.0: 100% wet
439 | //
440 | wetDry < 1 ?
441 | (
442 | spl0 = dryWet * dryL + wetDry * spl0;
443 | spl1 = dryWet * dryR + wetDry * spl1;
444 | );
445 |
446 |
--------------------------------------------------------------------------------
/Dynamics/gate_expander.jsfx:
--------------------------------------------------------------------------------
1 | desc: Gate/Expander
2 | version: 1.8.3
3 | author: chokehold
4 | tags: processing gate expander dynamics gain
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/gate_expander.png
7 | about:
8 | # Gate/Expander
9 |
10 | A combined noise gate and expander. Both are actually quite
11 | the same, with the main difference that when a gate closes,
12 | it closes all the way to silence - but expanders only close
13 | down to certain level. A gate is like an extreme flavour of
14 | expander, and an expander is like a soft type of noise gate.
15 |
16 | Why do you need both, and when would you use which? Simple.
17 |
18 | Things like distorted electric guitars may need to get rid
19 | of some hissing, and having any remainder of the hiss still
20 | in your signal won't be of any profit, so it's a clear vote
21 | for the gate that fades all the way to silence.
22 |
23 | Things like acoustic drum kits live and breathe through the
24 | combination of all their various microphone recordings. All
25 | of those microphones will have recorded "bleed" from other
26 | kit pieces in the room, e.g. there will be cymbal noise on
27 | the kick and vice versa. Rigorously cutting away everything
28 | from every track that wasn't intended to be there will make
29 | the combined mix of those tracks sound artificial and void
30 | of any life, and it will throw your mix out of balance. It
31 | just doesn't feel natural if everything is super clean and
32 | trimmed, and suddenly there's a hit on the tom that sets a
33 | few gates off that shouldn't have been set off, and right
34 | then is when your entire panorama mix and equalisation are
35 | done for. So in such cases, it may be more desirable to not
36 | let the attenuation happen down to full silence, but rather
37 | only for a limited range. So if many microphones are active
38 | at the same time, your mix won't fall out of proportion.
39 |
40 | Needless to say: the Exp. Range parameter will only work if
41 | the plugin is in expander mode, it has no effect in a gate.
42 |
43 | The Hysteresis is something like a little grace period when
44 | the gate/expander is about to close. Instead of stubbornly
45 | insisting on shutting the volume down because the level has
46 | just fallen below the magic threshold, the Hysteresis will
47 | let the signal fall just a few more dB below the threshold
48 | before the release envelope is triggered. This can be very
49 | useful for transient-rich material which may be quite loud
50 | at first, but then immediately loses its energy. Having a
51 | Hysteresis set will still require the transient to go past
52 | the defined threshold to open the gate/expander, but when
53 | the signal falls again, the gate/expander will stay open a
54 | little bit longer to let non-transient signal also pass.
55 |
56 | A quick word on the channel configurations:
57 |
58 | - Stereo (internal SC)
59 | Regular stereo/dual mono routing, the incoming audio is
60 | also the key signal and "gates itself".
61 |
62 | - Stereo (external SC)
63 | Stereo/dual mono routing, but only signal coming through
64 | plugin channels 3+4 will be used as the key source that
65 | triggers the gate/expander envelope.
66 |
67 | - Mono (L) amd Mono (R)
68 | Will route the selected input channel to all outputs,
69 | the incoming signal will be used to "gate itself".
70 |
71 | - Mono (signal L / key R) and Mono (key L / signal R)
72 | Similar to the previous, only the channel marked with
73 | "signal" will be routed to both outputs, and the other
74 | channel marked as "key" will be used as the triggering
75 | key signal to gate it.
76 |
77 | The stereo linking features is only active in the first two
78 | routing options, i.e. Stereo with internal/external SC. It
79 | uses "louder channel" priorisation, so whichever channel is
80 | louder will open/close the gate/expander for both channels.
81 |
82 | Just for fun, I added a primitive high-pass filter into the
83 | sidechain. This will be applied to any key input, internal
84 | external, stereo, mono. Any side-chain input sample will be
85 | filtered.
86 |
87 | // ----------------------------------------------------------------------------
88 | in_pin:Input L
89 | in_pin:Input R
90 | in_pin:External SC / L
91 | in_pin:External SC / R
92 | out_pin:Output L
93 | out_pin:Output R
94 |
95 | slider1:operation=0<0,1,{Gate [fade to silence],Expander [fade to range]}> Operation
96 | slider2:gateThresh=-60<-60,0,0.1> Threshold [dB]
97 | slider3:gateRange=-40<-40,0,0.1> Exp. Range [dB]
98 | slider4:gateHyst=-3<-12,0,0.01> Hysteresis [dB]
99 | slider5:gateAttack=5<1,50,0.1> Attack [ms]
100 | slider6:gateRelease=300<50, 2500, 0.1> Release [ms]
101 | slider7:linkAmount=0<0,100,1> Stereo Link [%]
102 | slider8:scFreq=70<20,350,1> SC High Pass [Hz]
103 | slider9:routing=0<0,3,{Stereo (internal SC),Stereo (external SC 3-4),Mono (L),Mono (R),Mono (signal L / key R),Mono (key L / signal R)}> Routing
104 |
105 | @init
106 |
107 | halfPi = $PI * 0.5;
108 |
109 | // Sample rate / 1000 = number of samples per millisecond
110 | msRate = srate * 0.001;
111 |
112 | // Global variables that will later hold the values
113 | // the channel envelope followers will follow.
114 | keyL = 0.0;
115 | keyR = 0.0;
116 |
117 | // Helper functions
118 | function dBToGain (decibels) (pow(10, decibels * 0.05));
119 |
120 | // Simple envelope follower, setup stage
121 | function envSetup (msAttack, msRelease, dBThreshold, dBHysteresis) instance (envelope, attack, release, hysteresis, threshold) local ()
122 | (
123 | attack = exp(-1000 / (msAttack * srate));
124 | release = exp(-1000 / (msRelease * srate));
125 | hysteresis = dBToGain(dBHysteresis);
126 | threshold = dBToGain(dBThreshold);
127 | );
128 |
129 | // Simple envelope follower, processing stage
130 | function envFollow (signal) instance (envelope, attack, release, hysteresis) local ()
131 | (
132 | // Conditional branching sucks, e.g. "if (x) then do (y) else if".
133 | // Evaluating conditional branches is a slow process and can become
134 | // taxing on the CPU if done too much. The problem lies not in the
135 | // conditions, but in the branches. What I do here removes branches
136 | // and just turns an if/else statement into conditions that evaluate
137 | // to true/false - or 1/0 - and therefor can be used as factors in a
138 | // simple row of additions and multiplications. It leads to the same
139 | // result, but it's faster in per-sample processes.
140 | //
141 | // Take the first example:
142 | //
143 | // ((signal > envelope) * attack)
144 | // + ((signal <= envelope) * release))
145 | //
146 | // This is a simple addition of two sides, the left one and the right.
147 | // If "signal > envelope" is true, it evaluates to 1. If that happens,
148 | // the "signal <= envelope" will be false and evaluate to 0. This will
149 | // simplify the above calculation like so:
150 | //
151 | // ((1) * attack) + ((0) * release) --> attack + 0
152 | //
153 | // If it were the other way round, and "signal <= envelope" were true,
154 | // then that would make "signal > envelope" false and invert it all.
155 | //
156 | // ((0) * attack) + ((1) * release) --> 0 + release
157 | //
158 | // The result is exactly what conditional branching would have given,
159 | // but without doing any actual branching. :)
160 | //
161 | // Either way, this next bit is "if input louder than envelope, start
162 | // attack stage - else if input quieter than envelope, start release
163 | // stage.
164 | envelope = (((signal > envelope) * attack) + ((signal <= envelope) * release)) * (envelope - signal) + signal;
165 | envelope;
166 | );
167 |
168 | // Gate configuration function
169 | function gateSetup (msAttack, msRelease, dBThreshold, dBRange, dBHysteresis)
170 | (
171 | this.envSetup(msAttack, msRelease, dBThreshold, dBHysteresis);
172 |
173 | // Difference between gate and expander: if Gate mode is selected, the
174 | // "final volume level" for the low end of the envelope will be zero,
175 | // that is absolute silence. But when Expander mode is selected, then
176 | // the actual range parameter from the UI will be used here.
177 | this.range = (operation == 1) ? dBToGain(dBRange) : 0;
178 | );
179 |
180 | // This is the gate function that processes every incoming sample
181 | function tickGate (sample) instance (threshold, gain, envelope, range, hysteresis) local ()
182 | (
183 | // Make the incoming sample all-positive, because the further used gain
184 | // factors like threshold and hysteresis are also positive values. Use
185 | // it to check if the sample is above or below threshold (+hysteresis)
186 | // and to decide whether to let the envelope rise to 1 or fall to range/0.
187 | this.envFollow((abs(sample) < (threshold * hysteresis)) ? range : 1);
188 |
189 | // After the envelope value was calculated, make sure the gain reduction
190 | // does not surpass the "maximum reduction" range parameter. If Gate mode
191 | // is active, the range parameter will be 0 i.e. total silence.
192 | gain = max(envelope, range);
193 |
194 | gain;
195 | );
196 |
197 | // Sidechain high-pass filter processing function
198 | //
199 | // Implemented after Andy Simper's (Cytomic) Biquad Paper. If you want to
200 | // know what these do and how and why, go find his PDF (it's everywhere on
201 | // the Web) and read it. :)
202 | //
203 | function eqTick (sample) instance (v1, v2, v3, ic1eq, ic2eq)
204 | (
205 | v3 = sample - ic2eq;
206 | v1 = this.a1 * ic1eq + this.a2 * v3;
207 | v2 = ic2eq + this.a2 * ic1eq + this.a3 * v3;
208 | ic1eq = 2.0 * v1 - ic1eq; ic2eq = 2.0 * v2 - ic2eq;
209 | (this.m0 * sample + this.m1 * v1 + this.m2 * v2);
210 | );
211 |
212 | // Sidechain high-pass filter coefficient calculation
213 | function eqHP (Hz, Q) instance (a1, a2, a3, m0, m1, m2) local (g, k)
214 | (
215 | g = tan(halfPi * (Hz / srate)); k = 1.0 / Q;
216 | a1 = 1.0 / (1.0 + g * (g + k)); a2 = a1 * g; a3 = a2 * g;
217 | m0 = 1.0; m1 = -k; m2 = -1.0;
218 | );
219 |
220 | @slider
221 |
222 | // Just passing UI values into the configuration method
223 | gateL.gateSetup(gateAttack, gateRelease, gateThresh, gateRange, gateHyst);
224 | gateR.gateSetup(gateAttack, gateRelease, gateThresh, gateRange, gateHyst);
225 |
226 | // Calculates the amount of stereo-linked signal used in the key signal.
227 | lnkMix = linkAmount * 0.01;
228 | splMix = 1.0 - lnkMix;
229 |
230 | // Make the sidechain high-pass filters
231 | filterL.eqHP(scFreq, 1.5);
232 | filterR.eqHP(scFreq, 1.5);
233 |
234 | @block
235 |
236 |
237 |
238 | @sample
239 |
240 | // Stereo int SC
241 | routing == 0 ?
242 | (
243 | // Store filtered sidechain samples
244 | keyL = filterL.eqTick(spl0);
245 | keyR = filterR.eqTick(spl1);
246 |
247 | // Link louder
248 | (lnkMix > 0) ?
249 | (
250 | // If stereo-linked, pick louder channel
251 | linked = max(keyL, keyR) * lnkMix;
252 |
253 | keyL *= splMix;
254 | keyR *= splMix;
255 |
256 | keyL += linked;
257 | keyR += linked;
258 | );
259 |
260 | // Run the gate calculations on whatever mixture
261 | // of the two input channels is left in the keys.
262 | spl0 *= gateL.tickGate(keyL);
263 | spl1 *= gateR.tickGate(keyR);
264 | );
265 |
266 | // Stereo ext SC
267 | (routing == 1) ?
268 | (
269 | // Store filtered sidechain samples
270 | keyL = filterL.eqTick(spl2);
271 | keyR = filterR.eqTick(spl3);
272 |
273 | // Link louder
274 | lnkMix > 0 ?
275 | (
276 | // If stereo-linked, pick louder channel
277 | linked = max(keyL, keyR) * lnkMix;
278 |
279 | keyL *= splMix;
280 | keyR *= splMix;
281 |
282 | keyL += linked;
283 | keyR += linked;
284 | );
285 |
286 | // Run the gate calculations on whatever mixture
287 | // of the two input channels is left in the keys.
288 | spl0 *= gateL.tickGate(keyL);
289 | spl1 *= gateR.tickGate(keyR);
290 | );
291 |
292 | // Mono L
293 | routing == 2 ?
294 | (
295 | // Process L channel and pass its result
296 | // through to R channel
297 | spl1 = spl0 *= gateL.tickGate(filterL.eqTick(spl0));
298 | );
299 |
300 | // Mono R
301 | routing == 3 ?
302 | (
303 | // Process R channel and pass its result
304 | // through to L channel
305 | spl0 = spl1 *= gateR.tickGate(filterR.eqTick(spl1));
306 | );
307 |
308 | // Mono L <- R
309 | routing == 4 ?
310 | (
311 | // Process L channel using R channel as key.
312 | // Duplicate result into channel R.
313 | spl1 = spl0 *= gateL.tickGate(filterL.eqTick(spl1));
314 | );
315 |
316 | // Mono L -> R
317 | routing == 5 ?
318 | (
319 | // Process R channel using R channel as key.
320 | // Duplicate result into channel L.
321 | spl0 = spl1 *= gateR.tickGate(filterR.eqTick(spl0));
322 | );
323 |
--------------------------------------------------------------------------------
/FX/ring_mod.jsfx:
--------------------------------------------------------------------------------
1 | desc: Ring Mod
2 | version: 1.8.3
3 | author: chokehold
4 | tags: effect fx modulation distortion
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/ring_mod.png
7 | about:
8 | # Ring Mod
9 |
10 | In simple terms, this multiplies the input signal with a carrier signal,
11 | which can lead to all sorts of warbly modulation and distortion effects.
12 |
13 | The carrier signal can be selected from a number of trivial oscillators,
14 | or it can be set to use the input signal itself for ultimate modception.
15 |
16 | Frequency selection uses two sliders: the first slider sets the base in
17 | Kilohertz, the second slider sets the offset from the base in Hertz. To
18 | use e.g. 1250 Hz as the carrier's frequency, use any combination of kHz
19 | and Hz, for example kHz at 1.0 and Hz at 250.0, or set e.g. kHz to 0.75
20 | and the Hz slider to 500.0. Either will sum to the 1250 Hz target.
21 |
22 | If the "self" carrier is picked, the two Frequency sliders are not used.
23 |
24 | The Bias slider will add a static offset to the carrier signal and push
25 | its polarity towards fully positive or negative.
26 |
27 | Turning up the Instability slider will introduce non-linearities to the
28 | carrier signal's frequency and the feedback path. The carrier frequency
29 | will become unstable and deviate up and down from the set value. Slight
30 | noise addition will make the feedback path a little more random.
31 |
32 | The Feedback slider defines how much of the previously processed signal
33 | (input * carrier) bleeds back into the input stage of the process.
34 |
35 | Turn up the Drive slider to add sweet saturation to the carrier signal.
36 |
37 | Use the Mix slider to find the perfect balance between the unprocessed
38 | dry input and the processed wet output signal. With the Mix slider set
39 | to 0.0, the processing is bypassed to save CPU.
40 |
41 | The Trim slider is a simple output gain stage, use it for compensation
42 | when the process makes things very loud or quiet. Even with processing
43 | bypassed (Mix at 0.0), the output trim gain will still be applied.
44 |
45 | // ----------------------------------------------------------------------------
46 | slider1:oscill=0<0,7,{Sine,Triangle,Saw rising,Saw falling,Pulse 25%,Pulse 50% / Square,Pulse 75%,Self}>Carrier
47 | slider2:hzFrqC=0<0,10,0.01>Frequency [kHz]
48 | slider3:hzFrqF=500.0<1,1000,0.1>Frequency [Hz]
49 | slider4:pctBis=0<-100,100,0.01>Bias [%]
50 | slider5:pctIns=0<0,100,0.01>Instability [%]
51 | slider6:pctFdb=0<0,100,0.01>Feedback [%]
52 | slider7:pctDrv=0<0,100,0.01>Drive [%]
53 | slider8:pctMix=100<0,100,0.01>Mix [%]
54 | slider9:dBTrim=0<-12,12,0.0001>Trim [dB]
55 |
56 | // By not having any in_pin and out_pin assignments, this plugin will
57 | // automatically adapt to the number of channels of the track.
58 |
59 | @init // =======================================================================
60 |
61 | // Convenience variables
62 | halfPi = 0.5 * $PI; // 1.5707963267949
63 | twoPi = 2.0 * $PI; // 6.28318530717959
64 | sqrt05 = sqrt(0.5); // 0.70710678118655
65 | oscPhase = 999; // Oscillator phase buffer
66 | fdbStore = 1000; // Process feedback buffer
67 | dcfStore = 2000; // DC filter bank buffer
68 |
69 | // Converts dB values to float gain factors
70 | function dBToGain (decibels) (pow(10, decibels * 0.05));
71 |
72 | // Maximum gain amount in Drive circuit
73 | maxDrive = dBToGain(6);
74 |
75 | // RANDOMIZATION -------------------------------------------------------------
76 | //
77 | // Returns a randomised value in range [-limit,+limit]
78 | //
79 | function random (limit) (rand() * 2.0 * limit - limit);
80 |
81 | // SINE WAVE -----------------------------------------------------------------
82 | // _ _
83 | // / \ / \
84 | // \_/ \_/
85 | //
86 | function tickSine (channel)
87 | (
88 | oscPhase[channel] += (twoPi * oscFreq);
89 |
90 | // Wrap phase >2π around from 0
91 | oscPhase[channel] += ((oscPhase[channel] >= twoPi) * -twoPi) + ((oscPhase[channel] < 0.0) * twoPi);
92 |
93 | sin(oscPhase[channel]);
94 | );
95 |
96 | // TRIANGLE WAVE -------------------------------------------------------------
97 | //
98 | // /\ /\ /\
99 | // \/ \/ \/
100 | //
101 | function tickTriangle (channel)
102 | (
103 | oscPhase[channel] += oscFreq;
104 | oscPhase[channel] += ((oscPhase[channel] > 1.0) * -1.0) + ((oscPhase[channel] < 0.0) * 1.0);
105 |
106 | ((oscPhase[channel] < 0.5) * (4.0 * oscPhase[channel] - 1.0)) + ((oscPhase[channel] >= 0.5) * (1.0 - 4.0 * (oscPhase[channel] - 0.5)));
107 | );
108 |
109 | // SAW WAVE (+1 rising, -1 falling) ------------------------------------------
110 | //
111 | // /| /| /|
112 | // Rising: / |/ |/ |
113 | //
114 | // |\ |\ |\
115 | // Falling: | \| \| \
116 | //
117 | function tickSaw (channel, direction)
118 | (
119 | oscPhase[channel] += direction * oscFreq;
120 | oscPhase[channel] += ((oscPhase[channel] > 1.0) * -1.0) + ((oscPhase[channel] < 0.0) * 1.0);
121 |
122 | ((oscPhase[channel] * 2.0) - 1.0);
123 | );
124 |
125 | // PULSE WAVE ----------------------------------------------------------------
126 | // ___
127 | // 25%: | |
128 | // |________|
129 | // ______
130 | // 50%: | |
131 | // |_____|
132 | // _________
133 | // 75%: | |
134 | // |__|
135 | //
136 | function tickPulse (channel, width)
137 | (
138 | oscPhase[channel] += oscFreq;
139 | oscPhase[channel] += ((oscPhase[channel] > 1.0) * -1.0) + ((oscPhase[channel] < 0.0) * 1.0);
140 |
141 | ((oscPhase[channel] < width) * 1) + ((oscPhase[channel] > width) * -1);
142 | );
143 |
144 | // HYPERBOLIC TANGENT --------------------------------------------------------
145 | //
146 | // Approximation implemented after code posted by Antto on KVR
147 | // https://www.kvraudio.com/forum/viewtopic.php?p=3781279#p3781279
148 | //
149 | function tanh (number) local (xa, x2, x3, x4, x7, res)
150 | (
151 | xa = abs(number); x2 = xa * xa; x3 = x2 * xa; x4 = x2 * x2; x7 = x4 * x3;
152 | res = (1.0 - 1.0 / (1.0 + xa + x2 + 0.58576695 * x3 + 0.55442112 * x4 + 0.057481508 * x7));
153 | sign(number) * res;
154 | );
155 |
156 | // ONE POLE LOW PASS FILTER --------------------------------------------------
157 | //
158 | // Implemented after Nigel Redmon
159 | // https://www.earlevel.com/main/2012/12/15/a-one-pole-filter/
160 | //
161 | function rcLP (SR, Hz) instance (a0, b1) (b1 = exp(-twoPi * (Hz / SR)); a0 = 1.0 - b1);
162 | function rcTick (sample) instance (z1) (z1 = (sample * this.a0) + (z1 * this.b1); z1);
163 |
164 | // DC BLOCKING FILTER --------------------------------------------------------
165 | //
166 | // Implemented after Julius O. Smith III
167 | // https://ccrma.stanford.edu/~jos/filters/DC_Blocker_Software_Implementations.html
168 | //
169 | function dcBlock (sample) instance (stateIn, stateOut)
170 | (
171 | stateOut *= 0.99988487;
172 | stateOut += sample - stateIn;
173 | stateIn = sample;
174 | stateOut;
175 | );
176 |
177 | @slider // =====================================================================
178 |
179 | // Convenience variable for faster calculations later
180 | invSrate = 1.0 / srate;
181 |
182 | // Oscillator frequency [0,1]
183 | ctrFreq = (hzFrqC * 1000) + hzFrqF;
184 | srcFreq = ctrFreq * invSrate;
185 |
186 | // Oscillator bias towards full +/- polarities [0,1]
187 | bias = pctBis * 0.01;
188 |
189 | // Instability factor [0,10]
190 | variation = pctIns * 0.005;
191 |
192 | // LP Filter to stabilise the instability (duh)
193 | lpInstability.rcLP(srate, 10 + (90 * variation));
194 |
195 | // Instability noise floor level in feedback path [-dB]
196 | noise = dBToGain(-144.0 + (pctIns * 0.01) * 35.5);
197 |
198 | // Process feedback amount [0,1]
199 | feedback = pctFdb * 0.01;
200 |
201 | // Soft saturation signal amounts [0,1]
202 | drive = pctDrv * 0.01;
203 | mixDrive = drive;
204 | mixClean = sin(halfPi + drive * halfPi); // Slow start, fast end
205 |
206 | // Wet and dry signal mix factors [0,1]
207 | mixWet = pctMix * 0.01;
208 | mixDry = 1.0 - mixWet;
209 |
210 | // Turns the Trim slider's dB value into a gain factor
211 | trim = dBToGain(dBTrim);
212 |
213 | // Evaluating certain conditions now saves doing it in the per-sample block
214 | processing = (pctMix > 0);
215 | unstable = (pctIns > 0);
216 | biased = (pctBis != 0);
217 | selfMode = (oscill == 7);
218 | driven = (pctDrv > 0);
219 |
220 | @sample // =====================================================================
221 |
222 | // Only do full processing if Mix percentage > 0
223 | processing ?
224 | (
225 | // Default values for signal modifiers
226 | carrier = 0.0;
227 | instability = 0.0;
228 |
229 | // If instability parameter turned up
230 | unstable ?
231 | (
232 | // Calculate and slow down instability offset for carrier oscillator
233 | randomise = (random(ctrFreq) * invSrate) * variation;
234 | instability = lpInstability.rcTick(randomise);
235 | );
236 |
237 | // Update carrier oscillator frequency, add instability
238 | oscFreq = srcFreq + instability;
239 |
240 | // Calculate selected carrier oscillator
241 | oscill == 0 ? (carrier = tickSine (0));
242 | oscill == 1 ? (carrier = tickTriangle(0));
243 | oscill == 2 ? (carrier = tickSaw (0, 1.00));
244 | oscill == 3 ? (carrier = tickSaw (0,-1.00));
245 | oscill == 4 ? (carrier = tickPulse(0, 0.25));
246 | oscill == 5 ? (carrier = tickPulse(0, 0.50));
247 | oscill == 6 ? (carrier = tickPulse(0, 0.75));
248 |
249 | // If non-self carrier oscillator selected
250 | oscill < 7 ?
251 | (
252 | // Apply bias to the carrier signal
253 | carrier += biased * bias;
254 | );
255 |
256 | // Start with first input channel
257 | channel = 0;
258 |
259 | // Cycle through all of the plugin's input channels
260 | while (channel < num_ch)
261 | (
262 | // If carrier oscillator is "self", use input signal of this channel
263 | oscill == 7 ? (carrier = spl(channel) + biased * bias);
264 |
265 | // Make copy of input sample because spl() addressing is slow
266 | splDry = spl(channel);
267 |
268 | // If carrier signal set to "self"
269 | selfMode ?
270 | (
271 | // Add input sample and (scaled) instability, already contains bias
272 | carrier += splDry + (instability * 10);
273 | );
274 |
275 | // Store copy of dry input sample in wet processing buffer
276 | splWet = splDry;
277 |
278 | // Add the stored feedback sample to the current wet sample
279 | splWet += fdbStore[channel] * feedback;
280 |
281 | // If Drive parameter is turned up
282 | driven ?
283 | (
284 | // Calculate driven sample from wet sample and compensate its level
285 | splDrv = tanh(splWet * drive * maxDrive) * sqrt05;
286 |
287 | // Mix driven sample in with un-driven wet sample
288 | splWet = (splWet * mixClean) + (splDrv * mixDrive);
289 | );
290 |
291 | // Multiply the wet sample with the carrier signal
292 | splWet *= carrier;
293 |
294 | // Generate a noise sample if instability active
295 | fdbNoise = unstable * random(noise);
296 |
297 | // Store the wet processed sample (and instability) in the feedback buffer
298 | fdbStore[channel] = (splWet + fdbNoise) * sqrt05;
299 |
300 | // Create mix of dry/wet signals
301 | splWet = (splDry * mixDry) + (splWet * mixWet);
302 |
303 | // Apply DC filtering
304 | dcf = dcfStore[channel]; // Pull filter for this channel from storage
305 | splWet = dcf.dcBlock(splWet); // Process DC filter for this channel
306 | dcfStore[channel] = dcf; // Write processed filter back to storage
307 |
308 | // Write fully processed sample to output and apply trim gain
309 | spl(channel) = splWet * trim;
310 |
311 | // Increment counter to next channel
312 | channel += 1;
313 | );
314 | )
315 | : // If not processing modulation, just do volume trim
316 | (
317 | // Start with first input channel
318 | channel = 0;
319 |
320 | // Cycle through all of the plugin's input channels
321 | while (channel < num_ch)
322 | (
323 | // Apply trim output gain
324 | spl(channel) *= trim;
325 |
326 | // Increment counter to next channel
327 | channel += 1;
328 | );
329 | );
330 |
331 |
--------------------------------------------------------------------------------
/Filter/dc_filter.jsfx:
--------------------------------------------------------------------------------
1 | desc: DC Filter
2 | version: 1.8.3
3 | author: chokehold
4 | tags: utility dc offset filter blocker
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/dc_filter.png
7 | about:
8 | # DC Filter
9 |
10 | Multi-channel capable and extremely narrow DC offset removal filter.
11 |
12 | Why would you need another DC filter plugin, if Reaper already comes with one?
13 | Simple: the DC Filter JSFX that comes with Reaper has a wider filter (meaning
14 | it removes more sub low-end) and it only operates on two channels, so it's of
15 | little use when processing multi-channel material.
16 |
17 | This DC filter is as narrow as makes sense, and it keeps sub-bass frequencies
18 | as untouched as possible. The processing automatically adapts to the count of
19 | channels of the track it operates on, even if the channel count should change.
20 |
21 | // ----------------------------------------------------------------------------
22 | // Metering is not really required here
23 | options:no_meter
24 |
25 | // By not having in_pin and out_pin assignments, this plugin is able to adapt to
26 | // the number of channels on its track automatically.
27 |
28 | @init // -----------------------------------------------------------------------
29 |
30 | // Stop Reaper from re-initialising the plugin every time playback is reset as
31 | // it could mess up the state buffers and cause DC to pass during operation.
32 | ext_noinit = 1;
33 |
34 | // DC REMOVAL FILTER ---------------------------------------------------------
35 | //
36 | // Removes 0 Hz content that would offset the signal to one polarity.
37 | //
38 | // The previousAddress argument needs to point to the memory address where the
39 | // previous state value is buffered. Needed for simple multi-channel operation.
40 | //
41 | // Implemented after Sam Koblenski:
42 | // https://sam-koblenski.blogspot.com/2015/11/everyday-dsp-for-programmers-dc-and.html
43 | // Modified "alpha" value from 0.9 for narrower filtering.
44 | //
45 | function dcFilter (sample, previousAddress) local (previous, filtered)
46 | (
47 | previous = previousAddress[0]; // Bracket addressing is slow, saving time
48 | sample += previous * 0.99988487;
49 | filtered = sample - previous;
50 | previousAddress[0] = sample; // Can't avoid bracket addressing here
51 | filtered;
52 | );
53 |
54 | // Memory buffer to store previous states of the DC filters
55 | dcBuffer = 1000;
56 |
57 | @sample // ---------------------------------------------------------------------
58 |
59 | // Start at channel 0
60 | channel = 0;
61 |
62 | // Cycle through all channels on this track
63 | while (channel < num_ch)
64 | (
65 | // Replace this channel's sample by a DC filtered version of itself
66 | spl(channel) = dcFilter(spl(channel), dcBuffer[channel]);
67 |
68 | // Advance to the sample on the next channel
69 | channel += 1;
70 | );
71 |
72 |
--------------------------------------------------------------------------------
/Generator/test_signals.jsfx:
--------------------------------------------------------------------------------
1 | desc: Test Signals
2 | version: 1.8.3
3 | author: chokehold
4 | tags: utility synth noise tone
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/test_signals.png
7 | about:
8 | # Test Signals
9 |
10 | A collection of things that make noises.
11 |
12 | I needed a suite of tools that help me examine plugins and hardware
13 | devices, so I made one. The output from this plugin is added on top
14 | of the input signal, so it's possible to chain several in a row.
15 |
16 | There are 14 possible signals that can be generated on each channel
17 | individually: silence, sine, rising or falling saw, triangle, three
18 | pulse widths (50% creates a rectangular wave), noise colours white,
19 | pink and purple, digital noise (random -1/0/+1 samples), as well as
20 | positive and negative DC offset.
21 |
22 | The Output Channels setting allows not just regular stereo signals,
23 | but also two L/R-only mono modes. The idea was to let two different
24 | signals run, and have the possibility to quickly switch them.
25 |
26 | Because having just one wide-range slider to set the frequency will
27 | give essentially zero resolution below ~1000 Hz, and too fine steps
28 | above ~1000 Hz, I decided to make the frequency setting easier, but
29 | maybe not immediately understandable.
30 |
31 | You set a base frequency first, then you increase the multiplier to
32 | multiply (Base Hz x Multiplier) which will be the actual frequency.
33 |
34 | Example:
35 | Base 10 Hz x Multiplier 10 = 100 Hz
36 | Base 100 Hz x Multiplier 4.4 = 440 Hz
37 | Base 100 Hz x Multiplier 10 = 1000 Hz
38 |
39 | The Volume L+R slider changes the level of both the L+R channels by
40 | the same amount. The L/R Offset sliders will change volume for each
41 | channel individually.
42 |
43 | Flipping a channel's polarity is also often called inverting phase,
44 | or rotating the phase by 180°. Having the same generator and volume
45 | both channels and flipping the polarity while the L+R Summed output
46 | mode is selected will result in silence from phase cancellation.
47 |
48 | The generators are trivial i.e. not band-limited or oversampled, so
49 | some of them (or their combinations) could cause DC offset. I added
50 | an optional DC blocker just before the output to remove this again.
51 |
52 | // ----------------------------------------------------------------------------
53 | slider1: generatorL=1<0,13,1{None,Sine,Saw rising,Saw falling,Triangle,Pulse 25%,Pulse 50% / Square,Pulse 75%,White Noise,Pink Noise,Purple Noise,Digital Noise,DC Offset positive,DC Offset negative}> Generator L
54 | slider2: generatorR=1<0,13,1{None,Sine,Saw rising,Saw falling,Triangle,Pulse 25%,Pulse 50% / Square,Pulse 75%,White Noise,Pink Noise,Purple Noise,Digital Noise,DC Offset positive,DC Offset negative}> Generator R
55 | slider3: viewFreq=1000<10,22000,0.1> Frequency [Hz]
56 | slider4: frequency=1<0,2,1{10 Hz,100 Hz,1 kHz}> Frequency base [Hz]
57 | slider5: multiply=10<0.1,22,0.1> Frequency multiplier
58 | slider6: volume=-12<-48,48,0.01> Volume L+R [dB]
59 | slider7: volumeL=0<-48,48,0.1> Volume L offset [dB]
60 | slider8: volumeR=0<-48,48,0.1> Volume R offset [dB]
61 | slider9: invertL=0<0,1,1{Normal,Flipped}> Flip Polarity L
62 | slider10:invertR=0<0,1,1{Normal,Flipped}> Flip Polarity R
63 | slider11:outRouting=0<0,3,1{Stereo,Mono (left),Mono (right),Mono (summed)}> Output Channels
64 | slider12:blockDC=1<0,1,1{Deactivated,Activated}> DC Blocker
65 |
66 | // This is a generator and doesn't need input monitoring
67 | out_pin:left output
68 | out_pin:right output
69 |
70 | @init
71 |
72 | // Stop Reaper from re-initialising the plugin every time playback is reset
73 | ext_noinit = 1;
74 |
75 | // Used for oscillator phase calculations
76 | twoPi = 6.28318530717958647692528676656;
77 |
78 | // Oscillator center frequency, default to
79 | // 1 kHz @ 44.1 kHz in case something goes wrong.
80 | oscFreq = 0.02267573696145124716553;
81 |
82 | // Parameter value containers
83 | oscType = 1000;
84 | oscPhase = 1010;
85 | dcStateIn = 1020;
86 | dcStateOut = 1030;
87 | outGain = 1040;
88 |
89 | noiseState = 10100;
90 | noiseBuffer = 10200;
91 |
92 | // Converts dB values to float gain factors
93 | function dBToGain (decibels) (pow(10, decibels * 0.05));
94 |
95 | // BASE FREQUENCY SELECTION
96 | //
97 | // Converts the slider's 0,1,2 values into
98 | // 10,100,1000 Hertz base frequency values.
99 | //
100 | function baseFrequency ()
101 | (
102 | frequency == 0 ? 10 : (frequency == 1 ? 100 : 1000);
103 | );
104 |
105 | // PHASE RESET
106 | //
107 | function resetPhases ()
108 | (
109 | oscPhase[0] = (oscType[0] == 4) ? 0.25 : 0.0;
110 | oscPhase[1] = (oscType[1] == 4) ? 0.25 : 0.0;
111 | );
112 |
113 | // DC BLOCKER
114 | //
115 | function dcBlocker (channel)
116 | (
117 | dcStateOut[channel] *= 0.99988487;
118 | dcStateOut[channel] += spl(channel) - dcStateIn[channel];
119 | dcStateIn [channel] = spl(channel);
120 | dcStateOut[channel];
121 | );
122 |
123 | // SINE WAVE
124 | // _ _
125 | // / \ / \
126 | // \_/ \_/
127 | //
128 | function tickSine (channel)
129 | (
130 | oscPhase[channel] += (twoPi * oscFreq);
131 |
132 | // Wrap phase >2π around from 0
133 | oscPhase[channel] += ((oscPhase[channel] >= twoPi) * -twoPi) + ((oscPhase[channel] < 0.0) * twoPi);
134 |
135 | sin(oscPhase[channel]);
136 | );
137 |
138 | // SAW WAVE (+1 rising, -1 falling)
139 | //
140 | // /| /| /|
141 | // Rising: / |/ |/ |
142 | //
143 | // |\ |\ |\
144 | // Falling: | \| \| \
145 | //
146 | function tickSaw (channel, direction)
147 | (
148 | oscPhase[channel] += direction * oscFreq;
149 | oscPhase[channel] += ((oscPhase[channel] > 1.0) * -1.0) + ((oscPhase[channel] < 0.0) * 1.0);
150 |
151 | ((oscPhase[channel] * 2.0) - 1.0);
152 | );
153 |
154 | // TRIANGLE WAVE
155 | //
156 | // /\ /\ /\
157 | // \/ \/ \/
158 | //
159 | function tickTriangle (channel)
160 | (
161 | oscPhase[channel] += oscFreq;
162 | oscPhase[channel] += ((oscPhase[channel] > 1.0) * -1.0) + ((oscPhase[channel] < 0.0) * 1.0);
163 |
164 | ((oscPhase[channel] < 0.5) * (4.0 * oscPhase[channel] - 1.0)) + ((oscPhase[channel] >= 0.5) * (1.0 - 4.0 * (oscPhase[channel] - 0.5)));
165 | );
166 |
167 | // PULSE WAVE
168 | // ___
169 | // 25%: | |
170 | // |________|
171 | // ______
172 | // 50%: | |
173 | // |_____|
174 | // _________
175 | // 75%: | |
176 | // |__|
177 | //
178 | function tickPulse (channel, width)
179 | (
180 | oscPhase[channel] += oscFreq;
181 | oscPhase[channel] += ((oscPhase[channel] > 1.0) * -1.0) + ((oscPhase[channel] < 0.0) * 1.0);
182 |
183 | ((oscPhase[channel] < width) * 1) + ((oscPhase[channel] > width) * -1);
184 | );
185 |
186 | // WHITE NOISE
187 | //
188 | // Just random values between -1 and +1
189 | //
190 | function tickWhite ()
191 | (
192 | ((rand() * 2)-1);
193 | );
194 |
195 | // PURPLE NOISE
196 | //
197 | // Harsh sounding, increases at +6 dBfs per octave across the whole
198 | // spectrum.
199 | //
200 | // Implementation inspired by Judd Niemann's ReSampler project:
201 | // https://github.com/jniemann66/ReSampler/blob/master/ditherer.h#L353
202 | //
203 | function tickPurple (channel) local (random)
204 | (
205 | random = tickWhite();
206 |
207 | noiseBuffer[channel] = random - noiseState[channel];
208 | noiseState [channel] = random;
209 | noiseBuffer[channel];
210 | );
211 |
212 | // DIGITAL NOISE
213 | //
214 | // My own creation, random picks of [-1, 0, +1]
215 | //
216 | function tickDigital () local (factor1, factor2)
217 | (
218 | factor1 = tickWhite() > 0;
219 | factor2 = tickWhite() < 0;
220 |
221 | factor1 + factor2 - 1;
222 | );
223 |
224 | // PINK NOISE
225 | //
226 | // "Warm" sounding, volume falls off at -3 dBfs per octave across the
227 | // spectrum. Often said to be similar to the optimal mix balance, and
228 | // to be generally pleasing to the human ear.
229 | //
230 | // Implemented after Larry Trammell's "newpink" method:
231 | // http://www.ridgerat-tech.us/tech/newpink.htm
232 | //
233 | function tickPink (channel) local (offset, break, sample)
234 | (
235 | offset = channel * 50;
236 | break = 0;
237 |
238 | sample = rand();
239 |
240 | break == 0 && sample <= 0.00198 ? (noiseState[offset+1] = tickWhite() * 3.8024; break = 1);
241 | break == 0 && sample <= 0.01478 ? (noiseState[offset+2] = tickWhite() * 2.9694; break = 1);
242 | break == 0 && sample <= 0.06378 ? (noiseState[offset+3] = tickWhite() * 2.5970; break = 1);
243 | break == 0 && sample <= 0.23378 ? (noiseState[offset+4] = tickWhite() * 3.0870; break = 1);
244 | break == 0 && sample <= 0.91578 ? (noiseState[offset+5] = tickWhite() * 3.4006; break = 1);
245 |
246 | noiseState[offset] = 0;
247 | noiseState[offset] += noiseState[offset+1];
248 | noiseState[offset] += noiseState[offset+2];
249 | noiseState[offset] += noiseState[offset+3];
250 | noiseState[offset] += noiseState[offset+4];
251 | noiseState[offset] += noiseState[offset+5];
252 |
253 | noiseState[offset] * dBToGain(-15);
254 | );
255 |
256 | @slider
257 | oscFreq = baseFrequency() * multiply / srate;
258 | viewFreq = baseFrequency() * multiply;
259 |
260 | outGain[0] = dBToGain(volume + volumeL);
261 | outGain[1] = dBToGain(volume + volumeR);
262 | oscType[0] != generatorL ? (oscType[0] = generatorL; resetPhases());
263 | oscType[1] != generatorR ? (oscType[1] = generatorR; resetPhases());
264 |
265 | @sample
266 |
267 | // I know it would be more economical to NOT
268 | // calculate two channels but then only send
269 | // one channel out, and instead just not let
270 | // the second channel be calculated. But the
271 | // CPU overhead is so low in this case, that
272 | // it won't result in a relevant difference.
273 | //
274 | // There are probably more efficient ways to
275 | // handle all those conditional cases below,
276 | // but it also doesn't generate any relevant
277 | // CPU overhead, so this will do just fine.
278 | //
279 | // Generator left
280 | generatorL == 1 ? (spl0 += tickSine(0));
281 | generatorL == 2 ? (spl0 += tickSaw(0,1));
282 | generatorL == 3 ? (spl0 += tickSaw(0,-1));
283 | generatorL == 4 ? (spl0 += tickTriangle(0));
284 | generatorL == 5 ? (spl0 += tickPulse(0,0.25));
285 | generatorL == 6 ? (spl0 += tickPulse(0,0.5));
286 | generatorL == 7 ? (spl0 += tickPulse(0,0.75));
287 | generatorL == 8 ? (spl0 += tickWhite(0));
288 | generatorL == 9 ? (spl0 += tickPink(0));
289 | generatorL == 10? (spl0 += tickPurple(0));
290 | generatorL == 11? (spl0 += tickDigital(0));
291 | generatorL == 12? (spl0 += 1);
292 | generatorL == 13? (spl0 -= 1);
293 | //
294 | // Generator right
295 | generatorR == 1 ? (spl1 += tickSine(1));
296 | generatorR == 2 ? (spl1 += tickSaw(1,1));
297 | generatorR == 3 ? (spl1 += tickSaw(1,-1));
298 | generatorR == 4 ? (spl1 += tickTriangle(1));
299 | generatorR == 5 ? (spl1 += tickPulse(1,0.25));
300 | generatorR == 6 ? (spl1 += tickPulse(1,0.5));
301 | generatorR == 7 ? (spl1 += tickPulse(1,0.75));
302 | generatorR == 8 ? (spl1 += tickWhite(1));
303 | generatorR == 9 ? (spl1 += tickPink(1));
304 | generatorR == 10? (spl1 += tickPurple(1));
305 | generatorR == 11? (spl1 += tickDigital(1));
306 | generatorR == 12? (spl1 += 1);
307 | generatorR == 13? (spl1 -= 1);
308 |
309 | // Polarity inversion
310 | invertL == 1 ? (spl0 *= -1);
311 | invertR == 1 ? (spl1 *= -1);
312 |
313 | // Output volume with channel sum compensation
314 | spl0 *= outGain[0] * (generatorR == 0 ? 2.0 : 1.0);
315 | spl1 *= outGain[1] * (generatorL == 0 ? 2.0 : 1.0);
316 |
317 | // Output channel routing
318 | outRouting == 1 ? (spl1 = spl0);
319 | outRouting == 2 ? (spl0 = spl1);
320 | outRouting == 3 ? (spl0 = spl1 = (spl0 + spl1) * 0.5);
321 |
322 | // DC Blocker
323 | blockDC == 1 ?
324 | (
325 | spl0 = dcBlocker(0);
326 | spl1 = dcBlocker(1);
327 | );
328 |
329 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019-2025 chokehold
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Metering/correlation_meter.jsfx:
--------------------------------------------------------------------------------
1 | desc: Correlation Meter
2 | version: 1.8.3
3 | author: chokehold
4 | tags: utility pan panorama stereo channel correlation phase scope meter
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/correlation_meter.png
7 | about:
8 | # Correlation Meter
9 |
10 | Stereo phase relation scope, Pearson product-moment correlation. Tells
11 | you how much of a difference there is between L/R channels of an audio
12 | signal.
13 |
14 | May not seem very important, but all over the world there are devices
15 | that will sum stereo mixes into mono signals, be it radio, club P.A. or
16 | mobile phone. If the L/R phase relation is too much out of balance, the
17 | best stereo mix will fall apart and sound like amateur's work once it's
18 | summed to mono. That's why it's good to keep an eye on phase correlation.
19 |
20 | Reading the meter is pretty easy. If no signal is present, there's no bar.
21 | As soon as a signal is present, the meter will display a green bar which
22 | informs you of the phase correlation.
23 |
24 | +1 is the optimal goal, this means both channels are in absolute harmony.
25 | 0 means everything is OK, but don't let the phases drift further apart.
26 | -1 is the worst case scenario, it means the channels are totally out of
27 | phase and would 100% cancel each other out when summed down to mono.
28 |
29 | I added colour coding to the moving indicator; red bad, green good.
30 |
31 | That's really all there's to it.
32 |
33 | // ----------------------------------------------------------------------------
34 | options: no_meter
35 |
36 | // Making this a hidden parameter because it add any worthwhile control or
37 | // functionality. It's still available as a parameter, so you could turn
38 | // it into a track control, if you must...
39 | slider1:1.0<0.5,2.0,0.01>-Speed
40 |
41 | in_pin:left input
42 | in_pin:right input
43 | out_pin:none
44 |
45 | @init
46 |
47 | // Level below which the indicator stops displaying
48 | // -96 dBfs = 10.0^(-96.0/20.0) = 0.00001584893192
49 | // -80 dBfs = 10.0^(-80.0/20.0) = 0.0001
50 | //
51 | signalThreshold = 0.0001;
52 |
53 | // Sample memory
54 | history = 2000;
55 |
56 | // Sample history statistics and properties
57 | historyLength = 45; // Default: ~1ms Window at 44.1 kHz
58 | historyDouble = 90; // 2x historyLength, used by methods
59 | historyScaler = 0.02222222222; // For mean calculation
60 |
61 | // Correlation result related values
62 | correlation = 0.0; // Raw Pearson result
63 | indicatorPosition = 0.0; // Smoothed position
64 |
65 | // Used for level detection
66 | absMeanL = 0.0;
67 | absMeanR = 0.0;
68 |
69 | // One-pole smooth movement filter coeffs
70 | slowDownA = 1.0;
71 | slowDownB = 0.0;
72 |
73 | // One-pole LP filter to, well, slow down the
74 | // indicator's movement.
75 | //
76 | function slowDown (value) local (state)
77 | (
78 | state = (value * slowDownA) + (state * slowDownB);
79 | state;
80 | );
81 |
82 | // The correlation detection window will be 1 ms
83 | // in any project with any samplerate.
84 | //
85 | function resizeHistory () local (samplesPerMS)
86 | (
87 | samplesPerMS = ceil(srate * 0.001); // round up +1 for safety
88 |
89 | (historyLength != samplesPerMS) ?
90 | (
91 | historyLength = samplesPerMS;
92 | historyDouble = historyLength * 2;
93 | historyScaler = 1.0/historyLength;
94 |
95 | memset(history, 0.0, historyDouble);
96 | );
97 | );
98 |
99 | // Move everything in the history back one sample,
100 | // then import the latest incoming samples.
101 | //
102 | function shuffle (newSampleL, newSampleR)
103 | (
104 | memcpy(history, history+1, historyDouble);
105 |
106 | history[historyLength-1] = newSampleL;
107 | history[historyDouble-1] = newSampleR;
108 | );
109 |
110 | // This will suck your CPU dry if it runs every
111 | // cycle, so that's why there's a countdown loop.
112 | // Recalculating everything once per Millisecond
113 | // is good enough for productive results, since
114 | // it's being low-pass-filtered later anyway.
115 | //
116 | function pearsonProduct () local (position, sumL, sumR, meanL, meanR, devL, devR, sqrMeanL, sqrMeanR, sumAbove, numBelow)
117 | (
118 | rounds += 1;
119 | rounds %= historyLength;
120 |
121 | // Every Millisecond
122 | (rounds == 0) ?
123 | (
124 | // Just in case the sample rate's changed
125 | resizeHistory();
126 |
127 | // SUM
128 | sumL = 0;
129 | sumR = 0;
130 |
131 | position = 0;
132 | while (position < historyLength)
133 | (
134 | sumL += history[position];
135 | sumR += history[position+historyLength];
136 |
137 | position += 1;
138 | );
139 |
140 | // MEAN
141 | meanL = (sumL * historyScaler);
142 | meanR = (sumR * historyScaler);
143 |
144 | absMeanL = abs(meanL);
145 | absMeanr = abs(meanR);
146 |
147 | // DEVIATION
148 | sqrMeanL = 0;
149 | sqrMeanR = 0;
150 |
151 | position = 0;
152 | while (position < historyLength)
153 | (
154 | sqrMeanL += sqr(history[position] - meanL);
155 | sqrMeanR += sqr(history[position+historyLength] - meanR);
156 |
157 | position += 1;
158 | );
159 |
160 | devL = sqrt(sqrMeanL * historyScaler);
161 | devR = sqrt(sqrMeanR * historyScaler);
162 |
163 | // SUM ABOVE
164 | sumAbove = 0.0;
165 |
166 | position = 0;
167 | while (position < historyLength)
168 | (
169 | sumAbove += (history[position] - meanL) * (history[position+historyLength] - meanR);
170 |
171 | position += 1;
172 | );
173 |
174 | // NUM BELOW
175 | numBelow = historyLength * devL * devR;
176 |
177 | // CORRELATION RESULT
178 | //
179 | // The value this entire processor is built around
180 | //
181 | correlation = sumAbove / numBelow;
182 | );
183 | );
184 |
185 | // Draws the user interface
186 | //
187 | function drawGfx () local (halfWidth, centerX, lineX, indicatorHistory0, indicatorHistory1, indicatorHistory2, dim)
188 | (
189 | dim = 0.175;
190 |
191 | gfx_mode = 1; // additive
192 | gfx_clear = 0; // black
193 |
194 | halfWidth = gfx_w/2;
195 |
196 | // Colour gray / see-through white
197 | gfx_set(1, 1, 1, 0.5);
198 |
199 | // Paint center 0 line
200 | centerX = halfWidth-1;
201 | gfx_x = centerX;
202 | gfx_y = 0;
203 | gfx_lineto(centerX, gfx_h);
204 |
205 | // Only draw indicator bar if signal present
206 | ((abs(spl0) > signalThreshold) || (abs(spl1) > signalThreshold)) ?
207 | (
208 | // Previous position -3
209 | (indicatorHistory2 != indicatorPosition) ?
210 | (
211 | indicatorHistory2 >= 0.0 ?
212 | (
213 | gfx_set(1-indicatorHistory2, 1, 0, 0.3);
214 | ):(
215 | gfx_set(1, sqr(1+indicatorHistory2), 0, 0.3);
216 | );
217 |
218 | lineX = centerX + (indicatorHistory2 * halfWidth) - (7 * indicatorHistory2);
219 | gfx_rect(lineX-8, 0, 16, gfx_h);
220 | gfx_x = lineX-10;
221 | gfx_y = 0;
222 | gfx_blurto(lineX+10 ,gfx_h);
223 | );
224 |
225 | // Previous position -2
226 | (indicatorHistory1 != indicatorPosition) ?
227 | (
228 | indicatorHistory1 >= 0.0 ?
229 | (
230 | gfx_set(1-indicatorHistory1, 1, 0, 0.6);
231 | ):(
232 | gfx_set(1, sqr(1+indicatorHistory1), 0, 0.6);
233 | );
234 |
235 | lineX = centerX + (indicatorHistory0 * halfWidth) - (5 * indicatorHistory0);
236 | gfx_rect(lineX-7, 0, 14, gfx_h);
237 | gfx_x = lineX-9;
238 | gfx_y = 0;
239 | gfx_blurto(lineX+9, gfx_h);
240 | );
241 |
242 | // Previous position -1
243 | (indicatorHistory0 != indicatorPosition) ?
244 | (
245 | indicatorHistory0 >= 0.0 ?
246 | (
247 | gfx_set(1-indicatorHistory0, 1, 0, 0.8);
248 | ):(
249 | gfx_set(1, sqr(1+indicatorHistory0), 0, 0.8 );
250 | );
251 |
252 | lineX = centerX + (indicatorHistory0 * halfWidth) - (4 * indicatorHistory0);
253 | gfx_rect(lineX-6, 0, 12, gfx_h);
254 | gfx_x = lineX-8;
255 | gfx_y = 0;
256 | gfx_blurto(lineX+8, gfx_h);
257 | );
258 |
259 | // Dim background
260 | gfx_muladdrect(0,0,gfx_w,gfx_h, dim, dim, dim);
261 |
262 | // Current position
263 | indicatorPosition >= 0.0 ?
264 | ( // Fade between green and yellow
265 | gfx_set(1-indicatorPosition, 1, 0, 1.0);
266 | ):(
267 | // Fade between yellow and red
268 | gfx_set(1, sqr(1+indicatorPosition), 0, 1.0);
269 | );
270 |
271 | lineX = centerX + (indicatorPosition * halfWidth) - (4 * indicatorPosition) + 1;
272 | gfx_rect(lineX-5, 0, 10, gfx_h);
273 | );
274 |
275 | // Paint white -1/+1 correlation numbers at top
276 | gfx_set(1, 1, 1, 0.9);
277 | gfx_y = 5;
278 | gfx_x = 10;
279 | gfx_drawnumber(-1,0);
280 | gfx_x = halfWidth-4;
281 | gfx_drawnumber(0,0);
282 | gfx_x = gfx_w-24;
283 | gfx_drawstr("+1");
284 |
285 | // Paint gray 0°-180° phase scale numbers at bottom.
286 | // As they're flipped against usual phase scopes and
287 | // not the prime intention of this plugin, they will
288 | // be rendered slightly darker/more transparent.
289 | gfx_set(1, 1, 1, 0.25);
290 | gfx_y = gfx_h-12;
291 | gfx_x = 10;
292 | gfx_drawstr("180 deg");
293 | gfx_x = halfWidth-22;
294 | gfx_drawstr("90 deg");
295 | gfx_x = gfx_w-48;
296 | gfx_drawstr("0 deg");
297 |
298 | // Shuffle indicator position history -- don't scale these!
299 | indicatorHistory2 = indicatorHistory1;
300 | indicatorHistory1 = indicatorHistory0;
301 | indicatorHistory0 = indicatorPosition;
302 | );
303 |
304 | // Still part of the @init section
305 | resizeHistory();
306 |
307 | @slider
308 | // Softens the curve of this parameter a bit
309 | speed = sqr(sqr(0.08 * slider1)); // 0.08 = indicatorSpeed
310 | slowDownA = speed;
311 | slowDownB = 1.0 - speed;
312 |
313 | @sample
314 | // History samples needs to be shuffled all the time,
315 | // even if the Pearson product isn't calculated.
316 | shuffle(spl0, spl1);
317 |
318 | // Calculates the L/R correlation value
319 | pearsonProduct();
320 |
321 | // Raw correlation result slowed down for smooth display
322 | indicatorPosition = slowDown(correlation);
323 |
324 | @gfx 600 20
325 |
326 | drawGfx();
327 |
--------------------------------------------------------------------------------
/Metering/phase_scope.jsfx:
--------------------------------------------------------------------------------
1 | desc: Phase Scope
2 | version: 1.8.3
3 | author: chokehold
4 | tags: utility metering visualization panorama stereo phase vector scope goniometer lissajous
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/phase_scope.png
7 | about:
8 | # Phase Scope
9 |
10 | This is also commonly known as Goniometer, Vector Scope or
11 | Lissajous display. It visualises the relation of amplitude
12 | and phase in a signal, i.e. "the stereo field".
13 |
14 | // ----------------------------------------------------------------------------
15 | in_pin:left input
16 | in_pin:right input
17 | out_pin:none
18 | options:no_meter
19 |
20 | slider1:view=0<0,2,1{Dots,Lines,Rays}>Visualisation
21 | slider2:tint=85<0,255,0.01>Colour
22 | slider3:glow=1<0,1,1{Off,On}>Glow
23 | slider4:axes=1<0,3,1{None,Left/Right,Left/Right + Decibels,Mid/Side,Mid/Side + Decibels,Left/Right + Mid/Side + Decibels}>Indicators
24 | slider5:labels=1<0,1,1{Off,On}>Labels
25 | slider6:freeze=2<0,2,1{Off,On,Stop/Pause}>Freeze
26 |
27 | @init
28 | // Simple hard clipping
29 | //
30 | function clamp (signal) (max(-1, min(1, signal)));
31 |
32 | // Some radian constants to minimise per-sample calculations
33 | quarterPi = $PI * 0.25; // 45°
34 | pi = $PI; // 180°
35 | oneDot5Pi = $PI * 1.5; // 270°
36 | twoPi = $PI * 2.0; // 360°
37 |
38 | // At 786 kHz sample rate, 20 ms = 15270 samples,
39 | // so 20000 samples buffer should be sufficient.
40 | //
41 | historyX = 10000; // Stores X coordinates
42 | historyY = 30000; // Stores Y coordinates
43 | numCoords = ceil(srate * 0.05); // # of samples ~20 ms window
44 | lastCoord = numCoords - 1; // To save calculations later
45 |
46 | // Things related to the display drawing colours
47 | //
48 | colourRange = 85; // Fader 0-255 --> 255/3 = 85
49 | colourR = 1; // Red colour default amount [0,1]
50 | colourG = 0; // Green colour default amount [0,1]
51 | colourB = 0; // Blue colour default amount [0,1]
52 | colourA = 0.75; // Alpha/transparency amount fixed
53 | //
54 | function setAxisHardColour () (gfx_set(1,1,1,0.4));
55 | function setAxisSoftColour () (gfx_set(1,1,1,0.2));
56 | function setDecibelColour () (gfx_set(1,1,1,0.1));
57 | function setLabelColour () (gfx_set(1,1,1,0.25));
58 | //
59 | // This sets the drawing colour of the display
60 | //
61 | // The UI fader ranges between [0,255] and its range is split into
62 | // three sections of an equal third (=85) each. In every section,
63 | // one colour is totally out and the two remaining colours fade in
64 | // opposite directions, i.e. one gets stronger and one drops off.
65 | //
66 | // Colour amount calculations implemented after FastLED "Spectrum"
67 | // hue chart rather than "Rainbow", merely for its simplicity.
68 | // https://github.com/FastLED/FastLED/wiki/FastLED-HSV-Colors
69 | // https://raw.githubusercontent.com/FastLED/FastLED/gh-pages/images/HSV-spectrum-with-desc.jpg
70 | //
71 | function tintColour () local (fraction)
72 | (
73 | // Positions 0 and 255 are always Red,
74 | // so no calculations necessary here.
75 | tint % 255 == 0 ?
76 | (
77 | colourR = 1; // full
78 | colourG = 0; // out
79 | colourB = 0; // out
80 | );
81 |
82 | // First third of the colour range:
83 | // Red falls, Green rises, no blue
84 | (tint > 0) && (tint < 85) ?
85 | (
86 | // How far into this third of 85 is
87 | // the fader, result in range [0,1]
88 | fraction = tint / 85;
89 |
90 | colourR = 1 - fraction; // fall
91 | colourG = fraction; // rise
92 | colourB = 0; // out
93 | );
94 |
95 | // Second third of the colour range:
96 | // No red, Green falls, Blue rises
97 | (tint >= 85) && (tint < 171) ?
98 | (
99 | // How far into this third of 85 is
100 | // the fader, result in range [0,1]
101 | fraction = (tint - 84) / 85;
102 |
103 | colourR = 0; // out
104 | colourG = 1 - fraction; // fall
105 | colourB = fraction; // rise
106 | );
107 |
108 | // Last third of the colour range:
109 | // Red rises, no Green, Blue falls
110 | (tint >= 171) && (tint < 255) ?
111 | (
112 | // How far into this third of 85 is
113 | // the fader, result in range [0,1]
114 | fraction = (tint - 170) / 85;
115 |
116 | colourR = fraction; // rise
117 | colourG = 0; // out
118 | colourB = 1 - fraction; // fall
119 | );
120 | );
121 |
122 | // This will take a regular set of L/R samples and turn them
123 | // into a set of X/Y coordinates for placement on the scope.
124 | //
125 | // The samples are turned into polar coordinates, this gives
126 | // a rotational angle and the distance from the center, from
127 | // which the carthesian X/Y coordinates are then calculated.
128 | //
129 | // Since this effect has no output pins, it's not necessary
130 | // to copy the input samples to variable containers, instead
131 | // the processing will be performed directly on the samples.
132 | //
133 | function calculateCoordinates () local (radius, angle)
134 | (
135 | // Calculate polar coordinate from input samples
136 | radius = sqrt(sqr(spl0) + sqr(spl1));
137 | angle = atan(spl1 / spl0);
138 |
139 | // Arctan doesn't like it if the samples are zero or lower,
140 | // so compensate the angle if that happens to be the case.
141 | angle += ((spl0 < 0 && spl1 != 0) * pi) + ((spl0 > 0 && spl1 < 0) * twoPi);
142 | spl0 == 0 ? angle = oneDot5Pi - (spl1 > 0) * pi;
143 | spl1 == 0 ? angle = (spl0 <= 0) * pi;
144 |
145 | // To make the scope display upright, add 45° to the angle
146 | angle += quarterPi;
147 |
148 | // Convert polar coordinate to cartesian X/Y coordinate
149 | spl0 = (radius * cos(angle)); // X value
150 | spl1 = (radius * sin(angle)); // Y value
151 | );
152 |
153 | // Move everything in the history back one step and add the
154 | // latest samples at the end. At this point, the values are
155 | // not the input samples anymore, but their X/Y coordinates.
156 | //
157 | function addToHistory ()
158 | (
159 | // Shift buffers -1 i.e. to the left, loses the last value
160 | memcpy(historyX, historyX+1, numCoords);
161 | memcpy(historyY, historyY+1, numCoords);
162 |
163 | // Insert the latest values at the back of the buffers
164 | historyX[lastCoord] = spl0;
165 | historyY[lastCoord] = spl1;
166 | );
167 |
168 | // Paints the L/R, M/S, dB guides and labels onto the canvas
169 | //
170 | function drawIndicators () local (top, left, right, bottom, middleH, middleV)
171 | (
172 | // Switch to the main screen buffer
173 | gfx_dest = -1;
174 |
175 | // Draw L/R indicator axes
176 | //
177 | (axes == 1 || axes == 2 || axes == 5) ?
178 | (
179 | // Convenience variables for code readability
180 | top = centerY - viewScale + 1;
181 | left = centerX - viewScale + 1;
182 | bottom = centerY + viewScale;
183 | right = centerX + viewScale;
184 |
185 | // Draw L/R axis guides
186 | setAxisHardColour();
187 | gfx_line(left, top, right, bottom, 1);
188 | gfx_line(left, bottom, right, top, 1);
189 |
190 | // Draw L/R labels
191 | labels == 1 ?
192 | (
193 | setLabelColour();
194 | gfx_y = top - 11;
195 | gfx_x = left - 12;
196 | gfx_drawstr("L");
197 | gfx_x = right + 6;
198 | gfx_drawstr("R");
199 | );
200 | );
201 |
202 | // Draw Mid/Side indicator axes
203 | //
204 | (axes == 3 || axes == 4 || axes == 5) ?
205 | (
206 | // Convenience variables for code readability
207 | top = centerY - viewSize;
208 | left = centerX - viewSize;
209 | bottom = centerY + viewSize;
210 | right = centerX + viewSize;
211 | middleH = centerX; // Horizontal = left/right
212 | middleV = centerY; // Vertical = up/down
213 |
214 | // Draw M/S axis guides (softer if mixed with L/R axes)
215 | axes == 5 ? setAxisSoftColour() : setAxisHardColour();
216 | gfx_line(left, middleV, right, middleV, 1);
217 | gfx_line(middleH, top, middleH, bottom, 1);
218 |
219 | // Draw M/+S/S- labels
220 | labels == 1 ?
221 | (
222 | setLabelColour();
223 | gfx_x = centerX + 8;
224 | gfx_y = centerY + viewSize - 15;
225 | gfx_drawstr("M");
226 | gfx_y = centerY + 7;
227 | gfx_x = centerX - viewSize + 6;
228 | gfx_drawstr("+S");
229 | gfx_x = centerX + viewSize - 22;
230 | gfx_drawstr("S-");
231 | );
232 | );
233 |
234 | // Draw dB indicator rings and labels
235 | //
236 | (axes == 2 || axes == 4 || axes == 5) ?
237 | (
238 | // 0 dBfs = 1.0 * viewSize = as far out as it will go
239 | //
240 | gfx_set(1, 1, 1, 0.4);
241 | gfx_circle(centerX, centerY, viewSize, 0, 1);
242 | labels == 1 ?
243 | (
244 | setLabelColour();
245 | gfx_y = centerY + viewScale - 4;
246 | gfx_x = max(centerX - viewScale - 19, 6);
247 | gfx_drawstr("0");
248 | );
249 |
250 | //
251 | // -3 dBfs = 0.7079457844
252 | //
253 | gfx_set(1,1,1,0.175);
254 | gfx_circle(centerX, centerY, viewSize * 0.7079457844, 0, 1);
255 | labels == 1 ?
256 | (
257 | //setLabelColour();
258 | gfx_y = centerY + viewScale * 0.7079457844 - 6;
259 | gfx_x = max(centerX - viewScale * 0.7079457844 - 28, 6);
260 | gfx_drawstr("-3");
261 | );
262 |
263 | //
264 | // -6 dBfs = 0.5011872336
265 | //
266 | gfx_set(1,1,1,0.15);
267 | gfx_circle(centerX, centerY, viewSize * 0.5011872336, 0, 1);
268 | labels == 1 ?
269 | (
270 | //setLabelColour();
271 | gfx_y = centerY + viewScale * 0.5011872336 - 6;
272 | gfx_x = max(centerX - viewScale * 0.5011872336 - 28, 6);
273 | gfx_drawstr("-6");
274 | );
275 |
276 | //
277 | // -12 dBfs = 0.2511886432
278 | //
279 | gfx_set(1,1,1,0.1);
280 | gfx_circle(centerX, centerY, viewSize * 0.2511886432, 0, 1);
281 | labels == 1 ?
282 | (
283 | //setLabelColour();
284 | gfx_y = centerY + viewScale * 0.2511886432 - 5;
285 | gfx_x = max(centerX - viewScale * 0.2511886432 - 35, 6);
286 | gfx_drawstr("-12");
287 | );
288 |
289 | //
290 | // -24 dBfs = 0.06309573445
291 | //
292 | gfx_set(1,1,1,0.075);
293 | gfx_circle(centerX, centerY, viewSize * 0.06309573445, 0, 1);
294 | //
295 | // Leaving out -24 dBfs label because too compact
296 | );
297 | );
298 |
299 | // Draws the sample history visualisations
300 | //
301 | // Blurring the main screen buffer would not just blur the history
302 | // display, but also the indicators and labels. To avoid this, the
303 | // history display will be drawn onto a separate off-screen buffer
304 | // first, so it doesn't mix with the axes and labels. If no "glow"
305 | // is required, the off-screen buffer can be blitted directly onto
306 | // the main screen buffer. If "glow" activated, blit the unblurred
307 | // history display to yet another off-screen buffer first, blur it
308 | // there, and finally blit it back to the unblurred buffer and mix
309 | // them in alpha, i.e. make both partially transparent so they add
310 | // up to an evenly lit image.
311 | //
312 | function drawHistory () local (dim, coord, offsetX, offsetY, posX, posY, lastX, lastY, thisR, thisG)
313 | (
314 | // Switch to off-screen frame buffer. Don't clear it, so
315 | // the last calculated history is still contained there.
316 | gfx_dest = 127;
317 | gfx_setimgdim(127, gfx_w, gfx_h);
318 |
319 | // Dim the existing history that's still in the buffer.
320 | // If "glow" activated, dimming can/should be faster so
321 | // the mix of the two doesn't accumulate too much after
322 | // a few seconds. The image should not dim/decay while
323 | // the display is "frozen".
324 | (freeze != 1 && stopped != 1) ?
325 | (
326 | dim = (glow == 1 ? 0.925 : 0.98);
327 | gfx_muladdrect(0,0,gfx_w,gfx_h, dim, dim, dim);
328 | );
329 |
330 | // Iterate through all buffered coordinates in the history
331 | coord = lastCoord;
332 | while
333 | (
334 | // Calculate this point's position in relation to X/Y center
335 | offsetX = (historyX[coord] * viewScale);
336 | offsetY = (historyY[coord] * viewScale);
337 |
338 | // Set the drawing position
339 | gfx_x = centerX - offsetX;
340 | gfx_y = centerY - offsetY;
341 |
342 | // History colour
343 | gfx_set(colourR, colourG, colourB, colourA, 127);
344 |
345 | // Draw point onto graph
346 | view == 0 ? (gfx_setpixel(colourR, colourG, colourB));
347 | view == 1 ? (gfx_line(lastX, lastY, gfx_x, gfx_y, 1));
348 | view == 2 ? (gfx_lineto(centerX, centerY, 1));
349 |
350 | // Save current coordinates so can be referred
351 | // to in next iterations when drawing lines.
352 | lastX = gfx_x;
353 | lastY = gfx_y;
354 |
355 | // Decrement iterator
356 | (coord -= 1) > -1;
357 | );
358 |
359 | // Blit the image from the off-screen buffer back to the
360 | // main buffer, i.e. plugin GUI. If "glow" active, lower
361 | // the blitting opacity as the intensity will accumulate
362 | // when mixed with the later blitted "glow" image buffer.
363 | gfx_dest = -1;
364 | gfx_x = gfx_y = 0;
365 | gfx_a = (glow == 1 ? 0.7 : 1.0);
366 | gfx_blit(127,1,0);
367 |
368 | // If the history display should "glow"
369 | glow == 1 ?
370 | (
371 | // Blit currently unblurred image to off-screen buffer
372 | gfx_dest = 100;
373 | gfx_setimgdim(100, gfx_w, gfx_h);
374 | gfx_a = 1.0; // full opacity before blurring
375 | gfx_x = gfx_y = 0;
376 | gfx_blit(127,1,0);
377 |
378 | // Blur it a few times, more blur runs = faster decay.
379 | gfx_x = gfx_y = 0;
380 | gfx_blurto(gfx_w, gfx_h);
381 | gfx_x = gfx_y = 0;
382 | gfx_blurto(gfx_w, gfx_h);
383 | gfx_x = gfx_y = 0;
384 | gfx_blurto(gfx_w, gfx_h);
385 |
386 | // Switch to the main buffer and blit the now blurred
387 | // visualisation on top of what's already there, i.e.
388 | // indicators and un-blurred visualisation.
389 | gfx_dest = -1;
390 | gfx_a = 0.3;
391 | gfx_x = gfx_y = 0;
392 | gfx_blit(100,1,0);
393 | );
394 | );
395 |
396 | @slider
397 |
398 | // Recalculate the RGB colours to tint the display
399 | tintColour();
400 |
401 | @block
402 |
403 | // Triggers a display freeze when project is stopped
404 | // or paused in "Play/Pause" freeze mode. Responsive
405 | // enough if this is switched in the @block section.
406 | stopped = (freeze == 2 && play_state < 1) ? 1 : 0;
407 |
408 | @sample
409 |
410 | // If the display is frozen, don't calculate any new
411 | // samples and don't mess with the history buffers.
412 | (freeze != 1 && stopped != 1) ?
413 | (
414 | calculateCoordinates();
415 | addToHistory();
416 | );
417 |
418 | @gfx 480 480
419 |
420 | // Switch to main drawing context
421 | gfx_dest = -1;
422 | gfx_mode = 1; // additive
423 | gfx_clear = 0; // black
424 |
425 | // Calculate the side length of a square centred in the window
426 | viewSize = min(gfx_w, gfx_h) / 2;
427 |
428 | // The distance a dot may be from center in any
429 | // direction to still appear inside a circle.
430 | viewScale = (viewSize * 0.70710681);
431 |
432 | // The absolute center point in the current window
433 | centerX = gfx_w * 0.5;
434 | centerY = gfx_h * 0.5;
435 |
436 | drawIndicators();
437 | drawHistory();
438 |
--------------------------------------------------------------------------------
/Metering/stereo_checker.jsfx:
--------------------------------------------------------------------------------
1 | desc: Stereo Checker
2 | version: 1.8.3
3 | author: chokehold
4 | tags: utility analysis meter mono stereo
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/stereo_checker.png
7 | about:
8 | # Stereo Checker
9 |
10 | Displays the inter-channel relation of a two-channel input signal.
11 |
12 | Super simple monitoring tool that analyses the audio going into it and tells
13 | you the relation of the two input channels.
14 |
15 | So... why would you want this?
16 |
17 | Especially when collaborating with others, it's common to send project files
18 | back and forth. When you receive e.g. a WAV file that is displayed in Reaper
19 | with two channel lanes, it's quite possible that the audio on those channels
20 | isn't actually a real stereo signal (with different content on both channels)
21 | but just a mono signal (with identical audio on both channels) that has been
22 | incorrectly exported or encoded at some point.
23 |
24 | If you have such a file, that appears to be stereo but in truth is only mono,
25 | then you can save a bit of project size and processing power by removing the
26 | duplicate (and thereby completely unnecessary) audio channel from it.
27 |
28 | To evaluate if your audio clip is really stereo, or only mono after all, you
29 | would usually have to mess around with routing tricks or the phase inversion
30 | test, and that's a bit much effort. Instead, just run your clip through this
31 | plugin and it will tell you how the audio channels are related.
32 |
33 | If playback is paused and/or no signal is received, the display says Silence.
34 | If signal is being received and the audio on both channels is identical, the
35 | display will say Mono. And finally, a difference between the two channels of
36 | incoming audio will make the display say Stereo.
37 |
38 | That's all there's to it. Simple to use, quick results. :)
39 |
40 | NOTE: depending on their content and processing, some audio signals can jump
41 | between Mono and Stereo. So it can be beneficial to let this plugin run over
42 | longer portions of audio, to see if what looks like a normal mono signal for
43 | the most part really doesn't have some stereo sound effect or section burned
44 | into it somewhere. Or just keep it on the master all the time by default, it
45 | doesn't use any worthwhile CPU resources anyway. :)
46 |
47 | // ----------------------------------------------------------------------------
48 | // This plugin only needs input pins, it writes no output
49 | in_pin:Input L
50 | in_pin:Input R
51 |
52 | // Hide input/output meters
53 | options:no_meter
54 |
55 | @init
56 |
57 | // Sets the foreground colour in RGB 0-255 values
58 | function gfxColour (Red, Green, Blue)
59 | (
60 | // 1/255 = 0.003921568627
61 | gfx_set(Red * 0.003921568627, Green * 0.003921568627, Blue * 0.003921568627);
62 | );
63 |
64 | @gfx 0 120
65 |
66 | // Make the background black
67 | gfx_clear = 0;
68 |
69 | // Set GFX position to upper left corner
70 | gfx_x = 0;
71 | gfx_y = 0;
72 |
73 | // Set foreground colour to light grey
74 | gfxColour(150,150,150);
75 |
76 | // Set the font to use, make its size relative to the window height
77 | gfx_setfont(1, "Arial", gfx_h / 3);
78 |
79 | // Draw text over the full window, centred horizontally and vertically
80 | gfx_drawstr(channelText, 1|4, gfx_w, gfx_h);
81 |
82 | @sample
83 |
84 | // Analyse the relation of the current two input samples
85 | silenceCh1 = (spl0 == 0);
86 | silenceCh2 = (spl1 == 0);
87 | equal = (spl0 == spl1);
88 |
89 | // Decide which text to draw, depending on the relation between the input samples
90 | channelText = (silenceCh1 && silenceCh2) ? "Silence" : (equal ? "Mono" : "Stereo");
91 |
92 |
--------------------------------------------------------------------------------
/Noise/interpolated_noise.jsfx:
--------------------------------------------------------------------------------
1 | desc: Interpolated Noise
2 | version: 1.8.3
3 | author: chokehold
4 | tags: generator synthesis noise
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/interpolated_noise.png
7 | about:
8 | # Interpolated Noise
9 |
10 | Creates various characters of filtered stereo noise.
11 |
12 | New samples are not just randomly generated and then
13 | shot through a set of "voicing filters", but they're
14 | calculated by interpolation between multiple old and
15 | new random values, as well as what I would call slew
16 | limiting. Restricting the ramping speed for changes
17 | will inherently filter out higher frequencies.
18 |
19 | These noises are layered in randomness, meaning they
20 | will generate fast "clouds of randomness" which then
21 | move along slower "trails of randomness". Send them
22 | through a goniometer/Lissajous scope and you'll see.
23 |
24 | I find these noise characters very calm and pleasing,
25 | and overall a lot more natural and organic than dumb
26 | filtered white noise. However, they are unstable in
27 | volume and stereo field, which adds to the naturally
28 | chaotic sound but might not always be desirable.
29 |
30 | I tried to level them all around 0 dBfs RMS.
31 |
32 | // ----------------------------------------------------------------------------
33 | slider1:0<0,2,1{Linear,Cosine,Cubic}> Type
34 | slider2:0<-144, 0,0.01> Level dBfs RMS
35 |
36 | in_pin:left input
37 | in_pin:right input
38 | out_pin:left output
39 | out_pin:right output
40 |
41 | @init
42 | m_pi = 3.14159265358;
43 |
44 | stateL = 0.0;
45 | stateR = 0.0;
46 |
47 | historyL0 = 0.0;
48 | historyL1 = 0.0;
49 | historyL2 = 0.0;
50 | historyL3 = 0.0;
51 |
52 | historyR0 = 0.0;
53 | historyR1 = 0.0;
54 | historyR2 = 0.0;
55 | historyR3 = 0.0;
56 |
57 | inertiaLin = 0.000000025;
58 | makeupLin = 3.5481339;
59 | slopeLinL = 0.015848931;
60 | slopeLinR = 0.015848931;
61 |
62 | inertiaCos = 0.00000001;
63 | makeupCos = 35.481339;
64 | slopeCosL = 0.01;
65 | slopeCosR = 0.01;
66 | ceilingCos = 0.01;
67 |
68 | inertiaCub = 0.00000001;
69 | makeupCub = 3.981071705535;
70 | slopeCubL = 0.00316227766;
71 | slopeCubR = 0.00316227766;
72 | ceilingCub = 0.0794328235;
73 |
74 | dcScale = 0.9998848736286163330078125;
75 | dcLastInL = 0.0;
76 | dcLastInR = 0.0;
77 | dcLastOutL = 0.0;
78 | dcLastOutR = 0.0;
79 |
80 | function dBToGain (decibels) (pow(10, decibels * 0.05));
81 | function clamp (number, low, high) (max(low, min(high, number)));
82 | function random (limit) (rand(2.0 * limit) - limit);
83 |
84 | function linear ()
85 | (
86 | slopeLinL += clamp(random(1.0), -inertiaLin, inertiaLin);
87 | slopeLinL = clamp(slopeLinL, inertiaLin, 1.0);
88 |
89 | slopeLinR += clamp(random(1.0), -inertiaLin, inertiaLin);
90 | slopeLinR = clamp(slopeLinR, inertiaLin, 1.0);
91 |
92 | sampleL = random(1.0);
93 | sampleR = random(1.0);
94 |
95 | stateL = stateL * (1.0 - slopeLinL) + sampleL * slopeLinL;
96 | stateR = stateR * (1.0 - slopeLinR) + sampleR * slopeLinR;
97 |
98 | spl0 += stateL * makeupLin * level;
99 | spl1 += stateR * makeupLin * level;
100 | );
101 |
102 | function cosine ()
103 | (
104 | slopeCosL += clamp(random(1.0), -inertiaCos, inertiaCos);
105 | slopeCosL = clamp(slopeCosL, inertiaCos, 1.0);
106 |
107 | slopeCosR += clamp(random(1.0), -inertiaCos, inertiaCos);
108 | slopeCosR = clamp(slopeCosL, inertiaCos, 1.0);
109 |
110 | sampleL = random(1.0);
111 | sampleR = random(1.0);
112 |
113 | adjustL = (1.0 - cos(slopeCosL * m_pi)) * 0.5;
114 | adjustR = (1.0 - cos(slopeCosR * m_pi)) * 0.5;
115 |
116 | stateL = stateL * (1.0 - adjustL) + sampleL * adjustL;
117 | stateR = stateR * (1.0 - adjustR) + sampleR * adjustR;
118 |
119 | stateL = clamp(stateL, -ceilingCos, ceilingCos);
120 | stateR = clamp(stateR, -ceilingCos, ceilingCos);
121 |
122 | spl0 += stateL * makeupCos * level;
123 | spl1 += stateR * makeupCos * level;
124 | );
125 |
126 | function cubic ()
127 | (
128 | historyL0 = historyL2;
129 | historyL1 = historyL3;
130 | historyL2 = random(1.0);
131 | historyL3 = random(1.0);
132 |
133 | historyR0 = historyR2;
134 | historyR1 = historyR3;
135 | historyR2 = random(1.0);
136 | historyR3 = random(1.0);
137 |
138 | slopeCubL += clamp(random(1.0), -inertiaCub, inertiaCub);
139 | slopeCubL = clamp(slopeCubL, inertiaCub, 1.0);
140 |
141 | slopeCubR += clamp(random(1.0), -inertiaCub, inertiaCub);
142 | slopeCubR = clamp(slopeCubR, inertiaCub, 1.0);
143 |
144 | historyL0 *= 0.4;
145 | historyL3 *= 0.6;
146 |
147 | historyR0 *= 0.4;
148 | historyR3 *= 0.6;
149 |
150 | a2L = historyL2 - historyL0;
151 | a0L = historyL3 - a2L + historyL1;
152 | a1L = historyL0 - historyL1 - a0L;
153 | a3L = historyL1;
154 |
155 | a2R = historyR2 - historyR0;
156 | a0R = historyR3 - a2R + historyR1;
157 | a1R = historyR0 - historyR1 - a0R;
158 | a3R = historyR1;
159 |
160 | slope2L = slopeCubL * slopeCubL;
161 | slope2R = slopeCubR * slopeCubR;
162 |
163 | stateL = a0L * slope2L * slopeCubL + a1L * slope2L + a2L * slopeCubL + a3L;
164 | stateR = a0R * slope2R * slopeCubR + a1R * slope2R + a2R * slopeCubR + a3R;
165 |
166 | stateL = clamp(stateL, -ceilingCub, ceilingCub);
167 | stateR = clamp(stateR, -ceilingCub, ceilingCub);
168 |
169 | historyL3 = stateL;
170 | historyR3 = stateR;
171 |
172 | spl0 += stateL * makeupCub * level;
173 | spl1 += stateR * makeupCub * level;
174 | );
175 |
176 | function dcBlocker ()
177 | (
178 | dcLastOutL = (dcLastOutL * dcScale) + spl0 - dcLastInL;
179 | dcLastOutR = (dcLastOutR * dcScale) + spl1 - dcLastInR;
180 |
181 | dcLastInL = spl0;
182 | dcLastInR = spl1;
183 |
184 | spl0 = dcLastOutL;
185 | spl1 = dcLastOutR;
186 | );
187 |
188 | @slider
189 | level = dBToGain(slider2);
190 |
191 | @sample
192 | (slider1 == 0) ? linear() : ((slider1 == 1) ? cosine() : cubic());
193 | (slider1 > 0) ? dcBlocker();
194 |
--------------------------------------------------------------------------------
/Noise/reference_noise.jsfx:
--------------------------------------------------------------------------------
1 | desc: Reference Noise
2 | version: 1.8.3
3 | author: chokehold
4 | tags: utility noise mastering analysis reference spectrum
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/reference_noise.png
7 | about:
8 | # Reference Noise
9 |
10 | Generates noise of different kinds to use as mixing reference.
11 |
12 | It's an age-old audio engineer's trick to use pink noise as a
13 | reference signal for mixing. The spectrum of pink noise isn't
14 | an accurate reference to human hearing though, much rather an
15 | approximation and a workaround.
16 |
17 | By examining the spectral images of existing songs and mixes,
18 | and applying a few filtering stages to a signal of basic pink
19 | noise, the approximated workaround curve of pink noise can be
20 | "bent" into much more useful reference signals.
21 |
22 | That's what this thing is. I analysed a wide variety of songs
23 | from various styles and eras, and created a list of filtering
24 | profiles that turn primitive pink noise into useful reference
25 | signals.
26 |
27 | Run the output of this plugin into the sidechain/key input of
28 | your favourite spectrum analyser, if it has a sidechain input,
29 | and try to mix your tracks to approximate its spectral curve.
30 |
31 | // ----------------------------------------------------------------------------
32 | slider1:preset=2<0,12,1{White Noise,Pink Noise,Generic Modern,Generic Low-End,Generic Classical,Folk/Acoustic,Hip Hop Classic,Hip Hop Modern,Rock 60s/70s,Rock 80s/90s,Metal 80s,Metal 90s,Metal Modern}>Noise Profile
33 | slider2:dBGain=0<-48,48,0.0001>Trim [dB]
34 |
35 | out_pin:Left
36 | out_pin:Right
37 |
38 | @init
39 |
40 | // State memory for noise generation
41 | noiseState = 0;
42 |
43 | // Preset switching state memory
44 | lastPreset = -1;
45 |
46 | // Variable noise gain adjustment factor
47 | gainAdjust = 1.0;
48 |
49 | // Converts dB values to float gain factors
50 | function dBToGain (decibels) (pow(10, decibels * 0.05));
51 |
52 | // EQ CLASSES
53 | //
54 | // Implemented after Andrew Simper's State Variable Filter paper.
55 | // https://cytomic.com/files/dsp/SvfLinearTrapOptimised2.pdf
56 | //
57 | function eqTick (sample) instance (v1, v2, v3, ic1eq, ic2eq)
58 | (
59 | v3 = sample - ic2eq;
60 | v1 = this.a1 * ic1eq + this.a2 * v3;
61 | v2 = ic2eq + this.a2 * ic1eq + this.a3 * v3;
62 | ic1eq = 2.0 * v1 - ic1eq; ic2eq = 2.0 * v2 - ic2eq;
63 | (this.m0 * sample + this.m1 * v1 + this.m2 * v2);
64 | );
65 | //
66 | // Peak EQ band
67 | //
68 | function eqPK (SR, Hz, Q, dB) instance (a1, a2, a3, m0, m1, m2) local (A, g, k)
69 | (
70 | A = pow(10.0, dB * 0.025); g = tan($PI * (Hz / SR)); k = 1.0 / (Q * A);
71 | a1 = 1.0 / (1.0 + g * (g + k)); a2 = a1 * g; a3 = a2 * g;
72 | m0 = 1.0; m1 = k * ((A * A) - 1.0); m2 = 0.0;
73 | );
74 | //
75 | // High pass filter
76 | //
77 | function eqHP (SR, Hz, Q) instance (a1, a2, a3, m0, m1, m2) local (g, k)
78 | (
79 | g = tan($PI * (Hz / SR)); k = 1.0 / Q;
80 | a1 = 1.0 / (1.0 + g * (g + k)); a2 = a1 * g; a3 = a2 * g;
81 | m0 = 1.0; m1 = -k; m2 = -1.0;
82 | );
83 | //
84 | // Low pass filter
85 | //
86 | function eqLP (SR, Hz, Q) instance (a1, a2, a3, m0, m1, m2) local (g, k)
87 | (
88 | g = tan($PI * (Hz / SR)); k = 1.0 / Q;
89 | a1 = 1.0 / (1.0 + g * (g + k)); a2 = a1 * g; a3 = a2 * g;
90 | m0 = 0.0; m1 = 0.0; m2 = 1.0;
91 | );
92 | //
93 | // Low shelf filter
94 | //
95 | function eqLS (SR, Hz, Q, dB) instance (a1, a2, a3, m0, m1, m2) local (A, g, k)
96 | (
97 | A = pow(10.0, dB * 0.025); g = tan($PI * (Hz / SR)); k = 1.0 / Q;
98 | a1 = 1.0 / (1.0 + g * (g + k)); a2 = a1 * g; a3 = a2 * g;
99 | m0 = 1.0; m1 = k * (A - 1.0); m2 = sqr(A) - 1.0;
100 | );
101 | //
102 | // High shelf filter
103 | //
104 | function eqHS (SR, Hz, Q, dB) instance (a1, a2, a3, m0, m1, m2) local (A, g, k)
105 | (
106 | A = pow(10.0, dB * 0.025); g = tan($PI * (Hz / SR)); k = 1.0 / Q;
107 | a1 = 1.0 / (1.0 + g * (g + k)); a2 = a1 * g; a3 = a2 * g;
108 | m0 = sqr(A); m1 = k * (1.0 - A) * A; m2 = 1.0 - m0;
109 | );
110 | //
111 | // Dummy pass-through non-filter
112 | //
113 | function eqDM () instance (a1, a2, a3, m0, m1, m2)
114 | (
115 | m0 = 1; m1 = 0; m2 = 0;
116 | );
117 |
118 | // WHITE NOISE
119 | //
120 | // Randomised sample values between -1 and +1
121 | //
122 | function tickWhite ()
123 | (
124 | ((rand() * 2)-1);
125 | );
126 |
127 | // PINK NOISE
128 | //
129 | // "Warm" sounding, volume falls off at -3 dBfs per octave across the
130 | // spectrum. Often said to be similar to the optimal mix balance, and
131 | // to be generally pleasing to the human ear.
132 | //
133 | // Implemented after Larry Trammell's "newpink" method:
134 | // http://www.ridgerat-tech.us/tech/newpink.htm
135 | //
136 | function tickPink () local (break, sample)
137 | (
138 | // Randomised sample in range [0,1]
139 | sample = rand();
140 |
141 | break = 0;
142 |
143 | break == 0 && sample <= 0.00198 ? (noiseState[1] = tickWhite() * 3.8024; break = 1);
144 | break == 0 && sample <= 0.01478 ? (noiseState[2] = tickWhite() * 2.9694; break = 1);
145 | break == 0 && sample <= 0.06378 ? (noiseState[3] = tickWhite() * 2.5970; break = 1);
146 | break == 0 && sample <= 0.23378 ? (noiseState[4] = tickWhite() * 3.0870; break = 1);
147 | break == 0 && sample <= 0.91578 ? (noiseState[5] = tickWhite() * 3.4006; break = 1);
148 |
149 | // Noise sample accumulation
150 | noiseState[0] = 0;
151 | noiseState[0] += noiseState[1];
152 | noiseState[0] += noiseState[2];
153 | noiseState[0] += noiseState[3];
154 | noiseState[0] += noiseState[4];
155 | noiseState[0] += noiseState[5];
156 |
157 | // -24 dB gain adjustment
158 | noiseState[0] *= 0.06309573445;
159 | );
160 |
161 | // FILTERED NOISE
162 | //
163 | // Generates a pink noise sample and applies filters to it in order
164 | // to "bend" its curve towards various musical spectral profiles.
165 | //
166 | function tickFiltered () local (noise)
167 | (
168 | // Get a pink noise sample to start with
169 | noise = tickPink();
170 |
171 | // Run the filters over the pink noise sample
172 | noise = filter1.eqTick(noise);
173 | noise = filter2.eqTick(noise);
174 | noise = filter3.eqTick(noise);
175 | noise = filter4.eqTick(noise);
176 | noise = filter5.eqTick(noise);
177 |
178 | // Return the filtered sample
179 | noise;
180 | );
181 |
182 | @slider
183 |
184 | // Turns the trim slider's dB value into a float gain factor
185 | trim = dBToGain(dBGain);
186 |
187 | // If a different preset was selected
188 | //preset != lastPreset ?
189 | 1 ?
190 | (
191 | // White and pink noises
192 | preset == 0 ? gainAdjust = 0.9;
193 | preset == 1 ? gainAdjust = 0.95;
194 |
195 | // Generic Modern
196 | preset == 2 ?
197 | (
198 | filter1.eqHS(srate, 750, 0.4, -8);
199 | filter2.eqHP(srate, 70, 0.5);
200 | filter3.eqLP(srate, 16000, 0.2);
201 | filter4.eqDM();
202 | filter5.eqDM();
203 |
204 | gainAdjust = 1.45;
205 | );
206 |
207 | // Generic Low-End
208 | preset == 3 ?
209 | (
210 | filter1.eqHS(srate, 750, 0.4, -10);
211 | filter2.eqHP(srate, 40, 0.55);
212 | filter3.eqLP(srate, 16000, 0.3);
213 | filter4.eqDM();
214 | filter5.eqDM();
215 |
216 | gainAdjust = 1.35;
217 | );
218 |
219 | // Generic Classical
220 | preset == 4 ?
221 | (
222 | filter1.eqLS(srate, 250, 0.50, -30);
223 | filter2.eqHS(srate, 750, 0.50, -20);
224 | filter3.eqHS(srate, 6000, 0.33, -20);
225 | filter4.eqDM();
226 | filter5.eqDM();
227 |
228 | gainAdjust = 2.8;
229 | );
230 |
231 | // Folk/Acoustic
232 | preset == 5 ?
233 | (
234 | filter1.eqHP(srate, 120, 1.5);
235 | filter2.eqHS(srate, 750, 0.3, -10);
236 | filter3.eqLP(srate, 17500, 0.25);
237 | filter4.eqDM();
238 | filter5.eqDM();
239 |
240 | gainAdjust = 1.25;
241 | );
242 |
243 | // Hip Hop Classic
244 | preset == 6 ?
245 | (
246 | filter1.eqHP(srate, 70, 2.0);
247 | filter2.eqHS(srate, 750, 0.3, -4.5);
248 | filter3.eqPK(srate, 800, 0.5, -3);
249 | filter4.eqPK(srate, 7500, 0.75, 4.5);
250 | filter5.eqLP(srate, 15000, 0.2);
251 |
252 | gainAdjust = 0.95;
253 | );
254 |
255 | // Hip Hop Modern
256 | preset == 7 ?
257 | (
258 | filter1.eqHP(srate, 70, 2.0);
259 | filter2.eqHS(srate, 300, 0.6, -9);
260 | filter3.eqPK(srate, 900, 0.7, 9);
261 | filter4.eqPK(srate, 2500, 0.7, 9);
262 | filter5.eqLP(srate, 13000, 0.9);
263 |
264 | gainAdjust = 0.75;
265 | );
266 |
267 | // Rock 60s/70s
268 | preset == 8 ?
269 | (
270 | filter1.eqHP(srate, 130, 0.75);
271 | filter2.eqHS(srate, 400, 1.0, -10);
272 | filter3.eqPK(srate, 300, 1.0, -6);
273 | filter4.eqPK(srate, 3500, 0.7, 5);
274 | filter5.eqLP(srate, 9500, 0.5);
275 |
276 | gainAdjust = 2.0;
277 | );
278 |
279 | // Rock 80s/90s
280 | preset == 9 ?
281 | (
282 | filter1.eqHP(srate, 140, 1.0);
283 | filter2.eqHS(srate, 200, 0.75, -9);
284 | filter3.eqPK(srate, 750, 2.0, 3);
285 | filter4.eqPK(srate, 3500, 1.5, 4);
286 | filter5.eqLP(srate, 12000, 0.2);
287 |
288 | gainAdjust = 1.65;
289 | );
290 |
291 | // Metal 80s
292 | preset == 10 ?
293 | (
294 | filter1.eqHP(srate, 80, 1.0);
295 | filter2.eqHS(srate, 300, 0.6, -12);
296 | filter3.eqPK(srate, 4000, 0.4, 9);
297 | filter4.eqLP(srate, 15000, 0.4);
298 | filter5.eqDM();
299 |
300 | gainAdjust = 1.3;
301 | );
302 |
303 | // Metal 90s
304 | preset == 11 ?
305 | (
306 | filter1.eqHP(srate, 30, 0.7);
307 | filter2.eqLS(srate, 120, 2.0, -6);
308 | filter3.eqHS(srate, 200, 0.6, -12);
309 | filter4.eqPK(srate, 3700, 0.7, 9);
310 | filter5.eqLP(srate, 14000, 0.4);
311 |
312 | gainAdjust = 1.7;
313 | );
314 |
315 | // Metal Modern
316 | preset == 12 ?
317 | (
318 | filter1.eqHP(srate, 80, 2.5);
319 | filter2.eqHS(srate, 600, 0.2, 6);
320 | filter3.eqPK(srate, 600, 1.0, -3);
321 | filter4.eqLP(srate, 12000, 0.3);
322 | filter5.eqDM();
323 |
324 | gainAdjust = 0.6;
325 | );
326 |
327 | // Update state flag
328 | lastPreset = preset;
329 | );
330 |
331 | @sample
332 |
333 | // Generate noise based on selected preset
334 | preset == 0 ? spl0 = tickWhite();
335 | preset == 1 ? spl0 = tickPink();
336 | preset >= 2 ? spl0 = tickFiltered();
337 |
338 | // Apply trim gain to signal and copy to 2nd output channel
339 | spl0 *= gainAdjust * trim;
340 | spl1 = spl0;
341 |
--------------------------------------------------------------------------------
/PLUGINS.md:
--------------------------------------------------------------------------------
1 | # chokehold JSFX
2 | This page is an index of all my currently published JSFX plugins, including screenshots and short descriptions.
3 | [<< Back to ReadMe](./README.md)
4 |
5 |
6 |
7 | ## Categories
8 | [Clipper](#clipper)
9 | [Distortion](#distortion)
10 | [Dynamics](#dynamics)
11 | [Equalizer](#equalizer)
12 | [Filter](#filter)
13 | [FX](#fx)
14 | [Generator](#generator)
15 | [Instrument FX](#instrument-fx)
16 | [Lo-Fi](#lo-fi)
17 | [Metering](#metering)
18 | [MIDI](#midi)
19 | [Noise](#noise)
20 | [Stereo](#stereo)
21 | [Utility](#utility)
22 |
23 |
24 |
25 |
26 | ## Clipper
27 | | Screenshot | Plugin | Description |
28 | | ---------- | ------ | ----------- |
29 | | [](./assets/screenshots/clipping_algorithm_comparison.png) | [Clipping Algorithm Comparison](./Clipper/clipping_algorithm_comparison.jsfx) | You can use this as a simple clipping, saturation or distortion effect if you wish, but to me it's more of an experiment, an exploration of various clipping algorithms, comparing what each of them do better or worse than the others. |
30 | | [](./assets/screenshots/hard_clipper.png) | [Hard Clipper](./Clipper/hard_clipper.jsfx) | Simple and effective, chops away peaks above an adjustable ceiling volume, call it cold transistor distortion if you must. |
31 | | [](./assets/screenshots/knee_clipper.png) | [Knee Clipper](./Clipper/knee_clipper.jsfx) | A clipper neither purely hard nor purely soft, and yet a unification of both. |
32 | | [](./assets/screenshots/loud_clipper.png) | [Loud Clipper](./Clipper/loud_clipper.jsfx) | The inevitable return of the original ClipMax "Loud" algorithm, plus a new sibling, plus a much louder one. |
33 | | [](./assets/screenshots/sine_clipper.png) | [Sine Clipper](./Clipper/sine_clipper.jsfx) | A clipper that uses the smoothness of the sine wave to tame loud signal peaks. |
34 | | [](./assets/screenshots/soft_clipper.png) | [Soft Clipper](./Clipper/soft_clipper.jsfx) | Increasingly lowers signal peaks the closer they get to an adjustable ceiling volume, call it warm tube overdrive if you must. Has up to 16x oversampling, DC blocker and optional final (non oversampled) hard clipping stage for true 0 dBfs output. |
35 | | [](./assets/screenshots/staging_clipper.png) | [Staging Clipper](./Clipper/staging_clipper.jsfx) | A clipper that automatically compensates for the ceiling, useful for leveling. |
36 |
37 |
38 |
39 | ## Distortion
40 | | Screenshot | Plugin | Description |
41 | | ---------- | ------ | ----------- |
42 | | [](./assets/screenshots/foldback_distortion.png) | [Foldback Distortion](./Distortion/foldback_distortion.jsfx) | Folds waves between 0 and an adjustable ceiling, very harsh and nasty distortion. |
43 |
44 |
45 |
46 | ## Dynamics
47 | | Screenshot | Plugin | Description |
48 | | ---------- | ------ | ----------- |
49 | | [](./assets/screenshots/bus_comp.png) | [Bus Comp](./Dynamics/bus_comp.jsfx) | Submix bus oriented compressor with automatic/program-dependent release, fadeable stereo linking, Mid/Side mode, external sidechain input with high pass filter, instability noise, dynamic saturation, optional output hard clipping and dry/wet mix for parallel compression. |
50 | | [](./assets/screenshots/consolidator.png) | [Consolidator](./Dynamics/consolidator.jsfx) | A set of 3 compressors with sidechain filter and stereo linking chained in a row, each with its own character. Has a dry/wet mix parameter for instant parallel/NY compression and Mid/Side mode. |
51 | | [](./assets/screenshots/gate_expander.png) | [Gate/Expander](./Dynamics/gate_expander.jsfx) | Several channel routings, external sidechain, sidechain filter, fadeable stereo linking, hysteresis, and it can be switched between gate (fades to silence) and expander (fades to defined volume) modes. |
52 | | [](./assets/screenshots/track_comp.png) | [Track Comp](./Dynamics/track_comp.jsfx) | A low-CPU and ultra-flexible compressor, with everything from peak/RMS detection over feed-forward/-back modes, stereo linking and channel setups to automatic gain compensation and dynamic saturation. |
53 |
54 |
55 |
56 | ## Equalizer
57 | | Screenshot | Plugin | Description |
58 | | ---------- | ------ | ----------- |
59 | | [](./assets/screenshots/eq_560.png) | [EQ 560](./Equalizer/eq_560.jsfx) | Classic American 10-band graphic console equalizer. Limited flexibility, fast and streamlined workflow. Uses 2x oversampling for accurate high frequency filter curves, adds some character. |
60 |
61 |
62 |
63 | ## Filter
64 | | Screenshot | Plugin | Description |
65 | | ---------- | ------ | ----------- |
66 | | No UI | [DC Filter](./Filter/dc_filter.jsfx) | Multi-channel capable and extremely narrow DC offset removal filter. |
67 |
68 |
69 |
70 | ## FX
71 | | Screenshot | Plugin | Description |
72 | | ---------- | ------ | ----------- |
73 | | [](./assets/screenshots/filthy_delay.png) | [Filthy Delay](./FX/filthy_delay.jsfx) | Stereo delay with multiple routings (currently: stereo, inverted, ping-pong, mono), plus optional filters, boostable saturation and downsampling degradation in the feedback path. |
74 | | [](./assets/screenshots/ring_mod.png) | [Ring Mod](./FX/ring_mod.jsfx) | Multiplies the input signal with a carrier signal, which can lead to all sorts of warbly modulation and distortion effects. |
75 |
76 |
77 |
78 | ## Generator
79 | | Screenshot | Plugin | Description |
80 | | ---------- | ------ | ----------- |
81 | | [](./assets/screenshots/test_signals.png) | [Test Signals](./Generator/test_signals.jsfx) | A collection of 13 different test tone and noise generators in one plugin, 14 if you count silence. Can have a different generator on each channel, can output both or just one channel, can sum both channels. |
82 |
83 |
84 |
85 | ## Instrument FX
86 | | Screenshot | Plugin | Description |
87 | | ---------- | ------ | ----------- |
88 | | [](./assets/screenshots/amp_sim.png) | [Amp Sim](./Instrument%20FX/amp_sim.jsfx) | Guitar and bass amplifier with up to 16x oversampling, mono/stereo routings, pre/post/triggered noise gate, booster pedal function, interactive inter-stage EQ bands, dynamic Depth and Presence bands, and maximizer. |
89 | | [](./assets/screenshots/bass_squeezer.png) | [Bass Squeezer](./Instrument%20FX/bass_squeezer.jsfx) | Split-band compression and filtered distortion for that instant bathtub bass sound. |
90 | | [](./assets/screenshots/cabinet_sim.png) | [Cabinet Sim](./Instrument%20FX/cabinet_sim.jsfx) | Simple cabinet sim with 5 burnt-in impulse responses, 4 for guitar and 1 for bass. Can **not** load IRs from file. |
91 | | [](./assets/screenshots/chug_thug.png) | [Chug Thug](./Instrument%20FX/chug_thug.jsfx) | Simplified split-band processor to tame the low end of distorted guitars when palm muting. |
92 | | [](./assets/screenshots/mic_combiner.png) | [Mic Combiner](./Instrument%20FX/mic_combiner.jsfx) | Utility to facilitate the process of merging two mono microphone signals into one. Processes each microphone signal individually, allows to set a balance/mix between both microphones, and sums them to mono. Can adjust timing between signals. |
93 |
94 |
95 |
96 | ## Lo-Fi
97 | | Screenshot | Plugin | Description |
98 | | ---------- | ------ | ----------- |
99 | | [](./assets/screenshots/signal_crusher.png) | [Signal Crusher](./Lo-Fi/signal_crusher.jsfx) | Sample rate changes, lost sample reconstruction, bit truncation, bit dithering... it's all in here. |
100 | | [](./assets/screenshots/telephone.png) | [Telephone](./Lo-Fi/telephone.jsfx) | Makes vocals sound like they're coming through a phone receiver, also worth slamming on drums. |
101 |
102 |
103 |
104 | ## Metering
105 | | Screenshot | Plugin | Description |
106 | | ---------- | ------ | ----------- |
107 | | [](./assets/screenshots/correlation_meter.png) | [Correlation Meter](./Metering/correlation_meter.jsfx) | Inspect your song's stereo phase balance and find mono-problematic areas by watching an indicator go red. |
108 | | [](./assets/screenshots/phase_scope.png) | [Phase Scope](./Metering/phase_scope.jsfx) | Visualizes the stereo field of a signal, also commonly known as Goniometer, Vector Scope or Lissajous. Selectable visualization colour and optional freeze-on-pause function. |
109 | | [](./assets/screenshots/stereo_checker.png) | [Stereo Checker](./Metering/stereo_checker.jsfx) | Displays the inter-channel relation of a two-channel input signal. |
110 | | [](./assets/screenshots/wave_scope.png) | [Wave Scope](./Metering/wave_scope.jsfx) | Waveform display that can visualize various mono/stereo channel streams as Decibels, signed or absolute samples. With fadeable colours and freeze-on-pause function. |
111 |
112 |
113 |
114 | ## MIDI
115 | | Screenshot | Plugin | Description |
116 | | ---------- | ------ | ----------- |
117 | | [](./assets/screenshots/midi_chord_trigger.png) | [MIDI Chord Trigger](./MIDI/midi_chord_trigger.jsfx) | Press one MIDI key to play a chord. Has 12 pre-selectable chord slots, uses one MIDI octave to switch them, uses one MIDI octave to switch in which MIDI octave the chord will be played, uses one MIDI octave to pick a root note and actually play the chord. Great help for background piano and orchestral ensembles. |
118 |
119 |
120 |
121 | ## Noise
122 | | Screenshot | Plugin | Description |
123 | | ---------- | ------ | ----------- |
124 | | [](./assets/screenshots/interpolated_noise.png) | [Interpolated Noise](./Noise/interpolated_noise.jsfx) | Various flavours of pleasingly natural and organically chaotic noise created with the help of various interpolation methods. |
125 | | [](./assets/screenshots/reference_noise.png) | [Reference Noise](./Noise/reference_noise.jsfx) | Provides various noise profiles, filtered or plain, to use as target reference for spectral/visual mixing. |
126 |
127 |
128 |
129 | ## Stereo
130 | | Screenshot | Plugin | Description |
131 | | ---------- | ------ | ----------- |
132 | | [](./assets/screenshots/m-s_fader.png) | [M-S Fader](./Stereo/m-s_fader.jsfx) | Converts a stereo source to a Mid/Side signal and then fades between 100% Mid and 100% Side signal, or a mix of both. Great to remove the center signal, or to focus in on it. |
133 | | [](./assets/screenshots/stereo_bleed_remover.png) | [Stereo Bleed Remover](./Stereo/stereo_bleed_remover.jsfx) | An attempt at removing unwanted bleed between channels in a stereo signal. |
134 | | [](./assets/screenshots/stereo_pan.png) | [Stereo Pan](./Stereo/stereo_pan.jsfx) | Utility that implements various standardized pan laws, as well as some custom methods by me. |
135 |
136 |
137 |
138 | ## Utility
139 | | Screenshot | Plugin | Description |
140 | | ---------- | ------ | ----------- |
141 | | [](./assets/screenshots/dc_offset.png) | [DC Offset](./Utility/dc_offset.jsfx) | Adds constant 0 Hz content to all channels of a signal and shifts waveforms up or down, probably only useful to test DC filters. |
142 | | [](./assets/screenshots/impulse_generator.png) | [Impulse Generator](./Utility/impulse_generator.jsfx) | Generates a 1-sample impulse when triggered, for use when sampling devices to IRs. |
143 | | [](./assets/screenshots/string_tuning_calculator.png) | [String Tuning Calculator](./Utility/string_tuning_calculator.jsfx) | Calculate frequencies (and other useful data) for precise EQ-ing or drum tuning based on stringed instrument tunings. |
144 | | [](./assets/screenshots/volume_range_trim.png) | [Volume Range Trim](./Utility/volume_range_trim.jsfx) | Fader to non-destructively alter a signal's volume within a specific range (+/- 6, 12, 24, 48 dB), gives finer control when automating volume. Auto-adapts to track's channel count. |
145 | | [](./assets/screenshots/volume_trim.png) | [Volume Trim](./Utility/volume_trim.jsfx) | Simple fader to alter a signal's volume, useful for gain-staging between plugins. Auto-adapts to track's channel count. |
146 |
147 |
148 | _(There may be more in the future)_
149 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # chokehold JSFX
2 | A variety of free [JSFX](https://www.reaper.fm/sdk/js/) plugins I created for use in [Reaper](https://www.reaper.fm/).
3 |
4 | Visit the [plugins index page](https://github.com/chkhld/jsfx/blob/main/PLUGINS.md) for an overview of all of my currently published JSFX plugins, including screenshots and short descriptions. _See the source files for more specific details and instructions._
5 |
6 |
7 |
8 |
9 | ## Installation
10 | If you have the [ReaPack Extension](https://reapack.com/) installed, you can add the repository to Reaper with this URL:
11 |
12 | ```
13 | https://github.com/chkhld/jsfx/raw/main/index.xml
14 | ```
15 |
16 |
17 | If you **don't** have ReaPack installed, you can also add these plugins on a per-file basis.
18 | - Visit the [plugins index page](https://github.com/chkhld/jsfx/blob/main/PLUGINS.md) and download what you want from there, or [get the full release here](https://github.com/chkhld/jsfx/releases/).
19 | - If you don't know what to do with these files, watch the [Reaper Blog tutorial](https://reaperblog.net/2015/06/quick-tip-how-to-install-js-plugins/) on how to install JSFX plugins.
20 |
21 |
22 |
23 |
24 | ## Support
25 | This is just a fun project for me, I do this on the side to waste time or template C++ code, so don't expect too much of an effort. But if you find any bugs, or if you have any suggestions for improvements, feel free to report them in the [issue tracker](https://github.com/chkhld/jsfx/issues), I'll have a look there every now and then.
26 |
27 |
28 |
29 | Enjoy!
--------------------------------------------------------------------------------
/Stereo/m-s_fader.jsfx:
--------------------------------------------------------------------------------
1 | desc: M-S Fader
2 | version: 1.8.2
3 | author: chokehold
4 | tags: utility mid-side processing mono stereo
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/m-s_fader.png
7 | about:
8 | # M-S Fader
9 |
10 | Converts a stereo L/R input to a Mid/Side signal and then fades
11 | between 100% Mid signal and 100% Side signal.
12 |
13 | If the fader is fully left at -100, then only the Mid signal is
14 | sent to the output. If the fader sits fully right at +100, only
15 | the side signal is sent to the output. If the fader is centered
16 | at 0, the output is the sum of 100% Mid and 100% Side, i.e. the
17 | unchanged input signal.
18 |
19 | You can use this to either cancel out the Mid signal (voices or
20 | drums usually) or to do the exact opposite, i.e. cancel out the
21 | stereo sides and focus in on the panorama center/mid signal.
22 |
23 | // ----------------------------------------------------------------------------
24 | slider1:balance=0<-100,100,0.01>Balance [M-MS-S]
25 | slider2:monofy=0<0,1,{Stereo,Mono}>Output width
26 |
27 | in_pin:left input
28 | in_pin:right input
29 | out_pin:left output
30 | out_pin:right output
31 |
32 | @init
33 |
34 | // Reciprocal constant 1.0 / sqrt(2.0)
35 | rcpSqrt2 = 0.7071067811865475244008443621;
36 |
37 | // L/R and M/S conversion functions
38 | //
39 | function lrToM (sampleLeft, sampleRight) ((sampleLeft + sampleRight) * rcpSqrt2);
40 | function lrToS (sampleLeft, sampleRight) ((sampleLeft - sampleRight) * rcpSqrt2);
41 | function msToL (sampleMid, sampleSide) ((sampleMid + sampleSide) * rcpSqrt2);
42 | function msToR (sampleMid, sampleSide) ((sampleMid - sampleSide) * rcpSqrt2);
43 |
44 | @slider
45 |
46 | // Default to mix of 100% Mid + 100% Side
47 | gainMid = 100;
48 | gainSide = 100;
49 |
50 | // If fade towards Side signal
51 | balance > 0 ?
52 | (
53 | gainMid = 100 - balance;
54 | gainSide = 100;
55 | ):
56 | // If fade towards Mid signal
57 | balance < 0 ?
58 | (
59 | gainMid = 100;
60 | gainSide = 100 + balance;
61 | );
62 |
63 | // Convert % to float gain
64 | gainMid *= 0.01;
65 | gainSide *= 0.01;
66 |
67 | @sample
68 |
69 | // Convert L/R samples to M/S samples
70 | splMid = lrToM(spl0, spl1);
71 | splSide = lrToS(spl0, spl1);
72 |
73 | // Adjust gain balance of M/S samples
74 | splMid *= gainMid;
75 | splSide *= gainSide;
76 |
77 | // Convert M/S samples back to L/R samples
78 | spl0 = msToL(splMid, splSide);
79 | spl1 = msToR(splMid, splSide);
80 |
81 | // If output should be summed to mono/0% width
82 | monofy == 1 ?
83 | (
84 | spl1 = spl0 = ((spl0 - spl1) + (spl0 + spl1)) * 0.5;
85 | );
86 |
--------------------------------------------------------------------------------
/Stereo/stereo_bleed_remover.jsfx:
--------------------------------------------------------------------------------
1 | desc: Stereo Bleed Remover
2 | version: 1.8.3
3 | author: chokehold
4 | tags: utility stereo bleed channel separation
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/stereo_bleed_remover.png
7 | about:
8 | # Stereo Bleed Remover
9 |
10 | Say you have a stereo clip, but some of the L signal
11 | is audible on the R channel and some of the R signal
12 | is audible on the L channel. Say you don't want that.
13 |
14 | This plugin will attempt to remove the channel bleed,
15 | so that you have two separate L/R channels, and only
16 | an absolute minimum trace of signal bleed is left.
17 |
18 | The process inherently loses some volume, so use the
19 | Trim slider to adjust the level (post FX) if desired.
20 |
21 | NOTE:
22 |
23 | This process will only work (well) with signals that
24 | have no phase differences between direct sources and
25 | their opposite bleed partials, like if stereo tracks
26 | were previously unsatisfyingly panned.
27 |
28 | Signals recorded with two microphones, and both mics
29 | panned to opposite L/R channels, will likely contain
30 | runtime/phase differences between the direct signals
31 | and their bleed partials on the opposite channels.
32 |
33 | With such material, this process will probably fail.
34 |
35 | // ----------------------------------------------------------------------------
36 | slider1:fxPcnt=0<0,100,0.01>Amount [%]
37 | slider2:dBTrim=0<-12,12,0.0001>Trim [dB]
38 |
39 | in_pin:Input Left
40 | in_pin:Input Right
41 | out_pin:Output Left
42 | out_pin:Output Right
43 |
44 | @init
45 |
46 | // Converts dB values to float gain factors. Divisions
47 | // are slow, so: dB/20 --> dB * 1/20 --> dB * 0.05
48 | function dBToGain (decibels) (pow(10, decibels * 0.05));
49 |
50 | @slider
51 |
52 | // Converts the slider's % value into linear [0,1] range
53 | amount = fxPcnt * 0.01; // 1/100 = 1 * 0.01
54 |
55 | // Turns the slider's dB value into a float gain factor
56 | trim = dBToGain(dBTrim);
57 |
58 | @sample
59 |
60 | // Buffer left input sample, so the following step
61 | // doesn't use its -then- already modified value
62 | splL = spl0;
63 |
64 | // Sum the inverted input samples onto their
65 | // opposite channels at the amount set by user
66 | spl0 += -spl1 * amount;
67 | spl1 += -splL * amount;
68 |
69 | // Output gain adjustment
70 | spl0 *= trim;
71 | spl1 *= trim;
72 |
73 |
--------------------------------------------------------------------------------
/Stereo/stereo_pan.jsfx:
--------------------------------------------------------------------------------
1 | desc: Stereo Pan
2 | version: 1.8.3
3 | author: chokehold
4 | tags: utility pan panorama stereo channel
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/stereo_pan.png
7 | about:
8 | # Stereo Pan
9 |
10 | Implementation of various standard panning laws,
11 | as well as some of my own customised types.
12 |
13 | // ----------------------------------------------------------------------------
14 | slider1:0<-100,100,0.001> Position [-L % R+]
15 | slider2:3<0,6,1{-3 dB Center,-4.5 Center,-5.4 dB Center custom near-constant power,-6 dB Center linear constant power,-6 dB Center constant power,-6 dB Center custom constant power,-7.6 dB Center custom}>Pan Law
16 | slider3:1<0,1,1{Off,On}>Volume Compensation
17 |
18 | in_pin:left input
19 | in_pin:right input
20 | out_pin:left output
21 | out_pin:right output
22 |
23 | @init
24 | function dBToGain (decibels) (pow(10, decibels * 0.05));
25 |
26 | quarterPi = 0.7853981634;
27 | halfPi = 1.5707963268;
28 |
29 | gainL = 1.0;
30 | gainR = 1.0;
31 |
32 | compensation = 1.0;
33 |
34 | @slider
35 | panPosition = (slider1 + 100) * 0.005;
36 |
37 | //
38 | // -3 dB Center
39 | //
40 | slider2 == 0 ? (
41 | compensation = (slider3 == 0) ? 1.0 : dBToGain(3.0);
42 |
43 | gainL = cos(panPosition * halfPi) * compensation;
44 | gainR = sin(panPosition * halfPi) * compensation;
45 | );
46 |
47 | //
48 | // -4.5 dB Center
49 | //
50 | slider2 == 1 ? (
51 | compensation = (slider3 == 0) ? 1.0 : dBToGain(4.5);
52 |
53 | gainL = pow(cos(panPosition * halfPi), 1.5) * compensation;
54 | gainR = pow(sin(panPosition * halfPi), 1.5) * compensation;
55 | );
56 |
57 | //
58 | // -5.4 dB Center custom near-constant power
59 | //
60 | slider2 == 2 ? (
61 | compensation = (slider3 == 0) ? 1.0 : dBToGain(5.4);
62 |
63 | gainL = pow(tan((1.0 - panPosition) * quarterPi), 0.707) * compensation;
64 | gainR = pow(tan(panPosition * quarterPi), 0.707) * compensation;
65 | );
66 |
67 | //
68 | // - 6 dB Center linear constant power
69 | //
70 | slider2 == 3 ? (
71 | compensation = (slider3 == 0) ? 1.0 : dBToGain(6.0);
72 |
73 | gainL = (1.0 - panPosition) * compensation;
74 | gainR = panPosition * compensation;
75 | );
76 |
77 | //
78 | // -6 dB Center constant power
79 | //
80 | slider2 == 4 ? (
81 | compensation = (slider3 == 0) ? 1.0 : dBToGain(6.0);
82 |
83 | gainL = pow(cos(panPosition* halfPi), 2.0) * compensation;
84 | gainR = pow(sin(panPosition* halfPi), 2.0) * compensation;
85 | );
86 |
87 | //
88 | // -6 dB Center custom constant power
89 | //
90 | slider2 == 5 ? (
91 | compensation = (slider3 == 0) ? 1.0 : dBToGain(6.0);
92 |
93 | panPolarity = sign(slider1);
94 | absPosition = abs(slider1 * 0.04);
95 |
96 | curve = sqrt(0.25 * pow(absPosition, 3.0)) * 0.125;
97 |
98 | posCurve = 0.5 + curve;
99 | negCurve = 0.5 - curve;
100 |
101 | gainL = (panPolarity == -1) ? posCurve : ((panPolarity == 1) ? negCurve : 0.5);
102 | gainR = (panPolarity == -1) ? negCurve : ((panPolarity == 1) ? posCurve : 0.5);
103 |
104 | gainL *= compensation;
105 | gainR *= compensation;
106 | );
107 |
108 | //
109 | // -7.6 dB Center custom
110 | //
111 | slider2 == 6 ? (
112 | compensation = (slider3 == 0) ? 1.0 : dBToGain(7.6);
113 |
114 | gainL = tan((1.0 - panPosition) * quarterPi) * compensation;
115 | gainR = tan(panPosition * quarterPi) * compensation;
116 | );
117 |
118 | @sample
119 | spl0 *= gainL;
120 | spl1 *= gainR;
121 |
--------------------------------------------------------------------------------
/Utility/dc_offset.jsfx:
--------------------------------------------------------------------------------
1 | desc: DC Offset
2 | version: 1.8.3
3 | author: chokehold
4 | tags: utility dc offset
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/dc_offset.png
7 | about:
8 | # DC Offset
9 |
10 | Adds a DC Offset (0 Hz non-moving frequency) to all channels of a signal.
11 |
12 | This will usually not be a desirable thing, but it will help you when testing
13 | DC filters for functionality. :)
14 |
15 | // ----------------------------------------------------------------------------
16 | // The range [-1,+1] adds DC of up to 0 dBfs, so that should be sufficient...
17 | slider1:dcOffset=0<-1,1,0.00001>DC Offset
18 |
19 | // By not having in_pin and out_pin assignments, this plugin is able to adapt to
20 | // the number of channels on its track automatically.
21 |
22 | @sample // ---------------------------------------------------------------------
23 |
24 | // Start at channel 0
25 | channel = 0;
26 |
27 | // Cycle through all channels on this track
28 | while (channel < num_ch)
29 | (
30 | // Replace this channel's sample by a DC filtered version of itself
31 | spl(channel) += dcOffset;
32 |
33 | // Advance to the sample on the next channel
34 | channel += 1;
35 | );
36 |
37 |
--------------------------------------------------------------------------------
/Utility/impulse_generator.jsfx:
--------------------------------------------------------------------------------
1 | desc: Impulse Generator
2 | version: 1.8.3
3 | author: chokehold
4 | tags: utility impulse response IR sampling
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/impulse_generator.png
7 | about:
8 | # Impulse Generator
9 |
10 | This will generate a 1-sample impulse when the trigger
11 | slider is pulled over to the right.
12 |
13 | May not sound terribly exciting, but creates the perfect
14 | source to send through devices you want to capture as IR.
15 |
16 | You would use this to capture a reverb or delay tail, the
17 | frequency profile of a guitar cabinet or EQ, etc.
18 |
19 | Load the generator as the first plugin on an empty track
20 | then insert the plugins or devices to capture behind it.
21 | Switch the record mode of the track to Output, start the
22 | recording and push the Trigger slider to the right. Stop
23 | the recording, trim the recorded item down to a sensible
24 | length, make sure it has no crossfades at start and end.
25 |
26 | IMPORTANT: you need to zoom right in and ensure that the
27 | first sample in the item is the first sample that is not
28 | on the center zero line. If you don't do this, the IR is
29 | going to be delayed, and you'll have to remove the delay
30 | in ReaVerb later by trimming the start/end times.
31 |
32 | Export the resulting item into a WAV file, and load that
33 | WAV into ReaVerb, done.
34 |
35 | // ----------------------------------------------------------------------------
36 | slider1:dBLevel=0<-48,0,0.0001>Level [dB]
37 | slider2:btnTrig=0<0,1,1>Trigger
38 |
39 | // By not having any in_pin and out_pin assignments, this plugin will
40 | // automatically adapt to the number of channels of the track.
41 |
42 | @init
43 |
44 | // Converts dB values to float gain factors.
45 | function dBToGain (decibels) (pow(10, decibels * 0.05));
46 |
47 | @slider
48 |
49 | // Turns the slider's dB value into a float gain factor.
50 | // Essentially the impulse sample sent on to the device.
51 | impulse = dBToGain(dBLevel);
52 |
53 | // If the trigger slider was just dragged to the right,
54 | // set the flag that signals the impulse should play.
55 | (btnTrig == 1 && prvTrig == 0) ? (triggered = 1) : (triggered = 0);
56 |
57 | // Update this status flag to track slider changes
58 | prvTrig = btnTrig;
59 |
60 | @sample
61 |
62 | // If the trigger slider was moved to the right
63 | (triggered == 1) ?
64 | (
65 | // Start at channel 0
66 | channel = 0;
67 |
68 | // Cycle through the available channels
69 | while (channel < num_ch)
70 | (
71 | // Play the impulse sample at the set level
72 | spl(channel) = impulse;
73 |
74 | // Increment counter to next channel
75 | channel += 1;
76 | );
77 |
78 | // For this kind of purpose the impulse should only last
79 | // for a duration of 1 sample. Having played the impulse
80 | // sample already, the trigger slider and state flag can
81 | // be reset immediately.
82 | btnTrig = 0;
83 | triggered = 0;
84 | );
85 |
86 |
--------------------------------------------------------------------------------
/Utility/string_tuning_calculator.jsfx:
--------------------------------------------------------------------------------
1 | desc: String Tuning Calculator
2 | version: 1.8.3
3 | author: chokehold
4 | tags: guitar bass tuner tuning frequency pitch utility
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/string_tuning_calculator.png
7 | about:
8 | # String Tuning Calculator
9 |
10 | A nifty little utility to quickly calculate MIDI note numbers,
11 | note names and their frequencies from a guitar/bass tuning. Or
12 | to use as a super-accurate and super-flexible tuner.
13 |
14 | The original idea was to have this as a reference to tune drum
15 | kits after. Kicks, snares, toms, they all hit hardest in a mix
16 | if they're tuned to notes in the scale of other instruments in
17 | the song.
18 |
19 | So you load this little utility, set up the guitar/bass tuning,
20 | set up the root note of the song, or another note in the scale,
21 | and find out what frequency your kick/snare should be tuned to.
22 |
23 | But that was just the beginning, there's more.
24 |
25 | You can use this tool to calculate which frequencies to cut or
26 | boost in an EQ, or to know which MIDI note corresponds to what
27 | you're playing on the instrument.
28 |
29 | If you activate the "Listen to pitch" box, you can use this as
30 | a normal tuner, or to check the intonation on your instrument.
31 |
32 | OK, let me walk you through this.
33 |
34 | First you pick a tuning standard, most commonly 440 Hz, others
35 | are available. If you don't know what this is, just leave it.
36 |
37 | The next slider is a global tune offset. If for any reason you
38 | need to tune your instrument to an exact pitch that's a little
39 | bit quirky and does not fall into an exact semitone, then this
40 | Cent-based detune offset slider would allow you to handle that.
41 |
42 | The following block of 3 sliders lets you set the note that is
43 | played on the instrument.
44 |
45 | String note name: just the name of the note, C-D-E-F-G... etc.
46 | the string of your instrument is tuned to.
47 |
48 | String octave - which octave is the string tuned to? If you're
49 | going for e.g. standard tuning of the low E string on a guitar,
50 | you would set these to E2. If you want the tuning of the low B
51 | string on a 5-string bass, you'd set this to B0. See the table
52 | below for some reference values:
53 |
54 | | MIDI | Name | Instrument String |
55 | |------|------|-------------------------|
56 | | 40 | E2 | Guitar E low |
57 | | 45 | A2 | Guitar A |
58 | | 50 | D3 | Guitar D |
59 | | 55 | G3 | Guitar G |
60 | | 59 | B3 | Guitar B |
61 | | 64 | E4 | Guitar E high |
62 |
63 | | MIDI | Name | Instrument String |
64 | |------|------|-------------------------|
65 | | 20 | G#0 | Guitar G# low 10-string |
66 | | 25 | C#1 | Guitar C# low 9-string |
67 | | 30 | F#1 | Guitar F# low 8-string |
68 | | 35 | B1 | Guitar B low 7-string |
69 |
70 | | MIDI | Name | Instrument String |
71 | |------|------|-------------------------|
72 | | 28 | E1 | Bass E |
73 | | 33 | A1 | Bass A |
74 | | 38 | D2 | Bass D |
75 | | 43 | G2 | Bass G |
76 |
77 | | MIDI | Name | Instrument String |
78 | |------|------|-------------------------|
79 | | 23 | B0 | Bass B low 5-string |
80 | | 48 | C3 | Bass C high 6-string |
81 |
82 | Fret on string: if the string is not played open but a fret is
83 | played on the string, maybe with a capo, then you can use this
84 | slider to pick the fret that is being played. 12 is one octave
85 | up, 24 is two.
86 |
87 | Beneath these is a block of four DISPLAY properties, these are
88 | read-only and set from inside the plugin, effectively readouts.
89 |
90 | MIDI note number: tells you the MIDI note corresponding to the
91 | string and fret settings you just made.
92 |
93 | Note name/octave: calculate and display name and octave of the
94 | note that is the result of what you set up earlier.
95 | E.g. string set to E2, playing fret 13 --> F3.
96 |
97 | Note frequency: this is the important one, the one this plugin
98 | was made for. Calculated from the string & fret settings, this
99 | will display the frequency in Hz the note would sit at, if you
100 | played it on your instrument.
101 |
102 | You can use this value to focus on important frequencies in an
103 | equaliser, or to have a reference for your drum tuning gadget.
104 |
105 | If you switch the "Listen to pitch" on, the plugin will output
106 | a simple sine wave for you to tune your instruments by. It's a
107 | boring sine wave, but in combination with the tuning standards
108 | and the detune settings, and by not being bound to the tunings
109 | someone else thought you should have, this little thing is far
110 | more flexible than a common clip-on or pedal tuner.
111 |
112 | // ----------------------------------------------------------------------------
113 | slider1:tuningStandard=5<0,8,1{392 Hz - French Baroque pitch,415 Hz - Baroque pitch (historical),428 Hz - Experimental alternative tuning,432 Hz - Verdi tuning,435 Hz - 19th-century French standard,440 Hz - Modern international standard,442 Hz - European orchestral pitch 1,443 Hz - European orchestral pitch 2,466 Hz - High Baroque (Chorton)}>Tuning standard
114 | slider2:detuneCents=0<-99,99,1>Root detune [Cent]
115 | slider3
116 | slider4:stringNote=4<0,11,{C,C#,D,D#,E,F,F#,G,G#,A,A#,B}>String note name
117 | slider5:stringOctave=2<0,5,1{0,1,2,3,4,5}>String octave
118 | slider6:stringFret=0<0,30,1>Fret on string
119 | slider7
120 | slider8:playedMidi=0<12,113,1>DISPLAY: Note MIDI number
121 | slider9:playedNote=4<0,11,{C,C#,D,D#,E,F,F#,G,G#,A,A#,B}>DISPLAY: Note name
122 | slider10:playedOctave=3<0,8,1{0,1,2,3,4,5,6,7,8}>DISPLAY: Note octave
123 | slider11:playedHertz=0<0,5588,0.0001:log=1000>DISPLAY: Note frequency [Hz]
124 | slider12
125 | slider13:listen=0<0,1,{Off,On}>Listen to pitch
126 | slider14:decibels=-36<-120,0,1>Volume [dBfs]
127 |
128 | // By not having any in_pin and out_pin assignments, this plugin will
129 | // automatically adapt to the number of channels of the track.
130 |
131 | @init
132 |
133 | // Converts signed Decibels to float amplitude value
134 | function dBToAmp (decibels) (pow(10, decibels * 0.05));
135 |
136 | // Offsets for the A4 note in various tuning standards.
137 | // These are added to 440 Hz to get the actual value.
138 | // Was more efficient this way than using 9+ conditionals.
139 | rootFrequencyOffsets = 0; // Array memory address
140 | rootFrequencyOffsets[0] = -48; // 392 Hz - French Baroque pitch
141 | rootFrequencyOffsets[1] = -25; // 415 Hz - Baroque pitch (historical)
142 | rootFrequencyOffsets[2] = -12; // 428 Hz - Experimental alternative tuning
143 | rootFrequencyOffsets[3] = -8; // 432 Hz - Verdi tuning
144 | rootFrequencyOffsets[4] = -5; // 435 Hz - 19th-century French standard
145 | rootFrequencyOffsets[5] = 0; // 440 Hz - MODERN INTERNATIONAL STANDARD
146 | rootFrequencyOffsets[6] = 2; // 442 Hz - European orchestral pitch 1
147 | rootFrequencyOffsets[7] = 3; // 443 Hz - European orchestral pitch 2
148 | rootFrequencyOffsets[8] = 26; // 466 Hz - High Baroque (Chorton)
149 |
150 | // Calculates the root frequency for A4 using the offset values above
151 | function setRootFrequency () local ()
152 | (
153 | 440 + rootFrequencyOffsets[tuningStandard];
154 | );
155 |
156 | // Turns the +/- Cent value into a factor that is later
157 | // used to scale the calculated note frequency
158 | function calculateRootDetune ()
159 | (
160 | 2 ^ (detuneCents / 1200);
161 | );
162 |
163 | // Calculate which MIDI note matches the string tuning and played fret
164 | function calculatePlayedMidiNumber ()
165 | (
166 | ((stringOctave + 1) * 12) + stringNote + stringFret;
167 | );
168 |
169 | // Calculate which note (C-D-E-F-...) the MIDI note corresponds to
170 | function calculatePlayedNoteName ()
171 | (
172 | playedMidi % 12;
173 | );
174 |
175 | // Calculate in which (MIDI) octave the MIDI note sits
176 | function calculatePlayedNoteOctave ()
177 | (
178 | ((playedMidi - 1) / 12) - 1;
179 | );
180 |
181 | // Calculate the exact frequency in Hertz the played note corresponds to.
182 | // Consider that the 440 Hz root frequency may be offset, and include the
183 | // Cent-based detuning factor.
184 | function calculatePlayedNoteFrequency ()
185 | (
186 | rootFrequency * (2 ^ ((playedMidi - 69) / 12)) * rootDetune;
187 | );
188 |
189 | // SINE WAVE OSCILLATOR
190 | //
191 | // Important values and memory buffers
192 | twoPi = 2.0 * $PI; // Performance constant
193 | oscPhase = 10; // Per-channel phase buffer (must be in memory after rootFrequencyOffsets)
194 | oscFreq = 0; // Oscillator frequency
195 | oscFreqHz = 0; // Oscillator frequency in Hertz
196 | oscRateHz = 0; // Oscillator sample rate in Hertz
197 | oscRootHz = 0; // Oscillator root frequency in Hertz
198 | oscDetune = 0; // Oscillator root detune factor
199 | listenState = 0; // Flag to track previous listen state
200 | // _ _
201 | // / \ / \
202 | // \_/ \_/
203 | //
204 | // Per-sample tick function
205 | function tickSine (channel)
206 | (
207 | // Advance the phase for this channel
208 | oscPhase[channel] += (twoPi * oscFreq);
209 |
210 | // Wrap phase >2π around from 0 (to keep angles inside 0°-360°)
211 | oscPhase[channel] += ((oscPhase[channel] >= twoPi) * -twoPi) + ((oscPhase[channel] < 0.0) * twoPi);
212 |
213 | // Generate and return sine value for this channel's advanced and wrapped phase
214 | sin(oscPhase[channel]);
215 | );
216 |
217 | // If the project sample rate or the calculated note frequency change,
218 | // the internal oscillator frequency value must be recalculated.
219 | function checkOscFreqChange () local ()
220 | (
221 | (oscFreqHz != playedHertz) || (oscRateHz != srate) ?
222 | (
223 | oscFreqHz = playedHertz;
224 | oscRateHz = srate;
225 |
226 | oscFreq = oscFreqHz / oscRateHz;
227 | );
228 | );
229 |
230 | // Oscillator phase (only) needs to be reset when pitch listening
231 | // was previously turned off, but is now detected as active.
232 | function checkOscPhaseReset ()
233 | (
234 | // If the pitch listening was just turned on
235 | (listenState != listen) ?
236 | (
237 | // Update the buffer flag
238 | listenState = listen;
239 |
240 | // Start with channel 0
241 | oscChannel=0;
242 |
243 | // Iterate through all available audio channels
244 | while (oscChannel < num_ch)
245 | (
246 | // Reset the oscillator phase for this channel to 0°
247 | oscPhase[oscChannel] = 0.0;
248 |
249 | // Increment loop counter
250 | oscChannel += 1;
251 | );
252 | );
253 | );
254 |
255 | @slider
256 |
257 | // Everything is connected, everything is easy to calculate,
258 | // so calculate everything if even only 1 slider is moved.
259 | rootFrequency = setRootFrequency();
260 | rootDetune = calculateRootDetune();
261 | playedMidi = calculatePlayedMidiNumber();
262 | playedNote = calculatePlayedNoteName();
263 | playedOctave = calculatePlayedNoteOctave();
264 | playedHertz = calculatePlayedNoteFrequency();
265 | volume = dBToAmp(decibels);
266 |
267 | @sample
268 |
269 | // Only if pitch listening mode is activated
270 | listen ?
271 | (
272 | // Make sure oscillator phase is reset if needs be
273 | checkOscPhaseReset();
274 |
275 | // Make sure the oscillator is set to the correct frequency
276 | checkOscFreqChange();
277 |
278 | // Start with channel 0
279 | channel = 0;
280 |
281 | // Iterate through all available audio channels
282 | while (channel < num_ch)
283 | (
284 | // Add the generated sine wave at the desired volume level to whatever
285 | // is coming into the plugin. To overwrite any incoming audio, remove
286 | // the "+" sign in the line below.
287 | spl(channel) += (tickSine(channel) * volume);
288 |
289 | // Increment loop counter
290 | channel += 1;
291 | );
292 | )
--------------------------------------------------------------------------------
/Utility/volume_range_trim.jsfx:
--------------------------------------------------------------------------------
1 | desc: Volume Range Trim
2 | version: 1.8.3
3 | author: chokehold
4 | tags: utility gain range volume trim
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/volume_range_trim.png
7 | about:
8 | # Volume Range Trim
9 |
10 | Volume adjustment in a selectable +/- Decibel range for controlled automation.
11 |
12 | Who needs another volume plugin?! Well, it depends. For regular track control
13 | or inter-plugin gain staging, probably nobody.
14 |
15 | This plugin is aimed at those unhappy with the way the volume envelopes scale
16 | in Reaper. Insert this plugin where you need it, select the Range you want to
17 | change the volume in, switch the Scaling to make the changes faster or slower,
18 | and then automate the Trim Amount slider instead of the track volume envelope.
19 |
20 | Remember drawing many, many points into an envelope and then messing with the
21 | point values and point shapes endlessly, hoping to eventually get them right?
22 |
23 | Just insert this plugin instead, set a few linear points on the envelope lane
24 | for the Trim Amount slider, and restrict or scale the linear envelope changes
25 | to more gentle or sudden ones with the Range and Scale options.
26 |
27 | // ----------------------------------------------------------------------------
28 | slider1:dBRange=0<0,3,1{6 dB,12 dB, 24 dB, 48 dB}>Range +/-
29 | slider2:scale=0<0,3,1{Linear,Smooth,Slow,Fast}>Scaling
30 | slider3
31 | slider4:amount=0<-1,1,0.000001>Trim amount
32 | slider5
33 | slider6:dBMeter=0<-48,48,0.01>Trim meter [dB]
34 |
35 | // By not having any in_pin and out_pin assignments, this plugin will
36 | // automatically adapt to the number of channels of the track.
37 |
38 | @init
39 |
40 | // Converts dB values to float gain factors
41 | function dBToGain (decibels) (pow(10, decibels * 0.05));
42 |
43 | // VALUE INTERPOLATION
44 | //
45 | // Return interpolated value at position [0,1] between
46 | // two not necessarily related input values.
47 | //
48 | // Implemented after Paul Bourke and Lewis Van Winkle
49 | // http://paulbourke.net/miscellaneous/interpolation/
50 | // https://codeplea.com/simple-interpolation
51 | //
52 | // Basic linear interpolation, constant speed
53 | function linearInterpolation (value1, value2, position)
54 | (
55 | (value1 * (1.0 - position) + value2 * position);
56 | );
57 | //
58 | // Slow start, fast stop
59 | function accelerate (value)
60 | (
61 | linearInterpolation(0.0, 1.0, sqr(value));
62 | );
63 | //
64 | // Fast start, slow stop
65 | function decelerate (value)
66 | (
67 | linearInterpolation(0.0, 1.0, 1.0 - sqr(1.0-value));
68 | );
69 |
70 | // SMOOTHEST STEP
71 | //
72 | // Particularly nice SmoothStep variation.
73 | //
74 | // Implemented after Kyle McDonald
75 | // https://twitter.com/kcimc/status/580738643347804160?lang=en
76 | //
77 | // Slow start, slow stop
78 | function smoothestStep (value) local (v2, v4, v5, v6, v7)
79 | (
80 | v2 = value*value; v4 = v2*v2; v5 = v4*value; v6 = v5*value; v7 = v6*value;
81 | (-20.0*v7 + 70.0*v6 - 84.0*v5 + 35.0*v4);
82 | );
83 |
84 | // Flags to keep track of parameter changes
85 | previousAmount = -100;
86 | previousRange = -1;
87 | previousScale = -1;
88 |
89 | // Stores the selected Range value as positive Decibels
90 | decibels = 0;
91 |
92 | // Amount slider value after scaling was applied
93 | scaledAmount = 0;
94 |
95 | // Stores the Range scaled by the Amount slider as signed Decibels
96 | dBTrim = 0;
97 |
98 | @slider
99 |
100 | // If the Range value was changed
101 | (dBRange != previousRange) ?
102 | (
103 | // Recalculate the scale of dB to scale with the Amount slider
104 | decibels = 6 * pow(2, dBRange);
105 |
106 | // Update the flag to not recalculate this again unless it needs to be
107 | previousRange = dBRange;
108 | );
109 |
110 | // If the Amount slider was changed
111 | (amount != previousAmount) || (scale != previousScale) ?
112 | (
113 | // Scale the Amount slider value
114 | /* Default scale Linear */ scaledAmount = amount;
115 | scale == 1 ? /* Smooth */ scaledAmount = smoothestStep(abs(amount)) * sign(amount);
116 | scale == 2 ? /* Slow */ scaledAmount = accelerate(abs(amount)) * sign(amount);
117 | scale == 3 ? /* Fast */ scaledAmount = decelerate(abs(amount)) * sign(amount);
118 |
119 | // Update the flags to not recalculate this again unless it needs to be
120 | previousScale = scale;
121 | previousAmount = amount;
122 | );
123 |
124 | // Scale the Range by the scaled Amount value and write to buffer variable
125 | dBTrim = scaledAmount * decibels;
126 |
127 | // Turn the scaled volume adjustment from Decibels into a float gain factor
128 | trim = dBToGain(dBTrim);
129 |
130 | // Make sure the Meter slider always reflects the latest Trim dB value
131 | dBMeter = dBTrim;
132 |
133 | @sample
134 |
135 | // Set this to 0 to start with first input channel
136 | channel = 0;
137 |
138 | // Cycle through all of the plugin's input channels
139 | while (channel < num_ch)
140 | (
141 | // Apply trim gain to the current channel's sample
142 | spl(channel) *= trim;
143 |
144 | // Increment counter to next channel
145 | channel += 1;
146 | );
147 |
148 |
--------------------------------------------------------------------------------
/Utility/volume_trim.jsfx:
--------------------------------------------------------------------------------
1 | desc: Volume Trim
2 | version: 1.8.3
3 | author: chokehold
4 | tags: utility gain volume trim
5 | link: https://github.com/chkhld/jsfx/
6 | screenshot: https://github.com/chkhld/jsfx/blob/main/assets/screenshots/volume_trim.png
7 | about:
8 | # Volume Trim
9 |
10 | Simple volume adjustment between -48 and +48 dB for non-destructive edits or
11 | gain staging between plugins.
12 |
13 | // ----------------------------------------------------------------------------
14 | slider1:dBTrim=0<-48,48,0.0001>Trim [dB]
15 |
16 | // By not having any in_pin and out_pin assignments, this plugin will
17 | // automatically adapt to the number of channels of the track.
18 |
19 | @init
20 |
21 | // Converts signed dB values to normalised float gain factors.
22 | // Divisions are slow, so: dB/20 --> dB * 1/20 --> dB * 0.05
23 | function dBToGain (decibels) (pow(10, decibels * 0.05));
24 |
25 | @slider
26 |
27 | // Turns the slider's dB value into a float gain factor
28 | trim = dBToGain(dBTrim);
29 |
30 | @sample
31 |
32 | // Start with first input channel
33 | channel = 0;
34 |
35 | // Cycle through all of the plugin's input channels
36 | while (channel < num_ch)
37 | (
38 | // Apply trim gain to the current channel's sample
39 | spl(channel) *= trim;
40 |
41 | // Increment counter to next channel
42 | channel += 1;
43 | );
44 |
45 |
--------------------------------------------------------------------------------
/assets/reapack-index command.txt:
--------------------------------------------------------------------------------
1 | reapack-index --name "chokehold JSFX" --about=README.md
--------------------------------------------------------------------------------
/assets/screenshots/amp_sim.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/amp_sim.png
--------------------------------------------------------------------------------
/assets/screenshots/bass_squeezer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/bass_squeezer.png
--------------------------------------------------------------------------------
/assets/screenshots/bus_comp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/bus_comp.png
--------------------------------------------------------------------------------
/assets/screenshots/cabinet_sim.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/cabinet_sim.png
--------------------------------------------------------------------------------
/assets/screenshots/chug_thug.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/chug_thug.png
--------------------------------------------------------------------------------
/assets/screenshots/clipping_algorithm_comparison.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/clipping_algorithm_comparison.png
--------------------------------------------------------------------------------
/assets/screenshots/consolidator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/consolidator.png
--------------------------------------------------------------------------------
/assets/screenshots/correlation_meter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/correlation_meter.png
--------------------------------------------------------------------------------
/assets/screenshots/dc_offset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/dc_offset.png
--------------------------------------------------------------------------------
/assets/screenshots/eq_560.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/eq_560.png
--------------------------------------------------------------------------------
/assets/screenshots/filthy_delay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/filthy_delay.png
--------------------------------------------------------------------------------
/assets/screenshots/foldback_distortion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/foldback_distortion.png
--------------------------------------------------------------------------------
/assets/screenshots/gate_expander.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/gate_expander.png
--------------------------------------------------------------------------------
/assets/screenshots/hard_clipper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/hard_clipper.png
--------------------------------------------------------------------------------
/assets/screenshots/impulse_generator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/impulse_generator.png
--------------------------------------------------------------------------------
/assets/screenshots/interpolated_noise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/interpolated_noise.png
--------------------------------------------------------------------------------
/assets/screenshots/knee_clipper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/knee_clipper.png
--------------------------------------------------------------------------------
/assets/screenshots/loud_clipper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/loud_clipper.png
--------------------------------------------------------------------------------
/assets/screenshots/m-s_fader.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/m-s_fader.png
--------------------------------------------------------------------------------
/assets/screenshots/mic_combiner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/mic_combiner.png
--------------------------------------------------------------------------------
/assets/screenshots/midi_chord_trigger.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/midi_chord_trigger.png
--------------------------------------------------------------------------------
/assets/screenshots/phase_scope.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/phase_scope.png
--------------------------------------------------------------------------------
/assets/screenshots/reference_noise.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/reference_noise.png
--------------------------------------------------------------------------------
/assets/screenshots/ring_mod.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/ring_mod.png
--------------------------------------------------------------------------------
/assets/screenshots/signal_crusher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/signal_crusher.png
--------------------------------------------------------------------------------
/assets/screenshots/sine_clipper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/sine_clipper.png
--------------------------------------------------------------------------------
/assets/screenshots/soft_clipper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/soft_clipper.png
--------------------------------------------------------------------------------
/assets/screenshots/staging_clipper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/staging_clipper.png
--------------------------------------------------------------------------------
/assets/screenshots/stereo_bleed_remover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/stereo_bleed_remover.png
--------------------------------------------------------------------------------
/assets/screenshots/stereo_checker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/stereo_checker.png
--------------------------------------------------------------------------------
/assets/screenshots/stereo_pan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/stereo_pan.png
--------------------------------------------------------------------------------
/assets/screenshots/string_tuning_calculator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/string_tuning_calculator.png
--------------------------------------------------------------------------------
/assets/screenshots/telephone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/telephone.png
--------------------------------------------------------------------------------
/assets/screenshots/test_signals.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/test_signals.png
--------------------------------------------------------------------------------
/assets/screenshots/track_comp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/track_comp.png
--------------------------------------------------------------------------------
/assets/screenshots/volume_range_trim.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/volume_range_trim.png
--------------------------------------------------------------------------------
/assets/screenshots/volume_trim.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/volume_trim.png
--------------------------------------------------------------------------------
/assets/screenshots/wave_scope.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/screenshots/wave_scope.png
--------------------------------------------------------------------------------
/assets/thumbnails/amp_sim.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/amp_sim.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/bass_squeezer.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/bass_squeezer.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/bus_comp.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/bus_comp.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/cabinet_sim.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/cabinet_sim.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/chug_thug.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/chug_thug.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/clipping_algorithm_comparison.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/clipping_algorithm_comparison.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/consolidator.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/consolidator.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/correlation_meter.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/correlation_meter.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/dc_offset.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/dc_offset.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/eq_560.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/eq_560.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/filthy_delay.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/filthy_delay.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/foldback_distortion.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/foldback_distortion.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/gate_expander.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/gate_expander.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/hard_clipper.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/hard_clipper.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/impulse_generator.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/impulse_generator.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/interpolated_noise.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/interpolated_noise.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/knee_clipper.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/knee_clipper.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/loud_clipper.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/loud_clipper.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/m-s_fader.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/m-s_fader.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/mic_combiner.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/mic_combiner.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/midi_chord_trigger.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/midi_chord_trigger.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/phase_scope.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/phase_scope.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/reference_noise.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/reference_noise.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/ring_mod.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/ring_mod.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/signal_crusher.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/signal_crusher.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/sine_clipper.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/sine_clipper.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/soft_clipper.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/soft_clipper.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/staging_clipper.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/staging_clipper.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/stereo_bleed_remover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/stereo_bleed_remover.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/stereo_checker.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/stereo_checker.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/stereo_pan.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/stereo_pan.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/string_tuning_calculator.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/string_tuning_calculator.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/telephone.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/telephone.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/test_signals.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/test_signals.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/track_comp.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/track_comp.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/volume_range_trim.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/volume_range_trim.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/volume_trim.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/volume_trim.jpg
--------------------------------------------------------------------------------
/assets/thumbnails/wave_scope.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chkhld/jsfx/9ce8f751d90d9c8ca3ba135bc0bcac088b5bbab6/assets/thumbnails/wave_scope.jpg
--------------------------------------------------------------------------------