|
From: Leslie Sanford on 5 May 2007 16:34 "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 5 May 2007 17:29 [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 8 May 2007 05:45 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 8 May 2007 15:06 "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 9 May 2007 05:31 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 -
|
Pages: 1 Prev: OO Books for newbies? Next: RESULT: comp.object.moderated will be removed |