Skip to content

Commit

Permalink
new module to convert LSL streams into OSC messages
Browse files Browse the repository at this point in the history
  • Loading branch information
gisogrimm committed Apr 24, 2024
1 parent 4ee986d commit 118691d
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 2 deletions.
3 changes: 2 additions & 1 deletion apps/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ BINFILES = tascar_cli tascar_showlicenses tascar_validatetsc \
tascar_levelmeter tascar_jackpar tascar_renderfile tascar_renderir \
tascar_gpx2csv tascar_version tascar_test_compare_sndfile \
tascar_test_compare_level_sum tascar_lsjackp tascar_sendosc \
tascar_listsrc tascar_getcalibfor tascar_spk2obj
tascar_listsrc tascar_getcalibfor tascar_spk2obj \
tascar_sceneskeleton

ifeq "$(HAS_LSL)" "yes"
BINFILES += tascar_osc2lsl
Expand Down
69 changes: 69 additions & 0 deletions apps/src/tascar_sceneskeleton.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* This file is part of the TASCAR software, see <http://tascar.org/>
*
* Copyright (c) 2024 Giso Grimm
*/
/*
* TASCAR is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, version 3 of the License.
*
* TASCAR is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHATABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License, version 3 for more details.
*
* You should have received a copy of the GNU General Public License,
* Version 3 along with TASCAR. If not, see <http://www.gnu.org/licenses/>.
*/

#include "../build/tascarver.h"
#include "cli.h"
#include <iostream>

int main(int argc, char** argv)
{
const char* options = "h";
struct option long_options[] = {{"help", 0, 0, 'h'}, {0, 0, 0, 0}};
int opt(0);
int option_index(0);
while((opt = getopt_long(argc, argv, options, long_options, &option_index)) !=
EOF) {
switch(opt) {
case 'h':
// usage(long_options);
TASCAR::app_usage("tascar_sceneskeleton", long_options, "",
"Show a generic TASCAR scene skeleton.");
return 0;
}
}

std::cout << "<?xml version=\"1.0\"?>\n"
<< "<session license=\"CC0\">\n"
<< " <!-- generated for version " << TASCARVER << " -->\n"
<< " <scene>\n" << " <!-- create your scene here -->\n"
<< " <!-- \n"
<< " <source name=\"in\">\n"
<< " <sound>\n"
<< " <plugins>\n"
<< " </plugins>\n"
<< " </sound>\n"
<< " </source>\n"
<< " <receiver name=\"out\" type=\"omni\"/>\n"
<< " <facegroup name=\"walls\" shoebox=\"7 11 3\"/>\n"
<< " --> \n"
<< " </scene>\n"
<< " <modules>\n"
<< " </modules>\n"
<< "</session>\n";
return 0;
}

/*
* Local Variables:
* mode: c++
* c-basic-offset: 2
* indent-tabs-mode: nil
* compile-command: "make -C .."
* End:
*/
1 change: 1 addition & 0 deletions manual/documentation.tsc
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
<systime/>
<system command="ls"/>
<timedisplay/>
<lsl2osc/>
</modules>
<connect src="a" dest="b"/>
<range/>
Expand Down
21 changes: 21 additions & 0 deletions manual/modlsl2osc.tex
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
Convert LSL streams into OSC messages.

\definecolor{shadecolor}{RGB}{255,230,204}\begin{snugshade}
{\footnotesize
\label{attrtab:lsl2osc}
Attributes of element {\bf lsl2osc}\nopagebreak

\begin{tabularx}{\textwidth}{lXl}
\hline
name & description (type, unit) & def.\\
\hline
\hline
\indattr{prefix} & OSC path prefix, "/" + name will be appended. (string) & /lsl2osc\\
\hline
\indattr{streams} & List of stream names to transmit (string array) & \\
\hline
\indattr{url} & OSC target URL, or empty to dispatch locally. (string) & \\
\hline
\end{tabularx}
}
\end{snugshade}
2 changes: 2 additions & 0 deletions packaging/deb/tascarcli.csv
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"apps/build/tascar_renderfile","usr/bin/"
"apps/build/tascar_renderir","usr/bin/"
"apps/build/tascar_sampler","usr/bin/"
"apps/build/tascar_sceneskeleton","usr/bin/"
"apps/build/tascar_sendosc","usr/bin/"
"apps/build/tascar_showlicenses","usr/bin/"
"apps/build/tascar_spk2obj","usr/bin/"
Expand Down Expand Up @@ -127,6 +128,7 @@
"plugins/build/tascar_jackrec.so","usr/lib/"
"plugins/build/tascar_levels2osc.so","usr/lib/"
"plugins/build/tascar_lightctl.so","usr/lib/"
"plugins/build/tascar_lsl2osc.so","usr/lib/"
"plugins/build/tascar_lsljacktime.so","usr/lib/"
"plugins/build/tascar_ltcgen.so","usr/lib/"
"plugins/build/tascar_mask_*.so","usr/lib/"
Expand Down
3 changes: 2 additions & 1 deletion plugins/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ ifeq "$(HAS_LSL)" "yes"
CXXFLAGS += -DHAS_LSL
TASCARMODS += lsljacktime lslactor levels2osc pos2lsl
AUDIOPLUGINS += speechactivity periodogram
TASCARMODSGUI += glabsensors waitforlslstream
TASCARMODSGUI += glabsensors waitforlslstream lsl2osc
GLABSENSORS += smiley eog emergency espheadtracker midicc \
qualisys jackstatus serial oscthreshold
endif
Expand Down Expand Up @@ -136,6 +136,7 @@ build/$(PLUGINPREFIX)tascar_ap_lipsync_paper$(DLLEXT): LDLIBS+=-lfftw3f
build/$(PLUGINPREFIX)tascar_ap_openmha$(DLLEXT): LDLIBS+=-lopenmha
build/$(PLUGINPREFIX)tascar_ap_periodogram$(DLLEXT): LDLIBS+=-llsl
build/$(PLUGINPREFIX)tascar_ap_speechactivity$(DLLEXT): LDLIBS+=-llsl
build/$(PLUGINPREFIX)tascar_lsl2osc$(DLLEXT): LDLIBS+=-llsl
build/$(PLUGINPREFIX)tascar_datalogging$(DLLEXT): LDLIBS += -lmatio
ifeq "$(HAS_LSL)" "yes"
build/$(PLUGINPREFIX)tascar_datalogging$(DLLEXT): LDLIBS += -llsl
Expand Down
128 changes: 128 additions & 0 deletions plugins/src/tascarmod_lsl2osc.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* This file is part of the TASCAR software, see <http://tascar.org/>
*
* Copyright (c) 2024 Giso Grimm
*/
/*
* TASCAR is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, version 3 of the License.
*
* TASCAR is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHATABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License, version 3 for more details.
*
* You should have received a copy of the GNU General Public License,
* Version 3 along with TASCAR. If not, see <http://www.gnu.org/licenses/>.
*/

