//-------------------------------------------------------------------------
//
// File:		AudioSpectrumC.h
// Desc:		Audio spectrum device.
// Author:		memon <memon@inside.org>
//
//-------------------------------------------------------------------------
//	Copyright (c) 2000-2004 Moppi Productions. All Rights Reserved.
//  This file is part of Moppi Demopaja SDK. For conditions of 
//  distribution and use, see the accompanying license.txt file.
//  http://www.demopaja.org
//-------------------------------------------------------------------------

#include "PajaTypes.h"
#include "ClassIdC.h"
#include "DataBlockI.h"
#include "FileIO.h"
#include "AudioSpectrumC.h"
#include "ClassIdC.h"
#include <math.h>
#include <assert.h>

#define		BAND_DATA_FREQ	60

using namespace PajaTypes;
using namespace PajaSystem;
using namespace PluginClass;
using namespace FileIO;


class AudioFilterC
{
public:
	AudioFilterC();
	virtual ~AudioFilterC();

	void			init( float32 f32CutOff, float32 f32SamplingRate );
	float32		process( float32 fSample );

protected:
	float32	b0, b1, b2, a0, a1, a2;
	float32	x1, x2, y1, y2;
	float32	m_f32CutOff;
};

AudioFilterC::AudioFilterC() :
	b0( 0 ),
	b1( 0 ),
	b2( 0 ),
	a0( 0 ),
	a1( 0 ),
	a2( 0 ),
	x1( 0 ),
	x2( 0 ),
	y1( 0 ),
	y2( 0 ),
	m_f32CutOff( 0 )
{
}

AudioFilterC::~AudioFilterC()
{
}

void
AudioFilterC::init( float32 fCutOff, float32 fSamplingRate )
{
	m_f32CutOff = fCutOff;
	float32	steep = 0.999f;
  float32	r = steep * 0.99609375f;
  float32	f = (float32)cos( M_PI * fCutOff / fSamplingRate );
  a0 = (1 - r) * (float32)sqrt( r * (r - 4 * (f * f) + 2) + 1 );
  b1 = 2 * f * r;
  b2 = -(r * r);

	x1 = 0;
	x2 = 0;
}

float32
AudioFilterC::process( float32 x0 )
{
  float32 outp = a0 * x0 + b1 * x1 + b2 * x2;
  x2 = x1;
  x1 = outp;

	return outp;
}




//////////////////////////////////////////////////////////////////////////
//
//  Audio Spectrum class descriptor.
//

AudioSpectrumDescC::AudioSpectrumDescC()
{
	// empty
}

AudioSpectrumDescC::~AudioSpectrumDescC()
{
	// empty
}

void*
AudioSpectrumDescC::create()
{
	return AudioSpectrumC::create_new();
}

int32
AudioSpectrumDescC::get_classtype() const
{
	return CLASS_TYPE_INTERNAL;
}

SuperClassIdC
AudioSpectrumDescC::get_super_class_id() const
{
	return SUPERCLASS_INTERNAL;
}

ClassIdC
AudioSpectrumDescC::get_class_id() const
{
	return CLASS_AUDIO_SPECTRUM;
}

const char*
AudioSpectrumDescC::get_name() const
{
	return "Audio Spectrum";
}

const char*
AudioSpectrumDescC::get_desc() const
{
	return "Audio Spectrum";
}

const char*
AudioSpectrumDescC::get_author_name() const
{
	return "Mikko \"memon\" Mononen";
}

const char*
AudioSpectrumDescC::get_copyright_message() const
{
	return "Copyright (c) 2004 Moppi Productions";
}

const char*
AudioSpectrumDescC::get_url() const
{
	return "http://www.demopaja.org";
}

const char*
AudioSpectrumDescC::get_help_filename() const
{
	return "help://create_scene.html";
}

uint32
AudioSpectrumDescC::get_required_device_driver_count() const
{
	return 0;
}

const ClassIdC&
AudioSpectrumDescC::get_required_device_driver( uint32 ui32Idx )
{
	return NULL_CLASSID;
}

uint32
AudioSpectrumDescC::get_ext_count() const
{
	return 0;
}

const char*
AudioSpectrumDescC::get_ext( uint32 ui32Index ) const
{
	return 0;
}


