#include #include #include #include #include #include #include #include "galsa.h" #include "gfft.h" GtkBuilder *builder; #define gladewidget(x) GtkWidget *x = \ GTK_WIDGET(gtk_builder_get_object(builder, #x)); static cairo_surface_t *cs_scroll; static cairo_t *c_scroll; gboolean on_draw(GtkWidget *drawer, cairo_t *cr, gpointer data) { cairo_rectangle(cr, 0, 0, 300, 400); cairo_set_source_surface(cr, cs_scroll, 0, 0); cairo_fill(cr); return FALSE; } static gboolean bother(gpointer param) { gladewidget(drawer) gladewidget(status) char buf[256]; gtk_widget_queue_draw(drawer); snprintf(buf, sizeof(buf), "blah"); 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 * 3) / 4 struct bdctx { double bandenergy[EVALBANDS][HISTSIZE]; // double boffs[EVALBANDS]; double goffs; bool beats[EVALBANDS]; }; struct renderargs { struct fft_runner *fftr; struct bdctx bdctx; }; size_t bd_nbands, bd_bandlim; double bd_charge, bd_decay, bd_cthres; 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 * 6.0; out[o][1] /= d * 6.0; } } 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 * bd_cthres * (1.0 + ctx->goffs)) { beatbands++; ctx->beats[i] = true; } else ctx->beats[i] = false; 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; double c = 0.0; size_t logsz = 400; fftw_complex *pos, *logsc; gint w = 300, h = 400, y; GdkColor col; logsc = fftw_malloc(sizeof(fftw_complex) * logsz); 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("/tmp/.dmxbeat", "w"); if (f) fclose(f); } cairo_rectangle(c_scroll, 0, 0, 300, 400); cairo_set_source_surface(c_scroll, cs_scroll, -1, 0); cairo_fill(c_scroll); 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++; if (c[0] > 1.0) { col.red = 65535; col.green = 65535 * (2.0 - (c[0] > 2.0 ? 0.0 : c[0])); } else { if (beat) { col.red = 49152; col.green = 49152 + 16383 * c[0]; } else { col.red = 0; col.green = 65535 * c[0]; } } col.blue = arg->bdctx.beats[(pos - logsc) / (logsz / EVALBANDS)] ? (beat ? 65535 : 32768) : 0; cairo_rectangle(c_scroll, w - 2, y, 1, 1); cairo_set_source_rgb(c_scroll, col.red / 65535., col.green / 65535., col.blue / 65535.); cairo_fill(c_scroll); } cairo_rectangle(c_scroll, 5, 5, 5, 5); cairo_set_source_rgb(c_scroll, 0.0, 0.0, c); cairo_fill(c_scroll); c += 0.05; if (c > 1.0) c = 0.0; cairo_surface_flush(cs_scroll); 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(bdcthres); 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 *cthres = gtk_entry_get_text(GTK_ENTRY(bdcthres)); 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_cthres = strtod(cthres, NULL); 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_hann, 5); 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(); builder = gtk_builder_new(); gtk_builder_add_from_file(builder, "gbeat.xml", NULL); gtk_builder_connect_signals(builder, NULL); gladewidget(edev) gtk_entry_set_text(GTK_ENTRY(edev), "hw:0,0"); gladewidget(erate) gtk_entry_set_text(GTK_ENTRY(erate), "48000"); gladewidget(drawer); gtk_widget_set_size_request(drawer, 300, 400); cs_scroll = cairo_image_surface_create(CAIRO_FORMAT_RGB24, 300, 400); c_scroll = cairo_create(cs_scroll); cairo_rectangle(c_scroll, 0, 0, 300, 400); cairo_set_source_rgb(c_scroll, 0.0, 0.0, 0.0); cairo_fill(c_scroll); cairo_rectangle(c_scroll, 5, 5, 5, 5); cairo_set_source_rgb(c_scroll, 0.0, 0.0, 1.0); cairo_fill(c_scroll); on_bdapply(NULL, NULL); gtk_main (); return 0; }