Hi, Sometimes I've noticed zipper noise when using Chuck. Specifically if I'm doing some sort of modulation in a loop, I'll get artifacts because I'm instantaneously setting a parameter once every 10ms or so. Ideally changing the time-length of the loop shouldn't really change the sound much (except to make longer latency), but I find that there are drastic differences between using 1::samp, 5::samp, 1::ms, 10::ms, mostly due (I think) to the frequency of these artifacts. Has anyone noticed this? What measures do you take to fix it? I'm wondering if it would be useful to be able to specify ramp times (or some global ramp time) for when you set a parameter. (Of course that would be a bit tricky to implement...) Perhaps each parameter could have a per-sample lowpass on it, but I guess that would greatly increase processing needs. How is this currently handled in Chuck? cheers, Steve
On 11/2/07, Stephen Sinclair
Hi,
I realize I'm not giving you a true solution but hopefully this will explain a little about the problem? In the meantime you can try to get clever, for example when modulating a oscillator's pitch you could make the modulation run at the same rate as the oscillator and try to only set it at times when it crosses the zero, that could save CPU while not zippering.
Yup, I understand a lot of the issues involved. The problem comes in when for example trying to compute filter coefficients, it's not something that is really wanted at every sample. On the other hand a possible part-solution might be to have it re-calculate the coefficients only while the value (e.g., cutoff) is actually "sliding".. Anyways, I know that this is not an easy thing to answer, and in a lot of cases will depend on the particular UGen and so is difficult to generalize. I'd think though that enveloping less demanding values (FM parameters, for example) should be possible.. I dunno, anyways I thought I'd toss the idea out there. The reason I guess it seems to be a bit of a problem with Chuck specifically is that you tend to want to abuse the sample-accuracy of it by doing things like frequency modulation or LFO-based effects in a simple while{}. But when you actually drop down to 1::samp, the CPU really starts spiking. I guess maybe the answer is to just build that kind of thing into a UGen, but it sort of defeats the idea of doing nice effects quickly using =>. Steve But then when you do this in Chuck code instead of building it into a UGen, you get
On 11/3/07, Stephen Sinclair
when for example trying to compute filter coefficients, it's not something that is really wanted at every sample.
On the other hand a possible part-solution might be to have it re-calculate the coefficients only while the value (e.g., cutoff) is actually "sliding"..
That would, I think, be a case where ChucK's dynamic control-rate could indeed yield savings. I'm not so sure interpolation would help with swwps either, if we have two sets of coefficients that both yield a stable filter I'm not so sure we can interpolate from one set to the other and assume all of the stages in between will be stable as well. The only real solutions there (as I see it) would be more "readymade" (perhaps importable and user constructible?) filters like LPF and so on where you can just set a frequency and have the C++ code deal with it or a lot of optimization in ChucK itself. If you want a sweeping filter those calculations will need to be done *somewhere* every sample in any case. Anyways, I know that this is not an easy thing to answer, and in a lot
of cases will depend on the particular UGen and so is difficult to generalize. I'd think though that enveloping less demanding values (FM parameters, for example) should be possible..
Yes, indeed, and anything that can be re-expressed as sums and multiplications can be dealt with by a clever network of Gain ugens. I dunno, anyways I thought I'd toss the idea out there. The reason I
guess it seems to be a bit of a problem with Chuck specifically is that you tend to want to abuse the sample-accuracy of it by doing things like frequency modulation or LFO-based effects in a simple while{}. But when you actually drop down to 1::samp, the CPU really starts spiking. I guess maybe the answer is to just build that kind of thing into a UGen, but it sort of defeats the idea of doing nice effects quickly using =>.
I wonder how bad something like the SndBuf b => dac; Envelope e => b.rate; example I gave would be for the CPU. theoretically it shouldn't be that bad as we have e.last() in the system anyway and the SndBuf would need to take it's rate into account every sample as well so perhaps some sort of asignment between the two could be used to get this effect "for free". This would of course run into unpredictable issues if somebody were to chuck multiple Ugens to a single parameter.... In that sense it's a lot less clean then what we have but it would be very powerful indeed. This would be one of those issues where I'm quite happy we have Ge to think of consistent solutions :¬) Yours, Kas.
Kassen wrote:
SndBuf b => dac; Envelope e => b.rate ;
That would be very nice indeed... -- peace, love & harmony Atte http://atte.dk | http://myspace.com/attejensen http://anagrammer.dk | http://modlys.dk
On 11/3/07, Atte André Jensen
Kassen wrote:
SndBuf b => dac; Envelope e => b.rate ;
That would be very nice indeed...
I would be bouncing up&down with happiness if we got this but there *must* be some catch. For one thing the whole "pull model" for samples would have to be re-written and we need some sort of protection against double connections to single parameters. "b.rate" above is probably just a single number but "my_filter.freq()" is a call to a more advanced function and running those, as we debated earlier in the topic, every sample will put a drain on the cpu and there I wonder how much cheaper a C++ implementation will be then a ChucK one. What we could also consider is enable the chucking of signals only to those member functions that cover for simple single values (with a cast to int where needed). . Kas.
I would be bouncing up&down with happiness if we got this but there *must* be some catch. For one thing the whole "pull model" for samples would have to be re-written and we need some sort of protection against double connections to single parameters.
Max/MSP and Pd handle the double-connection problem by just adding.
"b.rate" above is probably just a single number but "my_filter.freq()" is a call to a more advanced function and running those, as we debated earlier in the topic, every sample will put a drain on the cpu and there I wonder how much cheaper a C++ implementation will be then a ChucK one.
What we could also consider is enable the chucking of signals only to those member functions that cover for simple single values (with a cast to int where needed).
Yeah, I guess like I said it's a difficult problem to generalize. Since the "control" loops in Chuck can go right down to the sample level, I find the semantic difference between connecting two UGens and explicitly programming a connection in a while{} loop is actually somewhat of a gray area, the difference being more implementation. For instance, the decision is usually that if you chuck, say, a SinOsc to an LPF, what you are chucking is the audio signal. However, you could just as easily consider that you might want to chuck something to freq(). With the fact that you can then use a while{} to modulate freq() at the sample rate, you could argue that the only real difference between these two operations is syntactic. But of course, that's not to say that these choices are arbitrary either... Obviously they are made to maximize efficiency. Chucking something at freq() would imply a great deal of extra calculations per sample. However, maybe the answer is simply to program some extra UGens that allow these kinds of connections, when they are wanted. For instance, that is why we would not want to reprogram the STK synths using Chuck, and would rather keep them as UGens. I think a good solution for the zipper noise problem however, aside from the idea of chucking to object parameters, would be to have UGens automatically at least ramp their values, when they have been modified, for some determined number of samples. (Or with a certain "inertia".) This would greatly increase sound quality, imho. I guess this inertia value would have to be variable, either globally or per-UGen or per-parameter since otherwise it might interfere with single-sample computations (since it implies a certain frequency response), but ... well basically what I'm saying is that it would be nice to have this as an option to preserve sound quality without requiring the user to run his loop at 1::samp. Maybe one day I'll have time to look at the Chuck code and see how difficult it would be to implement. cheers, Steve
lör 2007-11-03 klockan 12:31 -0400 skrev Stephen Sinclair:
"b.rate" above is probably just a single number but "my_filter.freq()" is a call to a more advanced function and running those, as we debated earlier in the topic, every sample will put a drain on the cpu and there I wonder how much cheaper a C++ implementation will be then a ChucK one.
[...]
But of course, that's not to say that these choices are arbitrary either... Obviously they are made to maximize efficiency. Chucking something at freq() would imply a great deal of extra calculations per sample.
Hey, guys, before you continue! If this were implemented, ChucK would also get a "control rate", and the CPU-load would be about the same as any other realtime audio (programming) app. The difference would be that you can choose to not use it. Gasten
Martin Ahnelöv wrote:
Hey, guys, before you continue! If this were implemented, ChucK would also get a "control rate", and the CPU-load would be about the same as any other realtime audio (programming) app. The difference would be that you can choose to not use it.
Would it be insane to dream of something like (probably nightmare to implement): SndBuf b => dac; Envelope e => b.rate ; 100 => e.updaterate; // controlrate 100 hz So having updaterate (or simili) on every ugen would allow on a per ugen-instance choice of "control rate". -- peace, love & harmony Atte http://atte.dk | http://myspace.com/attejensen http://anagrammer.dk | http://modlys.dk
Hey, guys, before you continue! If this were implemented, ChucK would also get a "control rate", and the CPU-load would be about the same as any other realtime audio (programming) app. The difference would be that you can choose to not use it.
I don't agree that applying envelopes to parametric changes would imply a "control rate". The envelope would occur at sample rate. If anything it would imply some (adjustable) frequency response for changing the given parameter.
100 => e.updaterate; // controlrate 100 hz
So having updaterate (or simili) on every ugen would allow on a per ugen-instance choice of "control rate".
Don't think this is the right approach.. now _this_ would imply some kind of control-rate network that runs outside of the chuck code, which is not what we want. In cases where you want to do things at less than sample-rate, using while{} is perfectly fine. (For me at least.) The problem I started this thread about is simply that when you do things at a control rate, you can't easily envelope your changes without running your loop at 1::samp, so you get zipper. cheers, Steve
On 11/3/07, Stephen Sinclair
Yeah, I guess like I said it's a difficult problem to generalize. Since the "control" loops in Chuck can go right down to the sample level, I find the semantic difference between connecting two UGens and explicitly programming a connection in a while{} loop is actually somewhat of a gray area, the difference being more implementation.
It does make a huge difference in cpu load though and because Ugen connections are far more predictable then programmers it's easier to optimize. For instance, the decision is usually that if you chuck, say, a SinOsc
to an LPF, what you are chucking is the audio signal. However, you could just as easily consider that you might want to chuck something to freq(). With the fact that you can then use a while{} to modulate freq() at the sample rate, you could argue that the only real difference between these two operations is syntactic.
Yeah, I suppose... You can indeed also set the rate at which Ugens run, since that's the sample rate.... But I'd say that another major difference is that you can't modulate the VM's sample-rate while the VM is running. But of course, that's not to say that these choices are arbitrary
either... Obviously they are made to maximize efficiency. Chucking something at freq() would imply a great deal of extra calculations per sample.
However, maybe the answer is simply to program some extra UGens that allow these kinds of connections, when they are wanted. For instance, that is why we would not want to reprogram the STK synths using Chuck, and would rather keep them as UGens.
Yes, that would be a option that would make sense. What we could also do is look for Ugens that have parameters that could be modulated at sample rate without taking a big CPU hit, I think that's basically what Dan is doing with his new sync input option for LiSa but so far we have no real syntax for chucking signals to inputs that are different from each other. I suppose Gain set to substract would be a example and so is the DAC, in a way. I think a good solution for the zipper noise problem however, aside
from the idea of chucking to object parameters, would be to have UGens automatically at least ramp their values, when they have been modified, for some determined number of samples. (Or with a certain "inertia".) This would greatly increase sound quality, imho.
Hmmmm. It's not a bad idea at all but there are no free rides. LPF.freq() will still need to calculate it's coefficients every sample, as I mentioned earlier I don't think you can just ramp the individual coefficients and expect it to stay stable but perhaps there are filter designs for which that's possible?
well basically what I'm saying is that it would be nice to have this as an option to preserve sound quality without requiring the user to run his loop at 1::samp.
I have no real issues with running loops at 1::samp, except that it's so expensive on the CPU. Perhaps a big round of ChucK optimization would cure this but I'm also very much behind the choice to first focus on getting it all to work and only then optimize. Functionally speaking we can already do everything that has been mentioned, you can use Envelope to ramp for you, meaning you do need a loop at 1::samp but it can be a loop that only contains extremely simple commands. I also think it's important that choices made here would fit with the rest of the syntax. On the other hand; some modules, like the oscillators, already do have modulation inputs but for those a choice has to be made about the way the modulation affects the oscillator. I'm currently leaning towards extending the syntax there to enable us to use multiple modulation targets per Ugen. If we would do that, where it makes sense, this could easily be used for slides/interpolation as well by using Envelope. Doing it that way would allow the user to determine the way the interpolation works and it's shape, I like that much better then a ready-made and static solution. I think I'm also in favor of allowing negative targets for Envelope to ramp to. There are explicit checks in the code to prevent those but I don't quite understand why those are there. Maybe one day I'll have time to look at the Chuck code and see how
difficult it would be to implement.
I think this side of ChucK borrows a lot from the STK, if you want to go there you could do far worse then starting with Perry Cook's "Real sound synthesis for interactive applications" which is both serious in purpose and friendly tone, good properties for a book on that sort of topic, IMHO. I think there are some very hard choices to make here. I agree there is room for improvement but I also think there is very real danger of mucking up consistency. I hope Ge has some thoughts on this, so far his solutions are quite good and the very core of ChucK syntax *is* directed at this very issue and Gasten does have a good point in his last mail to this discussion. Still; thinking out loud is fun! Yours, Kas.
participants (4)
-
Atte André Jensen
-
Kassen
-
Martin Ahnelöv
-
Stephen Sinclair