// Game_Music_Emu $vers. http://www.slack.net/~ant/

#include "Effects_Buffer.h"

#include <cmath>
#include <cstring>

/* Copyright (C) 2006-2007 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */

#include "blargg_source.h"

static const int fixed_shift = 12;
template<typename T> static inline Effects_Buffer::fixed_t TO_FIXED(const T &f) { return static_cast<Effects_Buffer::fixed_t>(f * (static_cast<Effects_Buffer::fixed_t>(1) << fixed_shift)); }
static inline Effects_Buffer::fixed_t FROM_FIXED(Effects_Buffer::fixed_t f) { return f >> fixed_shift; }

static const int max_read = 2560; // determines minimum delay

Effects_Buffer::Effects_Buffer(int max_bufs, long echo_size_) : Multi_Buffer(stereo)
{
	this->echo_size = std::max<long>(max_read * stereo, echo_size_ & ~1);
	this->clock_rate_ = 0;
	this->bass_freq_ = 90;
	this->bufs.clear();
	this->bufs_size = 0;
	this->bufs_max = std::max<int>(max_bufs, extra_chans);
	this->no_echo = this->no_effects  = true;

	// defaults
	this->config_.enabled = false;
	this->config_.delay[0] = 120;
	this->config_.delay[1] = 122;
	this->config_.feedback = 0.2f;
	this->config_.treble = 0.4f;

	static const float sep = 0.8f;
	this->config_.side_chans[0].pan = -sep;
	this->config_.side_chans[1].pan = sep;
	this->config_.side_chans[0].vol = this->config_.side_chans[1].vol = 1.0f;

	memset(&this->s, 0, sizeof(this->s));
	this->clear();
}

Effects_Buffer::~Effects_Buffer()
{
	this->delete_bufs();
}

// avoid using new []
blargg_err_t Effects_Buffer::new_bufs(int size)
{
	this->delete_bufs();
	this->bufs.resize(size);
	for (int i = 0; i < size; ++i)
		this->bufs[i].reset(new buf_t);
	this->bufs_size = size;
	return 0;
}

void Effects_Buffer::delete_bufs()
{
	this->bufs.clear();
	this->bufs_size = 0;
}

blargg_err_t Effects_Buffer::set_sample_rate(long rate, int msec)
{
	// extra to allow farther past-the-end pointers
	this->mixer.samples_read = 0;
	this->echo.resize(echo_size + stereo);
	return Multi_Buffer::set_sample_rate(rate, msec);
}

void Effects_Buffer::clock_rate(long rate)
{
	this->clock_rate_ = rate;
	for (int i = this->bufs_size; --i >= 0; )
		this->bufs[i]->clock_rate(this->clock_rate_);
}

void Effects_Buffer::bass_freq(int freq)
{
	this->bass_freq_ = freq;
	for (int i = this->bufs_size; --i >= 0; )
		this->bufs[i]->bass_freq(this->bass_freq_);
}

blargg_err_t Effects_Buffer::set_channel_count(int count, const int *types)
{
	Multi_Buffer::set_channel_count(count, types);

	this->delete_bufs();

	this->mixer.samples_read = 0;

	this->chans.resize(count + extra_chans);

	this->new_bufs(std::min(this->bufs_max, count + extra_chans));

	for (int i = this->bufs_size; --i >= 0; )
		RETURN_ERR(this->bufs[i]->set_sample_rate(this->sample_rate(), this->length()));

	for (int i = this->chans.size(); --i >= 0; )
	{
		auto &ch = this->chans[i];
		ch.cfg.vol = 1.0f;
		ch.cfg.pan = 0.0f;
		ch.cfg.surround = ch.cfg.echo = false;
	}
	// side channels with echo
	this->chans[2].cfg.echo = this->chans[3].cfg.echo = true;

	this->clock_rate(this->clock_rate_);
	this->bass_freq(this->bass_freq_);
	this->apply_config();
	this->clear();

	return 0;
}

void Effects_Buffer::clear_echo()
{
	if (!this->echo.empty())
		memset(&this->echo[0], 0, this->echo.size() * sizeof(echo[0]));
}

void Effects_Buffer::clear()
{
	this->echo_pos = 0;
	this->s.low_pass[0] = this->s.low_pass[1] = 0;
	this->mixer.samples_read = 0;

	for (int i = this->bufs_size; --i >= 0; )
		this->bufs[i]->clear();
	this->clear_echo();
}

auto Effects_Buffer::channel(int i) -> channel_t
{
	i += extra_chans;
	assert(extra_chans <= i && i < static_cast<int>(this->chans.size()));
	return this->chans[i].channel;
}

// Configuration

// 3 wave positions with/without surround, 2 multi (one with same config as wave)
static const int simple_bufs = 3 * 2 + 2 - 1;

