Annotation of hatari/src/midi.c, revision 1.1.1.11

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: 

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.