// fftcalc class is designed to treat with fft calculations
FFTCalc::FFTCalc(QObject *parent)
  :QObject(parent){

  // fftcalc is done in other thread
  // so it cannot overload the main thread
  processor.moveToThread(&processorThread);

  // qRegisterMetaType is used to register QVector<double> as the typename for QVector<double>
  // it is necessary for signal/slots events treatment.
  qRegisterMetaType< QVector<double> >("QVector<double>");

  // when the spectrum is calculated, setSpectrum function will comunicate
  // such spectrum to Qt as an emitted signal
  connect(&processor, SIGNAL(calculatedSpectrum(QVector<double>)), SLOT(setSpectrum(QVector<double>)));

  // when the processor finishes the calculation, the busy condition is changed.
  connect(&processor, SIGNAL(allDone()),SLOT(freeCalc()));

  // start the processor thread with low priority
  processorThread.start(QThread::LowestPriority);

  // initially, the processor is not occupied
  isBusy = false;
}
void BufferProcessor::run(){
  // spectrum amplitude
  qreal amplitude;
  qreal SpectrumAnalyserMultiplier = 0.15e-3;

  // tells when all chunks has been processed
  if(pass == chunks){
    emit allDone();
    return;
  }

  // we does not calc spectra when array is too small
  if(array.size() < SPECSIZE){
    return;
  }

  // prepare complex frame for fft calculations
  for(uint i=0; i<SPECSIZE; i++){
    complexFrame[i] = Complex(window[i]*array[i+pass*SPECSIZE],0);
  }

  // do the magic
  fft(complexFrame);

  // some scaling/windowing is needed for displaying the fourier spectrum somewhere
  for(uint i=0; i<SPECSIZE/2;i++){
    amplitude = SpectrumAnalyserMultiplier*std::abs(complexFrame[i]);
    amplitude = qMax(qreal(0.0), amplitude);
    amplitude = qMin(qreal(1.0), amplitude);
    complexFrame[i] = amplitude;
  }

  // audio spectrum is usually compressed for better displaying
  if(compressed){
    for (int i = 0; i <SPECSIZE/2; i ++){
      /* sum up values in freq array between logscale[i] and logscale[i + 1],
         including fractional parts */
      int a = ceilf (logscale[i]);
      int b = floorf (logscale[i+1]);
      float sum = 0;

      if (b < a)
        sum += complexFrame[b].real()*(logscale[i+1]-logscale[i]);
      else{
        if (a > 0)
          sum += complexFrame[a-1].real()*(a-logscale[i]);
        for (; a < b; a++)
          sum += complexFrame[a].real();
        if (b < SPECSIZE/2)
          sum += complexFrame[b].real()*(logscale[i+1] - b);
      }

      /* fudge factor to make the graph have the same overall height as a
         12-band one no matter how many bands there are */
      sum *= (float) SPECSIZE/24;

      /* convert to dB */
      float val = 20*log10f (sum);

      /* scale (-DB_RANGE, 0.0) to (0.0, 1.0) */
      val = 1 + val / 40;
      spectrum[i] = CLAMP (val, 0, 1);
    }
  }
  else{
    // if not compressed, just copy the real part clamped between 0 and 1
    for(int i=0; i<SPECSIZE/2; i++){
      spectrum[i] = CLAMP(complexFrame[i].real()*100,0,1);
    }
  }
  // emit the spectrum
  emit calculatedSpectrum(spectrum);

  // count the pass
  pass++;
}
void FFTCalc::setSpectrum(QVector<double> spectrum){
  // tells Qt about that a new spectrum has arrived
  emit calculatedSpectrum(spectrum);
}
// constructor: warm up all stuff
MainWindow::MainWindow(QWidget *parent) :
  QMainWindow(parent),
  ui(new Ui::MainWindow)
