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

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: 

unix.superglobalmegacorp.com

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