summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--deadbeef.h9
-rw-r--r--plugins.c1
-rw-r--r--plugins/gtkui/gtkui.c1
-rw-r--r--plugins/gtkui/widgets.c83
-rw-r--r--plugins/gtkui/widgets.h2
-rw-r--r--streamer.c34
-rw-r--r--streamer.h3
7 files changed, 132 insertions, 1 deletions
diff --git a/deadbeef.h b/deadbeef.h
index 33df7740..a1298e05 100644
--- a/deadbeef.h
+++ b/deadbeef.h
@@ -307,6 +307,10 @@ enum ddb_sort_order_t {
DDB_SORT_RANDOM, // available since API 1.3
};
+// audio memory constants
+#define DDB_AUDIO_MEMORY_FRAMES 1000
+#define DDB_AUDIO_MEMORY_BUFFER_SIZE (DDB_AUDIO_MEMORY_FRAMES*4*20)
+
// typecasting macros
#define DB_PLUGIN(x) ((DB_plugin_t *)(x))
#define DB_CALLBACK(x) ((DB_callback_t)(x))
@@ -758,6 +762,11 @@ typedef struct {
// ******* new 1.3 APIs ********
int (*streamer_dsp_chain_save) (void);
+ // access real-time audio data (e.g. for visualization)
+ // returns data size in bytes
+ // fmt and data will be filled with last bytes that came to the output plugin
+ // data size must be at least DDB_AUDIO_MEMORY_BUFFER_SIZE
+ int (*audio_get_waveform_data) (ddb_waveformat_t *fmt, char *data);
} DB_functions_t;
// NOTE: an item placement must be selected like this
diff --git a/plugins.c b/plugins.c
index d19139ff..e15f640c 100644
--- a/plugins.c
+++ b/plugins.c
@@ -331,6 +331,7 @@ static DB_functions_t deadbeef_api = {
.pl_find_meta_raw = (const char *(*) (DB_playItem_t *it, const char *key))pl_find_meta_raw,
// ******* new 1.3 APIs ********
.streamer_dsp_chain_save = streamer_dsp_chain_save,
+ .audio_get_waveform_data = audio_get_waveform_data,
};
DB_functions_t *deadbeef = &deadbeef_api;
diff --git a/plugins/gtkui/gtkui.c b/plugins/gtkui/gtkui.c
index caab3be4..fc7700cf 100644
--- a/plugins/gtkui/gtkui.c
+++ b/plugins/gtkui/gtkui.c
@@ -1007,6 +1007,7 @@ gtkui_thread (void *ctx) {
w_reg_widget ("playlist", _("Playlist"), w_playlist_create);
w_reg_widget ("selproperties", _("Selection properties"), w_selproperties_create);
w_reg_widget ("coverart", _("Album art display"), w_coverart_create);
+ w_reg_widget ("scope", _("Scope"), w_scope_create);
mainwin = create_mainwin ();
diff --git a/plugins/gtkui/widgets.c b/plugins/gtkui/widgets.c
index e787304c..c584c555 100644
--- a/plugins/gtkui/widgets.c
+++ b/plugins/gtkui/widgets.c
@@ -86,6 +86,12 @@ typedef struct {
GtkWidget *drawarea;
} w_coverart_t;
+typedef struct {
+ ddb_gtkui_widget_t base;
+ GtkWidget *drawarea;
+ guint drawtimer;
+} w_scope_t;
+
static int design_mode;
static ddb_gtkui_widget_t *rootwidget;
@@ -181,7 +187,6 @@ w_init_cb (void *data) {
void
w_replace (ddb_gtkui_widget_t *w, ddb_gtkui_widget_t *from, ddb_gtkui_widget_t *to) {
- printf ("replace to %s\n", to->type);
if (w->replace) {
w->replace (w, from, to);
if (to->init) {
@@ -1685,3 +1690,79 @@ w_coverart_create (void) {
w_override_signals (w->base.widget, w);
return (ddb_gtkui_widget_t *)w;
}
+
+///// scope vis
+void
+w_scope_destroy (ddb_gtkui_widget_t *w) {
+ w_scope_t *s = (w_scope_t *)w;
+ if (s->drawtimer) {
+ g_source_remove (s->drawtimer);
+ }
+}
+
+gboolean
+w_scope_draw_cb (void *data) {
+ w_scope_t *s = data;
+ gtk_widget_queue_draw (s->drawarea);
+ return TRUE;
+}
+
+gboolean
+scope_draw (GtkWidget *widget, cairo_t *cr, gpointer user_data) {
+ ddb_waveformat_t fmt;
+ char data[DDB_AUDIO_MEMORY_BUFFER_SIZE];
+ int size = deadbeef->audio_get_waveform_data (&fmt, data);
+ if (fmt.channels && size > 0) {
+ cairo_set_source_rgb (cr, 0, 0, 0);
+ cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
+ cairo_set_line_width (cr, 1);
+ GtkAllocation a;
+ gtk_widget_get_allocation (widget, &a);
+ short *samples = (short *)data;
+
+ int nframes = size / (fmt.bps/8*fmt.channels);
+ float incr = nframes / (float)a.width;
+ float pos = 0;
+ for (int x = 0; x < a.width; x++, pos += incr) {
+ short s = max (samples[(int)pos*2], samples[(int)pos*2+1]);
+ cairo_line_to (cr, x, s * a.height/2 / 0x7fff + a.height/2);
+ }
+ cairo_stroke (cr);
+ }
+
+ return FALSE;
+}
+
+gboolean
+scope_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data) {
+ cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (widget));
+ gboolean res = scope_draw (widget, cr, user_data);
+ cairo_destroy (cr);
+ return res;
+}
+
+void
+w_scope_init (ddb_gtkui_widget_t *w) {
+ w_scope_t *s = (w_scope_t *)w;
+ s->drawtimer = g_timeout_add (33, w_scope_draw_cb, w);
+}
+
+ddb_gtkui_widget_t *
+w_scope_create (void) {
+ w_scope_t *w = malloc (sizeof (w_scope_t));
+ memset (w, 0, sizeof (w_scope_t));
+
+ w->base.widget = gtk_event_box_new ();
+ w->base.init = w_scope_init;
+ w->base.destroy = w_scope_destroy;
+ w->drawarea = gtk_drawing_area_new ();
+ gtk_widget_show (w->drawarea);
+ gtk_container_add (GTK_CONTAINER (w->base.widget), w->drawarea);
+#if !GTK_CHECK_VERSION(3,0,0)
+ g_signal_connect_after ((gpointer) w->drawarea, "expose_event", G_CALLBACK (scope_expose_event), w);
+#else
+ g_signal_connect_after ((gpointer) w->drawarea, "draw", G_CALLBACK (scope_draw), w);
+#endif
+ w_override_signals (w->base.widget, w);
+ return (ddb_gtkui_widget_t *)w;
+}
diff --git a/plugins/gtkui/widgets.h b/plugins/gtkui/widgets.h
index 93bdfd48..27e8c013 100644
--- a/plugins/gtkui/widgets.h
+++ b/plugins/gtkui/widgets.h
@@ -96,4 +96,6 @@ w_selproperties_create (void);
ddb_gtkui_widget_t *
w_coverart_create (void);
+ddb_gtkui_widget_t *
+w_scope_create (void);
#endif
diff --git a/streamer.c b/streamer.c
index 69db8029..edce8ecb 100644
--- a/streamer.c
+++ b/streamer.c
@@ -90,6 +90,7 @@ static char streambuffer[STREAM_BUFFER_SIZE];
static int bytes_until_next_song = 0;
static uintptr_t mutex;
static uintptr_t decodemutex;
+static uintptr_t audio_mem_mutex;
static int nextsong = -1;
static int nextsong_pstate = -1;
static int badsong = -1;
@@ -119,6 +120,10 @@ static int streamer_buffering;
// to allow interruption of stall file requests
static DB_FILE *streamer_file;
+// for vis plugins
+static char audio_data[DDB_AUDIO_MEMORY_BUFFER_SIZE];
+static ddb_waveformat_t audio_fmt;
+
#if DETECT_PL_LOCK_RC
volatile pthread_t streamer_lock_tid = 0;
#endif
@@ -1693,6 +1698,7 @@ streamer_init (void) {
#endif
mutex = mutex_create ();
decodemutex = mutex_create ();
+ audio_mem_mutex = mutex_create ();
ringbuf_init (&streamer_ringbuf, streambuffer, STREAM_BUFFER_SIZE);
@@ -1734,6 +1740,8 @@ streamer_free (void) {
decodemutex = 0;
mutex_free (mutex);
mutex = 0;
+ mutex_free (audio_mem_mutex);
+ audio_mem_mutex = 0;
streamer_dsp_chain_save();
@@ -1953,6 +1961,7 @@ streamer_read (char *bytes, int size) {
if (formatchanged && bytes_until_next_song <= 0) {
streamer_set_output_format ();
formatchanged = 0;
+ memset (audio_data, 0, sizeof (audio_data));
}
streamer_lock ();
int sz = min (size, streamer_ringbuf.remaining);
@@ -2002,6 +2011,18 @@ streamer_read (char *bytes, int size) {
printf ("streamer_read took %d ms\n", ms);
#endif
+ mutex_lock (audio_mem_mutex);
+ int mem_size = DDB_AUDIO_MEMORY_FRAMES * (output->fmt.bps >> 3) * output->fmt.channels;
+ if (sz < mem_size) {
+ memmove (audio_data, audio_data + mem_size - sz, sz);
+ memcpy (audio_data + mem_size - sz, bytes, sz);
+ }
+ else {
+ memcpy (audio_data, bytes + sz - mem_size, mem_size);
+ }
+ memcpy (&audio_fmt, &output->fmt, sizeof (ddb_waveformat_t));
+ mutex_unlock (audio_mem_mutex);
+
if (!output->has_volume) {
char *stream = bytes;
int bytesread = sz;
@@ -2251,3 +2272,16 @@ streamer_notify_order_changed (int prev_order, int new_order) {
streamer_unlock ();
}
}
+
+int
+audio_get_waveform_data (ddb_waveformat_t *fmt, char *data) {
+ if (!audio_mem_mutex) {
+ return -1;
+ }
+ mutex_lock (audio_mem_mutex);
+ memcpy (fmt, &audio_fmt, sizeof (ddb_waveformat_t));
+ int mem_size = DDB_AUDIO_MEMORY_FRAMES * (audio_fmt.bps >> 3) * audio_fmt.channels;
+ memcpy (data, audio_data, mem_size);
+ mutex_unlock (audio_mem_mutex);
+ return mem_size;
+}
diff --git a/streamer.h b/streamer.h
index 6b77f382..9df20372 100644
--- a/streamer.h
+++ b/streamer.h
@@ -130,4 +130,7 @@ streamer_dsp_chain_save (void);
void
streamer_notify_order_changed (int prev_order, int new_order);
+int
+audio_get_waveform_data (ddb_waveformat_t *fmt, char *data);
+
#endif // __STREAMER_H