Files
86Box/src/sound/snd_mpu401.c
Jasmine Iwanek 998cfe5cc8 Constification
2023-07-25 17:59:24 -04:00

2041 lines
63 KiB
C

/*
* 86Box A hypervisor and IBM PC system emulator that specializes in
* running old operating systems and software designed for IBM
* PC systems and compatibles from 1981 through fairly recent
* system designs based on the PCI bus.
*
* This file is part of the 86Box distribution.
*
* Roland MPU-401 emulation.
*
*
*
* Authors: Sarah Walker, <https://pcem-emulator.co.uk/>
* DOSBox Team,
* Miran Grca, <mgrca8@gmail.com>
* TheCollector1995, <mariogplayer@gmail.com>
*
* Copyright 2008-2020 Sarah Walker.
* Copyright 2008-2020 DOSBox Team.
* Copyright 2016-2020 Miran Grca.
*/
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/io.h>
#include <86box/machine.h>
#include <86box/mca.h>
#include <86box/midi.h>
#include <86box/pic.h>
#include <86box/plat.h>
#include <86box/timer.h>
#include <86box/snd_mpu401.h>
#include <86box/sound.h>
#include <86box/plat_unused.h>
static uint32_t MPUClockBase[8] = { 48, 72, 96, 120, 144, 168, 192 };
static uint8_t cth_data[16] = { 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0 };
enum {
STATUS_OUTPUT_NOT_READY = 0x40,
STATUS_INPUT_NOT_READY = 0x80
};
int mpu401_standalone_enable = 0;
static void MPU401_WriteCommand(mpu_t *mpu, uint8_t val);
static void MPU401_IntelligentOut(mpu_t *mpu, uint8_t track);
static void MPU401_EOIHandler(void *priv);
static void MPU401_EOIHandlerDispatch(void *priv);
static void MPU401_NotesOff(mpu_t *mpu, int i);
#ifdef ENABLE_MPU401_LOG
int mpu401_do_log = ENABLE_MPU401_LOG;
static void
mpu401_log(const char *fmt, ...)
{
va_list ap;
if (mpu401_do_log) {
va_start(ap, fmt);
pclog_ex(fmt, ap);
va_end(ap);
}
}
#else
# define mpu401_log(fmt, ...)
#endif
static void
MPU401_ReCalcClock(mpu_t *mpu)
{
int32_t mintempo = 16;
int32_t maxtempo = 240;
int32_t freq;
if (mpu->clock.timebase < 72) {
maxtempo = 240;
mintempo = 32;
} else if (mpu->clock.timebase < 120) {
maxtempo = 240;
mintempo = 16;
} else if (mpu->clock.timebase < 168) {
maxtempo = 208;
mintempo = 8;
} else {
maxtempo = 179;
mintempo = 8;
}
mpu->clock.freq = ((uint32_t) (mpu->clock.tempo * 2 * mpu->clock.tempo_rel)) >> 6;
mpu->clock.freq = mpu->clock.timebase * (mpu->clock.freq < (mintempo * 2) ? mintempo : ((mpu->clock.freq / 2) < maxtempo ? (mpu->clock.freq / 2) : maxtempo));
if (mpu->state.sync_in) {
freq = (int32_t) ((float) (mpu->clock.freq) * mpu->clock.freq_mod);
if ((freq > (mpu->clock.timebase * mintempo)) && (freq < (mpu->clock.timebase * maxtempo)))
mpu->clock.freq = freq;
}
}
static void
MPU401_StartClock(mpu_t *mpu)
{
if (mpu->clock.active)
return;
if (!(mpu->state.clock_to_host || mpu->state.playing || (mpu->state.rec == M_RECON)))
return;
mpu->clock.active = 1;
timer_set_delay_u64(&mpu->mpu401_event_callback, (MPU401_TIMECONSTANT / mpu->clock.freq) * 1000 * TIMER_USEC);
}
static void
MPU401_StopClock(mpu_t *mpu)
{
if (mpu->state.clock_to_host || mpu->state.playing || (mpu->state.rec == M_RECON))
return;
mpu->clock.active = 0;
timer_disable(&mpu->mpu401_event_callback);
}
static void
MPU401_RunClock(mpu_t *mpu)
{
if (!mpu->clock.active) {
timer_disable(&mpu->mpu401_event_callback);
return;
}
timer_set_delay_u64(&mpu->mpu401_event_callback, (MPU401_TIMECONSTANT / mpu->clock.freq) * 1000 * TIMER_USEC);
mpu401_log("Next event after %i us (time constant: %i)\n", (uint64_t) ((MPU401_TIMECONSTANT / mpu->clock.freq) * 1000 * TIMER_USEC), (int) MPU401_TIMECONSTANT);
}
static void
MPU401_QueueByteEx(mpu_t *mpu, uint8_t data, UNUSED(int irq))
{
if (mpu->state.block_ack) {
mpu->state.block_ack = 0;
return;
}
if (mpu->queue_used == 0) {
if (mpu->ext_irq_update)
mpu->ext_irq_update(mpu->priv, 1);
else {
mpu->state.irq_pending = 1;
picint(1 << mpu->irq);
}
}
if (mpu->queue_used < MPU401_QUEUE) {
int pos = mpu->queue_used + mpu->queue_pos;
if (mpu->queue_pos >= MPU401_QUEUE)
mpu->queue_pos -= MPU401_QUEUE;
if (pos >= MPU401_QUEUE)
pos -= MPU401_QUEUE;
mpu->queue_used++;
mpu->queue[pos] = data;
}
}
static void
MPU401_QueueByte(mpu_t *mpu, uint8_t data)
{
MPU401_QueueByteEx(mpu, data, 1);
}
static int
MPU401_IRQPending(mpu_t *mpu)
{
int irq_pending;
if (mpu->ext_irq_pending)
irq_pending = mpu->ext_irq_pending(mpu->priv);
else
irq_pending = mpu->state.irq_pending;
return irq_pending;
}
static void
MPU401_RecQueueBuffer(mpu_t *mpu, uint8_t *buf, uint32_t len, UNUSED(int block))
{
uint32_t cnt = 0;
int pos;
while (cnt < len) {
if (mpu->rec_queue_used < MPU401_INPUT_QUEUE) {
pos = mpu->rec_queue_used + mpu->rec_queue_pos;
if (pos >= MPU401_INPUT_QUEUE)
pos -= MPU401_INPUT_QUEUE;
mpu->rec_queue[pos] = buf[cnt];
mpu->rec_queue_used++;
if ((!mpu->state.sysex_in_finished) && (buf[cnt] == MSG_EOX)) {
/* finish sysex */
mpu->state.sysex_in_finished = 1;
break;
}
cnt++;
}
}
if (mpu->queue_used == 0) {
if (mpu->state.rec_copy || MPU401_IRQPending(mpu)) {
if (MPU401_IRQPending(mpu)) {
if (mpu->ext_irq_update)
mpu->ext_irq_update(mpu->priv, 0);
else {
mpu->state.irq_pending = 0;
picintc(1 << mpu->irq);
}
}
return;
}
mpu->state.rec_copy = 1;
if (mpu->rec_queue_pos >= MPU401_INPUT_QUEUE)
mpu->rec_queue_pos -= MPU401_INPUT_QUEUE;
MPU401_QueueByte(mpu, mpu->rec_queue[mpu->rec_queue_pos]);
mpu->rec_queue_used--;
mpu->rec_queue_pos++;
}
}
static void
MPU401_ClrQueue(mpu_t *mpu)
{
mpu->queue_used = 0;
mpu->queue_pos = 0;
mpu->rec_queue_used = 0;
mpu->rec_queue_pos = 0;
mpu->state.sysex_in_finished = 1;
if (mpu->ext_irq_update)
mpu->ext_irq_update(mpu->priv, 0);
else {
mpu->state.irq_pending = 0;
picintc(1 << mpu->irq);
}
}
static void
MPU401_Reset(mpu_t *mpu)
{
uint8_t i;
#ifdef DOSBOX_CODE
if (mpu->mode == M_INTELLIGENT) {
if (mpu->ext_irq_update)
mpu->ext_irq_update(mpu->priv, 0);
else {
mpu->state.irq_pending = 0;
picintc(1 << mpu->irq);
}
}
#else
if (mpu->ext_irq_update)
mpu->ext_irq_update(mpu->priv, 0);
else {
mpu->state.irq_pending = 0;
picintc(1 << mpu->irq);
}
#endif
mpu->mode = M_INTELLIGENT;
mpu->midi_thru = 0;
mpu->state.rec = M_RECOFF;
mpu->state.eoi_scheduled = 0;
mpu->state.wsd = 0;
mpu->state.wsm = 0;
mpu->state.conductor = 0;
mpu->state.cond_req = 0;
mpu->state.cond_set = 0;
mpu->state.playing = 0;
mpu->state.run_irq = 0;
mpu->state.cmask = 0xff;
mpu->state.amask = mpu->state.tmask = 0;
mpu->state.midi_mask = 0xffff;
mpu->state.command_byte = 0;
mpu->state.block_ack = 0;
mpu->clock.tempo = mpu->clock.old_tempo = 100;
mpu->clock.timebase = mpu->clock.old_timebase = 120;
mpu->clock.tempo_rel = mpu->clock.old_tempo_rel = 0x40;
mpu->clock.freq_mod = 1.0;
mpu->clock.tempo_grad = 0;
MPU401_StopClock(mpu);
MPU401_ReCalcClock(mpu);
for (i = 0; i < 4; i++)
mpu->clock.cth_rate[i] = 60;
mpu->clock.cth_counter = 0;
mpu->clock.midimetro = 12;
mpu->clock.metromeas = 8;
mpu->filter.rec_measure_end = 1;
mpu->filter.rt_out = 1;
mpu->filter.rt_affection = 1;
mpu->filter.allnotesoff_out = 1;
mpu->filter.all_thru = 1;
mpu->filter.midi_thru = 1;
mpu->filter.commonmsgs_thru = 1;
/* Reset channel reference and input tables. */
for (i = 0; i < 4; i++) {
mpu->chanref[i].on = 1;
mpu->chanref[i].chan = i;
mpu->ch_toref[i] = i;
}
for (i = 0; i < 16; i++) {
mpu->inputref[i].on = 1;
mpu->inputref[i].chan = i;
if (i > 3)
mpu->ch_toref[i] = 4; /* Dummy reftable. */
}
MPU401_ClrQueue(mpu);
mpu->state.data_onoff = -1;
mpu->state.req_mask = 0;
mpu->condbuf.counter = 0;
mpu->condbuf.type = T_OVERFLOW;
for (i = 0; i < 8; i++) {
mpu->playbuf[i].type = T_OVERFLOW;
mpu->playbuf[i].counter = 0;
}
/* Clear MIDI buffers, terminate notes. */
midi_clear_buffer();
for (i = 0xb0; i <= 0xbf; i++) {
midi_raw_out_byte(i);
midi_raw_out_byte(0x7b);
midi_raw_out_byte(0);
}
}
static void
MPU401_ResetDone(void *priv)
{
mpu_t *mpu = (mpu_t *) priv;
mpu401_log("MPU-401 reset callback\n");
timer_disable(&mpu->mpu401_reset_callback);
mpu->state.reset = 0;
if (mpu->state.cmd_pending) {
MPU401_WriteCommand(mpu, mpu->state.cmd_pending - 1);
mpu->state.cmd_pending = 0;
}
}
static void
MPU401_WriteCommand(mpu_t *mpu, uint8_t val)
{
uint8_t i;
uint8_t j;
uint8_t was_uart;
uint8_t recmsg[3];
if (mpu->state.reset)
mpu->state.cmd_pending = val + 1;
/* The only command recognized in UART mode is 0xFF: Reset and return to Intelligent mode. */
if ((val != 0xff) && (mpu->mode == M_UART))
return;
/* In Intelligent mode, UART-only variants of the MPU-401 only support commands 0x3F and 0xFF. */
if (!mpu->intelligent && (val != 0x3f) && (val != 0xff))
return;
/* Hack: Enable midi through after the first mpu401 command is written. */
mpu->midi_thru = 1;
if (val <= 0x2f) { /* Sequencer state */
int send_prchg = 0;
if ((val & 0xf) < 0xc) {
switch (val & 3) { /* MIDI realtime messages */
case 1:
mpu->state.last_rtcmd = 0xfc;
if (mpu->filter.rt_out)
midi_raw_out_rt_byte(0xfc);
mpu->clock.meas_old = mpu->clock.measure_counter;
mpu->clock.cth_old = mpu->clock.cth_counter;
break;
case 2:
mpu->state.last_rtcmd = 0xfa;
if (mpu->filter.rt_out)
midi_raw_out_rt_byte(0xfb);
mpu->clock.measure_counter = mpu->clock.meas_old = 0;
mpu->clock.cth_counter = mpu->clock.cth_old = 0;
break;
case 3:
mpu->state.last_rtcmd = 0xfc;
if (mpu->filter.rt_out)
midi_raw_out_rt_byte(0xfa);
mpu->clock.measure_counter = mpu->clock.meas_old;
mpu->clock.cth_counter = mpu->clock.cth_old;
break;
default:
break;
}
switch (val & 0xc) { /* Playing */
case 0x4: /* Stop */
mpu->state.playing = 0;
MPU401_StopClock(mpu);
for (i = 0; i < 16; i++)
MPU401_NotesOff(mpu, i);
mpu->filter.prchg_mask = 0;
break;
case 0x8: /* Start */
mpu->state.playing = 1;
MPU401_StartClock(mpu);
break;
default:
break;
}
switch (val & 0x30) { /* Recording */
case 0: /* check if it waited for MIDI RT command */
if (((val & 3) < 2) || !mpu->filter.rt_affection || (mpu->state.rec != M_RECSTB))
break;
mpu->state.rec = M_RECON;
MPU401_StartClock(mpu);
if (mpu->filter.prchg_mask)
send_prchg = 1;
break;
case 0x10: /* Stop */
mpu->state.rec = M_RECOFF;
MPU401_StopClock(mpu);
MPU401_QueueByte(mpu, MSG_MPU_ACK);
MPU401_QueueByte(mpu, mpu->clock.rec_counter);
MPU401_QueueByte(mpu, MSG_MPU_END);
mpu->filter.prchg_mask = 0;
mpu->clock.rec_counter = 0;
return;
case 0x20: /* Start */
if (!(mpu->state.rec == M_RECON)) {
mpu->clock.rec_counter = 0;
mpu->state.rec = M_RECSTB;
}
if ((mpu->state.last_rtcmd == 0xfa) || (mpu->state.last_rtcmd == 0xfb)) {
mpu->clock.rec_counter = 0;
mpu->state.rec = M_RECON;
if (mpu->filter.prchg_mask)
send_prchg = 1;
MPU401_StartClock(mpu);
}
break;
default:
break;
}
}
MPU401_QueueByte(mpu, MSG_MPU_ACK);
/* record counter hack: needed by Prism, but sent only on cmd 0x20/0x26 (or breaks Ballade) */
uint8_t rec_cnt = mpu->clock.rec_counter;
if (((val == 0x20) || (val == 0x26)) && (mpu->state.rec == M_RECON))
MPU401_RecQueueBuffer(mpu, &rec_cnt, 1, 0);
if (send_prchg) {
for (i = 0; i < 16; i++) {
if (mpu->filter.prchg_mask & (1 << i)) {
recmsg[0] = mpu->clock.rec_counter;
recmsg[1] = 0xc0 | i;
recmsg[2] = mpu->filter.prchg_buf[i];
MPU401_RecQueueBuffer(mpu, recmsg, 3, 0);
mpu->filter.prchg_mask &= ~(1 << i);
}
}
}
} else if ((val >= 0xa0) && (val <= 0xa7)) /* Request play counter */
MPU401_QueueByte(mpu, mpu->playbuf[val & 7].counter);
else if ((val >= 0xd0) && (val <= 0xd7)) { /* Send data */
mpu->state.old_track = mpu->state.track;
mpu->state.track = val & 7;
mpu->state.wsd = 1;
mpu->state.wsm = 0;
mpu->state.wsd_start = 1;
} else if ((val < 0x80) && (val >= 0x40)) { /* Set reference table channel */
mpu->chanref[(val >> 4) - 4].on = 1;
mpu->chanref[(val >> 4) - 4].chan = val & 0x0f;
mpu->chanref[(val >> 4) - 4].trmask = 0;
for (i = 0; i < 4; i++)
mpu->chanref[(val >> 4) - 4].key[i] = 0;
for (i = 0; i < 16; i++) {
if (mpu->ch_toref[i] == ((val >> 4) - 4))
mpu->ch_toref[i] = 4;
}
mpu->ch_toref[val & 0x0f] = (val >> 4) - 4;
} else
switch (val) {
case 0x30: /* Configuration 0x30 - 0x39 */
mpu->filter.allnotesoff_out = 0;
break;
case 0x32:
mpu->filter.rt_out = 0;
break;
case 0x33:
mpu->filter.all_thru = 0;
mpu->filter.commonmsgs_thru = 0;
mpu->filter.midi_thru = 0;
for (i = 0; i < 16; i++) {
mpu->inputref[i].on = 0;
for (j = 0; j < 4; j++)
mpu->inputref[i].key[j] = 0;
}
break;
case 0x34:
mpu->filter.timing_in_stop = 1;
break;
case 0x35:
mpu->filter.modemsgs_in = 1;
break;
case 0x37:
mpu->filter.sysex_thru = 1;
break;
case 0x38:
mpu->filter.commonmsgs_in = 1;
break;
case 0x39:
mpu->filter.rt_in = 1;
break;
case 0x3f: /* UART mode */
mpu401_log("MPU-401: Set UART mode %X\n", val);
MPU401_QueueByte(mpu, MSG_MPU_ACK);
mpu->mode = M_UART;
return;
case 0x80: /* Internal clock */
if (mpu->clock.active && mpu->state.sync_in) {
timer_set_delay_u64(&mpu->mpu401_event_callback, (MPU401_TIMECONSTANT / mpu->clock.freq) * 1000 * TIMER_USEC);
mpu->clock.freq_mod = 1.0;
}
mpu->state.sync_in = 0;
break;
case 0x81: /* Sync to tape signal */
case 0x82: /* Sync to MIDI */
mpu->clock.ticks_in = 0;
mpu->state.sync_in = 1;
break;
case 0x86:
case 0x87: /* Bender */
mpu->filter.bender_in = !!(val & 1);
break;
case 0x88:
case 0x89: /* MIDI through */
mpu->filter.midi_thru = !!(val & 1);
for (i = 0; i < 16; i++) {
mpu->inputref[i].on = mpu->filter.midi_thru;
if (!(val & 1)) {
for (j = 0; j < 4; j++)
mpu->inputref[i].key[j] = 0;
}
}
break;
case 0x8a:
case 0x8b: /* Data in stop */
mpu->filter.data_in_stop = !!(val & 1);
break;
case 0x8c:
case 0x8d: /* Send measure end */
mpu->filter.rec_measure_end = !!(val & 1);
break;
case 0x8e:
case 0x8f: /* Conductor */
mpu->state.cond_set = !!(val & 1);
break;
case 0x90:
case 0x91: /* Realtime affection */
mpu->filter.rt_affection = !!(val & 1);
break;
case 0x94: /* Clock to host */
mpu->state.clock_to_host = 0;
MPU401_StopClock(mpu);
break;
case 0x95:
mpu->state.clock_to_host = 1;
MPU401_StartClock(mpu);
break;
case 0x96:
case 0x97: /* Sysex input allow */
mpu->filter.sysex_in = !!(val & 1);
if (val & 1)
mpu->filter.sysex_thru = 0;
break;
case 0x98:
case 0x99:
case 0x9a:
case 0x9b: /* Reference tables on/off */
case 0x9c:
case 0x9d:
case 0x9e:
case 0x9f:
mpu->chanref[(val - 0x98) / 2].on = !!(val & 1);
break;
/* Commands 0xa# returning data */
case 0xab: /* Request and clear recording counter */
MPU401_QueueByte(mpu, MSG_MPU_ACK);
MPU401_QueueByte(mpu, 0);
return;
case 0xac: /* Request version */
MPU401_QueueByte(mpu, MSG_MPU_ACK);
MPU401_QueueByte(mpu, MPU401_VERSION);
return;
case 0xad: /* Request revision */
MPU401_QueueByte(mpu, MSG_MPU_ACK);
MPU401_QueueByte(mpu, MPU401_REVISION);
return;
case 0xaf: /* Request tempo */
MPU401_QueueByte(mpu, MSG_MPU_ACK);
MPU401_QueueByte(mpu, mpu->clock.tempo);
return;
case 0xb1: /* Reset relative tempo */
mpu->clock.old_tempo_rel = mpu->clock.tempo_rel;
mpu->clock.tempo_rel = 0x40;
break;
case 0xb8: /* Clear play counters */
mpu->state.last_rtcmd = 0;
for (i = 0; i < 8; i++) {
mpu->playbuf[i].counter = 0;
mpu->playbuf[i].type = T_OVERFLOW;
}
mpu->condbuf.counter = 0;
mpu->condbuf.type = T_OVERFLOW;
mpu->state.amask = mpu->state.tmask;
mpu->state.conductor = mpu->state.cond_set;
mpu->clock.cth_counter = mpu->clock.cth_old = 0;
mpu->clock.measure_counter = mpu->clock.meas_old = 0;
break;
case 0xb9: /* Clear play map */
for (i = 0; i < 16; i++)
MPU401_NotesOff(mpu, i);
for (i = 0; i < 8; i++) {
mpu->playbuf[i].counter = 0;
mpu->playbuf[i].type = T_OVERFLOW;
}
mpu->state.last_rtcmd = 0;
mpu->clock.cth_counter = mpu->clock.cth_old = 0;
mpu->clock.measure_counter = mpu->clock.meas_old = 0;
break;
case 0xba: /* Clear record counter */
mpu->clock.rec_counter = 0;
break;
case 0xc2:
case 0xc3:
case 0xc4: /* Internal timebase */
case 0xc5:
case 0xc6:
case 0xc7:
case 0xc8:
mpu->clock.timebase = MPUClockBase[val - 0xc2];
MPU401_ReCalcClock(mpu);
break;
case 0xdf: /* Send system message */
mpu->state.wsd = 0;
mpu->state.wsm = 1;
mpu->state.wsd_start = 1;
break;
/* Commands with data byte */
case 0xe0:
case 0xe1:
case 0xe2:
case 0xe4:
case 0xe6:
case 0xe7:
case 0xec:
case 0xed:
case 0xee:
case 0xef:
mpu->state.command_byte = val;
break;
case 0xff: /* Reset MPU-401 */
mpu401_log("MPU-401: Reset %X\n", val);
timer_set_delay_u64(&mpu->mpu401_reset_callback, MPU401_RESETBUSY * 33LL * TIMER_USEC);
mpu->state.reset = 1;
was_uart = (mpu->mode == M_UART);
MPU401_Reset(mpu);
if (was_uart)
return;
break;
default:
#if 0
mpu401_log("MPU-401:Unhandled command %X",val);
#endif
break;
}
MPU401_QueueByte(mpu, MSG_MPU_ACK);
}
static void
MPU401_WriteData(mpu_t *mpu, uint8_t val)
{
static int length;
static int cnt;
#ifdef DOSBOX_CODE
if (mpu->mode == M_UART) {
midi_raw_out_byte(val);
return;
}
if (!mpu->intelligent) {
mpu->state.command_byte = 0;
return;
}
#else
if (!mpu->intelligent || (mpu->mode == M_UART)) {
midi_raw_out_byte(val);
return;
}
#endif
switch (mpu->state.command_byte) { /* 0xe# command data */
case 0x00:
break;
case 0xe0: /* Set tempo */
mpu->state.command_byte = 0;
if (mpu->clock.tempo < 8)
mpu->clock.tempo = 8;
else if (mpu->clock.tempo > 250)
mpu->clock.tempo = 250;
else
mpu->clock.tempo = val;
MPU401_ReCalcClock(mpu);
return;
case 0xe1: /* Set relative tempo */
mpu->state.command_byte = 0;
mpu->clock.old_tempo_rel = mpu->clock.tempo_rel;
mpu->clock.tempo_rel = val;
MPU401_ReCalcClock(mpu);
return;
case 0xe2: /* Set gradation for relative tempo */
mpu->clock.tempo_grad = val;
MPU401_ReCalcClock(mpu);
return;
case 0xe4: /* Set MIDI clocks for metronome ticks */
mpu->state.command_byte = 0;
mpu->clock.midimetro = val;
return;
case 0xe6: /* Set metronome ticks per measure */
mpu->state.command_byte = 0;
mpu->clock.metromeas = val;
return;
case 0xe7: /* Set internal clock to host interval */
mpu->state.command_byte = 0;
if (!val)
val = 64;
for (uint8_t i = 0; i < 4; i++)
mpu->clock.cth_rate[i] = (val >> 2) + cth_data[(val & 3) * 4 + i];
mpu->clock.cth_mode = 0;
return;
case 0xec: /* Set active track mask */
mpu->state.command_byte = 0;
mpu->state.tmask = val;
return;
case 0xed: /* Set play counter mask */
mpu->state.command_byte = 0;
mpu->state.cmask = val;
return;
case 0xee: /* Set 1-8 MIDI channel mask */
mpu->state.command_byte = 0;
mpu->state.midi_mask &= 0xff00;
mpu->state.midi_mask |= val;
return;
case 0xef: /* Set 9-16 MIDI channel mask */
mpu->state.command_byte = 0;
mpu->state.midi_mask &= 0x00ff;
mpu->state.midi_mask |= ((uint16_t) val) << 8;
return;
default:
mpu->state.command_byte = 0;
return;
}
if (mpu->state.wsd && !mpu->state.track_req && !mpu->state.cond_req) {
/* Directly send MIDI message */
if (mpu->state.wsd_start) {
mpu->state.wsd_start = 0;
cnt = 0;
switch (val & 0xf0) {
case 0xc0:
case 0xd0:
length = mpu->playbuf[mpu->state.track].length = 2;
mpu->playbuf[mpu->state.track].type = T_MIDI_NORM;
break;
case 0x80:
case 0x90:
case 0xa0:
case 0xb0:
case 0xe0:
length = mpu->playbuf[mpu->state.track].length = 3;
mpu->playbuf[mpu->state.track].type = T_MIDI_NORM;
break;
case 0xf0:
#if 0
mpu401_log("MPU-401:Illegal WSD byte\n");
#endif
mpu->state.wsd = 0;
mpu->state.track = mpu->state.old_track;
return;
default: /* MIDI with running status */
cnt++;
length = mpu->playbuf[mpu->state.track].length;
mpu->playbuf[mpu->state.track].type = T_MIDI_NORM;
}
}
if (cnt < length) {
mpu->playbuf[mpu->state.track].value[cnt] = val;
cnt++;
}
if (cnt == length) {
MPU401_IntelligentOut(mpu, mpu->state.track);
mpu->state.wsd = 0;
mpu->state.track = mpu->state.old_track;
}
return;
}
if (mpu->state.wsm && !mpu->state.track_req && !mpu->state.cond_req) { /* Send system message */
if (mpu->state.wsd_start) {
mpu->state.wsd_start = 0;
cnt = 0;
switch (val) {
case 0xf2:
length = 3;
break;
case 0xf3:
length = 2;
break;
case 0xf6:
length = 1;
break;
case 0xf0:
length = 0;
break;
default:
mpu->state.wsm = 0;
return;
}
} else if (val & 0x80) {
midi_raw_out_byte(MSG_EOX);
mpu->state.wsm = 0;
return;
}
if (!length || (cnt < length)) {
midi_raw_out_byte(val);
cnt++;
}
if (cnt == length)
mpu->state.wsm = 0;
return;
}
if (mpu->state.cond_req) {
/* Command */
switch (mpu->state.data_onoff) {
case -1:
return;
case 0: /* Timing byte */
mpu->condbuf.length = 0;
if (val < 0xf0)
mpu->state.data_onoff++;
else {
mpu->state.cond_req = 0;
mpu->state.data_onoff = -1;
MPU401_EOIHandlerDispatch(mpu);
break;
}
mpu->state.send_now = !val ? 1 : 0;
mpu->condbuf.counter = val;
break;
case 1: /* Command byte #1 */
mpu->condbuf.type = T_COMMAND;
if ((val == 0xf8) || (val == 0xf9) || (val == 0xfc))
mpu->condbuf.type = T_OVERFLOW;
mpu->condbuf.value[mpu->condbuf.length] = val;
mpu->condbuf.length++;
if ((val & 0xf0) != 0xe0) { /*no cmd data byte*/
MPU401_EOIHandler(mpu);
mpu->state.data_onoff = -1;
mpu->state.cond_req = 0;
} else
mpu->state.data_onoff++;
break;
case 2: /* Command byte #2 */
mpu->condbuf.value[mpu->condbuf.length] = val;
mpu->condbuf.length++;
MPU401_EOIHandler(mpu);
mpu->state.data_onoff = -1;
mpu->state.cond_req = 0;
break;
default:
break;
}
return;
}
switch (mpu->state.data_onoff) {
/* Data */
case -1:
break;
case 0: /* Timing byte */
if (val < 0xf0)
mpu->state.data_onoff++;
else {
mpu->state.data_onoff = -1;
MPU401_EOIHandlerDispatch(mpu);
mpu->state.track_req = 0;
return;
}
mpu->state.send_now = !val ? 1 : 0;
mpu->playbuf[mpu->state.track].counter = val;
break;
case 1: /* MIDI */
cnt = 0;
mpu->state.data_onoff++;
switch (val & 0xf0) {
case 0xc0:
case 0xd0: /* MIDI Message */
length = mpu->playbuf[mpu->state.track].length = 2;
mpu->playbuf[mpu->state.track].type = T_MIDI_NORM;
break;
case 0x80:
case 0x90:
case 0xa0:
case 0xb0:
case 0xe0:
length = mpu->playbuf[mpu->state.track].length = 3;
mpu->playbuf[mpu->state.track].type = T_MIDI_NORM;
break;
case 0xf0: /* System message or mark */
mpu->playbuf[mpu->state.track].sys_val = val;
if (val > 0xf7) {
mpu->playbuf[mpu->state.track].type = T_MARK;
if (val == 0xf9)
mpu->clock.measure_counter = 0;
} else {
#if 0
mpu401_log("MPU-401:Illegal message");
#endif
mpu->playbuf[mpu->state.track].type = T_OVERFLOW;
}
mpu->state.data_onoff = -1;
MPU401_EOIHandler(mpu);
mpu->state.track_req = 0;
return;
default: /* MIDI with running status */
cnt++;
length = mpu->playbuf[mpu->state.track].length;
mpu->playbuf[mpu->state.track].type = T_MIDI_NORM;
break;
}
break;
case 2:
if (cnt < length) {
mpu->playbuf[mpu->state.track].value[cnt] = val;
cnt++;
}
if (cnt == length) {
mpu->state.data_onoff = -1;
mpu->state.track_req = 0;
MPU401_EOIHandler(mpu);
}
break;
default:
break;
}
return;
}
static void
MPU401_IntelligentOut(mpu_t *mpu, uint8_t track)
{
uint8_t chan;
uint8_t chrefnum;
uint8_t key;
uint8_t msg;
int send;
int retrigger;
switch (mpu->playbuf[track].type) {
case T_OVERFLOW:
break;
case T_MARK:
if (mpu->playbuf[track].sys_val == 0xfc) {
midi_raw_out_rt_byte(mpu->playbuf[track].sys_val);
mpu->state.amask &= ~(1 << track);
}
break;
case T_MIDI_NORM:
chan = mpu->playbuf[track].value[0] & 0xf;
key = mpu->playbuf[track].value[1] & 0x7f;
chrefnum = mpu->ch_toref[chan];
send = 1;
retrigger = 0;
switch (msg = mpu->playbuf[track].value[0] & 0xf0) {
case 0x80: /* note off */
if (mpu->inputref[chan].on && (mpu->inputref[chan].M_GETKEY))
send = 0;
if (mpu->chanref[chrefnum].on && (!(mpu->chanref[chrefnum].M_GETKEY)))
send = 0;
mpu->chanref[chrefnum].M_DELKEY;
break;
case 0x90: /* note on */
if (mpu->inputref[chan].on && (mpu->inputref[chan].M_GETKEY))
retrigger = 1;
if (mpu->chanref[chrefnum].on && (!(mpu->chanref[chrefnum].M_GETKEY)))
retrigger = 1;
mpu->chanref[chrefnum].M_SETKEY;
break;
case 0xb0:
if (mpu->playbuf[track].value[1] == 123) { /* All notes off */
MPU401_NotesOff(mpu, mpu->playbuf[track].value[0] & 0xf);
return;
}
break;
default:
break;
}
if (retrigger) {
midi_raw_out_byte(0x80 | chan);
midi_raw_out_byte(key);
midi_raw_out_byte(0);
}
if (send) {
for (uint8_t i = 0; i < mpu->playbuf[track].length; i++)
midi_raw_out_byte(mpu->playbuf[track].value[i]);
}
break;
default:
break;
}
}
static void
UpdateTrack(mpu_t *mpu, uint8_t track)
{
MPU401_IntelligentOut(mpu, track);
if (mpu->state.amask & (1 << track)) {
mpu->playbuf[track].type = T_OVERFLOW;
mpu->playbuf[track].counter = 0xf0;
mpu->state.req_mask |= (1 << track);
} else {
if ((mpu->state.amask == 0) && !mpu->state.conductor)
mpu->state.req_mask |= (1 << 12);
}
}
#if 0
static void
UpdateConductor(mpu_t *mpu)
{
if (mpu->condbuf.value[0] == 0xfc) {
mpu->condbuf.value[0] = 0;
mpu->state.conductor = 0;
mpu->state.req_mask &= ~(1 << 9);
if (mpu->state.amask == 0)
mpu->state.req_mask |= (1 << 12);
return;
}
mpu->condbuf.vlength = 0;
mpu->condbuf.counter = 0xf0;
mpu->state.req_mask |= (1 << 9);
}
#endif
/* Updates counters and requests new data on "End of Input" */
static void
MPU401_EOIHandler(void *priv)
{
mpu_t *mpu = (mpu_t *) priv;
uint8_t i;
mpu401_log("MPU-401 end of input callback\n");
timer_disable(&mpu->mpu401_eoi_callback);
mpu->state.eoi_scheduled = 0;
if (mpu->state.send_now) {
mpu->state.send_now = 0;
if (mpu->state.cond_req) {
mpu->condbuf.counter = 0xf0;
mpu->state.req_mask |= (1 << 9);
} else
UpdateTrack(mpu, mpu->state.track);
}
if (mpu->state.rec_copy || !mpu->state.sysex_in_finished)
return;
if (mpu->ext_irq_update)
mpu->ext_irq_update(mpu->priv, 0);
else {
mpu->state.irq_pending = 0;
picintc(1 << mpu->irq);
}
if (!(mpu->state.req_mask && mpu->clock.active))
return;
i = 0;
do {
if (mpu->state.req_mask & (1 << i)) {
MPU401_QueueByte(mpu, 0xf0 + i);
mpu->state.req_mask &= ~(1 << i);
break;
}
} while ((i++) < 16);
}
static void
MPU401_EOIHandlerDispatch(void *priv)
{
mpu_t *mpu = (mpu_t *) priv;
mpu401_log("EOI handler dispatch\n");
if (mpu->state.send_now) {
mpu->state.eoi_scheduled = 1;
timer_advance_u64(&mpu->mpu401_eoi_callback, 60LL * TIMER_USEC); /* Possibly a bit longer */
} else if (!mpu->state.eoi_scheduled)
MPU401_EOIHandler(mpu);
}
static void
imf_write(UNUSED(uint16_t addr), UNUSED(uint8_t val), UNUSED(void *priv))
{
mpu401_log("IMF:Wr %4X,%X\n", addr, val);
}
void
MPU401_ReadRaiseIRQ(mpu_t *mpu)
{
/* Clear IRQ. */
if (mpu->ext_irq_update)
mpu->ext_irq_update(mpu->priv, 0);
else {
mpu->state.irq_pending = 0;
picintc(1 << mpu->irq);
}
if (mpu->queue_used) {
/* Bytes remaining in queue, raise IRQ again. */
if (mpu->ext_irq_update)
mpu->ext_irq_update(mpu->priv, 1);
else {
mpu->state.irq_pending = 1;
picint(1 << mpu->irq);
}
}
}
uint8_t
MPU401_ReadData(mpu_t *mpu)
{
uint8_t ret;
ret = MSG_MPU_ACK;
if (mpu->queue_used) {
if (mpu->queue_pos >= MPU401_QUEUE)
mpu->queue_pos -= MPU401_QUEUE;
ret = mpu->queue[mpu->queue_pos];
mpu->queue_pos++;
mpu->queue_used--;
}
/* Shouldn't this check mpu->mode? */
#ifdef DOSBOX_CODE
if (mpu->mode == M_UART) {
MPU401_ReadRaiseIRQ(mpu);
return ret;
}
#else
if (!mpu->intelligent || (mpu->mode == M_UART)) {
MPU401_ReadRaiseIRQ(mpu);
return ret;
}
#endif
if (mpu->state.rec_copy && !mpu->rec_queue_used) {
mpu->state.rec_copy = 0;
MPU401_EOIHandler(mpu);
return ret;
}
/* Copy from recording buffer. */
if (!mpu->queue_used && mpu->rec_queue_used) {
mpu->state.rec_copy = 1;
if (mpu->rec_queue_pos >= MPU401_INPUT_QUEUE)
mpu->rec_queue_pos -= MPU401_INPUT_QUEUE;
MPU401_QueueByte(mpu, mpu->rec_queue[mpu->rec_queue_pos]);
mpu->rec_queue_pos++;
mpu->rec_queue_used--;
}
MPU401_ReadRaiseIRQ(mpu);
if ((ret >= 0xf0) && (ret <= 0xf7)) {
/* MIDI data request */
mpu->state.track = ret & 7;
mpu->state.data_onoff = 0;
mpu->state.cond_req = 0;
mpu->state.track_req = 1;
}
if (ret == MSG_MPU_COMMAND_REQ) {
mpu->state.data_onoff = 0;
mpu->state.cond_req = 1;
if (mpu->condbuf.type != T_OVERFLOW) {
mpu->state.block_ack = 1;
MPU401_WriteCommand(mpu, mpu->condbuf.value[0]);
if (mpu->state.command_byte)
MPU401_WriteData(mpu, mpu->condbuf.value[1]);
mpu->condbuf.type = T_OVERFLOW;
}
}
if ((ret == MSG_MPU_END) || (ret == MSG_MPU_CLOCK) || (ret == MSG_MPU_ACK) || (ret == MSG_MPU_OVERFLOW))
MPU401_EOIHandlerDispatch(mpu);
return ret;
}
void
mpu401_write(uint16_t addr, uint8_t val, void *priv)
{
mpu_t *mpu = (mpu_t *) priv;
/* mpu401_log("MPU401 Write Port %04X, val %x\n", addr, val); */
switch (addr & 1) {
case 0: /*Data*/
MPU401_WriteData(mpu, val);
mpu401_log("Write Data (0x330) %X\n", val);
break;
case 1: /*Command*/
MPU401_WriteCommand(mpu, val);
mpu401_log("Write Command (0x331) %x\n", val);
break;
default:
break;
}
}
uint8_t
mpu401_read(uint16_t addr, void *priv)
{
mpu_t *mpu = (mpu_t *) priv;
uint8_t ret = 0;
switch (addr & 1) {
case 0: /* Read Data */
ret = MPU401_ReadData(mpu);
mpu401_log("Read Data (0x330) %X\n", ret);
break;
case 1: /* Read Status */
if (mpu->state.cmd_pending)
ret = STATUS_OUTPUT_NOT_READY;
if (!mpu->queue_used)
ret = STATUS_INPUT_NOT_READY;
ret |= 0x3f;
mpu401_log("Read Status (0x331) %x\n", ret);
break;
default:
break;
}
/* mpu401_log("MPU401 Read Port %04X, ret %x\n", addr, ret); */
return ret;
}
static void
MPU401_Event(void *priv)
{
mpu_t *mpu = (mpu_t *) priv;
int max_meascnt;
mpu401_log("MPU-401 event callback\n");
#ifdef DOSBOX_CODE
if (mpu->mode == M_UART) {
timer_disable(&mpu->mpu401_event_callback);
return;
}
#else
if (!mpu->intelligent || (mpu->mode == M_UART)) {
timer_disable(&mpu->mpu401_event_callback);
return;
}
#endif
if (MPU401_IRQPending(mpu))
goto next_event;
if (mpu->state.playing) {
for (uint8_t i = 0; i < 8; i++) {
/* Decrease counters. */
if (mpu->state.amask & (1 << i)) {
mpu->playbuf[i].counter--;
if (mpu->playbuf[i].counter <= 0)
UpdateTrack(mpu, i);
}
}
if (mpu->state.conductor) {
mpu->condbuf.counter--;
if (mpu->condbuf.counter <= 0) {
mpu->condbuf.counter = 0xf0;
mpu->state.req_mask |= (1 << 9);
}
}
}
if (mpu->state.clock_to_host) {
mpu->clock.cth_counter++;
if (mpu->clock.cth_counter >= mpu->clock.cth_rate[mpu->clock.cth_mode]) {
mpu->clock.cth_counter = 0;
mpu->clock.cth_mode++;
mpu->clock.cth_mode %= 4;
mpu->state.req_mask |= (1 << 13);
}
}
if (mpu->state.rec == M_RECON) {
/* Recording. */
mpu->clock.rec_counter++;
if (mpu->clock.rec_counter >= 240) {
mpu->clock.rec_counter = 0;
mpu->state.req_mask |= (1 << 8);
}
}
if (mpu->state.playing || (mpu->state.rec == M_RECON)) {
max_meascnt = (mpu->clock.timebase * mpu->clock.midimetro * mpu->clock.metromeas) / 24;
if (max_meascnt != 0) {
/* Measure end. */
if (++mpu->clock.measure_counter >= max_meascnt) {
if (mpu->filter.rt_out)
midi_raw_out_rt_byte(0xf8);
mpu->clock.measure_counter = 0;
if (mpu->filter.rec_measure_end && (mpu->state.rec == M_RECON))
mpu->state.req_mask |= (1 << 12);
}
}
}
if (MPU401_IRQPending(mpu) && mpu->state.req_mask)
MPU401_EOIHandler(mpu);
next_event:
MPU401_RunClock(mpu);
if (mpu->state.sync_in)
mpu->clock.ticks_in++;
}
static void
MPU401_NotesOff(mpu_t *mpu, int i)
{
if (mpu->filter.allnotesoff_out && !(mpu->inputref[i].on && (mpu->inputref[i].key[0] | mpu->inputref[i].key[1] | mpu->inputref[i].key[2] | mpu->inputref[i].key[3]))) {
for (uint8_t j = 0; j < 4; j++)
mpu->chanref[mpu->ch_toref[i]].key[j] = 0;
midi_raw_out_byte(0xb0 | i);
midi_raw_out_byte(123);
midi_raw_out_byte(0);
} else if (mpu->chanref[mpu->ch_toref[i]].on) {
for (uint8_t key = 0; key < 128; key++) {
if ((mpu->chanref[mpu->ch_toref[i]].M_GETKEY) && !(mpu->inputref[i].on && (mpu->inputref[i].M_GETKEY))) {
midi_raw_out_byte(0x80 | i);
midi_raw_out_byte(key);
midi_raw_out_byte(0);
}
mpu->chanref[mpu->ch_toref[i]].M_DELKEY;
}
}
}
/*Input handler for SysEx */
int
MPU401_InputSysex(void *p, uint8_t *buffer, uint32_t len, int abort)
{
mpu_t *mpu = (mpu_t *) p;
int i;
uint8_t val_ff = 0xff;
mpu401_log("MPU401 Input Sysex\n");
#ifdef DOSBOX_CODE
if (mpu->mode == M_UART) {
#else
if (!mpu->intelligent || mpu->mode == M_UART) {
#endif
/* UART mode input. */
for (i = 0; i < len; i++)
MPU401_QueueByte(mpu, buffer[i]);
return 0;
}
if (mpu->filter.sysex_in) {
if (abort) {
mpu->state.sysex_in_finished = 1;
mpu->rec_queue_used = 0; /*reset also the input queue*/
return 0;
}
if (mpu->state.sysex_in_finished) {
if (mpu->rec_queue_used >= MPU401_INPUT_QUEUE)
return len;
MPU401_RecQueueBuffer(mpu, &val_ff, 1, 1);
mpu->state.sysex_in_finished = 0;
mpu->clock.rec_counter = 0;
}
if (mpu->rec_queue_used >= MPU401_INPUT_QUEUE)
return len;
int available = MPU401_INPUT_QUEUE - mpu->rec_queue_used;
if (available >= len) {
MPU401_RecQueueBuffer(mpu, buffer, len, 1);
return 0;
} else {
MPU401_RecQueueBuffer(mpu, buffer, available, 1);
if (mpu->state.sysex_in_finished)
return 0;
return (len - available);
}
} else if (mpu->filter.sysex_thru && mpu->midi_thru) {
midi_raw_out_byte(0xf0);
for (i = 0; i < len; i++)
midi_raw_out_byte(*(buffer + i));
}
return 0;
}
/*Input handler for MIDI*/
void
MPU401_InputMsg(void *p, uint8_t *msg, uint32_t len)
{
mpu_t *mpu = (mpu_t *) p;
int i;
int tick;
static uint8_t old_msg = 0;
uint8_t key;
uint8_t recdata[2];
uint8_t recmsg[4];
int send = 1;
int send_thru = 0;
int retrigger_thru = 0;
int chan;
int chrefnum;
/* Abort if sysex transfer is in progress. */
if (!mpu->state.sysex_in_finished) {
mpu401_log("SYSEX in progress\n");
return;
}
mpu401_log("MPU401 Input Msg\n");
#ifdef DOSBOX_CODE
if (mpu->mode == M_INTELLIGENT) {
#else
if (mpu->intelligent && (mpu->mode == M_INTELLIGENT)) {
#endif
if (msg[0] < 0x80) {
/* Expand running status */
msg[2] = msg[1];
msg[1] = msg[0];
msg[0] = old_msg;
}
old_msg = msg[0];
chan = msg[0] & 0xf;
chrefnum = mpu->ch_toref[chan];
key = msg[1] & 0x7f;
if (msg[0] < 0xf0) {
/* If non-system msg. */
if (!(mpu->state.midi_mask & (1 << chan)) && mpu->filter.all_thru)
send_thru = 1;
else if (mpu->filter.midi_thru)
send_thru = 1;
switch (msg[0] & 0xf0) {
case 0x80: /* Note off. */
if (send_thru) {
if (mpu->chanref[chrefnum].on && (mpu->chanref[chrefnum].M_GETKEY))
send_thru = 0;
if (!mpu->filter.midi_thru)
break;
if (!(mpu->inputref[chan].M_GETKEY))
send_thru = 0;
mpu->inputref[chan].M_DELKEY;
}
break;
case 0x90: /* Note on. */
if (send_thru) {
if (mpu->chanref[chrefnum].on && (mpu->chanref[chrefnum].M_GETKEY))
retrigger_thru = 1;
if (!mpu->filter.midi_thru)
break;
if (mpu->inputref[chan].M_GETKEY)
retrigger_thru = 1;
mpu->inputref[chan].M_SETKEY;
}
break;
case 0xb0:
if (msg[1] >= 120) {
send_thru = 0;
if (msg[1] == 123) {
/* All notes off. */
for (key = 0; key < 128; key++) {
if (!(mpu->chanref[chrefnum].on && (mpu->chanref[chrefnum].M_GETKEY))) {
if (mpu->inputref[chan].on && mpu->inputref[chan].M_GETKEY) {
midi_raw_out_byte(0x80 | chan);
midi_raw_out_byte(key);
midi_raw_out_byte(0);
}
mpu->inputref[chan].M_DELKEY;
}
}
}
}
break;
default:
break;
}
}
if ((msg[0] >= 0xf0) || (mpu->state.midi_mask & (1 << chan))) {
switch (msg[0] & 0xf0) {
case 0xa0: /* Aftertouch. */
if (!mpu->filter.bender_in)
send = 0;
break;
case 0xb0: /* Control change. */
if (!mpu->filter.bender_in && (msg[1] < 64))
send = 0;
if (msg[1] >= 120) {
if (mpu->filter.modemsgs_in)
send = 1;
}
break;
case 0xc0: /* Program change. */
if ((mpu->state.rec != M_RECON) && !mpu->filter.data_in_stop) {
mpu->filter.prchg_buf[chan] = msg[1];
mpu->filter.prchg_mask |= 1 << chan;
}
break;
case 0xd0: /* Ch pressure. */
case 0xe0: /* Pitch wheel. */
if (!mpu->filter.bender_in)
send = 0;
break;
case 0xf0: /* System message. */
if (msg[0] == 0xf8) {
send = 0;
if (mpu->clock.active && mpu->state.sync_in) {
send = 0; /* Don't pass to host in this mode? */
tick = mpu->clock.timebase / 24;
if (mpu->clock.ticks_in != tick) {
if (!mpu->clock.ticks_in || (mpu->clock.ticks_in > (tick * 2)))
mpu->clock.freq_mod *= 2.0;
else {
if (ABS(mpu->clock.ticks_in - tick) == 1)
mpu->clock.freq_mod /= mpu->clock.ticks_in / (float) (tick * 2);
else
mpu->clock.freq_mod /= mpu->clock.ticks_in / (float) (tick);
}
MPU401_ReCalcClock(mpu);
}
mpu->clock.ticks_in = 0;
}
} else if (msg[0] > 0xf8) { /* Realtime. */
if (!(mpu->filter.rt_in && (msg[0] <= 0xfc) && (msg[0] >= 0xfa))) {
recdata[0] = 0xff;
recdata[1] = msg[0];
MPU401_RecQueueBuffer(mpu, recdata, 2, 1);
send = 0;
}
} else { /* Common or system. */
send = 0;
if ((msg[0] == 0xf2) || (msg[0] == 0xf3) || (msg[0] == 0xf6)) {
if (mpu->filter.commonmsgs_in)
send = 1;
if (mpu->filter.commonmsgs_thru)
for (i = 0; i < len; i++)
midi_raw_out_byte(msg[i]);
}
}
if (send) {
recmsg[0] = 0xff;
recmsg[1] = msg[0];
recmsg[2] = msg[1];
recmsg[3] = msg[2];
MPU401_RecQueueBuffer(mpu, recmsg, len + 1, 1);
}
if (mpu->filter.rt_affection) {
switch (msg[0]) {
case 0xf2:
case 0xf3:
mpu->state.block_ack = 1;
MPU401_WriteCommand(mpu, 0xb8); /* Clear play counters. */
break;
case 0xfa:
mpu->state.block_ack = 1;
MPU401_WriteCommand(mpu, 0xa); /* Start, play. */
if (mpu->filter.rt_out)
midi_raw_out_rt_byte(msg[0]);
break;
case 0xfb:
mpu->state.block_ack = 1;
MPU401_WriteCommand(mpu, 0xb); /* Continue, play. */
if (mpu->filter.rt_out)
midi_raw_out_rt_byte(msg[0]);
break;
case 0xfc:
mpu->state.block_ack = 1;
MPU401_WriteCommand(mpu, 0xd); /* Stop: Play, rec, midi */
if (mpu->filter.rt_out)
midi_raw_out_rt_byte(msg[0]);
break;
default:
break;
}
return;
}
break;
default:
break;
}
}
if (send_thru && mpu->midi_thru) {
if (retrigger_thru) {
midi_raw_out_byte(0x80 | (msg[0] & 0xf));
midi_raw_out_byte(msg[1]);
midi_raw_out_byte(msg[2]);
}
for (i = 0; i < len; i++)
midi_raw_out_byte(msg[i]);
}
if (send) {
if (mpu->state.rec == M_RECON) {
recmsg[0] = mpu->clock.rec_counter;
recmsg[1] = msg[0];
recmsg[2] = msg[1];
recmsg[3] = msg[2];
MPU401_RecQueueBuffer(mpu, recmsg, len + 1, 1);
mpu->clock.rec_counter = 0;
} else if (mpu->filter.data_in_stop) {
if (mpu->filter.timing_in_stop) {
recmsg[0] = 0;
recmsg[1] = msg[0];
recmsg[2] = msg[1];
recmsg[3] = msg[2];
MPU401_RecQueueBuffer(mpu, recmsg, len + 1, 1);
} else {
recmsg[0] = msg[0];
recmsg[1] = msg[1];
recmsg[2] = msg[2];
recmsg[3] = 0;
MPU401_RecQueueBuffer(mpu, recmsg, len, 1);
}
}
}
return;
}
/* UART mode input. */
for (i = 0; i < len; i++)
MPU401_QueueByte(mpu, msg[i]);
}
void
mpu401_setirq(mpu_t *mpu, int irq)
{
mpu->irq = irq;
}
void
mpu401_change_addr(mpu_t *mpu, uint16_t addr)
{
if (mpu == NULL)
return;
if (mpu->addr)
io_removehandler(mpu->addr, 2,
mpu401_read, NULL, NULL, mpu401_write, NULL, NULL, mpu);
mpu->addr = addr;
if (mpu->addr)
io_sethandler(mpu->addr, 2,
mpu401_read, NULL, NULL, mpu401_write, NULL, NULL, mpu);
}
void
mpu401_init(mpu_t *mpu, uint16_t addr, int irq, int mode, int receive_input)
{
mpu->status = STATUS_INPUT_NOT_READY;
mpu->irq = irq;
mpu->queue_used = 0;
mpu->queue_pos = 0;
mpu->mode = M_UART;
mpu->addr = addr;
/* Expalantion:
MPU-401 starting in intelligent mode = Full MPU-401 intelligent mode capability;
MPU-401 starting in UART mode = Reduced MPU-401 intelligent mode capability seen on the Sound Blaster 16/AWE32,
only supporting commands 3F (set UART mode) and FF (reset). */
mpu->intelligent = (mode == M_INTELLIGENT) ? 1 : 0;
mpu401_log("Starting as %s (mode is %s)\n", mpu->intelligent ? "INTELLIGENT" : "UART", (mode == M_INTELLIGENT) ? "INTELLIGENT" : "UART");
if (mpu->addr)
io_sethandler(mpu->addr, 2,
mpu401_read, NULL, NULL, mpu401_write, NULL, NULL, mpu);
io_sethandler(0x2A20, 16,
NULL, NULL, NULL, imf_write, NULL, NULL, mpu);
timer_add(&mpu->mpu401_event_callback, MPU401_Event, mpu, 0);
timer_add(&mpu->mpu401_eoi_callback, MPU401_EOIHandler, mpu, 0);
timer_add(&mpu->mpu401_reset_callback, MPU401_ResetDone, mpu, 0);
MPU401_Reset(mpu);
if (receive_input)
midi_in_handler(1, MPU401_InputMsg, MPU401_InputSysex, mpu);
}
void
mpu401_device_add(void)
{
if (!mpu401_standalone_enable)
return;
if (machine_has_bus(machine, MACHINE_BUS_MCA))
device_add(&mpu401_mca_device);
else
device_add(&mpu401_device);
}
static uint8_t
mpu401_mca_read(int port, void *priv)
{
const mpu_t *mpu = (mpu_t *) priv;
return mpu->pos_regs[port & 7];
}
static void
mpu401_mca_write(int port, uint8_t val, void *priv)
{
mpu_t *mpu = (mpu_t *) priv;
uint16_t addr;
if (port < 0x102)
return;
addr = (mpu->pos_regs[2] & 2) ? 0x0330 : 0x1330;
port &= 7;
mpu->pos_regs[port] = val;
if (port == 2) {
io_removehandler(addr, 2,
mpu401_read, NULL, NULL, mpu401_write, NULL, NULL, mpu);
addr = (mpu->pos_regs[2] & 2) ? 0x1330 : 0x0330;
io_sethandler(addr, 2,
mpu401_read, NULL, NULL, mpu401_write, NULL, NULL, mpu);
}
}
static uint8_t
mpu401_mca_feedb(UNUSED(void *priv))
{
return 1;
}
void
mpu401_irq_attach(mpu_t *mpu, void (*ext_irq_update)(void *priv, int set), int (*ext_irq_pending)(void *priv), void *priv)
{
mpu->ext_irq_update = ext_irq_update;
mpu->ext_irq_pending = ext_irq_pending;
mpu->priv = priv;
}
static void *
mpu401_standalone_init(const device_t *info)
{
mpu_t *mpu;
int irq;
uint16_t base;
mpu = malloc(sizeof(mpu_t));
memset(mpu, 0, sizeof(mpu_t));
mpu401_log("mpu_init\n");
if (info->flags & DEVICE_MCA) {
mca_add(mpu401_mca_read, mpu401_mca_write, mpu401_mca_feedb, NULL, mpu);
mpu->pos_regs[0] = 0x0F;
mpu->pos_regs[1] = 0x6C;
base = 0; /* Tell mpu401_init() that this is the MCA variant. */
/* According to @6c0f.adf, the IRQ is supposed to be fixed to 2.
This is only true for earlier models. Later ones have selectable IRQ. */
irq = device_get_config_int("irq");
} else {
base = device_get_config_hex16("base");
irq = device_get_config_int("irq");
}
mpu401_init(mpu, base, irq, M_INTELLIGENT, device_get_config_int("receive_input"));
return mpu;
}
static void
mpu401_standalone_close(void *priv)
{
mpu_t *mpu = (mpu_t *) priv;
free(mpu);
}
static const device_config_t mpu401_standalone_config[] = {
// clang-format off
{
.name = "base",
.description = "MPU-401 Address",
.type = CONFIG_HEX16,
.default_string = "",
.default_int = 0x330,
.file_filter = "",
.spinner = { 0 },
.selection = {
{
.description = "0x220",
.value = 0x220
},
{
.description = "0x230",
.value = 0x230
},
{
.description = "0x240",
.value = 0x240
},
{
.description = "0x250",
.value = 0x250
},
{
.description = "0x300",
.value = 0x300
},
{
.description = "0x320",
.value = 0x320
},
{
.description = "0x330",
.value = 0x330
},
{
.description = "0x332",
.value = 0x332
},
{
.description = "0x334",
.value = 0x334
},
{
.description = "0x336",
.value = 0x336
},
{
.description = "0x340",
.value = 0x340
},
{
.description = "0x350",
.value = 0x350
},
{ .description = "" }
}
},
{
.name = "irq",
.description = "MPU-401 IRQ",
.type = CONFIG_SELECTION,
.default_string = "",
.default_int = 2,
.file_filter = "",
.spinner = { 0 },
.selection = {
{
.description = "IRQ 2",
.value = 2
},
{
.description = "IRQ 3",
.value = 3
},
{
.description = "IRQ 4",
.value = 4
},
{
.description = "IRQ 5",
.value = 5
},
{
.description = "IRQ 6",
.value = 6
},
{
.description = "IRQ 7",
.value = 7
},
{ .description = "" }
}
},
{
.name = "receive_input",
.description = "Receive input",
.type = CONFIG_BINARY,
.default_int = 1
},
{ .name = "", .description = "", .type = CONFIG_END }
// clang-format on
};
static const device_config_t mpu401_standalone_mca_config[] = {
// clang-format off
{
.name = "irq",
.description = "MPU-401 IRQ",
.type = CONFIG_SELECTION,
.default_string = "",
.default_int = 9,
.file_filter = "",
.spinner = { 0 },
.selection = {
{
.description = "IRQ 3",
.value = 3
},
{
.description = "IRQ 4",
.value = 4
},
{
.description = "IRQ 5",
.value = 5
},
{
.description = "IRQ 6",
.value = 6
},
{
.description = "IRQ 7",
.value = 7
},
{
.description = "IRQ 9",
.value = 9
},
{ .description = "" }
}
},
{
.name = "receive_input",
.description = "Receive input",
.type = CONFIG_BINARY,
.default_int = 1
},
{ .name = "", .description = "", .type = CONFIG_END }
// clang-format on
};
const device_t mpu401_device = {
.name = "Roland MPU-IPC-T",
.internal_name = "mpu401",
.flags = DEVICE_ISA,
.local = 0,
.init = mpu401_standalone_init,
.close = mpu401_standalone_close,
.reset = NULL,
{ .available = NULL },
.speed_changed = NULL,
.force_redraw = NULL,
.config = mpu401_standalone_config
};
const device_t mpu401_mca_device = {
.name = "Roland MPU-IMC",
.internal_name = "mpu401_mca",
.flags = DEVICE_MCA,
.local = 0,
.init = mpu401_standalone_init,
.close = mpu401_standalone_close,
.reset = NULL,
{ .available = NULL },
.speed_changed = NULL,
.force_redraw = NULL,
.config = mpu401_standalone_mca_config
};