(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.
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
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)
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
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.