File: midiclock.c

package info (click to toggle)
portmidi 1%3A217-6
  • links: PTS, VCS
  • area: main
  • in suites: buster, stretch
  • size: 2,988 kB
  • ctags: 2,395
  • sloc: ansic: 7,371; java: 865; lisp: 363; python: 299; cpp: 63; xml: 60; makefile: 45; awk: 35; sh: 18
file content (287 lines) | stat: -rw-r--r-- 8,928 bytes parent folder | download | duplicates (2)
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);
}