/*
 * Compressor.cpp
 * ---------------
 * Purpose: Implementation of the DMO Compressor DSP (for non-Windows platforms)
 * Notes  : The original plugin's integer and floating point code paths only
 *          behave identically when feeding floating point numbers in range
 *          [-32768, +32768] rather than the usual [-1, +1] into the plugin.
 * 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 "Compressor.h"
#include "DMOUtils.h"
#include "mpt/base/numbers.hpp"
#endif // !NO_PLUGINS

OPENMPT_NAMESPACE_BEGIN

#ifndef NO_PLUGINS

namespace DMO
{


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


Compressor::Compressor(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN &mixStruct)
	: IMixPlugin(factory, sndFile, mixStruct)
{
	m_param[kCompGain] = 0.5f;
	m_param[kCompAttack] = 0.02f;
	m_param[kCompRelease] = 150.0f / 2950.0f;
	m_param[kCompThreshold] = 2.0f / 3.0f;
	m_param[kCompRatio] = 0.02f;
	m_param[kCompPredelay] = 1.0f;

	m_mixBuffer.Initialize(2, 2);
}


void Compressor::Process(float *pOutL, float *pOutR, uint32 numFrames)
{
	if(!m_bufSize || !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) };

	for(uint32 i = numFrames; i != 0; i--)
	{
		float leftIn  = *(in[0])++;
		float rightIn = *(in[1])++;
			
		m_buffer[m_bufPos * 2] = leftIn;
		m_buffer[m_bufPos * 2 + 1] = rightIn;

		leftIn = std::abs(leftIn);
		rightIn = std::abs(rightIn);

		float mono = (leftIn + rightIn) * (0.5f * 32768.0f * 32768.0f);
		float monoLog = std::abs(logGain(mono, 31, 5)) * (1.0f / float(1u << 31));

		float newPeak = monoLog + (m_peak - monoLog) * ((m_peak <= monoLog) ? m_attack : m_release);
		m_peak = newPeak;

		if(newPeak < m_threshold)
			newPeak = m_threshold;

		float compGain = (m_threshold - newPeak) * m_ratio + 0.9999999f;

		// Computes 2 ^ (2 ^ (log2(x) - 26) - 1) (x = 0...2^31)
		uint32 compGainInt = static_cast<uint32>(compGain * 2147483648.0f);
		uint32 compGainPow = compGainInt << 5;
		compGainInt >>= 26;
		if(compGainInt)	// compGainInt >= 2^26
		{
			compGainPow |= 0x80000000u;
			compGainInt--;
		}
		compGainPow >>= (31 - compGainInt);
		
		int32 readOffset = m_predelay + m_bufSize - 1;
		readOffset /= 4096;
		readOffset = (readOffset + m_bufPos) % m_bufSize;

		float outGain = (static_cast<float>(compGainPow) * (1.0f / 2147483648.0f)) * m_gain;
		*(out[0])++ = m_buffer[readOffset * 2] * outGain;
		*(out[1])++ = m_buffer[readOffset * 2 + 1] * outGain;
		
		if(m_bufPos-- == 0)
			m_bufPos += m_bufSize;
	}

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


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


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


void Compressor::Resume()
{
	m_isResumed = true;
	PositionChanged();
	RecalculateCompressorParams();
}


void Compressor::PositionChanged()
{
	m_bufSize = Util::muldiv(m_SndFile.GetSampleRate(), 200, 1000);
	try
	{
		m_buffer.assign(m_bufSize * 2, 0.0f);
	} catch(mpt::out_of_memory e)
	{
		mpt::delete_out_of_memory(e);
		m_bufSize = 0;
	}
	m_bufPos = 0;
	m_peak = 0.0f;
}


#ifdef MODPLUG_TRACKER

CString Compressor::GetParamName(PlugParamIndex param)
{
	switch(param)
	{
	case kCompGain: return _T("Gain");
	case kCompAttack: return _T("Attack");
	case kCompRelease: return _T("Release");
	case kCompThreshold: return _T("Threshold");
	case kCompRatio: return _T("Ratio");
	case kCompPredelay: return _T("Predelay");
	}
	return CString();
}


CString Compressor::GetParamLabel(PlugParamIndex param)
{
	switch(param)
	{
	case kCompGain:
	case kCompThreshold:
		return _T("dB");
	case kCompAttack:
	case kCompRelease:
	case kCompPredelay:
		return _T("ms");
	}
	return CString();
}


CString Compressor::GetParamDisplay(PlugParamIndex param)
{
	float value = m_param[param];
	switch(param)
	{
	case kCompGain:
		value = GainInDecibel();
		break;
	case kCompAttack:
		value = AttackTime();
		break;
	case kCompRelease:
		value = ReleaseTime();
		break;
	case kCompThreshold:
		value = ThresholdInDecibel();
		break;
	case kCompRatio:
		value = CompressorRatio();
		break;
	case kCompPredelay:
		value = PreDelay();
		break;
	}
	CString s;
	s.Format(_T("%.2f"), value);
	return s;
}

#endif // MODPLUG_TRACKER


void Compressor::RecalculateCompressorParams()
{
	const float sampleRate = static_cast<float>(m_SndFile.GetSampleRate()) / 1000.0f;
	m_gain = std::pow(10.0f, GainInDecibel() / 20.0f);
	m_attack = std::pow(10.0f, -1.0f / (AttackTime() * sampleRate));
	m_release = std::pow(10.0f, -1.0f / (ReleaseTime() * sampleRate));
	const float _2e31 = float(1u << 31);
	const float _2e26 = float(1u << 26);
	m_threshold = std::min((_2e31 - 1.0f), (std::log(std::pow(10.0f, ThresholdInDecibel() / 20.0f) * _2e31) * _2e26) / mpt::numbers::ln2_v<float> + _2e26) * (1.0f / _2e31);
	m_ratio = 1.0f - (1.0f / CompressorRatio());
	m_predelay = static_cast<int32>((PreDelay() * sampleRate) + 2.0f);
}

} // namespace DMO

#else
MPT_MSVC_WORKAROUND_LNK4221(Compressor)

#endif // !NO_PLUGINS

OPENMPT_NAMESPACE_END
