/* 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" #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 ((long (*)(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 long 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; 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(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 = input_tempo; } else { printf("Tempo range is 1 to 300, current tempo is %d 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(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); }