mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-12-12 21:52:01 +00:00
6854a509e9
Most of the synchronization was too deep in the implementation so that it did not guard everything it needed. Now each song has precisely one mutex which must be locked for all access to its internals - this is done in the public ZMusic interface
1220 lines
31 KiB
C++
1220 lines
31 KiB
C++
/*
|
|
** 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 "../../libraries/music_common/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;
|
|
SoundStreamInfo GetFormat() 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 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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
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 GetFormat
|
|
//
|
|
//==========================================================================
|
|
|
|
SoundStreamInfo DumbSong::GetFormat()
|
|
{
|
|
return { 32*1024, srate, 2 };
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// 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;
|
|
}
|
|
}
|
|
|