From: John Tsiombikas Date: Fri, 23 Dec 2022 21:36:15 +0000 (+0200) Subject: initial commit X-Git-Url: http://git.mutantstargoat.com/user/nuclear/?p=midikeys;a=commitdiff_plain;h=d0265a282c42bb88cca767d92a607e66984e2c20 initial commit --- d0265a282c42bb88cca767d92a607e66984e2c20 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aeaa582 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.obj +*.OBJ +*.swp +*.lnk +*.LNK +*.exe +*.EXE diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8c08adc --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +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) diff --git a/keyb.c b/keyb.c new file mode 100644 index 0000000..295b191 --- /dev/null +++ b/keyb.c @@ -0,0 +1,222 @@ +/* +DOS interrupt-based keyboard driver. +Copyright (C) 2013 John Tsiombikas + +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 +*/ +#define KEYB_C_ + +#include +#include +#include +#include +#include +#include + +#ifdef __WATCOMC__ +#include +#endif +#ifdef __DJGPP__ +#include +#include +#include +#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 */ +} diff --git a/keyb.h b/keyb.h new file mode 100644 index 0000000..f70bfad --- /dev/null +++ b/keyb.h @@ -0,0 +1,71 @@ +/* +DOS interrupt-based keyboard driver. +Copyright (C) 2013-2023 John Tsiombikas + +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 +*/ +#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_ */ diff --git a/main.c b/main.c new file mode 100644 index 0000000..1d49603 --- /dev/null +++ b/main.c @@ -0,0 +1,201 @@ +#include +#include +#include +#include +#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 : set MIDI base I/O port\n" + " -c : set MIDI channel\n" + " -i : MIDI instrument\n" + " -o : 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 +#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; +} diff --git a/midi.h b/midi.h new file mode 100644 index 0000000..7b74491 --- /dev/null +++ b/midi.h @@ -0,0 +1,14 @@ +#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_ */ diff --git a/scancode.h b/scancode.h new file mode 100644 index 0000000..b0e398c --- /dev/null +++ b/scancode.h @@ -0,0 +1,61 @@ +/* +colcycle - color cycling image viewer +Copyright (C) 2016 John Tsiombikas + +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 . +*/ +#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 */ +}; +