// Gap killer DSP component for foobar2000
// Copyright (C) 2003 Janne Hyvärinen
// Ported to Foobar2000 0.9 by Stéphan Kochen
//
// Changes:
//	-- Stéphan:
//	0.4.0 (2006-04-11): Ported to Foobar2000 0.9 / VC++ 2005
//	-- Janne:
//  0.2.1 (2003-07-26): Added silence removing to the end of playback
//  0.2.0 (2003-07-17): Major improvements in silence detection code
//  0.1.5 (2003-05-26): Fixes
//  0.1.4 (2003-05-23): Console only reports total number of samples skipped at the beginning
//  0.1.3 (2003-05-21): Added support to smooth cut position, improved unintended silence detector, added config

#include "../SDK/foobar2000.h"
using namespace pfc;

#include "resource.h"
//#include <commctrl.h>
#include <math.h>
#define PI 3.1415926535897932384626433832795

#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;

// Import the MS XML Library
#import "msxml3.dll"
using namespace MSXML2;

#define MIN(a,b) (a<b?a:b)
#define MAX(a,b) (a>b?a:b)

#define FNG_VERSION     "0.4.0"

#define FNG_MAX_CHUNK   8192    // maximum number of samples in a chunk (initially)

#define FNG_NAME "Continuator"

DECLARE_COMPONENT_VERSION ( FNG_NAME, FNG_VERSION, "Advanced crossfading plugin" );

const char* szFadeLinear = "Linear";
const char* szFadeForceDb = "Const dB/s";
const char* szFadeSCurve = "S-curve";
const char* szFadeInverse = "Inverse";
const char* szContiOverlap = "Overlap";
const char* szContiCrossfade = "Crossfade";
const char* pszFadeOutCurves[] = {szFadeLinear, szFadeForceDb, szFadeSCurve, ""};
const char* pszFadeInCurves[] = {szFadeLinear, szFadeForceDb, szFadeSCurve, ""};
const char* pszContiModes[] = {szContiOverlap, szContiCrossfade, "" };

const long nMaxBufferSizeMs = 20000;

// {D64D6BD0-BAEB-42c4-94E5-B871CA32A3C6}
static const GUID cfg_enable_guid = 
{ 0xd64d6bd0, 0xbaeb, 0x42c4, { 0x94, 0xe5, 0xb8, 0x71, 0xca, 0x32, 0xa3, 0xc6 } };
// {24C078DC-8C30-4efc-8833-E22D981B4B22}
static const GUID cfg_mode_guid = 
{ 0x24c078dc, 0x8c30, 0x4efc, { 0x88, 0x33, 0xe2, 0x2d, 0x98, 0x1b, 0x4b, 0x22 } };
// {8C0CA03A-61C6-4470-9CFD-8D2BD22E20B6}
static const GUID cfg_tag_override_guid = 
{ 0x8c0ca03a, 0x61c6, 0x4470, { 0x9c, 0xfd, 0x8d, 0x2b, 0xd2, 0x2e, 0x20, 0xb6 } };
// {3098DDE1-832A-4e33-949D-6F21455E2F70}
static const GUID cfg_buffersize_guid = 
{ 0x3098dde1, 0x832a, 0x4e33, { 0x94, 0x9d, 0x6f, 0x21, 0x45, 0x5e, 0x2f, 0x70 } };
// {1D3E367C-50E5-4d5e-B479-C00B68891414}
static const GUID cfg_threshold_guid = 
{ 0x1d3e367c, 0x50e5, 0x4d5e, { 0xb4, 0x79, 0xc0, 0xb, 0x68, 0x89, 0x14, 0x14 } };
// {A85CDB10-A465-4cc9-A771-AD2B806F1B9A}
static const GUID cfg_unbuffer_guid = 
{ 0xa85cdb10, 0xa465, 0x4cc9, { 0xa7, 0x71, 0xad, 0x2b, 0x80, 0x6f, 0x1b, 0x9a } };
// {E95C01E3-D931-48d4-A8DE-6F482EB57BED}
static const GUID cfg_seamless_guid = 
{ 0xe95c01e3, 0xd931, 0x48d4, { 0xa8, 0xde, 0x6f, 0x48, 0x2e, 0xb5, 0x7b, 0xed } };
// {FE9930BF-62BF-4e01-B1CF-85E10E7DD837}
static const GUID cfg_cdblocksize_guid = 
{ 0xfe9930bf, 0x62bf, 0x4e01, { 0xb1, 0xcf, 0x85, 0xe1, 0xe, 0x7d, 0xd8, 0x37 } };
// {35E98668-FFDB-4104-8B06-48039345632B}
static const GUID cfg_crossfade_length_guid = 
{ 0x35e98668, 0xffdb, 0x4104, { 0x8b, 0x6, 0x48, 0x3, 0x93, 0x45, 0x63, 0x2b } };
// {268F1225-8A8F-44ee-A32F-CB39C357C193}
static const GUID cfg_fade_out_curve_guid = 
{ 0x268f1225, 0x8a8f, 0x44ee, { 0xa3, 0x2f, 0xcb, 0x39, 0xc3, 0x57, 0xc1, 0x93 } };
// {6D00EC93-0CD5-4c01-84D7-08962E5D1D87}
static const GUID cfg_fade_out_length_guid = 
{ 0x6d00ec93, 0xcd5, 0x4c01, { 0x84, 0xd7, 0x8, 0x96, 0x2e, 0x5d, 0x1d, 0x87 } };
// {9AB67D5E-2034-4d17-9F05-06CA14AAB944}
static const GUID cfg_fade_out_param_guid = 
{ 0x9ab67d5e, 0x2034, 0x4d17, { 0x9f, 0x5, 0x6, 0xca, 0x14, 0xaa, 0xb9, 0x44 } };
// {32D416C6-331A-4227-A8D0-51A05AC32231}
static const GUID cfg_fade_in_curve_guid = 
{ 0x32d416c6, 0x331a, 0x4227, { 0xa8, 0xd0, 0x51, 0xa0, 0x5a, 0xc3, 0x22, 0x31 } };
// {BA8FCA54-FD22-41c8-976C-CC4B49D1801C}
static const GUID cfg_fade_in_length_guid = 
{ 0xba8fca54, 0xfd22, 0x41c8, { 0x97, 0x6c, 0xcc, 0x4b, 0x49, 0xd1, 0x80, 0x1c } };
// {FE6E86FF-BCBC-48b7-9BD8-AC651E665257}
static const GUID cfg_fade_in_param_guid = 
{ 0xfe6e86ff, 0xbcbc, 0x48b7, { 0x9b, 0xd8, 0xac, 0x65, 0x1e, 0x66, 0x52, 0x57 } };
// {2B797B1C-896B-45f0-9B9D-50978B9DF91B}
static const GUID cfg_fade_out_on_sudden_end_guid = 
{ 0x2b797b1c, 0x896b, 0x45f0, { 0x9b, 0x9d, 0x50, 0x97, 0x8b, 0x9d, 0xf9, 0x1b } };

static cfg_bool cfg_enable ( cfg_enable_guid, true ); // enable/disable overlapping (menu command)
static cfg_int cfg_mode ( cfg_mode_guid, 0 ); // operation mode (overlap, crossfade)
static cfg_bool cfg_tag_override ( cfg_tag_override_guid, false ); // decides whether CONTINUATOR tag overrides settings
static cfg_int cfg_buffersize ( cfg_buffersize_guid, 10000 ); // in milliseconds
static cfg_int cfg_threshold ( cfg_threshold_guid, 120 );    // in centibels
static cfg_bool cfg_unbuffer ( cfg_unbuffer_guid, false ); // unbuffer on track change
static cfg_bool cfg_seamless ( cfg_seamless_guid, true );    // disable overlapping if seamless transition between tracks is detected
static cfg_bool cfg_cdblocksize ( cfg_cdblocksize_guid, true ); // force track length to integer multiples of cd blocksize (588 smp)
static cfg_int cfg_crossfade_length ( cfg_crossfade_length_guid, 10000 ); // in milliseconds
static cfg_int cfg_fade_out_curve ( cfg_fade_out_curve_guid, 1 ); // 
static cfg_int cfg_fade_out_length ( cfg_fade_out_length_guid, 1000 ); // in milliseconds
static cfg_int cfg_fade_out_param ( cfg_fade_out_param_guid, 0 ); // 
static cfg_int cfg_fade_in_curve ( cfg_fade_in_curve_guid, 0 ); // 
static cfg_int cfg_fade_in_length ( cfg_fade_in_length_guid, 1000 ); // in milliseconds
static cfg_int cfg_fade_in_param ( cfg_fade_in_param_guid, 0 ); // 
static cfg_bool cfg_fade_out_on_sudden_end ( cfg_fade_out_on_sudden_end_guid, false );

const double dGainFac = 100.; // fade-out gain factor

//static cfg_int cfg_absolute ( "absolute", 0 );
//static cfg_int cfg_smoothing ( "smoothing", 1 );

static bool seek;

// audio cd block size
const unsigned int audiocd_blocksize = 588;
// length of rms blocks
const unsigned int rms_len_ms = 20;
// length of smoothed rms blocks
const unsigned int rms_len_ms2 = 500;

// vector of doubles
typedef vector<double> vec_d;
typedef vector<double>::iterator vec_d_it;

#include <fstream>

template <class T>
void write_vector_to_file(char* file, vector<T>& vec)
{
	std::ofstream f;
	f.open(file);
	if (f != 0) 
	{
		for	(vector<T>::iterator it = vec.begin(); it != vec.end(); ++it)
		{
			f << *it << endl;
		}
		f.close();
	}
}

