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