mirror of
https://github.com/holub/mame
synced 2025-04-19 15:11:37 +03:00
330 lines
12 KiB
C
330 lines
12 KiB
C
/* midithread.c -- example program showing how to do midi processing
|
|
in a preemptive thread
|
|
|
|
Notes: if you handle midi I/O from your main program, there will be
|
|
some delay before handling midi messages whenever the program is
|
|
doing something like file I/O, graphical interface updates, etc.
|
|
|
|
To handle midi with minimal delay, you should do all midi processing
|
|
in a separate, high priority thread. A convenient way to get a high
|
|
priority thread in windows is to use the timer callback provided by
|
|
the PortTime library. That is what we show here.
|
|
|
|
If the high priority thread writes to a file, prints to the console,
|
|
or does just about anything other than midi processing, this may
|
|
create delays, so all this processing should be off-loaded to the
|
|
"main" process or thread. Communication between threads can be tricky.
|
|
If one thread is writing at the same time the other is reading, very
|
|
tricky race conditions can arise, causing programs to behave
|
|
incorrectly, but only under certain timing conditions -- a terrible
|
|
thing to debug. Advanced programmers know this as a synchronization
|
|
problem. See any operating systems textbook for the complete story.
|
|
|
|
To avoid synchronization problems, a simple, reliable approach is
|
|
to communicate via messages. PortMidi offers a message queue as a
|
|
datatype, and operations to insert and remove messages. Use two
|
|
queues as follows: midi_to_main transfers messages from the midi
|
|
thread to the main thread, and main_to_midi transfers messages from
|
|
the main thread to the midi thread. Queues are safe for use between
|
|
threads as long as ONE thread writes and ONE thread reads. You must
|
|
NEVER allow two threads to write to the same queue.
|
|
|
|
This program transposes incoming midi data by an amount controlled
|
|
by the main program. To change the transposition, type an integer
|
|
followed by return. The main program sends this via a message queue
|
|
to the midi thread. To quit, type 'q' followed by return.
|
|
|
|
The midi thread can also send a pitch to the main program on request.
|
|
Type 'm' followed by return to wait for the next midi message and
|
|
print the pitch.
|
|
|
|
This program illustrates:
|
|
Midi processing in a high-priority thread.
|
|
Communication with a main process via message queues.
|
|
|
|
*/
|
|
|
|
#include "stdio.h"
|
|
#include "stdlib.h"
|
|
#include "string.h"
|
|
#include "assert.h"
|
|
#include "portmidi.h"
|
|
#include "pmutil.h"
|
|
#include "porttime.h"
|
|
|
|
/* if INPUT_BUFFER_SIZE is 0, PortMidi uses a default value */
|
|
#define INPUT_BUFFER_SIZE 0
|
|
|
|
#define OUTPUT_BUFFER_SIZE 100
|
|
#define DRIVER_INFO NULL
|
|
#define TIME_PROC NULL
|
|
#define TIME_INFO NULL
|
|
/* use zero latency because we want output to be immediate */
|
|
#define LATENCY 0
|
|
|
|
#define STRING_MAX 80
|
|
|
|
/**********************************/
|
|
/* DATA USED ONLY BY process_midi */
|
|
/* (except during initialization) */
|
|
/**********************************/
|
|
|
|
int active = FALSE;
|
|
int monitor = FALSE;
|
|
int midi_thru = TRUE;
|
|
|
|
int transpose;
|
|
PmStream *midi_in;
|
|
PmStream *midi_out;
|
|
|
|
/****************************/
|
|
/* END OF process_midi DATA */
|
|
/****************************/
|
|
|
|
/* shared queues */
|
|
PmQueue *midi_to_main;
|
|
PmQueue *main_to_midi;
|
|
|
|
#define QUIT_MSG 1000
|
|
#define MONITOR_MSG 1001
|
|
#define THRU_MSG 1002
|
|
|
|
/* timer interrupt for processing midi data */
|
|
void process_midi(PtTimestamp timestamp, void *userData)
|
|
{
|
|
PmError result;
|
|
PmEvent buffer; /* just one message at a time */
|
|
int32_t msg;
|
|
|
|
/* do nothing until initialization completes */
|
|
if (!active)
|
|
return;
|
|
|
|
/* check for messages */
|
|
do {
|
|
result = Pm_Dequeue(main_to_midi, &msg);
|
|
if (result) {
|
|
if (msg >= -127 && msg <= 127)
|
|
transpose = msg;
|
|
else if (msg == QUIT_MSG) {
|
|
/* acknowledge receipt of quit message */
|
|
Pm_Enqueue(midi_to_main, &msg);
|
|
active = FALSE;
|
|
return;
|
|
} else if (msg == MONITOR_MSG) {
|
|
/* main has requested a pitch. monitor is a flag that
|
|
* records the request:
|
|
*/
|
|
monitor = TRUE;
|
|
} else if (msg == THRU_MSG) {
|
|
/* toggle Thru on or off */
|
|
midi_thru = !midi_thru;
|
|
}
|
|
}
|
|
} while (result);
|
|
|
|
/* see if there is any midi input to process */
|
|
do {
|
|
result = Pm_Poll(midi_in);
|
|
if (result) {
|
|
int status, data1, data2;
|
|
if (Pm_Read(midi_in, &buffer, 1) == pmBufferOverflow)
|
|
continue;
|
|
if (midi_thru)
|
|
Pm_Write(midi_out, &buffer, 1);
|
|
/* unless there was overflow, we should have a message now */
|
|
status = Pm_MessageStatus(buffer.message);
|
|
data1 = Pm_MessageData1(buffer.message);
|
|
data2 = Pm_MessageData2(buffer.message);
|
|
if ((status & 0xF0) == 0x90 ||
|
|
(status & 0xF0) == 0x80) {
|
|
|
|
/* this is a note-on or note-off, so transpose and send */
|
|
data1 += transpose;
|
|
|
|
/* keep within midi pitch range, keep proper pitch class */
|
|
while (data1 > 127)
|
|
data1 -= 12;
|
|
while (data1 < 0)
|
|
data1 += 12;
|
|
|
|
/* send the message */
|
|
buffer.message = Pm_Message(status, data1, data2);
|
|
Pm_Write(midi_out, &buffer, 1);
|
|
|
|
/* if monitor is set, send the pitch to the main thread */
|
|
if (monitor) {
|
|
Pm_Enqueue(midi_to_main, &data1);
|
|
monitor = FALSE; /* only send one pitch per request */
|
|
}
|
|
}
|
|
}
|
|
} while (result);
|
|
}
|
|
|
|
void exit_with_message(char *msg)
|
|
{
|
|
char line[STRING_MAX];
|
|
printf("%s\n", msg);
|
|
fgets(line, STRING_MAX, stdin);
|
|
exit(1);
|
|
}
|
|
|
|
int main()
|
|
{
|
|
int id;
|
|
int32_t n;
|
|
const PmDeviceInfo *info;
|
|
char line[STRING_MAX];
|
|
int spin;
|
|
int done = FALSE;
|
|
|
|
/* determine what type of test to run */
|
|
printf("begin PortMidi multithread test...\n");
|
|
|
|
/* note that it is safe to call PortMidi from the main thread for
|
|
initialization and opening devices. You should not make any
|
|
calls to PortMidi from this thread once the midi thread begins.
|
|
to make PortMidi calls.
|
|
*/
|
|
|
|
/* make the message queues */
|
|
/* messages can be of any size and any type, but all messages in
|
|
* a given queue must have the same size. We'll just use int32_t's
|
|
* for our messages in this simple example
|
|
*/
|
|
midi_to_main = Pm_QueueCreate(32, sizeof(int32_t));
|
|
assert(midi_to_main != NULL);
|
|
main_to_midi = Pm_QueueCreate(32, sizeof(int32_t));
|
|
assert(main_to_midi != NULL);
|
|
|
|
/* a little test of enqueue and dequeue operations. Ordinarily,
|
|
* you would call Pm_Enqueue from one thread and Pm_Dequeue from
|
|
* the other. Since the midi thread is not running, this is safe.
|
|
*/
|
|
n = 1234567890;
|
|
Pm_Enqueue(midi_to_main, &n);
|
|
n = 987654321;
|
|
Pm_Enqueue(midi_to_main, &n);
|
|
Pm_Dequeue(midi_to_main, &n);
|
|
if (n != 1234567890) {
|
|
exit_with_message("Pm_Dequeue produced unexpected result.");
|
|
}
|
|
Pm_Dequeue(midi_to_main, &n);
|
|
if(n != 987654321) {
|
|
exit_with_message("Pm_Dequeue produced unexpected result.");
|
|
}
|
|
|
|
/* always start the timer before you start midi */
|
|
Pt_Start(1, &process_midi, 0); /* start a timer with millisecond accuracy */
|
|
/* the timer will call our function, process_midi() every millisecond */
|
|
|
|
Pm_Initialize();
|
|
|
|
id = Pm_GetDefaultOutputDeviceID();
|
|
info = Pm_GetDeviceInfo(id);
|
|
if (info == NULL) {
|
|
printf("Could not open default output device (%d).", id);
|
|
exit_with_message("");
|
|
}
|
|
printf("Opening output device %s %s\n", info->interf, info->name);
|
|
|
|
/* use zero latency because we want output to be immediate */
|
|
Pm_OpenOutput(&midi_out,
|
|
id,
|
|
DRIVER_INFO,
|
|
OUTPUT_BUFFER_SIZE,
|
|
TIME_PROC,
|
|
TIME_INFO,
|
|
LATENCY);
|
|
|
|
id = Pm_GetDefaultInputDeviceID();
|
|
info = Pm_GetDeviceInfo(id);
|
|
if (info == NULL) {
|
|
printf("Could not open default input device (%d).", id);
|
|
exit_with_message("");
|
|
}
|
|
printf("Opening input device %s %s\n", info->interf, info->name);
|
|
Pm_OpenInput(&midi_in,
|
|
id,
|
|
DRIVER_INFO,
|
|
INPUT_BUFFER_SIZE,
|
|
TIME_PROC,
|
|
TIME_INFO);
|
|
|
|
active = TRUE; /* enable processing in the midi thread -- yes, this
|
|
is a shared variable without synchronization, but
|
|
this simple assignment is safe */
|
|
|
|
printf("Enter midi input; it will be transformed as specified by...\n");
|
|
printf("%s\n%s\n%s\n",
|
|
"Type 'q' to quit, 'm' to monitor next pitch, t to toggle thru or",
|
|
"type a number to specify transposition.",
|
|
"Must terminate with [ENTER]");
|
|
|
|
while (!done) {
|
|
int32_t msg;
|
|
int input;
|
|
int len;
|
|
fgets(line, STRING_MAX, stdin);
|
|
/* remove the newline: */
|
|
len = strlen(line);
|
|
if (len > 0) line[len - 1] = 0; /* overwrite the newline char */
|
|
if (strcmp(line, "q") == 0) {
|
|
msg = QUIT_MSG;
|
|
Pm_Enqueue(main_to_midi, &msg);
|
|
/* wait for acknowlegement */
|
|
do {
|
|
spin = Pm_Dequeue(midi_to_main, &msg);
|
|
} while (spin == 0); /* spin */ ;
|
|
done = TRUE; /* leave the command loop and wrap up */
|
|
} else if (strcmp(line, "m") == 0) {
|
|
msg = MONITOR_MSG;
|
|
Pm_Enqueue(main_to_midi, &msg);
|
|
printf("Waiting for note...\n");
|
|
do {
|
|
spin = Pm_Dequeue(midi_to_main, &msg);
|
|
} while (spin == 0); /* spin */ ;
|
|
// convert int32_t to long for safe printing
|
|
printf("... pitch is %ld\n", (long) msg);
|
|
} else if (strcmp(line, "t") == 0) {
|
|
/* reading midi_thru asynchronously could give incorrect results,
|
|
e.g. if you type "t" twice before the midi thread responds to
|
|
the first one, but we'll do it this way anyway. Perhaps a more
|
|
correct way would be to wait for an acknowledgement message
|
|
containing the new state. */
|
|
printf("Setting THRU %s\n", (midi_thru ? "off" : "on"));
|
|
msg = THRU_MSG;
|
|
Pm_Enqueue(main_to_midi, &msg);
|
|
} else if (sscanf(line, "%d", &input) == 1) {
|
|
if (input >= -127 && input <= 127) {
|
|
/* send transposition value, make sur */
|
|
printf("Transposing by %d\n", input);
|
|
msg = (int32_t) input;
|
|
Pm_Enqueue(main_to_midi, &msg);
|
|
} else {
|
|
printf("Transposition must be within -127...127\n");
|
|
}
|
|
} else {
|
|
printf("%s\n%s\n",
|
|
"Type 'q[ENTER]' to quit, 'm[ENTER]' to monitor next pitch, or",
|
|
"enter a number to specify transposition.");
|
|
}
|
|
}
|
|
|
|
/* at this point, midi thread is inactive and we need to shut down
|
|
* the midi input and output
|
|
*/
|
|
Pt_Stop(); /* stop the timer */
|
|
Pm_QueueDestroy(midi_to_main);
|
|
Pm_QueueDestroy(main_to_midi);
|
|
|
|
/* Belinda! if close fails here, some memory is deleted, right??? */
|
|
Pm_Close(midi_in);
|
|
Pm_Close(midi_out);
|
|
|
|
printf("finished portMidi multithread test...enter any character to quit [RETURN]...");
|
|
fgets(line, STRING_MAX, stdin);
|
|
return 0;
|
|
}
|