]> git.alsa-project.org Git - alsa-gobject.git/commitdiff
seq: user-client: add public API to schedule list of events
authorTakashi Sakamoto <o-takashi@sakamocchi.jp>
Tue, 14 Jun 2022 10:23:22 +0000 (19:23 +0900)
committer坂本 貴史 <o-takashi@sakamocchi.jp>
Thu, 16 Jun 2022 12:52:00 +0000 (21:52 +0900)
This commit adds ALSASeq.schedule_events() to schedule the list of
events.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
samples/seq
src/seq/alsaseq-enum-types.h
src/seq/alsaseq.map
src/seq/event.c
src/seq/privates.h
src/seq/user-client.c
src/seq/user-client.h
tests/alsaseq-enums
tests/alsaseq-user-client

index ddf14e89da4b05e692283edb8d9f5bd45393e526..c335338efea0303a86e4589cc15c58587a78dc58 100755 (executable)
@@ -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)
 
index 6da4963fa35bb0c3f297e6a62849bc816a80c8ca..d7ba4946fba818a3996bd2622779b488f635ef3d 100644 (file)
@@ -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;
 
 /**
index 1becdaf309f9d844b453a94b1b03fb251720101f..2cd638c31e20c21f6f77b58801f68ee99455bcde 100644 (file)
@@ -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;
index 1c6daf252b04fc80b2da64b5acd21d856c5e19e2..357a790aabbe30ca1b5fd2c60f8fe55761c63097 100644 (file)
@@ -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;
+    }
+}
index e0e38d61f8bda84faf8c53c3ed1e4da9ac91dd13..4f51007c410b3810d8e2713cb3a164c5936aeb08 100644 (file)
@@ -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"
 
index d8fae02dad4569e73defa1e2189c5958e568ea96..e359156098e9d61272580ef50aa67eed31e73b79 100644 (file)
@@ -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;
index 92df834f37e559a2430141dc628e2a255ad38870..c810cd7ce129b7223b0b25af1e1dcaf82aa3b03d 100644 (file)
@@ -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);
 
index 3a0ea6c60008fde0ceed171c604747f040113cdc..72b7747dce5917e30eac3c723b32aed91774ba09 100644 (file)
@@ -162,6 +162,7 @@ user_client_error_types = (
     'FAILED',
     'PORT_PERMISSION',
     'QUEUE_PERMISSION',
+    'EVENT_UNDELIVERABLE',
 )
 
 event_error_types = (
index 896c3709dbb0f8743ffca493667998fa52b38b70..cee0f5a150c0ced524aca9a5578a527cce240f6f 100644 (file)
@@ -38,6 +38,7 @@ methods = (
     'set_queue_timer',
     'get_queue_timer',
     'remove_events',
+    'schedule_events',
 )
 signals = ()