This chapter will focus upon granular synthesis used as a DSP technique upon recorded sound files and will introduce techniques including time stretching, time compressing and pitch shifting. The emphasis will be upon asynchronous granulation. For an introduction to synchronous granular synthesis using simple waveforms please refer to chapter 04F.
Csound offers a wide range of opcodes for sound granulation. Each has its own strengths and weaknesses and suitability for a particular task. Some are easier to use than others, some, such as granule and partikkel, are extremely complex and are, at least in terms of the number of input arguments they demand, amongst Csound's most complex opcodes.
sndwarp - Time Stretching and Pitch Shifting
sndwarp may not be Csound's newest or most advanced opcode for sound granulation but it is quite easy to use and is certainly up to the task of time stretching and pitch shifting. sndwarp has two modes by which we can modulate time stretching characteristics, one in which we define a 'stretch factor', a value of 2 defining a stretch to twice the normal length, and the other in which we directly control a pointer into the file. The following example uses sndwarp's first mode to produce a sequence of time stretches and pitch shifts. An overview of each procedure will be printed to the terminal as it occurs. sndwarp does not allow for k-rate modulation of grain size or density so for this level we need to look elsewhere.
You will need to make sure that a sound file is available to sndwarp via a GEN01 function table. You can replace the one used in this example with one of your own by replacing the reference to 'ClassicalGuitar.wav'. This sound file is stereo therefore instrument 1 uses the stereo version of sndwarp. 'sndwarpst'. A mismatch between the number of channels in the sound file and the version of sndwarp used will result in playback at an unexpected pitch. You will also need to give GEN01 an appropriate size that will be able to contain your chosen sound file. You can calculate the table size you will need by multiplying the duration of the sound file (in seconds) by the sample rate - for stereo files this value should be doubled - and then choose the next power of 2 above this value. If you wish to use the sound file used in this example it can be found here.
sndwarp describes grain size as 'window size' and it is defined in samples so therefore a window size of 44100 means that grains will last for 1s each (when sample rate is set at 44100). Window size randomization (irandw) adds a random number within that range to the duration of each grain. As these two parameters are closely related it is sometime useful to set irandw to be a fraction of window size. If irandw is set to zero we will get artefacts associated with synchronous granular synthesis.
sndwarp (along with many of Csound's other granular synthesis opcodes) requires us to supply it with a window function in the form of a function table according to which it will apply an amplitude envelope to each grain. By using different function tables we can alternatively create softer grains with gradual attacks and decays (as in this example), with more of a percussive character (short attack, long decay) or 'gate'-like (short attack, long sustain, short decay).EXAMPLE 05G01.csd
<CsoundSynthesizer> <CsOptions> -odevaudio -b512 -dm0 </CsOptions> <CsInstruments> ;example by Iain McCurdy sr = 44100 ksmps = 16 nchnls = 2 0dbfs = 1 ;waveforms used for granulation giSound ftgen 1,0,2097152,1,"ClassicalGuitar.wav",0,0,0 ;window function - used as an amplitude envelope for each grain ;(first half of a sine wave) giWFn ftgen 2,0,16384,9,0.5,1,0 instr 1 kamp = 0.1 ktimewarp expon p4,p3,p5 kresample line p6,p3,p7 ifn1 = giSound ifn2 = giWFn ibeg = 0 iwsize = 3000 irandw = 3000 ioverlap = 50 itimemode = 0 prints p8 aSigL,aSigR sndwarpst kamp,ktimewarp,kresample,ifn1,ibeg, \ iwsize,irandw,ioverlap,ifn2,itimemode outs aSigL,aSigR endin </CsInstruments> <CsScore> ;p3 = stretch factor begin / pointer location begin ;p4 = stretch factor end / pointer location end ;p5 = resample begin (transposition) ;p6 = resample end (transposition) ;p7 = procedure description ;p8 = description string ; p1 p2 p3 p4 p5 p6 p7 p8 i 1 0 10 1 1 1 1 "No time stretch. No pitch shift." i 1 10.5 10 2 2 1 1 "%nTime stretch x 2." i 1 21 20 1 20 1 1 "%nGradually increasing time stretch factor from x 1 to x 20." i 1 41.5 10 1 1 2 2 "%nPitch shift x 2 (up 1 octave)." i 1 52 10 1 1 0.5 0.5 "%nPitch shift x 0.5 (down 1 octave)." i 1 62.5 10 1 1 4 0.25 "%nPitch shift glides smoothly from 4 (up 2 octaves) to 0.25 (down 2 octaves)." i 1 73 15 4 4 1 1 "%nA chord containing three transpositions: unison, +5th, +10th. (x4 time stretch.)" i 1 73 15 4 4 [3/2] [3/2] "" i 1 73 15 4 4 3 3 "" e </CsScore> </CsoundSynthesizer>
The next example uses sndwarp's other timestretch mode with which we explicitly define a pointer position from where in the source file grains shall begin. This method allows us much greater freedom with how a sound will be time warped; we can even freeze movement an go backwards in time - something that is not possible with timestretching mode.
This example is self generative in that instrument 2, the instrument that actually creates the granular synthesis textures, is repeatedly triggered by instrument 1. Instrument 2 is triggered once every 12.5s and these notes then last for 40s each so will overlap. Instrument 1 is played from the score for 1 hour so this entire process will last that length of time. Many of the parameters of granulation are chosen randomly when a note begins so that each note will have unique characteristics. The timestretch is created by a line function: the start and end points of which are defined randomly when the note begins. Grain/window size and window size randomization are defined randomly when a note begins - notes with smaller window sizes will have a fuzzy airy quality wheres notes with a larger window size will produce a clearer tone. Each note will be randomly transposed (within a range of +/- 2 octaves) but that transposition will be quantized to a rounded number of semitones - this is done as a response to the equally tempered nature of source sound material used.
Each entire note is enveloped by an amplitude envelope and a resonant lowpass filter in each case encasing each note under a smooth arc. Finally a small amount of reverb is added to smooth the overall texture slightly
<CsoundSynthesizer> <CsOptions> -odevaudio -b1024 -dm0 </CsOptions> <CsInstruments> ;example by Iain McCurdy sr = 44100 ksmps = 32 nchnls = 2 0dbfs = 1 ;the name of the sound file used is defined as a string variable - ;- as it will be used twice in the code. ;The simplifies the task of adapting the orchestra - ;to use a different sound file gSfile = "ClassicalGuitar.wav" ;waveform used for granulation giSound ftgen 1,0,2097152,1,gSfile,0,0,0 ;window function - used as an amplitude envelope for each grain ;(first half of a sine wave) giWFn ftgen 2,0,16384,9,0.5,1,0 seed 0; seed the random generators from the system clock gaSendL init 0 gaSendR init 0 instr 1 ; triggers instrument 2 ktrigger metro 0.08 ;metronome of triggers. One every 12.5s schedkwhen ktrigger,0,0,2,0,40 ;trigger instr. 2 for 40s endin instr 2 ; generates granular synthesis textures ;define the input variables ifn1 = giSound ilen = nsamp(ifn1)/sr iPtrStart random 1,ilen-1 iPtrTrav random -1,1 ktimewarp line iPtrStart,p3,iPtrStart+iPtrTrav kamp linseg 0,p3/2,0.2,p3/2,0 iresample random -24,24.99 iresample = semitone(int(iresample)) ifn2 = giWFn ibeg = 0 iwsize random 400,10000 irandw = iwsize/3 ioverlap = 50 itimemode = 1 ;create a stereo granular synthesis texture using sndwarp aSigL,aSigR sndwarpst kamp,ktimewarp,iresample,ifn1,ibeg,\ iwsize,irandw,ioverlap,ifn2,itimemode ;envelope the signal with a lowpass filter kcf expseg 50,p3/2,12000,p3/2,50 aSigL moogvcf2 aSigL, kcf, 0.5 aSigR moogvcf2 aSigR, kcf, 0.5 ; add a little of out audio signals to the global send variables ; these will be sent to the reverb instrument (2) gaSendL = gaSendL+(aSigL*0.4) gaSendR = gaSendR+(aSigR*0.4) outs aSigL,aSigR endin instr 3 ; global reverb instrument (always on) ; use Sean Costello's high quality reverbsc opcode for creating reverb signal aRvbL,aRvbR reverbsc gaSendL,gaSendR,0.85,8000 outs aRvbL,aRvbR ;clear variables to prevent out of control accumulation clear gaSendL,gaSendR endin </CsInstruments> <CsScore> ; p1 p2 p3 i 1 0 3600 ; triggers instr 2 i 3 0 3600 ; reverb instrument e </CsScore> </CsoundSynthesizer>
granule - Clouds of Sound
The granule opcode is one of Csound's most complex opcodes requiring up to 22 input arguments in order to function. Only a few of these arguments are available during performance (k-rate) so it is less well suited for real-time modulation, for real-time a more nimble implementation such as syncgrain, fog, or grain3 would be recommended. Instead granule proves itself ideally suited at the production of massive clouds of granulated sound in which individual grains are often completed indistinguishable. There are still two important k-rate variables that have a powerful effect on the texture created when they are modulated during a note, they are: grain gap - effectively density - and grain size which will affect the clarity of the texture - textures with smaller grains will sound fuzzier and airier, textures with larger grains will sound clearer. In the following example transeg envelopes move the grain gap and grain size parameters through a variety of different states across the duration of each note.
With granule we define a number a grain streams for the opcode using its 'ivoice' input argument. This will also have an effect on the density of the texture produced. Like sndwarp's first timestretching mode, granule also has a stretch ratio parameter. Confusingly it works the other way around though, a value of 0.5 will slow movement through the file by 1/2, 2 will double is and so on. Increasing grain gap will also slow progress through the sound file. granule also provides up to four pitch shift voices so that we can create chord-like structures without having to use more than one iteration of the opcode. We define the number of pitch shifting voices we would like to use using the 'ipshift' parameter. If this is given a value of zero, all pitch shifting intervals will be ignored and grain-by-grain transpositions will be chosen randomly within the range +/-1 octave. granule contains built-in randomizing for several of it parameters in order to easier facilitate asynchronous granular synthesis. In the case of grain gap and grain size randomization these are defined as percentages by which to randomize the fixed values.
Unlike Csound's other granular synthesis opcodes, granule does not use a function table to define the amplitude envelope for each grain, instead attack and decay times are defined as percentages of the total grain duration using input arguments. The sum of these two values should total less than 100.
Five notes are played by this example. While each note explores grain gap and grain size in the same way each time, different permutations for the four pitch transpositions are explored in each note. Information about what these transpositions are, are printed to the terminal as each note begins.
<CsoundSynthesizer> <CsOptions> -odevaudio -b1024 -dm0 </CsOptions> <CsInstruments> ;example by Iain McCurdy sr = 44100 ksmps = 32 nchnls = 2 0dbfs = 1 ;waveforms used for granulation giSoundL ftgen 1,0,1048576,1,"ClassicalGuitar.wav",0,0,1 giSoundR ftgen 2,0,1048576,1,"ClassicalGuitar.wav",0,0,2 seed 0; seed the random generators from the system clock gaSendL init 0 gaSendR init 0 instr 1 ; generates granular synthesis textures prints p9 ;define the input variables kamp linseg 0,1,0.1,p3-1.2,0.1,0.2,0 ivoice = 64 iratio = 0.5 imode = 1 ithd = 0 ipshift = p8 igskip = 0.1 igskip_os = 0.5 ilength = nsamp(giSoundL)/sr kgap transeg 0,20,14,4, 5,8,8, 8,-10,0, 15,0,0.1 igap_os = 50 kgsize transeg 0.04,20,0,0.04, 5,-4,0.01, 8,0,0.01, 15,5,0.4 igsize_os = 50 iatt = 30 idec = 30 iseedL = 0 iseedR = 0.21768 ipitch1 = p4 ipitch2 = p5 ipitch3 = p6 ipitch4 = p7 ;create the granular synthesis textures; one for each channel aSigL granule kamp,ivoice,iratio,imode,ithd,giSoundL,ipshift,igskip,\ igskip_os,ilength,kgap,igap_os,kgsize,igsize_os,iatt,idec,iseedL,\ ipitch1,ipitch2,ipitch3,ipitch4 aSigR granule kamp,ivoice,iratio,imode,ithd,giSoundR,ipshift,igskip,\ igskip_os,ilength,kgap,igap_os,kgsize,igsize_os,iatt,idec,iseedR,\ ipitch1,ipitch2,ipitch3,ipitch4 ;send a little to the reverb effect gaSendL = gaSendL+(aSigL*0.3) gaSendR = gaSendR+(aSigR*0.3) outs aSigL,aSigR endin instr 2 ; global reverb instrument (always on) ; use reverbsc opcode for creating reverb signal aRvbL,aRvbR reverbsc gaSendL,gaSendR,0.85,8000 outs aRvbL,aRvbR ;clear variables to prevent out of control accumulation clear gaSendL,gaSendR endin </CsInstruments> <CsScore> ;p4 = pitch 1 ;p5 = pitch 2 ;p6 = pitch 3 ;p7 = pitch 4 ;p8 = number of pitch shift voices (0=random pitch) ; p1 p2 p3 p4 p5 p6 p7 p8 p9 i 1 0 48 1 1 1 1 4 "pitches: all unison" i 1 + . 1 0.5 0.25 2 4 "%npitches: 1(unison) 0.5(down 1 octave) 0.25(down 2 octaves) 2(up 1 octave)" i 1 + . 1 2 4 8 4 "%npitches: 1 2 4 8" i 1 + . 1 [3/4] [5/6] [4/3] 4 "%npitches: 1 3/4 5/6 4/3" i 1 + . 1 1 1 1 0 "%npitches: all random" i 2 0 [48*5+2]; reverb instrument e </CsScore> </CsoundSynthesizer>
Two contrasting opcodes for granular synthesis have been considered in this chapter but this is in no way meant to suggest that these are the best, in fact it is strongly recommended to explore all of Csound's other opcodes as they each have their own unique character. The syncgrain family of opcodes (including also syncloop and diskgrain) are deceptively simple as their k-rate controls encourages further abstractions of grain manipulation, fog is designed for FOF synthesis type synchronous granulation but with sound files and partikkel offers a comprehensive control of grain characteristics on a grain-by-grain basis inspired by Curtis Roads' encyclopedic book on granular synthesis 'Microsound'.