182 lines
6.1 KiB
C
182 lines
6.1 KiB
C
// The MIT License (MIT)
|
|
//
|
|
// Copyright (C) 2026 Scott Duensing
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to
|
|
// deal in the Software without restriction, including without limitation the
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
// IN THE SOFTWARE.
|
|
|
|
// mpuDrv.c -- the MPU-401 MIDI port driver (mpu401.drv)
|
|
//
|
|
// A synth driver (audioDrv.h "Synthesizer drivers") for an external MIDI
|
|
// synthesizer (a Roland MT-32 or SC-55, a General MIDI module) on an
|
|
// MPU-401 or compatible port, in UART mode: bytes written to the data
|
|
// port go out on the MIDI cable as they are. Looked for only when
|
|
// DVX.INI [audio] midi = mpu401 (at mpuPort, hex, default 330h), because
|
|
// a port with nothing plugged in would play nothing.
|
|
//
|
|
// Status port (data port + 1): bit 6 clear = ready to take a byte, bit 7
|
|
// clear = a byte to read. Reset: FFh to the command (status) port, the
|
|
// port answers FEh; UART mode: 3Fh, answered FEh too (UART-only clones
|
|
// may not answer that one). Bytes the port sends (active sensing, input)
|
|
// are read and dropped. put waits a little for the port to be ready and
|
|
// otherwise returns false, so the caller sends the byte later; it runs at
|
|
// interrupt time too and is pinned. Close sends All Notes Off on every
|
|
// channel and resets the port (out of UART mode).
|
|
|
|
#include "audioDrv.h"
|
|
|
|
#include <pc.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <strings.h>
|
|
|
|
#define PORT_BASE_HEX 16
|
|
#define MPU_STATUS 1 // status / command port: data port + 1
|
|
#define MPU_NOT_READY 0x40 // status: cannot take a byte
|
|
#define MPU_NO_DATA 0x80 // status: nothing to read
|
|
#define MPU_CMD_RESET 0xFF
|
|
#define MPU_CMD_UART 0x3F
|
|
#define MPU_ACK 0xFE
|
|
#define MPU_ACK_READS 50000 // status reads to wait for an answer
|
|
#define MPU_PUT_READS 64 // status reads put waits for the port
|
|
#define MPU_DRAIN_MAX 16 // input bytes dropped per put
|
|
|
|
// Sent on close: All Notes Off (controller 123) on each channel
|
|
#define MIDI_CONTROL 0xB0
|
|
#define MIDI_CHANNELS 16
|
|
#define MIDI_ALL_NOTES_OFF 123
|
|
|
|
typedef struct {
|
|
uint16_t data;
|
|
uint16_t status;
|
|
} MpuT;
|
|
|
|
static MpuT sMpu;
|
|
|
|
// ============================================================
|
|
// Prototypes
|
|
// ============================================================
|
|
|
|
static void mpuClose(void);
|
|
static bool mpuCommand(uint8_t cmd);
|
|
static bool mpuLock(bool lock);
|
|
static bool mpuOpen(const AudioDrvConfigT *cfg, AudioSynthDrvInfoT *info);
|
|
static bool mpuPut(uint8_t byte);
|
|
static void mpuPutWait(uint8_t byte);
|
|
|
|
AUDIO_SYNTH_DRIVER("mpu401", AUDIO_PRIORITY_CARD,
|
|
.kind = AUDIO_SYNTH_MIDI,
|
|
.open = mpuOpen,
|
|
.close = mpuClose,
|
|
.put = mpuPut
|
|
);
|
|
|
|
|
|
static void mpuClose(void) {
|
|
for (uint8_t ch = 0; ch < MIDI_CHANNELS; ch++) {
|
|
mpuPutWait((uint8_t)(MIDI_CONTROL | ch));
|
|
mpuPutWait(MIDI_ALL_NOTES_OFF);
|
|
mpuPutWait(0);
|
|
}
|
|
|
|
mpuCommand(MPU_CMD_RESET);
|
|
mpuLock(false);
|
|
}
|
|
|
|
|
|
// Send a command and wait for its acknowledgement (other bytes read on
|
|
// the way are dropped).
|
|
static bool mpuCommand(uint8_t cmd) {
|
|
int32_t reads = 0;
|
|
|
|
while ((inportb(sMpu.status) & MPU_NOT_READY) && reads < MPU_ACK_READS) {
|
|
reads++;
|
|
}
|
|
|
|
outportb(sMpu.status, cmd);
|
|
|
|
for (reads = 0; reads < MPU_ACK_READS; reads++) {
|
|
if (!(inportb(sMpu.status) & MPU_NO_DATA) && inportb(sMpu.data) == MPU_ACK) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static bool mpuLock(bool lock) {
|
|
const AudioLockItemT items[] = {
|
|
{ (const void *)mpuPut, 0 },
|
|
{ &sMpu, sizeof(sMpu) }
|
|
};
|
|
|
|
return audioLockSpan(items, (int32_t)(sizeof(items) / sizeof(items[0])), lock);
|
|
}
|
|
|
|
|
|
// A port left in UART mode ignores the first reset (it takes FFh as
|
|
// data); the second one is answered.
|
|
static bool mpuOpen(const AudioDrvConfigT *cfg, AudioSynthDrvInfoT *info) {
|
|
if (strcasecmp(prefsGetString(cfg->prefs, AUDIO_INI_SECTION, AUDIO_INI_MIDI, AUDIO_MIDI_AUTO), AUDIO_MIDI_MPU401) != 0) {
|
|
return false;
|
|
}
|
|
|
|
sMpu.data = (uint16_t)strtoul(prefsGetString(cfg->prefs, AUDIO_INI_SECTION, AUDIO_INI_MPU_PORT, AUDIO_MPU_PORT_DEFAULT), NULL, PORT_BASE_HEX);
|
|
sMpu.status = (uint16_t)(sMpu.data + MPU_STATUS);
|
|
|
|
if (!mpuCommand(MPU_CMD_RESET) && !mpuCommand(MPU_CMD_RESET)) {
|
|
return false;
|
|
}
|
|
|
|
// UART-only clones may not acknowledge this.
|
|
mpuCommand(MPU_CMD_UART);
|
|
|
|
if (!mpuLock(true)) {
|
|
return false;
|
|
}
|
|
|
|
snprintf(info->description, sizeof(info->description), "MPU-401 MIDI port at %Xh", (unsigned)sMpu.data);
|
|
return true;
|
|
}
|
|
|
|
|
|
// Drop what the port has sent, then send the byte if the port gets ready
|
|
// soon enough.
|
|
static bool mpuPut(uint8_t byte) {
|
|
for (int32_t i = 0; i < MPU_DRAIN_MAX && !(inportb(sMpu.status) & MPU_NO_DATA); i++) {
|
|
(void)inportb(sMpu.data);
|
|
}
|
|
|
|
for (int32_t i = 0; i < MPU_PUT_READS; i++) {
|
|
if (!(inportb(sMpu.status) & MPU_NOT_READY)) {
|
|
outportb(sMpu.data, byte);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
// put, retried for as long as an acknowledgement may take.
|
|
static void mpuPutWait(uint8_t byte) {
|
|
for (int32_t i = 0; i < MPU_ACK_READS / MPU_PUT_READS && !mpuPut(byte); i++) {
|
|
// the port is busy
|
|
}
|
|
}
|