diff --git a/.gitignore b/.gitignore index 9baecf6..981c569 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -compile.sh libyammer.so config.scm *.swp diff --git a/README.md b/README.md index 843e9dd..461d1b4 100644 --- a/README.md +++ b/README.md @@ -6,17 +6,30 @@ Audio visualizer thing in guile scheme `guile-sdl2`, `fftw` ## Compiling the library -You need to compile the yammer.c file because it has wrappers around fftw functions. +You need to compile the yammer.c file because it has wrappers and stuff. + +To disable FFTW, use `export NO_FFTW=1` + +To disable PulseAudio, use `export NO_PULSE=1` + +To enable debugging symbols, use `export DEBUG=1` + ```sh -gcc -shared yammer.c -o libyammer.so -Wall -Wextra -Werror -fPIC -shared -lfftw3 -lm -O3 -ggdb +./compile.sh ``` +You may need to adjust `PKG_CONFIG_PATH` if you use something like Guix System. # Configuration -Copy `config.example.scm` to `config.scm` +Copy `config.example.scm` to `~/.config/yammer/config.scm` Below is a list of values and what they do -* `fifo-file`: The MPD fifo +* `using-fftw?`: Was libyammer.so compiled using FFTW? +* `using-pulse?`: Was libyammer.so compiled using PulseAudio? + +* `source`: The MPD fifo or PulseAudio source +* `source-type`: One of `'mpd` or `'pulse` +* `pulse-latency`: Latency to request from PulseAudio * `bitrate`: The bitrate of the audio coming from the input * `sample-factor`: Proportional to quality, may cause stuttering when >1. * `fps`: Self explanatory @@ -34,7 +47,6 @@ Below is a list of values and what they do * `'exponential`: Simple (basic) exponential smoothing * `moving-average-block-size`: Size of the moving average window. Higher is smoother. * `exponential-factor`: Exponential smoothing factor, lower is more smooth. -* `exponential-init-factor`: The first value of the exponentially smoothed list is multiplied by this # Running Either install libyammer.so and yammer.h to their respective systemwide locations, or do the following: @@ -63,12 +75,14 @@ guile -L . yammer.scm ![Moving Average FFT Mode](http://git.vern.cc/cobra/yammer/raw/branch/main/screenshots/moving-average-fft.png) # Wishlist -* Native PulseAudio and PipeWire sources +* Native PipeWire, ALSA, JACK, sndio sources * More smoothing modes (one that actually works would be nice) * Average over `resolution` * Offload some calculations to the GPU +* Use more than one core lmao? # Known issues * Sometimes the read gets misaligned and just becomes noise * When MPD is paused or the program is sleeping for the next frame, the UI doesn't update so you can't exit * The surface can't be transparent +* PulseAudio can be reeaaaaally behind diff --git a/compile.sh b/compile.sh new file mode 100755 index 0000000..acfc21d --- /dev/null +++ b/compile.sh @@ -0,0 +1,6 @@ +LDFLAGS="$(pkg-config --cflags --libs ${NO_FFTW:-fftw3} ${NO_PULSE:-libpulse-simple})" +CFLAGS="-Wall -Wextra -Werror -fPIC -O3 ${DEBUG:+-ggdb}" + +gcc_invoke="gcc ${NO_FFTW:--DUSE_FFTW} ${NO_PULSE:--DUSE_PULSE} -shared yammer.c -o libyammer.so ${CFLAGS} -shared ${LDFLAGS}" + +eval "$gcc_invoke" diff --git a/config.example.scm b/config.example.scm index ed40889..e77fc36 100644 --- a/config.example.scm +++ b/config.example.scm @@ -1,4 +1,9 @@ -(define fifo-file "/tmp/mpd.fifo") +(define using-fftw? #t) +(define using-pulse? #t) + +(define source "/tmp/mpd.fifo") +(define source-type 'mpd) +(define pulse-latency 10) (define bitrate 44100) (define sample-factor 2) (define fps 30) @@ -12,4 +17,3 @@ (define smoothing-mode 'none) (define moving-average-block-size 25) (define exponential-factor 0.5) -(define exponential-init-factor 10) diff --git a/yammer.c b/yammer.c index 9f88527..f7b5de1 100644 --- a/yammer.c +++ b/yammer.c @@ -17,9 +17,9 @@ * Yammer. If not, see . * * @file yammer.c - * @brief Discrete Fourier Transform wrapper - * @details Wrapper around FFTW's real-to-complex (r2c) discrete fourier - * transform + * @brief C library for Yammer + * @details Library that wraps around other C libraries for easier access in + * Guile. */ #include "yammer.h" @@ -27,7 +27,8 @@ #include #include -#include + +#ifdef USE_FFTW /** * @brief Initializes the FFTW plan @@ -96,3 +97,34 @@ short int* do_dft(fftw_plan plan, short int* ins, int len) { // Return the output array return outs; } + +#endif // USE_FFTW + + +#ifdef USE_PULSE + +pa_simple* make_pa_simple(unsigned int bitrate, char* dev, int latency) { + pa_sample_spec ss = { + .format = PA_SAMPLE_S16NE, + .channels = 2, + .rate = bitrate + }; + + pa_buffer_attr attr; + attr.maxlength = (uint32_t) -1; + attr.tlength = (uint32_t) -1; + attr.prebuf = (uint32_t) -1; + attr.minreq = (uint32_t) -1; + attr.fragsize = latency; + + return pa_simple_new(NULL, "Yammer", PA_STREAM_RECORD, dev, + "Audio Visualizer", &ss, NULL, &attr, NULL); +} + +short int* read_from_pa(pa_simple* s, size_t bytes) { + short int* buf = malloc(sizeof(short int) * bytes); + pa_simple_read(s, buf, sizeof(short int) * bytes, NULL); + return buf; +} + +#endif // USE_PULSE diff --git a/yammer.h b/yammer.h index 8efdc65..1a77c9d 100644 --- a/yammer.h +++ b/yammer.h @@ -23,9 +23,16 @@ #ifndef _YAMMER_H_ #define _YAMMER_H_ +#ifdef USE_FFTW #include - fftw_plan init_plan(int vsize); short int* do_dft(fftw_plan plan, short int* ins, int len); +#endif // USE_FFTW + +#ifdef USE_PULSE +#include +pa_simple* make_pa_simple(unsigned int bitrate, char* dev, int latency); +short int* read_from_pa(pa_simple* s, size_t bytes); +#endif // USE_PULSE #endif // _YAMMER_H_ diff --git a/yammer.scm b/yammer.scm index efbd9c2..70e973e 100755 --- a/yammer.scm +++ b/yammer.scm @@ -37,24 +37,31 @@ #:use-module (scheme base) #:use-module (srfi srfi-1) #:use-module (system foreign) - #:use-module (yammer fftw)) + #:use-module (yammer ffi)) -;; Add this dir to load path, for config -(add-to-load-path ".") -(load "config.scm") +;; Add this config dir to load path +(add-to-load-path + (string-append + (if (getenv "XDG_CONFIG_HOME") + (getenv "XDG_CONFIG_HOME") + (string-append (getenv "HOME") "/.config")) + "/yammer")) +(load-from-path "config.scm") ;; Define widely used constants in relation to config values (define recip-fps (/ 1 fps)) -(define sample-size (* 4 (round (* sample-factor (* (/ bitrate 4) recip-fps))))) +(define sample-size + (exact (* 4 (round (* sample-factor (* (/ bitrate 4) recip-fps)))))) ;; Plan definition (if using fft) (define plan #f) -(if fft (monitor (set! plan (init_plan (exact (/ sample-size 4)))))) +(if (and fft using-fftw?) + (monitor (set! plan (init_plan (exact (/ sample-size 4)))))) ;; Initialize more values (define window #f) (define renderer #f) -(define bv (make-bytevector sample-size)) +(define bv (make-list (/ sample-size 2) 0)) (define queue (make-q)) ;; Turns a list lst into a list of pairs (chunks of 2) @@ -75,7 +82,7 @@ ;; Read from the MPD FIFO (define (read-fifo fd) (if (port-closed? fd) - (set! fd (open-fifo fifo-file))) + (set! fd (open-fifo source))) (define new-bv #vu8()) (define bytes-read 0) (while (< bytes-read sample-size) @@ -86,6 +93,17 @@ new-bv (get-bytevector-n fd (- sample-size bytes-read)))) (set! bytes-read (bytevector-length new-bv))) + (enq! queue (bytevector->sint-list new-bv (native-endianness) 2)) + (if (>= (q-length queue) queue-size) + (set! bv (deq! queue)))) + +(define (read-pa pa) + (define new-bv + (bytevector->sint-list + (pointer->bytevector + (read_from_pa pa (/ sample-size 2)) + (/ sample-size 2) (sizeof int16) 's16) + (native-endianness) 2)) (enq! queue new-bv) (if (>= (q-length queue) queue-size) (set! bv (deq! queue)))) @@ -168,7 +186,7 @@ (do ((i 0 (1+ i))) ((>= i (length input))) (if (= i 0) - (set! ret (list (* exponential-init-factor (car input)))) + (set! ret (list (car input))) (set! ret (append ret @@ -190,14 +208,18 @@ ;; Initialize fps sleep to touch later (set! sleep-future (future (usleep (round (* recip-fps 1000))))) ;; In the meantime, check if the read bytevector is the right size - (if (= (bytevector-length bv) sample-size) + (if (= (length bv) (/ sample-size 2)) (begin ;; Chunk the bytevector - (set! plst (chunk2 (bytevector->sint-list - bv (native-endianness) 2))) + (set! plst (chunk2 bv)) ;; Read again - (set! read-future (future (read-fifo fd))) - (if fft? + (cond + ((eq? source-type 'mpd) + (set! read-future (future (read-fifo fd)))) + ((eq? source-type 'pulse) + (set! read-future (future (read-pa pa-simple))))) + + (if (and fft? using-fftw?) ;; FFF smoothing enabled check (if (not (equal? smoothing-mode #f)) (draw ren @@ -226,7 +248,13 @@ (touch sleep-future))) ;; Initially open the MPD FIFO -(define fd (open-fifo fifo-file)) +(define fd #f) +(define pa-simple #f) +(cond + ((eq? source-type 'mpd) + (set! fd (open-fifo source))) + ((eq? source-type 'pulse) + (set! pa-simple (make-pa-simple bitrate source pulse-latency)))) (sdl-init '(video events)) ;; Initialize SDL2 (set! window (make-window #:title "Yammer")) ;; Create window @@ -238,6 +266,11 @@ (delete-renderer! renderer) ;; Free renderer on close (close-window! window) ;; Close window (sdl-quit) ;; Quit SDL2 -(close-port fd) ;; Close MPD FIFO +(cond + ((eq? source-type 'mpd) + (close-port fd)) + ((eq? source-type 'pulse) + (pa_simple_free pa-simple))) -(if fft (monitor (fftw_destroy_plan plan))) ;; Free FFT plan if FFT is enabled +;; Free FFT plan if FFT is enabled +(if (and fft using-fftw?) (monitor (fftw_destroy_plan plan))) diff --git a/yammer/ffi.scm b/yammer/ffi.scm new file mode 100644 index 0000000..5d0c4dd --- /dev/null +++ b/yammer/ffi.scm @@ -0,0 +1,80 @@ +(define-module (yammer ffi) + #:use-module (system foreign) + #:export (FFTW_ESTIMATE + fftw_destroy_plan + init_plan + do-dft + make-pa-simple + read_from_pa + pa_simple_free)) + +(add-to-load-path + (string-append + (if (getenv "XDG_CONFIG_HOME") + (getenv "XDG_CONFIG_HOME") + (string-append (getenv "HOME") "/.config")) + "/yammer")) +(load-from-path "config.scm") + +(define fftw #f) +(define pulseaudio #f) +(define libyammer (dynamic-link "libyammer")) + +(define FFTW_ESTIMATE #f) +(define fftw_destroy_plan #f) +(define do_dft #f) +(define init_plan #f) +(define (do-dft plan lst len) #f) + +(define make_pa_simple #f) +(define (make-pa-simple rate dev) #f) +(define read_from_pa #f) +(define pa_simple_free #f) + +(if using-fftw? + (begin + (set! fftw (dynamic-link "libfftw3")) + (set! FFTW_ESTIMATE 48) + + (set! fftw_destroy_plan + (pointer->procedure void + (dynamic-func "fftw_destroy_plan" fftw) + (list '*))) + + (set! do_dft + (pointer->procedure '* + (dynamic-func "do_dft" libyammer) + (list '* '* int))) + + (set! init_plan + (pointer->procedure '* + (dynamic-func "init_plan" libyammer) + (list int))) + + (set! do-dft + (lambda (plan lst len) + (define out (do_dft plan (bytevector->pointer (list->s16vector lst)) len)) + (pointer->bytevector out (floor/ len 2) (sizeof short) 's16))))) + +(if using-pulse? + (begin + (set! pulseaudio (dynamic-link "libpulse-simple")) + + (set! make_pa_simple + (pointer->procedure '* + (dynamic-func "make_pa_simple" libyammer) + (list unsigned-int '* int))) + + (set! make-pa-simple + (lambda (rate dev latency) + (make_pa_simple rate (string->pointer dev latency)))) + + (set! read_from_pa + (pointer->procedure '* + (dynamic-func "read_from_pa" libyammer) + (list '* size_t))) + + (set! pa_simple_free + (pointer->procedure void + (dynamic-func "pa_simple_free" pulseaudio) + (list '*))))) diff --git a/yammer/fftw.scm b/yammer/fftw.scm deleted file mode 100644 index e58f072..0000000 --- a/yammer/fftw.scm +++ /dev/null @@ -1,37 +0,0 @@ -(define-module (yammer fftw) - #:use-module (system foreign) - #:export (FFTW_ESTIMATE - fftw_plan_dft_r2c_1d - fftw_destroy_plan ;)) - init_plan - do-dft)) - -(define fftw (dynamic-link "libfftw3")) - -(define FFTW_ESTIMATE 48) - -(define fftw_plan_dft_r2c_1d - (pointer->procedure '* - (dynamic-func "fftw_plan_dft_r2c_1d" fftw) - (list int '* '* unsigned-int))) - -(define fftw_destroy_plan - (pointer->procedure void - (dynamic-func "fftw_destroy_plan" fftw) - (list '*))) - -(define libyammer (dynamic-link "libyammer")) - -(define do_dft - (pointer->procedure '* - (dynamic-func "do_dft" libyammer) - (list '* '* int))) - -(define init_plan - (pointer->procedure '* - (dynamic-func "init_plan" libyammer) - (list int))) - -(define (do-dft plan lst len) - (define out (do_dft plan (bytevector->pointer (list->s16vector lst)) len)) - (pointer->bytevector out (floor/ len 2) (sizeof short) 's16))