Getting Started with ChucK
Hello, I've been interested in trying ChucK for several years now but I've just recently started playing with it in earnest. I like what I see so far :) I have one thing that's bothering me - coming from SuperCollider, I'm used to being able to put UGens in pretty much any argument, like this: {a = SinOsc.kr(freq: 20, mul: 300); SinOsc.ar(freq: a, mul: 0.8)}.play If I wanted basic FM. So intuitively, trying out ChucK for the first time, I tried to do: SinOsc mod => SinOsc car.freq => dac; Which didn't work. After a bit of reading, I understand that UGens have "inputs" in ChucK, by default set to modify the frequency, and if you want to modulate a different parameter, you change that object's sync attribute. That seems kind of weird/unintuitive to me, is there something that I'm missing that went into making the syntax like this? Besides that, I find ChucK really fun to play with, and I look forward to making some zany sounds with it :) -Greg Sabo
Yeah, it is a little different. You can always spork another shred
that modulates a parameter, but it gets a little verbose.
andy
2009/7/19 Greg Sabo
Hello, I've been interested in trying ChucK for several years now but I've just recently started playing with it in earnest. I like what I see so far :) I have one thing that's bothering me - coming from SuperCollider, I'm used to being able to put UGens in pretty much any argument, like this: {a = SinOsc.kr(freq: 20, mul: 300); SinOsc.ar(freq: a, mul: 0.8)}.play If I wanted basic FM. So intuitively, trying out ChucK for the first time, I tried to do: SinOsc mod => SinOsc car.freq => dac; Which didn't work. After a bit of reading, I understand that UGens have "inputs" in ChucK, by default set to modify the frequency, and if you want to modulate a different parameter, you change that object's sync attribute. That seems kind of weird/unintuitive to me, is there something that I'm missing that went into making the syntax like this? Besides that, I find ChucK really fun to play with, and I look forward to making some zany sounds with it :) -Greg Sabo _______________________________________________ chuck-users mailing list chuck-users@lists.cs.princeton.edu https://lists.cs.princeton.edu/mailman/listinfo/chuck-users
Hi
In ChucK you don't have .kr or .ar signal divided, the kr signals are
defined in the code with control structures related to the time
structure. And the programming paradigm is a bit different.
SinOsc mod => blackhole;
SinOsc car => dac;
440 => float bfreq;
20 => mod.freq;
300 => mod.gain;
// the connection between ugens is inside the loop
while( true ) {
bfreq + mod.last() => car.freq;
64::samp => now; // kr
}
In the docs are a lot of examples. sync is other option, see
/examples/basic/fm*.ck
best
Lucas
2009/7/20, Andrew Turley
Yeah, it is a little different. You can always spork another shred that modulates a parameter, but it gets a little verbose.
andy
2009/7/19 Greg Sabo
: Hello, I've been interested in trying ChucK for several years now but I've just recently started playing with it in earnest. I like what I see so far :) I have one thing that's bothering me - coming from SuperCollider, I'm used to being able to put UGens in pretty much any argument, like this: {a = SinOsc.kr(freq: 20, mul: 300); SinOsc.ar(freq: a, mul: 0.8)}.play If I wanted basic FM. So intuitively, trying out ChucK for the first time, I tried to do: SinOsc mod => SinOsc car.freq => dac; Which didn't work. After a bit of reading, I understand that UGens have "inputs" in ChucK, by default set to modify the frequency, and if you want to modulate a different parameter, you change that object's sync attribute. That seems kind of weird/unintuitive to me, is there something that I'm missing that went into making the syntax like this? Besides that, I find ChucK really fun to play with, and I look forward to making some zany sounds with it :) -Greg Sabo _______________________________________________ chuck-users mailing list chuck-users@lists.cs.princeton.edu https://lists.cs.princeton.edu/mailman/listinfo/chuck-users
_______________________________________________ chuck-users mailing list chuck-users@lists.cs.princeton.edu https://lists.cs.princeton.edu/mailman/listinfo/chuck-users
I think the strange thing with ChucK (coming from SC) is that there
isn't a really easy way to modulate more than one parameter at a time.
If you wanted to do FM and AM at the same time in ChucK you would need
to create at least one new shred. In SC you could just pass in two
generators to do the modulation.
It would be cool if you could do something like this in ChucK:
SinOsc car => dac;
SinOsc mod;
440 => mod.dcOffset;
20 => mod.freq;
300 => mod.gain;
mod => car.freq;
andy
On Mon, Jul 20, 2009 at 4:40 PM, Lucas Samaruga
Hi
In ChucK you don't have .kr or .ar signal divided, the kr signals are defined in the code with control structures related to the time structure. And the programming paradigm is a bit different.
SinOsc mod => blackhole; SinOsc car => dac;
440 => float bfreq; 20 => mod.freq; 300 => mod.gain;
// the connection between ugens is inside the loop while( true ) { bfreq + mod.last() => car.freq; 64::samp => now; // kr }
In the docs are a lot of examples. sync is other option, see /examples/basic/fm*.ck
best Lucas
2009/7/20, Andrew Turley
: Yeah, it is a little different. You can always spork another shred that modulates a parameter, but it gets a little verbose.
andy
2009/7/19 Greg Sabo
: Hello, I've been interested in trying ChucK for several years now but I've just recently started playing with it in earnest. I like what I see so far :) I have one thing that's bothering me - coming from SuperCollider, I'm used to being able to put UGens in pretty much any argument, like this: {a = SinOsc.kr(freq: 20, mul: 300); SinOsc.ar(freq: a, mul: 0.8)}.play If I wanted basic FM. So intuitively, trying out ChucK for the first time, I tried to do: SinOsc mod => SinOsc car.freq => dac; Which didn't work. After a bit of reading, I understand that UGens have "inputs" in ChucK, by default set to modify the frequency, and if you want to modulate a different parameter, you change that object's sync attribute. That seems kind of weird/unintuitive to me, is there something that I'm missing that went into making the syntax like this? Besides that, I find ChucK really fun to play with, and I look forward to making some zany sounds with it :) -Greg Sabo _______________________________________________ chuck-users mailing list chuck-users@lists.cs.princeton.edu https://lists.cs.princeton.edu/mailman/listinfo/chuck-users
_______________________________________________ chuck-users mailing list chuck-users@lists.cs.princeton.edu https://lists.cs.princeton.edu/mailman/listinfo/chuck-users
Totally true. Except, it seems that once you get the hang of sporking
new shreds it's really not a big deal at all. It's maybe a few extra
lines of code. Is there a UGen param object? Like, could you make a
general shred:
fun void mod(UGen a, UGen.etc() b) {
a.last => b;
64::samp => now;
}
Is there any way to achieve the same functionality as this? This way
you could just do something like "spork mod(sine1, sine2.gain)" and
set up your modulation. Honestly, this isn't any trouble once you get
used to it. The main thing ChucK lacks is is the millions of UGens
that come with SC. But I like to think of this as a minimalist
feature instead of a lack.
Andrew
On Mon, Jul 20, 2009 at 8:06 PM, Andrew Turley
I think the strange thing with ChucK (coming from SC) is that there isn't a really easy way to modulate more than one parameter at a time. If you wanted to do FM and AM at the same time in ChucK you would need to create at least one new shred. In SC you could just pass in two generators to do the modulation.
It would be cool if you could do something like this in ChucK:
SinOsc car => dac; SinOsc mod; 440 => mod.dcOffset; 20 => mod.freq; 300 => mod.gain; mod => car.freq;
andy
On Mon, Jul 20, 2009 at 4:40 PM, Lucas Samaruga
wrote: Hi
In ChucK you don't have .kr or .ar signal divided, the kr signals are defined in the code with control structures related to the time structure. And the programming paradigm is a bit different.
SinOsc mod => blackhole; SinOsc car => dac;
440 => float bfreq; 20 => mod.freq; 300 => mod.gain;
// the connection between ugens is inside the loop while( true ) { bfreq + mod.last() => car.freq; 64::samp => now; // kr }
In the docs are a lot of examples. sync is other option, see /examples/basic/fm*.ck
best Lucas
2009/7/20, Andrew Turley
: Yeah, it is a little different. You can always spork another shred that modulates a parameter, but it gets a little verbose.
andy
2009/7/19 Greg Sabo
: Hello, I've been interested in trying ChucK for several years now but I've just recently started playing with it in earnest. I like what I see so far :) I have one thing that's bothering me - coming from SuperCollider, I'm used to being able to put UGens in pretty much any argument, like this: {a = SinOsc.kr(freq: 20, mul: 300); SinOsc.ar(freq: a, mul: 0.8)}.play If I wanted basic FM. So intuitively, trying out ChucK for the first time, I tried to do: SinOsc mod => SinOsc car.freq => dac; Which didn't work. After a bit of reading, I understand that UGens have "inputs" in ChucK, by default set to modify the frequency, and if you want to modulate a different parameter, you change that object's sync attribute. That seems kind of weird/unintuitive to me, is there something that I'm missing that went into making the syntax like this? Besides that, I find ChucK really fun to play with, and I look forward to making some zany sounds with it :) -Greg Sabo _______________________________________________ chuck-users mailing list chuck-users@lists.cs.princeton.edu https://lists.cs.princeton.edu/mailman/listinfo/chuck-users
_______________________________________________ chuck-users mailing list chuck-users@lists.cs.princeton.edu https://lists.cs.princeton.edu/mailman/listinfo/chuck-users
_______________________________________________ chuck-users mailing list chuck-users@lists.cs.princeton.edu https://lists.cs.princeton.edu/mailman/listinfo/chuck-users
I think to make the instance variables like control buses is not the design of chuck... but hmmm... sc have more development in high level programming interface than ck. If one want a custom fm class or function or a bus it can be made within the ck logic.
fun void mod(UGen a, UGen.etc() b) { a.last => b; 64::samp => now; }
Is there any way to achieve the same functionality as this?
You don't need a param, pass the entire UGen and make use of polymorphism //ck fun void modula(Osc car, Osc mod, float bfreq) { while(true) { mod.last() + bfreq => car.freq; 64::samp => now; } } SinOsc car => dac; SinOsc mod => blackhole; 440 => float bfreq; 20 => mod.freq; 300 => mod.gain; spork ~ modula(car, mod, bfreq); 2::second => now; car =< dac; SawOsc car2 => LPF lpf => dac; 2000 => lpf.freq; spork ~ modula(car2, mod, bfreq); 2::second => now; // sc ( t = Task{ var bfreq = 440; x = {SinOsc.ar(bfreq + SinOsc.kr(20, mul:300))}.play; 2.wait; x.release; y = {LPF.ar(LFSaw.ar(bfreq + SinOsc.kr(20, mul:300)), 2000)}.play; 2.wait; y.release; }.play; ) of course this can be made different in both languages. Is not a good comparison. greetings Lucas
sorry, add this
spork ~ modula(car, mod, bfreq) @=> Shred @modulaShred; // this
2::second => now;
car =< dac;
Machine.remove(modulaShred.id()); // this
2009/7/21 Lucas Samaruga
I think to make the instance variables like control buses is not the design of chuck... but hmmm... sc have more development in high level programming interface than ck. If one want a custom fm class or function or a bus it can be made within the ck logic.
fun void mod(UGen a, UGen.etc() b) { a.last => b; 64::samp => now; }
Is there any way to achieve the same functionality as this?
You don't need a param, pass the entire UGen and make use of polymorphism
//ck
fun void modula(Osc car, Osc mod, float bfreq) { while(true) { mod.last() + bfreq => car.freq; 64::samp => now; } }
SinOsc car => dac; SinOsc mod => blackhole;
440 => float bfreq; 20 => mod.freq; 300 => mod.gain;
spork ~ modula(car, mod, bfreq); 2::second => now;
car =< dac; SawOsc car2 => LPF lpf => dac;
2000 => lpf.freq;
spork ~ modula(car2, mod, bfreq); 2::second => now;
// sc
( t = Task{ var bfreq = 440; x = {SinOsc.ar(bfreq + SinOsc.kr(20, mul:300))}.play; 2.wait; x.release; y = {LPF.ar(LFSaw.ar(bfreq + SinOsc.kr(20, mul:300)), 2000)}.play; 2.wait; y.release; }.play; )
of course this can be made different in both languages. Is not a good comparison.
greetings Lucas
I'm not denying the logic is there. And certainly you can do certain
things in ChucK more easily that you can do them in SC. I'm just
saying that if you come from SC (or CSound) there are some things in
ChucK that may seem a little weird. And there might be some ways that
ChucK could deal with these things. But I'm not saying that ChucK is
REQUIRED to deal with them in the ways I'm talking about. I'm just
throwing out ideas.
At some point a programming language's utility is measured by it's
ability to provide an easy way to do a common task. So in the end it
depends on what you're trying to do. If you want to modulate on UGen
using some other UGens, SC gives you a fairly easy way to do this. If
you want sample-level control over your audio, ChucK might offer some
advantages.
Lucas, I agree that what I'm suggesting isn't necessarily in the
spirit of ChucK as it is currently implemented. But imagine a
situation in which the following was true:
1. Integer and Float values were objects.
2. These objects had a ".value()" method that returned the value of the object.
3. UGen objects had a ".value()" method that returned the current
value of the UGen.
As long as whatever you chucked to .freq had a ".value()" method, then
you could do what I described. Every sample, the UGen would simply
call "freq.value()" and get the current value of the frequency.
Like I said, I'm just throwing out ideas.
andy
On Mon, Jul 20, 2009 at 8:21 PM, Lucas Samaruga
sorry, add this
spork ~ modula(car, mod, bfreq) @=> Shred @modulaShred; // this 2::second => now;
car =< dac; Machine.remove(modulaShred.id()); // this
2009/7/21 Lucas Samaruga
: I think to make the instance variables like control buses is not the design of chuck... but hmmm... sc have more development in high level programming interface than ck. If one want a custom fm class or function or a bus it can be made within the ck logic.
fun void mod(UGen a, UGen.etc() b) { a.last => b; 64::samp => now; }
Is there any way to achieve the same functionality as this?
You don't need a param, pass the entire UGen and make use of polymorphism
//ck
fun void modula(Osc car, Osc mod, float bfreq) { while(true) { mod.last() + bfreq => car.freq; 64::samp => now; } }
SinOsc car => dac; SinOsc mod => blackhole;
440 => float bfreq; 20 => mod.freq; 300 => mod.gain;
spork ~ modula(car, mod, bfreq); 2::second => now;
car =< dac; SawOsc car2 => LPF lpf => dac;
2000 => lpf.freq;
spork ~ modula(car2, mod, bfreq); 2::second => now;
// sc
( t = Task{ var bfreq = 440; x = {SinOsc.ar(bfreq + SinOsc.kr(20, mul:300))}.play; 2.wait; x.release; y = {LPF.ar(LFSaw.ar(bfreq + SinOsc.kr(20, mul:300)), 2000)}.play; 2.wait; y.release; }.play; )
of course this can be made different in both languages. Is not a good comparison.
greetings Lucas
_______________________________________________ chuck-users mailing list chuck-users@lists.cs.princeton.edu https://lists.cs.princeton.edu/mailman/listinfo/chuck-users
On Tue, Jul 21, 2009 at 2:41 AM, Andrew Turley
Lucas, I agree that what I'm suggesting isn't necessarily in the spirit of ChucK as it is currently implemented. But imagine a situation in which the following was true: 1. Integer and Float values were objects. 2. These objects had a ".value()" method that returned the value of the object. 3. UGen objects had a ".value()" method that returned the current value of the UGen. As long as whatever you chucked to .freq had a ".value()" method, then you could do what I described. Every sample, the UGen would simply call "freq.value()" and get the current value of the frequency.
In ruck, I made all UGen attributes accept lambdas (anonymous functions), which to me seems a natural extension to ChucK, but I got the same response, that it wasn't very ChucK-like. What languages besides SC allow you to do things like this? -- Tom Lieber http://AllTom.com/
Tom;
In ruck, I made all UGen attributes accept lambdas (anonymous functions), which to me seems a natural extension to ChucK, but I got the same response, that it wasn't very ChucK-like. What languages besides SC allow you to do things like this?
Lisp does and most things based on it so that includes Scheme which in turn means Fluxus uses anonymous functions. Fluxus might be a nice system if you want Lambda's and creative programing, it even has a (modest) extension for sound but it's mainly OpenGL. I too feel a Lambda operator would be pushing it, I'm not sure we need it either. The main issue as I see it isn't one of syntax, but one of CPU. If you do all modulations at sample rate (as this sort of link implies) and you don't use block processing (we don't) then there will be a big hit on the CPU so like many systems we use rates for modulation, we just allow them to be set in a advanced way (you could also call it a roundabout way, if you wish, but I think there are big strengths to it). Considering that this is a fairly common request I think we might still want to look into it once we get our "context sensitive block processing" in place (meaning we block process except where there are feedback loops involved). I don't see a issue with anonymous functions in ChucK as such, aside from how a lack of a name will make it harder to later refer to them if/when we get around to editing running code. I could imagine this; spork ~ { while(1) { foo.output() => that.input; ms => now; } } That has been proposed before (I forgot by whom). It's not very useful in how much time/code it saves but it doesn't look incoherent to me. Doesn't look very dangerous either. More useful to me would be this; my_ADSR @=> my_filter.freq; or, more generally; my_ADSR.last() @=> my_filter.freq; //this is where the danger starts as we'll see below Or, making it yet more general and getting back to anonymous funtions something like this; {440 + my_ADSR.last() } @=> my_filter.freq; or even to pay due to the typesystem to some small degree; {return 440 + my_ADSR.last(); } @=> my_filter.freq; This would create a anonymous function that would implicidly be pulled whenever my_filter is ticked. Still seems somewhat coherent to me, syntax-wise, but it's aproaching the edge. While it can be debated whether any of this is "ChucKian" I do think that having the option of having the UGen graph pull values from some type of custom structure when a given UGen is ticked fits very harmoniously into a strongly timed paradigm. This would very quickly lead to very big issues though; when such a structure would be a function and this function would attempt to advance time (as funtions can) we would have a big problem (if I were the VM I'd simply give up at that point). Basically this is inserting code into the UGen graph which is the very last place where we want ChucK code in a normal sense of the word as ChucK code typically does the type of thing that we don't want done there, like change the graph or advance time. Maybe most coherent and safe would be allowing to extend UGen in our own classes, then allow for assigning UGens to UGen member functions and have those functions tick these UGens. This would mean that a custom class extending UGen would need to have a special type of function of type float called "tick()" and that inside of "tick()" we aren't allowed to advance time. Also; no linking/unlinking of UGens. Probably the only calls that we should be able to make would be to read from variables and maybe from other UGen members because if we allow function calls those functions in turn might try to do Bad Things. This is quite a easy place to do Bad Things in. Yours, Kas.
On 21 Jul 2009, at 17:59, Kassen wrote:
Lisp does and most things based on it so that includes Scheme which in turn means Fluxus uses anonymous functions. Fluxus might be a nice system if you want Lambda's and creative programing, it even has a (modest) extension for sound but it's mainly OpenGL.
I though that perhaps OCaml might be a good candidate. One reason is that it gives good performance. http://en.wikipedia.org/wiki/OCaml
I too feel a Lambda operator would be pushing it, I'm not sure we need it either.
There is a package Haskell package Haskore <http://haskell.org/haskore/
for doing MIDI that perhaps can give some ideas of the usability of lambda-calculus in music. Haskell though, because of its overall laziness, needs to use monads to express typing of imperative structures. Could be a bother in music.
Hans
Hans There is a package Haskell package Haskore http://haskell.org/haskore/ for
doing MIDI that perhaps can give some ideas of the usability of lambda-calculus in music. Haskell though, because of its overall laziness, needs to use monads to express typing of imperative structures. Could be a bother in music.
One of the questions is that a Lambda operator is typically used in languages that are very different from ChucK and combined with very different constructs. I think that in a strongly typed language it might be much less useful and there are things that are more or less equivalent. I'm still interested in ideas like operator overloading, particularly of the ChucK operator. I also think functors might be nice ideas. We are talking about syntactic sugar here. I do think sugar can make sense and it could work as a double edged sword. We could for example attempt to create a syntax for audio rate modulation that would be more predictable in structure than sporked shreds which we might also try to use behind the scenes to optimise computation; that to me would make loads of sense and it would serve two needs that I think are clearly held without excessively complicating syntax. I'd like to see a example of what you or Tom (or anyone else for that matter) would imagine a Lambda operator would look like in ChucK or what it would solve. I suspect we could do most of that sort of thing in other ways as well and I fear trouble could well follow. When I started writing my last email to this list it seemed to me like anonymous functions that would write to parameters at audio rate seemed like a fairly innocent thing, I think it's now clear that those aren't as innocent (I went "oops" myself). Yours, Kas.
On 21 Jul 2009, at 19:48, Kassen wrote:
One of the questions is that a Lambda operator is typically used in languages that are very different from ChucK and combined with very different constructs.
Even in Haskell, they have written packages that allows one to do essentially imperative programming. This will be wrapped up in complicated underlying constructs, such as monads, so it puts up the question of good language design.
I think that in a strongly typed language it might be much less useful and there are things that are more or less equivalent.
Both Haskell and OCaml are strongly typed functional languages. What do you have in your mind here?
I'm still interested in ideas like operator overloading, particularly of the ChucK operator. I also think functors might be nice ideas.
In Haskell, operator overloading is tied to a construction of polymorphic classes, because it needs to fit with the Hindley-Milner style type inference system. So it exists, but one can't overload functions standing alone. This limitation is eased up by a module system, essentially the same as C++ namespace.
We are talking about syntactic sugar here.
A functional language just adds one object: the lambda operator. It just makes it possible to create functions dynamically and fully treat them as objects.
I do think sugar can make sense and it could work as a double edged sword. We could for example attempt to create a syntax for audio rate modulation that would be more predictable in structure than sporked shreds which we might also try to use behind the scenes to optimise computation; that to me would make loads of sense and it would serve two needs that I think are clearly held without excessively complicating syntax.
This is an independent issue. Chuck needs some components so that the creation of basic units becomes simpler. This might be tied to a multiple timing model. The per-sample timing is only needed when creating a single sound generator, corresponding to a string or pipe in a music instrument. When coordinating those into music instrument, a timing of about 10 millisecond might suffice. A group of music instruments might do with somewhat less. - Just an input.
I'd like to see a example of what you or Tom (or anyone else for that matter) would imagine a Lambda operator would look like in ChucK or what it would solve.
One can write very expressive and compact code. There is a good chance it will help in music, as music often have structures that should be repeated with some variations. The Haskore package had some examples, for example the attached fractal music sample. It does not mean this how the music will sound :-), only that it becomes easier to write it.
I suspect we could do most of that sort of thing in other ways as well...
Strictly speaking, even function stacks are wholly unnecessary in a computer - any program can be expanded to a sequence of jumps. It was added over the years to support the way humans want to program.
...and I fear trouble could well follow. When I started writing my last email to this list it seemed to me like anonymous functions that would write to parameters at audio rate seemed like a fairly innocent thing, I think it's now clear that those aren't as innocent (I went "oops" myself).
It is though difficult to implement a functional language. One needs to deal with the relabeling of variables under substitution. So it might be better to tweak some existing functional language into accepting Chuck's timing concepts. Hans
Hans Aberg wrote:
This is an independent issue. Chuck needs some components so that the creation of basic units becomes simpler.
This might be tied to a multiple timing model. The per-sample timing is only needed when creating a single sound generator, corresponding to a string or pipe in a music instrument. When coordinating those into music instrument, a timing of about 10 millisecond might suffice. A group of music instruments might do with somewhat less. - Just an input.
This higher-level timing composition was my goal in the functor objects and Loops construct in LiCK: http://github.com/heuermh/lick/blob/b30c8ab28abe6e1580e43819fe586af247cfd298... Here's a stupid example: 96 => int bpm; 0::ms => dur none; 240000::ms / (1 * bpm) => dur w; 240000::ms / (2 * bpm) => dur h; 240000::ms / (4 * bpm) => dur q; 240000::ms / (8 * bpm) => dur e; 240000::ms / (16 * bpm) => dur s; class Sample extends Procedure { SndBuf buf => dac; { 0.0 => buf.gain; } fun void run() { 0 => buf.pos; 1.0 => buf.gain; 1.0 => buf.rate; } } class Kick extends Sample { { "samples/kick_opn_50.wav" => buf.read; } } class Snare extends Sample { { "samples/snr_ord_r42.wav" => buf.read; } } class Bass extends FloatProcedure { Wurley wurley => dac; float minFreq; float maxFreq; Interpolation interpolate; fun void run(float arg) { minFreq + (interpolate.evaluate(arg) * (maxFreq - minFreq)) => wurley.freq; wurley.noteOn(0.4); } } Kick kick; Snare snare; Loops.loop(kick, q, w) @=> Procedure kick0; Loops.loop(kick, e, h) @=> Procedure kick1; Loops.loop(kick, s, h) @=> Procedure kick2; Loops.append(kick0, Loops.append(kick1, kick2)) @=> Procedure kickVerse; Loops.loop(kickVerse, 4) @=> Procedure kickSong; Loops.loop(snare, q, w) @=> Procedure snare0; Loops.loop(snare0, 2) @=> Procedure snareVerse; Loops.loop(snareVerse, e, none, 4) @=> Procedure snareSong; Bass bass; 43.65 => bass.minFreq; 65.41 => bass.maxFreq; CircularOut circularOut; circularOut @=> bass.interpolate; Bass bassEnd; 43.65 => bassEnd.minFreq; 87.31 => bassEnd.maxFreq; QuadraticIn quadIn; quadIn @=> bassEnd.interpolate; Loops.loop(bass, e, 3 * q) @=> Procedure bassVerse; Loops.append(Loops.loop(bassVerse, w, none, 9), Loops.loop(bassEnd, s, 3 * q)) @=> Procedure bassSong; spork ~ kickSong.run(); spork ~ snareSong.run(); spork ~ bassSong.run(); 18 * w => now;
I'd like to see a example of what you or Tom (or anyone else for that matter) would imagine a Lambda operator would look like in ChucK or what it would solve.
One can write very expressive and compact code. There is a good chance it will help in music, as music often have structures that should be repeated with some variations.
Heh, as you may have noticed, the above is not very expressive or compact. Anonymous functions or closures or lambda functions or whatever you want to call them would help in that matter. michael
2009/7/21 Kassen
I'd like to see a example of what you or Tom (or anyone else for that matter) would imagine a Lambda operator would look like in ChucK or what it would solve. I suspect we could do most of that sort of thing in other ways as well and I fear trouble could well follow. When I started writing my last email to this list it seemed to me like anonymous functions that would write to parameters at audio rate seemed like a fairly innocent thing, I think it's now clear that those aren't as innocent (I went "oops" myself).
Like how you wrote: now => time start; SinOsc a => blackhole; SinOsc b => dac; { a.last(); } @=> b.freq; { now < start + 5::second ? 5 : 10 } @=> a.freq; { (now - start)/second } @=> a.gain; As long as they don't do the two things you said, change the graph or advance time, they are pretty harmless. Changing variables is legitimate as long as they're scheduled as if they were in a while(samp => now) loop. Actually, I could imagine a pre-processor that un-anonymizes that stuff automatically. It's pretty mechanical: now => time start; SinOsc a => blackhole; SinOsc b => dac; fun void bfreq() { while(samp => now) a.last() => b.freq; } spork ~ bfreq(); fun void afreq() { while(samp => now) now < start + 5::second ? 5 : 10 } spork ~ afreq(); fun void again() { while(samp => now) (now - start)/second => a.gain; spork ~ again(); -- Tom Lieber http://AllTom.com/
Tom;
Like how you wrote:
Ok, yes, I wrote that but I like the idea of assigning UGen outputs to UGen member function inputs much better. For one thing it seems much more simple, for another it should interact better with the UGen graph's pull-through model and so not depend on when we start such a shred relative to where the sample clock might be at that moment to the same degree. It seems much more predictable and intuitive to me.
now => time start; SinOsc a => blackhole;
Isn't blackhole a bit of a artefact of exactly the question this discussion is hoping to help solve?
SinOsc b => dac; { a.last(); } @=> b.freq;
Ok, this I can see. My one issue with it is a fairly big one though; this function isn't typed. As soon as we'd get this people like you and me will start writing spectacularly large functions with many "side effects" and it wouldn't be clear what value is being returned. Functions in ChucK should have a return type; so far we only considered modulation where both the in and output were of type float but we also have UGens with member functions of type int, string and even some arrays, I think. By the time we have defined a type and made a return statement a lot of the advantages to this in terseness will be lost but I don't see how we would or could get around those. When our shiny new anonymous sample rate functions have no clear return type the type system won't be able to check whether a legal connection has been made. Admittedly my proposal didn't allow for that either and admittedly it's member functions of type float that seem most interesting right now.
{ now < start + 5::second ? 5 : 10 } @=> a.freq;
I'm really sorry but you utterly lost me here. This looks a bit like Lisp without the brackets. You seem to mean something like; while( now < (start + 5::seconds) ) { Std.rand2(5,10) => a.freq; samp => now; } ...or something along those line? This does look very un-ChucKian to me, especially the question mark which seems to imply something like "if" or "while". Other things that haven't been addressed yet is how and whether these functions would self-terminate and matters like local variables to them. IMHO the "properly extend UGen and assign those to member functions of other UGens" strategy addresses those issues better.
As long as they don't do the two things you said, change the graph or advance time, they are pretty harmless. Changing variables is legitimate as long as they're scheduled as if they were in a while(samp => now) loop.
Yes, but if we demand those aspects of anonymous functions I would say they are starting to look suspiciously like UGens in how specialised they would be, right?
Actually, I could imagine a pre-processor that un-anonymizes that stuff automatically. It's pretty mechanical:
Yes, that's true, that could be done fairly easily, but that way there is no way of getting rid of these modulations again from within ChucK code... unless we allow for that with more structures, by which time it will be significantly more expensive on the CPU than if we'd do the same thing manually. All of those shreds from a single file/scope could easily be combined into a single shred, for example. I don't think I'm sold on this so far, sorry. It's certainly a stimulating topic for exploration though. Yours, Kas.
2009/7/21 Kassen
Ok, yes, I wrote that but I like the idea of assigning UGen outputs to UGen member function inputs much better. For one thing it seems much more simple, for another it should interact better with the UGen graph's pull-through model and so not depend on when we start such a shred relative to where the sample clock might be at that moment to the same degree. It seems much more predictable and intuitive to me.
Definitely.
SinOsc b => dac; { a.last(); } @=> b.freq;
Ok, this I can see. My one issue with it is a fairly big one though; this function isn't typed.
What? b.freq needs a float so the expression on the left would need to return a float.
{ now < start + 5::second ? 5 : 10 } @=> a.freq;
I'm really sorry but you utterly lost me here. This looks a bit like Lisp without the brackets. You seem to mean something like;
while( now < (start + 5::seconds) ) { Std.rand2(5,10) => a.freq; samp => now; }
"___ ? ___ : ___" is conditional syntax. It's an if-statement as an expression. My lambda is like: while(samp => now) if(now < start + 5::second) 5 => a.freq; else 10 => a.freq; Or, effectively, 5 => a.freq; 5::second => now; 10 => a.freq; The idea is that it's an example of something it's unlikely one would bother writing a UGen to do, and is easier to write with lambda syntax than today's ChucK.
Yes, but if we demand those aspects of anonymous functions I would say they are starting to look suspiciously like UGens in how specialised they would be, right?
Yeah, they're little UGens. That's the idea!
Yes, that's true, that could be done fairly easily, but that way there is no way of getting rid of these modulations again from within ChucK code... unless we allow for that with more structures, by which time it will be significantly more expensive on the CPU than if we'd do the same thing manually. All of those shreds from a single file/scope could easily be combined into a single shred, for example.
It wasn't an incredibly serious suggestion. :D In ruck the lambda assignment is just that, an assignment. To "disconnect" it you set a new value. { a.last() } => b.freq; 5 => b.freq; -- Tom Lieber http://AllTom.com/
Tom;
SinOsc b => dac; { a.last(); } @=> b.freq;
Ok, this I can see. My one issue with it is a fairly big one though; this function isn't typed.
What? b.freq needs a float so the expression on the left would need to return a float.
Ok, I see. And since we are avoiding a explicit return statement The last line of the function (where a function has multiple statements) would need to return a float or compilation would fail, is that right? It does make sense in that it could work but it doesn't seem especially coherent with how normal functions work.
"___ ? ___ : ___" is conditional syntax. It's an if-statement as an expression. My lambda is like:
Yes, I get that syntax but that's like having a separate sub-language for these things.
The idea is that it's an example of something it's unlikely one would bother writing a UGen to do, and is easier to write with lambda syntax than today's ChucK.
That is true, probably shorter than going; "class foo extends UGen{....." connecting it and maybe also giving it a signal to start doing it's thing. For repetitive tasks it would probably get quite tedious to write but maybe these could be named and re-used as well.
Yeah, they're little UGens. That's the idea!
Ok, yes. I could certainly see advantages to that but in general I still think extending UGen would be more useful. That seems more re-usable to me though it might also lead to more conservative and less impulsive designs. It wasn't an incredibly serious suggestion. :D
Well, you never know and it's a interesting topic for debate. It's at least fascinating to wonder what would go wrong, where and why. Theorising over this made me marvel at how there aren't so many more things that go wrong.
In ruck the lambda assignment is just that, an assignment. To "disconnect" it you set a new value.
I do like that. It does get around a .op() for each of the (new)inputs and it seems quite coherent with seeing these connections as assignments. Also; for your strategy (unlike my own version) there wouldn't be much use to a .op() there. I think that syntax-wise there are some nice ideas here though these ideas might only start to really shine with realtime performance and a interactive interpreter. Maybe ruck could be a front-end for SC? That might work, other such front-ends have been written, though that might mean losing strong timing. Yours, Kas.
I just now see, at the bottom of this paragraph: http://en.wikipedia.org/wiki/OCaml#Features that the GC of OCaml is not designed for concurrency. So it cannot be used on a multicore system, it seems (by this comment). In addition, a reference count GC good when exact timing is needed. So it might be best for a language that should produce real time music. Fortunately, it is easiest to implement. So it suggests that Chuck might be best off to implement its own lambda calculus on top of what already exits, just using a reference counts GC, as Kassen mentioned was planned. That is, would anybody like to implement it :-). Anyway, it shows some of the concerns that must be addressed when implementing lambda calculus. Hans
On 21 Jul 2009, at 08:41, Andrew Turley wrote:
At some point a programming language's utility is measured by it's ability to provide an easy way to do a common task. So in the end it depends on what you're trying to do.
This is in fact an important computer programming language design principle. If one has to spend time on finding or writing workarounds as opposed to what is close to the intention, then the language wasn't designed or well designed for the task: language and programming intention do not interface well. Hans
2009/7/21 Andrew Turley
I'm not denying the logic is there. And certainly you can do certain things in ChucK more easily that you can do them in SC. I'm just saying that if you come from SC (or CSound) there are some things in ChucK that may seem a little weird. And there might be some ways that ChucK could deal with these things. But I'm not saying that ChucK is REQUIRED to deal with them in the ways I'm talking about. I'm just throwing out ideas.
At some point a programming language's utility is measured by it's ability to provide an easy way to do a common task. So in the end it depends on what you're trying to do. If you want to modulate on UGen using some other UGens, SC gives you a fairly easy way to do this. If you want sample-level control over your audio, ChucK might offer some advantages.
Lucas, I agree that what I'm suggesting isn't necessarily in the spirit of ChucK as it is currently implemented. But imagine a situation in which the following was true: 1. Integer and Float values were objects. 2. These objects had a ".value()" method that returned the value of the object. 3. UGen objects had a ".value()" method that returned the current value of the UGen. As long as whatever you chucked to .freq had a ".value()" method, then you could do what I described. Every sample, the UGen would simply call "freq.value()" and get the current value of the frequency.
Like I said, I'm just throwing out ideas.
andy
Yep, was not my intention to talk about the design of chuck but the way the things can be done. Because that I put the examples like answers. I undestand your point, sorry if I make noise. Greetings Lucas
Lucas, no need to apologize. I think the discussion is good, if
nothing else to see where other people stand, and also to see some
good ideas.
Hans, I agree, one needs to make sure that one is working with the
grain when using a language, rather than fighting it. I'm just
thinking that on the surface of it there might be some fairly easy
ways to add this kind of functionality to ChucK, perhaps without
breaking existing code.
Tom, I like your idea of passing around functions (named or
anonymous), but I understand that it isn't everybody's cup of tea.
andy
On Tue, Jul 21, 2009 at 2:07 AM, Lucas Samaruga
2009/7/21 Andrew Turley
: I'm not denying the logic is there. And certainly you can do certain things in ChucK more easily that you can do them in SC. I'm just saying that if you come from SC (or CSound) there are some things in ChucK that may seem a little weird. And there might be some ways that ChucK could deal with these things. But I'm not saying that ChucK is REQUIRED to deal with them in the ways I'm talking about. I'm just throwing out ideas.
At some point a programming language's utility is measured by it's ability to provide an easy way to do a common task. So in the end it depends on what you're trying to do. If you want to modulate on UGen using some other UGens, SC gives you a fairly easy way to do this. If you want sample-level control over your audio, ChucK might offer some advantages.
Lucas, I agree that what I'm suggesting isn't necessarily in the spirit of ChucK as it is currently implemented. But imagine a situation in which the following was true: 1. Integer and Float values were objects. 2. These objects had a ".value()" method that returned the value of the object. 3. UGen objects had a ".value()" method that returned the current value of the UGen. As long as whatever you chucked to .freq had a ".value()" method, then you could do what I described. Every sample, the UGen would simply call "freq.value()" and get the current value of the frequency.
Like I said, I'm just throwing out ideas.
andy
Yep, was not my intention to talk about the design of chuck but the way the things can be done. Because that I put the examples like answers. I undestand your point, sorry if I make noise.
Greetings Lucas
participants (8)
-
Andrew C. Smith
-
Andrew Turley
-
Greg Sabo
-
Hans Aberg
-
Kassen
-
Lucas Samaruga
-
Michael Heuer
-
Tom Lieber