mididump(1): Initial revision

A new utility which dumps MIDI 1.0 events in real-time.

Sponsored by:	The FreeBSD Foundation
MFC after:	1 week
Reviewed by:	dev_submerge.ch
Differential Revision:	https://reviews.freebsd.org/D46418
This commit is contained in:
Christos Margiolis 2024-10-18 10:42:12 +02:00
parent 9ad2891558
commit f57efe95cc
4 changed files with 409 additions and 0 deletions

View File

@ -88,6 +88,7 @@ SUBDIR= alias \
mandoc \
mdo \
mesg \
mididump \
ministat \
mkdep \
mkfifo \

View File

@ -0,0 +1,8 @@
.include <src.opts.mk>
PROG= mididump
SRCS= ${PROG}.c
MAN= ${PROG}.1
LDFLAGS+= -lm
.include <bsd.prog.mk>

View File

@ -0,0 +1,80 @@
.\"-
.\" SPDX-License-Identifier: BSD-2-Clause
.\"
.\" Copyright (c) 2024 The FreeBSD Foundation
.\"
.\" Portions of this software were developed by Christos Margiolis
.\" <christos@FreeBSD.org> under sponsorship from the FreeBSD Foundation.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd September 14, 2024
.Dt MIDIDUMP 1
.Os
.Sh NAME
.Nm mididump
.Nd dump MIDI events
.Sh SYNOPSIS
.Nm
.Op Fl t
.Ar device
.Sh DESCRIPTION
The
.Nm
utility is used to dump MIDI 1.0 events in real-time.
.Pp
The options are as follows:
.Bl -tag -width "-t"
.It Fl t
Print "Timing Clock" events.
These events are not printed by default, as they tend to clutter output.
.El
.Pp
The
.Ar device
argument corresponds to the MIDI device (e.g.
.Pa /dev/umidi0.0 ) .
.Sh SEE ALSO
.Rs
.%T Summary of MIDI 1.0 Messages
.%U https://midi.org/summary-of-midi-1-0-messages
.Re
.Rs
.%T Expanded MIDI 1.0 Messages List (Status Bytes)
.%U https://midi.org/expanded-midi-1-0-messages-list
.Re
.Rs
.%T Standard MIDI-File Format Spec. 1.1, updated
.%U https://www.music.mcgill.ca/~ich/classes/mumt306/StandardMIDIfileformat.html
.Re
.Rs
.%T MIDI CC List for Continuous Controllers
.%U https://anotherproducer.com/online-tools-for-musicians/midi-cc-list/
.Re
.Sh AUTHORS
The
.Nm
utility was implemented by
.An Christos Margiolis Aq Mt christos@FreeBSD.org
under sponsorship from the
.Fx
Foundation.

320
usr.bin/mididump/mididump.c Normal file
View File

