/*
** music_dumb.cpp
** Alternative module player, using a modified DUMB for decoding.
** Based on the Foobar2000 component foo_dumb, version 0.9.8.4.
**
**---------------------------------------------------------------------------
** Copyright 2008 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/

// HEADER FILES ------------------------------------------------------------

#include <math.h>
#include <mutex>
#include <string>
#include <stdint.h>
#include "streamsource.h"


#undef CDECL	// w32api's windef.h defines this
#include "../dumb/include/dumb.h"
#include "../dumb/include/internal/it.h"
#include "zmusic/m_swap.h"
#include "zmusic/mididefs.h"
#include "zmusic/midiconfig.h"
#include "fileio.h"

// MACROS ------------------------------------------------------------------

// TYPES -------------------------------------------------------------------

DumbConfig dumbConfig;

class DumbSong : public StreamSource
{
public:
	DumbSong(DUH *myduh, int samplerate);
	~DumbSong();
	//bool SetPosition(int ms);
	bool SetSubsong(int subsong) override;
	bool Start() override;
	SoundStreamInfoEx GetFormatEx() override;
	void ChangeSettingNum(const char* setting, double val) override;
	std::string GetStats() override;

	std::string Codec;
	std::string TrackerVersion;
	std::string FormatVersion;
	int NumChannels;
	int NumPatterns;
	int NumOrders;
	float MasterVolume;

protected:
	int srate, interp, volramp;
	int start_order;
	double delta;
	double length;
	bool eof;
	bool started = false;
	size_t written;
	DUH *duh;
	DUH_SIGRENDERER *sr;

	bool open2(long pos);
	long render(double volume, double delta, long samples, sample_t **buffer);
	int decode_run(void *buffer, unsigned int size);
	bool GetData(void *buffer, size_t len) override;

};

#pragma pack(1)

#if defined(__GNUC__)
#define FORCE_PACKED __attribute__((__packed__))
#else
#define FORCE_PACKED
#endif

typedef struct tagITFILEHEADER
{
	uint32_t id;			// 0x4D504D49
	char songname[26];
	uint16_t reserved1;		// 0x1004
	uint16_t ordnum;
	uint16_t insnum;
	uint16_t smpnum;
	uint16_t patnum;
	uint16_t cwtv;
	uint16_t cmwt;
	uint16_t flags;
	uint16_t special;
	uint8_t globalvol;
	uint8_t mv;
	uint8_t speed;
	uint8_t tempo;
	uint8_t sep;
	uint8_t zero;
	uint16_t msglength;
	uint32_t msgoffset;
	uint32_t reserved2;
	uint8_t chnpan[64];
	uint8_t chnvol[64];
} FORCE_PACKED ITFILEHEADER, *PITFILEHEADER;

typedef struct MODMIDICFG
{
	char szMidiGlb[9*32];      // changed from CHAR
	char szMidiSFXExt[16*32];  // changed from CHAR
	char szMidiZXXExt[128*32]; // changed from CHAR
} FORCE_PACKED MODMIDICFG, *LPMODMIDICFG;

#pragma pack()

// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------

// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------

// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------

// EXTERNAL DATA DECLARATIONS ----------------------------------------------

// PUBLIC DATA DEFINITIONS -------------------------------------------------

// PRIVATE DATA DEFINITIONS ------------------------------------------------

// CODE --------------------------------------------------------------------

//==========================================================================
//
// ReadDUH
//
// Reads metadata from a generic module.
//
//==========================================================================

static void ReadDUH(DUH * duh, DumbSong *info, bool meta, bool dos)
{
	if (!duh) return;

	DUMB_IT_SIGDATA * itsd = duh_get_it_sigdata(duh);

	if (!itsd) return;

	info->NumChannels = itsd->n_pchannels;
	info->NumPatterns = itsd->n_patterns;
	info->NumOrders = itsd->n_orders;

	if (meta)
	{
		if (itsd->name[0])
		{
			//if (dos) info.meta_add(field_title, string_utf8_from_oem((char*)&itsd->name, sizeof(itsd->name)));
			//else info.meta_add(field_title, pfc::stringcvt::string_utf8_from_ansi((char *)&itsd->name, sizeof(itsd->name)));
		}
		if (itsd->song_message && *itsd->song_message)
		{
			//if (dos) info.meta_add(field_comment, string_utf8_from_oem_multiline((char*)itsd->song_message));
			//else info.meta_add(field_comment, pfc::stringcvt::string_utf8_from_ansi((char *)itsd->song_message));
		}
	}

	// std::string does not like nullptr assignments. Was this really necessary? :(
	auto a = duh_get_tag(duh, "FORMAT");
	if (a) info->Codec = a;
	a = duh_get_tag(duh, "TRACKERVERSION");
	if (a) info->TrackerVersion = a;
	a = duh_get_tag(duh, "FORMATVERSION");
	if (a) info->FormatVersion = a;


#if 0
	std::string name;

	if (itsd->n_samples)
	{
		int i, n;
		//info.info_set_int(field_samples, itsd->n_samples);

		if (meta && itsd->sample)
		{
			for (i = 0, n = itsd->n_samples; i < n; i++)
			{
				if (itsd->sample[i].name[0])
				{
					name = "smpl";
					if (i < 10) name += '0';
					name += '0' + i;
					//if (dos) info.meta_add(name, string_utf8_from_oem((char*)&itsd->sample[i].name, sizeof(itsd->sample[i].name)));
					//else info.meta_add(name, pfc::stringcvt::string_utf8_from_ansi((char *)&itsd->sample[i].name, sizeof(itsd->sample[i].name)));
				}
			}
		}
	}

	if (itsd->n_instruments)
	{
		//info.info_set_int(field_instruments, itsd->n_instruments);

		if (meta && itsd->instrument)
		{
			for (i = 0, n = itsd->n_instruments; i < n; i++)
			{
				if (itsd->instrument[i].name[0])
				{
					name = "inst";
					if (i < 10) name += '0';
					name += '0' + i;
					//if (dos) info.meta_add(name, string_utf8_from_oem((char*)&itsd->instrument[i].name, sizeof(itsd->instrument[i].name)));
					//else info.meta_add(name, pfc::stringcvt::string_utf8_from_ansi((char *)&itsd->instrument[i].name, sizeof(itsd->instrument[i].name)));
				}
			}
		}
	}
#endif
}

//==========================================================================
//
// ReadIT
//
// Reads metadata from an IT file.
//
//==========================================================================

static bool ReadIT(const uint8_t * ptr, unsigned size, DumbSong *info, bool meta)
{
	PITFILEHEADER pifh = (PITFILEHEADER) ptr;
	if ((!ptr) || (size < 0x100)) return false;
	if ((LittleLong(pifh->id) != 0x4D504D49) ||
		(LittleShort(pifh->insnum) >= 256) ||
		(!pifh->smpnum) || (LittleShort(pifh->smpnum) > 4000) || // XXX
		(!pifh->ordnum)) return false;
	if (sizeof(ITFILEHEADER) + LittleShort(pifh->ordnum) +
		LittleShort(pifh->insnum)*4 +
		LittleShort(pifh->smpnum)*4 +
		LittleShort(pifh->patnum)*4 > size) return false;

	char ver[40];

	snprintf(ver, 40, "IT v%u.%02x", LittleShort(pifh->cmwt) >> 8, LittleShort(pifh->cmwt) & 255);
	info->Codec = ver;

	snprintf(ver, 40, "%u.%02x", LittleShort(pifh->cwtv) >> 8, LittleShort(pifh->cwtv) & 255);
	info->TrackerVersion = ver;

	//if ( pifh->smpnum ) info.info_set_int( field_samples, LittleShort(pifh->smpnum) );
	//if ( pifh->insnum ) info.info_set_int( field_instruments, LittleShort(pifh->insnum) );

	//if ( meta && pifh->songname[0] ) info.meta_add( field_title, string_utf8_from_it( (char*)&pifh->songname, 26 ) );

	unsigned n, l, m_nChannels = 0;

	// bah, some file (jm-romance.it) with message length rounded up to a multiple of 256 (384 instead of 300)
	unsigned msgoffset = LittleLong(pifh->msgoffset);
	unsigned msgend = msgoffset + LittleShort(pifh->msglength);

	uint32_t * offset;
//	std::string name;
	
	if (meta)
	{
		offset = (uint32_t *)(ptr + 0xC0 + LittleShort(pifh->ordnum) + LittleShort(pifh->insnum) * 4);

		for (n = 0, l = LittleShort(pifh->smpnum); n < l; n++, offset++)
		{
			uint32_t offset_n = LittleLong( *offset );
			if ( offset_n >= msgoffset && offset_n < msgend ) msgend = offset_n;
			if ((!offset_n) || (offset_n + 0x14 + 26 + 2 >= size)) continue;
			// XXX
			if (ptr[offset_n] == 0 && ptr[offset_n + 1] == 0 &&
				ptr[offset_n + 2] == 'I' && ptr[offset_n + 3] == 'M' &&
				ptr[offset_n + 4] == 'P' && ptr[offset_n + 5] == 'S')
			{
				offset_n += 2;
			}
#if 0
			if (*(ptr + offset_n + 0x14))
			{
				name = "smpl";
				if (n < 10) name << '0';
				name << ('0' + n);
				//info.meta_add(name, string_utf8_from_it((const char *) ptr + offset_n + 0x14, 26));
			}
#endif
		}

		offset = (uint32_t *)(ptr + 0xC0 + LittleShort(pifh->ordnum));

		for (n = 0, l = LittleShort(pifh->insnum); n < l; n++, offset++)
		{
			uint32_t offset_n = LittleLong( *offset );
			if ( offset_n >= msgoffset && offset_n < msgend ) msgend = offset_n;
			if ((!offset_n) || (offset_n + 0x20 + 26 >= size)) continue;
#if 0
			if (*(ptr + offset_n + 0x20))
			{
				name = "inst";
				if (n < 10) name << '0';
				name << ('0' + n);
				//info.meta_add(name, string_utf8_from_it((const char *) ptr + offset_n + 0x20, 26));
			}
#endif
		}
	}

	unsigned pos = 0xC0 + LittleShort(pifh->ordnum) + LittleShort(pifh->insnum) * 4 + LittleShort(pifh->smpnum) * 4 + LittleShort(pifh->patnum) * 4;

	if (pos < size)
	{
		uint16_t val16 = LittleShort( *(uint16_t *)(ptr + pos) );
		pos += 2;
		if (pos + val16 * 8 < size) pos += val16 * 8;
	}

	if (LittleShort(pifh->flags) & 0x80)
	{
		if (pos + sizeof(MODMIDICFG) < size)
		{
			pos += sizeof(MODMIDICFG);
		}
	}

	if ((pos + 8 < size) && (*(uint32_t *)(ptr + pos) == MAKE_ID('P','N','A','M')))
	{
		unsigned len = LittleLong(*(uint32_t *)(ptr + pos + 4));
		pos += 8;
		if ((pos + len <= size) && (len <= 240 * 32) && (len >= 32))
		{
			if (meta)
			{
				l = len / 32;
				for (n = 0; n < l; n++)
				{
#if 0
					if (*(ptr + pos + n * 32))
					{
						name = "patt";
						if (n < 10) name << '0';
						name << ('0' + n);
						//info.meta_add(name, string_utf8_from_it((const char *) ptr + pos + n * 32, 32));
					}
#endif
				}
			}
			pos += len;
		}
	}

	if ((pos + 8 < size) && (*(uint32_t *)(ptr + pos) == MAKE_ID('C','N','A','M')))
	{
		unsigned len = LittleLong(*(uint32_t *)(ptr + pos + 4));
		pos += 8;
		if ((pos + len <= size) && (len <= 64 * 20) && (len >= 20))
		{
			l = len / 20;
			m_nChannels = l;
			if (meta)
			{
				for (n = 0; n < l; n++)
				{
#if 0
					if (*(ptr + pos + n * 20))
					{
						name = "chan";
						if (n < 10) name << '0';
						name << ('0' + n);
						//info.meta_add(name, string_utf8_from_it((const char *) ptr + pos + n * 20, 20));
					}
#endif
				}
			}
			pos += len;
		}
	}

	offset = (uint32_t *)(ptr + 0xC0 + LittleShort(pifh->ordnum) + LittleShort(pifh->insnum) * 4 + LittleShort(pifh->smpnum) * 4);

	uint8_t chnmask[64];

	for (n = 0, l = LittleShort(pifh->patnum); n < l; n++)
	{
		memset(chnmask, 0, sizeof(chnmask));
		uint32_t offset_n = LittleLong( offset[n] );
		if ((!offset_n) || (offset_n + 4 >= size)) continue;
		unsigned len = LittleShort(*(uint16_t *)(ptr + offset_n));
		unsigned rows = LittleShort(*(uint16_t *)(ptr + offset_n + 2));
		if ((rows < 4) || (rows > 256)) continue;
		if (offset_n + 8 + len > size) continue;
		unsigned i = 0;
		const uint8_t * p = ptr + offset_n + 8;
		unsigned nrow = 0;
		while (nrow < rows)
		{
			if (i >= len) break;
			uint8_t b = p[i++];
			if (!b)
			{
				nrow++;
				continue;
			}
			unsigned ch = b & 0x7F;
			if (ch) ch = (ch - 1) & 0x3F;
			if (b & 0x80)
			{
				if (i >= len) break;
				chnmask[ch] = p[i++];
			}
			// Channel used
			if (chnmask[ch] & 0x0F)
			{
				if ((ch >= m_nChannels) && (ch < 64)) m_nChannels = ch+1;
			}
			// Note
			if (chnmask[ch] & 1) i++;
			// Instrument
			if (chnmask[ch] & 2) i++;
			// Volume
			if (chnmask[ch] & 4) i++;
			// Effect
			if (chnmask[ch] & 8) i += 2;
			if (i >= len) break;
		}
	}

	if ( meta && ( LittleShort(pifh->special) & 1 ) && ( msgend - msgoffset ) && ( msgend < size ) )
	{
		const char * str = (const char *) ptr + msgoffset;
		std::string msg(str);
		//info.meta_add( field_comment, string_utf8_from_it_multiline( msg ) );
	}

	info->NumChannels = m_nChannels;
	info->NumPatterns = LittleShort(pifh->patnum);
	info->NumOrders = LittleShort(pifh->ordnum);

	return true;
}

//==========================================================================
//
// DUMB memory file system
//
//==========================================================================

typedef struct tdumbfile_mem_status
{
	const uint8_t *ptr;
	unsigned int offset, size;
} dumbfile_mem_status;

static int DUMBCALLBACK dumbfile_mem_skip(void *f, long n)
{
	dumbfile_mem_status * s = (dumbfile_mem_status *) f;
	s->offset += n;
	if (s->offset > s->size)
	{
		s->offset = s->size;
		return 1;
	}
	return 0;
}

static int DUMBCALLBACK dumbfile_mem_getc(void *f)
{
	dumbfile_mem_status * s = (dumbfile_mem_status *) f;
	if (s->offset < s->size)
	{
		return *(s->ptr + s->offset++);
	}
	return -1;
}

static int32 DUMBCALLBACK dumbfile_mem_getnc(char *ptr, int32 n, void *f)
{
	dumbfile_mem_status * s = (dumbfile_mem_status *) f;
	long max = s->size - s->offset;
	if (max > n) max = n;
	if (max)
	{
		memcpy(ptr, s->ptr + s->offset, max);
		s->offset += max;
	}
	return max;
}

static int DUMBCALLBACK dumbfile_mem_seek(void *f, long n)
{
	dumbfile_mem_status * s = (dumbfile_mem_status *) f;
	s->offset = n;
	if (s->offset > s->size)
	{
		s->offset = s->size;
		return 1;
	}
	return 0;
}

static long DUMBCALLBACK dumbfile_mem_get_size(void *f)
{
	dumbfile_mem_status * s = (dumbfile_mem_status *) f;
	return s->size;
}

static const DUMBFILE_SYSTEM mem_dfs = {
	NULL, // open
	&dumbfile_mem_skip,
	&dumbfile_mem_getc,
	&dumbfile_mem_getnc,
	NULL, // close
	&dumbfile_mem_seek,
	&dumbfile_mem_get_size
};

//==========================================================================
//
// dumb_read_allfile
//
//==========================================================================

DUMBFILE *dumb_read_allfile(dumbfile_mem_status *filestate, uint8_t *start, MusicIO::FileInterface *reader, int lenhave, int lenfull)
{
	filestate->size = lenfull;
	filestate->offset = 0;
	if (lenhave >= lenfull)
		filestate->ptr = (uint8_t *)start;
    else
    {
        uint8_t *mem = new uint8_t[lenfull];
        memcpy(mem, start, lenhave);
        if (reader->read(mem + lenhave, lenfull - lenhave) != (lenfull - lenhave))
        {
            delete[] mem;
            return NULL;
        }
        filestate->ptr = mem;
    }
    return dumbfile_open_ex(filestate, &mem_dfs);
}

//==========================================================================
//
// MOD_SetAutoChip
//
// Disables interpolation for short samples that meet criteria set by
// the cvars referenced at the top of this function.
//
//==========================================================================

static void MOD_SetAutoChip(DUH *duh)
{
	int size_force = dumbConfig.mod_autochip_size_force;
	int size_scan = dumbConfig.mod_autochip_size_scan;
	int scan_threshold_8 = ((dumbConfig.mod_autochip_scan_threshold * 0x100) + 50) / 100;
	int scan_threshold_16 = ((dumbConfig.mod_autochip_scan_threshold * 0x10000) + 50) / 100;
	DUMB_IT_SIGDATA * itsd = duh_get_it_sigdata(duh);

	if (itsd)
	{
		for (int i = 0, j = itsd->n_samples; i < j; i++)
		{
			IT_SAMPLE * sample = &itsd->sample[i];
			if (sample->flags & IT_SAMPLE_EXISTS)
			{
				int channels = sample->flags & IT_SAMPLE_STEREO ? 2 : 1;
				if (sample->length < size_force) sample->max_resampling_quality = 0;
				else if (sample->length < size_scan)
				{
					if ((sample->flags & (IT_SAMPLE_LOOP|IT_SAMPLE_PINGPONG_LOOP)) == IT_SAMPLE_LOOP)
					{
						int loop_start = sample->loop_start * channels;
						int loop_end = sample->loop_end * channels;
						int s1, s2;
						if (sample->flags & IT_SAMPLE_16BIT)
						{
							s1 = ((signed short *)sample->data)[loop_start];
							s2 = ((signed short *)sample->data)[loop_end - channels];
							if (abs(s1 - s2) > scan_threshold_16)
							{
								sample->max_resampling_quality = 0;
								continue;
							}
							if (channels == 2)
							{
								s1 = ((signed short *)sample->data)[loop_start + 1];
								s2 = ((signed short *)sample->data)[loop_end - 1];
								if (abs(s1 - s2) > scan_threshold_16)
								{
									sample->max_resampling_quality = 0;
									continue;
								}
							}
						}
						else
						{
							s1 = ((signed char *)sample->data)[loop_start];
							s2 = ((signed char *)sample->data)[loop_end - channels];
							if (abs(s1 - s2) > scan_threshold_8)
							{
								sample->max_resampling_quality = 0;
								continue;
							}
							if (channels == 2)
							{
								s1 = ((signed char *)sample->data)[loop_start + 1];
								s2 = ((signed char *)sample->data)[loop_end - 1];
								if (abs(s1 - s2) > scan_threshold_8)
								{
									sample->max_resampling_quality = 0;
									continue;
								}
							}
						}
					}
					if ((sample->flags & (IT_SAMPLE_SUS_LOOP|IT_SAMPLE_PINGPONG_SUS_LOOP)) == IT_SAMPLE_SUS_LOOP)
					{
						int sus_loop_start = sample->sus_loop_start * channels;
						int sus_loop_end = sample->sus_loop_end * channels;
						int s1, s2;
						if (sample->flags & IT_SAMPLE_16BIT)
						{
							s1 = ((signed short *)sample->data)[sus_loop_start];
							s2 = ((signed short *)sample->data)[sus_loop_end - channels];
							if (abs(s1 - s2) > scan_threshold_16)
							{
								sample->max_resampling_quality = 0;
								continue;
							}
							if (channels == 2)
							{
								s1 = ((signed short *)sample->data)[sus_loop_start + 1];
								s2 = ((signed short *)sample->data)[sus_loop_end - 1];
								if (abs(s1 - s2) > scan_threshold_16)
								{
									sample->max_resampling_quality = 0;
									continue;
								}
							}
						}
						else
						{
							s1 = ((signed char *)sample->data)[sus_loop_start];
							s2 = ((signed char *)sample->data)[sus_loop_end - channels];
							if (abs(s1 - s2) > scan_threshold_8)
							{
								sample->max_resampling_quality = 0;
								continue;
							}
							if (channels == 2)
							{
								s1 = ((signed char *)sample->data)[sus_loop_start + 1];
								s2 = ((signed char *)sample->data)[sus_loop_end - 1];
								if (abs(s1 - s2) > scan_threshold_8)
								{
									sample->max_resampling_quality = 0;
									continue;
								}
							}
						}
					}

					int k, l = sample->length * channels;
					if (sample->flags & IT_SAMPLE_LOOP) l = sample->loop_end * channels;
					if (sample->flags & IT_SAMPLE_16BIT)
					{
						for (k = channels; k < l; k += channels)
						{
							if (abs(((signed short *)sample->data)[k - channels] - ((signed short *)sample->data)[k]) > scan_threshold_16)
							{
								break;
							}
						}
						if (k < l)
						{
							sample->max_resampling_quality = 0;
							continue;
						}
						if (channels == 2)
						{
							for (k = 2 + 1; k < l; k += 2)
							{
								if (abs(((signed short *)sample->data)[k - 2] - ((signed short *)sample->data)[k]) > scan_threshold_16)
								{
									break;
								}
							}
						}
						if (k < l)
						{
							sample->max_resampling_quality = 0;
							continue;
						}
					}
					else
					{
						for (k = channels; k < l; k += channels)
						{
							if (abs(((signed char *)sample->data)[k - channels] - ((signed char *)sample->data)[k]) > scan_threshold_8)
							{
								break;
							}
						}
						if (k < l)
						{
							sample->max_resampling_quality = 0;
							continue;
						}
						if (channels == 2)
						{
							for (k = 2 + 1; k < l; k += 2)
							{
								if (abs(((signed char *)sample->data)[k - 2] - ((signed char *)sample->data)[k]) > scan_threshold_8)
								{
									break;
								}
							}
						}
						if (k < l)
						{
							sample->max_resampling_quality = 0;
							continue;
						}
					}
				}
			}
		}
	}
}

//==========================================================================
//
// MOD_OpenSong
//
//==========================================================================

StreamSource* MOD_OpenSong(MusicIO::FileInterface *reader, int samplerate)
{
	DUH *duh = 0;
	int headsize;
	union
	{
		uint8_t start[64];
		uint32_t dstart[64/4];
	};
	dumbfile_mem_status filestate;
	DUMBFILE *f = NULL;
	DumbSong *state = NULL;

	bool is_it = false;
	bool is_dos = true;

	auto fpos = reader->tell();
    int size = (int)reader->filelength();

	filestate.ptr = start;
	filestate.offset = 0;
	headsize = MIN((int)sizeof(start), size);

    if (headsize != reader->read(start, headsize))
    {
        return NULL;
    }

	if (size >= 4 && dstart[0] == MAKE_ID('I','M','P','M'))
	{
		is_it = true;
		if ((f = dumb_read_allfile(&filestate, start, reader, headsize, size)))
		{
			duh = dumb_read_it_quick(f);
		}
	}
	else if (size >= 17 && !memcmp(start, "Extended Module: ", 17))
	{
		if ((f = dumb_read_allfile(&filestate, start, reader, headsize, size)))
		{
			duh = dumb_read_xm_quick(f);
		}
	}
	else if (size >= 0x30 && dstart[11] == MAKE_ID('S','C','R','M'))
	{
		if ((f = dumb_read_allfile(&filestate, start, reader, headsize, size)))
		{
			duh = dumb_read_s3m_quick(f);
		}
	}
	else if (size >= 1168 &&
		/*start[28] == 0x1A &&*/ start[29] == 2 &&
		( !memcmp( &start[20], "!Scream!", 8 ) ||
		  !memcmp( &start[20], "BMOD2STM", 8 ) ||
		  !memcmp( &start[20], "WUZAMOD!", 8 ) ) )
	{
		if ((f = dumb_read_allfile(&filestate, start, reader, headsize, size)))
		{
			duh = dumb_read_stm_quick(f);
		}
	}
	else if (size >= 2 &&
		((start[0] == 0x69 && start[1] == 0x66) ||
		 (start[0] == 0x4A && start[1] == 0x4E)))
	{
		if ((f = dumb_read_allfile(&filestate, start, reader, headsize, size)))
		{
			duh = dumb_read_669_quick(f);
		}
	}
	else if (size >= 0x30 && dstart[11] == MAKE_ID('P','T','M','F'))
	{
		if ((f = dumb_read_allfile(&filestate, start, reader, headsize, size)))
		{
			duh = dumb_read_ptm_quick(f);
		}
	}
	else if (size >= 4 && dstart[0] == MAKE_ID('P','S','M',' '))
	{
		if ((f = dumb_read_allfile(&filestate, start, reader, headsize, size)))
		{
			duh = dumb_read_psm_quick(f, 0/*start_order*/);
			/*start_order = 0;*/
		}
	}
	else if (size >= 4 && dstart[0] == (uint32_t)MAKE_ID('P','S','M',254))
	{
		if ((f = dumb_read_allfile(&filestate, start, reader, headsize, size)))
		{
			duh = dumb_read_old_psm_quick(f);
		}
	}
	else if (size >= 3 && start[0] == 'M' && start[1] == 'T' && start[2] == 'M')
	{
		if ((f = dumb_read_allfile(&filestate, start, reader, headsize, size)))
		{
			duh = dumb_read_mtm_quick(f);
		}
	}
	else if (size >= 12 && dstart[0] == MAKE_ID('R','I','F','F') &&
		(dstart[2] == MAKE_ID('D','S','M','F') ||
		 dstart[2] == MAKE_ID('A','M',' ',' ') ||
		 dstart[2] == MAKE_ID('A','M','F','F')))
	{
		if ((f = dumb_read_allfile(&filestate, start, reader, headsize, size)))
		{
			duh = dumb_read_riff_quick(f);
		}
	}
	else if (size >= 32 &&
		!memcmp( start, "ASYLUM Music Format", 19 ) &&
		!memcmp( start + 19, " V1.0", 5 ) )
	{
		if ((f = dumb_read_allfile(&filestate, start, reader, headsize, size)))
		{
			duh = dumb_read_asy_quick(f);
		}
	}
	else if (size >= 8 &&
		dstart[0] == MAKE_ID('O','K','T','A') &&
		dstart[1] == MAKE_ID('S','O','N','G'))
	{
		if ((f = dumb_read_allfile(&filestate, start, reader, headsize, size)))
		{
			duh = dumb_read_okt_quick(f);
		}
	}
	else if (size >= 4 &&
		 (dstart[0] == MAKE_ID('A','M','F','\xa') ||
		 dstart[0] == MAKE_ID('A','M','F','\xb') ||
		 dstart[0] == MAKE_ID('A','M','F','\xc') ||
		 dstart[0] == MAKE_ID('A','M','F','\xd') ||
		 dstart[0] == MAKE_ID('A','M','F','\xe')))
	{
		if ((f = dumb_read_allfile(&filestate, start, reader, headsize, size)))
		{
			duh = dumb_read_amf_quick(f);
		}
	}

	if ( ! duh )
	{
		is_dos = false;
		if (filestate.ptr == (uint8_t *)start)
		{
			if (!(f = dumb_read_allfile(&filestate, start, reader, headsize, size)))
			{
                reader->seek(fpos, SEEK_SET);
				return NULL;
			}
		}
		else
		{
			filestate.offset = 0;
		}
		// No way to get the filename, so we can't check for a .mod extension, and
		// therefore, trying to load an old 15-instrument SoundTracker module is not
		// safe. We'll restrict MOD loading to 31-instrument modules with known
		// signatures and let the sound system worry about 15-instrument ones.
		// (Assuming it even supports them)
		duh = dumb_read_mod_quick(f, true);
	}

	if (f != NULL)
	{
		dumbfile_close(f);
	}
	if ( duh )
	{
		if (dumbConfig.mod_autochip)
		{
			MOD_SetAutoChip(duh);
		}
		state = new DumbSong(duh, samplerate);

		if (is_it) ReadIT(filestate.ptr, size, state, false);
		else ReadDUH(duh, state, false, is_dos);
	}
	else
	{
		// Reposition file pointer for other codecs to do their checks.
        reader->seek(fpos, SEEK_SET);
	}
	if (filestate.ptr != (uint8_t *)start)
	{
		delete[] const_cast<uint8_t *>(filestate.ptr);
	}
	return state;
}

