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;
};