Recording from Multiple MIDI Ports
Multiport recording implementation June 27, 2004
The implementation of the multiport recording functionality can be phased out, each step with separate development, test and debugging. At the end of each step, the changes will be committed using a branch created for the project, named 'multiport_recording'. Each step is an incremental and accumulative approach to the final goal: to have several input sources working at once in Rosegarden, and to have a smart and comfortable management of input subscriptions. This feature is limited to the ALSA driver.
Split the single Rosegarden port into an input port and an output one. This is not strictly necessary, but is better for readability and debugging purposes. The input port will be created with automatic timestamping in real time units. This allow the connections made with external programs behave like the internally made ones. The output port will be hidden, at least for polite programs (kaconnect). The client name will be renamed to only “Rosegarden”. Each port will have the following properties:
#0, name: Rosegarden input, capabilities: SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE #1, name: Rosegarden output, capabilities: SND_SEQ_PORT_CAP_READ
The AlsaDriver's variable member m_port will be replaced by two: m_inputport and m_outputport (both private). All the changes involve only the files sound/AlsaDriver.cpp and sound/AlsaDriver.h
At this point, we will be able to make subscriptions to the Rosegarden input port with a command like this:
$ aconnect 72:0 Rosegarden:0
Yes, it is not a mistake. You can use the client name instead the port name. We know that the input port number will be 0, and no options are required, because that has been done at input port creation time. Output port connection management isn't allowed outside of Rosegarden.
The selected input port is stored in the file “rosegardenrc”, section ”[SequencerOptions]”, key “midirecorddevice”. This was a single value (a device number), but it will be a list, now. So, the configuration files will be compatible, but the values are stored into a QStringList in the program, and must be read with KConfig::readListEntry()
The message id MappedEvent::SystemRecordDevice was used by the GUI to tell the sequencer that a new device is selected for recording. The only change required is to use another constructor with one more parameter to disconnect a recording port, too. The MappedEvent object will be constructed with the device as Data1 and true/false in Data2. See sequencemanager.cpp:1380 and devicemanager.cpp:460. When this message was received in AlsaDriver, the old connection was removed, and a new one was established. The new behavior is to only perform the requested operation (connect/disconnect) upon the indicated device.
The MappedEvent::SystemUpdateInstruments message is generated by a timer each 3 seconds when the ALSA port list has changed. It is necessary to generate it also when the connections to the Rosegarden input port has changed (checkForNewClients). Here may be useful to complement the 3 seconds timer with a subscription to the ALSA System:Announce port. Another needed change involves sound/MappedDevice, and base/MidiDevice. Both classes have now a boolean m_recording data member and public accesors.
Files Changed: (gui/) rosegardenguidoc.cpp, devicemanager.cpp, sequencemanager.cpp, (sound/) AlsaDriver.cpp, MappedDevice.cpp (base/) MidiDevice.C
At this point we can connect a external port to Rosegarden (with aconnect), and it will be reflected in the Studio window. We can also connect several recording inputs in the Studio window, and disconnect any connection. The port connection state will be saved in the resource settings file (rosegardenrc), and restored again in the next program run.
There is a funny behavior, though. If you connect another input device using a external program, like aconnect, the connection is shown in the Studio window, but it is not saved to the configuration file, so it won't be restored the next time rosegarden is run. Also, externally made connections take less precedence over persistent connections, so if you unsubscribe a saved (in rosegardenrc) connection with an external program, it will be restored by rosegarden as soon as the change is detected, obstinately.
We can store for each event recorded a set of properties. Two new properties will be created, with names “recorded-port” and “recorded-channel”. The former will hold the ALSA client:port pair of the originating event, and the latter the original MIDI channel. These properties will be used for future functionalities as “Split by channel” or “Split by input port” dialogs, similar to the “Split by pitch” one.
The implementation should modify MappedEvent class, including two new properties: recordedPort and recordedChannel, with the corresponding setters/getters. Store these properties in AlsaDriver::getMappedComposition(), when applicable. Note and other Channel Events have channel information, and can be retrieved with event→data.note.channel or event→data.control.channel. The property value for recordedPort can be retrieved from event→source.client and event→source.port (two integers); event→source is a snd_seq_addr_t. Files sound/MappedEvent.h and sound/MappedEvent.cpp
It is necessary also to modify rosegardenguidoc.cpp, method RosegardenGUIDoc::insertRecordedMidi(), inserting the new properties in rEvent. The new properties are non-persistent. See base/Event.[C|h]
Phase 3 can be delayed until it is required by a real functionality.
You can record from several input sources, but (of course) all the inputs are merged on a single output port. They are also merged in a single segment. The note duration detection is based on a single map (m_noteOnMap, member of SoundDriver). The map handles a event for each noteon received, and it's updated with the note length when a noteoff is received. When you record from several input sources, two musicians can collide in the same note, and one is lost (and the other note may have a wrong length).
To solve this issue, several changes are needed. m_noteOnMap is a SoundDriver data member but it's not referred to in any SoundDriver code and there are no public SoundDriver methods using it. Ergo, there's no compelling reason it should be a member of SoundDriver rather than the subclasses (AlsaDriver and ArtsDriver). And it's a bit of a hack anyway; conceptually it's really a map<ChannelNo, map<Pitch, MappedEvent *> > but the channel number and pitch have been rolled into a single int for brevity. I would see no problem with unrolling them, giving a more complex definition but probably simpler usage, and making it obvious how to include another level of indirection (say to map from origin port to channel number to pitch to mapped event).
Last update: July 3, 2004