gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

midiclock.c (8916B)


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