// Copyright 2003 Tom Felker, wore
//
// VLevel foobar2000 plugin
//
// VLevel is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// VLevel 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
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with VLevel; if not, write to the Free Software Foundation,
// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

// foo_dsp_vlevel.cpp - the foobar2000 plugin, uses VolumeLeveler
// based on vlevel-bin.cpp
// wore <info@wore.ma.cx>

#include "../SDK/foobar2000.h"
#include <math.h>
#include <float.h>
#include "volumeleveler.h"
#include "resource.h"


// configuration variables for preserving user-configurable settings across foobar sessions - ss97

// stores strength*10 - so by default the VLevel effect strength will be set to 0.8 by this plugin
static const GUID cfg_strength_guid = { 0x42969c96, 0xc0f6, 0x42c8, { 0x96, 0x69, 0x65, 0x2, 0xa7, 0x2e, 0x43, 0xef } };
static cfg_int cfg_strength(cfg_strength_guid, 8);

// stores max_multiplier value directly
static const GUID cfg_max_multiplier_guid = { 0x188e547e, 0xec47, 0x486b, { 0xbb, 0xc1, 0x1e, 0x7a, 0x36, 0xc7, 0x50, 0xb6 } };
static cfg_int cfg_max_multiplier(cfg_max_multiplier_guid, 25);



class dsp_vlevel : public dsp_impl_base
{
	VolumeLeveler *vl;

	// always == vl->GetChannels()
	size_t channels;
	
	// only applies to our local buffers which are cached across on_chunk() calls
	size_t samples;

	// our local buffers
	value_t *raw_value_buf;
	value_t **bufs;

	// only used to SetLengthAndChannels and to create chunks
	UINT srate;

	// how long the user wants the buffer to be
	// make sure GUI resets the flag
	// strength and max_mult can be set directly on the vl at any time
	double buffer_seconds;
	bool buffer_seconds_changed;

	bool ready()
	{
		// could be optimized to vl&&bufs, or expanded for complete sanity checks
		return vl && bufs && raw_value_buf && channels == vl->GetChannels();
	}

	// insert the contents of our buffers into the stream
	// can only be called from on_endoftrack(), on_endofplayback(), and on_chunk()
	void flush_data()
	{
		// foobar2000 sometimes calls this when it shouldn't, so this is necessary.
		if(!ready()) return;
		
		// do nothing if there's no audio in the vl
		// otherwise on_chunk() will make this chunk empty, and foobar2000 complains
		// next vl version will have GetGoodSamples(){return samples - silence};
		if(vl->GetSamples() - vl->GetSilence() == 0) return;

		// make a new chunk filled with enough silence to flush the buffers.
		audio_chunk *chunk = insert_chunk(vl->GetSamples() * channels);
		t_size new_size = vl->GetSamples() * channels;
		if (new_size > chunk->get_data_size())
			chunk->set_data_size(new_size);
		chunk->set_channels(channels);
		chunk->set_srate(srate);
		chunk->set_sample_count(0);
		chunk->pad_with_silence(vl->GetSamples());

		// yes, it's that simple.
		on_chunk(chunk);
		vl->Flush();
	}

	// only affects the local buffers, not the volumeleveler
	void allocate_buffers(size_t new_samples, size_t new_channels)
	{
		cleanup_buffers();
		samples = new_samples;
		channels = new_channels;
		
		raw_value_buf = new value_t[samples * channels];
		bufs = new value_t*[channels];
		for(size_t ch = 0; ch < channels; ++ch)
			bufs[ch] = new value_t[samples];
	}

	// only affects the local buffers, not the volumeleveler
	void cleanup_buffers(void)
	{
		if (raw_value_buf!=0) {
			delete [] raw_value_buf;
			raw_value_buf=0;
		}
		if (bufs!=0) {
			for(size_t ch = 0; ch < channels; ++ch)
				delete [] bufs[ch];
			delete [] bufs;
			bufs=0;
		}
		samples = 0;
	}

public:
	dsp_vlevel()
	{
		raw_value_buf = 0;
		bufs = 0;
		samples = channels = srate = 0;
		buffer_seconds_changed = true;
		
		vl = new VolumeLeveler();

		// Preferences
		buffer_seconds = 2;
		vl->SetStrength(1);
		vl->SetMaxMultiplier(25);
	}

	~dsp_vlevel()
	{
		cleanup_buffers();
		delete vl;
	}

	static GUID g_get_guid()
	{
		// {EC001F79-9D79-4dc8-B7CB-40818D7A1009}
		static const GUID guid =
		{ 0xec001f79, 0x9d79, 0x4dc8, { 0xb7, 0xcb, 0x40, 0x81, 0x8d, 0x7a, 0x10, 0x9 } };
		return guid;
	}

	static void g_get_name(pfc::string_base & p_out) { p_out = "VLevel";}

