Jump to content
IGNORED

[CylobBot] Dynamic Waveshaping in SuperCollider


Joyrex

Recommended Posts

Here is a short tutorial covering a technique for dynamic waveshaping in SuperCollider.Dynamic WaveshapingWaveshaping is a way of distorting a signal in a non-linear way. It involves the use of a transfer function in order to calculate the result. A transfer function is just a way of mapping a certain input value to a certain output value.In this transfer function, any input value will be mapped to the same output value – this is a linear function.waave2-300x276.pngIn this next example, the transfer function is a sine wave, so the output value at any point is going to be different from the input value.waave1-300x272.pngWaveshaping involves using a signal as an input. In the diagram below, a sine wave passes through a transfer function that is linear, which means there will be no difference in sound at all.Pasted%20Graphic.tiffIn the following diagram, the transfer function is the result of a few harmonics added together, making a more complex shape. Now it can be seen that the sine wave has been distorted by this function.Pasted%20Graphic%206.tiffWaveshaping in SuperColliderWaveshaping in SC traditionally involves the use of wavetables – a special way of formatting an array of numbers for use in oscillators which work in that way – i.e. reading through a table of values. Here is a small example:s = Server.internal.boot;

(// in order to be turned into a wavetable,// the signal must be half buffer size plus onex = Signal.sineFill(513, [0.5, 0.2, 0.3, 0.0, 0.2]);x.plot;b = Buffer.alloc(s, 1024, 1);)b.sendCollection(x.asWavetableNoWrap);({var input, output;input = SinOsc.ar(MouseX.kr(80, 800, exponential), 0, 0.7);// Shaper is the waveshaping Ugenoutput = Shaper.ar(b.bufnum, input);output ! 2}.scope;)However, there is another way, which allows an arbitrary buffer size to be used, as well as a little dynamism to be added to the result. BufRd is a UGen which indexes into a buffer. If the input signal (-1.0 to 1.0) is mapped to the index of a buffer containing a transfer function, the result will be waveshaping.There are a few ways to involve some modulation:• change the range that the input signal is mapped to• modulate the offset of the input signal• as smoothly as possible, change the contents of the buffer containing the function.fill the buffer with the transfer function – no need for special encoding or special buffer sizes(s = Server.internal;a = Signal.sineFill(1000, [1, 0.2, 0.7]);// or..//a = Signal.sineFill(1000, [0, 0.2, 0.8, 0.1, 0.5]);// the straight version…// a = Array.interpolation(1000, 0.0, 1.0);// check out the transfer functiona.plot;)// then…(b = Buffer.sendCollection(s, a, 1);)use audio input as an index into the buffer, using LinLin to map one to the other.perhaps modulate the range of the index, for a more dynamic sound.({var sinFreq, soundIn, playHead, output;var thisIndex;sinFreq = MouseX.kr(20, 1000, exponential).poll;soundIn = SinOsc.ar(sinFreq, 0, 0.8);thisIndex = LinLin.ar(soundIn, -1.0, 1.0, 0.0, BufFrames.kr(b.bufnum));// some gentle dynamic waveshaping -// modulate the range of the indexing// thisIndex = LinLin.ar(soundIn, -1.0, 1.0, 0.0, BufFrames.kr(b.bufnum) * SinOsc.kr(0.6).range(0.15, 1.0));// some over-aggressive modulation!// thisIndex = LinLin.ar(soundIn, -1.0, 1.0, 0.0, BufFrames.kr(b.bufnum) * SinOsc.ar(sinFreq * 0.25).range(0.15, 1.0));playHead = BufRd.ar(1, b.bufnum, thisIndex, 0, 4);// remove any DC weirdnessoutput = LeakDC.ar(playHead);output ! 2;}.scope;)…or offset the input before “waveshaping.” then use LeakDC to correct the output.({var sinFreq, soundIn, playHead, output;var thisIndex;var sinMult, sinOffsetRange;sinFreq = MouseX.kr(20, 2000, exponential);// now move the base position of the sine around, so that different areas of the transfer function get used// resulting in a nice shifting around of the phasesinMult = 0.32; sinOffsetRange = 0.64;soundIn = SinOsc.ar(sinFreq, 0, sinMult, SinOsc.kr(0.17).range(0.0 – sinOffsetRange, sinOffsetRange));thisIndex = LinLin.ar(soundIn, -1.0, 1.0, 0.0, BufFrames.kr(b.bufnum));playHead = BufRd.ar(1, b.bufnum, thisIndex, 0, 4);// remove any DC weirdnessoutput = LeakDC.ar(playHead);output ! 2;}.scope;)change the contents of the transfer function buffer using “harmonics” fading in and out({var sinFreq, soundIn, playHead, output;var thisIndex;var bufInput, bufInputFreq;sinFreq = MouseX.kr(20, 1000, exponential).poll;soundIn = SinOsc.ar(sinFreq, 0, 0.8);thisIndex = LinLin.ar(soundIn, -1.0, 1.0, 0.0, BufFrames.kr(b.bufnum));// fill one cycle of the buffer without glitches -// the frequency should be a period that fits into the size of the buffer,// according to the sample ratebufInputFreq = SampleRate.ir / BufFrames.kr(b.bufnum);// mix the first 6 tones of the harmonic series – let them fade in and out randomlybufInput = Mix(SinOsc.ar(bufInputFreq * (1 .. 6), Array.linrand(6, 0.0, 6.28), SinOsc.kr(Array.exprand(6, 0.1, 0.3), 0, 0.2)));// write this into the buffer that is being used as the transfer functionBufWr.ar(bufInput, b.bufnum, Phasor.ar(0, BufRateScale.kr(b.bufnum), 0, BufFrames.kr(b.bufnum)));playHead = BufRd.ar(1, b.bufnum, thisIndex, 0, 4);// remove any DC weirdnessoutput = LeakDC.ar(playHead);// uncomment to check out the transfer function// output = BufRd.ar(1, b.bufnum, Phasor.ar(0, BufRateScale.kr(b.bufnum), 0, BufFrames.kr(b.bufnum)), 0, 4);output ! 2;}.scope;)use an envelope with shifting points in order to dynamically change the transfer function({var sinFreq, soundIn, playHead, output;var thisIndex;var bufInput, bufInputFreq;var modPoints, env, envgen;sinFreq = MouseX.kr(20, 1000, exponential).poll;soundIn = SinOsc.ar(sinFreq, 0, 0.8);thisIndex = LinLin.ar(soundIn, -1.0, 1.0, 0.0, BufFrames.kr(b.bufnum));bufInputFreq = SampleRate.ir / BufFrames.kr(b.bufnum);// use these points for the env levels – one extra last one (release node) so that the env loops properlymodPoints = [-1.0, -0.2, -0.5, 0.9, 1.0, 1.0];// uncomment to see the points// modPoints.plot;// uncomment to replace the points with modulating values// 5.do { |i|// modPoints = SinOsc.kr(ExpRand(0.1, 0.8), Rand(0.0, 6.28)).range(-0.9, 0.9);// };// make an envelope which will result in a warped transfer functionenv = Env(modPoints, [0.25, 0.25, 0.25, 0.25, 0.0], [2, -4, 7, -5, 0], 4, 0);envgen = EnvGen.ar(env, timeScale: bufInputFreq.reciprocal);bufInput = envgen;// write this into the buffer that is being used as the transfer functionBufWr.ar(bufInput, b.bufnum, Phasor.ar(0, BufRateScale.kr(b.bufnum), 0, BufFrames.kr(b.bufnum)));playHead = BufRd.ar(1, b.bufnum, thisIndex, 0, 4);// remove any DC weirdnessoutput = LeakDC.ar(playHead);// uncomment to check out the transfer function// output = BufRd.ar(1, b.bufnum, Phasor.ar(0, BufRateScale.kr(b.bufnum), 0, BufFrames.kr(b.bufnum)), 0, 4);output ! 2;}.scope;

)Conclusion“Waveshaping” seems like an exotic concept, particularly if you use SC’s suggested method. However, it is nothing more than mapping one value to another using a “transfer function”. The input can be a signal, and the transfer function (basically an array of values) can be a buffer. The buffer can be changed as it’s being read, perhaps by recording the output of another UGen. This technique can create waveforms which shift around in a rich and interesting way.

 

View the full article

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.