From: Leslie Sanford on
"Mark Nicholls" wrote:
> "Leslie Sanford" write:
>>
>> Actually, I've been thinking about this design, and it may not be the
>> best example of 'peer to peer'. An alternative would be to have each
>> module announce when it has synthesized its output. We'd start out
>> with modules with no inputs. Each of those modules would synthesize
>> their output in response to a timer event. In turn, they would send
>> an "I'm Done" message to each module connected to their output. In
>> response, the modules receiving the message would keep track of when
>> they had received all of the messages from all of the modules
>> connected to their input. When this happens, they would synthesize
>> their own output and send messages along to modules connected to
>> their output. And so on.
>
>> This would require state machine logic in modules that have inputs to
>> keep things synchronized in response to the messages they receive.
>> I'll have to give this more thought.- Hide quoted text -
>>
>> - Show quoted text -
>
> I don't know......my gut feeling is 'pipe and filters' buschman.
>
> Most of the references on google seem to make it sound harder than it
> really is......

I've looked into using a pipes and filter architecture. I usually get
confused in the details. It's easy enough to understand how to implement
it when you have a pipeline that doesn't split:

A-->B-->C-->D-->

Just have a chain of objects that either call some kind of "Write"
method on the next object in the chain (the push model), or have the
objects call a "Read" method on the previous object in the chain (the
pull model).

If you have more than one pipe connected to one filter (is that even
allowed?), everything works ok if you have a pull architecture:

A--+
|
+-->C--D-->
|
B--+

D can call "Read" on C and C in turn can call Read on A and B. When it's
a push architecture with A and B calling "Write" on C, it's not clear to
me how C knows when it's ready to call Write on D or how it should
distinguish between the two Write operations, one from A and the other
from B. There's a synchronization problem.

Likewise, we can have a pipeline split into several pipes:

+-->B-->
|
A--+
|
+-->C-->

This works fine in a push architecture, but with a pull architecture,
A's Read could be called twice, once by B and once by C thus B and C
would be working with different data.

So it seems that a push architecture can handle the pipeline forking
into several pipes but has a harder time handling several pipes
streaming into one. The pull architecture has just the opposite problem.

The way I'm currently handling this is to not use a push or pull
architecture at all. Each filter has an ordering number. It's 1 plus the
number of other filters connected to it. The filters are sorted by this
ordering number in ascending order. Each time the pipeline needs to
filter data, the engine iterates through the sorted collection of
filters invoking a process method on each filter. Thus filters with no
connections (data sources) are invoked first while filters with the most
connections are invoked last.

The effect of this approach is that the data processed by one filter is
ready to be processed by the next filter; it takes care of the
synchronizing problem. Reading data from a filter is a passive operation
so several filters can read from the same filter without changing its
state. This seems to take care of the problems described above. It also
means that the engine that drives all of this can be reused no matter
what type of filters are in the pipeline.

It requires, though, that someone know which filter is the ultimate
endpoint. It should be the filter with the most connections. One
invariant I've thought of implementing is to make sure that there is
always one and only one filter that has more connections than any of the
others. The engine could recognize this filter as the last one in the
chain of filters.

I'd appreciate any thoughts anyone would have on this approach. Thanks.






From: Dmitry A. Kazakov on
[It is a mess, I have data-flow architectures]

On Sat, 5 May 2007 15:34:20 -0500, Leslie Sanford wrote:

> The way I'm currently handling this is to not use a push or pull
> architecture at all. Each filter has an ordering number. It's 1 plus the
> number of other filters connected to it. The filters are sorted by this
> ordering number in ascending order. Each time the pipeline needs to
> filter data, the engine iterates through the sorted collection of
> filters invoking a process method on each filter. Thus filters with no
> connections (data sources) are invoked first while filters with the most
> connections are invoked last.

Yes, you can always order a tree (or forest).

> The effect of this approach is that the data processed by one filter is
> ready to be processed by the next filter; it takes care of the
> synchronizing problem. Reading data from a filter is a passive operation
> so several filters can read from the same filter without changing its
> state. This seems to take care of the problems described above. It also
> means that the engine that drives all of this can be reused no matter
> what type of filters are in the pipeline.
>
> It requires, though, that someone know which filter is the ultimate
> endpoint.

