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