diff options
Diffstat (limited to '3rdparty/portmidi/pm_test/testio.c')
-rw-r--r-- | 3rdparty/portmidi/pm_test/testio.c | 535 |
1 files changed, 535 insertions, 0 deletions
diff --git a/3rdparty/portmidi/pm_test/testio.c b/3rdparty/portmidi/pm_test/testio.c new file mode 100644 index 00000000000..c71ae087307 --- /dev/null +++ b/3rdparty/portmidi/pm_test/testio.c @@ -0,0 +1,535 @@ +#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; +} |