	virtual bool on_chunk(audio_chunk * chunk)
	{
		// we must change the vl's settings if buffer length or channels changes
		if(chunk->get_srate() != srate || chunk->get_channels() != channels || buffer_seconds_changed) {
			flush_data();
			srate = chunk->get_srate();
			channels = chunk->get_channels();
			vl->SetSamplesAndChannels((size_t)(buffer_seconds * srate), channels);
			buffer_seconds_changed = false;
		}

		// if this chunk is a different size than last time, we must get new temp buffers
		if(chunk->get_sample_count() != samples || chunk->get_channels() != channels)
			allocate_buffers(chunk->get_sample_count(), chunk->get_channels());

		assert(ready());

		// set new VLevel parameters as configured - ss97
		vl->SetStrength((double)cfg_strength / 10);
		vl->SetMaxMultiplier(cfg_max_multiplier);

		audio_sample *data = chunk->get_data();

		// de-interleave the data
		size_t s;
		for(s = 0; s < samples; ++s)
			for(size_t ch = 0; ch < channels; ++ch)
				bufs[ch][s] = data[s * channels + ch];

		// do the exchange
		size_t silence_samples = vl->Exchange(bufs, bufs, samples);

		// interleave the data
		for(s = silence_samples; s < samples; ++s)
			for(size_t ch = 0; ch < channels; ++ch)
				data[(s - silence_samples) * channels + ch] = bufs[ch][s];

		// set the amount of good samples in the chunk
		chunk->set_sample_count(samples - silence_samples);

		return !chunk->is_empty();
	}

	// this is redundant iff on_endoftrack is always called first.
	virtual void on_endofplayback()
	{
		console::info("on_endofplayback");
		flush_data();
	}

	// we output the contents of our buffers
	virtual void on_endoftrack()
	{
		console::info("on_endoftrack");
		flush_data();
	}

	// called on seeks, so we drop our data
	virtual void flush()
	{
		console::info("flush");
		vl->Flush();
	}

	// this is called very often while playing
	virtual double get_latency()
	{
		return (srate == 0) ? 0 : ((double)(vl->GetSamples() - vl->GetSilence()) / srate);
	}

	virtual bool need_track_change_mark()
	{
		// FIXME: have someone take a look at this, g-lite
		return false;
	}
};

// added basic configuration for main VLevel parameters - ss97 (sue me :))
class config_vlevel : public preferences_page
{
	static void update_display(HWND wnd) // updates textual display of slider values
	{
		char temp[128];

		// convert strength slider position to display the actual strength value VLevel will receive later on
		sprintf(temp,"%1.1f",((double)uSendDlgItemMessage(wnd,IDC_STRENGTH,TBM_GETPOS,0,0)) / 10); 
		uSetDlgItemText(wnd,IDC_STATIC_STRENGTH,temp);

		sprintf(temp,"%d",uSendDlgItemMessage(wnd,IDC_MAX_MULTIPLIER,TBM_GETPOS,0,0));
		uSetDlgItemText(wnd,IDC_STATIC_MAX_MULTIPLIER,temp);
	}

	static BOOL CALLBACK ConfigProc(HWND wnd,UINT msg,WPARAM wp,LPARAM lp)
	{
		switch(msg)
		{
		case WM_INITDIALOG: // set slider positions 
			{
				HWND slider;

				// allow strength slider settings of 0-10 (strength*10) so users can set this parameter with 0.1 granularity
				slider = GetDlgItem(wnd,IDC_STRENGTH);
				uSendMessage(slider,TBM_SETRANGE,0,MAKELONG(0,10));
				uSendMessage(slider,TBM_SETPOS,1,cfg_strength);

				slider = GetDlgItem(wnd,IDC_MAX_MULTIPLIER);
				uSendMessage(slider,TBM_SETRANGE,0,MAKELONG(0,40));
				uSendMessage(slider,TBM_SETPOS,1,cfg_max_multiplier);

				update_display(wnd);
			}
			break;
		case WM_HSCROLL:
			{
				cfg_strength = uSendDlgItemMessage(wnd,IDC_STRENGTH,TBM_GETPOS,0,0);

				cfg_max_multiplier = uSendDlgItemMessage(wnd,IDC_MAX_MULTIPLIER,TBM_GETPOS,0,0);

				update_display(wnd);
			}
			break;
		case WM_COMMAND: break;
		default: break;
		}
		return 0;
	}
public:
	virtual HWND create(HWND parent) {
		return uCreateDialog(IDD_CONFIG,parent,ConfigProc);
	}

	virtual const char * get_name() { 
		return "VLevel"; 
	}

	virtual GUID get_guid() {
		static const GUID guid = { 0x7efe5a74, 0x41cc, 0x4f52, { 0x89, 0xb8, 0xd8, 0xa1, 0x17, 0xa0, 0x95, 0xe } };
		return guid;
	}

	virtual GUID get_parent_guid() {
		return guid_playback; 
	}

	virtual bool reset_query() {
		return true;
	}

	virtual void reset() {
		cfg_strength = 8;
		cfg_max_multiplier = 25;
	}

	virtual bool get_help_url(pfc::string_base & p_out) {
		p_out = "http://www.hydrogenaudio.org/forums/index.php?showtopic=22057";
		return true;
	}
};


static dsp_factory_nopreset_t<dsp_vlevel> foo;
static preferences_page_factory_t<config_vlevel> foo2;

// and last but not least the brand spankin' new about box - ss97
#ifdef _DEBUG
#define COMPONENT_VERSION_NAME "VLevel - DEBUG BUILD!"
#else
#define COMPONENT_VERSION_NAME "VLevel"
#endif
DECLARE_COMPONENT_VERSION(COMPONENT_VERSION_NAME,"20060323.0",
						  "volume levelling plugin\n\n"
						  "written by wore\n"
						  "additional code by Tom Felker and ssamadhi97\n"
						  "ported to foobar2000 0.9 by G-Lite\n\n"
						  "based on VLevel 0.5 by Tom Felker (http://vlevel.sourceforge.net/news/)" 
						  );
//wore
