This class handles the Mixer Strem, which is used by the BASS Player
Inheritance: IDisposable
    private void StopCommand()
    { 
      lock (_syncRoot)
      {
        _commandRegistered.Set();

        MusicStream stream = GetCurrentStream();
        try
        {
          if (stream != null && !stream.IsDisposed)
          {
            Log.Debug("BASS: Stop of stream {0}.", stream.FilePath);
            if (Config.SoftStop && !stream.IsDisposed && !stream.IsCrossFading)
            {
              if (Config.CrossFadeIntervalMs > 0)
              {
                Log.Debug("BASS: Performing Softstop of {0}", stream.FilePath);
                Bass.BASS_ChannelSlideAttribute(stream.BassStream, BASSAttribute.BASS_ATTRIB_VOL, 0,
                                                Config.CrossFadeIntervalMs);

                // Wait until the slide is done
                // Sometimes the slide is causing troubles, so we wait a maximum of CrossfadeIntervals + 100 ms
                // Enable only if it's music playing
                if (g_Player.IsMusic && g_Player._currentMediaForBassEngine != g_Player.MediaType.Video &&
                    g_Player._currentMediaForBassEngine != g_Player.MediaType.TV &&
                    g_Player._currentMediaForBassEngine != g_Player.MediaType.Recording)
                {
                  DateTime start = DateTime.Now;
                  while (Bass.BASS_ChannelIsSliding(stream.BassStream, BASSAttribute.BASS_ATTRIB_VOL))
                  {
                    System.Threading.Thread.Sleep(20);
                    if ((DateTime.Now - start).TotalMilliseconds > Config.CrossFadeIntervalMs + 100)
                    {
                      break;
                    }
                  }
                }
              }
            }
            BassMix.BASS_Mixer_ChannelRemove(stream.BassStream);
            stream.Dispose();
          }

          if (Config.MusicPlayer == AudioPlayer.Asio)
          {
            Log.Debug("BASS: Stopping ASIO Device");
            if (BassAsio.BASS_ASIO_IsStarted() && !BassAsio.BASS_ASIO_Stop())
            {
              Log.Error("BASS: Error freeing ASIO: {0}", BassAsio.BASS_ASIO_ErrorGetCode());
            }
            Log.Debug("BASS: unjoin ASIO CHannels");
            if (!BassAsio.BASS_ASIO_ChannelReset(false, -1, BASSASIOReset.BASS_ASIO_RESET_JOIN))
            {
              Log.Error("BASS: Error unjoining Asio Channels: {0}", BassAsio.BASS_ASIO_ErrorGetCode());
            }
            Log.Debug("BASS: disabling ASIO CHannels");
            if (!BassAsio.BASS_ASIO_ChannelReset(false, -1, BASSASIOReset.BASS_ASIO_RESET_ENABLE))
            {
              Log.Error("BASS: Error disabling Asio Channels: {0}", BassAsio.BASS_ASIO_ErrorGetCode());
            }
          }

          if (Config.MusicPlayer == AudioPlayer.WasApi)
          {
            try
            {
              if (BassWasapi.BASS_WASAPI_IsStarted())
              {
                Log.Debug("BASS: Stopping WASAPI Device");
                if (!BassWasapi.BASS_WASAPI_Stop(true))
                {
                  Log.Error("BASS: Error stopping WASAPI Device: {0}", Bass.BASS_ErrorGetCode());
                }
              }
            }
            catch (Exception ex)
            {
              Log.Error("BASS: Exception stopping WASAPI: {0} {1}", ex.Message, ex.StackTrace);
            }

            // Even if stopping the WASAPI device fails we need to free it to make sure 
            // the audio device is free to be used by others
            try
            {
              if (!BassWasapi.BASS_WASAPI_Free())
              {
                Log.Error("BASS: Error freeing WASAPI: {0}", Bass.BASS_ErrorGetCode());
              }
            }
            catch (Exception ex)
            {
              Log.Error("BASS: Exception freeing WASAPI: {0} {1}", ex.Message, ex.StackTrace);
            }
          }

          if (_mixer != null)
          {
            _mixer.Dispose();
            _mixer = null;
          }

          // If we did a playback of a Audio CD, release the CD, as we might have problems with other CD related functions
          if (_isCDDAFile)
          {
            int driveCount = BassCd.BASS_CD_GetDriveCount();
            for (int i = 0; i < driveCount; i++)
            {
              BassCd.BASS_CD_Release(i);
            }
          }

          if (PlaybackStop != null)
          {
            PlaybackStop(this);
          }

          HandleSongEnded();

          // Switching back to normal playback mode
          SwitchToDefaultPlaybackMode();
        }

        catch (Exception ex)
        {
          Log.Error("BASS: Stop command caused an exception - {0}. {1}", ex.Message, ex.StackTrace);
        }

        NotifyPlaying = false;
      }
    }
    /// <summary>
    /// Stopping Playback
    /// </summary>
    public override void Stop()
    {
      // We might have performed the Stop already, because the end of the playback list was reached
      // g_Player is calling the Stop a second time. Don't execute the commands in this case
      if (_mixer == null)
      {
        Log.Debug("BASS: Already stopped. Don't execute Stop a second time");
        return;
      }

      // Execute the Stop in a separate thread, so that it doesn't block the Main UI Render thread
      new Thread(() =>
                   {
                     // First deactivate Viz RenderThread, in HandleSongEnded, it's too late
                     VizWindow.Run = false;

                     MusicStream stream = GetCurrentStream();
                     try
                     {
                       if (stream != null && !stream.IsDisposed)
                       {
                         Log.Debug("BASS: Stop of stream {0}.", stream.FilePath);
                         if (Config.SoftStop && !stream.IsDisposed && !stream.IsCrossFading)
                         {
                           if (Config.CrossFadeIntervalMs > 0)
                           {
                             Log.Debug("BASS: Performing Softstop of {0}", stream.FilePath);
                             Bass.BASS_ChannelSlideAttribute(stream.BassStream, BASSAttribute.BASS_ATTRIB_VOL, 0,
                                                             Config.CrossFadeIntervalMs);

                             // Wait until the slide is done
                             // Sometimes the slide is causing troubles, so we wait a maximum of CrossfadeIntervals + 100 ms
                             // Enable only if it's music playing
                             if (g_Player.IsMusic && g_Player._currentMediaForBassEngine != g_Player.MediaType.Video &&
                                 g_Player._currentMediaForBassEngine != g_Player.MediaType.TV &&
                                 g_Player._currentMediaForBassEngine != g_Player.MediaType.Recording)
                             {
                             DateTime start = DateTime.Now;
                             while (Bass.BASS_ChannelIsSliding(stream.BassStream, BASSAttribute.BASS_ATTRIB_VOL))
                             {
                               System.Threading.Thread.Sleep(20);
                               if ((DateTime.Now - start).TotalMilliseconds > Config.CrossFadeIntervalMs + 100)
                               {
                                 break;
                               }
                             }
                           }
                         }
                         }
                         BassMix.BASS_Mixer_ChannelRemove(stream.BassStream);
                         stream.Dispose();
                       }

                       if (Config.MusicPlayer == AudioPlayer.Asio && BassAsio.BASS_ASIO_IsStarted())
                       {
                         Log.Debug("BASS: Stopping ASIO Device");
                         if (!BassAsio.BASS_ASIO_Stop())
                         {
                           Log.Error("BASS: Error freeing ASIO: {0}", BassAsio.BASS_ASIO_ErrorGetCode());
                         }
                         Log.Debug("BASS: unjoin ASIO CHannels");
                         if (!BassAsio.BASS_ASIO_ChannelReset(false, -1, BASSASIOReset.BASS_ASIO_RESET_JOIN))
                         {
                           Log.Error("BASS: Error unjoining Asio Channels: {0}", BassAsio.BASS_ASIO_ErrorGetCode());
                         }
                         Log.Debug("BASS: disabling ASIO CHannels");
                         if (!BassAsio.BASS_ASIO_ChannelReset(false, -1, BASSASIOReset.BASS_ASIO_RESET_ENABLE))
                         {
                           Log.Error("BASS: Error disabling Asio Channels: {0}", BassAsio.BASS_ASIO_ErrorGetCode());
                         }
                       }

                       if (Config.MusicPlayer == AudioPlayer.WasApi && BassWasapi.BASS_WASAPI_IsStarted())
                       {
                         try
                         {
                           Log.Debug("BASS: Stopping WASAPI Device");
                           if (!BassWasapi.BASS_WASAPI_Stop(true))
                           {
                             Log.Error("BASS: Error stopping WASAPI Device: {0}", Bass.BASS_ErrorGetCode());
                           }

                           if (!BassWasapi.BASS_WASAPI_Free())
                           {
                             Log.Error("BASS: Error freeing WASAPI: {0}", Bass.BASS_ErrorGetCode());
                           }
                         }
                         catch (Exception ex)
                         {
                           Log.Error("BASS: Exception freeing WASAPI. {0} {1}", ex.Message, ex.StackTrace);
                         }
                       }

                       if (_mixer != null)
                       {
                         _mixer.Dispose();
                         _mixer = null;
                       }

                       // If we did a playback of a Audio CD, release the CD, as we might have problems with other CD related functions
                       if (_isCDDAFile)
                       {
                         int driveCount = BassCd.BASS_CD_GetDriveCount();
                         for (int i = 0; i < driveCount; i++)
                         {
                           BassCd.BASS_CD_Release(i);
                         }
                       }

                       if (PlaybackStop != null)
                       {
                         PlaybackStop(this);
                       }

                       HandleSongEnded();

                       // Remove the Viz Window from the Main Form as it causes troubles to other plugin overlay window
                       try
                       {
                         RemoveVisualizationWindow();
                       }
                       catch (Exception)
                       {
                         Log.Error("BASS: Stop RemoveVisualizationWindow command caused an exception");
                       }

                       // Switching back to normal playback mode
                       SwitchToDefaultPlaybackMode();
                     }

                     catch (Exception ex)
                     {
                       Log.Error("BASS: Stop command caused an exception - {0}. {1}", ex.Message, ex.StackTrace);
                     }

                     NotifyPlaying = false;
                   }
        ) { Name = "BASS Stop" }.Start();
    }
    /// <summary>
    /// Called to start playback of next files internally, without makinbg use of g_player.
    /// This gives us the capability to achieve gapless playback.
    /// </summary>
    /// <param name="filePath"></param>
    private bool PlayInternal(string filePath)
    {
      if (filePath == string.Empty)
      {
        return false;
      }

      // Cue support
      if (HandleCueFile(ref filePath, true))
      {
        return true;
      }

      _filePath = filePath;

      MusicStream stream = new MusicStream(filePath);

      if (stream.BassStream == 0)
      {
        return false;
      }

      _streams.Add(stream);

      if (stream.Filetype.FileMainType == FileMainType.CDTrack)
      {
        _isCDDAFile = true;
      }
      else
      {
        _isCDDAFile = false;
      }

      if (_mixer == null)
      {
        _mixer = new MixerStream(this);
        _mixer.MusicStreamMessage += OnMusicStreamMessage;
        if (!_mixer.CreateMixer(stream))
        {
          Log.Error("BASS: Could not create Mixer. Aborting playback.");
          return false;
        }
      }
      else
      {
        if (NewMixerNeeded(stream))
        {
          Log.Debug("BASS: New stream has different number of channels or sample rate. Need a new mixer.");
          // Free Mixer
          _mixer.Dispose();
          _mixer = null;
          _mixer = new MixerStream(this);
          _mixer.MusicStreamMessage += OnMusicStreamMessage;
          if (!_mixer.CreateMixer(stream))
          {
            Log.Error("BASS: Could not create Mixer. Aborting playback.");
            return false;
          }
        }
      }

      // Enable events, for various Playback Actions to be handled
      stream.MusicStreamMessage += new MusicStream.MusicStreamMessageHandler(OnMusicStreamMessage);

      SetCueTrackEndPosition(stream, false);

      // Plug in the stream into the Mixer
      if (!_mixer.AttachStream(stream))
      {
        return false;
      }

      return true;
    }
    /// <summary>
    /// Starts Playback of the given file
    /// </summary>
    /// <param name="filePath"></param>
    /// <returns></returns>
    public override bool Play(string filePath)
    {
      if (!_initialized)
      {
        return false;
      }

      MusicStream currentStream = GetCurrentStream();

      bool result = true;
      Speed = 1; // Set playback Speed to normal speed

      try
      {
        if (currentStream != null && filePath.ToLowerInvariant().CompareTo(currentStream.FilePath.ToLowerInvariant()) == 0)
        {
          // Selected file is equal to current stream
          if (_state == PlayState.Paused)
          {
            // Resume paused stream
            currentStream.ResumePlayback();

            result = Bass.BASS_Start();

            if (Config.MusicPlayer == AudioPlayer.Asio)
            {
              result = BassAsio.BASS_ASIO_ChannelReset(false, 0, BASSASIOReset.BASS_ASIO_RESET_PAUSE);   // Continue playback of Paused stream
            }
            else if (Config.MusicPlayer == AudioPlayer.WasApi)
            {
              BassWasapi.BASS_WASAPI_Start();
            }

            if (result)
            {
              _state = PlayState.Playing;

              if (PlaybackStateChanged != null)
              {
                PlaybackStateChanged(this, PlayState.Paused, _state);
              }
            }

            return result;
          }
        }
        else
        {
          // Cue support
          if (HandleCueFile(ref filePath))
          {
            return true;
          }
        }

        // If we're not Crossfading, we want to stop the current stream at this time
        if (currentStream != null && currentStream.IsPlaying)
        {
          if (!currentStream.IsCrossFading)
          {
            currentStream.FadeOutStop();
          }
        }

        _state = PlayState.Init;

        if (filePath == string.Empty)
        {
          return result;
        }

        _filePath = filePath;

        MusicStream stream = new MusicStream(filePath);

        if (stream.BassStream == 0)
        {
          return false;
        }

        _streams.Add(stream);
        if (stream.Filetype.FileMainType == FileMainType.CDTrack)
        {
          _isCDDAFile = true;
        }
        else
        {
          _isCDDAFile = false;
        }

        bool playbackStarted = false;

        if (_mixer == null)
        {
          _mixer = new MixerStream(this);
          if (!_mixer.CreateMixer(stream))
          {
            Log.Error("BASS: Could not create Mixer. Aborting playback.");
            return false;
          }

          // Start a StreamCopy for Visualisation purposes
          if (HasViz)
          {
            _streamcopy.ChannelHandle = _mixer.BassStream;
            _streamcopy.Start();
          }
        }
        else
        {
          if (!_mixer.UpMixing)
          {
            BASS_CHANNELINFO chinfo = Bass.BASS_ChannelGetInfo(_mixer.BassStream);
            if (!_mixer.WasApiShared &&
                (chinfo.freq != stream.ChannelInfo.freq || chinfo.chans != stream.ChannelInfo.chans))
            {
              if (stream.ChannelInfo.freq != _mixer.WasApiMixedFreq ||
                  stream.ChannelInfo.chans != _mixer.WasApiMixedChans)
              {
                Log.Debug("BASS: New stream has different number of channels or sample rate. Need a new mixer.");
                // The new stream has a different frequency or number of channels
                // We need a new mixer
                _mixer.Dispose();
                _mixer = null;
                _mixer = new MixerStream(this);
                if (!_mixer.CreateMixer(stream))
                {
                  Log.Error("BASS: Could not create Mixer. Aborting playback.");
                  return false;
                }

                if (HasViz)
                {
                  _streamcopy.ChannelHandle = _mixer.BassStream;
                }
              }
            }
          }
        }

        // Enable events, for various Playback Actions to be handled
        stream.MusicStreamMessage += new MusicStream.MusicStreamMessageHandler(OnMusicStreamMessage);

        SetCueTrackEndPosition(stream);

        // Plugin the stream into the Mixer
        result = _mixer.AttachStream(stream);

        if (Config.MusicPlayer == AudioPlayer.Asio && !BassAsio.BASS_ASIO_IsStarted())
        {
          BassAsio.BASS_ASIO_Stop();
          playbackStarted = BassAsio.BASS_ASIO_Start(0);
        }
        else if (Config.MusicPlayer == AudioPlayer.WasApi && !BassWasapi.BASS_WASAPI_IsStarted())
        {
          playbackStarted = BassWasapi.BASS_WASAPI_Start();
        }
        else
        {
          if (Bass.BASS_ChannelIsActive(_mixer.BassStream) == BASSActive.BASS_ACTIVE_PLAYING)
          {
            playbackStarted = true;
          }
          else
          {
            playbackStarted = Bass.BASS_ChannelPlay(_mixer.BassStream, false);
          }
        }

        if (stream.BassStream != 0 && playbackStarted)
        {
          Log.Info("BASS: playback started");

          // Slide in the Stream over the Cross fade Interval
          stream.SlideIn();

          GUIMessage msg = new GUIMessage(GUIMessage.MessageType.GUI_MSG_PLAYBACK_STARTED, 0, 0, 0, 0, 0, null);
          msg.Label = _filePath;
          GUIWindowManager.SendThreadMessage(msg);
          NotifyPlaying = true;

          NeedUpdate = true;
          _IsFullScreen = GUIGraphicsContext.IsFullScreenVideo;
          _VideoPositionX = GUIGraphicsContext.VideoWindow.Left;
          _VideoPositionY = GUIGraphicsContext.VideoWindow.Top;
          _VideoWidth = GUIGraphicsContext.VideoWindow.Width;
          _VideoHeight = GUIGraphicsContext.VideoWindow.Height;

          // Re-Add the Viswindow to the Mainform Control (It got removed on a manual Stop)
          SetVisualizationWindow();
          SetVideoWindow();

          PlayState oldState = _state;
          _state = PlayState.Playing;

          if (oldState != _state && PlaybackStateChanged != null)
          {
            PlaybackStateChanged(this, oldState, _state);
          }

          if (PlaybackStart != null)
          {
            PlaybackStart(this, stream.TotalStreamSeconds);
          }
        }

        else
        {
          Log.Error("BASS: Unable to play {0}.  Reason: {1}.", filePath,
                    Enum.GetName(typeof(BASSError), Bass.BASS_ErrorGetCode()));

          stream.Dispose();
          result = false;
        }
      }
      catch (Exception ex)
      {
        result = false;
        Log.Error("BASS: Play caused an exception:  {0}.", ex);
      }

      return result;
    }