Squashed 'libraries/ZMusic/' content from commit ac3e232b00

git-subtree-dir: libraries/ZMusic
git-subtree-split: ac3e232b001129c740b7b65196ae0e1b13b82513
This commit is contained in:
Rachael Alexanderson 2025-07-30 00:24:15 -04:00
commit 9f3cb3d92e
852 changed files with 381622 additions and 0 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,374 @@
/*
** music_gme.cpp
** General game music player, using Game Music Emu for decoding.
**
**---------------------------------------------------------------------------
** Copyright 2009 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 ------------------------------------------------------------
// Uncomment if you are using the DLL version of GME.
//#define GME_DLL
#include <algorithm>
#include <mutex>
#include <stdexcept>
#include "streamsource.h"
#include <gme/gme.h>
#include "fileio.h"
// MACROS ------------------------------------------------------------------
// TYPES -------------------------------------------------------------------
class GMESong : public StreamSource
{
public:
GMESong(Music_Emu *emu, int sample_rate);
~GMESong();
bool SetSubsong(int subsong) override;
bool Start() override;
void ChangeSettingNum(const char *name, double val) override;
std::string GetStats() override;
bool GetData(void *buffer, size_t len) override;
SoundStreamInfoEx GetFormatEx() override;
protected:
Music_Emu *Emu;
gme_info_t *TrackInfo;
int SampleRate;
int CurrTrack;
bool started = false;
bool StartTrack(int track, bool getcritsec=true);
bool GetTrackInfo();
int CalcSongLength();
};
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
// PUBLIC DATA DEFINITIONS -------------------------------------------------
// Currently not used.
// PRIVATE DATA DEFINITIONS ------------------------------------------------
// CODE --------------------------------------------------------------------
//==========================================================================
//
// GME_CheckFormat
//
//==========================================================================
const char *GME_CheckFormat(uint32_t id)
{
return gme_identify_header(&id);
}
//==========================================================================
//
// GME_OpenSong
//
//==========================================================================
StreamSource *GME_OpenSong(MusicIO::FileInterface *reader, const char *fmt, int sample_rate)
{
gme_type_t type;
gme_err_t err;
uint8_t *song;
Music_Emu *emu;
type = gme_identify_extension(fmt);
if (type == NULL)
{
return NULL;
}
emu = gme_new_emu(type, sample_rate);
if (emu == nullptr)
{
return nullptr;
}
auto fpos = reader->tell();
auto len = reader->filelength();
song = new uint8_t[len];
if (reader->read(song, len) != len)
{
delete[] song;
gme_delete(emu);
reader->seek(fpos, SEEK_SET);
return nullptr;
}
err = gme_load_data(emu, song, (long)len);
delete[] song;
if (err != nullptr)
{
gme_delete(emu);
throw std::runtime_error(err);
}
gme_set_stereo_depth(emu, std::min(std::max(miscConfig.gme_stereodepth, 0.f), 1.f));
gme_set_fade(emu, -1); // Enable infinite loop
#if GME_VERSION >= 0x602
gme_set_autoload_playback_limit(emu, 0);
#endif // GME_VERSION >= 0x602
return new GMESong(emu, sample_rate);
}
//==========================================================================
//
// GMESong - Constructor
//
//==========================================================================
GMESong::GMESong(Music_Emu *emu, int sample_rate)
{
Emu = emu;
SampleRate = sample_rate;
CurrTrack = 0;
TrackInfo = NULL;
}
SoundStreamInfoEx GMESong::GetFormatEx()
{
return { 32*1024, SampleRate, SampleType_Int16, ChannelConfig_Stereo };
}
//==========================================================================
//
// GMESong - Destructor
//
//==========================================================================
GMESong::~GMESong()
{
if (TrackInfo != NULL)
{
gme_free_info(TrackInfo);
}
if (Emu != NULL)
{
gme_delete(Emu);
}
}
//==========================================================================
//
// GMESong :: GMEDepthChanged
//
//==========================================================================
void GMESong::ChangeSettingNum(const char *name, double val)
{
if (Emu != nullptr && !stricmp(name, "gme.stereodepth"))
{
gme_set_stereo_depth(Emu, std::min(std::max(0., val), 1.));
}
}
//==========================================================================
//
// GMESong :: Play
//
//==========================================================================
bool GMESong::Start()
{
return StartTrack(CurrTrack);
}
//==========================================================================
//
// GMESong :: SetSubsong
//
//==========================================================================
bool GMESong::SetSubsong(int track)
{
if (CurrTrack == track)
{
return true;
}
if (!started)
{
CurrTrack = track;
return true;
}
return StartTrack(track);
}
//==========================================================================
//
// GMESong :: StartTrack
//
//==========================================================================
bool GMESong::StartTrack(int track, bool getcritsec)
{
gme_err_t err;
if (getcritsec)
{
err = gme_start_track(Emu, track);
}
else
{
err = gme_start_track(Emu, track);
}
if (err != NULL)
{
// This is called in the data reader thread which may not interact with the UI.
// TBD: How to get the message across? An exception may not be used here!
// Printf("Could not start track %d: %s\n", track, err);
return false;
}
CurrTrack = track;
started = true;
GetTrackInfo();
if (!m_Looping)
{
gme_set_fade(Emu, CalcSongLength());
}
return true;
}
//==========================================================================
//
// GMESong :: GetStats
//
//==========================================================================
std::string GMESong::GetStats()
{
char out[80];
if (TrackInfo != NULL)
{
int time = gme_tell(Emu);
snprintf(out, 80,
"Track: %d Time: %3d:%02d:%03d System: %s",
CurrTrack,
time/60000,
(time/1000) % 60,
time % 1000,
TrackInfo->system);
}
return out;
}
//==========================================================================
//
// GMESong :: GetTrackInfo
//
//==========================================================================
bool GMESong::GetTrackInfo()
{
gme_err_t err;
if (TrackInfo != NULL)
{
gme_free_info(TrackInfo);
TrackInfo = NULL;
}
err = gme_track_info(Emu, &TrackInfo, CurrTrack);
if (err != NULL)
{
// This is called in the data reader thread which may not interact with the UI.
// TBD: How to get the message across? An exception may not be used here!
//Printf("Could not get track %d info: %s\n", CurrTrack, err);
return false;
}
return true;
}
//==========================================================================
//
// GMESong :: CalcSongLength
//
//==========================================================================
int GMESong::CalcSongLength()
{
if (TrackInfo == NULL)
{
return 150000;
}
if (TrackInfo->length > 0)
{
return TrackInfo->length;
}
if (TrackInfo->loop_length > 0)
{
return TrackInfo->intro_length + TrackInfo->loop_length * 2;
}
return 150000;
}
//==========================================================================
//
// GMESong :: Read STATIC
//
//==========================================================================
bool GMESong::GetData(void *buffer, size_t len)
{
gme_err_t err;
if (gme_track_ended(Emu))
{
if (m_Looping)
{
StartTrack(CurrTrack, false);
}
else
{
memset(buffer, 0, len);
return false;
}
}
err = gme_play(Emu, int(len >> 1), (short *)buffer);
return (err == NULL);
}

View file

@ -0,0 +1,568 @@
/*
** music_libsndfile.cpp
** Uses libsndfile for streaming music formats
**
**---------------------------------------------------------------------------
** Copyright 2017 Christoph Oelckers
** 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 <mutex>
#include <algorithm>
#include "zmusic_internal.h"
#include "streamsource.h"
#include "zmusic/sounddecoder.h"
// MACROS ------------------------------------------------------------------
// TYPES -------------------------------------------------------------------
class SndFileSong : public StreamSource
{
public:
SndFileSong(SoundDecoder *decoder, uint32_t loop_start, uint32_t loop_end, bool startass, bool endass);
~SndFileSong();
std::string GetStats() override;
SoundStreamInfoEx GetFormatEx() override;
bool GetData(void *buffer, size_t len) override;
protected:
SoundDecoder *Decoder;
unsigned int FrameSize;
uint32_t Loop_Start;
uint32_t Loop_End;
int CalcSongLength();
};
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
// PUBLIC DATA DEFINITIONS -------------------------------------------------
// PRIVATE DATA DEFINITIONS ------------------------------------------------
// CODE --------------------------------------------------------------------
//==========================================================================
//
// S_ParseTimeTag
//
// Passed the value of a loop point tag, converts it to numbers.
//
// This may be of the form 00:00:00.00 (HH:MM:SS.ss) to specify by play
// time. Various parts may be left off. The only requirement is that it
// contain a colon. e.g. To start the loop at 20 seconds in, you can use
// ":20", "0:20", "00:00:20", ":20.0", etc. Values after the decimal are
// fractions of a second.
//
// If you don't include a colon but just have a raw number, then it's
// the number of PCM samples at which to loop.
//
// Returns true if the tag made sense, false if not.
//
//==========================================================================
bool S_ParseTimeTag(const char* tag, zmusic_bool* as_samples, unsigned int* time)
{
const int time_count = 3;
const char* bit = tag;
char ms[3] = { 0 };
unsigned int times[time_count] = { 0 };
int ms_pos = 0, time_pos = 0;
bool pcm = true, in_ms = false;
for (bit = tag; *bit != '\0'; ++bit)
{
if (*bit >= '0' && *bit <= '9')
{
if (in_ms)
{
// Ignore anything past three fractional digits.
if (ms_pos < 3)
{
ms[ms_pos++] = *bit - '0';
}
}
else
{
times[time_pos] = times[time_pos] * 10 + *bit - '0';
}
}
else if (*bit == ':')
{
if (in_ms)
{ // If we already specified milliseconds, we can't take any more parts.
return false;
}
pcm = false;
if (++time_pos == time_count)
{ // Time too long. (Seriously, starting the loop days in?)
return false;
}
}
else if (*bit == '.')
{
if (pcm || in_ms)
{ // It doesn't make sense to have fractional PCM values.
// It also doesn't make sense to have more than one dot.
return false;
}
in_ms = true;
}
else
{ // Anything else: We don't understand this.
return false;
}
}
if (pcm)
{
*as_samples = true;
*time = times[0];
}
else
{
unsigned int mytime = 0;
// Add in hours, minutes, and seconds
for (int i = 0; i <= time_pos; ++i)
{
mytime = mytime * 60 + times[i];
}
// Add in milliseconds
mytime = mytime * 1000 + ms[0] * 100 + ms[1] * 10 + ms[2];
*as_samples = false;
*time = mytime;
}
return true;
}
//==========================================================================
//
// Try to find the LOOP_START/LOOP_END tags in a Vorbis Comment block
//
// We have to parse through the FLAC or Ogg headers manually, since sndfile
// doesn't provide proper access to the comments and we'd rather not require
// using libFLAC and libvorbisfile directly.
//
//==========================================================================
static void ParseVorbisComments(MusicIO::FileInterface *fr, uint32_t *start, zmusic_bool *startass, uint32_t *end, zmusic_bool *endass)
{
uint8_t vc_data[4];
// The VC block starts with a 32LE integer for the vendor string length,
// followed by the vendor string
if(fr->read(vc_data, 4) != 4)
return;
uint32_t vndr_len = vc_data[0] | (vc_data[1]<<8) | (vc_data[2]<<16) | (vc_data[3]<<24);
// Skip vendor string
if(fr->seek(vndr_len, SEEK_CUR) == -1)
return;
// Following the vendor string is a 32LE integer for the number of
// comments, followed by each comment.
if(fr->read(vc_data, 4) != 4)
return;
size_t count = vc_data[0] | (vc_data[1]<<8) | (vc_data[2]<<16) | (vc_data[3]<<24);
zmusic_bool loopass = false;
uint32_t looplen = 0;
bool endfound = false;
for(size_t i = 0; i < count; i++)
{
// Each comment is a 32LE integer for the comment length, followed by
// the comment text (not null terminated!)
if(fr->read(vc_data, 4) != 4)
return;
uint32_t length = vc_data[0] | (vc_data[1]<<8) | (vc_data[2]<<16) | (vc_data[3]<<24);
if(length >= 128)
{
// If the comment is "big", skip it
if(fr->seek(length, SEEK_CUR) == -1)
return;
continue;
}
char strdat[128];
if(fr->read(strdat, length) != (long)length)
return;
strdat[length] = 0;
static const char* loopStartTags[] = { "LOOP_START=", "LOOPSTART=", "LOOP=" };
static const char* loopEndTags[] = { "LOOP_END=", "LOOPEND=" };
static const char* loopLengthTags[] = { "LOOP_LENGTH=", "LOOPLENGTH=" };
for (auto tag : loopStartTags)
{
const size_t tagLength = strlen(tag);
if (!strnicmp(strdat, tag, tagLength))
{
S_ParseTimeTag(strdat + tagLength, startass, start);
break;
}
}
for (auto tag : loopEndTags)
{
const size_t tagLength = strlen(tag);
if (!strnicmp(strdat, tag, tagLength))
{
S_ParseTimeTag(strdat + tagLength, endass, end);
endfound = true;
break;
}
}
for (auto tag : loopLengthTags)
{
const size_t tagLength = strlen(tag);
if (!strnicmp(strdat, tag, tagLength))
{
S_ParseTimeTag(strdat + tagLength, &loopass, &looplen);
*end += *start;
break;
}
}
}
// Use loop length only if no end defined.
if (!endfound && looplen && loopass == *startass)
{
*endass = loopass;
*end = *start + looplen;
}
}
static void FindFlacComments(MusicIO::FileInterface *fr, uint32_t *loop_start, zmusic_bool *startass, uint32_t *loop_end, zmusic_bool *endass)
{
// Already verified the fLaC marker, so we're 4 bytes into the file
bool lastblock = false;
uint8_t header[4];
while(!lastblock && fr->read(header, 4) == 4)
{
// The first byte of the block header contains the type and a flag
// indicating the last metadata block
char blocktype = header[0]&0x7f;
lastblock = !!(header[0]&0x80);
// Following the type is a 24BE integer for the size of the block
uint32_t blocksize = (header[1]<<16) | (header[2]<<8) | header[3];
// FLAC__METADATA_TYPE_VORBIS_COMMENT is 4
if(blocktype == 4)
{
ParseVorbisComments(fr, loop_start, startass, loop_end, endass);
return;
}
if(fr->seek(blocksize, SEEK_CUR) == -1)
break;
}
}
static void FindOggComments(MusicIO::FileInterface *fr, uint32_t *loop_start, zmusic_bool *startass, uint32_t *loop_end, zmusic_bool *endass)
{
uint8_t ogghead[27];
// We already read and verified the OggS marker, so skip the first 4 bytes
// of the Ogg page header.
while(fr->read(ogghead+4, 23) == 23)
{
// The 19th byte of the Ogg header is a 32LE integer for the page
// number, and the 27th is a uint8 for the number of segments in the
// page.
uint32_t ogg_pagenum = ogghead[18] | (ogghead[19]<<8) | (ogghead[20]<<16) |
(ogghead[21]<<24);
uint8_t ogg_segments = ogghead[26];
// Following the Ogg page header is a series of uint8s for the length of
// each segment in the page. The page segment data follows contiguously
// after.
uint8_t segsizes[256];
if(fr->read(segsizes, ogg_segments) != ogg_segments)
break;
// Find the segment with the Vorbis Comment packet (type 3) or Opus tags.
bool vorbis_comments = false;
for(int i = 0; i < ogg_segments; ++i)
{
uint8_t segsize = segsizes[i];
if(segsize > 16)
{
uint8_t vorbhead[8];
if(fr->read(vorbhead, 8) != 8)
return;
if(vorbhead[0] == 3 && memcmp(vorbhead + 1, "vorbis", 6) == 0)
{
// Seek back because the vorbis tag is only 7 bytes long.
if(fr->seek(-1, SEEK_CUR) == -1)
return;
segsize++;
vorbis_comments = true;
}
else if(memcmp(vorbhead, "OpusTags", 8) == 0)
vorbis_comments = true;
if(vorbis_comments)
{
// If the packet is 'laced', it spans multiple segments (a
// segment size of 255 indicates the next segment continues
// the packet, ending with a size less than 255). Vorbis
// packets always start and end on segment boundaries. A
// packet that's an exact multiple of 255 ends with a
// segment of 0 size.
while(segsize == 255 && ++i < ogg_segments)
segsize = segsizes[i];
// TODO: A Vorbis packet can theoretically span multiple
// Ogg pages (e.g. start in the last segment of one page
// and end in the first segment of a following page). That
// will require extra logic to decode as the VC block will
// be broken up with non-Vorbis data in-between. For now,
// just handle the common case where it's all in one page.
if(i < ogg_segments)
ParseVorbisComments(fr, loop_start, startass, loop_end, endass);
return;
}
segsize -= 8;
}
if(fr->seek(segsize, SEEK_CUR) == -1)
return;
}
// Don't keep looking after the third page
if(ogg_pagenum >= 2)
break;
if(fr->read(ogghead, 4) != 4 || memcmp(ogghead, "OggS", 4) != 0)
break;
}
}
void FindLoopTags(MusicIO::FileInterface *fr, uint32_t *start, zmusic_bool *startass, uint32_t *end, zmusic_bool *endass)
{
uint8_t signature[4];
fr->read(signature, 4);
if(memcmp(signature, "fLaC", 4) == 0)
FindFlacComments(fr, start, startass, end, endass);
else if(memcmp(signature, "OggS", 4) == 0)
FindOggComments(fr, start, startass, end, endass);
}
DLL_EXPORT void FindLoopTags(const uint8_t* data, size_t size, uint32_t* start, zmusic_bool* startass, uint32_t* end, zmusic_bool* endass)
{
MusicIO::FileInterface* reader = new MusicIO::MemoryReader(data, (long)size);
FindLoopTags(reader, start, startass, end, endass);
reader->close();
}
//==========================================================================
//
// SndFile_OpenSong
//
//==========================================================================
StreamSource *SndFile_OpenSong(MusicIO::FileInterface *fr)
{
fr->seek(0, SEEK_SET);
uint32_t loop_start = 0, loop_end = ~0u;
zmusic_bool startass = false, endass = false;
FindLoopTags(fr, &loop_start, &startass, &loop_end, &endass);
fr->seek(0, SEEK_SET);
auto decoder = SoundDecoder::CreateDecoder(fr);
if (decoder == nullptr) return nullptr; // If this fails the file reader has not been taken over and the caller needs to clean up. This is to allow further analysis of the passed file.
return new SndFileSong(decoder, loop_start, loop_end, startass, endass);
}
//==========================================================================
//
// SndFileSong - Constructor
//
//==========================================================================
static int32_t Scale(int32_t a, int32_t b, int32_t c)
{
return (int32_t)(((int64_t)a * b) / c);
}
SndFileSong::SndFileSong(SoundDecoder *decoder, uint32_t loop_start, uint32_t loop_end, bool startass, bool endass)
{
ChannelConfig chanconf;
SampleType stype;
int srate;
decoder->getInfo(&srate, &chanconf, &stype);
if (!startass) loop_start = Scale(loop_start, srate, 1000);
if (!endass) loop_end = Scale(loop_end, srate, 1000);
const uint32_t sampleLength = (uint32_t)decoder->getSampleLength();
Loop_Start = loop_start;
Loop_End = sampleLength == 0 ? loop_end : std::min<uint32_t>(loop_end, sampleLength);
Decoder = decoder;
FrameSize = ZMusic_ChannelCount(chanconf) * ZMusic_SampleTypeSize(stype);
}
SoundStreamInfoEx SndFileSong::GetFormatEx()
{
ChannelConfig chanconf;
SampleType stype;
int srate;
Decoder->getInfo(&srate, &chanconf, &stype);
return { 64/*snd_streambuffersize*/ * 1024, srate, stype, chanconf };
}
//==========================================================================
//
// SndFileSong - Destructor
//
//==========================================================================
SndFileSong::~SndFileSong()
{
if (Decoder != nullptr)
{
delete Decoder;
}
}
//==========================================================================
//
// SndFileSong :: GetStats
//
//==========================================================================
std::string SndFileSong::GetStats()
{
char out[80];
ChannelConfig chanconf;
SampleType stype;
int srate;
Decoder->getInfo(&srate, &chanconf, &stype);
size_t SamplePos = Decoder->getSampleOffset();
int time = int (SamplePos / srate);
snprintf(out, 80,
"Track: %s, %dHz Time: %02d:%02d",
ZMusic_ChannelConfigName(chanconf), srate,
time/60,
time % 60);
return out;
}
//==========================================================================
//
// SndFileSong :: Read STATIC
//
//==========================================================================
bool SndFileSong::GetData(void *vbuff, size_t len)
{
char *buff = (char*)vbuff;
size_t currentpos = Decoder->getSampleOffset();
size_t framestoread = len / FrameSize;
bool err = false;
if (!m_Looping)
{
size_t maxpos = Decoder->getSampleLength();
if (currentpos == maxpos)
{
memset(buff, 0, len);
return false;
}
if (currentpos + framestoread > maxpos)
{
size_t got = Decoder->read(buff, (maxpos - currentpos) * FrameSize);
memset(buff + got, 0, len - got);
}
else
{
size_t got = Decoder->read(buff, len);
err = (got != len);
}
}
else
{
// This looks a bit more complicated than necessary because libmpg123 will not read the full requested length for the last block in the file.
if (currentpos + framestoread > Loop_End)
{
// Loop can be very short, make sure the current position doesn't exceed it
if (currentpos < Loop_End)
{
size_t endblock = (Loop_End - currentpos) * FrameSize;
size_t endlen = Decoder->read(buff, endblock);
// Even if zero bytes was read give it a chance to start from the beginning
buff += endlen;
len -= endlen;
}
Decoder->seek(Loop_Start, false, true);
}
while (len > 0)
{
size_t readlen = Decoder->read(buff, len);
if (readlen == 0)
{
return false;
}
buff += readlen;
len -= readlen;
if (len > 0)
{
Decoder->seek(Loop_Start, false, true);
}
}
}
return true;
}

