diff --git a/src/SOUND/snd_mpu401.c b/src/SOUND/snd_mpu401.c new file mode 100644 index 000000000..b56283e05 --- /dev/null +++ b/src/SOUND/snd_mpu401.c @@ -0,0 +1,647 @@ +#include "../ibm.h" +#include "../io.h" +#include "../pic.h" +#include "../timer.h" +#include "../plat-midi.h" +#include "snd_mpu401.h" + +enum +{ + STATUS_OUTPUT_NOT_READY = 0x40, + STATUS_INPUT_NOT_READY = 0x80 +}; + +static void MPU401_WriteCommand(mpu_t *mpu, uint8_t val); +static void MPU401_EOIHandlerDispatch(void *p); + +static int mpu401_event_callback = 0; + +static void QueueByte(mpu_t *mpu, uint8_t data) +{ + if (mpu->state.block_ack) + { + mpu->state.block_ack=0; + return; + } + + if (mpu->queue_used == 0 && mpu->intelligent) + { + mpu->state.irq_pending=1; + //PIC_ActivateIRQ(mpu->irq); + 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; + } + else + pclog("MPU401:Data queue full\n"); +} + +static void ClrQueue(mpu_t *mpu) +{ + mpu->queue_used=0; + mpu->queue_pos=0; +} + +static void MPU401_Reset(mpu_t *mpu) +{ + uint8_t i; + + picintc(1 << mpu->irq); + mpu->mode=(mpu->intelligent ? M_INTELLIGENT : M_UART); + 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.irq_pending=0; + 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->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=40; + mpu->clock.tempo_grad=0; + mpu->clock.clock_to_host=0; + mpu->clock.cth_rate=60; + mpu->clock.cth_counter=0; + ClrQueue(mpu); + 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;} +} + +static void MPU401_ResetDone(mpu_t *mpu, int reset) +{ + 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; + + if (mpu->state.reset) + { + mpu->state.cmd_pending=val+1; + return; + } + + if (val<=0x2f) + { + switch (val&3) + { /* MIDI stop, start, continue */ + case 1: {midi_write(0xfc);break;} + case 2: {midi_write(0xfa);break;} + case 3: {midi_write(0xfb);break;} + } +// if (val&0x20) LOG(LOG_MISC,LOG_ERROR)("MPU-401:Unhandled Recording Command %x",(int)val); + switch (val&0xc) + { + case 0x4: /* Stop */ + mpu->state.playing=0; + for (i=0xb0;i<0xbf;i++) + { /* All notes off */ + midi_write(i); + midi_write(0x7b); + midi_write(0); + } + break; + case 0x8: /* Play */ +// LOG(LOG_MISC,LOG_NORMAL)("MPU-401:Intelligent mode playback started"); + mpu->state.playing=1; + mpu401_event_callback = MPU401_TIMECONSTANT / (mpu->clock.tempo*mpu->clock.timebase); + ClrQueue(mpu); + break; + } + } + else if (val>=0xa0 && val<=0xa7) + { /* Request play counter */ + if (mpu->state.cmask&(1<<(val&7))) QueueByte(mpu, 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=1; + mpu->state.wsm=0; + mpu->state.wsd_start=1; + } + else + switch (val) + { + case 0xdf: /* Send system message */ + mpu->state.wsd=0; + mpu->state.wsm=1; + mpu->state.wsd_start=1; + break; + case 0x8e: /* Conductor */ + mpu->state.cond_set=0; + break; + case 0x8f: + mpu->state.cond_set=1; + break; + case 0x94: /* Clock to host */ + mpu->clock.clock_to_host=0; + break; + case 0x95: + mpu->clock.clock_to_host=1; + 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(mpu, MSG_MPU_ACK); + QueueByte(mpu, 0); + return; + case 0xac: /* Request version */ + QueueByte(mpu, MSG_MPU_ACK); + QueueByte(mpu, MPU401_VERSION); + return; + case 0xad: /* Request revision */ + QueueByte(mpu, MSG_MPU_ACK); + QueueByte(mpu, MPU401_REVISION); + return; + case 0xaf: /* Request tempo */ + QueueByte(mpu, MSG_MPU_ACK); + QueueByte(mpu, 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 (i=0xb0;i<0xbf;i++) + { /* All notes off */ + midi_write(i); + midi_write(0x7b); + midi_write(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; + 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=1; + break; + case 0xff: /* Reset MPU-401 */ + pclog("MPU-401:Reset %X\n",val); + MPU401_ResetDone(mpu, MPU401_RESETBUSY); + mpu->state.reset=1; + MPU401_Reset(mpu); + if (mpu->mode==M_UART) return;//do not send ack in UART mode + break; + case 0x3f: /* UART mode */ + pclog("MPU-401:Set UART mode %X\n",val); + mpu->mode=M_UART; + break; + default:; + //LOG(LOG_MISC,LOG_NORMAL)("MPU-401:Unhandled command %X",val); + } + QueueByte(mpu, MSG_MPU_ACK); +} + +static void MPU401_WriteData(mpu_t *mpu, uint8_t val) +{ + if (mpu->mode==M_UART) {midi_write(val);} + + 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; + if (val!=0x40) //default value + pclog("MPU-401:Relative tempo change not implemented\n"); + 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|=((uint16_t)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 int 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: + //pclog("MPU-401:Illegal WSD byte\n"); + mpu->state.wsd=0; + mpu->state.channel=mpu->state.old_chan; + return; + default: /* MIDI with running status */ + cnt++; + midi_write(mpu->playbuf[mpu->state.channel].value[0]); + } + } + if (cntstate.wsd=0; + mpu->state.channel=mpu->state.old_chan; + } + return; + } + if (mpu->state.wsm) + { /* Directly send system message */ + if (val==MSG_EOX) {midi_write(MSG_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 || cntstate.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_EOIHandlerDispatch(mpu); + return; + } + if (val==0) mpu->state.send_now=1; + else mpu->state.send_now=0; + mpu->condbuf.counter=val; + break; + case 1: /* Command byte #1 */ + mpu->condbuf.type=T_COMMAND; + if (val==0xf8 || val==0xf9) mpu->condbuf.type=T_OVERFLOW; + mpu->condbuf.value[mpu->condbuf.vlength]=val; + mpu->condbuf.vlength++; + if ((val&0xf0)!=0xe0) MPU401_EOIHandlerDispatch(mpu); + else mpu->state.data_onoff++; + break; + case 2:/* Command byte #2 */ + mpu->condbuf.value[mpu->condbuf.vlength]=val; + mpu->condbuf.vlength++; + MPU401_EOIHandlerDispatch(mpu); + 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_EOIHandlerDispatch(mpu); + return; + } + if (val==0) mpu->state.send_now=1; + else mpu->state.send_now=0; + 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=T_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=T_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=T_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=T_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=T_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_EOIHandlerDispatch(mpu); + } +} + +static void MPU401_IntelligentOut(mpu_t *mpu, uint8_t chan) +{ + uint8_t val; + uint8_t i; + switch (mpu->playbuf[chan].type) + { + case T_OVERFLOW: + break; + case T_MARK: + val=mpu->playbuf[chan].sys_val; + if (val==0xfc) + { + midi_write(val); + mpu->state.amask&=~(1<state.req_mask&=~(1<playbuf[chan].vlength;i++) + midi_write(mpu->playbuf[chan].value[i]); + break; + default: + break; + } +} + +static void UpdateTrack(mpu_t *mpu, uint8_t chan) +{ + MPU401_IntelligentOut(mpu, chan); + if (mpu->state.amask&(1<playbuf[chan].vlength=0; + mpu->playbuf[chan].type=T_OVERFLOW; + mpu->playbuf[chan].counter=0xf0; + mpu->state.req_mask|=(1<state.amask==0 && !mpu->state.conductor) mpu->state.req_mask|=(1<<12); + } +} + +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); +} + +//Updates counters and requests new data on "End of Input" +static void MPU401_EOIHandler(void *p, uint8_t val) +{ + mpu_t *mpu = (mpu_t *)p; + + mpu->state.eoi_scheduled=0; + if (mpu->state.send_now) + { + mpu->state.send_now=0; + if (mpu->state.cond_req) UpdateConductor(mpu); + else UpdateTrack(mpu, mpu->state.channel); + } + mpu->state.irq_pending=0; + if (!mpu->state.playing || !mpu->state.req_mask) return; + uint8_t i=0; + do { + if (mpu->state.req_mask&(1<state.req_mask&=~(1<state.send_now) + { + mpu->state.eoi_scheduled=1; + MPU401_EOIHandler(mpu, 0.06f); //Possible a bit longer + } + else if (!mpu->state.eoi_scheduled) + MPU401_EOIHandler(mpu, 0); +} + +static void mpu401_write(uint16_t addr, uint8_t val, void *p) +{ + mpu_t *mpu = (mpu_t *)p; + + pclog("MPU401 Write Port %04X, val %x\n", addr, val); + + switch (addr & 1) + { + case 0: /*Data*/ + MPU401_WriteData(mpu, val); + break; + + case 1: /*Command*/ + MPU401_WriteCommand(mpu, val); + break; + } +} + +static uint8_t mpu401_read(uint16_t addr, void *p) +{ + mpu_t *mpu = (mpu_t *)p; + uint8_t ret; + + switch (addr & 1) + { + case 0: //Read Data + 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--; + } + if (!mpu->intelligent) return ret; + + if (mpu->queue_used == 0) picintc(1 << mpu->irq); + + if (ret>=0xf0 && ret<=0xf7) + { /* MIDI data request */ + mpu->state.channel=ret&7; + mpu->state.data_onoff=0; + mpu->state.cond_req=0; + } + 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) { + mpu->state.data_onoff=-1; + MPU401_EOIHandlerDispatch(mpu); + } + break; + + case 1: //Read Status + mpu->status = 0x3f; /* Bits 6 and 7 clear */ + if (mpu->state.cmd_pending) ret|=STATUS_OUTPUT_NOT_READY; + if (!mpu->queue_used) ret|=STATUS_INPUT_NOT_READY; + return mpu->status; + } + pclog("MPU401 Read Port %04X, ret %x\n", addr, ret); + return ret; +} + + +static void MPU401_Event(void *p) +{ + mpu_t *mpu = (mpu_t *)p; + uint8_t i; + + if (mpu->mode==M_UART) return; + if (mpu->state.irq_pending) goto next_event; + for (i=0;i<8;i++) { /* Decrease counters */ + if (mpu->state.amask&(1<playbuf[i].counter--; + if (mpu->playbuf[i].counter<=0) UpdateTrack(mpu, i); + } + } + if (mpu->state.conductor) { + mpu->condbuf.counter--; + if (mpu->condbuf.counter<=0) UpdateConductor(mpu); + } + 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(mpu, 0); +next_event: + mpu401_event_callback = 0; + int new_time; + if ((new_time=mpu->clock.tempo*mpu->clock.timebase)==0) return; + mpu401_event_callback = MPU401_TIMECONSTANT/new_time; +} + +void mpu401_init(mpu_t *mpu, uint16_t addr, int irq, int mode) +{ + mpu->status = STATUS_INPUT_NOT_READY; + mpu->irq = irq; + mpu->queue_used = 0; + mpu->queue_pos = 0; + mpu->mode = mode; + + io_sethandler(addr, 0x0002, mpu401_read, NULL, NULL, mpu401_write, NULL, NULL, mpu); + timer_add(MPU401_Event, &mpu401_event_callback, &mpu401_event_callback, mpu); + + MPU401_Reset(mpu); +} diff --git a/src/SOUND/snd_mpu401.h b/src/SOUND/snd_mpu401.h new file mode 100644 index 000000000..d8eedc25b --- /dev/null +++ b/src/SOUND/snd_mpu401.h @@ -0,0 +1,61 @@ +#define MPU401_VERSION 0x15 +#define MPU401_REVISION 0x01 +#define MPU401_QUEUE 32 +#define MPU401_TIMECONSTANT (60000000/1000.0f) +#define MPU401_RESETBUSY 27.0f + +typedef enum MpuMode { M_UART,M_INTELLIGENT } MpuMode; +typedef enum MpuDataType {T_OVERFLOW,T_MARK,T_MIDI_SYS,T_MIDI_NORM,T_COMMAND} MpuDataType; + +/* 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 + +typedef struct mpu_t +{ + int intelligent; + MpuMode mode; + int irq; + uint8_t status; + uint8_t queue[MPU401_QUEUE]; + int queue_pos,queue_used; + struct track + { + int counter; + uint8_t value[8],sys_val; + uint8_t vlength,length; + MpuDataType type; + } playbuf[8],condbuf; + struct { + int conductor,cond_req,cond_set, block_ack; + int playing,reset; + int wsd,wsm,wsd_start; + int run_irq,irq_pending; + int send_now; + int eoi_scheduled; + int data_onoff; + uint32_t command_byte,cmd_pending; + uint8_t tmask,cmask,amask; + uint16_t midi_mask; + uint16_t req_mask; + uint8_t channel,old_chan; + } state; + struct { + uint8_t timebase,old_timebase; + uint8_t tempo,old_tempo; + uint8_t tempo_rel,old_tempo_rel; + uint8_t tempo_grad; + uint8_t cth_rate,cth_counter; + int clock_to_host,cth_active; + } clock; +} mpu_t; + +void mpu401_init(mpu_t *mpu, uint16_t addr, int irq, int mode);