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