//    tonalisa - software to look at overtone-structures
//    Copyright (C) 2016  Dominik Schmidt-Philipp
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, 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 GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
#include <math.h>
#include <QDebug>
#include "dissonance.h"

Dissonance::Dissonance(QObject *parent)
  : QObject(parent)
{
  m_curve_start = 1.0;
  m_curve_end = 2.1;
  m_resolution = 1.00057; // deciton
}

// getters: ----------------------------------------------
float Dissonance::from() const {
  return m_curve_start;
}
float Dissonance::to() const {
  return m_curve_end;
}
float Dissonance::resolution() const {
  return m_resolution;
}
QList<double> Dissonance::peaks() {
  QList<double> peaks;
  peaks.reserve(m_freqs.size()*2);
  for ( int i=0; i<m_freqs.size(); i++ ) {
    peaks << m_freqs[2*i];
    peaks << m_amps[2*i+1];
  }
  return peaks;
}
QList<double> Dissonance::freqs() const {
  return m_freqs;
}
QList<double> Dissonance::amps() const {
  return m_amps;
}
QList<double> Dissonance::dissonanceCurve() {
  return m_dissonance;
}
// setters: ----------------------------------------------
void Dissonance::setFrom(const float &from)
{
  if ( m_curve_start != from ) {
    m_curve_start = from;
    emit fromChanged();
  }
}
void Dissonance::setTo(const float &to)
{
  if ( m_curve_end != to ) {
    m_curve_end = to;
    emit toChanged();
  }
}
void Dissonance::setResolution(const float &resolution)
{
  if ( m_resolution != resolution ) {
    m_resolution = resolution;
    emit resolutionChanged();
  }
}
void Dissonance::setPeaks(QList<double> &peaks)
{
  int size = peaks.size()/2;
  m_freqs.clear();
  m_amps.clear();
  m_freqs.reserve(size);
  m_amps.reserve(size);
  for ( int i=0; i<size; i++ ) {
    m_freqs << peaks[2*i];
    m_amps << peaks[2*i+1];
  }
  emit peaksChanged();
  calculateCurve();  
}
void Dissonance::setFreqs(QList<double> &freqs) 
{
  bool same = true;
  if (freqs.size() == m_freqs.size()) {
    for (int i=0; i<m_freqs.size();i++)
      if (freqs[i] != m_freqs[i])
	same = false;
  } else { same = false; }
  if(!same){
    m_freqs = freqs;
    emit peaksChanged();
  }
}
void Dissonance::setAmps(QList<double> &amps) 
{
  bool same = true;
  if (amps.size() == m_amps.size()) {
    for (int i=0; i<m_amps.size();i++)
      if (amps[i] != m_amps[i])
	same = false;
  } else { same = false; }
  if(!same){
    m_amps = amps;
    emit peaksChanged();
  }
  calculateCurve();
}

double Dissonance::calculateDissonance(QList<double> freqs, QList<double> amps)
{
  double d=0.0;

  int length = freqs.size();
  for ( int i=0; i<length-1; i++ ) 
    for ( int j=1; j<length-i; j++ )
      d += roughness(freqs[i], freqs[i+j], amps[i], amps[i+j]);
 
  return d;
}

void Dissonance::calculateCurve()
{
  QList<double>freqs;
  QList<double>amps;
  freqs.reserve(m_freqs.size()*2);
  amps.reserve(m_amps.size()*2);
  int end = (int)(log(m_curve_end)/log(m_resolution));
  int begin = (int)(log(m_curve_start)/log(m_resolution));
  m_dissonance.clear();
  m_dissonance.reserve(end-begin);
  for (int i=begin; i<end; i++) {
    freqs.clear();
    amps.clear();
    freqs.reserve(m_freqs.size()*2);
    amps.reserve(m_amps.size()*2);
    for ( int j=0; j<m_freqs.size(); j++ ) {
      freqs << m_freqs[j];
      freqs << pow(m_resolution,i) * m_freqs[j];
      amps << m_amps[j];
      amps << m_amps[j];
    }
    m_dissonance << calculateDissonance(freqs,amps);
  }
  
  emit dissonanceChanged();
}
double Dissonance::roughness(double f1, double f2, double a1, double a2)
{
  // Roughness Calculation Model by P.N. Vassilakis
  double r,X,Y,Z;
  if ( fabs(f1/f2) > 2 ) return 0.0;
  double F[2] = {fmin(f1,f2), fmax(f1,f2)};
  double A[2] = {fmin(a1,a2), fmax(a1,a2)};

  float scale = 0.5;
  // W.A.Sethares' parameters for Plomp & Levelt's curve:
  float b1=3.51;
  float b2=5.75;
  float s1=0.0207;
  float s2=18.96;

  double s=0.24/(s1*F[0]+s2);

  X = A[0]*A[1];
  Y = 2*A[0]/(A[0]+A[1]);
  Z = exp(-b1*s*(F[1]-F[0]))-exp(-b2*s*(F[1]-F[0]));
  r = scale * pow(X,0.1) * pow(Y,3.11) * Z;
  return r;
}
