[SCM] calf/master: Add Analyzer plugin. Improve Stereo Tools plugin.

js at users.alioth.debian.org js at users.alioth.debian.org
Tue May 7 15:40:53 UTC 2013


The following commit has been merged in the master branch:
commit f834766f5cb62d7358efa61eb8077e8c07be7803
Author: Markus Schmidt <schmidt at boomshop.net>
Date:   Wed Feb 22 11:21:49 2012 +0000

    Add Analyzer plugin. Improve Stereo Tools plugin.
    
    Note: slightly modified by KF (removed global variables, fixed a bug caused
    by an obscure C++ rule).

diff --git a/doc/manuals/scripts/manual.js b/doc/manuals/scripts/manual.js
index 79cab78..acbe2f6 100644
--- a/doc/manuals/scripts/manual.js
+++ b/doc/manuals/scripts/manual.js
@@ -59,7 +59,8 @@ $(document).ready(function () {
                 ["Exciter", "Exciter.html", "images/Calf - Exciter.png"],
                 ["Bass Enhancer", "Bass Enhancer.html", "images/Calf - Bass Enhancer.png"],
                 ["Mono Input", "Mono Input.html", "images/Calf - Mono Input.png"],
-                ["Stereo Tools", "Stereo Tools.html", "images/Calf - Stereo Tools.png"]
+                ["Stereo Tools", "Stereo Tools.html", "images/Calf - Stereo Tools.png"],
+                ["Analyzer", "Analyzer.html", "images/Calf - Analyzer.png"]
             ]
         ]
     ];
diff --git a/src/calf/custom_ctl.h b/src/calf/custom_ctl.h
index 2162e13..826b22c 100644
--- a/src/calf/custom_ctl.h
+++ b/src/calf/custom_ctl.h
@@ -19,9 +19,14 @@
  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 
  * Boston, MA 02111-1307, USA.
  */
+ 
+#ifndef _USE_MATH_DEFINES
+#define _USE_MATH_DEFINES
+#endif
 #ifndef __CALF_CUSTOM_CTL
 #define __CALF_CUSTOM_CTL
 
+
 #include <gtk/gtk.h>
 #include <calf/giface.h>
 
@@ -40,7 +45,10 @@ struct CalfLineGraph
     const calf_plugins::line_graph_iface *source;
     int source_id;
     bool is_square;
+    bool use_fade;
+    float fade;
     cairo_surface_t *cache_surface;
+    cairo_surface_t *fade_surface;
     //GdkPixmap *cache_pixmap;
     int last_generation;
 };
@@ -58,6 +66,44 @@ extern void calf_line_graph_set_square(CalfLineGraph *graph, bool is_square);
 
 extern int calf_line_graph_update_if(CalfLineGraph *graph, int generation);
 
+#define CALF_TYPE_PHASE_GRAPH           (calf_phase_graph_get_type())
+#define CALF_PHASE_GRAPH(obj)           (G_TYPE_CHECK_INSTANCE_CAST ((obj), CALF_TYPE_PHASE_GRAPH, CalfPhaseGraph))
+#define CALF_IS_PHASE_GRAPH(obj)        (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CALF_TYPE_PHASE_GRAPH))
+#define CALF_PHASE_GRAPH_CLASS(klass)   (G_TYPE_CHECK_CLASS_CAST ((klass),  CALF_TYPE_PHASE_GRAPH, CalfPhaseGraphClass))
+#define CALF_IS_PHASE_GRAPH_CLASS(obj)  (G_TYPE_CHECK_CLASS_TYPE ((klass),  CALF_TYPE_PHASE_GRAPH))
+#define CALF_PHASE_GRAPH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),  CALF_TYPE_PHASE_GRAPH, CalfPhaseGraphClass))
+
+struct CalfPhaseGraph
+{
+    GtkDrawingArea parent;
+    const calf_plugins::phase_graph_iface *source;
+    int source_id;
+    cairo_surface_t *cache_surface;
+    cairo_surface_t *fade_surface;
+    inline float _atan(float x, float l, float r) {
+        // this is a wrapper for a CPU friendly implementation of atan()
+        if(l >= 0 and r >= 0) {
+            return atan(x);
+        } else if(l >= 0 and r < 0) {
+            return M_PI + atan(x);
+        } else if(l < 0 and r < 0) {
+            return M_PI + atan(x);
+        } else if(l < 0 and r >= 0) {
+            return (2.f * M_PI) + atan(x);
+        }
+        return 0.f;
+    }
+};
+
+struct CalfPhaseGraphClass
+{
+    GtkDrawingAreaClass parent_class;
+};
+
+extern GtkWidget *calf_phase_graph_new();
+
+extern GType calf_phase_graph_get_type();
+
 #define CALF_TYPE_KNOB          (calf_knob_get_type())
 #define CALF_KNOB(obj)          (G_TYPE_CHECK_INSTANCE_CAST ((obj), CALF_TYPE_KNOB, CalfKnob))
 #define CALF_IS_KNOB(obj)       (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CALF_TYPE_KNOB))
diff --git a/src/calf/giface.h b/src/calf/giface.h
index f64908b..eaad925 100644
--- a/src/calf/giface.h
+++ b/src/calf/giface.h
@@ -200,6 +200,13 @@ struct line_graph_iface
     virtual ~line_graph_iface() {}
 };
 
+/// 'provides live line graph values' interface
+struct phase_graph_iface
+{
+    virtual bool get_phase_graph(float ** _buffer, int *_length, int * _mode, bool * _use_fade, float * _fade, int * _accuracy, bool * _display) const { return false; };
+    virtual ~phase_graph_iface() {}
+};
+
 enum table_column_type
 {
     TCT_UNKNOWN, ///< guard invalid type
@@ -355,6 +362,8 @@ struct plugin_ctl_iface
     virtual const plugin_metadata_iface *get_metadata_iface() const = 0;
     /// @return line_graph_iface if any
     virtual const line_graph_iface *get_line_graph_iface() const = 0;
+    /// @return phase_graph_iface if any
+    virtual const phase_graph_iface *get_phase_graph_iface() const = 0;
     /// Do-nothing destructor to silence compiler warning
     virtual ~plugin_ctl_iface() {}
 };
@@ -435,6 +444,8 @@ struct audio_module_iface
     virtual uint32_t message_run(const void *valid_ports, void *output_ports) = 0;
     /// @return line_graph_iface if any
     virtual const line_graph_iface *get_line_graph_iface() const = 0;
+     /// @return phase_graph_iface if any
+    virtual const phase_graph_iface *get_phase_graph_iface() const = 0;
     virtual ~audio_module_iface() {}
 };
 
@@ -537,6 +548,8 @@ public:
     }
     /// @return line_graph_iface if any
     virtual const line_graph_iface *get_line_graph_iface() const { return dynamic_cast<const line_graph_iface *>(this); }
+    /// @return phase_graph_iface if any
+    virtual const phase_graph_iface *get_phase_graph_iface() const { return dynamic_cast<const phase_graph_iface *>(this); }
 };
 
 #if USE_EXEC_GUI || USE_DSSI
@@ -567,6 +580,9 @@ struct dssi_feedback_sender
     /// Source for the graph data (interface to marshal)
     const calf_plugins::line_graph_iface *graph;
     
+    /// Source for the graph data (interface to marshal)
+    const calf_plugins::phase_graph_iface *phase;
+    
     /// Create using a new client
     dssi_feedback_sender(const char *URI, const line_graph_iface *_graph);
     dssi_feedback_sender(osctl::osc_client *_client, const line_graph_iface *_graph);