enum ContinuatorMode
{
	mode_overlap,
	mode_crossfade
};

class dsp_nogaps : public dsp_impl_base {
private:
    unsigned int buffersize;    // maximum gap size in milliseconds
    double threshold_dB;        // cutoff threshold
    //int absolute;
//    int smoothing;


    unsigned int buffer_size;   // current size of buffer
    unsigned int buffer_samples;         // number of samples in buffer
    unsigned int buffer_pos;             // current playback position
    int buffer_wpos;            // current buffer fill position
    unsigned int silent_samples;         // number of silent samples
	unsigned int overlap_samples;		// samples to overlap tracks

	vec_d rms_vec;				// vector of rms values of current track
	vec_d rms_vec_smooth;		// smoothed vector of rms values of current track
	vec_d fade_gain_vec;		// vector with fade gains, calculated to achieve desired fade-out drop
	//vac_d_it fade_it;			// current gain iterator
	vec_d fadein_gain_vec;		// vector with fade-in gains
	vec_d_it fadein_it;			// current gain iterator
	unsigned int fade_cnt;		// counter for forced fade-out blocks
	audio_sample gain;			// current gain for fade-out
	audio_sample gain_in;		// current gain for fade-in
	unsigned int rms_len;		// length of rms segment in samples
	unsigned int rms_cnt;				// counter
	double rms_sum;				// used to sum squares of samples
	double rms_last;			// last rms value
	double rms_alpha;			// smoothing constant
	double rms_min;				// minimum rms value in dB
	double curr_peak;			// current peak value
	double rms_old;				// last rms value of old track

	__int64 track_cnt;			// track sample counter

	bool seamless;

    double last_rms;
    unsigned int counter;
    unsigned int max_chunk;

    int trackchange;            // flag indicating start of new track

    unsigned int srate;         // current sample rate
    unsigned int nch;           // number of channels
	
	array_t<audio_sample> buffer;

    double calc_rms ( const audio_sample *buffer, int dir, unsigned int n )
    {
        const int cadd = dir * nch;
        double sum = 0;
        double rms = 0;

        for ( unsigned int c = 0; c < nch; c++ ) {
            const audio_sample *buf = buffer + dir * c;

            for ( unsigned int i = 0; i < n; i++ ) {
                sum += (*buf) * (*buf);
                buf += cadd;
            }

            rms += sqrt ( sum/n ) / nch;
        }

        return rms < 1.e-5 ? 0 : rms;
	}

	double calc_rms_ab( const audio_sample *buffer, int dir, unsigned int n ) // ab for Axel Behrens
	{
		double sum = 0.;

		for (unsigned int i=0; i<n; i++) 
		{
			for (unsigned int c=0; c<nch; c++) 
			{
				audio_sample curr = buffer[i*nch*dir+c];
				sum += curr * curr;
			}
		}

		double rms = sqrt(sum / (n*nch));
		return rms;
    }

    unsigned int silence_accurate ( const audio_sample *buf, int dir, unsigned int samples )
    {
        for ( unsigned int j = 0; j < samples; j++ ) {
            double s = calc_rms ( buf, dir, 1 );

            if ( s > 0 && last_rms > 0 ) {
                if ( s > last_rms * 20 ) return j;
            } else {
                if ( s > 0.0005 ) return j;
            }

            buf += dir * nch;
        }

        return samples;
    }

    unsigned int silence ( const audio_sample *buffer, int dir, unsigned int samples )
    {
        const audio_sample *buf = buffer;
        const int rms_n = 100;
        unsigned int j = 0;

        for ( ; j < samples/rms_n; j++ ) {
            double rms = calc_rms ( buf, dir, rms_n );

            if ( (last_rms > 0) && (rms > last_rms * 20) ) {
                return (j*rms_n) + silence_accurate ( buf, dir, rms_n );
            }
            if ( rms > 0.005 ) {
                return (j*rms_n) + silence_accurate ( buf, dir, rms_n );
            }

            last_rms = rms;
            buf += rms_n * dir * nch;
        }

        if ( samples > j * rms_n ) {
            double rms = calc_rms ( buf, dir, samples-(j*rms_n) );

            if ( (last_rms > 0) && (rms > last_rms * 20) ) {
                return (j*rms_n) + silence_accurate ( buf, dir, samples-(j*rms_n) );
            }
            if ( rms > 0.005 ) {
                return (j*rms_n) + silence_accurate ( buf, dir, samples-(j*rms_n) );
            }

            last_rms = rms;
        }

        return samples;
    }

	unsigned int silence_ab( const audio_sample *buffer, int dir, unsigned int samples ) // ab for Axel Behrens
	{
		// return first sample position with absolute value > thresh
		double thresh = 0.0001;
		
		// pointer to current audio sample
		const audio_sample* buf = buffer; 
		if (dir == -1)
		{
			// if direction is backwards, set pointer to last channel
			buffer += nch-1;
		}

		for (unsigned int i=0; i<samples*nch; i++) 
		{
			if (fabs(*buf) > thresh) 
			{
				return i/nch;
			}
			buf += dir;
		}

		return samples;
	}

    void smooth ( audio_sample *buffer, const audio_sample *from, const audio_sample *to, unsigned int pos, unsigned int samples, unsigned int length )
    {
        for ( unsigned int c = 0; c < nch; c++ ) {
            audio_sample *buf = buffer + c;
            audio_sample _r = from[c];
            audio_sample _i = (to[c] - _r) / (audio_sample)length;

            for ( unsigned int i = pos; i < pos + samples; i++ ) {
                *buf = _r + _i * i;
                buf += nch;
            }
        }
    }

public:
    dsp_nogaps()
    {
//		flush();
		buffersize		= cfg_buffersize;
        nch            = 0;
		gain		   = 1.f;
		gain_in		   = 1.f;

		rms_min		   = -100.f;

		rms_old			= rms_min;
        
        threshold_dB	= cfg_threshold*.1; // cfg threshold is in centibels
		seamless		= cfg_seamless ? true : false;
        //absolute       = cfg_absolute;
//        smoothing      = cfg_smoothing;

//        buffer_size    = 0;
//        buffer_samples = 0;
//        buffer_pos     = 0;
//        buffer_wpos    = buffer_pos;
        silent_samples = 0;
        counter        = 0;
        last_rms       = 0;
        max_chunk      = FNG_MAX_CHUNK;

        trackchange    = 0;

//        srate          = 0;
//        nch            = 0;

		overlap_samples = 0;

		rms_vec.clear();
		rms_vec_smooth.clear();
		rms_cnt			= 0;
		rms_sum			= 0.;
		rms_last		= 0.;
		rms_alpha		= 0.;

		track_cnt		= 0;

		
		// start from zero
		buffer_size = 0;
		buffer_samples = 0;
		buffer_pos = 0;
		srate = 0;
		rms_len			= rms_len_ms*srate/1000;

		buffer_wpos = buffer_pos;

		fadein_it = fadein_gain_vec.begin();
    }

    ~dsp_nogaps()
    {
    }

	virtual void flush()
    {
        threshold_dB	= cfg_threshold*.1; // cfg threshold is in centibels
		seamless		= cfg_seamless ? true : false;
        //absolute       = cfg_absolute;
//        smoothing      = cfg_smoothing;

        buffer_size    = 0;
        buffer_samples = 0;
        buffer_pos     = 0;
        buffer_wpos    = buffer_pos;
        silent_samples = 0;
        counter        = 0;
        last_rms       = 0;
        max_chunk      = FNG_MAX_CHUNK;

        trackchange    = 0;

//        srate          = 0;
//        nch            = 0;

		overlap_samples = 0;

		rms_vec.clear();
		rms_vec_smooth.clear();
		rms_len			= rms_len_ms*srate/1000;
		rms_cnt			= 0;
		rms_sum			= 0.;
		rms_last		= 0.;
		rms_alpha		= 0.;

		rms_old			= rms_min;

		track_cnt		= 0;
    }

    static GUID g_get_guid()
    {
        // {03529A6F-4B29-4485-A70C-72E15E97AD15}
        static const GUID guid = { 0x3529a6f, 0x4b29, 0x4485, { 0xa7, 0xc, 0x72, 0xe1, 0x5e, 0x97, 0xad, 0x15 } };
        return guid;
    }

    static void g_get_name(pfc::string_base & p_out) { p_out = FNG_NAME; }

    virtual bool need_track_change_mark() { return true; }

    virtual double get_latency()
    {
        if ( srate == 0 ) return 0.;
        return (double)buffer_samples / (double)srate;
    }

