maintained by Frédéric Peters. You can reach Frédéric at fpeters (a.t) 0d (dot)
be, and Nedko at nedko (a.t) arnaudov (dot) name.
+## Acknowledgements
+
+K-meter implemenatation taken from jkmeter, licensed under
+the GPL 2, by Fons Adriaensen.
[COPYING]: ./COPYING
[INSTALL]: ./INSTALL
if not self.channel:
return
if self.stereo:
- meter_left, meter_right = self.channel.meter
- self.meter.set_values(meter_left, meter_right)
+ peak_left, peak_right, rms_left, rms_right = self.channel.kmeter
+ self.meter.set_values(peak_left, peak_right, rms_left, rms_right)
else:
- self.meter.set_value(self.channel.meter[0])
+ peak, rms = self.channel.kmeter
+ self.meter.set_values(peak, rms)
self.abspeak.set_peak(self.channel.abspeak)
#define MAX_BLOCK_SIZE (4 * 4096)
#define FLOAT_EXISTS(x) (!((x) - (x)))
+struct kmeter {
+ float _z1; // filter state
+ float _z2; // filter state
+ float _rms; // max rms value since last read()
+ float _dpk; // current digital peak value
+ int _cnt; // digital peak hold counter
+ bool _flag; // flag set by read(), resets _rms
+
+ int _hold; // number of JACK periods to hold peak value
+ float _fall; // per period fallback multiplier for peak value
+ float _omega; // ballistics filter constant.
+};
struct channel
{
float meter_left;
float meter_right;
float abspeak;
+ struct kmeter kmeter_left;
+ struct kmeter kmeter_right;
+
jack_port_t * port_left;
jack_port_t * port_right;
return powf(10.0, db/20.0);
}
+double interpolate(double start, double end, int step, int steps) {
+ double ret;
+ double frac = 0.01;
+ LOG_DEBUG("%f -> %f, %d", start, end, step);
+ if (start <= 0) {
+ if (step <= frac * steps) {
+ ret = frac * end * step / steps;
+ } else {
+ ret = db_to_value(value_to_db(frac * end) + (value_to_db(end) - value_to_db(frac * end)) * step / steps);;
+ }
+ } else if (end <= 0) {
+ if (step >= (1 - frac) * steps) {
+ ret = frac * start - frac * start * step / steps;
+ } else {
+ ret = db_to_value(value_to_db(start) + (value_to_db(frac * start) - value_to_db(start)) * step / steps);
+ }
+ } else {
+ ret = db_to_value(value_to_db(start) + (value_to_db(end) - value_to_db(start)) *step /steps);
+ }
+ LOG_DEBUG("interpolate: %f", ret);
+ return ret;
+}
+
#define channel_ptr ((struct channel *)channel)
const char*
*right_ptr = value_to_db(channel_ptr->meter_right);
}
-double interpolate(double start, double end, int step, int steps) {
- double ret;
- double frac = 0.01;
- LOG_DEBUG("%f -> %f, %d", start, end, step);
- if (start <= 0) {
- if (step <= frac * steps) {
- ret = frac * end * step / steps;
- } else {
- ret = db_to_value(value_to_db(frac * end) + (value_to_db(end) - value_to_db(frac * end)) * step / steps);;
- }
- } else if (end <= 0) {
- if (step >= (1 - frac) * steps) {
- ret = frac * start - frac * start * step / steps;
- } else {
- ret = db_to_value(value_to_db(start) + (value_to_db(frac * start) - value_to_db(start)) * step / steps);
- }
- } else {
- ret = db_to_value(value_to_db(start) + (value_to_db(end) - value_to_db(start)) *step /steps);
- }
- LOG_DEBUG("interpolate: %f", ret);
- return ret;
-}
void
channel_mono_meter_read(
*mono_ptr = value_to_db(channel_ptr->meter_left);
}
+void
+channel_stereo_kmeter_read(
+ jack_mixer_channel_t channel,
+ double * left_ptr,
+ double * right_ptr,
+ double * left_rms_ptr,
+ double * right_rms_ptr)
+{
+ assert(channel_ptr);
+ *left_ptr = value_to_db(channel_ptr->kmeter_left._dpk);
+ *right_ptr = value_to_db(channel_ptr->kmeter_right._dpk);
+ *left_rms_ptr = value_to_db(channel_ptr->kmeter_left._rms);
+ *right_rms_ptr = value_to_db(channel_ptr->kmeter_right._rms);
+ channel_ptr->kmeter_left._flag = true;
+ channel_ptr->kmeter_right._flag = true;
+}
+
+void
+channel_mono_kmeter_read(
+ jack_mixer_channel_t channel,
+ double * mono_ptr,
+ double * mono_rms_ptr)
+{
+ *mono_ptr = value_to_db(channel_ptr->kmeter_left._dpk);
+ *mono_rms_ptr = value_to_db(channel_ptr->kmeter_left._rms);
+ channel_ptr->kmeter_left._flag = true;
+}
+
void
channel_volume_write(
jack_mixer_channel_t channel,
jack_nframes_t end) /* index of sample to stop processing before */
{
jack_nframes_t i;
+
GSList *node_ptr;
struct channel * channel_ptr;
jack_default_audio_sample_t frame_left;
/* process main mix channel */
unsigned int steps = mix_channel->num_volume_transition_steps;
+
for (i = start ; i < end ; i++)
{
if (! output_mix_channel->prefader) {
}
}
+ if (mix_channel->stereo)
+ {
+ frame_left = fabsf(mix_channel->tmp_mixed_frames_left[i]);
+ frame_right = fabsf(mix_channel->tmp_mixed_frames_right[i]);
+
+ if (mix_channel->peak_left < frame_left)
+ {
+ mix_channel->peak_left = frame_left;
+
+ if (frame_left > mix_channel->abspeak)
+ {
+ mix_channel->abspeak = frame_left;
+ }
+ }
+
+ if (mix_channel->peak_right < frame_right)
+ {
+ mix_channel->peak_right = frame_right;
+
+ if (frame_right > mix_channel->abspeak)
+ {
+ mix_channel->abspeak = frame_right;
+ }
+ }
+ }
+ else
+ {
+ if (mix_channel->peak_left < frame_left)
+ {
+ mix_channel->peak_left = frame_left;
+
+ if (frame_left > mix_channel->abspeak)
+ {
+ mix_channel->abspeak = frame_left;
+ }
+ }
+ }
+
mix_channel->peak_frames++;
if (mix_channel->peak_frames >= PEAK_FRAMES_CHUNK)
{
mix_channel->right_buffer_ptr[i] = mix_channel->tmp_mixed_frames_right[i];
}
}
+ kmeter_process(&mix_channel->kmeter_left, mix_channel->tmp_mixed_frames_left, start, end);
+ kmeter_process(&mix_channel->kmeter_right, mix_channel->tmp_mixed_frames_right, start, end);
}
static inline void
jack_default_audio_sample_t frame_left = 0.0f;
jack_default_audio_sample_t frame_right = 0.0f;
unsigned int steps = channel_ptr->num_volume_transition_steps;
+
for (i = start ; i < end ; i++)
{
if (i-start >= MAX_BLOCK_SIZE)
}
else
{
+
frame_left = (fabsf(frame_left) + fabsf(frame_right)) / 2;
if (channel_ptr->peak_left < frame_left)
channel_ptr->peak_frames = 0;
}
+
channel_ptr->volume_idx++;
if ((channel_ptr->volume != channel_ptr->volume_new) &&
(channel_ptr->volume_idx == steps)) {
(channel_ptr->balance_idx >= steps)) {
channel_ptr->balance = channel_ptr->balance_new;
channel_ptr->balance_idx = 0;
- }
+ }
}
+
+ kmeter_process(&channel_ptr->kmeter_left, channel_ptr->frames_left, start, end);
+ if (channel_ptr->stereo) kmeter_process(&channel_ptr->kmeter_right, channel_ptr->frames_right, start, end);
+
}
static inline void
channel_ptr->stereo = stereo;
+ int sr = jack_get_sample_rate(channel_ptr->mixer_ptr->jack_client);
+ int fsize = jack_get_buffer_size(channel_ptr->mixer_ptr->jack_client);
+
channel_ptr->volume_transition_seconds = VOLUME_TRANSITION_SECONDS;
channel_ptr->num_volume_transition_steps =
- channel_ptr->volume_transition_seconds *
- jack_get_sample_rate(channel_ptr->mixer_ptr->jack_client) + 1;
+ channel_ptr->volume_transition_seconds * sr + 1;
channel_ptr->volume = 0.0;
channel_ptr->volume_new = 0.0;
channel_ptr->balance = 0.0;
channel_ptr->abspeak = 0.0;
channel_ptr->out_mute = false;
+ kmeter_init(&channel_ptr->kmeter_left, sr, fsize, .5f, 15.0f);
+ kmeter_init(&channel_ptr->kmeter_right, sr, fsize, .5f, 15.0f);
+
channel_ptr->peak_left = 0.0;
channel_ptr->peak_right = 0.0;
channel_ptr->peak_frames = 0;
channel_ptr->stereo = stereo;
channel_ptr->out_mute = false;
+ int sr = jack_get_sample_rate(channel_ptr->mixer_ptr->jack_client);
+ int fsize = jack_get_buffer_size(channel_ptr->mixer_ptr->jack_client);
+
channel_ptr->volume_transition_seconds = VOLUME_TRANSITION_SECONDS;
channel_ptr->num_volume_transition_steps =
- channel_ptr->volume_transition_seconds *
- jack_get_sample_rate(channel_ptr->mixer_ptr->jack_client) + 1;
+ channel_ptr->volume_transition_seconds * sr + 1;
channel_ptr->volume = 0.0;
channel_ptr->volume_new = 0.0;
channel_ptr->balance = 0.0;
channel_ptr->meter_left = -1.0;
channel_ptr->meter_right = -1.0;
channel_ptr->abspeak = 0.0;
+ kmeter_init(&channel_ptr->kmeter_left, sr, fsize, 0.5f, 15.0f);
+ kmeter_init(&channel_ptr->kmeter_right, sr, fsize, 0.5f, 15.0f);
channel_ptr->peak_left = 0.0;
channel_ptr->peak_right = 0.0;
}
}
+#define km ((struct kmeter *) kmeter)
+
+void kmeter_init(jack_mixer_kmeter_t kmeter, int sr, int fsize, float hold, float fall) {
+ km->_z1 = 0;
+ km->_z2 = 0;
+ km->_rms = 0;
+ km->_dpk = 0;
+ km->_cnt = 0;
+ km->_flag = false;
+
+ float t;
+ km->_omega = 9.72f / sr;
+ t = (float) fsize / sr;
+ km->_hold = (int)(hold / t + 0.5f);
+ km->_fall = powf(10.0f, -0.05f * fall * t);
+}
+
+void kmeter_process(jack_mixer_kmeter_t kmeter, jack_default_audio_sample_t *p, int start, int end) {
+ int i;
+ jack_default_audio_sample_t s, t, z1, z2;
+
+ if (km->_flag) {
+ km->_rms = 0;
+ km->_flag = 0;
+ }
+
+ z1 = km->_z1;
+ z2 = km->_z2;
+
+ t = 0;
+
+ for (i = start; i < end; i++) {
+ s = p[i];
+ s *= s;
+ if (t < s) t = s;
+ z1 += km->_omega * (s - z1);
+ z2 += km->_omega * (z1 - z2);
+ }
+ t = sqrtf(t);
+
+ km->_z1 = z1 + 1e-20f;
+ km->_z2 = z2 + 1e-20f;
+
+ s = sqrtf(2 * z2);
+ if (s > km->_rms) km->_rms = s;
+
+ if (t > km->_dpk) {
+ km->_dpk = t;
+ km->_cnt = km->_hold;
+ } else if (km->_cnt) {
+ km->_cnt--;
+ } else {
+ km->_dpk *= km->_fall;
+ km->_dpk += 1e-10f;
+ }
+}
+
void
remove_output_channel(
jack_mixer_output_channel_t output_channel)
#define JACK_MIXER_H__DAEB51D8_5861_40F2_92E4_24CA495A384D__INCLUDED
#include <stdbool.h>
+#include <jack/jack.h>
#include "scale.h"
typedef void * jack_mixer_t;
+typedef void * jack_mixer_kmeter_t;
typedef void * jack_mixer_channel_t;
typedef void * jack_mixer_output_channel_t;
typedef void * jack_mixer_threshold_t;
jack_mixer_t mixer,
int new_channel);
-
int
get_midi_behavior_mode(
jack_mixer_t mixer);
const char * channel_name,
bool stereo);
+void kmeter_init(jack_mixer_kmeter_t km,
+ int sr,
+ int fsize,
+ float hold,
+ float fall);
+
+void kmeter_process(
+ jack_mixer_kmeter_t km,
+ jack_default_audio_sample_t *p,
+ int start,
+ int end);
+
const char *
channel_get_name(
jack_mixer_channel_t channel);
jack_mixer_channel_t channel,
double * mono_ptr);
+
+/* returned values are in dBFS */
+void
+channel_stereo_kmeter_read(
+ jack_mixer_channel_t channel,
+ double * left_ptr,
+ double * right_ptr,
+ double * left_rms_ptr,
+ double * right_rms_ptr);
+
+/* returned value is in dBFS */
+void
+channel_mono_kmeter_read(
+ jack_mixer_channel_t channel,
+ double * mono_ptr,
+ double * mono_rms_ptr);
+
+
+
bool
channel_is_stereo(
jack_mixer_channel_t channel);
class JackMixer(SerializedObject):
# scales suitable as meter scales
- meter_scales = [scale.IEC268(), scale.Linear70dB(), scale.IEC268Minimalistic()]
+ meter_scales = [scale.K20(), scale.IEC268(), scale.Linear70dB(), scale.IEC268Minimalistic()]
# scales suitable as volume slider scales
slider_scales = [scale.Linear30dB(), scale.Linear70dB()]
self.monitor_channel = self.mixer.add_output_channel("Monitor", True, True)
self.save = False
- GLib.timeout_add(80, self.read_meters)
+ GLib.timeout_add(20, self.read_meters)
if with_nsm:
GLib.timeout_add(200, self.nsm_react)
GLib.timeout_add(50, self.midi_events_check)
return result;
}
+static PyObject*
+Channel_get_kmeter(ChannelObject *self, void *closure)
+{
+ PyObject *result;
+ double peak_left, peak_right, rms_left, rms_right;
+
+ if (channel_is_stereo(self->channel)) {
+ result = PyTuple_New(4);
+ channel_stereo_kmeter_read(self->channel, &peak_left, &peak_right, &rms_left, &rms_right);
+ PyTuple_SetItem(result, 0, PyFloat_FromDouble(peak_left));
+ PyTuple_SetItem(result, 1, PyFloat_FromDouble(peak_right));
+ PyTuple_SetItem(result, 2, PyFloat_FromDouble(rms_left));
+ PyTuple_SetItem(result, 3, PyFloat_FromDouble(rms_right));
+ } else {
+ result = PyTuple_New(2);
+ channel_mono_kmeter_read(self->channel, &peak_left, &rms_left);
+ PyTuple_SetItem(result, 0, PyFloat_FromDouble(peak_left));
+ PyTuple_SetItem(result, 1, PyFloat_FromDouble(rms_left));
+ }
+ return result;
+}
+
static PyObject*
Channel_get_abspeak(ChannelObject *self, void *closure)
{
{"meter",
(getter)Channel_get_meter, NULL,
"meter", NULL},
+ {"kmeter",
+ (getter)Channel_get_kmeter, NULL,
+ "kmeter", NULL},
{"abspeak",
(getter)Channel_get_abspeak, (setter)Channel_set_abspeak,
"balance", NULL},
self.scale = scale
self.connect("draw", self.draw)
- #self.connect("size-request", self.on_size_request)
self.connect("size-allocate", self.on_size_allocate)
self.color_bg = Gdk.Color(0,0,0)
else:
height = self.height
gradient = cairo.LinearGradient(1, 1, width-1, height-1)
- gradient.add_color_stop_rgb(0, 1, 0, 0)
- gradient.add_color_stop_rgb(0.2, 1, 1, 0)
- gradient.add_color_stop_rgb(1, 0, 1, 0)
+ if self.scale.scale_id == "K20":
+ gradient.add_color_stop_rgb(0, 1, 0, 0)
+ gradient.add_color_stop_rgb(0.38, 1, 1, 0)
+ gradient.add_color_stop_rgb(0.5, 0, 1, 0)
+ gradient.add_color_stop_rgb(1, 0, 0, 1)
+ else:
+ gradient.add_color_stop_rgb(0, 1, 0, 0)
+ gradient.add_color_stop_rgb(0.2, 1, 1, 0)
+ gradient.add_color_stop_rgb(1, 0, 1, 0)
cairo_ctx.set_source(gradient)
cairo_ctx.rectangle(x, self.height * (1 - value), width, self.height * value)
cairo_ctx.fill()
+ def draw_peak(self, cairo_ctx, value, x, width):
+ cairo_ctx.set_source_rgb(1, 1, 1)
+ cairo_ctx.rectangle(x, self.height * (1 - value), width, 2.5)
+ cairo_ctx.fill()
+
def set_scale(self, scale):
self.scale = scale
self.cache_surface = None
def __init__(self, scale):
MeterWidget.__init__(self, scale)
self.value = 0.0
+ self.pk = 0.0
self.raw_value = 0.0
+ self.raw_pk = 0.0
def draw(self, widget, cairo_ctx):
self.draw_background(cairo_ctx)
self.draw_value(cairo_ctx, self.value, self.width/4.0, self.width/2.0)
+ self.draw_peak(cairo_ctx, self.pk, self.width/4.0, self.width/2.0)
- def set_value(self, value):
- if value != self.raw_value:
- self.raw_value = value
- self.value = self.scale.db_to_scale(value)
- self.invalidate_all()
- if value == self.raw_value:
+ def set_values(self, pk, value):
+ if value == self.raw_value and pk == self.raw_pk:
return
self.raw_value = value
+ self.raw_pk = pk
old_value = self.value
+ old_pk = self.pk
self.value = self.scale.db_to_scale(value)
- if (abs(old_value-self.value) * self.height) > 1:
+ self.pk = self.scale.db_to_scale(pk)
+ if (abs(old_value-self.value) * self.height) > 0.01 or (abs(old_pk-self.pk) * self.height) > 0.01:
self.invalidate_all()
class StereoMeterWidget(MeterWidget):
def __init__(self, scale):
MeterWidget.__init__(self, scale)
+ self.pk_left = 0.0
+ self.pk_right = 0.0
+
self.left = 0.0
self.right = 0.0
+ self.raw_left_pk = 0.0
+ self.raw_right_pk = 0.0
+
self.raw_left = 0.0
self.raw_right = 0.0
self.draw_background(cairo_ctx)
self.draw_value(cairo_ctx, self.left, self.width/5.0, self.width/5.0)
self.draw_value(cairo_ctx, self.right, self.width/5.0 * 3.0, self.width/5.0)
+ self.draw_peak(cairo_ctx, self.pk_left, self.width/5.0, self.width/5.0)
+ self.draw_peak(cairo_ctx, self.pk_right, self.width/5.0 * 3.0, self.width/5.0)
+
- def set_values(self, left, right):
- if left == self.raw_left and right == self.raw_right:
+ def set_values(self, pk_l, pk_r, left, right):
+ if left == self.raw_left and right == self.raw_right and pk_l == self.raw_left_pk and pk_r == self.raw_right_pk:
return
self.raw_left = left
self.raw_right = right
+ self.raw_left_pk = pk_l
+ self.raw_right_pk = pk_r
old_left = self.left
old_right = self.right
+ old_pk_left = self.pk_left
+ old_pk_right = self.pk_right
self.left = self.scale.db_to_scale(left)
self.right = self.scale.db_to_scale(right)
- if (abs(old_left-self.left) * self.height) > 0.01 or (abs(old_right-self.right) * self.height) > 0.01:
+ self.pk_left = self.scale.db_to_scale(pk_l)
+ self.pk_right = self.scale.db_to_scale(pk_r)
+
+ if (abs(old_left-self.left) * self.height) > 0.01 or (abs(old_right-self.right) * self.height) > 0.01 or (abs(old_pk_left-self.pk_left) * self.height) > 0.01 or (abs(old_pk_right-self.pk_right) * self.height) > 0.01:
self.invalidate_all()
class Mark:
'''Encapsulates scale linear function edge and coefficients for scale = a * dB + b formula'''
- def __init__(self, db, scale):
+ def __init__(self, db, scale, text = None):
self.db = db
self.scale = scale
- self.text = "%.0f" % math.fabs(db)
-
+ if text == None:
+ self.text = "%.0f" % math.fabs(db)
+ else:
+ self.text = text
class Base:
'''Scale abstraction, various scale implementation derive from this class'''
self.description = description
self.scale = jack_mixer_c.Scale()
- def add_threshold(self, db, scale, is_mark):
+ def add_threshold(self, db, scale, is_mark, text = None):
self.scale.add_threshold(db, scale)
if is_mark:
- self.marks.append(Mark(db, scale))
+ self.marks.append(Mark(db, scale, text))
def calculate_coefficients(self):
self.scale.calculate_coefficients()
self.calculate_coefficients()
self.scale_marks()
+class K20(Base):
+ '''K20 scale'''
+ def __init__(self):
+ Base.__init__(self, "K20", "K20 scale")
+ self.add_threshold(-65, 1.e-10, True, "")
+ self.add_threshold(-60, 0.001, True, "-40")
+ self.add_threshold(-55, 0.001778, True, "")
+ self.add_threshold(-50, 0.003162, True, "-30")
+ self.add_threshold(-45, 0.00562, True, "")
+ self.add_threshold(-40, 0.01, True, "-20")
+ self.add_threshold(-35, 0.01778, True, "")
+ self.add_threshold(-30, 0.03162, True, "-10")
+ self.add_threshold(-26, 0.05, True, "-6")
+ self.add_threshold(-23, 0.0708, True, "-3")
+ self.add_threshold(-20, 0.1, True, "0")
+ self.add_threshold(-17, 0.1413, True, "3")
+ self.add_threshold(-14, 0.1995, True, "6")
+ self.add_threshold(-10, 0.3162, True, "10")
+ self.add_threshold(-5, 0.562, True, "15")
+ self.add_threshold(0, 1.0, True, "20")
+ self.calculate_coefficients()
+ self.scale_marks()
+
+ def mapk20(self, v):
+ if v < 0.001: return (24000 * v)
+ v = math.log10(v) + 3
+ if v < 2.0: return (24.3 + v * (100 + v * 16))
+ if v > 3.0: v = 3.0
+ return (v * 161.7 - 35.1)
+
+ def add_threshold(self, db, scale, is_mark, text):
+ v = self.mapk20(scale) / 450
+ Base.add_threshold(self, db, v, is_mark, text)
def scale_test1(scale):
for i in range(-97 * 2, 1, 1):