/*------------------------------------------
   DRUMDLL.C -- DLL module for DRUM program
              (c) Charles Petzold,  1992
  ------------------------------------------*/

#include <windows.h>
extern "C" {
#include <mmsystem.h>
}
#include <string.h>
#include "drumdll.h"

#define min(a,b)      (((a) < (b)) ? (a) : (b))
#define max(a,b)      (((a) > (b)) ? (a) : (b))
#define minmax(a,x,b) (min (max (x, a), b))

#define TIMER_RES   5

void FAR PASCAL _export DrumTimerFunc (WORD, WORD, DWORD, DWORD, DWORD) ;

BOOL     bSequenceGoing, bEndSequence ;
DRUM     drum ;
HMIDIOUT hMidiOut ;
HWND     hwndNotify ;
int      iIndex ;
WORD     wTimerRes, wTimerID ;

int FAR PASCAL LibMain (HANDLE hInstance, WORD wDataSeg, WORD wHeapSize,
                        LPSTR lpszCmdLine)
     {
     return 1 ;
     }

DWORD MidiOutMessage (HMIDIOUT hMidi, int iStatus, int iChannel,
                                      int iData1,  int iData2)
     {
     DWORD dwMessage ;

     dwMessage = iStatus | iChannel | (iData1 << 8) | ((long) iData2 << 16) ;

     return midiOutShortMsg (hMidi, dwMessage) ;
     }

void FAR PASCAL _export DrumSetParams (PDRUM pdrum)
     {
     _fmemcpy (&drum, pdrum, sizeof (DRUM)) ;
     }

BOOL FAR PASCAL _export DrumBeginSequence (HWND hwnd)
     {
     TIMECAPS tc ;

     hwndNotify = hwnd ;           // Save window handle for notification
     DrumEndSequence (TRUE) ;      // Stop current sequence if running

               // Open the MIDI Mapper output port

     if (midiOutOpen (&hMidiOut, (WORD) MIDIMAPPER, NULL, NULL, 0L))
          return FALSE ;

               // Send Program Change messages for channels 9 and 15

     MidiOutMessage (hMidiOut, 0xC0,  9, 0, 0) ;
     MidiOutMessage (hMidiOut, 0xC0, 15, 0, 0) ;

               // Begin sequence by setting a timer event

     timeGetDevCaps (&tc, sizeof (TIMECAPS)) ;
     wTimerRes = minmax (tc.wPeriodMin, TIMER_RES, tc.wPeriodMax) ;
     timeBeginPeriod (wTimerRes) ;

     wTimerID = timeSetEvent (max ((int) wTimerRes, drum.iMsecPerBeat),
                              wTimerRes, DrumTimerFunc, 0L, TIME_ONESHOT) ;

     if (wTimerID == 0)
          {
          timeEndPeriod (wTimerRes) ;
          midiOutClose (hMidiOut) ;
          return FALSE ;
          }

     iIndex = -1 ;
     bEndSequence = FALSE ;
     bSequenceGoing = TRUE ;

     return TRUE ;
     }

void FAR PASCAL _export DrumEndSequence (BOOL bRightAway)
     {
     if (bRightAway)
          {
          if (bSequenceGoing)
               {
                                                  // stop the timer
               if (wTimerID)
                    timeKillEvent (wTimerID) ;
               timeEndPeriod (wTimerRes) ;
                                                  // turn off all notes

               MidiOutMessage (hMidiOut, 0xB0,  9, 123, 0) ;
               MidiOutMessage (hMidiOut, 0xB0, 15, 123, 0) ;

                                                  // close the MIDI port
               midiOutClose (hMidiOut) ;
               bSequenceGoing = FALSE ;
               }
          }
     else
          bEndSequence = TRUE ;
     }

void FAR PASCAL _export DrumTimerFunc (WORD  wID, WORD  wMsg, DWORD dwUser,
                                       DWORD dw1, DWORD dw2)
     {
     static DWORD dwSeqExtLast [NUM_PERC], dwSeqBasLast [NUM_PERC] ;
     int          i ;

               // Note Off messages for channels 9 and 15

     if (iIndex != -1)
          {
          for (i = 0 ; i < NUM_PERC ; i++)
               {
               if (dwSeqExtLast[i] & 1L << iIndex)
                    MidiOutMessage (hMidiOut, 0x80,  9, i + 35, 0) ;

               if (dwSeqBasLast[i] & 1L << iIndex) ;
                    MidiOutMessage (hMidiOut, 0x80, 15, i + 35, 0) ;
               }
          }

               // Increment index and notify window to advance bouncing ball

     iIndex = (iIndex + 1) % drum.iNumBeats ;
     PostMessage (hwndNotify, WM_USER_NOTIFY, iIndex, timeGetTime ()) ;

               // Check if ending the sequence

     if (bEndSequence && iIndex == 0)
          {
          PostMessage (hwndNotify, WM_USER_FINISHED, 0, 0L) ;
          return ;
          }

               // Note On messages for channels 9 and 15

     for (i = 0 ; i < NUM_PERC ; i++)
          {
          if (drum.dwSeqExt[i] & 1L << iIndex)
               MidiOutMessage (hMidiOut, 0x90,  9, i + 35, drum.iVelocity) ;

          if (drum.dwSeqBas[i] & 1L << iIndex)
               MidiOutMessage (hMidiOut, 0x90, 15, i + 35, drum.iVelocity) ;

          dwSeqExtLast[i] = drum.dwSeqExt[i] ;
          dwSeqBasLast[i] = drum.dwSeqBas[i] ;
          }
               // Set a new timer event

     wTimerID = timeSetEvent (max ((int) wTimerRes, drum.iMsecPerBeat),
                              wTimerRes, DrumTimerFunc, 0L, TIME_ONESHOT) ;

     if (wTimerID == 0)
          {
          PostMessage (hwndNotify, WM_USER_ERROR, 0, 0L) ;
          }
     }
