diff options
Diffstat (limited to '3rdparty/portmidi/pm_test/midiclock.c')
-rw-r--r-- | 3rdparty/portmidi/pm_test/midiclock.c | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/3rdparty/portmidi/pm_test/midiclock.c b/3rdparty/portmidi/pm_test/midiclock.c new file mode 100644 index 00000000000..60fcf7a9aad --- /dev/null +++ b/3rdparty/portmidi/pm_test/midiclock.c @@ -0,0 +1,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(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(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); +} + |