mirror of
https://github.com/holub/mame
synced 2025-04-19 15:11:37 +03:00
288 lines
8.7 KiB
C
288 lines
8.7 KiB
C
/* 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);
|
|
}
|
|
|