Suppose a MIDI input device which sends events from a keyboard. The port associated with this device has READ capability - which means this port is readable from other ports. If a user program wants to capture events from keyboard and store them as MIDI stream, this program must subscribe itself to the MIDI port for read. Then, a connection from MIDI input port to this program is established. From this time, events from keyboard are automatically sent to this program. Timestamps will be updated according to the subscribed queue.
MIDI input port (keyboard) | V ALSA sequencer - update timestamp | V application port
There is another subscription type for opposite direction: Suppose a MIDI sequencer program which sends events to a MIDI output device. In ALSA system, MIDI device is not opened until the associated MIDI port is accessed. Thus, in order to activate MIDI device, we have to subscribe to MIDI port for write. After this connection is established, events will be properly sent to MIDI output device.
application port | V ALSA sequencer - events are scheduled | V MIDI output port (WaveTable etc.)
From the viewpoint of subscription, the examples above are special cases. Basically, subscription means the connection between two arbitrary ports. For example, imagine a filter application which modifies the MIDI events like program, velocity or chorus effects. This application can accept arbitrary MIDI input and send to arbitrary port, just like a Unix pipe application using stdin and stdout files. We can even connect several filter applictions which work individually in order to process the MIDI events. Subscription can be used for this purpose. The connection between ports can be done also by the "third" client. Thus, filter applications have to manage only input and output events regardless of receiver/sender addresses. This idea is similar to MidiShare connection mechanism.
sequencer port #1 | V ALSA sequencer (scheduled or real-time) | V sequencer port #2
READ capability means that the port allows to send events to
other ports,
while the WRITE capability menas that the port allows to receive events from
other ports.
You may have noticed that meanings of READ and WRITE
are permissions of the port from the viewpoint of other ports.
READ_SUBS and WRITE_SUBS are capabilities
which allow read and write subscriptions, respectively.
For example, the port with MIDI input device always has READ_SUBS
capability, and the port with MIDI output device always has
WRITE_SUBS capability
together with READ and WRITE capabilities,
respectively.
Obviously, these flags have no influence
if READ or WRITE capability is not set.
Note that these flags are not necessary if the client subscribes itself
to the spcified port.
For example, when a port makes READ subscription
to MIDI input port, this port must have WRITE capability,
but no WRITE_SUBS capability is required.
Only MIDI input port must have READ_SUBS capability.
NO_EXPORT capability prohibits the subscription by the third client.
If this flag is set, subscription must be done by sender or receiver
client itself.
It is useful to avoid unexpected disconnection.
The ports which won't accept subscription should have this capability
for better security.
Each ALSA client and port have "group" field. The access permission above can be defined also for the group. The "group" permission is checked only when access or subscription to "all" is refused.
SND_SEQ_IOCTL_SUBSCRIBE_PORT.
In ALSA library, this corresponds to snd_seq_subscribe_port()
function.
The argument has the following record:
typedef struct {
snd_seq_addr_t sender;
snd_seq_addr_t dest;
int exclusive: 1;
int realtime: 1;
int convert_time: 1;
} snd_seq_port_subscribe_t;
The sender and dest fields are the addresses
of sender and receiver ports, respectively.
For example, to connect from MIDI input port to the self port, set
the following values:
sender.client = MIDI_input_client; sender.port = MIDI_input_port; dest.client = my_client; dest.port = my_port;The
queue value is necessary
if convert_time flag is true. (See below).
If exclusive flag is true, only one connection is allowed.
The succeeding subscriptions will be refused.
If convert_time is true, the timestamp
through subscribed connections will be automatically converted
to the current time on the specified queue.
The queue must be specified at queue.
If realtime is true, the timestamp is converted
to timespec structure (sec/nsec). Otherwise, the timestamp
is stored in tempo/tick value.
This feature is useful when receiving events from MIDI input device.
The event time is automatically set in the event record.
Note that both sender and dest address don't have to be the ioctl caller
client (third client). In this case, however, the subscription will be
refused if NO_EXPORT capability is set in sender or receiver port.
SND_SEQ_PORT_CAP_WRITE.
(..in application 128:0..) snd_seq_port_subscribe_t subs; memset(&subs, 0, sizeof(subs)); subs.sender.client = 64; subs.sender.port = 0; subs.dest.client = 128; subs.dest.port = 0; subs.queue = 1; subs.time_convert = 1; subs.realtime = 1; snd_seq_subscribe_port(seq, &subs);
SND_SEQ_PORT_CAP_READ.
(..in application 128:0..) snd_seq_port_subscribe_t subs; memset(&subs, 0, sizeof(subs)); subs.sender.client = 128; subs.sender.port = 0; subs.dest.client = 65; subs.dest.port = 1; snd_seq_subscribe_port(seq, &subs);
READ and READ_SUBS,
and the latter WRITE and WRITE_SUBS, respectively.
The subscription is done by the third application (130:0).
(..in the third application..) snd_seq_port_subscribe_t subs; memset(&subs, 0, sizeof(subs)); subs.sender.client = 128; subs.sender.port = 0; subs.dest.client = 129; subs.dest.port = 0; snd_seq_subscribe_port(seq, &subs);
The subscribed port doesn't have to know the exact sender address.
Instead, there is a special address for subscribers,
SND_SEQ_ADDRESS_SUBSCRIBERS (= 254).
The sender must set this value as the destination client.
Destination port is ignored.
The other values in source and destination addresses are identical with the normal event record. If the event is scheduled, proper queue and timestamp values must be set.
There is a convenient function to set the address in an event record. In order to set destination as subscribers,
void snd_seq_ev_set_subs(snd_seq_event_t *ev);
t (tick)
on the queue Q,
the sender must set both schedule queue and time in the
event record.
There are functions to set the scheduling queue and time stamp.
void snd_seq_ev_schedule_tick(snd_seq_event_t *ev, int q, int relative, snd_seq_tick_time_t tick); void snd_seq_ev_schedule_real(snd_seq_event_t *ev, int q, int relative, snd_seq_real_time_t *real);The former set tick value on the specified queue
q, and
the latter set the real-time value.
If relative is non-zero, the relative time is set.
Thus, the program will become like:
snd_seq_event_t ev; snd_seq_ev_clear(&ev); snd_seq_ev_set_source(&ev, port); snd_seq_ev_set_subs(&ev); snd_seq_ev_schedule_tick(&ev, Q, 0, t); ... snd_seq_event_output(seq, &ev); ... snd_seq_flush_output(seq); // if necessaryOf course, you can use realtime stamp, too.
void snd_seq_ev_set_direct(snd_seq_event_t *ev);The program will be more simplified as:
snd_seq_event_t ev; snd_seq_ev_clear(&ev); snd_seq_ev_set_source(&ev, port); snd_seq_ev_set_subs(&ev); snd_seq_ev_set_direct(&ev); ... snd_seq_output_event(seq, &ev); snd_seq_flush_output(seq);You should flush event soon after output event. Otherwise, the event is enqueued on output queue of ALSA library (not in the kernel!), and will be never processed until this queue becomes full.
snd_seq_t *seq;
snd_seq_event_t *ev;
snd_seq_open(&seq, SND_SEQ_OPEN);
...
while (snd_seq_event_input(seq, &ev) >= 0) {
//.. modify input event ..
snd_seq_ev_set_source(ev, my_port);
snd_seq_ev_set_subs(ev);
snd_seq_ev_set_direct(ev);
snd_seq_event_output(seq, &ev);
snd_seq_flush_output(seq);
snd_seq_free_event(ev);
}