mame/3rdparty/portmidi/pm_test/testio.c
2025-04-20 08:16:03 +10:00

536 lines
17 KiB
C

#include "portmidi.h"
#include "porttime.h"
#include "stdlib.h"
#include "stdio.h"
#include "string.h"
#include "assert.h"
#define INPUT_BUFFER_SIZE 100
#define OUTPUT_BUFFER_SIZE 0
#define DRIVER_INFO NULL
#define TIME_PROC ((int32_t (*)(void *)) Pt_Time)
#define TIME_INFO NULL
#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
#define WAIT_FOR_ENTER while (getchar() != '\n') ;
int32_t latency = 0;
int verbose = FALSE;
/* crash the program to test whether midi ports are closed */
/**/
void doSomethingReallyStupid() {
int * tmp = NULL;
*tmp = 5;
}
/* exit the program without any explicit cleanup */
/**/
void doSomethingStupid() {
assert(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);
WAIT_FOR_ENTER
}
return i;
}
/*
* the somethingStupid parameter can be set to simulate a program crash.
* We want PortMidi to close Midi ports automatically in the event of a
* crash because Windows does not (and this may cause an OS crash)
*/
void main_test_input(unsigned int somethingStupid) {
PmStream * midi;
PmError status, length;
PmEvent buffer[1];
int num = 10;
int i = get_number("Type input number: ");
/* It is recommended to start timer before Midi; otherwise, PortMidi may
start the timer with its (default) parameters
*/
TIME_START;
/* open input device */
Pm_OpenInput(&midi,
i,
DRIVER_INFO,
INPUT_BUFFER_SIZE,
TIME_PROC,
TIME_INFO);
printf("Midi Input opened. Reading %d Midi messages...\n", num);
Pm_SetFilter(midi, PM_FILT_ACTIVE | PM_FILT_CLOCK | PM_FILT_SYSEX);
/* empty the buffer after setting filter, just in case anything
got through */
while (Pm_Poll(midi)) {
Pm_Read(midi, buffer, 1);
}
/* now start paying attention to messages */
i = 0; /* count messages as they arrive */
while (i < num) {
status = Pm_Poll(midi);
if (status == TRUE) {
length = Pm_Read(midi, buffer, 1);
if (length > 0) {
printf("Got message %d @ time %ld: timestamp %ld, "
"%2lx %2lx %2lx\n", i, (long) Pt_Time(),
(long) buffer[0].timestamp,
(long) Pm_MessageStatus(buffer[0].message),
(long) Pm_MessageData1(buffer[0].message),
(long) Pm_MessageData2(buffer[0].message));
i++;
} else {
assert(0);
}
}
/* simulate crash if somethingStupid is 1 or 2 */
if ((i > (num/2)) && (somethingStupid == 1)) {
doSomethingStupid();
} else if ((i > (num/2)) && (somethingStupid == 2)) {
doSomethingReallyStupid();
}
}
/* close device (this not explicitly needed in most implementations) */
printf("ready to close...");
Pm_Close(midi);
printf("done closing...");
}
void main_test_output(int isochronous_test)
{
PmStream * midi;
int32_t off_time;
int chord[] = { 60, 67, 76, 83, 90 };
#define chord_size 5
PmEvent buffer[chord_size];
PmTimestamp timestamp;
/* determine which output device to use */
int i = get_number("Type output number: ");
/* It is recommended to start timer before PortMidi */
TIME_START;
/* open output device -- since PortMidi avoids opening a timer
when latency is zero, we will pass in a NULL timer pointer
for that case. If PortMidi tries to access the time_proc,
we will crash, so this test will tell us something. */
Pm_OpenOutput(&midi,
i,
DRIVER_INFO,
OUTPUT_BUFFER_SIZE,
(latency == 0 ? NULL : TIME_PROC),
(latency == 0 ? NULL : TIME_INFO),
latency);
printf("Midi Output opened with %ld ms latency.\n", (long) latency);
/* output note on/off w/latency offset; hold until user prompts */
printf("ready to send program 1 change... (type ENTER):");
WAIT_FOR_ENTER
/* if we were writing midi for immediate output, we could always use
timestamps of zero, but since we may be writing with latency, we
will explicitly set the timestamp to "now" by getting the time.
The source of timestamps should always correspond to the TIME_PROC
and TIME_INFO parameters used in Pm_OpenOutput(). */
buffer[0].timestamp = Pt_Time();
/* Send a program change to increase the chances we will hear notes */
/* Program 0 is usually a piano, but you can change it here: */
#define PROGRAM 0
buffer[0].message = Pm_Message(0xC0, PROGRAM, 0);
Pm_Write(midi, buffer, 1);
if (isochronous_test) { // play 4 notes per sec for 20s
int count;
PmTimestamp start;
if (latency < 100) {
printf("Warning: latency < 100, but this test sends messages"
" at times that are jittered by up to 100ms, so you"
" may hear uneven timing\n");
}
printf("Starting in 1s..."); fflush(stdout);
Pt_Sleep(1000);
start = Pt_Time();
for (count = 0; count < 80; count++) {
PmTimestamp next_time;
buffer[0].timestamp = start + count * 250;
buffer[0].message = Pm_Message(0x90, 69, 100);
buffer[1].timestamp = start + count * 250 + 200;
buffer[1].message = Pm_Message(0x90, 69, 0);
Pm_Write(midi, buffer, 2);
next_time = start + (count + 1) * 250;
// sleep for a random time up to 100ms to add jitter to
// the times at which we send messages. PortMidi timing
// should remove the jitter if latency > 100
while (Pt_Time() < next_time) {
Pt_Sleep(rand() % 100);
}
}
printf("Done sending 80 notes at 4 notes per second.\n");
} else {
printf("ready to note-on... (type ENTER):");
WAIT_FOR_ENTER
buffer[0].timestamp = Pt_Time();
buffer[0].message = Pm_Message(0x90, 60, 100);
Pm_Write(midi, buffer, 1);
printf("ready to note-off... (type ENTER):");
WAIT_FOR_ENTER
buffer[0].timestamp = Pt_Time();
buffer[0].message = Pm_Message(0x90, 60, 0);
Pm_Write(midi, buffer, 1);
/* output short note on/off w/latency offset; hold until user prompts */
printf("ready to note-on (short form)... (type ENTER):");
WAIT_FOR_ENTER
Pm_WriteShort(midi, Pt_Time(),
Pm_Message(0x90, 60, 100));
printf("ready to note-off (short form)... (type ENTER):");
WAIT_FOR_ENTER
Pm_WriteShort(midi, Pt_Time(),
Pm_Message(0x90, 60, 0));
/* output several note on/offs to test timing.
Should be 1s between notes */
if (latency == 0) {
printf("chord should not arpeggiate, latency == 0\n");
} else {
printf("chord should arpeggiate (latency = %ld > 0\n",
(long) latency);
}
printf("ready to chord-on/chord-off... (type ENTER):");
WAIT_FOR_ENTER
timestamp = Pt_Time();
printf("starting timestamp %ld\n", (long) timestamp);
for (i = 0; i < chord_size; i++) {
buffer[i].timestamp = timestamp + 1000 * i;
buffer[i].message = Pm_Message(0x90, chord[i], 100);
}
Pm_Write(midi, buffer, chord_size);
off_time = timestamp + 1000 + chord_size * 1000;
while (Pt_Time() < off_time)
Pt_Sleep(10); /* wait */
for (i = 0; i < chord_size; i++) {
buffer[i].timestamp = timestamp + 1000 * i;
buffer[i].message = Pm_Message(0x90, chord[i], 0);
}
Pm_Write(midi, buffer, chord_size);
}
/* close device (this not explicitly needed in most implementations) */
printf("ready to close and terminate... (type ENTER):");
WAIT_FOR_ENTER
Pm_Close(midi);
Pm_Terminate();
printf("done closing and terminating...\n");
}
void main_test_both()
{
int i = 0;
int in, out;
PmStream * midi, * midiOut;
PmEvent buffer[1];
PmError status, length;
int num = 11;
in = get_number("Type input number: ");
out = get_number("Type output number: ");
/* In is recommended to start timer before PortMidi */
TIME_START;
Pm_OpenOutput(&midiOut,
out,
DRIVER_INFO,
OUTPUT_BUFFER_SIZE,
TIME_PROC,
TIME_INFO,
latency);
printf("Midi Output opened with %ld ms latency.\n", (long) latency);
/* open input device */
Pm_OpenInput(&midi,
in,
DRIVER_INFO,
INPUT_BUFFER_SIZE,
TIME_PROC,
TIME_INFO);
printf("Midi Input opened. Reading %d Midi messages...\n", num);
Pm_SetFilter(midi, PM_FILT_ACTIVE | PM_FILT_CLOCK);
/* empty the buffer after setting filter, just in case anything
got through */
while (Pm_Poll(midi)) {
Pm_Read(midi, buffer, 1);
}
i = 0;
while (i < num) {
status = Pm_Poll(midi);
if (status == TRUE) {
length = Pm_Read(midi,buffer,1);
if (length > 0) {
Pm_Write(midiOut, buffer, 1);
printf("Got message %d @ time %ld: timestamp %ld, "
"%2lx %2lx %2lx\n", i, (long) Pt_Time(),
(long) buffer[0].timestamp,
(long) Pm_MessageStatus(buffer[0].message),
(long) Pm_MessageData1(buffer[0].message),
(long) Pm_MessageData2(buffer[0].message));
i++;
} else {
assert(0);
}
}
}
/* allow time for last message to go out */
Pt_Sleep(100 + latency);
/* close midi devices */
Pm_Close(midi);
Pm_Close(midiOut);
Pm_Terminate();
}
/* main_test_stream exercises windows winmm API's stream mode */
/* The winmm stream mode is used for latency>0, and sends
timestamped messages. The timestamps are relative (delta)
times, whereas PortMidi times are absolute. Since peculiar
things happen when messages are not always sent in advance,
this function allows us to exercise the system and test it.
*/
void main_test_stream() {
PmStream * midi;
PmEvent buffer[16];
/* determine which output device to use */
int i = get_number("Type output number: ");
latency = 500; /* ignore LATENCY for this test and
fix the latency at 500ms */
/* It is recommended to start timer before PortMidi */
TIME_START;
/* open output device */
Pm_OpenOutput(&midi,
i,
DRIVER_INFO,
OUTPUT_BUFFER_SIZE,
TIME_PROC,
TIME_INFO,
latency);
printf("Midi Output opened with %ld ms latency.\n", (long) latency);
/* output note on/off w/latency offset; hold until user prompts */
printf("ready to send output... (type ENTER):");
WAIT_FOR_ENTER
/* if we were writing midi for immediate output, we could always use
timestamps of zero, but since we may be writing with latency, we
will explicitly set the timestamp to "now" by getting the time.
The source of timestamps should always correspond to the TIME_PROC
and TIME_INFO parameters used in Pm_OpenOutput(). */
buffer[0].timestamp = Pt_Time();
buffer[0].message = Pm_Message(0xC0, 0, 0);
buffer[1].timestamp = buffer[0].timestamp;
buffer[1].message = Pm_Message(0x90, 60, 100);
buffer[2].timestamp = buffer[0].timestamp + 1000;
buffer[2].message = Pm_Message(0x90, 62, 100);
buffer[3].timestamp = buffer[0].timestamp + 2000;
buffer[3].message = Pm_Message(0x90, 64, 100);
buffer[4].timestamp = buffer[0].timestamp + 3000;
buffer[4].message = Pm_Message(0x90, 66, 100);
buffer[5].timestamp = buffer[0].timestamp + 4000;
buffer[5].message = Pm_Message(0x90, 60, 0);
buffer[6].timestamp = buffer[0].timestamp + 4000;
buffer[6].message = Pm_Message(0x90, 62, 0);
buffer[7].timestamp = buffer[0].timestamp + 4000;
buffer[7].message = Pm_Message(0x90, 64, 0);
buffer[8].timestamp = buffer[0].timestamp + 4000;
buffer[8].message = Pm_Message(0x90, 66, 0);
Pm_Write(midi, buffer, 9);
#ifdef SEND8
/* Now, we're ready for the real test.
Play 4 notes at now, now+500, now+1000, and now+1500
Then wait until now+2000.
Play 4 more notes as before.
We should hear 8 evenly spaced notes. */
now = Pt_Time();
for (i = 0; i < 4; i++) {
buffer[i * 2].timestamp = now + (i * 500);
buffer[i * 2].message = Pm_Message(0x90, 60, 100);
buffer[i * 2 + 1].timestamp = now + 250 + (i * 500);
buffer[i * 2 + 1].message = Pm_Message(0x90, 60, 0);
}
Pm_Write(midi, buffer, 8);
while (Pt_Time() < now + 2500)
Pt_Sleep(10);
/* now we are 500 ms behind schedule, but since the latency
is 500, the delay should not be audible */
now += 2000;
for (i = 0; i < 4; i++) {
buffer[i * 2].timestamp = now + (i * 500);
buffer[i * 2].message = Pm_Message(0x90, 60, 100);
buffer[i * 2 + 1].timestamp = now + 250 + (i * 500);
buffer[i * 2 + 1].message = Pm_Message(0x90, 60, 0);
}
Pm_Write(midi, buffer, 8);
#endif
/* close device (this not explicitly needed in most implementations) */
printf("ready to close and terminate... (type ENTER):");
WAIT_FOR_ENTER
Pm_Close(midi);
Pm_Terminate();
printf("done closing and terminating...\n");
}
void show_usage()
{
printf("Usage: test [-h] [-l latency-in-ms] [-v]\n"
" -h for this help message (only)\n"
" -l for latency\n"
" -v for verbose (enables more output)\n");
}
int main(int argc, char *argv[])
{
int default_in;
int default_out;
int i = 0, n = 0;
int test_input = 0, test_output = 0, test_both = 0, somethingStupid = 0;
int isochronous_test = 0;
int stream_test = 0;
int latency_valid = FALSE;
show_usage();
if (sizeof(void *) == 8)
printf("Apparently this is a 64-bit machine.\n");
else if (sizeof(void *) == 4)
printf ("Apparently this is a 32-bit machine.\n");
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-h") == 0) {
exit(0);
} else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) {
i = i + 1;
latency = atoi(argv[i]);
printf("Latency will be %ld\n", (long) latency);
latency_valid = TRUE;
} else if (strcmp(argv[i], "-v") == 0) {
printf("Verbose is now TRUE\n");
verbose = TRUE; /* not currently used for anything */
} else {
show_usage();
exit(0);
}
}
while (!latency_valid) {
int lat; // declared int to match "%d"
printf("Latency in ms: ");
if (scanf("%d", &lat) == 1) {
latency = (int32_t) lat; // coerce from "%d" to known size
latency_valid = TRUE;
}
}
/* determine what type of test to run */
printf("begin portMidi test...\n");
printf("enter your choice...\n 1: test input\n"
" 2: test input (fail w/assert)\n"
" 3: test input (fail w/NULL assign)\n"
" 4: test output\n 5: test both\n"
" 6: stream test (for WinMM)\n"
" 7. isochronous out\n");
while (n != 1) {
n = scanf("%d", &i);
WAIT_FOR_ENTER
switch(i) {
case 1:
test_input = 1;
break;
case 2:
test_input = 1;
somethingStupid = 1;
break;
case 3:
test_input = 1;
somethingStupid = 2;
break;
case 4:
test_output = 1;
break;
case 5:
test_both = 1;
break;
case 6:
stream_test = 1;
break;
case 7:
test_output = 1;
isochronous_test = 1;
break;
default:
printf("got %d (invalid input)\n", n);
break;
}
}
/* list device information */
default_in = Pm_GetDefaultInputDeviceID();
default_out = Pm_GetDefaultOutputDeviceID();
for (i = 0; i < Pm_CountDevices(); i++) {
char *deflt;
const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
if (((test_input | test_both) & info->input) |
((test_output | test_both | stream_test) & info->output)) {
printf("%d: %s, %s", i, info->interf, info->name);
if (info->input) {
deflt = (i == default_in ? "default " : "");
printf(" (%sinput)", deflt);
}
if (info->output) {
deflt = (i == default_out ? "default " : "");
printf(" (%soutput)", deflt);
}
printf("\n");
}
}
/* run test */
if (stream_test) {
main_test_stream();
} else if (test_input) {
main_test_input(somethingStupid);
} else if (test_output) {
main_test_output(isochronous_test);
} else if (test_both) {
main_test_both();
}
printf("finished portMidi test...type ENTER to quit...");
WAIT_FOR_ENTER
return 0;
}