///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// File:           CC121.cpp                                                 //
//                                                                           //
// Functionality:  Control surface class implemention.                       //
//                                                                           //
// Author:         Rolf Meyerhoff (rm@matrix44.de)                           //
//                                                                           //
// Copyright:      (C) 2009 by Rolf Meyerhoff. All rights reserved.          //
//                                                                           //
///////////////////////////////////////////////////////////////////////////////
//                                                                           //
// License:                                                                  //
//                                                                           //
// This library is free software;  you can redistribute it  and/or modify it //
// under the terms of the GNU Lesser  General Public License as published by //
// the Free Software Foundation;  either version 2.1 of the License,  or (at //
// your option) any later version.                                           //
//                                                                           //
// This  library is  distributed in  the hope that  it will  be useful,  but //
// WITHOUT   ANY   WARRANTY;   without   even   the   implied  warranty   of //
// MERCHANTABILITY or FITNESS FOR A  PARTICULAR PURPOSE.  See the GNU Lesser //
// General Public License for more details.                                  //
//                                                                           //
// You should have received a copy of  the GNU Lesser General Public License //
// along with this library;  if not,  write to the Free Software Foundation, //
// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.              //
///////////////////////////////////////////////////////////////////////////////
#include "reaper_cc121.h"

static int panToInt14(double pan)
{
  double d=((1.0-pan)*16383.0*0.5);
  if (d<0.0)d=0.0;
  else if (d>16383.0)d=16383.0;
  return (int)(d+0.5);
}

static double int14ToPan(int val)
{
  return 1.0 - (val/(16383.0*0.5));
}