//////////////////////////////////////////////////////////////////////////
//
// Global descriptors, which will be returned to the Demopaja.
//

AudioSpectrumDescC			g_rAudioSpectrumDesc;

//////////////////////////////////////////////////////////////////////////
//
//  Audio Spectrum class.
//

AudioSpectrumC::AudioSpectrumC() :
	m_pTimeContext( 0 ),
	m_ui32BandDataSize( 0 )
{
}

AudioSpectrumC::~AudioSpectrumC()
{
	purge();
}

AudioSpectrumC*
AudioSpectrumC::create_new()
{
	return new AudioSpectrumC;
}

Edit::DataBlockI*
AudioSpectrumC::create()
{
	return new AudioSpectrumC;
}

PluginClass::SuperClassIdC
AudioSpectrumC::get_super_class_id() const
{
	return SUPERCLASS_INTERNAL;
}

PluginClass::ClassIdC
AudioSpectrumC::get_class_id() const
{
	return CLASS_AUDIO_SPECTRUM;
}

const char*
AudioSpectrumC::get_class_name()
{
	return "Audio Spectrum";
}

DeviceInterfaceI*
AudioSpectrumC::query_interface( const PluginClass::SuperClassIdC& rSuperClassId )
{
	return 0;
}

bool
AudioSpectrumC::analyze( PajaTypes::uint32 ui32NumBands, PajaTypes::float32* pData, PajaTypes::uint32 ui32DataSize, PajaTypes::uint32 ui32DataFreq, TimeContextC* pTimeContext )
{
	if( !pTimeContext )
		return false;

	m_pTimeContext = pTimeContext;

	purge();

	// Calcualte data size.
	m_ui32BandDataSize = (uint32)((float64)ui32DataSize * BAND_DATA_FREQ / (float64)ui32DataFreq);

	m_vecBands.resize( ui32NumBands );

	for( std::vector<BandS>::iterator BaIt = m_vecBands.begin(); BaIt != m_vecBands.end(); ++BaIt )
	{
		BandS&	Band = (*BaIt);
		Band.m_f32Freq = 0;
		Band.m_f32Value = 0;
		Band.m_pData = new uint8[m_ui32BandDataSize];
		memset( Band.m_pData, 0, m_ui32BandDataSize );
	}

	// Init filters.
	uint32	i, j, k;
	std::vector<AudioFilterC>	vecFilter;
	std::vector<float32>	vecTempBars;
	std::vector<float32>	vecBandValues;
	std::vector<float32>	vecBandNormValues;
	std::vector<float32>	vecBandMin;
	std::vector<float32>	vecBandMax;
	float32		f32FilterTimeBlur = 0.3f;

	vecFilter.resize( ui32NumBands );
	vecTempBars.resize( ui32NumBands );
	vecBandValues.resize( ui32NumBands );
	vecBandNormValues.resize( ui32NumBands );
	vecBandMin.resize( ui32NumBands );
	vecBandMax.resize( ui32NumBands );

	for( i = 0; i < ui32NumBands; i++ )
	{
		float32	f32Center = (float32)i / (float32)ui32NumBands;
		f32Center *= f32Center;
		float32	f32CutOff = 40.0f + f32Center * (17000.0f - 40.0f);
		vecFilter[i].init( f32CutOff, (float32)ui32DataFreq );

		vecBandValues[i] = 0.0f;
		vecBandNormValues[i] = 0.0f;
		vecBandMin[i] = 1.0f;
		vecBandMax[i] = 0.0f;
	}

	float32	f32Delta = (float32)ui32DataFreq / BAND_DATA_FREQ;
	float32	f32Index = 0;
	float32	f32MaxSize = (float32)ui32DataSize;
	uint32	ui32DstCount = 0;
	uint32	ui32PrevIndex = 0;

	for( f32Index = 0; f32Index < f32MaxSize && ui32DstCount < m_ui32BandDataSize; f32Index += f32Delta )
	{
		// Reset temp values.
		for( k = 0; k < ui32NumBands; k++ )
			vecTempBars[k] = 0.0f;

		// calculate integer index from float index.
		uint32	ui32Idx = (uint32)f32Index;
		if( ui32Idx > ui32DataSize )
			ui32Idx = ui32DataSize;

		// Process sample span.
		for( j = ui32PrevIndex; j < ui32Idx; j++ )
		{
			for( k = 0; k < ui32NumBands; k++ )
			{
				float32	f32Val = (float32)fabs( vecFilter[k].process( pData[j] ) );
				if( f32Val > vecTempBars[k] )
					vecTempBars[k] = f32Val;
			}
		}
		ui32PrevIndex = ui32Idx;

		for( k = 0; k < ui32NumBands; k++ )
		{
			float32	f32NewVal = (1.0f - f32FilterTimeBlur) * vecBandValues[k] + f32FilterTimeBlur * vecTempBars[k];
			if( vecTempBars[k] > f32NewVal )
				f32NewVal = vecTempBars[k];

			float32	f32Delta = f32NewVal - vecBandValues[k];

			// Decay values towards min/max.
			vecBandMax[k] = vecBandMax[k] * 0.9999f;
			vecBandMin[k] = 1.0f - (1.0f - vecBandMin[k]) * 0.9999f;

			if( f32Delta < 0.0f )
			{
				// Update max
				if( f32NewVal < vecBandMin[k] )
					vecBandMin[k] = f32NewVal;
			}
			else
			{
				// Update min
				if( f32NewVal > vecBandMax[k] )
					vecBandMax[k] = f32NewVal;
			}

			if( vecBandMax[k] < 0.0f )
				vecBandMax[k] = 0.0f;
			if( vecBandMax[k] > 1.0f )
				vecBandMax[k] = 1.0f;

			if( vecBandMin[k] < 0.0f )
				vecBandMin[k] = 0.0f;
			if( vecBandMin[k] > 1.0f )
				vecBandMin[k] = 1.0f;
			
			vecBandValues[k] = f32NewVal;

			float32	f32Range = vecBandMax[k] - vecBandMin[k];
			float32	f32NewNormVal = f32NewVal;
			if( f32Range > 0.0001f )
			{
				if( f32NewNormVal < vecBandMin[k] )
					f32NewNormVal = vecBandMin[k];
				else if( f32NewNormVal > vecBandMax[k] )
					f32NewNormVal = vecBandMax[k];
				f32NewNormVal = (f32NewNormVal - vecBandMin[k]) / f32Range;
			}
			if( f32NewNormVal > 1.0f )
				f32NewNormVal = 1.0f;
			vecBandNormValues[k] = f32NewNormVal;

			m_vecBands[k].m_pData[ui32DstCount] = (uint8)(vecBandNormValues[k] * 255.0f);
		}
		ui32DstCount++;
	}

	return true;
}

