Enhances audio effects.Adds TitleScene.

This commit is contained in:
falsycat 2021-08-27 16:42:58 +09:00
parent ea6fbd572e
commit 55b64ffa45
23 changed files with 1573 additions and 53 deletions

View File

@ -155,6 +155,7 @@
<ClCompile Include="src\PlayScene.cc" />
<ClCompile Include="src\ResultScene.cc" />
<ClCompile Include="src\Texture.cc" />
<ClCompile Include="src\TitleScene.cc" />
<ClCompile Include="src\Win32Console.cc" />
</ItemGroup>
<ItemGroup>
@ -183,6 +184,7 @@
<ClInclude Include="src\OffsetClock.h" />
<ClInclude Include="src\Period.h" />
<ClInclude Include="src\PlayScene.h" />
<ClInclude Include="src\GlitchPosteffect.h" />
<ClInclude Include="src\Rasterbuffer.h" />
<ClInclude Include="src\iConsole.h" />
<ClInclude Include="src\iAllocator.h" />
@ -197,6 +199,7 @@
<ClInclude Include="src\Texture.h" />
<ClInclude Include="src\TextureElement.h" />
<ClInclude Include="src\TickingClock.h" />
<ClInclude Include="src\TitleScene.h" />
<ClInclude Include="thirdparty\lauxlib.h" />
<ClInclude Include="thirdparty\linalg.h" />
<ClInclude Include="thirdparty\lua.h" />
@ -204,6 +207,7 @@
<ClInclude Include="thirdparty\luaconf.h" />
<ClInclude Include="thirdparty\lualib.h" />
<ClInclude Include="thirdparty\miniaudio.h" />
<ClInclude Include="thirdparty\picojson.h" />
<ClInclude Include="thirdparty\stb_truetype.h" />
<ClInclude Include="src\Win32Console.h" />
<ClInclude Include="thirdparty\utf8.h" />

View File

@ -48,6 +48,9 @@
<ClCompile Include="src\AudioDevice.cc">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\TitleScene.cc">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\iConsole.h">
@ -197,6 +200,15 @@
<ClInclude Include="src\MusicElementFactory.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\TitleScene.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="thirdparty\picojson.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\GlitchPosteffect.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Library Include="thirdparty\lua5.1.lib" />

View File

@ -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<uint64_t>(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<AudioDevice*>(ma->pUserData);
std::lock_guard _(dev->mtx_);
const double amp = dev->amp_.load();
const double lpf = dev->lpf_coe_.load();
float* dst = reinterpret_cast<float*>(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<size_t>(framecnt)*kChannel; ++i) {
dst[i] = static_cast<float>(dst[i]*amp);
dst[i] = static_cast<float>(dev->lpf_prev_*lpf + dst[i]*(1-lpf));
dev->lpf_prev_ = dst[i];
}
dev->time_.fetch_add(framecnt);
}

View File

@ -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<double> amp_ = 1;
std::atomic<double> lpf_coe_ = 0;
float lpf_prev_ = 0;
std::atomic<uint64_t> time_;
static void Callback_(ma_device* ma, void* out, const void* in, ma_uint32 framecnt);

View File

@ -14,7 +14,7 @@
namespace gj {
class Font {
class Font {
public:
Font() = delete;
Font(Font&&) = delete;

View File

@ -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<gj::iScene, gj::PlayScene>(std::move(param));
scene_ = alloc_->MakeUniq<gj::iScene, gj::TitleScene>(param);
}

48
src/GlitchPosteffect.h Normal file
View File

@ -0,0 +1,48 @@
#pragma once
#include <cstring>
#include <cmath>
#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<int32_t>(fb.width());
const int32_t h = static_cast<int32_t>(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<int32_t>(w*shift);
if (std::abs(shift) > 1) continue;
const size_t offset = static_cast<size_t>(y) * w;
float* src = ptr + offset;
float* dst = ptr + offset + s;
if (src > dst) std::swap(src, dst);
std::memmove(dst, src, static_cast<size_t>(w) - std::abs(s));
}
}
uint64_t seed = 1;
double maxShift = 0;
};
}

View File