    virtual bool on_chunk ( audio_chunk *chunk )
    {
//		if (cfg_enable == 0) {
//			if (buffer_samples > 0) {
//				unbuffer();
//				flush();
//			}
//			return true;
//		}

//		// max size of buffer
//		buffer.check_size( chunk->get_srate() * nMaxBufferSizeMs / 1000 * chunk->get_channels() );
//		buffersize = cfg_buffersize;

		int chunk_samples = chunk->get_sample_count();

        if ( chunk_samples == 0 ) 
			return false; // nothing to do

        if ( (nch && srate) && (chunk->get_srate() != srate || chunk->get_channels() != nch) ) 
		{
			unbuffer();
			flush();
		}

		check_chunk(chunk);

        srate = chunk->get_srate();
        nch = chunk->get_channels();

		// calc new rms length
		rms_len			= rms_len_ms*srate/1000;

        unsigned int gs = srate * buffersize/1000;

        if ( gs + max_chunk*2 > buffer_size ) 
			buffer_size = gs + max_chunk*2;
        buffer.grow_size ( buffer_size * nch );


        unsigned int skip_start = 0;
        unsigned int silent = silent_samples;

		if ( cfg_enable == 1 )
			skip_silence_begin(chunk, gs, skip_start);
		
        unsigned int samples = chunk_samples - skip_start;

        if ( samples > 0 ) 
		{
			check_seamless(chunk, skip_start);

			fill_buffer(chunk, skip_start);

			update_rms(chunk, skip_start);

			trackchange = 0;

			// increase track counter
			track_cnt += samples;
		}

//        char temp[80];

//        if ( overlap_samples > 0 && trackchange == 1) {
//            wsprintfA ( temp, "%u samples (%u ms) overlapped (from end)", overlap_samples, overlap_samples*1000/srate);
//            console::info ( temp );
//        }
//        if ( (skip_start > 0) && (silent_samples == 0) && (silent == 0) ) {
//            wsprintfA ( temp, "%u samples (%u ms) removed (from beginning)", skip_start, skip_start*1000/srate );
//            console::info ( temp );
//        }
//        if ( (silent > 0) && (silent_samples == 0) ) {
//            wsprintfA ( temp, "%u samples (%u ms) removed (from beginning)", silent + skip_start, (silent + skip_start)*1000/srate );
//            console::info ( temp );
//        }
//
        if ( (unsigned)buffer_samples < counter + chunk_samples ) 
			return false; // not enough data on buffer

        if ( (counter < gs) ) {
            counter += chunk_samples;
            if ( counter > gs ) 
				counter = gs;
        }

        // output
		unsigned int len1 = chunk_samples;
        unsigned int len2 = 0;

        if ( buffer_pos + len1 > buffer_size ) {
            len2 = len1 - (buffer_size - buffer_pos);
            len1 = buffer_size - buffer_pos;
        }

        memcpy ( chunk->get_data(), (audio_sample *)buffer.get_ptr() + buffer_pos * nch, len1 * sizeof(audio_sample) * nch );

        if ( len2 > 0 ) {
            memcpy ( (audio_sample *)chunk->get_data() + len1 * nch, buffer.get_ptr(), len2 * sizeof(audio_sample) * nch );
        }

        buffer_samples -= chunk_samples;
        buffer_pos += chunk_samples;
        if ( (unsigned)buffer_pos >= buffer_size )
			buffer_pos -= buffer_size;

        return true;
    }

	void check_chunk(audio_chunk* chunk)
	{
        if ( chunk->get_sample_count() > max_chunk ) { // chunk size exceeds our overflow buffer, increase buffer
            unsigned int old_size = buffer_size;

            max_chunk = chunk->get_sample_count() + 1024;

            buffer_size = chunk->get_srate() * buffersize/1000 + max_chunk*2;

            if ( buffer_size < old_size*2 ) {
                max_chunk = (old_size*2 - ((int)chunk->get_srate() * buffersize/1000)) / 2 + 512;
                buffer_size = chunk->get_srate() * buffersize/1000 + max_chunk*2;
            }

            buffer.grow_size ( buffer_size * chunk->get_channels() );

            if ( buffer_samples > 0 ) {
                unsigned int len = buffer_size - old_size;
                if ( len > old_size ) len = old_size;

                memcpy ( (audio_sample *)buffer.get_ptr() + old_size * nch, buffer.get_ptr(), len * sizeof(audio_sample) * nch );

                if ( buffer_wpos < buffer_pos ) buffer_wpos += old_size;
                if ( buffer_pos == 0 ) buffer_pos = old_size;
                if ( buffer_wpos == 0 ) buffer_wpos = old_size;
            }
        }

	}

	void check_seamless(audio_chunk* chunk, unsigned int skip_start)
	{
        if ( trackchange && overlap_samples > 0 ) 
		{ // search threshold position for last track
			
			seamless = cfg_seamless;
			if (seamless) 
			{
				// compare rms from track end with rms from new track start to detect continuity
				const audio_sample* new_track_start = chunk->get_data() + skip_start*nch;
				// pointer to end of old track, without overlapping
				const int old_end = buffer_wpos+overlap_samples-1 > buffer_size ? 
									buffer_wpos+overlap_samples-1 - buffer_size : 
									buffer_wpos+overlap_samples-1;
				const audio_sample* old_track_end = (audio_sample*)buffer.get_ptr()+ old_end*nch;

				unsigned int len = MIN(256, chunk->get_sample_count() - skip_start);
				// calculate rms value of new chunk
				double rms_new_dB = 20.*log10(calc_rms(new_track_start, 1, len));
				// calculate rms value of end of old track
				double rms_old_dB = rms_old;
//				if (len < old_end)
//				{
//					rms_old_dB = 20.*log10(calc_rms(old_track_end, -1, len));
//				}
// 				else
//				{
//					// first part
//					double rms1 = calc_rms(old_track_end, -1, old_end);
//					// second part
//					double rms2 = calc_rms((audio_sample *)buffer.get_ptr() + (buffer_size-1) * nch, -1, len-old_end);
//					rms_old_dB = 20.*log10(sqrt(0.5*(rms1*rms1+rms2*rms2)));
//				}
				double rms_thresh_dB = -50.;
				if (rms_new_dB > rms_thresh_dB) 
				{
					if (fabs(rms_old_dB - rms_new_dB) < 3.) 
					{
						// seamless track transition detected, disable track overlapping
						buffer_wpos += overlap_samples;
				        if ( (unsigned)buffer_wpos >= buffer_size ) 
							buffer_wpos -= buffer_size;
						buffer_samples += overlap_samples;
						overlap_samples = 0;
					
						// we need to disable fade-in as well
						fadein_gain_vec.clear();
						fadein_it = fadein_gain_vec.begin();
						gain_in = 1.;

					} // if (fabs(rms_old_dB - rms_new_dB) < 3.) 

				} // if (rms_new_dB > rms_thresh_dB)

			} // if (cfg_seamless)
			
		} // if ( trackchange && overlap_samples > 0 )

	}

	void skip_silence_begin(audio_chunk* chunk, unsigned int gs, unsigned int& skip_start)
	{
		if ( trackchange || silent_samples > 0 )
		{ // skip silence at the beginning of track

            if ( trackchange ) 
				last_rms = 0;

			audio_sample* data = chunk->get_data();
			unsigned int sample_count = chunk->get_sample_count();
            skip_start = silence_ab( data, +1, sample_count );

            if ( skip_start == sample_count ) 
			{
                if ( silent_samples + skip_start > gs ) 
				{
                    skip_start = gs - silent_samples;
                    silent_samples = 0;
                } 
				else 
				{
                    silent_samples += skip_start;
                }
            } 
			else 
			{
                silent_samples = 0;
            }
        } // if ( trackchange || silent_samples > 0 )

	}

	void skip_silence_end(unsigned int& skip_end)
	{
        unsigned int gs = srate * buffersize/1000;

        if ( buffer_samples > 0 ) 
		{ // remove silence from the end of last track
            last_rms = 0;

            int last_sample = buffer_wpos-1;
            if ( last_sample < 0 ) last_sample = buffer_size-1;

            unsigned int len1 = ((unsigned)buffer_samples < gs)  ?  buffer_samples  :  gs;
            unsigned int len2 = 0;

            if ( last_sample - (int)len1 < 0 ) 
			{
                len2 = len1 - (last_sample+1);
                len1 = last_sample+1;
            }

            audio_sample *ptr = (audio_sample *)buffer.get_ptr() + last_sample * nch;
            skip_end = silence_ab( ptr, -1, len1 );
            buffer_samples -= skip_end;
            buffer_wpos -= skip_end;
			track_cnt -= skip_end;

            if ( (skip_end == len1) && (len2 > 0) ) 
			{
                ptr = (audio_sample *)buffer.get_ptr() + (buffer_size-1) * nch;
                unsigned int skip = silence_ab( ptr, -1, len2 );
                buffer_samples -= skip;
                buffer_wpos -= skip;
				track_cnt -= skip_end;
                skip_end += skip;
            }

            if ( buffer_wpos < 0 )
				buffer_wpos += buffer_size;
        }

	}
	
	void fill_buffer(audio_chunk* chunk, unsigned int skip_start)
	{
		// fill buffer
        unsigned int len1 = chunk->get_sample_count()-skip_start;
        unsigned int len2 = 0;

        if ( buffer_wpos + len1 > buffer_size ) {
            len2 = len1 - (buffer_size - buffer_wpos);
            len1 = buffer_size - buffer_wpos;
        }

        audio_sample *src = (audio_sample *)chunk->get_data() + skip_start * nch;
        audio_sample *dst = (audio_sample *)buffer.get_ptr() + buffer_wpos * nch;

        // overlap on track change
		if ( fadein_it < fadein_gain_vec.end() || overlap_samples > 0 ) 
        {
			for (unsigned int i=0; i<len1; i++) 
			{
				if ( overlap_samples > 0 )
				{
					for	(unsigned int c=0; c<nch; c++)
					{
						dst[i*nch+c] += src[i*nch+c] * gain_in;
					}
					overlap_samples--;
				}
				else
				{
					for	(unsigned int c=0; c<nch; c++)
					{
						dst[i*nch+c] = src[i*nch+c] * gain_in;
					}
				}
				fade_cnt++;
				if (fade_cnt >= rms_len)
				{
					fade_cnt = 0;
					// increase fade vector iterator
					if ( fadein_it < fadein_gain_vec.end()-1 )
					{
						fadein_it++;
						// fade_vec is in dB
						gain_in = pow(10., *fadein_it*.05);
					}
				}
			}

        }
		else
		{
			memcpy ( dst, src, len1 * sizeof(audio_sample) * nch );
		}
		 
        buffer_samples += len1;
        buffer_wpos += len1;

        if ( len2 > 0 ) 
		{ 
            src += len1 * nch;
            dst = (audio_sample *)buffer.get_ptr();
            
			if ( fadein_it < fadein_gain_vec.end() || overlap_samples > 0 ) 
			{
				for (unsigned int i=0; i<len2; i++) 
				{
					if ( overlap_samples > 0)
					{
						for	(unsigned int c=0; c<nch; c++)
						{
							dst[i*nch+c] += src[i*nch+c] * gain_in;
						}
						overlap_samples--;
					}
					else
					{
						for	(unsigned int c=0; c<nch; c++)
						{
							dst[i*nch+c] = src[i*nch+c] * gain_in;
						}
					}
					fade_cnt++;
					if (fade_cnt >= rms_len)
					{
						fade_cnt = 0;
						// increase fade vector iterator
						if ( fadein_it < fadein_gain_vec.end()-1 )
						{
							fadein_it++;
							// fade_vec is in dB
							gain_in = pow(10., *fadein_it*.05);
						}
					}
				}
			}
			else
			{
				memcpy ( dst, src, len2 * sizeof(audio_sample) * nch );
			}
		
			buffer_samples += len2;
            buffer_wpos += len2;
        }

        if ( (unsigned)buffer_wpos >= buffer_size ) 
			buffer_wpos -= buffer_size;

	}

