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 = ¬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