Hmm, why? Once you have found an order, there is the first and the last
elements. Your method is OK, however a simple breadth-first traversal would
do it as well.

In the middleware we designed, the computable channels are like your
filters. Each one is a formula which may refer to the values of other
channels. For each channel a dependency list is evaluated upon compilation.
Then channels are ordered according to their lists. The run-time engine
evaluates all formulas in that order if any of the channels it depends gets
changed. Timer is a channel, so periodically computed channels also work
this way.

> I'd appreciate any thoughts anyone would have on this approach. Thanks.

An alternative approach, (also when you have cycles for instance), is that
you make a process vectors out of all inputs and outputs. The filters are
activated in an arbitrary order. They read the input vector write the
output one. When this cycle completes, outputs are transferred to the
inputs and the new cycle begins. This approach requires no ordering and can
handle any directed graph of connections. The price to pay is that if you
have long chains you will need many cycles to get a response through. This
is usually solved by introducing subcycles, N > the longest path in the
graph. Another problem is race conditions along the paths of different
lengths meeting in a node. That is again solved by brute force of
subcycles.

I bet there are lots of [expensive] tools to solve this problem and
optimize the solution, provided, the graph is static. In my case we could
not use anything like that because our channels can be created and
destroyed on-the-fly.

--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de
From: Mark Nicholls on
On 5 May, 21:34, "Leslie Sanford" <jabberdab...(a)hotmail.com> wrote:
> "Mark Nicholls" wrote:
> > "Leslie Sanford" write:
>
> >> Actually, I've been thinking about this design, and it may not be the
> >> best example of 'peer to peer'. An alternative would be to have each
> >> module announce when it has synthesized its output. We'd start out
> >> with modules with no inputs. Each of those modules would synthesize
> >> their output in response to a timer event. In turn, they would send
> >> an "I'm Done" message to each module connected to their output. In
> >> response, the modules receiving the message would keep track of when
> >> they had received all of the messages from all of the modules
> >> connected to their input. When this happens, they would synthesize
> >> their own output and send messages along to modules connected to
> >> their output. And so on.
>
> >> This would require state machine logic in modules that have inputs to
> >> keep things synchronized in response to the messages they receive.
> >> I'll have to give this more thought.- Hide quoted text -
>
> >> - Show quoted text -
>
> > I don't know......my gut feeling is 'pipe and filters' buschman.
>
> > Most of the references on google seem to make it sound harder than it
> > really is......
>
> I've looked into using a pipes and filter architecture. I usually get
> confused in the details. It's easy enough to understand how to implement
> it when you have a pipeline that doesn't split:

it is, don't get me wrong I tend to use it stylistically.

>
> A-->B-->C-->D-->

OK

>
> Just have a chain of objects that either call some kind of "Write"
> method on the next object in the chain (the push model), or have the
> objects call a "Read" method on the previous object in the chain (the
> pull model).

OK

>
> If you have more than one pipe connected to one filter (is that even
> allowed?), everything works ok if you have a pull architecture:
>
> A--+
> |
> +-->C--D-->
> |
> B--+
>
> D can call "Read" on C and C in turn can call Read on A and B. When it's
> a push architecture with A and B calling "Write" on C, it's not clear to
> me how C knows when it's ready to call Write on D or how it should
> distinguish between the two Write operations, one from A and the other
> from B. There's a synchronization problem.

yep...so in the example above, read would seem easier to implement.

>
> Likewise, we can have a pipeline split into several pipes:
>
> +-->B-->
> |
> A--+
> |
> +-->C-->
>
> This works fine in a push architecture, but with a pull architecture,
> A's Read could be called twice, once by B and once by C thus B and C
> would be working with different data.

correct.

>
> So it seems that a push architecture can handle the pipeline forking
> into several pipes but has a harder time handling several pipes
> streaming into one. The pull architecture has just the opposite problem.

yep....

I'm not too sure of the application.....if it's some sort of batch
process, I would set the roots to be specific input values....and then
pull the results out of the top (leaves) for a fixed set of
inputs....if you want you can cache results in intermediate filters,
as the values shouldn't change.

