1 /* miditime.c -- a test program that sends midi clock and MTC */
2
3 #include "portmidi.h"
4 #include "porttime.h"
5 #include <stdlib.h>
6 #include <stdio.h>
7 #include <string.h>
8 #include <assert.h>
9 #include <ctype.h>
10
11 #ifndef false
12 #define false 0
13 #define true 1
14 #endif
15
16 #define private static
17 typedef int boolean;
18
19 #define MIDI_TIME_CLOCK 0xf8
20 #define MIDI_START 0xfa
21 #define MIDI_CONTINUE 0xfb
22 #define MIDI_STOP 0xfc
23 #define MIDI_Q_FRAME 0xf1
24
25 #define OUTPUT_BUFFER_SIZE 0
26 #define DRIVER_INFO NULL
27 #define TIME_PROC ((int32_t (*)(void *)) Pt_Time)
28 #define TIME_INFO NULL
29 #define LATENCY 0
30 #define TIME_START Pt_Start(1, 0, 0) /* timer started w/millisecond accuracy */
31
32 #define STRING_MAX 80 /* used for console input */
33
34 /* to determine ms per clock:
35 * time per beat in seconds = 60 / tempo
36 * multiply by 1000 to get time per beat in ms: 60000 / tempo
37 * divide by 24 CLOCKs per beat: (60000/24) / tempo
38 * simplify: 2500 / tempo
39 */
40 #define TEMPO_TO_CLOCK 2500.0
41
42 boolean done = false;
43 PmStream *midi;
44 /* shared flags to control callback output generation: */
45 boolean clock_running = false;
46 boolean send_start_stop = false;
47 boolean time_code_running = false;
48 boolean active = false; /* tells callback to do its thing */
49 float tempo = 60.0F;
50 /* protocol for handing off portmidi to callback thread:
51 main owns portmidi
52 main sets active = true: ownership transfers to callback
53 main sets active = false: main requests ownership
54 callback sees active == false, yields ownership back to main
55 main waits 2ms to make sure callback has a chance to yield
56 (stop making PortMidi calls), then assumes it can close
57 PortMidi
58 */
59
60 /* timer_poll -- the timer callback function */
61 /*
62 * All MIDI sends take place here
63 */
timer_poll(PtTimestamp timestamp,void * userData)64 void timer_poll(PtTimestamp timestamp, void *userData)
65 {
66 static int callback_owns_portmidi = false;
67 static PmTimestamp clock_start_time = 0;
68 static double next_clock_time = 0;
69 /* SMPTE time */
70 static int frames = 0;
71 static int seconds = 0;
72 static int minutes = 0;
73 static int hours = 0;
74 static int mtc_count = 0; /* where are we in quarter frame sequence? */
75 static int smpte_start_time = 0;
76 static double next_smpte_time = 0;
77 #define QUARTER_FRAME_PERIOD (1.0 / 120.0) /* 30fps, 1/4 frame */
78
79 if (callback_owns_portmidi && !active) {
80 /* main is requesting (by setting active to false) that we shut down */
81 callback_owns_portmidi = false;
82 return;
83 }
84 if (!active) return; /* main still getting ready or it's closing down */
85 callback_owns_portmidi = true; /* main is ready, we have portmidi */
86 if (send_start_stop) {
87 if (clock_running) {
88 Pm_WriteShort(midi, 0, MIDI_STOP);
89 } else {
90 Pm_WriteShort(midi, 0, MIDI_START);
91 clock_start_time = timestamp;
92 next_clock_time = TEMPO_TO_CLOCK / tempo;
93 }
94 clock_running = !clock_running;
95 send_start_stop = false; /* until main sets it again */
96 /* note that there's a slight race condition here: main could
97 set send_start_stop asynchronously, but we assume user is
98 typing slower than the clock rate */
99 }
100 if (clock_running) {
101 if ((timestamp - clock_start_time) > next_clock_time) {
102 Pm_WriteShort(midi, 0, MIDI_TIME_CLOCK);
103 next_clock_time += TEMPO_TO_CLOCK / tempo;
104 }
105 }
106 if (time_code_running) {
107 int data = 0; // initialization avoids compiler warning
108 if ((timestamp - smpte_start_time) < next_smpte_time)
109 return;
110 switch (mtc_count) {
111 case 0: /* frames low nibble */
112 data = frames;
113 break;
114 case 1: /* frames high nibble */
115 data = frames >> 4;
116 break;
117 case 2: /* frames seconds low nibble */
118 data = seconds;
119 break;
120 case 3: /* frames seconds high nibble */
121 data = seconds >> 4;
122 break;
123 case 4: /* frames minutes low nibble */
124 data = minutes;
125 break;
126 case 5: /* frames minutes high nibble */
127 data = minutes >> 4;
128 break;
129 case 6: /* hours low nibble */
130 data = hours;
131 break;
132 case 7: /* hours high nibble */
133 data = hours >> 4;
134 break;
135 }
136 data &= 0xF; /* take only 4 bits */
137 Pm_WriteShort(midi, 0,
138 Pm_Message(MIDI_Q_FRAME, (mtc_count << 4) + data, 0));
139 mtc_count = (mtc_count + 1) & 7; /* wrap around */
140 if (mtc_count == 0) { /* update time by two frames */
141 frames += 2;
142 if (frames >= 30) {
143 frames = 0;
144 seconds++;
145 if (seconds >= 60) {
146 seconds = 0;
147 minutes++;
148 if (minutes >= 60) {
149 minutes = 0;
150 hours++;
151 /* just let hours wrap if it gets that far */
152 }
153 }
154 }
155 }
156 next_smpte_time += QUARTER_FRAME_PERIOD;
157 } else { /* time_code_running is false */
158 smpte_start_time = timestamp;
159 /* so that when it finally starts, we'll be in sync */
160 }
161 }
162
163
164 /* read a number from console */
165 /**/
get_number(char * prompt)166 int get_number(char *prompt)
167 {
168 char line[STRING_MAX];
169 int n = 0, i;
170 printf(prompt);
171 while (n != 1) {
172 n = scanf("%d", &i);
173 fgets(line, STRING_MAX, stdin);
174
175 }
176 return i;
177 }
178
179 /****************************************************************************
180 * showhelp
181 * Effect: print help text
182 ****************************************************************************/
183
showhelp()184 private void showhelp()
185 {
186 printf("\n");
187 printf("t toggles sending MIDI Time Code (MTC)\n");
188 printf("c toggles sending MIDI CLOCK (initially on)\n");
189 printf("m to set tempo (from 1bpm to 300bpm)\n");
190 printf("q quits\n");
191 printf("\n");
192 }
193
194 /****************************************************************************
195 * doascii
196 * Inputs:
197 * char c: input character
198 * Effect: interpret to control output
199 ****************************************************************************/
200
doascii(char c)201 private void doascii(char c)
202 {
203 if (isupper(c)) c = tolower(c);
204 if (c == 'q') done = true;
205 else if (c == 'c') {
206 printf("%s MIDI CLOCKs\n", (clock_running ? "Stopping" : "Starting"));
207 send_start_stop = true;
208 } else if (c == 't') {
209 printf("%s MIDI Time Code\n",
210 (time_code_running ? "Stopping" : "Starting"));
211 time_code_running = !time_code_running;
212 } else if (c == 'm') {
213 int input_tempo = get_number("Enter new tempo (bpm): ");
214 if (input_tempo >= 1 && input_tempo <= 300) {
215 printf("Changing tempo to %d\n", input_tempo);
216 tempo = (float) input_tempo;
217 } else {
218 printf("Tempo range is 1 to 300, current tempo is %g bpm\n",
219 tempo);
220 }
221 } else {
222 showhelp();
223 }
224 }
225
226
227 /* main - prompt for parameters, start processing */
228 /*
229 * Prompt user to type return.
230 * Then send START and MIDI CLOCK for 60 beats/min.
231 * Commands:
232 * t - toggle sending MIDI Time Code (MTC)
233 * c - toggle sending MIDI CLOCK
234 * m - set tempo
235 * q - quit
236 */
main(int argc,char ** argv)237 int main(int argc, char **argv)
238 {
239 char s[STRING_MAX]; /* console input */
240 int outp;
241 PmError err;
242 int i;
243 if (argc > 1) {
244 printf("Warning: command line arguments ignored\n");
245 }
246 showhelp();
247 /* use porttime callback to send midi */
248 Pt_Start(1, timer_poll, 0);
249 /* list device information */
250 printf("MIDI output devices:\n");
251 for (i = 0; i < Pm_CountDevices(); i++) {
252 const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
253 if (info->output) printf("%d: %s, %s\n", i, info->interf, info->name);
254 }
255 outp = get_number("Type output device number: ");
256 err = Pm_OpenOutput(&midi, outp, DRIVER_INFO, OUTPUT_BUFFER_SIZE,
257 TIME_PROC, TIME_INFO, LATENCY);
258 if (err) {
259 printf(Pm_GetErrorText(err));
260 goto error_exit_no_device;
261 }
262 active = true;
263
264 printf("Type RETURN to start MIDI CLOCK:\n");
265 if (!fgets(s, STRING_MAX, stdin)) goto error_exit;
266 send_start_stop = true; /* send START and then CLOCKs */
267
268 while (!done) {
269 if (fgets(s, STRING_MAX, stdin)) {
270 doascii(s[0]);
271 }
272 }
273
274 error_exit:
275 active = false;
276 Pt_Sleep(2); /* this is to allow callback to complete -- it's
277 real time, so it's either ok and it runs on
278 time, or there's no point to synchronizing
279 with it */
280 /* now we "own" portmidi again */
281 Pm_Close(midi);
282 error_exit_no_device:
283 Pt_Stop();
284 Pm_Terminate();
285 exit(0);
286 }
287
288