Reputation: 498
I am writing some software to create a complex waveform (actually a soundwave) as an array. Starting with some primitive waveforms (sine waves etc), there will be functions which combine them to create more complex waves, and yet more functions which combine those waves, etc.
It might look like:
f(mult(sine(), env(square(), ramp()))
but a lot more complex.
One way of doing this would be to make each function a generator, so that the whole function tree executes once per element with each generator yielding a single value each time.
The array could have several million elements, and the function tree could easily be 10 deep. Would generators be ridiculously inefficient for doing this?
The alternative would be for each function to create and return an entire array. This would presumably be more efficient, but has disadvantages (messier implementation, no results available until the end of the calculation, could use a lot of memory).
They always say you shouldn't try to second guess Python efficiency, but will generators take a long time in this case?
Upvotes: 4
Views: 2798
Reputation: 18950
In my opinion, generators are a good fit for this task.
Some signals have finite time (like an envelope, or a ramp), but some other signals are infinite (like oscillators).
Using generators you should no worry about this aspect, because -like the zip()
function- a function combining (e.g. multiplying) an oscillator with an envelope, would only consume a finite amount of items from oscillator gen, because there's at least one generator which yields a finite number of samples.
Yet, using generators is very elegant and pythonic.
Recall that a generator like this:
def sine(freq):
phase = 0.0
while True:
yield math.sin(phase)
phase += samplerate/freq
is just syntactic sugar for a class like this:
class sine:
def __init__(self, freq):
self.freq = freq
self.phase = 0.0
def __iter__(self):
return self
def __next__(self):
v = math.sin(self.phase)
self.phase += samplerate/freq
return v
# for this infinite gen we never raise StopIteration()
so the performance overhead is not much than any other solution you can handcraft (like the block processing, commonly used in DSP algorithms).
Perhaps you could gain some efficiency if instead of yielding individual samples, you yield blocks of samples (for example 1024 samples at time).
Upvotes: 1
Reputation: 49856
Generators are lazy sequences. They are perfect for use when you have sequences which may be very long, as long as you can operate piecewise (either elementwise, or on reasonably sized chunks).
This will tend to reduce your peak memory use. Just don't ruin that by then storing all elements of the sequence somewhere.
Upvotes: 2