diff --git a/GlyphsJuke.vcxproj b/GlyphsJuke.vcxproj index 5ed0e04..f347d0e 100644 --- a/GlyphsJuke.vcxproj +++ b/GlyphsJuke.vcxproj @@ -161,6 +161,7 @@ + @@ -178,6 +179,7 @@ + diff --git a/GlyphsJuke.vcxproj.filters b/GlyphsJuke.vcxproj.filters index 9e195cc..4afac60 100644 --- a/GlyphsJuke.vcxproj.filters +++ b/GlyphsJuke.vcxproj.filters @@ -209,6 +209,12 @@ Header Files + + Header Files + + + Header Files + diff --git a/src/AudioDevice.cc b/src/AudioDevice.cc index bfade3b..07f74e6 100644 --- a/src/AudioDevice.cc +++ b/src/AudioDevice.cc @@ -21,63 +21,14 @@ gj::AudioDevice::AudioDevice() { gj::AudioDevice::~AudioDevice() { ma_device_stop(&ma_); ma_device_uninit(&ma_); - - /* the worker thread has been exited so no lock is necessary anymore */ - - if (playing_) { - ma_decoder_uninit(&dec_); - } -} - -void gj::AudioDevice::PlayMusic(const std::string& path, double offset) { - std::lock_guard _(mtx_); - - if (playing_) { - ma_decoder_uninit(&dec_); - } - ma_decoder_config config = ma_decoder_config_init(kFormat, kChannel, kSampleRate); - if (ma_decoder_init_file(path.c_str(), &config, &dec_) != MA_SUCCESS) { - Abort("AudioDevice decoder error: "+path); - } - - const uint64_t offset_frame = static_cast(kSampleRate * offset); - if (ma_decoder_seek_to_pcm_frame(&dec_, offset_frame) != MA_SUCCESS) { - Abort("decoder seek failure"); - } - playing_ = true; -} - -void gj::AudioDevice::StopMusic() { - std::lock_guard _(mtx_); - if (!playing_) return; - - ma_decoder_uninit(&dec_); - playing_ = false; } void gj::AudioDevice::Callback_(ma_device* ma, void* out, const void* in, ma_uint32 framecnt) { AudioDevice* dev = reinterpret_cast(ma->pUserData); std::lock_guard _(dev->mtx_); - const double amp = dev->amp_.load(); - const double lpf = dev->lpf_coe_.load(); - - float* dst = reinterpret_cast(out); - - size_t wrote = 0; - if (dev->playing_) { - const ma_uint64 n = ma_decoder_read_pcm_frames(&dev->dec_, dst, framecnt); - if (n < framecnt) { - dev->playing_ = false; - ma_decoder_uninit(&dev->dec_); - } - wrote += n; - } - - for (size_t i = 0; i < static_cast(framecnt)*kChannel; ++i) { - dst[i] = static_cast(dst[i]*amp); - dst[i] = static_cast(dev->lpf_prev_*lpf + dst[i]*(1-lpf)); - dev->lpf_prev_ = dst[i]; + for (auto fx : dev->effects_) { + fx->Apply(reinterpret_cast(out), framecnt); } dev->time_.fetch_add(framecnt); } \ No newline at end of file diff --git a/src/AudioDevice.h b/src/AudioDevice.h index 138612a..1851c2d 100644 --- a/src/AudioDevice.h +++ b/src/AudioDevice.h @@ -5,6 +5,7 @@ #include #include #include +#include #define NOMINMAX /* miniaudio includes windows.h */ #include "thirdparty/miniaudio.h" @@ -32,14 +33,23 @@ class AudioDevice : public iAudioDevice, public iClock { AudioDevice(); ~AudioDevice(); - void PlayMusic(const std::string& path, double offset) override; - void StopMusic() override; - - void SetVolume(double amp) override { - amp_.store(amp); + void AddEffect(iAudioEffect* fx) override { + std::lock_guard _(mtx_); + effects_.push_back(fx); } - void SetLpfIntensity(double v) override { - lpf_coe_.store(v); + void RemoveEffect(iAudioEffect* fx) override { + std::lock_guard _(mtx_); + + auto itr = std::find(effects_.begin(), effects_.end(), fx); + if (itr == effects_.end()) return; + effects_.erase(itr); + } + + uint8_t ch() const override { + return kChannel; + } + uint32_t sampleRate() const override { + return kSampleRate; } uint64_t now() const override { @@ -51,16 +61,10 @@ class AudioDevice : public iAudioDevice, public iClock { ma_device ma_{0}; - bool playing_ = false; - ma_decoder dec_{0}; - - std::atomic amp_ = 1; - - std::atomic lpf_coe_ = 0; - float lpf_prev_ = 0; - std::atomic time_; + std::vector effects_; + static void Callback_(ma_device* ma, void* out, const void* in, ma_uint32 framecnt); }; diff --git a/src/ElementStore.h b/src/ElementStore.h index 5dce7aa..70eac24 100644 --- a/src/ElementStore.h +++ b/src/ElementStore.h @@ -67,10 +67,21 @@ class ElementStore { performing_.end()); } - bool IsEmpty() { + bool IsEmpty() const { return pending_.empty() && performing_.empty(); } + size_t CountPreparings() const { + size_t n = 0; + for (const auto& e : pending_) { + if (!e->HasPrepared()) ++n; + } + return n; + } + size_t size() const { + return pending_.size() + performing_.size(); + } + private: const iClock* clock_; diff --git a/src/InputWindowElement.h b/src/InputWindowElement.h index 5469501..d35d605 100644 --- a/src/InputWindowElement.h +++ b/src/InputWindowElement.h @@ -75,6 +75,10 @@ class InputWindowElement : public iElement { } } + bool HasPrepared() const override { + return true; + } + private: UniqPtr matcher_; UniqPtr drv_; diff --git a/src/Music.h b/src/Music.h new file mode 100644 index 0000000..2c95fae --- /dev/null +++ b/src/Music.h @@ -0,0 +1,116 @@ +#pragma once + +#include +#include + +#define NOMINMAX +#include "thirdparty/miniaudio.h" +#undef NOMINMAX + +#include "common.h" +#include "iAudioEffect.h" + + +namespace gj { + + +class Music : public iAudioEffect { + public: + static constexpr auto kVolumeLpf = .9f; + + Music() = delete; + Music(Music&&) = delete; + Music(const Music&) = delete; + + Music& operator=(Music&&) = delete; + Music& operator=(const Music&) = delete; + + Music(const std::string& path, uint8_t ch, uint32_t srate) : + ch_(ch), srate_(srate), lpf_prev_(ch) { + const auto config = ma_decoder_config_init(ma_format_f32, ch, srate); + + if (ma_decoder_init_file(path.c_str(), &config, &dec_)) { + Abort("decoder error: "+path); + } + } + ~Music() { + if (seeker_.joinable()) { + seeker_.join(); + } + ma_decoder_uninit(&dec_); + } + + void Play() { + playing_.store(true); + } + void Stop() { + playing_.store(false); + } + void Seek(double sec) { + if (seeker_.joinable()) { + seeker_.join(); + } + seeking_.store(true); + + const size_t frame = static_cast(srate_*sec); + seeker_ = std::thread([this, frame]() { + if (ma_decoder_seek_to_pcm_frame(&dec_, frame) != MA_SUCCESS) { + Abort("decoder seek failure"); + } + seeking_.store(false); + }); + } + + void SetVolume(float f) { + volume_.store(f); + } + void SetLpf(float f) { + lpf_.store(f); + } + + bool IsBusy() const { + return seeking_.load(); + } + + void Apply(float* ptr, size_t frames) override { + if (seeking_.load() || !playing_.load()) return; + + frames = ma_decoder_read_pcm_frames(&dec_, ptr, frames); + + const float lpf = lpf_.load(); + const float lpfinv = 1-lpf; + + const float volume = volume_.load(); + + const size_t n = frames * ch_; + for (size_t i = 0; i < frames; ++i) { + volume_actual_ = volume_actual_*kVolumeLpf + volume*(1-kVolumeLpf); + + float* sample = &ptr[i*ch_]; + for (uint8_t j = 0; j < ch_; ++j) { + sample[j] = lpf_prev_[j]*lpf + sample[j]*volume_actual_*lpfinv; + lpf_prev_[j] = sample[j]; + } + } + } + + private: + uint8_t ch_; + uint32_t srate_; + + std::thread seeker_; + + ma_decoder dec_{0}; + + std::atomic seeking_ = false; + std::atomic playing_ = false; + + std::atomic volume_ = 0; + std::atomic lpf_ = 0; + + float volume_actual_; + std::vector lpf_prev_; +}; + + +} \ No newline at end of file diff --git a/src/MusicElement.h b/src/MusicElement.h index c187318..bf4d254 100644 --- a/src/MusicElement.h +++ b/src/MusicElement.h @@ -5,7 +5,7 @@ #include "iAudioDevice.h" #include "iElement.h" #include "iElementDriver.h" -#include "Texture.h" +#include "Music.h" namespace gj { @@ -31,8 +31,12 @@ public: MusicElement& operator=(const MusicElement&) = delete; MusicElement(Param&& p) : - iElement(p.period), audio_(p.audio), path_(p.path), offset_(p.offset), + iElement(p.period), audio_(p.audio), + music_(p.path, audio_->ch(), audio_->sampleRate()), drv_(std::move(p.driver)) { + music_.Seek(p.offset); + audio_->AddEffect(&music_); + param_["volume"] = 0.; param_["lpf"] = 0.; } @@ -43,29 +47,29 @@ public: const double volume = std::get(param_["volume"]); const double lpf = std::get(param_["lpf"]); - audio_->SetVolume(volume); - audio_->SetLpfIntensity(lpf); + music_.SetVolume(static_cast(volume)); + music_.SetLpf(static_cast(lpf)); if (first_) { - audio_->PlayMusic(path_, offset_); + music_.Play(); first_ = false; } } void Finalize() override { - if (!first_) { - audio_->StopMusic(); - } + audio_->RemoveEffect(&music_); + } + + bool HasPrepared() const override { + return !music_.IsBusy(); } private: iAudioDevice* audio_; - std::string path_; + Music music_; bool first_ = true; - double offset_; - UniqPtr drv_; iElementDriver::Param param_; }; diff --git a/src/TextureElement.h b/src/TextureElement.h index 25514a2..4877a06 100644 --- a/src/TextureElement.h +++ b/src/TextureElement.h @@ -68,6 +68,10 @@ public: void Finalize() override { } + bool HasPrepared() const override { + return true; + } + private: Texture tex_; UniqPtr drv_; diff --git a/src/TitleScene.cc b/src/TitleScene.cc index 67741bb..45a6f4b 100644 --- a/src/TitleScene.cc +++ b/src/TitleScene.cc @@ -53,6 +53,7 @@ gj::TitleScene::TitleScene(const Param& p) : } gj::UniqPtr gj::TitleScene::Update(Frame& frame) { + /* input handling */ for (const auto c : frame.input) { switch (c) { case 'h': @@ -62,11 +63,31 @@ gj::UniqPtr gj::TitleScene::Update(Frame& frame) { SelectScore_((select_index_+1)%list_.size()); break; case ' ': + if (music_) param_.audio->RemoveEffect(music_.get()); return param_.alloc->MakeUniq( param_, list_[select_index_].displayName, list_[select_index_].score); } } + /* play music */ + if (trying_play_ && !(music_ && music_->IsBusy())) { + if (music_) { + param_.audio->RemoveEffect(music_.get()); + } + auto au = param_.audio; + + const auto& s = list_[select_index_]; + music_ = param_.alloc->MakeUniq(s.music, au->ch(), au->sampleRate()); + music_->Seek(s.playOffset); + music_->SetVolume(.2f); + music_->SetLpf(.99f); + music_->Play(); + + au->AddEffect(music_.get()); + trying_play_ = false; + } + + /* graphics calculation */ const uint64_t now = param_.clock->now(); const int32_t w = static_cast(frame.w); @@ -112,7 +133,7 @@ gj::UniqPtr gj::TitleScene::Update(Frame& frame) { frame.Add(&guide_); pe_.seed = XorShift(now/period1+10); - pe_.maxShift = (XorShift(now/period1+7)%100/100.*2 - 1)*.1; + pe_.maxShift = (XorShift(now/period1+7)%100/100.*2 - 1)*.03; frame.Add(&pe_); return nullptr; @@ -123,7 +144,5 @@ void gj::TitleScene::SelectScore_(size_t index) { score_ = Text(ConvertStrToWstr(s.displayName)); select_index_ = index; - param_.audio->SetVolume(.5); - param_.audio->SetLpfIntensity(.999); - param_.audio->PlayMusic(s.music, s.playOffset); + trying_play_ = true; } \ No newline at end of file diff --git a/src/TitleScene.h b/src/TitleScene.h index 1bb95b7..9a2d0b6 100644 --- a/src/TitleScene.h +++ b/src/TitleScene.h @@ -3,6 +3,7 @@ #include "common.h" #include "GlitchPosteffect.h" #include "iScene.h" +#include "Music.h" #include "Text.h" #include "Texture.h" @@ -29,7 +30,7 @@ class TitleScene : public iScene { std::string displayName; std::string score; std::string music; - double playOffset; + double playOffset = 0; }; Param param_; @@ -40,13 +41,17 @@ class TitleScene : public iScene { Text guide_; Texture logo_; + + GlitchPosteffect pe_; size_t select_index_; std::vector list_; - GlitchPosteffect pe_; + bool trying_play_ = false; + UniqPtr music_; void SelectScore_(size_t index); + void TryPlayingMusic_(); }; diff --git a/src/common.h b/src/common.h index 11de546..8399e54 100644 --- a/src/common.h +++ b/src/common.h @@ -48,10 +48,15 @@ static inline uint64_t XorShift(uint64_t x) { [[noreturn]] -static inline void Abort(const std::string& msg) { - MessageBox(NULL, ConvertStrToWstr(msg).c_str(), L"PROGRAM ABORTED", MB_OK); +static inline void Abort(const std::wstring& msg) { + MessageBox(NULL, msg.c_str(), L"PROGRAM ABORTED", MB_OK); std::exit(1); } +[[noreturn]] +static inline void Abort(const std::string& msg) { + Abort(ConvertStrToWstr(msg)); +} + } \ No newline at end of file diff --git a/src/iAudioDevice.h b/src/iAudioDevice.h index a38a675..5d7a087 100644 --- a/src/iAudioDevice.h +++ b/src/iAudioDevice.h @@ -2,6 +2,8 @@ #include +#include "iAudioEffect.h" + namespace gj { @@ -18,11 +20,11 @@ public: virtual ~iAudioDevice() = default; - virtual void PlayMusic(const std::string& path, double offset) = 0; - virtual void StopMusic() = 0; + virtual void AddEffect(iAudioEffect* fx) = 0; + virtual void RemoveEffect(iAudioEffect* fx) = 0; - virtual void SetVolume(double amp) = 0; - virtual void SetLpfIntensity(double v) = 0; + virtual uint8_t ch() const = 0; + virtual uint32_t sampleRate() const = 0; }; diff --git a/src/iAudioEffect.h b/src/iAudioEffect.h new file mode 100644 index 0000000..7f04b63 --- /dev/null +++ b/src/iAudioEffect.h @@ -0,0 +1,26 @@ +#pragma once + +#include + + +namespace gj { + + +/* Users have a responsible to remove AudioEffect from all devices at its destruction. */ +class iAudioEffect { + public: + iAudioEffect() = default; + iAudioEffect(iAudioEffect&&) = default; + iAudioEffect(const iAudioEffect&) = default; + + iAudioEffect& operator=(iAudioEffect&&) = default; + iAudioEffect& operator=(const iAudioEffect&) = default; + + virtual ~iAudioEffect() = default; + + virtual void Apply(float* ptr, size_t frame) = 0; +}; + + + +} \ No newline at end of file diff --git a/src/iElement.h b/src/iElement.h index 9cd6669..d8d0a3d 100644 --- a/src/iElement.h +++ b/src/iElement.h @@ -25,6 +25,8 @@ class iElement { virtual void Finalize() = 0; + virtual bool HasPrepared() const = 0; + /* Interfaces had better not have a variable but this is for optimization. */ const Period period; };