As part of the rsound package, I find myself reinventing data flow languages; or, at least, the parts that I need. I've come to a design question that I don't know how to answer, and I'm hoping that those with more experience can make suggestions.
First: I'm re-inventing dataflow languages because:
- the natural way to specify audio signals is as dataflow networks (e.g., I
have a low-frequency oscillator controlling the frequency of this other
oscillator…), and
- FrTime is sadly not fast enough to use, here.
Also, I'm fine with dataflow, here, I don't need full FRP.
The other connection here is to World-style programming; in a big-bang, you
specify an initial world and then a transition function (or functions), that
map one world to the next. I can't use this framework as-is because of parallel
composition issues; specifically, if I have the two oscillators I describe
above, then the natural way to represent their state would be as a structure
containing the state of each individual component. Allocating a new one of
these 44K times per second uses up a lot of memory really quickly. So, I've
developed my own framework, that uses mutation but hides it from the user.
Using my "network" syntax, I might specify the above as:
(network ()
[lfo (sine-wave 2)]
[out (sine-wave (+ 200 (* 10 lfo)))])
This creates a sine wave at 2 Hz, controlling another sine wave whose frequency
varies between 210 and 190 Hz twice per second.
Everything fine so far.
In order to make this work, we need signals that refer to old values of
themselves, as in the world model. To allow this, I have a "prev" primitive
that allows you to refer to the prior value of some signal node. So, for
instance, here's a simple counter that goes up one each tick:
(define simple-ctr
(network ()
[out (add1 (prev out)) #:init 0]))
So, this counter rises by one on each tick. Note that we need to specify an
initial value, as with big-bang.
BUT, HERE COMES THE PROBLEM.
What should the first value of this signal be? Should it be zero, or should it
be one? Put differently: does this node's updater get called on the first tick,
producing 1, or should we just use the initial value, zero?
Zero is the one I really want, but then I have a problem: how do I distinguish
between clauses, like this one, that should not be evaluated the first time
through, from others, something like this:
[sum (+ sig-a sig-b)]
… that *should* be evaluated the first time through?
My current solution is to treat them uniformly, and to evaluate all of them
each time. This means that I wind up with dreadful hacks like this one:
[out (add1 (prev out)) #:init -1]
… so that the -1 gets bumped up to a zero on the first output. It turns out
that this trick is fragile; if you don't know what the increment will be, you
can't accurately "pre-decrement" it.
I can imagine doing something more complicated, but what I really want to ask
is this: for those of you with experience in other dataflow languages, how do
they solve this?
Sorry for the long e-mail; I'm hoping that someone can point to a nice solution
that exists in another language!
John
smime.p7s
Description: S/MIME cryptographic signature
____________________ Racket Users list: http://lists.racket-lang.org/users

