summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Lamparter <equinox@diac24.net>2013-06-21 12:32:46 +0200
committerDavid Lamparter <equinox@diac24.net>2013-06-21 12:38:29 +0200
commite8d37e36f8b97fcc29c8f67d38f0cd5d48ec9d3f (patch)
treea3b493b069d16f4877cd43697bd194256d87c288
parent2fd3ece434eb3656f481afd4e8801637fa8ce844 (diff)
import
-rw-r--r--.gitignore2
-rw-r--r--Makefile18
-rw-r--r--galsa.c160
-rw-r--r--galsa.h49
-rw-r--r--gbeat.c370
-rw-r--r--gbeat.glade359
-rw-r--r--gfft.c100
-rw-r--r--gfft.h39
8 files changed, 1097 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9fae0f9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+gbeat
+*.o
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8a4edfd
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,18 @@
+love: gbeat
+.PHONY: love clean
+
+CC=gcc
+PKGS=glib-2.0 gtk+-2.0 libglade-2.0 gthread-2.0 fftw3 alsa
+CFLAGS=-std=gnu99 -Wall -Wextra -pedantic -Wno-unused-parameter -g -O0 \
+ `pkg-config --cflags $(PKGS)`
+LDFLAGS=-g -lm `pkg-config --libs $(PKGS)` -rdynamic
+
+clean:
+ rm -f *.o gbeat
+
+gbeat: galsa.o gfft.o gbeat.o
+ $(CC) $(LDFLAGS) -o $@ $^
+
+%.o: %.c *.h
+ $(CC) $(CFLAGS) -c -o $@ $<
+
diff --git a/galsa.c b/galsa.c
new file mode 100644
index 0000000..8e02c61
--- /dev/null
+++ b/galsa.c
@@ -0,0 +1,160 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <ctype.h>
+
+#include "galsa.h"
+
+struct device *device_open(const char *path, unsigned rate, int chan, size_t pktsize)
+{
+ struct device *dev;
+ snd_pcm_hw_params_t *hw_params = NULL;
+ snd_pcm_t *hdl = NULL;
+ char errmsg[256];
+
+#define E(x) do { int err; if ((err = x) < 0) { \
+ snprintf(errmsg, sizeof(errmsg), "%s:%d/%s(): %s() failed: %s (%d)\n",\
+ __FILE__, __LINE__, __func__, #x, snd_strerror(err), err); \
+ goto out_err; \
+ } } while (0)
+
+ printf("Opening alsa://%s with %d ch %dHz...\n", path, chan, rate);
+ E(snd_pcm_open(&hdl, path, SND_PCM_STREAM_CAPTURE, 0));
+ E(snd_pcm_hw_params_malloc(&hw_params));
+ E(snd_pcm_hw_params_any(hdl, hw_params));
+ E(snd_pcm_hw_params_set_access(hdl, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED));
+ E(snd_pcm_hw_params_set_format(hdl, hw_params, SND_PCM_FORMAT_S16_LE));
+ E(snd_pcm_hw_params_set_rate_near(hdl, hw_params, &rate, 0));
+ E(snd_pcm_hw_params_set_channels(hdl, hw_params, chan));
+ E(snd_pcm_hw_params(hdl, hw_params));
+ snd_pcm_hw_params_free(hw_params);
+ hw_params = NULL;
+
+ E(snd_pcm_prepare(hdl));
+ printf("... Ok.\n");
+
+ dev = (struct device *)malloc(sizeof(struct device));
+ dev->path = strdup(path);
+ dev->rate = rate;
+ dev->chan = chan;
+ dev->handle = hdl;
+ dev->thread = NULL;
+ dev->queue = g_async_queue_new();
+ dev->state = DEV_OPEN;
+ dev->pktsize = pktsize;
+ return dev;
+
+out_err:
+ if (hw_params)
+ snd_pcm_hw_params_free(hw_params);
+ if (hdl)
+ snd_pcm_close(hdl);
+ return NULL;
+}
+
+static gpointer threadf(gpointer param)
+{
+ struct device *dev = (struct device *)param;
+ struct qe *ent, *entx;
+ int pcmreturn;
+ ptrdiff_t pos;
+
+ do {
+ ent = malloc(sizeof(struct qe) +
+ dev->pktsize * sizeof(sample) * dev->chan);
+ ent->message = QE_DATA;
+ ent->size = dev->pktsize;
+ pos = 0;
+
+ do {
+ while ((pcmreturn = snd_pcm_readi(dev->handle,
+ ent->samples + pos, ent->size - pos)) < 0) {
+
+ entx = calloc(sizeof(struct qe), 1);
+ entx->message = QE_ERROR;
+ entx->errcode = pcmreturn;
+ asprintf(&entx->errp,
+ "PCM read failed: %s (%d)",
+ snd_strerror(pcmreturn), pcmreturn);
+ g_async_queue_push(dev->queue, entx);
+
+ if (dev->state != DEV_RUNNING) {
+ free(ent);
+ goto out;
+ }
+
+ snd_pcm_prepare(dev->handle);
+ }
+ pos += pcmreturn;
+ } while (pos < ent->size);
+ g_async_queue_push(dev->queue, ent);
+ } while (dev->state == DEV_RUNNING);
+
+out:
+ entx = calloc(sizeof(struct qe), 1);
+ entx->message = QE_STOPPED;
+ g_async_queue_push(dev->queue, entx);
+
+ return NULL;
+}
+
+void device_start(struct device *dev)
+{
+ GError *error = NULL;
+ if (dev->thread)
+ return;
+ dev->state = DEV_RUNNING;
+ dev->thread = g_thread_create(threadf, dev, 0, &error);
+}
+
+void device_stop(struct device *dev)
+{
+ if (dev->state == DEV_RUNNING)
+ dev->state = DEV_STOPREQ;
+}
+
+void device_stop_wait(struct device *dev)
+{
+ int got_stop = 0, iter = 0;
+ struct qe *qe;
+
+ if (dev->state == DEV_OPEN)
+ return;
+
+ device_stop(dev);
+
+ while (!got_stop && iter++ < 10) {
+ qe = (struct qe *)g_async_queue_timeout_pop(dev->queue, 250000);
+ switch (qe->message) {
+ case QE_DATA:
+ break;
+ case QE_ERROR:
+ free(qe->errp);
+ break;
+ case QE_STOPPED:
+ got_stop = 1;
+ break;
+ }
+ free(qe);
+ }
+ if (!got_stop)
+ fprintf(stderr, "failed to terminate recording thread!\n");
+ dev->state = DEV_OPEN;
+}
+
+void device_stop_done(struct device *dev)
+{
+ dev->state = DEV_OPEN;
+}
+
+void device_close(struct device *dev)
+{
+ device_stop_wait(dev);
+
+ snd_pcm_close(dev->handle);
+ free(dev->path);
+ free(dev);
+}
+
+
diff --git a/galsa.h b/galsa.h
new file mode 100644
index 0000000..5f5da40
--- /dev/null
+++ b/galsa.h
@@ -0,0 +1,49 @@
+#ifndef _GALSA_H
+#define _GALSA_H
+
+#include <alsa/asoundlib.h>
+#include <glib.h>
+
+typedef short sample;
+
+enum dev_runstate {
+ DEV_OPEN,
+ DEV_RUNNING,
+ DEV_STOPREQ,
+};
+enum qe_message {
+ QE_DATA,
+ QE_ERROR,
+ QE_STOPPED,
+};
+
+struct device {
+ char *path;
+ unsigned rate;
+ int chan;
+ snd_pcm_t *handle;
+ GAsyncQueue *queue;
+ size_t pktsize;
+
+ GThread *thread;
+ enum dev_runstate state;
+};
+
+struct qe {
+ enum qe_message message;
+ int errcode;
+ char *errp;
+
+ ssize_t size;
+ sample samples[];
+};
+
+extern struct device *device_open(const char *path,
+ unsigned rate, int chan, size_t pktsize);
+extern void device_start(struct device *dev);
+extern void device_stop(struct device *dev);
+extern void device_stop_wait(struct device *dev);
+extern void device_stop_done(struct device *dev);
+extern void device_close(struct device *dev);
+
+#endif
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;
+}
+
diff --git a/gbeat.glade b/gbeat.glade
new file mode 100644
index 0000000..ac0cc39
--- /dev/null
+++ b/gbeat.glade
@@ -0,0 +1,359 @@
+<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
+<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
+
+<glade-interface>
+
+<widget class="GtkWindow" id="mainwnd">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes">GtkAlsaViewer</property>
+ <property name="type">GTK_WINDOW_TOPLEVEL</property>
+ <property name="window_position">GTK_WIN_POS_NONE</property>
+ <property name="modal">False</property>
+ <property name="resizable">True</property>
+ <property name="destroy_with_parent">False</property>
+ <property name="decorated">True</property>
+ <property name="skip_taskbar_hint">False</property>
+ <property name="skip_pager_hint">False</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
+ <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
+ <property name="focus_on_map">True</property>
+ <property name="urgency_hint">False</property>
+ <signal name="delete_event" handler="gtk_false" last_modification_time="Mon, 04 Dec 2006 16:48:01 GMT"/>
+ <signal name="destroy" handler="gtk_main_quit" last_modification_time="Mon, 04 Dec 2006 16:48:32 GMT"/>
+
+ <child>
+ <widget class="GtkVBox" id="vbox1">
+ <property name="border_width">5</property>
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">3</property>
+
+ <child>
+ <widget class="GtkHBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">3</property>
+
+ <child>
+ <widget class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Device:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="edev">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Rate:</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="erate">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">5</property>
+ <property name="text" translatable="yes"></property>
+ <property name="has_frame">True</property>
+ <property name="invisible_char">*</property>
+ <property name="activates_default">False</property>
+ <property name="width_chars">5</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkToggleButton" id="bopen">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Open</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <signal name="toggled" handler="on_open_toggled" last_modification_time="Mon, 04 Dec 2006 16:49:00 GMT"/>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkToggleButton" id="brun">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Capture</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <property name="active">False</property>
+ <property name="inconsistent">False</property>
+ <signal name="toggled" handler="on_run_toggled" last_modification_time="Mon, 04 Dec 2006 16:51:14 GMT"/>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="homogeneous">False</property>
+ <property name="spacing">3</property>
+
+ <child>
+ <widget class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">nb, c, d</property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="bdnbands">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes">3</property>
+ <property name="has_frame">True</property>
+ <property name="activates_default">False</property>
+ <property name="width_chars">7</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="bdbandlim">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes">12</property>
+ <property name="has_frame">True</property>
+ <property name="activates_default">False</property>
+ <property name="width_chars">7</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="bdcharge">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes">1.4</property>
+ <property name="has_frame">True</property>
+ <property name="activates_default">False</property>
+ <property name="width_chars">7</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkEntry" id="bddecay">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="editable">True</property>
+ <property name="visibility">True</property>
+ <property name="max_length">0</property>
+ <property name="text" translatable="yes">0.6</property>
+ <property name="has_frame">True</property>
+ <property name="activates_default">False</property>
+ <property name="width_chars">7</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkButton" id="bdapply">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="label" translatable="yes">Apply</property>
+ <property name="use_underline">True</property>
+ <property name="relief">GTK_RELIEF_NORMAL</property>
+ <property name="focus_on_click">True</property>
+ <signal name="clicked" handler="on_bdapply" last_modification_time="Mon, 04 Dec 2006 16:49:00 GMT"/>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+
+
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkHSeparator" id="hseparator1">
+ <property name="visible">True</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkImage" id="img">
+ <property name="visible">True</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ </packing>
+ </child>
+
+ <child>
+ <widget class="GtkLabel" id="status">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"></property>
+ <property name="use_underline">False</property>
+ <property name="use_markup">False</property>
+ <property name="justify">GTK_JUSTIFY_LEFT</property>
+ <property name="wrap">False</property>
+ <property name="selectable">False</property>
+ <property name="xalign">0.5</property>
+ <property name="yalign">0.5</property>
+ <property name="xpad">0</property>
+ <property name="ypad">0</property>
+ <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
+ <property name="width_chars">-1</property>
+ <property name="single_line_mode">False</property>
+ <property name="angle">0</property>
+ </widget>
+ <packing>
+ <property name="padding">0</property>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+</widget>
+
+</glade-interface>
diff --git a/gfft.c b/gfft.c
new file mode 100644
index 0000000..fa21a11
--- /dev/null
+++ b/gfft.c
@@ -0,0 +1,100 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <ctype.h>
+
+#include "gfft.h"
+
+void window_hard(sample *in, double *out, size_t size, int ch, int nch)
+{
+ for (size_t i = 0; i < size; i++) {
+ sample val = in[i * nch + ch];
+ out[i] = (1./32768.) * val;
+ }
+}
+
+void window_hann(sample *in, double *out, size_t size, int ch, int nch)
+{
+ double cfac = 2. * M_PI / ((double) size - 1);
+ for (size_t i = 0; i < size; i++) {
+ sample val = in[i * nch + ch];
+ out[i] = (1./32768.) * val
+ * 0.5 * (1. - cos (cfac * (double)i));
+ }
+}
+
+static gpointer fftrun_threadf(gpointer param)
+{
+ struct fft_runner *fftr = param;
+ fftw_plan p;
+ double *in;
+ fftw_complex *out;
+ struct qe *qe;
+ struct fe *fe;
+ int stopped = 0;
+ sample *hist;
+ size_t span = fftr->span;
+ size_t pktsize = fftr->dev->pktsize;
+
+ hist = malloc(sizeof(sample) * pktsize * span * fftr->dev->chan);
+ in = fftw_malloc(sizeof(double) * pktsize * span);
+ out = fftw_malloc(sizeof(fftw_complex) * pktsize * span);
+ p = fftw_plan_dft_r2c_1d(pktsize * span, in, out,
+ FFTW_MEASURE | FFTW_DESTROY_INPUT);
+ free(out);
+
+ while (!stopped) {
+ qe = g_async_queue_pop(fftr->dev->queue);
+ fe = calloc(sizeof(struct fe), 1);
+
+ switch (qe->message) {
+ case QE_STOPPED:
+ fe->message = FE_STOPPED;
+ stopped = 1;
+ break;
+
+ case QE_ERROR:
+ fe->message = FE_DEV_ERROR;
+ fe->errcode = qe->errcode;
+ fe->errp = qe->errp;
+ break;
+
+ case QE_DATA:
+ memmove(hist, hist + pktsize,
+ pktsize * (span - 1) * fftr->dev->chan * sizeof(*hist));
+ memmove(hist + pktsize * (span - 1), qe->samples,
+ pktsize * fftr->dev->chan * sizeof(*hist));
+
+ fe->message = FE_DATA;
+ fe->data = fftw_malloc(sizeof(fftw_complex)
+ * pktsize * span);
+ fe->size = pktsize * span;
+ fftr->winf(hist, in, pktsize * span, 0, fftr->dev->chan);
+ fftw_execute_dft_r2c(p, in, fe->data);
+ break;
+ }
+ free(qe);
+
+ g_async_queue_push(fftr->queue, fe);
+ }
+
+ free(in);
+ fftw_destroy_plan(p);
+ return NULL;
+}
+
+struct fft_runner *fftrun_attach(struct device *device,
+ window_func *winf, size_t span)
+{
+ GError *error = NULL;
+ struct fft_runner *fftr;
+
+ fftr = malloc(sizeof(*fftr));
+ fftr->dev = device;
+ fftr->queue = g_async_queue_new();
+ fftr->thread = g_thread_create(fftrun_threadf, fftr, 0, &error);
+ fftr->winf = winf;
+ fftr->span = span;
+ return fftr;
+}
+
diff --git a/gfft.h b/gfft.h
new file mode 100644
index 0000000..a92b81c
--- /dev/null
+++ b/gfft.h
@@ -0,0 +1,39 @@
+#ifndef _GFFT_H
+#define _GFFT_H
+
+#include <glib.h>
+#include <fftw3.h>
+
+#include "galsa.h"
+
+typedef void window_func(sample *in, double *out, size_t size, int ch, int nch);
+
+struct fft_runner {
+ struct device *dev;
+ GAsyncQueue *queue;
+ GThread *thread;
+ window_func *winf;
+ size_t span;
+};
+
+enum fe_message {
+ FE_DATA,
+ FE_DEV_ERROR,
+ FE_STOPPED,
+};
+
+struct fe {
+ enum fe_message message;
+ int errcode;
+ char *errp;
+ fftw_complex *data;
+ size_t size;
+};
+
+extern void window_hard(sample *in, double *out, size_t size, int ch, int nch);
+extern void window_hann(sample *in, double *out, size_t size, int ch, int nch);
+
+extern struct fft_runner *fftrun_attach(struct device *device,
+ window_func *winf, size_t span);
+
+#endif