	void apply_gain( unsigned int relative_pos, vec_d& gain_vec )
	{
		vec_d_it gain_it = gain_vec.begin();
		audio_sample gain = pow( 10., *gain_it * .05 );
		unsigned int nCnt = 0;

		unsigned int start_pos = buffer_pos + relative_pos;
		if ( start_pos > buffer_size )
		{
			start_pos -= buffer_size;
		}

		audio_sample* dst = (audio_sample *)buffer.get_ptr() + start_pos * nch;
		unsigned int len1 = buffer_samples - relative_pos;
		unsigned int len2 = 0;

		if ( start_pos + len1 > buffer_size )
		{
			len2 = len1 - (buffer_size - start_pos);
			len1 = buffer_size - start_pos;
		}

		for (unsigned int i = 0; i < len1; i++ )
		{
			for	(unsigned int c=0; c<nch; c++)
			{
				dst[i*nch+c] *= gain;
			}
			nCnt++;
			if ( nCnt >= rms_len )
			{
				if ( gain_it < gain_vec.end()-1 )
				{
					gain_it++;
					gain = pow( 10., *gain_it * .05 );
				}
				else
				{
					break;
				}
				nCnt = 0;
			}
		}

		if ( len2 > 0 )
		{
			dst = (audio_sample*)buffer.get_ptr();
			for (unsigned int i = 0; i < len2; i++ )
			{
				for	(unsigned int c=0; c<nch; c++)
				{
					dst[i*nch+c] *= gain;
				}
				nCnt++;
				if ( nCnt >= rms_len )
				{
					if ( gain_it < gain_vec.end()-1 )
					{
						gain_it++;
						gain = pow( 10., *gain_it * .05 );
					}
					else
					{
						break;
					}
					nCnt = 0;
				}
			}
		}

	}

	void update_rms(audio_chunk* chunk, unsigned int skip_start)
	{
		// update rms sum
        audio_sample* src = (audio_sample *)chunk->get_data() + skip_start * nch;
		double curr_value;
		unsigned samplesLeft = chunk->get_sample_count()-skip_start;
		while (samplesLeft > 0) 
		{
			unsigned maxSamples = rms_len - rms_cnt;
			unsigned samplesToSum = MIN(maxSamples, samplesLeft);
			for (unsigned int i=0; i<samplesToSum; i++) 
			{
				for (unsigned int c=0; c<nch; c++) 
				{
					// current value
					curr_value = *src++;
					// rms
					rms_sum += curr_value*curr_value;
				}
			}
			rms_cnt += samplesToSum;
			samplesLeft -= samplesToSum;

			// calculate rms if enough samples are collected
			if (rms_cnt == rms_len) 
			{
				// calculate new rms value (without sqrt since we do a dB conversion anyway)
				double newRMS = rms_sum / (rms_len*nch) + 1e-15;
				// convert new rms value to dB and save (note 10.)
				rms_vec.push_back(MAX(rms_min, rms_alpha*rms_last + (1.-rms_alpha)*10.*log10(newRMS)));
				rms_last = rms_vec.back();

				// reset counter and sum
				rms_cnt = 0; 
				rms_sum = 0.;

			} // if (rms_cnt == rms_len

		} // while (samplesLeft > 0)
	}
	
	void smooth_vector(vec_d& vec_src, vec_d& vec_dst, unsigned int skip_begin, unsigned int smooth_len)
	{
		// clear destination vector if not already empty
		if (!vec_dst.empty()) 
		{
			vec_dst.clear();
		}
		vec_dst.reserve(vec_src.size()-skip_begin);

		// check size
		if (vec_src.size()-skip_begin < smooth_len/2) 
		{
			// no smoothing possible, just copy elements
			copy(vec_src.begin()+skip_begin, vec_src.end(), vec_dst.begin());
			return;
		}

		// iterators
		vec_d_it it_src_add = vec_src.begin()+skip_begin, it_src_sub = vec_src.begin()+skip_begin;

		// start with sum of first smooth_len values
		double sum = accumulate(it_src_add, it_src_add+smooth_len/2, 0.);
		it_src_add += smooth_len/2;
		double inv = 1./(smooth_len/2);
		vec_dst.push_back( sum*inv );

		// beginning
		while (it_src_add < vec_src.begin()+skip_begin+smooth_len) 
		{
			sum += *it_src_add++;
			inv = 1./(1./inv + 1.);
			vec_dst.push_back( sum*inv );
		}

		// middle part
		while (it_src_add < vec_src.end()) 
		{
			sum += *it_src_add++;
			sum -= *it_src_sub++;
			vec_dst.push_back( sum*inv );
		}

		// ending
		while (it_src_sub < vec_src.end()-smooth_len/2-1) 
		{
			sum -= *it_src_sub++;
			inv = 1./(1./inv - 1.);
			vec_dst.push_back( sum*inv );
		}

//		assert( vec_src.size()-skip_begin == vec_dst.size());
	}

