From cd0d1ab8fe35d6bc30a7e6c770b9b93267e77b86 Mon Sep 17 00:00:00 2001
From: Michael Sippel <micha@fragmental.art>
Date: Sat, 7 Dec 2024 02:42:48 +0100
Subject: [PATCH] initial pipewire setup

- setup audio in/out and midi in ports
- dump incoming midi messages
---
 guitfx.c    | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 meson.build |   7 ++
 2 files changed, 195 insertions(+)
 create mode 100644 guitfx.c
 create mode 100644 meson.build

diff --git a/guitfx.c b/guitfx.c
new file mode 100644
index 0000000..351eb99
--- /dev/null
+++ b/guitfx.c
@@ -0,0 +1,188 @@
+#include "pipewire/keys.h"
+#include "pipewire/port.h"
+#include "spa/pod/iter.h"
+#include "spa/utils/defs.h"
+#include <stdio.h>
+#include <signal.h>
+
+#include <spa/pod/pod.h>
+#include <spa/pod/builder.h>
+#include <spa/control/control.h>
+#include <spa/param/latency-utils.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/filter.h>
+
+struct data;
+
+struct port {
+	struct data *data;
+};
+
+struct data {
+	struct pw_main_loop *loop;
+	struct pw_filter *filter;
+	struct port *in_port;
+	struct port *midi_in_port;
+	struct port *out_port;
+
+	//! elapsed time in number of samples
+	uint64_t time;
+};
+
+static void on_process(void *userdata, struct spa_io_position *position)
+{
+	struct data *data = userdata;
+	float *in, *out;
+	uint32_t n_samples = position->clock.duration;
+	uint64_t frame = data->time;
+	data->time += n_samples;
+
+	//printf("do process (%d samples) at %u\n", n_samples, frame);
+
+	struct pw_buffer * b = pw_filter_dequeue_buffer(data->midi_in_port);
+	if( b == NULL ) {
+	   fprintf(stderr, "on_process(): no buffer for midi_in_port\n");
+	   return;
+	}
+
+	struct spa_buffer * buf = b->buffer;
+	spa_assert(buf->n_datas == 1);
+	struct spa_data   * d   = &buf->datas[0];
+
+	if( d->data ) {
+	    struct spa_pod * pod =
+			spa_pod_from_data(
+			    d->data,
+				d->maxsize,
+				d->chunk->offset,
+				d->chunk->size
+			);
+
+		if( pod ) {
+		    if( spa_pod_is_sequence(pod) ) {
+				struct spa_pod_sequence * pod_seq = (struct spa_pod_sequence*) pod;
+				struct spa_pod_control * c;
+				SPA_POD_SEQUENCE_FOREACH(pod_seq, c) {
+				    if( c->type == SPA_CONTROL_Midi ) {
+						unsigned sec =
+						      (frame + c->offset)
+							/ (float) position->clock.rate.denom;
+						char * data = SPA_POD_BODY(&c->value);
+						unsigned size = SPA_POD_BODY_SIZE(&c->value);
+
+						printf("[%d] MIDI message (%d bytes) : %x, %x, %x\n", sec, size, data[0], data[1], data[2]);
+					} else {
+					    printf("on_process(): non midi-control\n");
+					}
+				}
+			} else {
+			    fprintf(stderr, "on_process(): unexpected POD that is not a sequence (midi_in_port)\n");
+			}
+		} else {
+		    fprintf(stderr, "on_process(): pod is NULL\n");
+		}
+	} else {
+	    fprintf(stderr, "on_process(): no data in buffer of midi_in_port\n");
+	}
+
+	pw_filter_queue_buffer(data->midi_in_port, b);
+
+
+	in = pw_filter_get_dsp_buffer(data->in_port, n_samples);
+	out = pw_filter_get_dsp_buffer(data->out_port, n_samples);
+
+	if( in && out ) {
+	   memcpy(out, in, n_samples * sizeof(float));
+	}
+}
+
+static const struct pw_filter_events filter_events = {
+	PW_VERSION_FILTER_EVENTS,
+	.process = on_process,
+};
+
+static void do_quit(void *userdata, int signal_number)
+{
+	struct data *data = userdata;
+	pw_main_loop_quit(data->loop);
+}
+
+int main(int argc, char *argv[])
+{
+	struct data data = { 0, };
+	const struct spa_pod *params[1];
+	uint8_t buffer[1024];
+	struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+
+	pw_init(&argc, &argv);
+
+	data.loop = pw_main_loop_new(NULL);
+
+	pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data);
+	pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data);
+
+	data.filter = pw_filter_new_simple(
+			pw_main_loop_get_loop(data.loop),
+			"Guitar FX",
+			pw_properties_new(
+				PW_KEY_MEDIA_TYPE, "Audio",
+				PW_KEY_MEDIA_CATEGORY, "Filter",
+				PW_KEY_MEDIA_ROLE, "DSP",
+				NULL),
+			&filter_events,
+			&data);
+
+	data.midi_in_port = pw_filter_add_port(data.filter,
+		    PW_DIRECTION_INPUT,
+			PW_FILTER_PORT_FLAG_MAP_BUFFERS,
+			sizeof(struct port),
+			pw_properties_new(
+			    PW_KEY_FORMAT_DSP, "8 bit raw midi",
+				PW_KEY_PORT_NAME, "midi-input",
+				NULL
+			),
+			NULL, 0
+		);
+
+	data.in_port = pw_filter_add_port(data.filter,
+			PW_DIRECTION_INPUT,
+			PW_FILTER_PORT_FLAG_MAP_BUFFERS,
+			sizeof(struct port),
+			pw_properties_new(
+				PW_KEY_FORMAT_DSP, "32 bit float mono audio",
+				PW_KEY_PORT_NAME, "guitar input",
+				NULL),
+			NULL, 0);
+
+	data.out_port = pw_filter_add_port(data.filter,
+			PW_DIRECTION_OUTPUT,
+			PW_FILTER_PORT_FLAG_MAP_BUFFERS,
+			sizeof(struct port),
+			pw_properties_new(
+				PW_KEY_FORMAT_DSP, "32 bit float mono audio",
+				PW_KEY_PORT_NAME, "guitar output",
+				NULL),
+			NULL, 0);
+
+	params[0] = spa_process_latency_build(&b,
+			SPA_PARAM_ProcessLatency,
+			&SPA_PROCESS_LATENCY_INFO_INIT(
+				.ns = 10 * SPA_NSEC_PER_MSEC
+			));
+
+	if (pw_filter_connect(data.filter,
+				PW_FILTER_FLAG_RT_PROCESS,
+				params, 1) < 0) {
+		fprintf(stderr, "can't connect\n");
+		return -1;
+	}
+
+	pw_main_loop_run(data.loop);
+
+	pw_filter_destroy(data.filter);
+	pw_main_loop_destroy(data.loop);
+	pw_deinit();
+
+	return 0;
+}
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..0840c97
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,7 @@
+project('guitfx', 'c')
+pipewire_dep = dependency('libpipewire-0.3')
+executable(
+    'guitfx',
+    'guitfx.c',
+    dependencies : [pipewire_dep],
+)