From: Takashi Sakamoto Date: Tue, 14 Jun 2022 10:23:22 +0000 (+0900) Subject: seq: user-client: add public API to schedule list of events X-Git-Tag: v0.3.0~88 X-Git-Url: https://git.alsa-project.org/?a=commitdiff_plain;h=3348b38901ea562302a8a763e83f546d4b3a60ce;p=alsa-gobject.git seq: user-client: add public API to schedule list of events This commit adds ALSASeq.schedule_events() to schedule the list of events. Signed-off-by: Takashi Sakamoto --- diff --git a/samples/seq b/samples/seq index ddf14e8..c335338 100755 --- a/samples/seq +++ b/samples/seq @@ -66,32 +66,37 @@ for prop in ('name', 'queue-id', 'client-id', 'locked'): 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): @@ -140,6 +145,8 @@ GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, SIGINT, 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) diff --git a/src/seq/alsaseq-enum-types.h b/src/seq/alsaseq-enum-types.h index 6da4963..d7ba494 100644 --- a/src/seq/alsaseq-enum-types.h +++ b/src/seq/alsaseq-enum-types.h @@ -352,6 +352,7 @@ typedef enum /*< flags >*/ * @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. */ @@ -359,6 +360,7 @@ typedef enum { ALSASEQ_USER_CLIENT_ERROR_FAILED, ALSASEQ_USER_CLIENT_ERROR_PORT_PERMISSION, ALSASEQ_USER_CLIENT_ERROR_QUEUE_PERMISSION, + ALSASEQ_USER_CLIENT_ERROR_EVENT_UNDELIVERABLE, } ALSASeqUserClientError; /** diff --git a/src/seq/alsaseq.map b/src/seq/alsaseq.map index 1becdaf..2cd638c 100644 --- a/src/seq/alsaseq.map +++ b/src/seq/alsaseq.map @@ -269,4 +269,6 @@ ALSA_GOBJECT_0_3_0 { "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; diff --git a/src/seq/event.c b/src/seq/event.c index 1c6daf2..357a790 100644 --- a/src/seq/event.c +++ b/src/seq/event.c @@ -1619,3 +1619,69 @@ gboolean alsaseq_event_set_result_data(ALSASeqEvent *self, const ALSASeqEventDat 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; + } +} diff --git a/src/seq/privates.h b/src/seq/privates.h index e0e38d6..4f51007 100644 --- a/src/seq/privates.h +++ b/src/seq/privates.h @@ -39,6 +39,10 @@ void seq_event_cntr_set_buf(ALSASeqEventCntr *self, guint8 *buf, 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" diff --git a/src/seq/user-client.c b/src/seq/user-client.c index d8fae02..e359156 100644 --- a/src/seq/user-client.c +++ b/src/seq/user-client.c @@ -590,6 +590,109 @@ gboolean alsaseq_user_client_schedule_event(ALSASeqUserClient *self, ALSASeqEven 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; diff --git a/src/seq/user-client.h b/src/seq/user-client.h index 92df834..c810cd7 100644 --- a/src/seq/user-client.h +++ b/src/seq/user-client.h @@ -64,6 +64,8 @@ gboolean alsaseq_user_client_get_pool(ALSASeqUserClient *self, 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); diff --git a/tests/alsaseq-enums b/tests/alsaseq-enums index 3a0ea6c..72b7747 100644 --- a/tests/alsaseq-enums +++ b/tests/alsaseq-enums @@ -162,6 +162,7 @@ user_client_error_types = ( 'FAILED', 'PORT_PERMISSION', 'QUEUE_PERMISSION', + 'EVENT_UNDELIVERABLE', ) event_error_types = ( diff --git a/tests/alsaseq-user-client b/tests/alsaseq-user-client index 896c370..cee0f5a 100644 --- a/tests/alsaseq-user-client +++ b/tests/alsaseq-user-client @@ -38,6 +38,7 @@ methods = ( 'set_queue_timer', 'get_queue_timer', 'remove_events', + 'schedule_events', ) signals = ()