August'24: Kamaelia is in maintenance mode and will recieve periodic updates, about twice a year, primarily targeted around Python 3 and ecosystem compatibility. PRs are always welcome. Latest Release: 1.14.32 (2024/3/24)
So you've built your components and wired them up using Pipelines and Graphlines . But what do you do if you want to create or initialise a component at runtime?
Perhaps you can't know the value of some arguments until you start reading that input file. Or maybe you want to process several streams of data in sequence, but the component you want to use isn't designed to process several streams back to back. This is where a component like the Carousel comes in.
The Carousel gives us a way to create a component on-the-fly in
response to being sent a message.
Suppose we want to play an MP3 file ... we could use a simple
pipeline like this:
from Kamaelia.File.Reading import RateControlledFileReader from Kamaelia.Audio.Codec.PyMedia.Decoder import Decoder from Kamaelia.Audio.PyMedia.Output import Output from Kamaelia.Chassis.Pipeline import Pipeline import sys mp3filename=sys.argv[1] Pipeline( RateControlledFileReader( mp3filename, readmode="bytes", rate=256000/8), Decoder("mp3"), Output(sample_rate=44100, channels=2, format="S16_LE"), ).run()
That is all very nice; but what if we get the sample rate, number of
channels or format wrong? We can't get this information until we start
decoding it. If we get it wrong then the audio may be corrupted or
played at the wrong speed!
It would be great if, at runtime, we could create the audio playback
(Output) component in response to receiving a message from the MP3
decoder containing the audio format:
The PyMedia MP3 Decoder component we are using helpfully sends out a
message containing the information we need, so we can use the Carousel
component to do it like this:
from Kamaelia.Chassis.Graphline import Graphline from Kamaelia.Chassis.Carousel import Carousel def makeAudioOutput(metadata): return Output( metadata["sample_rate"], metadata["channels"], metadata["format"] ) Graphline( READ = RateControlledFileReader( mp3filename, readmode="bytes", rate=256000/8), DECODE = Decoder("mp3"), OUTPUT = Carousel( makeAudioOutput ), linkages = { ("READ", "outbox") : ("DECODE", "inbox"), ("DECODE", "outbox") : ("OUTPUT", "inbox"), ("DECODE", "format") : ("OUTPUT", "next"), ("READ", "signal") : ("DECODE", "control"), ("DECODE", "signal") : ("OUTPUT", "control"), } ).run()
This example is wired up using a Graphline component - find out more
about Graphlines here .
The MP3 Decoder component we are using helpfully sends out the format
of the decoded audio out of its "format" outbox, so we link this to the
Carousel's "next" inbox to control it. A message from the decoder wil
look like this:
{ "sample_rate" : 44100, "channels":2, "format":"S16_LE" }
We've also written a function makeAudioOutput(). When called with the message as its argument; it returns a new Output component set up with the right sample rate, number of channels, and format.
We give this function to the Carousel. Note that we don't call it - we just give it the function. The Carousel calls it when it receives a message on its "next" inbox and therefore needs to create the component:
So when the raw audio samples start to arrive at its inbox, there
will be a new Output component already linked in to receive them.
Note that it does not link the "signal" outbox - this is so that when
the component finishes and sends its own shutdown message, this doesn't
get passed on - after all, you might want to reuse the Carousel with
another component.
If you send another message to the "next" inbox, then the component
gets replaced. Any existing component is told to shutdown and is thrown
away as soon as possible, and a new one is created, by calling our
function with the new message as the parameter.
This kind of behaviour is a little like the carousel on an old slide
projector - when you want to move on, the old item is swapped for the
next one. Alternatively think of a fairground merry-go-round carousel -
where one horse comes by after another.
For example, suppose we want to improve our MP3 player by making it play
multiple files back to back. We could put everything in a Carousel, then
when it has finished, it could send us a message. We could then respond
by sending it the next filename to play, and letting it start again.
Something like this:
We can do this by using a Chooser component for the playlist and putting our existing player inside a Carousel. When all the player components finish, our Carousel will send out a "next" message from its "requestNext" outbox, which we can use to cause our Chooser to send back the next filename:
Notice that we can also wire up the "signal" and "control" boxes, so
that when the Chooser has no more names in its playlist, it can tell our
player Carousel to shut down.
So now lets build this! First, lets make a function that we will give
to the Carousel for it to use to create our player:
def makePlayer(mp3filename): return Graphline( READ = RateControlledFileReader( mp3filename, readmode="bytes", rate=256000/8), DECODE = Decoder("mp3"), OUTPUT = Carousel( makeAudioOutput ), linkages = { ("READ", "outbox") : ("DECODE", "inbox"), ("DECODE", "outbox") : ("OUTPUT", "inbox"), ("DECODE", "format") : ("OUTPUT", "next"), ("", "control") : ("READ", "control"), ("READ", "signal") : ("DECODE", "control"), ("DECODE", "signal") : ("OUTPUT", "control"), ("OUTPUT", "signal") : ("", "signal"), } )
This is almost identical to our player from before. Notice we've
added extra links to make sure shutdown messages can get into and out of
the Graphline. This is important, as Carousel will be listening for our
Graphline sending the shutdown message.
Now lets wire it all up! We will use a ForwardIteratingChooser
because it will send a shutdown message once all the filenames have been
iterated over:
from Kamaelia.Util.Chooser import ForwardIteratingChooser filenames = argv[1:] Graphline( PLAYLIST = ForwardIteratingChooser(filenames), PLAYER = Carousel( makePlayer, make1stRequest=True ), linkages = { ("PLAYER", "requestNext") : ("PLAYLIST", "inbox"), ("PLAYLIST", "outbox") : ("PLAYER", "next"), ("PLAYLIST", "signal") : ("PLAYER", "control"), } ).run()
Notice that we have asked the Carousel to make the 1st request. What
this means is that as soon as it starts it will send out its request for
the next item - instead of just waiting. This gets things going.
So there we have it, a simple mp3 playlist system, built entirely in
Kamaelia, using Carousels to create components with the right settings
when we need them.
-- 19 Dec 2006 - Matt Hammond