I know supercollider tutorials are pretty niche, but how are these not viewed more? These tutorials singlehandedly made me decide to pick up this language
***** Thanks. That's not a mistake, however. [0,1,2,3,4].do and 5.do perform the same action. The difference is that do returns its receiver. In the first case, the array is returned after the five integers are posted. In the second case, the integer 5 is returned instead. This behavior is really just a side effect of how do works, so I understand why it seems like a mistake at first glance.
Hey, quick question. In the construction: [ array ].do{ arg item, count; ... } how does the interpreter know what to do with "item" and "count"? Or rather, how does it know what information it should assign to each argument? Thanks in advance, I really appreciate these tutorials.
Chris Casey Simply, it's designed that way. The first argument in a 'do' function always represents the ordered content in the collection, and the second is always an integer index, starting at zero.
The curly braces delineate a function, and in combination with duplication (the ! symbol) it's a technique for creating an array of random numbers. When a function is duplicated, it is evaluated once for each item to be created in the resulting array. When the contents of the function are random, we get multiple uniquely-generated random numbers: {rrand(0,9)}!6; If the curly braces are removed, we create one random number, and then duplicate it: rrand(0,9)!6; The implications for synthesis should be obvious here - if we want a cluster of different random tones, the curly braces are necessary.
Hi Eli, I'm curious to ask why you use Rand within {curly brackets}, is it really important, or just a 'sugar' syntax, as you sometimes say. I tried with/without and couldn't hear any difference, but did wonder if I was missing something. I also looked up Rand in the help-files and see that its used - in the example - in the same way as yours, but without curly brackets. Thanks in advance.
At 13:15, couldn't you iterate over something like 0.dup(num) as a solution to a variable number of synths passed as an argument? It's not nearly as efficient (i presume; i'm not sure how supercollider implements this).
+Enrico Borba No, that would run into the same problem. It is not possible to modulate the size of UGen Arrays while the Synth which contains these Arrays is running on the server. UGen Array size is fixed and determined at SynthDef load time. Solutions include either creating different SynthDefs with different iteration counts (one with 10.do, one with 20.do, etc), or eliminate the iteration within the SynthDef altogether, and instead use iteration to create multiple Synths from a non-iterative SynthDef.
I am extremely new to this so I might be babbling but wouldn't it be possible to have a argument that limits the volume of the iteration if bigger than? as in: if ( count
It's a good thought, certainly, but because SC is an audio programming language, familiar problems can't always be solved by approaches that might be applied in more traditional programming languages. By design, the server (scsynth) can't dynamically change the size of arrays in UGen functions, once these functions are built and sent from the language (sclang). One apparent consequence of this design is that numChannels arguments within buffer-playing UGens can't be modulated. So, the operation 0.dup(num) gets evaluated once, when the UGen function is built, but can't be updated with set commands while playing on scsynth. Operations like if, do, collect, etc. are similar. They're language-side methods that the server doesn't understand. There are relatively simple workarounds to this seemingly annoying design choice, e.g. create a SynthDef that plays one partial and dynamically create and remove multiple Synth instances, or create a SynthDef that plays a fixed number of partials with a literal Array argument for setting individual partial amplitudes, etc. I see users encounter problems related to this feature from time to time, and usually direct them here to start: supercollider.github.io/tutorials/If-statements-in-a-SynthDef.html From the SynthDef help file: "It is important to understand that although a single def can provide a great deal of flexibility through its arguments, etc., it is nevertheless a static entity. A def's UGen graph function (and the SC code within it) is evaluated only when the def is created. Thus statements like while, do, collect etc. will have no further effect at the time the def is used to create a Synth, and it is important to understand that a UGen graph function should not be designed in the same way as functions in the language, where multiple evaluations can yield different results. It will be evaluated once and only once."
It depends on the units of the mapped parameter and/or how I want to distribute values within the range. For example, our perception of pitch is logarithmic (octaves "sound" equally spaced and appear equally spaced on the keyboard, but have exponentially increasing frequency values). So, if we are using a UGen to generate MIDI note numbers, range typically makes sense for an equal (linear) distribution and a more "natural" distribution. But, for generating frequency values, exprange makes more sense. Consider what would happen if we used range for an LFNoise1 generating frequency values between 20 and 20,000. Approximately half the values would fall in the upper half of the range, which is only one octave (10-20k). But there are nine more octaves below! So we'd hear disproportionately more high frequencies and fewer low/mid frequencies. A similar phenomenon happens with decibels, for which range is usually appropriate, vs. normalized amplitude values, for which exprange is usually more appropriate. Try the difference for yourself and listen closely!
hi, thanks for the tutorials, i have a question related to this topic, i have a code that works, but i have to write an argument that i actually dont use, but if i delete it, the code stops working i dont know why: can you explain why i have to write arg item in this case? ( SynthDef.new(\pulso, { arg freq = 440; var sig, env; env = EnvGen.kr(Env.perc(0.05,4)); sig = SinOsc.ar(freq,mul:env); sig = Splay.ar(sig); Out.ar(0,sig)*0.25; }).add; ) ( ~miSerie = Array.series(12,60); ~miSerie = ~miSerie.scramble; ~miDuraciones = Array.newClear(12); ~miDuraciones.collect{ arg item, count;//here ~miDuraciones[count] = (~miserie[count]-59)*0.5; }; ~miSerie.collect{ arg item, count;//here ~miSerie[count] = ~miSerie[count].midicps; }; ~miSerie.postln; ~miDuraciones.postln; ~serieDod = Pbind(\instrument, \pulso, \dur,Pseq(~miDuraciones,1), \freq, Pseq(~miSerie,1)).play; )
Iteration methods (do, collect, select, reject, etc) operate on a receiver, usually a collection (e.g. Array) and pass up to two arguments into their function. These arguments represent the current object from the collection, and the current integer index of that object, and are interpreted *in that order*. We gain access to these two values by declaring two arguments (e.g. "arg item, count") at the start of the function. We can name the arguments whatever we like (e.g. arg a, b; arg foo, bar), but the important point here is that the arguments are interpreted to have these meanings in this particular order. So, if we only declare one argument, it is interpreted as the object within the collection, and we have no access to the index within our function. If we want access to the index, we must declare two arguments, even if we don't plan on using the first one. A bigger problem is that, based on your approach, you are using "collect" in a case where it would be more appropriate to use "do". Remember that "do" returns its receiver, but "collect" returns a modified receiver, based on what happens inside the function. So, it is usually inappropriate/wrong to use the equals sign to update values within a collect function. When using collect, usually you will store the result of the collect operation in a new variable. With the approach you've chosen, you can simply replace both instances of "collect" with "do", and everything seems to work just fine. If you replace collect with do, then the receivers of do are simply counters that make sure the correct number of iterations occur. The actual value modification process occurs within the function, so technically, you could replace do's receivers with the number 12 -- which I think looks a little clearer. I still think collect is a more sensible choice, because it allows your array-creating code to be written more succinctly. Here's how I would do it: ~miSerie = Array.series(12,60).scramble; ~miDuraciones = ~miSerie.collect({arg item, count; item-59*0.5}); For extra brevity, you can also use the underscore character for the so-called "partial application syntax": ~miSerie = Array.series(12,60).scramble; ~miDuraciones = ~miSerie.collect(_-59*0.5); You also have one instance of ~miserie where you meant to type ~miSerie.
Hey Eli, thank you so much for these videos! Quick question: If I want to sonify an array of data by iterating over my array and using the array values as frequency arguments for a SinOsc, can I do that with .do? I've done it using Pbind without much trouble, but when I try it with .do, I'm not having any luck.
Great videos. If you want to change number of partials via parameter, such as for additive synthesis, i figured it would be easier to just set a max nimber of partials, like 64, and then have an argument which simply controls which partials are silent via multiplication by 0. Avoids having to update synthdefs or create array of synths. This way you can have a knob or something that dynamically changes partials over time which could be cool.
Clap, clap, clap... ok with this video you covered a lot of things I did not knew... I´ll probably have to really study what you showed here. Thanks a lot for the great service you´re providing us here! =D
It's a common way of applying a sequence of operations to a value, while overwriting the named container each time an operation is performed. In this case, we're calculating the sum of 'sig' and 'temp,' and storing the result in the 'sig' container, overwriting the old value. A simpler example of the same syntax looks like this: ( x = 4; x = x.pow(3); x = x - 7; )
This message occurs when you include tons and tons of UGens in your SynthDef. I think it means that your SynthDef is too big to stream to the server via add/send, so it creates a SynthDef file on your computer instead. You can try making your SynthDefs smaller or more efficient, or just ignore the warning. I get this message from time to time, and it doesn't interfere with my ability to create Synths from the SynthDef.
Hello :) I build a synthDef in which I have many iteration blocks, similar to your examples. I then add all to one output variable. I wonder if I can attach to each iteration cycle, an envelope. Meaning, if I have 3 iteration cycles, with different UGens, I would like to have different attack,sustain,release for each one. It seems that when I add them all to one output(the one in your example called "sum") these envelope instances cannot occur in the output. I understand there is the option to have just one envelope, of the final signal. But I also search ways to maintain an envelope of each iteration block alive, even after the addition of them to one audible event. So as to have different fade In and fade Out times for each UGen, although I treat them as one final signal. This is what my examples looks like, but the separate envelopes don't work: ( SynthDef.new(\iterDef, { var env1,env2,env3,inst1,inst2,inst3,cycl1,cycl2,cycl3,output,freq = \freq.ir(130); env1 = Env.linen(\att1.ir(0.5), \sus1.ir(1), ls1.ir(4)).ar(doneAction:0); env2 = Env.linen(\att2.ir(5), \sus2.ir(1), ls2.ir(1)).ar(doneAction:0); env3 = Env.linen(\att3.ir(2), \sus3.ir(2), ls3.ir(3)).ar(doneAction:0); cycl1 = 0; cycl2 = 0; cycl3 = 0; 4.do { arg counter; var ampCtrl, freqCtrl; ampCtrl = LFNoise1.kr(0.5, \ampDev1.ir(0.05), \amp1.ir(0.2)); freqCtrl = LFNoise1.kr(2, \dev1.ir(0), atio1.ir(1)); inst1 = LFTri.ar(freq * (counter + 1) * freqCtrl); inst1 = inst1 * ampCtrl * env1; cycl1 = cycl1 + inst1; }; 2.do { arg counter; var ampCtrl, freqCtrl; ampCtrl = LFNoise1.kr(0.5, \ampDev2.ir(0.0), \amp2.ir(0.3)); freqCtrl = LFNoise1.kr(2, \dev2.ir(0), atio2.ir(1)); inst2 = LFSaw.ar(freq * (counter + 1) * freqCtrl); inst2 = inst2 * ampCtrl * env2; cycl2 = cycl2 + inst2 }; 3.do { arg counter; var ampCtrl, freqCtrl; ampCtrl = LFNoise1.kr(0.5, \ampDev3.ir(0.0), \amp3.ir(0.1)); freqCtrl = LFNoise1.kr(2, \dev3.ir(0), atio3.ir(1)); inst3 = LFSaw.ar(freq * (counter + 1) * freqCtrl); inst3 = inst3 * ampCtrl * env3; cycl3 = cycl3 + inst3; }; output = cycl1 + cycl2 + cycl3; output = LPF.ar(output, 1100); output = output * \amp.ir(0.01); output = output!2; output = Out.ar(\out.ir(0), output); }).add;
As far as I can tell, this SynthDef is working properly. If you make your three envelopes very different from each other (one very long, two very short), you should be able to hear the result. It may also become more audible if you remove the lowpass filter. You should also put a doneAction:2 on the longest envelope.