Sclang-only distortion

One of my favorite bands of all time is the industrial band Skinny Puppy. Things like distortion are a big component of their sound, and industrial music as a whole. So I’ve been thinking about trying to make darker, grittier sounds with SuperCollider. I use Supriya, the Python API for SuperCollider, not sclang, though,. I know about the distortion extensions that exist for SC, but the extensions haven’t been included in Supriya. I’m also not sure what it would take to get an extension working in Supriya. So I was wondering if there are any sclang-only implementations of distortion? I haven’t found any when searching, although I’m not sure why. If I had an sclang implementation, I could probably get something similar working in Supriya, so that’s why I’m asking.

Thanks!

The most basic distortion technique is waveshaping – e.g., classic saturation using an S-shaped transfer function such as hyperbolic tangent, for instance, or the clever function that JMc implemented as .distort = signal / (abs(signal) + 1).

// hyperbolic tangent
sig = (sig * preamp).tanh;

// or, jmc's formula
sig = (sig * preamp).distort;

For both of these, the maximum approaches 1.0 as the input approaches infinity, so they will saturate without overloading.

For more exotic distortion, you can load a transfer function into a wavetable buffer and use Shaper.ar.

The MKPlugins pack has AnalogVintageDistortion | SuperCollider 3.14.0-dev Help

Or – I’m away from my computer and I forget which extension pack it’s in – but somebody did a virtual-analog wave folding plugin for some West Coast style squelchy squealiness. It might be here – GitHub - madskjeldgaard/portedplugins: A collection of plugins for the SuperCollider sound environment, all of which are ported / remixed from elsewhere – but I’m not certain. It might be somewhere else. They’re in Sam Pluta’s Oversampling Oscillators pack: BuchlaFoldOS and SergeFoldOS.

hjh

1 Like

These both work great. Thanks!

Some variations and ideas:

// Multi-stage distortion
{ |freq=440, drive1=2, drive2=3, fold=0.6|
  var sig = Saw.ar(freq);
  var bits = 6;
  
  sig = (sig * drive1).tanh;
  sig = Fold.ar(sig * drive2, fold.neg, fold);
  sig = (sig * (2.pow(bits-1))).round / (2.pow(bits-1));
  sig = sig.clip(-0.8, 0.9);
  
  LeakDC.ar(sig) * 0.3
}.play;

// Feedback distortion
{
  var input = Saw.ar([220, 221]);
  var fb = LocalIn.ar(2) * 0.7;
  var sig = input + fb;
  var filt = BPF.ar(sig.distort, LFNoise1.kr(0.1).exprange(200, 4000), 0.1);
  LocalOut.ar(filt);
  LeakDC.ar(sig) * 0.2
}.play;

// Modulated distortion
{
  var sig = SinOsc.ar(440);
  var modDrive = LFNoise1.kr(2).range(1, 8);
  var modFold = SinOsc.kr(0.3).range(0.3, 0.9);
  
  var distorted = Fold.ar(sig * modDrive, modFold.neg, modFold);
  distorted * 0.3
}.play;

// Fractional bit crushing
{ |input, bits=4.5|
  var levels = 2.pow(bits);
  (input * levels).round / levels
}

// Zero-crossing detection (multiplication method)
// != does not work with scsynth
{
  var sig = SinOsc.ar(440);
  var delayed = DelayN.ar(sig, 0.001, 1/SampleRate.ir);
  var zeroCross = (sig * delayed) < 0;
  var glitch = WhiteNoise.ar(0.3) * zeroCross;
  sig + (glitch * 0.5)
}.play;

// zero-crossing distortion adds digital artifacts reminiscent of circuit-bent gear or failing digital equipment.
//The key is controlling when and how these glitches occur. You might trigger different distortion types at zero crossings, 
// modulate the glitch intensity with envelopes, or use zero-crossing density as a control signal for other effects.

// You can trigger different distortion types at zero-crossing points - white noise bursts, high-frequency squeals, or bit-crushed segments.
// The timing and intensity of these artifacts shape the overall character, which can range from subtle degradation to aggressive digital chaos.
// 
// Since different waveforms cross zero at different rates (saw waves more frequently than sine waves), you can use crossing density as a modulation source. 
// 
// The result is a self-modulating system producing evolving textures

{
  var sig = SinOsc.ar(440);
  var delayed = DelayN.ar(sig, 0.001, 1/SampleRate.ir);
  var hysteresis = 0.001;
  
  var zeroCross = ((sig > hysteresis) * (delayed < hysteresis.neg)) + 
                  ((sig < hysteresis.neg) * (delayed > hysteresis));
  
  var glitch = WhiteNoise.ar(0.3) * zeroCross;
  sig + (glitch * 0.5)
}.play;

// "Industrial" zero-crossing distortion
{ |freq=440, glitchAmt=0.5, glitchDensity=1.0|
  var sig = Saw.ar(freq);
  var delayed = DelayN.ar(sig, 0.001, 1/SampleRate.ir);
  var zeroCross = (sig * delayed) < 0;
  
  var glitch = Select.ar(
    LFNoise0.kr(10).range(0, 2.999).floor,
    [WhiteNoise.ar(0.5), Impulse.ar(0, 0, 2), SinOsc.ar(8000, 0, 0.3)]
  ) * zeroCross * glitchDensity;
  
  var processed = sig + (glitch * glitchAmt);
  
  BPF.ar(processed, LFNoise1.kr(0.3).exprange(200, 2000), 0.3) * 0.3
}.play;
3 Likes