>
> The way I'm currently handling this is to not use a push or pull
> architecture at all. Each filter has an ordering number. It's 1 plus the
> number of other filters connected to it. The filters are sorted by this
> ordering number in ascending order. Each time the pipeline needs to
> filter data, the engine iterates through the sorted collection of
> filters invoking a process method on each filter. Thus filters with no
> connections (data sources) are invoked first while filters with the most
> connections are invoked last.

oooo....sounds complicated.

I think you've gone a step further and seperated the processesing,
from the representation.....which is absolutely fine.....your using
pipes and filters to represent the processing and something else
(visitor?) to process the data.

>
> The effect of this approach is that the data processed by one filter is
> ready to be processed by the next filter; it takes care of the
> synchronizing problem. Reading data from a filter is a passive operation
> so several filters can read from the same filter without changing its
> state. This seems to take care of the problems described above. It also
> means that the engine that drives all of this can be reused no matter
> what type of filters are in the pipeline.
>
> It requires, though, that someone know which filter is the ultimate
> endpoint. It should be the filter with the most connections. One
> invariant I've thought of implementing is to make sure that there is
> always one and only one filter that has more connections than any of the
> others. The engine could recognize this filter as the last one in the
> chain of filters.

personally I don't like it....your processing process now seems to be
leaking into your representation of the process.

>
> I'd appreciate any thoughts anyone would have on this approach. Thanks.- Hide quoted text -
>
> - Show quoted text -

Can you not just pull the data out of the leaves?

From: Leslie Sanford on

"Mark Nicholls" wrote:
> "Leslie Sanford" wrote:

<snip>

>> I've looked into using a pipes and filter architecture. I usually get
>> confused in the details. It's easy enough to understand how to
>> implement it when you have a pipeline that doesn't split:

<snip>

>> So it seems that a push architecture can handle the pipeline forking
>> into several pipes but has a harder time handling several pipes
>> streaming into one. The pull architecture has just the opposite
>> problem.
>
> yep....
>
> I'm not too sure of the application.....if it's some sort of batch
> process, I would set the roots to be specific input values....and then
> pull the results out of the top (leaves) for a fixed set of
> inputs....if you want you can cache results in intermediate filters,
> as the values shouldn't change.
>
>>
>> The way I'm currently handling this is to not use a push or pull
>> architecture at all. Each filter has an ordering number. It's 1 plus
>> the number of other filters connected to it. The filters are sorted
>> by this ordering number in ascending order. Each time the pipeline
>> needs to filter data, the engine iterates through the sorted
>> collection of filters invoking a process method on each filter. Thus
>> filters with no connections (data sources) are invoked first while
>> filters with the most connections are invoked last.
>
> oooo....sounds complicated.
>
> I think you've gone a step further and seperated the processesing,
> from the representation.....which is absolutely fine.....your using
> pipes and filters to represent the processing and something else
> (visitor?) to process the data.

Not really Visitor, I think. Maybe I can clarify my approach with some
code. In my toolkit I have a Synthesizer class. It represents a musical
synthesizer. The Synthesizer uses several voices. Each voice represents
functionality for playing a sound. The number of sounds a synthesizer
can play at once is called its polyphony. Hence the number of voices it
has defines it polyphony.

I have an abstract class called Voice representing a voice within a
synthesizer. Normally, I shy away from implementation inheritance, but
I've found it useful here. The Voice class is meant to be the base class
for custom voice classes users can define themselves.

Typically, each voice has its own collection of synth type components
such as oscillators, filters, envelopes, etc... When a voice is played,
i.e. when the synthesizer receives a note-on command and assigns a voice
to play the note, it uses its internal components to synthesize the
sound. The Synthesizer mixes the outputs of all playing voices into a
single buffer that is fed to a waveform output device. The device plays
the waveform data resulting in you hearing it at your computer's
speakers.

Those components I mentioned are all represented by their respective
classes. So we have an Oscillator class, a Filter class, and so on. Each
of those classes implement a common interface. They provide an "Ordinal"
number that I've described before. The more inputs a component has, the
higher its ordinal number, i.e. the further down it is in the chain of
components.

So if we're implementing our own Voice derived class, in the
constructor, we could do something like this:

