summaryrefslogtreecommitdiff
path: root/portmidi/pm_test/sysex.c
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2024-10-07 06:50:04 +0200
committerMitja Felicijan <mitja.felicijan@gmail.com>2024-10-07 06:50:04 +0200
commit988f5d2b5343850e19ad1512cefe6c53953aa02e (patch)
tree1ff29934294e73b2575488c06df91866ce251dbe /portmidi/pm_test/sysex.c
parent9b5839c58a2e1df8bddf6b98805998508ea417d5 (diff)
downloadttdaw-988f5d2b5343850e19ad1512cefe6c53953aa02e.tar.gz
Added bunch of examples
Diffstat (limited to 'portmidi/pm_test/sysex.c')
-rwxr-xr-xportmidi/pm_test/sysex.c556
1 files changed, 556 insertions, 0 deletions
diff --git a/portmidi/pm_test/sysex.c b/portmidi/pm_test/sysex.c
new file mode 100755
index 0000000..c2c7187
--- /dev/null
+++ b/portmidi/pm_test/sysex.c
@@ -0,0 +1,556 @@
+/* sysex.c -- example program showing how to send and receive sysex
+ messages
+
+ Messages are stored in a file using 2-digit hexadecimal numbers,
+ one per byte, separated by blanks, with up to 32 numbers per line:
+ F0 14 A7 4B ...
+
+ */
+
+#include "stdio.h"
+#include "stdlib.h"
+#include "assert.h"
+#include "portmidi.h"
+#include "porttime.h"
+#include "string.h"
+#ifdef WIN32
+// need to get declaration for Sleep()
+#include "windows.h"
+#else
+#include <unistd.h>
+#define Sleep(n) usleep(n * 1000)
+#endif
+
+// enable some extra printing
+#ifndef VERBOSE
+#define VERBOSE 0
+#endif
+
+#define MIDI_SYSEX 0xf0
+#define MIDI_EOX 0xf7
+
+#define STRING_MAX 80
+
+#ifndef true
+#define true 1
+#define false 0
+#endif
+
+int latency = 0;
+
+/* read a number from console */
+/**/
+int get_number(const char *prompt)
+{
+ int n = 0, i;
+ fputs(prompt, stdout);
+ while (n != 1) {
+ n = scanf("%d", &i);
+ while (getchar() != '\n') ;
+ }
+ return i;
+}
+
+
+/* loopback test -- send/rcv from 2 to 1000 bytes of random midi data */
+/**/
+void loopback_test()
+{
+ int outp;
+ int inp;
+ PmStream *midi_in;
+ PmStream *midi_out;
+ unsigned char msg[1024];
+ int32_t len;
+ int i;
+ int data;
+ PmEvent event;
+ int shift;
+ long total_bytes = 0;
+ int32_t begin_time;
+
+ Pt_Start(1, 0, 0);
+
+ printf("Connect a midi cable from an output port to an input port.\n");
+ printf("This test will send random data via sysex message from output\n");
+ printf("to input and check that the correct data was received.\n");
+ outp = get_number("Type output device number: ");
+ /* Open output with 1ms latency -- when latency is non-zero, the Win32
+ implementation supports sending sysex messages incrementally in a
+ series of buffers. This is nicer than allocating a big buffer for the
+ message, and it also seems to work better. Either way works.
+ */
+ while ((latency = get_number(
+ "Latency in milliseconds (0 to send data immediatedly,\n"
+ " >0 to send timestamped messages): ")) < 0);
+ Pm_OpenOutput(&midi_out, outp, NULL, 0, NULL, NULL, latency);
+ inp = get_number("Type input device number: ");
+ /* since we are going to send and then receive, make sure the input buffer
+ is large enough for the entire message */
+ Pm_OpenInput(&midi_in, inp, NULL, 512, NULL, NULL);
+
+ srand((unsigned int) Pt_Time()); /* seed for random numbers */
+
+ begin_time = Pt_Time();
+ while (total_bytes < 100000) {
+ PmError count;
+ int32_t start_time;
+ int error_position = -1; /* 0; -1; -1 for continuous */
+ int expected = 0;
+ int actual = 0;
+ /* this modification will run until an error is detected */
+ /* set error_position above to 0 for interactive, -1 for */
+ /* continuous */
+ if (error_position >= 0) {
+ int c;
+ printf("Type return to send message, q to quit: ");
+ while ((c = getchar()) != '\n') {
+ if (c == 'q') goto cleanup;
+ }
+ }
+
+ /* compose the message */
+ len = rand() % 998 + 2; /* len only counts data bytes */
+ msg[0] = (char) MIDI_SYSEX; /* start of SYSEX message */
+ /* data bytes go from 1 to len */
+ for (i = 0; i < len; i++) {
+/* pick whether data is sequential or random... (docs say random) */
+#define DATA_EXPR (i+1)
+// #define DATA_EXPR rand()
+ msg[i + 1] = DATA_EXPR & 0x7f; /* MIDI data */
+ }
+ /* final EOX goes in len+1, total of len+2 bytes in msg */
+ msg[len + 1] = (char) MIDI_EOX;
+
+ /* sanity check: before we send, there should be no queued data */
+ count = Pm_Read(midi_in, &event, 1);
+
+ if (count != 0) {
+ printf("Before sending anything, a MIDI message was found in\n");
+ printf("the input buffer. Please try again.\n");
+ break;
+ }
+
+ /* send the message two ways: 1) Pm_WriteSysEx, 2) Pm_Write */
+ if (total_bytes & 1) {
+ printf("Sending %d byte sysex msg via Pm_WriteSysEx.\n", len + 2);
+ Pm_WriteSysEx(midi_out, 0, msg);
+ } else {
+ PmEvent event = {0, 0};
+ int bits = 0;
+ printf("Sending %d byte sysex msg via Pm_Write(s).\n", len + 2);
+ for (i = 0; i < len + 2; i++) {
+ event.message |= (msg[i] << bits);
+ bits += 8;
+ if (bits == 32) { /* full message - send it */
+ Pm_Write(midi_out, &event, 1);
+ bits = 0;
+ event.message = 0;
+ }
+ }
+ if (bits > 0) { /* last message is partially full */
+ Pm_Write(midi_out, &event, 1);
+ }
+ }
+
+ /* receive the message and compare to msg[] */
+ data = 0;
+ shift = 0;
+ i = 0;
+ start_time = Pt_Time();
+ if (VERBOSE) {
+ printf("start_time %d\n", start_time);
+ }
+ error_position = -1;
+ /* allow up to 2 seconds for transmission */
+ while (data != MIDI_EOX && start_time + 2000 > Pt_Time()) {
+ count = Pm_Read(midi_in, &event, 1);
+ if (count == 0) {
+ Sleep(1); /* be nice: give some CPU time to the system */
+ continue; /* continue polling for input */
+ }
+ if (VERBOSE) {
+ printf("read %08x ", event.message);
+ fflush(stdout);
+ }
+ /* compare 4 bytes of data until you reach an eox */
+ for (shift = 0; shift < 32 && (data != MIDI_EOX); shift += 8) {
+ data = (event.message >> shift) & 0xFF;
+ if (data != msg[i] && error_position < 0) {
+ error_position = i;
+ expected = msg[i];
+ actual = data;
+ }
+ i++;
+ }
+ }
+ if (error_position >= 0) {
+ printf("Error at time %d byte %d: sent %x recd %x.\n", Pt_Time(),
+ error_position, expected, actual);
+ break;
+ } else if (i != len + 2) {
+ printf("Error at time %d: byte %d not received.\n", Pt_Time(), i);
+ break;
+ } else {
+ int seconds = (Pt_Time() - begin_time) / 1000;
+ if (seconds == 0) seconds = 1;
+ printf("Correctly received %d byte sysex message.\n", i);
+ total_bytes += i;
+ printf("Cummulative bytes/sec: %d, %d%% done.\n",
+ (int) (total_bytes / seconds),
+ (int) (100 * total_bytes / 100000));
+ }
+ }
+cleanup:
+ Pm_Close(midi_out);
+ Pm_Close(midi_in);
+ return;
+}
+
+
+/* send_multiple test -- send many sysex messages */
+/**/
+void send_multiple_test()
+{
+ int outp;
+ int length;
+ int num_msgs;
+ PmStream *midi_out;
+ unsigned char msg[1024];
+ int i;
+ PtTimestamp start_time;
+ PtTimestamp stop_time;
+
+ Pt_Start(1, 0, 0);
+
+ printf("This is for performance testing. You should be sending to this\n");
+ printf("program running the receive multiple test. Do NOT send to\n");
+ printf("a synthesizer or you risk reprogramming it\n");
+ outp = get_number("Type output device number: ");
+ while ((latency = get_number(
+ "Latency in milliseconds (0 to send data immediatedly,\n"
+ " >0 to send timestamped messages): ")) < 0);
+ Pm_OpenOutput(&midi_out, outp, NULL, 0, NULL, NULL, latency);
+ while ((length = get_number("Message length (7 - 1024): ")) < 7 ||
+ length > 1024) ;
+ while ((num_msgs = get_number("Number of messages: ")) < 1);
+ /* latency, length, and num_msgs should now all be valid */
+ /* compose the message except for sequence number in first 5 bytes */
+ msg[0] = (char) MIDI_SYSEX;
+ for (i = 6; i < length - 1; i++) {
+ msg[i] = i % 128; /* this is just filler */
+ }
+ msg[length - 1] = (char) MIDI_EOX;
+
+ start_time = Pt_Time();
+ /* send the messages */
+ for (i = num_msgs; i > 0; i--) {
+ /* insert sequence number into first 5 data bytes */
+ /* sequence counts down to zero */
+ int j;
+ int count = i;
+ /* 7 bits of message count i goes into each data byte */
+ for (j = 1; j <= 5; j++) {
+ msg[j] = count & 127;
+ count >>= 7;
+ }
+ /* send the message */
+ Pm_WriteSysEx(midi_out, 0, msg);
+ }
+ stop_time = Pt_Time();
+ Pm_Close(midi_out);
+ return;
+}
+
+#define MAX_MSG_LEN 1024
+static unsigned char receive_msg[MAX_MSG_LEN];
+static int receive_msg_index;
+static int receive_msg_length;
+static int receive_msg_count;
+static int receive_msg_error;
+static int receive_msg_messages;
+static PmStream *receive_msg_midi_in;
+static int receive_poll_running;
+
+/* receive_poll -- callback function to check for midi input */
+/**/
+void receive_poll(PtTimestamp timestamp, void *userData)
+{
+ PmError count;
+ PmEvent event;
+ int shift;
+ int data = 0;
+ int i;
+
+ if (!receive_poll_running) return; /* wait until midi device is opened */
+ shift = 0;
+ while (data != MIDI_EOX) {
+ count = Pm_Read(receive_msg_midi_in, &event, 1);
+ if (count == 0) return;
+
+ /* compare 4 bytes of data until you reach an eox */
+ for (shift = 0; shift < 32 && (data != MIDI_EOX); shift += 8) {
+ receive_msg[receive_msg_index++] = data =
+ (event.message >> shift) & 0xFF;
+ if (receive_msg_index >= MAX_MSG_LEN) {
+ printf("error: incoming sysex too long\n");
+ goto error;
+ }
+ }
+ }
+ /* check the message */
+ if (receive_msg_length == 0) {
+ receive_msg_length = receive_msg_index;
+ }
+ if (receive_msg_length != receive_msg_index) {
+ printf("error: incoming sysex wrong length\n");
+ goto error;
+ }
+ if (receive_msg[0] != MIDI_SYSEX) {
+ printf("error: incoming sysex missing status byte\n");
+ goto error;
+ }
+ /* get and check the count */
+ count = 0;
+ for (i = 0; i < 5; i++) {
+ count += receive_msg[i + 1] << (7 * i);
+ }
+ if (receive_msg_count == -1) {
+ receive_msg_count = count;
+ receive_msg_messages = count;
+ }
+ if (receive_msg_count != count) {
+ printf("error: incoming sysex has wrong count\n");
+ goto error;
+ }
+ for (i = 6; i < receive_msg_index - 1; i++) {
+ if (receive_msg[i] != i % 128) {
+ printf("error: incoming sysex has bad data\n");
+ goto error;
+ }
+ }
+ if (receive_msg[receive_msg_length - 1] != MIDI_EOX) goto error;
+ receive_msg_index = 0; /* get ready for next message */
+ receive_msg_count--;
+ return;
+ error:
+ receive_msg_error = 1;
+ return;
+}
+
+
+/* receive_multiple_test -- send/rcv from 2 to 1000 bytes of random midi data */
+/**/
+void receive_multiple_test()
+{
+ PmError err;
+ int inp;
+
+ printf("This test expects to receive data sent by the send_multiple test\n");
+ printf("The test will check that correct data is received.\n");
+
+ /* Important: start PortTime first -- if it is not started first, it will
+ be started by PortMidi, and then our attempt to open again will fail */
+ receive_poll_running = false;
+ if ((err = Pt_Start(1, receive_poll, 0))) {
+ printf("PortTime error code: %d\n", err);
+ goto cleanup;
+ }
+ inp = get_number("Type input device number: ");
+ Pm_OpenInput(&receive_msg_midi_in, inp, NULL, 512, NULL, NULL);
+ receive_msg_index = 0;
+ receive_msg_length = 0;
+ receive_msg_count = -1;
+ receive_msg_error = 0;
+ receive_poll_running = true;
+ while ((!receive_msg_error) && (receive_msg_count != 0)) {
+#ifdef WIN32
+ Sleep(1000);
+#else
+ sleep(1); /* block and wait */
+#endif
+ }
+ if (receive_msg_error) {
+ printf("Receive_multiple test encountered an error\n");
+ } else {
+ printf("Receive_multiple test successfully received %d sysex messages\n",
+ receive_msg_messages);
+ }
+cleanup:
+ receive_poll_running = false;
+ Pm_Close(receive_msg_midi_in);
+ Pt_Stop();
+ return;
+}
+
+
+#define is_real_time_msg(msg) ((0xF0 & Pm_MessageStatus(msg)) == 0xF8)
+
+
+void receive_sysex()
+{
+ char line[80];
+ FILE *f;
+ PmStream *midi;
+ int shift = 0;
+ int data = 0;
+ int bytes_on_line = 0;
+ PmEvent msg;
+
+ /* determine which output device to use */
+ int i = get_number("Type input device number: ");
+
+ /* open input device */
+ Pm_OpenInput(&midi, i, NULL, 512, NULL, NULL);
+ printf("Midi Input opened, type file for sysex data: ");
+
+ /* open file */
+ if (!fgets(line, STRING_MAX, stdin)) return; /* no more stdin? */
+ /* remove the newline character */
+ if (strlen(line) > 0) line[strlen(line) - 1] = 0;
+ f = fopen(line, "w");
+ if (!f) {
+ printf("Could not open %s\n", line);
+ Pm_Close(midi);
+ return;
+ }
+
+ printf("Ready to receive a sysex message\n");
+
+ /* read data and write to file */
+ while (data != MIDI_EOX) {
+ PmError count;
+ count = Pm_Read(midi, &msg, 1);
+ /* CAUTION: this causes busy waiting. It would be better to
+ be in a polling loop to avoid being compute bound. PortMidi
+ does not support a blocking read since this is so seldom
+ useful.
+ */
+ if (count == 0) continue;
+ /* ignore real-time messages */
+ if (is_real_time_msg(Pm_MessageStatus(msg.message))) continue;
+
+ /* write 4 bytes of data until you reach an eox */
+ for (shift = 0; shift < 32 && (data != MIDI_EOX); shift += 8) {
+ data = (msg.message >> shift) & 0xFF;
+ /* if this is a status byte that's not MIDI_EOX, the sysex
+ message is incomplete and there is no more sysex data */
+ if (data & 0x80 && data != MIDI_EOX) break;
+ fprintf(f, "%2x ", data);
+ if (++bytes_on_line >= 16) {
+ fprintf(f, "\n");
+ bytes_on_line = 0;
+ }
+ }
+ }
+ fclose(f);
+ Pm_Close(midi);
+}
+
+
+void send_sysex()
+{
+ char line[80];
+ FILE *f;
+ PmStream *midi;
+ int data;
+ int shift = 0;
+ PmEvent msg;
+
+ /* determine which output device to use */
+ int i = get_number("Type output device number: ");
+ while ((latency = get_number(
+ "Latency in milliseconds (0 to send data immediatedly,\n"
+ " >0 to send timestamped messages): ")) < 0);
+
+ msg.timestamp = 0; /* no need for timestamp */
+
+ /* open output device */
+ Pm_OpenOutput(&midi, i, NULL, 0, NULL, NULL, latency);
+ printf("Midi Output opened, type file with sysex data: ");
+
+ /* open file */
+ if (!fgets(line, STRING_MAX, stdin)) return; /* no more stdin? */
+ /* remove the newline character */
+ if (strlen(line) > 0) line[strlen(line) - 1] = 0;
+ f = fopen(line, "r");
+ if (!f) {
+ printf("Could not open %s\n", line);
+ Pm_Close(midi);
+ return;
+ }
+
+ /* read file and send data */
+ msg.message = 0;
+ while (1) {
+ /* get next byte from file */
+
+ if (fscanf(f, "%x", &data) == 1) {
+ /* printf("read %x, ", data); */
+ /* OR byte into message at proper offset */
+ msg.message |= (data << shift);
+ shift += 8;
+ }
+ /* send the message if it's full (shift == 32) or if we are at end */
+ if (shift == 32 || data == MIDI_EOX) {
+ /* this will send sysex data 4 bytes at a time -- it would
+ be much more efficient to send multiple PmEvents at once
+ but this method is simpler. See Pm_WriteSysEx for a more
+ efficient code example.
+ */
+ Pm_Write(midi, &msg, 1);
+ msg.message = 0;
+ shift = 0;
+ }
+ if (data == MIDI_EOX) { /* end of message */
+ fclose(f);
+ Pm_Close(midi);
+ return;
+ }
+ }
+}
+
+
+int main()
+{
+ int i;
+
+ /* list device information */
+ for (i = 0; i < Pm_CountDevices(); i++) {
+ const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
+ printf("%d: %s, %s", i, info->interf, info->name);
+ if (info->input) printf(" (input)");
+ if (info->output) printf(" (output)");
+ printf("\n");
+ }
+ while (1) {
+ char cmd;
+ printf("Type r to receive sysex, s to send,"
+ " l for loopback test, m to send multiple,"
+ " n to receive multiple, q to quit: ");
+ cmd = getchar();
+ while (getchar() != '\n') ;
+ switch (cmd) {
+ case 'r':
+ receive_sysex();
+ break;
+ case 's':
+ send_sysex();
+ break;
+ case 'l':
+ loopback_test();
+ break;
+ case 'm':
+ send_multiple_test();
+ break;
+ case 'n':
+ receive_multiple_test();
+ break;
+ case 'q':
+ exit(0);
+ default:
+ break;
+ }
+ }
+ return 0;
+}