queue_id = info.get_property('queue-id')
# Prepare two events; one to start the queue, another to deliver on the queue.
-ev_cntr = ALSASeq.EventCntr.new(2)
-
-ev_cntr.set_event_type(0, ALSASeq.EventType.START)
-ev_cntr.set_tstamp_mode(0, ALSASeq.EventTstampMode.REAL)
-ev_cntr.set_time_mode(0, ALSASeq.EventTimeMode.REL)
-ev_cntr.set_priority_mode(0, ALSASeq.EventPriorityMode.NORMAL)
-ev_cntr.set_tag(0, 0)
-ev_cntr.set_queue_id(0, ALSASeq.SpecificQueueId.DIRECT)
-ev_cntr.set_dst(0, ALSASeq.Addr.new(ALSASeq.SpecificClientId.SYSTEM,
- ALSASeq.SpecificPortId.TIMER));
-ev_cntr.set_src(0, ALSASeq.Addr.new(client.get_property('client-id'), 0));
-_, data = ev_cntr.get_queue_data(0)
+events = []
+
+ev = ALSASeq.Event.new(ALSASeq.EventType.START)
+ev.set_time_mode(ALSASeq.EventTimeMode.REL)
+ev.set_priority_mode(ALSASeq.EventPriorityMode.NORMAL)
+ev.set_tag(0)
+ev.set_queue_id(ALSASeq.SpecificQueueId.DIRECT)
+dst = ALSASeq.Addr.new(ALSASeq.SpecificClientId.SYSTEM,
+ ALSASeq.SpecificPortId.TIMER)
+ev.set_destination(dst)
+src = ALSASeq.Addr.new(client.get_property('client-id'), 0)
+ev.set_source(src)
+_, data = ev.get_queue_data()
data.set_queue_id(queue_id)
-ev_cntr.set_queue_data(0, data)
-
-ev_cntr.set_event_type(1, ALSASeq.EventType.CONTROLLER)
-ev_cntr.set_tstamp_mode(1, ALSASeq.EventTstampMode.REAL)
-ev_cntr.set_time_mode(1, ALSASeq.EventTimeMode.REL)
-ev_cntr.set_priority_mode(1, ALSASeq.EventPriorityMode.NORMAL)
-ev_cntr.set_tag(1, 10)
-ev_cntr.set_queue_id(1, queue_id)
-ev_cntr.set_dst(1, ALSASeq.Addr.new(client.get_property('client-id'), 0));
-ev_cntr.set_src(1, ALSASeq.Addr.new(client.get_property('client-id'), 0));
+ev.set_queue_data(data)
+events.append(ev)
+
+ev = ALSASeq.Event.new(ALSASeq.EventType.CONTROLLER)
+ev.set_time_mode(ALSASeq.EventTimeMode.REL)
+ev.set_priority_mode(ALSASeq.EventPriorityMode.NORMAL)
+ev.set_tag(10)
+ev.set_queue_id(queue_id)
+dst = ALSASeq.Addr.new(client.get_property('client-id'), 0)
+ev.set_destination(dst)
+src = ALSASeq.Addr.new(client.get_property('client-id'), 0)
+ev.set_source(src)
+events.append(ev)
# Schedule the events.
-client.schedule_event(ev_cntr, 2)
+_, count = client.schedule_events(events)
+print('scheduled:', count)
# Register event handler.
def handle_event(client, ev_cntr):
dispatcher.run()
# Stop the queue.
+ev_cntr = ALSASeq.EventCntr.new(1)
+ev_cntr.set_queue_id(0, queue_id)
ev_cntr.set_event_type(0, ALSASeq.EventType.STOP)
client.schedule_event(ev_cntr, 1)
* @ALSASEQ_USER_CLIENT_ERROR_FAILED: The system call failed.
* @ALSASEQ_USER_CLIENT_ERROR_PORT_PERMISSION: The operation fails due to access permission of port.
* @ALSASEQ_USER_CLIENT_ERROR_QUEUE_PERMISSION: The operation fails due to access permission of queue.
+ * @ALSASEQ_USER_CLIENT_ERROR_EVENT_UNDELIVERABLE: The operation failes due to undeliverable event.
*
* A set of error code for [structGLib.Error] with `struct@UserClientError` domain.
*/
ALSASEQ_USER_CLIENT_ERROR_FAILED,
ALSASEQ_USER_CLIENT_ERROR_PORT_PERMISSION,
ALSASEQ_USER_CLIENT_ERROR_QUEUE_PERMISSION,
+ ALSASEQ_USER_CLIENT_ERROR_EVENT_UNDELIVERABLE,
} ALSASeqUserClientError;
/**
"alsaseq_event_set_connect_data";
"alsaseq_event_get_result_data";
"alsaseq_event_set_result_data";
+
+ "alsaseq_user_client_schedule_events";
} ALSA_GOBJECT_0_2_0;
return TRUE;
}
+
+void seq_event_copy_flattened(const ALSASeqEvent *self, guint8 *buf, gsize length)
+{
+ memcpy(buf, self, sizeof(*self));
+
+ switch (self->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) {
+ case SNDRV_SEQ_EVENT_LENGTH_VARIABLE:
+ {
+ g_return_if_fail(sizeof(*self) + self->data.ext.len <= length);
+ memcpy(buf + sizeof(*self), self->data.ext.ptr, self->data.ext.len);
+ break;
+ }
+ case SNDRV_SEQ_EVENT_LENGTH_VARUSR:
+ case SNDRV_SEQ_EVENT_LENGTH_FIXED:
+ default:
+ break;
+ }
+}
+
+// Calculate the length of event followed by allocated object for blob data at variable type. This
+// is the default layout for buffer read from ALSA Sequencer core.
+gsize seq_event_calculate_flattened_length(const ALSASeqEvent *self, gboolean aligned)
+{
+ gsize length = sizeof(*self);
+
+ switch (self->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) {
+ case SNDRV_SEQ_EVENT_LENGTH_VARIABLE:
+ length += self->data.ext.len;
+ break;
+ case SNDRV_SEQ_EVENT_LENGTH_VARUSR:
+ case SNDRV_SEQ_EVENT_LENGTH_FIXED:
+ default:
+ break;
+ }
+
+ if (aligned)
+ length = (length + sizeof(*self) - 1) / sizeof(*self) * sizeof(*self);
+
+ return length;
+}
+
+gboolean seq_event_is_deliverable(const ALSASeqEvent *self)
+{
+ enum seq_event_data_flag flags = seq_event_data_flags[self->type];
+
+ switch (self->flags & SNDRV_SEQ_EVENT_LENGTH_MASK) {
+ case SNDRV_SEQ_EVENT_LENGTH_FIXED:
+ {
+ // NOTE: ALSA Sequencer core can emulates note on/off events by simple note event. The
+ // emulation is in queue implementation, thus it should not be dispatched directly.
+ if (self->type == ALSASEQ_EVENT_TYPE_NOTE)
+ return self->queue != SNDRV_SEQ_QUEUE_DIRECT;
+ else
+ return (flags & SEQ_EVENT_DATA_FLAG_FIXED_ANY) > 0;
+ }
+ case SNDRV_SEQ_EVENT_LENGTH_VARIABLE:
+ return (flags & SEQ_EVENT_DATA_FLAG_VARIABLE_ANY) > 0;
+ case SNDRV_SEQ_EVENT_LENGTH_VARUSR:
+ // NOTE: The data pointed by the pointer should be delivered to receivers immediately so
+ // that the user space application has no need to wait for completion of scheduled delivery.
+ return ((flags & SEQ_EVENT_DATA_FLAG_VARUSR_ANY) > 0) &&
+ (self->queue == SNDRV_SEQ_QUEUE_DIRECT);
+ default:
+ return FALSE;
+ }
+}
void seq_event_cntr_get_buf(ALSASeqEventCntr *self, gsize count,
const guint8 **buf, gsize *length);
+void seq_event_copy_flattened(const ALSASeqEvent *self, guint8 *buf, gsize length);
+gsize seq_event_calculate_flattened_length(const ALSASeqEvent *self, gboolean aligned);
+gboolean seq_event_is_deliverable(const ALSASeqEvent *self);
+
#define QUEUE_ID_PROP_NAME "queue-id"
#define TIMER_TYPE_PROP_NAME "timer-type"
return TRUE;
}
+/**
+ * alsaseq_user_client_schedule_events:
+ * @self: A [class@UserClient].
+ * @events: (element-type ALSASeq.Event) (transfer none): The list of [struct@Event].
+ * @count: (out): The number of events to be scheduled.
+ * @error: A [struct@GLib.Error]. Error is generated with two domains; `GLib.FileError` and
+ * `ALSASeq.UserClientError`.
+ *
+ * Deliver the events immediately, or schedule it into memory pool of the client.
+ *
+ * The call of function executes `write(2)` system call for ALSA sequencer character device.
+ *
+ * Returns: %TRUE when the overall operation finishes successfully, else %FALSE.
+ */
+gboolean alsaseq_user_client_schedule_events(ALSASeqUserClient *self, const GList *events,
+ gsize *count, GError **error)
+{
+ ALSASeqUserClientPrivate *priv;
+ const GList *entry;
+ gsize index;
+ gsize total_length;
+ guint8 *buf;
+ gsize pos;
+ ssize_t result;
+ gsize scheduled;
+
+ g_return_val_if_fail(ALSASEQ_IS_USER_CLIENT(self), FALSE);
+ priv = alsaseq_user_client_get_instance_private(self);
+
+ g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
+
+ // Calculate total length of flattened and unaligned events.
+ index = 0;
+ total_length = 0;
+ for (entry = events; entry != NULL; entry = g_list_next(entry)) {
+ const struct snd_seq_event *ev = (const struct snd_seq_event *)entry->data;
+
+ g_return_val_if_fail(ev != NULL, FALSE);
+ if (!seq_event_is_deliverable(ev)) {
+ g_set_error(error, ALSASEQ_USER_CLIENT_ERROR,
+ ALSASEQ_USER_CLIENT_ERROR_EVENT_UNDELIVERABLE,
+ "The operation failes due to undeliverable event: index %lu",
+ index);
+ return FALSE;
+ }
+
+ total_length += seq_event_calculate_flattened_length(ev, FALSE);
+ ++index;
+ }
+
+ // Nothing to do.
+ if (total_length == 0)
+ return TRUE;
+
+ buf = g_malloc0(total_length);
+
+ pos = 0;
+ for (entry = events; entry != NULL; entry = g_list_next(entry)) {
+ const struct snd_seq_event *ev = (const struct snd_seq_event *)entry->data;
+ gsize length;
+
+ g_return_val_if_fail(ev != NULL, FALSE);
+
+ length = seq_event_calculate_flattened_length(ev, FALSE);
+ seq_event_copy_flattened(ev, buf + pos, length);
+ pos += length;
+ }
+
+ g_return_val_if_fail(total_length == pos, FALSE);
+
+ result = write(priv->fd, buf, total_length);
+ g_free(buf);
+ if (result < 0) {
+ GFileError code = g_file_error_from_errno(errno);
+
+ if (code != G_FILE_ERROR_FAILED)
+ generate_file_error(error, errno, "write(%s)", priv->devnode);
+ else
+ generate_syscall_error(error, errno, "write(%s)", priv->devnode);
+
+ return FALSE;
+ }
+
+ // Compute the count of scheduled events.
+ pos = 0;
+ scheduled = 0;
+ for (entry = events; entry != NULL; entry = g_list_next(entry)) {
+ const struct snd_seq_event *ev = (const struct snd_seq_event *)entry->data;
+
+ ++scheduled;
+
+ pos += seq_event_calculate_flattened_length(ev, FALSE);
+ if (pos >= result)
+ break;
+ }
+
+ g_return_val_if_fail(result == pos, FALSE);
+
+ *count = scheduled;
+
+ return TRUE;
+}
+
static gboolean seq_user_client_check_src(GSource *gsrc)
{
UserClientSource *src = (UserClientSource *)gsrc;
gboolean alsaseq_user_client_schedule_event(ALSASeqUserClient *self, ALSASeqEventCntr *ev_cntr,
gsize count, GError **error);
+gboolean alsaseq_user_client_schedule_events(ALSASeqUserClient *self, const GList *events,
+ gsize *count, GError **error);
gboolean alsaseq_user_client_create_source(ALSASeqUserClient *self, GSource **gsrc, GError **error);
'FAILED',
'PORT_PERMISSION',
'QUEUE_PERMISSION',
+ 'EVENT_UNDELIVERABLE',
)
event_error_types = (
'set_queue_timer',
'get_queue_timer',
'remove_events',
+ 'schedule_events',
)
signals = ()