public MyVoice()
{
osc1 = new Oscillator();
osc2 = new Oscillator();
filter = new StateVariableFilter();
amEnvelope = new AdsrEnvelope();
fmEnvelope = new AdsrEnvelope();

// Connect them together:
filter.Input1 = osc1;
filter.Input2 = osc2;
filter.FmModulator = fmEnvelope;
filter.AmModulator = amEnvelope;

// Call the Add method in the Voice base class to add all of the
// components to the Voice's component collection.
Add(osc1);
Add(osc2);
Add(filter);
Add(amEnvelope);
Add(fmEnveope);
}

Just a basic synthesizer set up: Two oscillators feeding into one
filter. An envelope modulating the filter's cutoff frequency and another
envelope modulating its amplitude.

fmEnvelope
osc1--+ |
| v
+-->filter-->
| ^
osc2--+ |
amEnvelope

Each of the synth components are added to the base class's collection of
components. They're sorted in ascending order according to their ordinal
number (I've left some details out here such as the functionality for
changing the setup dynamically).

When it's time for a Voice to synthesize some waveform data, the
Synthesizer calls "Synthesize" on each of the currently playing voices.
In turn, each playing voice calls Synthesize on each of its components:

public void Synthesize(int startIndex, int count)
{
foreach(ISynthComponent component in components)
{
component.Synthesize(startIndex, count);
}
}

That's all there is to it. The order in which Synthesize is called on
each component ensures that the component's buffer is ready to be used
by any connected components. So in our Filter, we could have something
like this:

public void Synthesize(int startIndex, int count)
{
double in1, in2;
double output;

for(int i = startIndex; i < startIndex + count; i++)
{
// Access the buffers in the input components.
in1 = input1[i]; // Each component allows index access to its
// buffer.
in2 = input2[i];

// Do filtering stuff...

buffer[i] = output;
}
}

And the inputs would be up to date, everything's in sync.

Anyway, that was a bit longer than I planned on writing. But it gives
you a good idea of the architecture I've been designing.


>> The effect of this approach is that the data processed by one filter
>> is ready to be processed by the next filter; it takes care of the
>> synchronizing problem. Reading data from a filter is a passive
>> operation so several filters can read from the same filter without
>> changing its state. This seems to take care of the problems described
>> above. It also means that the engine that drives all of this can be
>> reused no matter what type of filters are in the pipeline.
>>
>> It requires, though, that someone know which filter is the ultimate
>> endpoint. It should be the filter with the most connections. One
>> invariant I've thought of implementing is to make sure that there is
>> always one and only one filter that has more connections than any of
>> the others. The engine could recognize this filter as the last one in
>> the chain of filters.
>
> personally I don't like it....your processing process now seems to be
> leaking into your representation of the process.
>
>>
>> I'd appreciate any thoughts anyone would have on this approach.
>> Thanks.
>
> Can you not just pull the data out of the leaves?

Yeah, that's one possibility. This is realtime processing, though, and I
need to make things as efficient as possible. Mixing the output of
several leaves/components together will take time, so I was thinking
that a reasonable limit to enforce would be to make sure there's always
a root component representing the end of the chain of components. Then
the Voice can just read its data. This endpoint component would just be
the last component in the collection of sorted components. So it would
be easy enough to access provided that the this limit is enforced.




From: Mark Nicholls on

ooo long post.

