Big refactor for pulseaudio addition

Signed-off-by: Skylar "The Cobra" Widulski <cobra@vern.cc>
This commit is contained in:
Skylar "The Cobra" Widulski 2023-12-22 22:17:23 -05:00
parent 48e99e057f
commit ed890c469b
Signed by: cobra
GPG Key ID: 4FD8F812083FF6F9
9 changed files with 206 additions and 68 deletions

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
compile.sh
libyammer.so libyammer.so
config.scm config.scm
*.swp *.swp

View File

@ -6,17 +6,30 @@ Audio visualizer thing in guile scheme
`guile-sdl2`, `fftw` `guile-sdl2`, `fftw`
## Compiling the library ## 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 ```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 # 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 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 * `bitrate`: The bitrate of the audio coming from the input
* `sample-factor`: Proportional to quality, may cause stuttering when >1. * `sample-factor`: Proportional to quality, may cause stuttering when >1.
* `fps`: Self explanatory * `fps`: Self explanatory
@ -34,7 +47,6 @@ Below is a list of values and what they do
* `'exponential`: Simple (basic) exponential smoothing * `'exponential`: Simple (basic) exponential smoothing
* `moving-average-block-size`: Size of the moving average window. Higher is smoother. * `moving-average-block-size`: Size of the moving average window. Higher is smoother.
* `exponential-factor`: Exponential smoothing factor, lower is more smooth. * `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 # Running
Either install libyammer.so and yammer.h to their respective systemwide locations, or do the following: 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) ![Moving Average FFT Mode](http://git.vern.cc/cobra/yammer/raw/branch/main/screenshots/moving-average-fft.png)
# Wishlist # Wishlist
* Native PulseAudio and PipeWire sources * Native PipeWire, ALSA, JACK, sndio sources
* More smoothing modes (one that actually works would be nice) * More smoothing modes (one that actually works would be nice)
* Average over `resolution` * Average over `resolution`
* Offload some calculations to the GPU * Offload some calculations to the GPU
* Use more than one core lmao?
# Known issues # Known issues
* Sometimes the read gets misaligned and just becomes noise * 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 * 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 * The surface can't be transparent
* PulseAudio can be reeaaaaally behind

6
compile.sh Executable file
View File

@ -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"

View File

@ -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 bitrate 44100)
(define sample-factor 2) (define sample-factor 2)
(define fps 30) (define fps 30)
@ -12,4 +17,3 @@
(define smoothing-mode 'none) (define smoothing-mode 'none)
(define moving-average-block-size 25) (define moving-average-block-size 25)
(define exponential-factor 0.5) (define exponential-factor 0.5)
(define exponential-init-factor 10)

View File

@ -17,9 +17,9 @@
* Yammer. If not, see <https://www.gnu.org/licenses/>. * Yammer. If not, see <https://www.gnu.org/licenses/>.
* *
* @file yammer.c * @file yammer.c
* @brief Discrete Fourier Transform wrapper * @brief C library for Yammer
* @details Wrapper around FFTW's real-to-complex (r2c) discrete fourier * @details Library that wraps around other C libraries for easier access in
* transform * Guile.
*/ */
#include "yammer.h" #include "yammer.h"
@ -27,7 +27,8 @@
#include <stdlib.h> #include <stdlib.h>
#include <math.h> #include <math.h>
#include <fftw3.h>
#ifdef USE_FFTW
/** /**
* @brief Initializes the FFTW plan * @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 the output array
return outs; 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

View File

@ -23,9 +23,16 @@
#ifndef _YAMMER_H_ #ifndef _YAMMER_H_
#define _YAMMER_H_ #define _YAMMER_H_
#ifdef USE_FFTW
#include <fftw3.h> #include <fftw3.h>
fftw_plan init_plan(int vsize); fftw_plan init_plan(int vsize);
short int* do_dft(fftw_plan plan, short int* ins, int len); short int* do_dft(fftw_plan plan, short int* ins, int len);
#endif // USE_FFTW
#ifdef USE_PULSE
#include <pulse/simple.h>
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_ #endif // _YAMMER_H_

View File

@ -37,24 +37,31 @@
#:use-module (scheme base) #:use-module (scheme base)
#:use-module (srfi srfi-1) #:use-module (srfi srfi-1)
#:use-module (system foreign) #:use-module (system foreign)
#:use-module (yammer fftw)) #:use-module (yammer ffi))
;; Add this dir to load path, for config ;; Add this config dir to load path
(add-to-load-path ".") (add-to-load-path
(load "config.scm") (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 widely used constants in relation to config values
(define recip-fps (/ 1 fps)) (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) ;; Plan definition (if using fft)
(define plan #f) (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 ;; Initialize more values
(define window #f) (define window #f)
(define renderer #f) (define renderer #f)
(define bv (make-bytevector sample-size)) (define bv (make-list (/ sample-size 2) 0))
(define queue (make-q)) (define queue (make-q))
;; Turns a list lst into a list of pairs (chunks of 2) ;; Turns a list lst into a list of pairs (chunks of 2)
@ -75,7 +82,7 @@
;; Read from the MPD FIFO ;; Read from the MPD FIFO
(define (read-fifo fd) (define (read-fifo fd)
(if (port-closed? fd) (if (port-closed? fd)
(set! fd (open-fifo fifo-file))) (set! fd (open-fifo source)))
(define new-bv #vu8()) (define new-bv #vu8())
(define bytes-read 0) (define bytes-read 0)
(while (< bytes-read sample-size) (while (< bytes-read sample-size)
@ -86,6 +93,17 @@
new-bv (get-bytevector-n new-bv (get-bytevector-n
fd (- sample-size bytes-read)))) fd (- sample-size bytes-read))))
(set! bytes-read (bytevector-length new-bv))) (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) (enq! queue new-bv)
(if (>= (q-length queue) queue-size) (if (>= (q-length queue) queue-size)
(set! bv (deq! queue)))) (set! bv (deq! queue))))
@ -168,7 +186,7 @@
(do ((i 0 (1+ i))) (do ((i 0 (1+ i)))
((>= i (length input))) ((>= i (length input)))
(if (= i 0) (if (= i 0)
(set! ret (list (* exponential-init-factor (car input)))) (set! ret (list (car input)))
(set! ret (set! ret
(append (append
ret ret
@ -190,14 +208,18 @@
;; Initialize fps sleep to touch later ;; Initialize fps sleep to touch later
(set! sleep-future (future (usleep (round (* recip-fps 1000))))) (set! sleep-future (future (usleep (round (* recip-fps 1000)))))
;; In the meantime, check if the read bytevector is the right size ;; 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 (begin
;; Chunk the bytevector ;; Chunk the bytevector
(set! plst (chunk2 (bytevector->sint-list (set! plst (chunk2 bv))
bv (native-endianness) 2)))
;; Read again ;; Read again
(set! read-future (future (read-fifo fd))) (cond
(if fft? ((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 ;; FFF smoothing enabled check
(if (not (equal? smoothing-mode #f)) (if (not (equal? smoothing-mode #f))
(draw ren (draw ren
@ -226,7 +248,13 @@
(touch sleep-future))) (touch sleep-future)))
;; Initially open the MPD FIFO ;; 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 (sdl-init '(video events)) ;; Initialize SDL2
(set! window (make-window #:title "Yammer")) ;; Create window (set! window (make-window #:title "Yammer")) ;; Create window
@ -238,6 +266,11 @@
(delete-renderer! renderer) ;; Free renderer on close (delete-renderer! renderer) ;; Free renderer on close
(close-window! window) ;; Close window (close-window! window) ;; Close window
(sdl-quit) ;; Quit SDL2 (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)))

80
yammer/ffi.scm Normal file
View File

@ -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 '*)))))

View File

@ -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))