Squashed 'libraries/ZMusic/' content from commit ac3e232b00
git-subtree-dir: libraries/ZMusic git-subtree-split: ac3e232b001129c740b7b65196ae0e1b13b82513
This commit is contained in:
commit
9f3cb3d92e
852 changed files with 381622 additions and 0 deletions
1232
source/streamsources/music_dumb.cpp
Normal file
1232
source/streamsources/music_dumb.cpp
Normal file
File diff suppressed because it is too large
Load diff
374
source/streamsources/music_gme.cpp
Normal file
374
source/streamsources/music_gme.cpp
Normal 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);
|
||||
}
|
||||
568
source/streamsources/music_libsndfile.cpp
Normal file
568
source/streamsources/music_libsndfile.cpp
Normal 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;
|
||||
}
|
||||
180
source/streamsources/music_libxmp.cpp
Normal file
180
source/streamsources/music_libxmp.cpp
Normal 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);
|
||||
}
|
||||
|
||||
159
source/streamsources/music_opl.cpp
Normal file
159
source/streamsources/music_opl.cpp
Normal 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
|
||||
354
source/streamsources/music_xa.cpp
Normal file
354
source/streamsources/music_xa.cpp
Normal 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);
|
||||
}
|
||||
|
||||
40
source/streamsources/streamsource.h
Normal file
40
source/streamsources/streamsource.h
Normal 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);
|
||||
Loading…
Add table
Add a link
Reference in a new issue