On 8 May, 20:06, "Leslie Sanford" <jabberdab...(a)hotmail.com> wrote:
> "Mark Nicholls" wrote:
> > "Leslie Sanford" wrote:
>
> <snip>
>
> >> I've looked into using a pipes and filter architecture. I usually get
> >> confused in the details. It's easy enough to understand how to
> >> implement it when you have a pipeline that doesn't split:
>
> <snip>
>
>
>
>
>
> >> So it seems that a push architecture can handle the pipeline forking
> >> into several pipes but has a harder time handling several pipes
> >> streaming into one. The pull architecture has just the opposite
> >> problem.
>
> > yep....
>
> > I'm not too sure of the application.....if it's some sort of batch
> > process, I would set the roots to be specific input values....and then
> > pull the results out of the top (leaves) for a fixed set of
> > inputs....if you want you can cache results in intermediate filters,
> > as the values shouldn't change.
>
> >> The way I'm currently handling this is to not use a push or pull
> >> architecture at all. Each filter has an ordering number. It's 1 plus
> >> the number of other filters connected to it. The filters are sorted
> >> by this ordering number in ascending order. Each time the pipeline
> >> needs to filter data, the engine iterates through the sorted
> >> collection of filters invoking a process method on each filter. Thus
> >> filters with no connections (data sources) are invoked first while
> >> filters with the most connections are invoked last.
>
> > oooo....sounds complicated.
>
> > I think you've gone a step further and seperated the processesing,
> > from the representation.....which is absolutely fine.....your using
> > pipes and filters to represent the processing and something else
> > (visitor?) to process the data.
>
> Not really Visitor, I think. Maybe I can clarify my approach with some
> code. In my toolkit I have a Synthesizer class. It represents a musical
> synthesizer. The Synthesizer uses several voices. Each voice represents
> functionality for playing a sound. The number of sounds a synthesizer
> can play at once is called its polyphony. Hence the number of voices it
> has defines it polyphony.
>
> I have an abstract class called Voice representing a voice within a
> synthesizer. Normally, I shy away from implementation inheritance, but
> I've found it useful here. The Voice class is meant to be the base class
> for custom voice classes users can define themselves.
>
> Typically, each voice has its own collection of synth type components
> such as oscillators, filters, envelopes, etc... When a voice is played,
> i.e. when the synthesizer receives a note-on command and assigns a voice
> to play the note, it uses its internal components to synthesize the
> sound. The Synthesizer mixes the outputs of all playing voices into a
> single buffer that is fed to a waveform output device. The device plays
> the waveform data resulting in you hearing it at your computer's
> speakers.
>
> Those components I mentioned are all represented by their respective
> classes. So we have an Oscillator class, a Filter class, and so on. Each
> of those classes implement a common interface. They provide an "Ordinal"
> number that I've described before. The more inputs a component has, the
> higher its ordinal number, i.e. the further down it is in the chain of
> components.
>
> So if we're implementing our own Voice derived class, in the
> constructor, we could do something like this:
>
> public MyVoice()
> {
> osc1 = new Oscillator();
> osc2 = new Oscillator();
> filter = new StateVariableFilter();
> amEnvelope = new AdsrEnvelope();
> fmEnvelope = new AdsrEnvelope();
>
> // Connect them together:
> filter.Input1 = osc1;
> filter.Input2 = osc2;
> filter.FmModulator = fmEnvelope;
> filter.AmModulator = amEnvelope;
>
> // Call the Add method in the Voice base class to add all of the
> // components to the Voice's component collection.
> Add(osc1);
> Add(osc2);
> Add(filter);
> Add(amEnvelope);
> Add(fmEnveope);
>
> }


hmmmm......I don't pretend to understand you completely....I think
that would take too long, my best approach is to throw hopefully
useful suggestions.

First I would keep pipes and filters in mind as a picture but possibly
do it using decorators, a 'filter' being a specific sort of
pipe....despite working in television I actually don't know how audio
works....I just know its harder than video....which is easy....so
excuse my ignorance, I will use naive abstactions like 'Sound'.

(lets ignore the visitor part and assume each pipe is responsible for
processing data directly)

// your common interface
interface IPipe
{
Sound GetSound();
}

class CStateVariableFilter : IPipe
{
IPipe input1;
IPipe input2;

CStateVariableFilter(IPipe input1,IPipe input2)
{
this.input1 = input1;
this.input2 = input2;
}

Sound GetSound()
{
.... so whatever with the inputs to get the 'sound'.
}
}

// source oscilator
class Oscilator : IPipe
{
Sound GetSound()
{
...
}
}

does a filter have to know about the fmModulator and amModulator
directly? or can the processing be split into new 'pipes'?

if so.....

class AmModulator : IPipe
{
IPipe input;

AmModulator(IPipe input)
{
this.input = input;
}

Sound GetSound()
{
....
}
}

class FmModulator : IPipe
{
IPipe input;

FmModulator(IPipe input)
{
this.input = input;
}

Sound GetSound()
{
....
}
}

