From 3fe8c3f6b1c145d540c2451741a86357f2f49455 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Fri, 18 Mar 2022 15:47:57 +0100 Subject: [PATCH 1/4] initial code for a multi osc output node --- Actors/OscMultiOut.cpp | 103 +++++++++++++++++++++++++++++++++++++++++ Actors/OscMultiOut.h | 23 +++++++++ actors.h | 1 + stage.cpp | 1 + 4 files changed, 128 insertions(+) create mode 100644 Actors/OscMultiOut.cpp create mode 100644 Actors/OscMultiOut.h diff --git a/Actors/OscMultiOut.cpp b/Actors/OscMultiOut.cpp new file mode 100644 index 00000000..8590e3c3 --- /dev/null +++ b/Actors/OscMultiOut.cpp @@ -0,0 +1,103 @@ +#include "OscMultiOut.h" +#include +#include + +const char * +OSCMultiOut::capabilities = "capabilities\n" + " data\n" + " name = \"host list\"\n" + " type = \"list\"\n" + " help = \"List of hosts\"\n" + " name\n" + " type = \"string\"\n" + " help = \"Just a name for your convenience\"\n" + " ip\n" + " type = \"ipaddress\"\n" + " help = \"The ipaddress of the host to send to\"\n" + " port\n" + " type = \"int\"\n" + " help = \"The port number of the host to send to\"\n" + " value = \"6200\"\n" + " min = \"1\"\n" + " max = \"65534\"\n" + "inputs\n" + " input\n" + " type = \"OSC\"\n"; + +zmsg_t* OSCMultiOut::handleInit( sphactor_event_t * ev ) { + this->dgrams = zsock_new_dgram("udp://*:*"); + return Sphactor::handleInit(ev); +} + +zmsg_t* OSCMultiOut::handleStop( sphactor_event_t * ev ) { + if ( this->dgrams != NULL ) { + zsock_destroy(&this->dgrams); + this->dgrams = NULL; + } + + return Sphactor::handleStop(ev); +} + +zmsg_t* OSCMultiOut::handleSocket( sphactor_event_t * ev ) { + if ( ev->msg == NULL ) return NULL; + if ( this->dgrams == NULL ) return ev->msg; + + byte *msgBuffer; + zframe_t* frame; + + do { + frame = zmsg_pop(ev->msg); + if ( frame ) { + msgBuffer = zframe_data(frame); + size_t len = zframe_size(frame); + + char *host = (char *)zlist_first(this->hosts); + while ( host ) + { + char *url = strchr(host, ','); + assert(url); + zstr_sendm(dgrams, url); + int rc = zsock_send(dgrams, "b", msgBuffer, len); + if ( rc != 0 ) { + zsys_info("Error sending zosc message to: %s, %i", url, rc); + } + host = (char *)zlist_next(this->hosts); + } + } + } while (frame != NULL ); + + return Sphactor::handleSocket(ev); +} + +zmsg_t* OSCMultiOut::handleAPI( sphactor_event_t * ev ) { + //pop msg for command + char * cmd = zmsg_popstr(ev->msg); + if (cmd) { + if ( streq(cmd, "SET HOSTS") ) { + // clear the current list + if (this->hosts) + zlist_destroy(&this->hosts); + this->hosts = zlist_new(); + zlist_autofree(this->hosts); + + // fill with new data + char *name; + while ( name = zmsg_popstr(ev->msg) ) + { + char *ip = zmsg_popstr(ev->msg); + assert(ip); + char *port = zmsg_popstr(ev->msg); + assert(port); + char host[PATH_MAX]; + snprintf(host, PATH_MAX, "%s,%s:%s"); + zlist_append(this->hosts, (void *)&host[0]); + zstr_free(&name); + zstr_free(&ip); + zstr_free(&port); + } + } + zstr_free(&cmd); + } + + return Sphactor::handleAPI(ev); +} diff --git a/Actors/OscMultiOut.h b/Actors/OscMultiOut.h new file mode 100644 index 00000000..1c93c9a5 --- /dev/null +++ b/Actors/OscMultiOut.h @@ -0,0 +1,23 @@ +#ifndef OSCMULTIOUT_H +#define OSCMULTIOUT_H +#include "libsphactor.hpp" + +class OSCMultiOut : public Sphactor +{ +public: + OSCMultiOut() : Sphactor() {} + static const char *capabilities; + zsock_t* dgrams = NULL; + zlist_t* hosts = nullptr; + + zmsg_t *handleInit(sphactor_event_t *ev); + + zmsg_t *handleAPI(sphactor_event_t *ev); + + zmsg_t *handleSocket(sphactor_event_t *ev); + + zmsg_t *handleStop(sphactor_event_t *ev); + +}; + +#endif // OSCMULTIOUT_H diff --git a/actors.h b/actors.h index 01b2df64..16c16187 100644 --- a/actors.h +++ b/actors.h @@ -3,6 +3,7 @@ #include "Actors/HTTPLaunchpod.h" #include "Actors/OSCOutputActor.h" +#include "Actors/OscMultiOut.h" #include "Actors/Midi2OSCActor.h" #include "Actors/NatNetActor.h" #include "Actors/NatNet2OSCActor.h" diff --git a/stage.cpp b/stage.cpp index a24b501c..b3b0e2b3 100644 --- a/stage.cpp +++ b/stage.cpp @@ -236,6 +236,7 @@ void RegisterActors() { sphactor_register("HTTPLaunchpod", &httplaunchpodactor_handler, zconfig_str_load(httplaunchpodactorcapabilities), &httplaunchpodactor_new_helper, NULL); // https://stackoverflow.com/questions/65957511/typedef-for-a-registering-a-constructor-function-in-c sphactor_register( "OSC Output", OSCOutput::capabilities); + sphactor_register( "OSC Multi Output", OSCMultiOut::capabilities); sphactor_register( "NatNet", NatNet::capabilities ); sphactor_register( "NatNet2OSC", NatNet2OSC::capabilities ); sphactor_register( "Midi2OSC", Midi2OSC::capabilities ); From f414284c1f7d4a0fec36417efb1d35fcdbda359c Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Fri, 25 Mar 2022 11:43:56 +0100 Subject: [PATCH 2/4] initial list type rendering BROKEN STILL --- ActorContainer.h | 134 +++++++++++++++++++++++++++++++++++++++++ Actors/OscMultiOut.cpp | 1 + 2 files changed, 135 insertions(+) diff --git a/ActorContainer.h b/ActorContainer.h index fcbfc311..2498e462 100644 --- a/ActorContainer.h +++ b/ActorContainer.h @@ -337,6 +337,9 @@ struct ActorContainer { else if ( streq(typeStr, "mediacontrol")) { RenderMediacontrol( nameStr, data ); } + else if ( streq(typeStr, "list")) { + RenderList( nameStr, data ); + } else if ( streq(typeStr, "trigger")) { RenderTrigger( nameStr, data ); } @@ -465,6 +468,137 @@ struct ActorContainer { *position += sizeof(T); } + // Make the UI compact because there are so many fields + static void PushStyleCompact() + { + ImGuiStyle& style = ImGui::GetStyle(); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(style.FramePadding.x, (float)(int)(style.FramePadding.y * 0.60f))); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, (float)(int)(style.ItemSpacing.y * 0.60f))); + } + + static void PopStyleCompact() + { + ImGui::PopStyleVar(2); + } + void RenderList(const char *name, zconfig_t *data) + { + ImVec2 size = ImVec2(300,100); // what's a reasonable size? + ImGui::BeginChild("##", size, false, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoSavedSettings ); + // The list capability expects a tree of zconfig data. + // Every node is a column + zconfig_t *namec = zconfig_locate(data, "name"); + int colcount = 0; + zconfig_t *columnc = zconfig_child(data); + while (columnc) + { + // we only count nodes not leaves + if ( zconfig_child(columnc) ) + { + colcount++; + } + columnc = zconfig_next(columnc); + } + assert(colcount); + + // By default, if we don't enable ScrollX the sizing policy for each columns is "Stretch" + // Each columns maintain a sizing weight, and they will occupy all available width. + static ImGuiTableFlags flags = ImGuiTableFlags_Resizable ;//ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersV | ImGuiTableFlags_ContextMenuInBody | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY; + if (ImGui::BeginTable(zconfig_value(namec), colcount, flags)) + { + unsigned int dirty = 0; //track changes bitwise + columnc = zconfig_child(data); + // generate table header + while (columnc) + { + if ( zconfig_child(columnc) ) + { + char *name = zconfig_name(columnc); + ImGui::TableSetupColumn(name, ImGuiTableColumnFlags_None); + } + columnc = zconfig_next(columnc); + } + ImGui::TableHeadersRow(); + // generate entries + + // always end with an empty entry row + char *values[10] = {NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL}; // capture input + columnc = zconfig_child(data); + ImGui::TableNextRow(); + int colidx = 0; + while (columnc) + { + zconfig_t *rowc = NULL; + if ( rowc = zconfig_child(columnc) ) + { + char *name = zconfig_name(columnc); + ImGui::TableSetColumnIndex(colidx); + zconfig_t *typec = zconfig_locate(columnc, "type"); + char *type = zconfig_value(typec); + ImGui::PushItemWidth( -1.0f ); + if ( streq(type, "intbla") ) + { + int value = 6200; + if ( ImGui::InputInt("##", &value, 1, 100,ImGuiInputTextFlags_EnterReturnsTrue ) ) + { + dirty = (1 << colidx) | colidx; + } + } + else + { + static char bla[256] = ""; + char label[256] = ""; + snprintf(label, 256, "##%s", name); + if ( ImGui::InputText(&label[0], &bla[0], 256, ImGuiInputTextFlags_None) ) + { + dirty = (1 << colidx) | colidx; + } + } + ImGui::PopItemWidth(); + colidx++; + } + columnc = zconfig_next(columnc); + } + ImGui::EndTable(); + + if (dirty) + { + // get current value, append new value if all input fields contain data + } + } + //PushStyleCompact(); + //ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags, ImGuiTableFlags_Resizable); + //ImGui::CheckboxFlags("ImGuiTableFlags_BordersV", &flags, ImGuiTableFlags_BordersV); + //ImGui::SameLine(); HelpMarker("Using the _Resizable flag automatically enables the _BordersInnerV flag as well, this is why the resize borders are still showing when unchecking this."); + //PopStyleCompact(); + + + /* + if (ImGui::BeginTable("table1", 3, flags)) + { + ImGui::TableSetupColumn("name", ImGuiTableColumnFlags_None, 80.f); + ImGui::TableSetupColumn("ip", ImGuiTableColumnFlags_None, 80.f); + ImGui::TableSetupColumn("port", ImGuiTableColumnFlags_None, 60.f); + ImGui::TableHeadersRow(); + for (int row = 0; row < 3; row++) + { + ImGui::TableNextRow(); + + for (int column = 0; column < 3; column++) + { + ImGui::TableSetColumnIndex(column); + static int port = 0; + static char bla[256] = ""; + if (column == 2) + ImGui::InputInt("##port", &port); + else + ImGui::InputText("##Hello", &bla[0], 256); + } + } + ImGui::EndTable(); + }*/ + ImGui::EndChild(); + } + void RenderMediacontrol(const char* name, zconfig_t *data) { if ( ImGui::Button(ICON_FA_BACKWARD) ) diff --git a/Actors/OscMultiOut.cpp b/Actors/OscMultiOut.cpp index 8590e3c3..8e9ca0ee 100644 --- a/Actors/OscMultiOut.cpp +++ b/Actors/OscMultiOut.cpp @@ -8,6 +8,7 @@ OSCMultiOut::capabilities = "capabilities\n" " name = \"host list\"\n" " type = \"list\"\n" " help = \"List of hosts\"\n" + " value = \"\"\n" " name\n" " type = \"string\"\n" " help = \"Just a name for your convenience\"\n" From 45730f9ccffafa31f7b5075340def8d23fd4e48f Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Wed, 6 Mar 2024 17:01:57 +0100 Subject: [PATCH 3/4] simplified multiosc by just using a multiline edit instead of a columns list. Only loading a saved file will fail because of newline chars --- ActorContainer.h | 40 +++++++++++++++++++++++++++++- Actors/OscMultiOut.cpp | 55 +++++++++++++++++++----------------------- 2 files changed, 64 insertions(+), 31 deletions(-) diff --git a/ActorContainer.h b/ActorContainer.h index 2498e462..39bfa3cb 100644 --- a/ActorContainer.h +++ b/ActorContainer.h @@ -338,7 +338,7 @@ struct ActorContainer { RenderMediacontrol( nameStr, data ); } else if ( streq(typeStr, "list")) { - RenderList( nameStr, data ); + RenderMultilineString( nameStr, data ); } else if ( streq(typeStr, "trigger")) { RenderTrigger( nameStr, data ); @@ -1036,6 +1036,44 @@ struct ActorContainer { } } + void RenderMultilineString(const char* name, zconfig_t *data) { + int max = MAX_STR_DEFAULT; + + zconfig_t * zvalue = zconfig_locate(data, "value"); + zconfig_t * zapic = zconfig_locate(data, "api_call"); + zconfig_t * zapiv = zconfig_locate(data, "api_value"); + assert(zvalue); + + zconfig_t * zmax = zconfig_locate(data, "max"); + + ReadInt( &max, zmax ); + + char buf[MAX_STR_DEFAULT]; + const char* zvalueStr = zconfig_value(zvalue); + strcpy(buf, zvalueStr); + char *p = &buf[0]; + + static char text[1024 * 16] = + "127.0.0.1:1234\n" + "127.0.0.1:6200\n"; + + + ImGui::SetNextItemWidth(200); + if ( ImGui::InputTextMultiline("##source", p, max, ImVec2(0, ImGui::GetTextLineHeight() * 16)) ) + { + //if ( ImGui::InputText(name, buf, max) ) { + zconfig_set_value(zvalue, "%s", buf); + sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), buf); + } + + zconfig_t *help = zconfig_locate(data, "help"); + if (help) + { + char *helpv = zconfig_value(help); + RenderTooltip(helpv); + } + } + void RenderTrigger(const char* name, zconfig_t *data) { zconfig_t * zapic = zconfig_locate(data, "api_call"); diff --git a/Actors/OscMultiOut.cpp b/Actors/OscMultiOut.cpp index 8e9ca0ee..be9d1f26 100644 --- a/Actors/OscMultiOut.cpp +++ b/Actors/OscMultiOut.cpp @@ -7,20 +7,10 @@ OSCMultiOut::capabilities = "capabilities\n" " data\n" " name = \"host list\"\n" " type = \"list\"\n" - " help = \"List of hosts\"\n" - " value = \"\"\n" - " name\n" - " type = \"string\"\n" - " help = \"Just a name for your convenience\"\n" - " ip\n" - " type = \"ipaddress\"\n" - " help = \"The ipaddress of the host to send to\"\n" - " port\n" - " type = \"int\"\n" - " help = \"The port number of the host to send to\"\n" - " value = \"6200\"\n" - " min = \"1\"\n" - " max = \"65534\"\n" + " help = \"List of hosts with port (host:port)\"\n" + " value = \"127.0.0.1:1234,127.0.0.1:6200\"\n" + " api_call = \"SET HOSTS\"\n" + " api_value = \"s\"\n" "inputs\n" " input\n" " type = \"OSC\"\n"; @@ -55,12 +45,12 @@ zmsg_t* OSCMultiOut::handleSocket( sphactor_event_t * ev ) { char *host = (char *)zlist_first(this->hosts); while ( host ) { - char *url = strchr(host, ','); - assert(url); - zstr_sendm(dgrams, url); + //char *url = strchr(host, ','); + //assert(url); + zstr_sendm(dgrams, host); int rc = zsock_send(dgrams, "b", msgBuffer, len); if ( rc != 0 ) { - zsys_info("Error sending zosc message to: %s, %i", url, rc); + zsys_info("Error sending zosc message to: %s, %i", host, rc); } host = (char *)zlist_next(this->hosts); } @@ -82,19 +72,24 @@ zmsg_t* OSCMultiOut::handleAPI( sphactor_event_t * ev ) { zlist_autofree(this->hosts); // fill with new data - char *name; - while ( name = zmsg_popstr(ev->msg) ) + char *hosts = zmsg_popstr(ev->msg); + while ( hosts != NULL ) { - char *ip = zmsg_popstr(ev->msg); - assert(ip); - char *port = zmsg_popstr(ev->msg); - assert(port); - char host[PATH_MAX]; - snprintf(host, PATH_MAX, "%s,%s:%s"); - zlist_append(this->hosts, (void *)&host[0]); - zstr_free(&name); - zstr_free(&ip); - zstr_free(&port); + ssize_t length = strlen(hosts); + char *host = hosts; + + // Iterate through the characters in the array + for (ssize_t i = 0; i < length; i++) { + // Check if the current character is a newline + if (hosts[i] == '\n') + { + hosts[i] = 0; + printf("Found a newline character at index %d\n", i); + zlist_append(this->hosts, host); //copies the string! + host = hosts+i+1; + } + } + hosts = zmsg_popstr(ev->msg); } } zstr_free(&cmd); From 99e4e407b9a0e1c6ff622149db07280baa7efe61 Mon Sep 17 00:00:00 2001 From: Arnaud Loonstra Date: Fri, 8 Mar 2024 13:03:50 +0100 Subject: [PATCH 4/4] use , as newline seperator for multiline widget and list parameter for actor --- ActorContainer.h | 30 +++++++++++++++++++++--------- Actors/OscMultiOut.cpp | 36 +++++++++++++++++++++++++++++------- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/ActorContainer.h b/ActorContainer.h index 39bfa3cb..80a05172 100644 --- a/ActorContainer.h +++ b/ActorContainer.h @@ -37,6 +37,19 @@ convert_to_relative_to_wd(const char *path) return strdup( &path[ wdpathlen ]); } +void +s_replace_char(char *str, char from, char to) +{ + ssize_t length = strlen(str); + for (int i = 0; iactor, zconfig_value(zapic), zconfig_value(zapiv), buf); + char *sendbuf = strdup(buf); + s_replace_char(sendbuf, '\n', ','); // We use ',' instead of newline since we can't save newlines in the stage save file + zconfig_set_value(zvalue, "%s", sendbuf); + sphactor_ask_api(this->actor, zconfig_value(zapic), zconfig_value(zapiv), sendbuf); + zstr_free(&sendbuf); } zconfig_t *help = zconfig_locate(data, "help"); diff --git a/Actors/OscMultiOut.cpp b/Actors/OscMultiOut.cpp index be9d1f26..27b87673 100644 --- a/Actors/OscMultiOut.cpp +++ b/Actors/OscMultiOut.cpp @@ -2,13 +2,26 @@ #include #include +static void +s_replace_char(char *str, char from, char to) +{ + ssize_t length = strlen(str); + for (int i = 0; idgrams); this->dgrams = NULL; } + if (this->hosts) + zlist_destroy(&this->hosts); return Sphactor::handleStop(ev); } @@ -76,19 +91,26 @@ zmsg_t* OSCMultiOut::handleAPI( sphactor_event_t * ev ) { while ( hosts != NULL ) { ssize_t length = strlen(hosts); + // replace , with 0x0. Multiline use , as seperator + s_replace_char(hosts, ',', (char)0x0); char *host = hosts; // Iterate through the characters in the array for (ssize_t i = 0; i < length; i++) { - // Check if the current character is a newline - if (hosts[i] == '\n') + // Check if the current character is a 0 terminator + if (hosts[i] == (char)0x0 ) { - hosts[i] = 0; - printf("Found a newline character at index %d\n", i); - zlist_append(this->hosts, host); //copies the string! + if (strlen(host) > 6) // just a validation as the host must contain a : and port + zlist_append(this->hosts, host); //copies the string! host = hosts+i+1; } + else if (i == length - 1) + { + if (strlen(host) > 6) + zlist_append(this->hosts, host); //copies the string! + } } + zstr_free(&hosts); hosts = zmsg_popstr(ev->msg); } }