/*
 * Copyright (C) 2006-2016  Music Technology Group - Universitat Pompeu Fabra
 *
 * This file is part of Essentia
 *
 * Essentia is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation (FSF), either version 3 of the License, or (at your
 * option) any later version.
 *
 * This program 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 General Public License for more
 * details.
 *
 * You should have received a copy of the Affero GNU General Public License
 * version 3 along with this program.  If not, see http://www.gnu.org/licenses/
 */

#include "keyextractor.h"
#include "algorithmfactory.h"
#include "poolstorage.h"

using namespace std;

namespace essentia {
namespace streaming {

const char* KeyExtractor::name = essentia::standard::KeyExtractor::name;
const char* KeyExtractor::category = essentia::standard::KeyExtractor::category;
const char* KeyExtractor::description = essentia::standard::KeyExtractor::description;


KeyExtractor::KeyExtractor(): _frameCutter(0), _windowing(0), _spectrum(0), _spectralPeaks(0),
                              _hpcpKey(0), _key(0) {

  declareInput(_audio, "audio", "the audio signal");
  declareOutput(_keyKey, "key", "see Key algorithm documentation");
  declareOutput(_keyScale, "scale", "see Key algorithm documentation");
  declareOutput(_keyStrength, "strength", "see Key algorithm documentation");

  createInnerNetwork();
}

void KeyExtractor::createInnerNetwork() {
  AlgorithmFactory& factory = AlgorithmFactory::instance();

  // instantiate all required algorithms
  _frameCutter   = factory.create("FrameCutter");
  _windowing     = factory.create("Windowing", "type", "blackmanharris62");
  _spectrum      = factory.create("Spectrum");
  _spectralPeaks = factory.create("SpectralPeaks",
                                  "orderBy", "magnitude", "magnitudeThreshold", 1e-05,
                                  "minFrequency", 40, "maxFrequency", 5000, "maxPeaks", 10000);
  _hpcpKey       = factory.create("HPCP");
  _key           = factory.create("Key");

  // attach input proxy(ies)
  _audio  >>  _frameCutter->input("signal");

  // connect inner algorithms
  _frameCutter->output("frame")          >>  _windowing->input("frame");
  _windowing->output("frame")            >>  _spectrum->input("frame");
  _spectrum->output("spectrum")          >>  _spectralPeaks->input("spectrum");
  _spectralPeaks->output("magnitudes")   >>  _hpcpKey->input("magnitudes");
  _spectralPeaks->output("frequencies")  >>  _hpcpKey->input("frequencies");
  _hpcpKey->output("hpcp")               >>  _key->input("pcp");

  // attach output proxy(ies)
  _key->output("key")       >>  _keyKey;
  _key->output("scale")     >>  _keyScale;
  _key->output("strength")  >>  _keyStrength;

  _network = new scheduler::Network(_frameCutter);
}

void KeyExtractor::configure() {
  int frameSize = parameter("frameSize").toInt();
  int hopSize = parameter("hopSize").toInt();
  Real tuningFrequency = parameter("tuningFrequency").toReal();

  _frameCutter->configure("frameSize", frameSize,
                          "hopSize", hopSize,
                          "silentFrames", "noise");

  _hpcpKey->configure("referenceFrequency", tuningFrequency,
                      "minFrequency", 40.0,
                      "nonLinear", false,
                      "maxFrequency", 5000.0,
                      "bandPreset", false,
                      "windowSize", 1.33333333333,
                      "weightType", "squaredCosine",
                      "size", 36);

  _configured = true;
}


KeyExtractor::~KeyExtractor() {
  delete _network;
}


} // namespace streaming
} // namespace essentia

namespace essentia {
namespace standard {

const char* KeyExtractor::name = "KeyExtractor";
const char* KeyExtractor::category = "Tonal";
const char* KeyExtractor::description = DOC("This algorithm extracts key/scale for an audio signal");


KeyExtractor::KeyExtractor() {
  declareInput(_audio, "audio", "the audio input signal");

  declareOutput(_key, "key", "See Key algorithm documentation");
  declareOutput(_scale, "scale", "See Key algorithm documentation");
  declareOutput(_strength, "strength", "See Key algorithm documentation");

  createInnerNetwork();
}

KeyExtractor::~KeyExtractor() {
  delete _network;
}

void KeyExtractor::reset() {
  _network->reset();
}

void KeyExtractor::configure() {
  _keyExtractor->configure(INHERIT("frameSize"),
                           INHERIT("hopSize"),
                           INHERIT("tuningFrequency"));
}

void KeyExtractor::createInnerNetwork() {
  _keyExtractor = streaming::AlgorithmFactory::create("KeyExtractor");
  _vectorInput = new streaming::VectorInput<Real>();

  *_vectorInput                      >>  _keyExtractor->input("audio");

  _keyExtractor->output("key")       >>  PC(_pool, "key");
  _keyExtractor->output("scale")     >>  PC(_pool, "scale");
  _keyExtractor->output("strength")  >>  PC(_pool, "strength");

  _network = new scheduler::Network(_vectorInput);
}

void KeyExtractor::compute() {
  const vector<Real>& audio = _audio.get();
  _vectorInput->setVector(&audio);

  _network->run();

  string& key      = _key.get();
  string& scale    = _scale.get();
  Real&   strength = _strength.get();

  key      = _pool.value<string>("key");
  scale    = _pool.value<string>("scale");
  strength = _pool.value<Real>("strength");
}

} // namespace standard
} // namespace essentia
