/*
 * ParamEq.cpp
 * -----------
 * Purpose: Implementation of the DMO Parametric Equalizer DSP (for non-Windows platforms)
 * Notes  : (currently none)
 * Authors: OpenMPT Devs
 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
 */


#include "stdafx.h"

#ifndef NO_PLUGINS
#include "../../Sndfile.h"
#include "ParamEq.h"
#include "mpt/base/numbers.hpp"
#endif // !NO_PLUGINS

OPENMPT_NAMESPACE_BEGIN

#ifndef NO_PLUGINS

namespace DMO
{

IMixPlugin* ParamEq::Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN &mixStruct)
{
	return new (std::nothrow) ParamEq(factory, sndFile, mixStruct);
}


ParamEq::ParamEq(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN &mixStruct)
	: IMixPlugin(factory, sndFile, mixStruct)
	, m_maxFreqParam(1.0f)
{
	m_param[kEqCenter] = (8000.0f - 80.0f) / 15920.0f;
	m_param[kEqBandwidth] = 0.314286f;
	m_param[kEqGain] = 0.5f;

	m_mixBuffer.Initialize(2, 2);
}


void ParamEq::Process(float *pOutL, float *pOutR, uint32 numFrames)
{
	if(!m_mixBuffer.Ok())
		return;

	const float *in[2] = { m_mixBuffer.GetInputBuffer(0), m_mixBuffer.GetInputBuffer(1) };
	float *out[2] = { m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1) };

	if(m_param[kEqGain] == 0.5f)
	{
		memcpy(out[0], in[0], numFrames * sizeof(float));
		memcpy(out[1], in[1], numFrames * sizeof(float));
	} else
	{
		for(uint32 i = numFrames; i != 0; i--)
		{
			for(uint8 channel = 0; channel < 2; channel++)
			{
				float x = *(in[channel])++;
				float y = b0DIVa0 * x + b1DIVa0 * x1[channel] + b2DIVa0 * x2[channel] - a1DIVa0 * y1[channel] - a2DIVa0 * y2[channel];

				x2[channel] = x1[channel];
				x1[channel] = x;
				y2[channel] = y1[channel];
				y1[channel] = y;

				*(out[channel])++ = y;
			}
		}
	}

	ProcessMixOps(pOutL, pOutR, m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1), numFrames);
}


PlugParamValue ParamEq::GetParameter(PlugParamIndex index)
{
	if(index < kEqNumParameters)
	{
		return m_param[index];
	}
	return 0;
}


void ParamEq::SetParameter(PlugParamIndex index, PlugParamValue value)
{
	if(index < kEqNumParameters)
	{
		value = mpt::safe_clamp(value, 0.0f, 1.0f);
		m_param[index] = value;
		RecalculateEqParams();
	}
}


void ParamEq::Resume()
{
	m_isResumed = true;
	// Limit center frequency to a third of the sampling rate.
	m_maxFreqParam = Clamp((static_cast<float>(m_SndFile.GetSampleRate()) / 3.0f - 80.0f) / 15920.0f, 0.0f, 1.0f);
	RecalculateEqParams();
	PositionChanged();
}


void ParamEq::PositionChanged()
{
	// Reset filter state
	x1[0] = x2[0] = 0;
	x1[1] = x2[1] = 0;
	y1[0] = y2[0] = 0;
	y1[1] = y2[1] = 0;
}


#ifdef MODPLUG_TRACKER

CString ParamEq::GetParamName(PlugParamIndex param)
{
	switch(param)
	{
	case kEqCenter: return _T("Center");
	case kEqBandwidth: return _T("Bandwidth");
	case kEqGain: return _T("Gain");
	}
	return CString();
}


CString ParamEq::GetParamLabel(PlugParamIndex param)
{
	switch(param)
	{
	case kEqCenter: return _T("Hz");
	case kEqBandwidth: return _T("Semitones");
	case kEqGain: return _T("dB");
	}
	return CString();
}


CString ParamEq::GetParamDisplay(PlugParamIndex param)
{
	float value = 0.0f;
	switch(param)
	{
	case kEqCenter:
		value = FreqInHertz();
		break;
	case kEqBandwidth:
		value = BandwidthInSemitones();
		break;
	case kEqGain:
		value = GainInDecibel();
		break;
	}
	CString s;
	s.Format(_T("%.2f"), value);
	return s;
}

#endif // MODPLUG_TRACKER


void ParamEq::RecalculateEqParams()
{
	LimitMax(m_param[kEqCenter], m_maxFreqParam);
	const float freq = FreqInHertz() / static_cast<float>(m_SndFile.GetSampleRate());
	const float a = std::pow(10.0f, GainInDecibel() / 40.0f);
	const float w0 = 2.0f * mpt::numbers::pi_v<float> * freq;
	const float sinW0 = std::sin(w0);
	const float cosW0 = std::cos(w0);
	const float alpha = sinW0 * std::sinh((BandwidthInSemitones() * (mpt::numbers::ln2_v<float> / 24.0f)) * w0 / sinW0);

	const float b0 = 1.0f + alpha * a;
	const float b1 = -2.0f * cosW0;
	const float b2 = 1.0f - alpha * a;
	const float a0 = 1.0f + alpha / a;
	const float a1 = -2.0f * cosW0;
	const float a2 = 1.0f - alpha / a;

	b0DIVa0 = b0 / a0;
	b1DIVa0 = b1 / a0;
	b2DIVa0 = b2 / a0;
	a1DIVa0 = a1 / a0;
	a2DIVa0 = a2 / a0;
}

} // namespace DMO

#else
MPT_MSVC_WORKAROUND_LNK4221(ParamEq)

#endif // !NO_PLUGINS

OPENMPT_NAMESPACE_END
