|
|
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: #define MIDI_TRANSFER_BIT_CYCLE ( 256 << nCpuFreqShift )
49: #define MIDI_TRANSFER_BYTE_CYCLE (MIDI_TRANSFER_BIT_CYCLE * 10)
1.1.1.2 root 50:
51: static Uint8 MidiControlRegister;
52: static Uint8 MidiStatusRegister;
1.1.1.6 root 53: static Uint8 nRxDataByte;
1.1.1.10 root 54: static Uint64 TDR_Write_Time; /* Time of the last write in TDR fffc06 */
55: static Uint64 TDR_Empty_Time; /* Time when TDR will be empty after a write to fffc06 (ie when TDR is transferred to TSR) */
56: static Uint64 TSR_Complete_Time; /* Time when TSR will be completely transferred */
57:
1.1.1.2 root 58:
1.1.1.12 root 59: /*
60: ** Host MIDI I/O
61: */
62: static bool Midi_Host_Open(void);
63: static void Midi_Host_Close(void);
64: static int Midi_Host_ReadByte(void);
65: static bool Midi_Host_WriteByte(Uint8 byte);
66:
67: #ifndef HAVE_PORTMIDI
68: static FILE *pMidiFhIn = NULL; /* File handle used for Midi input */
69: static FILE *pMidiFhOut = NULL; /* File handle used for Midi output */
70: #else
71: #include "portmidi.h"
72: #define INPUT_BUFFER_SIZE 1024 // PortMidi handles buffering
73: static PmStream* midiIn = NULL; // current midi input port
74: static PmStream* midiOut = NULL; // current midi output port
75: static int numInputs = 0; // number of available input ports
76: static int numOutputs = 0; // number of available output ports
77: static const PmDeviceInfo** inports = NULL; // array of available input ports
78: static const PmDeviceInfo** outports = NULL; // array of available output ports
79:
80: static bool Midi_Host_SwitchPort(const char* portName, bool forInput);
81: static int Midi_GetDataLength(Uint8 status);
82: static int Midi_SplitEvent(PmEvent* midiEvent, Uint8* msg);
83: static PmEvent* Midi_BuildEvent(Uint8 byte);
84: #endif
85:
1.1.1.2 root 86:
1.1.1.5 root 87: /**
88: * Initialization: Open MIDI device.
89: */
1.1.1.2 root 90: void Midi_Init(void)
91: {
1.1.1.13! root 92: if (ConfigureParams.Midi.bEnableMidi)
1.1.1.5 root 93: {
1.1.1.13! root 94: if (!Midi_Host_Open())
! 95: {
! 96: Log_AlertDlg(LOG_ERROR, "MIDI i/o open failed. MIDI support disabled.");
! 97: ConfigureParams.Midi.bEnableMidi = false;
! 98: }
1.1.1.2 root 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.12 root 107: Midi_Host_Close();
1.1.1.8 root 108: CycInt_RemovePendingInterrupt(INTERRUPT_MIDI);
1.1.1.2 root 109: }
110:
111:
1.1.1.5 root 112: /**
1.1.1.6 root 113: * Reset MIDI emulation.
1.1.1.5 root 114: */
1.1.1.6 root 115: void Midi_Reset(void)
1.1.1.2 root 116: {
1.1.1.10 root 117: //fprintf ( stderr , "midi reset\n" );
1.1.1.6 root 118: MidiControlRegister = 0;
119: MidiStatusRegister = ACIA_SR_TX_EMPTY;
120: nRxDataByte = 1;
1.1.1.10 root 121: TDR_Empty_Time = 0;
122: TSR_Complete_Time = 0;
1.1.1.2 root 123:
1.1.1.10 root 124: /* Set timer */
125: CycInt_AddRelativeInterrupt ( MIDI_TRANSFER_BYTE_CYCLE , INT_CPU_CYCLE , INTERRUPT_MIDI );
126: }
127:
128:
129: /**
130: * Save/Restore snapshot of local variables
131: */
132: void MIDI_MemorySnapShot_Capture(bool bSave)
133: {
134: MemorySnapShot_Store(&MidiControlRegister, sizeof(MidiControlRegister));
135: MemorySnapShot_Store(&MidiStatusRegister, sizeof(MidiStatusRegister));
136: MemorySnapShot_Store(&nRxDataByte, sizeof(nRxDataByte));
137: MemorySnapShot_Store(&TDR_Empty_Time, sizeof(TDR_Empty_Time));
138: MemorySnapShot_Store(&TSR_Complete_Time, sizeof(TSR_Complete_Time));
139: }
140:
141:
142: /*-----------------------------------------------------------------------*/
143: /**
144: * Check if the IRQ must be changed in SR.
145: * When there's a change, we must change the IRQ line too.
146: */
147: static void MIDI_UpdateIRQ ( void )
148: {
149: Uint8 irq_bit_new;
150:
151: irq_bit_new = 0;
152:
153: if ( ( ( MidiControlRegister & 0x80 ) == 0x80 ) /* Check for RX causes of interrupt */
154: && ( MidiStatusRegister & ACIA_SR_RX_FULL ) )
155: irq_bit_new = ACIA_SR_INTERRUPT_REQUEST;
156:
157: if ( ( ( MidiControlRegister & 0x60) == 0x20 ) /* Check for TX causes of interrupt */
158: && ( MidiStatusRegister & ACIA_SR_TX_EMPTY ) )
159: irq_bit_new = ACIA_SR_INTERRUPT_REQUEST;
160:
161: /* Update SR and IRQ line if a change happened */
162: if ( ( MidiStatusRegister & ACIA_SR_INTERRUPT_REQUEST ) != irq_bit_new )
163: {
164: LOG_TRACE ( TRACE_MIDI, "midi update irq irq_new=%d VBL=%d HBL=%d\n" , irq_bit_new?1:0 , nVBLs , nHBL );
165:
166: if ( irq_bit_new )
167: {
168: /* Request interrupt by setting GPIP to low/0 */
169: MFP_GPIP_Set_Line_Input ( MFP_GPIP_LINE_ACIA , MFP_GPIP_STATE_LOW );
170: MidiStatusRegister |= ACIA_SR_INTERRUPT_REQUEST;
171: }
172: else
173: {
174: /* Clear interrupt request by setting GPIP to high/1 */
175: MFP_GPIP_Set_Line_Input ( MFP_GPIP_LINE_ACIA , MFP_GPIP_STATE_HIGH );
176: MidiStatusRegister &= ~ACIA_SR_INTERRUPT_REQUEST;
177: }
178: }
1.1.1.2 root 179: }
180:
181:
1.1.1.10 root 182:
1.1.1.5 root 183: /**
1.1.1.6 root 184: * Read MIDI status register ($FFFC04).
1.1.1.5 root 185: */
1.1.1.6 root 186: void Midi_Control_ReadByte(void)
1.1.1.2 root 187: {
1.1.1.9 root 188: ACIA_AddWaitCycles (); /* Additional cycles when accessing the ACIA */
1.1.1.4 root 189:
1.1.1.10 root 190: /* Special case : if we wrote a byte into TDR, TX_EMPTY bit should be */
191: /* set approximatively after the first bit was transferred using TSR */
192: if ( ( ( MidiStatusRegister & ACIA_SR_TX_EMPTY ) == 0 )
193: && ( CyclesGlobalClockCounter > TDR_Empty_Time ) ) // OK avec 11 bits et 1 bit
194: {
195: MidiStatusRegister |= ACIA_SR_TX_EMPTY;
196:
197: /* Do we need to generate a transfer interrupt? */
198: MIDI_UpdateIRQ ();
199: }
200:
201: //fprintf ( stderr , "midi read sr %x %lld %lld\n" , MidiStatusRegister , CyclesGlobalClockCounter , TDR_Write_Time );
202:
1.1.1.6 root 203: IoMem[0xfffc04] = MidiStatusRegister;
1.1.1.10 root 204:
205: LOG_TRACE ( TRACE_MIDI, "midi read fffc04 sr=0x%02x VBL=%d HBL=%d\n" , MidiStatusRegister , nVBLs , nHBL );
1.1.1.2 root 206: }
207:
1.1 root 208:
1.1.1.5 root 209: /**
210: * Write to MIDI control register ($FFFC04).
211: */
1.1.1.3 root 212: void Midi_Control_WriteByte(void)
1.1.1.2 root 213: {
1.1.1.9 root 214: ACIA_AddWaitCycles (); /* Additional cycles when accessing the ACIA */
1.1.1.4 root 215:
1.1.1.3 root 216: MidiControlRegister = IoMem[0xfffc04];
1.1.1.2 root 217:
1.1.1.10 root 218: LOG_TRACE ( TRACE_MIDI, "midi write fffc04 cr=0x%02x VBL=%d HBL=%d\n" , MidiControlRegister , nVBLs , nHBL );
1.1.1.2 root 219:
1.1.1.10 root 220: MIDI_UpdateIRQ ();
1.1.1.2 root 221: }
222:
223:
1.1.1.6 root 224: /**
225: * Read MIDI data register ($FFFC06).
226: */
227: void Midi_Data_ReadByte(void)
228: {
1.1.1.10 root 229: LOG_TRACE ( TRACE_MIDI, "midi read fffc06 rdr=0x%02x VBL=%d HBL=%d\n" , nRxDataByte , nVBLs , nHBL );
230: //fprintf ( stderr , "midi rx %x\n" , nRxDataByte);
1.1.1.6 root 231:
1.1.1.9 root 232: ACIA_AddWaitCycles (); /* Additional cycles when accessing the ACIA */
1.1.1.6 root 233:
1.1.1.10 root 234: IoMem[0xfffc06] = nRxDataByte;
1.1.1.6 root 235:
1.1.1.10 root 236: MidiStatusRegister &= ~ACIA_SR_RX_FULL;
1.1.1.6 root 237:
1.1.1.10 root 238: MIDI_UpdateIRQ ();
1.1.1.6 root 239: }
240:
241:
1.1.1.5 root 242: /**
243: * Write to MIDI data register ($FFFC06).
1.1.1.10 root 244: * We should determine precisely when TDR will be empty and when TSR will be transferred.
245: * This is required to accurately emulate the TDRE bit in status register (fix the program 'Notator')
1.1.1.5 root 246: */
1.1.1.3 root 247: void Midi_Data_WriteByte(void)
1.1.1.2 root 248: {
1.1.1.6 root 249: Uint8 nTxDataByte;
1.1.1.12 root 250: bool ok;
251:
1.1.1.9 root 252: ACIA_AddWaitCycles (); /* Additional cycles when accessing the ACIA */
1.1.1.4 root 253:
1.1.1.6 root 254: nTxDataByte = IoMem[0xfffc06];
1.1.1.10 root 255: TDR_Write_Time = CyclesGlobalClockCounter;
256:
257: /* If TSR is already transferred, then TDR will be empty after 1 bit is transferred */
258: /* If TSR is not completely transferred, then TDR will be empty 1 bit after TSR is transferred */
259: if ( CyclesGlobalClockCounter >= TSR_Complete_Time )
260: {
261: TDR_Empty_Time = CyclesGlobalClockCounter + MIDI_TRANSFER_BIT_CYCLE;
262: TSR_Complete_Time = CyclesGlobalClockCounter + MIDI_TRANSFER_BYTE_CYCLE;
263: }
264: else
265: {
266: //fprintf ( stderr , "MIDI OVR %lld\n" , TSR_Complete_Time - CyclesGlobalClockCounter );
267: TDR_Empty_Time = TSR_Complete_Time + MIDI_TRANSFER_BIT_CYCLE;
268: TSR_Complete_Time += MIDI_TRANSFER_BYTE_CYCLE;
269: }
270:
271: LOG_TRACE ( TRACE_MIDI, "midi write fffc06 tdr=0x%02x VBL=%d HBL=%d\n" , nTxDataByte , nVBLs , nHBL );
272: //fprintf ( stderr , "midi tx %x sr=%x\n" , nTxDataByte , MidiStatusRegister );
1.1.1.3 root 273:
1.1.1.10 root 274: MidiStatusRegister &= ~ACIA_SR_TX_EMPTY;
1.1.1.2 root 275:
1.1.1.10 root 276: MIDI_UpdateIRQ ();
1.1.1.2 root 277:
278: if (!ConfigureParams.Midi.bEnableMidi)
279: return;
280:
1.1.1.12 root 281: ok = Midi_Host_WriteByte(nTxDataByte);
1.1.1.6 root 282:
1.1.1.12 root 283: /* If there was an error then stop the midi emulation */
284: if (!ok)
285: {
286: LOG_TRACE(TRACE_MIDI, "MIDI: write error -> stop MIDI\n");
287: Midi_UnInit();
288: return;
1.1.1.2 root 289: }
1.1.1.6 root 290: }
291:
292:
293: /**
294: * Read and write MIDI interface data regularly
295: */
296: void Midi_InterruptHandler_Update(void)
297: {
298: int nInChar;
299:
300: /* Remove this interrupt from list and re-order */
1.1.1.8 root 301: CycInt_AcknowledgeInterrupt();
1.1.1.6 root 302:
1.1.1.10 root 303: /* Special case : if we wrote a byte into TDR, TX_EMPTY bit should be */
304: /* set when reaching TDR_Empty_Time */
305: if ( ( ( MidiStatusRegister & ACIA_SR_TX_EMPTY ) == 0 )
306: && ( CyclesGlobalClockCounter > TDR_Empty_Time ) )
1.1.1.2 root 307: {
1.1.1.10 root 308: MidiStatusRegister |= ACIA_SR_TX_EMPTY;
309:
1.1.1.6 root 310: /* Do we need to generate a transfer interrupt? */
1.1.1.10 root 311: MIDI_UpdateIRQ ();
1.1.1.2 root 312:
1.1.1.10 root 313: /* Flush outgoing data (not necessary ?) */
1.1.1.6 root 314: // if (pMidiFhOut)
315: // fflush(pMidiFhOut);
1.1.1.2 root 316: }
1.1.1.6 root 317:
318: /* Read the bytes in, if we have any */
1.1.1.12 root 319: nInChar = Midi_Host_ReadByte();
320: if (nInChar != EOF)
1.1.1.6 root 321: {
1.1.1.12 root 322: LOG_TRACE(TRACE_MIDI, "MIDI: Read character -> $%x\n", nInChar);
323: /* Copy into our internal queue */
324: nRxDataByte = nInChar;
325: MidiStatusRegister |= ACIA_SR_RX_FULL;
326:
327: /* Do we need to generate a receive interrupt? */
328: MIDI_UpdateIRQ ();
329: }
330: #ifndef HAVE_PORTMIDI
331: else if (pMidiFhIn)
332: {
333: LOG_TRACE(TRACE_MIDI, "MIDI: read error (doesn't stop MIDI)\n");
334: clearerr(pMidiFhIn);
335: }
336: #endif
337:
338: /* Set timer */
339: CycInt_AddRelativeInterrupt ( MIDI_TRANSFER_BYTE_CYCLE , INT_CPU_CYCLE , INTERRUPT_MIDI );
340: }
1.1.1.10 root 341:
1.1.1.12 root 342:
343: // ============================================================================
344: // Host MIDI I/O
345: // ============================================================================
346:
347: /**
348: * open MIDI streams
349: * return true for no errors
350: */
351: static bool Midi_Host_Open(void)
352: {
353: #ifndef HAVE_PORTMIDI
354: if (ConfigureParams.Midi.sMidiOutFileName[0])
355: {
356: /* Open MIDI output file */
357: pMidiFhOut = File_Open(ConfigureParams.Midi.sMidiOutFileName, "wb");
358: if (!pMidiFhOut)
359: return false;
360: setvbuf(pMidiFhOut, NULL, _IONBF, 0); /* No output buffering! */
361: LOG_TRACE(TRACE_MIDI, "MIDI: Opened file '%s' for output\n",
362: ConfigureParams.Midi.sMidiOutFileName);
363: }
364: if (ConfigureParams.Midi.sMidiInFileName[0])
365: {
366: /* Try to open MIDI input file */
367: pMidiFhIn = File_Open(ConfigureParams.Midi.sMidiInFileName, "rb");
368: if (!pMidiFhIn)
369: return false;
370: setvbuf(pMidiFhIn, NULL, _IONBF, 0); /* No input buffering! */
371: LOG_TRACE(TRACE_MIDI, "MIDI: Opened file '%s' for input\n",
372: ConfigureParams.Midi.sMidiInFileName);
373: }
374: #else
375: /* Need to always get MIDI device info, for MIDI setup dialog */
376: int i;
377: int iindex = 0;
378: int oindex = 0;
379: int numPorts = 0;
380:
381: if (Pm_Initialize() != pmNoError)
382: {
383: LOG_TRACE(TRACE_MIDI, "MIDI: PortMidi initialization failed\n");
384: return false;
385: }
386: // -- get rid of earlier portmidi descriptor arrays (if allocated)
387: // -- the information may be stale (USB Midi etc)
388: if (inports)
389: free (inports);
390: if (outports)
391: free (outports);
392: inports = outports = NULL;
393: numInputs = numOutputs = 0;
394:
395: // -- count number of input and output ports
396: numPorts = Pm_CountDevices();
397: for (i = 0; i < numPorts; i++)
398: {
399: const PmDeviceInfo *info = Pm_GetDeviceInfo(i);
400: if (info->input)
401: numInputs++;
402: else if (info->output)
403: numOutputs++;
404: }
405:
406: // -- allocate descriptor arrays
407: inports = malloc(numInputs * sizeof(PmDeviceInfo*));
408: outports = malloc(numOutputs * sizeof(PmDeviceInfo*));
409:
410: // -- populate descriptor arrays
411: for (i = 0; i < numPorts; i++)
412: {
413: const PmDeviceInfo* info = Pm_GetDeviceInfo(i);
414: if (info)
415: {
416: LOG_TRACE(TRACE_MIDI, "MIDI: device %d: '%s'\n", i, info->name);
417: if (info->input)
418: inports[iindex++] = info;
419: if (info->output)
420: outports[oindex++] = info;
1.1.1.6 root 421: }
422: else
1.1.1.12 root 423: LOG_TRACE(TRACE_MIDI, "MIDI: info disappeared for device %d!\n", i);
424: }
425:
426: // -- open input and output ports according to configuration
427: if (ConfigureParams.Midi.sMidiInPortName[0])
428: Midi_Host_SwitchPort(ConfigureParams.Midi.sMidiInPortName, true);
429: if (ConfigureParams.Midi.sMidiOutPortName[0])
430: Midi_Host_SwitchPort(ConfigureParams.Midi.sMidiOutPortName, false);
431: #endif
432:
433: return true;
434: }
435:
436:
437: /* ---------------------------------------------------------------------------- */
438: /**
439: * close MIDI streams
440: */
441: static void Midi_Host_Close(void)
442: {
443: #ifndef HAVE_PORTMIDI
444: pMidiFhIn = File_Close(pMidiFhIn);
445: pMidiFhOut = File_Close(pMidiFhOut);
446: #else
447: if (midiIn)
448: Pm_Close(midiIn);
449: if (midiOut)
450: Pm_Close(midiOut);
451: midiIn = midiOut = NULL;
452:
453: // Can't terminate PM or free descriptor arrays as this gets
454: // called by any write errors and GUI won't then work.
455: // Pm_Terminate();
456: #endif
457: }
458:
459:
460:
461: /* ---------------------------------------------------------------------------- */
462: /**
463: * returns port name for input or output port at 'index'
464: */
465: const char* Midi_Host_GetPortName(int index, bool forInput)
466: {
467: #ifdef HAVE_PORTMIDI
468: if (forInput && index < numInputs)
469: return inports[index]->name;
470: else if (!forInput && index < numOutputs)
471: return outports[index]->name;
472: #endif
473:
474: return NULL;
475: }
476:
477: /* ---------------------------------------------------------------------------- */
478: /**
479: * returns port descriptor array index for input or output 'portName'
480: */
481: int Midi_Host_GetPortIndex(const char* portName, bool forInput)
482: {
483: #ifdef HAVE_PORTMIDI
484: int i = 0;
485: int numPorts = forInput ? numInputs : numOutputs;
486: const PmDeviceInfo** ports = forInput ? inports : outports;
487:
488: if (ports)
489: {
490: for (i = 0; i < numPorts; i++)
491: if (!strcmp(portName, ports[i]->name))
492: return i;
493: }
494: #endif
495:
496: return -1;
497: }
498:
499: /**
500: * closes current midi port (if any) and opens 'portName' if MIDI enabled
501: * returns true if successful, false otherwise
502: */
503: #ifdef HAVE_PORTMIDI
504: static bool Midi_Host_SwitchPort(const char* portName, bool forInput)
505: {
506: int i, count;
507: bool err;
508:
509: if (!ConfigureParams.Midi.bEnableMidi)
510: return false;
511:
512: // -- find PortMidi index for 'portName'
513: count = Pm_CountDevices();
514: for (i = 0; i < count; i++)
515: {
516: const PmDeviceInfo* info = Pm_GetDeviceInfo(i);
517: if (info)
1.1.1.6 root 518: {
1.1.1.12 root 519: if (forInput && !info->input)
520: continue;
521: else if (!forInput && info->input)
522: continue;
523: if (!strcmp(info->name, portName))
524: break;
1.1.1.6 root 525: }
526: }
1.1.1.12 root 527: if (i >= count)
528: return false;
1.1.1.6 root 529:
1.1.1.12 root 530: // -- close current port in any case, then try open new one
531: if (forInput == true)
532: {
533: if (midiIn) {
534: Pm_Close(midiIn);
535: midiIn = NULL;
536: }
537: err = (Pm_OpenInput(&midiIn, i, NULL, INPUT_BUFFER_SIZE, NULL, NULL) == pmNoError);
538: LOG_TRACE(TRACE_MIDI, "MIDI: input port %d '%s' open %s\n", i, portName, err ? "succeeded" : "failed");
539: return err;
540: }
541: else
542: {
543: if (midiOut) {
544: Pm_Close(midiOut);
545: midiOut = NULL;
546: }
547: err = (Pm_OpenOutput(&midiOut, i, NULL, 0, NULL, NULL, 0) == pmNoError);
548: LOG_TRACE(TRACE_MIDI, "MIDI: output port %d '%s' open %s\n", i, portName, err ? "succeeded" : "failed");
549: return err;
550: }
551:
552: return false;
553: }
554: #endif
555:
556: /**
557: * returns byte from input stream, or EOF if it is empty
558: */
559: static int Midi_Host_ReadByte(void)
560: {
561: #ifndef HAVE_PORTMIDI
562: if (pMidiFhIn && File_InputAvailable(pMidiFhIn))
563: return fgetc(pMidiFhIn);
564: else return EOF;
1.1.1.11 root 565: #else
1.1.1.12 root 566: static Uint8 msg[4];
567: static Uint8 ibyte = 0;
568: static int bytesAvailable = 0;
569:
570: if (midiIn)
571: {
572: // -- we have not yet returned all bytes from previous event
573: if (bytesAvailable > 0)
574: {
575: bytesAvailable--;
576: return msg[ibyte++];
577: }
578:
579: // -- read new event (if any)
580: else if (Pm_Poll(midiIn) == TRUE) {
581: PmEvent midiEvent;
582: if (Pm_Read(midiIn, &midiEvent, 1) != 1)
583: return EOF;
584: if ((bytesAvailable = Midi_SplitEvent(&midiEvent, msg)) > 0)
585: {
586: bytesAvailable--;
587: ibyte = 1;
588: return msg[0];
589: }
590: }
591: }
592:
593: // -- no more midi data
594: return EOF;
1.1.1.11 root 595: #endif
1.1.1.2 root 596: }
1.1.1.10 root 597:
1.1.1.12 root 598:
599: /**
600: * writes 'byte' into output stream, returns true on success
601: */
602: static bool Midi_Host_WriteByte(Uint8 byte)
603: {
604: #ifndef HAVE_PORTMIDI
605: if (pMidiFhOut)
606: {
607: /* Write the character to the output file: */
608: int ret = fputc(byte, pMidiFhOut);
609: return (ret != EOF);
610: }
611: #else
612: if (midiOut)
613: {
614: PmEvent* midiEvent = Midi_BuildEvent(byte);
615: if (midiEvent)
616: {
617: PmError error = Pm_Write(midiOut, midiEvent, 1);
618: if (error == pmNoError)
619: return true;
620: LOG_TRACE(TRACE_MIDI, "MIDI: PortMidi write error %d\n", error);
621: return false;
622: }
623: return true;
624: }
625: #endif
626:
627: return false;
628: }
629:
630:
631: #ifdef HAVE_PORTMIDI
632: // ============================================================================
633: // PortMidi utils
634: //
635: // PortMidi (as most native MIDI APIs) operate on complete MIDI messages
636: // we need therefore handle running status, variable number of data bytes
637: // and sysex correctly.
638: //
639: // ============================================================================
640:
641: /**
642: * return number of databytes that should accompany 'status' byte
643: * four bytes for sysex is a special case to simplify Midi_BuildEvent()
644: */
645: static int Midi_GetDataLength(Uint8 status)
646: {
647: static const Uint8 dataLength[] = { 2,2,2,2,1,2,2, 4,1,2,1,0,0,0,0 };
648:
649: if (status >= 0xF8 || status == 0)
650: return 0;
651: if (status >= 0xF0)
652: return dataLength[(status & 0x0F) + 7];
653: return dataLength[(status >> 4) - 8];
654: }
655:
656: /**
657: * collect bytes until valid MIDI event has been formed / four bytes of sysex data has been gathered
658: * returns PmEvent when done, or NULL if it needs still more data
659: * see MIDI 1.0 Detailed Spec 4.2, pages A-1..A-2 for discussion on running status
660: */
661: static PmEvent* Midi_BuildEvent(Uint8 byte)
662: {
663: static const Uint8 shifts[] = { 0,8,16,24 };
664: static PmEvent midiEvent = { 0,0 };
665: static Uint32 midimsg;
666: // static Uint8 runningStatus = 0;
667: static Uint8 bytesToWait = 0;
668: static Uint8 bytesCollected = 0;
669: static bool processingSysex = false;
670:
671: // -- status byte
672: if (byte & 0x80)
673: {
674: if (byte >= 0xF8)
675: {
676: midiEvent.message = Pm_Message(byte,0,0);
677: return &midiEvent;
678: }
679: else
680: {
681: processingSysex = false;
682: if (byte >= 0xF0)
683: {
684: // runningStatus = 0;
685: if (byte == 0xF0)
686: {
687: processingSysex = true;
688: bytesCollected = 1;
689: }
690: else if (byte == 0xF7)
691: {
692: midiEvent.message = midimsg | (((Uint32)byte) << shifts[bytesCollected]);
693: midimsg = bytesToWait = bytesCollected = 0;
694: return &midiEvent;
695: }
696: else
697: bytesCollected = 0;
698: }
699: else
700: {
701: // runningStatus = byte;
702: bytesCollected = 0;
703: }
704: }
705: midimsg = byte;
706: bytesToWait = Midi_GetDataLength(byte);
707: }
708:
709: // -- data byte
710: else
711: {
712: if (processingSysex)
713: midimsg |= ((Uint32)byte) << shifts[bytesCollected++];
714: else
715: midimsg |= ((Uint32)byte) << shifts[++bytesCollected];
716: if (bytesCollected >= bytesToWait)
717: {
718: midiEvent.message = midimsg;
719: midimsg = 0;
720: bytesCollected = 0;
721: bytesToWait = processingSysex ? 4 : 0;
722: return &midiEvent;
723: }
724: }
725:
726: return NULL;
727: }
728:
729:
730: /**
731: * extracts raw bytes from 'midiEvent' into 'msg'
732: * returns number of bytes available in 'msg'
733: *
734: * this method is required for sysex handling
735: * native framework has already handled running status
736: */
737: static int Midi_SplitEvent(PmEvent* midiEvent, Uint8* msg)
738: {
739: static bool processingSysex = false;
740: PmMessage midiMessage = midiEvent->message;
741: int i, bytesAvailable = 0;
742:
743: msg[0] = Pm_MessageStatus(midiMessage);
744:
745: // -- sysex start or continuation
746: if ((msg[0] == 0xF0) || (msg[0] < 0x80))
747: {
748: if (msg[0] == 0xF0)
749: processingSysex = true;
750:
751: if (processingSysex)
752: {
753: for (i = 0; i <= 3; i++)
754: {
755: msg[i] = midiMessage & 0xFF;
756: bytesAvailable = i;
757: if (msg[i] == 0xF7)
758: {
759: processingSysex = false;
760: break;
761: }
762: midiMessage >>= 8;
763: }
764: }
765: else bytesAvailable = -1;
766: }
767:
768: // -- non-sysex
769: else
770: {
771: if (msg[0] < 0xF8) // non-realtime
772: {
773: processingSysex = false;
774: midiMessage >>= 8;
775: bytesAvailable = Midi_GetDataLength(msg[0]);
776: for (i = 1; i <= bytesAvailable; i++)
777: {
778: msg[i] = midiMessage & 0xFF;
779: midiMessage >>= 8;
780: }
781: }
782: }
783:
784: return bytesAvailable + 1;
785: }
786: #endif
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.