aboutsummaryrefslogtreecommitdiff
path: root/portmidi/pm_test/midiclock.c
diff options
context:
space:
mode:
Diffstat (limited to 'portmidi/pm_test/midiclock.c')
-rw-r--r--portmidi/pm_test/midiclock.c282
1 files changed, 282 insertions, 0 deletions
diff --git a/portmidi/pm_test/midiclock.c b/portmidi/pm_test/midiclock.c
new file mode 100644
index 0000000..f0a6897
--- /dev/null
+++ b/portmidi/pm_test/midiclock.c
@@ -0,0 +1,282 @@
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
17typedef 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 ((PmTimeProcPtr) 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
42boolean done = false;
43PmStream *midi;
44/* shared flags to control callback output generation: */
45boolean clock_running = false;
46boolean send_start_stop = false;
47boolean time_code_running = false;
48boolean active = false; /* tells callback to do its thing */
49float 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 */
64void 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/**/
166int get_number(const char *prompt)
167{
168 int n = 0, i;
169 fputs(prompt, stdout);
170 while (n != 1) {
171 n = scanf("%d", &i);
172 while (getchar() != '\n') ;
173 }
174 return i;
175}
176
177/****************************************************************************
178* showhelp
179* Effect: print help text
180****************************************************************************/
181
182private void showhelp()
183{
184 printf("\n");
185 printf("t toggles sending MIDI Time Code (MTC)\n");
186 printf("c toggles sending MIDI CLOCK (initially on)\n");
187 printf("m to set tempo (from 1bpm to 300bpm)\n");
188 printf("q quits\n");
189 printf("\n");
190}
191
192/****************************************************************************
193* doascii
194* Inputs:
195* char c: input character
196* Effect: interpret to control output
197****************************************************************************/
198
199private void doascii(char c)
200{
201 if (isupper(c)) c = tolower(c);
202 if (c == 'q') done = true;
203 else if (c == 'c') {
204 printf("%s MIDI CLOCKs\n", (clock_running ? "Stopping" : "Starting"));
205 send_start_stop = true;
206 } else if (c == 't') {
207 printf("%s MIDI Time Code\n",
208 (time_code_running ? "Stopping" : "Starting"));
209 time_code_running = !time_code_running;
210 } else if (c == 'm') {
211 int input_tempo = get_number("Enter new tempo (bpm): ");
212 if (input_tempo >= 1 && input_tempo <= 300) {
213 printf("Changing tempo to %d\n", input_tempo);
214 tempo = (float) input_tempo;
215 } else {
216 printf("Tempo range is 1 to 300, current tempo is %g bpm\n",
217 tempo);
218 }
219 } else {
220 showhelp();
221 }
222}
223
224
225/* main - prompt for parameters, start processing */
226/*
227 * Prompt user to type return.
228 * Then send START and MIDI CLOCK for 60 beats/min.
229 * Commands:
230 * t - toggle sending MIDI Time Code (MTC)
231 * c - toggle sending MIDI CLOCK
232 * m - set tempo
233 * q - quit
234 */
235int main(int argc, char **argv)
236{
237 int outp;
238 PmError err;
239 int i;
240 if (argc > 1) {
241 printf("Warning: command line arguments ignored\n");
242 }
243 showhelp();
244 /* use porttime callback to send midi */
245 Pt_Start(1, timer_poll, 0);
246 /* list device information */
247 printf("MIDI output devices:\n");
248 for (i = 0; i < Pm_CountDevices(); i++) {
249 const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
250 if (info->output) printf("%d: %s, %s\n", i, info->interf, info->name);
251 }
252 outp = get_number("Type output device number: ");
253 err = Pm_OpenOutput(&midi, outp, DRIVER_INFO, OUTPUT_BUFFER_SIZE,
254 TIME_PROC, TIME_INFO, LATENCY);
255 if (err) {
256 puts(Pm_GetErrorText(err));
257 goto error_exit_no_device;
258 }
259 active = true;
260
261 printf("Type ENTER to start MIDI CLOCK:\n");
262 while (getchar() != '\n') ;
263 send_start_stop = true; /* send START and then CLOCKs */
264
265 while (!done) {
266 doascii(getchar());
267 while (getchar() != '\n') ;
268 }
269
270 active = false;
271 Pt_Sleep(2); /* this is to allow callback to complete -- it's
272 real time, so it's either ok and it runs on
273 time, or there's no point to synchronizing
274 with it */
275 /* now we "own" portmidi again */
276 Pm_Close(midi);
277 error_exit_no_device:
278 Pt_Stop();
279 Pm_Terminate();
280 exit(0);
281}
282