////////////////////////////////////////////////////////////////////////////////
// MIDI.cpp -- this file is part of the Emulator Developers Kit
// available at http://ourworld.compuserve.com/homepages/pc64/develop.htm
//
// The MIDI class provides an ACIA6850 at $DExx. Output is sent to
// Windows MIDI hardware. Input is not implemented yet. Playback
// has been tested with ScoreTrack from C-Lab. Recording is not
// implemented.
//
// In Visual C++ "Tools / Options / Directories", add
//   Include files                = C:\PC64Win\EDK\Include
//
// In Visual C++ "Build / Settings / Debug", set
//   Executable for debug session = ..\PC64Win.exe
//   Working Directory            = ..
//   Program arguments            = -extension MIDI.dll "MIDI\midi test.c64"

#include <EDK.h>


// delete this when the C64 class has been included in the EDK
#define C64 CC64
class C64 : public Chip {
public:
  global void MapROM8000(byte* pbROM);
  global void UnmapROM8000();
  global void MapROMA000(byte* pbROM);
  global void UnmapROMA000();
  global byte* GetBasic();
  global byte* GetKernal();
  global byte* GetCharset();
  global byte* GetIO();
};


#define mm(x) \
  { \
    MMRESULT i = (x); \
    if (i != MMSYSERR_NOERROR) { \
      char ac[MAXERRORLENGTH]; \
      midiOutGetErrorText(i, ac, sizeof ac); \
      error("%s failed\nin file %s at line %d\n%s", #x, __FILE__, __LINE__, ac); \
    } \
  }


class MIDI : public Chip {

  HMIDIOUT hmo;
  DWORD dwData;
  int iIndex;

  // registers
  byte ReadData();
  void WriteData(byte b);
  byte ReadStatus();
  void WriteControl(byte b);

public:

  // constructor
  MIDI() {
    hmo = NULL;
    dwData = 0;
    iIndex = 0;
  }

  // destructor
  virtual ~MIDI() {
    if (hmo != NULL) {
      midiOutReset(hmo);
      midiOutClose(hmo);
      hmo = NULL;
    }
  }

protected:

  // initialisation
  virtual void DoInit();

public:

  Line IRQ; // connected to C64 IRQ, use IRQ.SetOutputLow() and IRQ.SetOutputHigh() if necessary
};


// initialisation
void MIDI::DoInit() {

  // initialize base class
  Chip::DoInit();

  // initialize components
  IRQ.Init("IRQ", this, NULL, NULL);

  // allocate Windows MIDI output channel
  int iDevice = gconf.GetInt("Extensions\\MIDI", "Output Device Number (0..n-1)", 0);
  mm(midiOutOpen(&hmo, iDevice, NULL, NULL, CALLBACK_NULL));

  // set registers (weird mapping because R/W is connected to CA1)
  static const Registers aRegs[4] = {
    NULL, (pfnb)WriteControl, 0,
    NULL, (pfnb)WriteData, 0,
    (bpfn)ReadStatus, NULL, 0,
    (bpfn)ReadData, NULL, DummyReads,
  };
  SetRegisters(aRegs, 4);
}



byte MIDI::ReadData() {
  return 0; // not implemented
}


void MIDI::WriteData(byte b) {

  // flush old command if the new byte is a status byte
  if ((b & 0x80) != 0 && iIndex > 0) {
    mm(midiOutShortMsg(hmo, dwData));
    dwData = 0;
    iIndex = 0;
  }

  // add new byte to command
  ((byte*)&dwData)[iIndex++] = b;

  // flush new command if it has two data bytes
  if (iIndex == 3 || (iIndex == 2 && (dwData & 0x80) == 0)) {
    mm(midiOutShortMsg(hmo, dwData));
    dwData = 0;
    iIndex = 0;
  }

}


byte MIDI::ReadStatus() {
  return 0x02; // no IRQ pending, CTS and DCD low, transceiver empty
}


void MIDI::WriteControl(byte) {
  // not implemented
}


// DLL entry point
BOOL WINAPI DllMain(HINSTANCE, DWORD dwReason, LPVOID) {
  static C64* pC64;
  static CPU6510C64* pCPU;
  static byte* pbIO;
  static MIDI* pMIDI;
  try {
    switch (dwReason) {
    case DLL_PROCESS_ATTACH:
      assert(pC64 == NULL);
      assert(pCPU == NULL);
      pC64 = (C64*)gObjectRoot.FindChild("C64");
      pCPU = (CPU6510C64*)pC64->FindChild("CPU");
      pbIO = pC64->GetIO();
      pMIDI = new MIDI();
      pMIDI->Init("MIDI", pC64);
      pCPU->MapChip(pMIDI, pbIO + 0x0E00, 256);
      pMIDI->IRQ.ConnectTo(pCPU->IRQ);
      break;
    case DLL_PROCESS_DETACH:
      assert(pMIDI != NULL);
      pCPU->UnmapChip(pMIDI, pbIO + 0x0E00, 256);
      delete pMIDI;
      pMIDI = NULL;
      break;
    }
    return TRUE;
  } catch (...) {
    report();
    return FALSE;
  }
}