diff --git a/src/calf/gui_controls.h b/src/calf/gui_controls.h
index 15cca8e..bddbd70 100644
--- a/src/calf/gui_controls.h
+++ b/src/calf/gui_controls.h
@@ -216,6 +216,18 @@ struct line_graph_param_control: public param_control
     virtual ~line_graph_param_control();
 };
 
+/// Phase graph
+struct phase_graph_param_control: public param_control
+{
+    int last_generation;
+
+    virtual GtkWidget *create(plugin_gui *_gui, int _param_no);
+    virtual void get() {}
+    virtual void set();
+    virtual void on_idle();
+    virtual ~phase_graph_param_control();
+};
+
 /// Knob
 struct knob_param_control: public param_control
 {
diff --git a/src/calf/jackhost.h b/src/calf/jackhost.h
index 3edb720..adcf13b 100644
--- a/src/calf/jackhost.h
+++ b/src/calf/jackhost.h
@@ -134,6 +134,7 @@ public:
     virtual int send_status_updates(send_updates_iface *sui, int last_serial) { return module->send_status_updates(sui, last_serial); }
     virtual const plugin_metadata_iface *get_metadata_iface() const { return module->get_metadata_iface(); }
     virtual const line_graph_iface *get_line_graph_iface() const { return module->get_line_graph_iface(); }
+    virtual const phase_graph_iface *get_phase_graph_iface() const { return module->get_phase_graph_iface(); }
 };
 
 extern jack_host *create_jack_host(const char *name, const std::string &instance_name, calf_plugins::progress_report_iface *priface);
diff --git a/src/calf/lv2wrap.h b/src/calf/lv2wrap.h
index f204db6..7a9f82a 100644
--- a/src/calf/lv2wrap.h
+++ b/src/calf/lv2wrap.h
@@ -169,6 +169,7 @@ struct lv2_instance: public plugin_ctl_iface, public progress_report_iface
     }
     virtual const plugin_metadata_iface *get_metadata_iface() const { return metadata; }
     virtual const line_graph_iface *get_line_graph_iface() const { return module->get_line_graph_iface(); }
+    virtual const phase_graph_iface *get_phase_graph_iface() const { return module->get_phase_graph_iface(); }
     virtual int send_status_updates(send_updates_iface *sui, int last_serial) { return module->send_status_updates(sui, last_serial); }
 };
 
diff --git a/src/calf/metadata.h b/src/calf/metadata.h
index d0035c0..c84bd02 100644
--- a/src/calf/metadata.h
+++ b/src/calf/metadata.h
@@ -379,7 +379,7 @@ struct bassenhancer_metadata: public plugin_metadata<bassenhancer_metadata>
            param_freq, param_listen, param_floor_active, param_floor, param_count };
     PLUGIN_NAME_ID_LABEL("bassenhancer", "bassenhancer", "Bass Enhancer")
 };
-/// Markus's Mono Module - metadata
+/// Markus's Stereo Module - metadata
 struct stereo_metadata: public plugin_metadata<stereo_metadata>
 {
     enum { in_count = 2, out_count = 2, ins_optional = 1, outs_optional = 1, support_midi = false, require_midi = false, rt_capable = true };
@@ -387,7 +387,7 @@ struct stereo_metadata: public plugin_metadata<stereo_metadata>
            STEREO_VU_METER_PARAMS, param_balance_in, param_balance_out, param_softclip,
            param_mute_l, param_mute_r, param_phase_l, param_phase_r,
            param_mode, param_slev, param_sbal, param_mlev, param_mpan,
-           param_widener, param_delay,
+           param_stereo_base, param_delay,
            param_meter_phase,
            param_count };
     PLUGIN_NAME_ID_LABEL("stereo", "stereo", "Stereo Tools")
@@ -404,7 +404,16 @@ struct mono_metadata: public plugin_metadata<mono_metadata>
            param_count };
     PLUGIN_NAME_ID_LABEL("mono", "mono", "Mono Input")
 };
-
+/// Markus's and Chrischi's Analyzer
+struct analyzer_metadata: public plugin_metadata<analyzer_metadata>
+{
+    enum { in_count = 2, out_count = 2, ins_optional = 1, outs_optional = 1, support_midi = false, require_midi = false, rt_capable = true };
+    enum { param_meter_L, param_meter_R, param_clip_L, param_clip_R,
+           param_analyzer_level, param_analyzer_mode, param_analyzer_accuracy, param_analyzer_speed, param_analyzer_display, param_analyzer_smoothing, param_analyzer_hold, param_analyzer_freeze,
+           param_gonio_level, param_gonio_mode, param_gonio_use_fade, param_gonio_fade, param_gonio_accuracy, param_gonio_display,
+           param_count };
+    PLUGIN_NAME_ID_LABEL("analyzer", "analyzer", "Analyzer")
+};
 /// Organ - enums for parameter IDs etc. (this mess is caused by organ split between plugin and generic class - which was
 /// a bad design decision and should be sorted out some day) XXXKF @todo
 struct organ_enums
diff --git a/src/calf/modulelist.h b/src/calf/modulelist.h
index 8425794..4219e03 100644
--- a/src/calf/modulelist.h
+++ b/src/calf/modulelist.h
@@ -27,6 +27,7 @@
     PER_MODULE_ITEM(bassenhancer, false, "bassenhancer")
     PER_MODULE_ITEM(mono, false, "mono")
     PER_MODULE_ITEM(stereo, false, "stereo")
+    PER_MODULE_ITEM(analyzer, false, "analyzer")
 #ifdef ENABLE_EXPERIMENTAL
     PER_MODULE_ITEM(fluidsynth, true, "fluidsynth")
     PER_MODULE_ITEM(wavetable, true, "wavetable")
diff --git a/src/calf/modules.h b/src/calf/modules.h
index fb9ba77..deec06f 100644
--- a/src/calf/modules.h
+++ b/src/calf/modules.h
@@ -21,6 +21,7 @@
 #ifndef CALF_MODULES_H
 #define CALF_MODULES_H
 
+#include "rfftw.h"
 #include <assert.h>
 #include <limits.h>
 #include "biquad.h"
@@ -298,6 +299,53 @@ public:
     uint32_t process(uint32_t offset, uint32_t numsamples, uint32_t inputs_mask, uint32_t outputs_mask);
 };
 
+class analyzer_audio_module:
+    public audio_module<analyzer_metadata>, public frequency_response_line_graph, public phase_graph_iface
+{
+    typedef analyzer_audio_module AM;
+    uint32_t srate;
+    bool active;
+    int _accuracy;
+    int _acc_old;
+    uint32_t clip_L, clip_R;
+    float meter_L, meter_R;
+    
+public:
+    analyzer_audio_module();
+    void params_changed();
+    void activate();
+    void set_sample_rate(uint32_t sr);
+    void deactivate();
+    uint32_t process(uint32_t offset, uint32_t numsamples, uint32_t inputs_mask, uint32_t outputs_mask);
+    bool get_phase_graph(float ** _buffer, int * _length, int * _mode, bool * _use_fade, float * _fade, int * _accuracy, bool * _display) const;
+    bool get_graph(int index, int subindex, float *data, int points, cairo_iface *context) const;
+    
+
+protected:
+    static const int max_phase_buffer_size = 8192;
+    int phase_buffer_size;
+    float *phase_buffer;
+    int fft_buffer_size;
+    float *fft_buffer;
+    int plength;
+    int ppos;
+    int fpos;
+    rfftw_plan fft_plan;
+    static const int max_fft_cache_size = 32768;
+    static const int max_fft_buffer_size = max_fft_cache_size * 2;
+    fftw_real *fft_in;
+    fftw_real *fft_out;
+    fftw_real *fft_smooth;
+    float *fft_delta;
+    float *fft_hold;
+    float *fft_freeze;
+    float _hold;
+
+    mutable int ____analyzer_phase_was_drawn_here;
+    mutable int ____analyzer_smooth_dirty;
+    mutable int ____analyzer_hold_dirty;
+
+};
 
 };
 #endif