Simple_Effects_Buffer::Simple_Effects_Buffer() : Effects_Buffer(extra_chans + simple_bufs, 18 * 1024L)
{
	this->config_.echo = 0.20f;
	this->config_.stereo = 0.20f;
	this->config_.surround = true;
	this->config_.enabled = false;
}

void Simple_Effects_Buffer::apply_config()
{
	auto &c = Effects_Buffer::config();

	c.enabled = this->config_.enabled;
	if (c.enabled)
	{
		c.delay[0] = 120;
		c.delay[1] = 122;
		c.feedback = this->config_.echo * 0.7f;
		c.treble = 0.6f - 0.3f * this->config_.echo;

		float sep = this->config_.stereo + 0.80f;
		if (sep > 1.0f)
			sep = 1.0f;

		c.side_chans[0].pan = -sep;
		c.side_chans[1].pan = sep;

		for (int i = this->channel_count(); --i >= 0; )
		{
			auto &ch = Effects_Buffer::chan_config(i);

			ch.pan = 0.0f;
			ch.surround = this->config_.surround;
			ch.echo = false;

			int type = this->channel_types() ? this->channel_types()[i] : 0;
			if (!(type & noise_type))
			{
				int index = (type & type_index_mask) % 6 - 3;
				if (index < 0)
				{
					index += 3;
					ch.surround = false;
					ch.echo = true;
				}
				if (index >= 1)
				{
					ch.pan = this->config_.stereo;
					if (index == 1)
						ch.pan = -ch.pan;
				}
			}
			else if (type & 1)
				ch.surround = false;
		}
	}

	Effects_Buffer::apply_config();
}

int Effects_Buffer::min_delay() const
{
	assert(this->sample_rate());
	return max_read * 1000L / this->sample_rate();
}

int Effects_Buffer::max_delay() const
{
	assert(this->sample_rate());
	return (this->echo_size / stereo - max_read) * 1000L / this->sample_rate();
}

void Effects_Buffer::apply_config()
{
	if (!this->bufs_size)
		return;

	this->s.treble = TO_FIXED(this->config_.treble);

	bool echo_dirty = false;

	fixed_t old_feedback = this->s.feedback;
	this->s.feedback = TO_FIXED(this->config_.feedback);
	if (!old_feedback && this->s.feedback)
		echo_dirty = true;

	// delays
	int i;
	for (i = stereo; --i >= 0;)
	{
		long delay = this->config_.delay[i] * this->sample_rate() / 1000 * stereo;
		delay = std::max<long>(delay, max_read * stereo);
		delay = std::min<long>(delay, this->echo_size - max_read * stereo);
		if (this->s.delay[i] != delay)
		{
			this->s.delay[i] = delay;
			echo_dirty = true;
		}
	}

	// side channels
	for (i = 2; --i >= 0; )
	{
		this->chans[i + 2].cfg.vol = this->chans[i].cfg.vol = this->config_.side_chans[i].vol * 0.5f;
		this->chans[i + 2].cfg.pan = this->chans[i].cfg.pan = this->config_.side_chans[i].pan;
	}

	// convert volumes
	for (i = this->chans.size(); --i >= 0; )
	{
		auto &ch = this->chans[i];
		ch.vol[0] = TO_FIXED(ch.cfg.vol - ch.cfg.vol * ch.cfg.pan);
		ch.vol[1] = TO_FIXED(ch.cfg.vol + ch.cfg.vol * ch.cfg.pan);
		if (ch.cfg.surround)
			ch.vol[0] = -ch.vol [0];
	}

	this->assign_buffers();

	// set side channels
	for (i = this->chans.size(); --i >= 0; )
	{
		auto &ch = chans[i];
		ch.channel.left = this->chans[ch.cfg.echo * 2].channel.center;
		ch.channel.right = this->chans[ch.cfg.echo * 2 + 1].channel.center;
	}

	bool old_echo = !this->no_echo && !this->no_effects;

	// determine whether effects and echo are needed at all
	this->no_effects = this->no_echo = true;
	for (i = this->chans.size(); --i >= extra_chans; )
	{
		auto &ch = this->chans[i];
		if (ch.cfg.echo && this->s.feedback)
			this->no_echo = false;

		if (ch.vol[0] != TO_FIXED(1) || ch.vol[1] != TO_FIXED(1))
			this->no_effects = false;
	}
	if (!this->no_echo)
		this->no_effects = false;

	if (this->chans[0].vol[0] != TO_FIXED(1) || this->chans[0].vol[1] != TO_FIXED(0) || this->chans[1].vol[0] != TO_FIXED(0) || this->chans[1].vol[1] != TO_FIXED(1))
		this->no_effects = false;

	if (!this->config_.enabled)
		this->no_effects = true;

	if (this->no_effects)
	{
		for (i = this->chans.size(); --i >= 0; )
		{
			auto &ch = this->chans[i];
			ch.channel.center = this->bufs[2].get();
			ch.channel.left = this->bufs[0].get();
			ch.channel.right = this->bufs[1].get();
		}
	}

	this->mixer.bufs[0] = this->bufs[0].get();
	this->mixer.bufs[1] = this->bufs[1].get();
	this->mixer.bufs[2] = this->bufs[2].get();

	if (echo_dirty || (!old_echo && (!this->no_echo && !this->no_effects)))
		this->clear_echo();

	this->channels_changed();
}

