├── 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 | | [![clipping_algorithm_comparison](./assets/thumbnails/clipping_algorithm_comparison.jpg)](./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 | | [![hard_clipper](./assets/thumbnails/hard_clipper.jpg)](./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 | | [![knee_clipper](./assets/thumbnails/knee_clipper.jpg)](./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 | | [![loud_clipper](./assets/thumbnails/loud_clipper.jpg)](./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 | | [![sine_clipper](./assets/thumbnails/sine_clipper.jpg)](./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 | | [![soft_clipper](./assets/thumbnails/soft_clipper.jpg)](./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 | | [![staging_clipper](./assets/thumbnails/staging_clipper.jpg)](./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 | | [![foldback_distortion](./assets/thumbnails/foldback_distortion.jpg)](./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 | | [![bus_comp](./assets/thumbnails/bus_comp.jpg)](./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 | | [![consolidator](./assets/thumbnails/consolidator.jpg)](./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 | | [![gate_expander](./assets/thumbnails/gate_expander.jpg)](./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 | | [![track_comp](./assets/thumbnails/track_comp.jpg)](./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 | | [![eq_560](./assets/thumbnails/eq_560.jpg)](./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 | | [![filthy_delay](./assets/thumbnails/filthy_delay.jpg)](./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 | | [![ring_mod](./assets/thumbnails/ring_mod.jpg)](./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 | | [![test_signals](./assets/thumbnails/test_signals.jpg)](./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 | | [![amp_sim](./assets/thumbnails/amp_sim.jpg)](./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 | | [![bass_squeezer](./assets/thumbnails/bass_squeezer.jpg)](./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 | | [![cabinet_sim](./assets/thumbnails/cabinet_sim.jpg)](./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 | | [![chug_thug](./assets/thumbnails/chug_thug.jpg)](./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 | | [![mic_combiner](./assets/thumbnails/mic_combiner.jpg)](./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 | | [![signal_crusher](./assets/thumbnails/signal_crusher.jpg)](./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 | | [![telephone](./assets/thumbnails/telephone.jpg)](./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 | | [![correlation_meter](./assets/thumbnails/correlation_meter.jpg)](./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 | | [![phase_scope](./assets/thumbnails/phase_scope.jpg)](./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 | | [![stereo_checker](./assets/thumbnails/stereo_checker.jpg)](./assets/screenshots/stereo_checker.png) | [Stereo Checker](./Metering/stereo_checker.jsfx) | Displays the inter-channel relation of a two-channel input signal. | 110 | | [![wave_scope](./assets/thumbnails/wave_scope.jpg)](./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 | | [![midi_chord_trigger](./assets/thumbnails/midi_chord_trigger.jpg)](./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 | | [![interpolated_noise](./assets/thumbnails/interpolated_noise.jpg)](./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 | | [![reference_noise](./assets/thumbnails/reference_noise.jpg)](./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 | | [![m-s_fader](./assets/thumbnails/m-s_fader.jpg)](./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 | | [![stereo_bleed_remover](./assets/thumbnails/stereo_bleed_remover.jpg)](./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 | | [![stereo_pan](./assets/thumbnails/stereo_pan.jpg)](./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 | | [![dc_offset](./assets/thumbnails/dc_offset.jpg)](./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 | | [![impulse_generator](./assets/thumbnails/impulse_generator.jpg)](./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 | | [![string_tuning_calculator](./assets/thumbnails/string_tuning_calculator.jpg)](./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 | | [![volume_range_trim](./assets/thumbnails/volume_range_trim.jpg)](./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 | | [![volume_trim](./assets/thumbnails/volume_trim.jpg)](./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 --------------------------------------------------------------------------------