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
usage(void)137 usage(void)
138 {
139 fprintf(stderr, "usage: %s [-t] device\n", getprogname());
140 exit(1);
141 }
142
143 static uint8_t
read_byte(int fd)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
main(int argc,char * argv[])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 = ¬es[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