// Taken from https://github.com/manicken/Audio/tree/templateMixer
// Adapted for MiniDexed by Holger Wirtz <dcoredump@googlemail.com>

#ifndef effect_mixer_h_
#define effect_mixer_h_

#include <cstdint>
#include <assert.h>
#include "arm_math.h"

#define UNITY_GAIN 1.0f
#define MAX_GAIN 1.0f
#define MIN_GAIN 0.0f
#define UNITY_PANORAMA 1.0f
#define MAX_PANORAMA 1.0f
#define MIN_PANORAMA 0.0f

template <int NN> class AudioMixer
{
public:
	AudioMixer(uint16_t len)
	{
		buffer_length=len;
		for (uint8_t i=0; i<NN; i++)
			multiplier[i] = UNITY_GAIN;

		sumbufL=new float32_t[buffer_length];
		arm_fill_f32(0.0f, sumbufL, len);
	}

	~AudioMixer()
	{
		delete [] sumbufL;
	}

    void doAddMix(uint8_t channel, float32_t* in)
	{
		assert(in);

		float32_t tmp[buffer_length];
		bool isScaled = (multiplier[channel]!=UNITY_GAIN);

		if(isScaled)
			arm_scale_f32(in,multiplier[channel],tmp,buffer_length);
		arm_add_f32(sumbufL, (isScaled ? tmp : in), sumbufL, buffer_length);
	}

	void gain(uint8_t channel, float32_t gain)
	{
		if (channel >= NN) return;

		if (gain > MAX_GAIN)
			gain = MAX_GAIN;
		else if (gain < MIN_GAIN)
			gain = MIN_GAIN;
		multiplier[channel] = powf(gain, 4); // see: https://www.dr-lex.be/info-stuff/volumecontrols.html#ideal2
	}

	void gain(float32_t gain)
	{
		for (uint8_t i = 0; i < NN; i++)
		{
			if (gain > MAX_GAIN)
				gain = MAX_GAIN;
			else if (gain < MIN_GAIN)
				gain = MIN_GAIN;
			multiplier[i] = powf(gain, 4); // see: https://www.dr-lex.be/info-stuff/volumecontrols.html#ideal2
		} 
	}

	void getMix(float32_t* buffer)
	{
		assert(buffer);
		assert(sumbufL);
		arm_copy_f32(sumbufL, buffer, buffer_length);

		if(sumbufL)
			arm_fill_f32(0.0f, sumbufL, buffer_length);
	}

protected:
	float32_t multiplier[NN];
	float32_t* sumbufL;
	uint16_t buffer_length;
};

template <int NN> class AudioStereoMixer : public AudioMixer<NN>
{
public:
	AudioStereoMixer(uint16_t len) : AudioMixer<NN>(len)
	{
		for (uint8_t i=0; i<NN; i++)
		{
			panorama[i][0] = UNITY_PANORAMA;
			panorama[i][1] = UNITY_PANORAMA;
		}

		sumbufR=new float32_t[buffer_length];
		arm_fill_f32(0.0f, sumbufR, buffer_length);
	}

	~AudioStereoMixer()
	{
		delete [] sumbufR;
	}

        void pan(uint8_t channel, float32_t pan)
	{
		if (channel >= NN) return;

		if (pan > MAX_PANORAMA)
			pan = MAX_PANORAMA;
		else if (pan < MIN_PANORAMA)
			pan = MIN_PANORAMA;

		// From: https://stackoverflow.com/questions/67062207/how-to-pan-audio-sample-data-naturally
		panorama[channel][0]=arm_sin_f32(mapfloat(pan, MIN_PANORAMA, MAX_PANORAMA, 0.0, M_PI/2.0));
		panorama[channel][1]=arm_cos_f32(mapfloat(pan, MIN_PANORAMA, MAX_PANORAMA, 0.0, M_PI/2.0));
	}

	void doAddMix(uint8_t channel, float32_t* in)
	{
		assert(in);

		float32_t tmp[buffer_length];

		// left
		arm_scale_f32(in, panorama[channel][0] * multiplier[channel], tmp, buffer_length);
		arm_add_f32(sumbufL, tmp, sumbufL, buffer_length);

		// right
		arm_scale_f32(in, panorama[channel][1] * multiplier[channel], tmp, buffer_length);
		arm_add_f32(sumbufR, tmp, sumbufR, buffer_length);
	}

	void doAddMix(uint8_t channel, float32_t* inL, float32_t* inR)
	{
		assert(inL);
		assert(inR);

		float32_t tmp[buffer_length];
		bool isScaled = (multiplier[channel]!=UNITY_GAIN);

		// left
		if(isScaled)
			arm_scale_f32(inL,multiplier[channel],tmp,buffer_length);
		arm_add_f32(sumbufL, (isScaled ? tmp : inL), sumbufL, buffer_length);

		// right
		if(isScaled)
			arm_scale_f32(inR,multiplier[channel],tmp,buffer_length);
		arm_add_f32(sumbufR, (isScaled ? tmp : inR), sumbufR, buffer_length);
	}

	void getMix(float32_t* bufferL, float32_t* bufferR)
	{
		assert(bufferR);
		assert(bufferL);
		assert(sumbufL);
		assert(sumbufR);

		arm_copy_f32 (sumbufL, bufferL, buffer_length);
		arm_copy_f32 (sumbufR, bufferR, buffer_length);

		if(sumbufL)
			arm_fill_f32(0.0f, sumbufL, buffer_length);
		if(sumbufR)
			arm_fill_f32(0.0f, sumbufR, buffer_length);
	}

protected:
	using AudioMixer<NN>::sumbufL;
	using AudioMixer<NN>::multiplier;
	using AudioMixer<NN>::buffer_length;
	float32_t panorama[NN][2];
	float32_t* sumbufR;
};

#endif