    virtual void on_endoftrack()
    {
		if ( cfg_enable == 0 )
			return;

//		bool bMP3 = playingMP3();

		unsigned int gs = srate * buffersize/1000;
		unsigned int skip_silence   = 0;
		unsigned int skip_end   = 0;

		// remove silent samples from end of current track
		skip_silence_end(skip_silence);

        threshold_dB	= cfg_threshold*.1; // cfg threshold is in centibels

		int nFadeOutLength = MIN( buffer_samples, cfg_fade_out_length * srate / 1000. );
		int nFadeInLength = cfg_fade_in_length * srate / 1000.;

		int nCrossfadeLength = 0;
		int nMode = cfg_mode;
		ContinuatorMode mode = mode_overlap;
		switch ( nMode )
		{
		case 0:
			mode = mode_overlap;
			break;
		case 1:
			mode = mode_crossfade;
			nCrossfadeLength = MIN( buffer_samples, cfg_crossfade_length*srate/1000 );
			break;
		}

		bool bOverridden = false;

		// read desired operating mode from CONTINUATOR tag
		string8 str;
		if ( cfg_tag_override )
		{
			metadb_handle_ptr curfile;
			file_info_impl curinfo;

			if ( get_cur_file(curfile) && curfile->get_info(curinfo) && curinfo.meta_exists("CONTINUATOR") )
			{
				str = curinfo.meta_get( "CONTINUATOR", 0 );
				bOverridden = true;

				// tag found, override operation mode
				char* szLower = _strlwr( _strdup( str.get_ptr() ) );
				if ( strstr( szLower, "crossfade" ) != 0 )
				{
					mode = mode_crossfade;
					int n = str.find_first( ',' );
					if ( n >= 0 )
					{
						int nCrossfadeLength_ms = atol( str.get_ptr() + n + 1 );
						nCrossfadeLength = MIN( buffer_samples, nCrossfadeLength_ms*srate/1000 );
					}
					else
					{
						// no length specified, use maximum
						nCrossfadeLength = MIN( buffer_samples, cfg_crossfade_length*srate/1000 );
					}
				}
				else if ( strstr( szLower, "overlap" ) != 0 )
				{
					mode = mode_overlap;
					int n = str.find_first( ',' );
					if ( n >= 0 )
					{
						double dTemp = atof( str.get_ptr() + n + 1 );
						if ( dTemp > 0 && dTemp < 100 )
						{
							threshold_dB = dTemp;
						}
					}
				}
				free( szLower );
			}
		}

        trackchange    = 1;
        //silent_samples = 0;
        counter        = 0;

		// insert last rms value (from ongoing summation)
		if (rms_cnt > skip_silence)
		{
			double newRMS = rms_sum / (rms_cnt*nch + 1e-15);
			// convert new rms value to dB and save (note 10.)
			rms_vec.push_back(MAX(rms_min, rms_alpha*rms_last + (1.-rms_alpha)*10.*log10(newRMS)));
		}

		// remove silent rms values from end of vector
		if (skip_silence > 0)
		{
			unsigned int nSkip = skip_silence / rms_len;
			if (nSkip < rms_vec.size())
			{
				rms_vec.erase(rms_vec.end()-nSkip, rms_vec.end());
			}
		}

		// save last rms value for seamless transition detection
		// only if settings have not been overridden by CONTINUATOR-tag value
		if ( !bOverridden ) 
		{
			rms_old = !rms_vec.empty() ? rms_vec.back() : -90.;
		}

#ifdef _DEBUG
//		write_vector_to_file("c:\\temp\\rms_vec.dat", rms_vec);
#endif

        if ( rms_vec.size() > 0 ) 
		{ 
			// search threshold position for last track

			// search only last buffer_samples + 10 sec
			unsigned int skip_begin_vec = MAX(0L, (int)rms_vec.size() - (int)(buffer_samples + 10L*srate)/(int)rms_len);

			smooth_vector(rms_vec, rms_vec_smooth, skip_begin_vec, rms_len_ms2/rms_len_ms);

#ifdef _DEBUG
			write_vector_to_file( "c:\\temp\\rms_vec.txt", rms_vec );
			write_vector_to_file( "c:\\temp\\rms_vec_smooth.txt", rms_vec_smooth );
#endif

			// search maximum rms value
			vec_d_it itMax;
			itMax = max_element(rms_vec_smooth.begin(), rms_vec_smooth.end());

			// determine rms_threshold
			double rms_thresh_dB = *itMax - threshold_dB;

			// fade-out
			int nCntFadeOut = nFadeOutLength / rms_len;
			if ( cfg_fade_out_on_sudden_end )
			{
				// check if there's audio up to the end
				if ( rms_vec_smooth.back() > rms_thresh_dB )
					nCntFadeOut = nFadeOutLength / rms_len;
				else
					nCntFadeOut = 0;
			}
			fade_gain_vec.clear();
			fade_gain_vec.reserve(nCntFadeOut);
			if ( strcmp( pszFadeOutCurves[cfg_fade_out_curve], szFadeForceDb ) == 0 )
			{
				// TODO: make sure, that cnt isn't larger than rms_vec_smooth.length()

				// fade to target of cfg_fade_out_param dB
				vec_d_it itSmooth = rms_vec_smooth.end()-nCntFadeOut-1;
				double start_dB = *itSmooth;
				double gain_dB_inc = ( (-cfg_fade_out_param/100.)*dGainFac - start_dB ) / nCntFadeOut;
				for (unsigned int n=0; n<nCntFadeOut; n++)
				{
					double newVal = start_dB + n*gain_dB_inc - *itSmooth++;
					fade_gain_vec.push_back(min(0.,newVal));
				}
			}
			else if ( strcmp( pszFadeOutCurves[cfg_fade_out_curve], szFadeSCurve ) == 0 )
			{
				double dFac = .2 + cfg_fade_out_param / 100.;
				for (unsigned int n=0; n<nCntFadeOut; n++)
				{
					double fNewGainVal = ( pow( .5 * ( cos( PI * n / nCntFadeOut ) + 1. ), dFac ) - 1. ) * 80.;
//					fade_gain_vec.push_back( 20.*log10( MAX( pow(fNewGainVal, dFac), 1e-10 ) ) );
					fade_gain_vec.push_back( fNewGainVal );
				}
			}
			else if ( strcmp( pszFadeOutCurves[cfg_fade_out_curve], szFadeLinear ) == 0 )
			{
				for (unsigned int n=0; n<nCntFadeOut; n++)
				{
					double fNewGainVal = 1. - n / ( nCntFadeOut - 1. );
					fade_gain_vec.push_back( 20.*log10( MAX( fNewGainVal, 1e-10 ) ) );
				}
			}
			//			// check length covered by fade_gain_vec
			//			while (fade_gain_vec.size() > 0 && fade_gain_vec.size()*rms_len < overlap_samples) 
			//			{
			//				// push back additional elements repeating last value
			//				double lastVal = fade_gain_vec.back();
			//				fade_gain_vec.push_back(lastVal);
			//			}
			if ( !fade_gain_vec.empty() && !cfg_seamless ) // no fade-out when detecting seamless transitions, because decision can only be made after fade-out 
			{												   // has been processed	
				assert( buffer_samples >= nFadeOutLength );

				// apply gain to audio
				apply_gain( buffer_samples - nFadeOutLength, fade_gain_vec );

				// and to rms vector (for overlap computation)
				int nStart = rms_vec_smooth.size()-nCntFadeOut;
				for( int k = 0; k < fade_gain_vec.size(); k++ )
					rms_vec_smooth[nStart + k] += fade_gain_vec[k];
			}


			// search maximum rms value
			unsigned int n = buffer_samples / rms_len;
			itMax = max_element(rms_vec_smooth.begin(), rms_vec_smooth.end());

			// determine rms_threshold
			rms_thresh_dB = *itMax - threshold_dB;

			long cnt = 0;
			if ( mode == mode_crossfade)
			{
				// fade length fixed to buffer length
				cnt = nCrossfadeLength / rms_len;
			}
			else
			{
				// find first rms value greater than threshold (starting from the end)
				int itVal = rms_vec_smooth.size()-1;
				while (itVal >= 0 && cnt < n) 
				{
					if (rms_vec_smooth[itVal] > rms_thresh_dB) 
					{
						break;
					}
					itVal--;
					cnt++;
				}

				// check if rms drop is too fast, so that additional space is needed
				int it = itVal - rms_len_ms2/rms_len_ms/2;
				if ( it < 0)
					it = 0;
				int it2 = itVal + rms_len_ms2/rms_len_ms/2;
				if ( it2 >= rms_vec_smooth.size())
					it2 = rms_vec_smooth.size()-1;
				double dThreshDrop = abs( rms_vec_smooth[it2] - rms_vec_smooth[it] );
				unsigned long add_skip = MIN( 1., dThreshDrop / 20. ) * 500 / rms_len_ms + 0.5;
				cnt -= add_skip;
				if ( cnt < 0 )
					cnt = 0;


				// check if there's audio right to the end of the track as it is the case
				// with live albums

				// TODO

			}
			skip_end = MIN(cnt*rms_len+rms_cnt, buffer_samples);
//				skip_end = cnt*rms_len+rms_cnt;

			if (cfg_cdblocksize)
			{
				// track length must be multiple of audio cd blocksize

				// unconstrained track size
				__int64 old_size = track_cnt - skip_end;

				// ensure integer multiple of audio cd block size
				__int64 new_size = (old_size/audiocd_blocksize)*audiocd_blocksize;
			
				// new skip value
				skip_end = track_cnt - new_size;

				// check size
				if (skip_end < 0) 
				{
					skip_end += audiocd_blocksize;
				}
				if (skip_end > buffer_samples) 
				{
					skip_end -= audiocd_blocksize;
				}
			}
			
			buffer_samples -= skip_end;
			buffer_wpos -= skip_end;

		
			if ( buffer_wpos < 0 ) 
				buffer_wpos += buffer_size;

			// set samples to overlap
			overlap_samples = skip_end;

			// reset rms vector 
			rms_vec.clear();
			
			// reset rms_last value
			rms_last = rms_thresh_dB;

			// reset track counter
			track_cnt = 0;
			
			// reset rms sum and counter
			rms_sum = 0.;
			rms_cnt = 0;

			// set up fade gain vector if desired
			gain = 1.f;
			gain_in = 1.f;
			
			// fade-in
			int nCntFadeIn = nFadeInLength / rms_len;
			fadein_gain_vec.clear();
			fadein_gain_vec.reserve( nCntFadeIn );
			if ( strcmp( pszFadeInCurves[cfg_fade_in_curve], szFadeSCurve ) == 0 )
			{
				double dFac = cfg_fade_in_param / 100.;
				for (unsigned int n=0; n<nCntFadeIn; n++)
				{
					double fNewGainVal = .5 * ( cos( PI * n / nCntFadeIn ) + 1. );
					double fNewGainValIn = 1. - fNewGainVal;
					fadein_gain_vec.push_back( 20. * log10( MAX( pow( fNewGainValIn, dFac ), 1e-10 ) ) );
				}
			}
			else if ( strcmp( pszFadeInCurves[cfg_fade_in_curve], szFadeInverse ) == 0 )
			{
				for (unsigned int n=0; n<nCntFadeIn; n++)
				{
					double fNewGainVal = pow( 10., fade_gain_vec[n]*.05 );
					double fNewGainValIn = 1. - fNewGainVal;
					fadein_gain_vec.push_back( 20. * log10( MAX( sqrt(fNewGainValIn), 1e-10 ) ) );
				}
			}
			else if ( strcmp( pszFadeInCurves[cfg_fade_in_curve], szFadeLinear ) == 0 )
			{
				for (unsigned int n=0; n<nCntFadeIn; n++)
				{
					double fNewGainVal = n / ( nCntFadeIn - 1. );
					fadein_gain_vec.push_back( 20.*log10( MAX( fNewGainVal, 1e-10 ) ) );
				}
			}
			else if ( strcmp( pszFadeInCurves[cfg_fade_in_curve], szFadeForceDb) == 0 )
			{
				// fade to target of cfg_fade_out_param dB
				double start_dB = (-cfg_fade_in_param/100.)*dGainFac;
				double gain_dB_inc = -start_dB / nCntFadeIn;
				for (unsigned int n=0; n<nCntFadeIn; n++)
				{
					double newVal = start_dB + n*gain_dB_inc;
					fadein_gain_vec.push_back(min(0.,newVal));
				}
			}

			// reset counter
			fade_cnt = 0;
			fadein_it = fadein_gain_vec.begin();

			if (nCntFadeIn > 0)
			{
				// set gains to first values (converted to linear scale)
				gain_in = pow(10., fadein_gain_vec.front()*.05);
			}

			if (cfg_unbuffer) 
			{
				// dump samples up to new track starting point
				unbuffer();
			}
        }
    }