now the client looks something like

IPipe CreateVoice()
{
Oscillator osc1 = new Oscillator();
Oscillator osc2 = new Oscillator();
StateVariableFilter filter = new StateVariableFilter(osc1,osc2);
AmModulator amEnvelope = new AmModulator(filter);
FmModulator fmEnvelope = new AdsrEnvelope(amEnvelope);

return FmModulator;
}


>
> Just a basic synthesizer set up: Two oscillators feeding into one
> filter. An envelope modulating the filter's cutoff frequency and another
> envelope modulating its amplitude.
>
> fmEnvelope
> osc1--+ |
> | v
> +-->filter-->
> | ^
> osc2--+ |
> amEnvelope
>
> Each of the synth components are added to the base class's collection of
> components. They're sorted in ascending order according to their ordinal
> number (I've left some details out here such as the functionality for
> changing the setup dynamically).
>
> When it's time for a Voice to synthesize some waveform data, the
> Synthesizer calls "Synthesize" on each of the currently playing voices.
> In turn, each playing voice calls Synthesize on each of its components:
>
> public void Synthesize(int startIndex, int count)
> {
> foreach(ISynthComponent component in components)
> {
> component.Synthesize(startIndex, count);
> }
>
> }
>
> That's all there is to it. The order in which Synthesize is called on
> each component ensures that the component's buffer is ready to be used
> by any connected components. So in our Filter, we could have something
> like this:
>
> public void Synthesize(int startIndex, int count)
> {
> double in1, in2;
> double output;
>
> for(int i = startIndex; i < startIndex + count; i++)
> {
> // Access the buffers in the input components.
> in1 = input1[i]; // Each component allows index access to its
> // buffer.
> in2 = input2[i];
>
> // Do filtering stuff...
>
> buffer[i] = output;
> }
>
> }
>
> And the inputs would be up to date, everything's in sync.
>
> Anyway, that was a bit longer than I planned on writing. But it gives
> you a good idea of the architecture I've been designing.

OK I half understand it......I think in my setup the 'Sound'
abstraction would correspond to your buffer array.

In my construction there should be no need for this 'ordinal' number
thing, that I don't completely understand.

>
>
>
>
>
> >> The effect of this approach is that the data processed by one filter
> >> is ready to be processed by the next filter; it takes care of the
> >> synchronizing problem. Reading data from a filter is a passive
> >> operation so several filters can read from the same filter without
> >> changing its state. This seems to take care of the problems described
> >> above. It also means that the engine that drives all of this can be
> >> reused no matter what type of filters are in the pipeline.
>
> >> It requires, though, that someone know which filter is the ultimate
> >> endpoint. It should be the filter with the most connections. One
> >> invariant I've thought of implementing is to make sure that there is
> >> always one and only one filter that has more connections than any of
> >> the others. The engine could recognize this filter as the last one in
> >> the chain of filters.
>
> > personally I don't like it....your processing process now seems to be
> > leaking into your representation of the process.
>
> >> I'd appreciate any thoughts anyone would have on this approach.
> >> Thanks.
>
> > Can you not just pull the data out of the leaves?
>
> Yeah, that's one possibility. This is realtime processing, though, and I
> need to make things as efficient as possible. Mixing the output of
> several leaves/components together will take time, so I was thinking
> that a reasonable limit to enforce would be to make sure there's always
> a root component representing the end of the chain of components.

OK, so there should be some sort of 'Mix' pipe at the end....that
seens sensible....

> Then
> the Voice can just read its data. This endpoint component would just be
> the last component in the collection of sorted components. So it would
> be easy enough to access provided that the this limit is enforced.- Hide quoted text -

OK but the 'stream' of sound that comes out of a synthesiser is 1
stream (OK, we can go into stereo and dolby n:m, but you see what I
mean)......

so I think it is reasonable to have a single end 'Mix' pipe......but
I'm still not convinced by the adding of components to a simple array
and ordering them by this number....it may work....I just think the
decorator/pipe filter approach is cleaner.......but I'm not really in
a position to judge if it works......only you can say that,

>
> - Show quoted text -- Hide quoted text -
>
> - Show quoted text -