View file

@ -0,0 +1,180 @@
/*
** music_libxmp.cpp
** libxmp module player.
**
**---------------------------------------------------------------------------
** Copyright 2024 Cacodemon345
** 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.
**---------------------------------------------------------------------------
**
*/
#include <math.h>
#include <mutex>
#include <string>
#include <stdint.h>
#include <limits.h>
#include "streamsource.h"
#define LIBXMP_STATIC 1
#include "../libxmp/include/xmp.h"
#include "zmusic/m_swap.h"
#include "zmusic/mididefs.h"
#include "zmusic/midiconfig.h"
#include "fileio.h"
extern DumbConfig dumbConfig;
static unsigned long xmp_read(void *dest, unsigned long len, unsigned long nmemb, void *priv)
{
if (len == 0 || nmemb == 0)
return (unsigned long)0;
MusicIO::FileInterface* interface = (MusicIO::FileInterface*)priv;
auto origpos = interface->tell();
auto length = interface->read(dest, (int32_t)(len * nmemb));
if (length != len * nmemb)
{
// Let's hope the compiler doesn't misoptimize this.
interface->seek(origpos + (length / len) * len, SEEK_SET);
}
return length / len;
}
static struct xmp_callbacks callbacks =
{
xmp_read,
[](void *priv, long offset, int whence) -> int { return ((MusicIO::FileInterface*)priv)->seek(offset, whence); },
[](void *priv) -> long { return ((MusicIO::FileInterface*)priv)->tell(); },
[](void *priv) -> int { return 0; }
};
class XMPSong : public StreamSource
{
private:
xmp_context context = nullptr;
int samplerate = 44100;
int subsong = 0;
// libxmp can't output in float.
std::vector<int16_t> int16_buffer;
public:
XMPSong(xmp_context ctx, int samplerate);
~XMPSong();
bool SetSubsong(int subsong) override;
bool Start() override;
SoundStreamInfoEx GetFormatEx() override;
protected:
bool GetData(void *buffer, size_t len) override;
};
XMPSong::XMPSong(xmp_context ctx, int rate)
{
context = ctx;
samplerate = (dumbConfig.mod_samplerate != 0) ? dumbConfig.mod_samplerate : rate;
xmp_set_player(context, XMP_PLAYER_VOLUME, 100);
xmp_set_player(context, XMP_PLAYER_INTERP, dumbConfig.mod_interp);
int16_buffer.reserve(16 * 1024);
}
XMPSong::~XMPSong()
{
xmp_end_player(context);
xmp_free_context(context);
}
SoundStreamInfoEx XMPSong::GetFormatEx()
{
return { 32 * 1024, samplerate, SampleType_Float32, ChannelConfig_Stereo };
}
bool XMPSong::SetSubsong(int subsong)
{
this->subsong = subsong;
if (xmp_get_player(context, XMP_PLAYER_STATE) >= XMP_STATE_PLAYING)
return xmp_set_position(context, subsong) >= 0;
return true;
}
bool XMPSong::GetData(void *buffer, size_t len)
{
if ((len / 4) > int16_buffer.size())
int16_buffer.resize(len / 4);
int ret = xmp_play_buffer(context, (void*)int16_buffer.data(), len / 2, m_Looping? INT_MAX : 0);
xmp_set_player(context, XMP_PLAYER_INTERP, dumbConfig.mod_interp);
if (ret >= 0)
{
float* soundbuffer = (float*)buffer;
for (unsigned int i = 0; i < len / 4; i++)
{
soundbuffer[i] = ((int16_buffer[i] < 0.) ? (int16_buffer[i] / 32768.) : (int16_buffer[i] / 32767.)) * dumbConfig.mod_dumb_mastervolume;
}
}
if (ret < 0 && m_Looping)
{
xmp_restart_module(context);
xmp_set_position(context, subsong);
return true;
}
return ret >= 0;
}
bool XMPSong::Start()
{
int ret = xmp_start_player(context, samplerate, 0);
if (ret >= 0)
xmp_set_position(context, subsong);
return ret >= 0;
}
StreamSource* XMP_OpenSong(MusicIO::FileInterface* reader, int samplerate)
{
if (xmp_test_module_from_callbacks((void*)reader, callbacks, nullptr) < 0)
return nullptr;
xmp_context ctx = xmp_create_context();
if (!ctx)
return nullptr;
reader->seek(0, SEEK_SET);
if (xmp_load_module_from_callbacks(ctx, (void*)reader, callbacks) < 0)
{
return nullptr;
}
return new XMPSong(ctx, samplerate);
}

