/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */

/*
    QM DSP Library

    Centre for Digital Music, Queen Mary, University of London.
    This file 2005-2006 Christian Landone.

    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 2 of the
    License, or (at your option) any later version.  See the file
    COPYING included with this distribution for more information.
*/

#include <iostream>
#include <cmath>
#include "maths/MathUtilities.h"
#include "Chromagram.h"

//----------------------------------------------------------------------------

Chromagram::Chromagram( ChromaConfig Config ) :
    m_skGenerated(false)
{
    initialise( Config );
}

int Chromagram::initialise( ChromaConfig Config )
{	
    m_FMin = Config.min;		// min freq
    m_FMax = Config.max;		// max freq
    m_BPO  = Config.BPO;		// bins per octave
    m_normalise = Config.normalise;     // if frame normalisation is required

    // No. of constant Q bins
    m_uK = ( unsigned int ) ceil( m_BPO * log(m_FMax/m_FMin)/log(2.0));	

    // Create array for chroma result
    m_chromadata = new double[ m_BPO ];

    // Create Config Structure for ConstantQ operator
    CQConfig ConstantQConfig;

    // Populate CQ config structure with parameters
    // inherited from the Chroma config
    ConstantQConfig.FS	 = Config.FS;
    ConstantQConfig.min = m_FMin;
    ConstantQConfig.max = m_FMax;
    ConstantQConfig.BPO = m_BPO;
    ConstantQConfig.CQThresh = Config.CQThresh;
	
    // Initialise ConstantQ operator
    m_ConstantQ = new ConstantQ( ConstantQConfig );

    // Initialise working arrays
    m_frameSize = m_ConstantQ->getfftlength();
    m_hopSize = m_ConstantQ->gethop();

    // Initialise FFT object	
    m_FFT = new FFTReal(m_frameSize);

    m_FFTRe = new double[ m_frameSize ];
    m_FFTIm = new double[ m_frameSize ];
    m_CQRe  = new double[ m_uK ];
    m_CQIm  = new double[ m_uK ];

    m_window = 0;
    m_windowbuf = 0;

    return 1;
}

Chromagram::~Chromagram()
{
    deInitialise();
}

int Chromagram::deInitialise()
{
    delete[] m_windowbuf;
    delete m_window;

    delete [] m_chromadata;

    delete m_FFT;

    delete m_ConstantQ;

    delete [] m_FFTRe;
    delete [] m_FFTIm;
    delete [] m_CQRe;
    delete [] m_CQIm;
    return 1;
}

//----------------------------------------------------------------------------------
// returns the absolute value of complex number xx + i*yy
double Chromagram::kabs(double xx, double yy)
{
    double ab = sqrt(xx*xx + yy*yy);
    return(ab);
}
//-----------------------------------------------------------------------------------


void Chromagram::unityNormalise(double *src)
{
    double min, max;

    double val = 0;

    MathUtilities::getFrameMinMax( src, m_BPO, & min, &max );

    for( unsigned int i = 0; i < m_BPO; i++ )
    {
	val = src[ i ] / max;

	src[ i ] = val;
    }
}


double* Chromagram::process( const double *data )
{
    if (!m_skGenerated) {
        // Generate CQ Kernel 
        m_ConstantQ->sparsekernel();
        m_skGenerated = true;
    }

    if (!m_window) {
        m_window = new Window<double>(HammingWindow, m_frameSize);
        m_windowbuf = new double[m_frameSize];
    }

    for (int i = 0; i < m_frameSize; ++i) {
        m_windowbuf[i] = data[i];
    }
    m_window->cut(m_windowbuf);

    // FFT of current frame
    m_FFT->process(false, m_windowbuf, m_FFTRe, m_FFTIm);

    return process(m_FFTRe, m_FFTIm);
}

double* Chromagram::process( const double *real, const double *imag )
{
    if (!m_skGenerated) {
        // Generate CQ Kernel 
        m_ConstantQ->sparsekernel();
        m_skGenerated = true;
    }

    // initialise chromadata to 0
    for (unsigned i = 0; i < m_BPO; i++) m_chromadata[i] = 0;

    double cmax = 0.0;
    double cval = 0;

    // Calculate ConstantQ frame
    m_ConstantQ->process( real, imag, m_CQRe, m_CQIm );
	
    // add each octave of cq data into Chromagram
    const unsigned octaves = (int)floor(double( m_uK/m_BPO))-1;
    for (unsigned octave = 0; octave <= octaves; octave++) 
    {
	unsigned firstBin = octave*m_BPO;
	for (unsigned i = 0; i < m_BPO; i++) 
	{
	    m_chromadata[i] += kabs( m_CQRe[ firstBin + i ], m_CQIm[ firstBin + i ]);
	}
    }

    MathUtilities::normalise(m_chromadata, m_BPO, m_normalise);

    return m_chromadata;
}