//==========================================================================
//
// DumbSong :: read												static
//
//==========================================================================

bool DumbSong::GetData(void *buffer, size_t sizebytes)
{
	if (eof)
	{
		memset(buffer, 0, sizebytes);
		return false;
	}
	
	while (sizebytes > 0)
	{
		int written = decode_run(buffer, (unsigned)sizebytes / 8);
		if (written < 0)
		{
			return false;
		}
		if (written == 0)
		{
			memset(buffer, 0, sizebytes);
			return true;
		}
		else
		{
			// Convert to float
			for (int i = 0; i < written * 2; ++i)
			{
				((float *)buffer)[i] = (((int *)buffer)[i] / (float)(1 << 24)) * MasterVolume;
			}
		}
		buffer = (uint8_t *)buffer + written * 8;
		sizebytes -= written * 8;
	}
	return true;
}

//==========================================================================
//
// ChangeSetting
//
//==========================================================================

void DumbSong::ChangeSettingNum(const char* setting, double val)
{
	if (!stricmp(setting, "dumb.mastervolume"))
		MasterVolume = (float)val;
}

//==========================================================================
//
// DumbSong constructor
//
//==========================================================================

DumbSong::DumbSong(DUH* myduh, int samplerate)
{
	duh = myduh;
	sr = NULL;
	eof = false;
	interp = dumbConfig.mod_interp;
	volramp = dumbConfig.mod_volramp;
	written = 0;
	length = 0;
	start_order = 0;
	MasterVolume = (float)dumbConfig.mod_dumb_mastervolume * 4;
	if (dumbConfig.mod_samplerate != 0)
	{
		srate = dumbConfig.mod_samplerate;
	}
	else
	{
		srate = samplerate;
	}
	delta = 65536.0 / srate;
}

