(How would I) Moosify this!
In my previous post, I asked for critique and suggestions for a Moose alternative to my app's handrolled AUTOLOAD implementation of
- constructor attributes overriding same-named object methods, and
- constructor attributes overriding object proxy methods
I asked, because this is the most complicated behavior of my OO code, and would be a key issue in porting the code to Moose. I also wanted to know if there would be other benefits to using Moose for this app.
Unlike my earlier OO-related posts, no comments appeared.
After some days, Aristotle replied that he believes my question was Warnock'ed (ignored) because the description lacked specifics.
Here is my attempt to provide them. Since I'm not sure exactly what to include and what to exclude, I'm documenting the process of generating the signal routing specification for a run of the Ecasound audio engine.
The big picture - a simple recording operation
Say I have a project with three tracks: sax, piano, drums. I want to record only drums, a mono signal, from the soundcard channel 1. I set drums to REC. I set piano and sax to OFF. Something has to happen before I can press START.
Anatomy of a Track object
Here are relevant attributes of the Track object:
name: drums
class: Track
n: 3 # unique integer index
group: Main # bus affiliation
rw: REC # could be REC, MON or OFF
source_id: 1 # input device ID, here the soundcard channel
source_type: soundcard # input device type
send_id: ~ # aux output device ID
send_type: ~ # aux output device type
width: 1 # number of channels in the input signal
Routing considerations
The 'drums' track belongs to Bus 'Main'. All tracks in Main automatically provide a signal to the 'Master' track. Master connects to the soundcard by default. It serves as a mixer, providing effects and a fader.
Each track has a raw (prefader) input signal as well as a "cooked" (postfader) signal that has undergone effects processing. Mono input signals get copied to allow for pan control and stereo monitoring.
To accomplish the routing and recording, we generate a file called a 'chain setup' that is used to configure Ecasound, the audio engine.
The desired output - an Ecasound chain setup
Here is the output we want:
# audio inputs
-a:1 -i:loop,Master_in
-a:3,R3 -i:alsa,default
# post-input processing
-a:3 -chcopy:1,2
# audio outputs
-a:1 -o:alsa,default
-a:3 -o:loop,Master_in
-a:R3 -f:s16_le,1,44100,i -o:/home/jroth/nama/test0329/.wav/drums_1.wav
In brief, each of the '-a' statements names one or more chains. Each chain has one input and one output (specified by -i and -o). Loop devices such as 'loop,Master_in' allow one chain to connect to another, one chain to have multiple inputs our outputs.
Track objects in Nama map to Ecasound chains. For the chain setup, we use Track indices rather than names as arguments to '-a'.
Generating the routing graph
To generate a chain setup, we first create a graph based on the following:
* the state of Tracks
* the state of Buses
* various global variables
* a specific user command
Here is the graph for this example:
x soundcard_in # endpoint (signal input)
x | |
x drums drums_rec_file # Tracks
x | |
x | wav_out # endpoint (signal output)
x |
x Master_in # endpoint (loop device)
x |
x Master # Track
x |
x soundcard_out # endpoint (signal output)
Two observations:
We have added a temporary track 'drumsrecfile' (an alias to 'drums') to provide a chain for recording the signal.
We have inserted a loop device 'Master_in' to be able to connect 'drums' to 'Master'.
When we create the graph, we may supply attributes to the nodes or edges to control behavior of the back end.
For example, the chain_id for the edge (chain) corresponding the temporary track 'drums_rec_file' is set to 'R3', overriding the default value (which would be the track index).
As another example, when a Bus routes a signal, it sets the 'send_type' and 'send_id' attributes in the graph because it doesn't care about the track's own values for these attributes.
Processing the Graph
Now we are ready to process our graph. We want to use each edge of the graph to generate the lines of our chain setup.
We start with a dispatch() routine that creates an IO object for each edge of the graph. The object then generates either an input clause (-i) or an output clause (-o).
The following output lists the class of each object generated from the above graph, and the corresponding attributes that dispatch() passes to the object constructor.
Class: IO::from_soundcard_device
---
chain_id: 3
endpoint: soundcard_in
track: drums
Class: IO::to_loop
---
chain_id: 3
device_id: loop,Master_in
endpoint: Master_in
track: drums
Class: IO::from_loop
---
chain_id: 1
device_id: loop,Master_in
endpoint: Master_in
track: Master
Class: IO::to_soundcard_device
---
chain_id: 1
endpoint: soundcard_out
track: Master
Class: IO::from_soundcard_device
---
chain_id: R3
endpoint: soundcard_in
mono_to_stereo: ''
track: drums_rec_file
Class: IO::to_wav
---
chain_id: R3
endpoint: wav_out
mono_to_stereo: ''
track: drums_rec_file
Discussion:
The 'endpoint' maps to the class, and the class provides methods to generate the output.
The track name is provided to enable the class to find other attributes, such as signal width, or the name of the output file.
Generally the input classes convert a mono signal to stereo; we need to suppress behavior when we are recording a signal. Hence we pass a 'mono_to_stereo' attribute that overrides the output of eponymous object method.
As I write this, I can see that some override complexity could be reduced by specifying whether the signal is a 'raw' (prefader) signal or 'cooked' (postfader) signal.
There is flexibility from being able to pass attributes that override the result of any object method. I could potentially modify the methods to provide this behavior rather than use AUTOLOAD.
You do that by writing the blurb on the “Body” tab in the post editor, and the rest of the post (which you want to place after the jump) on the “Extended” tab. I’ve used my admin access to move the text for you on this post – just look at what I did.
Thanks for the formatting help!
Having written that longish post, I think that I could certainly accomplish using Moose what I had previous implemented using standard perl OO (SPOO?)
I think that the weaknesses in the app's organization are more likely in other places than its OO, however as I get the opportunity, it will be interesting to experiment with different forms of OO. I would especially like to get some facility with roles.