diff --git a/src/custom_ctl.cpp b/src/custom_ctl.cpp
index be8234e..d4454e1 100644
--- a/src/custom_ctl.cpp
+++ b/src/custom_ctl.cpp
@@ -18,8 +18,6 @@
  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 
  * Boston, MA  02110-1301  USA
  */
-
-    
 #include "config.h"
 #include <calf/custom_ctl.h>
 #include <gdk/gdkkeysyms.h>
@@ -29,10 +27,10 @@
 #include <sys/time.h>
 
 static void
-calf_line_graph_copy_cache_to_window( CalfLineGraph *lg, cairo_t *c )
+calf_line_graph_copy_cache_to_window( cairo_surface_t *lg, cairo_t *c )
 {
     cairo_save( c );
-    cairo_set_source_surface( c, lg->cache_surface, 0,0 );
+    cairo_set_source_surface( c, lg, 0,0 );
     cairo_paint( c );
     cairo_restore( c );
 }
@@ -82,13 +80,16 @@ calf_line_graph_draw_graph( cairo_t *c, float *data, int sx, int sy )
 {
     int ox=5, oy=5;
 
-    for (int i = 0; i < 2 * sx; i++)
+    for (int i = 0; i < sx; i++)
     {
         int y = (int)(oy + sy / 2 - (sy / 2 - 1) * data[i]);
         //if (y < oy) y = oy;
         //if (y >= oy + sy) y = oy + sy - 1;
-        if (i)
-            cairo_line_to(c, ox + i * 0.5, y);
+        if (i and (data[i] < INFINITY or i == sx - 1)) {
+            cairo_line_to(c, ox + i, y);
+        } else if (i) {
+            continue;
+        }
         else
             cairo_move_to(c, ox, y);
     }
@@ -111,7 +112,8 @@ calf_line_graph_expose (GtkWidget *widget, GdkEventExpose *event)
     GdkColor sc = { 0, 0, 0, 0 };
 
     bool cache_dirty = 0;
-
+    bool fade_dirty = 0;
+    
     if( lg->cache_surface == NULL ) {
         // looks like its either first call or the widget has been resized.
         // create the cache_surface.
@@ -125,7 +127,17 @@ calf_line_graph_expose (GtkWidget *widget, GdkEventExpose *event)
         
         cache_dirty = 1;
     }
-
+    if( lg->fade_surface == NULL ) {
+        // looks like its either first call or the widget has been resized.
+        // create the cache_surface.
+        cairo_surface_t *window_surface = cairo_get_target( c );
+        lg->fade_surface = cairo_surface_create_similar( window_surface, 
+                                  CAIRO_CONTENT_COLOR,
+                                  widget->allocation.width,
+                                  widget->allocation.height );
+        fade_dirty = 1;
+    }
+    
     cairo_select_font_face(c, "Bitstream Vera Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
     cairo_set_font_size(c, 9);
     gdk_cairo_set_source_color(c, &sc);
@@ -213,7 +225,7 @@ calf_line_graph_expose (GtkWidget *widget, GdkEventExpose *event)
             gdk_cairo_set_source_color(cache_cr, &sc2);
             cairo_set_line_join(cache_cr, CAIRO_LINE_JOIN_MITER);
             cairo_set_line_width(cache_cr, 1);
-            for(graph_n = 0; (graph_n<cache_graph_index) && lg->source->get_graph(lg->source_id, graph_n, data, 2 * sx, &cache_cimpl); graph_n++)
+            for(graph_n = 0; (graph_n<cache_graph_index) && lg->source->get_graph(lg->source_id, graph_n, data, sx, &cache_cimpl); graph_n++)
             {
                 calf_line_graph_draw_graph( cache_cr, data, sx, sy );
             }
@@ -225,47 +237,56 @@ calf_line_graph_expose (GtkWidget *widget, GdkEventExpose *event)
                 cairo_fill(cache_cr);
             }
 
-            // copy window to cache.
             cairo_destroy( cache_cr );
-            calf_line_graph_copy_cache_to_window( lg, c );
         } else {
             grid_n_save = cache_grid_index;
             graph_n = cache_graph_index;
             dot_n = cache_dot_index;
-            calf_line_graph_copy_cache_to_window( lg, c );
         }
-
-        cairo_rectangle(c, ox, oy, sx, sy);
-        cairo_clip(c);  
         
-        cairo_set_line_width(c, 1);
+        cairo_t *cache_cr = cairo_create( lg->fade_surface );
+        cairo_set_source_surface(cache_cr, lg->cache_surface, 0, 0);
+        if(fade_dirty or !lg->use_fade) {
+            cairo_paint(cache_cr);
+        } else {
+            cairo_paint_with_alpha(cache_cr, lg->fade * 0.35 + 0.05);
+        }
+        
+        cairo_impl cache_cimpl;
+        cache_cimpl.context = cache_cr;
+        
+        cairo_rectangle(cache_cr, ox, oy, sx, sy);
+        cairo_clip(cache_cr);  
+        
+        cairo_set_line_width(cache_cr, 1);
         for(int phase = 1; phase <= 2; phase++)
         {
-            for(int gn=grid_n_save; legend = std::string(), cairo_set_source_rgba(c, 0, 0, 0, 0.6), lg->source->get_gridline(lg->source_id, gn, pos, vertical, legend, &cimpl); gn++)
+            for(int gn=grid_n_save; legend = std::string(), cairo_set_source_rgba(c, 0, 0, 0, 0.6), lg->source->get_gridline(lg->source_id, gn, pos, vertical, legend, &cache_cimpl); gn++)
             {
-                calf_line_graph_draw_grid( c, legend, vertical, pos, phase, sx, sy );
+                calf_line_graph_draw_grid( cache_cr, legend, vertical, pos, phase, sx, sy );
             }
         }
 
-        gdk_cairo_set_source_color(c, &sc2);
-        cairo_set_line_join(c, CAIRO_LINE_JOIN_MITER);
-        cairo_set_line_width(c, 1);
-        for(int gn = graph_n; lg->source->get_graph(lg->source_id, gn, data, 2 * sx, &cimpl); gn++)
+        gdk_cairo_set_source_color(cache_cr, &sc2);
+        cairo_set_line_join(cache_cr, CAIRO_LINE_JOIN_MITER);
+        cairo_set_line_width(cache_cr, 1);
+        for(int gn = graph_n; lg->source->get_graph(lg->source_id, gn, data, sx, &cache_cimpl); gn++)
         {
-            calf_line_graph_draw_graph( c, data, sx, sy );
+            calf_line_graph_draw_graph( cache_cr, data, sx, sy );
         }
-        gdk_cairo_set_source_color(c, &sc3);
-        for(int gn = dot_n; lg->source->get_dot(lg->source_id, gn, x, y, size = 3, &cimpl); gn++)
+        gdk_cairo_set_source_color(cache_cr, &sc3);
+        for(int gn = dot_n; lg->source->get_dot(lg->source_id, gn, x, y, size = 3, &cache_cimpl); gn++)
         {
             int yv = (int)(oy + sy / 2 - (sy / 2 - 1) * y);
-            cairo_arc(c, ox + x * sx, yv, size, 0, 2 * M_PI);
-            cairo_fill(c);
+            cairo_arc(cache_cr, ox + x * sx, yv, size, 0, 2 * M_PI);
+            cairo_fill(cache_cr);
         }
         delete []data;
