--- /dev/null
+*.obj
+*.OBJ
+*.swp
+*.lnk
+*.LNK
+*.exe
+*.EXE
--- /dev/null
+obj = main.obj midi.obj keyb.obj
+bin = midikeys.exe
+
+CC = wcc
+LD = wlink
+CFLAGS = -d3 -s -zq -bt=dos
+
+$(bin): $(obj)
+ %write objects.lnk $(obj)
+ $(LD) debug all name $@ system dos file { @objects } $(LDFLAGS)
+
+.c.obj:
+ $(CC) -fo=$@ $(CFLAGS) $<
+
+.asm.obj:
+ nasm -f obj -o $@ $(ASFLAGS) $<
+
+clean: .symbolic
+ del *.obj
+ del $(bin)
--- /dev/null
+/*
+DOS interrupt-based keyboard driver.
+Copyright (C) 2013 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with the program. If not, see <http://www.gnu.org/licenses/>
+*/
+#define KEYB_C_
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <conio.h>
+#include <dos.h>
+
+#ifdef __WATCOMC__
+#include <i86.h>
+#endif
+#ifdef __DJGPP__
+#include <dpmi.h>
+#include <go32.h>
+#include <pc.h>
+#endif
+
+#include "keyb.h"
+#include "scancode.h"
+
+#define KB_INTR 0x9
+#define KB_PORT 0x60
+
+#define PIC1_CMD_PORT 0x20
+#define OCW2_EOI (1 << 5)
+
+#ifdef __WATCOMC__
+#define INTERRUPT __interrupt __far
+
+#define DONE_INIT (prev_handler)
+static void (INTERRUPT *prev_handler)();
+#endif
+
+#ifdef __DJGPP__
+#define INTERRUPT
+
+#define DONE_INIT prev_intr.pm_offset
+static _go32_dpmi_seginfo intr, prev_intr;
+#endif
+
+static void INTERRUPT kbintr();
+
+#define BUFSZ 32
+static struct kb_event evbuf[BUFSZ];
+static int buf_ridx, buf_widx;
+static int last_key;
+
+static unsigned int num_pressed;
+unsigned char keystate[256];
+
+#define ADVANCE(x) ((x) = ((x) + 1) & (BUFSZ - 1))
+
+int kb_init(int bufsz)
+{
+ if(DONE_INIT) {
+ fprintf(stderr, "keyboard driver already initialized!\n");
+ return 0;
+ }
+
+ buf_ridx = buf_widx = 0;
+ last_key = -1;
+
+ memset(keystate, 0, sizeof keystate);
+ num_pressed = 0;
+
+ /* set our interrupt handler */
+ _disable();
+#ifdef __WATCOMC__
+ prev_handler = _dos_getvect(KB_INTR);
+ _dos_setvect(KB_INTR, kbintr);
+#endif
+#ifdef __DJGPP__
+ _go32_dpmi_get_protected_mode_interrupt_vector(KB_INTR, &prev_intr);
+ intr.pm_offset = (intptr_t)kbintr;
+ intr.pm_selector = _go32_my_cs();
+ _go32_dpmi_allocate_iret_wrapper(&intr);
+ _go32_dpmi_set_protected_mode_interrupt_vector(KB_INTR, &intr);
+#endif
+ _enable();
+
+ return 0;
+}
+
+void kb_shutdown(void)
+{
+ if(!DONE_INIT) {
+ return;
+ }
+
+ /* restore the original interrupt handler */
+ _disable();
+#ifdef __WATCOMC__
+ _dos_setvect(KB_INTR, prev_handler);
+#endif
+#ifdef __DJGPP__
+ _go32_dpmi_set_protected_mode_interrupt_vector(KB_INTR, &prev_intr);
+ _go32_dpmi_free_iret_wrapper(&intr);
+#endif
+ _enable();
+}
+
+int kb_isdown(int key)
+{
+ switch(key) {
+ case KB_ANY:
+ return num_pressed;
+
+ case KB_ALT:
+ return keystate[KB_LALT] + keystate[KB_RALT];
+
+ case KB_CTRL:
+ return keystate[KB_LCTRL] + keystate[KB_RCTRL];
+ }
+
+ if(isalpha(key)) {
+ key = tolower(key);
+ }
+ return keystate[key];
+}
+
+#ifdef __WATCOMC__
+void halt(void);
+#pragma aux halt = \
+ "sti" \
+ "hlt";
+#endif
+
+#ifdef __DJGPP__
+#define halt() asm volatile("sti\n\thlt\n\t")
+#endif
+
+
+int kb_event(struct kb_event *ev)
+{
+ struct kb_event tmp;
+
+ if(!ev) ev = &tmp;
+
+ _disable();
+ while(buf_ridx == buf_widx) {
+ _enable();
+ halt();
+ _disable();
+ }
+
+ *ev = evbuf[buf_ridx];
+ ADVANCE(buf_ridx);
+ _enable();
+
+ return ev->press ? ev->key : (ev->key | 0x100);
+}
+
+static void INTERRUPT kbintr()
+{
+ struct kb_event *ev;
+ unsigned char code;
+ int key, c, press;
+ static int ext;
+
+ code = inp(KB_PORT);
+
+ if(code == 0xe0) {
+ ext = 1;
+ goto eoi;
+ }
+
+ if(code & 0x80) {
+ press = 0;
+ code &= 0x7f;
+
+ if(num_pressed > 0) {
+ num_pressed--;
+ }
+ } else {
+ press = 1;
+
+ num_pressed++;
+ }
+
+ if(ext) {
+ key = scantbl_ext[code];
+ c = key;
+ ext = 0;
+ } else {
+ key = scantbl[code];
+ c = (keystate[KB_LSHIFT] | keystate[KB_RSHIFT]) ? scantbl_shift[code] : key;
+ }
+
+ ev = evbuf + buf_widx;
+ ADVANCE(buf_widx);
+ if(buf_widx == buf_ridx) {
+ ADVANCE(buf_ridx);
+ }
+
+ ev->key = key;
+ ev->code = code;
+ ev->press = press;
+
+ /* and update keystate table */
+ keystate[key] = press;
+
+eoi:
+ outp(PIC1_CMD_PORT, OCW2_EOI); /* send end-of-interrupt */
+}
--- /dev/null
+/*
+DOS interrupt-based keyboard driver.
+Copyright (C) 2013-2023 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with the program. If not, see <http://www.gnu.org/licenses/>
+*/
+#ifndef KEYB_H_
+#define KEYB_H_
+
+/* special keys */
+enum {
+ KB_BACKSP = 8,
+ KB_ESC = 27,
+ KB_DEL = 127,
+
+ KB_NUM_0, KB_NUM_1, KB_NUM_2, KB_NUM_3, KB_NUM_4,
+ KB_NUM_5, KB_NUM_6, KB_NUM_7, KB_NUM_8, KB_NUM_9,
+ KB_NUM_DOT, KB_NUM_DIV, KB_NUM_MUL, KB_NUM_MINUS, KB_NUM_PLUS, KB_NUM_ENTER, KB_NUM_EQUALS,
+ KB_UP, KB_DOWN, KB_RIGHT, KB_LEFT,
+ KB_INSERT, KB_HOME, KB_END, KB_PGUP, KB_PGDN,
+ KB_F1, KB_F2, KB_F3, KB_F4, KB_F5, KB_F6,
+ KB_F7, KB_F8, KB_F9, KB_F10, KB_F11, KB_F12,
+ KB_F13, KB_F14, KB_F15,
+ KB_NUMLK, KB_CAPSLK, KB_SCRLK,
+ KB_RSHIFT, KB_LSHIFT, KB_RCTRL, KB_LCTRL, KB_RALT, KB_LALT,
+ KB_RMETA, KB_LMETA, KB_LSUPER, KB_RSUPER, KB_MODE, KB_COMPOSE,
+ KB_HELP, KB_PRINT, KB_SYSRQ, KB_BREAK
+};
+
+#define KB_ANY (-1)
+#define KB_ALT (-2)
+#define KB_CTRL (-3)
+#define KB_SHIFT (-4)
+
+struct kb_event {
+ int key;
+ int code;
+ int press;
+};
+
+extern unsigned char keystate[256];
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int kb_init(void);
+void kb_shutdown(void);
+
+/* Returns the ASCII key event with bit 8 set of release, clear for press
+ * kb_event filled if the pointer is non-null
+ */
+int kb_event(struct kb_event *ev);
+int kb_pending(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* KEYB_H_ */
--- /dev/null
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <conio.h>
+#include "midi.h"
+#include "keyb.h"
+
+static int parse_args(int argc, char **argv);
+
+static int bottom[] = {
+ 0, 2, 4, 5, 7, 9, 11, 12, 14, 16
+};
+static int top[] = {
+ -1, 1, 3, -1, 6, 8, 10, -1, 13, 15
+};
+
+static int port = 0x330;
+static int chan = 0;
+static int octave = 4;
+static int octave_offs = 60;
+static int prog = 0;
+static int vel = 127;
+
+static int note_state[128];
+
+static const char *helptext =
+ "Controls:\n"
+ " Play using the bottom two rows of the keyboard. Esc to exit.\n"
+ " change MIDI channel: 1-8 change instrument: (/)\n"
+ " shift octave: [/] change velocity: +/-\n";
+
+
+int main(int argc, char **argv)
+{
+ int i, note, c;
+ char *env, *ptr, *endp;
+
+ if((env = getenv("BLASTER")) && ((ptr = strstr(env, "P:")) ||
+ (ptr = strstr(env, "p:")))) {
+ port = strtol(ptr + 2, &endp, 16);
+ if(endp == ptr + 2) {
+ fprintf(stderr, "invalid MPU port specified in BLASTER environment variable, ignoring\n");
+ port = 0x330;
+ }
+ }
+
+ if(parse_args(argc, argv) == -1) {
+ return 1;
+ }
+
+ kb_init();
+
+ printf("Initializing MIDI interface at port %x\n", port);
+ if(midi_init(port) == -1) {
+ fprintf(stderr, "failed to initialize MPU port\n");
+ return 1;
+ }
+ midi_chprog(chan, prog);
+
+ fputs(helptext, stdout);
+
+ for(;;) {
+ struct kb_event ev;
+ c = kb_event(&ev);
+ if(c == 27) {
+ break;
+ }
+
+ if(ev.code >= 44 && ev.code <= 53) {
+ note = bottom[ev.code - 44] + octave_offs;
+ if(note_state[note] != ev.press) {
+ midi_note(chan, note, ev.press ? vel : 0);
+ }
+ note_state[note] = ev.press;
+
+ } else if(ev.code >= 30 && ev.code <= 40) {
+ if((note = top[ev.code - 30]) != -1) {
+ note += octave_offs;
+ if(note_state[note] != ev.press) {
+ midi_note(chan, note, ev.press ? vel : 0);
+ }
+ note_state[note] = ev.press;
+ }
+ } else if(c >= '1' && c <= '8') {
+ chan = c - '1';
+ } else {
+ switch(c) {
+ case '[':
+ if(octave > 1) {
+ octave_offs = --octave * 12 + 12;
+ printf("octave: %d (midi offset: %d)\n", octave, octave_offs);
+ }
+ break;
+
+ case ']':
+ if(octave < 7) {
+ octave_offs = ++octave * 12 + 12;
+ printf("octave: %d (midi offset: %d)\n", octave, octave_offs);
+ }
+ break;
+
+ case '9':
+ prog = (prog - 1) & 0x7f;
+ printf("[%d] instrument: %d\n", chan, prog);
+ midi_chprog(chan, prog);
+ break;
+
+ case '0':
+ prog = (prog + 1) & 0x7f;
+ printf("[%d] instrument: %d\n", chan, prog);
+ midi_chprog(chan, prog);
+ break;
+
+ case '-':
+ if(vel > 0) {
+ vel--;
+ printf("velocity: %d\n", vel);
+ }
+ break;
+
+ case '=':
+ if(vel < 127) {
+ vel++;
+ printf("velocity: %d\n", vel);
+ }
+ break;
+ }
+ }
+ }
+
+ midi_shutdown();
+
+ kb_shutdown();
+ return 0;
+}
+
+static const char *usage_fmt = "Usage: %s [options]\n"
+ "Options:\n"
+ " -p <port>: set MIDI base I/O port\n"
+ " -c <channel>: set MIDI channel\n"
+ " -i <instrument>: MIDI instrument\n"
+ " -o <octave>: select octave (1-7)\n"
+ " -h: print usage information and exit\n"
+ "\n";
+
+static int parse_args(int argc, char **argv)
+{
+ int i;
+ char *endp;
+
+ for(i=1; i<argc; i++) {
+ if(argv[i][0] == '-') {
+ if(argv[i][2]) {
+ fprintf(stderr, "invalid option: %s\n", argv[i]);
+ printf(usage_fmt, argv[0]);
+ return -1;
+ }
+ switch(argv[i][1]) {
+ case 'p':
+ if(!argv[++i] || ((port = strtol(argv[i], &endp, 16)), endp == argv[i])) {
+ fprintf(stderr, "-p should be followed by the port number in hex\n");
+ return -1;
+ }
+ break;
+
+ case 'c':
+ if(!argv[++i] || !(chan = atoi(argv[i]))) {
+ fprintf(stderr, "-c should be followed by the channel number\n");
+ return -1;
+ }
+ break;
+
+ case 'i':
+ if(!argv[++i] || !(prog = atoi(argv[i]))) {
+ fprintf(stderr, "-i should be followed by a valid channel number\n");
+ return -1;
+ }
+ break;
+
+ case 'o':
+ if(!argv[++i] || !(octave = atoi(argv[i]))) {
+ fprintf(stderr, "-o should be followed by the octane number.\n");
+ return -1;
+ }
+
+ case 'h':
+ printf(usage_fmt, argv[0]);
+ exit(0);
+
+ default:
+ fprintf(stderr, "invalid option: %s\n", argv[i]);
+ printf(usage_fmt, argv[0]);
+ return -1;
+ }
+ }
+ fprintf(stderr, "unexpected argument: %s\n", argv[i]);
+ printf(usage_fmt, argv[0]);
+ return -1;
+ }
+ return 0;
+}
--- /dev/null
+#include <conio.h>
+#include "midi.h"
+
+static int iobase = 0x330;
+
+#define MPU_PORT_DATA iobase
+#define MPU_PORT_STAT (iobase | 1)
+#define MPU_PORT_CMD (iobase | 1)
+
+#define MPU_STAT_ORDY 0x40
+#define MPU_STAT_IRDY 0x80
+
+#define MPU_ACK 0xfe
+#define MPU_CMD_RESET 0xff
+#define MPU_CMD_UARTMODE 0x3f
+
+#define MIDI_CMD_NOTEON 0x90
+#define MIDI_CMD_NOTEOFF 0x80
+#define MIDI_CMD_CHANMSG 0xb0
+#define MIDI_CMD_CHPROG 0xc0
+
+#define MIDI_CHANMSG_NOTESOFF 0x7b
+
+int midi_init(int port)
+{
+ iobase = port;
+
+ if(midi_send_cmd(MPU_CMD_RESET) == -1) {
+ return -1;
+ }
+ if(midi_send_cmd(MPU_CMD_UARTMODE) == -1) {
+ return -1;
+ }
+ midi_alloff();
+ return 0;
+}
+
+void midi_shutdown(void)
+{
+ midi_alloff();
+ midi_send_cmd(MPU_CMD_RESET);
+}
+
+void midi_note(int chan, int note, int vel)
+{
+ if(vel > 0) {
+ midi_send_data(MIDI_CMD_NOTEON | chan);
+ } else {
+ midi_send_data(MIDI_CMD_NOTEOFF | chan);
+ }
+ midi_send_data(note);
+ midi_send_data(vel);
+}
+
+void midi_chprog(int chan, int prog)
+{
+ midi_send_data(MIDI_CMD_CHPROG | chan);
+ midi_send_data(prog);
+}
+
+void midi_alloff(void)
+{
+ midi_send_data(MIDI_CMD_CHANMSG);
+ midi_send_data(MIDI_CHANMSG_NOTESOFF);
+}
+
+static int wait_ordy(void)
+{
+ int i;
+ for(i=0; i<1024; i++) {
+ if((inp(MPU_PORT_STAT) & MPU_STAT_ORDY) == 0) {
+ return 0;
+ }
+ }
+ return -1;
+}
+
+int midi_send_cmd(int cmd)
+{
+ if(wait_ordy() == -1) {
+ return -1;
+ }
+ outp(MPU_PORT_CMD, cmd);
+ return 0;
+}
+
+int midi_send_data(int data)
+{
+ if(wait_ordy() == -1) {
+ return -1;
+ }
+ outp(MPU_PORT_DATA, data);
+ return 0;
+}
--- /dev/null
+#ifndef MIDI_H_
+#define MIDI_H_
+
+int midi_init(int port);
+void midi_shutdown(void);
+
+void midi_note(int chan, int note, int vel);
+void midi_chprog(int chan, int prog);
+void midi_alloff(void);
+
+int midi_send_cmd(int cmd);
+int midi_send_data(int data);
+
+#endif /* MIDI_H_ */
--- /dev/null
+/*
+colcycle - color cycling image viewer
+Copyright (C) 2016 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef KEYB_C_
+#error "do not include scancode.h anywhere..."
+#endif
+
+/* table with rough translations from set 1 scancodes to ASCII-ish */
+static int scantbl[] = {
+ 0, KB_ESC, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b', /* 0 - e */
+ '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n', /* f - 1c */
+ KB_LCTRL, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', /* 1d - 29 */
+ KB_LSHIFT, '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', KB_RSHIFT, /* 2a - 36 */
+ KB_NUM_MUL, KB_LALT, ' ', KB_CAPSLK, KB_F1, KB_F2, KB_F3, KB_F4, KB_F5, KB_F6, KB_F7, KB_F8, KB_F9, KB_F10, /* 37 - 44 */
+ KB_NUMLK, KB_SCRLK, KB_NUM_7, KB_NUM_8, KB_NUM_9, KB_NUM_MINUS, KB_NUM_4, KB_NUM_5, KB_NUM_6, KB_NUM_PLUS, /* 45 - 4e */
+ KB_NUM_1, KB_NUM_2, KB_NUM_3, KB_NUM_0, KB_NUM_DOT, KB_SYSRQ, 0, 0, KB_F11, KB_F12, /* 4d - 58 */
+ 0, 0, 0, 0, 0, 0, 0, /* 59 - 5f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60 - 6f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 70 - 7f */
+};
+
+static int scantbl_shift[] = {
+ 0, KB_ESC, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b', /* 0 - e */
+ '\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n', /* f - 1c */
+ KB_LCTRL, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~', /* 1d - 29 */
+ KB_LSHIFT, '|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', KB_RSHIFT, /* 2a - 36 */
+ KB_NUM_MUL, KB_LALT, ' ', KB_CAPSLK, KB_F1, KB_F2, KB_F3, KB_F4, KB_F5, KB_F6, KB_F7, KB_F8, KB_F9, KB_F10, /* 37 - 44 */
+ KB_NUMLK, KB_SCRLK, KB_NUM_7, KB_NUM_8, KB_NUM_9, KB_NUM_MINUS, KB_NUM_4, KB_NUM_5, KB_NUM_6, KB_NUM_PLUS, /* 45 - 4e */
+ KB_NUM_1, KB_NUM_2, KB_NUM_3, KB_NUM_0, KB_NUM_DOT, KB_SYSRQ, 0, 0, KB_F11, KB_F12, /* 4d - 58 */
+ 0, 0, 0, 0, 0, 0, 0, /* 59 - 5f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60 - 6f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 70 - 7f */
+};
+
+
+/* extended scancodes, after the 0xe0 prefix */
+static int scantbl_ext[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0 - f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '\r', KB_RCTRL, 0, 0, /* 10 - 1f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 20 - 2f */
+ 0, 0, 0, 0, 0, KB_NUM_MINUS, 0, KB_SYSRQ, KB_RALT, 0, 0, 0, 0, 0, 0, 0, /* 30 - 3f */
+ 0, 0, 0, 0, 0, 0, 0, KB_HOME, KB_UP, KB_PGUP, 0, KB_LEFT, 0, KB_RIGHT, 0, KB_END, /* 40 - 4f */
+ KB_DOWN, KB_PGDN, KB_INSERT, KB_DEL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 50 - 5f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 60 - 6f */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 70 - 7f */
+};
+