Skip to content

Commit

Permalink
add plugin to convert levels to value of hsv lamps
Browse files Browse the repository at this point in the history
  • Loading branch information
gisogrimm committed May 10, 2024
1 parent 83f7af8 commit bd3bb12
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 8 deletions.
21 changes: 14 additions & 7 deletions libtascar/include/levelmeter.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
#ifndef LEVELMETER_H
#define LEVELMETER_H

#include "tscconfig.h"
#include "filterclass.h"
#include "tscconfig.h"

namespace TASCAR {

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

Expand Down
35 changes: 35 additions & 0 deletions manual/apmodlevel2hsv.tex
Original file line number Diff line number Diff line change
@@ -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}
1 change: 1 addition & 0 deletions manual/documentation.tsc
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
</layout>
</spkcalib>
<transportramp/>
<level2hsv/>
</plugins>
</sound>
<sound type="cardioidmod"/>
Expand Down
2 changes: 1 addition & 1 deletion plugins/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
200 changes: 200 additions & 0 deletions plugins/src/tascar_ap_level2hsv.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
* 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 "audioplugin.h"
#include "errorhandling.h"
#include "levelmeter.h"
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <lo/lo.h>
#include <mutex>
#include <thread>

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<TASCAR::wave_t>& 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<float> frange = {62.5f, 4000.0f};
std::vector<float> 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<std::mutex> 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<TASCAR::wave_t>& 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:
*/

0 comments on commit bd3bb12

Please sign in to comment.