Call phasor in loop
I'm trying to call phasor in a loop in gen and it seems to be behaving in a way that I wouldn't expect.
I'd like to be able to call phasor repeatedly in the loop with a different frequency each time, and pass the result of that into sin. Basically doing additive synthesis by generating a series of sine waves with different frequencies within a loop and summing them together and then outputting that.
However what actually happens is that it just generates a bunch of sine waves all at the same frequency.
The relevant gen code is:
freq = 100;
sig = 0;
n = 3;
amp = 1 / n;
for (i = 0; i < n; i += 1) {
phase = phasor(freq, 0);
phase_in_rads = phase * twopi;
sig += sin(phase_in_rads) * amp;
freq += 100;
}
out = sig;
You have run into one of the unusual aspects of the genexpr language. Specifically, that "stateful" operators like phasor (stateful because they retain state, in this case, the oscillator phase) are "lexically instanced", which means there's exactly one oscillator for each time you write "phasor" in your code. That means, inside a for loop, each iteration of the loop is using the same phasor oscillator. The same by the way also applies to genexpr functions you write yourself that have any state or stateful operators in them.
Why is genexpr like this? Because a for (or while) loop is computed at runtime, we might not know how many iterations (how many calls to phasor()) there will be, so we can't pre-allocate the unknown amount of memory needed by them. And gen will never allocate memory dynamically during audio processing, because that runs the risk of audio dropouts or crashes.
If you want a fixed number of phasors, you can either implement phasor directly in code and use a [data] to store the phases, or you can "unroll" the loop manually to get the number of instances you want.
In your case in the code above, since n=3, I suggest unrolling:
Param freq(100);
sum = sin(phasor(1*freq) * twopi);
+ sin(phasor(2*freq) * twopi);
+ sin(phasor(3*freq) * twopi);
out = sum/3;
Alternatively, if the per-voice code is going to get more complex, use a function:
voice(n, freq, phasereset) {
return sin(phasor(n*freq, phasereset) * twopi);
}
Param freq(100);
phasereset = in1;
sum = voice(1, freq, phasereset)
+ voice(2, freq, phasereset)
+ voice(3, freq, phasereset);
out = sum/3;
Here's a Data based version, which will let you do the more generic case of variable 'n' (up to the limit of your Data length). But this one will get messier as you add more complexity to per-voice algorithms:
// fundamental frequency
Param freq(100);
// number of voices
// any integer up to the length of the data
Param n(3, max=3);
// stores phase, freq in each data frame
Data phases(3);
sum = 0;
for (i=0; i<n; i+=1) {
// get frequency for this voice:
f = freq*(i+1);
// get state for this voice:
phase = peek(phases, i);
// equivalent to phasor(freq):
phase = wrap(phase + f/samplerate, 0, 1);
// write phase back into [data]:
poke(phases, phase, i);
// do the additive voice:
sum += sin(phase*twopi);
}
out = sum/n;
Relevant threads where this concept has been discussed in more detail:
Very interesting, thanks Graham! I really like that last approach, it seems to work well. So which gen operators are stateful? Or to put it another way, what is safe to call within a loop, and what is not?
Another question -- I thought maybe I could be clever and just use one phasor with a frequency of 1 hz as a sort of "master phasor" and derive other frequencies from that by wrapping it like so:
freq = in1;
phase = phasor(1, 0);
wrapped_phase = wrap(phase * freq);
phase_in_rads = wrapped_phase * twopi;
out = sin(phase_in_rads);
This seems to work fine with a fixed frequency, but when I try to use an envelope to make the frequency change over time it sounds very odd. Any ideas about this? Here's a quick example patch.
Another interesting thing to note is that the last patch that you supplied (the one using Data) doesn't have this issue, I can actually modulate the pitch of that one with an envelope just fine.