    virtual void on_endofplayback()
    {
		unsigned int gs = srate * buffersize/1000;
        unsigned int skip_end   = 0;

		// check overlap samples
		if (overlap_samples > 0)
		{
			buffer_samples += overlap_samples;
			buffer_wpos += overlap_samples;
			if (buffer_wpos > buffer_size)
			{
				buffer_wpos -= buffer_size;
			}
		}
		
        //skip_silence_end(skip_end);

//        if ( skip_end > 0 ) {
//            char temp[80];
//            wsprintfA ( temp, "%u samples (%d ms) removed (from end)", skip_end, skip_end*1000/srate );
//            console::info ( temp );
//        }

        unbuffer();
		flush();
    }
 
	// unbuffer in small chunks
    void unbuffer(unsigned nSamples = 16384)
    {
		if ( nSamples <= 0 || nSamples > buffer_samples )
			nSamples = buffer_samples;

		while ( buffer_samples > 0 ) 
		{
			if ( nSamples > buffer_samples )
				nSamples = buffer_samples;

	        audio_chunk *t = insert_chunk ( nSamples * nch );
			t->grow_data_size ( nSamples * nch );
            audio_sample *tc = t->get_data();
            t->set_channels ( nch );
            t->set_srate ( srate );
            t->set_sample_count ( nSamples );

            unsigned int len1 = nSamples;
            unsigned int len2 = 0;

            if ( buffer_pos + len1 > buffer_size ) {
                len2 = len1 - (buffer_size - buffer_pos);
                len1 = buffer_size - buffer_pos;
            }

            memcpy ( tc, (audio_sample *)buffer.get_ptr() + buffer_pos * nch, len1 * sizeof(audio_sample) * nch ); 

            if ( len2 > 0 ) {
                tc += len1 * nch;
                memcpy ( tc, buffer.get_ptr(), len2 * sizeof(audio_sample) * nch ); 
            }
	
			buffer_pos += nSamples;
			if ( (unsigned)buffer_pos >= buffer_size ) buffer_pos -= buffer_size;
			buffer_samples -= nSamples;

//			Sleep( (double)nSamples / srate * 1000. );
       }

    }

	bool playingMP3(void)
	{
		metadb_handle_ptr file;
		get_cur_file(file);
		file->metadb_lock();
		const file_info* info;
		file->get_info_locked(info);
		string8 test;
		test = info->info_get("codec");
		file->metadb_unlock();
		return stricmp_utf8(test, "MP3") == 0;
	}

};

/*
class presets
{
public:
	presets()
	{
		// read preset file
		// 30 bytes preset name
		// 4 bytes unsigned long mem_block size
		// mem_block
		// ...

		m_nCurrPreset = 0;
	}
	
	~presets()
	{
		// save preset file
	}

	void getPresetName( int iIndex, string_simple& strPresetName )
	{
		if ( iIndex < m_vecPresetNames.size() )
			strPresetName = m_vecPresetNames[iIndex];
	}

	void setPresetName( int iIndex, string_simple strPresetName )
	{
		if ( iIndex < m_vecPresetNames.size() )
			m_vecPresetNames[iIndex] = strPresetName;
	}

	void newPreset()
	{
		m_vecPresetData.push_back( mem_block() );
		m_vecPresetNames.push_back( string_simple() );

		m_nCurrPreset = m_vecPresetData.size() - 1;
	}

	void updateCurrentPreset()
	{
		write_config_callback_i_ref out( m_vecPresetData[m_nCurrPreset] );
		cfg_var::config_write_file( out );
	}

	void switchPreset( string_simple strPresetName )
	{
	}

private:
	vector<mem_block> m_vecPresetData;
	vector<string_simple> m_vecPresetNames;

	unsigned long m_nCurrPreset;
};
//*/


// {DD02539E-0152-4d37-B0D0-42324F3EBF15}
static const GUID config_nogaps_guid = 
{ 0xdd02539e, 0x152, 0x4d37, { 0xb0, 0xd0, 0x42, 0x32, 0x4f, 0x3e, 0xbf, 0x15 } };

class config_nogaps : public preferences_page {
private:
    static void do_update_display ( HWND wnd )
    {
        char temp[32];
		int val = cfg_buffersize;
		wsprintfA ( temp, "%i", val );
		uSetDlgItemText ( wnd, IDC_BUFFER_DISPLAY, temp );

		val = cfg_threshold;
		int absval = abs(val);
		wsprintfA ( temp, "%i.%01i", val/10, absval%10 );
		uSetDlgItemText ( wnd, IDC_THRESHOLD_DISPLAY, temp );

		val = cfg_crossfade_length;
		wsprintfA ( temp, "%i", val );
		uSetDlgItemText ( wnd, IDC_CROSSFADE_LENGTH_DISPLAY, temp );

		val = cfg_fade_out_length;
		wsprintfA ( temp, "%i", val );
		uSetDlgItemText ( wnd, IDC_FADE_OUT_LENGTH_DISPLAY, temp );

		val = cfg_fade_in_length;
		wsprintfA ( temp, "%i", val );
		uSetDlgItemText ( wnd, IDC_FADE_IN_LENGTH_DISPLAY, temp );

		val = cfg_fade_out_param;
		if ( strcmp( pszFadeOutCurves[cfg_fade_out_curve], szFadeForceDb ) == 0 )
		{
			val *= dGainFac;
			val /= 10.;
			wsprintfA ( temp, "-%i.%01i", val/10, val%10 );
			uSetDlgItemText( wnd, IDC_FADE_OUT_CAPTION, "Fade target (dB):" );
		}
		else if ( strcmp( pszFadeOutCurves[cfg_fade_out_curve], szFadeSCurve ) == 0 )
		{
			wsprintfA ( temp, "%i.%02i", val/100, val%100 );
			uSetDlgItemText( wnd, IDC_FADE_OUT_CAPTION, "Steepness:" );
		}
		uSetDlgItemText ( wnd, IDC_FADE_OUT_DISPLAY, temp );

		val = cfg_fade_in_param;
		if ( strcmp( pszFadeInCurves[cfg_fade_in_curve], szFadeForceDb ) == 0 )
		{
			val *= dGainFac;
			val /= 10.;
			wsprintfA ( temp, "-%i.%01i", val/10, val%10 );
			uSetDlgItemText( wnd, IDC_FADE_IN_CAPTION, "Fade start (dB):" );
		}
		else if ( strcmp( pszFadeInCurves[cfg_fade_in_curve], szFadeSCurve ) == 0 )
		{
			wsprintfA ( temp, "%i.%02i", val/100, val%100 );
			uSetDlgItemText( wnd, IDC_FADE_IN_CAPTION, "Steepness:" );
		}
		uSetDlgItemText ( wnd, IDC_FADE_IN_DISPLAY, temp );

		val = cfg_fade_in_param;
		if ( strcmp( pszFadeInCurves[cfg_fade_in_curve], szFadeInverse ) == 0 )
		{
			ShowWindow( GetDlgItem( wnd, IDC_FADE_IN_PARAM), FALSE);
			ShowWindow( GetDlgItem( wnd, IDC_FADE_IN_DISPLAY), FALSE);
			ShowWindow( GetDlgItem( wnd, IDC_FADE_IN_CAPTION), FALSE);
		}
		else if ( strcmp( pszFadeInCurves[cfg_fade_in_curve], szFadeSCurve ) == 0 )
		{
			ShowWindow( GetDlgItem( wnd, IDC_FADE_IN_PARAM), TRUE);
			ShowWindow( GetDlgItem( wnd, IDC_FADE_IN_DISPLAY), TRUE);
			ShowWindow( GetDlgItem( wnd, IDC_FADE_IN_CAPTION), TRUE);
		}
		else if ( strcmp( pszFadeInCurves[cfg_fade_in_curve], szFadeForceDb ) == 0 )
		{
			ShowWindow( GetDlgItem( wnd, IDC_FADE_IN_PARAM), TRUE);
			ShowWindow( GetDlgItem( wnd, IDC_FADE_IN_DISPLAY), TRUE);
			ShowWindow( GetDlgItem( wnd, IDC_FADE_IN_CAPTION), TRUE);
		}
		else if ( strcmp( pszFadeInCurves[cfg_fade_in_curve], szFadeLinear ) == 0 )
		{
			ShowWindow( GetDlgItem( wnd, IDC_FADE_IN_PARAM), FALSE);
			ShowWindow( GetDlgItem( wnd, IDC_FADE_IN_DISPLAY), FALSE);
			ShowWindow( GetDlgItem( wnd, IDC_FADE_IN_CAPTION), FALSE);
		}

		val = cfg_fade_out_param;
		if ( strcmp( pszFadeInCurves[cfg_fade_out_curve], szFadeInverse ) == 0 )
		{
			ShowWindow( GetDlgItem( wnd, IDC_FADE_OUT_PARAM), FALSE);
			ShowWindow( GetDlgItem( wnd, IDC_FADE_OUT_DISPLAY), FALSE);
			ShowWindow( GetDlgItem( wnd, IDC_FADE_OUT_CAPTION), FALSE);
		}
		else if ( strcmp( pszFadeInCurves[cfg_fade_out_curve], szFadeSCurve ) == 0 )
		{
			ShowWindow( GetDlgItem( wnd, IDC_FADE_OUT_PARAM), TRUE);
			ShowWindow( GetDlgItem( wnd, IDC_FADE_OUT_DISPLAY), TRUE);
			ShowWindow( GetDlgItem( wnd, IDC_FADE_OUT_CAPTION), TRUE);
		}
		else if ( strcmp( pszFadeInCurves[cfg_fade_out_curve], szFadeForceDb ) == 0 )
		{
			ShowWindow( GetDlgItem( wnd, IDC_FADE_OUT_PARAM), TRUE);
			ShowWindow( GetDlgItem( wnd, IDC_FADE_OUT_DISPLAY), TRUE);
			ShowWindow( GetDlgItem( wnd, IDC_FADE_OUT_CAPTION), TRUE);
		}
		else if ( strcmp( pszFadeInCurves[cfg_fade_out_curve], szFadeLinear ) == 0 )
		{
			ShowWindow( GetDlgItem( wnd, IDC_FADE_OUT_PARAM), FALSE);
			ShowWindow( GetDlgItem( wnd, IDC_FADE_OUT_DISPLAY), FALSE);
			ShowWindow( GetDlgItem( wnd, IDC_FADE_OUT_CAPTION), FALSE);
		}

		CheckDlgButton ( wnd, IDC_ENABLE, cfg_enable);
		CheckDlgButton ( wnd, IDC_OVERRIDE, cfg_tag_override);
		CheckDlgButton ( wnd, IDC_SEAMLESS, cfg_seamless);
        CheckDlgButton ( wnd, IDC_UNBUFFER, cfg_unbuffer);
        CheckDlgButton ( wnd, IDC_CDBLOCKSIZE, cfg_cdblocksize);
		CheckDlgButton ( wnd, IDC_FADE_OUT_ON_SUDDEN_END, cfg_fade_out_on_sudden_end );

		switch ( cfg_mode )
		{
		case mode_overlap:
			ShowWindow( GetDlgItem( wnd, IDC_THRESHOLD ), TRUE );
			ShowWindow( GetDlgItem( wnd, IDC_THRESHOLD_CAPTION ), TRUE );
			ShowWindow( GetDlgItem( wnd, IDC_THRESHOLD_DISPLAY ), TRUE );
			ShowWindow( GetDlgItem( wnd, IDC_CROSSFADE_LENGTH ), FALSE );
			ShowWindow( GetDlgItem( wnd, IDC_CROSSFADE_CAPTION ), FALSE );
			ShowWindow( GetDlgItem( wnd, IDC_CROSSFADE_LENGTH_DISPLAY ), FALSE );
			ShowWindow( GetDlgItem( wnd, IDC_FADE_OUT_ON_SUDDEN_END ), TRUE );
			break;
		case mode_crossfade:
			ShowWindow( GetDlgItem( wnd, IDC_THRESHOLD ), FALSE );
			ShowWindow( GetDlgItem( wnd, IDC_THRESHOLD_CAPTION ), FALSE );
			ShowWindow( GetDlgItem( wnd, IDC_THRESHOLD_DISPLAY ), FALSE );
			ShowWindow( GetDlgItem( wnd, IDC_CROSSFADE_LENGTH ), TRUE);
			ShowWindow( GetDlgItem( wnd, IDC_CROSSFADE_CAPTION ), TRUE );
			ShowWindow( GetDlgItem( wnd, IDC_CROSSFADE_LENGTH_DISPLAY ), TRUE );
			ShowWindow( GetDlgItem( wnd, IDC_FADE_OUT_ON_SUDDEN_END ), FALSE );
			break;
		}

		// disable fade-out configuration if check seamless transistion is active
		if ( cfg_seamless )
		{
			EnableWindow( GetDlgItem( wnd, IDC_FADE_OUT_ON_SUDDEN_END ), FALSE );
			EnableWindow( GetDlgItem( wnd, IDC_FADE_OUT_CURVE ), FALSE );
			EnableWindow( GetDlgItem( wnd, IDC_FADE_OUT_LENGTH ), FALSE );
			EnableWindow( GetDlgItem( wnd, IDC_FADE_OUT_PARAM ), FALSE );
			EnableWindow( GetDlgItem( wnd, IDC_FADE_OUT_LENGTH_DISPLAY ), FALSE );
			EnableWindow( GetDlgItem( wnd, IDC_FADE_OUT_DISPLAY ), FALSE );
		}
		else 
		{
			EnableWindow( GetDlgItem( wnd, IDC_FADE_OUT_ON_SUDDEN_END ), TRUE );
			EnableWindow( GetDlgItem( wnd, IDC_FADE_OUT_CURVE ), TRUE );
			EnableWindow( GetDlgItem( wnd, IDC_FADE_OUT_LENGTH ), TRUE );
			EnableWindow( GetDlgItem( wnd, IDC_FADE_OUT_PARAM ), TRUE );
			EnableWindow( GetDlgItem( wnd, IDC_FADE_OUT_LENGTH_DISPLAY ), TRUE );
			EnableWindow( GetDlgItem( wnd, IDC_FADE_OUT_DISPLAY ), TRUE );
		}
//        CheckDlgButton ( wnd, IDC_SMOOTH, cfg_smoothing );
        //CheckDlgButton ( wnd, IDC_ABSOLUTE, cfg_absolute );
    }

