I'm playing around with some additive synthesis in Python.
I've implemented an oscillator as a generator that takes a number of parameters. It is then possible to mix multiple oscillators using zip (or better, itertools.izip) over them and doing a (weighted) sum.
However, I wanted to be able to factor out common arguments to the oscillators so I didn't have to specify the frequency of each one individually.
I knew functools.partial would be part of the solution but it took me a while to work out how to combine its use with generators and itertools.izip.
Here is a simplified progression of what I came up with.
Rather than use oscillators, let's just imagine with have a generator that works a lot like xrange:
def gen1(start, stop, step): n = start while n <= stop: yield n n += step
then we can combine multiple generators and, say, sum the corresponding elements like this:
for x in zip(gen1(10, 20, 2), gen1(10, 25, 3)): print sum(x),
Let's abstract this into a function that takes generators as arguments (and uses itertools.izip)
def mixer1(*generators): return (sum(x) for x in izip(*generators))
for x in mixer1(gen1(10, 20, 2), gen1(10, 25, 3)): print x,
mixer1 is similar to my mixer (although without weighting)
But now say we wanted to factor out the common start parameter. First we need a partial version of function gen1:
gen2 = lambda **kwargs: partial(gen1, **kwargs)
This allows one to say
partial_gen = gen2(stop=20, step=2)
and then later call
partial_gen(start=10)
to get the generator.
But what we now need is a new version of the mixer that takes the extra keyword args and passes them in to each partial function to turn them back into generators:
def mixer2(*generators, **kwargs): return mixer1(*[gen(**kwargs) for gen in generators])
and now we can say:
for x in mixer2(gen2(stop=20, step=2), gen2(stop=25, step=3), start=10): print x,
Here's the final version:
gen2 = lambda **kwargs: partial(gen1, **kwargs)
def mixer3(*generators, **kwargs): return (sum(x) for x in izip(*[gen(**kwargs) for gen in generators]))
The real thing is a little more involved because of the weighted summing, etc but the hard parts are shown.