//==========================================================================
//
// DumbSong destructor
//
//==========================================================================

DumbSong::~DumbSong()
{
	if (sr) duh_end_sigrenderer(sr);
	if (duh) unload_duh(duh);
}

//==========================================================================
//
// DumbSong GetFormatEx
//
//==========================================================================

SoundStreamInfoEx DumbSong::GetFormatEx()
{
	return { 32*1024, srate, SampleType_Float32, ChannelConfig_Stereo };
}

//==========================================================================
//
// DumbSong :: Play
//
//==========================================================================

bool DumbSong::Start()
{
	started = open2(0);
	return started;
}

//==========================================================================
//
// DumbSong :: SetSubsong
//
//==========================================================================

bool DumbSong::SetSubsong(int order)
{
	if (order == start_order)
	{
		return true;
	}
	if (!started)
	{
		start_order = order;
		return true;
	}
	DUH_SIGRENDERER *oldsr = sr;
	sr = NULL;
	start_order = order;
	if (!open2(0))
	{
		sr = oldsr;
		return false;
	}
	duh_end_sigrenderer(oldsr);
	return true;
}

//==========================================================================
//
// DumbSong :: open2
//
//==========================================================================

bool DumbSong::open2(long pos)
{
	if (start_order != 0)
	{
		sr = dumb_it_start_at_order(duh, 2, start_order);
		if (sr && pos) duh_sigrenderer_generate_samples(sr, 0, 1, pos, 0);
	}
	else
	{
		sr = duh_start_sigrenderer(duh, 0, 2, pos);
	}
	if (!sr)
	{
		return false;
	}

	DUMB_IT_SIGRENDERER *itsr = duh_get_it_sigrenderer(sr);
	dumb_it_set_resampling_quality(itsr, interp);
	dumb_it_set_ramp_style(itsr, volramp);
	if (!m_Looping)
	{
		dumb_it_set_loop_callback(itsr, &dumb_it_callback_terminate, NULL);
	}
	dumb_it_set_xm_speed_zero_callback(itsr, &dumb_it_callback_terminate, NULL);
	dumb_it_set_global_volume_zero_callback(itsr, &dumb_it_callback_terminate, NULL);
	return true;
}

