diff --git a/ControlManager.cpp b/ControlManager.cpp index 37487e0..5e96dff 100644 --- a/ControlManager.cpp +++ b/ControlManager.cpp @@ -24,12 +24,15 @@ #include "osc/OscOutboundPacketStream.h" +#include "defines.h" #include "Log.h" #include "Settings.h" #include "BaseToolkit.h" #include "Mixer.h" #include "Source.h" #include "ActionManager.h" +#include "SystemToolkit.h" +#include "tinyxml2Toolkit.h" #include "ControlManager.h" @@ -50,10 +53,11 @@ void Control::RequestListener::ProcessMessage( const osc::ReceivedMessage& m, #ifdef CONTROL_DEBUG Log::Info(CONTROL_OSC_MSG "received '%s' from %s", FullMessage(m).c_str(), sender); #endif - // TODO Preprocessing with Translator + // Preprocessing with Translator + std::string address_pattern = Control::manager().translate(m.AddressPattern()); // structured OSC address - std::list address = BaseToolkit::splitted(m.AddressPattern(), OSC_SEPARATOR); + std::list address = BaseToolkit::splitted(address_pattern, OSC_SEPARATOR); // // A wellformed OSC address is in the form '/vimix/target/attribute {arguments}' // First test: should have 3 elements and start with APP_NAME ('vimix') @@ -224,15 +228,6 @@ std::string Control::RequestListener::FullMessage( const osc::ReceivedMessage& m return message.str(); } -void Control::listen() -{ -#ifdef CONTROL_DEBUG - Log::Info(CONTROL_OSC_MSG "Accepting messages on port %d", Settings::application.control.osc_port_receive); -#endif - if (Control::manager().receiver_) - Control::manager().receiver_->Run(); -} - Control::Control() : receiver_(nullptr) { @@ -240,37 +235,151 @@ Control::Control() : receiver_(nullptr) Control::~Control() { - if (receiver_!=nullptr) { - receiver_->Break(); - delete receiver_; - } + terminate(); +} + +std::string Control::translate (std::string addresspattern) +{ + std::string translation = addresspattern; + + auto it_translation = translation_.find(addresspattern); + if ( it_translation != translation_.end() ) + translation = it_translation->second; + + return translation; +} + +bool Control::configOscLoad() +{ + // reset translations + translation_.clear(); + + // load osc config file + tinyxml2::XMLDocument xmlDoc; + tinyxml2::XMLError eResult = xmlDoc.LoadFile(Settings::application.control.osc_filename.c_str()); + + // found the file & managed to open it + if (eResult == tinyxml2::XML_SUCCESS) { + // parse all entries 'osc' + tinyxml2::XMLElement* osc = xmlDoc.FirstChildElement("osc"); + for( ; osc ; osc=osc->NextSiblingElement()) { + // get the 'from' entry + tinyxml2::XMLElement* from = osc->FirstChildElement("from"); + const char *str_from = from->GetText(); + // get the 'to' entry + tinyxml2::XMLElement* to = osc->FirstChildElement("to"); + const char *str_to = to->GetText(); + // if could get both, add to translator + if (str_from && str_to) + translation_[str_from] = str_to; + } + return true; + } + else + return false; +} + +void Control::configOscReset() +{ + // create and save a new configOscFilename_ + tinyxml2::XMLDocument xmlDoc; + tinyxml2::XMLDeclaration *pDec = xmlDoc.NewDeclaration(); + xmlDoc.InsertFirstChild(pDec); + tinyxml2::XMLComment *pComment = xmlDoc.NewComment("Complete the OSC message translator by adding as many blocs as you want.\n" + "Each should contain one osc address to translate into a osc address."); + xmlDoc.InsertEndChild(pComment); + + tinyxml2::XMLElement *osc = xmlDoc.NewElement("osc"); + + tinyxml2::XMLElement *from = xmlDoc.NewElement( "from" ); + tinyxml2::XMLText *text = xmlDoc.NewText("/example/osc/message"); + from->InsertEndChild(text); + osc->InsertEndChild(from); + + tinyxml2::XMLElement *to = xmlDoc.NewElement( "to" ); + text = xmlDoc.NewText("/vimix/log/info"); + to->InsertEndChild(text); + osc->InsertEndChild(to); + + xmlDoc.InsertEndChild(osc); + xmlDoc.SaveFile(Settings::application.control.osc_filename.c_str()); + + // reset and fill translation with default example + translation_.clear(); + translation_["/example/osc/message"] = "/vimix/log/info"; } bool Control::init() { + // + // terminate before init (allows calling init() multiple times) + // + terminate(); + + // + // load OSC Translator + // + if (configOscLoad()) + Log::Info(CONTROL_OSC_MSG "Loaded %d translations.", translation_.size()); + else + configOscReset(); + + // + // launch OSC listener + // try { // try to create listenning socket // through exception runtime if fails receiver_ = new UdpListeningReceiveSocket( IpEndpointName( IpEndpointName::ANY_ADDRESS, Settings::application.control.osc_port_receive ), &listener_ ); - } - catch (const std::runtime_error&) { - // arg, the receiver could not be initialized - // because the port was not available - receiver_ = nullptr; - } + // listen for answers in a separate thread + std::thread(listen).detach(); - // listen for answers - std::thread(listen).detach(); + // inform user + IpEndpointName ip = receiver_->LocalEndpointFor( IpEndpointName( NetworkToolkit::hostname().c_str(), + Settings::application.control.osc_port_receive )); + char *addresseip = (char *)malloc(IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH); + ip.AddressAndPortAsString(addresseip); + + Log::Info(CONTROL_OSC_MSG "Listening to UDP on %s", addresseip); + } + catch (const std::runtime_error &e) { + // arg, the receiver could not be initialized + // (often because the port was not available) + receiver_ = nullptr; + Log::Warning(CONTROL_OSC_MSG "Failed to init listener on port %d; %s", Settings::application.control.osc_port_receive, e.what()); + } return receiver_ != nullptr; } +void Control::listen() +{ + if (Control::manager().receiver_) + Control::manager().receiver_->Run(); + + Control::manager().receiver_end_.notify_all(); +} + void Control::terminate() { - if (receiver_!=nullptr) + if ( receiver_ != nullptr ) { + + // request termination of receiver receiver_->AsynchronousBreak(); + + // wait for the receiver_end_ notification + std::mutex mtx; + std::unique_lock lck(mtx); + // if waited more than 2 seconds, its dead :( + if ( receiver_end_.wait_for(lck,std::chrono::seconds(2)) == std::cv_status::timeout) + Log::Warning(CONTROL_OSC_MSG "Failed to terminate."); + + // delete receiver and ready to initialize + delete receiver_; + receiver_ = nullptr; + } } diff --git a/ControlManager.h b/ControlManager.h index 42c5ff4..7d57cb8 100644 --- a/ControlManager.h +++ b/ControlManager.h @@ -1,6 +1,9 @@ #ifndef CONTROL_H #define CONTROL_H +#include +#include +#include #include "NetworkToolkit.h" #define OSC_SYNC "/sync" @@ -61,7 +64,7 @@ public: bool init(); void terminate(); - // void setOscPort(int P); + std::string translate (std::string addresspattern); protected: @@ -89,8 +92,13 @@ private: static void listen(); RequestListener listener_; + std::condition_variable receiver_end_; UdpListeningReceiveSocket *receiver_; + std::map translation_; + bool configOscLoad(); + void configOscReset(); + }; #endif // CONTROL_H diff --git a/Settings.cpp b/Settings.cpp index 5e8303e..a88db61 100644 --- a/Settings.cpp +++ b/Settings.cpp @@ -258,7 +258,13 @@ void Settings::Save(uint64_t runtime) timerConfNode->SetAttribute("link_quantum", application.timer.link_quantum); timerConfNode->SetAttribute("link_start_stop_sync", application.timer.link_start_stop_sync); timerConfNode->SetAttribute("stopwatch_duration", application.timer.stopwatch_duration); - pRoot->InsertEndChild(timerConfNode); + pRoot->InsertEndChild(timerConfNode); + + // Controller + XMLElement *controlConfNode = xmlDoc.NewElement( "Control" ); + controlConfNode->SetAttribute("osc_port_receive", application.control.osc_port_receive); + controlConfNode->SetAttribute("osc_port_send", application.control.osc_port_send); + // First save : create filename if (settingsFilename.empty()) @@ -302,9 +308,12 @@ void Settings::Load() // impose C locale for all app setlocale(LC_ALL, "C"); + // set filenames from settings path + application.control.osc_filename = SystemToolkit::full_filename(SystemToolkit::settings_path(), OSC_CONFIG_FILE); + settingsFilename = SystemToolkit::full_filename(SystemToolkit::settings_path(), APP_SETTINGS); + + // try to load settings file XMLDocument xmlDoc; - if (settingsFilename.empty()) - settingsFilename = SystemToolkit::full_filename(SystemToolkit::settings_path(), APP_SETTINGS); XMLError eResult = xmlDoc.LoadFile(settingsFilename.c_str()); // do not warn if non existing file @@ -314,8 +323,10 @@ void Settings::Load() else if (XMLResultError(eResult)) return; + // first element should be called by the application name XMLElement *pRoot = xmlDoc.FirstChildElement(application.name.c_str()); - if (pRoot == nullptr) return; + if (pRoot == nullptr) + return; // cancel on different root name if (application.name.compare( string( pRoot->Value() ) ) != 0 ) @@ -535,6 +546,13 @@ void Settings::Load() timerconfnode->QueryUnsigned64Attribute("stopwatch_duration", &application.timer.stopwatch_duration); } + // bloc Controller + XMLElement *controlconfnode = pRoot->FirstChildElement("Control"); + if (controlconfnode != nullptr) { + controlconfnode->QueryIntAttribute("osc_port_receive", &application.control.osc_port_receive); + controlconfnode->QueryIntAttribute("osc_port_send", &application.control.osc_port_send); + } + } void Settings::History::push(const string &filename) diff --git a/Settings.h b/Settings.h index 2b6051b..b66f4a6 100644 --- a/Settings.h +++ b/Settings.h @@ -193,6 +193,7 @@ struct ControllerConfig { int osc_port_receive; int osc_port_send; + std::string osc_filename; ControllerConfig() { osc_port_receive = OSC_PORT_RECV_DEFAULT; diff --git a/SourceCallback.cpp b/SourceCallback.cpp index 5fcf19b..f0ccea6 100644 --- a/SourceCallback.cpp +++ b/SourceCallback.cpp @@ -97,10 +97,10 @@ void Loom::update(Source *s, float dt) initialized_ = true; } - // calculate amplitude of movement + // time passed progress_ += dt; - // perform movement + // move target by speed vector (in the direction of step_, amplitude of speed * time (in second)) pos_ += step_ * ( speed_ * dt * 0.001f); // apply alpha if valid in range [0 1] @@ -108,7 +108,7 @@ void Loom::update(Source *s, float dt) if ( alpha > DELTA_ALPHA && alpha < 1.0 - DELTA_ALPHA ) s->group(View::MIXING)->translation_ = glm::vec3(pos_, s->group(View::MIXING)->translation_.z); - // timeout + // time-out if ( progress_ > duration_ ) { // done finished_ = true; @@ -135,14 +135,14 @@ void SetDepth::update(Source *s, float dt) initialized_ = true; } - // calculate amplitude of movement + // time passed progress_ += dt; // perform movement if ( ABS(duration_) > 0.f) s->group(View::LAYER)->translation_.z = start_ + (progress_/duration_) * (target_ - start_); - // end of movement + // time-out if ( progress_ > duration_ ) { // apply depth to target s->group(View::LAYER)->translation_.z = target_; @@ -203,14 +203,14 @@ void Grab::update(Source *s, float dt) initialized_ = true; } - // calculate amplitude of movement + // time passed progress_ += dt; - // perform movement + // move target by speed vector * time (in second) glm::vec2 pos = start_ + speed_ * ( dt * 0.001f); s->group(View::GEOMETRY)->translation_ = glm::vec3(pos, s->group(View::GEOMETRY)->translation_.z); - // timeout + // time-out if ( progress_ > duration_ ) { // done finished_ = true; @@ -237,14 +237,14 @@ void Resize::update(Source *s, float dt) initialized_ = true; } - // calculate amplitude of movement + // time passed progress_ += dt; - // perform movement + // move target by speed vector * time (in second) glm::vec2 scale = start_ + speed_ * ( dt * 0.001f); s->group(View::GEOMETRY)->scale_ = glm::vec3(scale, s->group(View::GEOMETRY)->scale_.z); - // timeout + // time-out if ( progress_ > duration_ ) { // done finished_ = true; diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index f682c69..c4c8b4d 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -62,6 +62,7 @@ using namespace std; #include "ImGuiToolkit.h" #include "ImGuiVisitor.h" #include "RenderingManager.h" +#include "ControlManager.h" #include "Connection.h" #include "ActionManager.h" #include "Resource.h" @@ -5409,7 +5410,7 @@ void Navigator::RenderMainPannelSettings() // Recording preferences // ImGuiToolkit::Spacing(); - ImGui::Text("Recording"); + ImGui::Text("Record"); // select CODEC and FPS ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN); @@ -5436,22 +5437,62 @@ void Navigator::RenderMainPannelSettings() ImGui::SliderInt("Buffer", &Settings::application.record.buffering_mode, 0, IM_ARRAYSIZE(VideoRecorder::buffering_preset_name)-1, VideoRecorder::buffering_preset_name[Settings::application.record.buffering_mode]); - ImGuiToolkit::HelpMarker("Priority when buffer is full and recorder skips frames;\n" - ICON_FA_CARET_RIGHT " Clock: variable framerate, correct duration.\n" - ICON_FA_CARET_RIGHT " Framerate: correct framerate, shorter duration."); + ImGuiToolkit::HelpMarker("Priority when buffer is full and recorder has to skip frames;\n" + ICON_FA_CARET_RIGHT " Duration:\n Variable framerate, correct duration.\n" + ICON_FA_CARET_RIGHT " Framerate:\n Correct framerate, shorter duration."); ImGui::SameLine(0); ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN); ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); - ImGui::Combo("Priority", &Settings::application.record.priority_mode, "Clock\0Framerate\0"); + ImGui::Combo("Priority", &Settings::application.record.priority_mode, "Duration\0Framerate\0"); + + // + // OSC preferences + // + ImGuiToolkit::Spacing(); + ImGui::Text("OSC"); + + char msg[256]; + sprintf(msg, "You can send OSC messages via UDP to the IP address %s", NetworkToolkit::host_ips()[1].c_str()); + ImGuiToolkit::HelpMarker(msg, ICON_FA_INFO_CIRCLE); + ImGui::SameLine(0); + + ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + char bufreceive[7] = ""; + sprintf(bufreceive, "%d", Settings::application.control.osc_port_receive); + ImGui::InputTextWithHint("Port in", "UDP Port to receive", bufreceive, 7, ImGuiInputTextFlags_CharsDecimal); + if (ImGui::IsItemDeactivatedAfterEdit()){ + if ( BaseToolkit::is_a_number(bufreceive, &Settings::application.control.osc_port_receive)) + Control::manager().init(); + } + + ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN); + ImGui::SetNextItemWidth(IMGUI_RIGHT_ALIGN); + char bufsend[7] = ""; + sprintf(bufsend, "%d", Settings::application.control.osc_port_send); + ImGui::InputTextWithHint("Port out", "UDP Port to send", bufsend, 7, ImGuiInputTextFlags_CharsDecimal); + if (ImGui::IsItemDeactivatedAfterEdit()){ + if ( BaseToolkit::is_a_number(bufsend, &Settings::application.control.osc_port_send)) + Control::manager().init(); + } + + ImGui::SetCursorPosX(-1.f * IMGUI_RIGHT_ALIGN); + const float w = IMGUI_RIGHT_ALIGN - ImGui::GetFrameHeightWithSpacing(); + ImGuiToolkit::ButtonOpenUrl( "Edit", Settings::application.control.osc_filename.c_str(), ImVec2(w, 0) ); + ImGui::SameLine(0, 6); + if ( ImGuiToolkit::IconButton(5, 15, "Reload") ) + Control::manager().init(); + ImGui::SameLine(); + ImGui::Text("Translator"); // // System preferences // ImGuiToolkit::Spacing(); +// ImGuiToolkit::HelpMarker("If you encounter some rendering issues on your machine, " +// "you can try to disable some of the OpenGL optimizations below."); +// ImGui::SameLine(); ImGui::Text("System"); - ImGui::SameLine( ImGui::GetContentRegionAvailWidth() IMGUI_RIGHT_ALIGN * 0.8); - ImGuiToolkit::HelpMarker("If you encounter some rendering issues on your machine, " - "you can try to disable some of the OpenGL optimizations below."); static bool need_restart = false; static bool vsync = (Settings::application.render.vsync > 0); @@ -5463,10 +5504,10 @@ void Navigator::RenderMainPannelSettings() change |= ImGuiToolkit::ButtonSwitch( "Blit framebuffer", &blit); change |= ImGuiToolkit::ButtonSwitch( "Antialiasing framebuffer", &multi); // hardware support deserves more explanation - ImGuiToolkit::HelpMarker("If enabled, tries to find a platform adapted hardware accelerated " + ImGuiToolkit::HelpMarker("If enabled, tries to find a platform adapted hardware-accelerated " "driver to decode (read) or encode (record) videos.", ICON_FA_MICROCHIP); ImGui::SameLine(0); - change |= ImGuiToolkit::ButtonSwitch( "Hardware video de/encoding", &gpu); + change |= ImGuiToolkit::ButtonSwitch( "Hardware video en/decoding", &gpu); if (change) { need_restart = ( vsync != (Settings::application.render.vsync > 0) || diff --git a/defines.h b/defines.h index 43462f9..732c1c4 100644 --- a/defines.h +++ b/defines.h @@ -107,5 +107,6 @@ #define OSC_PORT_RECV_DEFAULT 7000 #define OSC_PORT_SEND_DEFAULT 3000 +#define OSC_CONFIG_FILE "osc.xml" #endif // VMIX_DEFINES_H