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
config.scm
*.swp

View File

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

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

View File

@ -17,9 +17,9 @@
* Yammer. If not, see <https://www.gnu.org/licenses/>.
*
* @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 <stdlib.h>
#include <math.h>
#include <fftw3.h>
#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

View File

@ -23,9 +23,16 @@
#ifndef _YAMMER_H_
#define _YAMMER_H_
#ifdef USE_FFTW
#include <fftw3.h>
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 <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_

View File

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

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