From 90d5b8b4a064420d74678654e94ea4755b377f21 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Thu, 15 Dec 2005 00:57:02 +0000 Subject: checking in missing files on behalf of Miller (cleared it with him first). The files are from portmidi17nov04.zip svn path=/trunk/; revision=4216 --- pd/portmidi/pm_test/latency.c | 278 ++++++++++++++++++++++ pd/portmidi/pm_test/latency.dsp | 102 ++++++++ pd/portmidi/pm_test/midithread.c | 327 ++++++++++++++++++++++++++ pd/portmidi/pm_test/midithread.dsp | 102 ++++++++ pd/portmidi/pm_test/midithru.c | 364 ++++++++++++++++++++++++++++ pd/portmidi/pm_test/midithru.dsp | 102 ++++++++ pd/portmidi/pm_test/midithru.dsw | 29 +++ pd/portmidi/pm_test/sysex.c | 319 +++++++++++++++++++++++++ pd/portmidi/pm_test/sysex.dsp | 102 ++++++++ pd/portmidi/pm_test/test.c | 469 +++++++++++++++++++++++++++++++++++++ pd/portmidi/pm_test/test.dsp | 102 ++++++++ pd/portmidi/pm_test/txdata.syx | 257 ++++++++++++++++++++ 12 files changed, 2553 insertions(+) create mode 100644 pd/portmidi/pm_test/latency.c create mode 100644 pd/portmidi/pm_test/latency.dsp create mode 100644 pd/portmidi/pm_test/midithread.c create mode 100644 pd/portmidi/pm_test/midithread.dsp create mode 100644 pd/portmidi/pm_test/midithru.c create mode 100644 pd/portmidi/pm_test/midithru.dsp create mode 100644 pd/portmidi/pm_test/midithru.dsw create mode 100644 pd/portmidi/pm_test/sysex.c create mode 100644 pd/portmidi/pm_test/sysex.dsp create mode 100644 pd/portmidi/pm_test/test.c create mode 100644 pd/portmidi/pm_test/test.dsp create mode 100644 pd/portmidi/pm_test/txdata.syx (limited to 'pd/portmidi/pm_test') 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 -- cgit v1.2.1