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