aboutsummaryrefslogtreecommitdiff
path: root/pd/portmidi/pm_test
diff options
context:
space:
mode:
authorHans-Christoph Steiner <eighthave@users.sourceforge.net>2005-12-15 00:57:02 +0000
committerHans-Christoph Steiner <eighthave@users.sourceforge.net>2005-12-15 00:57:02 +0000
commit90d5b8b4a064420d74678654e94ea4755b377f21 (patch)
tree5d49b9f6ae17b75c98ad1a8302a0d84f1d99f75b /pd/portmidi/pm_test
parent59ad9e9cf0a72b31f8bfd371cb97536ed4d4fe61 (diff)
checking in missing files on behalf of Miller (cleared it with him first). The files are from portmidi17nov04.zip
svn path=/trunk/; revision=4216
Diffstat (limited to 'pd/portmidi/pm_test')
-rw-r--r--pd/portmidi/pm_test/latency.c278
-rw-r--r--pd/portmidi/pm_test/latency.dsp102
-rw-r--r--pd/portmidi/pm_test/midithread.c327
-rw-r--r--pd/portmidi/pm_test/midithread.dsp102
-rw-r--r--pd/portmidi/pm_test/midithru.c364
-rw-r--r--pd/portmidi/pm_test/midithru.dsp102
-rw-r--r--pd/portmidi/pm_test/midithru.dsw29
-rw-r--r--pd/portmidi/pm_test/sysex.c319
-rw-r--r--pd/portmidi/pm_test/sysex.dsp102
-rw-r--r--pd/portmidi/pm_test/test.c469
-rw-r--r--pd/portmidi/pm_test/test.dsp102
-rw-r--r--pd/portmidi/pm_test/txdata.syx257
12 files changed, 2553 insertions, 0 deletions
diff --git a/pd/portmidi/pm_test/latency.c b/pd/portmidi/pm_test/latency.c
new file mode 100644
index 00000000..87b1965b
--- /dev/null
+++ b/pd/portmidi/pm_test/latency.c
@@ -0,0 +1,278 @@
+/* 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(char *prompt);
+
+PtTimestamp previous_callback_time = 0;
+
+int period; /* milliseconds per callback */
+
+long histogram[HIST_LEN];
+long max_latency = 0; /* worst latency observed */
+long 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(NULL);
+ 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()
+{
+ char line[STRING_MAX];
+ int i;
+ int len;
+ int choice;
+ PtTimestamp stop;
+ printf("Latency histogram.\n");
+ period = get_number("Choose timer period (in ms): ");
+ assert(period >= 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,
+ (long (*)(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,
+ (long (*)(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("%s%s", "Latency measurements will start in 5 seconds. ",
+ "Type return to stop: ");
+ Pt_Start(period, &pt_callback, 0);
+ fgets(line, STRING_MAX, stdin);
+ stop = Pt_Time();
+ Pt_Stop();
+
+ /* courteously turn off the last note, if necessary */
+ if (note_on) {
+ PmEvent buffer[1];
+ buffer[0].timestamp = Pt_Time(NULL);
+ 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 %10ld\n", i, histogram[i]);
+ }
+ printf("Number of points greater than %dms: %ld\n",
+ HIST_LEN - 1, out_of_range);
+ printf("Maximum latency: %ld 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...");
+ fgets(line, STRING_MAX, stdin);
+ return 0;
+}
+
+
+/* read a number from console */
+int get_number(char *prompt)
+{
+ char line[STRING_MAX];
+ int n = 0, i;
+ printf(prompt);
+ while (n != 1) {
+ n = scanf("%d", &i);
+ fgets(line, STRING_MAX, stdin);
+
+ }
+ return i;
+}
diff --git a/pd/portmidi/pm_test/latency.dsp b/pd/portmidi/pm_test/latency.dsp
new file mode 100644
index 00000000..ff6bbbd7
--- /dev/null
+++ b/pd/portmidi/pm_test/latency.dsp
@@ -0,0 +1,102 @@
+# Microsoft Developer Studio Project File - Name="latency" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Console Application" 0x0103
+
+CFG=latency - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "latency.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "latency.mak" CFG="latency - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "latency - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "latency - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "latency - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "latency___Win32_Release"
+# PROP BASE Intermediate_Dir "latency___Win32_Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "latencyRelease"
+# PROP Intermediate_Dir "latencyRelease"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD CPP /nologo /MT /W3 /GX /O2 /I "../porttime" /I "../pm_common" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
+# ADD LINK32 ..\Release\portmidi.lib ..\porttime\Release\porttime.lib winmm.lib /nologo /subsystem:console /machine:I386
+
+!ELSEIF "$(CFG)" == "latency - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "latency___Win32_Debug"
+# PROP BASE Intermediate_Dir "latency___Win32_Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "latencyDebug"
+# PROP Intermediate_Dir "latencyDebug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../porttime" /I "../pm_common" /D "_CONSOLE" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "USE_DLL_FOR_CLEANUP" /YX /FD /GZ /c
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 ..\pm_win\Debug\portmidi.lib ..\porttime\Debug\porttime.lib ..\pm_win\Debug\pm_dll.lib winmm.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+
+!ENDIF
+
+# Begin Target
+
+# Name "latency - Win32 Release"
+# Name "latency - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\latency.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/pd/portmidi/pm_test/midithread.c b/pd/portmidi/pm_test/midithread.c
new file mode 100644
index 00000000..861b347c
--- /dev/null
+++ b/pd/portmidi/pm_test/midithread.c
@@ -0,0 +1,327 @@
+/* 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;
+
+long 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 */
+ long 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) {
+ long status, data1, data2;
+ if (Pm_Read(midi_in, &buffer, 1) == pmBufferOverflow)
+ continue;
+ if (midi_thru)
+ Pm_Write(midi_out, &buffer, 1);
+ /* unless there was overflow, we should have a message now */
+ status = Pm_MessageStatus(buffer.message);
+ data1 = Pm_MessageData1(buffer.message);
+ data2 = Pm_MessageData2(buffer.message);
+ if ((status & 0xF0) == 0x90 ||
+ (status & 0xF0) == 0x80) {
+
+ /* this is a note-on or note-off, so transpose and send */
+ data1 += transpose;
+
+ /* keep within midi pitch range, keep proper pitch class */
+ while (data1 > 127)
+ data1 -= 12;
+ while (data1 < 0)
+ data1 += 12;
+
+ /* send the message */
+ buffer.message = Pm_Message(status, data1, data2);
+ Pm_Write(midi_out, &buffer, 1);
+
+ /* if monitor is set, send the pitch to the main thread */
+ if (monitor) {
+ Pm_Enqueue(midi_to_main, &data1);
+ monitor = FALSE; /* only send one pitch per request */
+ }
+ }
+ }
+ } while (result);
+}
+
+void exit_with_message(char *msg)
+{
+ char line[STRING_MAX];
+ printf("%s\n", msg);
+ fgets(line, STRING_MAX, stdin);
+ exit(1);
+}
+
+int main()
+{
+ int id;
+ long n;
+ const PmDeviceInfo *info;
+ char line[STRING_MAX];
+ int spin;
+ int done = FALSE;
+
+ /* determine what type of test to run */
+ printf("begin PortMidi multithread test...\n");
+
+ /* note that it is safe to call PortMidi from the main thread for
+ initialization and opening devices. You should not make any
+ calls to PortMidi from this thread once the midi thread begins.
+ to make PortMidi calls.
+ */
+
+ /* make the message queues */
+ /* messages can be of any size and any type, but all messages in
+ * a given queue must have the same size. We'll just use long's
+ * for our messages in this simple example
+ */
+ midi_to_main = Pm_QueueCreate(32, sizeof(long));
+ assert(midi_to_main != NULL);
+ main_to_midi = Pm_QueueCreate(32, sizeof(long));
+ assert(main_to_midi != NULL);
+
+ /* a little test of enqueue and dequeue operations. Ordinarily,
+ * you would call Pm_Enqueue from one thread and Pm_Dequeue from
+ * the other. Since the midi thread is not running, this is safe.
+ */
+ n = 1234567890;
+ Pm_Enqueue(midi_to_main, &n);
+ n = 987654321;
+ Pm_Enqueue(midi_to_main, &n);
+ Pm_Dequeue(midi_to_main, &n);
+ if (n != 1234567890) {
+ exit_with_message("Pm_Dequeue produced unexpected result.");
+ }
+ Pm_Dequeue(midi_to_main, &n);
+ if(n != 987654321) {
+ exit_with_message("Pm_Dequeue produced unexpected result.");
+ }
+
+ /* always start the timer before you start midi */
+ Pt_Start(1, &process_midi, 0); /* start a timer with millisecond accuracy */
+ /* the timer will call our function, process_midi() every millisecond */
+
+ Pm_Initialize();
+
+ id = Pm_GetDefaultOutputDeviceID();
+ info = Pm_GetDeviceInfo(id);
+ if (info == NULL) {
+ printf("Could not open default output device (%d).", id);
+ exit_with_message("");
+ }
+ printf("Opening output device %s %s\n", info->interf, info->name);
+
+ /* use zero latency because we want output to be immediate */
+ Pm_OpenOutput(&midi_out,
+ id,
+ DRIVER_INFO,
+ OUTPUT_BUFFER_SIZE,
+ TIME_PROC,
+ TIME_INFO,
+ LATENCY);
+
+ id = Pm_GetDefaultInputDeviceID();
+ info = Pm_GetDeviceInfo(id);
+ if (info == NULL) {
+ printf("Could not open default input device (%d).", id);
+ exit_with_message("");
+ }
+ printf("Opening input device %s %s\n", info->interf, info->name);
+ Pm_OpenInput(&midi_in,
+ id,
+ DRIVER_INFO,
+ INPUT_BUFFER_SIZE,
+ TIME_PROC,
+ TIME_INFO);
+
+ active = TRUE; /* enable processing in the midi thread -- yes, this
+ is a shared variable without synchronization, but
+ this simple assignment is safe */
+
+ printf("Enter midi input; it will be transformed as specified by...\n");
+ printf("%s\n%s\n%s\n",
+ "Type 'q' to quit, 'm' to monitor next pitch, t to toggle thru or",
+ "type a number to specify transposition.",
+ "Must terminate with [ENTER]");
+
+ while (!done) {
+ long msg;
+ int len;
+ fgets(line, STRING_MAX, stdin);
+ /* remove the newline: */
+ len = strlen(line);
+ if (len > 0) line[len - 1] = 0; /* overwrite the newline char */
+ if (strcmp(line, "q") == 0) {
+ msg = QUIT_MSG;
+ Pm_Enqueue(main_to_midi, &msg);
+ /* wait for acknowlegement */
+ do {
+ spin = Pm_Dequeue(midi_to_main, &msg);
+ } while (spin == 0); /* spin */ ;
+ done = TRUE; /* leave the command loop and wrap up */
+ } else if (strcmp(line, "m") == 0) {
+ msg = MONITOR_MSG;
+ Pm_Enqueue(main_to_midi, &msg);
+ printf("Waiting for note...\n");
+ do {
+ spin = Pm_Dequeue(midi_to_main, &msg);
+ } while (spin == 0); /* spin */ ;
+ printf("... pitch is %ld\n", 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, "%ld", &msg) == 1) {
+ if (msg >= -127 && msg <= 127) {
+ /* send transposition value */
+ printf("Transposing by %ld\n", msg);
+ Pm_Enqueue(main_to_midi, &msg);
+ } else {
+ printf("Transposition must be within -127...127\n");
+ }
+ } else {
+ printf("%s\n%s\n%s\n",
+ "Type 'q' to quit, 'm' to monitor next pitch, or",
+ "type a number to specify transposition.",
+ "Must terminate with [ENTER]");
+ }
+ }
+
+ /* at this point, midi thread is inactive and we need to shut down
+ * the midi input and output
+ */
+ Pt_Stop(); /* stop the timer */
+ Pm_QueueDestroy(midi_to_main);
+ Pm_QueueDestroy(main_to_midi);
+
+ /* Belinda! if close fails here, some memory is deleted, right??? */
+ Pm_Close(midi_in);
+ Pm_Close(midi_out);
+
+ printf("finished portMidi multithread test...enter any character to quit [RETURN]...");
+ fgets(line, STRING_MAX, stdin);
+ return 0;
+}
diff --git a/pd/portmidi/pm_test/midithread.dsp b/pd/portmidi/pm_test/midithread.dsp
new file mode 100644
index 00000000..7dc0a16f
--- /dev/null
+++ b/pd/portmidi/pm_test/midithread.dsp
@@ -0,0 +1,102 @@
+# Microsoft Developer Studio Project File - Name="midithread" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Console Application" 0x0103
+
+CFG=midithread - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "midithread.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "midithread.mak" CFG="midithread - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "midithread - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "midithread - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "midithread - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "midithreadRelease"
+# PROP Intermediate_Dir "midithreadRelease"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD CPP /nologo /MT /W3 /GX /O2 /I "../pm_common" /I "../porttime" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
+# ADD LINK32 ..\Release\portmidi.lib ..\porttime\Release\porttime.lib winmm.lib /nologo /subsystem:console /machine:I386
+
+!ELSEIF "$(CFG)" == "midithread - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "midithreadDebug"
+# PROP BASE Intermediate_Dir "midithreadDebug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "midithreadDebug"
+# PROP Intermediate_Dir "midithreadDebug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../pm_common" /I "../porttime" /D "_CONSOLE" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "USE_DLL_FOR_CLEANUP" /YX /FD /GZ /c
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 ..\pm_win\Debug\portmidi.lib ..\porttime\Debug\porttime.lib ..\pm_win\Debug\pm_dll.lib winmm.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+
+!ENDIF
+
+# Begin Target
+
+# Name "midithread - Win32 Release"
+# Name "midithread - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\midithread.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/pd/portmidi/pm_test/midithru.c b/pd/portmidi/pm_test/midithru.c
new file mode 100644
index 00000000..270246fb
--- /dev/null
+++ b/pd/portmidi/pm_test/midithru.c
@@ -0,0 +1,364 @@
+/* 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. 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
+
+/* active is set true when midi processing should start */
+int active = FALSE;
+/* process_midi_exit_flag is set when the timer thread shuts down */
+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;
+PmTimestamp current_timestamp = 0;
+int thru_sysex_in_progress = FALSE;
+int app_sysex_in_progress = FALSE;
+PmTimestamp last_timestamp = 0;
+
+
+/* time proc parameter for Pm_MidiOpen */
+long 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 */
+ /* if (current_timestamp % 1000 == 0)
+ printf("time %d\n", current_timestamp); */
+
+ /* 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) {
+ long status;
+ if (Pm_Read(midi_in, &buffer, 1) == pmBufferOverflow)
+ continue;
+
+ /* 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 */
+ long status = Pm_MessageStatus(buffer.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
+ char line[STRING_MAX];
+ printf("%s\nType ENTER...", msg);
+ fgets(line, STRING_MAX, stdin);
+ exit(1);
+}
+
+
+void initialize()
+/* 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;
+ int id;
+
+ /* 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();
+
+ id = Pm_GetDefaultOutputDeviceID();
+ info = Pm_GetDeviceInfo(id);
+ if (info == NULL) {
+ printf("Could not open default output device (%d).", id);
+ exit_with_message("");
+ }
+ printf("Opening output device %s %s\n", info->interf, info->name);
+
+ /* use zero latency because we want output to be immediate */
+ Pm_OpenOutput(&midi_out,
+ id,
+ NULL /* driver info */,
+ OUT_QUEUE_SIZE,
+ &midithru_time_proc,
+ NULL /* time info */,
+ 0 /* Latency */);
+
+ id = Pm_GetDefaultInputDeviceID();
+ info = Pm_GetDeviceInfo(id);
+ if (info == NULL) {
+ printf("Could not open default input device (%d).", id);
+ exit_with_message("");
+ }
+ printf("Opening input device %s %s\n", info->interf, info->name);
+ Pm_OpenInput(&midi_in,
+ id,
+ NULL /* driver info */,
+ 0 /* use default input size */,
+ &midithru_time_proc,
+ NULL /* time info */);
+ /* 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;
+
+ /* determine what type of test to run */
+ printf("begin PortMidi midithru program...\n");
+
+ initialize(); /* set up and start midi processing */
+
+ printf("%s\n%s\n",
+ "This program will run for 60 seconds, or until you play middle C,",
+ "echoing all input with a 2 second delay.");
+
+ 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); */
+ buffer.timestamp = buffer.timestamp + 2000; /* delay */
+ Pm_Enqueue(out_queue, &buffer);
+ /* play middle C to break out of loop */
+ if (Pm_MessageStatus(buffer.message) == 0x90 &&
+ Pm_MessageData1(buffer.message) == 60) {
+ 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/pd/portmidi/pm_test/midithru.dsp b/pd/portmidi/pm_test/midithru.dsp
new file mode 100644
index 00000000..83f28cfc
--- /dev/null
+++ b/pd/portmidi/pm_test/midithru.dsp
@@ -0,0 +1,102 @@
+# Microsoft Developer Studio Project File - Name="midithru" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Console Application" 0x0103
+
+CFG=midithru - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "midithru.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "midithru.mak" CFG="midithru - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "midithru - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "midithru - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "midithru - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "midithruRelease"
+# PROP Intermediate_Dir "midithruRelease"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD CPP /nologo /MT /W3 /GX /O2 /I "../pm_common" /I "../porttime" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
+# ADD LINK32 ..\Release\portmidi.lib ..\porttime\Release\porttime.lib winmm.lib /nologo /subsystem:console /machine:I386
+
+!ELSEIF "$(CFG)" == "midithru - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "midithruDebug"
+# PROP BASE Intermediate_Dir "midithruDebug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "midithruDebug"
+# PROP Intermediate_Dir "midithruDebug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../pm_common" /I "../porttime" /D "_CONSOLE" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "USE_DLL_FOR_CLEANUP" /YX /FD /GZ /c
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 ..\pm_win\Debug\portmidi.lib ..\porttime\Debug\porttime.lib ..\pm_win\Debug\pm_dll.lib winmm.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+
+!ENDIF
+
+# Begin Target
+
+# Name "midithru - Win32 Release"
+# Name "midithru - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\midithru.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/pd/portmidi/pm_test/midithru.dsw b/pd/portmidi/pm_test/midithru.dsw
new file mode 100644
index 00000000..b244a104
--- /dev/null
+++ b/pd/portmidi/pm_test/midithru.dsw
@@ -0,0 +1,29 @@
+Microsoft Developer Studio Workspace File, Format Version 6.00
+# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
+
+###############################################################################
+
+Project: "midithread"=.\midithru.dsp - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Global:
+
+Package=<5>
+{{{
+}}}
+
+Package=<3>
+{{{
+}}}
+
+###############################################################################
+
diff --git a/pd/portmidi/pm_test/sysex.c b/pd/portmidi/pm_test/sysex.c
new file mode 100644
index 00000000..f49bf962
--- /dev/null
+++ b/pd/portmidi/pm_test/sysex.c
@@ -0,0 +1,319 @@
+/* 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"
+
+#define MIDI_SYSEX 0xf0
+#define MIDI_EOX 0xf7
+
+#define STRING_MAX 80
+
+int latency = 0;
+
+/* read a number from console */
+/**/
+int get_number(char *prompt)
+{
+ char line[STRING_MAX];
+ int n = 0, i;
+ printf(prompt);
+ while (n != 1) {
+ n = scanf("%d", &i);
+ fgets(line, STRING_MAX, stdin);
+
+ }
+ 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];
+ char line[80];
+ long len;
+ int i;
+ int data;
+ PmEvent event;
+ int shift;
+
+ 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.
+ */
+ 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 */
+
+ while (1) {
+ PmError count;
+ long start_time;
+ long error_position;
+ long expected = 0;
+ long actual = 0;
+ printf("Type return to send message, q to quit: ");
+ fgets(line, STRING_MAX, stdin);
+ if (line[0] == '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++) {
+ msg[i + 1] = rand() & 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 */
+ printf("Sending %ld byte sysex message.\n", len + 2);
+ Pm_WriteSysEx(midi_out, 0, msg);
+
+ /* receive the message and compare to msg[] */
+ data = 0;
+ shift = 0;
+ i = 0;
+ start_time = Pt_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);
+ /* 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. There is no timeout, so if we don't receive a sysex
+ message, or at least an EOX, the program will hang here.
+ */
+ if (count == 0) continue;
+
+ /* printf("read %lx ", 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 byte %ld: sent %lx recd %lx\n", error_position, expected, actual);
+ } else if (i != len + 2) {
+ printf("Error: byte %d not received\n", i);
+ } else {
+ printf("Correctly ");
+ }
+ printf("received %d byte sysex message.\n", i);
+ }
+cleanup:
+ Pm_Close(midi_out);
+ Pm_Close(midi_in);
+ 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 */
+ fgets(line, STRING_MAX, 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: ");
+
+ 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 */
+ fgets(line, STRING_MAX, 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;
+ char line[80];
+
+ /* 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");
+ }
+ latency = get_number("Latency in milliseconds (0 to send data immediatedly,\n"
+ " >0 to send timestamped messages): ");
+ while (1) {
+ printf("Type r to receive sysex, s to send,"
+ " l for loopback test, q to quit: ");
+ fgets(line, STRING_MAX, stdin);
+ switch (line[0]) {
+ case 'r':
+ receive_sysex();
+ break;
+ case 's':
+ send_sysex();
+ break;
+ case 'l':
+ loopback_test();
+ case 'q':
+ exit(0);
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+
+
+
+
diff --git a/pd/portmidi/pm_test/sysex.dsp b/pd/portmidi/pm_test/sysex.dsp
new file mode 100644
index 00000000..329d3ef9
--- /dev/null
+++ b/pd/portmidi/pm_test/sysex.dsp
@@ -0,0 +1,102 @@
+# Microsoft Developer Studio Project File - Name="sysex" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Console Application" 0x0103
+
+CFG=sysex - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "sysex.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "sysex.mak" CFG="sysex - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "sysex - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "sysex - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "sysex - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "sysexRelease"
+# PROP Intermediate_Dir "sysexRelease"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD CPP /nologo /MT /W3 /GX /O2 /I "..\pm_common" /I "..\porttime" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib ..\Release\portmidi.lib ..\porttime\Release\porttime.lib ..\pm_win\Release\pm_dll.lib /nologo /subsystem:console /machine:I386
+
+!ELSEIF "$(CFG)" == "sysex - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "sysexDebug"
+# PROP Intermediate_Dir "sysexDebug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "..\pm_common" /I "..\porttime" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib ..\pm_win\Debug\portmidi.lib ..\porttime\Debug\porttime.lib ..\pm_win\Debug\pm_dll.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+
+!ENDIF
+
+# Begin Target
+
+# Name "sysex - Win32 Release"
+# Name "sysex - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\sysex.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/pd/portmidi/pm_test/test.c b/pd/portmidi/pm_test/test.c
new file mode 100644
index 00000000..ade8564d
--- /dev/null
+++ b/pd/portmidi/pm_test/test.c
@@ -0,0 +1,469 @@
+#include "portmidi.h"
+#include "porttime.h"
+#include "stdlib.h"
+#include "stdio.h"
+#include "string.h"
+#include "assert.h"
+
+#define INPUT_BUFFER_SIZE 100
+#define OUTPUT_BUFFER_SIZE 0
+#define DRIVER_INFO NULL
+#define TIME_PROC ((long (*)(void *)) 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 */
+
+long latency = 0;
+
+/* 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(char *prompt)
+{
+ char line[STRING_MAX];
+ int n = 0, i;
+ printf(prompt);
+ while (n != 1) {
+ n = scanf("%d", &i);
+ fgets(line, STRING_MAX, stdin);
+
+ }
+ return i;
+}
+
+
+/*
+ * the somethingStupid parameter can be set to simulate a program crash.
+ * We want PortMidi to close Midi ports automatically in the event of a
+ * crash because Windows does not (and this may cause an OS crash)
+ */
+void main_test_input(unsigned int somethingStupid) {
+ PmStream * midi;
+ PmError status, length;
+ PmEvent buffer[1];
+ int num = 10;
+ int i = get_number("Type input number: ");
+ /* It is recommended to start timer before Midi; otherwise, PortMidi may
+ start the timer with its (default) parameters
+ */
+ TIME_START;
+
+ /* open input device */
+ Pm_OpenInput(&midi,
+ i,
+ DRIVER_INFO,
+ INPUT_BUFFER_SIZE,
+ TIME_PROC,
+ TIME_INFO);
+
+ printf("Midi Input opened. Reading %d Midi messages...\n",num);
+ Pm_SetFilter(midi, PM_FILT_ACTIVE | PM_FILT_CLOCK);
+ /* 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, %2lx %2lx %2lx\n",
+ i,
+ buffer[0].timestamp,
+ Pm_MessageStatus(buffer[0].message),
+ Pm_MessageData1(buffer[0].message),
+ 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() {
+ PmStream * midi;
+ char line[80];
+ long off_time;
+ int chord[] = { 60, 67, 76, 83, 90 };
+ #define chord_size 5
+ PmEvent buffer[chord_size];
+ PmTimestamp timestamp;
+
+ /* determine which output device to use */
+ int i = get_number("Type output number: ");
+
+ /* It is recommended to start timer before PortMidi */
+ TIME_START;
+
+ /* open output device -- since PortMidi avoids opening a timer
+ when latency is zero, we will pass in a NULL timer pointer
+ for that case. If PortMidi tries to access the time_proc,
+ we will crash, so this test will tell us something. */
+ Pm_OpenOutput(&midi,
+ i,
+ DRIVER_INFO,
+ OUTPUT_BUFFER_SIZE,
+ (latency == 0 ? NULL : TIME_PROC),
+ (latency == 0 ? NULL : TIME_INFO),
+ latency);
+ printf("Midi Output opened with %ld ms latency.\n", latency);
+
+ /* output note on/off w/latency offset; hold until user prompts */
+ printf("ready to send program 1 change... (type RETURN):");
+ fgets(line, STRING_MAX, stdin);
+ /* 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 = TIME_PROC(TIME_INFO);
+ buffer[0].message = Pm_Message(0xC0, 0, 0);
+ Pm_Write(midi, buffer, 1);
+
+ printf("ready to note-on... (type RETURN):");
+ fgets(line, STRING_MAX, stdin);
+ buffer[0].timestamp = TIME_PROC(TIME_INFO);
+ buffer[0].message = Pm_Message(0x90, 60, 100);
+ Pm_Write(midi, buffer, 1);
+ printf("ready to note-off... (type RETURN):");
+ fgets(line, STRING_MAX, stdin);
+ buffer[0].timestamp = TIME_PROC(TIME_INFO);
+ buffer[0].message = Pm_Message(0x90, 60, 0);
+ Pm_Write(midi, buffer, 1);
+
+ /* output short note on/off w/latency offset; hold until user prompts */
+ printf("ready to note-on (short form)... (type RETURN):");
+ fgets(line, STRING_MAX, stdin);
+ Pm_WriteShort(midi, TIME_PROC(TIME_INFO),
+ Pm_Message(0x90, 60, 100));
+ printf("ready to note-off (short form)... (type RETURN):");
+ fgets(line, STRING_MAX, stdin);
+ Pm_WriteShort(midi, TIME_PROC(TIME_INFO),
+ Pm_Message(0x90, 60, 0));
+
+ /* output several note on/offs to test timing.
+ Should be 1s between notes */
+ printf("chord will arpeggiate if latency > 0\n");
+ printf("ready to chord-on/chord-off... (type RETURN):");
+ fgets(line, STRING_MAX, stdin);
+ timestamp = TIME_PROC(TIME_INFO);
+ 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 (TIME_PROC(TIME_INFO) < off_time)
+ /* busy 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 RETURN):");
+ fgets(line, STRING_MAX, stdin);
+
+ 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 = 10;
+
+ in = get_number("Type input number: ");
+ out = get_number("Type output number: ");
+
+ /* In is recommended to start timer before PortMidi */
+ TIME_START;
+
+ Pm_OpenOutput(&midiOut,
+ out,
+ DRIVER_INFO,
+ OUTPUT_BUFFER_SIZE,
+ TIME_PROC,
+ TIME_INFO,
+ latency);
+ printf("Midi Output opened with %ld ms latency.\n", latency);
+ /* open input device */
+ Pm_OpenInput(&midi,
+ in,
+ DRIVER_INFO,
+ INPUT_BUFFER_SIZE,
+ TIME_PROC,
+ TIME_INFO);
+ printf("Midi Input opened. Reading %d Midi messages...\n",num);
+ Pm_SetFilter(midi, PM_FILT_ACTIVE | PM_FILT_CLOCK);
+ /* empty the buffer after setting filter, just in case anything
+ got through */
+ while (Pm_Poll(midi)) {
+ Pm_Read(midi, buffer, 1);
+ }
+ i = 0;
+ while (i < num) {
+ status = Pm_Poll(midi);
+ if (status == TRUE) {
+ length = Pm_Read(midi,buffer,1);
+ if (length > 0) {
+ Pm_Write(midiOut, buffer, 1);
+ printf("Got message %d: time %ld, %2lx %2lx %2lx\n",
+ i,
+ buffer[0].timestamp,
+ Pm_MessageStatus(buffer[0].message),
+ Pm_MessageData1(buffer[0].message),
+ Pm_MessageData2(buffer[0].message));
+ i++;
+ } else {
+ assert(0);
+ }
+ }
+ }
+
+ /* since close device should not needed, lets get
+ rid of it just to make sure program exit closes 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;
+ char line[80];
+ PmEvent buffer[16];
+
+ /* determine which output device to use */
+ int i = get_number("Type output number: ");
+
+ latency = 500; /* ignore LATENCY for this test and
+ fix the latency at 500ms */
+
+ /* It is recommended to start timer before PortMidi */
+ TIME_START;
+
+ /* open output device */
+ Pm_OpenOutput(&midi,
+ i,
+ DRIVER_INFO,
+ OUTPUT_BUFFER_SIZE,
+ TIME_PROC,
+ TIME_INFO,
+ latency);
+ printf("Midi Output opened with %ld ms latency.\n", latency);
+
+ /* output note on/off w/latency offset; hold until user prompts */
+ printf("ready to send output... (type RETURN):");
+ fgets(line, STRING_MAX, stdin);
+
+ /* 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 = TIME_PROC(TIME_INFO);
+ 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 = TIME_PROC(TIME_INFO);
+ 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)
+ /* busy wait */;
+ /* 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 RETURN):");
+ fgets(line, STRING_MAX, stdin);
+
+ Pm_Close(midi);
+ Pm_Terminate();
+ printf("done closing and terminating...\n");
+}
+
+
+void show_usage()
+{
+ printf("Usage: test [-h] [-l latency-in-ms]\n");
+ exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+ int i = 0, n = 0;
+ char line[STRING_MAX];
+ int test_input = 0, test_output = 0, test_both = 0, somethingStupid = 0;
+ int stream_test = 0;
+ int latency_valid = FALSE;
+
+ 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", latency);
+ latency_valid = TRUE;
+ } else {
+ show_usage();
+ }
+ }
+
+ while (!latency_valid) {
+ printf("Latency in ms: ");
+ if (scanf("%ld", &latency) == 1) {
+ latency_valid = TRUE;
+ }
+ }
+
+ /* determine what type of test to run */
+ printf("begin portMidi test...\n");
+ printf("%s%s%s%s%s",
+ "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\n");
+ while (n != 1) {
+ n = scanf("%d", &i);
+ fgets(line, STRING_MAX, stdin);
+ 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;
+ default:
+ printf("got %d (invalid input)\n", n);
+ break;
+ }
+ }
+
+ /* list device information */
+ for (i = 0; i < Pm_CountDevices(); i++) {
+ 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) printf(" (input)");
+ if (info->output) printf(" (output)");
+ 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();
+ } else if (test_both) {
+ main_test_both();
+ }
+
+ printf("finished portMidi test...type ENTER to quit...");
+ fgets(line, STRING_MAX, stdin);
+ return 0;
+}
diff --git a/pd/portmidi/pm_test/test.dsp b/pd/portmidi/pm_test/test.dsp
new file mode 100644
index 00000000..66ba3e91
--- /dev/null
+++ b/pd/portmidi/pm_test/test.dsp
@@ -0,0 +1,102 @@
+# Microsoft Developer Studio Project File - Name="test" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Console Application" 0x0103
+
+CFG=test - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "test.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "test.mak" CFG="test - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "test - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "test - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "test - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "testRelease"
+# PROP Intermediate_Dir "testRelease"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD CPP /nologo /MT /W3 /GX /O2 /I "../pm_common" /I "../porttime" /D "WIN32" /D "DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib ..\Release\portmidi.lib ..\porttime\Release\porttime.lib ..\pm_win\Release\pm_dll.lib /nologo /subsystem:console /machine:I386
+
+!ELSEIF "$(CFG)" == "test - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "testDebug"
+# PROP Intermediate_Dir "testDebug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../pm_common" /I "../porttime" /D "_CONSOLE" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "USE_DLL_FOR_CLEANUP" /FR /YX /FD /GZ /c
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib ..\pm_win\Debug\portmidi.lib ..\porttime\Debug\porttime.lib ..\pm_win\Debug\pm_dll.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+
+!ENDIF
+
+# Begin Target
+
+# Name "test - Win32 Release"
+# Name "test - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE=.\test.c
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
diff --git a/pd/portmidi/pm_test/txdata.syx b/pd/portmidi/pm_test/txdata.syx
new file mode 100644
index 00000000..1e06e5a6
--- /dev/null
+++ b/pd/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