summaryrefslogtreecommitdiff
path: root/gbeat.c
diff options
context:
space:
mode:
Diffstat (limited to 'gbeat.c')
-rw-r--r--gbeat.c370
1 files changed, 370 insertions, 0 deletions
diff --git a/gbeat.c b/gbeat.c
new file mode 100644
index 0000000..ff5fb8e
--- /dev/null
+++ b/gbeat.c
@@ -0,0 +1,370 @@
+#include <gtk/gtk.h>
+#include <glade/glade.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <math.h>
+#include <ctype.h>
+
+#include "galsa.h"
+#include "gfft.h"
+
+#define gladewidget(x) GtkWidget *x = glade_xml_get_widget(xml, #x);
+GladeXML *xml;
+
+static GdkPixmap *pxm;
+//static GdkImage *img_buffer;
+//static GdkPixbuf *pxb;
+double ofac_s = 110./64000.; // 1./(perpixel);
+
+static gboolean bother(gpointer param)
+{
+ gladewidget(img)
+ gladewidget(status)
+ char buf[256];
+ gtk_widget_queue_draw(img);
+ snprintf(buf, sizeof(buf), "scaling: %lf", ofac_s);
+ gtk_label_set_text(GTK_LABEL(status), buf);
+ return FALSE;
+}
+
+static double clen(fftw_complex c)
+{
+ return sqrt(c[0] * c[0] + c[1] * c[1]);
+}
+
+static double degs(fftw_complex c, double radbase)
+{
+ double len = clen(c);
+ double a = c[0] / len, b = c[1] / len;
+ double alpha = asin(b);
+ if (a < 0.)
+ alpha = M_PI - alpha;
+ alpha -= radbase;
+ while (alpha < 0)
+ alpha += 2 * M_PI;
+ while (alpha >= 2 * M_PI)
+ alpha -= 2 * M_PI;
+ return alpha * 180 / M_PI;
+}
+
+static double hsvcos(double i)
+{
+ i /= 180;
+ while (i < -1)
+ i += 2;
+ while (i > 1)
+ i -= 2;
+ if (i < -0.667 || i > 0.667)
+ return 0.0;
+ return cos(i * 0.75 * M_PI);
+}
+
+static GdkColor complex2color(fftw_complex c, double lscale)
+{
+ double l = clen(c), d = degs(c, 0.0);
+ GdkColor ret;
+
+ l /= lscale;
+ if (l > 1.0)
+ l = 1.0;
+ l *= l;
+
+ ret.red = 65535 * l * hsvcos(d);
+ ret.green = 65535 * l * hsvcos(d + 120);
+ ret.blue = 65535 * l * hsvcos(d + 240);
+ return ret;
+}
+
+static GdkColor colorinv(GdkColor c)
+{
+ c.red = 65535 - c.red;
+ c.green = 65535 - c.green;
+ c.blue = 65535 - c.blue;
+ return c;
+}
+
+static gboolean errbox(gpointer data)
+{
+ struct fe *fe = data;
+
+ gladewidget(mainwnd)
+ GtkWidget *dlg = gtk_message_dialog_new(GTK_WINDOW(mainwnd),
+ GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+ "capture error:\n%s", fe->errp);
+ gtk_dialog_run(GTK_DIALOG(dlg));
+ gtk_widget_destroy(dlg);
+
+ free(fe->errp);
+ free(fe);
+ return FALSE;
+}
+
+#define NBANDS 32
+#define EVALBANDS 32
+#define FPS 60
+#define HISTSIZE FPS * 7
+
+struct bdctx {
+ double bandenergy[EVALBANDS][HISTSIZE];
+// double boffs[EVALBANDS];
+ double goffs;
+};
+
+struct renderargs {
+ struct fft_runner *fftr;
+ struct bdctx bdctx;
+};
+
+size_t bd_nbands, bd_bandlim;
+double bd_charge, bd_decay;
+
+static void logscale(fftw_complex *in, size_t isz, fftw_complex *out, size_t osz)
+{
+ double tlog = 1. / log(isz + 1.0);
+ double *divs = calloc(sizeof(double), osz);
+ memset(out, 0, osz * sizeof(fftw_complex));
+
+ for (size_t i = 0; i < isz; i++) {
+ double bpos = log(i + 1.0) * tlog;
+ double tpos = log(i + 2.0) * tlog;
+ double len = tpos - bpos;
+
+ double xdiv = 0.25 * log(i + 2.) / log(isz + 2.);
+
+ for (size_t o = 0; o < osz; o++) {
+ double dpos = o / (double) osz;
+ if (dpos < bpos || dpos > tpos)
+ continue;
+ double factor = 0.5 * (1. - cos ((dpos - bpos) / len));
+ out[o][0] += in[i][0] * factor * xdiv;
+ out[o][1] += in[i][1] * factor * xdiv;
+ divs[o] += factor;
+ }
+ }
+ for (size_t o = 0; o < osz; o++) {
+ double d = divs[o];
+ out[o][0] /= d;
+ out[o][1] /= d;
+ }
+}
+
+static bool bd_perform(struct bdctx *ctx, fftw_complex *data, size_t size)
+{
+ size_t bandsize = size / NBANDS;
+ size_t beatbands = 0;
+
+ for (size_t i = 0; i < bd_bandlim; i++) {
+ double energy = 0.0, average = 0.0;
+ for (size_t j = 0; j < bandsize; j++) {
+ energy += sqrt(
+ pow(data[i * bandsize + j][0], 2) +
+ pow(data[i * bandsize + j][1], 2));
+ }
+ energy /= bandsize;
+
+ for (size_t j = 0; j < HISTSIZE; j++)
+ average += ctx->bandenergy[i][j];
+ average /= HISTSIZE;
+
+ if (energy > average * 1.5 * (1.0 + ctx->goffs))
+ beatbands++;
+
+ memmove(&ctx->bandenergy[i][0], &ctx->bandenergy[i][1],
+ (HISTSIZE - 1) * sizeof(double));
+ ctx->bandenergy[i][HISTSIZE - 1] = energy;
+ }
+
+ if (beatbands > bd_nbands)
+ ctx->goffs = bd_charge;
+ else
+ ctx->goffs *= bd_decay;
+
+ return beatbands > bd_nbands;
+}
+
+static gpointer renderf(gpointer param)
+{
+ struct renderargs *arg = param;
+ struct fe *fe;
+
+ size_t logsz = 400;
+ fftw_complex *pos, *logsc;
+
+ GdkDrawable *drawto = GDK_DRAWABLE(pxm);
+ GdkGC *gc = gdk_gc_new(drawto);
+
+ gint w, h, y;
+ GdkColor col;
+
+ logsc = fftw_malloc(sizeof(fftw_complex) * logsz);
+
+ gdk_drawable_get_size(drawto, &w, &h);
+#if 0
+ if (h > (gint)arg->fftr->dev->pktsize)
+ h = arg->fftr->dev->pktsize;
+#endif
+ if (h > (gint)logsz)
+ h = logsz;
+
+ while (1) {
+ fe = g_async_queue_pop(arg->fftr->queue);
+
+ if (fe->message == FE_STOPPED) {
+ free(fe);
+ break;
+ } else if (fe->message == FE_DEV_ERROR) {
+ g_idle_add(errbox, fe);
+ continue;
+ }
+
+ logscale(fe->data, arg->fftr->dev->pktsize, logsc, logsz);
+
+ // bool beat = bd_perform(&arg->bdctx, fe->data, fe->size);
+ bool beat = bd_perform(&arg->bdctx, logsc, logsz);
+
+ if (beat) {
+ FILE *f = fopen("/home/equinox/python/.dmxbeat", "w");
+ if (f)
+ fclose(f);
+ }
+
+ gdk_threads_enter();
+ gdk_draw_drawable(drawto, gc, drawto, 1, 0, 0, 0, w - 1, h);
+
+ pos = logsc; // fe->data;
+ for (y = h - 1; y >= 0; y--) {
+ fftw_complex c;
+ c[0] = c[1] = sqrt(
+ pow(pos[0][0], 2.0) +
+ pow(pos[0][1], 2.0));
+ pos++;
+ col = complex2color(c, 4.0);
+ if (beat)
+ col = colorinv(col);
+
+ gdk_gc_set_rgb_fg_color(gc, &col);
+ gdk_draw_point(drawto, gc, w - 1, y);
+ }
+ gdk_threads_leave();
+ g_idle_add(bother, NULL);
+
+ fftw_free(fe->data);
+ free(fe);
+ };
+
+ fftw_free(logsc);
+ return NULL;
+}
+
+
+struct device *dev;
+
+void on_bdapply(GtkWidget *widget, gpointer user_data)
+{
+ gladewidget(bdnbands);
+ gladewidget(bdbandlim);
+ gladewidget(bdcharge);
+ gladewidget(bddecay);
+
+ const gchar *nbands = gtk_entry_get_text(GTK_ENTRY(bdnbands));
+ const gchar *bandlim = gtk_entry_get_text(GTK_ENTRY(bdbandlim));
+ const gchar *charge = gtk_entry_get_text(GTK_ENTRY(bdcharge));
+ const gchar *decay = gtk_entry_get_text(GTK_ENTRY(bddecay));
+
+ bd_nbands = strtoul(nbands, NULL, 10);
+ bd_bandlim = strtoul(bandlim, NULL, 10);
+ if (bd_bandlim > EVALBANDS)
+ bd_bandlim = EVALBANDS;
+ bd_charge = strtod(charge, NULL);
+ bd_decay = strtod(decay, NULL);
+}
+
+void on_open_toggled(GtkWidget *widget, gpointer user_data)
+{
+ //gladewidget(mainwnd)
+ gladewidget(bopen)
+ gladewidget(brun)
+ gboolean state;
+
+ if (!!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(bopen)) == !!(dev != NULL))
+ return;
+
+ if (dev) {
+ device_close(dev);
+ dev = NULL;
+ } else do {
+ gladewidget(edev)
+ gladewidget(erate)
+ const gchar *udev = gtk_entry_get_text(GTK_ENTRY(edev));
+ const gchar *rate = gtk_entry_get_text(GTK_ENTRY(erate));
+ gchar *errptr;
+ unsigned long urate;
+
+ urate = strtoul(rate, &errptr, 0);
+/* if (*errptr || !*rate) {
+ strcpy(errmsg, "Invalid rate");
+ break;
+ } */
+
+ dev = device_open(udev, urate, 2, urate / FPS);
+ } while (0);
+
+ state = dev != NULL;
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(brun), 0);
+ gtk_widget_set_sensitive(brun, state);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bopen), state);
+
+/* if (*errmsg) {
+ GtkWidget *dlg = gtk_message_dialog_new(GTK_WINDOW(mainwnd),
+ GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+ "Error: %s", errmsg);
+ gtk_dialog_run(GTK_DIALOG(dlg));
+ gtk_widget_destroy(dlg);
+ } */
+}
+
+void on_run_toggled(GtkWidget *widget, gpointer user_data)
+{
+ gladewidget(brun)
+ GError *error = NULL;
+
+ if (!dev)
+ return;
+ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(brun))) {
+ struct fft_runner *fftr = fftrun_attach(dev, window_hard, 6);
+ struct renderargs *arg = calloc(sizeof(*arg), 1);
+ arg->fftr = fftr;
+ g_thread_create(renderf, arg, 0, &error);
+
+ device_start(dev);
+ } else {
+ device_stop(dev);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ g_thread_init(NULL);
+ gtk_init(&argc, &argv);
+ gdk_threads_init();
+ xml = glade_xml_new("gbeat.glade", NULL, NULL);
+ glade_xml_signal_autoconnect(xml);
+ {
+ gladewidget(edev)
+ gladewidget(erate)
+ gladewidget(img)
+ gtk_entry_set_text(GTK_ENTRY(edev), "hw:0,0");
+ gtk_entry_set_text(GTK_ENTRY(erate), "48000");
+ //pxb = gdk_pixbuf_new(GDK_COLORSPACE_RGB, 0, 8, 300, 720);
+ //gtk_image_set_from_pixbuf(GTK_IMAGE(img), pxb);
+ //img_buffer = gdk_image_new(GDK_IMAGE_FASTEST, gdk_visual_get_system(), 300, 740);
+ //gtk_image_set_from_image(GTK_IMAGE(img), img_buffer, NULL);
+ pxm = gdk_pixmap_new(NULL, 300, 720, 24);
+ gtk_image_set_from_pixmap(GTK_IMAGE(img), pxm, NULL);
+ }
+ on_bdapply(NULL, NULL);
+ gtk_main ();
+ return 0;
+}
+