2016-11-08 02:59:05 +00:00
|
|
|
|
2017-02-25 08:15:36 +00:00
|
|
|
#include "compat.h"
|
2016-11-08 02:59:05 +00:00
|
|
|
|
|
|
|
#ifdef HAVE_XMP
|
|
|
|
|
|
|
|
#include "pitch.h"
|
|
|
|
#include "multivoc.h"
|
|
|
|
#include "_multivc.h"
|
|
|
|
|
|
|
|
#define BUILDING_STATIC
|
|
|
|
#include "libxmp-lite/xmp.h"
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
void * ptr;
|
|
|
|
VoiceNode *owner;
|
|
|
|
size_t length;
|
|
|
|
xmp_context context;
|
|
|
|
int time;
|
|
|
|
} xmp_data;
|
|
|
|
|
|
|
|
int32_t MV_GetXMPPosition(VoiceNode *voice)
|
|
|
|
{
|
2018-12-15 01:39:51 +00:00
|
|
|
auto xmpd = (xmp_data *)voice->rawdataptr;
|
2016-11-08 02:59:05 +00:00
|
|
|
return xmpd->time;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MV_SetXMPPosition(VoiceNode *voice, int32_t position)
|
|
|
|
{
|
2018-12-15 01:39:51 +00:00
|
|
|
auto xmpd = (xmp_data *)voice->rawdataptr;
|
2016-11-08 02:59:05 +00:00
|
|
|
xmp_seek_time(xmpd->context, position);
|
|
|
|
}
|
|
|
|
|
|
|
|
static playbackstatus MV_GetNextXMPBlock(VoiceNode *voice)
|
|
|
|
{
|
2018-12-15 01:39:51 +00:00
|
|
|
auto xmpd = (xmp_data *)voice->rawdataptr;
|
2016-11-08 02:59:05 +00:00
|
|
|
struct xmp_frame_info mi;
|
|
|
|
|
|
|
|
if (xmp_play_frame(xmpd->context) != 0)
|
|
|
|
{
|
|
|
|
if (voice->LoopSize > 0)
|
|
|
|
{
|
|
|
|
xmp_restart_module(xmpd->context);
|
|
|
|
if (xmp_play_frame(xmpd->context) != 0)
|
|
|
|
return NoMoreData;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return NoMoreData;
|
|
|
|
}
|
|
|
|
|
|
|
|
xmp_get_frame_info(xmpd->context, &mi);
|
|
|
|
|
|
|
|
xmpd->time = mi.time;
|
|
|
|
|
2019-05-27 05:45:33 +00:00
|
|
|
uint32_t const samples = mi.buffer_size / (2 * (16/8)); // since 2-channel, 16-bit is hardcoded
|
|
|
|
// uint32_t const samples = mi.buffer_size / (voice->channels * (voice->bits / 8));
|
|
|
|
|
2016-11-08 02:59:05 +00:00
|
|
|
voice->sound = (char const *)mi.buffer;
|
2019-05-27 05:45:33 +00:00
|
|
|
voice->length = samples << 16;
|
2016-11-08 02:59:05 +00:00
|
|
|
voice->position = 0;
|
|
|
|
voice->BlockLength = 0;
|
|
|
|
|
|
|
|
MV_SetVoiceMixMode(voice);
|
|
|
|
|
|
|
|
return KeepPlaying;
|
|
|
|
}
|
|
|
|
|
2019-09-19 18:22:09 +00:00
|
|
|
int32_t MV_PlayXMP3D(char *ptr, uint32_t length, int32_t loophow, int32_t pitchoffset, int32_t angle, int32_t distance, int32_t priority, float volume, intptr_t callbackval)
|
2016-11-08 02:59:05 +00:00
|
|
|
{
|
|
|
|
int32_t left;
|
|
|
|
int32_t right;
|
|
|
|
int32_t mid;
|
2018-10-25 23:32:14 +00:00
|
|
|
int32_t vol;
|
2016-11-08 02:59:05 +00:00
|
|
|
int32_t status;
|
|
|
|
|
|
|
|
if (!MV_Installed)
|
|
|
|
{
|
|
|
|
MV_SetErrorCode(MV_NotInstalled);
|
|
|
|
return MV_Error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (distance < 0)
|
|
|
|
{
|
|
|
|
distance = -distance;
|
|
|
|
angle += MV_NUMPANPOSITIONS / 2;
|
|
|
|
}
|
|
|
|
|
2018-10-25 23:32:14 +00:00
|
|
|
vol = MIX_VOLUME(distance);
|
2016-11-08 02:59:05 +00:00
|
|
|
|
|
|
|
// Ensure angle is within 0 - 127
|
|
|
|
angle &= MV_MAXPANPOSITION;
|
|
|
|
|
2018-10-25 23:32:14 +00:00
|
|
|
left = MV_PanTable[angle][vol].left;
|
|
|
|
right = MV_PanTable[angle][vol].right;
|
2016-11-08 02:59:05 +00:00
|
|
|
mid = max( 0, 255 - distance );
|
|
|
|
|
2018-10-25 23:32:14 +00:00
|
|
|
status = MV_PlayXMP(ptr, length, loophow, -1, pitchoffset, mid, left, right, priority, volume, callbackval);
|
2016-11-08 02:59:05 +00:00
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2019-09-19 18:22:09 +00:00
|
|
|
int32_t MV_PlayXMP(char *ptr, uint32_t length, int32_t loopstart, int32_t loopend, int32_t pitchoffset, int32_t vol, int32_t left, int32_t right, int32_t priority, float volume, intptr_t callbackval)
|
2016-11-08 02:59:05 +00:00
|
|
|
{
|
|
|
|
VoiceNode *voice;
|
|
|
|
xmp_data * xmpd = 0;
|
|
|
|
int retval;
|
|
|
|
|
|
|
|
UNREFERENCED_PARAMETER(loopend);
|
|
|
|
|
|
|
|
if (!MV_Installed)
|
|
|
|
{
|
|
|
|
MV_SetErrorCode(MV_NotInstalled);
|
|
|
|
return MV_Error;
|
|
|
|
}
|
|
|
|
|
2018-10-25 23:28:56 +00:00
|
|
|
xmpd = (xmp_data *)Xcalloc(1, sizeof(xmp_data));
|
2016-11-08 02:59:05 +00:00
|
|
|
if (!xmpd)
|
|
|
|
{
|
|
|
|
MV_SetErrorCode(MV_InvalidFile);
|
|
|
|
return MV_Error;
|
|
|
|
}
|
|
|
|
|
|
|
|
xmpd->ptr = ptr;
|
2018-10-25 23:32:14 +00:00
|
|
|
xmpd->length = length;
|
2016-11-08 02:59:05 +00:00
|
|
|
|
|
|
|
if ((xmpd->context = xmp_create_context()) == NULL)
|
|
|
|
{
|
2019-06-25 11:29:08 +00:00
|
|
|
Xfree(xmpd);
|
2016-11-08 02:59:05 +00:00
|
|
|
MV_SetErrorCode(MV_InvalidFile);
|
|
|
|
return MV_Error;
|
|
|
|
}
|
|
|
|
|
2018-10-25 23:32:14 +00:00
|
|
|
if ((retval = xmp_load_module_from_memory(xmpd->context, ptr, length)) != 0)
|
2016-11-08 02:59:05 +00:00
|
|
|
{
|
2019-06-25 11:29:08 +00:00
|
|
|
Xfree(xmpd);
|
2016-11-08 02:59:05 +00:00
|
|
|
MV_Printf("MV_PlayXMP: xmp_load_module_from_memory failed (%i)\n", retval);
|
|
|
|
MV_SetErrorCode(MV_InvalidFile);
|
|
|
|
return MV_Error;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Request a voice from the voice pool
|
|
|
|
voice = MV_AllocVoice(priority);
|
|
|
|
if (voice == NULL)
|
|
|
|
{
|
|
|
|
xmp_release_module(xmpd->context);
|
|
|
|
xmp_free_context(xmpd->context);
|
2019-06-25 11:29:08 +00:00
|
|
|
Xfree(xmpd);
|
2016-11-08 02:59:05 +00:00
|
|
|
MV_SetErrorCode(MV_NoVoices);
|
|
|
|
return MV_Error;
|
|
|
|
}
|
|
|
|
|
|
|
|
xmpd->owner = voice;
|
|
|
|
|
2017-12-17 02:03:52 +00:00
|
|
|
voice->length = 0;
|
|
|
|
voice->sound = 0;
|
|
|
|
|
2016-11-08 02:59:05 +00:00
|
|
|
voice->wavetype = FMT_XMP;
|
|
|
|
voice->rawdataptr = (void*)xmpd;
|
|
|
|
voice->GetSound = MV_GetNextXMPBlock;
|
|
|
|
voice->LoopCount = 0;
|
|
|
|
voice->BlockLength = 0;
|
|
|
|
voice->PitchScale = PITCH_GetScale(pitchoffset);
|
|
|
|
voice->next = NULL;
|
|
|
|
voice->prev = NULL;
|
|
|
|
voice->priority = priority;
|
|
|
|
voice->callbackval = callbackval;
|
|
|
|
|
|
|
|
voice->bits = 16;
|
|
|
|
voice->channels = 2;
|
|
|
|
voice->SamplingRate = MV_MixRate;
|
|
|
|
|
|
|
|
voice->Paused = FALSE;
|
|
|
|
|
|
|
|
voice->LoopStart = 0;
|
|
|
|
voice->LoopEnd = 0;
|
|
|
|
voice->LoopSize = loopstart >= 0 ? 1 : 0;
|
|
|
|
|
|
|
|
xmp_start_player(xmpd->context, MV_MixRate, 0);
|
2018-11-18 18:07:04 +00:00
|
|
|
xmp_set_player(xmpd->context, XMP_PLAYER_INTERP, XMP_INTERP_SPLINE);
|
2016-11-08 02:59:05 +00:00
|
|
|
|
|
|
|
// CODEDUP multivoc.c MV_SetVoicePitch
|
|
|
|
voice->RateScale = (voice->SamplingRate * voice->PitchScale) / MV_MixRate;
|
|
|
|
voice->FixedPointBufferSize = (voice->RateScale * MV_MIXBUFFERSIZE) - voice->RateScale;
|
|
|
|
MV_SetVoiceMixMode(voice);
|
|
|
|
|
2018-10-25 23:32:50 +00:00
|
|
|
MV_SetVoiceVolume(voice, vol, left, right, volume);
|
2016-11-08 02:59:05 +00:00
|
|
|
MV_PlayVoice(voice);
|
|
|
|
|
|
|
|
return voice->handle;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MV_ReleaseXMPVoice(VoiceNode * voice)
|
|
|
|
{
|
2018-12-15 01:39:51 +00:00
|
|
|
auto xmpd = (xmp_data *) voice->rawdataptr;
|
2016-11-08 02:59:05 +00:00
|
|
|
|
|
|
|
if (voice->wavetype != FMT_XMP)
|
|
|
|
return;
|
|
|
|
|
2017-12-17 02:03:52 +00:00
|
|
|
voice->rawdataptr = 0;
|
2019-05-27 05:45:38 +00:00
|
|
|
voice->length = 0;
|
|
|
|
voice->sound = nullptr;
|
2017-12-17 02:03:52 +00:00
|
|
|
|
2016-11-08 02:59:05 +00:00
|
|
|
xmp_end_player(xmpd->context);
|
|
|
|
xmp_release_module(xmpd->context);
|
|
|
|
xmp_free_context(xmpd->context);
|
2019-06-25 11:29:08 +00:00
|
|
|
Xfree(xmpd);
|
2016-11-08 02:59:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
#include "_multivc.h"
|
|
|
|
|
|
|
|
static char const NoXMP[] = "MV_PlayXMP: libxmp-lite support not included in this binary.\n";
|
|
|
|
|
|
|
|
int32_t MV_PlayXMP(char *ptr, uint32_t ptrlength, int32_t loopstart, int32_t loopend, int32_t pitchoffset, int32_t vol,
|
2019-09-19 18:22:09 +00:00
|
|
|
int32_t left, int32_t right, int32_t priority, float volume, intptr_t callbackval)
|
2016-11-08 02:59:05 +00:00
|
|
|
{
|
|
|
|
UNREFERENCED_PARAMETER(ptr);
|
|
|
|
UNREFERENCED_PARAMETER(ptrlength);
|
|
|
|
UNREFERENCED_PARAMETER(loopstart);
|
|
|
|
UNREFERENCED_PARAMETER(loopend);
|
|
|
|
UNREFERENCED_PARAMETER(pitchoffset);
|
|
|
|
UNREFERENCED_PARAMETER(vol);
|
|
|
|
UNREFERENCED_PARAMETER(left);
|
|
|
|
UNREFERENCED_PARAMETER(right);
|
|
|
|
UNREFERENCED_PARAMETER(priority);
|
2018-10-25 23:32:14 +00:00
|
|
|
UNREFERENCED_PARAMETER(volume);
|
2016-11-08 02:59:05 +00:00
|
|
|
UNREFERENCED_PARAMETER(callbackval);
|
|
|
|
|
|
|
|
MV_Printf(NoXMP);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t MV_PlayXMP3D(char *ptr, uint32_t ptrlength, int32_t loophow, int32_t pitchoffset, int32_t angle,
|
2019-09-19 18:22:09 +00:00
|
|
|
int32_t distance, int32_t priority, float volume, intptr_t callbackval)
|
2016-11-08 02:59:05 +00:00
|
|
|
{
|
|
|
|
UNREFERENCED_PARAMETER(ptr);
|
|
|
|
UNREFERENCED_PARAMETER(ptrlength);
|
|
|
|
UNREFERENCED_PARAMETER(loophow);
|
|
|
|
UNREFERENCED_PARAMETER(pitchoffset);
|
|
|
|
UNREFERENCED_PARAMETER(angle);
|
|
|
|
UNREFERENCED_PARAMETER(distance);
|
|
|
|
UNREFERENCED_PARAMETER(priority);
|
2018-10-25 23:32:14 +00:00
|
|
|
UNREFERENCED_PARAMETER(volume);
|
2016-11-08 02:59:05 +00:00
|
|
|
UNREFERENCED_PARAMETER(callbackval);
|
|
|
|
|
|
|
|
MV_Printf(NoXMP);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2017-10-15 23:09:59 +00:00
|
|
|
// KEEPINSYNC libxmp-lite/src/*_load.c
|
2016-11-08 02:59:05 +00:00
|
|
|
|
|
|
|
static int it_test_memory(char const *ptr, uint32_t ptrlength)
|
|
|
|
{
|
|
|
|
static char const it_magic[] = "IMPM";
|
|
|
|
|
|
|
|
if (ptrlength < sizeof(it_magic)-1 ||
|
|
|
|
memcmp(ptr, it_magic, sizeof(it_magic)-1))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int mod_test_memory(char const *ptr, uint32_t ptrlength)
|
|
|
|
{
|
|
|
|
if (ptrlength < 1084)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
char const * const buf = ptr + 1080;
|
|
|
|
|
|
|
|
if (!strncmp(buf + 2, "CH", 2) && isdigit((int)buf[0]) && isdigit((int)buf[1]))
|
|
|
|
{
|
|
|
|
int i = (buf[0] - '0') * 10 + buf[1] - '0';
|
|
|
|
if (i > 0 && i <= 32)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!strncmp(buf + 1, "CHN", 3) && isdigit((int)*buf))
|
|
|
|
{
|
|
|
|
if (*buf >= '0' && *buf <= '9')
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!memcmp(buf, "M.K.", 4))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int s3m_test_memory(char const *ptr, uint32_t ptrlength)
|
|
|
|
{
|
|
|
|
static char const s3m_magic[] = "SCRM";
|
|
|
|
#define s3m_magic_offset 44
|
|
|
|
|
|
|
|
if (ptrlength < s3m_magic_offset + sizeof(s3m_magic)-1 ||
|
|
|
|
memcmp(ptr + s3m_magic_offset, s3m_magic, sizeof(s3m_magic)-1) ||
|
|
|
|
ptr[29] != 0x10)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int xm_test_memory(char const *ptr, uint32_t ptrlength)
|
|
|
|
{
|
|
|
|
static char const xm_magic[] = "Extended Module: ";
|
|
|
|
|
|
|
|
if (ptrlength < sizeof(xm_magic)-1 ||
|
|
|
|
memcmp(ptr, xm_magic, sizeof(xm_magic)-1))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-10-15 23:09:59 +00:00
|
|
|
static int mtm_test_memory(char const *ptr, uint32_t ptrlength)
|
|
|
|
{
|
|
|
|
static char const mtm_magic[] = "MTM\x10";
|
|
|
|
|
|
|
|
if (ptrlength < sizeof(mtm_magic)-1 ||
|
|
|
|
memcmp(ptr, mtm_magic, sizeof(mtm_magic)-1))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-11-08 02:59:05 +00:00
|
|
|
int MV_IdentifyXMP(char const *ptr, uint32_t ptrlength)
|
|
|
|
{
|
2017-10-15 23:09:59 +00:00
|
|
|
static decltype(mod_test_memory) * const module_test_functions[] =
|
|
|
|
{
|
|
|
|
it_test_memory,
|
|
|
|
mod_test_memory,
|
|
|
|
s3m_test_memory,
|
|
|
|
xm_test_memory,
|
|
|
|
mtm_test_memory,
|
|
|
|
};
|
|
|
|
|
|
|
|
for (auto const test_module : module_test_functions)
|
|
|
|
{
|
|
|
|
if (test_module(ptr, ptrlength) == 0)
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2016-11-08 02:59:05 +00:00
|
|
|
}
|