uint32
AudioSpectrumC::get_band_count() const
{
	return m_vecBands.size();
}

uint8*
AudioSpectrumC::get_band_data_ptr( uint32 ui32Band )
{
	if( ui32Band < (uint32)m_vecBands.size() )
		return m_vecBands[ui32Band].m_pData;
	return 0;
}

uint32
AudioSpectrumC::get_band_data_size() const
{
	return m_ui32BandDataSize;
}

uint32
AudioSpectrumC::get_band_data_freq() const
{
	return BAND_DATA_FREQ;
}

void
AudioSpectrumC::eval_state( PajaTypes::int32 i32Time, PajaSystem::TimeContextC* pTimeContext )
{
	float64	f64TimeScale = (float64)pTimeContext->get_beats_per_min() * (float64)pTimeContext->get_qnotes_per_beat() * 256.0 / 60.0;

	float64		f64Int = 0;
	float64		f64Frac = modf( (float64)i32Time / f64TimeScale * (float64)BAND_DATA_FREQ, &f64Int );
	int32			i32Index = (int32)f64Int;
	float32		f32A = (float32)f64Frac;

	if( i32Index < 0 || i32Index >= (int32)m_ui32BandDataSize )
	{
		for( std::vector<BandS>::iterator BaIt = m_vecBands.begin(); BaIt != m_vecBands.end(); ++BaIt )
		{
			BandS&	Band = (*BaIt);
			Band.m_f32Value = 0;
		}
		return;
	}

	int32			i32NextIndex = i32Index + 1;
	if( i32NextIndex >= (int32)m_ui32BandDataSize )
		i32NextIndex = (int32)m_ui32BandDataSize - 1;

	for( std::vector<BandS>::iterator BaIt = m_vecBands.begin(); BaIt != m_vecBands.end(); ++BaIt )
	{
		BandS&	Band = (*BaIt);
		Band.m_f32Value = ((float32)Band.m_pData[i32Index] * (1.0f - f32A) + (float32)Band.m_pData[i32NextIndex] * f32A) / 255.0f;
	}
}