    static BOOL CALLBACK ConfigProc ( HWND wnd, UINT msg, WPARAM wp, LPARAM lp )
    {
        switch ( msg ) {
        case WM_INITDIALOG:
            uSendDlgItemMessage ( wnd, IDC_BUFFER, TBM_SETRANGE, 0, MAKELONG(10, nMaxBufferSizeMs) );
            uSendDlgItemMessage ( wnd, IDC_BUFFER, TBM_SETPOS, 1, cfg_buffersize );

			uSendDlgItemMessage ( wnd, IDC_THRESHOLD, TBM_SETRANGE, 0, MAKELONG(0, 500) );
			uSendDlgItemMessage ( wnd, IDC_THRESHOLD, TBM_SETPOS, 1, cfg_threshold );

			uSendDlgItemMessage ( wnd, IDC_CROSSFADE_LENGTH, TBM_SETRANGE, 0, MAKELONG(10, nMaxBufferSizeMs ) );
			uSendDlgItemMessage ( wnd, IDC_CROSSFADE_LENGTH, TBM_SETPOS, 1, cfg_crossfade_length );

			uSendDlgItemMessage ( wnd, IDC_FADE_OUT_LENGTH, TBM_SETRANGE, 0, MAKELONG(10, nMaxBufferSizeMs ) );
			uSendDlgItemMessage ( wnd, IDC_FADE_OUT_LENGTH, TBM_SETPOS, 1, cfg_fade_out_length );

			uSendDlgItemMessage ( wnd, IDC_FADE_IN_LENGTH, TBM_SETRANGE, 0, MAKELONG(10, nMaxBufferSizeMs ) );
			uSendDlgItemMessage ( wnd, IDC_FADE_IN_LENGTH, TBM_SETPOS, 1, cfg_fade_in_length );

			uSendDlgItemMessage ( wnd, IDC_FADE_OUT_PARAM, TBM_SETRANGE, 0, MAKELONG(0, 100) );
			uSendDlgItemMessage ( wnd, IDC_FADE_OUT_PARAM, TBM_SETPOS, 1, cfg_fade_out_param);

			uSendDlgItemMessage ( wnd, IDC_FADE_IN_PARAM, TBM_SETRANGE, 0, MAKELONG(0, 100) );
			uSendDlgItemMessage ( wnd, IDC_FADE_IN_PARAM, TBM_SETPOS, 1, cfg_fade_in_param);

			int nCnt;
			nCnt = 0;
			while ( pszContiModes[nCnt] != "" )
			{
				uSendDlgItemMessageText( wnd, IDC_MODE, CB_ADDSTRING, 0, pszContiModes[nCnt] );
				nCnt++;
			}
			uSendDlgItemMessage( wnd, IDC_MODE, CB_SETCURSEL, cfg_mode, 0 );

			nCnt = 0;
			while ( pszFadeOutCurves[nCnt] != "" )
			{
				uSendDlgItemMessageText( wnd, IDC_FADE_OUT_CURVE, CB_ADDSTRING, 0, pszFadeOutCurves[nCnt] );
				nCnt++;
			}
			uSendDlgItemMessage( wnd, IDC_FADE_OUT_CURVE, CB_SETCURSEL, cfg_fade_out_curve, 0 );

			nCnt = 0;
			while ( pszFadeInCurves[nCnt] != "" )
			{
				uSendDlgItemMessageText( wnd, IDC_FADE_IN_CURVE, CB_ADDSTRING, 0, pszFadeInCurves[nCnt] );
				nCnt++;
			}
			uSendDlgItemMessage( wnd, IDC_FADE_IN_CURVE, CB_SETCURSEL, cfg_fade_in_curve, 0 );

			do_update_display ( wnd );
            return TRUE;

        case WM_COMMAND:
            switch ( LOWORD(wp) ) 
			{
			case IDC_ENABLE:
				cfg_enable = !cfg_enable;
				CheckDlgButton ( wnd, IDC_ENABLE, cfg_enable);
				return FALSE;
			case IDC_MODE:
				if ( HIWORD( wp ) == CBN_SELCHANGE )
				{
					cfg_mode = uSendDlgItemMessage( wnd, IDC_MODE, CB_GETCURSEL, 0, 0 );
					do_update_display( wnd );
				}
				break;
			case IDC_OVERRIDE:
				cfg_tag_override = !cfg_tag_override;
				CheckDlgButton ( wnd, IDC_OVERRIDE, cfg_tag_override );
				return FALSE;
			case IDC_UNBUFFER:
				cfg_unbuffer = !cfg_unbuffer;
				CheckDlgButton ( wnd, IDC_UNBUFFER, cfg_unbuffer);
				return FALSE;
			case IDC_SEAMLESS:
				cfg_seamless = !cfg_seamless;
				CheckDlgButton ( wnd, IDC_SEAMLESS, cfg_seamless);
				do_update_display( wnd );
				return FALSE;
			case IDC_FADE_OUT_ON_SUDDEN_END:
				cfg_fade_out_on_sudden_end = !cfg_fade_out_on_sudden_end;
				CheckDlgButton ( wnd, IDC_FADE_OUT_ON_SUDDEN_END, cfg_fade_out_on_sudden_end );
				return FALSE;
            case IDC_CDBLOCKSIZE:
                cfg_cdblocksize = !cfg_cdblocksize;
                CheckDlgButton ( wnd, IDC_CDBLOCKSIZE, cfg_cdblocksize);
                return FALSE;
			case IDC_FADE_OUT_CURVE:
				if ( HIWORD( wp ) == CBN_SELCHANGE )
				{
					cfg_fade_out_curve = uSendDlgItemMessage( wnd, IDC_FADE_OUT_CURVE, CB_GETCURSEL, 0, 0 );
					do_update_display( wnd );
				}
				break;
			case IDC_FADE_IN_CURVE:
				if ( HIWORD( wp ) == CBN_SELCHANGE )
				{
					cfg_fade_in_curve = uSendDlgItemMessage( wnd, IDC_FADE_IN_CURVE, CB_GETCURSEL, 0, 0 );
					do_update_display( wnd );
				}
				break;
          }
            break;

        case WM_HSCROLL:
            cfg_buffersize = uSendDlgItemMessage ( wnd, IDC_BUFFER, TBM_GETPOS, 0, 0 );
            cfg_threshold = uSendDlgItemMessage ( wnd, IDC_THRESHOLD, TBM_GETPOS, 0, 0 );
			cfg_crossfade_length = uSendDlgItemMessage( wnd, IDC_CROSSFADE_LENGTH, TBM_GETPOS, 0, 0 );
			cfg_fade_out_length = uSendDlgItemMessage( wnd, IDC_FADE_OUT_LENGTH, TBM_GETPOS, 0, 0 );
			cfg_fade_in_length = uSendDlgItemMessage( wnd, IDC_FADE_IN_LENGTH, TBM_GETPOS, 0, 0 );
			cfg_fade_out_param = uSendDlgItemMessage ( wnd, IDC_FADE_OUT_PARAM, TBM_GETPOS, 0, 0 );
			cfg_fade_in_param = uSendDlgItemMessage ( wnd, IDC_FADE_IN_PARAM, TBM_GETPOS, 0, 0 );

            do_update_display ( wnd );
            break;
        }

        return FALSE;
    }

public:
	config_nogaps()
	{
		m_bConfigFileLoaded = false;

		::CoInitialize( NULL );
	}

