diff --git a/libtascar/include/levelmeter.h b/libtascar/include/levelmeter.h
index ff9d0fe4..6f65335b 100644
--- a/libtascar/include/levelmeter.h
+++ b/libtascar/include/levelmeter.h
@@ -20,8 +20,8 @@
#ifndef LEVELMETER_H
#define LEVELMETER_H
-#include "tscconfig.h"
#include "filterclass.h"
+#include "tscconfig.h"
namespace TASCAR {
@@ -35,7 +35,7 @@ namespace TASCAR {
\ingroup levels
*/
class levelmeter_t : public TASCAR::wave_t {
- public:
+ public:
/**
\param fs Audio sampling rate in Hz
\param tc Time constant in seconds
@@ -46,7 +46,7 @@ namespace TASCAR {
Change meter weighting during runtime
*/
void set_weight(levelmeter::weight_t weight);
- levelmeter::weight_t get_weight() const { return w;};
+ levelmeter::weight_t get_weight() const { return w; };
/**
\brief Add audio samples to level meter container
\param src Audio samples
@@ -57,11 +57,14 @@ namespace TASCAR {
\retval rms RMS value in dB SPL
\retval peak Peak value in dB
*/
- void get_rms_and_peak( float& rms, float& peak ) const;
+ void get_rms_and_peak(float& rms, float& peak) const;
/**
- \brief Return percentile levels, similar to ISMADHA standard for hearing aids analysis.
+ \brief Return percentile levels, similar to ISMADHA standard for hearing
+ aids analysis.
*/
- void get_percentile_levels( float& q30, float& q50, float& q65, float& g95, float& q99 ) const;
+ void get_percentile_levels(float& q30, float& q50, float& q65, float& g95,
+ float& q99) const;
+
private:
TASCAR::levelmeter::weight_t w;
uint32_t segment_length;
@@ -72,12 +75,16 @@ namespace TASCAR {
uint32_t i65;
uint32_t i95;
uint32_t i99;
+
+ public:
bandpass_t bp;
+
+ private:
bandpass_t bp_C;
aweighting_t flt_A;
};
-}
+} // namespace TASCAR
#endif
diff --git a/manual/apmodlevel2hsv.tex b/manual/apmodlevel2hsv.tex
new file mode 100644
index 00000000..e5c0e6fd
--- /dev/null
+++ b/manual/apmodlevel2hsv.tex
@@ -0,0 +1,35 @@
+Convert sound pressure level to value component of a lamp.
+
+\definecolor{shadecolor}{RGB}{255,230,204}\begin{snugshade}
+{\footnotesize
+\label{attrtab:level2hsv}
+Attributes of element {\bf level2hsv}\nopagebreak
+
+\begin{tabularx}{\textwidth}{lXl}
+\hline
+name & description (type, unit) & def.\\
+\hline
+\hline
+\indattr{frange} & Frequency range in bandpass mode (float array, Hz) & 62.5 4000\\
+\hline
+\indattr{hue} & Hue component (0-360) (float, degree) & 0\\
+\hline
+\indattr{lrange} & Level range (float array, dB) & 40 90\\
+\hline
+\indattr{mode} & Level mode [dbspl|rms|max] (string) & dbspl\\
+\hline
+\indattr{path} & Target path (string) & /hsv\\
+\hline
+\indattr{saturation} & Saturation component (0-1) (float) & 1\\
+\hline
+\indattr{skip} & Skip frames (uint32) & 0\\
+\hline
+\indattr{tau} & Leq duration, or 0 to use block size (float, s) & 0\\
+\hline
+\indattr{url} & Target URL (string) & {\tiny osc.udp://localhost:9999/}\\
+\hline
+\indattr{weight} & Level meter weight (f-weight) & Z\\
+\hline
+\end{tabularx}
+}
+\end{snugshade}
diff --git a/manual/documentation.tsc b/manual/documentation.tsc
index 92782148..d45884f2 100644
--- a/manual/documentation.tsc
+++ b/manual/documentation.tsc
@@ -50,6 +50,7 @@
+
diff --git a/plugins/Makefile b/plugins/Makefile
index a72567c0..7da51a65 100644
--- a/plugins/Makefile
+++ b/plugins/Makefile
@@ -29,7 +29,7 @@ AUDIOPLUGINS = delay dummy gate hannenv identity lipsync \
gainramp pulse pink dumplevels feedbackdelay bandpass filter \
level2osc const noise loopmachine pos2osc gain sndfileasync \
addchannel tubesim spkcalib sessiontime flanger timestamp \
- burglatticelpc fence transportramp allpass bandlevel2osc
+ burglatticelpc fence transportramp allpass bandlevel2osc level2hsv
MASKPLUGINS = fig8 multibeam sampledgain
diff --git a/plugins/src/tascar_ap_level2hsv.cc b/plugins/src/tascar_ap_level2hsv.cc
new file mode 100644
index 00000000..a7aaa4bf
--- /dev/null
+++ b/plugins/src/tascar_ap_level2hsv.cc
@@ -0,0 +1,200 @@
+/*
+ * This file is part of the TASCAR software, see
+ *
+ * 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 .
+ */
+
+#include "audioplugin.h"
+#include "errorhandling.h"
+#include "levelmeter.h"
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace std::chrono_literals;
+
+enum levelmode_t { dbspl, rms, max };
+
+class level2hsv_t : public TASCAR::audioplugin_base_t {
+public:
+ level2hsv_t(const TASCAR::audioplugin_cfg_t& cfg);
+ void ap_process(std::vector& chunk, const TASCAR::pos_t& pos,
+ const TASCAR::zyx_euler_t&, const TASCAR::transport_t& tp);
+ void configure();
+ void release();
+ ~level2hsv_t();
+
+private:
+ uint32_t skip = 0;
+ float tau = 0;
+ std::string url = "osc.udp://localhost:9999/";
+ std::string path = "/hsv";
+ float hue = 0;
+ float saturation = 1;
+ TASCAR::levelmeter::weight_t weight = TASCAR::levelmeter::Z;
+ std::vector frange = {62.5f, 4000.0f};
+ std::vector lrange = {40.0f, 90.0f};
+ // float value = 0;
+ // derived variables:
+ levelmode_t imode = dbspl;
+ lo_address lo_addr;
+ uint32_t skipcnt = 0;
+ lo_message msg;
+ std::thread thread;
+ std::atomic_bool run_thread = true;
+ void sendthread();
+ std::mutex mtx;
+ std::condition_variable cond;
+ std::atomic_bool has_data = false;
+ TASCAR::levelmeter_t* lmeter = NULL;
+ double currenttime = 0;
+ float* p_value = NULL;
+};
+
+level2hsv_t::level2hsv_t(const TASCAR::audioplugin_cfg_t& cfg)
+ : audioplugin_base_t(cfg)
+{
+ GET_ATTRIBUTE(skip, "", "Skip frames");
+ GET_ATTRIBUTE(url, "", "Target URL");
+ GET_ATTRIBUTE(path, "", "Target path");
+ GET_ATTRIBUTE(tau, "s", "Leq duration, or 0 to use block size");
+ GET_ATTRIBUTE(hue, "degree", "Hue component (0-360)");
+ GET_ATTRIBUTE(saturation, "", "Saturation component (0-1)");
+ std::string mode("dbspl");
+ GET_ATTRIBUTE(mode, "", "Level mode [dbspl|rms|max]");
+ if(mode == "dbspl")
+ imode = dbspl;
+ else if(mode == "rms")
+ imode = rms;
+ else if(mode == "max")
+ imode = max;
+ else
+ throw TASCAR::ErrMsg("Invalid level mode: " + mode);
+ GET_ATTRIBUTE_NOUNIT(weight, "Level meter weight");
+ GET_ATTRIBUTE(frange, "Hz", "Frequency range in bandpass mode");
+ if(weight == TASCAR::levelmeter::bandpass)
+ if(frange.size() != 2)
+ throw TASCAR::ErrMsg(
+ "Frequency range requires exactly two entries (min max)");
+ GET_ATTRIBUTE(lrange, "dB", "Level range");
+ if((lrange.size() != 2) || (lrange[0] == lrange[1]))
+ throw TASCAR::ErrMsg(
+ "Level range requires exactly two different entries (min max)");
+ lo_addr = lo_address_new_from_url(url.c_str());
+ msg = lo_message_new();
+ lo_message_add_float(msg, hue);
+ lo_message_add_float(msg, saturation);
+ lo_message_add_float(msg, 0);
+ lo_message_add_float(msg, 0.01);
+ auto oscmsgargv = lo_message_get_argv(msg);
+ p_value = &(oscmsgargv[2]->f);
+ thread = std::thread(&level2hsv_t::sendthread, this);
+}
+
+void level2hsv_t::sendthread()
+{
+ std::unique_lock lk(mtx);
+ while(run_thread) {
+ cond.wait_for(lk, 100ms);
+ if(has_data) {
+ float l = 0.0f;
+ switch(imode) {
+ case dbspl:
+ l = lmeter->spldb();
+ break;
+ case rms:
+ l = lmeter->rms();
+ break;
+ case max:
+ l = lmeter->maxabsdb();
+ break;
+ }
+ l = std::min(1.0f,
+ std::max(0.0f, (l - lrange[0]) / (lrange[1] - lrange[0])));
+ *p_value = l;
+ lo_send_message(lo_addr, path.c_str(), msg);
+ has_data = false;
+ }
+ }
+}
+
+void level2hsv_t::configure()
+{
+ audioplugin_base_t::configure();
+ float tau_ = tau;
+ if(tau_ == 0)
+ tau_ = t_fragment;
+ if(lmeter)
+ delete lmeter;
+ lmeter = NULL;
+ lmeter = new TASCAR::levelmeter_t(f_sample, tau_, weight);
+ if(weight == TASCAR::levelmeter::bandpass)
+ lmeter->bp.set_range(frange[0], frange[1]);
+}
+
+void level2hsv_t::release()
+{
+ audioplugin_base_t::release();
+}
+
+level2hsv_t::~level2hsv_t()
+{
+ run_thread = false;
+ thread.join();
+ lo_address_free(lo_addr);
+ if(lmeter)
+ delete lmeter;
+ lo_message_free(msg);
+}
+
+void level2hsv_t::ap_process(std::vector& chunk,
+ const TASCAR::pos_t&, const TASCAR::zyx_euler_t&,
+ const TASCAR::transport_t&)
+{
+ if(chunk.size() != n_channels)
+ throw TASCAR::ErrMsg(
+ "Programming error (invalid channel number, expected " +
+ TASCAR::to_string(n_channels) + ", got " +
+ std::to_string(chunk.size()) + ").");
+ if(chunk.size() == 0)
+ return;
+ lmeter->update(chunk[0]);
+ if(skipcnt) {
+ skipcnt--;
+ } else {
+ // data will be sent in extra thread:
+ if(mtx.try_lock()) {
+ has_data = true;
+ mtx.unlock();
+ cond.notify_one();
+ }
+ skipcnt = skip;
+ }
+}
+
+REGISTER_AUDIOPLUGIN(level2hsv_t);
+
+/*
+ * Local Variables:
+ * mode: c++
+ * c-basic-offset: 2
+ * indent-tabs-mode: nil
+ * compile-command: "make -C .."
+ * End:
+ */