Big refactor for pulseaudio addition
Signed-off-by: Skylar "The Cobra" Widulski <cobra@vern.cc>
This commit is contained in:
parent
48e99e057f
commit
ed890c469b
|
@ -1,4 +1,3 @@
|
|||
compile.sh
|
||||
libyammer.so
|
||||
config.scm
|
||||
*.swp
|
||||
|
|
26
README.md
26
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
|
||||
|
|
|
@ -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"
|
|
@ -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)
|
||||
|
|
40
yammer.c
40
yammer.c
|
@ -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
|
||||
|
|
9
yammer.h
9
yammer.h
|
@ -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_
|
||||
|
|
67
yammer.scm
67
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)))
|
||||
|
|
|
@ -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 '*)))))
|
|
@ -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))
|
Loading…
Reference in New Issue