version 1.11, 2009/10/14 18:22:49 |
version 1.12, 2010/02/13 13:45:29 |
|
|
|
|
#include <sys/types.h> |
#include <sys/types.h> |
#include <sys/stat.h> |
#include <sys/stat.h> |
#include <sys/ioctl.h> |
#include <signal.h> |
#include <sys/midiio.h> |
|
|
|
#include <stdio.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <stdlib.h> |
#include <limits.h> |
#include <limits.h> |
|
|
#include <err.h> |
#include <err.h> |
#include <unistd.h> |
#include <unistd.h> |
#include <string.h> |
#include <string.h> |
|
#include <sndio.h> |
|
|
#define DEVMUSIC "/dev/music" |
|
|
|
struct track { |
struct track { |
u_char *start, *end; |
u_char *start, *end; |
u_long curtime; |
u_long curtime; |
|
|
/* Number of bytes in a MIDI command */ |
/* Number of bytes in a MIDI command */ |
#define MIDI_LENGTH(d) (midi_lengths[((d) >> 4) & 7]) |
#define MIDI_LENGTH(d) (midi_lengths[((d) >> 4) & 7]) |
|
|
|
#define MIDI_IS_STATUS(d) ((d) & 0x80) |
|
#define MIDI_IS_COMMON(d) ((d) < 0xf0) |
|
#define MIDI_SYSEX_START 0xf0 |
|
#define MIDI_SYSEX_STOP 0xf7 |
|
|
void usage(void); |
void usage(void); |
void send_event(seq_event_rec *); |
void send_event(u_char, u_char *, u_int); |
void dometa(u_int, u_char *, u_int); |
void dometa(u_int, u_char *, u_int); |
void midireset(void); |
void midireset(void); |
void send_sysex(u_char *, u_int); |
|
u_long getvar(struct track *); |
u_long getvar(struct track *); |
void playfile(FILE *, char *); |
void playfile(FILE *, char *); |
void playdata(u_char *, u_int, char *); |
void playdata(u_char *, u_int, char *); |
|
|
usage(void) |
usage(void) |
{ |
{ |
printf("usage: " |
printf("usage: " |
"%s [-glmqvx] [-d devno] [-f file] [-t tempo] [file ...]\n", |
"%s [-gmqvx] [-f device] [-t tempo] [file ...]\n", |
__progname); |
__progname); |
exit(1); |
exit(1); |
} |
} |
|
|
int showmeta = 0; |
int showmeta = 0; |
int verbose = 0; |
int verbose = 0; |
#define BASETEMPO 400000 |
u_int tempo = 60 * 1000000 / 100; /* default tempo is 100bpm */ |
u_int tempo = BASETEMPO; /* microsec / quarter note */ |
|
u_int ttempo = 100; |
|
int unit = 0; |
|
int play = 1; |
int play = 1; |
int fd; |
struct mio_hdl *hdl; |
|
struct timespec ts, ts_last; |
|
|
void |
void |
send_event(seq_event_rec *ev) |
send_event(u_char status, u_char *data, u_int len) |
{ |
{ |
/* |
u_int i; |
printf("%02x %02x %02x %02x %02x %02x %02x %02x\n", |
|
ev->arr[0], ev->arr[1], ev->arr[2], ev->arr[3], |
if (verbose > 1) { |
ev->arr[4], ev->arr[5], ev->arr[6], ev->arr[7]); |
printf("MIDI %02x", status); |
*/ |
for (i = 0; i < len; i++) |
if (play) |
printf(" %02x", data[i]); |
write(fd, ev, sizeof *ev); |
printf("\n"); |
|
} |
|
if (play) { |
|
mio_write(hdl, &status, 1); |
|
mio_write(hdl, data, len); |
|
} |
} |
} |
|
|
u_long |
u_long |
|
|
/* General MIDI reset sequence */ |
/* General MIDI reset sequence */ |
static u_char gm_reset[] = { 0x7e, 0x7f, 0x09, 0x01, 0xf7 }; |
static u_char gm_reset[] = { 0x7e, 0x7f, 0x09, 0x01, 0xf7 }; |
|
|
send_sysex(gm_reset, sizeof gm_reset); |
send_event(MIDI_SYSEX_START, gm_reset, sizeof gm_reset); |
} |
} |
|
|
#define SYSEX_CHUNK 6 |
|
void |
void |
send_sysex(u_char *p, u_int l) |
|
{ |
|
seq_event_rec event; |
|
u_int n; |
|
|
|
event.arr[0] = SEQ_SYSEX; |
|
event.arr[1] = unit; |
|
do { |
|
n = SYSEX_CHUNK; |
|
if (l < n) { |
|
memset(&event.arr[2], 0xff, SYSEX_CHUNK); |
|
n = l; |
|
} |
|
memcpy(&event.arr[2], p, n); |
|
send_event(&event); |
|
l -= n; |
|
p += n; |
|
} while (l > 0); |
|
} |
|
|
|
void |
|
playfile(FILE *f, char *name) |
playfile(FILE *f, char *name) |
{ |
{ |
u_char *buf, *newbuf; |
u_char *buf, *newbuf; |
|
|
} |
} |
|
|
void |
void |
|
sigalrm(int i) |
|
{ |
|
} |
|
|
|
void |
playdata(u_char *buf, u_int tot, char *name) |
playdata(u_char *buf, u_int tot, char *name) |
{ |
{ |
|
long long delta_nsec = 0; |
|
u_int delta_ticks; |
int format, ntrks, divfmt, ticks, t, besttrk = 0; |
int format, ntrks, divfmt, ticks, t, besttrk = 0; |
u_int len, mlen, status, chan; |
u_int len, mlen; |
u_char *p, *end, byte, meta, *msg; |
u_char *p, *end, byte, meta; |
struct track *tracks; |
struct track *tracks; |
u_long bestcur, now; |
u_long bestcur, now; |
struct track *tp; |
struct track *tp; |
seq_event_rec event; |
|
|
|
end = buf + tot; |
end = buf + tot; |
if (verbose) |
if (verbose) |
|
|
* curtime. Execute the event, update the curtime and repeat. |
* curtime. Execute the event, update the curtime and repeat. |
*/ |
*/ |
|
|
/* |
|
* The ticks variable is the number of ticks that make up a quarter |
|
* note and is used as a reference value for the delays between |
|
* the MIDI events. |
|
* The sequencer has two "knobs": the TIMEBASE and the TEMPO. |
|
* The delay specified in TMR_WAIT_REL is specified in |
|
* sequencer time units. The length of a unit is |
|
* 60*1000000 / (TIMEBASE * TEMPO). |
|
* Set it to 1ms/unit (adjusted by user tempo changes). |
|
*/ |
|
t = 500 * ttempo / 100; |
|
if (ioctl(fd, SEQUENCER_TMR_TIMEBASE, &t) < 0) |
|
err(1, "SEQUENCER_TMR_TIMEBASE"); |
|
t = 120; |
|
if (ioctl(fd, SEQUENCER_TMR_TEMPO, &t) < 0) |
|
err(1, "SEQUENCER_TMR_TEMPO"); |
|
if (ioctl(fd, SEQUENCER_TMR_START, 0) < 0) |
|
err(1, "SEQUENCER_TMR_START"); |
|
now = 0; |
now = 0; |
|
delta_nsec = 0; |
|
if (clock_gettime(CLOCK_MONOTONIC, &ts_last) < 0) |
|
err(1, "clock_gettime"); |
for (;;) { |
for (;;) { |
/* Locate lowest curtime */ |
/* Locate lowest curtime */ |
bestcur = ~0; |
bestcur = ~0; |
|
|
printf("DELAY %4ld TRACK %2d ", bestcur-now, besttrk); |
printf("DELAY %4ld TRACK %2d ", bestcur-now, besttrk); |
fflush(stdout); |
fflush(stdout); |
} |
} |
if (now < bestcur) { |
while (now < bestcur) { |
union { |
pause(); |
u_int32_t i; |
if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) |
u_int8_t b[4]; |
err(1, "clock_gettime"); |
} u; |
delta_nsec += 1000000000L * (ts.tv_sec - ts_last.tv_sec); |
u_int32_t delta = bestcur - now; |
delta_nsec += ts.tv_nsec - ts_last.tv_nsec; |
delta = (int)((double)delta * tempo / (1000.0*ticks)); |
ts_last = ts; |
u.i = delta; |
if (delta_nsec <= 0) |
if (delta != 0) { |
continue; |
event.arr[0] = SEQ_TIMING; |
delta_ticks = delta_nsec * ticks / (1000LL * tempo); |
event.arr[1] = TMR_WAIT_REL; |
delta_nsec -= 1000LL * delta_ticks * tempo / ticks; |
event.arr[4] = u.b[0]; |
now += delta_ticks; |
event.arr[5] = u.b[1]; |
|
event.arr[6] = u.b[2]; |
|
event.arr[7] = u.b[3]; |
|
send_event(&event); |
|
} |
|
} |
} |
now = bestcur; |
|
tp = &tracks[besttrk]; |
tp = &tracks[besttrk]; |
byte = *tp->start++; |
byte = *tp->start++; |
if (byte == MIDI_META) { |
if (byte == MIDI_META) { |
|
|
tp->status = byte; |
tp->status = byte; |
else |
else |
tp->start--; |
tp->start--; |
mlen = MIDI_LENGTH(tp->status); |
if (MIDI_IS_COMMON(tp->status)) { |
msg = tp->start; |
mlen = MIDI_LENGTH(tp->status); |
if (verbose > 1) { |
send_event(tp->status, tp->start, mlen); |
if (mlen == 1) |
} else if (tp->status == MIDI_SYSEX_START) { |
printf("MIDI %02x (%d) %02x\n", |
|
tp->status, mlen, msg[0]); |
|
else |
|
printf("MIDI %02x (%d) %02x %02x\n", |
|
tp->status, mlen, msg[0], msg[1]); |
|
} |
|
status = MIDI_GET_STATUS(tp->status); |
|
chan = MIDI_GET_CHAN(tp->status); |
|
switch (status) { |
|
case MIDI_NOTEOFF: |
|
case MIDI_NOTEON: |
|
case MIDI_KEY_PRESSURE: |
|
SEQ_MK_CHN_VOICE(&event, unit, status, chan, |
|
msg[0], msg[1]); |
|
send_event(&event); |
|
break; |
|
case MIDI_CTL_CHANGE: |
|
SEQ_MK_CHN_COMMON(&event, unit, status, chan, |
|
msg[0], 0, msg[1]); |
|
send_event(&event); |
|
break; |
|
case MIDI_PGM_CHANGE: |
|
case MIDI_CHN_PRESSURE: |
|
SEQ_MK_CHN_COMMON(&event, unit, status, chan, |
|
msg[0], 0, 0); |
|
send_event(&event); |
|
break; |
|
case MIDI_PITCH_BEND: |
|
SEQ_MK_CHN_COMMON(&event, unit, status, chan, |
|
0, 0, |
|
(msg[0] & 0x7f) | |
|
((msg[1] & 0x7f) << 7)); |
|
send_event(&event); |
|
break; |
|
case MIDI_SYSTEM_PREFIX: |
|
mlen = getvar(tp); |
mlen = getvar(tp); |
if (tp->status == MIDI_SYSEX_START) |
send_event(MIDI_SYSEX_START, tp->start, mlen); |
send_sysex(tp->start, mlen); |
} else if (tp->status == MIDI_SYSEX_STOP) { |
else |
mlen = getvar(tp); |
/* Sorry, can't do this yet */; |
/* Sorry, can't do this yet */; |
break; |
} else { |
default: |
|
if (verbose) |
if (verbose) |
printf("MIDI event 0x%02x ignored\n", |
printf("MIDI event 0x%02x ignored\n", |
tp->status); |
tp->status); |
|
|
else |
else |
tp->curtime += getvar(tp); |
tp->curtime += getvar(tp); |
} |
} |
if (ioctl(fd, SEQUENCER_SYNC, 0) < 0) |
|
err(1, "SEQUENCER_SYNC"); |
|
|
|
ret: |
ret: |
free(tracks); |
free(tracks); |
} |
} |
|
|
main(int argc, char **argv) |
main(int argc, char **argv) |
{ |
{ |
int ch; |
int ch; |
int listdevs = 0; |
|
int example = 0; |
int example = 0; |
int gmreset = 0; |
int gmreset = 0; |
int nmidi; |
char *file = NULL; |
char *file = DEVMUSIC; |
|
struct synth_info info; |
|
FILE *f; |
FILE *f; |
const char *errstr; |
const char *errstr; |
|
struct sigaction sa; |
|
struct itimerval it; |
|
|
while ((ch = getopt(argc, argv, "?d:f:glmqt:vx")) != -1) { |
while ((ch = getopt(argc, argv, "?d:f:glmqt:vx")) != -1) { |
switch (ch) { |
switch (ch) { |
case 'd': |
|
unit = strtonum(optarg, 0, INT_MAX, &errstr); |
|
if (errstr) |
|
errx(1, "unit is %s: %s", errstr, optarg); |
|
break; |
|
case 'f': |
case 'f': |
file = optarg; |
file = optarg; |
break; |
break; |
case 'g': |
case 'g': |
gmreset++; |
gmreset++; |
break; |
break; |
case 'l': |
|
listdevs++; |
|
break; |
|
case 'm': |
case 'm': |
showmeta++; |
showmeta++; |
break; |
break; |
|
|
play = 0; |
play = 0; |
break; |
break; |
case 't': |
case 't': |
ttempo = strtonum(optarg, 0, INT_MAX, &errstr); |
tempo = 60 * 1000000 / |
|
strtonum(optarg, 40, 240, &errstr); |
if (errstr) |
if (errstr) |
errx(1, "tempo is %s: %s", errstr, optarg); |
errx(1, "tempo is %s: %s", errstr, optarg); |
break; |
break; |
|
|
argc -= optind; |
argc -= optind; |
argv += optind; |
argv += optind; |
|
|
fd = open(file, O_WRONLY); |
hdl = mio_open(file, MIO_OUT, 0); |
if (fd < 0) |
if (hdl == NULL) { |
err(1, "%s", file); |
fprintf(stderr, "failed to open MIDI output\n"); |
if (ioctl(fd, SEQUENCER_NRMIDIS, &nmidi) < 0) |
exit(1); |
err(1, "ioctl(SEQUENCER_NRMIDIS) failed, "); |
|
if (nmidi == 0) |
|
errx(1, "Sorry, no MIDI devices available"); |
|
if (listdevs) { |
|
for (info.device = 0; info.device < nmidi; info.device++) { |
|
if (ioctl(fd, SEQUENCER_INFO, &info) < 0) |
|
err(1, "ioctl(SEQUENCER_INFO) failed, "); |
|
printf("%d: %s\n", info.device, info.name); |
|
} |
|
exit(0); |
|
} |
} |
if (gmreset) |
if (gmreset) |
midireset(); |
midireset(); |
|
|
|
sa.sa_flags = SA_RESTART; |
|
sa.sa_handler = sigalrm; |
|
sigfillset(&sa.sa_mask); |
|
if (sigaction(SIGALRM, &sa, NULL) < 0) |
|
err(1, "sigaction"); |
|
it.it_interval.tv_sec = it.it_value.tv_sec = 0; |
|
it.it_interval.tv_usec = it.it_value.tv_usec = 1000; |
|
if (setitimer(ITIMER_REAL, &it, NULL) < 0) |
|
err(1, "setitimer"); |
|
|
if (example) |
if (example) |
playdata(sample, sizeof sample, "<Gubben Noa>"); |
playdata(sample, sizeof sample, "<Gubben Noa>"); |
else if (argc == 0) |
else if (argc == 0) |