diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9af325791..dcabeb2a0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1245,6 +1245,7 @@ set (PCH_SOURCES sound/musicformats/music_cd.cpp sound/musicformats/music_dumb.cpp sound/musicformats/music_gme.cpp + sound/musicformats/music_libsndfile.cpp sound/musicformats/music_mus_midiout.cpp sound/musicformats/music_smf_midiout.cpp sound/musicformats/music_hmi_midiout.cpp diff --git a/src/sound/i_music.cpp b/src/sound/i_music.cpp index bf3ca81de..e00fdae4a 100644 --- a/src/sound/i_music.cpp +++ b/src/sound/i_music.cpp @@ -488,6 +488,11 @@ retry_as_sndsys: { info = MOD_OpenSong(*reader); } + if (info == nullptr) + { + info = SndFile_OpenSong(*reader); + if (info != nullptr) reader = nullptr; + } if (info == NULL) { diff --git a/src/sound/i_musicinterns.h b/src/sound/i_musicinterns.h index ba69741a5..227464c11 100644 --- a/src/sound/i_musicinterns.h +++ b/src/sound/i_musicinterns.h @@ -672,6 +672,7 @@ MusInfo *MOD_OpenSong(FileReader &reader); const char *GME_CheckFormat(uint32_t header); MusInfo *GME_OpenSong(FileReader &reader, const char *fmt); +MusInfo *SndFile_OpenSong(FileReader &fr); // -------------------------------------------------------------------------- diff --git a/src/sound/i_sound.h b/src/sound/i_sound.h index cd58c6f64..5f1917d68 100644 --- a/src/sound/i_sound.h +++ b/src/sound/i_sound.h @@ -160,8 +160,7 @@ public: virtual MIDIDevice* CreateMIDIDevice() const = 0; -protected: - virtual SoundDecoder *CreateDecoder(FileReader *reader); + static SoundDecoder *CreateDecoder(FileReader *reader); }; extern SoundRenderer *GSnd; diff --git a/src/sound/i_soundinternal.h b/src/sound/i_soundinternal.h index 786ff5dca..bb154a195 100644 --- a/src/sound/i_soundinternal.h +++ b/src/sound/i_soundinternal.h @@ -132,7 +132,7 @@ struct SoundDecoder virtual size_t read(char *buffer, size_t bytes) = 0; virtual TArray readAll(); - virtual bool seek(size_t ms_offset) = 0; + virtual bool seek(size_t ms_offset, bool ms) = 0; virtual size_t getSampleOffset() = 0; virtual size_t getSampleLength() { return 0; } diff --git a/src/sound/mpg123_decoder.cpp b/src/sound/mpg123_decoder.cpp index 605970bc9..1aa5a0e2f 100644 --- a/src/sound/mpg123_decoder.cpp +++ b/src/sound/mpg123_decoder.cpp @@ -134,14 +134,14 @@ size_t MPG123Decoder::read(char *buffer, size_t bytes) return amt; } -bool MPG123Decoder::seek(size_t ms_offset) +bool MPG123Decoder::seek(size_t ms_offset, bool ms) { int enc, channels; long srate; if(mpg123_getformat(MPG123, &srate, &channels, &enc) == MPG123_OK) { - size_t smp_offset = (size_t)((double)ms_offset / 1000. * srate); + size_t smp_offset = ms? (size_t)((double)ms_offset / 1000. * srate) : ms_offset; if(mpg123_seek(MPG123, (off_t)smp_offset, SEEK_SET) >= 0) { Done = false; diff --git a/src/sound/mpg123_decoder.h b/src/sound/mpg123_decoder.h index 59e1df2ca..e1c786888 100644 --- a/src/sound/mpg123_decoder.h +++ b/src/sound/mpg123_decoder.h @@ -16,7 +16,7 @@ struct MPG123Decoder : public SoundDecoder virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type); virtual size_t read(char *buffer, size_t bytes); - virtual bool seek(size_t ms_offset); + virtual bool seek(size_t ms_offset, bool ms); virtual size_t getSampleOffset(); virtual size_t getSampleLength(); diff --git a/src/sound/musicformats/music_libsndfile.cpp b/src/sound/musicformats/music_libsndfile.cpp new file mode 100644 index 000000000..6676d498f --- /dev/null +++ b/src/sound/musicformats/music_libsndfile.cpp @@ -0,0 +1,264 @@ +/* +** 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 "i_musicinterns.h" +#include "c_cvars.h" +#include "critsec.h" +#include "v_text.h" +#include "files.h" +#include "templates.h" +#include "sndfile_decoder.h" +#include "mpg123_decoder.h" + +// MACROS ------------------------------------------------------------------ + +// TYPES ------------------------------------------------------------------- + +class SndFileSong : public StreamSong +{ +public: + SndFileSong(FileReader *reader, SoundDecoder *decoder, uint32_t loop_start, uint32_t loop_end); + ~SndFileSong(); + bool SetSubsong(int subsong); + void Play(bool looping, int subsong); + FString GetStats(); + +protected: + FCriticalSection CritSec; + FileReader *Reader; + SoundDecoder *Decoder; + int Channels; + int SampleRate; + + uint32_t Loop_Start; + uint32_t Loop_End; + + int CalcSongLength(); + + static bool Read(SoundStream *stream, void *buff, int len, void *userdata); +}; + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// GME_OpenSong +// +//========================================================================== + +MusInfo *SndFile_OpenSong(FileReader &fr) +{ + uint8_t signature[4]; + + fr.Seek(0, SEEK_SET); + fr.Read(signature, 4); + uint32_t loop_start = 0, loop_end = ~0u; + + if (!memcmp(signature, "OggS", 4) || !memcmp(signature, "fLaC", 4)) + { + // Todo: Read loop points from metadata + + + // ms to samples. + //size_t smp_offset = ms? (size_t)((double)ms_offset / 1000. * SndInfo.samplerate) : ms_offset; + + } + fr.Seek(0, SEEK_SET); + auto decoder = SoundRenderer::CreateDecoder(&fr); + if (decoder == nullptr) return nullptr; + return new SndFileSong(&fr, decoder, loop_start, loop_end); +} + +//========================================================================== +// +// SndFileSong - Constructor +// +//========================================================================== + +SndFileSong::SndFileSong(FileReader *reader, SoundDecoder *decoder, uint32_t loop_start, uint32_t loop_end) +{ + ChannelConfig iChannels; + SampleType Type; + + decoder->getInfo(&SampleRate, &iChannels, &Type); + + Loop_Start = loop_start; + Loop_End = clamp(loop_end, 0, (uint32_t)decoder->getSampleLength()); + Reader = reader; + Decoder = decoder; + Channels = iChannels == ChannelConfig_Stereo? 2:1; + m_Stream = GSnd->CreateStream(Read, 32*1024, iChannels == ChannelConfig_Stereo? 0 : SoundStream::Mono, SampleRate, this); +} + +//========================================================================== +// +// SndFileSong - Destructor +// +//========================================================================== + +SndFileSong::~SndFileSong() +{ + Stop(); + if (m_Stream != nullptr) + { + delete m_Stream; + m_Stream = nullptr; + } + if (Decoder != nullptr) + { + delete Decoder; + } + if (Reader != nullptr) + { + delete Reader; + } +} + + +//========================================================================== +// +// 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 +// +//========================================================================== + +FString SndFileSong::GetStats() +{ + FString out; + + size_t SamplePos; + + SamplePos = Decoder->getSampleOffset(); + int time = int (SamplePos / SampleRate); + + out.Format( + "Track: " TEXTCOLOR_YELLOW "%s, %dHz" TEXTCOLOR_NORMAL + " Time:" TEXTCOLOR_YELLOW "%02d:%02d" TEXTCOLOR_NORMAL, + Channels == 2? "Stereo" : "Mono", SampleRate, + time/60, + time % 60); + return out; +} + +//========================================================================== +// +// SndFileSong :: Read STATIC +// +//========================================================================== + +bool SndFileSong::Read(SoundStream *stream, void *vbuff, int ilen, void *userdata) +{ + char *buff = (char*)vbuff; + SndFileSong *song = (SndFileSong *)userdata; + song->CritSec.Enter(); + + size_t len = size_t(ilen); + size_t currentpos = song->Decoder->getSampleOffset(); + size_t framestoread = len / (song->Channels*2); + bool err = false; + if (!song->m_Looping) + { + size_t maxpos = song->Decoder->getSampleLength(); + if (currentpos == maxpos) + { + memset(buff, 0, len); + song->CritSec.Leave(); + return false; + } + if (currentpos + framestoread > maxpos) + { + size_t got = song->Decoder->read(buff, (maxpos - currentpos) * song->Channels * 2); + memset(buff + got, 0, len - got); + } + else + { + size_t got = song->Decoder->read(buff, len); + err = (got != len); + } + } + else + { + if (currentpos + framestoread > song->Loop_End) + { + size_t endblock = (song->Loop_End - currentpos) * song->Channels * 2; + err = (song->Decoder->read(buff, endblock) != endblock); + buff = buff + endblock; + len -= endblock; + song->Decoder->seek(song->Loop_Start, false); + } + err |= song->Decoder->read(buff, len) != len; + } + song->CritSec.Leave(); + return !err; +} diff --git a/src/sound/oalsound.cpp b/src/sound/oalsound.cpp index 90639624a..d38435be1 100644 --- a/src/sound/oalsound.cpp +++ b/src/sound/oalsound.cpp @@ -212,7 +212,7 @@ class OpenALSoundStream : public SoundStream size_t got = self->Decoder->read((char*)ptr, length); if(got < (unsigned int)length) { - if(!self->Looping || !self->Decoder->seek(0)) + if(!self->Looping || !self->Decoder->seek(0, false)) return false; got += self->Decoder->read((char*)ptr+got, length-got); } @@ -361,7 +361,7 @@ public: virtual bool SetPosition(unsigned int ms_pos) { std::unique_lock lock(Renderer->StreamLock); - if(!Decoder->seek(ms_pos)) + if(!Decoder->seek(ms_pos, true)) return false; if(!Playing.load()) diff --git a/src/sound/sndfile_decoder.cpp b/src/sound/sndfile_decoder.cpp index 5a957eb27..9d0d2331b 100644 --- a/src/sound/sndfile_decoder.cpp +++ b/src/sound/sndfile_decoder.cpp @@ -132,9 +132,9 @@ TArray SndFileDecoder::readAll() return output; } -bool SndFileDecoder::seek(size_t ms_offset) +bool SndFileDecoder::seek(size_t ms_offset, bool ms) { - size_t smp_offset = (size_t)((double)ms_offset / 1000. * SndInfo.samplerate); + size_t smp_offset = ms? (size_t)((double)ms_offset / 1000. * SndInfo.samplerate) : ms_offset; if(sf_seek(SndFile, smp_offset, SEEK_SET) < 0) return false; return true; diff --git a/src/sound/sndfile_decoder.h b/src/sound/sndfile_decoder.h index f53f7e52a..0c4cfe935 100644 --- a/src/sound/sndfile_decoder.h +++ b/src/sound/sndfile_decoder.h @@ -13,7 +13,7 @@ struct SndFileDecoder : public SoundDecoder virtual size_t read(char *buffer, size_t bytes); virtual TArray readAll(); - virtual bool seek(size_t ms_offset); + virtual bool seek(size_t ms_offset, bool ms); virtual size_t getSampleOffset(); virtual size_t getSampleLength();