@ -0,0 +1,320 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2024 The FreeBSD Foundation
*
* This software was developed by Christos Margiolis <christos@FreeBSD.org>
* under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/soundcard.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
#define NOTE2OCTAVE(n) (n / 12 - 1)
#define NOTE2FREQ(n) (440 * pow(2.0f, ((float)n - 69) / 12))
#define CHAN_MASK 0x0f
struct note {
const char *name;
const char *alt;
} static notes[] = {
{ "C", NULL },
{ "C#", "Db" },
{ "D", NULL },
{ "D#", "Eb" },
{ "E", NULL },
{ "F", NULL },
{ "F#", "Gb" },
{ "G", NULL },
{ "G#", "Ab" },
{ "A", NULL },
{ "A#", "Bb" },
{ "B", NULL },
};
/* Hardcoded values are not defined in sys/soundcard.h. */
static const char *ctls[] = {
[CTL_BANK_SELECT] = "Bank Select",
[CTL_MODWHEEL] = "Modulation Wheel",
[CTL_BREATH] = "Breath Controller",
[0x03] = "Undefined",
[CTL_FOOT] = "Foot Pedal",
[CTL_PORTAMENTO_TIME] = "Portamento Time",
[CTL_DATA_ENTRY] = "Data Entry",
[CTL_MAIN_VOLUME] = "Volume",
[CTL_BALANCE] = "Balance",
[0x09] = "Undefined",
[CTL_PAN] = "Pan",
[CTL_EXPRESSION] = "Expression",
[0x0c] = "Effect Controller 1",
[0x0d] = "Effect Controller 2",
[0x0e] = "Undefined",
[0x0f] = "Undefined",
[CTL_GENERAL_PURPOSE1] = "General Purpose 1",
[CTL_GENERAL_PURPOSE2] = "General Purpose 2",
[CTL_GENERAL_PURPOSE3] = "General Purpose 3",
[CTL_GENERAL_PURPOSE4] = "General Purpose 4",
[0x14 ... 0x1f] = "Undefined",
[0x20 ... 0x3f] = "LSB Controller",
[CTL_DAMPER_PEDAL] = "Damper Pedal (Sustain)",
[CTL_PORTAMENTO] = "Portamento",
[CTL_SOSTENUTO] = "Sostenuto Pedal",
[CTL_SOFT_PEDAL] = "Soft Pedal",
[0x44] = "Legato Foot-Switch",
[CTL_HOLD2] = "Hold 2",
[0x46] = "Sound Controller 1",
[0x47] = "Sound Controller 2",
[0x48] = "Sound Controller 3",
[0x49] = "Sound Controller 4",
[0x4a] = "Sound Controller 5",
[0x4b] = "Sound Controller 6",
[0x4c] = "Sound Controller 7",
[0x4d] = "Sound Controller 8",
[0x4e] = "Sound Controller 9",
[0x4f] = "Sound Controller 10",
[CTL_GENERAL_PURPOSE5] = "General Purpose 5",
[CTL_GENERAL_PURPOSE6] = "General Purpose 6",
[CTL_GENERAL_PURPOSE7] = "General Purpose 7",
[CTL_GENERAL_PURPOSE8] = "General Purpose 8",
[0x54] = "Portamento CC",
[0x55 ... 0x57] = "Undefined",
[0x58] = "Hi-Res Velocity Prefix",
[0x59 ... 0x5a] = "Undefined",
[CTL_EXT_EFF_DEPTH] = "Effect 1 Depth",
[CTL_TREMOLO_DEPTH] = "Effect 2 Depth",
[CTL_CHORUS_DEPTH] = "Effect 3 Depth",
[CTL_DETUNE_DEPTH] = "Effect 4 Depth",
[CTL_PHASER_DEPTH] = "Effect 5 Depth",
[CTL_DATA_INCREMENT] = "Data Increment",
[CTL_DATA_DECREMENT] = "Data Decrement",
[CTL_NONREG_PARM_NUM_LSB] = "NRPN (LSB)",
[CTL_NONREG_PARM_NUM_MSB] = "NRPN (MSB)",
[CTL_REGIST_PARM_NUM_LSB] = "RPN (LSB)",
[CTL_REGIST_PARM_NUM_MSB] = "RPN (MSB)",
[0x66 ... 0x77] = "Undefined",
/* Channel mode messages */
[0x78] = "All Sound Off",
[0x79] = "Reset All Controllers",
[0x7a] = "Local On/Off Switch",
[0x7b] = "All Notes Off",
[0x7c] = "Omni Mode Off",
[0x7d] = "Omni Mode On",
[0x7e] = "Mono Mode",
[0x7f] = "Poly Mode",
};
static void __dead2
usage(void)
{
fprintf(stderr, "usage: %s [-t] device\n", getprogname());
exit(1);
}
static uint8_t
read_byte(int fd)
{
uint8_t byte;
if (read(fd, &byte, sizeof(byte)) < (ssize_t)sizeof(byte))
err(1, "read");
return (byte);
}
int
main(int argc, char *argv[])
{
struct note *pn;
char buf[16];
int fd, ch, tflag = 0;
uint8_t event, chan, b1, b2;
while ((ch = getopt(argc, argv, "t")) != -1) {
switch (ch) {
case 't':
tflag = 1;
break;
case '?': /* FALLTHROUGH */
default:
usage();
}
}
argc -= optind;
argv += optind;
if (argc < 1)
usage();
if ((fd = open(*argv, O_RDONLY)) < 0)
err(1, "open(%s)", *argv);
for (;;) {
event = read_byte(fd);
if (!(event & 0x80))
continue;
chan = (event & CHAN_MASK) + 1;
switch (event) {
case 0x80 ... 0x8f: /* FALLTHROUGH */
case 0x90 ... 0x9f:
b1 = read_byte(fd);
b2 = read_byte(fd);
pn = &notes[b1 % ARRLEN(notes)];
snprintf(buf, sizeof(buf), "%s%d", pn->name,
NOTE2OCTAVE(b1));
if (pn->alt != NULL) {
snprintf(buf + strlen(buf), sizeof(buf),
"/%s%d", pn->alt, NOTE2OCTAVE(b1));
}
printf("Note %-3s channel=%d, "
"note=%d (%s, %.2fHz), velocity=%d\n",
(event >= 0x80 && event <= 0x8f) ? "off" : "on",
chan, b1, buf, NOTE2FREQ(b1), b2);
break;
case 0xa0 ... 0xaf:
b1 = read_byte(fd);
b2 = read_byte(fd);
printf("Polyphonic aftertouch channel=%d, note=%d, "
"pressure=%d\n",
chan, b1, b2);
break;
case 0xb0 ... 0xbf:
b1 = read_byte(fd);
b2 = read_byte(fd);
if (b1 < 0 || b1 > ARRLEN(ctls) - 1)
break;
printf("Control/Mode change channel=%d, "
"control=%d (%s), value=%d",
chan, b1, ctls[b1], b2);
if (b1 >= 0x40 && b1 <= 0x45) {
if (b2 <= 63)
printf(" (off)");
else
printf(" (on)");
}
if (b1 == 0x7a) {
if (b2 == 0)
printf(" (off)");
else if (b2 == 127)
printf(" (on");
}
putchar('\n');
break;
case 0xc0 ... 0xcf:
b1 = read_byte(fd);
printf("Program change channel=%d, "
"program=%d\n",
chan, b1);
break;
case 0xd0 ... 0xdf:
b1 = read_byte(fd);
printf("Channel aftertouch channel=%d, "
"pressure=%d\n",
chan, b1);
break;
case 0xe0 ... 0xef:
/* TODO Improve */
b1 = read_byte(fd);
b2 = read_byte(fd);
printf("Pitch bend channel=%d, change=%d\n",
chan, b1 | b2 << 7);
break;
case 0xf0:
printf("SysEx vendorid=");
b1 = read_byte(fd);
printf("0x%02x", b1);
if (b1 == 0) {
printf(" 0x%02x 0x%02x",
read_byte(fd), read_byte(fd));
}
printf(" data=");
for (;;) {
b1 = read_byte(fd);
printf("0x%02x ", b1);
/* End of SysEx (EOX) */
if (b1 == 0xf7)
break;
}
putchar('\n');
break;
case 0xf2:
b1 = read_byte(fd);
b2 = read_byte(fd);
printf("Song position pointer ptr=%d\n",
b1 | b2 << 7);
break;
case 0xf3:
b1 = read_byte(fd);
printf("Song select song=%d\n", b1);
break;
case 0xf6:
printf("Tune request\n");
break;
case 0xf7:
printf("End of SysEx (EOX)\n");
break;
case 0xf8:
if (tflag)
printf("Timing clock\n");
break;
case 0xfa:
printf("Start\n");
break;
case 0xfb:
printf("Continue\n");
break;
case 0xfc:
printf("Stop\n");
break;
case 0xfe:
printf("Active sensing\n");
break;
case 0xff:
printf("System reset\n");
break;
case 0xf1: /* TODO? MIDI time code qtr. frame */
case 0xf4: /* Undefined (reserved) */
case 0xf5:
case 0xf9:
case 0xfd:
break;
default:
printf("Unknown event type: 0x%02x\n", event);
break;
}
}
close(fd);
return (0);
}