|
|
1.1 root 1: /*
1.1.1.2 root 2: Hatari - midi.c
1.1 root 3:
1.1.1.9 root 4: This file is distributed under the GNU General Public License, version 2
5: or at your option any later version. Read the file gpl.txt for details.
1.1.1.2 root 6:
7: MIDI communication.
8:
9: TODO:
10: - Most bits in the ACIA's status + control registers are currently ignored.
1.1.1.10! root 11:
! 12: NOTE [NP] :
! 13: In all accuracy, we should use a complete emulation of the acia serial line,
! 14: as for the ikbd. But as the MIDI's baudrate is rather high and could require
! 15: more resources to emulate at the bit level, we handle transfer 1 byte a time
! 16: instead of sending each bit one after the other.
! 17: This way, we only need a timer every 2560 cycles (instead of 256 cycles per bit).
! 18:
! 19: We handle a special case for the TX_EMPTY bit when reading SR : this bit should be set
! 20: after TDR was copied into TSR, which is approximatively when the next bit should
! 21: be transferred (256 cycles) (fix the program 'Notator')
1.1 root 22: */
1.1.1.6 root 23: const char Midi_fileid[] = "Hatari midi.c : " __DATE__ " " __TIME__;
1.1.1.2 root 24:
25: #include <SDL_types.h>
1.1 root 26:
27: #include "main.h"
1.1.1.2 root 28: #include "configuration.h"
1.1.1.3 root 29: #include "ioMem.h"
1.1.1.4 root 30: #include "m68000.h"
1.1.1.10! root 31: #include "memorySnapShot.h"
1.1.1.2 root 32: #include "mfp.h"
33: #include "midi.h"
1.1.1.5 root 34: #include "file.h"
1.1.1.9 root 35: #include "acia.h"
1.1.1.10! root 36: #include "screen.h"
! 37: #include "video.h"
1.1.1.2 root 38:
39:
40: #define ACIA_SR_INTERRUPT_REQUEST 0x80
1.1.1.6 root 41: #define ACIA_SR_TX_EMPTY 0x02
42: #define ACIA_SR_RX_FULL 0x01
1.1.1.2 root 43:
1.1.1.10! root 44: /* Delay to send/receive 1 byte through MIDI (in cpu cycles)
! 45: * Serial line is set to 31250 bps, 1 start bit, 8 bits, 1 stop, no parity, which gives 256 cycles
! 46: * per bit at 8 MHz, and 2560 cycles to transfer 10 bits
! 47: */
! 48: #define MIDI_TRANSFER_BIT_CYCLE 256
! 49: #define MIDI_TRANSFER_BYTE_CYCLE (MIDI_TRANSFER_BIT_CYCLE * 10)
1.1.1.2 root 50:
51:
1.1.1.6 root 52: static FILE *pMidiFhIn = NULL; /* File handle used for Midi input */
53: static FILE *pMidiFhOut = NULL; /* File handle used for Midi output */
1.1.1.2 root 54: static Uint8 MidiControlRegister;
55: static Uint8 MidiStatusRegister;
1.1.1.6 root 56: static Uint8 nRxDataByte;
1.1.1.10! root 57: static Uint64 TDR_Write_Time; /* Time of the last write in TDR fffc06 */
! 58: static Uint64 TDR_Empty_Time; /* Time when TDR will be empty after a write to fffc06 (ie when TDR is transferred to TSR) */
! 59: static Uint64 TSR_Complete_Time; /* Time when TSR will be completely transferred */
! 60:
1.1.1.2 root 61:
62:
1.1.1.5 root 63: /**
64: * Initialization: Open MIDI device.
65: */
1.1.1.2 root 66: void Midi_Init(void)
67: {
1.1.1.5 root 68: if (!ConfigureParams.Midi.bEnableMidi)
69: return;
1.1.1.2 root 70:
1.1.1.6 root 71: if (ConfigureParams.Midi.sMidiOutFileName[0])
1.1.1.5 root 72: {
1.1.1.6 root 73: /* Open MIDI output file */
74: pMidiFhOut = File_Open(ConfigureParams.Midi.sMidiOutFileName, "wb");
75: if (!pMidiFhOut)
76: {
77: Log_AlertDlg(LOG_ERROR, "MIDI output file open failed. MIDI support disabled.");
1.1.1.7 root 78: ConfigureParams.Midi.bEnableMidi = false;
1.1.1.6 root 79: return;
80: }
81: setvbuf(pMidiFhOut, NULL, _IONBF, 0); /* No output buffering! */
1.1.1.10! root 82: LOG_TRACE(TRACE_MIDI, "MIDI: Opened file '%s' for output\n",
! 83: ConfigureParams.Midi.sMidiOutFileName);
1.1.1.6 root 84: }
85: if (ConfigureParams.Midi.sMidiInFileName[0])
86: {
87: /* Try to open MIDI input file */
88: pMidiFhIn = File_Open(ConfigureParams.Midi.sMidiInFileName, "rb");
89: if (!pMidiFhIn)
90: {
91: Log_AlertDlg(LOG_ERROR, "MIDI input file open failed. MIDI support disabled.");
1.1.1.7 root 92: ConfigureParams.Midi.bEnableMidi = false;
1.1.1.6 root 93: return;
94: }
95: setvbuf(pMidiFhIn, NULL, _IONBF, 0); /* No input buffering! */
1.1.1.10! root 96: LOG_TRACE(TRACE_MIDI, "MIDI: Opened file '%s' for input\n",
! 97: ConfigureParams.Midi.sMidiInFileName);
1.1.1.2 root 98: }
99: }
100:
101:
1.1.1.5 root 102: /**
103: * Close MIDI device.
104: */
1.1.1.2 root 105: void Midi_UnInit(void)
106: {
1.1.1.6 root 107: pMidiFhIn = File_Close(pMidiFhIn);
108: pMidiFhOut = File_Close(pMidiFhOut);
109:
1.1.1.8 root 110: CycInt_RemovePendingInterrupt(INTERRUPT_MIDI);
1.1.1.2 root 111: }
112:
113:
1.1.1.5 root 114: /**
1.1.1.6 root 115: * Reset MIDI emulation.
1.1.1.5 root 116: */
1.1.1.6 root 117: void Midi_Reset(void)
1.1.1.2 root 118: {
1.1.1.10! root 119: //fprintf ( stderr , "midi reset\n" );
1.1.1.6 root 120: MidiControlRegister = 0;
121: MidiStatusRegister = ACIA_SR_TX_EMPTY;
122: nRxDataByte = 1;
1.1.1.10! root 123: TDR_Empty_Time = 0;
! 124: TSR_Complete_Time = 0;
1.1.1.2 root 125:
1.1.1.10! root 126: /* Set timer */
! 127: CycInt_AddRelativeInterrupt ( MIDI_TRANSFER_BYTE_CYCLE , INT_CPU_CYCLE , INTERRUPT_MIDI );
! 128: }
! 129:
! 130:
! 131: /**
! 132: * Save/Restore snapshot of local variables
! 133: */
! 134: void MIDI_MemorySnapShot_Capture(bool bSave)
! 135: {
! 136: MemorySnapShot_Store(&MidiControlRegister, sizeof(MidiControlRegister));
! 137: MemorySnapShot_Store(&MidiStatusRegister, sizeof(MidiStatusRegister));
! 138: MemorySnapShot_Store(&nRxDataByte, sizeof(nRxDataByte));
! 139: MemorySnapShot_Store(&TDR_Empty_Time, sizeof(TDR_Empty_Time));
! 140: MemorySnapShot_Store(&TSR_Complete_Time, sizeof(TSR_Complete_Time));
! 141: }
! 142:
! 143:
! 144: /*-----------------------------------------------------------------------*/
! 145: /**
! 146: * Check if the IRQ must be changed in SR.
! 147: * When there's a change, we must change the IRQ line too.
! 148: */
! 149: static void MIDI_UpdateIRQ ( void )
! 150: {
! 151: Uint8 irq_bit_new;
! 152:
! 153: irq_bit_new = 0;
! 154:
! 155: if ( ( ( MidiControlRegister & 0x80 ) == 0x80 ) /* Check for RX causes of interrupt */
! 156: && ( MidiStatusRegister & ACIA_SR_RX_FULL ) )
! 157: irq_bit_new = ACIA_SR_INTERRUPT_REQUEST;
! 158:
! 159: if ( ( ( MidiControlRegister & 0x60) == 0x20 ) /* Check for TX causes of interrupt */
! 160: && ( MidiStatusRegister & ACIA_SR_TX_EMPTY ) )
! 161: irq_bit_new = ACIA_SR_INTERRUPT_REQUEST;
! 162:
! 163: /* Update SR and IRQ line if a change happened */
! 164: if ( ( MidiStatusRegister & ACIA_SR_INTERRUPT_REQUEST ) != irq_bit_new )
! 165: {
! 166: LOG_TRACE ( TRACE_MIDI, "midi update irq irq_new=%d VBL=%d HBL=%d\n" , irq_bit_new?1:0 , nVBLs , nHBL );
! 167:
! 168: if ( irq_bit_new )
! 169: {
! 170: /* Request interrupt by setting GPIP to low/0 */
! 171: MFP_GPIP_Set_Line_Input ( MFP_GPIP_LINE_ACIA , MFP_GPIP_STATE_LOW );
! 172: MidiStatusRegister |= ACIA_SR_INTERRUPT_REQUEST;
! 173: }
! 174: else
! 175: {
! 176: /* Clear interrupt request by setting GPIP to high/1 */
! 177: MFP_GPIP_Set_Line_Input ( MFP_GPIP_LINE_ACIA , MFP_GPIP_STATE_HIGH );
! 178: MidiStatusRegister &= ~ACIA_SR_INTERRUPT_REQUEST;
! 179: }
! 180: }
1.1.1.2 root 181: }
182:
183:
1.1.1.10! root 184:
1.1.1.5 root 185: /**
1.1.1.6 root 186: * Read MIDI status register ($FFFC04).
1.1.1.5 root 187: */
1.1.1.6 root 188: void Midi_Control_ReadByte(void)
1.1.1.2 root 189: {
1.1.1.9 root 190: ACIA_AddWaitCycles (); /* Additional cycles when accessing the ACIA */
1.1.1.4 root 191:
1.1.1.10! root 192: /* Special case : if we wrote a byte into TDR, TX_EMPTY bit should be */
! 193: /* set approximatively after the first bit was transferred using TSR */
! 194: if ( ( ( MidiStatusRegister & ACIA_SR_TX_EMPTY ) == 0 )
! 195: && ( CyclesGlobalClockCounter > TDR_Empty_Time ) ) // OK avec 11 bits et 1 bit
! 196: {
! 197: MidiStatusRegister |= ACIA_SR_TX_EMPTY;
! 198:
! 199: /* Do we need to generate a transfer interrupt? */
! 200: MIDI_UpdateIRQ ();
! 201: }
! 202:
! 203: //fprintf ( stderr , "midi read sr %x %lld %lld\n" , MidiStatusRegister , CyclesGlobalClockCounter , TDR_Write_Time );
! 204:
1.1.1.6 root 205: IoMem[0xfffc04] = MidiStatusRegister;
1.1.1.10! root 206:
! 207: LOG_TRACE ( TRACE_MIDI, "midi read fffc04 sr=0x%02x VBL=%d HBL=%d\n" , MidiStatusRegister , nVBLs , nHBL );
1.1.1.2 root 208: }
209:
1.1 root 210:
1.1.1.5 root 211: /**
212: * Write to MIDI control register ($FFFC04).
213: */
1.1.1.3 root 214: void Midi_Control_WriteByte(void)
1.1.1.2 root 215: {
1.1.1.9 root 216: ACIA_AddWaitCycles (); /* Additional cycles when accessing the ACIA */
1.1.1.4 root 217:
1.1.1.3 root 218: MidiControlRegister = IoMem[0xfffc04];
1.1.1.2 root 219:
1.1.1.10! root 220: LOG_TRACE ( TRACE_MIDI, "midi write fffc04 cr=0x%02x VBL=%d HBL=%d\n" , MidiControlRegister , nVBLs , nHBL );
1.1.1.2 root 221:
1.1.1.10! root 222: MIDI_UpdateIRQ ();
1.1.1.2 root 223: }
224:
225:
1.1.1.6 root 226: /**
227: * Read MIDI data register ($FFFC06).
228: */
229: void Midi_Data_ReadByte(void)
230: {
1.1.1.10! root 231: LOG_TRACE ( TRACE_MIDI, "midi read fffc06 rdr=0x%02x VBL=%d HBL=%d\n" , nRxDataByte , nVBLs , nHBL );
! 232: //fprintf ( stderr , "midi rx %x\n" , nRxDataByte);
1.1.1.6 root 233:
1.1.1.9 root 234: ACIA_AddWaitCycles (); /* Additional cycles when accessing the ACIA */
1.1.1.6 root 235:
1.1.1.10! root 236: IoMem[0xfffc06] = nRxDataByte;
1.1.1.6 root 237:
1.1.1.10! root 238: MidiStatusRegister &= ~ACIA_SR_RX_FULL;
1.1.1.6 root 239:
1.1.1.10! root 240: MIDI_UpdateIRQ ();
1.1.1.6 root 241: }
242:
243:
1.1.1.5 root 244: /**
245: * Write to MIDI data register ($FFFC06).
1.1.1.10! root 246: * We should determine precisely when TDR will be empty and when TSR will be transferred.
! 247: * This is required to accurately emulate the TDRE bit in status register (fix the program 'Notator')
1.1.1.5 root 248: */
1.1.1.3 root 249: void Midi_Data_WriteByte(void)
1.1.1.2 root 250: {
1.1.1.6 root 251: Uint8 nTxDataByte;
1.1.1.4 root 252:
1.1.1.9 root 253: ACIA_AddWaitCycles (); /* Additional cycles when accessing the ACIA */
1.1.1.4 root 254:
1.1.1.6 root 255: nTxDataByte = IoMem[0xfffc06];
1.1.1.10! root 256: TDR_Write_Time = CyclesGlobalClockCounter;
! 257:
! 258: /* If TSR is already transferred, then TDR will be empty after 1 bit is transferred */
! 259: /* If TSR is not completely transferred, then TDR will be empty 1 bit after TSR is transferred */
! 260: if ( CyclesGlobalClockCounter >= TSR_Complete_Time )
! 261: {
! 262: TDR_Empty_Time = CyclesGlobalClockCounter + MIDI_TRANSFER_BIT_CYCLE;
! 263: TSR_Complete_Time = CyclesGlobalClockCounter + MIDI_TRANSFER_BYTE_CYCLE;
! 264: }
! 265: else
! 266: {
! 267: //fprintf ( stderr , "MIDI OVR %lld\n" , TSR_Complete_Time - CyclesGlobalClockCounter );
! 268: TDR_Empty_Time = TSR_Complete_Time + MIDI_TRANSFER_BIT_CYCLE;
! 269: TSR_Complete_Time += MIDI_TRANSFER_BYTE_CYCLE;
! 270: }
! 271:
! 272: LOG_TRACE ( TRACE_MIDI, "midi write fffc06 tdr=0x%02x VBL=%d HBL=%d\n" , nTxDataByte , nVBLs , nHBL );
! 273: //fprintf ( stderr , "midi tx %x sr=%x\n" , nTxDataByte , MidiStatusRegister );
1.1.1.3 root 274:
1.1.1.10! root 275: MidiStatusRegister &= ~ACIA_SR_TX_EMPTY;
1.1.1.2 root 276:
1.1.1.10! root 277: MIDI_UpdateIRQ ();
1.1.1.2 root 278:
279: if (!ConfigureParams.Midi.bEnableMidi)
280: return;
281:
1.1.1.6 root 282: if (pMidiFhOut)
1.1.1.2 root 283: {
284: int ret;
1.1.1.6 root 285:
1.1.1.2 root 286: /* Write the character to the output file: */
1.1.1.6 root 287: ret = fputc(nTxDataByte, pMidiFhOut);
288:
1.1.1.2 root 289: /* If there was an error then stop the midi emulation */
290: if (ret == EOF)
291: {
1.1.1.10! root 292: LOG_TRACE(TRACE_MIDI, "MIDI: write error -> stop MIDI\n");
1.1.1.2 root 293: Midi_UnInit();
294: return;
295: }
296: }
1.1.1.6 root 297: }
298:
299:
300: /**
301: * Read and write MIDI interface data regularly
302: */
303: void Midi_InterruptHandler_Update(void)
304: {
305: int nInChar;
306:
307: /* Remove this interrupt from list and re-order */
1.1.1.8 root 308: CycInt_AcknowledgeInterrupt();
1.1.1.6 root 309:
1.1.1.10! root 310: /* Special case : if we wrote a byte into TDR, TX_EMPTY bit should be */
! 311: /* set when reaching TDR_Empty_Time */
! 312: if ( ( ( MidiStatusRegister & ACIA_SR_TX_EMPTY ) == 0 )
! 313: && ( CyclesGlobalClockCounter > TDR_Empty_Time ) )
1.1.1.2 root 314: {
1.1.1.10! root 315: MidiStatusRegister |= ACIA_SR_TX_EMPTY;
! 316:
1.1.1.6 root 317: /* Do we need to generate a transfer interrupt? */
1.1.1.10! root 318: MIDI_UpdateIRQ ();
1.1.1.2 root 319:
1.1.1.10! root 320: /* Flush outgoing data (not necessary ?) */
1.1.1.6 root 321: // if (pMidiFhOut)
322: // fflush(pMidiFhOut);
1.1.1.2 root 323: }
1.1.1.6 root 324:
325: /* Read the bytes in, if we have any */
326: if (pMidiFhIn && File_InputAvailable(pMidiFhIn))
327: {
328: nInChar = fgetc(pMidiFhIn);
329: if (nInChar != EOF)
330: {
1.1.1.10! root 331: LOG_TRACE(TRACE_MIDI, "MIDI: Read character -> $%x\n", nInChar);
1.1.1.6 root 332: /* Copy into our internal queue */
333: nRxDataByte = nInChar;
334: MidiStatusRegister |= ACIA_SR_RX_FULL;
1.1.1.10! root 335:
! 336: /* Do we need to generate a receive interrupt? */
! 337: MIDI_UpdateIRQ ();
1.1.1.6 root 338: }
339: else
340: {
1.1.1.10! root 341: LOG_TRACE(TRACE_MIDI, "MIDI: read error (doesn't stop MIDI)\n");
1.1.1.6 root 342: clearerr(pMidiFhIn);
343: }
344: }
345:
1.1.1.10! root 346: /* Set timer */
! 347: CycInt_AddRelativeInterrupt ( MIDI_TRANSFER_BYTE_CYCLE , INT_CPU_CYCLE , INTERRUPT_MIDI );
1.1.1.2 root 348: }
1.1.1.10! root 349:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.