C++로 프론트엔드 건드리기
2019-12-20, Fri
Emscripten을 이용하면 C/C++을 JS로 컴파일할 수 있다.
JS와 DOM HTML을 다 알아야 짤 수는 있지만(ㅋㅋ) 일단 JS 자체를 사용하진 않았다.
WebAudio로 FFT하기 여기 있는 첫번째 예제를 따라해본건데,
<select id='waveform'>
<option value='sine'>Sine</option>
<option value='square'>Square</option>
<option value='sawtooth'>Sawtooth</option>
<option value='triangle'>Triangle</option>
</select>
<input type="number" id="freq" value="440">
<button class="cppAudioButton" id="pl">Play</button>
<button class="cppAudioButton" id="st" style="display:none">Stop</button>
<style>
.cppAudioButton {
margin: 10px;
padding: 10px;
border-radius: 10px;
font-size: 20px;
}
</style>
<script src='a.out.js'></script>
이런 식으로 기본적인 틀을 잡아주고
#include <iostream>
#include <emscripten/bind.h>
#include <emscripten/val.h>
using emscripten::val;
using namespace std::literals;
bool hasStarted = false;
val AudioContext = val::null(), osc = val::null();
val html_element(const std::string& elemSelector)
{
return val::global("document").call<val>("querySelector", elemSelector);
}
void setType(const val& = val::null())
{
osc.set("type", html_element("#waveform")["value"]);
}
void setFreq(const val& = val::null())
{
osc["frequency"].call<void>("setValueAtTime", val::global("window").call<int>("parseInt", html_element("#freq")["value"]), AudioContext["currentTime"]);
}
void toggle()
{
val v = val::global("document").call<val>("querySelectorAll", ".cppAudioButton"s);
auto querys = emscripten::vecFromJSArray<val>(v);
for (auto elem : querys)
{
if (val::global("window").call<val>("getComputedStyle", elem)["display"].as<std::string>() == "none")
elem["style"].set("display", "block");
else
elem["style"].set("display", "none");
}
}
void play(const val& = val::null())
{
setFreq();
setType();
osc.call<void>("connect", AudioContext["destination"]);
if (!hasStarted)
{
osc.call<void>("start", 0);
hasStarted = true;
}
toggle();
}
void stop(const val& = val::null())
{
osc.call<void>("disconnect");
toggle();
}
int main()
{
val AudioCtx = val::global("AudioContext");
if (!AudioCtx.as<bool>())
{
std::cout << "No global AudioContext, trying webkitAudioContext\n";
AudioCtx = val::global("webkitAudioContext");
}
std::cout << "Got an AudioContext\n";
AudioContext = AudioCtx.new_();
osc = AudioContext.call<val>("createOscillator");
html_element("#pl").set("onclick", val::module_property("play"));
html_element("#st").set("onclick", val::module_property("stop"));
html_element("#waveform").set("onchange", val::module_property("setType"));
html_element("#freq").set("onchange", val::module_property("setFreq"));
}
EMSCRIPTEN_BINDINGS(my_module)
{
emscripten::function("play", play);
emscripten::function("stop", stop);
emscripten::function("setType", setType);
emscripten::function("setFreq", setFreq);
}
이렇게 구현한다. 위의 링크와 emscripten 레퍼런스를 보고 구현해본 건데, emscripten을 처음 쓰는 것도 아닌데도 한 6시간 붙잡고 있었다.
콜백에 람다를 넘기고 싶은데 그게 안돼서 한시간 정도 헤매다가 전역함수로 깔고, 상태도 어찌 해야 하나 하다가 전역변수로 전부 깔았다. 그 와중에 emscripten::val
은 기본 생성자가 delete되어 있어서 null로 초기화해줘야 된다. 이유는 모르겠으나 구현 마음대로겠지 뭐 ㅋㅋ
규모가 커지면 이렇게 구현이 안되겠지만 그건 그때 가서 디자인 패턴으로 만져야 할 것이기에 일단은 이렇게만 한다..