From 988f5d2b5343850e19ad1512cefe6c53953aa02e Mon Sep 17 00:00:00 2001 From: Mitja Felicijan Date: Mon, 7 Oct 2024 06:50:04 +0200 Subject: Added bunch of examples --- portmidi/pm_test/CMakeLists.txt | 46 ++++ portmidi/pm_test/README.txt | 363 ++++++++++++++++++++++++ portmidi/pm_test/fast.c | 290 ++++++++++++++++++++ portmidi/pm_test/fastrcv.c | 255 +++++++++++++++++ portmidi/pm_test/latency.c | 287 +++++++++++++++++++ portmidi/pm_test/midiclock.c | 282 +++++++++++++++++++ portmidi/pm_test/midithread.c | 343 +++++++++++++++++++++++ portmidi/pm_test/midithru.c | 455 ++++++++++++++++++++++++++++++ portmidi/pm_test/mm.c | 595 ++++++++++++++++++++++++++++++++++++++++ portmidi/pm_test/multivirtual.c | 223 +++++++++++++++ portmidi/pm_test/pmlist.c | 63 +++++ portmidi/pm_test/qtest.c | 174 ++++++++++++ portmidi/pm_test/recvvirtual.c | 175 ++++++++++++ portmidi/pm_test/sendvirtual.c | 194 +++++++++++++ portmidi/pm_test/sysex.c | 556 +++++++++++++++++++++++++++++++++++++ portmidi/pm_test/testio.c | 594 +++++++++++++++++++++++++++++++++++++++ portmidi/pm_test/txdata.syx | 257 +++++++++++++++++ portmidi/pm_test/virttest.c | 339 +++++++++++++++++++++++ 18 files changed, 5491 insertions(+) create mode 100644 portmidi/pm_test/CMakeLists.txt create mode 100644 portmidi/pm_test/README.txt create mode 100644 portmidi/pm_test/fast.c create mode 100644 portmidi/pm_test/fastrcv.c create mode 100755 portmidi/pm_test/latency.c create mode 100644 portmidi/pm_test/midiclock.c create mode 100755 portmidi/pm_test/midithread.c create mode 100755 portmidi/pm_test/midithru.c create mode 100755 portmidi/pm_test/mm.c create mode 100644 portmidi/pm_test/multivirtual.c create mode 100644 portmidi/pm_test/pmlist.c create mode 100644 portmidi/pm_test/qtest.c create mode 100644 portmidi/pm_test/recvvirtual.c create mode 100644 portmidi/pm_test/sendvirtual.c create mode 100755 portmidi/pm_test/sysex.c create mode 100755 portmidi/pm_test/testio.c create mode 100755 portmidi/pm_test/txdata.syx create mode 100644 portmidi/pm_test/virttest.c (limited to 'portmidi/pm_test') diff --git a/portmidi/pm_test/CMakeLists.txt b/portmidi/pm_test/CMakeLists.txt new file mode 100644 index 0000000..ae0fe48 --- /dev/null +++ b/portmidi/pm_test/CMakeLists.txt @@ -0,0 +1,46 @@ +# CMake file to build tests in this directory: pm_test + +# set the build directory to be in portmidi, not in portmidi/pm_test +# this is required for Xcode: +if(APPLE) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) +endif(APPLE) + +# if(WIN32) +# if(NOT BUILD_SHARED_LIBS) + # /MDd is multithread debug DLL, /MTd is multithread debug + # /MD is multithread DLL, /MT is multithread. Change to static: +# include(../pm_win/static.cmake) +# endif() +# endif(WIN32) + +if(HAIKU) + add_compile_options(-fPIC) # Haiku x86_64 needs this explicitly +endif() + +macro(add_test name) + add_executable(${name} ${name}.c) + target_link_libraries(${name} PRIVATE portmidi) + set_property(TARGET ${name} PROPERTY MSVC_RUNTIME_LIBRARY + "MultiThreaded$<$:Debug>${MSVCRT_DLL}") +endmacro(add_test) + +add_test(testio) +add_test(midithread) +add_test(midithru) +add_test(sysex) +add_test(latency) +add_test(mm) +add_test(midiclock) +add_test(qtest) +add_test(fast) +add_test(fastrcv) +add_test(pmlist) +if(WIN32) +# windows does not implement Pm_CreateVirtualInput or Pm_CreateVirtualOutput +else(WIN32) +add_test(recvvirtual) +add_test(sendvirtual) +add_test(multivirtual) +add_test(virttest) +endif(WIN32) diff --git a/portmidi/pm_test/README.txt b/portmidi/pm_test/README.txt new file mode 100644 index 0000000..6c0c7ab --- /dev/null +++ b/portmidi/pm_test/README.txt @@ -0,0 +1,363 @@ +README.txt - for pm_test directory + +These are all test programs for PortMidi + +Because device numbers depend on the system, there is no automated +script to run all tests on PortMidi. + +To run the full set of tests manually: + +Note: everything is run from the ../Debug or ../Release directory. +Actual or example input is marked with >>, e.g., >>0 means type 0 +Comments are shown in square brackets [like this] + +1. ./qtest -- output should show a bunch of tests and no error message. + +2. ./testio [test input] +Latency in ms: >>0 +enter your choice... >>1 +Type input number: >>6 [pick a working input device] +[play some notes, look for note-on (0x90) with pitch and velocity data] + +3. ./testio [test input (fail w/assert)] +Latency in ms: >>0 +enter your choice... >>2 +Type input number: >>6 [pick a working input device] +[play some notes, program will abort after 5 messages +(this test only applies to a Debug build, otherwise +the assert() macro is disabled.)] + +4. ./testio [test input (fail w/NULL assign)] +Latency in ms: >>0 +enter your choice... >>3 +Type input number: >>6 [pick a working input device] +[play some notes, program will Segmentation fault after 5 messages +(this test may not Segfault in the Release build; if not +try testing with a Debug build.)] + +5. ./testio [test output, no latency] +Latency in ms: >>0 +enter your choice... >>4 +Type output number: >>2 [pick a working output device] +>> [type ENTER when prompted (7 times)] +[hear note on, note off, note on, note off, chord] + +6. ./testio [test output, latency > 0] +Latency in ms: >>300 +enter your choice... >>4 +Type output number: >>2 [pick a working output device] +>> [type ENTER when prompted (7 times)] +[hear note on, note off, note on, note off, arpeggiated chord + (delay of 300ms should be apparent)] + +7. ./testio [for both, no latency] +Latency in ms: >>0 +enter your choice... >>5 +Type input number: >>6 [pick a working input device] +Type output number: >>2 [pick a working output device] +[play notes on input, hear them on output] + +8. ./testio [for both, latency > 0] +Latency in ms: >>300 +enter your choice... >>5 +Type input number: >>6 [pick a working input device] +Type output number: >>2 [pick a working output device] +[play notes on input, hear them on output (delay of 300ms is apparent)] + +9. ./testio [stream test] +Latency in ms: >>0 [does not matter] +enter your choice... >>6 +Type output number: >>2 [pick a working output device] +>> [type ENTER to start] +[hear 4 notes: C D E F# at one note per second, then all turn off] +ready to close and terminate... (type ENTER) :>> [type ENTER (twice)] + +10. ./testio [isochronous out] +Latency in ms: >>300 +enter your choice... >>7 +Type output number: >>2 [pick a working output device] +ready to send program 1 change... (type ENTER): >> [type ENTER] +[hear 80 notes, exactly 4 notes per second, no jitter] + +11. ./latency [no MIDI, histogram] +Choose timer period (in ms, >= 1): >>1 +? >>1 [No MIDI traffic option] +[wait about 10 seconds] +>> [type ENTER] +[output should be something like ... Maximum latency: 1 milliseconds] + +12. ./latency [MIDI input, histogram] +Choose timer period (in ms, >= 1): >>1 +? >>2 [MIDI input option] +Midi input device number: >>6 [pick a working input device] +[wait about 5 seconds, play input for 10 seconds ] +>> [type ENTER] +[output should be something like ... Maximum latency: 3 milliseconds] + +13. ./latency [MIDI output, histogram] +Choose timer period (in ms, >= 1): >>1 +? >>3 [MIDI output option] +Midi output device number: >>2 [pick a working output device] +Midi output should be sent every __ callback iterations: >>50 +[wait until you hear notes for 5 or 10 seconds] +>> [type ENTER to stop] +[output should be something like ... Maximum latency: 2 milliseconds] + +14. ./latency [MIDI input and output, histogram] +Choose timer period (in ms, >= 1): >>1 +? >>4 [MIDI input and output option] +Midi input device number: >>6 [pick a working input device] +Midi output device number: >>2 [pick a working output device] +Midi output should be sent every __ callback iterations: >>50 +[wait until you hear notes, simultaneously play notes for 5 or 10 seconds] +>> [type ENTER to stop] +[output should be something like ... Maximum latency: 1 milliseconds] + +15. ./mm [test with device input] +Type input device number: >>6 [pick a working input device] +[play some notes, see notes printed] +>>q [Type q ENTER when finished to exit] + +16. ./midithread -i 6 -o 2 [use working input/output device numbers] +>>5 [enter a transposition number] +[play some notes, hear parallel 4ths] +>>q [quit after ENTER a couple of times] + +17. ./midiclock [in one shell] + ./mm [in another shell] +[Goal is send clock messages to MIDI monitor program. This requires + either a hardware loopback (MIDI cable from OUT to IN on interface) + or a software loopback (macOS IAC bus or ALSA MIDI Through Port)] +[For midiclock application:] + Type output device number: >>0 [pick a device with loopback] + Type ENTER to start MIDI CLOCK: >> [type ENTER] +[For mm application:] + Type input device number: >>1 [pick device with loopback] + [Wait a few seconds] + >>s [to get Clock Count] + >>s [expect to get a higher Clock Count] +[For midiclock application:] + >>c [turn off clocks] +[For mm application:] + >>s [to get Clock Count] + >>s [expect to Clock Count stays the same] +[For midiclock application:] + >>t [turn on time code, see Time Code Quarter Frame messages from mm] + >>q [to quit] +[For mm application:] + >>q [to quit] + +18. ./midithru -i 6 -o 2 [use working input/output device numbers] +[Play notes on input evice; notes are sent immediately and also with a + 2 sec delay to the output device; program terminates in 60 seconds or + when you play B3 (B below Middle C)] +>> [ENTER to exit] + +19. ./recvvirtual -h [in one shell, macOS and Linux only] + ./recvvirtual -m vvv [for mac, or -c vvv -p vvvport for linux] + ./testio [in another shell] +[For testio application:] + Latency in ms: >>0 + enter your choice... >>4 [test output] + Type output number: >>9 [select the "portmidi (output)" device] + [type ENTER to each prompt, see that recvvirtual "Got message 0" + through "Got message 9"] + >> [ENTER to quit] +[For recvvirtual application:] + >> [ENTER to quit] + +20. ./sendvirtual -h [in one shell, macOS and Linux only] + ./sendvirtual -m vvv [for mac, or -c vvv -p vvvport for linux] + ./mm [in another shell] +[For mm application:] + Type input device number: >>10 [select the "portmidi" device] +[For sendvirtual application:] + Type ENTER to send messages: >> [type ENTER] + [see NoteOn and off messages received by mm for Key 60-64] + >> [ENTER to quit] +[For mm application:] + >>q [and ENTER twice to quit] + +21. ./sysex [no latency] +[This requires either a hardware loopback (MIDI cable from OUT to IN + on interface) or a software loopback (macOS IAC bus or ALSA MIDI + Through Port)] +>>l [for loopback test] +Type output device number: >>0 [pick output device to loopback] +Latency in milliseconds: >>0 +Type input device number: >>0 [pick input device for loopback] +[Program will send 100,000 bytes. After awhile, program will quit. + You can read the Cummulative bytes/sec value.] + +22. ./sysex [latency > 0] +[This requires either a hardware loopback (MIDI cable from OUT to IN + on interface) or a software loopback (macOS IAC bus or ALSA MIDI + Through Port)] +>>l [for loopback test] +Type output device number: >>0 [pick output device to loopback] +Latency in milliseconds: >>100 +Type input device number: >>0 [pick input device for loopback] +[Program will send 100,000 bytes. After awhile, program will quit. You + can read the Cummulative bytes/sec value; it is affected by latency.] + +23. ./fast [no latency] + ./fastrcv [in another shell] +[This is a speed check, especially for macOSX IAC bus connections, + which are known to drop messages if you send messages too fast. + fast and fastrcv must use a loopback to function.] +[In fastrcv:] + Input device number: >>1 [pick a non-hardware device if possible] +[In fast:] + Latency in ms: >>0 + Rate in messages per second: >>10000 + Duration in seconds: >>10 + Output device number: >>0 [pick a non-hardware device if possible] + sending output... +[see message counts and times; on Linux you should get about 10000 + messages/s; on macOS you should get about 1800 messages/s; Windows + does not have software ports, so data rate might be limited by the + loopback device you use.] + +Check output of fastrcv: there should be no errors, just msg/sec.] + +24. ./fast [latency > 0] + ./fastrcv [in another shell] +[This is a speed check, especially for macOSX IAC bus connections, + which are known to drop messages if you send messages too fast. + fast and fastrcv must use a loopback to function.] +[In fastrcv:] + Input device number: >>1 [pick a non-hardware device if possible] +[In fast:] + Latency in ms: >>30 [Note for ALSA, use latency * msgs/ms < 400] + Rate in messages per second: >>10000 + Duration in seconds: >>10 + Output device number: >>0 [pick a non-hardware device if possible] + sending output... +[see message counts and times; on Linux you should get about 10000 + messages/s; on macOS you should get about 1800 messages/s; Windows + does not have software ports, so data rate might be limited by the + loopback device you use.] + +Check output of fastrcv: there should be no errors, just msg/sec.] + +25. ./fast [virtual output port, latency = 0, macOS and Linux only] + ./fastrcv [in another shell] +[Start fast first:] + Latency in ms: >>0 + Rate in messages per second: >>10000 + Duration in seconds: >>10 + Output device number: >>9 [enter number listed for "Create virtual + port named 'fast' (output)"] + Pausing so you can connect a receiver to the newly created + "fast" port. Type ENTER to proceed: +[In fastrcv:] + Input device number: >>3 [pick the device named "fast (input)"] +[In fast:] + >> [type ENTER to start] +[see message counts and times as above ] + +Check output of fastrcv: there should be no errors, just msg/sec.] + +26. ./fast [virtual output port, latency > 0, macOS and Linux only] + ./fastrcv [in another shell] +[Start fast first:] + Latency in ms: >>30 [Note for ALSA, use latency * msgs/ms < 400] + Rate in messages per second: >>10000 + Duration in seconds: >>10 + Output device number: >>9 [enter number listed for "Create virtual + port named 'fast' (output)"] + Pausing so you can connect a receiver to the newly created + "fast" port. Type ENTER to proceed: +[In fastrcv:] + Input device number: >>3 [pick the device named "fast (input)"] +[In fast:] + >> [type ENTER to start] +[see message counts and times as above ] + +Check output of fastrcv: there should be no errors, just msg/sec.] + +27. ./fast [latency = 0, macOS and Linux only] + ./fastrcv [virtual input port, in another shell] +[In fastrcv:] + Input device number: >>8 [enter number listed for "Create virtual + port named 'fastrcv' (input)"] +[In fast:] + Latency in ms: >>0 + Rate in messages per second: >>10000 + Duration in seconds: >>10 + Output device number: >>7 [pick the device named "fastrcv (output)"] + sending output... +[see message counts and times as above ] + +Check output of fastrcv: there should be no errors, just msg/sec.] + +28. ./fast [latency > 0, macOS and Linux only] + ./fastrcv [virtual input port, in another shell] +[In fastrcv:] + Input device number: >>8 [enter number listed for "Create virtual + port named 'fastrcv' (input)"] +[In fast:] + Latency in ms: >>30 [Note for ALSA, use latency * msgs/ms < 400] + Rate in messages per second: >>10000 + Duration in seconds: >>10 + Output device number: >>7 [pick the device named "fastrcv (output)"] + sending output... +[see message counts and times as above ] + +Check output of fastrcv: there should be no errors, just msg/sec.] + +29. ./midithru -v -n [virtual input and output, macOS and Linux only] + ./fast [latency = 0] + ./fastrcv [in another shell] +[Start midithru first, it will run for 60 seconds] +[In fastrcv:] + Input device number: >>3 [pick the device named + port named "midithru (input)"] +[In fast:] + Latency in ms: >>0 + Rate in messages per second: >>10000 + Duration in seconds: >>10 + Output device number: >>8 [pick the device named "midithru (output)"] + sending output... +[see message counts and times as above, on Mac, output from fast to + midithru AND output from midithru to fastrcv are rate limited, so + as in other tests, it will take more than 10s to receive all the + messages and the receiving message rate will be about 1800 messages/second] + +30. ./multivirtual [macOS and Linux only] + ./testio + ./testio +[Start multivirtual first] +[In first testio:] + Latency in ms: >>0 + enter your choice... >>5 [test both] + Type input number: >>1 [pick portmidi1 (input) + Type output number: >>4 [pick portmidi1 (output) +[In second testio:] + Latency in ms: >>10 + enter your choice... >>5 [test both] + Type input number: >>2 [pick portmidi2 (input) + Type output number: >>5 [pick portmidi2 (output) +[In multivirtual:] + Type ENTER to send messages: >> [type ENTER to start] +[see that each testio gets 11 messages (0 to 10) at reasonable times + (e.g. 2077 to 7580, and the "@" times (real times) should match the + timestamps). multivirtual should also report reasonable times and + line near the end of output should be "Got 11 messages from + portmidi1 and 11 from portmidi2; expected 11."] + +31. ./multivirtual [macOS and Linux only] + ./multivirtual +[Second instance should report "PortMidi call failed... + PortMidi: Cannot create virtual device: name is taken"] + +32. pmlist + ./pmlist [check the output] + [plug in or remove a device] + >> [type RETURN] + [check for changes in device list] + >>q + + + + diff --git a/portmidi/pm_test/fast.c b/portmidi/pm_test/fast.c new file mode 100644 index 0000000..102697e --- /dev/null +++ b/portmidi/pm_test/fast.c @@ -0,0 +1,290 @@ +/* fast.c -- send many MIDI messages very fast. + * + * This is a stress test created to explore reports of + * pm_write() call blocking (forever) on Linux when + * sending very dense MIDI sequences. + * + * Modified 8 Aug 2017 with -n to send expired timestamps + * to test a theory about why Linux ALSA hangs in Audacity. + * + * Modified 9 Aug 2017 with -m, -p to test when timestamps are + * wrapping from negative to positive or positive to negative. + * + * Roger B. Dannenberg, Aug 2017 + */ + +#include "portmidi.h" +#include "porttime.h" +#include "stdlib.h" +#include "stdio.h" +#include "string.h" +#include "assert.h" + +#define DEVICE_INFO NULL +#define DRIVER_INFO NULL +#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ + +#define STRING_MAX 80 /* used for console input */ +// need to get declaration for Sleep() +#ifdef WIN32 +#include "windows.h" +#else +#include +#define Sleep(n) usleep(n * 1000) +#endif + + +int32_t latency = 0; +int32_t msgrate = 0; +int deviceno = -9999; +int duration = 0; +int expired_timestamps = FALSE; +int use_timeoffset = 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; +} + + +/* get_time -- the time reference. Normally, this will be the default + * time, Pt_Time(), but if you use the -p or -m option, the time + * reference will start at an offset of -10s for -m, or + * maximum_time - 10s for -p, so that we can observe what happens + * with negative time or when time changes sign or wraps (by + * generating output for more than 10s). + */ +PmTimestamp get_time(void *info) +{ + PmTimestamp now = (PmTimestamp) (Pt_Time() + use_timeoffset); + return now; +} + + +void fast_test() +{ + PmStream *midi; + char line[STRING_MAX]; + int pause = FALSE; /* pause if this is a virtual output port */ + PmError err = pmNoError; + /* output buffer size should be a little more than + msgrate * latency / 1000. PortMidi will guarantee + a minimum of latency / 2 */ + int buffer_size = msgrate * latency / 900; + PmTimestamp start, now; + int msgcnt = 0; + int polling_count = 0; + int pitch = 60; + int printtime = 1000; + + /* It is recommended to start timer before PortMidi */ + TIME_START; + + /* open output device */ + if (deviceno == Pm_CountDevices()) { + deviceno = Pm_CreateVirtualOutput("fast", NULL, DEVICE_INFO); + if (deviceno >= 0) { + err = Pm_OpenOutput(&midi, deviceno, DRIVER_INFO, buffer_size, + get_time, NULL, latency); + pause = TRUE; + } + } else if (err >= pmNoError) { + err = Pm_OpenOutput(&midi, deviceno, DRIVER_INFO, buffer_size, + get_time, NULL, latency); + } + if (err == pmHostError) { + Pm_GetHostErrorText(line, STRING_MAX); + printf("PortMidi found host error...\n %s\n", line); + goto done; + } else if (err < 0) { + printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); + goto done; + } + printf("Midi Output opened with %ld ms latency.\n", (long) latency); + if (pause) { + printf("Pausing so you can connect a receiver to the newly created\n" + " \"fast\" port. Type ENTER to proceed: "); + while (getchar() != '\n') ; + } + /* wait a sec after printing previous line */ + start = get_time(NULL) + 1000; + while (start > get_time(NULL)) { + Sleep(10); + } + printf("sending output...\n"); + fflush(stdout); /* make sure message goes to console */ + + /* every 10ms send on/off pairs at timestamps set to current time */ + now = get_time(NULL); + /* if expired_timestamps, we want to send timestamps that have + * expired. They should be sent immediately, but there's a suggestion + * that negative delay might cause problems in the ALSA implementation + * so this is something we can test using the -n flag. + */ + if (expired_timestamps) { + now = now - 2 * latency; + } + + while (((PmTimestamp) (now - start)) < duration * 1000 || pitch != 60) { + /* how many messages do we send? Total should be + * (elapsed * rate) / 1000 + */ + int send_total = (((PmTimestamp) ((now - start))) * msgrate) / 1000; + /* always send until pitch would be 60 so if we run again, the + next pitch (60) will be expected */ + if (msgcnt < send_total) { + if ((msgcnt & 1) == 0) { + Pm_WriteShort(midi, now, Pm_Message(0x90, pitch, 100)); + } else { + Pm_WriteShort(midi, now, Pm_Message(0x90, pitch, 0)); + /* play 60, 61, 62, ... 71, then wrap back to 60, 61, ... */ + pitch = (pitch - 59) % 12 + 60; + } + msgcnt += 1; + if (((PmTimestamp) (now - start)) >= printtime) { + printf("%d at %dms, polling count %d\n", msgcnt, now - start, + polling_count); + fflush(stdout); /* make sure message goes to console */ + printtime += 1000; /* next msg in 1s */ + } + } + now = get_time(NULL); + polling_count++; + } + /* close device (this not explicitly needed in most implementations) */ + printf("ready to close and terminate... (type RETURN):"); + while (getchar() != '\n') ; + + Pm_Close(midi); + done: + Pm_Terminate(); + printf("done closing and terminating...\n"); +} + + +void show_usage() +{ + printf("Usage: fast [-h] [-l latency] [-r rate] [-d device] [-s dur] " + "[-n] [-p] [-m]\n" + ", where latency is in ms,\n" + " rate is messages per second,\n" + " device is the PortMidi device number,\n" + " dur is the length of the test in seconds,\n" + " -n means send timestamps in the past,\n" + " -p means use a large positive time offset,\n" + " -m means use a large negative time offset, and\n" + " -h means help.\n"); +} + +int main(int argc, char *argv[]) +{ + int default_in; + int default_out; + char *deflt; + int i = 0; + int latency_valid = FALSE; + int rate_valid = FALSE; + int device_valid = FALSE; + int dur_valid = FALSE; + + 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"); + + if (argc <= 1) { + show_usage(); + } else { + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-h") == 0) { + show_usage(); + } 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], "-r") == 0) { + i = i + 1; + msgrate = atoi(argv[i]); + printf("Rate will be %d messages/second\n", msgrate); + rate_valid = TRUE; + } else if (strcmp(argv[i], "-d") == 0) { + i = i + 1; + deviceno = atoi(argv[i]); + printf("Device will be %d\n", deviceno); + } else if (strcmp(argv[i], "-s") == 0) { + i = i + 1; + duration = atoi(argv[i]); + printf("Duration will be %d seconds\n", duration); + dur_valid = TRUE; + } else if (strcmp(argv[i], "-n") == 0) { + printf("Sending expired timestamps (-n)\n"); + expired_timestamps = TRUE; + } else if (strcmp(argv[i], "-p") == 0) { + printf("Time offset set to 2147473648 (-p)\n"); + use_timeoffset = 2147473648; + } else if (strcmp(argv[i], "-m") == 0) { + printf("Time offset set to -10000 (-m)\n"); + use_timeoffset = -10000; + } else { + show_usage(); + } + } + } + + if (!latency_valid) { + // coerce to known size + latency = (int32_t) get_number("Latency in ms: "); + } + + if (!rate_valid) { + // coerce from "%d" to known size + msgrate = (int32_t) get_number("Rate in messages per second: "); + } + + if (!dur_valid) { + duration = get_number("Duration in seconds: "); + } + + /* list device information */ + default_in = Pm_GetDefaultInputDeviceID(); + default_out = Pm_GetDefaultOutputDeviceID(); + for (i = 0; i < Pm_CountDevices(); i++) { + const PmDeviceInfo *info = Pm_GetDeviceInfo(i); + if (info->output) { + printf("%d: %s, %s", i, info->interf, info->name); + if (i == deviceno) { + device_valid = TRUE; + deflt = "selected "; + } else if (i == default_out) { + deflt = "default "; + } else { + deflt = ""; + } + printf(" (%soutput)\n", deflt); + } + } + printf("%d: Create virtual port named \"fast\"", i); + if (i == deviceno) { + device_valid = TRUE; + deflt = "selected "; + } else { + deflt = ""; + } + printf(" (%soutput)\n", deflt); + + if (!device_valid) { + deviceno = get_number("Output device number: "); + } + + fast_test(); + return 0; +} diff --git a/portmidi/pm_test/fastrcv.c b/portmidi/pm_test/fastrcv.c new file mode 100644 index 0000000..dabf9fa --- /dev/null +++ b/portmidi/pm_test/fastrcv.c @@ -0,0 +1,255 @@ +/* fastrcv.c -- send many MIDI messages very fast. + * + * This is a stress test created to explore reports of + * pm_write() call blocking (forever) on Linux when + * sending very dense MIDI sequences. + * + * Modified 8 Aug 2017 with -n to send expired timestamps + * to test a theory about why Linux ALSA hangs in Audacity. + * + * Roger B. Dannenberg, Aug 2017 + */ + +#include "portmidi.h" +#include "porttime.h" +#include "stdlib.h" +#include "stdio.h" +#include "string.h" +#include "assert.h" + +#define INPUT_BUFFER_SIZE 1000 /* big to avoid losing any input */ +#define DEVICE_INFO NULL +#define DRIVER_INFO NULL +#define TIME_PROC ((PmTimeProcPtr) Pt_Time) +#define TIME_INFO NULL +#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ + +#define STRING_MAX 80 /* used for console input */ +// need to get declaration for Sleep() +#ifdef WIN32 +#include "windows.h" +#else +#include +#define Sleep(n) usleep(n * 1000) +#endif + + +int deviceno = -9999; +int verbose = FALSE; + + +static void prompt_and_exit(void) +{ + printf("type ENTER..."); + while (getchar() != '\n') ; + /* this will clean up open ports: */ + exit(-1); +} + + +static PmError checkerror(PmError err) +{ + if (err == pmHostError) { + /* it seems pointless to allocate memory and copy the string, + * so I will do the work of Pm_GetHostErrorText directly + */ + char errmsg[80]; + Pm_GetHostErrorText(errmsg, 80); + printf("PortMidi found host error...\n %s\n", errmsg); + prompt_and_exit(); + } else if (err < 0) { + printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); + prompt_and_exit(); + } + return err; +} + + +/* 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; +} + + +void fastrcv_test() +{ + PmStream * midi; + PmError status, length; + PmEvent buffer[1]; + PmTimestamp start; + /* every 10ms read all messages, keep counts */ + /* every 1000ms, print report */ + int msgcnt = 0; + /* expect repeating sequence of 60 through 71, alternating on/off */ + int expected_pitch = 60; + int expected_on = TRUE; + int report_time; + PmTimestamp last_timestamp = -1; + PmTimestamp last_delta = -1; + + /* It is recommended to start timer before PortMidi */ + TIME_START; + + /* open output device */ + if (deviceno == Pm_CountDevices()) { + int id = Pm_CreateVirtualInput("fastrcv", NULL, DEVICE_INFO); + if (id < 0) checkerror(id); /* error reporting */ + checkerror(Pm_OpenInput(&midi, id, DRIVER_INFO, + INPUT_BUFFER_SIZE, TIME_PROC, TIME_INFO)); + } else { + Pm_OpenInput(&midi, deviceno, DRIVER_INFO, INPUT_BUFFER_SIZE, + TIME_PROC, TIME_INFO); + } + printf("Midi Input opened.\n"); + + /* wait a sec after printing previous line */ + start = Pt_Time() + 1000; + while (start > Pt_Time()) { + Sleep(10); + } + + report_time = Pt_Time() + 1000; /* report every 1s */ + while (TRUE) { + PmTimestamp now = Pt_Time(); + status = Pm_Poll(midi); + if (status == TRUE) { + length = Pm_Read(midi, buffer, 1); + if (length > 0) { + int status = Pm_MessageStatus(buffer[0].message); + if (status == 0x80) { /* convert NoteOff to NoteOn, vel=0 */ + status = 0x90; + buffer[0].message = Pm_Message(status, + Pm_MessageData1(buffer[0].message), 0); + } + /* only listen to NOTEON messages */ + if (status == 0x90) { + int pitch = Pm_MessageData1(buffer[0].message); + int vel = Pm_MessageData2(buffer[0].message); + int is_on = (vel > 0); + if (verbose) { + printf("Note pitch %d vel %d\n", pitch, vel); + } + msgcnt++; + if (pitch != expected_pitch || expected_on != is_on) { + printf("Unexpected note-on: pitch %d vel %d, " + "expected: pitch %d Note%s\n", pitch, vel, + expected_pitch, (expected_on ? "On" : "Off")); + } + if (is_on) { + expected_on = FALSE; + expected_pitch = pitch; + } else { + expected_on = TRUE; + expected_pitch = (pitch + 1) % 72; + if (expected_pitch < 60) expected_pitch = 60; + } + if (last_timestamp >= 0) { + last_delta = buffer[0].timestamp - last_timestamp; + } + last_timestamp = buffer[0].timestamp; + } + } + } + if (now >= report_time) { + printf("%d msgs/sec", msgcnt); + /* if available, print the last timestamp and last delta time */ + if (last_timestamp >= 0) { + printf(" last timestamp %d", (int) last_timestamp); + last_timestamp = -1; + } + if (last_delta >= 0) { + printf(" last delta time %d", (int) last_delta); + last_delta = -1; + } + printf("\n"); + report_time += 1000; + msgcnt = 0; + } + } +} + + +void show_usage() +{ + printf("Usage: fastrcv [-h] [-v] [-d device], where\n" + "device is the PortMidi device number,\n" + "-h means help,\n" + "-v means verbose (print messages)\n"); +} + +int main(int argc, char *argv[]) +{ + int default_in; + int default_out; + char *deflt; + + int i = 0; + int test_input = 0, test_output = 0, test_both = 0; + int stream_test = 0; + int device_valid = FALSE; + + 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"); + + if (argc <= 1) { + show_usage(); + } else { + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-h") == 0) { + show_usage(); + } else if (strcmp(argv[i], "-v") == 0) { + verbose = TRUE; + } else if (strcmp(argv[i], "-d") == 0) { + i = i + 1; + deviceno = atoi(argv[i]); + printf("Device will be %d\n", deviceno); + } else { + show_usage(); + } + } + } + + /* list device information */ + default_in = Pm_GetDefaultInputDeviceID(); + default_out = Pm_GetDefaultOutputDeviceID(); + for (i = 0; i < Pm_CountDevices(); i++) { + const PmDeviceInfo *info = Pm_GetDeviceInfo(i); + if (!info->output) { + printf("%d: %s, %s", i, info->interf, info->name); + if (i == deviceno) { + device_valid = TRUE; + deflt = "selected "; + } else if (i == default_out) { + deflt = "default "; + } else { + deflt = ""; + } + printf(" (%sinput)\n", deflt); + } + } + printf("%d: Create virtual port named \"fastrcv\"", i); + if (i == deviceno) { + device_valid = TRUE; + deflt = "selected "; + } else { + deflt = ""; + } + printf(" (%sinput)\n", deflt); + + if (!device_valid) { + deviceno = get_number("Input device number: "); + } + + fastrcv_test(); + return 0; +} diff --git a/portmidi/pm_test/latency.c b/portmidi/pm_test/latency.c new file mode 100755 index 0000000..06ea80d --- /dev/null +++ b/portmidi/pm_test/latency.c @@ -0,0 +1,287 @@ +/* latency.c -- measure latency of OS */ + +#include "porttime.h" +#include "portmidi.h" +#include "stdlib.h" +#include "stdio.h" +#include "string.h" +#include "assert.h" + +/* Latency is defined here to mean the time starting when a + process becomes ready to run, and ending when the process + actually runs. Latency is due to contention for the + processor, usually due to other processes, OS activity + including device drivers handling interrupts, and + waiting for the scheduler to suspend the currently running + process and activate the one that is waiting. + + Latency can affect PortMidi applications: if a process fails + to wake up promptly, MIDI input may sit in the input buffer + waiting to be handled, and MIDI output may not be generated + with accurate timing. Using the latency parameter when + opening a MIDI output port allows the caller to defer timing + to PortMidi, which in most implementations will pass the + data on to the OS. By passing timestamps and data to the + OS kernel, device driver, or even hardware, there are fewer + sources of latency that can affect the ultimate timing of + the data. On the other hand, the application must generate + and deliver the data ahead of the timestamp. The amount by + which data is computed early must be at least as large as + the worst-case latency to avoid timing problems. + + Latency is even more important in audio applications. If an + application lets an audio output buffer underflow, an audible + pop or click is produced. Audio input buffers can overflow, + causing data to be lost. In general the audio buffers must + be large enough to buffer the worst-case latency that the + application will encounter. + + This program measures latency by recording the difference + between the scheduled callback time and the current real time. + We do not really know the scheduled callback time, so we will + record the differences between the real time of each callback + and the real time of the previous callback. Differences that + are larger than the scheduled difference are recorded. Smaller + differences indicate the system is recovering from an earlier + latency, so these are ignored. + Since printing by the callback process can cause all sorts of + delays, this program records latency observations in a + histogram. When the program is stopped, the histogram is + printed to the console. + + Optionally the system can be tested under a load of MIDI input, + MIDI output, or both. If MIDI input is selected, the callback + thread will read any waiting MIDI events each iteration. You + must generate events on this interface for the test to actually + put any appreciable load on PortMidi. If MIDI output is + selected, alternating note on and note off events are sent each + X iterations, where you specify X. For example, with a timer + callback period of 2ms and X=1, a MIDI event is sent every 2ms. + + + INTERPRETING RESULTS: Time is quantized to 1ms, so there is + some uncertainty due to rounding. A microsecond latency that + spans the time when the clock is incremented will be reported + as a latency of 1. On the other hand, a latency of almost + 1ms that falls between two clock ticks will be reported as + zero. In general, if the highest nonzero bin is numbered N, + then the maximum latency is N+1. + +CHANGE LOG + +18-Jul-03 Mark Nelson -- Added code to generate MIDI or receive + MIDI during test, and made period user-settable. + */ + +#define HIST_LEN 21 /* how many 1ms bins in the histogram */ + +#define STRING_MAX 80 /* used for console input */ + +#define INPUT_BUFFER_SIZE 100 +#define OUTPUT_BUFFER_SIZE 0 + +#ifndef max +#define max(a, b) ((a) > (b) ? (a) : (b)) +#endif +#ifndef min +#define min(a, b) ((a) <= (b) ? (a) : (b)) +#endif + +int get_number(const char *prompt); + +PtTimestamp previous_callback_time = 0; + +int period; /* milliseconds per callback */ + +int histogram[HIST_LEN]; +int max_latency = 0; /* worst latency observed */ +int out_of_range = 0; /* how many points outside of HIST_LEN? */ + +int test_in, test_out; /* test MIDI in and/or out? */ +int output_period; /* output MIDI every __ iterations if test_out true */ +int iteration = 0; +PmStream *in, *out; +int note_on = 0; /* is the note currently on? */ + +/* callback function for PortTime -- computes histogram */ +void pt_callback(PtTimestamp timestamp, void *userData) +{ + PtTimestamp difference = timestamp - previous_callback_time - period; + previous_callback_time = timestamp; + + /* allow 5 seconds for the system to settle down */ + if (timestamp < 5000) return; + + iteration++; + /* send a note on/off if user requested it */ + if (test_out && (iteration % output_period == 0)) { + PmEvent buffer[1]; + buffer[0].timestamp = Pt_Time(); + if (note_on) { + /* note off */ + buffer[0].message = Pm_Message(0x90, 60, 0); + note_on = 0; + } else { + /* note on */ + buffer[0].message = Pm_Message(0x90, 60, 100); + note_on = 1; + } + Pm_Write(out, buffer, 1); + iteration = 0; + } + + /* read all waiting events (if user requested) */ + if (test_in) { + PmError status; + PmEvent buffer[1]; + do { + status = Pm_Poll(in); + if (status == TRUE) { + Pm_Read(in,buffer,1); + } + } while (status == TRUE); + } + + if (difference < 0) return; /* ignore when system is "catching up" */ + + /* update the histogram */ + if (difference < HIST_LEN) { + histogram[difference]++; + } else { + out_of_range++; + } + + if (max_latency < difference) max_latency = difference; +} + + +int main() +{ + int i; + int len; + int choice; + PtTimestamp stop; + printf("Latency histogram.\n"); + period = 0; + while (period < 1) { + period = get_number("Choose timer period (in ms, >= 1): "); + } + printf("Benchmark with:\n\t%s\n\t%s\n\t%s\n\t%s\n", + "1. No MIDI traffic", + "2. MIDI input", + "3. MIDI output", + "4. MIDI input and output"); + choice = get_number("? "); + switch (choice) { + case 1: test_in = 0; test_out = 0; break; + case 2: test_in = 1; test_out = 0; break; + case 3: test_in = 0; test_out = 1; break; + case 4: test_in = 1; test_out = 1; break; + default: assert(0); + } + if (test_in || test_out) { + /* list device information */ + for (i = 0; i < Pm_CountDevices(); i++) { + const PmDeviceInfo *info = Pm_GetDeviceInfo(i); + if ((test_in && info->input) || + (test_out && info->output)) { + printf("%d: %s, %s", i, info->interf, info->name); + if (info->input) printf(" (input)"); + if (info->output) printf(" (output)"); + printf("\n"); + } + } + /* open stream(s) */ + if (test_in) { + int i = get_number("MIDI input device number: "); + Pm_OpenInput(&in, + i, + NULL, + INPUT_BUFFER_SIZE, + (PmTimestamp (*)(void *)) Pt_Time, + NULL); + /* turn on filtering; otherwise, input might overflow in the + 5-second period before timer callback starts reading midi */ + Pm_SetFilter(in, PM_FILT_ACTIVE | PM_FILT_CLOCK); + } + if (test_out) { + int i = get_number("MIDI output device number: "); + PmEvent buffer[1]; + Pm_OpenOutput(&out, + i, + NULL, + OUTPUT_BUFFER_SIZE, + (PmTimestamp (*)(void *)) Pt_Time, + NULL, + 0); /* no latency scheduling */ + + /* send a program change to force a status byte -- this fixes + a problem with a buggy linux MidiSport driver, and shouldn't + hurt anything else + */ + buffer[0].timestamp = 0; + buffer[0].message = Pm_Message(0xC0, 0, 0); /* program change */ + Pm_Write(out, buffer, 1); + + output_period = get_number( + "MIDI out should be sent every __ callback iterations: "); + + assert(output_period >= 1); + } + } + + printf("Latency measurements will start in 5 seconds. " + "Type return to stop: "); + Pt_Start(period, &pt_callback, 0); + while (getchar() != '\n') ; + stop = Pt_Time(); + Pt_Stop(); + + /* courteously turn off the last note, if necessary */ + if (note_on) { + PmEvent buffer[1]; + buffer[0].timestamp = Pt_Time(); + buffer[0].message = Pm_Message(0x90, 60, 0); + Pm_Write(out, buffer, 1); + } + + /* print the histogram */ + printf("Duration of test: %g seconds\n\n", max(0, stop - 5000) * 0.001); + printf("Latency(ms) Number of occurrences\n"); + /* avoid printing beyond last non-zero histogram entry */ + len = min(HIST_LEN, max_latency + 1); + for (i = 0; i < len; i++) { + printf("%2d %10d\n", i, histogram[i]); + } + printf("Number of points greater than %dms: %d\n", + HIST_LEN - 1, out_of_range); + printf("Maximum latency: %d milliseconds\n", max_latency); + printf("\nNote that due to rounding, actual latency can be 1ms higher\n"); + printf("than the numbers reported here.\n"); + printf("Type return to exit..."); + while (getchar() != '\n') ; + + if(choice == 2) + Pm_Close(in); + else if(choice == 3) + Pm_Close(out); + else if(choice == 4) + { + Pm_Close(in); + Pm_Close(out); + } + return 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; +} diff --git a/portmidi/pm_test/midiclock.c b/portmidi/pm_test/midiclock.c new file mode 100644 index 0000000..f0a6897 --- /dev/null +++ b/portmidi/pm_test/midiclock.c @@ -0,0 +1,282 @@ +/* miditime.c -- a test program that sends midi clock and MTC */ + +#include "portmidi.h" +#include "porttime.h" +#include +#include +#include +#include +#include + +#ifndef false +#define false 0 +#define true 1 +#endif + +#define private static +typedef int boolean; + +#define MIDI_TIME_CLOCK 0xf8 +#define MIDI_START 0xfa +#define MIDI_CONTINUE 0xfb +#define MIDI_STOP 0xfc +#define MIDI_Q_FRAME 0xf1 + +#define OUTPUT_BUFFER_SIZE 0 +#define DRIVER_INFO NULL +#define TIME_PROC ((PmTimeProcPtr) Pt_Time) +#define TIME_INFO NULL +#define LATENCY 0 +#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ + +#define STRING_MAX 80 /* used for console input */ + +/* to determine ms per clock: + * time per beat in seconds = 60 / tempo + * multiply by 1000 to get time per beat in ms: 60000 / tempo + * divide by 24 CLOCKs per beat: (60000/24) / tempo + * simplify: 2500 / tempo + */ +#define TEMPO_TO_CLOCK 2500.0 + +boolean done = false; +PmStream *midi; +/* shared flags to control callback output generation: */ +boolean clock_running = false; +boolean send_start_stop = false; +boolean time_code_running = false; +boolean active = false; /* tells callback to do its thing */ +float tempo = 60.0F; +/* protocol for handing off portmidi to callback thread: + main owns portmidi + main sets active = true: ownership transfers to callback + main sets active = false: main requests ownership + callback sees active == false, yields ownership back to main + main waits 2ms to make sure callback has a chance to yield + (stop making PortMidi calls), then assumes it can close + PortMidi + */ + +/* timer_poll -- the timer callback function */ +/* + * All MIDI sends take place here + */ +void timer_poll(PtTimestamp timestamp, void *userData) +{ + static int callback_owns_portmidi = false; + static PmTimestamp clock_start_time = 0; + static double next_clock_time = 0; + /* SMPTE time */ + static int frames = 0; + static int seconds = 0; + static int minutes = 0; + static int hours = 0; + static int mtc_count = 0; /* where are we in quarter frame sequence? */ + static int smpte_start_time = 0; + static double next_smpte_time = 0; + #define QUARTER_FRAME_PERIOD (1.0 / 120.0) /* 30fps, 1/4 frame */ + + if (callback_owns_portmidi && !active) { + /* main is requesting (by setting active to false) that we shut down */ + callback_owns_portmidi = false; + return; + } + if (!active) return; /* main still getting ready or it's closing down */ + callback_owns_portmidi = true; /* main is ready, we have portmidi */ + if (send_start_stop) { + if (clock_running) { + Pm_WriteShort(midi, 0, MIDI_STOP); + } else { + Pm_WriteShort(midi, 0, MIDI_START); + clock_start_time = timestamp; + next_clock_time = TEMPO_TO_CLOCK / tempo; + } + clock_running = !clock_running; + send_start_stop = false; /* until main sets it again */ + /* note that there's a slight race condition here: main could + set send_start_stop asynchronously, but we assume user is + typing slower than the clock rate */ + } + if (clock_running) { + if ((timestamp - clock_start_time) > next_clock_time) { + Pm_WriteShort(midi, 0, MIDI_TIME_CLOCK); + next_clock_time += TEMPO_TO_CLOCK / tempo; + } + } + if (time_code_running) { + int data = 0; // initialization avoids compiler warning + if ((timestamp - smpte_start_time) < next_smpte_time) + return; + switch (mtc_count) { + case 0: /* frames low nibble */ + data = frames; + break; + case 1: /* frames high nibble */ + data = frames >> 4; + break; + case 2: /* frames seconds low nibble */ + data = seconds; + break; + case 3: /* frames seconds high nibble */ + data = seconds >> 4; + break; + case 4: /* frames minutes low nibble */ + data = minutes; + break; + case 5: /* frames minutes high nibble */ + data = minutes >> 4; + break; + case 6: /* hours low nibble */ + data = hours; + break; + case 7: /* hours high nibble */ + data = hours >> 4; + break; + } + data &= 0xF; /* take only 4 bits */ + Pm_WriteShort(midi, 0, + Pm_Message(MIDI_Q_FRAME, (mtc_count << 4) + data, 0)); + mtc_count = (mtc_count + 1) & 7; /* wrap around */ + if (mtc_count == 0) { /* update time by two frames */ + frames += 2; + if (frames >= 30) { + frames = 0; + seconds++; + if (seconds >= 60) { + seconds = 0; + minutes++; + if (minutes >= 60) { + minutes = 0; + hours++; + /* just let hours wrap if it gets that far */ + } + } + } + } + next_smpte_time += QUARTER_FRAME_PERIOD; + } else { /* time_code_running is false */ + smpte_start_time = timestamp; + /* so that when it finally starts, we'll be in sync */ + } +} + + +/* 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; +} + +/**************************************************************************** +* showhelp +* Effect: print help text +****************************************************************************/ + +private void showhelp() +{ + printf("\n"); + printf("t toggles sending MIDI Time Code (MTC)\n"); + printf("c toggles sending MIDI CLOCK (initially on)\n"); + printf("m to set tempo (from 1bpm to 300bpm)\n"); + printf("q quits\n"); + printf("\n"); +} + +/**************************************************************************** +* doascii +* Inputs: +* char c: input character +* Effect: interpret to control output +****************************************************************************/ + +private void doascii(char c) +{ + if (isupper(c)) c = tolower(c); + if (c == 'q') done = true; + else if (c == 'c') { + printf("%s MIDI CLOCKs\n", (clock_running ? "Stopping" : "Starting")); + send_start_stop = true; + } else if (c == 't') { + printf("%s MIDI Time Code\n", + (time_code_running ? "Stopping" : "Starting")); + time_code_running = !time_code_running; + } else if (c == 'm') { + int input_tempo = get_number("Enter new tempo (bpm): "); + if (input_tempo >= 1 && input_tempo <= 300) { + printf("Changing tempo to %d\n", input_tempo); + tempo = (float) input_tempo; + } else { + printf("Tempo range is 1 to 300, current tempo is %g bpm\n", + tempo); + } + } else { + showhelp(); + } +} + + +/* main - prompt for parameters, start processing */ +/* + * Prompt user to type return. + * Then send START and MIDI CLOCK for 60 beats/min. + * Commands: + * t - toggle sending MIDI Time Code (MTC) + * c - toggle sending MIDI CLOCK + * m - set tempo + * q - quit + */ +int main(int argc, char **argv) +{ + int outp; + PmError err; + int i; + if (argc > 1) { + printf("Warning: command line arguments ignored\n"); + } + showhelp(); + /* use porttime callback to send midi */ + Pt_Start(1, timer_poll, 0); + /* list device information */ + printf("MIDI output devices:\n"); + for (i = 0; i < Pm_CountDevices(); i++) { + const PmDeviceInfo *info = Pm_GetDeviceInfo(i); + if (info->output) printf("%d: %s, %s\n", i, info->interf, info->name); + } + outp = get_number("Type output device number: "); + err = Pm_OpenOutput(&midi, outp, DRIVER_INFO, OUTPUT_BUFFER_SIZE, + TIME_PROC, TIME_INFO, LATENCY); + if (err) { + puts(Pm_GetErrorText(err)); + goto error_exit_no_device; + } + active = true; + + printf("Type ENTER to start MIDI CLOCK:\n"); + while (getchar() != '\n') ; + send_start_stop = true; /* send START and then CLOCKs */ + + while (!done) { + doascii(getchar()); + while (getchar() != '\n') ; + } + + active = false; + Pt_Sleep(2); /* this is to allow callback to complete -- it's + real time, so it's either ok and it runs on + time, or there's no point to synchronizing + with it */ + /* now we "own" portmidi again */ + Pm_Close(midi); + error_exit_no_device: + Pt_Stop(); + Pm_Terminate(); + exit(0); +} + diff --git a/portmidi/pm_test/midithread.c b/portmidi/pm_test/midithread.c new file mode 100755 index 0000000..ea0613b --- /dev/null +++ b/portmidi/pm_test/midithread.c @@ -0,0 +1,343 @@ +/* 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) +{ + printf("%s\n", msg); + while (getchar() != '\n') ; + exit(1); +} + +int main(int argc, char *argv[]) +{ + int32_t n; + const PmDeviceInfo *info; + char line[STRING_MAX]; + int spin; + int done = FALSE; + int i; + int input = -1, output = -1; + + printf("Usage: midithread [-i input] [-o output]\n" + "where input and output are portmidi device numbers\n"); + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-i") == 0) { + i++; + input = atoi(argv[i]); + printf("Input device number: %d\n", input); + } else if (strcmp(argv[i], "-o") == 0) { + i++; + output = atoi(argv[i]); + printf("Output device number: %d\n", output); + } else { + return -1; + } + } + 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(); + + output = (output < 0 ? Pm_GetDefaultOutputDeviceID() : output); + info = Pm_GetDeviceInfo(output); + if (info == NULL) { + printf("Could not open output device (%d).", output); + 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, + output, + DRIVER_INFO, + OUTPUT_BUFFER_SIZE, + TIME_PROC, + TIME_INFO, + LATENCY); + + input = (input < 0 ? Pm_GetDefaultInputDeviceID() : input); + info = Pm_GetDeviceInfo(input); + if (info == NULL) { + printf("Could not open default input device (%d).", input); + exit_with_message(""); + } + printf("Opening input device %s %s\n", info->interf, info->name); + Pm_OpenInput(&midi_in, + input, + 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("Type 'q' to quit, 'm' to monitor next pitch, t to toggle thru or\n" + "type a number to specify transposition.\n" + "Must terminate with [ENTER]\n"); + + while (!done) { + int32_t msg; + int input; + int len; + if (!fgets(line, STRING_MAX, stdin)) break; /* no stdin? */ + /* remove the newline: */ + len = (int) 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); + + fputs("finished portMidi multithread test.\n" + "type ENTER to quit:", stdout); + while (getchar() != '\n') ; + return 0; +} diff --git a/portmidi/pm_test/midithru.c b/portmidi/pm_test/midithru.c new file mode 100755 index 0000000..94b4f13 --- /dev/null +++ b/portmidi/pm_test/midithru.c @@ -0,0 +1,455 @@ +/* midithru.c -- example program implementing background thru processing */ + +/* suppose you want low-latency midi-thru processing, but your + application wants to take advantage of the input buffer and + timestamped data so that it does not have to operate with very low + latency. + + This program illustrates how to use a timer callback from PortTime + to implement a low-latency process that handles midi thru, + including correctly merging midi data from the application with + midi data from the input port. + + The main application, which runs in the main program thread, will + use an interface similar to that of PortMidi, but since PortMidi + does not allow concurrent threads to share access to a stream, the + application will call private methods that transfer MIDI messages + to and from the timer thread using lock-free queues. All PortMidi + API calls are made from the timer thread. + */ + +/* DESIGN + +All setup will be done by the main thread. Then, all direct access to +PortMidi will be handed off to the timer callback thread. + +After this hand-off, the main thread will get/send messages via a queue. + +The goal is to send incoming messages to the midi output while merging +any midi data generated by the application. Sysex is a problem here +because you cannot insert (merge) a midi message while a sysex is in +progress. There are at least three ways to implement midi thru with +sysex messages: + +1) Turn them off. If your application does not need them, turn them off + with Pm_SetFilter(midi_in, PM_FILT_ACTIVE | PM_FILT_SYSEX). You will + not receive sysex (or active sensing messages), so you will not have + to handle them. + +2) Make them atomic. As you receive sysex messages, copy the data into + a (big) buffer. Ideally, expand the buffer as needed -- sysex messages + do not have any maximum length. Even more ideally, use a list structure + and real-time memory allocation to avoid latency in the timer thread. + When a full sysex message is received, send it to the midi output all + at once. + +3) Process sysex incrementally. Send sysex data to midi output as it + arrives. Block any non-real-time messages from the application until + the sysex message completes. There is the risk that an incomplete + sysex message will block messages forever, so implement a 5-second + timeout: if no sysex data is seen for 5 seconds, release the block, + possibly losing the rest of the sysex message. + + Application messages must be processed similarly: once started, a + sysex message will block MIDI THRU processing. We will assume that + the application will not abort a sysex message, so timeouts are not + necessary here. + +This code implements (3). + +Latency is also an issue. PortMidi requires timestamps to be in +non-decreasing order. Since we'll be operating with a low-latency +timer thread, we can just set the latency to zero meaning timestamps +are ignored by PortMidi. This will allow thru to go through with +minimal latency. The application, however, needs to use timestamps +because we assume it is high latency (the whole purpose of this +example is to illustrate how to get low-latency thru with a high-latency +application.) So the callback thread will implement midi timing by +observing timestamps. The current timestamp will be available in the +global variable current_timestamp. + +*/ + + +#include "stdio.h" +#include "stdlib.h" +#include "string.h" +#include "assert.h" +#include "portmidi.h" +#include "pmutil.h" +#include "porttime.h" + +#define MIDI_SYSEX 0xf0 +#define MIDI_EOX 0xf7 +#define STRING_MAX 80 /* used for console input */ + +/* active is set true when midi processing should start, must be + * volatile to force thread to check for updates by other thread */ +int active = FALSE; +/* process_midi_exit_flag is set when the timer thread shuts down; + * must be volatile so it is re-read in the while loop that waits on it */ +volatile int process_midi_exit_flag; + +PmStream *midi_in; +PmStream *midi_out; + +/* shared queues */ +#define IN_QUEUE_SIZE 1024 +#define OUT_QUEUE_SIZE 1024 +PmQueue *in_queue; +PmQueue *out_queue; +/* this is volatile because it is set in the process_midi callback and + * the main thread reads it to sense elapsed time. Without volatile, the + * optimizer can put it in a register and not see the updates. + */ +volatile PmTimestamp current_timestamp = 0; +int thru_sysex_in_progress = FALSE; +int app_sysex_in_progress = FALSE; +PmTimestamp last_timestamp = 0; + + +static void prompt_and_exit(void) +{ + printf("type ENTER..."); + while (getchar() != '\n') ; + /* this will clean up open ports: */ + exit(-1); +} + + +static PmError checkerror(PmError err) +{ + if (err == pmHostError) { + /* it seems pointless to allocate memory and copy the string, + * so I will do the work of Pm_GetHostErrorText directly + */ + char errmsg[80]; + Pm_GetHostErrorText(errmsg, 80); + printf("PortMidi found host error...\n %s\n", errmsg); + prompt_and_exit(); + } else if (err < 0) { + printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); + prompt_and_exit(); + } + return err; +} + + +/* time proc parameter for Pm_MidiOpen */ +PmTimestamp midithru_time_proc(void *info) +{ + return current_timestamp; +} + + +/* timer interrupt for processing midi data. + Incoming data is delivered to main program via in_queue. + Outgoing data from main program is delivered via out_queue. + Incoming data from midi_in is copied with low latency to midi_out. + Sysex messages from either source block messages from the other. + */ +void process_midi(PtTimestamp timestamp, void *userData) +{ + PmError result; + PmEvent buffer; /* just one message at a time */ + + current_timestamp++; /* update every millisecond */ + + /* do nothing until initialization completes */ + if (!active) { + /* this flag signals that no more midi processing will be done */ + process_midi_exit_flag = TRUE; + return; + } + + /* see if there is any midi input to process */ + if (!app_sysex_in_progress) { + do { + result = Pm_Poll(midi_in); + if (result) { + int status; + PmError rslt = Pm_Read(midi_in, &buffer, 1); + if (rslt == pmBufferOverflow) + continue; + assert(rslt == 1); + + /* record timestamp of most recent data */ + last_timestamp = current_timestamp; + + /* the data might be the end of a sysex message that + has timed out, in which case we must ignore it. + It's a continuation of a sysex message if status + is actually a data byte (high-order bit is zero). */ + status = Pm_MessageStatus(buffer.message); + if (((status & 0x80) == 0) && !thru_sysex_in_progress) { + continue; /* ignore this data */ + } + + /* implement midi thru */ + /* note that you could output to multiple ports or do other + processing here if you wanted + */ + /* printf("thru: %x\n", buffer.message); */ + Pm_Write(midi_out, &buffer, 1); + + /* send the message to the application */ + /* you might want to filter clock or active sense messages here + to avoid sending a bunch of junk to the application even if + you want to send it to MIDI THRU + */ + Pm_Enqueue(in_queue, &buffer); + + /* sysex processing */ + if (status == MIDI_SYSEX) thru_sysex_in_progress = TRUE; + else if ((status & 0xF8) != 0xF8) { + /* not MIDI_SYSEX and not real-time, so */ + thru_sysex_in_progress = FALSE; + } + if (thru_sysex_in_progress && /* look for EOX */ + (((buffer.message & 0xFF) == MIDI_EOX) || + (((buffer.message >> 8) & 0xFF) == MIDI_EOX) || + (((buffer.message >> 16) & 0xFF) == MIDI_EOX) || + (((buffer.message >> 24) & 0xFF) == MIDI_EOX))) { + thru_sysex_in_progress = FALSE; + } + } + } while (result); + } + + + /* see if there is application midi data to process */ + while (!Pm_QueueEmpty(out_queue)) { + /* see if it is time to output the next message */ + PmEvent *next = (PmEvent *) Pm_QueuePeek(out_queue); + assert(next); /* must be non-null because queue is not empty */ + if (next->timestamp <= current_timestamp) { + /* time to send a message, first make sure it's not blocked */ + int status = Pm_MessageStatus(next->message); + if ((status & 0xF8) == 0xF8) { + ; /* real-time messages are not blocked */ + } else if (thru_sysex_in_progress) { + /* maybe sysex has timed out (output becomes unblocked) */ + if (last_timestamp + 5000 < current_timestamp) { + thru_sysex_in_progress = FALSE; + } else break; /* output is blocked, so exit loop */ + } + Pm_Dequeue(out_queue, &buffer); + Pm_Write(midi_out, &buffer, 1); + + /* inspect message to update app_sysex_in_progress */ + if (status == MIDI_SYSEX) app_sysex_in_progress = TRUE; + else if ((status & 0xF8) != 0xF8) { + /* not MIDI_SYSEX and not real-time, so */ + app_sysex_in_progress = FALSE; + } + if (app_sysex_in_progress && /* look for EOX */ + (((buffer.message & 0xFF) == MIDI_EOX) || + (((buffer.message >> 8) & 0xFF) == MIDI_EOX) || + (((buffer.message >> 16) & 0xFF) == MIDI_EOX) || + (((buffer.message >> 24) & 0xFF) == MIDI_EOX))) { + app_sysex_in_progress = FALSE; + } + } else break; /* wait until indicated timestamp */ + } +} + + +void exit_with_message(char *msg) +{ +#define STRING_MAX 80 + printf("%s\nType ENTER...", msg); + while (getchar() != '\n') ; + exit(1); +} + + +void initialize(int input, int output, int virtual) +/* set up midi processing thread and open midi streams */ +{ + /* 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. + */ + + /* note that this routine provides minimal error checking. If + you use the PortMidi library compiled with PM_CHECK_ERRORS, + then error messages will be printed and the program will exit + if an error is encountered. Otherwise, you should add some + error checking to this code. + */ + + const PmDeviceInfo *info; + + /* make the message queues */ + in_queue = Pm_QueueCreate(IN_QUEUE_SIZE, sizeof(PmEvent)); + assert(in_queue != NULL); + out_queue = Pm_QueueCreate(OUT_QUEUE_SIZE, sizeof(PmEvent)); + assert(out_queue != NULL); + + /* 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(); + + if (output < 0) { + if (!virtual) { + output = Pm_GetDefaultOutputDeviceID(); + } + } + if (output >= 0) { + info = Pm_GetDeviceInfo(output); + if (info == NULL) { + printf("Could not open default output device (%d).", output); + 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, + output, + NULL /* driver info */, + OUT_QUEUE_SIZE, + &midithru_time_proc, + NULL /* time info */, + 0 /* Latency */); + } else { /* send to virtual port */ + int id; + printf("Opening virtual output device \"midithru\"\n"); + id = Pm_CreateVirtualOutput("midithru", NULL, NULL); + if (id < 0) checkerror(id); /* error reporting */ + checkerror(Pm_OpenOutput(&midi_out, id, NULL, OUT_QUEUE_SIZE, + &midithru_time_proc, NULL, 0)); + } + if (input < 0) { + if (!virtual) { + input = Pm_GetDefaultInputDeviceID(); + } + } + if (input >= 0) { + info = Pm_GetDeviceInfo(input); + if (info == NULL) { + printf("Could not open default input device (%d).", input); + exit_with_message(""); + } + + printf("Opening input device %s %s\n", info->interf, info->name); + Pm_OpenInput(&midi_in, + input, + NULL /* driver info */, + 0 /* use default input size */, + &midithru_time_proc, + NULL /* time info */); + } else { /* receive from virtual port */ + int id; + printf("Opening virtual input device \"midithru\"\n"); + id = Pm_CreateVirtualInput("midithru", NULL, NULL); + if (id < 0) checkerror(id); /* error reporting */ + checkerror(Pm_OpenInput(&midi_in, id, NULL, 0, + &midithru_time_proc, NULL)); + } + /* Note: if you set a filter here, then this will filter what goes + to the MIDI THRU port. You may not want to do this. + */ + Pm_SetFilter(midi_in, PM_FILT_ACTIVE | PM_FILT_CLOCK); + + active = TRUE; /* enable processing in the midi thread -- yes, this + is a shared variable without synchronization, but + this simple assignment is safe */ + +} + + +void finalize() +{ + /* the timer thread could be in the middle of accessing PortMidi stuff */ + /* to detect that it is done, we first clear process_midi_exit_flag and + then wait for the timer thread to set it + */ + process_midi_exit_flag = FALSE; + active = FALSE; + /* busy wait for flag from timer thread that it is done */ + while (!process_midi_exit_flag) ; + /* 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(in_queue); + Pm_QueueDestroy(out_queue); + + Pm_Close(midi_in); + Pm_Close(midi_out); + + Pm_Terminate(); +} + + +int main(int argc, char *argv[]) +{ + PmTimestamp last_time = 0; + PmEvent buffer; + int i; + int input = -1, output = -1; + int virtual = FALSE; + int delay_enable = TRUE; + + printf("Usage: midithru [-i input] [-o output] [-v] [-n]\n" + "where input and output are portmidi device numbers\n" + "if -v and input and/or output are not specified,\n" + "then virtual ports are created and used instead.\n" + "-n turns off the default MIDI delay effect.\n"); + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-i") == 0) { + i++; + input = atoi(argv[i]); + printf("Input device number: %d\n", input); + } else if (strcmp(argv[i], "-o") == 0) { + i++; + output = atoi(argv[i]); + printf("Output device number: %d\n", output); + } else if (strcmp(argv[i], "-v") == 0) { + virtual = TRUE; + } else if (strcmp(argv[i], "-n") == 0) { + delay_enable = FALSE; + printf("delay_effect is disabled\n"); + } else { + return -1; + } + } + printf("begin PortMidi midithru program...\n"); + + initialize(input, output, virtual); /* set up and start midi processing */ + + printf("This program will run for 60 seconds, " + "or until you play B below middle C,\n" + "All input is sent immediately, implementing software MIDI THRU.\n" + "Also, all input is echoed with a 2 second delay.\n"); + + while (current_timestamp < 60000) { + /* just to make the point that this is not a low-latency process, + spin until half a second has elapsed */ + last_time = last_time + 500; + while (last_time > current_timestamp) ; + + /* now read data and send it after changing timestamps */ + while (Pm_Dequeue(in_queue, &buffer) == 1) { + /* printf("timestamp %d\n", buffer.timestamp); */ + /* printf("message %x\n", buffer.message); */ + if (delay_enable) { + buffer.timestamp = buffer.timestamp + 2000; /* delay */ + Pm_Enqueue(out_queue, &buffer); + } + /* play B3 to break out of loop */ + if (Pm_MessageStatus(buffer.message) == 0x90 && + Pm_MessageData1(buffer.message) == 59) { + goto quit_now; + } + } + } +quit_now: + finalize(); + exit_with_message("finished PortMidi midithru program."); + return 0; /* never executed, but keeps the compiler happy */ +} diff --git a/portmidi/pm_test/mm.c b/portmidi/pm_test/mm.c new file mode 100755 index 0000000..ab9d32e --- /dev/null +++ b/portmidi/pm_test/mm.c @@ -0,0 +1,595 @@ +/* mm.c -- midi monitor */ + +/***************************************************************************** +* Change Log +* Date | Change +*-----------+----------------------------------------------------------------- +* 7-Apr-86 | Created changelog +* 31-Jan-90 | GWL : use new cmdline +* 5-Apr-91 | JDW : Further changes +* 16-Feb-92 | GWL : eliminate label mmexit:; add error recovery +* 18-May-92 | GWL : continuous clocks, etc. +* 17-Jan-94 | GWL : option to display notes +* 20-Nov-06 | RBD : port mm.c from CMU Midi Toolkit to PortMidi +* | mm.c -- revealing MIDI secrets for over 20 years! +*****************************************************************************/ + +#include "stdlib.h" +#include "ctype.h" +#include "string.h" +#include "stdio.h" +#include "porttime.h" +#include "portmidi.h" + +#define STRING_MAX 80 + +#define MIDI_CODE_MASK 0xf0 +#define MIDI_CHN_MASK 0x0f +/*#define MIDI_REALTIME 0xf8 + #define MIDI_CHAN_MODE 0xfa */ +#define MIDI_OFF_NOTE 0x80 +#define MIDI_ON_NOTE 0x90 +#define MIDI_POLY_TOUCH 0xa0 +#define MIDI_CTRL 0xb0 +#define MIDI_CH_PROGRAM 0xc0 +#define MIDI_TOUCH 0xd0 +#define MIDI_BEND 0xe0 + +#define MIDI_SYSEX 0xf0 +#define MIDI_Q_FRAME 0xf1 +#define MIDI_SONG_POINTER 0xf2 +#define MIDI_SONG_SELECT 0xf3 +#define MIDI_TUNE_REQ 0xf6 +#define MIDI_EOX 0xf7 +#define MIDI_TIME_CLOCK 0xf8 +#define MIDI_START 0xfa +#define MIDI_CONTINUE 0xfb +#define MIDI_STOP 0xfc +#define MIDI_ACTIVE_SENSING 0xfe +#define MIDI_SYS_RESET 0xff + +#define MIDI_ALL_SOUND_OFF 0x78 +#define MIDI_RESET_CONTROLLERS 0x79 +#define MIDI_LOCAL 0x7a +#define MIDI_ALL_OFF 0x7b +#define MIDI_OMNI_OFF 0x7c +#define MIDI_OMNI_ON 0x7d +#define MIDI_MONO_ON 0x7e +#define MIDI_POLY_ON 0x7f + + +#define private static + +#ifndef false +#define false 0 +#define true 1 +#endif + +typedef int boolean; + +int debug = false; /* never set, but referenced by userio.c */ +PmStream *midi_in; /* midi input */ +boolean active = false; /* set when midi_in is ready for reading */ +boolean in_sysex = false; /* we are reading a sysex message */ +boolean inited = false; /* suppress printing during command line parsing */ +boolean done = false; /* when true, exit */ +boolean notes = true; /* show notes? */ +boolean controls = true; /* show continuous controllers */ +boolean bender = true; /* record pitch bend etc.? */ +boolean excldata = true; /* record system exclusive data? */ +boolean verbose = true; /* show text representation? */ +boolean realdata = true; /* record real time messages? */ +boolean clksencnt = true; /* clock and active sense count on */ +boolean chmode = true; /* show channel mode messages */ +boolean pgchanges = true; /* show program changes */ +boolean flush = false; /* flush all pending MIDI data */ + +uint32_t filter = 0; /* remember state of midi filter */ + +uint32_t clockcount = 0; /* count of clocks */ +uint32_t actsensecount = 0; /* cout of active sensing bytes */ +uint32_t notescount = 0; /* #notes since last request */ +uint32_t notestotal = 0; /* total #notes */ + +char val_format[] = " Val %d\n"; + +/***************************************************************************** +* Imported variables +*****************************************************************************/ + +extern int abort_flag; + +/***************************************************************************** +* Routines local to this module +*****************************************************************************/ + +private void mmexit(int code); +private void output(PmMessage data); +private int put_pitch(int p); +private void showhelp(); +private void showbytes(PmMessage data, int len, boolean newline); +private void showstatus(boolean flag); +private void doascii(char c); +private int get_number(const char *prompt); + + +/* 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; +} + + +void receive_poll(PtTimestamp timestamp, void *userData) +{ + PmEvent event; + int count; + if (!active) return; + while ((count = Pm_Read(midi_in, &event, 1))) { + if (count == 1) output(event.message); + else puts(Pm_GetErrorText(count)); + } +} + + +/**************************************************************************** +* main +* Effect: prompts for parameters, starts monitor +****************************************************************************/ + +int main(int argc, char **argv) +{ + char *argument; + int inp; + PmError err; + int i; + if (argc > 1) { /* first arg can change defaults */ + argument = argv[1]; + while (*argument) doascii(*argument++); + } + showhelp(); + /* use porttime callback to empty midi queue and print */ + Pt_Start(1, receive_poll, 0); + /* list device information */ + puts("MIDI input devices:"); + for (i = 0; i < Pm_CountDevices(); i++) { + const PmDeviceInfo *info = Pm_GetDeviceInfo(i); + if (info->input) printf("%d: %s, %s\n", i, info->interf, info->name); + } + inp = get_number("Type input device number: "); + err = Pm_OpenInput(&midi_in, inp, NULL, 512, NULL, NULL); + if (err) { + puts(Pm_GetErrorText(err)); + Pt_Stop(); + mmexit(1); + } + Pm_SetFilter(midi_in, filter); + inited = true; /* now can document changes, set filter */ + printf("Midi Monitor ready.\n"); + active = true; + while (!done) { + doascii(getchar()); + while (getchar() != '\n') ; + } + active = false; + Pm_Close(midi_in); + Pt_Stop(); + Pm_Terminate(); + mmexit(0); + return 0; // make the compiler happy be returning a value +} + + +/**************************************************************************** +* doascii +* Inputs: +* char c: input character +* Effect: interpret to revise flags +****************************************************************************/ + +private void doascii(char c) +{ + if (isupper(c)) c = tolower(c); + if (c == 'q') done = true; + else if (c == 'b') { + bender = !bender; + filter ^= PM_FILT_PITCHBEND; + if (inited) + printf("Pitch Bend, etc. %s\n", (bender ? "ON" : "OFF")); + } else if (c == 'c') { + controls = !controls; + filter ^= PM_FILT_CONTROL; + if (inited) + printf("Control Change %s\n", (controls ? "ON" : "OFF")); + } else if (c == 'h') { + pgchanges = !pgchanges; + filter ^= PM_FILT_PROGRAM; + if (inited) + printf("Program Changes %s\n", (pgchanges ? "ON" : "OFF")); + } else if (c == 'n') { + notes = !notes; + filter ^= PM_FILT_NOTE; + if (inited) + printf("Notes %s\n", (notes ? "ON" : "OFF")); + } else if (c == 'x') { + excldata = !excldata; + filter ^= PM_FILT_SYSEX; + if (inited) + printf("System Exclusive data %s\n", (excldata ? "ON" : "OFF")); + } else if (c == 'r') { + realdata = !realdata; + filter ^= (PM_FILT_PLAY | PM_FILT_RESET | PM_FILT_TICK | PM_FILT_UNDEFINED); + if (inited) + printf("Real Time messages %s\n", (realdata ? "ON" : "OFF")); + } else if (c == 'k') { + clksencnt = !clksencnt; + if (inited) { + printf("Clock and Active Sense Counting %s\n", (clksencnt ? "ON" : "OFF")); + printf("Resetting Clock and Active Sense counts.\n"); + clockcount = actsensecount = 0; + } + } else if (c == 's') { + if (inited) { + printf("Clock Count %ld\nActive Sense Count %ld\n", + (long) clockcount, (long) actsensecount); + } + } else if (c == 't') { + notestotal+=notescount; + if (inited) + printf("This Note Count %ld\nTotal Note Count %ld\n", + (long) notescount, (long) notestotal); + notescount=0; + } else if (c == 'v') { + verbose = !verbose; + if (inited) + printf("Verbose %s\n", (verbose ? "ON" : "OFF")); + } else if (c == 'm') { + chmode = !chmode; + if (inited) + printf("Channel Mode Messages %s", (chmode ? "ON" : "OFF")); + } else { + if (inited) { + if (c == ' ') { + PmEvent event; + while (Pm_Read(midi_in, &event, 1)) ; /* flush midi input */ + printf("...FLUSHED MIDI INPUT\n\n"); + } else showhelp(); + } + } + if (inited) Pm_SetFilter(midi_in, filter); +} + + + +private void mmexit(int code) +{ + /* if this is not being run from a console, maybe we should wait for + * the user to read error messages before exiting + */ + exit(code); +} + + +/**************************************************************************** +* output +* Inputs: +* data: midi message buffer holding one command or 4 bytes of sysex msg +* Effect: format and print midi data +****************************************************************************/ + +char vel_format[] = " Vel %d\n"; + +private void output(PmMessage data) +{ + int command; /* the current command */ + int chan; /* the midi channel of the current event */ + int len; /* used to get constant field width */ + + /* printf("output data %8x; ", data); */ + + command = Pm_MessageStatus(data) & MIDI_CODE_MASK; + chan = Pm_MessageStatus(data) & MIDI_CHN_MASK; + + if (in_sysex || Pm_MessageStatus(data) == MIDI_SYSEX) { +#define sysex_max 16 + int i; + PmMessage data_copy = data; + in_sysex = true; + /* look for MIDI_EOX in first 3 bytes + * if realtime messages are embedded in sysex message, they will + * be printed as if they are part of the sysex message + */ + for (i = 0; (i < 4) && ((data_copy & 0xFF) != MIDI_EOX); i++) + data_copy >>= 8; + if (i < 4) { + in_sysex = false; + i++; /* include the EOX byte in output */ + } + showbytes(data, i, verbose); + if (verbose) printf("System Exclusive\n"); + } else if (command == MIDI_ON_NOTE && Pm_MessageData2(data) != 0) { + notescount++; + if (notes) { + showbytes(data, 3, verbose); + if (verbose) { + printf("NoteOn Chan %2d Key %3d ", chan, Pm_MessageData1(data)); + len = put_pitch(Pm_MessageData1(data)); + printf(vel_format + len, Pm_MessageData2(data)); + } + } + } else if ((command == MIDI_ON_NOTE /* && Pm_MessageData2(data) == 0 */ || + command == MIDI_OFF_NOTE)) { + if (notes) { + showbytes(data, 3, verbose); + if (verbose) { + printf("NoteOff Chan %2d Key %3d ", chan, + Pm_MessageData1(data)); + len = put_pitch(Pm_MessageData1(data)); + printf(vel_format + len, Pm_MessageData2(data)); + } + } + } else if (command == MIDI_CH_PROGRAM) { + if (pgchanges) { + showbytes(data, 2, verbose); + if (verbose) { + printf(" ProgChg Chan %2d Prog %2d\n", chan, + Pm_MessageData1(data) + 1); + } + } + } else if (command == MIDI_CTRL) { + /* controls 121 (MIDI_RESET_CONTROLLER) to 127 are channel + * mode messages. */ + if (Pm_MessageData1(data) < MIDI_ALL_SOUND_OFF) { + if (controls) { + showbytes(data, 3, verbose); + if (verbose) { + printf("CtrlChg Chan %2d Ctrl %2d Val %2d\n", + chan, Pm_MessageData1(data), Pm_MessageData2(data)); + } + } else /* channel mode */ if (chmode) { + showbytes(data, 3, verbose); + if (verbose) { + switch (Pm_MessageData1(data)) { + case MIDI_ALL_SOUND_OFF: + printf("All Sound Off, Chan %2d\n", chan); + break; + case MIDI_RESET_CONTROLLERS: + printf("Reset All Controllers, Chan %2d\n", chan); + break; + case MIDI_LOCAL: + printf("LocCtrl Chan %2d %s\n", + chan, Pm_MessageData2(data) ? "On" : "Off"); + break; + case MIDI_ALL_OFF: + printf("All Off Chan %2d\n", chan); + break; + case MIDI_OMNI_OFF: + printf("OmniOff Chan %2d\n", chan); + break; + case MIDI_OMNI_ON: + printf("Omni On Chan %2d\n", chan); + break; + case MIDI_MONO_ON: + printf("Mono On Chan %2d\n", chan); + if (Pm_MessageData2(data)) + printf(" to %d received channels\n", + Pm_MessageData2(data)); + else + printf(" to all received channels\n"); + break; + case MIDI_POLY_ON: + printf("Poly On Chan %2d\n", chan); + break; + } + } + } + } + } else if (command == MIDI_POLY_TOUCH) { + if (bender) { + showbytes(data, 3, verbose); + if (verbose) { + printf("P.Touch Chan %2d Key %2d ", chan, + Pm_MessageData1(data)); + len = put_pitch(Pm_MessageData1(data)); + printf(val_format + len, Pm_MessageData2(data)); + } + } + } else if (command == MIDI_TOUCH) { + if (bender) { + showbytes(data, 2, verbose); + if (verbose) { + printf(" A.Touch Chan %2d Val %2d\n", chan, + Pm_MessageData1(data)); + } + } + } else if (command == MIDI_BEND) { + if (bender) { + showbytes(data, 3, verbose); + if (verbose) { + printf("P.Bend Chan %2d Val %2d\n", chan, + (Pm_MessageData1(data) + (Pm_MessageData2(data)<<7))); + } + } + } else if (Pm_MessageStatus(data) == MIDI_SONG_POINTER) { + showbytes(data, 3, verbose); + if (verbose) { + printf(" Song Position %d\n", + (Pm_MessageData1(data) + (Pm_MessageData2(data)<<7))); + } + } else if (Pm_MessageStatus(data) == MIDI_SONG_SELECT) { + showbytes(data, 2, verbose); + if (verbose) { + printf(" Song Select %d\n", Pm_MessageData1(data)); + } + } else if (Pm_MessageStatus(data) == MIDI_TUNE_REQ) { + showbytes(data, 1, verbose); + if (verbose) { + printf(" Tune Request\n"); + } + } else if (Pm_MessageStatus(data) == MIDI_Q_FRAME) { + if (realdata) { + showbytes(data, 2, verbose); + if (verbose) { + printf(" Time Code Quarter Frame Type %d Values %d\n", + (Pm_MessageData1(data) & 0x70) >> 4, + Pm_MessageData1(data) & 0xf); + } + } + } else if (Pm_MessageStatus(data) == MIDI_START) { + if (realdata) { + showbytes(data, 1, verbose); + if (verbose) { + printf(" Start\n"); + } + } + } else if (Pm_MessageStatus(data) == MIDI_CONTINUE) { + if (realdata) { + showbytes(data, 1, verbose); + if (verbose) { + printf(" Continue\n"); + } + } + } else if (Pm_MessageStatus(data) == MIDI_STOP) { + if (realdata) { + showbytes(data, 1, verbose); + if (verbose) { + printf(" Stop\n"); + } + } + } else if (Pm_MessageStatus(data) == MIDI_SYS_RESET) { + if (realdata) { + showbytes(data, 1, verbose); + if (verbose) { + printf(" System Reset\n"); + } + } + } else if (Pm_MessageStatus(data) == MIDI_TIME_CLOCK) { + clockcount++; + if (clksencnt) { + showbytes(data, 1, verbose); + if (verbose) { + printf(" Clock\n"); + } + } + } else if (Pm_MessageStatus(data) == MIDI_ACTIVE_SENSING) { + actsensecount++; + if (clksencnt) { + showbytes(data, 1, verbose); + if (verbose) { + printf(" Active Sensing\n"); + } + } + } else showbytes(data, 3, verbose); + fflush(stdout); +} + + +/**************************************************************************** +* put_pitch +* Inputs: +* int p: pitch number +* Effect: write out the pitch name for a given number +****************************************************************************/ + +private int put_pitch(int p) +{ + char result[8]; + static char *ptos[] = { + "c", "cs", "d", "ef", "e", "f", "fs", "g", + "gs", "a", "bf", "b" }; + /* note octave correction below */ + sprintf(result, "%s%d", ptos[p % 12], (p / 12) - 1); + fputs(result, stdout); + return (int) strlen(result); +} + + +/**************************************************************************** +* showbytes +* Effect: print hex data, precede with newline if asked +****************************************************************************/ + +char nib_to_hex[] = "0123456789ABCDEF"; + +private void showbytes(PmMessage data, int len, boolean newline) +{ + int count = 0; + int i; + +/* if (newline) { + putchar('\n'); + count++; + } */ + for (i = 0; i < len; i++) { + putchar(nib_to_hex[(data >> 4) & 0xF]); + putchar(nib_to_hex[data & 0xF]); + count += 2; + if (count > 72) { + putchar('.'); + putchar('.'); + putchar('.'); + break; + } + data >>= 8; + } + putchar(' '); +} + + + +/**************************************************************************** +* showhelp +* Effect: print help text +****************************************************************************/ + +private void showhelp() +{ + printf("\n"); + printf(" Item Reported Range Item Reported Range\n"); + printf(" ------------- ----- ------------- -----\n"); + printf(" Channels 1 - 16 Programs 1 - 128\n"); + printf(" Controllers 0 - 127 After Touch 0 - 127\n"); + printf(" Loudness 0 - 127 Pitch Bend 0 - 16383, " + "center = 8192\n"); + printf(" Pitches 0 - 127, 60 = c4 = middle C\n"); + printf(" \n"); + printf("n toggles notes"); + showstatus(notes); + printf("t displays noteon count since last t\n"); + printf("b toggles pitch bend, aftertouch"); + showstatus(bender); + printf("c toggles continuous control"); + showstatus(controls); + printf("h toggles program changes"); + showstatus(pgchanges); + printf("x toggles system exclusive"); + showstatus(excldata); + printf("k toggles clock and sense messages, clears counts"); + showstatus(clksencnt); + printf("r toggles other real time messages & SMPTE"); + showstatus(realdata); + printf("s displays clock and sense count since last k\n"); + printf("m toggles channel mode messages"); + showstatus(chmode); + printf("v toggles verbose text"); + showstatus(verbose); + printf("q quits\n"); + printf("\n"); +} + +/**************************************************************************** +* showstatus +* Effect: print status of flag +****************************************************************************/ + +private void showstatus(boolean flag) +{ + printf(", now %s\n", flag ? "ON" : "OFF"); +} diff --git a/portmidi/pm_test/multivirtual.c b/portmidi/pm_test/multivirtual.c new file mode 100644 index 0000000..b90d860 --- /dev/null +++ b/portmidi/pm_test/multivirtual.c @@ -0,0 +1,223 @@ +/* multivirtual.c -- test for creating two input and two output virtual ports */ +/* + * Roger B. Dannenberg + * Oct 2021 + */ +#include "portmidi.h" +#include "porttime.h" +#include "stdlib.h" +#include "stdio.h" +#include "string.h" +#include "assert.h" + +#define OUTPUT_BUFFER_SIZE 0 +#define DEVICE_INFO NULL +#define DRIVER_INFO NULL +#define TIME_PROC ((PmTimeProcPtr) Pt_Time) +#define TIME_INFO NULL +#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ + +int latency = 0; + +static void prompt_and_exit(void) +{ + printf("type ENTER..."); + while (getchar() != '\n') ; + /* this will clean up open ports: */ + exit(-1); +} + + +static PmError checkerror(PmError err) +{ + if (err == pmHostError) { + /* it seems pointless to allocate memory and copy the string, + * so I will do the work of Pm_GetHostErrorText directly + */ + char errmsg[80]; + Pm_GetHostErrorText(errmsg, 80); + printf("PortMidi found host error...\n %s\n", errmsg); + prompt_and_exit(); + } else if (err < 0) { + printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); + prompt_and_exit(); + } + return err; +} + +static int msg_count[2] = {0, 0}; + +void poll_input(PmStream *in, int which) +{ + PmEvent buffer[1]; + int pitch, expected, length; + PmError status = Pm_Poll(in); + if (status == TRUE) { + length = Pm_Read(in, buffer, 1); + if (length > 0) { + printf("Got message %d from portmidi%d: " + "time %ld, %2x %2x %2x\n", + msg_count[which], which + 1, (long) buffer[0].timestamp, + (status = Pm_MessageStatus(buffer[0].message)), + (pitch = Pm_MessageData1(buffer[0].message)), + Pm_MessageData2(buffer[0].message)); + if (status == 0x90) { /* 1 & 2 are on/off 60, 3 & 4 are 61, etc. */ + expected = (((msg_count[which] - 1) / 2) % 12) + 60 + + which * 12; + if (pitch != expected) { + printf("WARNING: expected pitch %d, got pitch %d\n", + expected, pitch); + } + } + msg_count[which]++; + } else { + assert(0); + } + } +} + + +void wait_until(PmTimestamp when, PmStream *in1, PmStream *in2) +{ + while (when > Pt_Time()) { + poll_input(in1, 0); + poll_input(in2, 1); + Pt_Sleep(10); + } +} + + +/* create one virtual output device and one input device */ +void init(const char *name, PmStream **midi_out, PmStream **midi_in, + int *id_out, int *id_in) +{ + PmEvent buffer[1]; + + *id_out = checkerror(Pm_CreateVirtualOutput(name, NULL, DEVICE_INFO)); + checkerror(Pm_OpenOutput(midi_out, *id_out, DRIVER_INFO, OUTPUT_BUFFER_SIZE, + TIME_PROC, TIME_INFO, latency)); + printf("Virtual Output \"%s\" id %d created and opened.\n", name, *id_out); + + *id_in = checkerror(Pm_CreateVirtualInput(name, NULL, DRIVER_INFO)); + checkerror(Pm_OpenInput(midi_in, *id_in, NULL, 0, NULL, NULL)); + printf("Virtual Input \"%s\" id %d created and opened.\n", name, *id_in); + Pm_SetFilter(*midi_in, PM_FILT_ACTIVE | PM_FILT_CLOCK | PM_FILT_SYSEX); + /* empty the buffer after setting filter, just in case anything + got through */ + while (Pm_Read(*midi_in, buffer, 1)) ; +} + + +void main_test(int num) +{ + PmStream *midi1_out; + PmStream *midi2_out; + PmStream *midi1_in; + PmStream *midi2_in; + int id1_out; + int id2_out; + int id1_in; + int id2_in; + int32_t next_time; + PmEvent buffer[1]; + int pitch = 60; + int expected_count = num + 1; /* add 1 for MIDI Program message */ + + /* It is recommended to start timer before Midi; otherwise, PortMidi may + start the timer with its (default) parameters + */ + TIME_START; + + init("portmidi1", &midi1_out, &midi1_in, &id1_out, &id1_in); + init("portmidi2", &midi2_out, &midi2_in, &id2_out, &id2_in); + + printf("Type ENTER to send messages: "); + while (getchar() != '\n') ; + + buffer[0].timestamp = Pt_Time(); +#define PROGRAM 0 + buffer[0].message = Pm_Message(0xC0, PROGRAM, 0); + Pm_Write(midi1_out, buffer, 1); + Pm_Write(midi2_out, buffer, 1); + next_time = Pt_Time() + 1000; /* wait 1s */ + while (num > 0) { + wait_until(next_time, midi1_in, midi2_in); + Pm_WriteShort(midi1_out, next_time, Pm_Message(0x90, pitch, 100)); + Pm_WriteShort(midi2_out, next_time, Pm_Message(0x90, pitch + 12, 100)); + printf("Note On pitch %d\n", pitch); + num--; + next_time += 500; + + wait_until(next_time, midi1_in, midi2_in); + Pm_WriteShort(midi1_out, next_time, Pm_Message(0x90, pitch, 0)); + Pm_WriteShort(midi2_out, next_time, Pm_Message(0x90, pitch + 12, 0)); + printf("Note Off pitch %d\n", pitch); + num--; + pitch = (pitch + 1) % 12 + 60; + next_time += 500; + } + wait_until(next_time, midi1_in, midi2_in); /* get final note-offs */ + + printf("Got %d messages from portmidi1 and %d from portmidi2; " + "expected %d.\n", msg_count[0], msg_count[1], expected_count); + + /* close devices (this not explicitly needed in most implementations) */ + printf("ready to close..."); + checkerror(Pm_Close(midi1_out)); + checkerror(Pm_Close(midi2_out)); + checkerror(Pm_Close(midi1_in)); + checkerror(Pm_Close(midi2_in)); + printf("done closing.\nNow delete the virtual devices..."); + checkerror(Pm_DeleteVirtualDevice(id1_out)); + checkerror(Pm_DeleteVirtualDevice(id1_in)); + checkerror(Pm_DeleteVirtualDevice(id2_out)); + checkerror(Pm_DeleteVirtualDevice(id2_in)); + printf("done deleting.\n"); +} + + +void show_usage() +{ + printf("Usage: multivirtual [-h] [-l latency-in-ms] [n]\n" + " -h for this message,\n" + " -l ms designates latency for precise timing (default 0),\n" + " n is number of message to send each output, not counting\n" + " initial program change.\n" + "sends change program to 1, then one note per second with 0.5s on,\n" + "0.5s off, for n/2 seconds to both output ports portmidi1 and\n" + "portmidi2. portmidi1 gets pitches from C4 (60). portmidi2 gets\n" + "pitches an octave higher. Latency >0 uses the device driver for \n" + "precise timing (see PortMidi documentation). Inputs print what\n" + "they get and print WARNING if they get something unexpected.\n" + "The expected test is use two instances of testio to loop\n" + "portmidi1 back to portmidi1 and portmidi2 back to portmidi2.\n"); + exit(0); +} + + +int main(int argc, char *argv[]) +{ + int num = 10; + int i; + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-h") == 0) { + show_usage(); + } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) { + i = i + 1; + latency = atoi(argv[i]); + printf("Latency will be %d\n", latency); + } else { + num = atoi(argv[1]); + if (num <= 0) { + show_usage(); + } + printf("Sending %d messages.\n", num); + } + } + + main_test(num); + + printf("finished sendvirtual test...type ENTER to quit..."); + while (getchar() != '\n') ; + return 0; +} diff --git a/portmidi/pm_test/pmlist.c b/portmidi/pm_test/pmlist.c new file mode 100644 index 0000000..5e3d1db --- /dev/null +++ b/portmidi/pm_test/pmlist.c @@ -0,0 +1,63 @@ +/* pmlist.c -- list portmidi devices and numbers + * + * This program lists devices. When you type return, it + * restarts portmidi and lists devices again. It is mainly + * a test for shutting down and restarting. + * + * Roger B. Dannenberg, Feb 2022 + */ + +#include "portmidi.h" +#include "porttime.h" +#include "stdlib.h" +#include "stdio.h" +#include "string.h" +#include "assert.h" + +#define DEVICE_INFO NULL +#define DRIVER_INFO NULL +#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ + +#define STRING_MAX 80 /* used for console input */ + +void show_usage() +{ + printf("Usage: pmlist [-h]\n -h means help.\n" + " Type return to rescan and list devices, q to quit\n"); +} + + +int main(int argc, char *argv[]) +{ + if (argc > 1) { + show_usage(); + exit(0); + } + + while (1) { + char input[STRING_MAX]; + const char *deflt; + const char *in_or_out; + int default_in, default_out, i; + + // Pm_Initialize(); + /* list device information */ + default_in = Pm_GetDefaultInputDeviceID(); + default_out = Pm_GetDefaultOutputDeviceID(); + for (i = 0; i < Pm_CountDevices(); i++) { + const PmDeviceInfo *info = Pm_GetDeviceInfo(i); + printf("%d: %s, %s", i, info->interf, info->name); + deflt = ""; + if (i == default_out || i == default_in) { + deflt = "default "; + } + in_or_out = (info->input ? "input" : "output"); + printf(" (%s%s)\n", deflt, in_or_out); + } + if (fgets(input, STRING_MAX, stdin) && input[0] == 'q') { + return 0; + } + Pm_Terminate(); + } + return 0; +} diff --git a/portmidi/pm_test/qtest.c b/portmidi/pm_test/qtest.c new file mode 100644 index 0000000..14d803e --- /dev/null +++ b/portmidi/pm_test/qtest.c @@ -0,0 +1,174 @@ +#include "portmidi.h" +#include "pmutil.h" +#include "stdlib.h" +#include "stdio.h" + + +/* make_msg -- make a psuedo-random message of length n whose content + * is purely a function of i + */ +void make_msg(long msg[], int n, int i) +{ + int j; + for (j = 0; j < n; j++) { + msg[j] = i % (j + 5); + } +} + + +/* print_msg -- print the content of msg of length n to stdout */ +/**/ +void print_msg(long msg[], int n) +{ + int i; + for (i = 0; i < n; i++) { + printf(" %li", msg[i]); + } +} + + +/* cmp_msg -- compare two messages of length n */ +/**/ +int cmp_msg(long msg[], long msg2[], int n, int i) +{ + int j; + for (j = 0; j < n; j++) { + if (msg[j] != msg2[j]) { + printf("Received message %d doesn't match sent message\n", i); + printf("in: "); print_msg(msg, n); printf("\n"); + printf("out:"); print_msg(msg2, n); printf("\n"); + return FALSE; + } + } + return TRUE; +} + + +int main() +{ + int msg_len; + for (msg_len = 4; msg_len < 100; msg_len += 5) { + PmQueue *queue = Pm_QueueCreate(100, msg_len * sizeof(long)); + int i; + long msg[100]; + long msg2[100]; + + printf("msg_len = %d\n", msg_len); + if (!queue) { + printf("Could not allocate queue\n"); + return 1; + } + + /* insert/remove 1000 messages */ + printf("test 1\n"); + for (i = 0; i < 1357; i++) { + make_msg(msg, msg_len, i); + if (Pm_Enqueue(queue, msg)) { + printf("Pm_Enqueue error\n"); + return 1; + } + if (Pm_Dequeue(queue, msg2) != 1) { + printf("Pm_Dequeue error\n"); + return 1; + } + if (!cmp_msg(msg, msg2, msg_len, i)) { + return 1; + } + } + + /* make full */ + printf("test 2\n"); + for (i = 0; i < 100; i++) { + make_msg(msg, msg_len, i); + if (Pm_Enqueue(queue, msg)) { + printf("Pm_Enqueue error\n"); + return 1; + } + } + + /* alternately remove and insert */ + for (i = 100; i < 1234; i++) { + make_msg(msg, msg_len, i - 100); /* what we expect */ + if (Pm_Dequeue(queue, msg2) != 1) { + printf("Pm_Dequeue error\n"); + return 1; + } + if (!cmp_msg(msg, msg2, msg_len, i)) { + return 1; + } + make_msg(msg, msg_len, i); + if (Pm_Enqueue(queue, msg)) { + printf("Pm_Enqueue error\n"); + return 1; + } + } + + /* remove all */ + while (!Pm_QueueEmpty(queue)) { + make_msg(msg, msg_len, i - 100); /* what we expect */ + if (Pm_Dequeue(queue, msg2) != 1) { + printf("Pm_Dequeue error\n"); + return 1; + } + if (!cmp_msg(msg, msg2, msg_len, i)) { + return 1; + } + i++; + } + if (i != 1334) { + printf("Message count error\n"); + return 1; + } + + /* now test overflow */ + printf("test 3\n"); + for (i = 0; i < 110; i++) { + make_msg(msg, msg_len, i); + if (Pm_Enqueue(queue, msg) == pmBufferOverflow) { + break; /* this is supposed to execute after 100 messages */ + } + } + for (i = 0; i < 100; i++) { + make_msg(msg, msg_len, i); + if (Pm_Dequeue(queue, msg2) != 1) { + printf("Pm_Dequeue error\n"); + return 1; + } + if (!cmp_msg(msg, msg2, msg_len, i)) { + return 1; + } + } + /* we should detect overflow after removing 100 messages */ + if (Pm_Dequeue(queue, msg2) != pmBufferOverflow) { + printf("Pm_Dequeue overflow expected\n"); + return 1; + } + + /* after overflow is detected (and cleared), sender can + * send again + */ + /* see if function is restored, also test peek */ + printf("test 4\n"); + for (i = 0; i < 1357; i++) { + long *peek; + make_msg(msg, msg_len, i); + if (Pm_Enqueue(queue, msg)) { + printf("Pm_Enqueue error\n"); + return 1; + } + peek = (long *) Pm_QueuePeek(queue); + if (!cmp_msg(msg, peek, msg_len, i)) { + return 1; + } + if (Pm_Dequeue(queue, msg2) != 1) { + printf("Pm_Dequeue error\n"); + return 1; + } + if (!cmp_msg(msg, msg2, msg_len, i)) { + return 1; + } + } + Pm_QueueDestroy(queue); + } + return 0; +} diff --git a/portmidi/pm_test/recvvirtual.c b/portmidi/pm_test/recvvirtual.c new file mode 100644 index 0000000..f8d9848 --- /dev/null +++ b/portmidi/pm_test/recvvirtual.c @@ -0,0 +1,175 @@ +#include "portmidi.h" +#include "porttime.h" +#include "stdlib.h" +#include "stdio.h" +#include "string.h" +#include "assert.h" + +#define INPUT_BUFFER_SIZE 100 +#define TIME_PROC ((PmTimeProcPtr) Pt_Time) +#define TIME_INFO NULL +#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ + +#define STRING_MAX 80 /* used for console input */ + +char *portname = "portmidi"; + +PmSysDepInfo *sysdepinfo = NULL; +char *port_name = "portmidi"; + +static void set_sysdepinfo(char m_or_p, const char *name) +{ + if (!sysdepinfo) { + // allocate some space we will alias with open-ended PmDriverInfo: + // there is space for 4 parameters: + static char dimem[sizeof(PmSysDepInfo) + sizeof(void *) * 8]; + sysdepinfo = (PmSysDepInfo *) dimem; + // build the driver info structure: + sysdepinfo->structVersion = PM_SYSDEPINFO_VERS; + sysdepinfo->length = 0; + } + if (sysdepinfo->length > 1) { + printf("Error: sysdepinfo was allocated to hold 2 parameters\n"); + exit(1); + } + int i = sysdepinfo->length++; + enum PmSysDepPropertyKey k = pmKeyNone; + if (m_or_p == 'm') k = pmKeyCoreMidiManufacturer; + else if (m_or_p == 'p') k = pmKeyAlsaPortName; + else if (m_or_p == 'c') k = pmKeyAlsaClientName; + sysdepinfo->properties[i].key = k; + sysdepinfo->properties[i].value = name; +} + + +static void prompt_and_exit(void) +{ + printf("type ENTER..."); + while (getchar() != '\n') ; + /* this will clean up open ports: */ + exit(-1); +} + + +static PmError checkerror(PmError err) +{ + if (err == pmHostError) { + /* it seems pointless to allocate memory and copy the string, + * so I will do the work of Pm_GetHostErrorText directly + */ + char errmsg[80]; + Pm_GetHostErrorText(errmsg, 80); + printf("PortMidi found host error...\n %s\n", errmsg); + prompt_and_exit(); + } else if (err < 0) { + printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); + prompt_and_exit(); + } + return err; +} + + +void main_test_input(int num) +{ + PmStream *midi; + PmError status, length; + PmEvent buffer[1]; + int id; + int i = 0; /* count messages as they arrive */ + /* It is recommended to start timer before Midi; otherwise, PortMidi may + start the timer with its (default) parameters + */ + TIME_START; + + /* create a virtual input device */ + id = checkerror(Pm_CreateVirtualInput(port_name, NULL, sysdepinfo)); + checkerror(Pm_OpenInput(&midi, id, sysdepinfo, 0, NULL, NULL)); + + 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 */ + 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, %2lx %2lx %2lx\n", + i, + (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); + } + } + } + + /* close device (this not explicitly needed in most implementations) */ + printf("ready to close..."); + Pm_Close(midi); + printf("done closing.\nNow delete the virtual device..."); + checkerror(Pm_DeleteVirtualDevice(id)); + printf("done deleting.\n"); +} + + +void show_usage() +{ + printf("Usage: recvvirtual [-h] [-m manufacturer] [-c clientname] " + "[-p portname] [n]\n" + " -h for this message,\n" + " -m name designates a manufacturer name (macOS only),\n" + " -c name designates a client name (linux only),\n" + " -p name designates a port name (linux only),\n" + " n is number of message to wait for.\n"); + exit(0); +} + + +int main(int argc, char *argv[]) +{ + char line[STRING_MAX]; + int num = 10; + int i; + if (argc <= 1) { + show_usage(); + } + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-h") == 0) { + show_usage(); + } else if (strcmp(argv[i], "-m") == 0 && (i + 1 < argc)) { + i = i + 1; + set_sysdepinfo('m', argv[i]); + printf("Manufacturer name will be %s\n", argv[i]); + } else if (strcmp(argv[i], "-p") == 0 && (i + 1 < argc)) { + i = i + 1; + port_name = argv[i]; + set_sysdepinfo('p', port_name); + printf("Port name will be %s\n", port_name); + } else if (strcmp(argv[i], "-c") == 0 && (i + 1 < argc)) { + i = i + 1; + set_sysdepinfo('c', argv[i]); + printf("Client name will be %s\n", argv[i]); + } else { + num = atoi(argv[i]); + if (num <= 0) { + printf("Zero value or non-number for n\n"); + show_usage(); + } + printf("Waiting for %d messages.\n", num); + } + } + + main_test_input(num); + + printf("finished portMidi test...type ENTER to quit..."); + while (getchar() != '\n') ; + return 0; +} diff --git a/portmidi/pm_test/sendvirtual.c b/portmidi/pm_test/sendvirtual.c new file mode 100644 index 0000000..a60a48f --- /dev/null +++ b/portmidi/pm_test/sendvirtual.c @@ -0,0 +1,194 @@ +/* sendvirtual.c -- test for creating a virtual device and sending to it */ +/* + * Roger B. Dannenberg + * Sep 2021 + */ +#include "portmidi.h" +#include "porttime.h" +#include "stdlib.h" +#include "stdio.h" +#include "string.h" +#include "assert.h" + +#define OUTPUT_BUFFER_SIZE 0 +#define TIME_PROC ((PmTimeProcPtr) Pt_Time) +#define TIME_INFO NULL +#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ + +int latency = 0; +PmSysDepInfo *sysdepinfo = NULL; +char *port_name = "portmidi"; + +static void set_sysdepinfo(char m_or_p, const char *name) +{ + if (!sysdepinfo) { + // allocate some space we will alias with open-ended PmDriverInfo: + // there is space for 4 parameters: + static char dimem[sizeof(PmSysDepInfo) + sizeof(void *) * 8]; + sysdepinfo = (PmSysDepInfo *) dimem; + // build the driver info structure: + sysdepinfo->structVersion = PM_SYSDEPINFO_VERS; + sysdepinfo->length = 0; + } + if (sysdepinfo->length > 1) { + printf("Error: sysdepinfo was allocated to hold 2 parameters\n"); + exit(1); + } + int i = sysdepinfo->length++; + enum PmSysDepPropertyKey k = pmKeyNone; + if (m_or_p == 'm') k = pmKeyCoreMidiManufacturer; + else if (m_or_p == 'p') k = pmKeyAlsaPortName; + else if (m_or_p == 'c') k = pmKeyAlsaClientName; + sysdepinfo->properties[i].key = k; + sysdepinfo->properties[i].value = name; +} + + +static void prompt_and_exit(void) +{ + printf("type ENTER..."); + while (getchar() != '\n') ; + /* this will clean up open ports: */ + exit(-1); +} + + +static PmError checkerror(PmError err) +{ + if (err == pmHostError) { + /* it seems pointless to allocate memory and copy the string, + * so I will do the work of Pm_GetHostErrorText directly + */ + char errmsg[80]; + Pm_GetHostErrorText(errmsg, 80); + printf("PortMidi found host error...\n %s\n", errmsg); + prompt_and_exit(); + } else if (err < 0) { + printf("PortMidi call failed...\n %s\n", Pm_GetErrorText(err)); + prompt_and_exit(); + } + return err; +} + + +void wait_until(PmTimestamp when) +{ + PtTimestamp now = Pt_Time(); + if (when > now) { + Pt_Sleep(when - now); + } +} + + +void main_test_output(int num) +{ + PmStream *midi; + int32_t next_time; + PmEvent buffer[1]; + PmTimestamp timestamp; + int pitch = 60; + int id; + + /* It is recommended to start timer before Midi; otherwise, PortMidi may + start the timer with its (default) parameters + */ + TIME_START; + + /* create a virtual output device */ + id = checkerror(Pm_CreateVirtualOutput(port_name, NULL, sysdepinfo)); + checkerror(Pm_OpenOutput(&midi, id, sysdepinfo, OUTPUT_BUFFER_SIZE, + TIME_PROC, TIME_INFO, latency)); + + printf("Midi Output Virtual Device \"%s\" created.\n", port_name); + printf("Type ENTER to send messages: "); + while (getchar() != '\n') ; + + buffer[0].timestamp = Pt_Time(); +#define PROGRAM 0 + buffer[0].message = Pm_Message(0xC0, PROGRAM, 0); + Pm_Write(midi, buffer, 1); + next_time = Pt_Time() + 1000; /* wait 1s */ + while (num > 0) { + wait_until(next_time); + Pm_WriteShort(midi, next_time, Pm_Message(0x90, pitch, 100)); + printf("Note On pitch %d\n", pitch); + num--; + next_time += 500; + + wait_until(next_time); + Pm_WriteShort(midi, next_time, Pm_Message(0x90, pitch, 0)); + printf("Note Off pitch %d\n", pitch); + num--; + pitch = (pitch + 1) % 12 + 60; + next_time += 500; + } + + /* close device (this not explicitly needed in most implementations) */ + printf("ready to close..."); + Pm_Close(midi); + printf("done closing.\nNow delete the virtual device..."); + checkerror(Pm_DeleteVirtualDevice(id)); + printf("done deleting.\n"); +} + + +void show_usage() +{ + printf("Usage: sendvirtual [-h] [-l latency-in-ms] [-m manufacturer] " + "[-c clientname] [-p portname] [n]\n" + " -h for this message,\n" + " -l ms designates latency for precise timing (default 0),\n" + " -m name designates a manufacturer name (macOS only),\n" + " -c name designates a client name (linux only),\n" + " -p name designates a port name (linux only),\n" + " n is number of message to send.\n" + "sends change program to 1, then one note per second with 0.5s on,\n" + "0.5s off, for n/2 seconds. Latency >0 uses the device driver for \n" + "precise timing (see PortMidi documentation).\n"); + exit(0); +} + + +int main(int argc, char *argv[]) +{ + int num = 10; + int i; + if (argc <= 1) { + show_usage(); + } + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-h") == 0) { + show_usage(); + } else if (strcmp(argv[i], "-l") == 0 && (i + 1 < argc)) { + i = i + 1; + latency = atoi(argv[i]); + printf("Latency will be %d\n", latency); + } else if (strcmp(argv[i], "-m") == 0 && (i + 1 < argc)) { + i = i + 1; + set_sysdepinfo('m', argv[i]); + printf("Manufacturer name will be %s\n", argv[i]); + } else if (strcmp(argv[i], "-p") == 0 && (i + 1 < argc)) { + i = i + 1; + port_name = argv[i]; + set_sysdepinfo('p', port_name); + printf("Port name will be %s\n", port_name); + } else if (strcmp(argv[i], "-c") == 0 && (i + 1 < argc)) { + i = i + 1; + set_sysdepinfo('c', argv[i]); + printf("Client name will be %s\n", argv[i]); + } else { + num = atoi(argv[i]); + if (num <= 0) { + printf("Zero value or non-number for n\n"); + show_usage(); + } + printf("Sending %d messages.\n", num); + } + } + + main_test_output(num); + + printf("finished sendvirtual test...type ENTER to quit..."); + while (getchar() != '\n') ; + return 0; +} 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 +#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; +} diff --git a/portmidi/pm_test/testio.c b/portmidi/pm_test/testio.c new file mode 100755 index 0000000..2711286 --- /dev/null +++ b/portmidi/pm_test/testio.c @@ -0,0 +1,594 @@ +#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 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; +PmSysDepInfo *sysdepinfo = NULL; + +/* 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; +} + + +static void set_sysdepinfo(char m_or_p, const char *name) +{ + if (!sysdepinfo) { + // allocate some space we will alias with open-ended PmDriverInfo: + // there is space for 4 parameters: + static char dimem[sizeof(PmSysDepInfo) + sizeof(void *) * 8]; + sysdepinfo = (PmSysDepInfo *) dimem; + // build the driver info structure: + sysdepinfo->structVersion = PM_SYSDEPINFO_VERS; + sysdepinfo->length = 0; + } + if (sysdepinfo->length > 1) { + printf("Error: sysdepinfo was allocated to hold 2 parameters\n"); + exit(1); + } + int i = sysdepinfo->length++; + enum PmSysDepPropertyKey k = pmKeyNone; + if (m_or_p == 'm') k = pmKeyCoreMidiManufacturer; + else if (m_or_p == 'p') k = pmKeyAlsaPortName; + else if (m_or_p == 'c') k = pmKeyAlsaClientName; + sysdepinfo->properties[i].key = k; + sysdepinfo->properties[i].value = name; +} + + +/* + * 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, + sysdepinfo, + 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, + sysdepinfo, + 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 { + PmError err = 0; + printf("ready to note-on... (type ENTER):"); + WAIT_FOR_ENTER + buffer[0].timestamp = Pt_Time(); + buffer[0].message = Pm_Message(0x90, 60, 100); + if ((err = Pm_Write(midi, buffer, 1))) { + printf("Pm_Write returns error: %d (%s)\n", + err, Pm_GetErrorText(err)); + if (err == pmHostError) { + char errmsg[128]; + Pm_GetHostErrorText(errmsg, 127); + printf(" Host error: %s\n", errmsg); + } + } + printf("ready to note-off... (type ENTER):"); + WAIT_FOR_ENTER + buffer[0].timestamp = Pt_Time(); + buffer[0].message = Pm_Message(0x90, 60, 0); + if ((err = Pm_Write(midi, buffer, 1))) { + printf("Pm_Write returns error: %d (%s)\n", + err, Pm_GetErrorText(err)); + if (err == pmHostError) { + char errmsg[128]; + Pm_GetHostErrorText(errmsg, 127); + printf(" Host error: %s\n", errmsg); + } + } + + /* 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) + /* There was a report that Pm_Write with zero length sent last + * message again, so call Pm_Write here to see if note repeats + */ + Pm_Write(midi, buffer, 0); + Pt_Sleep(20); /* 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, + sysdepinfo, + 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, + sysdepinfo, + 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, + sysdepinfo, + 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] [-c clientname] " + "[-p portname] [-v]\n" + " -h for this help message (only)\n" + " -l for latency\n" + " -c name designates a client name (linux only),\n" + " -p name designates a port name (linux only),\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], "-p") == 0 && (i + 1 < argc)) { + i = i + 1; + const char *port_name = argv[i]; + set_sysdepinfo('p', port_name); + printf("Port name will be %s\n", port_name); + } else if (strcmp(argv[i], "-c") == 0 && (i + 1 < argc)) { + i = i + 1; + set_sysdepinfo('c', argv[i]); + printf("Client name will be %s\n", argv[i]); + } 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; +} diff --git a/portmidi/pm_test/txdata.syx b/portmidi/pm_test/txdata.syx new file mode 100755 index 0000000..1e06e5a --- /dev/null +++ b/portmidi/pm_test/txdata.syx @@ -0,0 +1,257 @@ +20 0 1d 4 c 6 0 34 1 4d 4 d 1f 7 3 6 + c 5e 4 4d d b 18 5 3 6 0 3d 1 4a 16 18 +1f 8 3 6 d 0 1 63 4 13 3a 23 0 0 0 2 + c 2 4 0 63 32 0 0 0 32 0 47 72 61 6e 64 +50 69 61 6e 6f 63 63 63 32 32 32 0 0 0 0 0 +10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 9 9 f c 27 2 35 37 10 1f 4 3 4 + d 19 4 56 5 16 1f f 8 d c 0 43 60 4 e +1f c 3 7 e 0 43 63 5 10 3c 14 8 2 1b 56 + 5 2 4 0 63 32 0 0 0 32 0 4c 6f 54 69 6e +65 38 31 5a 20 63 63 63 32 32 32 0 7f 0 1 0 +18 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f e f e 9 0 3 43 2d e 1f f 5 7 + f 16 43 5a 0 0 1f 12 6 8 d 0 3 63 4 0 +1f 12 6 8 f 0 2 63 4 6 34 14 0 1 2 4e +18 2 4 0 63 32 0 32 0 32 0 44 79 6e 6f 6d +69 74 65 45 50 63 63 63 32 32 32 0 70 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f b 1 b 8 18 40 5f a e 1f 1f 0 a + f 0 40 5f 4 0 1f 1f 0 a f 0 40 63 5 6 +1f 1f 0 a f 0 40 5f 0 8 1f 20 0 3 0 5a +18 4 4 0 63 32 32 0 0 32 0 50 65 72 63 4f +72 67 61 6e 20 63 63 63 32 32 32 0 0 0 0 0 + 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f b 7 f 9 0 4 49 13 13 1f 8 7 5 + e 0 2 58 0 c 1f 6 4 6 f 23 3 46 10 a +1f 7 8 c d 0 2 63 8 b 2 1c 0 0 0 52 +18 4 4 0 63 32 0 32 0 32 0 54 68 69 6e 20 +43 6c 61 76 20 63 63 63 32 32 32 0 70 0 20 0 +10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f c 0 6 1 a 4 50 20 e 1f c 0 6 + 1 a 4 50 1f 8 1f b 9 5 e 0 2 63 5 e +1f b 9 5 e 0 3 63 4 8 4 1a 0 0 0 52 +1d 2 4 0 63 32 0 32 0 32 0 42 72 69 74 65 +43 65 6c 73 74 63 63 63 32 32 32 0 20 0 26 0 + 1 0 8 4 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 f 1f 4 8 f 0 3a 51 4 b e 1f 0 8 + f 0 22 4b 4 3 f 1a b 8 d 0 3b 36 9 3 +12 1f 0 8 f 0 22 5d 4 b 3a 1e 19 5 0 52 +18 4 4 0 63 32 0 0 0 32 0 54 72 75 6d 70 +65 74 38 31 5a 63 63 63 32 32 32 0 0 0 50 0 +51 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 c 5 0 8 0 0 2 4a 4 b f 1f 0 8 + f 0 2 3f 4 3 1f f 0 8 0 23 3 44 b 3 +10 1f 0 9 f 0 2 5e 4 c 3a 1f 19 7 0 52 +18 4 4 0 63 32 0 0 0 32 0 46 6c 75 67 65 +6c 68 6f 72 6e 63 63 63 32 32 32 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 10 1f 0 8 f 0 42 4a 0 3 11 1f 0 8 + f a 43 51 0 3 11 9 0 8 d 0 42 2b 16 6 +10 1f 0 9 f 0 42 63 4 b 3a 1e 9 9 0 5a +24 4 4 0 63 32 31 0 0 32 0 52 61 73 70 41 +6c 74 6f 20 20 63 63 63 32 32 32 0 10 0 20 0 +54 0 20 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 10 9 2 6 d 0 41 3e 4 15 c b 2 3 + e 0 41 4f 4 12 c e 2 8 d 0 42 4b a 1c + d b 1 9 e 0 3 63 a 14 0 23 f 2 1b 5e +18 4 5 0 63 28 50 32 0 32 0 48 61 72 6d 6f +6e 69 63 61 20 63 63 63 32 32 32 0 50 10 50 0 +50 0 10 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1c 2 0 4 e 63 0 4e 4 3 d 5 0 6 + e 63 1 56 a 8 12 7 0 6 9 63 2 47 1b e + a a 0 5 f 0 1 63 4 b 32 1a 8 d 0 52 + c 4 4 0 63 32 0 0 0 32 0 44 6f 75 62 6c +65 42 61 73 73 63 63 63 32 32 32 0 10 0 0 0 + 3 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 b 4 0 4 f 14 2 49 9 6 a 7 0 4 + f 14 2 51 a 0 8 1f 0 5 f 0 1 63 9 6 + a 1f 0 5 f 0 1 63 a 0 3c 1f 6 9 0 52 + 5 4 4 0 63 32 0 0 0 32 0 48 69 53 74 72 +69 6e 67 20 31 63 63 63 32 32 32 0 2 0 30 0 +32 0 10 5 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 10 13 f 4 a 0 3 3b 14 14 1f e 8 7 + 9 0 2 42 5 e 18 13 d 9 c 0 2 3c 13 8 +1f 11 7 4 f 0 42 63 4 10 3a 1b 0 0 0 52 +1d 4 4 0 63 32 0 0 0 32 0 48 61 72 70 20 +20 20 20 20 20 63 63 63 32 32 32 8 0 0 21 0 + 0 0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 6 6 4 f 0 40 48 5 0 c 8 7 5 + f 5 0 52 4 0 f 7 3 7 e 8 3 63 4 6 + f 8 4 5 f 0 3 63 4 6 7c 1f 0 6 0 4a +11 2 4 0 63 32 0 0 0 32 0 46 61 6e 66 61 +72 54 70 74 73 63 63 63 32 32 32 6 1 0 38 0 + 8 0 48 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 d b 0 1 c 0 2 2c 3d 3 d 7 0 1 + c 0 2 1f 3c 3 d 1f 0 5 f 0 2 63 5 6 + d 1f 0 5 f 0 2 63 4 0 3c 63 0 2f 0 53 +11 4 4 0 63 32 0 0 0 32 0 42 72 65 61 74 +68 4f 72 67 6e 63 63 63 32 32 32 4 30 5 50 0 +11 0 18 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 9 0 6 0 27 2 51 19 b 1c 6 0 8 + 0 37 2 47 a 3 1f a 0 9 0 3d 2 4d a e +1f 12 8 8 f 0 3 61 4 b 28 1f 0 3 0 52 + c 3 4 0 63 32 1 32 0 32 0 4e 79 6c 6f 6e +47 75 69 74 20 63 63 63 32 32 32 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f e e f f 0 3 48 2d 6 1f f 4 f + f 25 3 5b 0 0 1f 12 6 c e 1c 3 55 0 10 +1f 13 7 8 e 6 4 62 4 e 3b 14 0 0 0 42 +18 2 4 0 63 32 0 32 0 32 0 47 75 69 74 61 +72 20 23 31 20 63 63 63 32 32 32 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 19 8 a 3 0 3 63 10 18 1f c 5 b + 5 0 3 52 0 b 1f 19 6 b 5 0 3 63 a 16 +1f f 11 9 7 0 4 63 4 3 3a 14 0 0 0 42 +18 2 4 0 63 32 0 32 0 32 0 46 75 6e 6b 79 +20 50 69 63 6b 63 63 63 32 32 32 0 30 0 0 0 + 0 0 0 7 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 1 0 8 4 0 3 3d a 1e 1f 1 0 8 + 0 0 0 43 0 10 1f 9 6 8 c 1b 7 46 1c 1e +1f 9 0 9 9 0 1 63 4 3 3a 1c 0 0 0 52 + c 4 5 0 63 4b 0 0 0 32 0 45 6c 65 63 42 +61 73 73 20 31 63 63 63 32 32 32 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f f f e 9 0 3 46 1d 16 1f f 5 e + e d 3 63 0 b 1f 13 6 5 d 1c 3 63 0 0 +1f 13 6 8 f 0 4 63 4 6 3b 1f 0 0 0 42 + c 4 4 0 63 32 0 32 0 32 0 53 79 6e 46 75 +6e 6b 42 61 73 63 63 63 32 32 32 d 6c 0 0 0 +70 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 10 7 8 3 0 3 4f 4 3 1f 9 0 8 + 0 0 1 4a 0 b 1f 11 0 8 0 0 1 47 4 8 +1f 9 0 8 0 0 0 63 0 b 39 19 0 7 0 52 + c 2 4 0 63 32 0 32 0 32 0 4c 61 74 65 6c +79 42 61 73 73 63 63 63 32 32 32 2 0 0 0 0 +40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 13 12 0 9 d 22 0 51 0 b 1f 14 0 5 + 8 24 40 5c 0 3 1f 11 0 6 c 2c 0 53 9 0 +10 1f 0 b f 0 0 5c a e 3a 22 11 e 1e 5e +18 7 4 0 63 32 0 32 0 32 0 53 79 6e 63 20 +4c 65 61 64 20 63 63 63 32 32 32 0 70 0 40 0 + 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 13 1e 0 9 e 0 0 63 3f b 1f 14 0 5 + e 24 1 51 4 3 1f 14 0 f 1 0 41 4d 8 3 + f 1f 0 b f 0 2 63 4 b 3b 20 11 12 33 56 +18 4 4 0 63 37 e 0 0 32 0 4a 61 7a 7a 20 +46 6c 75 74 65 63 63 63 32 32 32 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 15 13 d 3 d 1e 2 50 18 e 15 14 9 4 + c 1e 2 56 11 8 1b 1f f 7 f 0 1 63 4 6 +1a 1f e 6 f 0 2 63 4 0 7c b 0 8 0 62 +18 4 4 0 63 32 0 0 0 32 0 4a 61 76 61 20 +4a 69 76 65 20 63 63 63 32 32 32 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 0 0 4 f 0 40 63 3c 0 b 8 7 7 + f 5 0 63 4 6 f 5 3 7 f 8 0 3b 5 6 + e 8 4 5 f 0 3 63 3 0 7e 1d 6 f 0 4a +11 0 4 0 63 32 0 0 0 32 0 42 61 61 64 42 +72 65 61 74 68 63 63 63 32 32 32 6 30 0 38 0 + 1 0 46 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 0 0 4 f 0 40 47 2f 0 e 8 7 7 + f 5 0 4c 0 6 13 1c d c 6 8 0 63 5 6 +14 11 d b 0 0 3 63 4 0 7a 10 0 51 0 68 +17 0 4 0 63 32 0 0 0 32 0 56 6f 63 61 6c +4e 75 74 73 20 63 63 63 32 32 32 6 30 0 30 0 + 1 0 10 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 1f 0 5 f 0 0 41 32 3 1f 14 10 5 + 5 1 2 63 7 3 1f b 12 8 f 0 1 63 c 3 +1f 1f f 8 f 0 1 63 4 3 39 23 0 0 0 62 +18 7 4 0 63 32 0 0 0 32 0 57 61 74 65 72 +47 6c 61 73 73 63 63 63 32 32 32 0 0 0 0 0 + 0 0 0 7 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 16 2 0 4 6 9 1 4f 8 0 19 e 1 4 + 0 20 1 43 19 0 1f 12 10 6 7 0 0 54 3d 3 +16 d 6 6 2 1e 3 61 8 e 3a 20 1 14 0 42 + c 2 4 2 63 63 63 0 0 32 0 46 75 7a 7a 79 +20 4b 6f 74 6f 63 63 63 32 32 32 0 0 0 0 b +50 0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1c 8 0 3 e 0 1 55 12 3 1c 7 0 1 + e 2e 1 58 27 b e 4 0 2 a 0 2 63 4 a + d 9 0 2 c 1 2 63 10 b 4 54 0 47 0 53 +18 7 4 0 63 32 0 0 0 32 0 42 72 74 68 62 +65 6c 6c 73 20 63 63 63 32 32 32 0 4 0 40 0 +40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1a 4 1 1 b 16 0 47 5 3 15 e 0 1 + d 0 0 4c 5 16 1c 6 4 2 7 0 0 63 4 16 +18 18 3 1 e 0 0 5e 4 10 24 7 0 4 0 62 +24 4 4 0 63 32 0 0 0 32 0 54 75 62 65 20 +42 65 6c 6c 73 63 63 63 32 32 32 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 1f 13 3 0 0 0 5f 3d 6 1f 12 13 2 + 0 0 1 52 5 2 1f 14 13 3 0 0 1 56 28 5 +1e b 13 f 9 0 0 63 6 3 3b 63 0 63 0 73 +23 7 4 0 63 32 0 0 0 32 0 4e 6f 69 73 65 +20 53 68 6f 74 63 63 63 32 32 32 8 0 0 0 8 + 0 0 0 6 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 1f 16 0 3 7 0 1 50 0 3 1f 18 3 3 + 3 22 0 63 0 14 1d 7 6 3 6 0 1 3c 8 3 +1f 5 7 3 0 0 1 63 4 1b 39 23 0 8 0 42 +18 4 4 0 63 32 0 0 0 32 0 48 61 6e 64 20 +44 72 75 6d 20 63 63 63 32 32 32 0 1 0 3 0 + 1 0 1 3 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 7d f7 \ No newline at end of file diff --git a/portmidi/pm_test/virttest.c b/portmidi/pm_test/virttest.c new file mode 100644 index 0000000..1aeb09b --- /dev/null +++ b/portmidi/pm_test/virttest.c @@ -0,0 +1,339 @@ +/* virttest.c -- test for creating/deleting virtual ports */ +/* + * Roger B. Dannenberg + * Oct 2021 + +This test is performed by running 2 instances of the program. The +first instance makes input and output ports named portmidi and waits +for a message. The second tries to do the same, but will fail because +portmidi already exists. It then opens portmidi (both input and +output). In greater detail: + +FIRST INSTANCE SECOND INSTANCE +-------------- --------------- + +initialize PortMidi initialize PortMidi +create portmidi in +create portmidi out +wait for input + create portmidi in -> fails + open portmidi in/out + send to portmidi +recv from portmidi +send to portmidi +wait 1s recv from portmidi + close portmidi in and out + terminate PortMidi +list all devices: + - check for correct number + - check for good description of portmidi in port (open) + - check for good description of portmidi out port (open) +close portmidi in +list all devices: + - check for correct number + - check for good description of portmidi in port (closed) + - check for good description of portmidi out port (open) +close portmidi out +list all devices: + - check for correct number + - check for good description of portmidi in port (closed) + - check for good description of portmidi out port (closed) +delete portmidi in + - check for correct number + - check for NULL description of portmidi in port + - check for good description of portmidi out port (closed) +delete portmidi out + - check for correct number + - check for NULL description of portmidi in port + - check for NULL description of portmidi out port +terminate portmidi +REPEAT 3 TIMES wait 2 seconds to give head start to other instance + REPEAT 3 TIMES + */ + +#include "portmidi.h" +#include "porttime.h" +#include "stdlib.h" +#include "stdio.h" +#include "string.h" +#include "assert.h" + +#define OUTPUT_BUFFER_SIZE 0 +#define INPUT_BUFFER_SIZE 10 +#define DEVICE_INFO NULL +#define DRIVER_INFO NULL +#define TIME_PROC ((PmTimeProcPtr) Pt_Time) +#define TIME_INFO NULL +#define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */ + + +static void prompt_and_exit(void) +{ + printf("type ENTER..."); + while (getchar() != '\n') ; + /* this will clean up open ports: */ + exit(-1); +} + + +static PmError printerror(PmError err, const char *msg) +{ + if (err == pmHostError) { + /* it seems pointless to allocate memory and copy the string, + * so I will do the work of Pm_GetHostErrorText directly + */ + char errmsg[80]; + Pm_GetHostErrorText(errmsg, 80); + printf("%s\n %s\n", msg, errmsg); + } else if (err < 0) { + printf("%s\n %s\n", msg, Pm_GetErrorText(err)); + } + return err; +} + + +static PmError checkerror(PmError err) +{ + if (err < 0) { + printerror(err, "PortMidi call failed..."); + prompt_and_exit(); + } + return err; +} + + +void wait_until(PmTimestamp when) +{ + PtTimestamp now = Pt_Time(); + if (when > now) { + Pt_Sleep(when - now); + } +} + + +void show_usage() +{ + printf("Usage: virttest\n" + " run two instances to test virtual port create/delete\n"); +} + + +void check_info(int id, char stat, int input, int virtual) +{ + const PmDeviceInfo *info = Pm_GetDeviceInfo(id); + if (stat == 'd') { + if (info) { + printf("Expected device %d to be deleted.\n", id); + prompt_and_exit(); + } + return; + } + if (!info) { + printf("Expected device %d to not be deleted.\n", id); + prompt_and_exit(); + } + if (strcmp("portmidi", info->name) != 0) { + printf("Device %d name is %s, not \"portmidi\".\n", id, info->name); + prompt_and_exit(); + } + if (info->input != input || (!info->output) != input) { + printf("Device %d input/output fields are wrong.\n", id); + prompt_and_exit(); + } + if ((!info->opened && stat == 'o') || (info->opened && stat == 'c')) { + printf("Device %d opened==%d, status should be %c.\n", id, + info->opened, stat); + prompt_and_exit(); + } + if (info->is_virtual != virtual) { + printf("Expected device %d to be virtual.\n", id); + prompt_and_exit(); + } +} + + +/* stat is 'o' for open, 'c' for closed, 'd' for deleted device */ +void check_ports(int cnt, int in_id, char in_stat, + int out_id, char out_stat, int virtual) +{ + if (cnt != Pm_CountDevices()) { + printf("Device count changed from %d to %d.\n", cnt, Pm_CountDevices()); + prompt_and_exit(); + } + check_info(in_id, in_stat, TRUE, virtual); + check_info(out_id, out_stat, FALSE, virtual); +} + + +void devices_list() +{ + int i; + for (i = 0; i < Pm_CountDevices(); i++) { + const PmDeviceInfo *info = Pm_GetDeviceInfo(i); + if (info) { + printf("%d: %s %s %s %s\n", i, info->name, + (info->input ? "input" : "output"), + (info->is_virtual ? "virtual" : "real_device"), + (info->opened ? "opened" : "closed")); + } + } +} + + +void test2() +{ + PmStream *out = NULL; + PmStream *in = NULL; + int out_id; + int in_id; + PmEvent buffer[1]; + PmTimestamp timestamp; + int pitch = 60; + int device_count = 0; + int i; + + printf("This must be virttest instance #2\n"); + + /* find and open portmidi in and out */ + device_count = Pm_CountDevices(); + for (i = 0; i < device_count; i++) { + const PmDeviceInfo *info = Pm_GetDeviceInfo(i); + if (info && strcmp(info->name, "portmidi") == 0) { + if (info->input) { + checkerror(Pm_OpenInput(&in, i, DRIVER_INFO, + INPUT_BUFFER_SIZE, TIME_PROC, TIME_INFO)); + in_id = i; + } else { + checkerror(Pm_OpenOutput(&out, i, DRIVER_INFO, + OUTPUT_BUFFER_SIZE, NULL, NULL, 0)); + out_id = i; + } + } + } + if (!in) { + printf("Did not open portmidi as input (virtual output).\n"); + prompt_and_exit(); + } + if (!out) { + printf("Did not open portmidi as output (virtual input).\n"); + prompt_and_exit(); + } + printf("Input device %d and output device %d are open.\n", in_id, out_id); + + /* send a message */ + buffer[0].timestamp = 0; + buffer[0].message = Pm_Message(0x90, pitch, 100); + checkerror(Pm_Write(out, buffer, 1)); + + /* wait for reply */ + printf("Sent message, waiting for reply...\n"); + while (Pm_Read(in, buffer, 1) < 1) Pt_Sleep(10); + + printf("********** GOT THE MESSAGE, SHUTTING DOWN ************\n"); + + /* close in */ + checkerror(Pm_Close(in)); + check_ports(device_count, in_id, 'c', out_id, 'o', FALSE); + printf("Closed input %d\n", in_id); + + /* close out */ + checkerror(Pm_Close(out)); + check_ports(device_count, in_id, 'c', out_id, 'c', FALSE); + printf("Closed output %d\n", out_id); + + Pt_Sleep(1000); + /* wrap it up */ + Pm_Terminate(); + printf("Got reply and terminated...\n"); + Pt_Sleep(2000); /* 2 seconds because other is waiting 1s. */ + /* 1 more second to make sure other shuts down before test repeats. */ +} + +extern int pm_check_errors; + +void test() +{ + PmStream *out; + PmStream *in; + int out_id; + int in_id; + PmEvent buffer[1]; + PmTimestamp timestamp; + int device_count = 0; + + TIME_START; + + printf("******** INITIALIZING PORTMIDI ***********\n"); + timestamp = Pt_Time(); + Pm_Initialize(); + printf("Pm_Initialize took %dms\n", Pt_Time() - timestamp); + devices_list(); + + pm_check_errors = FALSE; /* otherwise, PM_CHECK_ERRORS, if defined, */ + /* can cause this program to report an error and exit on pmNameConflict. */ + in_id = Pm_CreateVirtualInput("portmidi", NULL, DEVICE_INFO); + pm_check_errors = TRUE; /* there should be no other errors */ + if (in_id < 0) { + printerror(in_id, "Pm_CreateVirtualInput failed..."); + test2(); + return; + } + printf("Created portmidi virtual input; this is virttest instance #1\n"); + out_id = checkerror(Pm_CreateVirtualOutput("portmidi", NULL, DRIVER_INFO)); + device_count = Pm_CountDevices(); + + checkerror(Pm_OpenInput(&in, in_id, NULL, 0, NULL, NULL)); + checkerror(Pm_OpenOutput(&out, out_id, DRIVER_INFO, OUTPUT_BUFFER_SIZE, + TIME_PROC, TIME_INFO, 0)); + printf("Created/Opened input %d and output %d\n", in_id, out_id); + Pm_SetFilter(in, PM_FILT_ACTIVE | PM_FILT_CLOCK | PM_FILT_SYSEX); + /* empty the buffer after setting filter, just in case anything + got through */ + while (Pm_Read(in, buffer, 1)) ; + + /* wait for input */ + printf("Waiting for input...\n"); + while (Pm_Read(in, buffer, 1) < 1) Pt_Sleep(10); + + /* send two replies (only one would be fine) */ + checkerror(Pm_Write(out, buffer, 1)); + printf("Received input, writing output...\n"); + + /* wait 1s so receiver can get the message before we shut down */ + Pt_Sleep(1000); + printf("****** Closing everything and shutting down...\n"); + + /* expect 2 open ports */ + check_ports(device_count, in_id, 'o', out_id, 'o', TRUE); + /* close in */ + checkerror(Pm_Close(in)); + check_ports(device_count, in_id, 'c', out_id, 'o', TRUE); + + /* close out */ + checkerror(Pm_Close(out)); + check_ports(device_count, in_id, 'c', out_id, 'c', TRUE); + + /* delete in */ + checkerror(Pm_DeleteVirtualDevice(in_id)); + check_ports(device_count, in_id, 'd', out_id, 'c', TRUE); + + /* delete out */ + checkerror(Pm_DeleteVirtualDevice(out_id)); + check_ports(device_count, in_id, 'd', out_id, 'd', TRUE); + + /* we are done */ + Pm_Terminate(); +} + + +int main(int argc, char *argv[]) +{ + int i; + show_usage(); + for (i = 0; i < 3; i++) { + test(); + } + printf("finished virttest (SUCCESS). Type ENTER to quit..."); + while (getchar() != '\n') ; + return 0; +} -- cgit v1.2.3