//==========================================================================
//
// DumbSong :: render
//
//==========================================================================

long DumbSong::render(double volume, double delta, long samples, sample_t **buffer)
{
	long written = duh_sigrenderer_generate_samples(sr, volume, delta, samples, buffer);

	if (written < samples)
	{
		if (!m_Looping)
		{
			eof = true;
		}
		else
		{
			duh_end_sigrenderer(sr);
			sr = NULL;
			if (!open2(0))
			{
				eof = true;
			}
		}
	}
	return written;
}

//==========================================================================
//
// DumbSong :: decode_run
//
// Given a buffer of 32-bit PCM stereo pairs and a size specified in
// samples, returns the number of samples written to the buffer.
//
//==========================================================================

int DumbSong::decode_run(void *buffer, unsigned int size)
{
	if (eof) return 0;

	DUMB_IT_SIGRENDERER *itsr = duh_get_it_sigrenderer(sr);
	if (itsr == nullptr) return 0;
	int dt = int(delta * 65536.0 + 0.5);
	long samples = long((((LONG_LONG)itsr->time_left << 16) | itsr->sub_time_left) / dt);
	if (samples == 0 || samples > (long)size) samples = size;
	sample_t **buf = (sample_t **)&buffer;
	int written = 0;

retry:
	dumb_silence(buf[0], size * 2);
	written = render(1, delta, samples, buf);

	if (eof) return false;
	else if (written == 0) goto retry;
	else if (written == -1) return -1;

	return written;
}

