1 /* midithread.c -- example program showing how to do midi processing
2                    in a preemptive thread
3 
4   Notes: if you handle midi I/O from your main program, there will be
5   some delay before handling midi messages whenever the program is
6   doing something like file I/O, graphical interface updates, etc.
7 
8   To handle midi with minimal delay, you should do all midi processing
9   in a separate, high priority thread. A convenient way to get a high
10   priority thread in windows is to use the timer callback provided by
11   the PortTime library. That is what we show here.
12 
13   If the high priority thread writes to a file, prints to the console,
14   or does just about anything other than midi processing, this may
15   create delays, so all this processing should be off-loaded to the
16   "main" process or thread. Communication between threads can be tricky.
17   If one thread is writing at the same time the other is reading, very
18   tricky race conditions can arise, causing programs to behave
19   incorrectly, but only under certain timing conditions -- a terrible
20   thing to debug. Advanced programmers know this as a synchronization
21   problem. See any operating systems textbook for the complete story.
22 
23   To avoid synchronization problems, a simple, reliable approach is
24   to communicate via messages. PortMidi offers a message queue as a
25   datatype, and operations to insert and remove messages. Use two
26   queues as follows: midi_to_main transfers messages from the midi
27   thread to the main thread, and main_to_midi transfers messages from
28   the main thread to the midi thread. Queues are safe for use between
29   threads as long as ONE thread writes and ONE thread reads. You must
30   NEVER allow two threads to write to the same queue.
31 
32   This program transposes incoming midi data by an amount controlled
33   by the main program. To change the transposition, type an integer
34   followed by return. The main program sends this via a message queue
35   to the midi thread. To quit, type 'q' followed by return.
36 
37   The midi thread can also send a pitch to the main program on request.
38   Type 'm' followed by return to wait for the next midi message and
39   print the pitch.
40 
41   This program illustrates:
42     Midi processing in a high-priority thread.
43     Communication with a main process via message queues.
44 
45  */
46 
47 #include "stdio.h"
48 #include "stdlib.h"
49 #include "string.h"
50 #include "assert.h"
51 #include "portmidi.h"
52 #include "pmutil.h"
53 #include "porttime.h"
54 
55 /* if INPUT_BUFFER_SIZE is 0, PortMidi uses a default value */
56 #define INPUT_BUFFER_SIZE 0
57 
58 #define OUTPUT_BUFFER_SIZE 100
59 #define DRIVER_INFO NULL
60 #define TIME_PROC NULL
61 #define TIME_INFO NULL
62 /* use zero latency because we want output to be immediate */
63 #define LATENCY 0
64 
65 #define STRING_MAX 80
66 
67 /**********************************/
68 /* DATA USED ONLY BY process_midi */
69 /* (except during initialization) */
70 /**********************************/
71 
72 int active = FALSE;
73 int monitor = FALSE;
74 int midi_thru = TRUE;
75 
76 int transpose;
77 PmStream *midi_in;
78 PmStream *midi_out;
79 
80 /****************************/
81 /* END OF process_midi DATA */
82 /****************************/
83 
84 /* shared queues */
85 PmQueue *midi_to_main;
86 PmQueue *main_to_midi;
87 
88 #define QUIT_MSG 1000
89 #define MONITOR_MSG 1001
90 #define THRU_MSG 1002
91 
92 /* timer interrupt for processing midi data */
process_midi(PtTimestamp timestamp,void * userData)93 void process_midi(PtTimestamp timestamp, void *userData)
94 {
95     PmError result;
96     PmEvent buffer; /* just one message at a time */
97     int32_t msg;
98 
99     /* do nothing until initialization completes */
100     if (!active)
101         return;
102 
103     /* check for messages */
104     do {
105         result = Pm_Dequeue(main_to_midi, &msg);
106         if (result) {
107             if (msg >= -127 && msg <= 127)
108                 transpose = msg;
109             else if (msg == QUIT_MSG) {
110                 /* acknowledge receipt of quit message */
111                 Pm_Enqueue(midi_to_main, &msg);
112                 active = FALSE;
113                 return;
114             } else if (msg == MONITOR_MSG) {
115                 /* main has requested a pitch. monitor is a flag that
116                  * records the request:
117                  */
118                 monitor = TRUE;
119             } else if (msg == THRU_MSG) {
120                 /* toggle Thru on or off */
121                 midi_thru = !midi_thru;
122             }
123         }
124     } while (result);
125 
126     /* see if there is any midi input to process */
127     do {
128 		result = Pm_Poll(midi_in);
129         if (result) {
130             int status, data1, data2;
131             if (Pm_Read(midi_in, &buffer, 1) == pmBufferOverflow)
132                 continue;
133             if (midi_thru)
134                 Pm_Write(midi_out, &buffer, 1);
135             /* unless there was overflow, we should have a message now */
136             status = Pm_MessageStatus(buffer.message);
137             data1 = Pm_MessageData1(buffer.message);
138             data2 = Pm_MessageData2(buffer.message);
139             if ((status & 0xF0) == 0x90 ||
140                 (status & 0xF0) == 0x80) {
141 
142                 /* this is a note-on or note-off, so transpose and send */
143                 data1 += transpose;
144 
145                 /* keep within midi pitch range, keep proper pitch class */
146                 while (data1 > 127)
147                     data1 -= 12;
148                 while (data1 < 0)
149                     data1 += 12;
150 
151                 /* send the message */
152                 buffer.message = Pm_Message(status, data1, data2);
153                 Pm_Write(midi_out, &buffer, 1);
154 
155                 /* if monitor is set, send the pitch to the main thread */
156                 if (monitor) {
157                     Pm_Enqueue(midi_to_main, &data1);
158                     monitor = FALSE; /* only send one pitch per request */
159                 }
160             }
161         }
162     } while (result);
163 }
164 
exit_with_message(char * msg)165 void exit_with_message(char *msg)
166 {
167     char line[STRING_MAX];
168     printf("%s\n", msg);
169     fgets(line, STRING_MAX, stdin);
170     exit(1);
171 }
172 
main()173 int main()
174 {
175     int id;
176     int32_t n;
177     const PmDeviceInfo *info;
178     char line[STRING_MAX];
179     int spin;
180     int done = FALSE;
181 
182     /* determine what type of test to run */
183     printf("begin PortMidi multithread test...\n");
184 
185     /* note that it is safe to call PortMidi from the main thread for
186        initialization and opening devices. You should not make any
187        calls to PortMidi from this thread once the midi thread begins.
188        to make PortMidi calls.
189      */
190 
191     /* make the message queues */
192     /* messages can be of any size and any type, but all messages in
193      * a given queue must have the same size. We'll just use int32_t's
194      * for our messages in this simple example
195      */
196     midi_to_main = Pm_QueueCreate(32, sizeof(int32_t));
197     assert(midi_to_main != NULL);
198     main_to_midi = Pm_QueueCreate(32, sizeof(int32_t));
199     assert(main_to_midi != NULL);
200 
201     /* a little test of enqueue and dequeue operations. Ordinarily,
202      * you would call Pm_Enqueue from one thread and Pm_Dequeue from
203      * the other. Since the midi thread is not running, this is safe.
204      */
205     n = 1234567890;
206     Pm_Enqueue(midi_to_main, &n);
207     n = 987654321;
208     Pm_Enqueue(midi_to_main, &n);
209 	Pm_Dequeue(midi_to_main, &n);
210 	if (n != 1234567890) {
211         exit_with_message("Pm_Dequeue produced unexpected result.");
212     }
213     Pm_Dequeue(midi_to_main, &n);
214 	if(n != 987654321) {
215         exit_with_message("Pm_Dequeue produced unexpected result.");
216     }
217 
218     /* always start the timer before you start midi */
219     Pt_Start(1, &process_midi, 0); /* start a timer with millisecond accuracy */
220     /* the timer will call our function, process_midi() every millisecond */
221 
222 	Pm_Initialize();
223 
224     id = Pm_GetDefaultOutputDeviceID();
225     info = Pm_GetDeviceInfo(id);
226     if (info == NULL) {
227         printf("Could not open default output device (%d).", id);
228         exit_with_message("");
229     }
230     printf("Opening output device %s %s\n", info->interf, info->name);
231 
232     /* use zero latency because we want output to be immediate */
233     Pm_OpenOutput(&midi_out,
234                   id,
235                   DRIVER_INFO,
236                   OUTPUT_BUFFER_SIZE,
237                   TIME_PROC,
238                   TIME_INFO,
239                   LATENCY);
240 
241     id = Pm_GetDefaultInputDeviceID();
242     info = Pm_GetDeviceInfo(id);
243     if (info == NULL) {
244         printf("Could not open default input device (%d).", id);
245         exit_with_message("");
246     }
247     printf("Opening input device %s %s\n", info->interf, info->name);
248     Pm_OpenInput(&midi_in,
249                  id,
250                  DRIVER_INFO,
251                  INPUT_BUFFER_SIZE,
252                  TIME_PROC,
253                  TIME_INFO);
254 
255     active = TRUE; /* enable processing in the midi thread -- yes, this
256                       is a shared variable without synchronization, but
257                       this simple assignment is safe */
258 
259     printf("Enter midi input; it will be transformed as specified by...\n");
260     printf("%s\n%s\n%s\n",
261            "Type 'q' to quit, 'm' to monitor next pitch, t to toggle thru or",
262            "type a number to specify transposition.",
263 		   "Must terminate with [ENTER]");
264 
265     while (!done) {
266         int32_t msg;
267         int input;
268         int len;
269         fgets(line, STRING_MAX, stdin);
270         /* remove the newline: */
271         len = strlen(line);
272         if (len > 0) line[len - 1] = 0; /* overwrite the newline char */
273         if (strcmp(line, "q") == 0) {
274             msg = QUIT_MSG;
275             Pm_Enqueue(main_to_midi, &msg);
276             /* wait for acknowlegement */
277             do {
278                 spin = Pm_Dequeue(midi_to_main, &msg);
279             } while (spin == 0); /* spin */ ;
280             done = TRUE; /* leave the command loop and wrap up */
281         } else if (strcmp(line, "m") == 0) {
282             msg = MONITOR_MSG;
283             Pm_Enqueue(main_to_midi, &msg);
284             printf("Waiting for note...\n");
285             do {
286                 spin = Pm_Dequeue(midi_to_main, &msg);
287             } while (spin == 0); /* spin */ ;
288             // convert int32_t to long for safe printing
289             printf("... pitch is %ld\n", (long) msg);
290         } else if (strcmp(line, "t") == 0) {
291             /* reading midi_thru asynchronously could give incorrect results,
292                e.g. if you type "t" twice before the midi thread responds to
293                the first one, but we'll do it this way anyway. Perhaps a more
294                correct way would be to wait for an acknowledgement message
295                containing the new state. */
296             printf("Setting THRU %s\n", (midi_thru ? "off" : "on"));
297             msg = THRU_MSG;
298             Pm_Enqueue(main_to_midi, &msg);
299         } else if (sscanf(line, "%d", &input) == 1) {
300             if (input >= -127 && input <= 127) {
301                 /* send transposition value, make sur */
302                 printf("Transposing by %d\n", input);
303                 msg = (int32_t) input;
304                 Pm_Enqueue(main_to_midi, &msg);
305             } else {
306                 printf("Transposition must be within -127...127\n");
307             }
308         } else {
309             printf("%s\n%s\n",
310               "Type 'q[ENTER]' to quit, 'm[ENTER]' to monitor next pitch, or",
311               "enter a number to specify transposition.");
312         }
313     }
314 
315     /* at this point, midi thread is inactive and we need to shut down
316      * the midi input and output
317      */
318     Pt_Stop(); /* stop the timer */
319     Pm_QueueDestroy(midi_to_main);
320     Pm_QueueDestroy(main_to_midi);
321 
322     /* Belinda! if close fails here, some memory is deleted, right??? */
323     Pm_Close(midi_in);
324     Pm_Close(midi_out);
325 
326     printf("finished portMidi multithread test...enter any character to quit [RETURN]...");
327     fgets(line, STRING_MAX, stdin);
328     return 0;
329 }
330