xref: /freebsd/usr.bin/mididump/mididump.c (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2024 The FreeBSD Foundation
5  *
6  * This software was developed by Christos Margiolis <christos@FreeBSD.org>
7  * under sponsorship from the FreeBSD Foundation.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  */
30 
31 #include <sys/param.h>
32 #include <sys/soundcard.h>
33 
34 #include <err.h>
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <math.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 
43 #define NOTE2OCTAVE(n)	(n / 12 - 1)
44 #define NOTE2FREQ(n)	(440 * pow(2.0f, ((float)n - 69) / 12))
45 #define CHAN_MASK	0x0f
46 
47 static struct note {
48 	const char *name;
49 	const char *alt;
50 } notes[] = {
51 	{ "C",	NULL },
52 	{ "C#",	"Db" },
53 	{ "D",	NULL },
54 	{ "D#",	"Eb" },
55 	{ "E",	NULL },
56 	{ "F",	NULL },
57 	{ "F#",	"Gb" },
58 	{ "G",	NULL },
59 	{ "G#",	"Ab" },
60 	{ "A",	NULL },
61 	{ "A#",	"Bb" },
62 	{ "B",	NULL },
63 };
64 
65 /* Hardcoded values are not defined in sys/soundcard.h. */
66 static const char *ctls[] = {
67 	[CTL_BANK_SELECT]		= "Bank Select",
68 	[CTL_MODWHEEL]			= "Modulation Wheel",
69 	[CTL_BREATH]			= "Breath Controller",
70 	[0x03]				= "Undefined",
71 	[CTL_FOOT]			= "Foot Pedal",
72 	[CTL_PORTAMENTO_TIME]		= "Portamento Time",
73 	[CTL_DATA_ENTRY]		= "Data Entry",
74 	[CTL_MAIN_VOLUME]		= "Volume",
75 	[CTL_BALANCE]			= "Balance",
76 	[0x09]				= "Undefined",
77 	[CTL_PAN]			= "Pan",
78 	[CTL_EXPRESSION]		= "Expression",
79 	[0x0c]				= "Effect Controller 1",
80 	[0x0d]				= "Effect Controller 2",
81 	[0x0e]				= "Undefined",
82 	[0x0f]				= "Undefined",
83 	[CTL_GENERAL_PURPOSE1]		= "General Purpose 1",
84 	[CTL_GENERAL_PURPOSE2]		= "General Purpose 2",
85 	[CTL_GENERAL_PURPOSE3]		= "General Purpose 3",
86 	[CTL_GENERAL_PURPOSE4]		= "General Purpose 4",
87 	[0x14 ... 0x1f]			= "Undefined",
88 	[0x20 ... 0x3f]			= "LSB Controller",
89 	[CTL_DAMPER_PEDAL]		= "Damper Pedal (Sustain)",
90 	[CTL_PORTAMENTO]		= "Portamento",
91 	[CTL_SOSTENUTO]			= "Sostenuto Pedal",
92 	[CTL_SOFT_PEDAL]		= "Soft Pedal",
93 	[0x44]				= "Legato Foot-Switch",
94 	[CTL_HOLD2]			= "Hold 2",
95 	[0x46]				= "Sound Controller 1",
96 	[0x47]				= "Sound Controller 2",
97 	[0x48]				= "Sound Controller 3",
98 	[0x49]				= "Sound Controller 4",
99 	[0x4a]				= "Sound Controller 5",
100 	[0x4b]				= "Sound Controller 6",
101 	[0x4c]				= "Sound Controller 7",
102 	[0x4d]				= "Sound Controller 8",
103 	[0x4e]				= "Sound Controller 9",
104 	[0x4f]				= "Sound Controller 10",
105 	[CTL_GENERAL_PURPOSE5]		= "General Purpose 5",
106 	[CTL_GENERAL_PURPOSE6]		= "General Purpose 6",
107 	[CTL_GENERAL_PURPOSE7]		= "General Purpose 7",
108 	[CTL_GENERAL_PURPOSE8]		= "General Purpose 8",
109 	[0x54]				= "Portamento CC",
110 	[0x55 ... 0x57]			= "Undefined",
111 	[0x58]				= "Hi-Res Velocity Prefix",
112 	[0x59 ... 0x5a]			= "Undefined",
113 	[CTL_EXT_EFF_DEPTH]		= "Effect 1 Depth",
114 	[CTL_TREMOLO_DEPTH]		= "Effect 2 Depth",
115 	[CTL_CHORUS_DEPTH]		= "Effect 3 Depth",
116 	[CTL_DETUNE_DEPTH]		= "Effect 4 Depth",
117 	[CTL_PHASER_DEPTH]		= "Effect 5 Depth",
118 	[CTL_DATA_INCREMENT]		= "Data Increment",
119 	[CTL_DATA_DECREMENT]		= "Data Decrement",
120 	[CTL_NONREG_PARM_NUM_LSB]	= "NRPN (LSB)",
121 	[CTL_NONREG_PARM_NUM_MSB]	= "NRPN (MSB)",
122 	[CTL_REGIST_PARM_NUM_LSB]	= "RPN (LSB)",
123 	[CTL_REGIST_PARM_NUM_MSB]	= "RPN (MSB)",
124 	[0x66 ... 0x77]			= "Undefined",
125 	/* Channel mode messages */
126 	[0x78]				= "All Sound Off",
127 	[0x79]				= "Reset All Controllers",
128 	[0x7a]				= "Local On/Off Switch",
129 	[0x7b]				= "All Notes Off",
130 	[0x7c]				= "Omni Mode Off",
131 	[0x7d]				= "Omni Mode On",
132 	[0x7e]				= "Mono Mode",
133 	[0x7f]				= "Poly Mode",
134 };
135 
136 static void __dead2
137 usage(void)
138 {
139 	fprintf(stderr, "usage: %s [-t] device\n", getprogname());
140 	exit(1);
141 }
142 
143 static uint8_t
144 read_byte(int fd)
145 {
146 	uint8_t byte;
147 
148 	if (read(fd, &byte, sizeof(byte)) < (ssize_t)sizeof(byte))
149 		err(1, "read");
150 
151 	return (byte);
152 }
153 
154 int
155 main(int argc, char *argv[])
156 {
157 	struct note *pn;
158 	char buf[16];
159 	int fd, ch, tflag = 0;
160 	uint8_t event, chan, b1, b2;
161 
162 	while ((ch = getopt(argc, argv, "t")) != -1) {
163 		switch (ch) {
164 		case 't':
165 			tflag = 1;
166 			break;
167 		case '?':	/* FALLTHROUGH */
168 		default:
169 			usage();
170 		}
171 	}
172 	argc -= optind;
173 	argv += optind;
174 
175 	if (argc < 1)
176 		usage();
177 
178 	if ((fd = open(*argv, O_RDONLY)) < 0)
179 		err(1, "open(%s)", *argv);
180 
181 	for (;;) {
182 		event = read_byte(fd);
183 		if (!(event & 0x80))
184 			continue;
185 		chan = (event & CHAN_MASK) + 1;
186 
187 		switch (event) {
188 		case 0x80 ... 0x8f:	/* FALLTHROUGH */
189 		case 0x90 ... 0x9f:
190 			b1 = read_byte(fd);
191 			b2 = read_byte(fd);
192 			pn = &notes[b1 % nitems(notes)];
193 			snprintf(buf, sizeof(buf), "%s%d", pn->name,
194 			    NOTE2OCTAVE(b1));
195 			if (pn->alt != NULL) {
196 				snprintf(buf + strlen(buf), sizeof(buf),
197 				    "/%s%d", pn->alt, NOTE2OCTAVE(b1));
198 			}
199 			printf("Note %-3s		channel=%d, "
200 			    "note=%d (%s, %.2fHz), velocity=%d\n",
201 			    (event >= 0x80 && event <= 0x8f) ? "off" : "on",
202 			    chan, b1, buf, NOTE2FREQ(b1), b2);
203 			break;
204 		case 0xa0 ... 0xaf:
205 			b1 = read_byte(fd);
206 			b2 = read_byte(fd);
207 			printf("Polyphonic aftertouch	channel=%d, note=%d, "
208 			    "pressure=%d\n",
209 			    chan, b1, b2);
210 			break;
211 		case 0xb0 ... 0xbf:
212 			b1 = read_byte(fd);
213 			b2 = read_byte(fd);
214 			if (b1 > nitems(ctls) - 1)
215 				break;
216 			printf("Control/Mode change	channel=%d, "
217 			    "control=%d (%s), value=%d",
218 			    chan, b1, ctls[b1], b2);
219 			if (b1 >= 0x40 && b1 <= 0x45) {
220 				if (b2 <= 63)
221 					printf(" (off)");
222 				else
223 					printf(" (on)");
224 			}
225 			if (b1 == 0x7a) {
226 				if (b2 == 0)
227 					printf(" (off)");
228 				else if (b2 == 127)
229 					printf(" (on");
230 			}
231 			putchar('\n');
232 			break;
233 		case 0xc0 ... 0xcf:
234 			b1 = read_byte(fd);
235 			printf("Program change		channel=%d, "
236 			    "program=%d\n",
237 			    chan, b1);
238 			break;
239 		case 0xd0 ... 0xdf:
240 			b1 = read_byte(fd);
241 			printf("Channel aftertouch	channel=%d, "
242 			    "pressure=%d\n",
243 			    chan, b1);
244 			break;
245 		case 0xe0 ... 0xef:
246 			/* TODO Improve */
247 			b1 = read_byte(fd);
248 			b2 = read_byte(fd);
249 			printf("Pitch bend		channel=%d, change=%d\n",
250 			    chan, b1 | b2 << 7);
251 			break;
252 		case 0xf0:
253 			printf("SysEx			vendorid=");
254 			b1 = read_byte(fd);
255 			printf("0x%02x", b1);
256 			if (b1 == 0) {
257 				printf(" 0x%02x 0x%02x",
258 				    read_byte(fd), read_byte(fd));
259 			}
260 			printf(" data=");
261 			for (;;) {
262 				b1 = read_byte(fd);
263 				printf("0x%02x ", b1);
264 				/* End of SysEx (EOX) */
265 				if (b1 == 0xf7)
266 					break;
267 			}
268 			putchar('\n');
269 			break;
270 		case 0xf2:
271 			b1 = read_byte(fd);
272 			b2 = read_byte(fd);
273 			printf("Song position pointer	ptr=%d\n",
274 			    b1 | b2 << 7);
275 			break;
276 		case 0xf3:
277 			b1 = read_byte(fd);
278 			printf("Song select		song=%d\n", b1);
279 			break;
280 		case 0xf6:
281 			printf("Tune request\n");
282 			break;
283 		case 0xf7:
284 			printf("End of SysEx (EOX)\n");
285 			break;
286 		case 0xf8:
287 			if (tflag)
288 				printf("Timing clock\n");
289 			break;
290 		case 0xfa:
291 			printf("Start\n");
292 			break;
293 		case 0xfb:
294 			printf("Continue\n");
295 			break;
296 		case 0xfc:
297 			printf("Stop\n");
298 			break;
299 		case 0xfe:
300 			printf("Active sensing\n");
301 			break;
302 		case 0xff:
303 			printf("System reset\n");
304 			break;
305 		case 0xf1:		/* TODO? MIDI time code qtr. frame */
306 		case 0xf4:		/* Undefined (reserved) */
307 		case 0xf5:
308 		case 0xf9:
309 		case 0xfd:
310 			break;
311 		default:
312 			printf("Unknown event type: 0x%02x\n", event);
313 			break;
314 		}
315 	}
316 
317 	close(fd);
318 
319 	return (0);
320 }
321