float32
AudioSpectrumC::get_band_value( uint32 ui32Band ) const
{
	if( ui32Band < (uint32)m_vecBands.size() )
		return m_vecBands[ui32Band].m_f32Value;
	return 0.0f;
}

void
AudioSpectrumC::purge()
{
	for( std::vector<BandS>::iterator BaIt = m_vecBands.begin(); BaIt != m_vecBands.end(); ++BaIt )
	{
		BandS&	Band = (*BaIt);
		delete [] Band.m_pData;
		Band.m_pData = 0;
	}
	m_vecBands.clear();
}


enum AudioSpectrumChunksE {
	CHUNK_AUDIOSPEC_BASE =				0x10,
	CHUNK_AUDIOSPEC_BAND =				0x20,
};

const uint32	AUDIOSPEC_VERSION = 1;


uint32
AudioSpectrumC::save( FileIO::SaveC* pSave )
{
	uint32	ui32Error = IO_OK;
	uint32	ui32Tmp;

	// base
	pSave->begin_chunk( CHUNK_AUDIOSPEC_BASE, AUDIOSPEC_VERSION );
		// band count
		ui32Tmp = m_vecBands.size();
		ui32Error = pSave->write( &ui32Tmp, sizeof( ui32Tmp ) );
		// data len
		ui32Error = pSave->write( &m_ui32BandDataSize, sizeof( m_ui32BandDataSize ) );
	pSave->end_chunk();

	for( std::vector<BandS>::iterator BaIt = m_vecBands.begin(); BaIt != m_vecBands.end(); ++BaIt )
	{
		BandS&	Band = (*BaIt);
		pSave->begin_chunk( CHUNK_AUDIOSPEC_BAND, AUDIOSPEC_VERSION );
			// freq
			ui32Error = pSave->write( &Band.m_f32Freq, sizeof( float32 ) );
			// data
			ui32Error = pSave->write( Band.m_pData, m_ui32BandDataSize );
		pSave->end_chunk();
	}

	return IO_OK;
}

uint32
AudioSpectrumC::load( FileIO::LoadC* pLoad )
{
	uint32	ui32Error = IO_OK;

	uint32	ui32CurBand = 0;

	while( (ui32Error = pLoad->open_chunk()) == IO_OK ) {

		switch( pLoad->get_chunk_id() ) {
		case CHUNK_AUDIOSPEC_BASE:
			if( pLoad->get_chunk_version() == AUDIOSPEC_VERSION )
			{
				uint32	ui32NumBands = 0;
				// band count
				ui32Error = pLoad->read( &ui32NumBands, sizeof( ui32NumBands ) );
				// data len
				ui32Error = pLoad->read( &m_ui32BandDataSize, sizeof( m_ui32BandDataSize ) );

				if( ui32NumBands > 0 )
				{
					m_vecBands.resize( ui32NumBands );

					for( std::vector<BandS>::iterator BaIt = m_vecBands.begin(); BaIt != m_vecBands.end(); ++BaIt )
					{
						BandS&	Band = (*BaIt);
						Band.m_f32Freq = 0;
						Band.m_f32Value = 0;
						Band.m_pData = new uint8[m_ui32BandDataSize];
						memset( Band.m_pData, 0, m_ui32BandDataSize );
					}
				}
			}
			break;

		case CHUNK_AUDIOSPEC_BAND:
			if( pLoad->get_chunk_version() == AUDIOSPEC_VERSION )
			{
				if( ui32CurBand < m_vecBands.size() )
				{
					BandS&	Band = m_vecBands[ui32CurBand];
					// freq
					ui32Error = pLoad->read( &Band.m_f32Freq, sizeof( float32 ) );
					// data
					ui32Error = pLoad->read( Band.m_pData, m_ui32BandDataSize );
				}
				ui32CurBand++;
			}
			break;

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

		if( ui32Error != IO_OK && ui32Error != IO_END )
			return ui32Error;
	}

	return ui32Error;
}