From d0265a282c42bb88cca767d92a607e66984e2c20 Mon Sep 17 00:00:00 2001 From: John Tsiombikas Date: Fri, 23 Dec 2022 23:36:15 +0200 Subject: [PATCH 1/1] initial commit --- .gitignore | 7 ++ Makefile | 20 ++++++ keyb.c | 222 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ keyb.h | 71 +++++++++++++++++++ main.c | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ midi.c | 94 +++++++++++++++++++++++++ midi.h | 14 ++++ scancode.h | 61 +++++++++++++++++ 8 files changed, 690 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 keyb.c create mode 100644 keyb.h create mode 100644 main.c create mode 100644 midi.c create mode 100644 midi.h create mode 100644 scancode.h 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 */ +}; + -- 1.7.10.4