+        cairo_destroy(cache_cr);
     }
-
-    cairo_destroy(c);
-
+    
+    calf_line_graph_copy_cache_to_window( lg->fade_surface, c );
+    
     // printf("exposed %p %dx%d %d+%d\n", widget->window, event->area.x, event->area.y, event->area.width, event->area.height);
 
     return TRUE;
@@ -394,6 +415,381 @@ calf_line_graph_get_type (void)
     return type;
 }
 
+
+///////////////////////////////////////// phase graph ///////////////////////////////////////////////
+
+static void
+calf_phase_graph_copy_cache_to_window( cairo_surface_t *pg, cairo_t *c )
+{
+    cairo_save( c );
+    cairo_set_source_surface( c, pg, 0,0 );
+    cairo_paint( c );
+    cairo_restore( c );
+}
+
+static gboolean
+calf_phase_graph_expose (GtkWidget *widget, GdkEventExpose *event)
+{
+    g_assert(CALF_IS_PHASE_GRAPH(widget));
+
+    CalfPhaseGraph *pg = CALF_PHASE_GRAPH(widget);
+    //int ox = widget->allocation.x + 1, oy = widget->allocation.y + 1;
+    int ox = 5, oy = 5, pad;
+    int sx = widget->allocation.width - ox * 2, sy = widget->allocation.height - oy * 2;
+    sx += sx % 2 - 1;
+    sy += sy % 2 - 1;
+    int rad = sx / 2 * 0.8;
+    int cx = ox + sx / 2;
+    int cy = oy + sy / 2;
+    cairo_t *c = gdk_cairo_create(GDK_DRAWABLE(widget->window));
+    GdkColor sc = { 0, 0, 0, 0 };
+
+    bool cache_dirty = 0;
+    bool fade_dirty = 0;
+
+    if( pg->cache_surface == NULL ) {
+        // looks like its either first call or the widget has been resized.
+        // create the cache_surface.
+        cairo_surface_t *window_surface = cairo_get_target( c );
+        pg->cache_surface = cairo_surface_create_similar( window_surface, 
+                                  CAIRO_CONTENT_COLOR,
+                                  widget->allocation.width,
+                                  widget->allocation.height );
+        cache_dirty = 1;
+    }
+    if( pg->fade_surface == NULL ) {
+        // looks like its either first call or the widget has been resized.
+        // create the cache_surface.
+        cairo_surface_t *window_surface = cairo_get_target( c );
+        pg->fade_surface = cairo_surface_create_similar( window_surface, 
+                                  CAIRO_CONTENT_COLOR,
+                                  widget->allocation.width,
+                                  widget->allocation.height );
+        fade_dirty = 1;
+    }
+
+    cairo_select_font_face(c, "Bitstream Vera Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+    cairo_set_font_size(c, 9);
+    gdk_cairo_set_source_color(c, &sc);
+    
+    cairo_impl cimpl;
+    cimpl.context = c;
+
+    if (pg->source) {
+        std::string legend;
+        float *data = new float[2 * sx];
+        GdkColor sc2 = { 0, 0.35 * 65535, 0.4 * 65535, 0.2 * 65535 };
+        GdkColor sc3 = { 0, 0.35 * 65535, 0.4 * 65535, 0.2 * 65535 };
+
+        if( cache_dirty ) {
+            
+            cairo_t *cache_cr = cairo_create( pg->cache_surface );
+        
+//            if(widget->style->bg_pixmap[0] == NULL) {
+                cairo_set_source_rgb(cache_cr, 0, 0, 0);
+//            } else {
+//                gdk_cairo_set_source_pixbuf(cache_cr, GDK_PIXBUF(&style->bg_pixmap[GTK_STATE_NORMAL]), widget->allocation.x, widget->allocation.y + 20);
+//            }
+            cairo_paint(cache_cr);
+            
+            // outer (black)
+            pad = 0;
+            cairo_rectangle(cache_cr, pad, pad, sx + ox * 2 - pad * 2, sy + oy * 2 - pad * 2);
+            cairo_set_source_rgb(cache_cr, 0, 0, 0);
+            cairo_fill(cache_cr);
+            
+            // inner (bevel)
+            pad = 1;
+            cairo_rectangle(cache_cr, pad, pad, widget->allocation.width - pad * 2, widget->allocation.height - pad + 2);
+            cairo_pattern_t *pat2 = cairo_pattern_create_linear (0, 0, 0, sy + oy * 2 - pad * 2);
+            cairo_pattern_add_color_stop_rgba (pat2, 0, 0.23, 0.23, 0.23, 1);
+            cairo_pattern_add_color_stop_rgba (pat2, 0.5, 0, 0, 0, 1);
+            cairo_set_source (cache_cr, pat2);
+            cairo_fill(cache_cr);
+            cairo_pattern_destroy(pat2);
+            
+            cairo_rectangle(cache_cr, ox - 1, oy - 1, sx + 2, sy + 2);
+            cairo_set_source_rgb (cache_cr, 0, 0, 0);
+            cairo_fill(cache_cr);
+            
+            cairo_pattern_t *pt = cairo_pattern_create_linear(ox, oy, ox, sy);
+            cairo_pattern_add_color_stop_rgb(pt, 0.0,     0.44,    0.44,    0.30);
+            cairo_pattern_add_color_stop_rgb(pt, 0.025,   0.89,    0.99,    0.54);
+            cairo_pattern_add_color_stop_rgb(pt, 0.4,     0.78,    0.89,    0.45);
+            cairo_pattern_add_color_stop_rgb(pt, 0.400001,0.71,    0.82,    0.33);
+            cairo_pattern_add_color_stop_rgb(pt, 1.0,     0.89,    1.00,    0.45);
+            cairo_set_source (cache_cr, pt);
+            cairo_rectangle(cache_cr, ox, oy, sx, sy);
+            cairo_fill(cache_cr);
+            
+            gdk_cairo_set_source_color(cache_cr, &sc2);
+            
+            cairo_select_font_face(cache_cr, "Bitstream Vera Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+            cairo_set_font_size(cache_cr, 9);
+            cairo_text_extents_t te;
+            
+            cairo_text_extents (cache_cr, "M", &te);
+            cairo_move_to (cache_cr, cx + 5, oy + 12);
+            cairo_show_text (cache_cr, "M");
+            
+            cairo_text_extents (cache_cr, "S", &te);
+            cairo_move_to (cache_cr, ox + 5, cy - 5);
+            cairo_show_text (cache_cr, "S");
+            
+            cairo_text_extents (cache_cr, "L", &te);
+            cairo_move_to (cache_cr, ox + 18, oy + 12);
+            cairo_show_text (cache_cr, "L");
+            
+            cairo_text_extents (cache_cr, "R", &te);
+            cairo_move_to (cache_cr, ox + sx - 22, oy + 12);
+            cairo_show_text (cache_cr, "R");
+            
+            cairo_impl cache_cimpl;
+            cache_cimpl.context = cache_cr;
+
+            // draw style elements here
+            cairo_set_line_width(cache_cr, 1);
+            
+            cairo_move_to(cache_cr, ox, oy + sy * 0.5);
+            cairo_line_to(cache_cr, ox + sx, oy + sy * 0.5);
+            cairo_stroke(cache_cr);
+            
+            cairo_move_to(cache_cr, ox + sx * 0.5, oy);
+            cairo_line_to(cache_cr, ox + sx * 0.5, oy + sy);
+            cairo_stroke(cache_cr);
+            
+            cairo_set_source_rgba(cache_cr, 0, 0, 0, 0.2);
+            cairo_move_to(cache_cr, ox, oy);
+            cairo_line_to(cache_cr, ox + sx, oy + sy);
+            cairo_stroke(cache_cr);
+            
+            cairo_move_to(cache_cr, ox, oy + sy);
+            cairo_line_to(cache_cr, ox + sx, oy);
+            cairo_stroke(cache_cr);
+        }
+        
+        
+        float * phase_buffer = 0;
+        int length = 0;
+        int mode = 2;
+        float fade = 0.05;
+        bool use_fade = true;
+        int accuracy = 1;
+        bool display = true;
+        
+        pg->source->get_phase_graph(&phase_buffer, &length, &mode, &use_fade, &fade, &accuracy, &display);
+        
+        fade *= 0.35;
+        fade += 0.05;
+        accuracy *= 2;
+        accuracy = 12 - accuracy;
+        
+        cairo_t *cache_cr = cairo_create( pg->fade_surface );
+        cairo_set_source_surface(cache_cr, pg->cache_surface, 0, 0);
+        if(fade_dirty or !use_fade or ! display) {
+            cairo_paint(cache_cr);
+        } else {
+            cairo_paint_with_alpha(cache_cr, fade);
+        }
+        
+        if(display) {
+            cairo_rectangle(cache_cr, ox, oy, sx, sy);
+            cairo_clip(cache_cr);
+            gdk_cairo_set_source_color(cache_cr, &sc3);
+            
+            double _a;
+            for(int i = 0; i < length; i+= accuracy) {
+                float l = phase_buffer[i];
+                float r = phase_buffer[i + 1];
+                if(l == 0.f and r == 0.f) continue;
+                else if(r == 0.f and l > 0.f) _a = M_PI / 2.f;
+                else if(r == 0.f and l < 0.f) _a = 3.f *M_PI / 2.f;
+                else _a = pg->_atan(l / r, l, r);
+                double _R = sqrt(pow(l, 2) + pow(r, 2));
+                _a += M_PI / 4.f;
+                float x = (-1.f)*_R * cos(_a);
+                float y = _R * sin(_a);
+                // mask the cached values
+                switch(mode) {
+                    case 0:
+                        // small dots
+                        cairo_rectangle (cache_cr, x * rad + cx, y * rad + cy, 1, 1);
+                        break;
+                    case 1:
+                        // medium dots
+                        cairo_rectangle (cache_cr, x * rad + cx - 0.25, y * rad + cy - 0.25, 1.5, 1.5);
+                        break;
+                    case 2:
+                        // big dots
+                        cairo_rectangle (cache_cr, x * rad + cx - 0.5, y * rad + cy - 0.5, 2, 2);
+                        break;
+                    case 3:
+                        // fields
+                        if(i == 0) cairo_move_to(cache_cr,
+                            x * rad + cx, y * rad + cy);
+                        else cairo_line_to(cache_cr,
+                            x * rad + cx, y * rad + cy);
+                        break;
+                    case 4:
+                        // lines
+                        if(i == 0) cairo_move_to(cache_cr,
+                            x * rad + cx, y * rad + cy);
+                        else cairo_line_to(cache_cr,
+                            x * rad + cx, y * rad + cy);
+                        break;
+                }
+            }
+            // draw
+            switch(mode) {
+                case 0:
+                case 1:
+                case 2:
+                    cairo_fill (cache_cr);
+                    break;
+                case 3:
+                    cairo_fill (cache_cr);
+                    break;
+                case 4:
+                    cairo_set_line_width(cache_cr, 1);
+                    cairo_stroke (cache_cr);
+                    break;
+            }
+            
+            cairo_destroy( cache_cr );
+        }
+        
+        calf_phase_graph_copy_cache_to_window( pg->fade_surface, c );
+        
+        
+//        cairo_set_line_width(c, 1);
+//        for(int phase = 1; phase <= 2; phase++)
+//        {
+//            for(int gn=grid_n_save; legend = std::string(), cairo_set_source_rgba(c, 0, 0, 0, 0.6), lg->source->get_gridline(lg->source_id, gn, pos, vertical, legend, &cimpl); gn++)
+//            {
+//                calf_line_graph_draw_grid( c, legend, vertical, pos, phase, sx, sy );
+//            }
+//        }
+
+//        gdk_cairo_set_source_color(c, &sc2);
+//        cairo_set_line_join(c, CAIRO_LINE_JOIN_MITER);
+//        cairo_set_line_width(c, 1);
+//        for(int gn = graph_n; lg->source->get_graph(lg->source_id, gn, data, 2 * sx, &cimpl); gn++)
+//        {
+//            calf_line_graph_draw_graph( c, data, sx, sy );
+//        }
+        delete []data;
+    }
+
+    cairo_destroy(c);
+
+    // printf("exposed %p %dx%d %d+%d\n", widget->window, event->area.x, event->area.y, event->area.width, event->area.height);
+
+    return TRUE;
+}
+
+static void
+calf_phase_graph_size_request (GtkWidget *widget,
+                           GtkRequisition *requisition)
+{
+    g_assert(CALF_IS_PHASE_GRAPH(widget));
+    
+    // CalfLineGraph *lg = CALF_LINE_GRAPH(widget);
+}
+
+static void
+calf_phase_graph_size_allocate (GtkWidget *widget,
+                           GtkAllocation *allocation)
+{
+    g_assert(CALF_IS_PHASE_GRAPH(widget));
+    CalfPhaseGraph *lg = CALF_PHASE_GRAPH(widget);
+
+    GtkWidgetClass *parent_class = (GtkWidgetClass *) g_type_class_peek_parent( CALF_PHASE_GRAPH_GET_CLASS( lg ) );
+
+    if( lg->cache_surface )
+        cairo_surface_destroy( lg->cache_surface );
+    lg->cache_surface = NULL;
+    if( lg->fade_surface )
+        cairo_surface_destroy( lg->fade_surface );
+    lg->fade_surface = NULL;
+    
+    widget->allocation = *allocation;
+    GtkAllocation &a = widget->allocation;
+    if (a.width > a.height)
+    {
+        a.x += (a.width - a.height) / 2;
+        a.width = a.height;
+    }
+    if (a.width < a.height)
+    {
+        a.y += (a.height - a.width) / 2;
+        a.height = a.width;
+    }
+    parent_class->size_allocate( widget, &a );
+}
+
+static void
+calf_phase_graph_class_init (CalfPhaseGraphClass *klass)
+{
+    // GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+    widget_class->expose_event = calf_phase_graph_expose;
+    widget_class->size_request = calf_phase_graph_size_request;
+    widget_class->size_allocate = calf_phase_graph_size_allocate;
+}
+
+static void
+calf_phase_graph_init (CalfPhaseGraph *self)
+{
+    GtkWidget *widget = GTK_WIDGET(self);
+    widget->requisition.width = 40;
+    widget->requisition.height = 40;
+    self->cache_surface = NULL;
+    self->fade_surface = NULL;
+}
+
+GtkWidget *
+calf_phase_graph_new()
+{
+    return GTK_WIDGET( g_object_new (CALF_TYPE_PHASE_GRAPH, NULL ));
+}
+
+GType
+calf_phase_graph_get_type (void)
+{
+    static GType type = 0;
+    if (!type) {
+        static const GTypeInfo type_info = {
+            sizeof(CalfPhaseGraphClass),
+            NULL, /* base_init */
+            NULL, /* base_finalize */
+            (GClassInitFunc)calf_phase_graph_class_init,
+            NULL, /* class_finalize */
+            NULL, /* class_data */
+            sizeof(CalfPhaseGraph),
+            0,    /* n_preallocs */
+            (GInstanceInitFunc)calf_phase_graph_init
+        };
+
+        GTypeInfo *type_info_copy = new GTypeInfo(type_info);
+
+        for (int i = 0; ; i++) {
+            char *name = g_strdup_printf("CalfPhaseGraph%u%d", ((unsigned int)(intptr_t)calf_phase_graph_class_init) >> 16, i);
+            if (g_type_from_name(name)) {
+                free(name);
+                continue;
+            }
+            type = g_type_register_static( GTK_TYPE_DRAWING_AREA,
+                                           name,
+                                           type_info_copy,
+                                           (GTypeFlags)0);
+            free(name);
+            break;
+        }
+    }
+    return type;
+}
+
+
 ///////////////////////////////////////// knob ///////////////////////////////////////////////
 
 static gboolean
diff --git a/src/gui.cpp b/src/gui.cpp
index dc7b7ba..dac7f76 100644
--- a/src/gui.cpp
+++ b/src/gui.cpp
@@ -72,6 +72,8 @@ param_control *plugin_gui::create_control_from_xml(const char *element, const ch
         return new vumeter_param_control;
     if (!strcmp(element, "line-graph"))
         return new line_graph_param_control;
+    if (!strcmp(element, "phase-graph"))
+        return new phase_graph_param_control;
     if (!strcmp(element, "keyboard"))
         return new keyboard_param_control;
     if (!strcmp(element, "curve"))
diff --git a/src/gui_controls.cpp b/src/gui_controls.cpp
index 4405c06..b35a6f9 100644
--- a/src/gui_controls.cpp
+++ b/src/gui_controls.cpp
@@ -917,6 +917,8 @@ GtkWidget *line_graph_param_control::create(plugin_gui *_gui, int _param_no)
     calf_line_graph_set_square(clg, get_int("square", 0));
     clg->source = gui->plugin->get_line_graph_iface();
     clg->source_id = param_no;
+    CALF_LINE_GRAPH(widget)->use_fade = get_int("use_fade", 0);
+    CALF_LINE_GRAPH(widget)->fade = get_float("fade", 0.5);
     gtk_widget_set_name(GTK_WIDGET(widget), "Calf-LineGraph");
     return widget;
 }
@@ -937,6 +939,48 @@ line_graph_param_control::~line_graph_param_control()
 {
 }
 
+// phase graph
+
+void phase_graph_param_control::on_idle()
+{
+    if (get_int("refresh", 0))
+        set();
+}
+
+GtkWidget *phase_graph_param_control::create(plugin_gui *_gui, int _param_no)
+{
+    gui = _gui;
+    param_no = _param_no;
+    last_generation = -1;
+    
+    widget = calf_phase_graph_new ();
+    gtk_widget_set_name(GTK_WIDGET(widget), "calf-phase");
+    CalfPhaseGraph *clg = CALF_PHASE_GRAPH(widget);
+    widget->requisition.width = get_int("size", 40);
+    widget->requisition.height = get_int("size", 40);
+    clg->source = gui->plugin->get_phase_graph_iface();
+    clg->source_id = param_no;
+    gtk_widget_set_name(GTK_WIDGET(widget), "Calf-PhaseGraph");
+    return widget;
+}
+
+void phase_graph_param_control::set()
+{
+    GtkWidget *tw = gtk_widget_get_toplevel(widget);
+    gtk_widget_queue_draw(tw);
+//    if (tw && GTK_WIDGET_TOPLEVEL(tw) && widget->window)
+//    {
+//        int ws = gdk_window_get_state(widget->window);
+//        if (ws & (GDK_WINDOW_STATE_WITHDRAWN | GDK_WINDOW_STATE_ICONIFIED))
+//            return;
+//        //last_generation = calf_phase_graph_update_if(CALF_PHASE_GRAPH(widget), last_generation);
+//    }
+}
+
+phase_graph_param_control::~phase_graph_param_control()
+{
+}
+
 // list view
 
 GtkWidget *listview_param_control::create(plugin_gui *_gui, int _param_no)
diff --git a/src/lv2gui.cpp b/src/lv2gui.cpp
index f10c23c..6f20aa7 100644
--- a/src/lv2gui.cpp
+++ b/src/lv2gui.cpp
@@ -88,6 +88,9 @@ struct plugin_proxy_base
     /// Obtain line graph interface if available
     const line_graph_iface *get_line_graph_iface() const;
     
+    /// Obtain phase graph interface if available
+    const phase_graph_iface *get_phase_graph_iface() const;
+    
     /// Map an URI to an integer value using a given URI map
     uint32_t map_uri(const char *mapURI, const char *keyURI);
 
@@ -170,6 +173,13 @@ const line_graph_iface *plugin_proxy_base::get_line_graph_iface() const
     return NULL;
 }
 
+const phase_graph_iface *plugin_proxy_base::get_phase_graph_iface() const
+{
+    if (instance)
+        return instance->get_phase_graph_iface();
+    return NULL;
+}
+
 char *plugin_proxy_base::configure(const char *key, const char *value)
 {
     if (instance)
@@ -249,6 +259,9 @@ struct lv2_plugin_proxy: public plugin_ctl_iface, public plugin_proxy_base, publ
     virtual const plugin_metadata_iface *get_metadata_iface() const { return plugin_metadata; }
     /// Override for a method in plugin_ctl_iface - trivial delegation to base class
     virtual const line_graph_iface *get_line_graph_iface() const { return plugin_proxy_base::get_line_graph_iface(); }
+    
+    /// Override for a method in plugin_ctl_iface - trivial delegation to base class
+    virtual const phase_graph_iface *get_phase_graph_iface() const { return plugin_proxy_base::get_phase_graph_iface(); }
 };
 
 static gboolean plugin_on_idle(void *data)
diff --git a/src/metadata.cpp b/src/metadata.cpp
index 45891e9..f5f2329 100644
--- a/src/metadata.cpp
+++ b/src/metadata.cpp
@@ -964,7 +964,7 @@ CALF_PORT_PROPS(stereo) = {
     { 0.f,      -1.f,            1.f,   0,  PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_GRAPH, NULL, "mlev", "M Level" },
     { 0.f,      -1.f,            1.f,   0,  PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_GRAPH, NULL, "mpan", "M Panorama" },
 
-    { 0,           0,           1,    0,  PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_NOBOUNDS, NULL, "widener", "Widener" },
+    { 0.f,           -1.f,           1.f,    0,  PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_NOBOUNDS, NULL, "stereo_base", "Stereo Base" },
     { 0.f,         -20.f,        20.f,  0,  PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_COEF, NULL, "delay", "Delay" },
 
     { 0.f,      0.f,           1.f,   0,  PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_METER | PF_CTLO_LABEL | PF_UNIT_COEF | PF_PROP_OUTPUT | PF_PROP_OPTIONAL, NULL, "meter_phase", "Phase Correlation" },
@@ -974,6 +974,39 @@ CALF_PORT_PROPS(stereo) = {
 
 CALF_PLUGIN_INFO(stereo) = { 0x8588, "StereoTools", "Calf Stereo Tools", "Markus Schmidt", calf_plugins::calf_copyright_info, "Utility" };
 
+////////////////////////////////////////////////////////////////////////////
+
+CALF_PORT_NAMES(analyzer) = {"In L", "In R", "Out L", "Out R"};
+const char *gonio_mode_names[] = { "Small Dots", "Medium Dots", "Big Dots", "Fields", "Lines" };
+const char *analyzer_mode_names[] = { "Average", "Maximum", "Left Channel", "Right Channel" };
+const char *analyzer_smooth_names[] = { "Falling", "Smoothing", "Off" };
+CALF_PORT_PROPS(analyzer) = {
+    { 0,           0,           1,     0,  PF_FLOAT | PF_SCALE_GAIN | PF_CTL_METER | PF_CTLO_LABEL | PF_UNIT_DB | PF_PROP_OUTPUT | PF_PROP_OPTIONAL, NULL, "meter_L", "Level L" },
+    { 0,           0,           1,     0,  PF_FLOAT | PF_SCALE_GAIN | PF_CTL_METER | PF_CTLO_LABEL | PF_UNIT_DB | PF_PROP_OUTPUT | PF_PROP_OPTIONAL, NULL, "meter_R", "Level R" },
+    { 0,           0,           1,     0,  PF_FLOAT | PF_CTL_LED | PF_PROP_OUTPUT | PF_PROP_OPTIONAL, NULL, "clip_L", "Clip L" },
+    { 0,           0,           1,     0,  PF_FLOAT | PF_CTL_LED | PF_PROP_OUTPUT | PF_PROP_OPTIONAL, NULL, "clip_R", "Clip R" },
+    
+    { 1,           0.125,           8,    0,  PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_NOBOUNDS, NULL, "analyzer_level", "Analyzer Level" },
+    { 0,           0,           3,     0,  PF_ENUM | PF_CTL_COMBO, analyzer_mode_names, "analyzer_mode", "Analyzer Mode" },
+    { 6,      2,           8,   0,  PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_GRAPH, NULL, "analyzer_accuracy", "Analyzer Accuracy" },
+    { 13,      1,           15,   0,  PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_GRAPH, NULL, "analyzer_speed", "Analyzer Speed" },
+    { 1,           0,           1,     0,  PF_BOOL | PF_CTL_TOGGLE, NULL, "analyzer_display", "Analyzer Display" },
+    { 0,           0,           2,     0,  PF_ENUM | PF_CTL_COMBO, analyzer_smooth_names, "analyzer_smoothing", "Analyzer Smoothing" },
+    { 0,           0,           1,     0,  PF_BOOL | PF_CTL_TOGGLE, NULL, "analyzer_hold", "Analyzer Hold" },
+    { 0,          0, 1,     2, PF_BOOL | PF_CTL_TOGGLE , NULL, "analyzer_freeze", "Analyzer Freeze" },
+    
+    { 1,           0.125,           8,    0,  PF_FLOAT | PF_SCALE_GAIN | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_NOBOUNDS, NULL, "gonio_level", "Gonio Level" },
+    { 1,           0,           4,     0,  PF_ENUM | PF_CTL_COMBO, gonio_mode_names, "gonio_mode", "Gonio Mode" },
+    { 1,           0,           1,     0,  PF_BOOL | PF_CTL_TOGGLE, NULL, "gonio_use_fade", "Gonio Fade Active" },
+    { 0.5f,      0.f,           1.f,   0,  PF_FLOAT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_GRAPH, NULL, "gonio_fade", "Gonio Fade" },
+    { 4,      1,           5,   0,  PF_INT | PF_SCALE_LINEAR | PF_CTL_KNOB | PF_UNIT_COEF | PF_PROP_GRAPH, NULL, "gonio_accuracy", "Gonio Accuracy" },
+    { 1,           0,           1,     0,  PF_BOOL | PF_CTL_TOGGLE, NULL, "gonio_display", "Gonio Display" },
+    
+    {}
+};
+
+CALF_PLUGIN_INFO(analyzer) = { 0x8588, "Analyzer", "Calf Analyzer", "Markus Schmidt / Christian Holschuh", calf_plugins::calf_copyright_info, "Analyzer" };
+
 
 ////////////////////////////////////////////////////////////////////////////
 
diff --git a/src/modules.cpp b/src/modules.cpp
index ba06f02..206dcc2 100644
--- a/src/modules.cpp
+++ b/src/modules.cpp
@@ -20,10 +20,13 @@
  */
 #include <limits.h>
 #include <memory.h>
+#include <math.h>
+#include <rfftw.h>
 #include <calf/giface.h>
 #include <calf/modules.h>
 #include <calf/modules_dev.h>
 
+
 using namespace dsp;
 using namespace calf_plugins;
 
@@ -607,9 +610,15 @@ uint32_t stereo_audio_module::process(uint32_t offset, uint32_t numsamples, uint
             L += LL*L + RL*R;
             R += RR*R + LR*L;
             
-            // widener
-            L += *params[param_widener] * R * -1;
-            R += *params[param_widener] * L * -1;
+            // stereo base
+            float _sb = *params[param_stereo_base];
+            if(_sb < 0) _sb *= 0.5;
+            
+            float __l = L +_sb * L - _sb * R;
+            float __r = R + _sb * R - _sb * L;
+            
+            L = __l;
+            R = __r;
             
             // delay
             buffer[pos]     = L;
@@ -795,3 +804,245 @@ void mono_audio_module::set_sample_rate(uint32_t sr)
     memset(buffer, 0, buffer_size * sizeof(float)); // reset buffer to zero
     pos = 0;
 }
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+analyzer_audio_module::analyzer_audio_module() {
+    active = false;
+    clip_L   = 0.f;
+    clip_R   = 0.f;
+    meter_L = 0.f;
+    meter_R = 0.f;
+    _accuracy = -1;
+    _acc_old = -1;
+    ppos = 0;
+    plength = 0;
+    fpos = 0;
+    _hold = 0.f;
+    phase_buffer = (float*) calloc(max_phase_buffer_size, sizeof(float));
+    memset(phase_buffer, 0, max_phase_buffer_size * sizeof(float)); // reset buffer to zero
+    fft_buffer = (float*) calloc(max_fft_buffer_size, sizeof(float));
+    memset(fft_buffer, 0, max_fft_buffer_size * sizeof(float)); // reset buffer to zero
+    fft_in = (fftw_real*) calloc(max_fft_cache_size, sizeof(fftw_real));
+    fft_out = (fftw_real*) calloc(max_fft_cache_size, sizeof(fftw_real));
+    fft_smooth = (fftw_real*) calloc(max_fft_cache_size, sizeof(fftw_real));
+    memset(fft_smooth, 0, max_fft_cache_size * sizeof(fftw_real)); // reset buffer to zero
+    fft_delta = (float*) calloc(max_fft_cache_size, sizeof(float));
+    memset(fft_delta, 0, max_fft_cache_size * sizeof(float)); // reset buffer to zero
+    fft_hold = (float*) calloc(max_fft_cache_size, sizeof(float));
+    memset(fft_hold, 0, max_fft_cache_size * sizeof(float)); // reset buffer to zero
+    fft_freeze = (float*) calloc(max_fft_cache_size, sizeof(float));
+    memset(fft_freeze, 0, max_fft_cache_size * sizeof(float)); // reset buffer to zero
+    
+    ____analyzer_phase_was_drawn_here = 0;
+    ____analyzer_smooth_dirty = 0;
+    ____analyzer_hold_dirty = 0;
+
+}
+
+void analyzer_audio_module::activate() {
+    active = true;
+}
+
+void analyzer_audio_module::deactivate() {
+    active = false;
+}
+
+void analyzer_audio_module::params_changed() {
+    if(*params[param_analyzer_accuracy] != _acc_old) {
+        _accuracy = pow(2, 7 + *params[param_analyzer_accuracy]);
+        _acc_old = *params[param_analyzer_accuracy];
+        // recreate fftw plan
+        fft_plan = rfftw_create_plan(_accuracy, FFTW_FORWARD, 0);
+    }
+    if(*params[param_analyzer_hold] != _hold) {
+        ____analyzer_hold_dirty = 1;
+        _hold = *params[param_analyzer_hold];
+    }
+}
+
+uint32_t analyzer_audio_module::process(uint32_t offset, uint32_t numsamples, uint32_t inputs_mask, uint32_t outputs_mask) {
+    for(uint32_t i = offset; i < offset + numsamples; i++) {
+        // let meters fall a bit
+        clip_L   -= std::min(clip_L, numsamples);
+        clip_R   -= std::min(clip_R, numsamples);
+        meter_L   = 0.f;
+        meter_R   = 0.f;
+        
+        float L = ins[0][i];
+        float R = ins[1][i];
+        
+        // GUI stuff
+        if(L > 1.f) clip_L = srate >> 3;
+        if(R > 1.f) clip_R = srate >> 3;
+        
+        // goniometer
+        phase_buffer[ppos] = L * *params[param_gonio_level];
+        phase_buffer[ppos + 1] = R * *params[param_gonio_level];
+        
+        plength = std::min(phase_buffer_size, plength + 2);
+        ppos += 2;
+        ppos %= (phase_buffer_size - 2);
+        
+        // analyzer
+        fft_buffer[fpos] = L * *params[param_analyzer_level];
+        fft_buffer[fpos + 1] = R * *params[param_analyzer_level];
+        
+        fpos += 2;
+        fpos %= (max_fft_buffer_size - 2);
+        
+        // meter
+        meter_L = L;
+        meter_R = R;
+        
+        //output
+        outs[0][i] = L;
+        outs[1][i] = R;
+    }
+    // draw meters
+    SET_IF_CONNECTED(clip_L);
+    SET_IF_CONNECTED(clip_R);
+    SET_IF_CONNECTED(meter_L);
+    SET_IF_CONNECTED(meter_R);
+    return outputs_mask;
+}
+
+void analyzer_audio_module::set_sample_rate(uint32_t sr)
+{
+    srate = sr;
+    phase_buffer_size = srate / 30 * 2;
+    phase_buffer_size -= phase_buffer_size % 2;
+    phase_buffer_size = std::min(phase_buffer_size, (int)max_phase_buffer_size);
+}
+
+bool analyzer_audio_module::get_phase_graph(float ** _buffer, int *_length, int * _mode, bool * _use_fade, float * _fade, int * _accuracy, bool * _display) const {
+    *_buffer = &phase_buffer[0];
+    *_length = plength;
+    *_use_fade = *params[param_gonio_use_fade];
+    *_fade = *params[param_gonio_fade];
+    *_mode = *params[param_gonio_mode];
+    *_accuracy = *params[param_gonio_accuracy];
+    *_display = *params[param_gonio_display];
+    return false;
+}
+
+bool analyzer_audio_module::get_graph(int index, int subindex, float *data, int points, cairo_iface *context) const
+{
+    if (!active or subindex > 1 or !*params[param_analyzer_display]
+        or (subindex > 0 and !*params[param_analyzer_hold]))
+        return false;
+    float ret;
+    double freq;
+    int _last = 0;
+    if(subindex == 0) {
+        ____analyzer_phase_was_drawn_here ++;
+        int _param_speed = 16 - (int)*params[param_analyzer_speed];
+        if(!((int)____analyzer_phase_was_drawn_here % _param_speed)) {
+            for(int i = 0; i < _accuracy; i++) {
+                int _fpos = (fpos - _accuracy * 2 + (i * 2)) % max_fft_buffer_size;
+                if(_fpos < 0)
+                    _fpos = max_fft_buffer_size + _fpos;
+                float L = fft_buffer[_fpos];
+                float R = fft_buffer[_fpos + 1];
+                
+                // get the right value for calculations
+                fftw_real val;
+                switch((int)*params[param_analyzer_mode]) {
+                    case 0:
+                        // average
+                        val = (L + R) / 2;
+                        break;
+                    case 1:
+                        // maximum
+                        val = std::max(L, R);
+                        break;
+                    case 2:
+                        // left channel
+                        val = L;
+                        break;
+                    case 3:
+                        // right channel
+                        val = R;
+                        break;
+                }
+                fft_in[i] = (fftw_real)val;
+                
+                // fill smoothing buffer
+                if(*params[param_analyzer_smoothing] == 1.f)
+                    fft_smooth[i] = fabs(fft_out[i]);
+                if(*params[param_analyzer_smoothing] == 0.f and fft_smooth[i] < fabs(fft_out[i])) {
+                    fft_smooth[i] = fabs(fft_out[i]);
+                }
+                
+                // fill delta buffer
+                if(*params[param_analyzer_smoothing] == 0.f) {
+                    fft_delta[i] = fft_smooth[i] / sqrt(_param_speed) / -8.f;
+                }
+                
+                // fill hold buffer
+                if(____analyzer_hold_dirty) {
+                    fft_hold[i] = 0.f;
+                } else if(fabs(fft_out[i]) > fft_hold[i]) {
+                    fft_hold[i] = fabs(fft_out[i]);
+                }
+            }
+            rfftw_one(fft_plan, fft_in, fft_out);
+            ____analyzer_hold_dirty = 0;
+            ____analyzer_smooth_dirty = 1;
+        }
+        for (int i = 0; i < points; i++)
+        {
+            ret = 1.f;
+            freq = 20.0 * pow (20000.0 / 20.0, i * 1.0 / points);
+            int __last = floor(freq * (float)_accuracy / (float)srate);
+            if(!i or __last > _last) {
+                int iter;
+                if(!i) {
+                    iter = 0;
+                } else {
+                    _last = __last;
+                    iter = _last;
+                }
+                if(____analyzer_smooth_dirty and *params[param_analyzer_smoothing] == 1.f) {
+                    fft_delta[iter] = (fabs(fft_out[iter]) - fft_smooth[iter]) / _param_speed;
+                }
+                if(*params[param_analyzer_smoothing] == 1.f) {
+                    fft_smooth[iter] += fft_delta[iter];
+                }
+                if(*params[param_analyzer_smoothing] == 0.f) {
+                    fft_smooth[iter] += fft_delta[iter];
+                    fft_delta[iter] /= 1.01f;
+                }
+                if (*params[param_analyzer_freeze] > 0.f)
+                    ret =  dB_grid(fabs(fft_freeze[iter]) / 800.f);
+                else if(*params[param_analyzer_smoothing] < 2.f) {
+                    ret =  dB_grid(fabs(fft_smooth[iter]) / 800.f);
+                    fft_freeze[iter] = fft_smooth[iter];
+                } else {
+                    ret =  dB_grid(fabs(fft_out[iter]) / 800.f);
+                    fft_freeze[iter] = fft_out[iter];
+                }
+            } else
+                ret = INFINITY;
+            data[i] = ret;
+            
+        }
+        ____analyzer_smooth_dirty = 0;
+        return true;
+    } else {
+        context->set_source_rgba(0.35, 0.4, 0.2, 0.2);
+        for (int i = 0; i < points; i++)
+        {
+            freq = 20.0 * pow (20000.0 / 20.0, i * 1.0 / points);
+            int __last = floor(freq * (float)_accuracy / (float)srate);
+            if(!i) {
+                ret =  dB_grid(fft_hold[0] / 800.f);
+            } else if(__last > _last) {
+                _last = __last;
+                ret =  dB_grid(fft_hold[_last] / 800.f);
+            } else
+                ret = INFINITY;
+            data[i] = ret;
+        }
+    }
+    return true;
+}

-- 
calf audio plugins packaging



More information about the pkg-multimedia-commits mailing list