Implementation of OSC settings and translator

Translations are in a config xml file in settings directory, and can be directly edited in text by the user. Settings UI allows changing Ports for incoming and outgoing UDP.
This commit is contained in:
Bruno Herbelin
2021-12-27 01:04:49 +01:00
parent 626eab7e8f
commit c79be090df
7 changed files with 228 additions and 50 deletions

View File

@@ -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<std::string> address = BaseToolkit::splitted(m.AddressPattern(), OSC_SEPARATOR);
std::list<std::string> 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 <osc> blocs as you want.\n"
"Each <osc> should contain one <from> osc address to translate into a <to> 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
// listen for answers in a separate thread
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<std::mutex> 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;
}
}

View File

@@ -1,6 +1,9 @@
#ifndef CONTROL_H
#define CONTROL_H
#include <map>
#include <string>
#include <condition_variable>
#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<std::string, std::string> translation_;
bool configOscLoad();
void configOscReset();
};
#endif // CONTROL_H

View File

@@ -260,6 +260,12 @@ void Settings::Save(uint64_t runtime)
timerConfNode->SetAttribute("stopwatch_duration", application.timer.stopwatch_duration);
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())
settingsFilename = SystemToolkit::full_filename(SystemToolkit::settings_path(), APP_SETTINGS);
@@ -302,9 +308,12 @@ void Settings::Load()
// impose C locale for all app
setlocale(LC_ALL, "C");
XMLDocument xmlDoc;
if (settingsFilename.empty())
// 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;
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)

View File

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

View File

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

View File

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

View File

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