	~config_nogaps()
	{
		if ( m_pConfigFile != NULL )
			m_pConfigFile.Release();
		
		::CoUninitialize( );
	}
    virtual HWND create ( HWND parent )
    {
		if ( !m_bConfigFileLoaded )
			load_config_file();

        return uCreateDialog ( IDD_CONFIG, parent, ConfigProc, NULL );
    }

	virtual GUID get_guid() { return config_nogaps_guid; }

    virtual const char *get_name() { return FNG_NAME; }

    virtual GUID get_parent_guid() { return guid_playback; }

	virtual bool reset_query() { return false; }

	virtual void reset() {}

	virtual bool get_help_url(pfc::string_base & p_out) { return false; }

	bool load_config_file( void )
	{
		m_pConfigFile.CreateInstance( __uuidof( MSXML2::DOMDocument ) );
		m_pConfigFile->async = VARIANT_FALSE;
		const char* pProfilePath = core_api::get_profile_path();
		string_simple strProfilePath;
		if ( pProfilePath )
			strProfilePath = pProfilePath;
		if ( m_pConfigFile->load( strProfilePath + "continuator_config.xml" ) != VARIANT_TRUE )
			return false;

		m_bConfigFileLoaded = true;
		return true;
	}

	bool save_config_file( void )
	{
		return true;
	}

protected:
	MSXML2::IXMLDOMDocumentPtr m_pConfigFile;
	bool m_bConfigFileLoaded;
};

class play_callback_dsp_nogaps : public play_callback_static
{
    virtual unsigned get_flags()
	{
		return flag_on_playback_seek;
	}

	virtual void on_playback_seek(double p_time) { seek = true; }
	
	virtual void on_playback_starting(play_control::t_track_command p_command,bool p_paused) {}
	virtual void on_playback_new_track(metadb_handle_ptr p_track) {}
	virtual void on_playback_stop(play_control::t_stop_reason p_reason) {}
	virtual void on_playback_pause(bool p_state) {}
	virtual void on_playback_edited(metadb_handle_ptr p_track)  {}
	virtual void on_playback_dynamic_info(const file_info & p_info) {}
	virtual void on_playback_dynamic_info_track(const file_info & p_info) {}
	virtual void on_playback_time(double p_time) {}
	virtual void on_volume_change(float p_new_val) {}
};

// {D7FE08CD-5B0B-4154-8F2A-9AA63619A4BB}
static const GUID menu_group_continuator_guid = 
{ 0xd7fe08cd, 0x5b0b, 0x4154, { 0x8f, 0x2a, 0x9a, 0xa6, 0x36, 0x19, 0xa4, 0xbb } };

class menu_group_continuator : public mainmenu_group_popup
{
	virtual GUID get_guid() { return menu_group_continuator_guid; }
	virtual GUID get_parent() { return mainmenu_groups::playback_etc; }
	virtual t_uint32 get_sort_priority() { return mainmenu_commands::sort_priority_dontcare; }
	virtual void get_display_string(pfc::string_base & p_out) { p_out = FNG_NAME; }
};

class menu_commands_continuator : public mainmenu_commands
{
	virtual t_uint32 get_command_count() { return 5; }

	virtual GUID get_command(t_uint32 p_index)
	{
		// {9B70B0E6-7625-4298-BAFD-4532EAAF70BD}
		static const GUID toggle_state = 
		{ 0x9b70b0e6, 0x7625, 0x4298, { 0xba, 0xfd, 0x45, 0x32, 0xea, 0xaf, 0x70, 0xbd } };
		// {D8999013-F5A6-45dd-A599-73C4A8CB17F4}
		static const GUID enable = 
		{ 0xd8999013, 0xf5a6, 0x45dd, { 0xa5, 0x99, 0x73, 0xc4, 0xa8, 0xcb, 0x17, 0xf4 } };
		// {EB6F6139-A8DB-4e96-B1C7-27A6770F458B}
		static const GUID disable = 
		{ 0xeb6f6139, 0xa8db, 0x4e96, { 0xb1, 0xc7, 0x27, 0xa6, 0x77, 0xf, 0x45, 0x8b } };
		// {3C603727-2039-4b2f-9483-5B5A79638935}
		static const GUID tag_override = 
		{ 0x3c603727, 0x2039, 0x4b2f, { 0x94, 0x83, 0x5b, 0x5a, 0x79, 0x63, 0x89, 0x35 } };
		// {C70E12AA-F113-4786-8413-AA3CD240800C}
		static const GUID settings = 
		{ 0xc70e12aa, 0xf113, 0x4786, { 0x84, 0x13, 0xaa, 0x3c, 0xd2, 0x40, 0x80, 0xc } };

		switch (p_index)
		{
		case 0: return toggle_state;
		case 1: return enable;
		case 2: return disable;
		case 3: return tag_override;
		case 4: return settings;
		}
		return GUID_NULL;
	}

	virtual void get_name(t_uint32 p_index,pfc::string_base & p_out)
	{
		switch (p_index)
		{
		case 0:
			p_out = "Toggle state";
			break;
		case 1:
			p_out = "Enable";
			break;
		case 2:
			p_out = "Disable";
			break;
		case 3:
			p_out = "Override settings by CONTINUATOR-tag";
			break;
		case 4:
			p_out = "Settings...";
			break;
		}
	}

	virtual bool get_description(t_uint32 p_index,pfc::string_base & p_out) {
		if (p_index == 0) {
			p_out = "enables/disables track overlapping";
			return true;
		}
		return false;
	}

	virtual GUID get_parent() { return menu_group_continuator_guid; }

	virtual bool get_display(t_uint32 p_index,pfc::string_base & p_text,t_uint32 & p_flags)
	{
		p_flags = 0;
		switch (p_index)
		{
		case 0:
			if (cfg_enable)
				p_flags |= flag_checked;
			break;
		case 1:
			if (cfg_enable)
				p_flags |= flag_checked;
			break;
		case 2:
			if (!cfg_enable)
				p_flags |= flag_checked;
			break;
		case 3:
			if (cfg_tag_override)
				p_flags |= flag_checked;
			break;
		}
		get_name(p_index,p_text);
		return true;
	}

	virtual void execute(t_uint32 p_index,service_ptr_t<service_base> p_callback) {
		if ( core_api::assert_main_thread() )
			switch (p_index)
			{
			case 0:
				cfg_enable = !cfg_enable;
				break;
			case 1:
				cfg_enable = true;
				break;
			case 2:
				cfg_enable = false;
				break;
			case 3:
				cfg_tag_override = !cfg_tag_override;
				break;
			case 4:
				static_api_ptr_t<ui_control>()->show_preferences(config_nogaps_guid);
				break;
			}
	}
};

static play_callback_static_factory_t<play_callback_dsp_nogaps> foo_play_callback_dsp_nogaps;
static dsp_factory_nopreset_t<dsp_nogaps> foo_dsp_nogaps;
static preferences_page_factory_t<config_nogaps> foo_dsp_nogaps_cfg;
static service_factory_single_t<menu_group_continuator> foo_menu_group;
static mainmenu_commands_factory_t<menu_commands_continuator> foo_menu_commands;