/* * Copyright (C) 2002-2005 The DOSBox Team * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* $Id: mpu401.cpp,v 1.15 2005-03-25 11:52:32 qbix79 Exp $ */ #include #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 void MPU401_Event(Bitu); static void MPU401_Reset(void); static void MPU401_EOIHandler(void); #define MPU401_VERSION 0x15 #define MPU401_REVISION 0x01 #define MPU401_QUEUE 32 #define MPU401_TIMECONSTANT (60000000/1000.0f) enum MpuMode { M_UART,M_INTELLIGENT }; enum MpuDataType {OVERFLOW,MARK,MIDI_SYS,MIDI_NORM,COMMAND}; /* Messages sent to MPU-401 from host */ #define MSG_EOX 0xf7 #define MSG_OVERFLOW 0xf8 #define MSG_MARK 0xfc /* Messages sent to host from MPU-401 */ #define MSG_MPU_OVERFLOW 0xf8 #define MSG_MPU_COMMAND_REQ 0xf9 #define MSG_MPU_END 0xfc #define MSG_MPU_CLOCK 0xfd #define MSG_MPU_ACK 0xfe static struct { bool intelligent; MpuMode mode; Bitu irq; Bit8u queue[MPU401_QUEUE]; Bitu queue_pos,queue_used; struct track { Bits counter; Bit8u value[8],sys_val; Bit8u vlength,length; MpuDataType type; } playbuf[8],condbuf; struct { bool conductor,cond_req,cond_set; bool playing,reset; bool wsd,wsm,wsd_start; 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,old_chan; } 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==0 && mpu.intelligent) { mpu.state.irq_pending=true; PIC_ActivateIRQ(mpu.irq); } if (mpu.queue_used=MPU401_QUEUE) mpu.queue_pos-=MPU401_QUEUE; if (pos>=MPU401_QUEUE) pos-=MPU401_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) { mpu.state.reset=0; if (val && val<=0x2f) { switch (val&3) { /* MIDI stop, start, continue */ 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); } break; case 0x8: /* Play */ LOG(LOG_MISC,LOG_NORMAL)("MPU-401:Intelligent mode playback started"); mpu.state.playing=true; PIC_RemoveEvents(MPU401_Event); PIC_AddEvent(MPU401_Event,MPU401_TIMECONSTANT/(mpu.clock.tempo*mpu.clock.timebase)); ClrQueue(); 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) { /* Send data */ mpu.state.old_chan=mpu.state.channel; mpu.state.channel=val&7; mpu.state.wsd=true; mpu.state.wsm=false; mpu.state.wsd_start=true; } else switch (val) { case 0xdf: /* Send system message */ mpu.state.wsd=false; mpu.state.wsm=true; mpu.state.wsd_start=true; break; case 0x8e: /* Conductor */ mpu.state.cond_set=false; break; case 0x8f: mpu.state.cond_set=true; break; case 0x94: /* Clock to host */ mpu.clock.clock_to_host=false; break; case 0x95: mpu.clock.clock_to_host=true; break; case 0xc2: /* Internal timebase */ mpu.clock.timebase=48; break; case 0xc3: mpu.clock.timebase=72; break; case 0xc4: mpu.clock.timebase=96; break; case 0xc5: mpu.clock.timebase=120; break; case 0xc6: mpu.clock.timebase=144; break; case 0xc7: mpu.clock.timebase=168; break; case 0xc8: mpu.clock.timebase=192; 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; /* Commands 0xa# returning data */ case 0xab: /* Request and clear recording counter */ QueueByte(MSG_MPU_ACK); QueueByte(0); return; case 0xac: /* Request version */ QueueByte(MSG_MPU_ACK); QueueByte(MPU401_VERSION); return; case 0xad: /* Request revision */ QueueByte(MSG_MPU_ACK); QueueByte(MPU401_REVISION); return; case 0xaf: /* Request tempo */ QueueByte(MSG_MPU_ACK); QueueByte(mpu.clock.tempo); return; case 0xb1: /* Reset relative tempo */ mpu.clock.tempo_rel=40; break; case 0xb9: /* Clear play map */ case 0xb8: /* 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; mpu.state.irq_pending=true; break; case 0xff: /* Reset MPU-401 */ LOG(LOG_MISC,LOG_NORMAL)("MPU-401:Reset %X",val); mpu.state.reset=1; MPU401_Reset(); break; case 0x3f: /* UART mode */ LOG(LOG_MISC,LOG_NORMAL)("MPU-401:Set UART mode %X",val); mpu.mode=M_UART; break; default:; //LOG(LOG_MISC,LOG_NORMAL)("MPU-401:Unhandled command %X",val); } QueueByte(MSG_MPU_ACK); } static Bitu MPU401_ReadData(Bitu port,Bitu iolen) { Bit8u ret=MSG_MPU_ACK; if (mpu.queue_used) { ret=mpu.queue[mpu.queue_pos]; if (mpu.queue_pos>=MPU401_QUEUE) mpu.queue_pos-=MPU401_QUEUE; mpu.queue_pos++;mpu.queue_used--; } if (!mpu.intelligent) return ret; if (ret>=0xf0 && ret<=0xf7) { /* MIDI data request */ mpu.state.channel=ret&7; mpu.state.data_onoff=0; mpu.state.cond_req=false; } if (ret==MSG_MPU_COMMAND_REQ) { mpu.state.data_onoff=0; mpu.state.cond_req=true; } if (ret==MSG_MPU_END || ret==MSG_MPU_CLOCK || ret==MSG_MPU_ACK) { 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;} switch (mpu.state.command_byte) { /* 0xe# command data */ case 0x00: break; case 0xe0: /* Set tempo */ mpu.state.command_byte=0; mpu.clock.tempo=val; return; case 0xe1: /* Set relative tempo */ mpu.state.command_byte=0; LOG(LOG_MISC,LOG_ERROR)("MPU-401:Relative tempo not implemented"); return; case 0xe7: /* Set internal clock to host interval */ mpu.state.command_byte=0; mpu.clock.cth_rate=val>>2; 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|=((Bit16u)val)<<8; return; //case 0xe2: /* Set graduation for relative tempo */ //case 0xe4: /* Set metronome */ //case 0xe6: /* Set metronome measure length */ default: mpu.state.command_byte=0; return; } static Bitu length,cnt,posd; if (mpu.state.wsd) { /* Directly send MIDI message */ 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; mpu.state.channel=mpu.state.old_chan; return; default: /* MIDI with running status */ cnt++; MIDI_RawOutByte(mpu.playbuf[mpu.state.channel].value[0]); } } if (cnt0xf7) { mpu.playbuf[mpu.state.channel].type=MARK; mpu.playbuf[mpu.state.channel].sys_val=val; length=1; } else { LOG(LOG_MISC,LOG_ERROR)("MPU-401:Illegal message"); mpu.playbuf[mpu.state.channel].type=MIDI_SYS; mpu.playbuf[mpu.state.channel].sys_val=val; 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; } } if (!(posd==1 && val>=0xf0)) 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].sys_val; if (val==0xfc) { MIDI_RawOutByte(val); mpu.state.amask&=~(1<= 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,MPU401_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; Bitu i=0; do { if (mpu.state.req_mask&(1<(configuration); if(!section->Get_bool("mpu401")) return; if (!MIDI_Available()) return; /*Enabled and there is a Midi */ installed = true; /* Install a new irq 9 handler that is more suited for the mpu401 */ callbackhandler.Install(&MPU401_INT71_Handler,CB_IRET,"irq 9 mpu"); callbackhandler.Set_RealVec(0x71); WriteHandler[0].Install(0x330,&MPU401_WriteData,IO_MB); WriteHandler[1].Install(0x331,&MPU401_WriteCommand,IO_MB); ReadHandler[0].Install(0x330,&MPU401_ReadData,IO_MB); ReadHandler[1].Install(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; /*Set IRQ and unmask it(for timequest/princess maker 2) */ mpu.irq=9; PIC_SetIRQMask(mpu.irq,false); MPU401_Reset(); } ~MPU401(){ if(!installed) return; Section_prop * section=static_cast(m_configuration); if(!section->Get_bool("intelligent")) return; PIC_SetIRQMask(mpu.irq,true); } }; static MPU401* test; void MPU401_Destroy(Section* sec){ delete test; } void MPU401_Init(Section* sec) { test = new MPU401(sec); sec->AddDestroyFunction(&MPU401_Destroy,true); }