View file

@ -0,0 +1,159 @@
/*
** music_opl.cpp
** Plays raw OPL formats
**
**---------------------------------------------------------------------------
** Copyright 1998-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.
**---------------------------------------------------------------------------
*/
#include "zmusic_internal.h"
#ifdef HAVE_OPL
#include <stdexcept>
#include "streamsource.h"
#include "oplsynth/opl.h"
#include "oplsynth/opl_mus_player.h"
#include "fileio.h"
#include "zmusic/midiconfig.h"
//==========================================================================
//
// OPL file played by a software OPL2 synth and streamed through the sound system
//
//==========================================================================
class OPLMUSSong : public StreamSource
{
public:
OPLMUSSong (MusicIO::FileInterface *reader, OPLConfig *config);
~OPLMUSSong ();
bool Start() override;
void ChangeSettingInt(const char *name, int value) override;
SoundStreamInfoEx GetFormatEx() override;
protected:
bool GetData(void *buffer, size_t len) override;
OPLmusicFile *Music;
int current_opl_core;
};
//==========================================================================
//
//
//
//==========================================================================
OPLMUSSong::OPLMUSSong(MusicIO::FileInterface* reader, OPLConfig* config)
{
const char* error = nullptr;
reader->seek(0, SEEK_END);
auto fs = reader->tell();
reader->seek(0, SEEK_SET);
std::vector<uint8_t> data(fs);
reader->read(data.data(), (int)data.size());
Music = new OPLmusicFile(data.data(), data.size(), config->core, config->numchips, error);
if (error)
{
delete Music;
throw std::runtime_error(error);
}
current_opl_core = config->core;
}
//==========================================================================
//
//
//
//==========================================================================
SoundStreamInfoEx OPLMUSSong::GetFormatEx()
{
int samples = int(OPL_SAMPLE_RATE / 14);
return { samples * 4, int(OPL_SAMPLE_RATE), SampleType_Float32,
current_opl_core == 0? ChannelConfig_Mono:ChannelConfig_Stereo };
}
//==========================================================================
//
//
//
//==========================================================================
OPLMUSSong::~OPLMUSSong ()
{
if (Music != NULL)
{
delete Music;
}
}
//==========================================================================
//
//
//
//==========================================================================
void OPLMUSSong::ChangeSettingInt(const char * name, int val)
{
if (!strcmp(name, "opl.numchips"))
Music->ResetChips (val);
}
//==========================================================================
//
//
//
//==========================================================================
bool OPLMUSSong::Start()
{
Music->SetLooping (m_Looping);
Music->Restart ();
return true;
}
//==========================================================================
//
//
//
//==========================================================================
bool OPLMUSSong::GetData(void *buffer, size_t len)
{
return Music->ServiceStream(buffer, int(len)) ? len : 0;
}
StreamSource *OPL_OpenSong(MusicIO::FileInterface* reader, OPLConfig *config)
{
return new OPLMUSSong(reader, config);
}
#endif

