- uncoupled the stream sources from the low level implementation. The entire setup had the stream sources depend on the SoundStream class, severely limiting reusability. This was changed that there is one SoundStream class that uses the StreamSources as mere data source that has no knowledge and no connection to the underlying system, similar to how the MIDI system works. With this there are only 3 top level music classes left - MIDIStreamer, StreamSong and CDSong.

Also made the decode_vorbis function in DUMB a function pointer so that the library does not depend on high level code and can just ignore the vorbis case if no supported.
This commit is contained in:
Christoph Oelckers 2019-09-28 20:33:25 +02:00
parent cfe89ef6e6
commit d94b63b486
17 changed files with 389 additions and 379 deletions

View file

@ -802,6 +802,8 @@ DUH *make_duh(
void DUMBEXPORT duh_set_length(DUH *duh, int32 length);
extern short *(*dumb_decode_vorbis)(int outlen, const void *oggstream, int sizebytes);
#ifdef __cplusplus
}

View file

@ -28,7 +28,7 @@
#include <stdlib.h>
#include <assert.h>
extern short *DUMBCALLBACK dumb_decode_vorbis(int outlen, const void *oggstream, int sizebytes);
short * (*dumb_decode_vorbis)(int outlen, const void *oggstream, int sizebytes);
/** TODO:
@ -830,7 +830,7 @@ static int it_xm_read_sample_data(IT_SAMPLE *sample, unsigned char roguebytes, D
sample->loop_start >>= 1;
sample->loop_end >>= 1;
}
output = dumb_decode_vorbis(outlen, (char *)sample->data + 4, datasizebytes - 4);
output = dumb_decode_vorbis? dumb_decode_vorbis(outlen, (char *)sample->data + 4, datasizebytes - 4) : NULL;
if (output != NULL)
{
free(sample->data);

View file

@ -20,14 +20,6 @@ struct MidiHeader
MidiHeader *lpNext;
};
struct SoundStreamInfo
{
// Format is always 32 bit float. If mBufferSize is 0, the song doesn't use streaming but plays through a different interface.
int mBufferSize;
int mSampleRate;
int mNumChannels;
};
class MIDIDevice
{
public:

View file

@ -44,3 +44,12 @@ enum EMidiDevice
MDEV_COUNT
};
struct SoundStreamInfo
{
// Format is always 32 bit float. If mBufferSize is 0, the song doesn't use streaming but plays through a different interface.
int mBufferSize;
int mSampleRate;
int mNumChannels;
};

View file

@ -61,7 +61,6 @@ EXTERN_CVAR (Bool, snd_pitched)
EXTERN_CVAR (Color, am_wallcolor)
EXTERN_CVAR (Color, am_fdwallcolor)
EXTERN_CVAR (Color, am_cdwallcolor)
EXTERN_CVAR (Float, spc_amp)
EXTERN_CVAR (Bool, wi_percents)
EXTERN_CVAR (Int, gl_texture_hqresizemode)
EXTERN_CVAR (Int, gl_texture_hqresizemult)
@ -324,6 +323,7 @@ void FGameConfigFile::DoGlobalSetup ()
vsync->ResetToDefault ();
}
}
/* spc_amp no longer exists
if (last < 206)
{ // spc_amp is now a float, not an int.
if (spc_amp > 16)
@ -331,6 +331,7 @@ void FGameConfigFile::DoGlobalSetup ()
spc_amp = spc_amp / 16.f;
}
}
*/
if (last < 207)
{ // Now that snd_midiprecache works again, you probably don't want it on.
FBaseCVar *precache = FindCVar ("snd_midiprecache", NULL);

View file

@ -379,20 +379,6 @@ void SoundRenderer::DrawWaveDebug(int mode)
{
}
SoundStream::~SoundStream ()
{
}
bool SoundStream::SetPosition(unsigned int pos)
{
return false;
}
bool SoundStream::SetOrder(int order)
{
return false;
}
FString SoundStream::GetStats()
{
return "No stream stats available.";

View file

@ -58,7 +58,7 @@ enum EStartSoundFlags
class SoundStream
{
public:
virtual ~SoundStream ();
virtual ~SoundStream () {}
enum
{ // For CreateStream
@ -77,8 +77,6 @@ public:
virtual bool SetPaused(bool paused) = 0;
virtual unsigned int GetPosition() = 0;
virtual bool IsEnded() = 0;
virtual bool SetPosition(unsigned int pos);
virtual bool SetOrder(int order);
virtual FString GetStats();
};

View file

@ -47,8 +47,9 @@
#include "stats.h"
#include "vm.h"
#include "s_music.h"
#include "..//libraries/zmusic/midisources/midisource.h"
#include "../libraries/zmusic/midisources/midisource.h"
EXTERN_CVAR(Float, gme_stereodepth)
#define GZIP_ID1 31
@ -261,11 +262,6 @@ FString MusInfo::GetStats()
return "No stats available for this song";
}
MusInfo *MusInfo::GetOPLDumper(const char *filename)
{
return nullptr;
}
MusInfo *MusInfo::GetWaveDumper(const char *filename, int rate)
{
return nullptr;
@ -371,6 +367,7 @@ static EMIDIType IdentifyMIDIType(uint32_t *id, int size)
MusInfo *I_RegisterSong (FileReader &reader, MidiDeviceSetting *device)
{
MusInfo *info = nullptr;
StreamSource *streamsource = nullptr;
const char *fmt;
uint32_t id[32/4];
@ -446,26 +443,28 @@ MusInfo *I_RegisterSong (FileReader &reader, MidiDeviceSetting *device)
(id[0] == MAKE_ID('D','B','R','A') && id[1] == MAKE_ID('W','O','P','L')) || // DosBox Raw OPL
(id[0] == MAKE_ID('A','D','L','I') && *((uint8_t *)id + 4) == 'B')) // Martin Fernandez's modified IMF
{
info = new OPLMUSSong (reader, device != nullptr? device->args.GetChars() : "");
streamsource = OPL_OpenSong(reader, device != nullptr? device->args.GetChars() : "");
}
else if ((id[0] == MAKE_ID('R', 'I', 'F', 'F') && id[2] == MAKE_ID('C', 'D', 'X', 'A')))
{
info = XA_OpenSong(reader);
streamsource = XA_OpenSong(reader);
}
// Check for game music
else if ((fmt = GME_CheckFormat(id[0])) != nullptr && fmt[0] != '\0')
{
info = GME_OpenSong(reader, fmt);
streamsource = GME_OpenSong(reader, fmt, gme_stereodepth);
}
// Check for module formats
else
{
info = MOD_OpenSong(reader);
streamsource = MOD_OpenSong(reader);
}
if (info == nullptr)
if (info == nullptr && streamsource == nullptr)
{
info = SndFile_OpenSong(reader);
streamsource = SndFile_OpenSong(reader);
}
if (streamsource) info = OpenStreamSong(streamsource);
if (info == nullptr)
{

View file

@ -78,7 +78,6 @@ public:
virtual void Update();
virtual int GetDeviceType() const { return MDEV_DEFAULT; } // MDEV_DEFAULT stands in for anything that cannot change playback parameters which needs a restart.
virtual FString GetStats();
virtual MusInfo *GetOPLDumper(const char *filename);
virtual MusInfo *GetWaveDumper(const char *filename, int rate);
virtual void ChangeSettingInt(const char *setting, int value); // FluidSynth settings
virtual void ChangeSettingNum(const char *setting, double value); // "

View file

@ -110,45 +110,31 @@ protected:
// Anything supported by the sound system out of the box --------------------
class StreamSong : public MusInfo
class StreamSource
{
protected:
bool m_Looping = true;
int m_OutputRate;
public:
StreamSong (FileReader &reader);
~StreamSong ();
void Play (bool looping, int subsong);
void Pause ();
void Resume ();
void Stop ();
bool IsPlaying ();
bool IsValid () const { return m_Stream != NULL; }
bool SetPosition (unsigned int pos);
bool SetSubsong (int subsong);
FString GetStats();
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 SoundStreamInfo GetFormat() { return {65536, m_OutputRate, 2 }; } // Default format is: System's output sample rate, 32 bit float, stereo
virtual FString 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:
StreamSong () : m_Stream(NULL) {}
SoundStream *m_Stream;
StreamSource() = default;
};
// MUS file played by a software OPL2 synth and streamed through the sound system
class OPLMUSSong : public StreamSong
{
public:
OPLMUSSong (FileReader &reader, const char *args);
~OPLMUSSong ();
void Play (bool looping, int subsong);
bool IsPlaying ();
bool IsValid () const;
void ChangeSettingInt (const char *, int) override;
protected:
static bool FillStream (SoundStream *stream, void *buff, int len, void *userdata);
OPLmusicFile *Music;
};
// CD track/disk played through the multimedia system -----------------------
@ -191,14 +177,17 @@ bool WildMidi_SetupConfig(WildMidiConfig* config, const char* args);
// Module played via foo_dumb -----------------------------------------------
MusInfo *MOD_OpenSong(FileReader &reader);
StreamSource *MOD_OpenSong(FileReader &reader);
StreamSource *GME_OpenSong(FileReader &reader, const char *fmt, float depth);
StreamSource *SndFile_OpenSong(FileReader &fr);
StreamSource* XA_OpenSong(FileReader& reader);
StreamSource *OPL_OpenSong(FileReader &reader, const char *args);
// Music played via Game Music Emu ------------------------------------------
// stream song ------------------------------------------
MusInfo *OpenStreamSong(StreamSource *source);
const char *GME_CheckFormat(uint32_t header);
MusInfo *GME_OpenSong(FileReader &reader, const char *fmt);
MusInfo *SndFile_OpenSong(FileReader &fr);
MusInfo* XA_OpenSong(FileReader& reader);
// --------------------------------------------------------------------------

View file

@ -915,3 +915,11 @@ bool WildMidi_SetupConfig(WildMidiConfig* config, const char* args)
// This one is for Win32 MMAPI.
CVAR(Bool, snd_midiprecache, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG);
// GME
CUSTOM_CVAR(Float, gme_stereodepth, 0.f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
if (currSong != nullptr)
currSong->ChangeSettingNum("GME.stereodepth", *self);
}

View file

@ -44,19 +44,31 @@
#include "../dumb/include/dumb.h"
#include "../dumb/include/internal/it.h"
//==========================================================================
//
// dumb_decode_vorbis
//
//==========================================================================
static short *DUMBCALLBACK dumb_decode_vorbis_(int outlen, const void *oggstream, int sizebytes)
{
return GSnd->DecodeSample(outlen, oggstream, sizebytes, CODEC_Vorbis);
}
// MACROS ------------------------------------------------------------------
// TYPES -------------------------------------------------------------------
class input_mod : public StreamSong
class DumbSong : public StreamSource
{
public:
input_mod(DUH *myduh);
~input_mod();
DumbSong(DUH *myduh);
~DumbSong();
//bool SetPosition(int ms);
bool SetSubsong(int subsong);
void Play(bool looping, int subsong);
FString GetStats();
bool SetSubsong(int subsong) override;
bool Start() override;
SoundStreamInfo GetFormat() override;
FString GetStats() override;
FString Codec;
FString TrackerVersion;
@ -71,6 +83,7 @@ protected:
double delta;
double length;
bool eof;
bool started = false;
size_t written;
DUH *duh;
DUH_SIGRENDERER *sr;
@ -79,7 +92,8 @@ protected:
bool open2(long pos);
long render(double volume, double delta, long samples, sample_t **buffer);
int decode_run(void *buffer, unsigned int size);
static bool read(SoundStream *stream, void *buff, int len, void *userdata);
bool GetData(void *buffer, size_t len) override;
};
#pragma pack(1)
@ -165,7 +179,7 @@ static inline uint64_t time_to_samples(double p_time,int p_sample_rate)
//
//==========================================================================
static void ReadDUH(DUH * duh, input_mod *info, bool meta, bool dos)
static void ReadDUH(DUH * duh, DumbSong *info, bool meta, bool dos)
{
if (!duh) return;
@ -249,7 +263,7 @@ static void ReadDUH(DUH * duh, input_mod *info, bool meta, bool dos)
//
//==========================================================================
static bool ReadIT(const uint8_t * ptr, unsigned size, input_mod *info, bool meta)
static bool ReadIT(const uint8_t * ptr, unsigned size, DumbSong *info, bool meta)
{
PITFILEHEADER pifh = (PITFILEHEADER) ptr;
if ((!ptr) || (size < 0x100)) return false;
@ -759,7 +773,7 @@ static void MOD_SetAutoChip(DUH *duh)
//
//==========================================================================
MusInfo *MOD_OpenSong(FileReader &reader)
StreamSource *MOD_OpenSong(FileReader &reader)
{
DUH *duh = 0;
int headsize;
@ -770,7 +784,7 @@ MusInfo *MOD_OpenSong(FileReader &reader)
};
dumbfile_mem_status filestate;
DUMBFILE *f = NULL;
input_mod *state = NULL;
DumbSong *state = NULL;
bool is_it = false;
bool is_dos = true;
@ -920,17 +934,10 @@ MusInfo *MOD_OpenSong(FileReader &reader)
{
MOD_SetAutoChip(duh);
}
state = new input_mod(duh);
if (!state->IsValid())
{
delete state;
state = NULL;
}
else
{
if (is_it) ReadIT(filestate.ptr, size, state, false);
else ReadDUH(duh, state, false, is_dos);
}
state = new DumbSong(duh);
if (is_it) ReadIT(filestate.ptr, size, state, false);
else ReadDUH(duh, state, false, is_dos);
}
else
{
@ -946,22 +953,22 @@ MusInfo *MOD_OpenSong(FileReader &reader)
//==========================================================================
//
// input_mod :: read static
// DumbSong :: read static
//
//==========================================================================
bool input_mod::read(SoundStream *stream, void *buffer, int sizebytes, void *userdata)
bool DumbSong::GetData(void *buffer, size_t sizebytes)
{
input_mod *state = (input_mod *)userdata;
if (state->eof)
if (eof)
{
memset(buffer, 0, sizebytes);
return false;
}
std::lock_guard<std::mutex> lock(state->crit_sec);
std::lock_guard<std::mutex> lock_(crit_sec);
while (sizebytes > 0)
{
int written = state->decode_run(buffer, sizebytes / 8);
int written = decode_run(buffer, (unsigned)sizebytes / 8);
if (written < 0)
{
return false;
@ -987,12 +994,13 @@ bool input_mod::read(SoundStream *stream, void *buffer, int sizebytes, void *use
//==========================================================================
//
// input_mod constructor
// DumbSong constructor
//
//==========================================================================
input_mod::input_mod(DUH *myduh)
DumbSong::DumbSong(DUH *myduh)
{
dumb_decode_vorbis = dumb_decode_vorbis_;
duh = myduh;
sr = NULL;
eof = false;
@ -1009,57 +1017,60 @@ input_mod::input_mod(DUH *myduh)
{
srate = (int)GSnd->GetOutputRate();
}
m_Stream = GSnd->CreateStream(read, 32*1024, SoundStream::Float, srate, this);
delta = 65536.0 / srate;
}
//==========================================================================
//
// input_mod destructor
// DumbSong destructor
//
//==========================================================================
input_mod::~input_mod()
DumbSong::~DumbSong()
{
Stop();
if (m_Stream != NULL)
{
delete m_Stream;
m_Stream = NULL;
}
if (sr) duh_end_sigrenderer(sr);
if (duh) unload_duh(duh);
}
//==========================================================================
//
// input_mod :: Play
// DumbSong GetFormat
//
//==========================================================================
void input_mod::Play(bool looping, int order)
SoundStreamInfo DumbSong::GetFormat()
{
m_Status = STATE_Stopped;
m_Looping = looping;
start_order = order;
if (open2(0) && m_Stream->Play(m_Looping, 1))
{
m_Status = STATE_Playing;
}
return { 32*1024, srate, 2 };
}
//==========================================================================
//
// input_mod :: SetSubsong
// DumbSong :: Play
//
//==========================================================================
bool input_mod::SetSubsong(int order)
bool DumbSong::Start()
{
started = open2(0);
return started;
}
//==========================================================================
//
// DumbSong :: SetSubsong
//
//==========================================================================
bool DumbSong::SetSubsong(int order)
{
if (order == start_order)
{
return false;
return true;
}
if (!started)
{
start_order = order;
return true;
}
std::lock_guard<std::mutex> lock(crit_sec);
DUH_SIGRENDERER *oldsr = sr;
@ -1076,11 +1087,11 @@ bool input_mod::SetSubsong(int order)
//==========================================================================
//
// input_mod :: open2
// DumbSong :: open2
//
//==========================================================================
bool input_mod::open2(long pos)
bool DumbSong::open2(long pos)
{
if (start_order != 0)
{
@ -1110,11 +1121,11 @@ bool input_mod::open2(long pos)
//==========================================================================
//
// input_mod :: render
// DumbSong :: render
//
//==========================================================================
long input_mod::render(double volume, double delta, long samples, sample_t **buffer)
long DumbSong::render(double volume, double delta, long samples, sample_t **buffer)
{
long written = duh_sigrenderer_generate_samples(sr, volume, delta, samples, buffer);
@ -1139,18 +1150,19 @@ long input_mod::render(double volume, double delta, long samples, sample_t **buf
//==========================================================================
//
// input_mod :: decode_run
// DumbSong :: decode_run
//
// Given a buffer of 32-bit PCM stereo pairs and a size specified in
// samples, returns the number of samples written to the buffer.
//
//==========================================================================
int input_mod::decode_run(void *buffer, unsigned int size)
int DumbSong::decode_run(void *buffer, unsigned int size)
{
if (eof) return 0;
DUMB_IT_SIGRENDERER *itsr = duh_get_it_sigrenderer(sr);
if (itsr == nullptr) return 0;
int dt = int(delta * 65536.0 + 0.5);
long samples = long((((LONG_LONG)itsr->time_left << 16) | itsr->sub_time_left) / dt);
if (samples == 0 || samples > (long)size) samples = size;
@ -1170,11 +1182,11 @@ retry:
//==========================================================================
//
// input_mod :: GetStats
// DumbSong :: GetStats
//
//==========================================================================
FString input_mod::GetStats()
FString DumbSong::GetStats()
{
//return StreamSong::GetStats();
DUMB_IT_SIGRENDERER *itsr = duh_get_it_sigrenderer(sr);
@ -1211,13 +1223,3 @@ FString input_mod::GetStats()
return out;
}
//==========================================================================
//
// dumb_decode_vorbis
//
//==========================================================================
extern "C" short *DUMBCALLBACK dumb_decode_vorbis(int outlen, const void *oggstream, int sizebytes)
{
return GSnd->DecodeSample(outlen, oggstream, sizebytes, CODEC_Vorbis);
}

View file

@ -37,6 +37,7 @@
// Uncomment if you are using the DLL version of GME.
//#define GME_DLL
#include <algorithm>
#include "i_musicinterns.h"
#include <gme/gme.h>
#include <mutex>
@ -47,14 +48,17 @@
// TYPES -------------------------------------------------------------------
class GMESong : public StreamSong
class GMESong : public StreamSource
{
public:
GMESong(Music_Emu *emu, int sample_rate);
~GMESong();
bool SetSubsong(int subsong);
void Play(bool looping, int subsong);
FString GetStats();
bool SetSubsong(int subsong) override;
bool Start() override;
void ChangeSettingNum(const char *name, double val) override;
FString GetStats() override;
bool GetData(void *buffer, size_t len) override;
SoundStreamInfo GetFormat() override;
protected:
std::mutex CritSec;
@ -62,13 +66,11 @@ protected:
gme_info_t *TrackInfo;
int SampleRate;
int CurrTrack;
bool started = false;
bool StartTrack(int track, bool getcritsec=true);
bool GetTrackInfo();
int CalcSongLength();
void GMEDepthChanged(float val);
static bool Read(SoundStream *stream, void *buff, int len, void *userdata);
};
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
@ -82,13 +84,6 @@ protected:
// PUBLIC DATA DEFINITIONS -------------------------------------------------
// Currently not used.
CVAR (Float, spc_amp, 1.875f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
CUSTOM_CVAR(Float, gme_stereodepth, 0.f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
{
if (currSong != nullptr)
currSong->GMEDepthChanged(self);
}
// PRIVATE DATA DEFINITIONS ------------------------------------------------
@ -111,7 +106,7 @@ const char *GME_CheckFormat(uint32_t id)
//
//==========================================================================
MusInfo *GME_OpenSong(FileReader &reader, const char *fmt)
StreamSource *GME_OpenSong(FileReader &reader, const char *fmt, float stereo_depth)
{
gme_type_t type;
gme_err_t err;
@ -152,7 +147,7 @@ MusInfo *GME_OpenSong(FileReader &reader, const char *fmt)
reader.Seek(fpos, FileReader::SeekSet);
return nullptr;
}
gme_set_stereo_depth(emu, clamp(*gme_stereodepth, 0.f, 1.f));
gme_set_stereo_depth(emu, std::min(std::max(stereo_depth, 0.f), 1.f));
gme_set_fade(emu, -1); // Enable infinite loop
return new GMESong(emu, sample_rate);
}
@ -169,7 +164,12 @@ GMESong::GMESong(Music_Emu *emu, int sample_rate)
SampleRate = sample_rate;
CurrTrack = 0;
TrackInfo = NULL;
m_Stream = GSnd->CreateStream(Read, 32*1024, 0, sample_rate, this);
}
SoundStreamInfo GMESong::GetFormat()
{
return { 32*1024, SampleRate, -2 };
}
//==========================================================================
@ -180,12 +180,6 @@ GMESong::GMESong(Music_Emu *emu, int sample_rate)
GMESong::~GMESong()
{
Stop();
if (m_Stream != NULL)
{
delete m_Stream;
m_Stream = NULL;
}
if (TrackInfo != NULL)
{
gme_free_info(TrackInfo);
@ -203,29 +197,26 @@ GMESong::~GMESong()
//
//==========================================================================
void GMESong::GMEDepthChanged(float val)
void GMESong::ChangeSettingNum(const char *name, double val)
{
if (Emu != nullptr)
gme_set_stereo_depth(Emu, clamp(val, 0.f, 1.f));
if (Emu != nullptr && !stricmp(name, "gme.stereodepth"))
{
gme_set_stereo_depth(Emu, clamp((float)val, 0.f, 1.f));
}
}
//==========================================================================
//
// GMESong :: Play
//
//==========================================================================
void GMESong::Play(bool looping, int track)
bool GMESong::Start()
{
m_Status = STATE_Stopped;
m_Looping = looping;
if (StartTrack(track) && m_Stream->Play(looping, 1))
{
m_Status = STATE_Playing;
}
return StartTrack(CurrTrack);
}
//==========================================================================
//
// GMESong :: SetSubsong
@ -236,7 +227,12 @@ bool GMESong::SetSubsong(int track)
{
if (CurrTrack == track)
{
return false;
return true;
}
if (!started)
{
CurrTrack = track;
return true;
}
return StartTrack(track);
}
@ -353,24 +349,23 @@ int GMESong::CalcSongLength()
//
//==========================================================================
bool GMESong::Read(SoundStream *stream, void *buff, int len, void *userdata)
bool GMESong::GetData(void *buffer, size_t len)
{
gme_err_t err;
GMESong *song = (GMESong *)userdata;
std::lock_guard<std::mutex> lock(song->CritSec);
if (gme_track_ended(song->Emu))
std::lock_guard<std::mutex> lock(CritSec);
if (gme_track_ended(Emu))
{
if (song->m_Looping)
if (m_Looping)
{
song->StartTrack(song->CurrTrack, false);
StartTrack(CurrTrack, false);
}
else
{
memset(buff, 0, len);
memset(buffer, 0, len);
return false;
}
}
err = gme_play(song->Emu, len >> 1, (short *)buff);
err = gme_play(Emu, int(len >> 1), (short *)buffer);
return (err == NULL);
}

View file

@ -44,14 +44,14 @@
// TYPES -------------------------------------------------------------------
class SndFileSong : public StreamSong
class SndFileSong : public StreamSource
{
public:
SndFileSong(FileReader &reader, SoundDecoder *decoder, uint32_t loop_start, uint32_t loop_end, bool startass, bool endass);
~SndFileSong();
bool SetSubsong(int subsong);
void Play(bool looping, int subsong);
FString GetStats();
FString GetStats() override;
SoundStreamInfo GetFormat() override;
bool GetData(void *buffer, size_t len) override;
protected:
std::mutex CritSec;
@ -64,8 +64,6 @@ protected:
uint32_t Loop_End;
int CalcSongLength();
static bool Read(SoundStream *stream, void *buff, int len, void *userdata);
};
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
@ -267,7 +265,7 @@ void FindLoopTags(FileReader &fr, uint32_t *start, bool *startass, uint32_t *end
//
//==========================================================================
MusInfo *SndFile_OpenSong(FileReader &fr)
StreamSource *SndFile_OpenSong(FileReader &fr)
{
fr.Seek(0, FileReader::SeekSet);
@ -303,7 +301,11 @@ SndFileSong::SndFileSong(FileReader &reader, SoundDecoder *decoder, uint32_t loo
Reader = std::move(reader);
Decoder = decoder;
Channels = iChannels == ChannelConfig_Stereo? 2:1;
m_Stream = GSnd->CreateStream(Read, snd_streambuffersize * 1024, iChannels == ChannelConfig_Stereo? 0 : SoundStream::Mono, SampleRate, this);
}
SoundStreamInfo SndFileSong::GetFormat()
{
return { snd_streambuffersize * 1024, SampleRate, -Channels };
}
//==========================================================================
@ -314,46 +316,12 @@ SndFileSong::SndFileSong(FileReader &reader, SoundDecoder *decoder, uint32_t loo
SndFileSong::~SndFileSong()
{
Stop();
if (m_Stream != nullptr)
{
delete m_Stream;
m_Stream = nullptr;
}
if (Decoder != nullptr)
{
delete Decoder;
}
}
//==========================================================================
//
// SndFileSong :: Play
//
//==========================================================================
void SndFileSong::Play(bool looping, int track)
{
m_Status = STATE_Stopped;
m_Looping = looping;
if (m_Stream->Play(looping, 1))
{
m_Status = STATE_Playing;
}
}
//==========================================================================
//
// SndFileSong :: SetSubsong
//
//==========================================================================
bool SndFileSong::SetSubsong(int track)
{
return false;
}
//==========================================================================
//
// SndFileSong :: GetStats
@ -384,19 +352,17 @@ FString SndFileSong::GetStats()
//
//==========================================================================
bool SndFileSong::Read(SoundStream *stream, void *vbuff, int ilen, void *userdata)
bool SndFileSong::GetData(void *vbuff, size_t len)
{
char *buff = (char*)vbuff;
SndFileSong *song = (SndFileSong *)userdata;
std::lock_guard<std::mutex> lock(song->CritSec);
std::lock_guard<std::mutex> lock(CritSec);
size_t len = size_t(ilen);
size_t currentpos = song->Decoder->getSampleOffset();
size_t framestoread = len / (song->Channels*2);
size_t currentpos = Decoder->getSampleOffset();
size_t framestoread = len / (Channels*2);
bool err = false;
if (!song->m_Looping)
if (!m_Looping)
{
size_t maxpos = song->Decoder->getSampleLength();
size_t maxpos = Decoder->getSampleLength();
if (currentpos == maxpos)
{
memset(buff, 0, len);
@ -404,36 +370,36 @@ bool SndFileSong::Read(SoundStream *stream, void *vbuff, int ilen, void *userdat
}
if (currentpos + framestoread > maxpos)
{
size_t got = song->Decoder->read(buff, (maxpos - currentpos) * song->Channels * 2);
size_t got = Decoder->read(buff, (maxpos - currentpos) * Channels * 2);
memset(buff + got, 0, len - got);
}
else
{
size_t got = song->Decoder->read(buff, len);
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 > song->Loop_End)
if (currentpos + framestoread > Loop_End)
{
// Loop can be very short, make sure the current position doesn't exceed it
if (currentpos < song->Loop_End)
if (currentpos < Loop_End)
{
size_t endblock = (song->Loop_End - currentpos) * song->Channels * 2;
size_t endlen = song->Decoder->read(buff, endblock);
size_t endblock = (Loop_End - currentpos) * Channels * 2;
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;
}
song->Decoder->seek(song->Loop_Start, false, true);
Decoder->seek(Loop_Start, false, true);
}
while (len > 0)
{
size_t readlen = song->Decoder->read(buff, len);
size_t readlen = Decoder->read(buff, len);
if (readlen == 0)
{
return false;
@ -442,7 +408,7 @@ bool SndFileSong::Read(SoundStream *stream, void *vbuff, int ilen, void *userdat
len -= readlen;
if (len > 0)
{
song->Decoder->seek(song->Loop_Start, false, true);
Decoder->seek(Loop_Start, false, true);
}
}
}

View file

@ -35,59 +35,78 @@
#include "../libraries/oplsynth/oplsynth/opl.h"
#include "../libraries/oplsynth/oplsynth/opl_mus_player.h"
static bool OPL_Active;
EXTERN_CVAR (Int, opl_numchips)
EXTERN_CVAR(Int, opl_core)
int getOPLCore(const char* args)
{
int current_opl_core = opl_core;
if (args != NULL && *args >= '0' && *args < '4') current_opl_core = *args - '0';
return current_opl_core;
}
//==========================================================================
//
// OPL file played by a software OPL2 synth and streamed through the sound system
//
//==========================================================================
class OPLMUSSong : public StreamSource
{
public:
OPLMUSSong (FileReader &reader, const char *args);
~OPLMUSSong ();
bool Start() override;
void ChangeSettingInt(const char *name, int value) override;
SoundStreamInfo GetFormat() override;
protected:
bool GetData(void *buffer, size_t len) override;
OPLmusicFile *Music;
int current_opl_core;
};
//==========================================================================
//
//
//
//==========================================================================
OPLMUSSong::OPLMUSSong (FileReader &reader, const char *args)
{
int samples = int(OPL_SAMPLE_RATE / 14);
const char* error;
current_opl_core = opl_core;
if (args != NULL && *args >= '0' && *args < '4') current_opl_core = *args - '0';
int core = getOPLCore(args);
auto data = reader.Read();
Music = new OPLmusicFile (data.Data(), data.Size(), core, opl_numchips, error);
if (!Music->IsValid())
{
Printf(PRINT_BOLD, "%s", error? error : "Invalid OPL format\n");
delete Music;
return;
}
m_Stream = GSnd->CreateStream (FillStream, samples*4,
(core == 0 ? SoundStream::Mono : 0) | SoundStream::Float, int(OPL_SAMPLE_RATE), this);
if (m_Stream == NULL)
{
Printf (PRINT_BOLD, "Could not create music stream.\n");
delete Music;
return;
}
OPL_Active = true;
Music = nullptr ;// new OPLmusicFile(reader, current_opl_core);
}
//==========================================================================
//
//
//
//==========================================================================
SoundStreamInfo OPLMUSSong::GetFormat()
{
int samples = int(OPL_SAMPLE_RATE / 14);
return { samples * 4, int(OPL_SAMPLE_RATE), current_opl_core == 0? 1:2 };
}
//==========================================================================
//
//
//
//==========================================================================
OPLMUSSong::~OPLMUSSong ()
{
OPL_Active = false;
Stop ();
if (Music != NULL)
{
delete Music;
}
}
bool OPLMUSSong::IsValid () const
{
return m_Stream != NULL;
}
//==========================================================================
//
//
//
//==========================================================================
void OPLMUSSong::ChangeSettingInt(const char * name, int val)
{
@ -95,28 +114,31 @@ void OPLMUSSong::ChangeSettingInt(const char * name, int val)
Music->ResetChips (val);
}
bool OPLMUSSong::IsPlaying ()
{
return m_Status == STATE_Playing;
}
//==========================================================================
//
//
//
//==========================================================================
void OPLMUSSong::Play (bool looping, int subsong)
bool OPLMUSSong::Start()
{
m_Status = STATE_Stopped;
m_Looping = looping;
Music->SetLooping (looping);
Music->SetLooping (m_Looping);
Music->Restart ();
if (m_Stream == NULL || m_Stream->Play (true, snd_musicvolume))
{
m_Status = STATE_Playing;
}
return true;
}
bool OPLMUSSong::FillStream (SoundStream *stream, void *buff, int len, void *userdata)
//==========================================================================
//
//
//
//==========================================================================
bool OPLMUSSong::GetData(void *buffer, size_t len)
{
OPLMUSSong *song = (OPLMUSSong *)userdata;
return song->Music->ServiceStream (buff, len);
return Music->ServiceStream(buffer, int(len)) ? len : 0;
}
StreamSource *OPL_OpenSong(FileReader &reader, const char *args)
{
return new OPLMUSSong(reader, args);
}

View file

@ -1,9 +1,10 @@
/*
** music_stream.cpp
** Wrapper around the sound system's streaming feature for music.
** Plays a streaming song from a StreamSource
**
**---------------------------------------------------------------------------
** Copyright 2008 Randy Heit
** Copyright 2019 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
@ -33,18 +34,50 @@
#include "i_musicinterns.h"
class StreamSong : public MusInfo
{
public:
StreamSong (StreamSource *source);
~StreamSong ();
void Play (bool looping, int subsong) override;
void Pause () override;
void Resume () override;
void Stop () override;
bool IsPlaying () override;
bool IsValid () const override { return m_Stream != nullptr && m_Source != nullptr; }
bool SetPosition (unsigned int pos) override;
bool SetSubsong (int subsong) override;
FString GetStats() override;
void ChangeSettingInt(const char *name, int value) override { if (m_Source) m_Source->ChangeSettingInt(name, value); }
void ChangeSettingNum(const char *name, double value) override { if (m_Source) m_Source->ChangeSettingNum(name, value); }
void ChangeSettingString(const char *name, const char *value) override { if(m_Source) m_Source->ChangeSettingString(name, value); }
protected:
SoundStream *m_Stream = nullptr;
StreamSource *m_Source = nullptr;
private:
static bool FillStream (SoundStream *stream, void *buff, int len, void *userdata);
};
void StreamSong::Play (bool looping, int subsong)
{
m_Status = STATE_Stopped;
m_Looping = looping;
if (m_Stream->Play (m_Looping, 1))
if (m_Stream != nullptr && m_Source != nullptr)
{
if (subsong != 0)
m_Source->SetPlayMode(looping);
m_Source->SetSubsong(subsong);
if (m_Source->Start())
{
m_Stream->SetOrder (subsong);
m_Status = STATE_Playing;
m_Stream->Play(m_Looping, 1);
}
m_Status = STATE_Playing;
}
}
@ -78,16 +111,26 @@ void StreamSong::Stop ()
StreamSong::~StreamSong ()
{
Stop ();
if (m_Stream != NULL)
if (m_Stream != nullptr)
{
delete m_Stream;
m_Stream = NULL;
m_Stream = nullptr;
}
if (m_Source != nullptr)
{
delete m_Source;
m_Source = nullptr;
}
}
StreamSong::StreamSong (FileReader &reader)
StreamSong::StreamSong (StreamSource *source)
{
m_Stream = GSnd->OpenStream (reader, SoundStream::Loop);
m_Source = source;
auto fmt = source->GetFormat();
int flags = fmt.mNumChannels < 0? 0 : SoundStream::Float;
if (abs(fmt.mNumChannels) < 2) flags |= SoundStream::Mono;
m_Stream = GSnd->CreateStream(FillStream, fmt.mBufferSize, flags, fmt.mSampleRate, this);
}
bool StreamSong::IsPlaying ()
@ -111,9 +154,9 @@ bool StreamSong::IsPlaying ()
bool StreamSong::SetPosition(unsigned int pos)
{
if (m_Stream != NULL)
if (m_Source != nullptr)
{
return m_Stream->SetPosition(pos);
return m_Source->SetPosition(pos);
}
else
{
@ -123,9 +166,9 @@ bool StreamSong::SetPosition(unsigned int pos)
bool StreamSong::SetSubsong(int subsong)
{
if (m_Stream != NULL)
if (m_Stream != nullptr)
{
return m_Stream->SetOrder(subsong);
return m_Source->SetSubsong(subsong);
}
else
{
@ -135,9 +178,39 @@ bool StreamSong::SetSubsong(int subsong)
FString StreamSong::GetStats()
{
FString s1, s2;
if (m_Stream != NULL)
{
return m_Stream->GetStats();
s1 = m_Stream->GetStats();
}
return "No song loaded\n";
if (m_Source != NULL)
{
s2 = m_Source->GetStats();
}
if (s1.IsEmpty() && s2.IsEmpty()) return "No song loaded\n";
if (s1.IsEmpty()) return s2;
if (s2.IsEmpty()) return s1;
return FStringf("%s\n%s", s1.GetChars(), s2.GetChars());
}
bool StreamSong::FillStream (SoundStream *stream, void *buff, int len, void *userdata)
{
StreamSong *song = (StreamSong *)userdata;
bool written = song->m_Source->GetData(buff, len);
if (!written)
{
memset((char*)buff, 0, len);
return false;
}
return true;
}
MusInfo *OpenStreamSong(StreamSource *source)
{
auto song = new StreamSong(source);
if (song->IsValid()) return song;
delete song;
return nullptr;
}

View file

@ -227,18 +227,16 @@ static void getNextXABlock(xa_data *xad, bool looping )
//
//==========================================================================
class XASong : public StreamSong
class XASong : public StreamSource
{
public:
XASong(FileReader & readr);
~XASong();
bool SetSubsong(int subsong);
void Play(bool looping, int subsong);
SoundStreamInfo GetFormat() override;
bool Start() override;
bool GetData(void *buffer, size_t len) override;
protected:
xa_data xad;
static bool Read(SoundStream *stream, void *buff, int len, void *userdata);
};
//==========================================================================
@ -253,81 +251,51 @@ XASong::XASong(FileReader &reader)
xad.reader = std::move(reader);
xad.t1 = xad.t2 = xad.t1_x = xad.t2_x = 0;
getNextXABlock(&xad, false);
auto SampleRate = xad.blockIs18K? 18900 : 37800;
m_Stream = GSnd->CreateStream(Read, 64 * 1024, SoundStream::Float, SampleRate, this); // create a floating point stereo stream.
}
//==========================================================================
//
// XASong - Destructor
//
//==========================================================================
XASong::~XASong()
SoundStreamInfo XASong::GetFormat()
{
Stop();
if (m_Stream != nullptr)
{
delete m_Stream;
m_Stream = nullptr;
}
auto SampleRate = xad.blockIs18K? 18900 : 37800;
return { 64*1024, SampleRate, 2};
}
//==========================================================================
//
// XASong :: Play
//
//==========================================================================
void XASong::Play(bool looping, int track)
bool XASong::Start()
{
m_Status = STATE_Stopped;
m_Looping = looping;
if (xad.finished && looping)
if (xad.finished && m_Looping)
{
xad.reader.Seek(XA_DATA_START, FileReader::SeekSet);
xad.t1 = xad.t2 = xad.t1_x = xad.t2_x = 0;
xad.finished = false;
}
if (m_Stream->Play(looping, 1))
{
m_Status = STATE_Playing;
}
return true;
}
//==========================================================================
//
// XASong :: SetSubsong
// XASong :: Read
//
//==========================================================================
bool XASong::SetSubsong(int track)
bool XASong::GetData(void *vbuff, size_t len)
{
return false;
}
//==========================================================================
//
// XASong :: Read STATIC
//
//==========================================================================
bool XASong::Read(SoundStream *stream, void *vbuff, int ilen, void *userdata)
{
auto self = (XASong*)userdata;
auto olen = len;
float *dest = (float*)vbuff;
while (ilen > 0)
while (len > 0)
{
auto ptr = self->xad.committed;
auto block = self->xad.block + ptr;
auto ptr = xad.committed;
auto block = xad.block + ptr;
if (ptr < kBufSize)
{
// commit the data
if (self->xad.blockIsMono)
if (xad.blockIsMono)
{
size_t numsamples = ilen / 8;
size_t numsamples = len / 8;
size_t availdata = kBufSize - ptr;
for(size_t tocopy = std::min(numsamples, availdata); tocopy > 0; tocopy--)
@ -335,35 +303,36 @@ bool XASong::Read(SoundStream *stream, void *vbuff, int ilen, void *userdata)
float f = *block++;
*dest++ = f;
*dest++ = f;
ilen -= 8;
len -= 8;
ptr++;
}
self->xad.committed = ptr;
xad.committed = ptr;
}
else
{
size_t availdata = (kBufSize - ptr) * 4;
size_t tocopy = std::min(availdata, (size_t)ilen);
size_t tocopy = std::min(availdata, len);
memcpy(dest, block, tocopy);
dest += tocopy / 4;
ilen -= (int)tocopy;
self->xad.committed += tocopy / 4;
len -= tocopy;
xad.committed += tocopy / 4;
}
}
if (self->xad.finished)
if (xad.finished)
{
// fill the rest with 0's.
memset(dest, 0, len);
return true;
}
if (ilen > 0)
if (len > 0)
{
// we ran out of data and need more
getNextXABlock(&self->xad, self->m_Looping);
getNextXABlock(&xad, m_Looping);
// repeat until done.
}
else break;
}
return !self->xad.finished;
return !xad.finished;
}
//==========================================================================
@ -372,7 +341,7 @@ bool XASong::Read(SoundStream *stream, void *vbuff, int ilen, void *userdata)
//
//==========================================================================
MusInfo *XA_OpenSong(FileReader &reader)
StreamSource *XA_OpenSong(FileReader &reader)
{
return new XASong(reader);
}