diff --git a/include/Nazara/Audio.hpp b/include/Nazara/Audio.hpp index c5ff22166..0d68b6d83 100644 --- a/include/Nazara/Audio.hpp +++ b/include/Nazara/Audio.hpp @@ -32,8 +32,10 @@ #include #include #include +#include #include #include #include +#include #endif // NAZARA_GLOBAL_AUDIO_HPP diff --git a/include/Nazara/Audio/Music.hpp b/include/Nazara/Audio/Music.hpp new file mode 100644 index 000000000..2191003b8 --- /dev/null +++ b/include/Nazara/Audio/Music.hpp @@ -0,0 +1,70 @@ +// Copyright (C) 2012 Jérôme Leclercq +// This file is part of the "Nazara Engine - Audio module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_MUSIC_HPP +#define NAZARA_MUSIC_HPP + +#include +#include +#include +#include + +struct NzMusicParams +{ + bool IsValid() const; +}; + +class NzMusic; +class NzSoundStream; +class NzThread; + +using NzMusicLoader = NzResourceLoader; + +struct NzMusicImpl; + +class NAZARA_API NzMusic : public NzSoundEmitter +{ + friend NzMusicLoader; + + public: + NzMusic() = default; + ~NzMusic(); + + bool Create(NzSoundStream* soundStream); + void Destroy(); + + void EnableLooping(bool loop); + + nzUInt32 GetDuration() const; + nzAudioFormat GetFormat() const; + nzUInt32 GetPlayingOffset() const; + unsigned int GetSampleCount() const; + unsigned int GetSampleRate() const; + nzSoundStatus GetStatus() const; + + bool IsLooping() const; + + bool OpenFromFile(const NzString& filePath, const NzMusicParams& params = NzMusicParams()); + bool OpenFromMemory(const void* data, std::size_t size, const NzMusicParams& params = NzMusicParams()); + bool OpenFromStream(NzInputStream& stream, const NzMusicParams& params = NzMusicParams()); + + void Pause(); + void Play(); + + void SetPlayingOffset(nzUInt32 offset); + + void Stop(); + + private: + NzMusicImpl* m_impl = nullptr; + + bool FillBuffer(unsigned int buffer); + void MusicThread(); + + static NzMusicLoader::LoaderList s_loaders; +}; + +#endif // NAZARA_MUSIC_HPP diff --git a/include/Nazara/Audio/Sound.hpp b/include/Nazara/Audio/Sound.hpp index a7fba9f47..6be27f5b8 100644 --- a/include/Nazara/Audio/Sound.hpp +++ b/include/Nazara/Audio/Sound.hpp @@ -36,7 +36,7 @@ class NAZARA_API NzSound : public NzSoundEmitter bool LoadFromStream(NzInputStream& stream, const NzSoundBufferParams& params = NzSoundBufferParams()); void Pause(); - bool Play(); + void Play(); void SetBuffer(const NzSoundBuffer* buffer); void SetPlayingOffset(nzUInt32 offset); diff --git a/include/Nazara/Audio/SoundEmitter.hpp b/include/Nazara/Audio/SoundEmitter.hpp index 752eb61d1..322cee09c 100644 --- a/include/Nazara/Audio/SoundEmitter.hpp +++ b/include/Nazara/Audio/SoundEmitter.hpp @@ -37,7 +37,7 @@ class NAZARA_API NzSoundEmitter bool IsSpatialized() const; virtual void Pause() = 0; - virtual bool Play() = 0; + virtual void Play() = 0; void SetAttenuation(float attenuation); void SetMinDistance(float minDistance); diff --git a/include/Nazara/Audio/SoundStream.hpp b/include/Nazara/Audio/SoundStream.hpp new file mode 100644 index 000000000..7a0c98705 --- /dev/null +++ b/include/Nazara/Audio/SoundStream.hpp @@ -0,0 +1,28 @@ +// Copyright (C) 2012 Jérôme Leclercq +// This file is part of the "Nazara Engine - Audio module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_SOUNDSTREAM_HPP +#define NAZARA_SOUNDSTREAM_HPP + +#include +#include + +class NAZARA_API NzSoundStream +{ + public: + NzSoundStream() = default; + virtual ~NzSoundStream(); + + virtual nzUInt32 GetDuration() const = 0; + virtual nzAudioFormat GetFormat() const = 0; + virtual unsigned int GetSampleCount() const = 0; + virtual unsigned int GetSampleRate() const = 0; + + virtual unsigned int Read(void* buffer, unsigned int sampleCount) = 0; + virtual void Seek(nzUInt32 offset) = 0; +}; + +#endif // NAZARA_SOUNDSTREAM_HPP diff --git a/src/Nazara/Audio/Loaders/sndfile/Loader.cpp b/src/Nazara/Audio/Loaders/sndfile/Loader.cpp index edcd23b9e..73e839bf1 100644 --- a/src/Nazara/Audio/Loaders/sndfile/Loader.cpp +++ b/src/Nazara/Audio/Loaders/sndfile/Loader.cpp @@ -4,7 +4,10 @@ #include #include +#include +#include #include +#include #include #include #include @@ -61,6 +64,109 @@ namespace static SF_VIRTUAL_IO callbacks = {GetSize, Seek, Read, nullptr, Tell}; + class sndfileStream : public NzSoundStream + { + public: + sndfileStream() : + m_file(nullptr), + m_handle(nullptr) + { + } + + ~sndfileStream() + { + if (m_handle) + sf_close(m_handle); + + if (m_file) + delete m_file; + } + + nzUInt32 GetDuration() const + { + return m_duration; + } + + nzAudioFormat GetFormat() const + { + return m_format; + } + + unsigned int GetSampleCount() const + { + return m_sampleCount; + } + + unsigned int GetSampleRate() const + { + return m_sampleRate; + } + + bool Open(const NzString& filePath) + { + m_file = new NzFile(filePath); + if (!m_file->Open(NzFile::ReadOnly)) + { + NazaraError("Failed to open file " + filePath); + return false; + } + + return Open(*m_file); + } + + bool Open(NzInputStream& stream) + { + SF_INFO infos; + m_handle = sf_open_virtual(&callbacks, SFM_READ, &infos, &stream); + if (!m_handle) + { + NazaraError("Failed to open sound: " + NzString(sf_strerror(m_handle))); + return false; + } + + m_format = NzAudio::GetAudioFormat(infos.channels); + if (m_format == nzAudioFormat_Unknown) + { + NazaraError("Channel count not handled"); + sf_close(m_handle); + m_handle = nullptr; + + return false; + } + + m_sampleCount = infos.channels*infos.frames; + m_sampleRate = infos.samplerate; + + m_duration = 1000*m_sampleCount / (m_format*m_sampleRate); + + // https://github.com/LaurentGomila/SFML/issues/271 + // http://www.mega-nerd.com/libsndfile/command.html#SFC_SET_SCALE_FLOAT_INT_READ + ///FIXME: Seulement le Vorbis ? + if (infos.format & SF_FORMAT_VORBIS) + sf_command(m_handle, SFC_SET_SCALE_FLOAT_INT_READ, nullptr, SF_TRUE); + + return true; + } + + unsigned int Read(void* buffer, unsigned int sampleCount) + { + return sf_read_short(m_handle, reinterpret_cast(buffer), sampleCount); + } + + void Seek(nzUInt32 offset) + { + sf_seek(m_handle, offset*m_sampleRate / 1000, SEEK_SET); + } + + private: + nzAudioFormat m_format; + NzFile* m_file; + SNDFILE* m_handle; + unsigned int m_duration; + unsigned int m_sampleCount; + unsigned int m_sampleRate; + }; + bool IsSupported(const NzString& extension) { static std::set supportedExtensions = { @@ -71,7 +177,7 @@ namespace return supportedExtensions.find(extension) != supportedExtensions.end(); } - nzTernary Check(NzInputStream& stream, const NzSoundBufferParams& parameters) + nzTernary CheckMusic(NzInputStream& stream, const NzMusicParams& parameters) { NazaraUnused(parameters); @@ -88,7 +194,68 @@ namespace return nzTernary_False; } - bool Load(NzSoundBuffer* soundBuffer, NzInputStream& stream, const NzSoundBufferParams& parameters) + bool LoadMusicFile(NzMusic* music, const NzString& filePath, const NzMusicParams& parameters) + { + NazaraUnused(parameters); + + std::unique_ptr musicStream(new sndfileStream); + if (!musicStream->Open(filePath)) + { + NazaraError("Failed to open music stream"); + return false; + } + + if (!music->Create(musicStream.get())) + { + NazaraError("Failed to create music"); + return false; + } + + musicStream.release(); + + return true; + } + + bool LoadMusicStream(NzMusic* music, NzInputStream& stream, const NzMusicParams& parameters) + { + NazaraUnused(parameters); + + std::unique_ptr musicStream(new sndfileStream); + if (!musicStream->Open(stream)) + { + NazaraError("Failed to open music stream"); + return false; + } + + if (!music->Create(musicStream.get())) + { + NazaraError("Failed to create music"); + return false; + } + + musicStream.release(); + + return true; + } + + nzTernary CheckSoundBuffer(NzInputStream& stream, const NzSoundBufferParams& parameters) + { + NazaraUnused(parameters); + + SF_INFO info; + info.format = 0; + + SNDFILE* file = sf_open_virtual(&callbacks, SFM_READ, &info, &stream); + if (file) + { + sf_close(file); + return nzTernary_True; + } + else + return nzTernary_False; + } + + bool LoadSoundBuffer(NzSoundBuffer* soundBuffer, NzInputStream& stream, const NzSoundBufferParams& parameters) { NazaraUnused(parameters); @@ -123,16 +290,16 @@ namespace if (sf_read_short(file, samples.get(), sampleCount) != sampleCount) { sf_close(file); - NazaraError("Failed to read samples"); + NazaraError("Failed to read samples"); return false; } if (!soundBuffer->Create(format, static_cast(sampleCount), info.samplerate, samples.get())) { sf_close(file); - NazaraError("Failed to create sound buffer"); + NazaraError("Failed to create sound buffer"); return false; } @@ -142,10 +309,12 @@ namespace void NzLoaders_sndfile_Register() { - NzSoundBufferLoader::RegisterLoader(IsSupported, Check, Load); + NzMusicLoader::RegisterLoader(IsSupported, CheckMusic, LoadMusicStream, LoadMusicFile); + NzSoundBufferLoader::RegisterLoader(IsSupported, CheckSoundBuffer, LoadSoundBuffer); } void NzLoaders_sndfile_Unregister() { - NzSoundBufferLoader::UnregisterLoader(IsSupported, Check, Load); + NzMusicLoader::UnregisterLoader(IsSupported, CheckMusic, LoadMusicStream, LoadMusicFile); + NzSoundBufferLoader::UnregisterLoader(IsSupported, CheckSoundBuffer, LoadSoundBuffer); } diff --git a/src/Nazara/Audio/Music.cpp b/src/Nazara/Audio/Music.cpp new file mode 100644 index 000000000..d098970c8 --- /dev/null +++ b/src/Nazara/Audio/Music.cpp @@ -0,0 +1,273 @@ +// Copyright (C) 2012 Jérôme Leclercq +// This file is part of the "Nazara Engine - Audio module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include +#include + +bool NzMusicParams::IsValid() const +{ + return true; +} + +struct NzMusicImpl +{ + ALenum audioFormat; + std::vector chunkSamples; + NzSoundStream* stream; + NzThread thread; + bool loop = false; + bool streaming = false; + bool paused = false; + unsigned int sampleRate; +}; + +NzMusic::~NzMusic() +{ + Destroy(); +} + +bool NzMusic::Create(NzSoundStream* soundStream) +{ + Destroy(); + + #if NAZARA_AUDIO_SAFE + if (!soundStream) + { + NazaraError("Sound stream must be valid"); + return false; + } + #endif + + nzAudioFormat format = soundStream->GetFormat(); + + m_impl = new NzMusicImpl; + m_impl->sampleRate = soundStream->GetSampleRate(); + m_impl->audioFormat = NzAudio::GetOpenALFormat(format); + m_impl->chunkSamples.resize(format * m_impl->sampleRate); // Une seconde de samples + m_impl->stream = soundStream; + + return true; +} + +void NzMusic::Destroy() +{ + if (m_impl) + { + Stop(); + + delete m_impl->stream; + delete m_impl; + m_impl = nullptr; + } +} + +void NzMusic::EnableLooping(bool loop) +{ + #if NAZARA_AUDIO_SAFE + if (!m_impl) + { + NazaraError("Music not created"); + return; + } + #endif + + m_impl->loop = loop; +} + +nzUInt32 NzMusic::GetDuration() const +{ + #if NAZARA_AUDIO_SAFE + if (!m_impl) + { + NazaraError("Music not created"); + return 0; + } + #endif + + return m_impl->stream->GetDuration(); +} + +nzUInt32 NzMusic::GetPlayingOffset() const +{ + #if NAZARA_AUDIO_SAFE + if (!m_impl) + { + NazaraError("Music not created"); + return 0; + } + #endif + + return 0; +} + +nzSoundStatus NzMusic::GetStatus() const +{ + #if NAZARA_AUDIO_SAFE + if (!m_impl) + { + NazaraError("Music not created"); + return nzSoundStatus_Stopped; + } + #endif + + nzSoundStatus status = GetInternalStatus(); + + if (m_impl->streaming && status == nzSoundStatus_Stopped) + status = nzSoundStatus_Playing; + + return status; +} + +bool NzMusic::IsLooping() const +{ + #if NAZARA_AUDIO_SAFE + if (!m_impl) + { + NazaraError("Music not created"); + return false; + } + #endif + + return m_impl->loop; +} + +bool NzMusic::OpenFromFile(const NzString& filePath, const NzMusicParams& params) +{ + return NzMusicLoader::LoadFromFile(this, filePath, params); +} + +bool NzMusic::OpenFromMemory(const void* data, std::size_t size, const NzMusicParams& params) +{ + return NzMusicLoader::LoadFromMemory(this, data, size, params); +} + +bool NzMusic::OpenFromStream(NzInputStream& stream, const NzMusicParams& params) +{ + return NzMusicLoader::LoadFromStream(this, stream, params); +} + +void NzMusic::Pause() +{ + alSourcePause(m_source); +} + +void NzMusic::Play() +{ + #if NAZARA_AUDIO_SAFE + if (!m_impl) + { + NazaraError("Music not created"); + return; + } + #endif + + if (m_impl->streaming) + { + if (GetStatus() != nzSoundStatus_Playing) + alSourcePlay(m_source); + + return; + } + + m_impl->stream->Seek(0); + m_impl->streaming = true; + m_impl->thread = NzThread(&NzMusic::MusicThread, this); + + return; +} + +void NzMusic::Stop() +{ + #if NAZARA_AUDIO_SAFE + if (!m_impl) + { + NazaraError("Music not created"); + return; + } + #endif + + if (m_impl->streaming) + { + m_impl->streaming = false; + m_impl->thread.Join(); + } +} + +bool NzMusic::FillBuffer(unsigned int buffer) +{ + unsigned int sampleCount = m_impl->chunkSamples.size(); + unsigned int sampleRead = 0; + + for (;;) + { + sampleRead += m_impl->stream->Read(&m_impl->chunkSamples[sampleRead], sampleCount-sampleRead); + if (sampleRead != sampleCount && m_impl->loop) + m_impl->stream->Seek(0); + else + break; + } + + if (sampleRead > 0) + alBufferData(buffer, m_impl->audioFormat, &m_impl->chunkSamples[0], sampleRead*sizeof(nzInt16), m_impl->sampleRate); + + return sampleRead != sampleCount; // Fin du fichier (N'arrive pas en cas de loop) +} + +void NzMusic::MusicThread() +{ + ALuint buffers[NAZARA_AUDIO_STREAMEDBUFFERCOUNT]; + alGenBuffers(NAZARA_AUDIO_STREAMEDBUFFERCOUNT, buffers); + + for (unsigned int i = 0; i < NAZARA_AUDIO_STREAMEDBUFFERCOUNT; ++i) + { + bool eof = FillBuffer(buffers[i]); + alSourceQueueBuffers(m_source, 1, &buffers[i]); + + if (eof) + break; // Nous avons fini, nous ne continuons pas + } + + alSourcePlay(m_source); + + while (m_impl->streaming) + { + nzSoundStatus status = GetInternalStatus(); + if (status == nzSoundStatus_Stopped) + { + m_impl->streaming = false; + break; + } + + ALint processedCount = 0; + alGetSourcei(m_source, AL_BUFFERS_PROCESSED, &processedCount); + + ALuint buffer; + while (processedCount--) + { + alSourceUnqueueBuffers(m_source, 1, &buffer); + if (!FillBuffer(buffer)) // Fin du fichier ? + alSourceQueueBuffers(m_source, 1, &buffer); + } + + NzThread::Sleep(50); + } + + alSourceStop(m_source); + + ALint queuedBufferCount; + alGetSourcei(m_source, AL_BUFFERS_QUEUED, &queuedBufferCount); + + ALuint buffer; + for (ALint i = 0; i < queuedBufferCount; ++i) + alSourceUnqueueBuffers(m_source, 1, &buffer); + + alDeleteBuffers(NAZARA_AUDIO_STREAMEDBUFFERCOUNT, buffers); +} + +NzMusicLoader::LoaderList NzMusic::s_loaders; diff --git a/src/Nazara/Audio/Sound.cpp b/src/Nazara/Audio/Sound.cpp index 3ee44bbc6..9c9352077 100644 --- a/src/Nazara/Audio/Sound.cpp +++ b/src/Nazara/Audio/Sound.cpp @@ -138,19 +138,17 @@ void NzSound::Pause() alSourcePause(m_source); } -bool NzSound::Play() +void NzSound::Play() { #if NAZARA_AUDIO_SAFE if (!m_buffer) { NazaraError("Invalid sound buffer"); - return false; + return; } #endif alSourcePlay(m_source); - - return true; } void NzSound::SetBuffer(const NzSoundBuffer* buffer) diff --git a/src/Nazara/Audio/SoundStream.cpp b/src/Nazara/Audio/SoundStream.cpp new file mode 100644 index 000000000..bc56f1d1a --- /dev/null +++ b/src/Nazara/Audio/SoundStream.cpp @@ -0,0 +1,7 @@ +// Copyright (C) 2012 Jérôme Leclercq +// This file is part of the "Nazara Engine - Audio module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include + +NzSoundStream::~NzSoundStream() = default;