View file

@ -0,0 +1,354 @@
#include <algorithm>
#include "streamsource.h"
#include "fileio.h"
/**
* PlayStation XA (ADPCM) source support for MultiVoc
* Adapted and remixed from superxa2wav
*
* taken from EDuke32 and adapted for GZDoom by Christoph Oelckers
*/
//#define NO_XA_HEADER
enum
{
kNumOfSamples = 224,
kNumOfSGs = 18,
TTYWidth = 80,
kBufSize = (kNumOfSGs*kNumOfSamples),
kSamplesMono = (kNumOfSGs*kNumOfSamples),
kSamplesStereo = (kNumOfSGs*kNumOfSamples/2),
/* ADPCM */
XA_DATA_START = (0x44-48)
};
inline float constexpr DblToPCMF(double dt) { return float(dt) * (1.f/32768.f); }
typedef struct {
MusicIO::FileInterface *reader;
size_t committed;
size_t length;
bool blockIsMono;
bool blockIs18K;
bool finished;
double t1, t2;
double t1_x, t2_x;
float block[kBufSize];
} xa_data;
typedef int8_t SoundGroup[128];
typedef struct XASector {
int8_t sectorFiller[48];
SoundGroup SoundGroups[18];
} XASector;
static double K0[4] = {
0.0,
0.9375,
1.796875,
1.53125
};
static double K1[4] = {
0.0,
0.0,
-0.8125,
-0.859375
};
static int8_t getSoundData(int8_t *buf, int32_t unit, int32_t sample)
{
int8_t ret;
int8_t *p;
int32_t offset, shift;
p = buf;
shift = (unit%2) * 4;
offset = 16 + (unit / 2) + (sample * 4);
p += offset;
ret = (*p >> shift) & 0x0F;
if (ret > 7) {
ret -= 16;
}
return ret;
}
static int8_t getFilter(const int8_t *buf, int32_t unit)
{
return (*(buf + 4 + unit) >> 4) & 0x03;
}
static int8_t getRange(const int8_t *buf, int32_t unit)
{
return *(buf + 4 + unit) & 0x0F;
}
static void decodeSoundSectMono(XASector *ssct, xa_data * xad)
{
size_t count = 0;
int8_t snddat, filt, range;
int32_t unit, sample;
int32_t sndgrp;
double tmp2, tmp3, tmp4, tmp5;
auto &decodeBuf = xad->block;
for (sndgrp = 0; sndgrp < kNumOfSGs; sndgrp++)
{
for (unit = 0; unit < 8; unit++)
{
range = getRange(ssct->SoundGroups[sndgrp], unit);
filt = getFilter(ssct->SoundGroups[sndgrp], unit);
for (sample = 0; sample < 28; sample++)
{
snddat = getSoundData(ssct->SoundGroups[sndgrp], unit, sample);
tmp2 = (double)(1 << (12 - range));
tmp3 = (double)snddat * tmp2;
tmp4 = xad->t1 * K0[filt];
tmp5 = xad->t2 * K1[filt];
xad->t2 = xad->t1;
xad->t1 = tmp3 + tmp4 + tmp5;
decodeBuf[count++] = DblToPCMF(xad->t1);
}
}
}
}
static void decodeSoundSectStereo(XASector *ssct, xa_data * xad)
{
size_t count = 0;
int8_t snddat, filt, range;
int8_t filt1, range1;
int32_t unit, sample;
int32_t sndgrp;
double tmp2, tmp3, tmp4, tmp5;
auto &decodeBuf = xad->block;
for (sndgrp = 0; sndgrp < kNumOfSGs; sndgrp++)
{
for (unit = 0; unit < 8; unit+= 2)
{
range = getRange(ssct->SoundGroups[sndgrp], unit);
filt = getFilter(ssct->SoundGroups[sndgrp], unit);
range1 = getRange(ssct->SoundGroups[sndgrp], unit+1);
filt1 = getFilter(ssct->SoundGroups[sndgrp], unit+1);
for (sample = 0; sample < 28; sample++)
{
// Channel 1
snddat = getSoundData(ssct->SoundGroups[sndgrp], unit, sample);
tmp2 = (double)(1 << (12 - range));
tmp3 = (double)snddat * tmp2;
tmp4 = xad->t1 * K0[filt];
tmp5 = xad->t2 * K1[filt];
xad->t2 = xad->t1;
xad->t1 = tmp3 + tmp4 + tmp5;
decodeBuf[count++] = DblToPCMF(xad->t1);
// Channel 2
snddat = getSoundData(ssct->SoundGroups[sndgrp], unit+1, sample);
tmp2 = (double)(1 << (12 - range1));
tmp3 = (double)snddat * tmp2;
tmp4 = xad->t1_x * K0[filt1];
tmp5 = xad->t2_x * K1[filt1];
xad->t2_x = xad->t1_x;
xad->t1_x = tmp3 + tmp4 + tmp5;
decodeBuf[count++] = DblToPCMF(xad->t1_x);
}
}
}
}
//==========================================================================
//
// Get one decoded block of data
//
//==========================================================================
static void getNextXABlock(xa_data *xad, bool looping )
{
XASector ssct;
int coding;
const int SUBMODE_REAL_TIME_SECTOR = (1 << 6);
const int SUBMODE_FORM = (1 << 5);
const int SUBMODE_AUDIO_DATA = (1 << 2);
do
{
size_t bytes = xad->length - xad->reader->tell();
if (sizeof(XASector) < bytes)
bytes = sizeof(XASector);
xad->reader->read(&ssct, (int)bytes);
}
while (ssct.sectorFiller[46] != (SUBMODE_REAL_TIME_SECTOR | SUBMODE_FORM | SUBMODE_AUDIO_DATA));
coding = ssct.sectorFiller[47];
xad->committed = 0;
xad->blockIsMono = (coding & 3) == 0;
xad->blockIs18K = (((coding >> 2) & 3) == 1);
if (!xad->blockIsMono)
{
decodeSoundSectStereo(&ssct, xad);
}
else
{
decodeSoundSectMono(&ssct, xad);
}
if (xad->length == xad->reader->tell())
{
if (looping)
{
xad->reader->seek(XA_DATA_START, SEEK_SET);
xad->t1 = xad->t2 = xad->t1_x = xad->t2_x = 0;
}
else
xad->finished = true;
}
xad->finished = false;
}
//==========================================================================
//
// XASong
//
//==========================================================================
class XASong : public StreamSource
{
public:
XASong(MusicIO::FileInterface *readr);
SoundStreamInfoEx GetFormatEx() override;
bool Start() override;
bool GetData(void *buffer, size_t len) override;
protected:
xa_data xad;
};
//==========================================================================
//
// XASong - Constructor
//
//==========================================================================
XASong::XASong(MusicIO::FileInterface * reader)
{
reader->seek(0, SEEK_END);
xad.length = reader->tell();
reader->seek(XA_DATA_START, SEEK_SET);
xad.reader = reader;
xad.t1 = xad.t2 = xad.t1_x = xad.t2_x = 0;
getNextXABlock(&xad, false);
}
SoundStreamInfoEx XASong::GetFormatEx()
{
auto SampleRate = xad.blockIs18K? 18900 : 37800;
return { 64*1024, SampleRate, SampleType_Float32, ChannelConfig_Stereo };
}
//==========================================================================
//
// XASong :: Play
//
//==========================================================================
bool XASong::Start()
{
if (xad.finished && m_Looping)
{
xad.reader->seek(XA_DATA_START, SEEK_SET);
xad.t1 = xad.t2 = xad.t1_x = xad.t2_x = 0;
xad.finished = false;
}
return true;
}
//==========================================================================
//
// XASong :: Read
//
//==========================================================================
bool XASong::GetData(void *vbuff, size_t len)
{
float *dest = (float*)vbuff;
while (len > 0)
{
auto ptr = xad.committed;
auto block = xad.block + ptr;
if (ptr < kBufSize)
{
// commit the data
if (xad.blockIsMono)
{
size_t numsamples = len / 8;
size_t availdata = kBufSize - ptr;
for(size_t tocopy = std::min(numsamples, availdata); tocopy > 0; tocopy--)
{
float f = *block++;
*dest++ = f;
*dest++ = f;
len -= 8;
ptr++;
}
xad.committed = ptr;
}
else
{
size_t availdata = (kBufSize - ptr) * 4;
size_t tocopy = std::min(availdata, len);
memcpy(dest, block, tocopy);
dest += tocopy / 4;
len -= tocopy;
xad.committed += tocopy / 4;
}
}
if (xad.finished)
{
memset(dest, 0, len);
return true;
}
if (len > 0)
{
// we ran out of data and need more
getNextXABlock(&xad, m_Looping);
// repeat until done.
}
else break;
}
return !xad.finished;
}
//==========================================================================
//
// XA_OpenSong
//
//==========================================================================
StreamSource *XA_OpenSong(MusicIO::FileInterface *reader)
{
return new XASong(reader);
}