void Effects_Buffer::assign_buffers()
{
	// assign channels to buffers
	int buf_count = 0;
	for (int i = 0; i < static_cast<int>(this->chans.size()); ++i)
	{
		// put second two side channels at end to give priority to main channels
		// in case closest matching is necessary
		int x = i;
		if (i > 1)
			x += 2;
		if (x >= static_cast<int>(this->chans.size()))
			x -= this->chans.size() - 2;
		auto &ch = this->chans[x];

		int b = 0;
		for (; b < buf_count; ++b)
		{
			if (ch.vol[0] == this->bufs[b]->vol[0] && ch.vol[1] == this->bufs[b]->vol[1] && (ch.cfg.echo == this->bufs[b]->echo || !this->s.feedback))
				break;
		}

		if (b >= buf_count)
		{
			if (buf_count < this->bufs_max)
			{
				this->bufs[b]->vol[0] = ch.vol[0];
				this->bufs[b]->vol[1] = ch.vol[1];
				this->bufs[b]->echo = ch.cfg.echo;
				++buf_count;
			}
			else
			{
				// TODO: this is a mess, needs refinement
				b = 0;
				fixed_t best_dist = TO_FIXED(8);
				for (int h = buf_count; --h >= 0; )
				{
					auto CALC_LEVELS = [&](fixed_t vols[], fixed_t &sum, fixed_t &diff, bool &surround)
					{
						fixed_t vol_0 = vols[0];
						if (vol_0 < 0)
						{
							vol_0 = -vol_0;
							surround = true;
						}
						fixed_t vol_1 = vols[1];
						if (vol_1 < 0)
						{
							vol_1 = -vol_1;
							surround = true;
						}
						sum = vol_0 + vol_1;
						diff = vol_0 - vol_1;
					};
					fixed_t ch_sum, ch_diff, buf_sum, buf_diff;
					bool ch_surround, buf_surround;
					CALC_LEVELS(ch.vol, ch_sum, ch_diff, ch_surround);
					CALC_LEVELS(this->bufs[h]->vol, buf_sum, buf_diff, buf_surround);

					fixed_t dist = std::abs(ch_sum - buf_sum) + std::abs(ch_diff - buf_diff);

					if (ch_surround != buf_surround)
						dist += TO_FIXED(1) / 2;

					if (this->s.feedback && ch.cfg.echo != this->bufs[h]->echo)
						dist += TO_FIXED(1) / 2;

					if (best_dist > dist)
					{
						best_dist = dist;
						b = h;
					}
				}
			}
		}

		ch.channel.center = this->bufs[b].get();
	}
}

// Mixing

void Effects_Buffer::end_frame(blip_time_t time)
{
	for (int i = bufs_size; --i >= 0; )
		this->bufs[i]->end_frame(time);
}

long Effects_Buffer::read_samples(blip_sample_t *out, long out_size)
{
	out_size = std::min(out_size, this->samples_avail());

	int pair_count = static_cast<int>(out_size >> 1);
	assert(pair_count * stereo == out_size); // must read an even number of samples
	if (pair_count)
	{
		if (this->no_effects)
			this->mixer.read_pairs(out, pair_count);
		else
		{
			int pairs_remain = pair_count;
			do
			{
				// mix at most max_read pairs at a time
				int count = max_read;
				if (count > pairs_remain)
					count = pairs_remain;

				if (this->no_echo)
				{
					// optimization: clear echo here to keep mix_effects() a leaf function
					this->echo_pos = 0;
					memset(&this->echo[0], 0, count * stereo * sizeof(this->echo[0]));
				}
				this->mix_effects(out, count);

				int32_t new_echo_pos = this->echo_pos + count * stereo;
				if (new_echo_pos >= this->echo_size)
					new_echo_pos -= this->echo_size;
				this->echo_pos = new_echo_pos;
				assert(this->echo_pos < this->echo_size);

				out += count * stereo;
				this->mixer.samples_read += count;
				pairs_remain -= count;
			} while (pairs_remain);
		}

		if (this->samples_avail() <= 0 || this->immediate_removal())
		{
			for (int i = this->bufs_size; --i >= 0; )
			{
				auto &b = this->bufs[i];
				// TODO: might miss non-silence settling since it checks END of last read
				if (b->non_silent())
					b->remove_samples(this->mixer.samples_read);
				else
					b->remove_silence(this->mixer.samples_read);
			}
			this->mixer.samples_read = 0;
		}
	}
	return out_size;
}