#include "session.h"
#include <lsl_cpp.h>
#include <mutex>
#include <thread>

class lsl2osc_t : public TASCAR::module_base_t {
public:
lsl2osc_t(const TASCAR::module_cfg_t& cfg);
void scanthread(const std::string& name);
~lsl2osc_t();

private:
std::vector<std::thread*> scanthreads;
bool runthreads = true;
std::string prefix = "/lsl2osc";
lo_address loaddr = NULL;
};

void lsl2osc_t::scanthread(const std::string& name)
{
try {
std::string oscpath = prefix + "/" + name;
while(runthreads) {
auto infos = lsl::resolve_stream("name", name, 1, 1.0);
if(infos.size() > 0) {
if(infos.size() > 1)
TASCAR::add_warning("More than one LSL stream with name \"" + name +
"\" sound. Using the first match.");
DEBUG(infos.size());
auto cfmt = infos[0].channel_format();
if(!((cfmt == lsl::cf_float32) || (cfmt == lsl::cf_double64))) {
TASCAR::add_warning(
"The LSL stream with name \"" + name +
"\" has no floating point data. The stream will be ignored.");
return;
}
auto streamchannels = infos[0].channel_count();
auto msg = lo_message_new();
if(!msg) {
TASCAR::add_warning(
"Unable to create OSC message. Ending LSL thread for stream \"" +
name + "\".");
return;
}
lo_message_add_double(msg, 0.0);
for(int ch = 0; ch < streamchannels; ++ch)
lo_message_add_double(msg, 0.0);
auto msgd = lo_message_get_argv(msg);
auto inlet = lsl::stream_inlet(infos[0]);
std::vector<double> sample;
while(runthreads) {
// get LSL sample, store time:
msgd[0]->d = inlet.pull_sample(sample, 0.1);
size_t ch = 1;
for(auto s : sample) {
msgd[ch]->d = s;
++ch;
}
if(loaddr)
lo_send_message(loaddr, oscpath.c_str(), msg);
else
session->dispatch_data_message(oscpath.c_str(), msg);
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
}
catch(const std::exception& e) {
std::string msg = "An error occured while scanning LSL stream \"" + name +
"\": " + e.what();
TASCAR::add_warning(msg);
}
}

lsl2osc_t::lsl2osc_t(const TASCAR::module_cfg_t& cfg) : module_base_t(cfg)
{
std::vector<std::string> streams;
GET_ATTRIBUTE(streams, "", "List of stream names to transmit");
GET_ATTRIBUTE(prefix, "", "OSC path prefix, \"/\" + name will be appended.");
std::string url;
GET_ATTRIBUTE(url, "", "OSC target URL, or empty to dispatch locally.");
if(url.size())
loaddr = lo_address_new_from_url(url.c_str());
for(auto p : streams) {
scanthreads.push_back(new std::thread(&lsl2osc_t::scanthread, this, p));
}
}

lsl2osc_t::~lsl2osc_t()
{
runthreads = false;
for(auto th : scanthreads) {
th->join();
delete th;
}
if(loaddr)
lo_address_free(loaddr);
}

REGISTER_MODULE(lsl2osc_t);

/*
* Local Variables:
* mode: c++
* c-basic-offset: 2
* indent-tabs-mode: nil
* compile-command: "make -C .."
* End:
*/

0 comments on commit 118691d

Please sign in to comment.