diff --git a/GlyphsJuke.vcxproj b/GlyphsJuke.vcxproj
index 68b0abc..5ed0e04 100644
--- a/GlyphsJuke.vcxproj
+++ b/GlyphsJuke.vcxproj
@@ -155,6 +155,7 @@
+
@@ -183,6 +184,7 @@
+
@@ -197,6 +199,7 @@
+
@@ -204,6 +207,7 @@
+
diff --git a/GlyphsJuke.vcxproj.filters b/GlyphsJuke.vcxproj.filters
index 0e30ff5..9e195cc 100644
--- a/GlyphsJuke.vcxproj.filters
+++ b/GlyphsJuke.vcxproj.filters
@@ -48,6 +48,9 @@
Source Files
+
+ Source Files
+
@@ -197,6 +200,15 @@
Header Files
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
diff --git a/src/AudioDevice.cc b/src/AudioDevice.cc
index 6b475dc..bfade3b 100644
--- a/src/AudioDevice.cc
+++ b/src/AudioDevice.cc
@@ -29,7 +29,7 @@ gj::AudioDevice::~AudioDevice() {
}
}
-void gj::AudioDevice::PlayMusic(const std::string& path) {
+void gj::AudioDevice::PlayMusic(const std::string& path, double offset) {
std::lock_guard _(mtx_);
if (playing_) {
@@ -39,6 +39,11 @@ void gj::AudioDevice::PlayMusic(const std::string& path) {
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;
}
@@ -54,6 +59,9 @@ void gj::AudioDevice::Callback_(ma_device* ma, void* out, const void* in, ma_uin
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;
@@ -65,5 +73,11 @@ void gj::AudioDevice::Callback_(ma_device* ma, void* out, const void* in, ma_uin
}
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];
+ }
dev->time_.fetch_add(framecnt);
}
\ No newline at end of file
diff --git a/src/AudioDevice.h b/src/AudioDevice.h
index 3b8f02f..138612a 100644
--- a/src/AudioDevice.h
+++ b/src/AudioDevice.h
@@ -32,9 +32,16 @@ class AudioDevice : public iAudioDevice, public iClock {
AudioDevice();
~AudioDevice();
- void PlayMusic(const std::string& path) override;
+ void PlayMusic(const std::string& path, double offset) override;
void StopMusic() override;
+ void SetVolume(double amp) override {
+ amp_.store(amp);
+ }
+ void SetLpfIntensity(double v) override {
+ lpf_coe_.store(v);
+ }
+
uint64_t now() const override {
return time_.load() * 1000 / kSampleRate;
}
@@ -47,6 +54,11 @@ class AudioDevice : public iAudioDevice, public iClock {
bool playing_ = false;
ma_decoder dec_{0};
+ std::atomic amp_ = 1;
+
+ std::atomic lpf_coe_ = 0;
+ float lpf_prev_ = 0;
+
std::atomic time_;
static void Callback_(ma_device* ma, void* out, const void* in, ma_uint32 framecnt);
diff --git a/src/Font.h b/src/Font.h
index 175dbfd..005d8f6 100644
--- a/src/Font.h
+++ b/src/Font.h
@@ -14,7 +14,7 @@
namespace gj {
- class Font {
+class Font {
public:
Font() = delete;
Font(Font&&) = delete;
diff --git a/src/Game.cc b/src/Game.cc
index 7a5c03f..69b95c2 100644
--- a/src/Game.cc
+++ b/src/Game.cc
@@ -1,5 +1,7 @@
#include "Game.h"
-#include "PlayScene.h"
+
+#include "iScene.h"
+#include "TitleScene.h"
gj::Game::Game(gj::Game::Param&& p) :
@@ -8,10 +10,9 @@ gj::Game::Game(gj::Game::Param&& p) :
logger_(p.h),
w_(p.w), h_(p.h),
frame_(p.w, p.h, kReserveDrawable, kReserveWritable) {
- gj::PlayScene::Param param;
+ gj::iScene::Param param;
param.alloc = alloc_;
param.clock = &clock_;
param.audio = p.audio;
- param.score = "test"; /* TODO test */
- scene_ = alloc_->MakeUniq(std::move(param));
+ scene_ = alloc_->MakeUniq(param);
}
\ No newline at end of file
diff --git a/src/GlitchPosteffect.h b/src/GlitchPosteffect.h
new file mode 100644
index 0000000..e62c487
--- /dev/null
+++ b/src/GlitchPosteffect.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include
+#include
+
+#include "iDrawable.h"
+
+
+namespace gj {
+
+
+class GlitchPosteffect : public iDrawable {
+ public:
+ GlitchPosteffect(GlitchPosteffect&&) = delete;
+ GlitchPosteffect(const GlitchPosteffect&) = delete;
+
+ GlitchPosteffect& operator=(GlitchPosteffect&&) = delete;
+ GlitchPosteffect& operator=(const GlitchPosteffect&) = delete;
+
+ GlitchPosteffect() {
+ }
+
+ void Draw(Colorbuffer& fb) const override {
+ const int32_t w = static_cast(fb.width());
+ const int32_t h = static_cast(fb.height());
+
+ float* ptr = fb.ptr();
+
+ for (int32_t y = 0; y < h; ++y) {
+ const double shift = (XorShift(seed+y)%100/100.*2-1)*maxShift;
+ const int32_t s = static_cast(w*shift);
+ if (std::abs(shift) > 1) continue;
+
+ const size_t offset = static_cast(y) * w;
+ float* src = ptr + offset;
+ float* dst = ptr + offset + s;
+ if (src > dst) std::swap(src, dst);
+
+ std::memmove(dst, src, static_cast(w) - std::abs(s));
+ }
+ }
+
+ uint64_t seed = 1;
+ double maxShift = 0;
+};
+
+
+}
\ No newline at end of file
diff --git a/src/GlyphElementFactory.h b/src/GlyphElementFactory.h
index 9ddf32e..1b07633 100644
--- a/src/GlyphElementFactory.h
+++ b/src/GlyphElementFactory.h
@@ -42,7 +42,7 @@ class GlyphElementFactory : public iElementFactory {
if (found != fonts_.end()) {
return *found->second;
}
- auto f = alloc_->MakeUniq(alloc_, "res/font/"+name);
+ auto f = alloc_->MakeUniq(alloc_, name);
auto ptr = f.get();
fonts_[name] = std::move(f);
return *ptr;
diff --git a/src/HiraganaMatcher.cc b/src/HiraganaMatcher.cc
index 5e396ee..99b9129 100644
--- a/src/HiraganaMatcher.cc
+++ b/src/HiraganaMatcher.cc
@@ -189,10 +189,9 @@ bool gj::HiraganaMatcher::Input_(wchar_t c, bool force_cut) {
if (itr.size() == newbuf.size()) {
comp_match = i;
if (force_cut) {
- buffer_ = {c};
+ buffer_ = L"";
state_.match += i;
- UpdateExpects_();
- return true;
+ return Input_(c, false);
}
} else {
part_match = i;
@@ -248,6 +247,8 @@ void gj::HiraganaMatcher::UpdateExpects_() {
}
remain = remain.substr(len);
}
- if (prev == remain.size()) Abort("invalid pattern for InputWin");
+ if (prev == remain.size()) {
+ Abort("invalid pattern for InputWin");
+ }
}
}
\ No newline at end of file
diff --git a/src/Lua.cc b/src/Lua.cc
index 63d3382..dad16ab 100644
--- a/src/Lua.cc
+++ b/src/Lua.cc
@@ -1,5 +1,7 @@
#include "Lua.h"
+#include "thirdparty/lualib.h"
+
struct LuaPusher {
LuaPusher() = delete;
@@ -142,6 +144,7 @@ gj::Lua::Lua(iAllocator* alloc, ElementStore* store, const FactoryMap& factory,
if (L == nullptr) {
Abort("lua_newstate failure");
}
+ luaopen_math(L);
for (const auto& f : factory) {
lua_pushstring(L, f.first.c_str());
diff --git a/src/MusicElement.h b/src/MusicElement.h
index 38acbc6..c187318 100644
--- a/src/MusicElement.h
+++ b/src/MusicElement.h
@@ -12,6 +12,17 @@ namespace gj {
class MusicElement : public iElement {
public:
+ struct Param {
+ iAudioDevice* audio;
+
+ Period period;
+
+ std::string path;
+ double offset;
+
+ UniqPtr driver;
+ };
+
MusicElement() = delete;
MusicElement(MusicElement&&) = delete;
MusicElement(const MusicElement&) = delete;
@@ -19,13 +30,24 @@ public:
MusicElement& operator=(MusicElement&&) = delete;
MusicElement& operator=(const MusicElement&) = delete;
- MusicElement(const Period& p, iAudioDevice* audio, const std::string& name) :
- iElement(p), audio_(audio), path_("res/music/"+name) {
+ MusicElement(Param&& p) :
+ iElement(p.period), audio_(p.audio), path_(p.path), offset_(p.offset),
+ drv_(std::move(p.driver)) {
+ param_["volume"] = 0.;
+ param_["lpf"] = 0.;
}
void Update(Frame& frame, double t) override {
+ drv_->Update(param_, t);
+
+ const double volume = std::get(param_["volume"]);
+ const double lpf = std::get(param_["lpf"]);
+
+ audio_->SetVolume(volume);
+ audio_->SetLpfIntensity(lpf);
+
if (first_) {
- audio_->PlayMusic(path_);
+ audio_->PlayMusic(path_, offset_);
first_ = false;
}
}
@@ -41,6 +63,11 @@ public:
std::string path_;
bool first_ = true;
+
+ double offset_;
+
+ UniqPtr drv_;
+ iElementDriver::Param param_;
};
diff --git a/src/MusicElementFactory.h b/src/MusicElementFactory.h
index fe7037c..67a667e 100644
--- a/src/MusicElementFactory.h
+++ b/src/MusicElementFactory.h
@@ -22,11 +22,18 @@ class MusicElementFactory : public iElementFactory {
}
UniqPtr Create(Param&& param) override {
- if (param.custom.size() != 1) return nullptr;
+ if (param.custom.size() != 2) return nullptr;
- const std::string name = std::get(param.custom[0]);
+ const std::string path = std::get(param.custom[0]);
+ const double offset = std::get(param.custom[1]);
- return alloc_->MakeUniq(param.period, audio_, name);
+ MusicElement::Param p;
+ p.audio = audio_;
+ p.period = param.period;
+ p.path = path;
+ p.offset = offset;
+ p.driver = std::move(param.driver);
+ return alloc_->MakeUniq(std::move(p));
}
private:
diff --git a/src/PlayScene.cc b/src/PlayScene.cc
index 51270e5..dd927aa 100644
--- a/src/PlayScene.cc
+++ b/src/PlayScene.cc
@@ -7,29 +7,28 @@
#include "ResultScene.h"
-gj::PlayScene::PlayScene(Param&& p) :
- alloc_(p.alloc), audio_(p.audio),
- clock_(p.clock), store_(&clock_, 256) {
+gj::PlayScene::PlayScene(const Param& p, const std::string& title, const std::string& path) :
+ param_(p), clock_(p.clock), store_(&clock_, 256) {
- GlyphElementFactory glyph(alloc_);
- InputWindowElementFactory inputWin(alloc_, &sb_);
- MusicElementFactory music(alloc_, audio_);
+ GlyphElementFactory glyph(p.alloc);
+ InputWindowElementFactory inputWin(p.alloc, &sb_);
+ MusicElementFactory music(p.alloc, p.audio);
- sb_.title = ConvertStrToWstr(p.score);
+ sb_.title = ConvertStrToWstr(title);
Lua::FactoryMap map = {
{ "Glyph", &glyph },
{ "InputWin", &inputWin },
{ "Music", &music },
};
- lua_ = alloc_->MakeUniq(
- alloc_, &store_, map, "res/score/" + p.score + ".lua");
+ lua_ = p.alloc->MakeUniq(
+ p.alloc, &store_, map, path);
}
gj::UniqPtr gj::PlayScene::Update(Frame& f) {
if (store_.IsEmpty()) {
- return alloc_->MakeUniq(alloc_, clock_.parent(), sb_);
+ return param_.alloc->MakeUniq(param_, sb_);
}
store_.Update(f);
diff --git a/src/PlayScene.h b/src/PlayScene.h
index 6ef21b3..9332a9f 100644
--- a/src/PlayScene.h
+++ b/src/PlayScene.h
@@ -2,10 +2,6 @@
#include "ElementStore.h"
-#include "Frame.h"
-#include "iAllocator.h"
-#include "iAudioDevice.h"
-#include "iLogger.h"
#include "iScene.h"
#include "Lua.h"
#include "OffsetClock.h"
@@ -17,14 +13,6 @@ namespace gj {
class PlayScene : public iScene {
public:
- struct Param {
- iAllocator* alloc;
- iAudioDevice* audio;
- const iClock* clock;
-
- std::string score;
- };
-
PlayScene() = delete;
PlayScene(PlayScene&&) = delete;
PlayScene(const PlayScene&) = delete;
@@ -32,13 +20,12 @@ class PlayScene : public iScene {
PlayScene& operator=(PlayScene&&) = delete;
PlayScene& operator=(const PlayScene&) = delete;
- PlayScene(Param&& p);
+ PlayScene(const Param& p, const std::string& title, const std::string& path);
UniqPtr Update(Frame& f) override;
private:
- iAllocator* alloc_;
- iAudioDevice* audio_;
+ Param param_;
OffsetClock clock_;
diff --git a/src/ResultScene.cc b/src/ResultScene.cc
index bbc9951..fca5a6e 100644
--- a/src/ResultScene.cc
+++ b/src/ResultScene.cc
@@ -1,8 +1,10 @@
#include "ResultScene.h"
+#include "TitleScene.h"
-gj::ResultScene::ResultScene(iAllocator* alloc, const iClock* clock, const Scoreboard& sb) :
- alloc_(alloc), clock_(clock), sb_(sb),
+
+gj::ResultScene::ResultScene(const Param& p, const Scoreboard& sb) :
+ param_(p), clock_(p.clock), sb_(sb),
title_(sb.title),
correct_label_(L"CORRECT TYPES"),
correct_num_(std::to_wstring(sb.correct)),
@@ -10,7 +12,7 @@ gj::ResultScene::ResultScene(iAllocator* alloc, const iClock* clock, const Score
line_label_(L"COMPLETE LINES"),
line_num_(std::to_wstring(sb.completeLines)),
line_den_(std::to_wstring(sb.lines)),
- guide_(L"~ PRESS ENTER ~") {
+ guide_(L"~ PRESS SPACE ~") {
}
gj::UniqPtr gj::ResultScene::Update(Frame& f) {
@@ -51,5 +53,9 @@ gj::UniqPtr gj::ResultScene::Update(Frame& f) {
guide_.SetPosition((w-guide_.width())/2, static_cast(h*.8));
f.Add(&guide_);
+ if (f.input.find(' ') != std::string::npos) {
+ return param_.alloc->MakeUniq(param_);
+ }
+
return nullptr;
}
\ No newline at end of file
diff --git a/src/ResultScene.h b/src/ResultScene.h
index 14b6d52..9ee0500 100644
--- a/src/ResultScene.h
+++ b/src/ResultScene.h
@@ -1,8 +1,5 @@
#pragma once
-#include "ElementStore.h"
-#include "Frame.h"
-#include "iAllocator.h"
#include "iScene.h"
#include "OffsetClock.h"
#include "Scoreboard.h"
@@ -21,12 +18,13 @@ class ResultScene : public iScene {
ResultScene& operator=(ResultScene&&) = delete;
ResultScene& operator=(const ResultScene&) = delete;
- ResultScene(iAllocator* alloc, const iClock* clock, const Scoreboard& sb);
+ ResultScene(const Param& p, const Scoreboard& sb);
UniqPtr Update(Frame& f) override;
private:
- iAllocator* alloc_;
+ Param param_;
+
OffsetClock clock_;
Scoreboard sb_;
diff --git a/src/Texture.cc b/src/Texture.cc
index 7012004..9b3588f 100644
--- a/src/Texture.cc
+++ b/src/Texture.cc
@@ -81,7 +81,7 @@ void gj::Texture::Draw(Colorbuffer& fb) const {
if (dstx < 0 || w <= dstx) continue;
if (dsty < 0 || h <= dsty) continue;
- dst[dstx + w*dsty] = src[srcx + srcw*srcy] * alpha_;
+ dst[dstx + w*dsty] += src[srcx + srcw*srcy] * alpha_;
}
}
}
\ No newline at end of file
diff --git a/src/TitleScene.cc b/src/TitleScene.cc
new file mode 100644
index 0000000..9d1b91c
--- /dev/null
+++ b/src/TitleScene.cc
@@ -0,0 +1,129 @@
+#include "TitleScene.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include "thirdparty/picojson.h"
+
+#include "common.h"
+#include "Font.h"
+#include "PlayScene.h"
+
+
+gj::TitleScene::TitleScene(const Param& p) :
+ param_(p),
+ score_(L"penguin: you didn't see anything..."),
+ next_(L"> L"), prev_(L"H <"),
+ guide_(L"H:PREV / SPACE:PLAY / L:NEXT"),
+ logo_(Colorbuffer(p.alloc, 1, 1)) {
+ /* load score list */
+ std::ifstream ifs(kListPath);
+ if (!ifs) Abort(std::string(kListPath)+" is missing");
+
+ ::picojson::value root;
+ const std::string err = ::picojson::parse(root, ifs);
+ ifs.close();
+
+ if (err.size()) Abort(std::string(kListPath)+": "+err);
+
+ std::string line;
+ auto& list = root.get<::picojson::array>();
+ for (auto& e : list) {
+ auto& obj = e.get<::picojson::object>();
+
+ Score s;
+
+ s.displayName = obj["displayName"].get();
+ s.music = obj["music"].get();
+ s.score = obj["score"].get();
+ s.playOffset = static_cast(obj["playOffset"].get());
+
+ list_.push_back(s);
+ }
+ if (list_.size() == 0) Abort("no score is registered");
+
+ SelectScore_(0);
+
+ /* render logo */
+ Font font(param_.alloc, "res/font/shippori.ttf");
+ logo_ = Texture(font.RenderGlyphs(L"GlyphsJuke", 64));
+}
+
+gj::UniqPtr gj::TitleScene::Update(Frame& frame) {
+ for (const auto c : frame.input) {
+ switch (c) {
+ case 'h':
+ SelectScore_(select_index_? select_index_-1: list_.size()-1);
+ break;
+ case 'l':
+ SelectScore_((select_index_+1)%list_.size());
+ break;
+ case ' ':
+ return param_.alloc->MakeUniq(
+ param_, list_[select_index_].displayName, list_[select_index_].score);
+ }
+ }
+
+ const uint64_t now = param_.clock->now();
+
+ const int32_t w = static_cast(frame.w);
+ const int32_t h = static_cast(frame.h);
+
+ const int32_t selector_y = static_cast(h*.75);
+
+ const uint64_t period1 = XorShift(now/1000+1)%1000 + 500;
+ const uint64_t period2 = XorShift(now/1000+5)%1000 + 500;
+ logo_.SetAlpha(static_cast(XorShift(now/period1+1)%100/100.*.4+.4));
+
+ const double shift_x = (XorShift(now/period1+1)%100/100.*2 - 1)*.05;
+ const double shift_y = (XorShift(now/period2+3)%100/100.*2 - 1)*.05;
+ const double scale_x = (XorShift(now/period1+3)%100/100.*2 - 1)*.1;
+
+ const double theta = (XorShift(now/period2+13)%100/100.*2 - 1)*.01*kPi*2;
+
+ const double c = cos(theta), s = sin(theta);
+ auto M = mat3{
+ {1+scale_x, 0, 0},
+ {0, .8, 0},
+ {shift_x, shift_y+.4, 1}
+ };
+ auto Mr = mat3{
+ {c, -s, 0},
+ {s, c, 0},
+ {0, 0, 1},
+ };
+ M = ::linalg::mul(Mr, M);
+ logo_.SetMatrix(M);
+ frame.Add(&logo_);
+
+ next_.SetPosition(static_cast(w*.8-next_.width()), selector_y);
+ frame.Add(&next_);
+
+ prev_.SetPosition(static_cast(w*.2), selector_y);
+ frame.Add(&prev_);
+
+ score_.SetPosition((w-score_.width())/2, selector_y);
+ frame.Add(&score_);
+
+ guide_.SetPosition((w-guide_.width())/2, selector_y+3);
+ frame.Add(&guide_);
+
+ pe_.seed = XorShift(now/period1+10);
+ pe_.maxShift = (XorShift(now/period1+7)%100/100.*2 - 1)*.1;
+ frame.Add(&pe_);
+
+ return nullptr;
+}
+
+void gj::TitleScene::SelectScore_(size_t index) {
+ const auto& s = list_[index];
+ score_ = Text(ConvertStrToWstr(s.displayName));
+ select_index_ = index;
+
+ param_.audio->SetVolume(.2);
+ param_.audio->SetLpfIntensity(.99);
+ param_.audio->PlayMusic(s.music, s.playOffset);
+}
\ No newline at end of file
diff --git a/src/TitleScene.h b/src/TitleScene.h
new file mode 100644
index 0000000..1bb95b7
--- /dev/null
+++ b/src/TitleScene.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include "common.h"
+#include "GlitchPosteffect.h"
+#include "iScene.h"
+#include "Text.h"
+#include "Texture.h"
+
+namespace gj {
+
+
+class TitleScene : public iScene {
+ public:
+ static constexpr auto kListPath = "res/list.json";
+
+ TitleScene() = delete;
+ TitleScene(TitleScene&&) = delete;
+ TitleScene(const TitleScene&) = delete;
+
+ TitleScene& operator=(TitleScene&&) = delete;
+ TitleScene& operator=(const TitleScene&) = delete;
+
+ TitleScene(const Param& p);
+
+ UniqPtr Update(Frame& frame) override;
+
+ private:
+ struct Score {
+ std::string displayName;
+ std::string score;
+ std::string music;
+ double playOffset;
+ };
+
+ Param param_;
+
+ Text score_;
+ Text next_;
+ Text prev_;
+ Text guide_;
+
+ Texture logo_;
+
+ size_t select_index_;
+ std::vector list_;
+
+ GlitchPosteffect pe_;
+
+ void SelectScore_(size_t index);
+};
+
+
+}
\ No newline at end of file
diff --git a/src/common.h b/src/common.h
index c4edbbf..11de546 100644
--- a/src/common.h
+++ b/src/common.h
@@ -17,6 +17,8 @@ namespace gj {
using mat3 = ::linalg::mat;
using vec3 = ::linalg::vec;
+constexpr double kPi = 3.14159265358979323846264338327950288;
+
static inline std::wstring ConvertStrToWstr(const std::string& str) {
std::wstring ret;
@@ -38,6 +40,12 @@ static inline size_t CountWstrBytes(const std::wstring& str) {
return n;
}
+static inline uint64_t XorShift(uint64_t x) {
+ x = x ^ (x << 13);
+ x = x ^ (x >> 7);
+ return x ^ (x << 17);
+}
+
[[noreturn]]
static inline void Abort(const std::string& msg) {
diff --git a/src/iAudioDevice.h b/src/iAudioDevice.h
index b418850..a38a675 100644
--- a/src/iAudioDevice.h
+++ b/src/iAudioDevice.h
@@ -18,8 +18,11 @@ public:
virtual ~iAudioDevice() = default;
- virtual void PlayMusic(const std::string& path) = 0;
+ virtual void PlayMusic(const std::string& path, double offset) = 0;
virtual void StopMusic() = 0;
+
+ virtual void SetVolume(double amp) = 0;
+ virtual void SetLpfIntensity(double v) = 0;
};
diff --git a/src/iScene.h b/src/iScene.h
index f9feb02..c9c2fa3 100644
--- a/src/iScene.h
+++ b/src/iScene.h
@@ -4,6 +4,8 @@
#include "Frame.h"
#include "iAllocator.h"
+#include "iAudioDevice.h"
+#include "iClock.h"
#include "iDrawable.h"
#include "iWritable.h"
@@ -13,6 +15,12 @@ namespace gj {
class iScene {
public:
+ struct Param {
+ iAllocator* alloc;
+ iAudioDevice* audio;
+ const iClock* clock;
+ };
+
iScene() = default;
iScene(iScene&&) = default;
iScene(const iScene&) = default;
diff --git a/thirdparty/picojson.h b/thirdparty/picojson.h
new file mode 100644
index 0000000..76742fe
--- /dev/null
+++ b/thirdparty/picojson.h
@@ -0,0 +1,1200 @@
+/*
+ * Copyright 2009-2010 Cybozu Labs, Inc.
+ * Copyright 2011-2014 Kazuho Oku
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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.
+ */
+#ifndef picojson_h
+#define picojson_h
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include