/**********************************************************************

  Audacity: A Digital Audio Editor

  StereoToMono.cpp

  Lynn Allan

*******************************************************************//**

\class EffectStereoToMono
\brief An Effect to convert stereo to mono.

*//*******************************************************************/

#include "../Audacity.h"
#include "StereoToMono.h"
#include "LoadEffects.h"

#include <wx/intl.h>

#include "../Mix.h"
#include "../Project.h"
#include "../TimeTrack.h"
#include "../WaveTrack.h"

const ComponentInterfaceSymbol EffectStereoToMono::Symbol
{ XO("Stereo To Mono") };

namespace{ BuiltinEffectsModule::Registration< EffectStereoToMono > reg; }

EffectStereoToMono::EffectStereoToMono()
{
}

EffectStereoToMono::~EffectStereoToMono()
{
}

// ComponentInterface implementation

ComponentInterfaceSymbol EffectStereoToMono::GetSymbol()
{
   return Symbol;
}

TranslatableString EffectStereoToMono::GetDescription()
{
   return XO("Converts stereo tracks to mono");
}

// EffectDefinitionInterface implementation

EffectType EffectStereoToMono::GetType()
{
   // Really EffectTypeProcess, but this prevents it from showing in the Effect Menu
   return EffectTypeHidden;
}

bool EffectStereoToMono::IsInteractive()
{
   return false;
}

// EffectClientInterface implementation

unsigned EffectStereoToMono::GetAudioInCount()
{
   return 2;
}

unsigned EffectStereoToMono::GetAudioOutCount()
{
   return 1;
}

// Effect implementation

bool EffectStereoToMono::Process()
{
   // Do not use mWaveTracks here.  We will possibly DELETE tracks,
   // so we must use the "real" tracklist.
   this->CopyInputTracks(); // Set up mOutputTracks.
   bool bGoodResult = true;

   // Determine the total time (in samples) used by all of the target tracks
   sampleCount totalTime = 0;
   
   auto trackRange = mOutputTracks->SelectedLeaders< WaveTrack >();
   while (trackRange.first != trackRange.second)
   {
      auto left = *trackRange.first;
      auto channels = TrackList::Channels(left);
      if (channels.size() > 1)
      {
         auto right = *channels.rbegin();
         if (left->GetRate() == right->GetRate())
         {
            auto start = wxMin(left->TimeToLongSamples(left->GetStartTime()),
                               right->TimeToLongSamples(right->GetStartTime()));
            auto end = wxMin(left->TimeToLongSamples(left->GetEndTime()),
                               right->TimeToLongSamples(right->GetEndTime()));

            totalTime += (end - start);
         }
      }

      ++trackRange.first;
   }

   // Process each stereo track
   sampleCount curTime = 0;
   bool refreshIter = false;

   trackRange = mOutputTracks->SelectedLeaders< WaveTrack >();
   while (trackRange.first != trackRange.second)
   {
      auto left = *trackRange.first;
      auto channels = TrackList::Channels(left);
      if (channels.size() > 1)
      {
         auto right = *channels.rbegin();

         if (left->GetRate() == right->GetRate())
         {
            bGoodResult = ProcessOne(curTime, totalTime, left, right);
            if (!bGoodResult)
            {
               break;
            }

            // The right channel has been deleted, so we must restart from the beginning
            refreshIter = true;
         }
      }

      if (refreshIter)
      {
         trackRange = mOutputTracks->SelectedLeaders< WaveTrack >();
         refreshIter = false;
      }
      else
      {
         ++trackRange.first;
      }
   }

   this->ReplaceProcessedTracks(bGoodResult);
   return bGoodResult;
}

bool EffectStereoToMono::ProcessOne(sampleCount & curTime, sampleCount totalTime, WaveTrack *left, WaveTrack *right)
{
   auto idealBlockLen = left->GetMaxBlockSize() * 2;
   bool bResult = true;
   sampleCount processed = 0;

   auto start = wxMin(left->TimeToLongSamples(left->GetStartTime()),
                      right->TimeToLongSamples(right->GetStartTime()));
   auto end = wxMin(left->TimeToLongSamples(left->GetEndTime()),
                    right->TimeToLongSamples(right->GetEndTime()));

   WaveTrackConstArray tracks;
   tracks.push_back(left->SharedPointer< const WaveTrack >());
   tracks.push_back(right->SharedPointer< const WaveTrack >());

   auto timeTrack = *(inputTracks()->Any<const TimeTrack>().begin());

   Mixer mixer(tracks,
               true,                // Throw to abort mix-and-render if read fails:
               Mixer::WarpOptions(timeTrack ? timeTrack->GetEnvelope() : nullptr),
               start.as_double(),
               end.as_double(),
               1,
               idealBlockLen,
               false,               // Not interleaved
               mProjectRate,
               floatSample);

   auto outTrack = left->EmptyCopy();
   outTrack->ConvertToSampleFormat(floatSample);

   while (auto blockLen = mixer.Process(idealBlockLen))
   {
      auto buffer = mixer.GetBuffer();
      for (auto i = 0; i < blockLen; i++)
      {
         ((float *)buffer)[i] /= 2.0;
      }
      outTrack->Append(buffer, floatSample, blockLen);

      curTime += blockLen;
      if (TotalProgress(curTime.as_double() / totalTime.as_double()))
      {
         return false;
      }
   }
   outTrack->Flush();

   double minStart = wxMin(left->GetStartTime(), right->GetStartTime());
   left->Clear(left->GetStartTime(), left->GetEndTime());
   left->Paste(minStart, outTrack.get());
   mOutputTracks->GroupChannels(*left,  1);
   mOutputTracks->Remove(right);

   return bResult;
}

bool EffectStereoToMono::IsHidden()
{
   return true;
}