void Effects_Buffer::mix_effects(blip_sample_t *out_, int pair_count)
{
	typedef fixed_t stereo_fixed_t[stereo];

	// add channels with echo, do echo, add channels without echo, then convert to 16-bit and output
	int echo_phase = 1;
	do
	{
		// mix any modified buffers
		{
			size_t bufNum = 0;
			int bufs_remain = this->bufs_size;
			do
			{
				auto &buf = this->bufs[bufNum++];
				if (buf->non_silent() && (buf->echo == !!echo_phase))
				{
					auto out = reinterpret_cast<stereo_fixed_t *>(&this->echo[this->echo_pos]);
					int bass = BLIP_READER_BASS(*buf);
					BLIP_READER_BEGIN(in, *buf);
					BLIP_READER_ADJ_(in, this->mixer.samples_read);
					fixed_t vol_0 = buf->vol[0];
					fixed_t vol_1 = buf->vol[1];

					int count = static_cast<unsigned>(echo_size - echo_pos) / stereo;
					int remain = pair_count;
					if (count > remain)
						count = remain;
					do
					{
						remain -= count;
						BLIP_READER_ADJ_(in, count);

						out += count;
						int offset = -count;
						do
						{
							fixed_t s = BLIP_READER_READ(in);
							BLIP_READER_NEXT_IDX_(in, bass, offset);

							out[offset][0] += s * vol_0;
							out[offset][1] += s * vol_1;
						} while ( ++offset );

						out = reinterpret_cast<stereo_fixed_t *>(&this->echo[0]);
						count = remain;
					} while (remain);

					BLIP_READER_END(in, *buf);
				}
			} while (--bufs_remain);
		}

		// add echo
		if (echo_phase && !this->no_echo)
		{
			fixed_t feedback = this->s.feedback;
			fixed_t treble = this->s.treble;

			int i = 1;
			do
			{
				fixed_t low_pass = this->s.low_pass[i];

				auto echo_end = &this->echo[this->echo_size + i];
				auto in_pos = &this->echo[this->echo_pos + i];
				int32_t out_offset = this->echo_pos + i + this->s.delay[i];
				if (out_offset >= this->echo_size)
					out_offset -= this->echo_size;
				assert(out_offset < this->echo_size);
				auto out_pos = &this->echo[out_offset];

				// break into up to three chunks to avoid having to handle wrap-around
				// in middle of core loop
				int remain = pair_count;
				do
				{
					auto pos = in_pos;
					if (pos < out_pos)
						pos = out_pos;
					int count = static_cast<uint32_t>(reinterpret_cast<char *>(echo_end) - reinterpret_cast<const char *>(pos)) / (stereo * sizeof(fixed_t));
					if (count > remain)
						count = remain;
					remain -= count;

					in_pos += count * stereo;
					out_pos += count * stereo;
					int offset = -count;
					do
					{
						low_pass += FROM_FIXED(in_pos[offset * stereo] - low_pass) * treble;
						out_pos[offset * stereo] = FROM_FIXED(low_pass) * feedback;
					} while (++offset);

					if (in_pos >= echo_end)
						in_pos -= echo_size;
					if (out_pos >= echo_end)
						out_pos -= echo_size;
				} while (remain);

				this->s.low_pass [i] = low_pass;
			} while (--i >= 0);
		}
	} while (--echo_phase >= 0);

	// clamp to 16 bits
	auto in = reinterpret_cast<stereo_fixed_t *>(&this->echo[this->echo_pos]);
	typedef blip_sample_t stereo_blip_sample_t[stereo];
	auto out = reinterpret_cast<stereo_blip_sample_t *>(out_);
	int count = static_cast<unsigned>(this->echo_size - this->echo_pos) / stereo;
	int remain = pair_count;
	if (count > remain)
		count = remain;
	do
	{
		remain -= count;
		in  += count;
		out += count;
		int offset = -count;
		do
		{
			fixed_t in_0 = FROM_FIXED(in[offset][0]);
			fixed_t in_1 = FROM_FIXED(in[offset][1]);

			BLIP_CLAMP(in_0, in_0);
			out[offset][0] = static_cast<blip_sample_t>(in_0);

			BLIP_CLAMP(in_1, in_1);
			out[offset][1] = static_cast<blip_sample_t>(in_1);
		} while (++offset);

		in = reinterpret_cast<stereo_fixed_t *>(&this->echo[0]);
		count = remain;
	} while (remain);
}