From e8d37e36f8b97fcc29c8f67d38f0cd5d48ec9d3f Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Fri, 21 Jun 2013 12:32:46 +0200 Subject: import --- .gitignore | 2 + Makefile | 18 +++ galsa.c | 160 ++++++++++++++++++++++++++ galsa.h | 49 ++++++++ gbeat.c | 370 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ gbeat.glade | 359 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ gfft.c | 100 ++++++++++++++++ gfft.h | 39 +++++++ 8 files changed, 1097 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 galsa.c create mode 100644 galsa.h create mode 100644 gbeat.c create mode 100644 gbeat.glade create mode 100644 gfft.c create mode 100644 gfft.h 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 +#include +#include +#include + +#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 +#include + +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 +#include +#include +#include +#include +#include +#include + +#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 @@ + + + + + + + True + GtkAlsaViewer + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + True + False + + + + + + 5 + True + False + 3 + + + + True + False + 3 + + + + True + Device: + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + True + True + True + 0 + + True + * + False + + + 0 + True + True + + + + + + True + Rate: + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + True + True + True + 5 + + True + * + False + 5 + + + 0 + False + True + + + + + + True + True + Open + True + GTK_RELIEF_NORMAL + True + False + False + + + + 0 + False + False + + + + + + True + False + True + Capture + True + GTK_RELIEF_NORMAL + True + False + False + + + + 0 + False + False + + + + + 0 + False + True + + + + + + True + False + 3 + + + + True + nb, c, d + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + True + True + True + 0 + 3 + True + False + 7 + + + 0 + True + True + + + + + + True + True + True + True + 0 + 12 + True + False + 7 + + + 0 + True + True + + + + + + True + True + True + True + 0 + 1.4 + True + False + 7 + + + 0 + True + True + + + + + + True + True + True + True + 0 + 0.6 + True + False + 7 + + + 0 + True + True + + + + + + True + True + Apply + True + GTK_RELIEF_NORMAL + True + + + + 0 + False + False + + + + + + + 0 + False + True + + + + + + True + + + 0 + False + True + + + + + + True + 0.5 + 0.5 + 0 + 0 + + + 0 + True + True + + + + + + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + + 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 +#include +#include +#include + +#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 +#include + +#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 -- cgit v1.2.1