]> git.0d.be Git - jack_mixer.git/commitdiff
Add K20 scale
authorDaniel Sheeler <dsheeler@pobox.com>
Sun, 19 Jul 2020 21:25:47 +0000 (16:25 -0500)
committerDaniel Sheeler <dsheeler@pobox.com>
Sun, 19 Jul 2020 21:29:45 +0000 (16:29 -0500)
README.md
channel.py
jack_mixer.c
jack_mixer.h
jack_mixer.py
jack_mixer_c.c
meter.py
scale.py

index dab10212e7544c62bcac34aa145c77180591708f..5abc0c489009f46308076af93ad813430caf07d0 100644 (file)
--- a/README.md
+++ b/README.md
@@ -56,6 +56,10 @@ jack_mixer was initially written and supported by Nedko Arnaudov, it is now
 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
index 3ce02b0a490cc5773925fe4efc77b1e11b41860c..177fbbd481635990b3076f6075dd166245b4fbf1 100644 (file)
@@ -305,10 +305,11 @@ class Channel(Gtk.VBox, SerializedObject):
         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)
 
index 243e6d0f953e9841e291674f93a93f8f94d72a6e..22faff4e3d56bc0657a1f847c56977858cff9fd5 100644 (file)
 #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
 {
@@ -76,6 +88,9 @@ 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;
 
@@ -167,6 +182,29 @@ db_to_value(
   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*
@@ -518,28 +556,6 @@ channel_stereo_meter_read(
   *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(
@@ -549,6 +565,34 @@ 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,
@@ -721,6 +765,7 @@ mix_one(
   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;
@@ -778,6 +823,7 @@ mix_one(
 
   /* process main mix channel */
   unsigned int steps = mix_channel->num_volume_transition_steps;
+
   for (i = start ; i < end ; i++)
   {
     if (! output_mix_channel->prefader) {
@@ -837,6 +883,44 @@ mix_one(
       }
     }
 
+    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)
     {
@@ -868,6 +952,8 @@ mix_one(
           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
@@ -880,6 +966,7 @@ calc_channel_frames(
   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)
@@ -968,6 +1055,7 @@ calc_channel_frames(
     }
     else
     {
+
       frame_left = (fabsf(frame_left) + fabsf(frame_right)) / 2;
 
       if (channel_ptr->peak_left < frame_left)
@@ -995,6 +1083,7 @@ calc_channel_frames(
 
       channel_ptr->peak_frames = 0;
     }
+
     channel_ptr->volume_idx++;
     if ((channel_ptr->volume != channel_ptr->volume_new) &&
      (channel_ptr->volume_idx == steps)) {
@@ -1006,8 +1095,12 @@ calc_channel_frames(
      (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
@@ -1475,10 +1568,12 @@ add_channel(
 
   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;
@@ -1488,6 +1583,9 @@ add_channel(
   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;
@@ -1603,10 +1701,12 @@ create_output_channel(
   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;
@@ -1614,6 +1714,8 @@ create_output_channel(
   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;
@@ -1700,6 +1802,63 @@ remove_channels(
   }
 }
 
+#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)
index 395e1a2d8af3e1520959e71ad2a30a91e210e75b..17b5fb739f36cf4899eb3ee554f4f3c90c89f817 100644 (file)
 #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;
@@ -60,7 +62,6 @@ set_last_midi_channel(
   jack_mixer_t mixer,
   int new_channel);
 
-
 int
 get_midi_behavior_mode(
   jack_mixer_t mixer);
@@ -76,6 +77,18 @@ add_channel(
   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);
@@ -93,6 +106,25 @@ channel_mono_meter_read(
   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);
index e8eb74d9bab23c1e18c6670c2747efe63b475a5b..c0274cae92d8fc0ad23831675f6ca33186048be2 100755 (executable)
@@ -53,7 +53,7 @@ log = logging.getLogger("jack_mixer")
 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()]
@@ -94,7 +94,7 @@ class JackMixer(SerializedObject):
         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)
index 3e93a7e55a1e0df0168c624b53ed376ade20cce2..f19a5b56e18d04c31d3a9fa99931e766a8c04b48 100644 (file)
@@ -302,6 +302,28 @@ Channel_get_meter(ChannelObject *self, void *closure)
        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)
 {
@@ -513,6 +535,9 @@ static PyGetSetDef Channel_getseters[] = {
        {"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},
index 7d1aa9abc924b250fbcf1669ab301ed9813b0336..e3c158457883da9c914855de50417c90df84df23 100644 (file)
--- a/meter.py
+++ b/meter.py
@@ -33,7 +33,6 @@ class MeterWidget(Gtk.DrawingArea):
         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)
@@ -115,13 +114,24 @@ class MeterWidget(Gtk.DrawingArea):
         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
@@ -132,32 +142,40 @@ class MonoMeterWidget(MeterWidget):
     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
 
@@ -165,15 +183,25 @@ class StereoMeterWidget(MeterWidget):
         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()
index 5282154631a9ab61128614edccb638029deae88f..f098ae6d3e6bf4efcf33d45a609a5f7e5760c00c 100644 (file)
--- a/scale.py
+++ b/scale.py
@@ -26,11 +26,13 @@ log = logging.getLogger(__name__)
 
 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'''
@@ -40,10 +42,10 @@ class Base:
         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()
@@ -140,6 +142,39 @@ class Linear30dB(Base):
         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):