( // Synthdef for sinusoidal granular synthesis. // OffsetOut is used for sample accurate grains ~sg = CtkSynthDef(\sg, {arg dur = 1, amp = -3, freq = 440; var env, halfdur, car; halfdur = dur * 0.5; amp = amp.dbamp; env = EnvGen.kr( Env([0, amp, 0], [halfdur, halfdur], \sin)); car = SinOsc.ar(freq, 0, 1); OffsetOut.ar(0, car * env); }); ) ( // function that generates periodic grains, grainperiod defines the rate of the grains // grainfreq and grainamp should be an Env with times normalized to 1.0 (total duration) ~periodic = {arg score, starttime, duration, grainperiod, grainfreq, grainamp; var now = 0.0, n, idur, curamp, curfreq; // the reciprocal of duration idur = duration.reciprocal; // now is our time counter we check first if we've got to the end while({ now < duration; }, { // n is our normalized counter (it is 1.0 at the total duration) n = now * idur; // access values from the envelopes using Env's 'at' method // n is within 0 and 1 and the Env's should be defined that way curamp = grainamp.at(n); // poll the grain amp curfreq = grainfreq.at(n); // poll the grain-freq // add the event to the score score.add( ~sg.new(now + starttime, grainperiod) .freq_(curfreq) .amp_(curamp) .dur_(grainperiod) ); // increment time by grain period now = now + grainperiod; }) }; ) // ServerOptions to be used by CtkScore's write method ~options = ServerOptions.new.numOutputBusChannels_(1); ( var score; score = CtkScore.new; // low-density (10 grains per second) // we pass envelopes for grainfreq and grainamp (this last one uses values in dB) // (note their duration slot always adds up to 1.0 which correspond to the total duration) ~periodic.value(score, 1.0, 15, 1/10, Env([440, 440], [1]), Env([-24.0, -3.0, -24.0], [0.5, 0.5])); // write sound file out score.write("~/Desktop/test.aiff".standardizePath, options: ~options); ) // open file Player.new("~/Desktop/test.aiff".standardizePath).gui; // You can also use the Tendency object for the grainfreq parameter: // Tendency(lowerBound, upperBound) -> retunrs random values between lowerBound and upperBound // which can either be numbers or Env's (with duration normalized to 1.0) ( var score; score = CtkScore.new; // low-density (10 grains per second) ~periodic.value(score, 1.0, 15, 1/10, Tendency(440, 550), Env([-24.0, -3.0, -24.0], [0.5, 0.5])); // write sound file out score.write("~/Desktop/test_1.aiff".standardizePath, options: ~options); ) // open file Player.new("~/Desktop/test_1.aiff".standardizePath).gui; // 100 Hz formant sweep // while grain period is fixed at 1/100 (100 Hz) grainfreq moves around the region // of the first two formants of vowel |A| (609.0 and 1000.0 Hz) ( var score; score = CtkScore.new; ~periodic.value(score, 1.0, 15.0, 1/100, Env([1000.0, 609.0, 1000.0], [0.5, 0.5]),Ê Env([-24.0, -3.0, -24.0], [0.5, 0.5])); score.write("~/Desktop/test_2.aiff".standardizePath, options: ~options); ) Player.new("~/Desktop/test_2.aiff".standardizePath).gui; // Synthesis of vowel |A| using 5 formants (tenor range) // each stream of grains is used to define a formant // all streams share the same grain period ( var score; score = CtkScore.new; ~periodic.value(score, 1.0, 15.0, 1/100, Env([609, 609], [1.0]),Ê Env([-60.0, -30.0, -30.0, -60.0], [0.3, 0.4, 0.3])); ~periodic.value(score, 1.0, 15.0, 1/100, Env([1000, 1000], [1.0]),Ê Env([-60.0, -30.0, -30.0, -60.0], [0.3, 0.4, 0.3])); ~periodic.value(score, 1.0, 15.0, 1/100, Env([2450, 2450], [1.0]),Ê Env([-60.0, -30.0, -30.0, -60.0], [0.3, 0.4, 0.3])); ~periodic.value(score, 1.0, 15.0, 1/100, Env([2700, 2700], [1.0]),Ê Env([-60.0, -30.0, -30.0, -60.0], [0.3, 0.4, 0.3])); ~periodic.value(score, 1.0, 15.0, 1/100, Env([3240, 3240], [1.0]),Ê Env([-60.0, -30.0, -30.0, -60.0], [0.3, 0.4, 0.3])); score.write("~/Desktop/test_3.aiff".standardizePath, options: ~options); ) Player.new("~/Desktop/test_3.aiff".standardizePath).gui; /*Ê Granular Synthsis using built-in grain generators SinGrain and FMGrain. These work on triggers that tell the UGen when to make a new grain A trigger is any signal that crosses from <= 0 to > 0. The Impulse UGen works well for this. If the trigger signal is audio rate, the grains will be sample accurate. */ // SynthDef to generate periodic sinusoidal grains // note the the use of Control.names to pass envelopes to the synth // (in this case they are limited to 4 breakpoints) ( ~singrain = CtkSynthDef(\singrain, {arg dur, grainrate; var grains, env, freq, amp; freq = EnvGen.kr( Control.names([\freq]).kr(Env.newClear(4)), timeScale: dur); amp = EnvGen.kr( Control.names([\amp]).kr(Env.newClear(4)), timeScale: dur); // Impulse.ar triggers the grains at grainrate // grainrate.reciprocal is the grain duration (or period) // freq its internal frequency grains = SinGrain.ar(Impulse.ar(grainrate), grainrate.reciprocal, freq, amp.dbamp); OffsetOut.ar(0, grains); }); ) // start the server s = Server.internal.boot; // Important!: set the server as the defaut server Server.default = s; ( // fixed frequency grains at 10 Hz // note we pass envelopes for freq and amp // their total duration is normalized to 1.0 ~singrain.new(0.0, 15.0) .dur_(15) .grainrate_(10) .freq_(Env([440, 440], [1])) .amp_(Env([-60, -3, -60], [0.5, 0.5])) .play; ) ( // formant example ~singrain.new(0.0, 15.0) .dur_(15) .grainrate_(100) .freq_(Env([1000, 609, 1000], [0.5, 0.5], \exp)) .amp_(Env([-60, -3, -60], [0.5, 0.5])) .play; ) ( // SynthDef to generate periodic fm grains // most parameters controlled through envelopes ~fmgrain =Ê CtkSynthDef(\fmgrain, {arg dur, grainrate; var grains, env, carfreq, modfreq, index, amp; // carrier carfreq = EnvGen.kr( Control.names([\carfreq]).kr(Env.newClear(4)), timeScale: dur); // modulator modfreq = EnvGen.kr( Control.names([\modfreq]).kr(Env.newClear(4)), timeScale: dur); // index index = EnvGen.kr( Control.names([\index]).kr(Env.newClear(4)), timeScale: dur); // amplitude amp = EnvGen.kr( Control.names([\amp]).kr(Env.newClear(4)), timeScale: dur); // the grain parameters are polled when a new grain is created, so you can use // other UGens to control grains grains = FMGrain.ar(Impulse.ar(grainrate), grainrate.reciprocal, carfreq, modfreq, index); OffsetOut.ar(0, grains * amp.dbamp); }); ) ( ~fmgrain.new(0.0, 15.0) .dur_(15) .grainrate_(50) .carfreq_(Env([440, 460, 440], [0.7, 0.3], \sin)) .modfreq_(Env([200, 130, 97], [0.7, 0.3], \exp)) .index_(Env([3, 15, 5], [0.7, 0.3], \sin)) .amp_(Env([-60, -12, -12, -60], [0.2, 0.6, 0.2])) .play; )