///////////////////////////////////////////////////////////////////////////////
// CC121::CC121()
///////////////////////////////////////////////////////////////////////////////
// Description:  Default constructor of this class.
// Parameters:   None.
// Return value: No return value.
// Remarks:      Explicit construction is required so this constructor will
//               throw an exception.
///////////////////////////////////////////////////////////////////////////////
CC121::CC121()
{
  // Should never reach here.
  assert(FALSE);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::~CC121()
///////////////////////////////////////////////////////////////////////////////
// Description:  Destructor of this class.
// Parameters:   None.
// Return value: No return value.
// Remarks:      Closes the MIDI input and MIDI output and frees all
//               allocated resources.
///////////////////////////////////////////////////////////////////////////////
CC121::~CC121()
{
  // Reset surface:
  UpdateDevice(ButtonNone, faderState);

  // Give the MIDI out enough time to process the messages:
  Sleep(5);

  // Delete MIDI in and MIDI out:
  // TODO: Both were created by REAPER through CreateMIDI*put(). Delete them
  // with the runtime of this plugin might have serious side effects:
  if (midiOut)
    delete midiOut;
  if (midiIn)
    delete midiIn;
}

///////////////////////////////////////////////////////////////////////////////
// CC121::GetTypeString()
///////////////////////////////////////////////////////////////////////////////
// Description:  Retrieve the type ID of this control surface.
// Parameters:   None.
// Return value: Returns the type ID as string.
// Remarks:      This ID seems to be used to select the right surface and to
//               load and store configurations. This has to be a simple
//               unique string with only A-Z, 0-9, no spaces or other chars.
///////////////////////////////////////////////////////////////////////////////
const char* CC121::GetTypeString()
{
  // Return ID:
  return "CC121";
}

///////////////////////////////////////////////////////////////////////////////
// CC121::GetDescString()
///////////////////////////////////////////////////////////////////////////////
// Description:  Retrieve the human readable description of this surface.
// Parameters:   None.
// Return value: Returns the description as string.
// Remarks:      This description is displayed in the control surface dialog
//               box. Instance specific informations are allowed here.
///////////////////////////////////////////////////////////////////////////////
const char* CC121::GetDescString()
{
  // Temporary space for the plugin description:
  static char desctmp[1024];

  // Get human readable name of the MIDI input:
  char in_buff[256];
  if (options.midiInDev >= 0 && options.midiInDev < GetNumMIDIInputs())
    GetMIDIInputName(options.midiInDev, in_buff, sizeof(in_buff) / sizeof(in_buff[0]));
  else
    strcpy(in_buff, "No Input");
   
  // Get human readable name of the MIDI output:
  char out_buff[256];
  if (options.midiOutDev >= 0 && options.midiOutDev < GetNumMIDIOutputs())
    GetMIDIOutputName(options.midiOutDev, out_buff, sizeof(out_buff) / sizeof(out_buff[0]));
  else
    strcpy(out_buff, "No Output");

  // Format display string:
  sprintf(desctmp, "Steinberg CC121 (%s, %s)", in_buff, out_buff);
  return desctmp;
}

///////////////////////////////////////////////////////////////////////////////
// CC121::GetConfigString()
///////////////////////////////////////////////////////////////////////////////
// Description:  Retrieve the configuration of this surface.
// Parameters:   None.
// Return value: Returns the current configuration as string.
// Remarks:      This configuration string is actually stored in REAPER's
//               configuration.
///////////////////////////////////////////////////////////////////////////////
const char* CC121::GetConfigString()
{
  // Temporary space for the plugin configuration:
  static char configtmp[1024];

  // Write config into a temporary string:
  options.WriteConfig(configtmp);

  // Return the temp string:
  return configtmp;
}

///////////////////////////////////////////////////////////////////////////////
// CC121::CloseNoReset()
///////////////////////////////////////////////////////////////////////////////
// Description:  Close this surface without resetting the state.
// Parameters:   None.
// Return value: No return value.
// Remarks:      Close without sending "reset" messages, prevent "reset"
//               being sent on destructor. 
///////////////////////////////////////////////////////////////////////////////
void CC121::CloseNoReset()
{
  // Delete MIDI in and MIDI out:
  // TODO: Both were created by REAPER through CreateMIDI*put(). Delete them
  // with the runtime of this plugin might have serious side effects:
  if (midiOut)
    delete midiOut;
  midiOut = NULL;
  if (midiIn)
    delete midiIn;
  midiIn = NULL;
}

///////////////////////////////////////////////////////////////////////////////
// CC121::Run()
///////////////////////////////////////////////////////////////////////////////
// Description:  The main message handling routine.
// Parameters:   None.
// Return value: No return value.
// Remarks:      This one is called 30 times a sec or so.
///////////////////////////////////////////////////////////////////////////////
void CC121::Run()
{
  // Hardware available?
  if (midiOut == NULL || midiIn == NULL)
    return;

  // Get current time of this run:
  DWORD curTime = timeGetTime();

  // Keep up connection:
  CheckRunningStatus(curTime);

  // Time to check monitor button status?
  if (curTime > (lastMonChkTime + 100))
  {
    // Get track:
    MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
    if (tr)
    {
      // Get current state:
      int state = *static_cast<int*>(GetSetMediaTrackInfo(tr, "I_RECMON", NULL));

      // Update light state:
      SwitchLight(ButtonMonitor, state != 0);
    }

    // Save send time:
    lastMonChkTime = curTime;
  }

  // Time to check EQ status?
  if (curTime > (lastEQChkTime + 200))
  {
    if (eqEditMode != EqEditQuickControl)
    {
      int state = 0;
      bool has_eq = false;

      // Find a matching EQ map:
      int fxid = 0;
      const CC121EqMap* eqMap = FindTrackEQ(currentTrackID, fxid);
      if (eqMap != NULL)
      {
        has_eq = true;

        // Get track:
        MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);

        // Find a matching EQ parameter for the Bypass parameter:
        int param = eqMap->MapParameter(CC121EqMap::EqBypass, 0);
        if (param >= 0)
        {
          double minval, maxval;
          double val = TrackFX_GetParam(tr, fxid, param, &minval, &maxval);
          if (val > 0.5)
            state |= ButtonEQBypass;
        }

        // Find matching parameters for the band bypasses:
        if (eqEditMode == EqEditBands)
        {
          for (int chan = 0; chan < 4; chan++)
          {
            param = eqMap->MapParameter(CC121EqMap::BandBypass, chan);
            if (param >= 0)
            {
              double minval, maxval;
              double val = TrackFX_GetParam(tr, fxid, param, &minval, &maxval);
              if (val > 0.5)
                state |= ButtonEQ1 << chan;
            }
          }
        }
      }

      // Update light:
      SwitchLight(ButtonEQBypass, (state & ButtonEQBypass) != 0);
      SwitchLight(ButtonEQType, has_eq && eqEditMode == EqEditType);
      for (int i = 0; i < 4; i++)
      {
        ButtonState s = static_cast<ButtonState>(ButtonEQ1 << i);
        SwitchLight(s, (state & s) != 0);
      }
    }
    else if (eqEditMode == EqEditQuickControl)
    {
      // Update state of the send lights:
      MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
      if (tr)
      {
        for (int i = 0; i < 4; i++)
        {
          bool send_off = true;

          // See if we have a send target:
          if (GetSetTrackSendInfo(tr, 0, i, "P_DESTTRACK", NULL) != NULL)
          {
            // Get send state:
            send_off = *((bool*)GetSetTrackSendInfo(tr, 0, i, "B_MUTE", NULL));
          }

          // Update light:
          SwitchLight(static_cast<ButtonState>(ButtonEQ1 << i), !send_off);
        }
      }
    }

    // Save send time:
    lastEQChkTime = curTime;
  }

  // Time to check for fx display?
  if (curTime > (lastFXChkTime + 200))
  {
    // Defaults to off:
    bool fxWndState = false;

    // Get track:
    MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
    if (tr)
    {
      char wnd_title[256];
      const char* track_name = NULL;
      int flags = 0;

      // Format window title:
      if (currentTrackID == 0)
      {
        // Master track:
        strcpy(wnd_title, "FX: Master Track");
      }
      else
      {
        const char* track_name = GetTrackInfo(currentTrackID - 1, &flags);
        if (!track_name || track_name[0] == 0)
          sprintf(wnd_title, "FX: Track %d", currentTrackID);
        else
          sprintf(wnd_title, "FX: Track %d \"%s\"", currentTrackID, track_name);

        // Add folder state:
        if (flags & 1)
          strcat(wnd_title, " (folder)");
      }

      // Search for the FX window:
      HWND hFx = FindWindowEx(NULL, NULL, MAKEINTATOM(32770), wnd_title);

      // Update surface if it was found:
      if (hFx != NULL && IsWindowVisible(hFx))
        fxWndState = true;
    }

    // Update light:
    SwitchLight(ButtonVSTi, fxWndState);

    // Save send time:
    lastFXChkTime = curTime;
  }

  // Time to check for io display?
  if (curTime > (lastIOChkTime + 200))
  {
    // Defaults to off:
    bool ioWndState = false;

    // Get track:
    MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
    if (tr)
    {
      // Format window title:
      char wnd_title[256];
      if (currentTrackID == 0)
      {
        // Master has a different naming scheme:
        strcpy(wnd_title, "Outputs for Master Track");
      }
      else
      {
        int flags = 0;
        const char* track_name = GetTrackInfo(currentTrackID - 1, &flags);
        sprintf(wnd_title, "Routing for track %d \"%s\"", currentTrackID, track_name);

        // Add folder state:
        if (flags & 1)
          strcat(wnd_title, " (folder)");
      }

      // Search for the FX window:
      HWND hIO = FindWindowEx(NULL, NULL, MAKEINTATOM(32770), wnd_title);

      // Update surface if it was found:
      if (hIO != NULL && IsWindowVisible(hIO))
        ioWndState = true;
    }

    // Update light:
    SwitchLight(ButtonEdit, ioWndState);

    // Save send time:
    lastIOChkTime = curTime;
  }

  // Read MIDI events, swap to active buffer:
  midiIn->SwapBufs(curTime);

  // Get event list:
  MIDI_eventlist* list = midiIn->GetReadBuf();

  // Loop through events:
  int l = 0;
  MIDI_event_t* evts = list->EnumItems(&l);
  while (evts != NULL)
  {
    // Handle event:
    OnMIDIEvent(evts, curTime);

    // Next event:
    evts = list->EnumItems(&l);
  }

  // Seeking through project?
  if (GetButtonState(ButtonForward))
    CSurf_OnFwd(0);
  else if (GetButtonState(ButtonRewind))
    CSurf_OnRew(0);

  // Time to blink with the buttons:
  if (curTime > (lastBlinkTime + 500))
  {
    // Time to blink with the play button?
    if (paused)
    {
      // Set state:
      SwitchLight(ButtonPlay, (lightState & ButtonPlay) == 0);
    }

    // Time to blink with the eq buttons for quick control?
    if (eqEditMode == EqEditQuickControl)
    {
      // Set state:
      SwitchLight(ButtonEQBypass, false);
      SwitchLight(ButtonEQType, (lightState & ButtonEQType) == 0);
    }

    // Save blink time:
    lastBlinkTime = curTime;
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::SetTrackListChange()
///////////////////////////////////////////////////////////////////////////////
// Description:  Notification that the track list has changed.
// Parameters:   None.
// Return value: No return value.
// Remarks:      This surface has only one channel so we don't care.
///////////////////////////////////////////////////////////////////////////////
void CC121::SetTrackListChange()
{
  // Reset automation mode:
  SetAutoMode(0);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::SetSurfaceVolume()
///////////////////////////////////////////////////////////////////////////////
// Description:  Notification that the volume of a track has changed.
// Parameters:   [in] trackid: The track changed.
//               [in] volume:  The new volume.
// Return value: No return value.
// Remarks:      Updates the fader of the surface.
///////////////////////////////////////////////////////////////////////////////
void CC121::SetSurfaceVolume(MediaTrack* trackid, double volume)
{
  // Don't update fader when touching the handle:
  if (touching || faderSwapped)
    return;

  // Is this our selected track?
  if (CSurf_TrackToID(trackid, options.useMCP) == currentTrackID)
  {
    // Update fader state:
    if (midiOut != NULL)
    {
      // Convert from linear volume to 14 bit pitch bend:
      int volint = volToInt14(volume);

      // Update controller:
      UpdateDevice(lightState, volint);
    }
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::SetSurfacePan()
///////////////////////////////////////////////////////////////////////////////
// Description:  Notification that the pan of a track has changed.
// Parameters:   [in] trackid: The track changed.
//               [in] pan:     The new pan.
// Return value: No return value.
// Remarks:      Updates the fader of the surface.
///////////////////////////////////////////////////////////////////////////////
void CC121::SetSurfacePan(MediaTrack* trackid, double pan)
{
  // Don't update fader when touching the handle:
  if (touching || !faderSwapped)
    return;

  // Is this our selected track?
  if (CSurf_TrackToID(trackid, options.useMCP) == currentTrackID)
  {
    // Update fader state:
    if (midiOut != NULL)
    {
      // Convert from pan to 14 bit pitch bend:
      int panint = panToInt14(pan);

      // Update controller:
      UpdateDevice(lightState, panint);
    }
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::SetSurfaceMute()
///////////////////////////////////////////////////////////////////////////////
// Description:  Notification that the mute state of a track has changed.
// Parameters:   [in] trackid: The track changed.
//               [in] mute:    New mute state of the track.
// Return value: No return value.
// Remarks:      Just updates the light on the surface.
///////////////////////////////////////////////////////////////////////////////
void CC121::SetSurfaceMute(MediaTrack* trackid, bool mute)
{
  // Is this our selected track?
  if (CSurf_TrackToID(trackid, options.useMCP) == currentTrackID)
  {
    // Update button state:
    SwitchLight(ButtonMute, mute);
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::SetSurfaceSelected()
///////////////////////////////////////////////////////////////////////////////
// Description:  Sets the selection state of a track.
// Parameters:   [in] trackid:  The track changed.
//               [in] selected: New selection state of the track.
// Return value: No return value.
// Remarks:      Validates the selection state of this surface.
///////////////////////////////////////////////////////////////////////////////
void CC121::SetSurfaceSelected(MediaTrack* trackid, bool selected)
{
  // First time selection?
  if (selected && (currentTrackID == -1))
  {
    // Initialize currently selected track:
    currentTrackID = CSurf_TrackToID(trackid, options.useMCP);
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::SetSurfaceSolo()
///////////////////////////////////////////////////////////////////////////////
// Description:  Notification that the solo state of a track has changed.
// Parameters:   [in] trackid: The track changed.
//               [in] solo:    New solo state of the track.
// Return value: No return value.
// Remarks:      Just updates the light on the surface.
///////////////////////////////////////////////////////////////////////////////
void CC121::SetSurfaceSolo(MediaTrack* trackid, bool solo)
{
  // Is this our selected track?
  if (CSurf_TrackToID(trackid, options.useMCP) == currentTrackID)
  {
    // Update button state:
    SwitchLight(ButtonSolo, solo);
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::SetSurfaceRecArm()
///////////////////////////////////////////////////////////////////////////////
// Description:  Notification that the record arm of a track has changed.
// Parameters:   [in] trackid: The track changed.
//               [in] recarm:  New record arm state of the track.
// Return value: No return value.
// Remarks:      Just updates the light on the surface.
///////////////////////////////////////////////////////////////////////////////
void CC121::SetSurfaceRecArm(MediaTrack* trackid, bool recarm)
{
  // Is this our selected track?
  if (CSurf_TrackToID(trackid, options.useMCP) == currentTrackID)
  {
    // Update button state:
    SwitchLight(ButtonRecArm, recarm);
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::SetPlayState()
///////////////////////////////////////////////////////////////////////////////
// Description:  Notification about a change in transport state.
// Parameters:   [in] play:  Current state of the play button.
//               [in] pause: Current state of the pause button.
//               [in] rec:   Current state of the record button.
// Return value: No return value.
// Remarks:      Updates the corresponding lights on the surface.
///////////////////////////////////////////////////////////////////////////////
void CC121::SetPlayState(bool play, bool pause, bool rec)
{
  // Update play and record buttons:
  SwitchLight(ButtonPlay, play || pause);
  SwitchLight(ButtonRecord, rec);

  // Store paused state:
  paused = pause;
}

///////////////////////////////////////////////////////////////////////////////
// CC121::SetRepeatState()
///////////////////////////////////////////////////////////////////////////////
// Description:  Notification that the repeat state has changed.
// Parameters:   [in] bool: New repeat state.
// Return value: No return value.
// Remarks:      Just updates the light on the surface.
///////////////////////////////////////////////////////////////////////////////
void CC121::SetRepeatState(bool rep)
{
  // Update button:
  SwitchLight(ButtonLoop, rep);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::SetTrackTitle()
///////////////////////////////////////////////////////////////////////////////
// Description:  Notification that the name of a track has changed.
// Parameters:   [in] trackid: The track changed.
//               [in] title:   New title of the track.
// Return value: No return value.
// Remarks:      We do nothing as we don't have a place to display this info.
///////////////////////////////////////////////////////////////////////////////
void CC121::SetTrackTitle(MediaTrack* trackid, const char* title)
{
  // Don't care.
}

///////////////////////////////////////////////////////////////////////////////
// CC121::GetTouchState()
///////////////////////////////////////////////////////////////////////////////
// Description:  Request for the current touch states of the faders.
// Parameters:   [in] trackid: The track in question.
//               [in] isPan:   Check pan fader?
// Return value: Returns true if the fader currently has been touched.
// Remarks:      This is used for automation to detect fader moves. Only the
//               volume fader is supported now.
///////////////////////////////////////////////////////////////////////////////
bool CC121::GetTouchState(MediaTrack* trackid, int isPan)
{
  // Is this our selected track?
  if (CSurf_TrackToID(trackid, options.useMCP) == currentTrackID)
    return false;

  // Our pan encoder has no touch mode:
  if (isPan && !faderSwapped)
    return false;

  // Return current fader handle state:
  return touching;
}

///////////////////////////////////////////////////////////////////////////////
// CC121::SetRepeatState()
///////////////////////////////////////////////////////////////////////////////
// Description:  Notification that the automation mode has changed.
// Parameters:   [in] mode: New automation.
// Return value: No return value.
// Remarks:      Uses the currently selected track as this parameter seems to
//               be missing here. Just updates the automation lights on the
//               surface.
///////////////////////////////////////////////////////////////////////////////
void CC121::SetAutoMode(int mode)
{
  // Hardware available?
  if (midiOut)
  {
    // Get current track and it's automation mode:
    MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
    if (tr)
      mode = GetTrackAutomationMode(tr);

    // Trim/Read mode, both lights off:
    if (mode == 0)
    {
      SwitchLight(ButtonRead,  false);
      SwitchLight(ButtonWrite, false);
    }

    // Read mode, R is on, W is off:
    else if (mode == 1)
    {
      SwitchLight(ButtonRead,  true);
      SwitchLight(ButtonWrite, false);
    }

    // Touch and Latch mode, both lights on:
    else if (mode == 2 || mode == 4)
    {
      SwitchLight(ButtonRead,  true);
      SwitchLight(ButtonWrite, true);
    }

    // Write mode, R is off, W is on:
    else if (mode == 3)
    {
      SwitchLight(ButtonRead,  false);
      SwitchLight(ButtonWrite, true);
    }
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::ResetCachedVolPanStates()
///////////////////////////////////////////////////////////////////////////////
// Description:  Request to reset internal control states.
// Parameters:   None.
// Return value: No return value.
// Remarks:      Good to flush your control states.
///////////////////////////////////////////////////////////////////////////////
void CC121::ResetCachedVolPanStates()
{
  // Reset states:
  faderState = 0x3200;
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnTrackSelection()
///////////////////////////////////////////////////////////////////////////////
// Description:  Notify the extension about newly selected track.
// Parameters:   None.
// Return value: No return value.
// Remarks:      Updates the internal selection states.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnTrackSelection(MediaTrack* trackid)
{
  // Get track ID:
  int newpos = CSurf_TrackToID(trackid, options.useMCP);

  // Is this already our track?
  if ((newpos >= 0) && (newpos != currentTrackID))
  {
    // Update internal track state:
    SkipTracks(newpos - currentTrackID, false);

    // Update control surface:
    TrackList_UpdateAllExternalSurfaces();
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::IsKeyDown()
///////////////////////////////////////////////////////////////////////////////
// Description:  Request for the state of the special keys on a surface.
// Parameters:   [in] key: The key in question.
// Return value: Returns true if the key is down or false otherwise.
// Remarks:      The key may be VK_CONTROL, VK_MENU, VK_SHIFT, etc, whatever
//               makes sense for a surface.
///////////////////////////////////////////////////////////////////////////////
bool CC121::IsKeyDown(int key)
{
  // We don't have special keys:
  return false;
}

///////////////////////////////////////////////////////////////////////////////
// CC121::Extended()
///////////////////////////////////////////////////////////////////////////////
// Description:  Extended command handling.
// Parameters:   [in]     call:  Message ID.
//               [in/out] parm1: First message parameter.
//               [in/out] parm2: Second message parameter.
//               [in/out] parm3: Third message parameter.
// Return value: Returns a value depending on the call ID. Returning zero
//               means that the call is not supported.
// Remarks:      There are no public defined message IDs yet so this function
//               always returns zero.
///////////////////////////////////////////////////////////////////////////////
int CC121::Extended(int call, void* parm1, void* parm2, void* parm3)
{
  // Unsupported:
  return 0;
}

///////////////////////////////////////////////////////////////////////////////
// CC121::CreateInstance()
///////////////////////////////////////////////////////////////////////////////
// Description:  Create an instance of this plugin.
// Parameters:   [in]  typeString:   Denotes the type of plugin to create (in
//                                   case an extension has registered more
//                                   plugins with this creation function).
//               [in]  configString: Configuration to use as string.
//               [out] errStats:     Returns error statistics back to REAPER.
//                                   Bit 0 denotes an input error and bit 1
//                                   an output error.
// Return value: Returns the newly created instance.
// Remarks:      This function will be registered to REAPER when the plugin
//               gets loaded. REAPER calls this function whenever it needs an
//               instance of this surface.
///////////////////////////////////////////////////////////////////////////////
IReaperControlSurface* CC121::CreateInstance(const char* typeString, const char* configString, int* errStats)
{
  // Create control surface class:
  // TODO: The created class will be deleted by REAPER's runtime. This might
  // introduce serious side effects:
  return new CC121(CC121Options(configString), errStats);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::CC121()
///////////////////////////////////////////////////////////////////////////////
// Description:  Copy constructor of this class.
// Parameters:   [in] other: The source instance to copy.
// Return value: No return value.
// Remarks:      Copying this class is prohibited and explicit construction
//               is required so this constructor is declared as private.
///////////////////////////////////////////////////////////////////////////////
CC121::CC121(const CC121& other)
{
  // Should never reach here.
  assert(FALSE);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::CC121()
///////////////////////////////////////////////////////////////////////////////
// Description:  Initialization constructor of this class.
// Parameters:   [in]  opt:      Options for this control surface.
//               [out] errStats: Returns error stats to REAPER.
// Return value: No return value.
// Remarks:      Initializes the members and opens the required MIDI input
//               and MIDI output.
///////////////////////////////////////////////////////////////////////////////
CC121::CC121(const CC121Options& opt, int* errStats) :
  options(opt),
  lastAISendTime(0),
  midiIn(NULL),
  midiOut(NULL),
  touching(false),
  lastMonChkTime(0),
  lastEQChkTime(0),
  lastFXChkTime(0),
  lastIOChkTime(0),
  currentTrackID(-1),
  paused(false),
  buttonState(ButtonNone),
  lightState(ButtonNone),
  faderState(0x3200),
  faderOff(false),
  faderSwapped(false),
  lastDblButtonTime(0),
  eqEditMode(EqEditBands),
  lastBlinkTime(0),
  jogOn(false)
{
  // Create MIDI input:
  if (options.midiInDev >= 0)
  {
    midiIn = CreateMIDIInput(options.midiInDev);
    if (midiIn == NULL)
    {
      options.midiInDev = -1;
      if (errStats != NULL)
        *errStats |= 1;
    }
  }

  // Create MIDI output:
  if (options.midiOutDev >= 0)
  {
    midiOut = CreateThreadedMIDIOutput(CreateMIDIOutput(options.midiOutDev, false, NULL));
    if (midiOut == NULL)
    {
      options.midiOutDev = -1;
      if (errStats != NULL)
        *errStats |= 2;
    }
  }

  // Load EQ maps:
  LoadEqMaps();

  // No maps found?
  if (eqMaps.size() == 0)
  {
    // Add hard coded entry for ReaEQ:
    eqMaps.push_back(CC121EqMap());
  }

  // Start the wait for MIDI events:
  if (midiIn)
    midiIn->start();

  // Reset surface:
  TrackList_UpdateAllExternalSurfaces();
}

///////////////////////////////////////////////////////////////////////////////
// CC121::operator = ()
///////////////////////////////////////////////////////////////////////////////
// Description:  Assignment operator of this class.
// Parameters:   [in] other: The source instance to copy.
// Return value: No return value.
// Remarks:      Copying this class is prohibited and explicit construction
//               is required so this operator is declared as private too.
///////////////////////////////////////////////////////////////////////////////
void CC121::operator = (const CC121& other)
{
  // Should never reach here.
  assert(FALSE);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::UpdateDevice()
///////////////////////////////////////////////////////////////////////////////
// Description:  Internal helper function to update the status of the device.
// Parameters:   [in] lts: New state of the lights (see enum above).
//               [in] vol: 
// Return value: No return value.
// Remarks:      This function is all that is needed to set the state of the
//               surface.
///////////////////////////////////////////////////////////////////////////////
void CC121::UpdateDevice(const ButtonState lts, const int vol)
{
  // Anything to do?
  if (lts == lightState && vol == faderState)
    return;

  if (midiOut)
  {
    // Update fader:
    if (!faderOff && vol != faderState)
      midiOut->Send(0xE0, vol & 0x7F, (vol >> 7) & 0x7F, -1);

    // Get bitwise difference of the current and the new state. This gives us
    // all the lights that have different settings and need an update:
    int lightDiff = lts ^ lightState;

    // Anything to change?
    if (lightDiff)
    {
      #define LIGHT_NOTE_ON(_light, _note) \
      if (lightDiff & _light) \
        midiOut->Send(0x90, _note, (lts & _light) ? 0x7F : 0x00, -1);

      #define LIGHT_NOTE_OFF(_light, _note) \
      if (lightDiff & _light) \
        midiOut->Send(0x80, _note, 0x00, -1);

      // Note-on messages:
      LIGHT_NOTE_ON(ButtonRecArm,   0x00);
      LIGHT_NOTE_ON(ButtonSolo,     0x08);
      LIGHT_NOTE_ON(ButtonMute,     0x10);
      LIGHT_NOTE_ON(ButtonEdit,     0x33);
      LIGHT_NOTE_ON(ButtonFunc1,    0x36);
      LIGHT_NOTE_ON(ButtonFunc2,    0x37);
      LIGHT_NOTE_ON(ButtonFunc3,    0x38);
      LIGHT_NOTE_ON(ButtonFunc4,    0x39);
      LIGHT_NOTE_ON(ButtonRead,     0x4A);
      LIGHT_NOTE_ON(ButtonWrite,    0x4B);
      LIGHT_NOTE_ON(ButtonLoop,     0x56);
      LIGHT_NOTE_ON(ButtonPlay,     0x5E);
      LIGHT_NOTE_ON(ButtonRecord,   0x5F);
      LIGHT_NOTE_ON(ButtonEQ1,      0x70);
      LIGHT_NOTE_ON(ButtonEQ2,      0x71);
      LIGHT_NOTE_ON(ButtonEQ3,      0x72);
      LIGHT_NOTE_ON(ButtonEQ4,      0x73);
      LIGHT_NOTE_ON(ButtonEQType,   0x74);
      LIGHT_NOTE_ON(ButtonEQBypass, 0x75);
      LIGHT_NOTE_ON(ButtonJog,      0x76);
      LIGHT_NOTE_ON(ButtonLock,     0x77);
      LIGHT_NOTE_ON(ButtonMonitor,  0x78);
      LIGHT_NOTE_ON(ButtonVSTi,     0x79);

      // Add a small pause to give the surface a break to handle all
      // the note-ons:
      Sleep(5);

      // Note-off messages:
      LIGHT_NOTE_OFF(ButtonRecArm,   0x00);
      LIGHT_NOTE_OFF(ButtonSolo,     0x08);
      LIGHT_NOTE_OFF(ButtonMute,     0x10);
      LIGHT_NOTE_OFF(ButtonEdit,     0x33);
      LIGHT_NOTE_OFF(ButtonFunc1,    0x36);
      LIGHT_NOTE_OFF(ButtonFunc2,    0x37);
      LIGHT_NOTE_OFF(ButtonFunc3,    0x38);
      LIGHT_NOTE_OFF(ButtonFunc4,    0x39);
      LIGHT_NOTE_OFF(ButtonRead,     0x4A);
      LIGHT_NOTE_OFF(ButtonWrite,    0x4B);
      LIGHT_NOTE_OFF(ButtonLoop,     0x56);
      LIGHT_NOTE_OFF(ButtonPlay,     0x5E);
      LIGHT_NOTE_OFF(ButtonRecord,   0x5F);
      LIGHT_NOTE_OFF(ButtonEQ1,      0x70);
      LIGHT_NOTE_OFF(ButtonEQ2,      0x71);
      LIGHT_NOTE_OFF(ButtonEQ3,      0x72);
      LIGHT_NOTE_OFF(ButtonEQ4,      0x73);
      LIGHT_NOTE_OFF(ButtonEQType,   0x74);
      LIGHT_NOTE_OFF(ButtonEQBypass, 0x75);
      LIGHT_NOTE_OFF(ButtonJog,      0x76);
      LIGHT_NOTE_OFF(ButtonLock,     0x77);
      LIGHT_NOTE_OFF(ButtonMonitor,  0x78);
      LIGHT_NOTE_OFF(ButtonVSTi,     0x79);

      #undef LIGHT_NOTE_ON
      #undef LIGHT_NOTE_OFF
    }
  }

  // Save state:
  lightState = lts;
  faderState = vol;
}

///////////////////////////////////////////////////////////////////////////////
// CC121::SwitchLight()
///////////////////////////////////////////////////////////////////////////////
// Description:  Set the state of a specific light on the control surface.
// Parameters:   [in] which: Light to toggle (for values see enum above).
//               [in] on:    Turn this light on or off?
// Return value: No return value.
// Remarks:      This is just a shortcut to the UpdateDevice function above.
//               The state of the light is changed immediately.
///////////////////////////////////////////////////////////////////////////////
void CC121::SwitchLight(const ButtonState which, const bool on)
{
  // Choose flags:
  if (on)
    UpdateDevice(static_cast<ButtonState>(lightState | which), faderState);
  else
    UpdateDevice(static_cast<ButtonState>(lightState & ~which), faderState);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::CheckRunningStatus()
///////////////////////////////////////////////////////////////////////////////
// Description:  Sends a message to the surface to ensure 'Cubase Ready'.
// Parameters:   [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      The CC121 will need to receive this message once a second to
//               remain operational. If the message has not been sent for
//               about three seconds then the device will turn off the lights
//               and the 'Cubase Ready' LED starts to blink.
///////////////////////////////////////////////////////////////////////////////
void CC121::CheckRunningStatus(const DWORD curTime)
{
  // Time to send running status?
  if (curTime > (lastAISendTime + 900))
  {
    // Create running status SysEx string:
    struct
    {
      MIDI_event_t  evt;
      unsigned char data[8];
    } sysex;
    sysex.evt.frame_offset    = 0;
    sysex.evt.size            = 8;
    sysex.evt.midi_message[0] = 0xF0;
    sysex.evt.midi_message[1] = 0x43;
    sysex.evt.midi_message[2] = 0x10;
    sysex.evt.midi_message[3] = 0x3E;
    sysex.evt.midi_message[4] = 0x15;
    sysex.evt.midi_message[5] = 0x00;
    sysex.evt.midi_message[6] = 0x01;
    sysex.evt.midi_message[7] = 0xF7;

    // Send the message:
    midiOut->SendMsg(&sysex.evt, -1);
    Sleep(5);

    // Lost connection?
    if (curTime > (lastAISendTime + 1500))
    {
      // Cache old light state:
      ButtonState currentLight = lightState;

      // Get current state of the channel volume:
      int currentFader = 0;
      MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
      if (!faderOff && tr)
      {
        if (faderSwapped)
        {
          double pan = *static_cast<double*>(GetSetMediaTrackInfo(tr, "D_PAN", NULL));
          currentFader = panToInt14(pan);
        }
        else
        {
          double vol = *static_cast<double*>(GetSetMediaTrackInfo(tr, "D_VOL", NULL));
          currentFader = volToInt14(vol);
        }
      }

      // Update state:
      lightState = ButtonNone;
      faderState = -1;
      UpdateDevice(currentLight, currentFader);

      // Update control surface:
      TrackList_UpdateAllExternalSurfaces();
    }

    // Save send time:
    lastAISendTime = curTime;
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::SetButtonState()
///////////////////////////////////////////////////////////////////////////////
// Description:  Set the up/down state of a device button.
// Parameters:   [in] which: ID of the button to modify.
//               [in] down:  Is it pressed?
// Return value: No return value.
// Remarks:      The button state is usually toggled by note-on and note-off
//               messages.
///////////////////////////////////////////////////////////////////////////////
void CC121::SetButtonState(const ButtonState which, const bool down)
{
  // Choose flags:
  if (down)
    buttonState = static_cast<ButtonState>(buttonState | which);
  else
    buttonState = static_cast<ButtonState>(buttonState & ~which);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::GetButtonState()
///////////////////////////////////////////////////////////////////////////////
// Description:  Retrieve the up/down state of a device button.
// Parameters:   [in] which: ID of the button to check.
// Return value: Returns the current pressed state of the requested button.
// Remarks:      The button state is usually toggled by note-on and note-off
//               messages.
///////////////////////////////////////////////////////////////////////////////
bool CC121::GetButtonState(const ButtonState which) const
{
  // Extract button state:
  return (buttonState & which) != 0;
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_RecArm()
///////////////////////////////////////////////////////////////////////////////
// Description:  The Record Arm button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_RecArm(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Get track:
    MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
    if (tr)
    {
      // Toggle record arm:
      SetSurfaceRecArm(tr, CSurf_OnRecArmChange(tr, -1));
    }
  }

  // Update button state:
  SetButtonState(ButtonRecArm, down);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Solo()
///////////////////////////////////////////////////////////////////////////////
// Description:  The Solo button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Solo(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Get track:
    MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
    if (tr)
    {
      // Toggle solo state:
      SetSurfaceSolo(tr, CSurf_OnSoloChange(tr, -1));
    }
  }

  // Update button state:
  SetButtonState(ButtonSolo, down);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Mute()
///////////////////////////////////////////////////////////////////////////////
// Description:  The Mute button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Mute(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Check for simultaneous press with the monitor button:
    if (GetButtonState(ButtonMonitor))
    {
      // Outside the time frame of simul click?
      if (curTime > (lastDblButtonTime + 100))
      {
        // Ignore this click:
        return;
      }

      // No, toggle fader function:
      else
      {
        // Toggle state:
        faderSwapped = !faderSwapped;

        // Update control surface:
        if (!faderOff)
        {
          faderState = -1;
          TrackList_UpdateAllExternalSurfaces();
        }

        // Reset buttons:
        SetButtonState(ButtonMonitor, false);
        SetButtonState(ButtonMute, false);
      }
    }

    else
    {
      // Set time out to give the user some time to click the monitor button:
      lastDblButtonTime = curTime;

      // Update button state:
      SetButtonState(ButtonMute, true);
    }
  }

  else if (GetButtonState(ButtonMute))
  {
    // Get track:
    MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
    if (tr)
    {
      // Toggle mute state:
      SetSurfaceMute(tr, CSurf_OnMuteChange(tr, -1));
    }

    // Update button state:
    SetButtonState(ButtonMute, false);
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Prev()
///////////////////////////////////////////////////////////////////////////////
// Description:  The Previous Channel button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Prev(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Check for simultaneous press with 'Next' button:
    if (GetButtonState(ButtonNext))
    {
      // Outside the time frame of simul click?
      if (curTime > (lastDblButtonTime + 100))
      {
        // Ignore this click:
        return;
      }

      // No, toggle folders:
      else
      {
        // Get folder state:
        int flags = 0;
        GetTrackInfo(currentTrackID - 1, &flags);
        if (flags & 1)
        {
          // Get track:
          MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
          if (tr)
          {
            // Get current compact state;
            int curState = *((int*)GetSetMediaTrackInfo(tr, "I_FOLDERCOMPACT", NULL));

            // Toggle state:
            curState = (curState + 1) % 3;

            // Set new state:
            GetSetMediaTrackInfo(tr, "I_FOLDERCOMPACT", &curState);
          }
        }

        // Reset buttons:
        SetButtonState(ButtonPrev, false);
        SetButtonState(ButtonNext, false);
      }
    }

    else
    {
      // Set time out to give the user some time to click the 'Next' button:
      lastDblButtonTime = curTime;

      // Update button state:
      SetButtonState(ButtonPrev, true);
    }
  }

  else if (GetButtonState(ButtonPrev))
  {
    // Advance to previous channel:
    SkipTracks(-1, true);
    TrackList_UpdateAllExternalSurfaces();

    // Update button state:
    SetButtonState(ButtonPrev, false);
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Next()
///////////////////////////////////////////////////////////////////////////////
// Description:  The Next Channel button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Next(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Check for simultaneous press with 'Prev' button:
    if (GetButtonState(ButtonPrev))
    {
      // Outside the time frame of simul click?
      if (curTime > (lastDblButtonTime + 100))
      {
        // Ignore this click:
        return;
      }

      // No, toggle folders:
      else
      {
        // Get folder state:
        int flags = 0;
        GetTrackInfo(currentTrackID - 1, &flags);
        if (flags & 1)
        {
          // Get track:
          MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
          if (tr)
          {
            // Get current compact state;
            int curState = *((int*)GetSetMediaTrackInfo(tr, "I_FOLDERCOMPACT", NULL));

            // Toggle state:
            curState = (curState + 1) % 3;

            // Set new state:
            GetSetMediaTrackInfo(tr, "I_FOLDERCOMPACT", &curState);
          }
        }

        // Reset buttons:
        SetButtonState(ButtonPrev, false);
        SetButtonState(ButtonNext, false);
      }
    }

    else
    {
      // Set time out to give the user some time to click the 'Next' button:
      lastDblButtonTime = curTime;

      // Update button state:
      SetButtonState(ButtonNext, true);
    }
  }

  else if (GetButtonState(ButtonNext))
  {
    // Advance to next channel:
    SkipTracks(1, true);
    TrackList_UpdateAllExternalSurfaces();

    // Update button state:
    SetButtonState(ButtonNext, false);
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Edit()
///////////////////////////////////////////////////////////////////////////////
// Description:  The 'e' button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Edit(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Get track:
    MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
    if (tr)
    {
      // Format window title:
      char wnd_title[256];
      if (currentTrackID == 0)
      {
        // Master has a different naming scheme:
        strcpy(wnd_title, "Outputs for Master Track");
      }
      else
      {
        int flags = 0;
        const char* track_name = GetTrackInfo(currentTrackID - 1, &flags);
        sprintf(wnd_title, "Routing for track %d \"%s\"", currentTrackID, track_name);

        // Add folder state:
        if (flags & 1)
          strcat(wnd_title, " (folder)");
      }
      
      // Search for the I/O window:
      HWND hIo = FindWindowEx(NULL, NULL, MAKEINTATOM(32770), wnd_title);

      // I/O editor open?
      if (hIo != NULL)
      {
        // Found fx window, close it:
        SendMessage(hIo, WM_CLOSE, 0, 0);
      }

      else if (currentTrackID != 0)
      {
        // Open track I/O panel:
        Main_OnCommand(ID_SHOW_TRACK_IO, 0);
      }

      else
      {
        // Open master output panel:
        // ...
      }
    }
  }

  // Update button state:
  SetButtonState(ButtonEdit, down);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Func1()
///////////////////////////////////////////////////////////////////////////////
// Description:  The Function 1 button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Func1(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Process command:
    if (options.user1BtnCmd.length())
    {
      // Try as number:
      int cmdID = atoi(options.user1BtnCmd.c_str());
      if (cmdID == 0)
      {
        // Not a number, try extension ID:
        cmdID = NamedCommandLookup(options.user1BtnCmd.c_str());
      }

      // Found?
      if (cmdID != 0)
      {
        // Send command:
        Main_OnCommand(cmdID, 0);
      }
    }
  }

  // Update button state:
  SetButtonState(ButtonFunc1, down);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Func2()
///////////////////////////////////////////////////////////////////////////////
// Description:  The Function 2 button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Func2(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Process command:
    if (options.user2BtnCmd.length())
    {
      // Try as number:
      int cmdID = atoi(options.user2BtnCmd.c_str());
      if (cmdID == 0)
      {
        // Not a number, try extension ID:
        cmdID = NamedCommandLookup(options.user2BtnCmd.c_str());
      }

      // Found?
      if (cmdID != 0)
      {
        // Send command:
        Main_OnCommand(cmdID, 0);
      }
    }
  }

  // Update button state:
  SetButtonState(ButtonFunc2, down);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Func3()
///////////////////////////////////////////////////////////////////////////////
// Description:  The Function 3 button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Func3(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Process command:
    if (options.user3BtnCmd.length())
    {
      // Try as number:
      int cmdID = atoi(options.user3BtnCmd.c_str());
      if (cmdID == 0)
      {
        // Not a number, try extension ID:
        cmdID = NamedCommandLookup(options.user3BtnCmd.c_str());
      }

      // Found?
      if (cmdID != 0)
      {
        // Send command:
        Main_OnCommand(cmdID, 0);
      }
    }
  }

  // Update button state:
  SetButtonState(ButtonFunc3, down);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Func4()
///////////////////////////////////////////////////////////////////////////////
// Description:  The Function 4 button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Func4(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Process command:
    if (options.user4BtnCmd.length())
    {
      // Try as number:
      int cmdID = atoi(options.user4BtnCmd.c_str());
      if (cmdID == 0)
      {
        // Not a number, try extension ID:
        cmdID = NamedCommandLookup(options.user4BtnCmd.c_str());
      }

      // Found?
      if (cmdID != 0)
      {
        // Send command:
        Main_OnCommand(cmdID, 0);
      }
    }
  }

  // Update button state:
  SetButtonState(ButtonFunc4, down);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Encoder()
///////////////////////////////////////////////////////////////////////////////
// Description:  The Value Encoder button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Encoder(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Process command:
    if (options.valueBtnCmd.length())
    {
      // Try as number:
      int cmdID = atoi(options.valueBtnCmd.c_str());
      if (cmdID == 0)
      {
        // Not a number, try extension ID:
        cmdID = NamedCommandLookup(options.valueBtnCmd.c_str());
      }

      // Found?
      if (cmdID != 0)
      {
        // Send command:
        Main_OnCommand(cmdID, 0);
      }
    }
  }

  // Update button state:
  SetButtonState(ButtonEncoder, down);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_FootSW()
///////////////////////////////////////////////////////////////////////////////
// Description:  The foot switch has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_FootSW(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Process command:
    if (options.ftSwitchCmd.length())
    {
      // Try as number:
      int cmdID = atoi(options.ftSwitchCmd.c_str());
      if (cmdID == 0)
      {
        // Not a number, try extension ID:
        cmdID = NamedCommandLookup(options.ftSwitchCmd.c_str());
      }

      // Found?
      if (cmdID != 0)
      {
        // Send command:
        Main_OnCommand(cmdID, 0);
      }
    }
  }

  // Update button state:
  SetButtonState(ButtonFootSW, down);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Read()
///////////////////////////////////////////////////////////////////////////////
// Description:  The Automation Read button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Read(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Get track:
    MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
    if (tr)
    {
      // Get current automation mode:
      int mode = GetTrackAutomationMode(tr);

      // Current mode is Trim/Read, switch to Read:
      if (mode == 0)
        SetTrackAutomationMode(tr, 1);

      // Current mode is Read, switch to Trim/Read:
      else if (mode == 1)
        SetTrackAutomationMode(tr, 0);

      // Current mode is Touch, switch to Write:
      else if (mode == 2)
        SetTrackAutomationMode(tr, 4);

      // Current mode is Write, switch to Touch:
      else if (mode == 4)
        SetTrackAutomationMode(tr, 2);

      // Ignore Latch mode:
      else
        return;

      // Update surface:
      CSurf_SetAutoMode(-1, NULL);
    }
  }

  // Update button state:
  SetButtonState(ButtonRead, down);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Write()
///////////////////////////////////////////////////////////////////////////////
// Description:  The Automation Write button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Write(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Check for simultaneous press with 'VSTi' button:
    if (GetButtonState(ButtonVSTi))
    {
      // Outside the time frame of simul click?
      if (curTime > (lastDblButtonTime + 100))
      {
        // Ignore this click:
        return;
      }

      // No, toggle fader state:
      else
      {
        // Toggle state:
        faderOff = !faderOff;

        // Update control surface:
        if (!faderOff)
        {
          faderState = -1;
          TrackList_UpdateAllExternalSurfaces();
        }

        // Reset buttons:
        SetButtonState(ButtonWrite, false);
        SetButtonState(ButtonVSTi, false);
      }
    }

    else
    {
      // Set time out to give the user some time to click the 'VSTi' button:
      lastDblButtonTime = curTime;

      // Update button state:
      SetButtonState(ButtonWrite, true);
    }
  }

  else if (GetButtonState(ButtonWrite))
  {
    // Get track:
    MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
    if (tr)
    {
      // Get current automation mode:
      int mode = GetTrackAutomationMode(tr);

      // Current mode is Trim/Read, switch to Write:
      if (mode == 0)
        SetTrackAutomationMode(tr, 3);

      // Current mode is Read, switch to Touch:
      else if (mode == 1)
        SetTrackAutomationMode(tr, 2);

      // Current mode is Touch, switch to Read:
      else if (mode == 2)
        SetTrackAutomationMode(tr, 1);

      // Current mode is Write, switch to Trim/Read:
      else if (mode == 3)
        SetTrackAutomationMode(tr, 0);

      // Ignore Latch mode:
      else
        return;

      // Update surface:
      CSurf_SetAutoMode(-1, NULL);
    }

    // Update button state:
    SetButtonState(ButtonWrite, true);
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Loop()
///////////////////////////////////////////////////////////////////////////////
// Description:  The Loop button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Loop(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Toggle loop:
    Main_OnCommand(IDC_REPEAT, 0);
  }

  // Update button state:
  SetButtonState(ButtonLoop, down);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_ToStart()
///////////////////////////////////////////////////////////////////////////////
// Description:  The To Start button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_ToStart(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Ignore markers?
    if (options.skipToStartEnd)
    {
      // Go to start of project:
      CSurf_GoStart();
    }
    else
    {
      // Go to previous marker:
      Main_OnCommand(ID_MARKER_PREV, 0);
    }
  }

  // Update button state:
  SetButtonState(ButtonToStart, down);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_ToEnd()
///////////////////////////////////////////////////////////////////////////////
// Description:  The To End button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_ToEnd(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Ignore markers?
    if (options.skipToStartEnd)
    {
      // Got to end of project:
      CSurf_GoEnd();
    }
    else
    {
      // Go to next marker:
      Main_OnCommand(ID_MARKER_NEXT, 0);
    }
  }

  // Update button state:
  SetButtonState(ButtonToEnd, down);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Rewind()
///////////////////////////////////////////////////////////////////////////////
// Description:  The Rewind button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Rewind(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Dont seek into both directions:
    if (GetButtonState(ButtonForward))
      return;
  }
  else if (GetButtonState(ButtonRewind))
  {
    // Play from edit cursor without start/stop:
    double pos = GetCursorPosition();
    SetEditCurPos(pos, true, true);
  }

  // Update button state:
  SetButtonState(ButtonRewind, down);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Forward()
///////////////////////////////////////////////////////////////////////////////
// Description:  The Forward button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Forward(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Dont seek into both directions:
    if (GetButtonState(ButtonRewind))
      return;
  }
  else if (GetButtonState(ButtonForward))
  {
    // Play from edit cursor without start/stop:
    double pos = GetCursorPosition();
    SetEditCurPos(pos, true, true);
  }

  // Update button state:
  SetButtonState(ButtonForward, down);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Stop()
///////////////////////////////////////////////////////////////////////////////
// Description:  The Stop button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Stop(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Stop REAPER:
    CSurf_OnStop();
  }

  // Update button state:
  SetButtonState(ButtonStop, down);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Play()
///////////////////////////////////////////////////////////////////////////////
// Description:  The Play button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Play(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Start REAPER:
    CSurf_OnPlay();
  }

  // Update button state:
  SetButtonState(ButtonPlay, down);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Record()
///////////////////////////////////////////////////////////////////////////////
// Description:  The Record button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Record(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Toggle record:
    CSurf_OnRecord();
  }

  // Update button state:
  SetButtonState(ButtonRecord, down);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_EQ()
///////////////////////////////////////////////////////////////////////////////
// Description:  One of the EQ buttons has been pressed or released.
// Parameters:   [in] number:  Number of the button (0, 1, 2 or 3).
//               [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_EQ(const int number, const bool down, const DWORD curTime)
{
  if (down)
  {
    // Quick control mode?
    if (eqEditMode == EqEditQuickControl)
    {
      // Toggle state of the send:
      MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
      if (tr)
      {
        // See if we have a send target:
        if (GetSetTrackSendInfo(tr, 0, number, "P_DESTTRACK", NULL) != NULL)
        {
          // Get old send state:
          bool old_val = *static_cast<bool*>(GetSetTrackSendInfo(tr, 0, number, "B_MUTE", NULL));

          // Invert state:
          old_val = !old_val;

          // Sent new:
          GetSetTrackSendInfo(tr, 0, number, "B_MUTE", &old_val);

          // Update light:
          SwitchLight(static_cast<ButtonState>(ButtonEQ1 << number), !old_val);
        }
      }
    }

    else if (eqEditMode == EqEditBands)
    {
      // Find a matching EQ map:
      int fxid = 0;
      const CC121EqMap* eqMap = FindTrackEQ(currentTrackID, fxid);
      if (eqMap != NULL)
      {
        // Find a matching EQ parameter:
        int param = eqMap->MapParameter(CC121EqMap::BandBypass, number);
        if (param >= 0)
        {
          // Get current state:
          double minval, maxval, val;
          MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
          val = TrackFX_GetParam(tr, fxid, param, &minval, &maxval);

          // Invert value:
          if (val < ((maxval - minval) / 2.0))
            val = maxval;
          else
            val = minval;

          // Set new value:
          TrackFX_SetParam(tr, fxid, param, val);
        }
      }
    }
  }

  // Update button state:
  SetButtonState(static_cast<ButtonState>(ButtonEQ1 + number), down);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_EQType()
///////////////////////////////////////////////////////////////////////////////
// Description:  The EQ Type button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_EQType(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Check for simultaneous press with 'All Bypass' button:
    if (GetButtonState(ButtonEQBypass))
    {
      // Outside the time frame of simul click?
      if (curTime > (lastDblButtonTime + 100))
      {
        // Ignore this click:
        return;
      }

      // No, toggle quick control state:
      else
      {
        // Toggle QuickControlMode...
        if (eqEditMode != EqEditQuickControl)
          eqEditMode = EqEditQuickControl;
        else
          eqEditMode = EqEditBands;

        // Reset buttons:
        SetButtonState(ButtonEQType, false);
        SetButtonState(ButtonEQBypass, false);
      }
    }

    else
    {
      // Set time out to give the user some time to click the 'All Bypass' button:
      lastDblButtonTime = curTime;

      // Update button state:
      SetButtonState(ButtonEQType, true);
    }
  }

  else if (GetButtonState(ButtonEQType))
  {
    if (eqEditMode != EqEditQuickControl)
    {
      // Toggle state:
      eqEditMode = eqEditMode == EqEditBands ? EqEditType : EqEditBands;

      // Update lights:
      SwitchLight(ButtonEQType, eqEditMode == EqEditType);
    }

    // Update button state:
    SetButtonState(ButtonEQType, false);
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_EQBypass()
///////////////////////////////////////////////////////////////////////////////
// Description:  The All Bypass button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_EQBypass(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Check for simultaneous press with 'EQ Type' button:
    if (GetButtonState(ButtonEQType))
    {
      // Outside the time frame of simul click?
      if (curTime > (lastDblButtonTime + 100))
      {
        // Ignore this click:
        return;
      }

      // No, toggle quick control state:
      else
      {
        // Toggle QuickControlMode...
        if (eqEditMode != EqEditQuickControl)
          eqEditMode = EqEditQuickControl;
        else
          eqEditMode = EqEditBands;

        // Reset buttons:
        SetButtonState(ButtonEQType, false);
        SetButtonState(ButtonEQBypass, false);
      }
    }

    else
    {
      // Set time out to give the user some time to click the 'EQ Type' button:
      lastDblButtonTime = curTime;

      // Update button state:
      SetButtonState(ButtonEQBypass, true);
    }
  }

  else if (GetButtonState(ButtonEQBypass))
  {
    if (eqEditMode != EqEditQuickControl)
    {
      bool bypassed = false;

      // Find a matching EQ map:
      int fxid = 0;
      const CC121EqMap* eqMap = FindTrackEQ(currentTrackID, fxid);
      if (eqMap != NULL)
      {
        // Find a matching EQ parameter:
        int param = eqMap->MapParameter(CC121EqMap::EqBypass, 0);
        if (param >= 0)
        {
          double minval, maxval, val;

          // Invert bypass state:
          MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
          val = TrackFX_GetParam(tr, fxid, param, &minval, &maxval);
          TrackFX_SetParam(tr, fxid, param, 1.0 - val);

          // Update state for the light:
          bypassed = val < 0.5;
        }
      }

      // Update lights:
      SwitchLight(ButtonEQBypass, bypassed);
    }

    // Update button state:
    SetButtonState(ButtonEQBypass, false);
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Jog()
///////////////////////////////////////////////////////////////////////////////
// Description:  The Jog button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Jog(const bool down, const DWORD curTime)
{
  // Toggle state:
  if (down)
    jogOn = !jogOn;

  // Update light:
  SwitchLight(ButtonJog, jogOn);

  // Update button state:
  SetButtonState(ButtonJog, down);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Lock()
///////////////////////////////////////////////////////////////////////////////
// Description:  The Lock button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Lock(const bool down, const DWORD curTime)
{
  if (down)
  {
  }

  // Update button state:
  SetButtonState(ButtonLock, down);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Monitor()
///////////////////////////////////////////////////////////////////////////////
// Description:  The Input Monitor button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Monitor(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Check for simultaneous press with the mute button:
    if (GetButtonState(ButtonMute))
    {
      // Outside the time frame of simul click?
      if (curTime > (lastDblButtonTime + 100))
      {
        // Ignore this click:
        return;
      }

      // No, toggle fader function:
      else
      {
        // Toggle state:
        faderSwapped = !faderSwapped;

        // Update control surface:
        if (!faderOff)
        {
          faderState = -1;
          TrackList_UpdateAllExternalSurfaces();
        }

        // Reset buttons:
        SetButtonState(ButtonMonitor, false);
        SetButtonState(ButtonMute, false);
      }
    }

    else
    {
      // Set time out to give the user some time to click the mute button:
      lastDblButtonTime = curTime;

      // Update button state:
      SetButtonState(ButtonMonitor, true);
    }
  }

  else if (GetButtonState(ButtonMonitor))
  {
    // Don't monitor master track:
    if (currentTrackID != 0)
    {
      // Get track:
      MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
      if (tr)
      {
        // Get current state:
        int state = *static_cast<int*>(GetSetMediaTrackInfo(tr, "I_RECMON", NULL));

        // Set new state:
        state = (state + 1) % 3;
        GetSetMediaTrackInfo(tr, "I_RECMON", &state);

        // Update light state:
        SwitchLight(ButtonMonitor, state != 0);

        // Init timer:
        lastMonChkTime = timeGetTime();
      }
    }

    // Update button state:
    SetButtonState(ButtonMonitor, false);
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_VSTi()
///////////////////////////////////////////////////////////////////////////////
// Description:  The VSTi button has been pressed or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_VSTi(const bool down, const DWORD curTime)
{
  if (down)
  {
    // Check for simultaneous press with 'W' button:
    if (GetButtonState(ButtonWrite))
    {
      // Outside the time frame of simul click?
      if (curTime > (lastDblButtonTime + 100))
      {
        // Ignore this click:
        return;
      }

      // No, toggle fader state:
      else
      {
        // Toggle state:
        faderOff = !faderOff;

        // Update control surface:
        if (!faderOff)
        {
          faderState = -1;
          TrackList_UpdateAllExternalSurfaces();
        }

        // Reset buttons:
        SetButtonState(ButtonWrite, false);
        SetButtonState(ButtonVSTi, false);
      }
    }

    else
    {
      // Set time out to give the user some time to click the 'W' button:
      lastDblButtonTime = curTime;

      // Update button state:
      SetButtonState(ButtonVSTi, true);
    }
  }

  else if (GetButtonState(ButtonVSTi))
  {
    // Get track:
    MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
    if (tr)
    {
      char wnd_title[256];
      const char* track_name = NULL;
      int flags = 0;

      // Format window title:
      if (currentTrackID == 0)
      {
        // Master track:
        strcpy(wnd_title, "FX: Master Track");
      }
      else
      {
        track_name = GetTrackInfo(currentTrackID - 1, &flags);
        if (!track_name || track_name[0] == 0)
          sprintf(wnd_title, "FX: Track %d", currentTrackID);
        else
          sprintf(wnd_title, "FX: Track %d \"%s\"", currentTrackID, track_name);

        // Add folder state:
        if (flags & 1)
          strcat(wnd_title, " (folder)");
      }
      
      // Search for the FX window:
      HWND hFx = FindWindowEx(NULL, NULL, MAKEINTATOM(32770), wnd_title);

      // FX editor open?
      if (hFx != NULL)
      {
        // Found fx window, close it:
        SendMessage(hFx, WM_CLOSE, 0, 0);

        // Search for a matching Add FX window and close it too:
        if (currentTrackID == 0)
        {
          // Master track:
          strcpy(wnd_title, "Add FX to: Master Track");
        }
        else
        {
          // Regular track:
          if (!track_name || track_name[0] == 0)
            sprintf(wnd_title, "Add FX to: Track %d", currentTrackID);
          else
            sprintf(wnd_title, "Add FX to: Track %d \"%s\"", currentTrackID, track_name);
          if (flags & 1)
            strcat(wnd_title, " (folder)");
        }
        hFx = FindWindowEx(NULL, NULL, MAKEINTATOM(32770), wnd_title);
        if (hFx != NULL)
          SendMessage(hFx, WM_CLOSE, 0, 0);
      }

      // Open the FX window:
      else if (currentTrackID != 0)
      {
        // Show track FX:
        Main_OnCommand(ID_SHOW_FX_BROWSER, 0);
      }
      else
      {
        // Show master FX:
        Main_OnCommand(ID_SHOW_MASTER_FX, 0);
      }
    }

    // Update button state:
    SetButtonState(ButtonVSTi, false);
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Btn_Fader()
///////////////////////////////////////////////////////////////////////////////
// Description:  The fader has been touched or released.
// Parameters:   [in] down:    True if pressed, false if released.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Btn_Fader(const bool down, const DWORD curTime)
{
  // Save state:
  touching = down;
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Enc_Pan()
///////////////////////////////////////////////////////////////////////////////
// Description:  The pan encoder has been moved.
// Parameters:   [in] amount:  Amount of clicks, already accelerated.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device. The number of clicks has already been scaled
//               by the AccelRotary() function.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Enc_Pan(const double amount, const DWORD curTime)
{
  // Get current track:
  MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
  if (tr)
  {
    if (faderSwapped)
    {
      // Get amount of change (encoder clicks) * (change per click):
      double adj = static_cast<int>(amount * 3.0);

      // Update volume:
      CSurf_SetSurfaceVolume(tr, CSurf_OnVolumeChange(tr, adj, true), NULL);
    }
    else
    {
      // Get amount of change (encoder clicks) * (change per click):
      double adj = amount * (options.rotarySpeed / 1000.0);

      // Update pan value:
      CSurf_SetSurfacePan(tr, CSurf_OnPanChange(tr, adj, true), NULL);
    }
  }
}


///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Enc_EQ()
///////////////////////////////////////////////////////////////////////////////
// Description:  One of the EQ encoders has been moved.
// Parameters:   [in] amount:  Amount of clicks, already accelerated.
//               [in] row:     Encoder row (0 = Q, 1 = F, 2 = G).
//               [in] chan:    Encoder channel (0, 1, 2, 3)
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device. The number of clicks has already been scaled
//               by the AccelRotary() function.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Enc_EQ(const double amount, const int row, const int chan, const DWORD curTime)
{
  if (eqEditMode == EqEditBands)
  {
    // Find a matching EQ map:
    int fxid = 0;
    const CC121EqMap* eqMap = FindTrackEQ(currentTrackID, fxid);
    if (eqMap != NULL)
    {
      // Find a matching EQ parameter:
      double scale = 0.0;
      int param = eqMap->MapParameter(static_cast<CC121EqMap::EqParam>(row), chan, &scale);
      if (param >= 0)
      {
        // Get amount of change (encoder clicks) * (change per click):
        double adj = amount * (options.rotarySpeed / scale);

        // Get current state:
        double minval, maxval, val;
        MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
        val = TrackFX_GetParam(tr, fxid, param, &minval, &maxval);
        
        // Clip value:
        val += adj;
        if (val < minval)
          val = minval;
        else if (val > maxval)
          val = maxval;

        // Set new value:
        TrackFX_SetParam(tr, fxid, param, val);
      }
    }
  }

  else if (eqEditMode == EqEditType && row == 2)
  {
    // Find a matching EQ map:
    int fxid = 0;
    const CC121EqMap* eqMap = FindTrackEQ(currentTrackID, fxid);
    if (eqMap != NULL)
    {
      // Find a matching EQ parameter:
      double scale = 0.0;
      int param = eqMap->MapParameter(CC121EqMap::Type, chan, &scale);
      if (param >= 0)
      {
        // Get amount of change (encoder clicks) * (change per click):
        double adj = amount * (1.0 / scale);

        // Get current state:
        double minval, maxval, val;
        MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
        val = TrackFX_GetParam(tr, fxid, param, &minval, &maxval);
        
        // Clip value:
        val += adj;
        if (val < minval)
          val = minval;
        else if (val > maxval)
          val = maxval;

        // Set new value:
        TrackFX_SetParam(tr, fxid, param, val);
      }
    }
  }

  else if (eqEditMode == EqEditQuickControl)
  {
    // Get current track:
    MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
    if (tr)
    {
      // Gain knob? Adjust sends:
      if (row == 2)
      {
        // See if we have a send target:
        if (GetSetTrackSendInfo(tr, 0, chan, "P_DESTTRACK", NULL) != NULL)
        {
          // Get amount of change (encoder clicks) * (change per click):
          double adj =amount * (options.rotarySpeed / 100.0);

          // Get old send value:
          double old_val = *((double*)GetSetTrackSendInfo(tr, 0, chan, "D_VOL", NULL));

          // Calc new send value:
          old_val = VAL2DB(old_val);
          old_val += adj;
          old_val = DB2VAL(old_val);

          // Sent new:
          GetSetTrackSendInfo(tr, 0, chan, "D_VOL", &old_val);
        }
      }

      // No, q or freq knob:
      else
      {
        // Get knob:
        int knob = chan + (row * 4);

        // Get amount of change (encoder clicks) * (change per click):
        int adj = static_cast<int>(amount * 8.0);

        // Move FX parameter:
        KBD_OnMainActionEx(950 + knob, adj, -1, 1, 0, 0);
      }
    }
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Enc_Value()
///////////////////////////////////////////////////////////////////////////////
// Description:  The value encoder has been moved.
// Parameters:   [in] amount:  Amount of clicks, already accelerated.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device. The number of clicks has already been scaled
//               by the AccelRotary() function.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Enc_Value(const double amount, const DWORD curTime)
{
  // Process command:
  if (options.valueEncCmd.length())
  {
    // Try as number:
    int cmdID = atoi(options.valueEncCmd.c_str());
    if (cmdID == 0)
    {
      // Not a number, try extension ID:
      cmdID = NamedCommandLookup(options.valueEncCmd.c_str());
    }

    // Found?
    if (cmdID != 0)
    {
      // Get amount of change (encoder clicks) * (change per click):
      int adj = static_cast<int>(amount * 8.0);

      // Move parameter:
      KBD_OnMainActionEx(cmdID, adj, -1, 1, 0, 0);
    }
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Enc_Jog()
///////////////////////////////////////////////////////////////////////////////
// Description:  The jog/AI encoder has been moved.
// Parameters:   [in] amount:  Amount of clicks, already accelerated.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device. The number of clicks has already been scaled
//               by the AccelRotary() function.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Enc_Jog(const double amount, const DWORD curTime)
{
  // Jogging?
  if (jogOn)
    CSurf_ScrubAmt(amount / 2.0);

  // No, control mode:
  else
  {
    // Get cursor position as POINTS:
    POINT p32;
    GetCursorPos(&p32);

    // Get amount:
    int adj = static_cast<int>(amount * 120.0);

    // Send the message:
    INPUT ip;
    ip.type           = INPUT_MOUSE;
    ip.mi.dwFlags     = MOUSEEVENTF_WHEEL | MOUSEEVENTF_ABSOLUTE;
    ip.mi.dx          = p32.x;
    ip.mi.dy          = p32.y;
    ip.mi.mouseData   = adj;
    ip.mi.dwExtraInfo = 0;
    ip.mi.time        = 0;
    SendInput(1, &ip, sizeof(INPUT));
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnCC121Fader_Move()
///////////////////////////////////////////////////////////////////////////////
// Description:  The pan fader has been moved.
// Parameters:   [in] amount:  Fader position as 14 bit (pitch bend) value.
//               [in] curTime: Time stamp of the event.
// Return value: No return value.
// Remarks:      Called when the appropriate MIDI message is received from the
//               hardware device.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnCC121Fader_Move(const int amount, const DWORD curTime)
{
  // Get current track:
  MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
  if (tr)
  {
    if (faderSwapped)
    {
      // Update pan:
      CSurf_SetSurfacePan(tr, CSurf_OnPanChange(tr, int14ToPan(amount), false), NULL);
    }
    else
    {
      // Update volume value:
      CSurf_SetSurfaceVolume(tr, CSurf_OnVolumeChange(tr, int14ToVol(amount), false), NULL);
    }
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::OnMIDIEvent()
///////////////////////////////////////////////////////////////////////////////
// Description:  Internal helper function to handle incomming MIDI events.
// Parameters:   [in] evt:     The event to decode.
//               [in] curTime: Current time.
// Return value: No return value.
// Remarks:      This is the function where the actual messages of the
//               hardware are decoded and dispatched.
///////////////////////////////////////////////////////////////////////////////
void CC121::OnMIDIEvent(const MIDI_event_t* evt, const DWORD curTime)
{
  // Note on?
  if (evt->midi_message[0] == 0x90 && evt->midi_message[2] > 0)
  {
    if      (evt->midi_message[1] == 0x00) OnCC121Btn_RecArm(true, curTime);
    else if (evt->midi_message[1] == 0x08) OnCC121Btn_Solo(true, curTime);
    else if (evt->midi_message[1] == 0x10) OnCC121Btn_Mute(true, curTime);
    else if (evt->midi_message[1] == 0x30) OnCC121Btn_Prev(true, curTime);
    else if (evt->midi_message[1] == 0x31) OnCC121Btn_Next(true, curTime);
    else if (evt->midi_message[1] == 0x33) OnCC121Btn_Edit(true, curTime);
    else if (evt->midi_message[1] == 0x36) OnCC121Btn_Func1(true, curTime);
    else if (evt->midi_message[1] == 0x37) OnCC121Btn_Func2(true, curTime);
    else if (evt->midi_message[1] == 0x38) OnCC121Btn_Func3(true, curTime);
    else if (evt->midi_message[1] == 0x39) OnCC121Btn_Func4(true, curTime);
    else if (evt->midi_message[1] == 0x3A) OnCC121Btn_Encoder(true, curTime);
    else if (evt->midi_message[1] == 0x3B) OnCC121Btn_FootSW(true, curTime);
    else if (evt->midi_message[1] == 0x4A) OnCC121Btn_Read(true, curTime);
    else if (evt->midi_message[1] == 0x4B) OnCC121Btn_Write(true, curTime);
    else if (evt->midi_message[1] == 0x56) OnCC121Btn_Loop(true, curTime);
    else if (evt->midi_message[1] == 0x58) OnCC121Btn_ToStart(true, curTime);
    else if (evt->midi_message[1] == 0x5A) OnCC121Btn_ToEnd(true, curTime);
    else if (evt->midi_message[1] == 0x5B) OnCC121Btn_Rewind(true, curTime);
    else if (evt->midi_message[1] == 0x5C) OnCC121Btn_Forward(true, curTime);
    else if (evt->midi_message[1] == 0x5D) OnCC121Btn_Stop(true, curTime);
    else if (evt->midi_message[1] == 0x5E) OnCC121Btn_Play(true, curTime);
    else if (evt->midi_message[1] == 0x5F) OnCC121Btn_Record(true, curTime);
    else if (evt->midi_message[1] == 0x70) OnCC121Btn_EQ(0, true, curTime);
    else if (evt->midi_message[1] == 0x71) OnCC121Btn_EQ(1, true, curTime);
    else if (evt->midi_message[1] == 0x72) OnCC121Btn_EQ(2, true, curTime);
    else if (evt->midi_message[1] == 0x73) OnCC121Btn_EQ(3, true, curTime);
    else if (evt->midi_message[1] == 0x74) OnCC121Btn_EQType(true, curTime);
    else if (evt->midi_message[1] == 0x75) OnCC121Btn_EQBypass(true, curTime);
    else if (evt->midi_message[1] == 0x76) OnCC121Btn_Jog(true, curTime);
    else if (evt->midi_message[1] == 0x77) OnCC121Btn_Lock(true, curTime);
    else if (evt->midi_message[1] == 0x78) OnCC121Btn_Monitor(true, curTime);
    else if (evt->midi_message[1] == 0x79) OnCC121Btn_VSTi(true, curTime);
    else if (evt->midi_message[1] == 0x68) OnCC121Btn_Fader(true, curTime);
  }

  // Note off?
  else if (evt->midi_message[0] == 0x80 || (evt->midi_message[0] == 0x90 && evt->midi_message[2] == 0))
  {
    if      (evt->midi_message[1] == 0x00) OnCC121Btn_RecArm(false, curTime);
    else if (evt->midi_message[1] == 0x08) OnCC121Btn_Solo(false, curTime);
    else if (evt->midi_message[1] == 0x10) OnCC121Btn_Mute(false, curTime);
    else if (evt->midi_message[1] == 0x30) OnCC121Btn_Prev(false, curTime);
    else if (evt->midi_message[1] == 0x31) OnCC121Btn_Next(false, curTime);
    else if (evt->midi_message[1] == 0x33) OnCC121Btn_Edit(false, curTime);
    else if (evt->midi_message[1] == 0x36) OnCC121Btn_Func1(false, curTime);
    else if (evt->midi_message[1] == 0x37) OnCC121Btn_Func2(false, curTime);
    else if (evt->midi_message[1] == 0x38) OnCC121Btn_Func3(false, curTime);
    else if (evt->midi_message[1] == 0x39) OnCC121Btn_Func4(false, curTime);
    else if (evt->midi_message[1] == 0x3A) OnCC121Btn_Encoder(false, curTime);
    else if (evt->midi_message[1] == 0x3B) OnCC121Btn_FootSW(false, curTime);
    else if (evt->midi_message[1] == 0x4A) OnCC121Btn_Read(false, curTime);
    else if (evt->midi_message[1] == 0x4B) OnCC121Btn_Write(false, curTime);
    else if (evt->midi_message[1] == 0x56) OnCC121Btn_Loop(false, curTime);
    else if (evt->midi_message[1] == 0x58) OnCC121Btn_ToStart(false, curTime);
    else if (evt->midi_message[1] == 0x5A) OnCC121Btn_ToEnd(false, curTime);
    else if (evt->midi_message[1] == 0x5B) OnCC121Btn_Rewind(false, curTime);
    else if (evt->midi_message[1] == 0x5C) OnCC121Btn_Forward(false, curTime);
    else if (evt->midi_message[1] == 0x5D) OnCC121Btn_Stop(false, curTime);
    else if (evt->midi_message[1] == 0x5E) OnCC121Btn_Play(false, curTime);
    else if (evt->midi_message[1] == 0x5F) OnCC121Btn_Record(false, curTime);
    else if (evt->midi_message[1] == 0x70) OnCC121Btn_EQ(0, false, curTime);
    else if (evt->midi_message[1] == 0x71) OnCC121Btn_EQ(1, false, curTime);
    else if (evt->midi_message[1] == 0x72) OnCC121Btn_EQ(2, false, curTime);
    else if (evt->midi_message[1] == 0x73) OnCC121Btn_EQ(3, false, curTime);
    else if (evt->midi_message[1] == 0x74) OnCC121Btn_EQType(false, curTime);
    else if (evt->midi_message[1] == 0x75) OnCC121Btn_EQBypass(false, curTime);
    else if (evt->midi_message[1] == 0x76) OnCC121Btn_Jog(false, curTime);
    else if (evt->midi_message[1] == 0x77) OnCC121Btn_Lock(false, curTime);
    else if (evt->midi_message[1] == 0x78) OnCC121Btn_Monitor(false, curTime);
    else if (evt->midi_message[1] == 0x79) OnCC121Btn_VSTi(false, curTime);
    else if (evt->midi_message[1] == 0x68) OnCC121Btn_Fader(false, curTime);
  }

  // Control change?
  else if (evt->midi_message[0] == 0xB0)
  {
    if      (evt->midi_message[1] == 0x10) OnCC121Enc_Pan(AccelRotary(evt->midi_message[2]), curTime);
    else if (evt->midi_message[1] == 0x20) OnCC121Enc_EQ(AccelRotary(evt->midi_message[2]), 0, 0, curTime);
    else if (evt->midi_message[1] == 0x21) OnCC121Enc_EQ(AccelRotary(evt->midi_message[2]), 0, 1, curTime);
    else if (evt->midi_message[1] == 0x22) OnCC121Enc_EQ(AccelRotary(evt->midi_message[2]), 0, 2, curTime);
    else if (evt->midi_message[1] == 0x23) OnCC121Enc_EQ(AccelRotary(evt->midi_message[2]), 0, 3, curTime);
    else if (evt->midi_message[1] == 0x30) OnCC121Enc_EQ(AccelRotary(evt->midi_message[2]), 1, 0, curTime);
    else if (evt->midi_message[1] == 0x31) OnCC121Enc_EQ(AccelRotary(evt->midi_message[2]), 1, 1, curTime);
    else if (evt->midi_message[1] == 0x32) OnCC121Enc_EQ(AccelRotary(evt->midi_message[2]), 1, 2, curTime);
    else if (evt->midi_message[1] == 0x33) OnCC121Enc_EQ(AccelRotary(evt->midi_message[2]), 1, 3, curTime);
    else if (evt->midi_message[1] == 0x40) OnCC121Enc_EQ(AccelRotary(evt->midi_message[2]), 2, 0, curTime);
    else if (evt->midi_message[1] == 0x41) OnCC121Enc_EQ(AccelRotary(evt->midi_message[2]), 2, 1, curTime);
    else if (evt->midi_message[1] == 0x42) OnCC121Enc_EQ(AccelRotary(evt->midi_message[2]), 2, 2, curTime);
    else if (evt->midi_message[1] == 0x43) OnCC121Enc_EQ(AccelRotary(evt->midi_message[2]), 2, 3, curTime);
    else if (evt->midi_message[1] == 0x50) OnCC121Enc_Value(AccelRotary(evt->midi_message[2]), curTime);
    else if (evt->midi_message[1] == 0x3C) OnCC121Enc_Jog(AccelRotary(evt->midi_message[2]), curTime);
  }

  // Pitch bend?
  if (evt->midi_message[0] == 0xE0)
    OnCC121Fader_Move(evt->midi_message[1] | (evt->midi_message[2] << 7), curTime);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::SkipTracks()
///////////////////////////////////////////////////////////////////////////////
// Description:  Internal helper function to move from track to track.
// Parameters:   [in] amt:   Amount of tracks to move, may be negative.
//               [in] dosel: Select the choosen track.
// Return value: No return value.
// Remarks:      The wrap around mode is controlled by the options.
///////////////////////////////////////////////////////////////////////////////
void CC121::SkipTracks(const int amt, const bool dosel)
{
  // Anything to do?
  if (amt == 0)
    return;

  // Calc new track ID:
  int newTrackID = currentTrackID + amt;

  // Clip value:
  if (newTrackID < 0)
  {
    if (options.wrapAround)
      newTrackID = CSurf_NumTracks(options.useMCP);
    else
      newTrackID = 0;
  }
  else if (amt > 0)
  {
    int trackCnt = CSurf_NumTracks(options.useMCP);
    if (newTrackID > trackCnt)
    {
      if (options.wrapAround)
        newTrackID = 0;
      else
        newTrackID = trackCnt;
    }
  }

  // Anything to do?
  if (newTrackID == currentTrackID)
    return;

  // Set new track ID:
  currentTrackID = newTrackID;

  // Force a channel state check:
  lastMonChkTime = 0;
  lastEQChkTime  = 0;
  lastFXChkTime  = 0;
  lastIOChkTime  = 0;

  // Select it?
  if (dosel)
  {
    // Select our track:
    MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
    if (tr)
    {
      CSurf_SetSurfaceSelected(tr, CSurf_OnSelectedChange(tr, true), NULL);
      SetLastTouchedTrack(tr);
    }

    // Deselect all tracks:
    for (int x = 0; ; x++)
    {
      // Get track state:
      int flags = 0;
      if (!GetTrackInfo(x - 1, &flags))
        break;
      
      // Get track:
      MediaTrack* tt = CSurf_TrackFromID(x, options.useMCP);
      if (!tt || tr == tt)
        continue;

      // Skip already deselected tracks:
      if ((flags & 2) == 0)
        continue;

      // Deselect track:
      SetTrackSelected(tt, false);
    }
  }
}

///////////////////////////////////////////////////////////////////////////////
// CC121::int14ToVol()
///////////////////////////////////////////////////////////////////////////////
// Description:  Convert a 14 bit value to a REAPER compatible volume value.
// Parameters:   [in] val: The 14 bit value.
// Return value: Returns the converted volume.
// Remarks:      The volume value reaches from -oo to +24dB. This version
//               differs from the original SDK version as it introduces an
//               offset to hit the 0 dB marker on the actual control surface.
///////////////////////////////////////////////////////////////////////////////
double CC121::int14ToVol(const int val)
{
  double pos = (static_cast<double>(val) * 1000.0) / 16384.0;
  pos = SLIDER2DB(pos);
  return DB2VAL(pos - 0.33);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::volToInt14()
///////////////////////////////////////////////////////////////////////////////
// Description:  Convert a REAPER volume value to a 14 bit MSB/LSB value.
// Parameters:   [in] vol: The volume value.
// Return value: Returns the converted volume.
// Remarks:      The volume value reaches from -oo to +24dB. This version
//               differs from the original SDK version as it introduces an
//               offset to hit the 0 dB marker on the actual control surface.
///////////////////////////////////////////////////////////////////////////////
int CC121::volToInt14(const double vol)
{
  double d = (DB2SLIDER(VAL2DB(vol) + 0.33) * 16384.0 / 1000.0);
  if (d <0.0)
    d = 0.0;
  else if (d > 16383.0)
    d = 16383.0;
  return static_cast<int>(d + 0.5);
}

///////////////////////////////////////////////////////////////////////////////
// CC121::SetLastTouchedTrack()
///////////////////////////////////////////////////////////////////////////////
// Description:  Internal helper to set the active track for REAPER's actions.
// Parameters:   [in] tr: The target track.
// Return value: No return value.
// Remarks:      Many of REPEAR's actions which are invoked with Main_OnCommand
//               work on the track that has been touched last by the user and
//               not the selected tracks.
///////////////////////////////////////////////////////////////////////////////
void CC121::SetLastTouchedTrack(MediaTrack* tr) const
{
  // Get positions of the current touched track and the desired track:
  int new_id = CSurf_TrackToID(tr, options.useMCP);
  int old_id = CSurf_TrackToID(GetLastTouchedTrack(), options.useMCP);

  // Move 'touch cursor' to the desired track (one track a time):
  while (old_id < new_id)
  {
    old_id++;
    Main_OnCommand(ID_TRACK_NEXT, 0);
  }
  while (old_id > new_id)
  {
		old_id--;
    Main_OnCommand(ID_TRACK_PREV, 0);
  }
}

/////////////////////////////////////////////////////////////////////////////////
// CC121::AccelRotary()
/////////////////////////////////////////////////////////////////////////////////
// Description:  Convert encoder speed to value change.
// Parameters:   [in] speed: The rotation speed as reported by the device.
// Return value: Returns the rotation speed as accellerated value.
// Remarks:      Uses an exponential curve to create a reasonable feel on the
//               encoder.
/////////////////////////////////////////////////////////////////////////////////
double CC121::AccelRotary(const int speed) const
{
  // Extract absolute value:
  double adj = static_cast<double>(speed & ~0x40);

  // Get direction (negative values start at 0x40):
  double sign = (speed & 0x40) ? -1.0 : 1.0;

  // Create an exponential curve:
  return sign * pow(adj, 1.0 + static_cast<double>(options.rotaryAccel) / 10.0);
}

/////////////////////////////////////////////////////////////////////////////////
// CC121::LoadEqMaps()
/////////////////////////////////////////////////////////////////////////////////
// Description:  Load the EQ map from file.
// Parameters:   None.
// Return value: No return value.
// Remarks:      EQ maps have a *.eqmap extension. This functions searches for
//               maps under $REAPERDIR/Data/CC121/*.
/////////////////////////////////////////////////////////////////////////////////
void CC121::LoadEqMaps()
{
  // Find map directory:
  const char* exePath = GetExePath();
  if (!exePath)
    return;

  // Compose map path:
  char map_path[_MAX_PATH];
  strcpy(map_path, exePath);
  strcat(map_path, "\\InstallData\\Data\\CC121\\");

  // Compose filter:
  char filter[_MAX_PATH];
  strcpy(filter, map_path);
  strcat(filter, "*.eqmap");

  // Search for files:
  WIN32_FIND_DATAA fd;
  HANDLE hFind = FindFirstFileA(filter, &fd);
  if (hFind != INVALID_HANDLE_VALUE)
  {
    do
    {
      // Compose full name:
      char file_name[_MAX_PATH];
      strcpy(file_name, map_path);
      strcat(file_name, fd.cFileName);

      // Try to load the file:
      CC121EqMap m;
      if (m.LoadMap(file_name))
      {
        // Found one:
        eqMaps.push_back(m);
      }
    } while (FindNextFileA(hFind, &fd));

    // Done:
    FindClose(hFind);
  }
}

/////////////////////////////////////////////////////////////////////////////////
// CC121::FindTrackEQ()
/////////////////////////////////////////////////////////////////////////////////
// Description:  Searches the FX list of the current track for a supported EQ.
// Parameters:   [in]  trackID: ID of the source track.
//               [out] fxid:    At exit: ID of the FX in the list of the track.
// Return value: Returns the requested parameter map or 0 if no map was found.
// Remarks:      The parameter map is only for the first EQ on the track.
/////////////////////////////////////////////////////////////////////////////////
const CC121EqMap* CC121::FindTrackEQ(const int trackID, int& fxid) const
{
  // Get track:
  MediaTrack* tr = CSurf_TrackFromID(currentTrackID, options.useMCP);
  if (tr)
  {
    // Get number of FX:
    int fxcnt = TrackFX_GetCount(tr);
    if (fxcnt)
    {
      // Loop through FX:
      for (fxid = 0; fxid < fxcnt; fxid++)
      {
        // Get FX name:
        char fxname[256];
        if (TrackFX_GetFXName(tr, fxid, fxname, sizeof(fxname) / sizeof(fxname[0])))
        {
          // Find a matching EQ map:
          for (size_t i = 0; i < eqMaps.size(); i++)
          {
            if (strcmp(fxname, eqMaps[i].getMapName().c_str()) == 0)
              return &eqMaps[i];
          }
        }
      }
    }
  }
  return 0;
}

///////////////////////////////// End of File /////////////////////////////////
