742 lines
20 KiB
C++
742 lines
20 KiB
C++
#include <string.h>
|
|
#include "dosbox.h"
|
|
#include "inout.h"
|
|
#include "pic.h"
|
|
#include "setup.h"
|
|
#include "cpu.h"
|
|
#include "callback.h"
|
|
|
|
void MIDI_RawOutByte(Bit8u data);
|
|
bool MIDI_Available(void);
|
|
|
|
static Bitu call_irq9;
|
|
static void MPU401_Event(Bitu);
|
|
static void MPU401_Reset(void);
|
|
static void MPU401_EOIHandler(void);
|
|
|
|
#define MPU_QUEUE 32
|
|
#define TIMECONSTANT 60000000
|
|
|
|
enum MpuMode { M_UART,M_INTELLIGENT } ;
|
|
enum MpuDataType {OVERFLOW,MARK,MIDI_SYS,MIDI_NORM,COMMAND};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// I/O
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
#define MPU_STATUS_DSR (1 << 7)
|
|
#define MPU_STATUS_DRR (1 << 6)
|
|
#define MPU_STATUS_PAD (0xff & (~(MPU_STATUS_DRR | MPU_STATUS_DSR)))
|
|
|
|
#define MK_MPU_STATUS(dsr, drr)\
|
|
(((dsr) ? 0 : MPU_STATUS_DSR) | ((drr) ? 0 : MPU_STATUS_DRR) | MPU_STATUS_PAD)
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Commands
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
/** Copyright notice for MPU-401 intelligent-mode command constants *********
|
|
|
|
MPU-401 MIDI Interface Module v1.0
|
|
Copyright (c) 1991, Robin Davies. All Rights Reserved.
|
|
|
|
Robin Davies
|
|
224 3rd Avenue
|
|
Ottawa, Ontario
|
|
Canada. K1S 2K3.
|
|
|
|
updated by:
|
|
|
|
Larry Troxler, Compuserve 73520,1736
|
|
|
|
|
|
15.02.2004: updated and implemented intelligent mode
|
|
|
|
****************************************************************************/
|
|
|
|
// Start/Stop Commands
|
|
|
|
#define CMD_MIDI_STOP 0x01
|
|
#define CMD_MIDI_START 0x02
|
|
#define CMD_MIDI_CONTINUE 0x03
|
|
|
|
#define CMD_PLAY_STOP 0x04
|
|
#define CMD_PLAY_START 0x08
|
|
#define CMD_PLAY_CONTINUE 0x0c
|
|
|
|
#define CMD_RECORD_STOP 0x10
|
|
#define CMD_RECORD_START 0x20
|
|
|
|
// Commands
|
|
|
|
#define CMD_DISABLE_ALL_NOTES_OFF 0x30
|
|
#define CMD_DISABLE_REAL_TIME_OUT 0x32
|
|
#define CMD_DISABLE_ALL_THRU_OFF 0x33
|
|
#define CMD_TIMING_BYTE_ALWAYS 0x34
|
|
#define CMD_MODE_MESS_ON 0x35
|
|
#define CMD_EXCLUSIVE_THRU_ON 0x37
|
|
#define CMD_COMMON_TO_HOST_ON 0x38
|
|
#define CMD_REAL_TIME_TO_HOST_ON 0x39
|
|
#define CMD_UART_MODE 0x3f
|
|
|
|
#define CMD_INT_CLOCK 0x80
|
|
#define CMD_FSK_CLOCK 0x81
|
|
#define CMD_MIDI_CLOCK 0x82
|
|
#define CMD_METRONOME_ON 0x83
|
|
#define CMD_METRONOME_OFF 0x84
|
|
#define CMD_METRONOME_W_ACCENTS 0x85
|
|
#define CMD_BENDER_OFF 0x86
|
|
#define CMD_BENDER_ON 0x87
|
|
#define CMD_MIDI_THRU_OFF 0x88
|
|
#define CMD_MIDI_THRU_ON 0x89
|
|
#define CMD_DATA_IN_STOP_MODE_OFF 0x8a
|
|
#define CMD_DATA_IN_STOP_MODE_ON 0x8b
|
|
#define CMD_SEND_MEASURE_END_OFF 0x8c
|
|
#define CMD_SEND_MEASURE_END_ON 0x8d
|
|
#define CMD_CONDUCTOR_OFF 0x8e
|
|
#define CMD_CONDUCTOR_ON 0x8f
|
|
#define CMD_REAL_TIME_AFFECTION_OFF 0x90
|
|
#define CMD_REAL_TIME_AFFECTION_ON 0x91
|
|
#define CMD_FSK_TO_INTERNAL 0x92
|
|
#define CMD_FSK_TO_MIDI 0x93
|
|
#define CMD_CLOCK_TO_HOST_OFF 0x94
|
|
#define CMD_CLOCK_TO_HOST_ON 0x95
|
|
#define CMD_EXCLUSIVE_TO_HOST_OFF 0x96
|
|
#define CMD_EXCLUSIVE_TO_HOST_ON 0x97
|
|
|
|
#define CMD_RESET_RELATIVE_TEMPO 0xb1
|
|
#define CMD_CLEAR_PLAY_COUNTERS 0xb8
|
|
#define CMD_CLEAR_PLAY_MAP 0xb9
|
|
#define CMD_CLEAR_RECORD_COUNTER 0xba
|
|
#define CMD_TIMEBASE_48 0xc2
|
|
#define CMD_TIMEBASE_72 0xc3
|
|
#define CMD_TIMEBASE_96 0xc4
|
|
#define CMD_TIMEBASE_120 0xc5
|
|
#define CMD_TIMEBASE_144 0xc6
|
|
#define CMD_TIMEBASE_168 0xc7
|
|
#define CMD_TIMEBASE_192 0xc8
|
|
|
|
#define CMD_REQUEST_TO_SEND_DATA 0xd0 /* + track #! */
|
|
#define CMD_REQUEST_TO_SEND_SYSTEM_MSG 0xdf
|
|
|
|
#define CMD_SET_TEMPO 0xe0
|
|
#define CMD_RELATIVE_TEMPO 0xe1
|
|
#define CMD_RELATIVE_TEMPO_GRADUATION 0xe2
|
|
#define CMD_MIDI_METRONOME 0xe4
|
|
#define CMD_MEASURE_LENGTH 0xe6
|
|
#define CMD_INTERNAL_CLOCK_LENGTH_TO_HOST 0xe7
|
|
#define CMD_ACTIVE_TRACK_MASK 0xec
|
|
#define CMD_SEND_PLAY_COUNTER_MASK 0xed
|
|
#define CMD_MIDI_CHANNEL_MASK_LO 0xee
|
|
#define CMD_MIDI_CHANNEL_MASK_HI 0xef
|
|
|
|
#define CMD_EOX 0xf7
|
|
#define CMD_TIMING_OVERFLOW 0xf8
|
|
#define CMD_MPU_MARK 0xfc
|
|
#define CMD_RESET 0xff
|
|
|
|
// Commands that return data
|
|
|
|
#define CMD_REQUEST_PLAY_COUNTER 0xa0 /* + track # */
|
|
#define CMD_REQUEST_AND_CLEAR_REC_COUNTER 0xab
|
|
#define CMD_REQUEST_VERSION 0xac
|
|
#define CMD_REQUEST_REVISION 0xad
|
|
#define CMD_REQUEST_TEMPO 0xaf
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Messages
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
#define MSG_TIMING_OVERFLOW 0xf8
|
|
#define MSG_ALL_END 0xfc
|
|
#define MSG_CLOCK_TO_HOST 0xfd
|
|
#define MSG_CMD_ACK 0xfe
|
|
#define MSG_REQUEST_DATA 0xf0
|
|
#define MSG_REQUEST_COMMAND 0xf9
|
|
|
|
#define MPU_VERSION 0x15
|
|
#define MPU_REVISION 0x01
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
static struct {
|
|
bool intelligent;
|
|
MpuMode mode;
|
|
Bitu irq;
|
|
Bit8u queue[MPU_QUEUE];
|
|
Bitu queue_pos,queue_used;
|
|
struct track {
|
|
Bits counter;
|
|
Bit8u value[8];
|
|
Bit8u vlength,length;
|
|
MpuDataType type;
|
|
} playbuf[8],condbuf;
|
|
struct {
|
|
bool conductor,cond_req,cond_set;
|
|
bool allnotes,realtime,allthru;
|
|
bool playing;
|
|
bool wsd,wsm,wsd_start;
|
|
bool midi_thru;
|
|
bool run_irq,irq_pending;
|
|
bool send_now;
|
|
Bits data_onoff;
|
|
Bitu command_byte;
|
|
Bit8u tmask,cmask,amask;
|
|
Bit16u midi_mask;
|
|
Bit16u req_mask;
|
|
Bit8u channel;
|
|
} state;
|
|
struct {
|
|
Bit8u timebase,old_timebase;
|
|
Bit8u tempo,old_tempo;
|
|
Bit8u tempo_rel,old_tempo_rel;
|
|
Bit8u tempo_grad;
|
|
Bit8u cth_rate,cth_counter;
|
|
bool clock_to_host,cth_active;
|
|
} clock;
|
|
} mpu;
|
|
|
|
|
|
static void QueueByte(Bit8u data) {
|
|
if (mpu.queue_used<MPU_QUEUE) {
|
|
Bitu pos=mpu.queue_used+mpu.queue_pos;
|
|
if (mpu.queue_pos>=MPU_QUEUE) mpu.queue_pos-=MPU_QUEUE;
|
|
if (pos>=MPU_QUEUE) pos-=MPU_QUEUE;
|
|
mpu.queue_used++;
|
|
mpu.queue[pos]=data;
|
|
} else LOG(LOG_MISC,LOG_NORMAL)("MPU401:Data queue full");
|
|
}
|
|
|
|
static void ClrQueue(void) {
|
|
mpu.queue_used=0;
|
|
mpu.queue_pos=0;
|
|
}
|
|
|
|
static Bitu MPU401_ReadStatus(Bitu port,Bitu iolen) {
|
|
Bit8u ret=0x3f; /* Bits 6 and 7 clear */
|
|
if (!mpu.queue_used) ret|=0x80;
|
|
return ret;
|
|
}
|
|
|
|
static void MPU401_WriteCommand(Bitu port,Bitu val,Bitu iolen) {
|
|
LOG(LOG_MISC,LOG_NORMAL)("MPU-401:Command %x",val);
|
|
if (val && val<=0x2f) {
|
|
switch (val&3) {
|
|
case 1: {MIDI_RawOutByte(0xfc);break;}
|
|
case 2: {MIDI_RawOutByte(0xfa);break;}
|
|
case 3: {MIDI_RawOutByte(0xfb);break;}
|
|
}
|
|
if (val&0x20) LOG(LOG_MISC,LOG_ERROR)("MPU-401:Unhandled Recording Command %x",val);
|
|
switch (val&0xc) {
|
|
case 0x4: /* Stop */
|
|
PIC_RemoveEvents(MPU401_Event);
|
|
mpu.state.playing=false;
|
|
for (Bitu i=0xb0;i<0xbf;i++) {//All notes off
|
|
MIDI_RawOutByte(i);
|
|
MIDI_RawOutByte(0x7b);
|
|
MIDI_RawOutByte(0);
|
|
}
|
|
ClrQueue();
|
|
break;
|
|
case 0x8: /* Play */
|
|
mpu.state.playing=true;
|
|
PIC_RemoveEvents(MPU401_Event);
|
|
PIC_AddEvent(MPU401_Event,TIMECONSTANT/(mpu.clock.tempo*mpu.clock.timebase));
|
|
mpu.state.irq_pending=false;
|
|
break;
|
|
}
|
|
}
|
|
else if (val>=0xa0 && val<=0xa7) {/* Request play counter */
|
|
if (mpu.state.cmask&(1<<(val&7))) QueueByte(mpu.playbuf[val&7].counter);
|
|
}
|
|
else if (val>=0xd0 && val<=0xd7) { /* Request to send data */
|
|
mpu.state.channel=val&7;
|
|
mpu.state.wsd=true;
|
|
mpu.state.wsm=false;
|
|
mpu.state.wsd_start=true;
|
|
}
|
|
else
|
|
switch (val) {
|
|
case CMD_REQUEST_TO_SEND_SYSTEM_MSG:
|
|
mpu.state.wsd=false;
|
|
mpu.state.wsm=true;
|
|
mpu.state.wsd_start=true;
|
|
break;
|
|
case CMD_CONDUCTOR_ON:
|
|
mpu.state.cond_set=true;
|
|
break;
|
|
case CMD_CONDUCTOR_OFF:
|
|
mpu.state.cond_set=false;
|
|
break;
|
|
case CMD_CLOCK_TO_HOST_OFF:
|
|
mpu.clock.clock_to_host=false;
|
|
break;
|
|
case CMD_CLOCK_TO_HOST_ON:
|
|
mpu.clock.clock_to_host=true;
|
|
break;
|
|
case CMD_REAL_TIME_AFFECTION_OFF:
|
|
break;
|
|
case CMD_REAL_TIME_AFFECTION_ON:
|
|
LOG(LOG_MISC,LOG_ERROR)("MPU401:Unimplemented:Realtime affection:ON");
|
|
break;
|
|
case CMD_MIDI_THRU_OFF:
|
|
mpu.state.midi_thru=false;
|
|
break;
|
|
case CMD_MIDI_THRU_ON:
|
|
mpu.state.midi_thru=true;
|
|
break;
|
|
case CMD_TIMEBASE_48: /* Internal clock resolution per beat */
|
|
mpu.clock.timebase=48;
|
|
break;
|
|
case CMD_TIMEBASE_72:
|
|
mpu.clock.timebase=72;
|
|
break;
|
|
case CMD_TIMEBASE_96:
|
|
mpu.clock.timebase=96;
|
|
break;
|
|
case CMD_TIMEBASE_120:
|
|
mpu.clock.timebase=120;
|
|
break;
|
|
case CMD_TIMEBASE_144:
|
|
mpu.clock.timebase=144;
|
|
break;
|
|
case CMD_TIMEBASE_168:
|
|
mpu.clock.timebase=168;
|
|
break;
|
|
case CMD_TIMEBASE_192:
|
|
mpu.clock.timebase=192;
|
|
break;
|
|
/* Commands with data byte */
|
|
case CMD_MIDI_METRONOME:
|
|
case CMD_MEASURE_LENGTH:
|
|
case CMD_RELATIVE_TEMPO:
|
|
case CMD_SET_TEMPO:
|
|
case CMD_RELATIVE_TEMPO_GRADUATION:
|
|
case CMD_INTERNAL_CLOCK_LENGTH_TO_HOST:
|
|
case CMD_ACTIVE_TRACK_MASK:
|
|
case CMD_SEND_PLAY_COUNTER_MASK:
|
|
case CMD_MIDI_CHANNEL_MASK_LO:
|
|
case CMD_MIDI_CHANNEL_MASK_HI:
|
|
mpu.state.command_byte=val;
|
|
break;
|
|
/* Commands Returning Data */
|
|
case CMD_REQUEST_VERSION:
|
|
QueueByte(MSG_CMD_ACK);
|
|
QueueByte(MPU_VERSION);
|
|
return;
|
|
case CMD_REQUEST_REVISION:
|
|
QueueByte(MSG_CMD_ACK);
|
|
QueueByte(MPU_REVISION);
|
|
return;
|
|
case CMD_REQUEST_TEMPO:
|
|
QueueByte(MSG_CMD_ACK);
|
|
QueueByte(mpu.clock.tempo);
|
|
return;
|
|
case CMD_REQUEST_AND_CLEAR_REC_COUNTER:
|
|
QueueByte(MSG_CMD_ACK);
|
|
QueueByte(0);
|
|
return;
|
|
case CMD_RESET_RELATIVE_TEMPO:
|
|
mpu.clock.tempo_rel=40;
|
|
break;
|
|
case CMD_CLEAR_PLAY_MAP:
|
|
mpu.state.tmask=0;
|
|
case CMD_CLEAR_PLAY_COUNTERS:
|
|
for (Bitu i=0xb0;i<0xbf;i++) {//All notes off
|
|
MIDI_RawOutByte(i);
|
|
MIDI_RawOutByte(0x7b);
|
|
MIDI_RawOutByte(0);
|
|
}
|
|
for (Bitu i=0;i<8;i++) {
|
|
mpu.playbuf[i].counter=0;
|
|
mpu.playbuf[i].type=OVERFLOW;
|
|
}
|
|
mpu.condbuf.counter=0;
|
|
mpu.condbuf.type=OVERFLOW;
|
|
if (!(mpu.state.conductor=mpu.state.cond_set)) mpu.state.cond_req=0;
|
|
mpu.state.amask=mpu.state.tmask;
|
|
mpu.state.req_mask=0;
|
|
break;
|
|
case CMD_RESET: /* Reset MPU401 */
|
|
MPU401_Reset();
|
|
if (mpu.intelligent) {
|
|
QueueByte(MSG_CMD_ACK); //additional ACK for interrupt routine
|
|
mpu.state.irq_pending=true;
|
|
PIC_ActivateIRQ(mpu.irq); //UNDOCUMENTED
|
|
}
|
|
break;
|
|
/* Initialization Commands */
|
|
case CMD_UART_MODE:
|
|
mpu.mode=M_UART;
|
|
break;
|
|
case CMD_DISABLE_ALL_NOTES_OFF:
|
|
mpu.state.allnotes=false;
|
|
break;
|
|
case CMD_DISABLE_REAL_TIME_OUT:
|
|
mpu.state.realtime=false;
|
|
break;
|
|
case CMD_DISABLE_ALL_THRU_OFF:
|
|
mpu.state.allthru=false;
|
|
break;
|
|
default:
|
|
LOG(LOG_MISC,LOG_NORMAL)("MPU-401:Unhandled command %X",val);
|
|
}
|
|
QueueByte(MSG_CMD_ACK);
|
|
}
|
|
|
|
static Bitu MPU401_ReadData(Bitu port,Bitu iolen) {
|
|
Bit8u ret=MSG_CMD_ACK;
|
|
if (mpu.queue_used) {
|
|
ret=mpu.queue[mpu.queue_pos];
|
|
if (mpu.queue_pos>=MPU_QUEUE) mpu.queue_pos-=MPU_QUEUE;
|
|
mpu.queue_pos++;mpu.queue_used--;
|
|
}
|
|
if (ret>=0xf0 && ret<=0xf7) {
|
|
mpu.state.channel=ret&7;
|
|
mpu.state.data_onoff=0;
|
|
mpu.state.cond_req=false;
|
|
}
|
|
if (ret==MSG_REQUEST_COMMAND) {
|
|
mpu.state.data_onoff=0;
|
|
mpu.state.cond_req=true;
|
|
}
|
|
if (ret==MSG_ALL_END || ret==MSG_CLOCK_TO_HOST) {
|
|
mpu.state.data_onoff=-1;
|
|
MPU401_EOIHandler();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void MPU401_WriteData(Bitu port,Bitu val,Bitu iolen) {
|
|
if (mpu.mode==M_UART) {MIDI_RawOutByte(val);return;}
|
|
if (mpu.state.command_byte) MPU401_EOIHandler(); /* S101, Time Quest */
|
|
switch (mpu.state.command_byte) {
|
|
case 0:
|
|
break;
|
|
case CMD_SET_TEMPO:
|
|
mpu.state.command_byte=0;
|
|
mpu.clock.tempo=val;
|
|
return;
|
|
case CMD_INTERNAL_CLOCK_LENGTH_TO_HOST:
|
|
mpu.state.command_byte=0;
|
|
mpu.clock.cth_rate=val>>2;
|
|
return;
|
|
case CMD_ACTIVE_TRACK_MASK:
|
|
mpu.state.command_byte=0;
|
|
mpu.state.tmask=val;
|
|
return;
|
|
case CMD_SEND_PLAY_COUNTER_MASK:
|
|
mpu.state.command_byte=0;
|
|
mpu.state.cmask=val;
|
|
return;
|
|
case CMD_MIDI_CHANNEL_MASK_LO:
|
|
mpu.state.command_byte=0;
|
|
mpu.state.midi_mask&=0xff00;
|
|
mpu.state.midi_mask|=val;
|
|
return;
|
|
case CMD_MIDI_CHANNEL_MASK_HI:
|
|
mpu.state.command_byte=0;
|
|
mpu.state.midi_mask&=0x00ff;
|
|
mpu.state.midi_mask|=((Bit16u)val)<<8;
|
|
return;
|
|
//case CMD_RELATIVE_TEMPO:
|
|
//case CMD_RELATIVE_TEMPO_GRADUATION:
|
|
//case CMD_MIDI_METRONOME:
|
|
//case CMD_MIDI_MEASURE:
|
|
default:
|
|
mpu.state.command_byte=0;
|
|
return;
|
|
}
|
|
static Bitu length,cnt,posd;
|
|
if (mpu.state.wsd) {
|
|
if (mpu.state.wsd_start) {
|
|
mpu.state.wsd_start=0;
|
|
cnt=0;
|
|
switch (val&0xf0) {
|
|
case 0xc0:case 0xd0:
|
|
mpu.playbuf[mpu.state.channel].value[0]=val;
|
|
length=2;
|
|
break;
|
|
case 0x80:case 0x90:case 0xa0:case 0xb0:case 0xe0:
|
|
mpu.playbuf[mpu.state.channel].value[0]=val;
|
|
length=3;
|
|
break;
|
|
case 0xf0:
|
|
LOG(LOG_MISC,LOG_ERROR)("MPU-401:Illegal WSD byte");
|
|
mpu.state.wsd=0;
|
|
return;
|
|
default: /* MIDI with running status */
|
|
cnt++;
|
|
MIDI_RawOutByte(mpu.playbuf[mpu.state.channel].value[0]);
|
|
}
|
|
}
|
|
if (cnt<length) {MIDI_RawOutByte(val);cnt++;}
|
|
if (cnt==length) mpu.state.wsd=0;
|
|
return;
|
|
}
|
|
if (mpu.state.wsm) {
|
|
if (val==CMD_EOX) {mpu.state.wsm=0;return;}
|
|
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:
|
|
length=0;
|
|
}
|
|
}
|
|
if (!length || cnt<length) {MIDI_RawOutByte(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.vlength=0;
|
|
if (val<0xf0) mpu.state.data_onoff++;
|
|
else {
|
|
mpu.state.data_onoff=-1;
|
|
MPU401_EOIHandler();
|
|
return;
|
|
}
|
|
if (val==0) mpu.state.send_now=true;
|
|
else mpu.state.send_now=false;
|
|
mpu.condbuf.counter=val;
|
|
break;
|
|
case 1: /* Command byte #1 */
|
|
mpu.condbuf.type=COMMAND;
|
|
if ((val&0xd0)==0xd0)
|
|
LOG(LOG_MISC,LOG_ERROR)("MPU-401:'Want to send data' used with conductor");
|
|
mpu.condbuf.value[mpu.condbuf.vlength]=val;
|
|
mpu.condbuf.vlength++;
|
|
if ((val&0xe0)!=0xe0) MPU401_EOIHandler();
|
|
else mpu.state.data_onoff++;
|
|
break;
|
|
case 2:/* Command byte #2 */
|
|
mpu.condbuf.value[mpu.condbuf.vlength]=val;
|
|
mpu.condbuf.vlength++;
|
|
MPU401_EOIHandler();
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
switch (mpu.state.data_onoff) { /* Data */
|
|
case -1:
|
|
return;
|
|
case 0: /* Timing byte */
|
|
if (val<0xf0) mpu.state.data_onoff=1;
|
|
else {
|
|
mpu.state.data_onoff=-1;
|
|
MPU401_EOIHandler();
|
|
return;
|
|
}
|
|
if (val==0) mpu.state.send_now=true;
|
|
else mpu.state.send_now=false;
|
|
mpu.playbuf[mpu.state.channel].counter=val;
|
|
break;
|
|
case 1: /* MIDI */
|
|
mpu.playbuf[mpu.state.channel].vlength++;
|
|
posd=mpu.playbuf[mpu.state.channel].vlength;
|
|
if (posd==1) {
|
|
switch (val&0xf0) {
|
|
case 0xf0: /* System message or mark */
|
|
if (val>0xf7) {
|
|
mpu.playbuf[mpu.state.channel].type=MARK;
|
|
length=1;
|
|
} else {
|
|
LOG(LOG_MISC,LOG_ERROR)("MPU-401:Illegal message");
|
|
mpu.playbuf[mpu.state.channel].type=MIDI_SYS;
|
|
length=1;
|
|
}
|
|
break;
|
|
case 0xc0: case 0xd0: /* MIDI Message */
|
|
mpu.playbuf[mpu.state.channel].type=MIDI_NORM;
|
|
length=mpu.playbuf[mpu.state.channel].length=2;
|
|
break;
|
|
case 0x80: case 0x90: case 0xa0: case 0xb0: case 0xe0:
|
|
mpu.playbuf[mpu.state.channel].type=MIDI_NORM;
|
|
length=mpu.playbuf[mpu.state.channel].length=3;
|
|
break;
|
|
default: /* MIDI data with running status */
|
|
posd++;
|
|
mpu.playbuf[mpu.state.channel].vlength++;
|
|
mpu.playbuf[mpu.state.channel].type=MIDI_NORM;
|
|
length=mpu.playbuf[mpu.state.channel].length;
|
|
break;
|
|
}
|
|
}
|
|
mpu.playbuf[mpu.state.channel].value[posd-1]=val;
|
|
if (posd==length) MPU401_EOIHandler();
|
|
}
|
|
}
|
|
|
|
static void MPU401_IntelligentOut(Bit8u chan) {
|
|
Bitu val;
|
|
switch (mpu.playbuf[chan].type) {
|
|
case OVERFLOW:
|
|
break;
|
|
case MARK:
|
|
val=mpu.playbuf[chan].value[0];
|
|
if (val==0xfc) {
|
|
MIDI_RawOutByte(val);
|
|
mpu.state.amask&=~(1<<chan);
|
|
mpu.state.req_mask&=~(1<<chan);
|
|
}
|
|
break;
|
|
case MIDI_NORM://TODO apply channel mask
|
|
for (Bitu i=0;i<mpu.playbuf[chan].vlength;i++)
|
|
MIDI_RawOutByte(mpu.playbuf[chan].value[i]);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void UpdateTrack(Bit8u chan) {
|
|
MPU401_IntelligentOut(chan);
|
|
if (mpu.state.amask&(1<<chan)) {
|
|
mpu.playbuf[chan].vlength=0;
|
|
mpu.playbuf[chan].type=OVERFLOW;
|
|
mpu.playbuf[chan].counter=0xf0;
|
|
mpu.state.req_mask|=(1<<chan);
|
|
} else {
|
|
if (mpu.state.amask==0) mpu.state.req_mask|=(1<<12);
|
|
}
|
|
}
|
|
|
|
static void UpdateConductor(void) {
|
|
if (mpu.condbuf.type!=OVERFLOW) {
|
|
MPU401_WriteCommand(0x331,mpu.condbuf.value[0],1);
|
|
if (mpu.state.command_byte) MPU401_WriteData(0x330,mpu.condbuf.value[1],1);
|
|
}
|
|
mpu.condbuf.vlength=0;
|
|
mpu.condbuf.type=OVERFLOW;
|
|
mpu.condbuf.counter=0xf0;
|
|
mpu.state.req_mask|=(1<<9);
|
|
}
|
|
|
|
static void MPU401_Event(Bitu val) {
|
|
if (mpu.mode==M_UART) return;
|
|
if (mpu.state.irq_pending) goto next_event;
|
|
for (Bitu i=0;i<8;i++) { /* Decrease play counters */
|
|
if (mpu.state.amask&(1<<i)) {
|
|
mpu.playbuf[i].counter--;
|
|
if (mpu.playbuf[i].counter<=0) UpdateTrack(i);
|
|
}
|
|
}
|
|
if (mpu.state.conductor) {
|
|
mpu.condbuf.counter--;
|
|
if (mpu.condbuf.counter<=0) UpdateConductor();
|
|
}
|
|
if (mpu.clock.clock_to_host) {
|
|
mpu.clock.cth_counter++;
|
|
if (mpu.clock.cth_counter >= mpu.clock.cth_rate) {
|
|
mpu.clock.cth_counter=0;
|
|
mpu.state.req_mask|=(1<<13);
|
|
}
|
|
}
|
|
if (!mpu.state.irq_pending && mpu.state.req_mask) MPU401_EOIHandler();
|
|
next_event:
|
|
PIC_RemoveEvents(MPU401_Event);
|
|
Bitu new_time;
|
|
if ((new_time=mpu.clock.tempo*mpu.clock.timebase)==0) return;
|
|
PIC_AddEvent(MPU401_Event,TIMECONSTANT/new_time);
|
|
}
|
|
|
|
|
|
static void MPU401_EOIHandler(void) {
|
|
if (mpu.state.send_now) {
|
|
mpu.state.send_now=false;
|
|
if (mpu.state.cond_req) UpdateConductor();
|
|
else UpdateTrack(mpu.state.channel);
|
|
}
|
|
mpu.state.irq_pending=false;
|
|
if (!mpu.state.playing || !mpu.state.req_mask) return;
|
|
ClrQueue();
|
|
Bitu i=0;
|
|
do {
|
|
if (mpu.state.req_mask&(1<<i)) {
|
|
QueueByte(0xf0+i);
|
|
mpu.state.req_mask&=~(1<<i);
|
|
break;
|
|
}
|
|
} while ((i++)<16);
|
|
#if 0
|
|
if (CPU_Cycles>=500) {
|
|
CPU_CycleLeft+=CPU_Cycles-500;
|
|
CPU_Cycles=500;
|
|
}
|
|
#endif
|
|
PIC_ActivateIRQ(mpu.irq);
|
|
mpu.state.irq_pending=true;
|
|
}
|
|
|
|
static Bitu INT71_Handler() {
|
|
CALLBACK_RunRealInt(0xa);
|
|
IO_Write(0xa0,0x61);
|
|
return CBRET_NONE;
|
|
}
|
|
|
|
static void MPU401_Reset(void) {
|
|
PIC_DeActivateIRQ(mpu.irq);
|
|
mpu.mode=(mpu.intelligent ? M_INTELLIGENT : M_UART);
|
|
mpu.state.wsd=false;
|
|
mpu.state.wsm=false;
|
|
mpu.state.conductor=false;
|
|
mpu.state.cond_req=false;
|
|
mpu.state.cond_set=false;
|
|
mpu.state.allnotes=true;
|
|
mpu.state.allthru=true;
|
|
mpu.state.realtime=true;
|
|
mpu.state.playing=false;
|
|
mpu.state.run_irq=false;
|
|
mpu.state.irq_pending=false;
|
|
mpu.state.cmask=0xff;
|
|
mpu.state.amask=mpu.state.tmask=0;
|
|
mpu.state.midi_mask=0xffff;
|
|
mpu.state.data_onoff=0;
|
|
mpu.state.command_byte=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=40;
|
|
mpu.clock.tempo_grad=0;
|
|
mpu.clock.clock_to_host=false;
|
|
mpu.clock.cth_rate=60;
|
|
mpu.clock.cth_counter=0;
|
|
ClrQueue();
|
|
mpu.state.req_mask=0;
|
|
mpu.condbuf.counter=0;
|
|
mpu.condbuf.type=OVERFLOW;
|
|
for (Bitu i=0;i<8;i++) {mpu.playbuf[i].type=OVERFLOW;mpu.playbuf[i].counter=0;}
|
|
}
|
|
|
|
void MPU401_Init(Section* sec) {
|
|
call_irq9=CALLBACK_Allocate(); //allocate handler for irq 9
|
|
CALLBACK_Setup(call_irq9,&INT71_Handler,CB_IRET);
|
|
RealSetVec(0x71,CALLBACK_RealPointer(call_irq9));
|
|
|
|
Section_prop * section=static_cast<Section_prop *>(sec);
|
|
if(!section->Get_bool("mpu401")) return;
|
|
|
|
if (!MIDI_Available()) return;
|
|
|
|
IO_RegisterWriteHandler(0x330,&MPU401_WriteData,IO_MB);
|
|
IO_RegisterWriteHandler(0x331,&MPU401_WriteCommand,IO_MB);
|
|
IO_RegisterReadHandler(0x330,&MPU401_ReadData,IO_MB);
|
|
IO_RegisterReadHandler(0x331,&MPU401_ReadStatus,IO_MB);
|
|
|
|
mpu.queue_used=0;
|
|
mpu.queue_pos=0;
|
|
mpu.mode=M_UART;
|
|
|
|
if (!(mpu.intelligent=section->Get_bool("intelligent"))) return;
|
|
mpu.irq=2;
|
|
MPU401_Reset();
|
|
}
|