2022-03-20 11:57:12 +01:00
|
|
|
#include "cinder/app/App.h"
|
|
|
|
|
#include "cinder/app/RendererGl.h"
|
|
|
|
|
#include "cinder/gl/gl.h"
|
2022-03-20 12:25:41 +01:00
|
|
|
#include "cinder/gl/TextureFont.h"
|
|
|
|
|
#include "cinder/osc/Osc.h"
|
|
|
|
|
#include "cinder/Json.h"
|
|
|
|
|
#include "cinder/Log.h"
|
|
|
|
|
#include "cinder/Timeline.h"
|
|
|
|
|
|
|
|
|
|
#include "Voice.h"
|
2022-03-20 11:57:12 +01:00
|
|
|
|
|
|
|
|
using namespace ci;
|
|
|
|
|
using namespace ci::app;
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
2022-03-20 12:25:41 +01:00
|
|
|
int WINDOW_H = 1500;
|
|
|
|
|
float RATIO = 0.5625;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*- NET -*/
|
|
|
|
|
using Receiver = osc::ReceiverUdp;
|
|
|
|
|
using protocol = asio::ip::udp;
|
|
|
|
|
|
|
|
|
|
std::string hexA(ColorA8u c) {
|
|
|
|
|
//https://stackoverflow.com/questions/5100718/integer-to-hex-string-in-c
|
|
|
|
|
uint32_t cu = c.a << 24 | c.r << 16 | c.g << 8 | c.b;
|
|
|
|
|
std::stringstream hex_s;
|
|
|
|
|
hex_s << "0x" << std::setfill ('0') << std::setw(sizeof(uint32_t)) << std::hex << cu;
|
|
|
|
|
return hex_s.str();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ColorA8u lerp_color(const ColorA8u &start, const ColorA8u &end, float t ) {
|
|
|
|
|
uint8_t r = abs(floor(start.r + (end.r - start.r) * t));
|
|
|
|
|
uint8_t g = abs(floor(start.g + (end.g - start.g) * t));
|
|
|
|
|
uint8_t b = abs(floor(start.b + (end.b - start.b) * t));
|
|
|
|
|
uint8_t a = abs(floor(start.a + (end.a - start.a) * t));
|
|
|
|
|
return ColorA8u(r, g, b, a);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class VoiceMachineApp : public App {
|
2022-03-20 11:57:12 +01:00
|
|
|
public:
|
2022-03-20 12:25:41 +01:00
|
|
|
VoiceMachineApp();
|
|
|
|
|
void setup() override;
|
|
|
|
|
void update() override;
|
|
|
|
|
void draw() override;
|
|
|
|
|
void cleanup() override;
|
|
|
|
|
|
|
|
|
|
void keyDown( KeyEvent event ) override;
|
|
|
|
|
|
|
|
|
|
void stage_config();
|
|
|
|
|
void save_config();
|
|
|
|
|
|
|
|
|
|
void utterance(Voice* v);
|
|
|
|
|
void change(Voice* v);
|
|
|
|
|
void clear();
|
|
|
|
|
|
|
|
|
|
std::map<std::string, Voice*> _map_voices;
|
|
|
|
|
Voice* _current_voice = NULL;
|
|
|
|
|
|
|
|
|
|
Anim<ColorA8u> _background = ColorA8u::black();
|
|
|
|
|
Anim<ColorA8u> _voice_color = ColorA8u::white();
|
|
|
|
|
|
|
|
|
|
// ColorA8u _background = ColorA8u::black();
|
|
|
|
|
// ColorA8u _voice_color = ColorA8u::white();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*- NET -*/
|
|
|
|
|
std::shared_ptr<asio::io_service> _io_service;
|
|
|
|
|
std::shared_ptr<asio::io_service::work> _work;
|
|
|
|
|
std::thread _thread;
|
|
|
|
|
std::shared_ptr<Receiver> _receiver;
|
|
|
|
|
std::map<uint64_t, protocol::endpoint> _connections;
|
|
|
|
|
std::mutex _command_mutex;
|
|
|
|
|
|
|
|
|
|
// VoiceAloud audio = VoiceAloud(100, 0.5f);
|
|
|
|
|
|
2022-03-20 11:57:12 +01:00
|
|
|
};
|
|
|
|
|
|
2022-03-20 12:25:41 +01:00
|
|
|
VoiceMachineApp::VoiceMachineApp()
|
|
|
|
|
: _io_service(new asio::io_service),
|
|
|
|
|
_work(new asio::io_service::work(*_io_service))
|
|
|
|
|
// _receiver(PORT, protocol::v4(), *_io_service)
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
void VoiceMachineApp::stage_config()
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
const JsonTree config(loadAsset("voice.config.json"));
|
|
|
|
|
|
|
|
|
|
// RECEIVER PORT
|
|
|
|
|
int port = config["port_voicemachine"].getValue<int>();
|
|
|
|
|
_receiver = std::shared_ptr<Receiver>(new Receiver(port, protocol::v4(), *_io_service));
|
|
|
|
|
|
|
|
|
|
//VOICES
|
|
|
|
|
for(auto &voice: config["voices"].getChildren()){
|
|
|
|
|
std::string name = voice["name"].getValue();
|
|
|
|
|
std::string channel = voice["osc_channel"]["root"].getValue();
|
|
|
|
|
std::string font = voice["font"].getValue();
|
|
|
|
|
int size = voice["size"].getValue<int>();
|
|
|
|
|
std::string hexcolor = voice["color"].getValue();
|
|
|
|
|
ColorA8u color = ColorA8u::hexA(std::stoul(hexcolor, nullptr, 16));
|
|
|
|
|
std::string hexbackground = voice["background"].getValue();
|
|
|
|
|
ColorA background = ColorA::hexA(std::stoul(hexbackground, nullptr, 16));
|
|
|
|
|
|
|
|
|
|
std::function<void(Voice*)> utterance_cb = [=](Voice* v) {
|
|
|
|
|
this->utterance(v);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::function<void(Voice*)> change_cb = [=](Voice* v) {
|
|
|
|
|
this->change(v);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Voice* v = new Voice(name, channel, font, size, color, background, utterance_cb, change_cb);
|
|
|
|
|
_map_voices[name] = v;
|
|
|
|
|
|
|
|
|
|
// //garbage
|
|
|
|
|
// _current_voice = v;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string command_channel = config["command_osc_channel"].getValue();
|
|
|
|
|
|
|
|
|
|
_receiver->setListener(command_channel,
|
|
|
|
|
[&](const osc::Message &m){
|
|
|
|
|
std::lock_guard<std::mutex> lock(_command_mutex);
|
|
|
|
|
std::string cmd = m[0].string();
|
|
|
|
|
|
|
|
|
|
console() << "command: " << cmd << endl;
|
|
|
|
|
|
|
|
|
|
if(cmd == "save") save_config();
|
|
|
|
|
else if (cmd == "clear") clear();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
} catch (ci::Exception &exc) {
|
|
|
|
|
CI_LOG_D("Failed to stage config: " << exc.what());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VoiceMachineApp::save_config()
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
JsonTree config(loadAsset("voice.config.json"));
|
|
|
|
|
|
|
|
|
|
JsonTree voices = config.getChild("voices");
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < voices.getNumChildren(); i++) {
|
|
|
|
|
Voice* v = _map_voices[voices[i]["name"].getValue()];
|
|
|
|
|
voices[i]["font"] = JsonTree("font", v->_font.getName());
|
|
|
|
|
voices[i]["size"] = JsonTree("size", v->_size);
|
|
|
|
|
voices[i]["color"] = JsonTree("color", hexA(v->_color));
|
|
|
|
|
voices[i]["background"] = JsonTree("background", hexA(v->_background));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
config["voices"] = voices;
|
|
|
|
|
config.write(getAssetPath("voice.config.json"));
|
|
|
|
|
|
|
|
|
|
CI_LOG_D("saved config");
|
|
|
|
|
|
|
|
|
|
} catch (ci::Exception &exc) {
|
|
|
|
|
CI_LOG_D("Failed to save config: " << exc.what());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VoiceMachineApp::utterance(Voice* v)
|
|
|
|
|
{
|
|
|
|
|
console() << "utterance : " << v->_name << endl;
|
|
|
|
|
if(_current_voice != v) {
|
|
|
|
|
_current_voice = v;
|
|
|
|
|
timeline().apply( &_voice_color, _current_voice->_color, 4.5f, EaseInSine(), lerp_color );
|
|
|
|
|
timeline().apply( &_background, _current_voice->_background, 7.5f, EaseInCubic(), lerp_color ).appendTo(&_voice_color);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VoiceMachineApp::change(Voice* v)
|
|
|
|
|
{
|
|
|
|
|
console() << "change : " << v->_name << endl;
|
|
|
|
|
_current_voice = v;
|
|
|
|
|
_background = _current_voice->_background;
|
|
|
|
|
_voice_color = _current_voice->_color;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void VoiceMachineApp::clear()
|
|
|
|
|
{
|
|
|
|
|
console() << "clearing" << endl;
|
|
|
|
|
_current_voice = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VoiceMachineApp::setup()
|
2022-03-20 11:57:12 +01:00
|
|
|
{
|
2022-03-20 12:25:41 +01:00
|
|
|
|
|
|
|
|
// audio.setup();
|
|
|
|
|
|
|
|
|
|
stage_config();
|
|
|
|
|
|
|
|
|
|
for(const auto& [n, v]: _map_voices)
|
|
|
|
|
v->setup(*_receiver);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
_receiver->bind();
|
|
|
|
|
} catch (const osc::Exception &e) {
|
|
|
|
|
CI_LOG_E("Error receiver bind: " << e.what() << " - " << e.value());
|
|
|
|
|
quit();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_receiver->listen(
|
|
|
|
|
[](asio::error_code e, protocol::endpoint end) -> bool {
|
|
|
|
|
if(e){
|
|
|
|
|
CI_LOG_E("Error receiver listen: " << e.message() << " - " << e.value() << " - " << end);
|
|
|
|
|
return false;
|
|
|
|
|
} return true;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//3. start thread
|
|
|
|
|
_thread = std::thread(std::bind(
|
|
|
|
|
[](std::shared_ptr<asio::io_service> &service){
|
|
|
|
|
service->run();
|
|
|
|
|
}, _io_service));
|
2022-03-20 11:57:12 +01:00
|
|
|
}
|
|
|
|
|
|
2022-03-20 12:25:41 +01:00
|
|
|
void VoiceMachineApp::update()
|
2022-03-20 11:57:12 +01:00
|
|
|
{
|
2022-03-20 12:25:41 +01:00
|
|
|
for(const auto& [n, v]: _map_voices)
|
|
|
|
|
v->update();
|
|
|
|
|
|
|
|
|
|
// audio.update();
|
2022-03-20 11:57:12 +01:00
|
|
|
}
|
|
|
|
|
|
2022-03-20 12:25:41 +01:00
|
|
|
void VoiceMachineApp::draw()
|
2022-03-20 11:57:12 +01:00
|
|
|
{
|
2022-03-20 12:25:41 +01:00
|
|
|
gl::setMatricesWindow( getWindowSize() );
|
|
|
|
|
gl::enableAlphaBlending();
|
|
|
|
|
|
|
|
|
|
gl::clear(_background.value());
|
|
|
|
|
gl::color(_voice_color);
|
|
|
|
|
|
|
|
|
|
if(_current_voice){
|
|
|
|
|
Rectf bounds( 40, _current_voice->_tex->getAscent() + 40, getWindowWidth() - 40, getWindowHeight() - 40 );
|
|
|
|
|
_current_voice->draw(bounds);
|
|
|
|
|
}
|
2022-03-20 11:57:12 +01:00
|
|
|
}
|
|
|
|
|
|
2022-03-20 12:25:41 +01:00
|
|
|
void VoiceMachineApp::cleanup()
|
2022-03-20 11:57:12 +01:00
|
|
|
{
|
2022-03-20 12:25:41 +01:00
|
|
|
_work.reset();
|
|
|
|
|
_io_service->stop();
|
|
|
|
|
_thread.join();
|
2022-03-20 11:57:12 +01:00
|
|
|
}
|
|
|
|
|
|
2022-03-20 12:25:41 +01:00
|
|
|
void VoiceMachineApp::keyDown( KeyEvent event )
|
|
|
|
|
{
|
|
|
|
|
switch( event.getChar() ) {
|
|
|
|
|
case 's':
|
|
|
|
|
{
|
|
|
|
|
CI_LOG_D("saving config...");
|
|
|
|
|
save_config();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
// case '-':
|
|
|
|
|
// mFont = Font( mFont.getName(), mFont.getSize() - 1 );
|
|
|
|
|
// mTextureFont = gl::TextureFont::create( mFont );
|
|
|
|
|
// break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auto settingsFunc = [](App::Settings *settings) {
|
|
|
|
|
settings->setMultiTouchEnabled( false );
|
|
|
|
|
settings->setFrameRate(25);
|
|
|
|
|
settings->setWindowSize(int(WINDOW_H * RATIO), WINDOW_H );
|
|
|
|
|
// settings->setFullScreen();
|
|
|
|
|
//FullScreenOptions& exclusive( bool enable = true )
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CINDER_APP( VoiceMachineApp, RendererGl, settingsFunc )
|