View file

@ -0,0 +1,40 @@
#pragma once
// Anything streamed to the sound system as raw wave data, except MIDIs --------------------
#include <stdlib.h>
#include "zmusic/mididefs.h" // for StreamSourceInfo
#include "zmusic/midiconfig.h"
class StreamSource
{
protected:
bool m_Looping = true;
int m_OutputRate;
public:
StreamSource (int outputRate) { m_OutputRate = outputRate; }
virtual ~StreamSource () {}
virtual void SetPlayMode(bool looping) { m_Looping = looping; }
virtual bool Start() { return true; }
virtual bool SetPosition(unsigned position) { return false; }
virtual bool SetSubsong(int subsong) { return false; }
virtual bool GetData(void *buffer, size_t len) = 0;
virtual SoundStreamInfoEx GetFormatEx() = 0;
virtual std::string GetStats() { return ""; }
virtual void ChangeSettingInt(const char *name, int value) { }
virtual void ChangeSettingNum(const char *name, double value) { }
virtual void ChangeSettingString(const char *name, const char *value) { }
protected:
StreamSource() = default;
};
StreamSource *MOD_OpenSong(MusicIO::FileInterface* reader, int samplerate);
StreamSource *XMP_OpenSong(MusicIO::FileInterface* reader, int samplerate);
StreamSource* GME_OpenSong(MusicIO::FileInterface* reader, const char* fmt, int sample_rate);
StreamSource *SndFile_OpenSong(MusicIO::FileInterface* fr);
StreamSource* XA_OpenSong(MusicIO::FileInterface* reader);
StreamSource* OPL_OpenSong(MusicIO::FileInterface* reader, OPLConfig *config);