//==========================================================================
//
// DumbSong :: GetStats
//
//==========================================================================

std::string DumbSong::GetStats()
{
	//return StreamSong::GetStats();
	DUMB_IT_SIGRENDERER *itsr = duh_get_it_sigrenderer(sr);
	DUMB_IT_SIGDATA *itsd = duh_get_it_sigdata(duh);

	int channels = 0;
	for (int i = 0; i < DUMB_IT_N_CHANNELS; i++)
	{
		IT_PLAYING * playing = itsr->channel[i].playing;
		if (playing && !(playing->flags & IT_PLAYING_DEAD)) channels++;
	}
	for (int i = 0; i < DUMB_IT_N_NNA_CHANNELS; i++)
	{
		if (itsr->playing[i]) channels++;
	}

	if (itsr == NULL || itsd == NULL)
	{
		return "Problem getting stats";
	}
	else
	{
		char out[120];
		snprintf(out, 120, "%s, Order:%3d/%d Patt:%2d/%d Row:%2d/%2d Chan:%2d/%2d Speed:%2d Tempo:%3d",
			Codec.c_str(),
			itsr->order, NumOrders,
			(itsd->order && itsr->order < itsd->n_orders ? itsd->order[itsr->order] : 0), NumPatterns,
			itsr->row, itsr->n_rows,
			channels, NumChannels,
			itsr->speed,
			itsr->tempo
			);
		return out;
	}
}