@ -42,7 +42,7 @@ class GlyphElementFactory : public iElementFactory {
if (found != fonts_.end()) {
return *found->second;
}
auto f = alloc_->MakeUniq<Font>(alloc_, "res/font/"+name);
auto f = alloc_->MakeUniq<Font>(alloc_, name);
auto ptr = f.get();
fonts_[name] = std::move(f);
return *ptr;

View File

@ -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");
}
}
}

View File

@ -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());

View File

@ -12,6 +12,17 @@ namespace gj {
class MusicElement : public iElement {
public:
struct Param {
iAudioDevice* audio;
Period period;
std::string path;
double offset;
UniqPtr<iElementDriver> 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<double>(param_["volume"]);
const double lpf = std::get<double>(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<iElementDriver> drv_;
iElementDriver::Param param_;
};

View File

@ -22,11 +22,18 @@ class MusicElementFactory : public iElementFactory {
}
UniqPtr<iElement> Create(Param&& param) override {
if (param.custom.size() != 1) return nullptr;
if (param.custom.size() != 2) return nullptr;
const std::string name = std::get<std::string>(param.custom[0]);
const std::string path = std::get<std::string>(param.custom[0]);
const double offset = std::get<double>(param.custom[1]);
return alloc_->MakeUniq<iElement, MusicElement>(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<iElement, MusicElement>(std::move(p));
}
private:

View File

@ -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<Lua>(
alloc_, &store_, map, "res/score/" + p.score + ".lua");
lua_ = p.alloc->MakeUniq<Lua>(
p.alloc, &store_, map, path);
}
gj::UniqPtr<gj::iScene> gj::PlayScene::Update(Frame& f) {
if (store_.IsEmpty()) {
return alloc_->MakeUniq<iScene, ResultScene>(alloc_, clock_.parent(), sb_);
return param_.alloc->MakeUniq<iScene, ResultScene>(param_, sb_);
}
store_.Update(f);

View File

@ -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<iScene> Update(Frame& f) override;
private:
iAllocator* alloc_;
iAudioDevice* audio_;
Param param_;
OffsetClock clock_;

View File

@ -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::iScene> gj::ResultScene::Update(Frame& f) {
@ -51,5 +53,9 @@ gj::UniqPtr<gj::iScene> gj::ResultScene::Update(Frame& f) {
guide_.SetPosition((w-guide_.width())/2, static_cast<int32_t>(h*.8));
f.Add(&guide_);
if (f.input.find(' ') != std::string::npos) {
return param_.alloc->MakeUniq<iScene, TitleScene>(param_);
}
return nullptr;
}

View File

@ -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<iScene> Update(Frame& f) override;
private:
iAllocator* alloc_;
Param param_;
OffsetClock clock_;
Scoreboard sb_;

View File

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

129
src/TitleScene.cc Normal file
View File

@ -0,0 +1,129 @@
#include "TitleScene.h"
#include <cinttypes>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#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<std::string>();
s.music = obj["music"].get<std::string>();
s.score = obj["score"].get<std::string>();
s.playOffset = static_cast<uint64_t>(obj["playOffset"].get<double>());
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::iScene> 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<iScene, PlayScene>(
param_, list_[select_index_].displayName, list_[select_index_].score);
}
}
const uint64_t now = param_.clock->now();
const int32_t w = static_cast<int32_t>(frame.w);
const int32_t h = static_cast<int32_t>(frame.h);
const int32_t selector_y = static_cast<int32_t>(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<float>(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<int32_t>(w*.8-next_.width()), selector_y);
frame.Add(&next_);
prev_.SetPosition(static_cast<int32_t>(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);
}

53
src/TitleScene.h Normal file
View File

@ -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<iScene> 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<Score> list_;
GlitchPosteffect pe_;
void SelectScore_(size_t index);
};
}

View File

@ -17,6 +17,8 @@ namespace gj {
using mat3 = ::linalg::mat<double, 3, 3>;
using vec3 = ::linalg::vec<double, 3>;
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) {

View File

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

View File

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

1200
thirdparty/picojson.h vendored Normal file

File diff suppressed because it is too large Load Diff