//,audioInfo(QAudioDeviceInfo::defaultInputDevice())
{
  // draws the ui
  ui->setupUi(this);

  // test for saving settings
  QCoreApplication::setOrganizationName("Agostinho");

  /** some settings attempt
   */
  QSettings settings; /*!<aloha */
  settings.setValue("alo","maria");

  // defines sample size equals to spectrum size
  sample.resize(SPECSIZE);

  // threads are as separate processes running within the same
  // program. for fft calculation, it is better to move it
  // to another thread to make the calcs faster.
  // moreover, it will not slow down the ui
//  fftThread = new QThread(this);

  calculator = new FFTCalc();
 // calculator->moveToThread(fftThread);

  // launches the new media player
  player = new QMediaPlayer();

  // starts a new playlist
  playlist = new QMediaPlaylist();

  // starts the playlist model
  playlistModel = new PlaylistModel(this);

  // tell playlistmodel where is the playlist
  playlistModel->setPlaylist(playlist);

  // attach the listView to the playlistModel
  ui->listViewPlaylist->setModel(playlistModel);

  // set current index to the first element
  ui->listViewPlaylist->setCurrentIndex(playlistModel->index(playlist->currentIndex(), 0));

  loadPlaylist();

  // attachs the playlist to the player
  player->setPlaylist(playlist);

  // playlist plays in loop mode. It restarts after last song has finished playing.
  playlist->setPlaybackMode(QMediaPlaylist::Loop);

  // this allow the user to select the media it wants to play
  connect(ui->listViewPlaylist, SIGNAL(doubleClicked(QModelIndex)),
          this, SLOT(goToItem(QModelIndex)));

  // if some metadata changed for media, display it somewhere
  // it seems not work on windows
  // but works for linux :)
  connect(player,SIGNAL(metaDataChanged()),
          this, SLOT(metaDataChanged()));

  // the media status changed (new stream has arrived)
  connect(player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)),
          this, SLOT(mediaStatusChanged(QMediaPlayer::MediaStatus)));

  // the user selected a new position on music to play
  // perharps using some scrollbar
  connect(this,SIGNAL(positionChanged(qint64)),
          player,SLOT(setPosition(qint64)));

  connect(player,SIGNAL(volumeChanged(int)),
                        ui->control,SLOT(onVolumeChanged(int)));

  connect(player,SIGNAL(stateChanged(QMediaPlayer::State)),
          SLOT(mediaStateChanged(QMediaPlayer::State)));
  // that is the audio probe object that "listen to"
  // the music. It will help with fft stuff
  probe = new QAudioProbe();

  // fft is delivered using a QVector<double> but
  // signal/slot scheme does not recognizes this type by default
  // therefore, we have to register it
  qRegisterMetaType< QVector<double> >("QVector<double>");

  // here goes the control unit event handlers
  connect(ui->control, SIGNAL(playPause()), this, SLOT(playPause()));
  connect(ui->control, SIGNAL(prev()), this, SLOT(prev()));
  connect(ui->control, SIGNAL(next()), this, SLOT(next()));
  connect(this, SIGNAL(playPauseChanged(bool)),
          ui->control,SLOT(onPlayerStateChanged(bool)));

  // when the music position changes on player, it has to be
  // informed to the control unit to redraw it ui
  connect(player, SIGNAL(positionChanged(qint64)),
          ui->control,SLOT(onElapsedChanged(qint64)));

  // fft goes here...
  // if a new audio buffer is ok, we have to make some
  // calcs (fft) to display the spectrum
  connect(probe, SIGNAL(audioBufferProbed(QAudioBuffer)),
          this, SLOT(processBuffer(QAudioBuffer)));

  // when fft is available, we deliver it to
  // the visualization widget
  connect(this,  SIGNAL(spectrumChanged(QVector<double>&)),
          ui->visualizer,SLOT(loadSamples(QVector<double>&)));

  // communicate the left and right audio levels...
  // ...mean levels
  connect(this,  SIGNAL(levels(double,double)),
          ui->visualizer,SLOT(loadLevels(double,double)));

  // when fft is available, we deliver it to
  // the visualization widget
  //connect(this,  SIGNAL(spectrumChanged(QVector<double>&)),
  //       ui->glVisualizer,SLOT(loadSamples(QVector<double>&)));

  // communicate the left and right audio levels...
  // ...mean levels
  //connect(this,  SIGNAL(levels(double,double)),
  //       ui->glVisualizer,SLOT(loadLevels(double,double)));

  // if the user selected a new position on stream to play
  // we have to tell it to the player
  connect(ui->control, SIGNAL(elapsedSelected(qint64)),
          player, SLOT(setPosition(qint64)));

  // changing audio volume
  connect(ui->control, SIGNAL(volumeSelected(int)),
          player, SLOT(setVolume(int)));

  // calculator is the thead that calcs the ffts we need to display
  // every time a new spectrum is available, the calculator
  // emits a calculatedSpectrum signal
  connect(calculator, SIGNAL(calculatedSpectrum(QVector<double>)),
          this, SLOT(spectrumAvailable(QVector<double>)));

  connect(ui->library,SIGNAL(addMediaToPlayList(QString)),
          SLOT(onAddMediaToPlayList(QString)));

  // tells the probe what to probe
  probe->setSource(player);

  // load directories to library
  connect(ui->actionLoadDirectory,SIGNAL(triggered()),this,SLOT(onAddFolderToLibrary()));

  // load a single file to library
  connect(ui->actionLoadFile,SIGNAL(triggered()),this,SLOT(loadMedia()));

  // it connects the signals emiteds via the visualizer to the buttons (ui->control) and the lightCycle(ui->widgetInfo)
  connect(ui->visualizer,SIGNAL(trocaCor(QColor)),ui->control,SLOT(onColorChanged(QColor)));
  connect(ui->visualizer,SIGNAL(trocaCor(QColor)),ui->widgetInfo,SLOT(changedColor(QColor)));

  //this->setStyleSheet(QString("QMainWindow {background-color: black}"));
}