diff options
Diffstat (limited to 'portmidi/pm_test/midiclock.c')
| -rw-r--r-- | portmidi/pm_test/midiclock.c | 282 |
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 | ||
| 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 ((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 | |||
| 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 | */ | ||
| 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 | /**/ | ||
| 166 | int 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 | |||
| 182 | private 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 | |||
| 199 | private 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 | */ | ||
| 235 | int 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 | |||
