|
|
dev:recording_from_multiple_midi_ports [2018/02/07 16:07] |
dev:recording_from_multiple_midi_ports [2022/05/06 16:07] (current) |
| ====== 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. |
| |
| ===== Phase 1 ===== |
| |
| 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. |
| |
| ===== Phase 2 ===== |
| |
| 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. |
| |
| ===== Phase 3 ===== |
| |
| 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. |
| |
| ===== Known issues ===== |
| |
| 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 |
| |