summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLasse Flygenring-Harrsen <Lasse@Flygenring-Harrsen.com>2020-07-09 00:42:03 +0200
committerLasse Flygenring-Harrsen <Lasse@Flygenring-Harrsen.com>2020-07-09 00:42:03 +0200
commit6d2fa862858dafdea29fc2e4a3dc39c3dbb17f2e (patch)
treeb03c3dc5c5c08129ed7f0642dd3e64d75aa866e0
Import Upstream version 0.7.1
-rw-r--r--.clang-format6
-rw-r--r--.github/workflows/build.yml36
-rw-r--r--.gitignore33
-rw-r--r--CONTRIBUTING.md15
-rw-r--r--LICENSE19
-rw-r--r--Makefile.am55
-rw-r--r--README.md444
-rwxr-xr-xautogen.sh19
-rw-r--r--cava.c1077
-rw-r--r--cava.psfbin0 -> 5558 bytes
-rw-r--r--config.c539
-rw-r--r--config.h68
-rw-r--r--configure.ac312
-rw-r--r--debug.h9
-rw-r--r--example_files/config167
-rw-r--r--example_files/etc/asound.conf20
-rw-r--r--example_files/etc/modprobe.d/alsa-aloop.conf1
-rw-r--r--iniparser/AUTHORS6
-rw-r--r--iniparser/LICENSE21
-rw-r--r--iniparser/Makefile.am3
-rw-r--r--iniparser/README.md67
-rw-r--r--iniparser/src/dictionary.c374
-rw-r--r--iniparser/src/dictionary.h169
-rw-r--r--iniparser/src/iniparser.c743
-rw-r--r--iniparser/src/iniparser.h313
-rw-r--r--input/alsa.c165
-rw-r--r--input/alsa.h5
-rw-r--r--input/common.c53
-rw-r--r--input/common.h36
-rw-r--r--input/fifo.c79
-rw-r--r--input/fifo.h5
-rw-r--r--input/portaudio.c204
-rw-r--r--input/portaudio.h3
-rw-r--r--input/pulse.c142
-rw-r--r--input/pulse.h6
-rw-r--r--input/shmem.c82
-rw-r--r--input/shmem.h5
-rw-r--r--input/sndio.c63
-rw-r--r--input/sndio.h5
-rw-r--r--output/raw.c48
-rw-r--r--output/raw.h2
-rw-r--r--output/terminal_bcircle.c85
-rw-r--r--output/terminal_bcircle.h6
-rw-r--r--output/terminal_ncurses.c280
-rw-r--r--output/terminal_ncurses.h8
-rw-r--r--output/terminal_noncurses.c287
-rw-r--r--output/terminal_noncurses.h5
-rw-r--r--todo11
-rw-r--r--util.h10
49 files changed, 6111 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..8c7cd3e
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,6 @@
+BasedOnStyle: LLVM
+---
+Language: Cpp
+PointerAlignment: Right
+ColumnLimit: 100
+IndentWidth: 4
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..49398d0
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,36 @@
+name: build
+on: [push, pull_request]
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v1
+ - uses: DoozyX/clang-format-lint-action@v0.5
+ build-linux:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v1
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install libfftw3-dev libasound2-dev libncursesw5-dev libpulse-dev libtool automake libiniparser-dev portaudio19-dev libsndio-dev
+ - name: Generate configure
+ run: ./autogen.sh
+ - name: Run ./configure
+ run: CPPFLAGS=-I/usr/include/iniparser ./configure
+ - name: Run make
+ run: make
+ build-macos:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v1
+ - name: Install dependencies
+ run: |
+ brew install fftw ncurses libtool automake portaudio iniparser
+ ln -s /usr/local/bin/glibtoolize /usr/local/bin/libtoolize
+ - name: Generate configure
+ run: ./autogen.sh
+ - name: Run ./configure
+ run: LDFLAGS="-L/usr/local/opt/ncurses/lib" CPPFLAGS="-I/usr/local/opt/ncurses/include" ./configure
+ - name: Run make
+ run: make
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1b1eb3f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,33 @@
+cava
+*.so
+*.so.0
+*.o
+*.a
+.deps/
+.libs/
+Makefile
+Makefile.in
+aclocal.m4
+ar-lib
+autom4te.cache/
+compile
+config.guess
+config.log
+config.status
+config.sub
+configure
+depcomp
+iniparser/.libs/
+iniparser/Makefile
+iniparser/Makefile.in
+iniparser/libiniparser.la
+iniparser/src/.deps/
+iniparser/src/.dirstamp
+iniparser/src/dictionary.lo
+iniparser/src/iniparser.lo
+install-sh
+libtool
+ltmain.sh
+m4/
+missing
+version
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..2ea8a0b
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,15 @@
+# How to contribute
+Thank you for wanting to contribute to this project. In order to get the development of this project to run as smooth as possible, we will encourage all contributions to follw this simple process:
+
+1. Before any pull request is submitted a github issue must be created for the task.
+2. The new github task is to be opened for suggestions from other contributors.
+3. A pull request is published.
+4. Contributors code review pull request and approve/reject pull request.
+5. Pull request either gets approved or rejected.
+5. Github task is updated and everyone is notified.
+
+# Coding conventions
+* 4 spaces for indents
+* curly braces on same line as if, while, for statements
+
+Generally please try to keep the style consistent with the code as it is.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d3f3d12
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015 Karl Stavestrand <karl@stavestrand.no>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..0e109e3
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,55 @@
+AUTOMAKE_OPTIONS = foreign
+
+if !SYSTEM_LIBINIPARSER
+ SUBDIRS = iniparser
+endif
+
+ACLOCAL_AMFLAGS = -I m4
+
+M_CPPFLAGS = -DSYSTEM_LIBINIPARSER=@SYSTEM_LIBINIPARSER@
+
+bin_PROGRAMS = cava
+cava_SOURCES = cava.c config.c input/common.c input/fifo.c input/shmem.c \
+ output/terminal_noncurses.c output/raw.c
+cava_LDFLAGS = -L/usr/local/lib -Wl,-rpath /usr/local/lib
+cava_CPPFLAGS = -DPACKAGE=\"$(PACKAGE)\" -DVERSION=\"$(VERSION)\" \
+ -D_POSIX_SOURCE -D _POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE_EXTENDED
+cava_CFLAGS = -std=c99 -Wall -Werror -Wextra -Wno-unused-result -Wno-unknown-warning-option -Wno-maybe-uninitialized
+
+if OSX
+ cava_CFLAGS += -DNORT
+else
+ cava_LDFLAGS += -lrt
+endif
+
+if ALSA
+ cava_SOURCES += input/alsa.c
+endif
+
+if PORTAUDIO
+ cava_SOURCES += input/portaudio.c
+endif
+
+if PULSE
+ cava_SOURCES += input/pulse.c
+endif
+
+if SNDIO
+ cava_SOURCES += input/sndio.c
+endif
+
+if NCURSES
+ cava_SOURCES += output/terminal_ncurses.c
+endif
+
+cava_font_dir = @FONT_DIR@
+cava_font__DATA = cava.psf
+
+if !SYSTEM_LIBINIPARSER
+ cava_LDADD = -liniparser
+ cava_SOURCES += iniparser/libiniparser.la
+ cava_LDADD += -Liniparser/.libs
+ cava_CPPFLAGS += -Iiniparser/src
+endif
+
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..71d7444
--- /dev/null
+++ b/README.md
@@ -0,0 +1,444 @@
+C.A.V.A. [![Build Status](https://github.com/karlstav/cava/workflows/build/badge.svg)](https://github.com/karlstav/cava/actions)
+====================
+
+**C**onsole-based **A**udio **V**isualizer for **A**LSA
+
+also supports audio input from Pulseaudio, fifo (mpd), sndio, squeezelite and portaudio.
+
+Now also works on macOS!
+
+by [Karl Stavestrand](mailto:karl@stavestrand.no)
+
+![spectrum](https://raw.githubusercontent.com/karlstav/cava/gh-pages/cava_gradient.gif "spectrum")
+
+thanks to [anko](https://github.com/anko) for the gif, here is the [recipe]( http://unix.stackexchange.com/questions/113695/gif-screencastng-the-unix-way).
+
+[Demo video](https://youtu.be/9PSp8VA6yjU)
+
+<!-- START doctoc generated TOC please keep comment here to allow auto update -->
+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
+**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
+
+- [What it is](#what-it-is)
+- [Installing](#installing)
+ - [From Source](#from-source)
+ - [Installing Build Requirements](#installing-build-requirements)
+ - [Building](#building)
+ - [Installing](#installing-1)
+ - [Uninstalling](#uninstalling)
+ - [Some distro specific pre-made binaries/recipes](#some-distro-specific-pre-made-binariesrecipes)
+ - [openSUSE](#opensuse)
+ - [Fedora](#fedora)
+ - [Arch](#arch)
+ - [Ubuntu](#ubuntu)
+- [Capturing audio](#capturing-audio)
+ - [From Pulseaudio monitor source (Easy, default if supported)](#from-pulseaudio-monitor-source-easy-default-if-supported)
+ - [From ALSA-loopback device (Tricky)](#from-alsa-loopback-device-tricky)
+ - [From mpd's fifo output](#from-mpds-fifo-output)
+ - [sndio](#sndio)
+ - [squeezelite](#squeezelite)
+ - [macOS](#macos)
+- [Running via ssh](#running-via-ssh)
+ - [Raw Output](#raw-output)
+- [Font notes](#font-notes)
+ - [In ttys](#in-ttys)
+ - [In terminal emulators](#in-terminal-emulators)
+- [Latency notes](#latency-notes)
+- [Usage](#usage)
+ - [Controls](#controls)
+- [Configuration](#configuration)
+- [Contribution](#contribution)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
+
+
+What it is
+----------
+
+C.A.V.A. is a bar spectrum audio visualizer for the Linux terminal using ALSA, pulseaudio or fifo buffer for input.
+
+This program is not intended for scientific use. It's written to look responsive and aesthetic when used to visualize music.
+
+
+Installing
+------------------
+
+### From Source
+
+#### Installing Build Requirements
+
+Required components:
+* [FFTW](http://www.fftw.org/)
+* libtool
+* automake
+* build-essentials
+* [iniparser](https://github.com/ndevilla/iniparser)
+
+
+Recomended components:
+* [ncursesw dev files](http://www.gnu.org/software/ncurses/) (bundled in ncurses in arch)
+* [ALSA dev files](http://alsa-project.org/), or
+* [Pulseaudio dev files](http://freedesktop.org/software/pulseaudio/doxygen/), or
+* Portaudio, or
+* Sndio
+
+Only FFTW and the other build tools are actually required for CAVA to compile, but this will only give you the ability to read from fifo files. To more easly grab audio from your system pulseaudio, alsa, sndio or portaudio dev files are recommended (depending on what audio system you are using). Not sure how to get the pulseaudio dev files for other distros than debian/ubuntu or if they are bundled in pulseaudio.
+
+
+For better a better visual experience ncurses is also recomended.
+
+Iniparser is also required, but if it is not already installed, a bundled version will be used.
+
+All the requirements can be installed easily in all major distros:
+
+Debian Buster or higher/Ubuntu 18.04 or higher :
+
+ apt-get install libfftw3-dev libasound2-dev libncursesw5-dev libpulse-dev libtool automake libiniparser-dev
+ export CPPFLAGS=-I/usr/include/iniparser
+
+
+older Debian/Ubuntu:
+
+ apt-get install libfftw3-dev libasound2-dev libncursesw5-dev libpulse-dev libtool automake
+
+
+ArchLinux:
+
+ pacman -S base-devel fftw ncurses alsa-lib iniparser pulseaudio
+
+
+openSUSE:
+
+ zypper install alsa-devel ncurses-devel fftw3-devel libpulse-devel libtool
+
+
+Fedora:
+
+ dnf install alsa-lib-devel ncurses-devel fftw3-devel pulseaudio-libs-devel libtool
+
+
+macOS:
+
+First install homebrew if you have't already:
+
+ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
+
+Then install prerequisites:
+
+ brew install fftw ncurses libtool automake portaudio
+
+Then fix macOS not finding libtool and ncursesw:
+
+ export LIBTOOL=`which glibtool`
+ export LIBTOOLIZE=`which glibtoolize`
+ ln -s `which glibtoolize` /usr/local/bin/libtoolize
+ ln -s /usr/lib/libncurses.dylib /usr/local/lib/libncursesw.dylib
+
+Tested on macOS High Sierra.
+
+
+
+#### Building
+ First of all clone this repo and cd in to it, then run:
+
+ ./autogen.sh
+ ./configure
+ make
+
+If you have a recommended component installed, but do not wish to use it (perhaps if building a binary on one machine to be used on another), then the corresponding feature can be disabled during configuration (see configure --help for details).
+
+
+
+#### Installing
+
+Install `cava` to default `/usr/local`:
+
+ make install
+
+Or you can change `PREFIX`, for example:
+
+ ./configure --prefix=PREFIX
+
+#### Uninstalling
+
+ make uninstall
+
+
+### Some distro specific pre-made binaries/recipes
+#### openSUSE
+
+Tumbleweed users have cava in their repo. They can just use:
+
+ zypper in cava
+
+Leap users need to add the multimedia:apps repository first:
+
+ zypper ar -f obs://multimedia:apps/openSUSE_Leap_42.2 multimedia
+
+If you use another version just replace *openSUSE_Leap_42.2* with *openSUSE_13.2*, adjust it to your version.
+
+#### Fedora
+
+Cava is available in Fedora 26 and later. You can install Cava by
+running:
+
+ dnf install cava
+
+#### Arch
+
+Cava is in [AUR](https://aur.archlinux.org/packages/cava/).
+
+ pacaur -S cava
+
+#### Ubuntu
+
+Michael Nguyen has added CAVA to his PPA, it can be installed with:
+
+ sudo add-apt-repository ppa:tehtotalpwnage/ppa
+ sudo apt-get update
+ sudo apt-get install cava
+
+For Ubuntu 18 or newer, you can use Harshal Sheth's PPA:
+
+ sudo add-apt-repository ppa:hsheth2/ppa
+ sudo apt-get update
+ sudo apt-get install cava
+
+All distro specific instalation sources might be out of date.
+
+
+Capturing audio
+---------------
+
+### From Pulseaudio monitor source (Easy, default if supported)
+
+First make sure you have installed pulseaudio dev files and that cava has been built with pulseaudio support (it should be automatically if the dev files are found).
+
+If you're lucky all you have to do is to uncomment this line in the config file under input:
+
+ method = pulse
+
+If nothing happens you might have to use a different source than the default. The default might also be your microphone. Look at the config file for help.
+
+
+### From ALSA-loopback device (Tricky)
+
+Set
+
+ method = alsa
+
+in the config file.
+
+ALSA can be difficult because there is no native way to grab audio from an output. If you want to capture audio straight fom the output (not just mic or line-in), you must create an ALSA loopback interface, then output the audio simultaneously to both the loopback and your normal interface.
+
+To create a loopback interface simply run:
+
+`sudo modprobe snd_aloop`
+
+Hopefully your `aplay -l` should now contain a loopback interface.
+
+To make it persistent across boot add the line `snd-aloop` to "/etc/modules". To keep it from being loaded as the first soundcard add the line `options snd-aloop index=1` to "/etc/modprobe.d/alsa-base.conf", this will load it at '1'. You can replace '1' with whatever makes most sense in your audio setup.
+
+Playing the audio through your Loopback interface makes it possible for cava to capture it, but there will be no sound in your speakers. In order to play audio on the loopback interface and your actual interface you must make use of the ALSA multi channel.
+
+Look at the included example file `example_files/etc/asound.conf` on how to use the multi channel. I was able to make this work on my laptop (an Asus UX31 running Ubuntu), but I had no luck with the ALSA method on my Raspberry Pi (Rasbian) with an USB DAC. The PulseAudio method however works perfectly on my Pi.
+
+Read more about the ALSA method [here](http://stackoverflow.com/questions/12984089/capture-playback-on-play-only-sound-card-with-alsa).
+
+If you are having problems with the alsa method on Rasberry PI, try enabling `mmap` by adding the following line to `/boot/config.txt` and reboot:
+
+```
+dtoverlay=i2s-mmap
+```
+
+### From mpd's fifo output
+
+Add these lines in mpd:
+
+ audio_output {
+ type "fifo"
+ name "my_fifo"
+ path "/tmp/mpd.fifo"
+ format "44100:16:2"
+ }
+
+Uncomment and change input method to `fifo` in the config file.
+
+The path of the fifo can be specified with the `source` parameter.
+
+I had some trouble with sync (the visualizer was ahead of the sound). Reducing the ALSA buffer in mpd fixed it:
+
+ audio_output {
+ type "alsa"
+ name "My ALSA"
+ buffer_time "50000" # (50ms); default is 500000 microseconds (0.5s)
+ }
+
+### sndio
+
+sndio is the audio framework used on OpenBSD, but it's also available on
+FreeBSD and Linux. So far this is only tested on FreeBSD.
+
+To test it
+```bash
+# Start sndiod with a monitor sub-device
+$ sndiod -dd -s default -m mon -s monitor
+
+# Set the AUDIODEVICE environment variable to override the default
+# sndio device and run cava
+$ AUDIODEVICE=snd/0.monitor cava
+```
+
+### squeezelite
+[squeezelite](https://en.wikipedia.org/wiki/Squeezelite) is one of several software clients available for the Logitech Media Server. Squeezelite can export its audio data as shared memory, which is what this input module uses.
+Just adapt your config:
+```
+method = shmem
+source = /squeezelite-AA:BB:CC:DD:EE:FF
+```
+where `AA:BB:CC:DD:EE:FF` is squeezelite's MAC address (check the LMS Web GUI (Settings>Information) if unsure).
+Note: squeezelite must be started with the `-v` flag to enable visualizer support.
+
+### macOS
+Install [Soundflower](https://github.com/mattingalls/Soundflower) to create a loopback interface. Use Audio MIDI Setup to configure a virtual interface that outputs audio to both your speakers and the loopbacl interface, following [this](https://github.com/RogueAmoeba/Soundflower-Original/issues/44#issuecomment-151586106) recipe.
+
+Then edit your config to use this interface with portaudio:
+
+```
+method = portaudio
+source = "Soundflower (2ch)"
+```
+
+Note: cava looks no good in the default macOS terminal. For a better look install [kitty](https://sw.kovidgoyal.net/kitty/index.html). Be where that you might run into #109, that can be fixed like [this](https://stackoverflow.com/questions/7165108/in-os-x-lion-lang-is-not-set-to-utf-8-how-to-fix-it).
+
+
+Running via ssh
+---------------
+
+To run via ssh to an external monitor, redirect output to `/dev/console`:
+
+ ~# ./cava <> /dev/console >&0 2>&1
+
+exit with ctrl+z then run 'bg' to keep it running after you log out.
+
+(You must be root to redirect to console. Simple sudo is not enough: Run `sudo su` first.)
+
+### Raw Output
+
+You can also use Cava's output for other programs by using raw output mode, which will write bar data to `STDOUT` that can be piped into other processes. More information on this option is documented in [the example config file](/example_files/config).
+
+Font notes
+----------
+
+Since the graphics are simply based on characters, performance is dependent on the terminal font.
+
+### In ttys
+
+If you run this in a TTY the program will change the font to the included `cava.psf` (actually a slightly modified "unifont").
+
+In console fonts it seems that only 256 Unicode characters are supported, probably because they are bitmap fonts. I could not find a font with Unicode characters 2581-2587 (the 1/8 - 7/8 blocks used on the top of each bar to increase resolution).
+
+So in `cava.psf`, the characters 1-7 are actually replaced by Unicode characters 2581-2587. When cava exits, it changes the font back. If cava exits abnormally and you notice that 1-7 are replaced by partial blocks, just change the font with `setfont`.
+
+Actually, `setfont` is supposed to return the default font, but this usually isn't set. I haven't found another way to get the current font. So cava sets the font to "Lat2-Fixed16" when interrupted. All major distros should have it. It will revert to your default font at reboot.
+
+### In terminal emulators
+
+In terminal emulators like `xterm`, the font settings is chosen in the software and cannot be changed by an application. So find your terminal settings and try out different fonts and settings. Also character spacing affects the look of the bar spectrum.
+
+Performance is also different, urxvt is the best I found so far, while Gnome-terminal is quite slow.
+
+Cava also disables the terminal cursor, and turns it back on on exit, but in case it terminates unexpectedly, run `setterm -cursor on` to get it back.
+
+Tip: Cava will look much nicer in small font sizes. Use a second terminal emulator for cava and set the font size to 1. Warning, can cause high CPU usage and latency if the terminal window is too large!
+
+
+Latency notes
+-------------
+
+If you see latency issues (sound before image) in a terminal emulator, try increasing the font size. This will reduce the number of characters that have to be shown.
+
+If your audio device has a huge buffer, you might experience that cava is actually faster than the audio you hear. This reduces the experience of the visualization. To fix this, try decreasing the buffer settings in your audio playing software.
+
+Usage
+-----
+
+ Usage : cava [options]
+ Visualize audio input in terminal.
+
+ Options:
+ -p path to config file
+ -v print version
+
+
+
+Exit with ctrl+c or q.
+
+If cava quits unexpectedly or is force killed, echo must be turned on manually with `stty -echo`.
+
+### Controls
+
+NOTE: only works in ncurses output mode.
+
+| Key | Description |
+| --- | ----------- |
+| <kbd>up</kbd> / <kbd>down</kbd>| increase/decrease sensitivity |
+| <kbd>left</kbd> / <kbd>right</kbd>| increase/decrease bar width |
+| <kbd>f</kbd> / <kbd>b</kbd>| change foreground/background color |
+| <kbd>r</kbd> | Reload configuration |
+| <kbd>c</kbd> | Reload colors only |
+| <kbd>q</kbd> or <kbd>CTRL-C</kbd>| Quit C.A.V.A. |
+
+Configuration
+-------------
+
+As of version 0.4.0 all options are done in the config file, no more command-line arguments!
+
+By default a configuration file is located in `$XDG_CONFIG_HOME/cava/config` or `$HOME/.config/cava/config`, but cava can also be made to use a different file with the `-p` option.
+
+If for some reason the config file is not in the config dir, copy the [bundled configuration file](/example_files/config) to the config dir manually.
+
+Sending cava a SIGUSR1 signal, will force cava to reload its configuration file. Thus, it behaves as if the user pressed <kbd>r</kbd> in the terminal. One might send a SIGUSR1 signal using `pkill` or `killall`.
+For example:
+```
+$ pkill -USR1 cava
+```
+
+Similarly, sending cava a SIGUSR2 signal will only reload the colors from the configuration file, which is the same as pressing <kbd>c</kbd> in the terminal. This is slightly faster than reloading the entire config as the audio processing does not need to reinitialize.
+```
+$ pkill -USR2 cava
+```
+
+**Examples on how the equalizer works:**
+
+ [eq]
+ 1=0
+ 2=1
+ 3=0
+ 4=1
+ 5=0
+
+![3_138](https://cloud.githubusercontent.com/assets/6376571/8670183/a54a851e-29e8-11e5-9eff-346bf6ed91e0.png)
+
+ [eq]
+ 1=2
+ 2=2
+ 3=1
+ 4=1
+ 5=0.5
+
+![3_139](https://cloud.githubusercontent.com/assets/6376571/8670181/9db0ef50-29e8-11e5-81bc-3e2bb9892da0.png)
+
+Contribution
+------
+
+Please read CONTRIBUTING.md before opening a pull request.
+
+Thanks to:
+* [CelestialWalrus](https://github.com/CelestialWalrus)
+* [anko](https://github.com/anko)
+* [livibetter](https://github.com/livibetter)
+
+for major contributions in the early development of this project.
+
+Also thanks to [dpayne](https://github.com/dpayne/) for figuring out how to find the pulseaudio default sink name.
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..0638e7c
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+if [ -d .git ]; then
+ git describe --always --tags --dirty > version # get version from git
+else
+ echo 0.7.1 > version # hard coded versions
+fi
+
+libtoolize
+aclocal
+autoconf
+automake --add-missing
+
+CONFIGDIR=$XDG_CONFIG_HOME/cava
+
+if [ -z "$XDG_CONFIG_HOME" ]; then CONFIGDIR=$HOME/.config/cava; fi
+
+mkdir -p "$CONFIGDIR"
+[ -f "$CONFIGDIR"/config ] || cp example_files/config "$CONFIGDIR"/config
diff --git a/cava.c b/cava.c
new file mode 100644
index 0000000..45ec0c3
--- /dev/null
+++ b/cava.c
@@ -0,0 +1,1077 @@
+#include <locale.h>
+
+#ifdef HAVE_ALLOCA_H
+#include <alloca.h>
+#else
+#include <stdlib.h>
+#endif
+
+#include <fcntl.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <termios.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <fftw3.h>
+#include <getopt.h>
+#include <pthread.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "debug.h"
+#include "util.h"
+
+#ifdef NCURSES
+#include "output/terminal_bcircle.h"
+#include "output/terminal_ncurses.h"
+#include <curses.h>
+#endif
+
+#include "output/raw.h"
+#include "output/terminal_noncurses.h"
+
+#include "input/alsa.h"
+#include "input/common.h"
+#include "input/fifo.h"
+#include "input/portaudio.h"
+#include "input/pulse.h"
+#include "input/shmem.h"
+#include "input/sndio.h"
+
+#include "config.h"
+
+#ifdef __GNUC__
+// curses.h or other sources may already define
+#undef GCC_UNUSED
+#define GCC_UNUSED __attribute__((unused))
+#else
+#define GCC_UNUSED /* nothing */
+#endif
+
+#define LEFT_CHANNEL 1
+#define RIGHT_CHANNEL 2
+
+// struct termios oldtio, newtio;
+// int M = 8 * 1024;
+
+// used by sig handler
+// needs to know output mode in orer to clean up terminal
+int output_mode;
+// whether we should reload the config or not
+int should_reload = 0;
+// whether we should only reload colors or not
+int reload_colors = 0;
+
+// these variables are used only in main, but making them global
+// will allow us to not free them on exit without ASan complaining
+struct config_params p;
+
+fftw_complex *out_bass_l, *out_bass_r;
+fftw_plan p_bass_l, p_bass_r;
+fftw_complex *out_mid_l, *out_mid_r;
+fftw_plan p_mid_l, p_mid_r;
+fftw_complex *out_treble_l, *out_treble_r;
+fftw_plan p_treble_l, p_treble_r;
+
+// general: cleanup
+void cleanup(void) {
+ if (output_mode == OUTPUT_NCURSES) {
+#ifdef NCURSES
+ cleanup_terminal_ncurses();
+#else
+ ;
+#endif
+ } else if (output_mode == OUTPUT_NONCURSES) {
+ cleanup_terminal_noncurses();
+ }
+}
+
+// general: handle signals
+void sig_handler(int sig_no) {
+ if (sig_no == SIGUSR1) {
+ should_reload = 1;
+ return;
+ }
+
+ if (sig_no == SIGUSR2) {
+ reload_colors = 1;
+ return;
+ }
+
+ cleanup();
+ if (sig_no == SIGINT) {
+ printf("CTRL-C pressed -- goodbye\n");
+ }
+ signal(sig_no, SIG_DFL);
+ raise(sig_no);
+}
+
+#ifdef ALSA
+static bool is_loop_device_for_sure(const char *text) {
+ const char *const LOOPBACK_DEVICE_PREFIX = "hw:Loopback,";
+ return strncmp(text, LOOPBACK_DEVICE_PREFIX, strlen(LOOPBACK_DEVICE_PREFIX)) == 0;
+}
+
+static bool directory_exists(const char *path) {
+ DIR *const dir = opendir(path);
+ if (dir == NULL)
+ return false;
+
+ closedir(dir);
+ return true;
+}
+
+#endif
+
+int *separate_freq_bands(int FFTbassbufferSize, fftw_complex out_bass[FFTbassbufferSize / 2 + 1],
+ int FFTmidbufferSize, fftw_complex out_mid[FFTmidbufferSize / 2 + 1],
+ int FFTtreblebufferSize,
+ fftw_complex out_treble[FFTtreblebufferSize / 2 + 1], int bass_cut_off_bar,
+ int treble_cut_off_bar, int number_of_bars,
+ int FFTbuffer_lower_cut_off[256], int FFTbuffer_upper_cut_off[256],
+ double eq[256], int channel, double sens, double ignore) {
+ int n, i;
+ double peak[257];
+ static int bars_left[256];
+ static int bars_right[256];
+ double y[FFTbassbufferSize / 2 + 1];
+ double temp;
+
+ // process: separate frequency bands
+ for (n = 0; n < number_of_bars; n++) {
+
+ peak[n] = 0;
+ i = 0;
+
+ // process: get peaks
+ for (i = FFTbuffer_lower_cut_off[n]; i <= FFTbuffer_upper_cut_off[n]; i++) {
+ if (n <= bass_cut_off_bar) {
+ y[i] = hypot(out_bass[i][0], out_bass[i][1]);
+ } else if (n > bass_cut_off_bar && n <= treble_cut_off_bar) {
+ y[i] = hypot(out_mid[i][0], out_mid[i][1]);
+ } else if (n > treble_cut_off_bar) {
+ y[i] = hypot(out_treble[i][0], out_treble[i][1]);
+ }
+
+ peak[n] += y[i]; // adding upp band
+ }
+
+ peak[n] = peak[n] /
+ (FFTbuffer_upper_cut_off[n] - FFTbuffer_lower_cut_off[n] + 1); // getting average
+ temp = peak[n] * sens * eq[n]; // multiplying with k and sens
+ // printf("%d peak o: %f * sens: %f * k: %f = f: %f\n", o, peak[o], sens, eq[o], temp);
+ if (temp <= ignore)
+ temp = 0;
+ if (channel == LEFT_CHANNEL)
+ bars_left[n] = temp;
+ else
+ bars_right[n] = temp;
+ }
+
+ if (channel == LEFT_CHANNEL)
+ return bars_left;
+ else
+ return bars_right;
+}
+
+int *monstercat_filter(int *bars, int number_of_bars, int waves, double monstercat) {
+
+ int z;
+
+ // process [smoothing]: monstercat-style "average"
+
+ int m_y, de;
+ if (waves > 0) {
+ for (z = 0; z < number_of_bars; z++) { // waves
+ bars[z] = bars[z] / 1.25;
+ // if (bars[z] < 1) bars[z] = 1;
+ for (m_y = z - 1; m_y >= 0; m_y--) {
+ de = z - m_y;
+ bars[m_y] = max(bars[z] - pow(de, 2), bars[m_y]);
+ }
+ for (m_y = z + 1; m_y < number_of_bars; m_y++) {
+ de = m_y - z;
+ bars[m_y] = max(bars[z] - pow(de, 2), bars[m_y]);
+ }
+ }
+ } else if (monstercat > 0) {
+ for (z = 0; z < number_of_bars; z++) {
+ // if (bars[z] < 1)bars[z] = 1;
+ for (m_y = z - 1; m_y >= 0; m_y--) {
+ de = z - m_y;
+ bars[m_y] = max(bars[z] / pow(monstercat, de), bars[m_y]);
+ }
+ for (m_y = z + 1; m_y < number_of_bars; m_y++) {
+ de = m_y - z;
+ bars[m_y] = max(bars[z] / pow(monstercat, de), bars[m_y]);
+ }
+ }
+ }
+
+ return bars;
+}
+
+// general: entry point
+int main(int argc, char **argv) {
+
+ // general: define variables
+ pthread_t p_thread;
+ int thr_id GCC_UNUSED;
+ float cut_off_frequency[256];
+ float relative_cut_off[256];
+ int bars[256], FFTbuffer_lower_cut_off[256], FFTbuffer_upper_cut_off[256];
+ int *bars_left, *bars_right, *bars_mono;
+ int bars_mem[256];
+ int bars_last[256];
+ int previous_frame[256];
+ int sleep = 0;
+ int n, height, lines, width, c, rest, inAtty, fp, fptest, rc;
+ bool silence;
+ // int cont = 1;
+ int fall[256];
+ // float temp;
+ float bars_peak[256];
+ double eq[256];
+ float g;
+ struct timespec req = {.tv_sec = 0, .tv_nsec = 0};
+ char configPath[PATH_MAX];
+ char *usage = "\n\
+Usage : " PACKAGE " [options]\n\
+Visualize audio input in terminal. \n\
+\n\
+Options:\n\
+ -p path to config file\n\
+ -v print version\n\
+\n\
+Keys:\n\
+ Up Increase sensitivity\n\
+ Down Decrease sensitivity\n\
+ Left Decrease number of bars\n\
+ Right Increase number of bars\n\
+ r Reload config\n\
+ c Reload colors only\n\
+ f Cycle foreground color\n\
+ b Cycle background color\n\
+ q Quit\n\
+\n\
+as of 0.4.0 all options are specified in config file, see in '/home/username/.config/cava/' \n";
+
+ char ch = '\0';
+ int number_of_bars = 25;
+ int sourceIsAuto = 1;
+ double userEQ_keys_to_bars_ratio;
+
+ struct audio_data audio;
+ memset(&audio, 0, sizeof(audio));
+
+#ifndef NDEBUG
+ int maxvalue = 0;
+ int minvalue = 0;
+#endif
+ // general: console title
+ printf("%c]0;%s%c", '\033', PACKAGE, '\007');
+
+ configPath[0] = '\0';
+
+ // general: handle Ctrl+C
+ struct sigaction action;
+ memset(&action, 0, sizeof(action));
+ action.sa_handler = &sig_handler;
+ sigaction(SIGINT, &action, NULL);
+ sigaction(SIGTERM, &action, NULL);
+ sigaction(SIGUSR1, &action, NULL);
+ sigaction(SIGUSR2, &action, NULL);
+
+ // general: handle command-line arguments
+ while ((c = getopt(argc, argv, "p:vh")) != -1) {
+ switch (c) {
+ case 'p': // argument: fifo path
+ snprintf(configPath, sizeof(configPath), "%s", optarg);
+ break;
+ case 'h': // argument: print usage
+ printf("%s", usage);
+ return 1;
+ case '?': // argument: print usage
+ printf("%s", usage);
+ return 1;
+ case 'v': // argument: print version
+ printf(PACKAGE " " VERSION "\n");
+ return 0;
+ default: // argument: no arguments; exit
+ abort();
+ }
+
+ n = 0;
+ }
+
+ // general: main loop
+ while (1) {
+
+ debug("loading config\n");
+ // config: load
+ struct error_s error;
+ error.length = 0;
+ if (!load_config(configPath, &p, 0, &error)) {
+ fprintf(stderr, "Error loading config. %s", error.message);
+ exit(EXIT_FAILURE);
+ }
+
+ output_mode = p.om;
+
+ if (output_mode != OUTPUT_RAW) {
+ // Check if we're running in a tty
+ inAtty = 0;
+ if (strncmp(ttyname(0), "/dev/tty", 8) == 0 || strcmp(ttyname(0), "/dev/console") == 0)
+ inAtty = 1;
+
+ // in macos vitual terminals are called ttys(xyz) and there are no ttys
+ if (strncmp(ttyname(0), "/dev/ttys", 9) == 0)
+ inAtty = 0;
+ if (inAtty) {
+ system("setfont cava.psf >/dev/null 2>&1");
+ system("setterm -blank 0");
+ }
+
+ // We use unicode block characters to draw the bars and
+ // the locale var LANG must be set to use unicode chars.
+ // For some reason this var can't be retrieved with
+ // setlocale(LANG, NULL), so we get it with getenv.
+ // Also we can't set it with setlocale(LANG "") so we
+ // must set LC_ALL instead.
+ // Attempting to set to en_US if not set, if that lang
+ // is not installed and LANG is not set there will be
+ // no output, for mor info see #109 #344
+ if (!getenv("LANG"))
+ setlocale(LC_ALL, "en_US.utf8");
+ else
+ setlocale(LC_ALL, "");
+ }
+
+ // input: init
+ int bass_cut_off = 150;
+ int treble_cut_off = 1500;
+
+ audio.source = malloc(1 + strlen(p.audio_source));
+ strcpy(audio.source, p.audio_source);
+
+ audio.format = -1;
+ audio.rate = 0;
+ audio.FFTbassbufferSize = 4096;
+ audio.FFTmidbufferSize = 1024;
+ audio.FFTtreblebufferSize = 512;
+ audio.terminate = 0;
+ if (p.stereo)
+ audio.channels = 2;
+ if (!p.stereo)
+ audio.channels = 1;
+ audio.average = false;
+ audio.left = false;
+ audio.right = false;
+ if (strcmp(p.mono_option, "average") == 0)
+ audio.average = true;
+ if (strcmp(p.mono_option, "left") == 0)
+ audio.left = true;
+ if (strcmp(p.mono_option, "right") == 0)
+ audio.right = true;
+ audio.bass_index = 0;
+ audio.mid_index = 0;
+ audio.treble_index = 0;
+
+ // BASS
+ // audio.FFTbassbufferSize = audio.rate / 20; // audio.FFTbassbufferSize;
+
+ audio.in_bass_r = fftw_alloc_real(2 * (audio.FFTbassbufferSize / 2 + 1));
+ audio.in_bass_l = fftw_alloc_real(2 * (audio.FFTbassbufferSize / 2 + 1));
+ memset(audio.in_bass_r, 0, 2 * (audio.FFTbassbufferSize / 2 + 1) * sizeof(double));
+ memset(audio.in_bass_l, 0, 2 * (audio.FFTbassbufferSize / 2 + 1) * sizeof(double));
+
+ out_bass_l = fftw_alloc_complex(2 * (audio.FFTbassbufferSize / 2 + 1));
+ out_bass_r = fftw_alloc_complex(2 * (audio.FFTbassbufferSize / 2 + 1));
+ memset(out_bass_l, 0, 2 * (audio.FFTbassbufferSize / 2 + 1) * sizeof(fftw_complex));
+ memset(out_bass_r, 0, 2 * (audio.FFTbassbufferSize / 2 + 1) * sizeof(fftw_complex));
+
+ p_bass_l = fftw_plan_dft_r2c_1d(audio.FFTbassbufferSize, audio.in_bass_l, out_bass_l,
+ FFTW_MEASURE);
+ p_bass_r = fftw_plan_dft_r2c_1d(audio.FFTbassbufferSize, audio.in_bass_r, out_bass_r,
+ FFTW_MEASURE);
+
+ // MID
+ // audio.FFTmidbufferSize = audio.rate / bass_cut_off; // audio.FFTbassbufferSize;
+ audio.in_mid_r = fftw_alloc_real(2 * (audio.FFTmidbufferSize / 2 + 1));
+ audio.in_mid_l = fftw_alloc_real(2 * (audio.FFTmidbufferSize / 2 + 1));
+ memset(audio.in_mid_r, 0, 2 * (audio.FFTmidbufferSize / 2 + 1) * sizeof(double));
+ memset(audio.in_mid_l, 0, 2 * (audio.FFTmidbufferSize / 2 + 1) * sizeof(double));
+
+ out_mid_l = fftw_alloc_complex(2 * (audio.FFTmidbufferSize / 2 + 1));
+ out_mid_r = fftw_alloc_complex(2 * (audio.FFTmidbufferSize / 2 + 1));
+ memset(out_mid_l, 0, 2 * (audio.FFTmidbufferSize / 2 + 1) * sizeof(fftw_complex));
+ memset(out_mid_r, 0, 2 * (audio.FFTmidbufferSize / 2 + 1) * sizeof(fftw_complex));
+
+ p_mid_l =
+ fftw_plan_dft_r2c_1d(audio.FFTmidbufferSize, audio.in_mid_l, out_mid_l, FFTW_MEASURE);
+ p_mid_r =
+ fftw_plan_dft_r2c_1d(audio.FFTmidbufferSize, audio.in_mid_r, out_mid_r, FFTW_MEASURE);
+
+ // TRIEBLE
+ // audio.FFTtreblebufferSize = audio.rate / treble_cut_off; // audio.FFTbassbufferSize;
+ audio.in_treble_r = fftw_alloc_real(2 * (audio.FFTtreblebufferSize / 2 + 1));
+ audio.in_treble_l = fftw_alloc_real(2 * (audio.FFTtreblebufferSize / 2 + 1));
+ memset(audio.in_treble_r, 0, 2 * (audio.FFTtreblebufferSize / 2 + 1) * sizeof(double));
+ memset(audio.in_treble_l, 0, 2 * (audio.FFTtreblebufferSize / 2 + 1) * sizeof(double));
+
+ out_treble_l = fftw_alloc_complex(2 * (audio.FFTtreblebufferSize / 2 + 1));
+ out_treble_r = fftw_alloc_complex(2 * (audio.FFTtreblebufferSize / 2 + 1));
+ memset(out_treble_l, 0, 2 * (audio.FFTtreblebufferSize / 2 + 1) * sizeof(fftw_complex));
+ memset(out_treble_r, 0, 2 * (audio.FFTtreblebufferSize / 2 + 1) * sizeof(fftw_complex));
+
+ p_treble_l = fftw_plan_dft_r2c_1d(audio.FFTtreblebufferSize, audio.in_treble_l,
+ out_treble_l, FFTW_MEASURE);
+ p_treble_r = fftw_plan_dft_r2c_1d(audio.FFTtreblebufferSize, audio.in_treble_r,
+ out_treble_r, FFTW_MEASURE);
+
+ debug("got buffer size: %d, %d, %d", audio.FFTbassbufferSize, audio.FFTmidbufferSize,
+ audio.FFTtreblebufferSize);
+
+ reset_output_buffers(&audio);
+
+ debug("starting audio thread\n");
+ switch (p.im) {
+#ifdef ALSA
+ case INPUT_ALSA:
+ // input_alsa: wait for the input to be ready
+ if (is_loop_device_for_sure(audio.source)) {
+ if (directory_exists("/sys/")) {
+ if (!directory_exists("/sys/module/snd_aloop/")) {
+ cleanup();
+ fprintf(stderr,
+ "Linux kernel module \"snd_aloop\" does not seem to be loaded.\n"
+ "Maybe run \"sudo modprobe snd_aloop\".\n");
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+
+ thr_id = pthread_create(&p_thread, NULL, input_alsa,
+ (void *)&audio); // starting alsamusic listener
+
+ n = 0;
+
+ while (audio.format == -1 || audio.rate == 0) {
+ req.tv_sec = 0;
+ req.tv_nsec = 1000000;
+ nanosleep(&req, NULL);
+ n++;
+ if (n > 2000) {
+ cleanup();
+ fprintf(stderr, "could not get rate and/or format, problems with audio thread? "
+ "quiting...\n");
+ exit(EXIT_FAILURE);
+ }
+ }
+ debug("got format: %d and rate %d\n", audio.format, audio.rate);
+ break;
+#endif
+ case INPUT_FIFO:
+ // starting fifomusic listener
+ thr_id = pthread_create(&p_thread, NULL, input_fifo, (void *)&audio);
+ audio.rate = p.fifoSample;
+ audio.format = p.fifoSampleBits;
+ break;
+#ifdef PULSE
+ case INPUT_PULSE:
+ if (strcmp(audio.source, "auto") == 0) {
+ getPulseDefaultSink((void *)&audio);
+ sourceIsAuto = 1;
+ } else
+ sourceIsAuto = 0;
+ // starting pulsemusic listener
+ thr_id = pthread_create(&p_thread, NULL, input_pulse, (void *)&audio);
+ audio.rate = 44100;
+ break;
+#endif
+#ifdef SNDIO
+ case INPUT_SNDIO:
+ thr_id = pthread_create(&p_thread, NULL, input_sndio, (void *)&audio);
+ audio.rate = 44100;
+ break;
+#endif
+ case INPUT_SHMEM:
+ thr_id = pthread_create(&p_thread, NULL, input_shmem, (void *)&audio);
+ // audio.rate = 44100;
+ break;
+#ifdef PORTAUDIO
+ case INPUT_PORTAUDIO:
+ thr_id = pthread_create(&p_thread, NULL, input_portaudio, (void *)&audio);
+ audio.rate = 44100;
+ break;
+#endif
+ default:
+ exit(EXIT_FAILURE); // Can't happen.
+ }
+
+ if (p.upper_cut_off > audio.rate / 2) {
+ cleanup();
+ fprintf(stderr, "higher cuttoff frequency can't be higher then sample rate / 2");
+ exit(EXIT_FAILURE);
+ }
+
+ bool reloadConf = false;
+
+ while (!reloadConf) { // jumbing back to this loop means that you resized the screen
+ for (n = 0; n < 256; n++) {
+ bars_last[n] = 0;
+ previous_frame[n] = 0;
+ fall[n] = 0;
+ bars_peak[n] = 0;
+ bars_mem[n] = 0;
+ bars[n] = 0;
+ }
+
+ switch (output_mode) {
+#ifdef NCURSES
+ // output: start ncurses mode
+ case OUTPUT_NCURSES:
+ init_terminal_ncurses(p.color, p.bcolor, p.col, p.bgcol, p.gradient,
+ p.gradient_count, p.gradient_colors, &width, &lines);
+ // we have 8 times as much height due to using 1/8 block characters
+ height = lines * 8;
+ break;
+#endif
+ case OUTPUT_NONCURSES:
+ get_terminal_dim_noncurses(&width, &lines);
+ init_terminal_noncurses(inAtty, p.col, p.bgcol, width, lines, p.bar_width);
+ height = (lines - 1) * 8;
+ break;
+
+ case OUTPUT_RAW:
+ if (strcmp(p.raw_target, "/dev/stdout") != 0) {
+ // checking if file exists
+ if (access(p.raw_target, F_OK) != -1) {
+ // testopening in case it's a fifo
+ fptest = open(p.raw_target, O_RDONLY | O_NONBLOCK, 0644);
+
+ if (fptest == -1) {
+ printf("could not open file %s for writing\n", p.raw_target);
+ exit(1);
+ }
+ } else {
+ printf("creating fifo %s\n", p.raw_target);
+ if (mkfifo(p.raw_target, 0664) == -1) {
+ printf("could not create fifo %s\n", p.raw_target);
+ exit(1);
+ }
+ // fifo needs to be open for reading in order to write to it
+ fptest = open(p.raw_target, O_RDONLY | O_NONBLOCK, 0644);
+ }
+ }
+
+ fp = open(p.raw_target, O_WRONLY | O_NONBLOCK | O_CREAT, 0644);
+ if (fp == -1) {
+ printf("could not open file %s for writing\n", p.raw_target);
+ exit(1);
+ }
+ printf("open file %s for writing raw output\n", p.raw_target);
+
+ // width must be hardcoded for raw output.
+ width = 256;
+
+ if (strcmp(p.data_format, "binary") == 0) {
+ height = pow(2, p.bit_format) - 1;
+ } else {
+ height = p.ascii_range;
+ }
+ break;
+
+ default:
+ exit(EXIT_FAILURE); // Can't happen.
+ }
+
+ // handle for user setting too many bars
+ if (p.fixedbars) {
+ p.autobars = 0;
+ if (p.fixedbars * p.bar_width + p.fixedbars * p.bar_spacing - p.bar_spacing > width)
+ p.autobars = 1;
+ }
+
+ // getting orignial numbers of barss incase of resize
+ if (p.autobars == 1) {
+ number_of_bars = (width + p.bar_spacing) / (p.bar_width + p.bar_spacing);
+ // if (p.bar_spacing != 0) number_of_bars = (width - number_of_bars * p.bar_spacing
+ // + p.bar_spacing) / bar_width;
+ } else
+ number_of_bars = p.fixedbars;
+
+ if (number_of_bars < 1)
+ number_of_bars = 1; // must have at least 1 bars
+ if (number_of_bars > 256)
+ number_of_bars = 256; // cant have more than 256 bars
+
+ if (p.stereo) { // stereo must have even numbers of bars
+ if (number_of_bars % 2 != 0)
+ number_of_bars--;
+ }
+
+ // checks if there is stil extra room, will use this to center
+ rest = (width - number_of_bars * p.bar_width - number_of_bars * p.bar_spacing +
+ p.bar_spacing) /
+ 2;
+ if (rest < 0)
+ rest = 0;
+
+ // process [smoothing]: calculate gravity
+ g = p.gravity * ((float)height / 2160) * pow((60 / (float)p.framerate), 2.5);
+
+ // calculate integral value, must be reduced with height
+ double integral = p.integral;
+ if (height > 320)
+ integral = p.integral * 1 / sqrt((log10((float)height / 10)));
+
+#ifndef NDEBUG
+ debug("height: %d width: %d bars:%d bar width: %d rest: %d\n", height, width,
+ number_of_bars, p.bar_width, rest);
+#endif
+
+ if (p.stereo)
+ number_of_bars =
+ number_of_bars / 2; // in stereo onle half number of number_of_bars per channel
+
+ if (p.userEQ_enabled && (number_of_bars > 0)) {
+ userEQ_keys_to_bars_ratio =
+ (double)(((double)p.userEQ_keys) / ((double)number_of_bars));
+ }
+
+ // calculate frequency constant (used to distribute bars across the frequency band)
+ double frequency_constant = log10((float)p.lower_cut_off / (float)p.upper_cut_off) /
+ (1 / ((float)number_of_bars + 1) - 1);
+
+ // process: calculate cutoff frequencies and eq
+ int bass_cut_off_bar = -1;
+ int treble_cut_off_bar = -1;
+ bool first_bar = false;
+ int first_treble_bar = 0;
+
+ for (n = 0; n < number_of_bars + 1; n++) {
+ double bar_distribution_coefficient = frequency_constant * (-1);
+ bar_distribution_coefficient +=
+ ((float)n + 1) / ((float)number_of_bars + 1) * frequency_constant;
+ cut_off_frequency[n] = p.upper_cut_off * pow(10, bar_distribution_coefficient);
+ relative_cut_off[n] = cut_off_frequency[n] / (audio.rate / 2);
+ // remember nyquist!, pr my calculations this should be rate/2
+ // and nyquist freq in M/2 but testing shows it is not...
+ // or maybe the nq freq is in M/4
+
+ eq[n] = pow(cut_off_frequency[n], 1);
+ eq[n] *= (float)height / pow(2, 28);
+ if (p.userEQ_enabled)
+ eq[n] *= p.userEQ[(int)floor(((double)n) * userEQ_keys_to_bars_ratio)];
+
+ eq[n] /= log2(audio.FFTbassbufferSize);
+
+ if (cut_off_frequency[n] < bass_cut_off) {
+ // BASS
+ FFTbuffer_lower_cut_off[n] =
+ relative_cut_off[n] * (audio.FFTbassbufferSize / 2) + 1;
+ bass_cut_off_bar++;
+ treble_cut_off_bar++;
+
+ eq[n] *= log2(audio.FFTbassbufferSize);
+ } else if (cut_off_frequency[n] > bass_cut_off &&
+ cut_off_frequency[n] < treble_cut_off) {
+ // MID
+ FFTbuffer_lower_cut_off[n] =
+ relative_cut_off[n] * (audio.FFTmidbufferSize / 2) + 1;
+ treble_cut_off_bar++;
+ if ((treble_cut_off_bar - bass_cut_off_bar) == 1) {
+ first_bar = true;
+ FFTbuffer_upper_cut_off[n - 1] =
+ relative_cut_off[n] * (audio.FFTbassbufferSize / 2);
+ if (FFTbuffer_upper_cut_off[n - 1] < FFTbuffer_lower_cut_off[n - 1])
+ FFTbuffer_upper_cut_off[n - 1] = FFTbuffer_lower_cut_off[n - 1];
+ } else {
+ first_bar = false;
+ }
+
+ eq[n] *= log2(audio.FFTmidbufferSize);
+ } else {
+ // TREBLE
+ FFTbuffer_lower_cut_off[n] =
+ relative_cut_off[n] * (audio.FFTtreblebufferSize / 2) + 1;
+ first_treble_bar++;
+ if (first_treble_bar == 1) {
+ first_bar = true;
+ FFTbuffer_upper_cut_off[n - 1] =
+ relative_cut_off[n] * (audio.FFTmidbufferSize / 2);
+ if (FFTbuffer_upper_cut_off[n - 1] < FFTbuffer_lower_cut_off[n - 1])
+ FFTbuffer_upper_cut_off[n - 1] = FFTbuffer_lower_cut_off[n - 1];
+ } else {
+ first_bar = false;
+ }
+
+ eq[n] *= log2(audio.FFTtreblebufferSize);
+ }
+
+ if (n != 0 && !first_bar) {
+ FFTbuffer_upper_cut_off[n - 1] = FFTbuffer_lower_cut_off[n] - 1;
+
+ // pushing the spectrum up if the exponential function gets "clumped" in the
+ // bass
+ if (FFTbuffer_lower_cut_off[n] <= FFTbuffer_lower_cut_off[n - 1])
+ FFTbuffer_lower_cut_off[n] = FFTbuffer_lower_cut_off[n - 1] + 1;
+ FFTbuffer_upper_cut_off[n - 1] = FFTbuffer_lower_cut_off[n] - 1;
+ }
+
+#ifndef NDEBUG
+ initscr();
+ curs_set(0);
+ timeout(0);
+ if (n != 0) {
+ mvprintw(n, 0, "%d: %f -> %f (%d -> %d) bass: %d, treble:%d \n", n,
+ cut_off_frequency[n - 1], cut_off_frequency[n],
+ FFTbuffer_lower_cut_off[n - 1], FFTbuffer_upper_cut_off[n - 1],
+ bass_cut_off_bar, treble_cut_off_bar);
+ }
+#endif
+ }
+
+ if (p.stereo)
+ number_of_bars = number_of_bars * 2;
+
+ bool resizeTerminal = false;
+
+ while (!resizeTerminal) {
+
+// general: keyboard controls
+#ifdef NCURSES
+ if (output_mode == OUTPUT_NCURSES)
+ ch = getch();
+#endif
+
+ switch (ch) {
+ case 65: // key up
+ p.sens = p.sens * 1.05;
+ break;
+ case 66: // key down
+ p.sens = p.sens * 0.95;
+ break;
+ case 68: // key right
+ p.bar_width++;
+ resizeTerminal = true;
+ break;
+ case 67: // key left
+ if (p.bar_width > 1)
+ p.bar_width--;
+ resizeTerminal = true;
+ break;
+ case 'r': // reload config
+ should_reload = 1;
+ break;
+ case 'c': // reload colors
+ reload_colors = 1;
+ break;
+ case 'f': // change forground color
+ if (p.col < 7)
+ p.col++;
+ else
+ p.col = 0;
+ resizeTerminal = true;
+ break;
+ case 'b': // change backround color
+ if (p.bgcol < 7)
+ p.bgcol++;
+ else
+ p.bgcol = 0;
+ resizeTerminal = true;
+ break;
+
+ case 'q':
+ if (sourceIsAuto)
+ free(audio.source);
+ cleanup();
+ return EXIT_SUCCESS;
+ }
+
+ if (should_reload) {
+
+ reloadConf = true;
+ resizeTerminal = true;
+ should_reload = 0;
+ }
+
+ if (reload_colors) {
+ struct error_s error;
+ error.length = 0;
+ if (!load_config(configPath, (void *)&p, 1, &error)) {
+ cleanup();
+ fprintf(stderr, "Error loading config. %s", error.message);
+ exit(EXIT_FAILURE);
+ }
+ resizeTerminal = true;
+ reload_colors = 0;
+ }
+
+ // if (cont == 0) break;
+
+#ifndef NDEBUG
+ // clear();
+ refresh();
+#endif
+
+ // process: check if input is present
+ silence = true;
+
+ for (n = 0; n < audio.FFTbassbufferSize; n++) {
+ if (audio.in_bass_l[n] || audio.in_bass_r[n]) {
+ silence = false;
+ break;
+ }
+ }
+
+ if (silence)
+ sleep++;
+ else
+ sleep = 0;
+
+ // process: if input was present for the last 5 seconds apply FFT to it
+ if (sleep < p.framerate * 5) {
+
+ // process: execute FFT and sort frequency bands
+ if (p.stereo) {
+ fftw_execute(p_bass_l);
+ fftw_execute(p_bass_r);
+ fftw_execute(p_mid_l);
+ fftw_execute(p_mid_r);
+ fftw_execute(p_treble_l);
+ fftw_execute(p_treble_r);
+
+ bars_left = separate_freq_bands(
+ audio.FFTbassbufferSize, out_bass_l, audio.FFTmidbufferSize, out_mid_l,
+ audio.FFTtreblebufferSize, out_treble_l, bass_cut_off_bar,
+ treble_cut_off_bar, number_of_bars / 2, FFTbuffer_lower_cut_off,
+ FFTbuffer_upper_cut_off, eq, LEFT_CHANNEL, p.sens, p.ignore);
+
+ bars_right = separate_freq_bands(
+ audio.FFTbassbufferSize, out_bass_r, audio.FFTmidbufferSize, out_mid_r,
+ audio.FFTtreblebufferSize, out_treble_r, bass_cut_off_bar,
+ treble_cut_off_bar, number_of_bars / 2, FFTbuffer_lower_cut_off,
+ FFTbuffer_upper_cut_off, eq, RIGHT_CHANNEL, p.sens, p.ignore);
+
+ } else {
+ fftw_execute(p_bass_l);
+ fftw_execute(p_mid_l);
+ fftw_execute(p_treble_l);
+ bars_mono = separate_freq_bands(
+ audio.FFTbassbufferSize, out_bass_l, audio.FFTmidbufferSize, out_mid_l,
+ audio.FFTtreblebufferSize, out_treble_l, bass_cut_off_bar,
+ treble_cut_off_bar, number_of_bars, FFTbuffer_lower_cut_off,
+ FFTbuffer_upper_cut_off, eq, LEFT_CHANNEL, p.sens, p.ignore);
+ }
+
+ } else { //**if in sleep mode wait and continue**//
+#ifndef NDEBUG
+ printw("no sound detected for 5 sec, going to sleep mode\n");
+#endif
+ // wait 0.1 sec, then check sound again.
+ req.tv_sec = 0;
+ req.tv_nsec = 100000000;
+ nanosleep(&req, NULL);
+ continue;
+ }
+
+ // process [filter]
+
+ if (p.monstercat) {
+ if (p.stereo) {
+ bars_left =
+ monstercat_filter(bars_left, number_of_bars / 2, p.waves, p.monstercat);
+ bars_right = monstercat_filter(bars_right, number_of_bars / 2, p.waves,
+ p.monstercat);
+ } else {
+ bars_mono =
+ monstercat_filter(bars_mono, number_of_bars, p.waves, p.monstercat);
+ }
+ }
+
+ // processing signal
+
+ bool senselow = true;
+
+ for (n = 0; n < number_of_bars; n++) {
+ // mirroring stereo channels
+ if (p.stereo) {
+ if (n < number_of_bars / 2) {
+ bars[n] = bars_left[number_of_bars / 2 - n - 1];
+ } else {
+ bars[n] = bars_right[n - number_of_bars / 2];
+ }
+
+ } else {
+ bars[n] = bars_mono[n];
+ }
+
+ // process [smoothing]: falloff
+ if (g > 0) {
+ if (bars[n] < bars_last[n]) {
+ bars[n] = bars_peak[n] - (g * fall[n] * fall[n]);
+ if (bars[n] < 0)
+ bars[n] = 0;
+ fall[n]++;
+ } else {
+ bars_peak[n] = bars[n];
+ fall[n] = 0;
+ }
+
+ bars_last[n] = bars[n];
+ }
+
+ // process [smoothing]: integral
+ if (p.integral > 0) {
+ bars[n] = bars_mem[n] * integral + bars[n];
+ bars_mem[n] = bars[n];
+
+ int diff = height - bars[n];
+ if (diff < 0)
+ diff = 0;
+ double div = 1 / (diff + 1);
+ // bars[n] = bars[n] - pow(div, 10) * (height + 1);
+ bars_mem[n] = bars_mem[n] * (1 - div / 20);
+ }
+#ifndef NDEBUG
+ mvprintw(n, 0, "%d: f:%f->%f (%d->%d), eq:\
+ %15e, peak:%d \n",
+ n, cut_off_frequency[n], cut_off_frequency[n + 1],
+ FFTbuffer_lower_cut_off[n], FFTbuffer_upper_cut_off[n], eq[n],
+ bars[n]);
+
+ if (bars[n] < minvalue) {
+ minvalue = bars[n];
+ debug("min value: %d\n", minvalue); // checking maxvalue 10000
+ }
+ if (bars[n] > maxvalue) {
+ maxvalue = bars[n];
+ }
+ if (bars[n] < 0) {
+ debug("negative bar value!! %d\n", bars[n]);
+ // exit(EXIT_FAILURE); // Can't happen.
+ }
+
+#endif
+
+ // zero values causes divided by zero segfault (if not raw)
+ if (output_mode != OUTPUT_RAW && bars[n] < 1)
+ bars[n] = 1;
+
+ // autmatic sens adjustment
+ if (p.autosens) {
+ if (bars[n] > height && senselow) {
+ p.sens = p.sens * 0.98;
+ senselow = false;
+ }
+ }
+ }
+
+ if (p.autosens && !silence && senselow)
+ p.sens = p.sens * 1.001;
+
+#ifndef NDEBUG
+ mvprintw(n + 1, 0, "sensitivity %.10e", p.sens);
+ mvprintw(n + 2, 0, "min value: %d\n", minvalue); // checking maxvalue 10000
+ mvprintw(n + 3, 0, "max value: %d\n", maxvalue); // checking maxvalue 10000
+#endif
+
+// output: draw processed input
+#ifdef NDEBUG
+ switch (output_mode) {
+ case OUTPUT_NCURSES:
+#ifdef NCURSES
+ rc = draw_terminal_ncurses(inAtty, lines, width, number_of_bars, p.bar_width,
+ p.bar_spacing, rest, bars, previous_frame,
+ p.gradient);
+ break;
+#endif
+ case OUTPUT_NONCURSES:
+ rc = draw_terminal_noncurses(inAtty, lines, width, number_of_bars, p.bar_width,
+ p.bar_spacing, rest, bars, previous_frame);
+ break;
+ case OUTPUT_RAW:
+ rc = print_raw_out(number_of_bars, fp, p.is_bin, p.bit_format, p.ascii_range,
+ p.bar_delim, p.frame_delim, bars);
+ break;
+
+ default:
+ exit(EXIT_FAILURE); // Can't happen.
+ }
+
+ // terminal has been resized breaking to recalibrating values
+ if (rc == -1)
+ resizeTerminal = true;
+
+#endif
+ if (p.framerate <= 1) {
+ req.tv_sec = 1 / (float)p.framerate;
+ } else {
+ req.tv_sec = 0;
+ req.tv_nsec = (1 / (float)p.framerate) * 1000000000;
+ }
+
+ nanosleep(&req, NULL);
+
+ memcpy(previous_frame, bars, 256 * sizeof(int));
+
+ // checking if audio thread has exited unexpectedly
+ if (audio.terminate == 1) {
+ cleanup();
+ fprintf(stderr, "Audio thread exited unexpectedly. %s\n", audio.error_message);
+ exit(EXIT_FAILURE);
+ }
+
+ } // resize terminal
+
+ } // reloading config
+ req.tv_sec = 0;
+ req.tv_nsec = 100; // waiting some time to make shure audio is ready
+ nanosleep(&req, NULL);
+
+ //**telling audio thread to terminate**//
+ audio.terminate = 1;
+ pthread_join(p_thread, NULL);
+
+ if (p.userEQ_enabled)
+ free(p.userEQ);
+ if (sourceIsAuto)
+ free(audio.source);
+
+ fftw_free(audio.in_bass_r);
+ fftw_free(audio.in_bass_l);
+ fftw_free(out_bass_r);
+ fftw_free(out_bass_l);
+ fftw_destroy_plan(p_bass_l);
+ fftw_destroy_plan(p_bass_r);
+
+ fftw_free(audio.in_mid_r);
+ fftw_free(audio.in_mid_l);
+ fftw_free(out_mid_r);
+ fftw_free(out_mid_l);
+ fftw_destroy_plan(p_mid_l);
+ fftw_destroy_plan(p_mid_r);
+
+ fftw_free(audio.in_treble_r);
+ fftw_free(audio.in_treble_l);
+ fftw_free(out_treble_r);
+ fftw_free(out_treble_l);
+ fftw_destroy_plan(p_treble_l);
+ fftw_destroy_plan(p_treble_r);
+
+ cleanup();
+
+ // fclose(fp);
+ }
+}
diff --git a/cava.psf b/cava.psf
new file mode 100644
index 0000000..40e1365
--- /dev/null
+++ b/cava.psf
Binary files differ
diff --git a/config.c b/config.c
new file mode 100644
index 0000000..30a1bf2
--- /dev/null
+++ b/config.c
@@ -0,0 +1,539 @@
+#include "config.h"
+#include "util.h"
+
+#include <ctype.h>
+#include <iniparser.h>
+#include <math.h>
+
+#ifdef SNDIO
+#include <sndio.h>
+#endif
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <sys/stat.h>
+
+double smoothDef[5] = {1, 1, 1, 1, 1};
+
+enum input_method default_methods[] = {
+ INPUT_FIFO,
+ INPUT_PORTAUDIO,
+ INPUT_ALSA,
+ INPUT_PULSE,
+};
+
+char *outputMethod, *channels;
+
+const char *input_method_names[] = {
+ "fifo", "portaudio", "alsa", "pulse", "sndio", "shmem",
+};
+
+const bool has_input_method[] = {
+ true, /** Always have at least FIFO and shmem input. */
+ HAS_PORTAUDIO, HAS_ALSA, HAS_PULSE, HAS_SNDIO, true,
+};
+
+enum input_method input_method_by_name(const char *str) {
+ for (int i = 0; i < INPUT_MAX; i++) {
+ if (!strcmp(str, input_method_names[i])) {
+ return (enum input_method)i;
+ }
+ }
+
+ return INPUT_MAX;
+}
+
+void write_errorf(void *err, const char *fmt, ...) {
+ struct error_s *error = (struct error_s *)err;
+ va_list args;
+ va_start(args, fmt);
+ error->length +=
+ vsnprintf((char *)error->message + error->length, MAX_ERROR_LEN - error->length, fmt, args);
+ va_end(args);
+}
+
+int validate_color(char *checkColor, int om, void *err) {
+ struct error_s *error = (struct error_s *)err;
+ int validColor = 0;
+ if (checkColor[0] == '#' && strlen(checkColor) == 7) {
+ // If the output mode is not ncurses, tell the user to use a named colour instead of hex
+ // colours.
+ if (om != OUTPUT_NCURSES) {
+ write_errorf(
+ error, "Only 'ncurses' output method supports HTML colors. Please change "
+ "the colours or the output method.\nAs of version 0.7.0 ncurses is no longer"
+ " the default output method\n");
+ return 0;
+ }
+ // 0 to 9 and a to f
+ for (int i = 1; checkColor[i]; ++i) {
+ if (!isdigit(checkColor[i])) {
+ if (tolower(checkColor[i]) >= 'a' && tolower(checkColor[i]) <= 'f') {
+ validColor = 1;
+ } else {
+ validColor = 0;
+ break;
+ }
+ } else {
+ validColor = 1;
+ }
+ }
+ } else {
+ if ((strcmp(checkColor, "black") == 0) || (strcmp(checkColor, "red") == 0) ||
+ (strcmp(checkColor, "green") == 0) || (strcmp(checkColor, "yellow") == 0) ||
+ (strcmp(checkColor, "blue") == 0) || (strcmp(checkColor, "magenta") == 0) ||
+ (strcmp(checkColor, "cyan") == 0) || (strcmp(checkColor, "white") == 0) ||
+ (strcmp(checkColor, "default") == 0))
+ validColor = 1;
+ }
+ return validColor;
+}
+
+bool validate_colors(void *params, void *err) {
+ struct config_params *p = (struct config_params *)params;
+ struct error_s *error = (struct error_s *)err;
+
+ // validate: color
+ if (!validate_color(p->color, p->om, error)) {
+ write_errorf(error, "The value for 'foreground' is invalid. It can be either one of the 7 "
+ "named colors or a HTML color of the form '#xxxxxx'.\n");
+ return false;
+ }
+
+ // validate: background color
+ if (!validate_color(p->bcolor, p->om, error)) {
+ write_errorf(error, "The value for 'background' is invalid. It can be either one of the 7 "
+ "named colors or a HTML color of the form '#xxxxxx'.\n");
+ return false;
+ }
+
+ if (p->gradient) {
+ for (int i = 0; i < p->gradient_count; i++) {
+ if (!validate_color(p->gradient_colors[i], p->om, error)) {
+ write_errorf(
+ error,
+ "Gradient color %d is invalid. It must be HTML color of the form '#xxxxxx'.\n",
+ i + 1);
+ return false;
+ }
+ }
+ }
+
+ // In case color is not html format set bgcol and col to predefinedint values
+ p->col = -1;
+ if (strcmp(p->color, "black") == 0)
+ p->col = 0;
+ if (strcmp(p->color, "red") == 0)
+ p->col = 1;
+ if (strcmp(p->color, "green") == 0)
+ p->col = 2;
+ if (strcmp(p->color, "yellow") == 0)
+ p->col = 3;
+ if (strcmp(p->color, "blue") == 0)
+ p->col = 4;
+ if (strcmp(p->color, "magenta") == 0)
+ p->col = 5;
+ if (strcmp(p->color, "cyan") == 0)
+ p->col = 6;
+ if (strcmp(p->color, "white") == 0)
+ p->col = 7;
+ // default if invalid
+
+ // validate: background color
+ if (strcmp(p->bcolor, "black") == 0)
+ p->bgcol = 0;
+ if (strcmp(p->bcolor, "red") == 0)
+ p->bgcol = 1;
+ if (strcmp(p->bcolor, "green") == 0)
+ p->bgcol = 2;
+ if (strcmp(p->bcolor, "yellow") == 0)
+ p->bgcol = 3;
+ if (strcmp(p->bcolor, "blue") == 0)
+ p->bgcol = 4;
+ if (strcmp(p->bcolor, "magenta") == 0)
+ p->bgcol = 5;
+ if (strcmp(p->bcolor, "cyan") == 0)
+ p->bgcol = 6;
+ if (strcmp(p->bcolor, "white") == 0)
+ p->bgcol = 7;
+ // default if invalid
+
+ return true;
+}
+
+bool validate_config(struct config_params *p, struct error_s *error) {
+ // validate: output method
+ p->om = OUTPUT_NOT_SUPORTED;
+ if (strcmp(outputMethod, "ncurses") == 0) {
+ p->om = OUTPUT_NCURSES;
+ p->bgcol = -1;
+#ifndef NCURSES
+ write_errorf(error, "cava was built without ncurses support, install ncursesw dev files "
+ "and run make clean && ./configure && make again\n");
+ return false;
+#endif
+ }
+ if (strcmp(outputMethod, "noncurses") == 0) {
+ p->om = OUTPUT_NONCURSES;
+ p->bgcol = 0;
+ }
+ if (strcmp(outputMethod, "raw") == 0) { // raw:
+ p->om = OUTPUT_RAW;
+ p->bar_spacing = 0;
+ p->bar_width = 1;
+
+ // checking data format
+ p->is_bin = -1;
+ if (strcmp(p->data_format, "binary") == 0) {
+ p->is_bin = 1;
+ // checking bit format:
+ if (p->bit_format != 8 && p->bit_format != 16) {
+ write_errorf(
+ error,
+ "bit format %d is not supported, supported data formats are: '8' and '16'\n",
+ p->bit_format);
+ return false;
+ }
+ } else if (strcmp(p->data_format, "ascii") == 0) {
+ p->is_bin = 0;
+ if (p->ascii_range < 1) {
+ write_errorf(error, "ascii max value must be a positive integer\n");
+ return false;
+ }
+ } else {
+ write_errorf(error,
+ "data format %s is not supported, supported data formats are: 'binary' "
+ "and 'ascii'\n",
+ p->data_format);
+ return false;
+ }
+ }
+ if (p->om == OUTPUT_NOT_SUPORTED) {
+#ifndef NCURSES
+ write_errorf(
+ error,
+ "output method %s is not supported, supported methods are: 'noncurses' and 'raw'\n",
+ outputMethod);
+ return false;
+#endif
+
+#ifdef NCURSES
+ write_errorf(error,
+ "output method %s is not supported, supported methods are: 'ncurses', "
+ "'noncurses' and 'raw'\n",
+ outputMethod);
+ return false;
+#endif
+ }
+
+ // validate: output channels
+ p->stereo = -1;
+ if (strcmp(channels, "mono") == 0) {
+ p->stereo = 0;
+ if (strcmp(p->mono_option, "average") != 0 && strcmp(p->mono_option, "left") != 0 &&
+ strcmp(p->mono_option, "right") != 0) {
+
+ write_errorf(error,
+ "mono option %s is not supported, supported options are: 'average', "
+ "'left' or 'right'\n",
+ p->mono_option);
+ return false;
+ }
+ }
+ if (strcmp(channels, "stereo") == 0)
+ p->stereo = 1;
+ if (p->stereo == -1) {
+ write_errorf(
+ error,
+ "output channels %s is not supported, supported channelss are: 'mono' and 'stereo'\n",
+ channels);
+ return false;
+ }
+
+ // validate: bars
+ p->autobars = 1;
+ if (p->fixedbars > 0)
+ p->autobars = 0;
+ if (p->fixedbars > 256)
+ p->fixedbars = 256;
+ if (p->bar_width > 256)
+ p->bar_width = 256;
+ if (p->bar_width < 1)
+ p->bar_width = 1;
+
+ // validate: framerate
+ if (p->framerate < 0) {
+ write_errorf(error, "framerate can't be negative!\n");
+ return false;
+ }
+
+ if (!validate_colors(p, error)) {
+ return false;
+ }
+
+ // validate: gravity
+ p->gravity = p->gravity / 100;
+ if (p->gravity < 0) {
+ p->gravity = 0;
+ }
+
+ // validate: integral
+ p->integral = p->integral / 100;
+ if (p->integral < 0) {
+ p->integral = 0;
+ } else if (p->integral > 1) {
+ p->integral = 1;
+ }
+
+ // validate: cutoff
+ if (p->lower_cut_off == 0)
+ p->lower_cut_off++;
+ if (p->lower_cut_off > p->upper_cut_off) {
+ write_errorf(error,
+ "lower cutoff frequency can't be higher than higher cutoff frequency\n");
+ return false;
+ }
+
+ // setting sens
+ p->sens = p->sens / 100;
+
+ // validate FFT buffer
+ if (p->FFTbufferSize >= 8 && p->FFTbufferSize <= 16) {
+ p->FFTbufferSize = pow(2, p->FFTbufferSize);
+ } else {
+ write_errorf(error, "FFT buffer is set in the exponent of 2 and must be between 8 - 16\n");
+ return false;
+ }
+
+ return true;
+}
+
+bool load_colors(struct config_params *p, dictionary *ini, void *err) {
+ struct error_s *error = (struct error_s *)err;
+
+ free(p->color);
+ free(p->bcolor);
+
+ p->color = strdup(iniparser_getstring(ini, "color:foreground", "default"));
+ p->bcolor = strdup(iniparser_getstring(ini, "color:background", "default"));
+
+ p->gradient = iniparser_getint(ini, "color:gradient", 0);
+ if (p->gradient) {
+ for (int i = 0; i < p->gradient_count; ++i) {
+ free(p->gradient_colors[i]);
+ }
+ p->gradient_count = iniparser_getint(ini, "color:gradient_count", 8);
+ if (p->gradient_count < 2) {
+ write_errorf(error, "\nAtleast two colors must be given as gradient!\n");
+ return false;
+ }
+ if (p->gradient_count > 8) {
+ write_errorf(error, "\nMaximum 8 colors can be specified as gradient!\n");
+ return false;
+ }
+ p->gradient_colors = (char **)malloc(sizeof(char *) * p->gradient_count * 9);
+ p->gradient_colors[0] =
+ strdup(iniparser_getstring(ini, "color:gradient_color_1", "#59cc33"));
+ p->gradient_colors[1] =
+ strdup(iniparser_getstring(ini, "color:gradient_color_2", "#80cc33"));
+ p->gradient_colors[2] =
+ strdup(iniparser_getstring(ini, "color:gradient_color_3", "#a6cc33"));
+ p->gradient_colors[3] =
+ strdup(iniparser_getstring(ini, "color:gradient_color_4", "#cccc33"));
+ p->gradient_colors[4] =
+ strdup(iniparser_getstring(ini, "color:gradient_color_5", "#cca633"));
+ p->gradient_colors[5] =
+ strdup(iniparser_getstring(ini, "color:gradient_color_6", "#cc8033"));
+ p->gradient_colors[6] =
+ strdup(iniparser_getstring(ini, "color:gradient_color_7", "#cc5933"));
+ p->gradient_colors[7] =
+ strdup(iniparser_getstring(ini, "color:gradient_color_8", "#cc3333"));
+ }
+ return true;
+}
+
+bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colorsOnly,
+ struct error_s *error) {
+ FILE *fp;
+
+ // config: creating path to default config file
+ if (configPath[0] == '\0') {
+ char *configFile = "config";
+ char *configHome = getenv("XDG_CONFIG_HOME");
+ if (configHome != NULL) {
+ sprintf(configPath, "%s/%s/", configHome, PACKAGE);
+ } else {
+ configHome = getenv("HOME");
+ if (configHome != NULL) {
+ sprintf(configPath, "%s/%s/", configHome, ".config");
+ mkdir(configPath, 0777);
+ sprintf(configPath, "%s/%s/%s/", configHome, ".config", PACKAGE);
+ } else {
+ write_errorf(error, "No HOME found (ERR_HOMELESS), exiting...");
+ return false;
+ }
+ }
+
+ // config: create directory
+ mkdir(configPath, 0777);
+
+ // config: adding default filename file
+ strcat(configPath, configFile);
+
+ fp = fopen(configPath, "ab+");
+ if (fp) {
+ fclose(fp);
+ } else {
+ write_errorf(error, "Unable to access config '%s', exiting...\n", configPath);
+ return false;
+ }
+
+ } else { // opening specified file
+
+ fp = fopen(configPath, "rb+");
+ if (fp) {
+ fclose(fp);
+ } else {
+ write_errorf(error, "Unable to open file '%s', exiting...\n", configPath);
+ return false;
+ }
+ }
+
+ // config: parse ini
+ dictionary *ini;
+ ini = iniparser_load(configPath);
+
+ if (colorsOnly) {
+ if (!load_colors(p, ini, error)) {
+ return false;
+ }
+ return validate_colors(p, error);
+ }
+
+#ifdef NCURSES
+ outputMethod = (char *)iniparser_getstring(ini, "output:method", "noncurses");
+#endif
+#ifndef NCURSES
+ outputMethod = (char *)iniparser_getstring(ini, "output:method", "noncurses");
+#endif
+
+ p->monstercat = 1.5 * iniparser_getdouble(ini, "smoothing:monstercat", 0);
+ p->waves = iniparser_getint(ini, "smoothing:waves", 0);
+ p->integral = iniparser_getdouble(ini, "smoothing:integral", 77);
+ p->gravity = iniparser_getdouble(ini, "smoothing:gravity", 100);
+ p->ignore = iniparser_getdouble(ini, "smoothing:ignore", 0);
+
+ if (!load_colors(p, ini, error)) {
+ return false;
+ }
+
+ p->fixedbars = iniparser_getint(ini, "general:bars", 0);
+ p->bar_width = iniparser_getint(ini, "general:bar_width", 2);
+ p->bar_spacing = iniparser_getint(ini, "general:bar_spacing", 1);
+ p->framerate = iniparser_getint(ini, "general:framerate", 60);
+ p->sens = iniparser_getint(ini, "general:sensitivity", 100);
+ p->autosens = iniparser_getint(ini, "general:autosens", 1);
+ p->overshoot = iniparser_getint(ini, "general:overshoot", 20);
+ p->lower_cut_off = iniparser_getint(ini, "general:lower_cutoff_freq", 50);
+ p->upper_cut_off = iniparser_getint(ini, "general:higher_cutoff_freq", 10000);
+ p->FFTbufferSize = iniparser_getint(ini, "general:FFTbufferSize", 12);
+
+ // config: output
+ free(channels);
+ free(p->mono_option);
+ free(p->raw_target);
+ free(p->data_format);
+
+ channels = strdup(iniparser_getstring(ini, "output:channels", "stereo"));
+ p->mono_option = strdup(iniparser_getstring(ini, "output:mono_option", "average"));
+ p->raw_target = strdup(iniparser_getstring(ini, "output:raw_target", "/dev/stdout"));
+ p->data_format = strdup(iniparser_getstring(ini, "output:data_format", "binary"));
+ p->bar_delim = (char)iniparser_getint(ini, "output:bar_delimiter", 59);
+ p->frame_delim = (char)iniparser_getint(ini, "output:frame_delimiter", 10);
+ p->ascii_range = iniparser_getint(ini, "output:ascii_max_range", 1000);
+ p->bit_format = iniparser_getint(ini, "output:bit_format", 16);
+
+ // read & validate: eq
+ p->userEQ_keys = iniparser_getsecnkeys(ini, "eq");
+ if (p->userEQ_keys > 0) {
+ p->userEQ_enabled = 1;
+ p->userEQ = calloc(p->userEQ_keys + 1, sizeof(p->userEQ));
+#ifndef LEGACYINIPARSER
+ const char *keys[p->userEQ_keys];
+ iniparser_getseckeys(ini, "eq", keys);
+#endif
+#ifdef LEGACYINIPARSER
+ char **keys = iniparser_getseckeys(ini, "eq");
+#endif
+ for (int sk = 0; sk < p->userEQ_keys; sk++) {
+ p->userEQ[sk] = iniparser_getdouble(ini, keys[sk], 1);
+ }
+ } else {
+ p->userEQ_enabled = 0;
+ }
+
+ free(p->audio_source);
+
+ char *input_method_name;
+ for (size_t i = 0; i < ARRAY_SIZE(default_methods); i++) {
+ enum input_method method = default_methods[i];
+ if (has_input_method[method]) {
+ input_method_name =
+ (char *)iniparser_getstring(ini, "input:method", input_method_names[method]);
+ }
+ }
+
+ p->im = input_method_by_name(input_method_name);
+ switch (p->im) {
+#ifdef ALSA
+ case INPUT_ALSA:
+ p->audio_source = strdup(iniparser_getstring(ini, "input:source", "hw:Loopback,1"));
+ break;
+#endif
+ case INPUT_FIFO:
+ p->audio_source = strdup(iniparser_getstring(ini, "input:source", "/tmp/mpd.fifo"));
+ p->fifoSample = iniparser_getint(ini, "input:sample_rate", 44100);
+ p->fifoSampleBits = iniparser_getint(ini, "input:sample_bits", 16);
+ break;
+#ifdef PULSE
+ case INPUT_PULSE:
+ p->audio_source = strdup(iniparser_getstring(ini, "input:source", "auto"));
+ break;
+#endif
+#ifdef SNDIO
+ case INPUT_SNDIO:
+ p->audio_source = strdup(iniparser_getstring(ini, "input:source", SIO_DEVANY));
+ break;
+#endif
+ case INPUT_SHMEM:
+ p->audio_source =
+ strdup(iniparser_getstring(ini, "input:source", "/squeezelite-00:00:00:00:00:00"));
+ break;
+#ifdef PORTAUDIO
+ case INPUT_PORTAUDIO:
+ p->audio_source = strdup(iniparser_getstring(ini, "input:source", "auto"));
+ break;
+#endif
+ case INPUT_MAX: {
+ char supported_methods[255] = "";
+ for (int i = 0; i < INPUT_MAX; i++) {
+ if (has_input_method[i]) {
+ strcat(supported_methods, "'");
+ strcat(supported_methods, input_method_names[i]);
+ strcat(supported_methods, "' ");
+ }
+ }
+ write_errorf(error, "input method '%s' is not supported, supported methods are: %s\n",
+ input_method_name, supported_methods);
+ return false;
+ }
+ default:
+ write_errorf(error, "cava was built without '%s' input support\n",
+ input_method_names[p->im]);
+ return false;
+ }
+
+ bool result = validate_config(p, error);
+ iniparser_freedict(ini);
+ return result;
+}
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..b037026
--- /dev/null
+++ b/config.h
@@ -0,0 +1,68 @@
+#pragma once
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#define MAX_ERROR_LEN 1024
+
+#ifdef PORTAUDIO
+#define HAS_PORTAUDIO true
+#else
+#define HAS_PORTAUDIO false
+#endif
+
+#ifdef ALSA
+#define HAS_ALSA true
+#else
+#define HAS_ALSA false
+#endif
+
+#ifdef PULSE
+#define HAS_PULSE true
+#else
+#define HAS_PULSE false
+#endif
+
+#ifdef SNDIO
+#define HAS_SNDIO true
+#else
+#define HAS_SNDIO false
+#endif
+
+// These are in order of least-favourable to most-favourable choices, in case
+// multiple are supported and configured.
+enum input_method {
+ INPUT_FIFO,
+ INPUT_PORTAUDIO,
+ INPUT_ALSA,
+ INPUT_PULSE,
+ INPUT_SNDIO,
+ INPUT_SHMEM,
+ INPUT_MAX
+};
+
+enum output_method { OUTPUT_NCURSES, OUTPUT_NONCURSES, OUTPUT_RAW, OUTPUT_NOT_SUPORTED };
+
+struct config_params {
+ char *color, *bcolor, *raw_target, *audio_source,
+ /**gradient_color_1, *gradient_color_2,*/ **gradient_colors, *data_format, *mono_option;
+ char bar_delim, frame_delim;
+ double monstercat, integral, gravity, ignore, sens;
+ unsigned int lower_cut_off, upper_cut_off;
+ double *userEQ;
+ enum input_method im;
+ enum output_method om;
+ int userEQ_keys, userEQ_enabled, col, bgcol, autobars, stereo, is_bin, ascii_range, bit_format,
+ gradient, gradient_count, fixedbars, framerate, bar_width, bar_spacing, autosens, overshoot,
+ waves, FFTbufferSize, fifoSample, fifoSampleBits;
+};
+
+struct error_s {
+ char message[MAX_ERROR_LEN];
+ int length;
+};
+
+bool load_config(char configPath[PATH_MAX], struct config_params *p, bool colorsOnly,
+ struct error_s *error);
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..2331142
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,312 @@
+AC_INIT([cava], [m4_esyscmd_s([cat version])], [karl@stavestrand.no])
+AM_INIT_AUTOMAKE([subdir-objects -Wall -Werror foreign])
+dnl AC_CONFIG_MACRO_DIRS([m4])
+AM_PROG_AR
+LT_INIT
+AC_PROG_CC
+AC_PROG_CC_STDC
+
+AM_PROG_LIBTOOL
+
+
+dnl ############################
+dnl checking if debug is enabled
+dnl ############################
+
+AC_ARG_ENABLE([debug],
+ AS_HELP_STRING([--enable-debug],
+ [enable debug messages and frequency table output])
+)
+
+AS_IF([test "x$enable_debug" != "xyes"], [
+ dnl enabling debug mode
+ CPPFLAGS="$CPPFLAGS -DNDEBUG"
+])
+
+AC_ARG_ENABLE([asan],
+ AS_HELP_STRING([--enable-asan],
+ [build with AddressSanitizer])
+)
+
+AS_IF([test "x$enable_asan" = "xyes"], [
+ dnl enabling asan
+ CPPFLAGS="$CPPFLAGS -fsanitize=address"
+ LDFLAGS="$LDFLAGS -fsanitize=address"
+])
+
+AC_ARG_ENABLE([tsan],
+ AS_HELP_STRING([--enable-tsan],
+ [build with ThreadSanitizer])
+)
+
+AS_IF([test "x$enable_tsan" = "xyes"], [
+ dnl enabling tsan
+ CPPFLAGS="$CPPFLAGS -fsanitize=thread"
+ LDFLAGS="$LDFLAGS -fsanitize=thread"
+])
+
+AC_ARG_ENABLE([ubsan],
+ AS_HELP_STRING([--enable-ubsan],
+ [build with UndefinedBehaviorSanitizer])
+)
+
+AS_IF([test "x$enable_ubsan" = "xyes"], [
+ dnl enabling ubsan
+ CPPFLAGS="$CPPFLAGS -fsanitize=undefined"
+ LDFLAGS="$LDFLAGS -fsanitize=undefined"
+])
+
+
+dnl ######################
+dnl checking for pthread
+dnl ######################
+
+AC_CHECK_HEADERS([pthread.h],
+ AC_CHECK_LIB(pthread, pthread_create, LIBS="$LIBS -lpthread",
+ AC_MSG_ERROR([pthread.h found but there is no pthread library to make use of])
+ ),
+ AC_MSG_ERROR([no pthread.h header header file found])
+)
+
+dnl ######################
+dnl checking for alloca.h
+dnl ######################
+
+AC_CHECK_HEADER([alloca.h], [CPPFLAGS="$CPPFLAGS -DHAVE_ALLOCA_H"])
+
+dnl ######################
+dnl checking for alsa dev
+dnl ######################
+AC_ARG_ENABLE([input_alsa],
+ AS_HELP_STRING([--disable-input-alsa],
+ [do not include support for input from alsa streams])
+)
+
+AS_IF([test "x$enable_input_alsa" != "xno"], [
+ AC_CHECK_LIB(asound, snd_pcm_open, have_alsa=yes, have_alsa=no)
+ if [[ $have_alsa = "yes" ]] ; then
+ LIBS="$LIBS -lasound"
+ CPPFLAGS="$CPPFLAGS -DALSA"
+ fi
+ if [[ $have_alsa = "no" ]] ; then
+ AC_MSG_NOTICE([WARNING: No alsa dev files found building without alsa support])
+ fi],
+ [have_alsa=no]
+)
+
+AM_CONDITIONAL([ALSA], [test "x$have_alsa" = "xyes"])
+
+
+dnl ######################
+dnl checking for pulse dev
+dnl ######################
+AC_ARG_ENABLE([input_pulse],
+ AS_HELP_STRING([--disable-input-pulse],
+ [do not include support for input from pulseaudio])
+)
+
+AS_IF([test "x$enable_input_pulse" != "xno"], [
+ AC_CHECK_LIB(pulse-simple, pa_simple_new, have_pulse=yes, have_pulse=no)
+ if [[ $have_pulse = "yes" ]] ; then
+ LIBS="$LIBS -lpulse-simple -lpulse"
+ CPPFLAGS="$CPPFLAGS -DPULSE"
+ fi
+
+ if [[ $have_pulse = "no" ]] ; then
+ AC_MSG_NOTICE([WARNING: No pusleaudio dev files found building without pulseaudio support])
+ fi],
+ [have_pulse=no]
+)
+
+AM_CONDITIONAL([PULSE], [test "x$have_pulse" = "xyes"])
+
+dnl ######################
+dnl checking for portaudio dev
+dnl ######################
+AC_ARG_ENABLE([input_portaudio],
+ AS_HELP_STRING([--disable-input-portaudio],
+ [do not include support for input from portaudio])
+)
+
+AS_IF([test "x$enable_input_portaudio" != "xno"], [
+ AC_CHECK_LIB(portaudio, Pa_Initialize, have_portaudio=yes, have_portaudio=no)
+ if [[ $have_portaudio = "yes" ]] ; then
+ LIBS="$LIBS -lportaudio"
+ CPPFLAGS="$CPPFLAGS -DPORTAUDIO"
+ fi
+
+ if [[ $have_portaudio = "no" ]] ; then
+ AC_MSG_NOTICE([WARNING: No portaudio dev files found building without portaudio support])
+ fi],
+ [have_portaudio=no]
+)
+
+AM_CONDITIONAL([PORTAUDIO], [test "x$have_portaudio" = "xyes"])
+
+dnl ######################
+dnl checking for sndio dev
+dnl ######################
+AC_ARG_ENABLE([input_sndio],
+ AS_HELP_STRING([--disable-input-sndio],
+ [do not include support for input from sndio])
+)
+
+AS_IF([test "x$enable_input_sndio" != "xno"], [
+ AC_CHECK_LIB(sndio, sio_open, have_sndio=yes, have_sndio=no)
+ if [[ $have_sndio = "yes" ]] ; then
+ LIBS="$LIBS -lsndio"
+ CPPFLAGS="$CPPFLAGS -DSNDIO"
+ fi
+
+ if [[ $have_sndio = "no" ]] ; then
+ AC_MSG_NOTICE([WARNING: No sndio dev files found building without sndio support])
+ fi],
+ [have_portaudio=no]
+)
+
+AM_CONDITIONAL([SNDIO], [test "x$have_sndio" = "xyes"])
+
+dnl ######################
+dnl checking for math lib
+dnl ######################
+AC_CHECK_LIB(m, sqrt, have_m=yes, have_m=no)
+ if [[ $have_m = "yes" ]] ; then
+ LIBS="$LIBS -lm"
+ fi
+
+ if [[ $have_m = "no" ]] ; then
+ AC_MSG_ERROR([math library is required!])
+ fi
+
+
+dnl ######################
+dnl checking for fftw3
+dnl ######################
+AC_CHECK_LIB(fftw3,fftw_execute, have_fftw=yes, have_fftw=no)
+ if [[ $have_fftw = "yes" ]] ; then
+ LIBS="$LIBS -lfftw3"
+ fi
+
+ if [[ $have_fftw = "no" ]] ; then
+ AC_MSG_ERROR([fftw library is required!])
+ fi
+
+dnl ######################
+dnl checking for ncursesw
+dnl ######################
+AC_ARG_ENABLE([output_ncurses],
+ AS_HELP_STRING([--disable-output-ncurses],
+ [do not include support for output with ncurses])
+)
+
+AS_IF([test "x$enable_output_ncurses" != "xno"], [
+ curses_config_bin="ncursesw6-config ncursesw5-config"
+
+ AC_PATH_PROGS(CURSES_CONFIG, $curses_config_bin)
+
+ AC_CHECK_LIB(ncursesw, initscr,
+ curses_lib=ncursesw
+ )
+
+ AC_CHECK_LIB($curses_lib, initscr,
+ if test "$CURSES_CONFIG" = "" ; then
+ LIBS="$LIBS -l$curses_lib"
+ CPPFLAGS="$CPPFLAGS -DNCURSES"
+ fi
+ if test "$CURSES_CONFIG" != "" ; then
+ CPPFLAGS="$CPPFLAGS `$CURSES_CONFIG --cflags` -DNCURSES"
+ LIBS="$LIBS `$CURSES_CONFIG --libs`"
+ fi
+
+ AC_CHECK_HEADERS([curses.h], , AC_MSG_ERROR([missing curses.h header]))
+ have_ncurses=yes,
+
+ AC_MSG_NOTICE([WARNING: building without ncursesw support ncursesw is recomended!])
+ have_ncurses=no
+ )],
+ [have_ncurses=no]
+)
+
+AM_CONDITIONAL([NCURSES], [test "x$have_ncurses" = "xyes"])
+
+
+dnl ######################
+dnl checking for system iniparser
+dnl ######################
+
+AC_ARG_ENABLE([system_iniparser],
+ AS_HELP_STRING([--disable-system-iniparser],
+ [do not use system iniparser library (use bundled iniparser library)])
+)
+
+AS_IF([test "x$enable_system_iniparser" != "xno"], [
+ AC_SEARCH_LIBS([iniparser_load], [iniparser], [
+ AC_CHECK_HEADERS([iniparser.h], [have_system_iniparser=yes])
+ ])
+ ],
+ [have_system_iniparser=no]
+)
+
+AM_CONDITIONAL([SYSTEM_LIBINIPARSER], [test "x$have_system_iniparser" = "xyes"])
+
+if test "x$have_system_iniparser" = "xyes"; then
+ AC_SUBST(SYSTEM_LIBINIPARSER, 1)
+ AC_MSG_NOTICE([Using installed iniparser])
+ LIBS="$LIBS -liniparser"
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <iniparser.h>]],
+ [[dictionary* ini;
+ const char *keys[3];
+ iniparser_getseckeys(ini, "eq", keys);]])],
+ [AC_MSG_RESULT(iniparser > 3.2 test OK)],
+ [AC_MSG_RESULT(iniparser > 3.2 test failed falling back to legacy iniparser mode)
+ CPPFLAGS="$CPPFLAGS -DLEGACYINIPARSER"])
+else
+ AC_SUBST(SYSTEM_LIBINIPARSER, 0)
+ AC_CONFIG_FILES(iniparser/Makefile)
+ AC_MSG_NOTICE([Building iniparser])
+fi
+
+
+dnl ############################
+dnl Set font directory
+dnl ############################
+DEFAULT_FONT_DIR="/usr/share/consolefonts"
+AC_ARG_VAR(FONT_DIR, [Directory where the font will be installed.])
+if test -z "$FONT_DIR" ; then
+ FONT_DIR="$DEFAULT_FONT_DIR"
+fi
+
+AC_CANONICAL_HOST
+
+build_linux=no
+build_windows=no
+build_mac=no
+
+AC_MSG_NOTICE([Checking OS])
+# Detect the target system
+case "${host_os}" in
+ linux*)
+ AC_MSG_NOTICE([Linux detected])
+ build_linux=yes
+ ;;
+ darwin*)
+ AC_MSG_NOTICE([OSX detected])
+ build_mac=yes
+ ;;
+ *)
+ AC_MSG_ERROR(["OS $host_os is not supported"])
+ ;;
+esac
+
+# Pass the conditionals to automake
+AM_CONDITIONAL([LINUX], [test "$build_linux" = "yes"])
+AM_CONDITIONAL([OSX], [test "$build_mac" = "yes"])
+
+
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
+
+
+
+
+
diff --git a/debug.h b/debug.h
new file mode 100644
index 0000000..f1f3c91
--- /dev/null
+++ b/debug.h
@@ -0,0 +1,9 @@
+#include <stdio.h>
+
+#ifdef NDEBUG
+#define debug(...) \
+ do { \
+ } while (0)
+#else
+#define debug(...) fprintf(stderr, __VA_ARGS__)
+#endif
diff --git a/example_files/config b/example_files/config
new file mode 100644
index 0000000..463a85d
--- /dev/null
+++ b/example_files/config
@@ -0,0 +1,167 @@
+## Configuration file for CAVA. Default values are commented out. Use either ';' or '#' for commenting.
+
+
+[general]
+
+# Smoothing mode. Can be 'normal', 'scientific' or 'waves'. DEPRECATED as of 0.6.0
+; mode = normal
+
+# Accepts only non-negative values.
+; framerate = 60
+
+# 'autosens' will attempt to decrease sensitivity if the bars peak. 1 = on, 0 = off
+# new as of 0.6.0 autosens of low values (dynamic range)
+# 'overshoot' allows bars to overshoot (in % of terminal height) without initiating autosens. DEPRECATED as of 0.6.0
+; autosens = 1
+; overshoot = 20
+
+# Manual sensitivity in %. Autosens must be turned off for this to take effect.
+# 200 means double height. Accepts only non-negative values.
+; sensitivity = 100
+
+# The number of bars (0-200). 0 sets it to auto (fill up console).
+# Bars' width and space between bars in number of characters.
+; bars = 0
+; bar_width = 2
+; bar_spacing = 1
+
+
+# Lower and higher cutoff frequencies for lowest and highest bars
+# the bandwidth of the visualizer.
+# Note: there is a minimum total bandwidth of 43Mhz x number of bars.
+# Cava will automatically increase the higher cutoff if a too low band is specified.
+; lower_cutoff_freq = 50
+; higher_cutoff_freq = 10000
+
+# FFT buffer is set in the exponent of 2 and must be between 8 - 16, 8 = 256, 16 = 65536
+# this is the audio buffer used to create the spectrum
+# increasing the will improve the accuracy of the visualization,
+# but will also make it slower and increase CPU usage
+# anything below 10 and above 13 is considered experimental.
+; FFTbufferSize = 12;
+
+
+
+[input]
+
+# Audio capturing method. Possible methods are: 'pulse', 'alsa', 'fifo', 'sndio' or 'shmem'
+# Defaults to 'pulse', 'alsa' or 'fifo', in that order, dependent on what support cava was built with.
+#
+# All input methods uses the same config variable 'source'
+# to define where it should get the audio.
+#
+# For pulseaudio 'source' will be the source. Default: 'auto', which uses the monitor source of the default sink
+# (all pulseaudio sinks(outputs) have 'monitor' sources(inputs) associated with them).
+#
+# For alsa 'source' will be the capture device.
+# For fifo 'source' will be the path to fifo-file.
+# For shmem 'source' will be /squeezelite-AA:BB:CC:DD:EE:FF where 'AA:BB:CC:DD:EE:FF' will be squeezelite's MAC address
+; method = pulse
+; source = auto
+
+; method = alsa
+; source = hw:Loopback,1
+
+; method = fifo
+; source = /tmp/mpd.fifo
+; sample_rate = 44100
+; sample_bits = 16
+
+; method = shmem
+; source = /squeezelite-AA:BB:CC:DD:EE:FF
+
+; method = portaudio
+; source = auto
+
+
+[output]
+
+# Output method. Can be 'ncurses', 'noncurses' or 'raw'.
+# 'noncurses' uses a custom framebuffer technique and draws only changes
+# from frame to frame. As of version 0.7.0 'noncurses' is default.
+#
+# 'raw' is an 8 or 16 bit (configurable via the 'bit_format' option) data
+# stream of the bar heights that can be used to send to other applications.
+# 'raw' defaults to 200 bars, which can be adjusted in the 'bars' option above.
+; method = ncurses
+
+# Visual channels. Can be 'stereo' or 'mono'.
+# 'stereo' mirrors both channels with low frequencies in center.
+# 'mono' outputs left to right lowest to highest frequencies.
+# 'mono_option' set mono to either take input from 'left', 'right' or 'average'.
+; channels = stereo
+; mono_option = average
+
+# Raw output target. A fifo will be created if target does not exist.
+; raw_target = /dev/stdout
+
+# Raw data format. Can be 'binary' or 'ascii'.
+; data_format = binary
+
+# Binary bit format, can be '8bit' (0-255) or '16bit' (0-65530).
+; bit_format = 16bit
+
+# Ascii max value. In 'ascii' mode range will run from 0 to value specified here
+; ascii_max_range = 1000
+
+# Ascii delimiters. In ascii format each bar and frame is separated by a delimiters.
+# Use decimal value in ascii table (i.e. 59 = ';' and 10 = '\n' (line feed)).
+; bar_delimiter = 59
+; frame_delimiter = 10
+
+
+
+[color]
+
+# Colors can be one of seven predefined: black, blue, cyan, green, magenta, red, white, yellow.
+# Or defined by hex code '#xxxxxx' (hex code must be within ''). User defined colors requires
+# ncurses output method and a terminal that can change color definitions such as Gnome-terminal or rxvt.
+# default is to keep current terminal color
+; background = default
+; foreground = default
+
+# Gradient mode, only hex defined colors (and thereby ncurses mode) are supported,
+# background must also be defined in hex or remain commented out. 1 = on, 0 = off.
+# You can define as many as 8 different colors. They range from bottom to top of screen
+; gradient = 1
+; gradient_count = 8
+; gradient_color_1 = '#59cc33'
+; gradient_color_2 = '#80cc33'
+; gradient_color_3 = '#a6cc33'
+; gradient_color_4 = '#cccc33'
+; gradient_color_5 = '#cca633'
+; gradient_color_6 = '#cc8033'
+; gradient_color_7 = '#cc5933'
+; gradient_color_8 = '#cc3333'
+
+
+
+[smoothing]
+
+# Percentage value for integral smoothing. Takes values from 0 - 100.
+# Higher values means smoother, but less precise. 0 to disable.
+; integral = 77
+
+# Disables or enables the so-called "Monstercat smoothing" with or without "waves". Set to 0 to disable.
+; monstercat = 0
+; waves = 0
+
+# Set gravity percentage for "drop off". Higher values means bars will drop faster.
+# Accepts only non-negative values. 50 means half gravity, 200 means double. Set to 0 to disable "drop off".
+; gravity = 100
+
+
+# In bar height, bars that would have been lower that this will not be drawn.
+; ignore = 0
+
+
+[eq]
+
+# This one is tricky. You can have as much keys as you want.
+# Remember to uncomment more then one key! More keys = more precision.
+# Look at readme.md on github for further explanations and examples.
+; 1 = 1 # bass
+; 2 = 1
+; 3 = 1 # midtone
+; 4 = 1
+; 5 = 1 # treble
diff --git a/example_files/etc/asound.conf b/example_files/etc/asound.conf
new file mode 100644
index 0000000..601d676
--- /dev/null
+++ b/example_files/etc/asound.conf
@@ -0,0 +1,20 @@
+pcm.!default {
+ type plug # <-- no { here
+ slave.pcm {
+ type multi
+ slaves {
+ a { channels 2 pcm "hw:0,0" } # the real device
+ b { channels 2 pcm "hw:Loopback,0" } # the loopback driver
+ }
+ bindings {
+ 0 { slave a channel 0 }
+ 1 { slave a channel 1 }
+ 2 { slave b channel 0 }
+ 3 { slave b channel 1 }
+ }
+ }
+ ttable [
+ [ 1 0 1 0 ] # left -> a.left, b.left
+ [ 0 1 0 1 ] # right -> a.right, b.right
+ ]
+}
diff --git a/example_files/etc/modprobe.d/alsa-aloop.conf b/example_files/etc/modprobe.d/alsa-aloop.conf
new file mode 100644
index 0000000..dbd5b8f
--- /dev/null
+++ b/example_files/etc/modprobe.d/alsa-aloop.conf
@@ -0,0 +1 @@
+options snd-aloop index=1 enable=1 pcm_substreams=4 id=Loopback
diff --git a/iniparser/AUTHORS b/iniparser/AUTHORS
new file mode 100644
index 0000000..d5a3f6b
--- /dev/null
+++ b/iniparser/AUTHORS
@@ -0,0 +1,6 @@
+Author: Nicolas Devillard <ndevilla@free.fr>
+
+This tiny library has received countless contributions and I have
+not kept track of all the people who contributed. Let them be thanked
+for their ideas, code, suggestions, corrections, enhancements!
+
diff --git a/iniparser/LICENSE b/iniparser/LICENSE
new file mode 100644
index 0000000..5a3a80b
--- /dev/null
+++ b/iniparser/LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) 2000-2011 by Nicolas Devillard.
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
diff --git a/iniparser/Makefile.am b/iniparser/Makefile.am
new file mode 100644
index 0000000..6cafc5f
--- /dev/null
+++ b/iniparser/Makefile.am
@@ -0,0 +1,3 @@
+noinst_LTLIBRARIES = libiniparser.la
+libiniparser_la_SOURCES = src/iniparser.c src/dictionary.c
+#libiniparser_la_LDFLAGS = -version-info 4
diff --git a/iniparser/README.md b/iniparser/README.md
new file mode 100644
index 0000000..7a8a523
--- /dev/null
+++ b/iniparser/README.md
@@ -0,0 +1,67 @@
+<!-- START doctoc generated TOC please keep comment here to allow auto update -->
+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
+**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
+
+- [Iniparser 4](#iniparser-4)
+ - [I - Overview](#i---overview)
+ - [II - Building project](#ii---building-project)
+ - [III - License](#iii---license)
+ - [IV - Versions](#iv---versions)
+ - [V - FAQ](#v---faq)
+ - [Is Iniparser thread safe ?](#is-iniparser-thread-safe-)
+ - [Your build system isn't portable, let me help you...](#your-build-system-isnt-portable-let-me-help-you)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
+
+[![Build Status](https://travis-ci.org/ndevilla/iniparser.svg?branch=master)](https://travis-ci.org/ndevilla/iniparser)
+
+# Iniparser 4 #
+
+
+## I - Overview
+
+This modules offers parsing of ini files from the C level.
+See a complete documentation in HTML format, from this directory
+open the file html/index.html with any HTML-capable browser.
+
+Key features :
+
+ - Small : around 1500 sloc inside 4 files (2 .c and 2 .h)
+ - Portable : no dependancies, written in `-ansi -pedantic` C89
+ - Fully reintrant : easy to make it thread-safe (just surround
+ library calls by mutex)
+
+## II - Building project
+
+A simple `make` at the root of the project should be enough to get the static
+(i.e. `libiniparser.a`) and shared (i.e. `libiniparser.so.0`) libraries compiled.
+
+You should consider trying the following rules too :
+
+ - `make check` : run the unitary tests
+ - `make example` : compile the example, run it with `./example/iniexample`
+
+## III - License
+
+This software is released under MIT License.
+See LICENSE for full informations
+
+## IV - Versions
+
+Current version is 4.0 which introduces breaking changes in the api.
+Older versions 3.1 and 3.2 with the legacy api are available as tags.
+
+
+## V - FAQ
+
+### Is Iniparser thread safe ?
+
+Starting from version 4, iniparser is designed to be thread-safe, provided you surround it with your own mutex logic.
+The choice not to add thread safety inside the library has been done to provide more freedom for the developer, especially when dealing with it own custom reading logic (i.g. acquiring the mutex, reading plenty of entries in iniparser, then releasing the mutex).
+
+### Your build system isn't portable, let me help you...
+
+I have received countless contributions from distrib people to modify the Makefile into what they think is the "standard", which I had to reject.
+The default, standard Makefile for Debian bears absolutely no relationship with the one from SuSE or RedHat and there is no possible way to merge them all.
+A build system is something so specific to each environment that it is completely pointless to try and push anything that claims to be standard. The provided Makefile in this project is purely here to have something to play with quickly.
+
diff --git a/iniparser/src/dictionary.c b/iniparser/src/dictionary.c
new file mode 100644
index 0000000..e98926e
--- /dev/null
+++ b/iniparser/src/dictionary.c
@@ -0,0 +1,374 @@
+/*-------------------------------------------------------------------------*/
+/**
+ @file dictionary.c
+ @author N. Devillard
+ @brief Implements a dictionary for string variables.
+
+ This module implements a simple dictionary object, i.e. a list
+ of string/string associations. This object is useful to store e.g.
+ informations retrieved from a configuration file (ini files).
+*/
+/*--------------------------------------------------------------------------*/
+
+/*---------------------------------------------------------------------------
+ Includes
+ ---------------------------------------------------------------------------*/
+#include "dictionary.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/** Maximum value size for integers and doubles. */
+#define MAXVALSZ 1024
+
+/** Minimal allocated number of entries in a dictionary */
+#define DICTMINSZ 128
+
+/** Invalid key token */
+#define DICT_INVALID_KEY ((char *)-1)
+
+/*---------------------------------------------------------------------------
+ Private functions
+ ---------------------------------------------------------------------------*/
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Duplicate a string
+ @param s String to duplicate
+ @return Pointer to a newly allocated string, to be freed with free()
+
+ This is a replacement for strdup(). This implementation is provided
+ for systems that do not have it.
+ */
+/*--------------------------------------------------------------------------*/
+static char *xstrdup(const char *s) {
+ char *t;
+ size_t len;
+ if (!s)
+ return NULL;
+
+ len = strlen(s) + 1;
+ t = (char *)malloc(len);
+ if (t) {
+ memcpy(t, s, len);
+ }
+ return t;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Double the size of the dictionary
+ @param d Dictionary to grow
+ @return This function returns non-zero in case of failure
+ */
+/*--------------------------------------------------------------------------*/
+static int dictionary_grow(dictionary *d) {
+ char **new_val;
+ char **new_key;
+ unsigned *new_hash;
+
+ new_val = (char **)calloc(d->size * 2, sizeof *d->val);
+ new_key = (char **)calloc(d->size * 2, sizeof *d->key);
+ new_hash = (unsigned *)calloc(d->size * 2, sizeof *d->hash);
+ if (!new_val || !new_key || !new_hash) {
+ /* An allocation failed, leave the dictionary unchanged */
+ if (new_val)
+ free(new_val);
+ if (new_key)
+ free(new_key);
+ if (new_hash)
+ free(new_hash);
+ return -1;
+ }
+ /* Initialize the newly allocated space */
+ memcpy(new_val, d->val, d->size * sizeof(char *));
+ memcpy(new_key, d->key, d->size * sizeof(char *));
+ memcpy(new_hash, d->hash, d->size * sizeof(unsigned));
+ /* Delete previous data */
+ free(d->val);
+ free(d->key);
+ free(d->hash);
+ /* Actually update the dictionary */
+ d->size *= 2;
+ d->val = new_val;
+ d->key = new_key;
+ d->hash = new_hash;
+ return 0;
+}
+
+/*---------------------------------------------------------------------------
+ Function codes
+ ---------------------------------------------------------------------------*/
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Compute the hash key for a string.
+ @param key Character string to use for key.
+ @return 1 unsigned int on at least 32 bits.
+
+ This hash function has been taken from an Article in Dr Dobbs Journal.
+ This is normally a collision-free function, distributing keys evenly.
+ The key is stored anyway in the struct so that collision can be avoided
+ by comparing the key itself in last resort.
+ */
+/*--------------------------------------------------------------------------*/
+unsigned dictionary_hash(const char *key) {
+ size_t len;
+ unsigned hash;
+ size_t i;
+
+ if (!key)
+ return 0;
+
+ len = strlen(key);
+ for (hash = 0, i = 0; i < len; i++) {
+ hash += (unsigned)key[i];
+ hash += (hash << 10);
+ hash ^= (hash >> 6);
+ }
+ hash += (hash << 3);
+ hash ^= (hash >> 11);
+ hash += (hash << 15);
+ return hash;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Create a new dictionary object.
+ @param size Optional initial size of the dictionary.
+ @return 1 newly allocated dictionary objet.
+
+ This function allocates a new dictionary object of given size and returns
+ it. If you do not know in advance (roughly) the number of entries in the
+ dictionary, give size=0.
+ */
+/*-------------------------------------------------------------------------*/
+dictionary *dictionary_new(size_t size) {
+ dictionary *d;
+
+ /* If no size was specified, allocate space for DICTMINSZ */
+ if (size < DICTMINSZ)
+ size = DICTMINSZ;
+
+ d = (dictionary *)calloc(1, sizeof *d);
+
+ if (d) {
+ d->size = size;
+ d->val = (char **)calloc(size, sizeof *d->val);
+ d->key = (char **)calloc(size, sizeof *d->key);
+ d->hash = (unsigned *)calloc(size, sizeof *d->hash);
+ }
+ return d;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Delete a dictionary object
+ @param d dictionary object to deallocate.
+ @return void
+
+ Deallocate a dictionary object and all memory associated to it.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_del(dictionary *d) {
+ ssize_t i;
+
+ if (d == NULL)
+ return;
+ for (i = 0; i < d->size; i++) {
+ if (d->key[i] != NULL)
+ free(d->key[i]);
+ if (d->val[i] != NULL)
+ free(d->val[i]);
+ }
+ free(d->val);
+ free(d->key);
+ free(d->hash);
+ free(d);
+ return;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get a value from a dictionary.
+ @param d dictionary object to search.
+ @param key Key to look for in the dictionary.
+ @param def Default value to return if key not found.
+ @return 1 pointer to internally allocated character string.
+
+ This function locates a key in a dictionary and returns a pointer to its
+ value, or the passed 'def' pointer if no such key can be found in
+ dictionary. The returned character pointer points to data internal to the
+ dictionary object, you should not try to free it or modify it.
+ */
+/*--------------------------------------------------------------------------*/
+const char *dictionary_get(const dictionary *d, const char *key, const char *def) {
+ unsigned hash;
+ ssize_t i;
+
+ hash = dictionary_hash(key);
+ for (i = 0; i < d->size; i++) {
+ if (d->key[i] == NULL)
+ continue;
+ /* Compare hash */
+ if (hash == d->hash[i]) {
+ /* Compare string, to avoid hash collisions */
+ if (!strcmp(key, d->key[i])) {
+ return d->val[i];
+ }
+ }
+ }
+ return def;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Set a value in a dictionary.
+ @param d dictionary object to modify.
+ @param key Key to modify or add.
+ @param val Value to add.
+ @return int 0 if Ok, anything else otherwise
+
+ If the given key is found in the dictionary, the associated value is
+ replaced by the provided one. If the key cannot be found in the
+ dictionary, it is added to it.
+
+ It is Ok to provide a NULL value for val, but NULL values for the dictionary
+ or the key are considered as errors: the function will return immediately
+ in such a case.
+
+ Notice that if you dictionary_set a variable to NULL, a call to
+ dictionary_get will return a NULL value: the variable will be found, and
+ its value (NULL) is returned. In other words, setting the variable
+ content to NULL is equivalent to deleting the variable from the
+ dictionary. It is not possible (in this implementation) to have a key in
+ the dictionary without value.
+
+ This function returns non-zero in case of failure.
+ */
+/*--------------------------------------------------------------------------*/
+int dictionary_set(dictionary *d, const char *key, const char *val) {
+ ssize_t i;
+ unsigned hash;
+
+ if (d == NULL || key == NULL)
+ return -1;
+
+ /* Compute hash for this key */
+ hash = dictionary_hash(key);
+ /* Find if value is already in dictionary */
+ if (d->n > 0) {
+ for (i = 0; i < d->size; i++) {
+ if (d->key[i] == NULL)
+ continue;
+ if (hash == d->hash[i]) { /* Same hash value */
+ if (!strcmp(key, d->key[i])) { /* Same key */
+ /* Found a value: modify and return */
+ if (d->val[i] != NULL)
+ free(d->val[i]);
+ d->val[i] = (val ? xstrdup(val) : NULL);
+ /* Value has been modified: return */
+ return 0;
+ }
+ }
+ }
+ }
+ /* Add a new value */
+ /* See if dictionary needs to grow */
+ if (d->n == d->size) {
+ /* Reached maximum size: reallocate dictionary */
+ if (dictionary_grow(d) != 0)
+ return -1;
+ }
+
+ /* Insert key in the first empty slot. Start at d->n and wrap at
+ d->size. Because d->n < d->size this will necessarily
+ terminate. */
+ for (i = d->n; d->key[i];) {
+ if (++i == d->size)
+ i = 0;
+ }
+ /* Copy key */
+ d->key[i] = xstrdup(key);
+ d->val[i] = (val ? xstrdup(val) : NULL);
+ d->hash[i] = hash;
+ d->n++;
+ return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Delete a key in a dictionary
+ @param d dictionary object to modify.
+ @param key Key to remove.
+ @return void
+
+ This function deletes a key in a dictionary. Nothing is done if the
+ key cannot be found.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_unset(dictionary *d, const char *key) {
+ unsigned hash;
+ ssize_t i;
+
+ if (key == NULL || d == NULL) {
+ return;
+ }
+
+ hash = dictionary_hash(key);
+ for (i = 0; i < d->size; i++) {
+ if (d->key[i] == NULL)
+ continue;
+ /* Compare hash */
+ if (hash == d->hash[i]) {
+ /* Compare string, to avoid hash collisions */
+ if (!strcmp(key, d->key[i])) {
+ /* Found key */
+ break;
+ }
+ }
+ }
+ if (i >= d->size)
+ /* Key not found */
+ return;
+
+ free(d->key[i]);
+ d->key[i] = NULL;
+ if (d->val[i] != NULL) {
+ free(d->val[i]);
+ d->val[i] = NULL;
+ }
+ d->hash[i] = 0;
+ d->n--;
+ return;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Dump a dictionary to an opened file pointer.
+ @param d Dictionary to dump
+ @param f Opened file pointer.
+ @return void
+
+ Dumps a dictionary onto an opened file pointer. Key pairs are printed out
+ as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as
+ output file pointers.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_dump(const dictionary *d, FILE *out) {
+ ssize_t i;
+
+ if (d == NULL || out == NULL)
+ return;
+ if (d->n < 1) {
+ fprintf(out, "empty dictionary\n");
+ return;
+ }
+ for (i = 0; i < d->size; i++) {
+ if (d->key[i]) {
+ fprintf(out, "%20s\t[%s]\n", d->key[i], d->val[i] ? d->val[i] : "UNDEF");
+ }
+ }
+ return;
+}
diff --git a/iniparser/src/dictionary.h b/iniparser/src/dictionary.h
new file mode 100644
index 0000000..796c4ff
--- /dev/null
+++ b/iniparser/src/dictionary.h
@@ -0,0 +1,169 @@
+
+/*-------------------------------------------------------------------------*/
+/**
+ @file dictionary.h
+ @author N. Devillard
+ @brief Implements a dictionary for string variables.
+
+ This module implements a simple dictionary object, i.e. a list
+ of string/string associations. This object is useful to store e.g.
+ informations retrieved from a configuration file (ini files).
+*/
+/*--------------------------------------------------------------------------*/
+
+#ifndef _DICTIONARY_H_
+#define _DICTIONARY_H_
+
+/*---------------------------------------------------------------------------
+ Includes
+ ---------------------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*---------------------------------------------------------------------------
+ New types
+ ---------------------------------------------------------------------------*/
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Dictionary object
+
+ This object contains a list of string/string associations. Each
+ association is identified by a unique string key. Looking up values
+ in the dictionary is speeded up by the use of a (hopefully collision-free)
+ hash function.
+ */
+/*-------------------------------------------------------------------------*/
+typedef struct _dictionary_ {
+ int n; /** Number of entries in dictionary */
+ ssize_t size; /** Storage size */
+ char **val; /** List of string values */
+ char **key; /** List of string keys */
+ unsigned *hash; /** List of hash values for keys */
+} dictionary;
+
+/*---------------------------------------------------------------------------
+ Function prototypes
+ ---------------------------------------------------------------------------*/
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Compute the hash key for a string.
+ @param key Character string to use for key.
+ @return 1 unsigned int on at least 32 bits.
+
+ This hash function has been taken from an Article in Dr Dobbs Journal.
+ This is normally a collision-free function, distributing keys evenly.
+ The key is stored anyway in the struct so that collision can be avoided
+ by comparing the key itself in last resort.
+ */
+/*--------------------------------------------------------------------------*/
+unsigned dictionary_hash(const char *key);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Create a new dictionary object.
+ @param size Optional initial size of the dictionary.
+ @return 1 newly allocated dictionary objet.
+
+ This function allocates a new dictionary object of given size and returns
+ it. If you do not know in advance (roughly) the number of entries in the
+ dictionary, give size=0.
+ */
+/*--------------------------------------------------------------------------*/
+dictionary *dictionary_new(size_t size);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Delete a dictionary object
+ @param d dictionary object to deallocate.
+ @return void
+
+ Deallocate a dictionary object and all memory associated to it.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_del(dictionary *vd);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get a value from a dictionary.
+ @param d dictionary object to search.
+ @param key Key to look for in the dictionary.
+ @param def Default value to return if key not found.
+ @return 1 pointer to internally allocated character string.
+
+ This function locates a key in a dictionary and returns a pointer to its
+ value, or the passed 'def' pointer if no such key can be found in
+ dictionary. The returned character pointer points to data internal to the
+ dictionary object, you should not try to free it or modify it.
+ */
+/*--------------------------------------------------------------------------*/
+const char *dictionary_get(const dictionary *d, const char *key, const char *def);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Set a value in a dictionary.
+ @param d dictionary object to modify.
+ @param key Key to modify or add.
+ @param val Value to add.
+ @return int 0 if Ok, anything else otherwise
+
+ If the given key is found in the dictionary, the associated value is
+ replaced by the provided one. If the key cannot be found in the
+ dictionary, it is added to it.
+
+ It is Ok to provide a NULL value for val, but NULL values for the dictionary
+ or the key are considered as errors: the function will return immediately
+ in such a case.
+
+ Notice that if you dictionary_set a variable to NULL, a call to
+ dictionary_get will return a NULL value: the variable will be found, and
+ its value (NULL) is returned. In other words, setting the variable
+ content to NULL is equivalent to deleting the variable from the
+ dictionary. It is not possible (in this implementation) to have a key in
+ the dictionary without value.
+
+ This function returns non-zero in case of failure.
+ */
+/*--------------------------------------------------------------------------*/
+int dictionary_set(dictionary *vd, const char *key, const char *val);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Delete a key in a dictionary
+ @param d dictionary object to modify.
+ @param key Key to remove.
+ @return void
+
+ This function deletes a key in a dictionary. Nothing is done if the
+ key cannot be found.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_unset(dictionary *d, const char *key);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Dump a dictionary to an opened file pointer.
+ @param d Dictionary to dump
+ @param f Opened file pointer.
+ @return void
+
+ Dumps a dictionary onto an opened file pointer. Key pairs are printed out
+ as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as
+ output file pointers.
+ */
+/*--------------------------------------------------------------------------*/
+void dictionary_dump(const dictionary *d, FILE *out);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/iniparser/src/iniparser.c b/iniparser/src/iniparser.c
new file mode 100644
index 0000000..339bc1f
--- /dev/null
+++ b/iniparser/src/iniparser.c
@@ -0,0 +1,743 @@
+
+/*-------------------------------------------------------------------------*/
+/**
+ @file iniparser.c
+ @author N. Devillard
+ @brief Parser for ini files.
+*/
+/*--------------------------------------------------------------------------*/
+/*---------------------------- Includes ------------------------------------*/
+#include "iniparser.h"
+#include <ctype.h>
+
+/*---------------------------- Defines -------------------------------------*/
+#define ASCIILINESZ (1024)
+#define INI_INVALID_KEY ((char *)-1)
+
+/*---------------------------------------------------------------------------
+ Private to this module
+ ---------------------------------------------------------------------------*/
+/**
+ * This enum stores the status for each parsed line (internal use only).
+ */
+typedef enum _line_status_ {
+ LINE_UNPROCESSED,
+ LINE_ERROR,
+ LINE_EMPTY,
+ LINE_COMMENT,
+ LINE_SECTION,
+ LINE_VALUE
+} line_status;
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Convert a string to lowercase.
+ @param in String to convert.
+ @param out Output buffer.
+ @param len Size of the out buffer.
+ @return ptr to the out buffer or NULL if an error occured.
+
+ This function convert a string into lowercase.
+ At most len - 1 elements of the input string will be converted.
+ */
+/*--------------------------------------------------------------------------*/
+static const char *strlwc(const char *in, char *out, unsigned len) {
+ unsigned i;
+
+ if (in == NULL || out == NULL || len == 0)
+ return NULL;
+ i = 0;
+ while (in[i] != '\0' && i < len - 1) {
+ out[i] = (char)tolower((int)in[i]);
+ i++;
+ }
+ out[i] = '\0';
+ return out;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Duplicate a string
+ @param s String to duplicate
+ @return Pointer to a newly allocated string, to be freed with free()
+
+ This is a replacement for strdup(). This implementation is provided
+ for systems that do not have it.
+ */
+/*--------------------------------------------------------------------------*/
+static char *xstrdup(const char *s) {
+ char *t;
+ size_t len;
+ if (!s)
+ return NULL;
+
+ len = strlen(s) + 1;
+ t = (char *)malloc(len);
+ if (t) {
+ memcpy(t, s, len);
+ }
+ return t;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Remove blanks at the beginning and the end of a string.
+ @param str String to parse and alter.
+ @return unsigned New size of the string.
+ */
+/*--------------------------------------------------------------------------*/
+unsigned strstrip(char *s) {
+ char *last = NULL;
+ char *dest = s;
+
+ if (s == NULL)
+ return 0;
+
+ last = s + strlen(s);
+ while (isspace((int)*s) && *s)
+ s++;
+ while (last > s) {
+ if (!isspace((int)*(last - 1)))
+ break;
+ last--;
+ }
+ *last = (char)0;
+
+ memmove(dest, s, last - s + 1);
+ return last - s;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get number of sections in a dictionary
+ @param d Dictionary to examine
+ @return int Number of sections found in dictionary
+
+ This function returns the number of sections found in a dictionary.
+ The test to recognize sections is done on the string stored in the
+ dictionary: a section name is given as "section" whereas a key is
+ stored as "section:key", thus the test looks for entries that do not
+ contain a colon.
+
+ This clearly fails in the case a section name contains a colon, but
+ this should simply be avoided.
+
+ This function returns -1 in case of error.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getnsec(const dictionary *d) {
+ int i;
+ int nsec;
+
+ if (d == NULL)
+ return -1;
+ nsec = 0;
+ for (i = 0; i < d->size; i++) {
+ if (d->key[i] == NULL)
+ continue;
+ if (strchr(d->key[i], ':') == NULL) {
+ nsec++;
+ }
+ }
+ return nsec;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get name for section n in a dictionary.
+ @param d Dictionary to examine
+ @param n Section number (from 0 to nsec-1).
+ @return Pointer to char string
+
+ This function locates the n-th section in a dictionary and returns
+ its name as a pointer to a string statically allocated inside the
+ dictionary. Do not free or modify the returned string!
+
+ This function returns NULL in case of error.
+ */
+/*--------------------------------------------------------------------------*/
+const char *iniparser_getsecname(const dictionary *d, int n) {
+ int i;
+ int foundsec;
+
+ if (d == NULL || n < 0)
+ return NULL;
+ foundsec = 0;
+ for (i = 0; i < d->size; i++) {
+ if (d->key[i] == NULL)
+ continue;
+ if (strchr(d->key[i], ':') == NULL) {
+ foundsec++;
+ if (foundsec > n)
+ break;
+ }
+ }
+ if (foundsec <= n) {
+ return NULL;
+ }
+ return d->key[i];
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Dump a dictionary to an opened file pointer.
+ @param d Dictionary to dump.
+ @param f Opened file pointer to dump to.
+ @return void
+
+ This function prints out the contents of a dictionary, one element by
+ line, onto the provided file pointer. It is OK to specify @c stderr
+ or @c stdout as output files. This function is meant for debugging
+ purposes mostly.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_dump(const dictionary *d, FILE *f) {
+ int i;
+
+ if (d == NULL || f == NULL)
+ return;
+ for (i = 0; i < d->size; i++) {
+ if (d->key[i] == NULL)
+ continue;
+ if (d->val[i] != NULL) {
+ fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]);
+ } else {
+ fprintf(f, "[%s]=UNDEF\n", d->key[i]);
+ }
+ }
+ return;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Save a dictionary to a loadable ini file
+ @param d Dictionary to dump
+ @param f Opened file pointer to dump to
+ @return void
+
+ This function dumps a given dictionary into a loadable ini file.
+ It is Ok to specify @c stderr or @c stdout as output files.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_dump_ini(const dictionary *d, FILE *f) {
+ int i;
+ int nsec;
+ const char *secname;
+
+ if (d == NULL || f == NULL)
+ return;
+
+ nsec = iniparser_getnsec(d);
+ if (nsec < 1) {
+ /* No section in file: dump all keys as they are */
+ for (i = 0; i < d->size; i++) {
+ if (d->key[i] == NULL)
+ continue;
+ fprintf(f, "%s = %s\n", d->key[i], d->val[i]);
+ }
+ return;
+ }
+ for (i = 0; i < nsec; i++) {
+ secname = iniparser_getsecname(d, i);
+ iniparser_dumpsection_ini(d, secname, f);
+ }
+ fprintf(f, "\n");
+ return;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Save a dictionary section to a loadable ini file
+ @param d Dictionary to dump
+ @param s Section name of dictionary to dump
+ @param f Opened file pointer to dump to
+ @return void
+
+ This function dumps a given section of a given dictionary into a loadable ini
+ file. It is Ok to specify @c stderr or @c stdout as output files.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_dumpsection_ini(const dictionary *d, const char *s, FILE *f) {
+ int j;
+ char keym[ASCIILINESZ + 1];
+ int seclen;
+
+ if (d == NULL || f == NULL)
+ return;
+ if (!iniparser_find_entry(d, s))
+ return;
+
+ seclen = (int)strlen(s);
+ fprintf(f, "\n[%s]\n", s);
+ sprintf(keym, "%s:", s);
+ for (j = 0; j < d->size; j++) {
+ if (d->key[j] == NULL)
+ continue;
+ if (!strncmp(d->key[j], keym, seclen + 1)) {
+ fprintf(f, "%-30s = %s\n", d->key[j] + seclen + 1, d->val[j] ? d->val[j] : "");
+ }
+ }
+ fprintf(f, "\n");
+ return;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the number of keys in a section of a dictionary.
+ @param d Dictionary to examine
+ @param s Section name of dictionary to examine
+ @return Number of keys in section
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getsecnkeys(const dictionary *d, const char *s) {
+ int seclen, nkeys;
+ char keym[ASCIILINESZ + 1];
+ int j;
+
+ nkeys = 0;
+
+ if (d == NULL)
+ return nkeys;
+ if (!iniparser_find_entry(d, s))
+ return nkeys;
+
+ seclen = (int)strlen(s);
+ sprintf(keym, "%s:", s);
+
+ for (j = 0; j < d->size; j++) {
+ if (d->key[j] == NULL)
+ continue;
+ if (!strncmp(d->key[j], keym, seclen + 1))
+ nkeys++;
+ }
+
+ return nkeys;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the number of keys in a section of a dictionary.
+ @param d Dictionary to examine
+ @param s Section name of dictionary to examine
+ @param keys Already allocated array to store the keys in
+ @return The pointer passed as `keys` argument or NULL in case of error
+
+ This function queries a dictionary and finds all keys in a given section.
+ The keys argument should be an array of pointers which size has been
+ determined by calling `iniparser_getsecnkeys` function prior to this one.
+
+ Each pointer in the returned char pointer-to-pointer is pointing to
+ a string allocated in the dictionary; do not free or modify them.
+ */
+/*--------------------------------------------------------------------------*/
+const char **iniparser_getseckeys(const dictionary *d, const char *s, const char **keys) {
+ int i, j, seclen;
+ char keym[ASCIILINESZ + 1];
+
+ if (d == NULL || keys == NULL)
+ return NULL;
+ if (!iniparser_find_entry(d, s))
+ return NULL;
+
+ seclen = (int)strlen(s);
+ sprintf(keym, "%s:", s);
+
+ i = 0;
+
+ for (j = 0; j < d->size; j++) {
+ if (d->key[j] == NULL)
+ continue;
+ if (!strncmp(d->key[j], keym, seclen + 1)) {
+ keys[i] = d->key[j];
+ i++;
+ }
+ }
+
+ return keys;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param def Default value to return if key not found.
+ @return pointer to statically allocated character string
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the pointer passed as 'def' is returned.
+ The returned char pointer is pointing to a string allocated in
+ the dictionary, do not free or modify it.
+ */
+/*--------------------------------------------------------------------------*/
+const char *iniparser_getstring(const dictionary *d, const char *key, const char *def) {
+ const char *lc_key;
+ const char *sval;
+ char tmp_str[ASCIILINESZ + 1];
+
+ if (d == NULL || key == NULL)
+ return def;
+
+ lc_key = strlwc(key, tmp_str, sizeof(tmp_str));
+ sval = dictionary_get(d, lc_key, def);
+ return sval;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key, convert to an int
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param notfound Value to return in case of error
+ @return integer
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the notfound value is returned.
+
+ Supported values for integers include the usual C notation
+ so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
+ are supported. Examples:
+
+ "42" -> 42
+ "042" -> 34 (octal -> decimal)
+ "0x42" -> 66 (hexa -> decimal)
+
+ Warning: the conversion may overflow in various ways. Conversion is
+ totally outsourced to strtol(), see the associated man page for overflow
+ handling.
+
+ Credits: Thanks to A. Becker for suggesting strtol()
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getint(const dictionary *d, const char *key, int notfound) {
+ const char *str;
+
+ str = iniparser_getstring(d, key, INI_INVALID_KEY);
+ if (str == INI_INVALID_KEY)
+ return notfound;
+ return (int)strtol(str, NULL, 0);
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key, convert to a double
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param notfound Value to return in case of error
+ @return double
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the notfound value is returned.
+ */
+/*--------------------------------------------------------------------------*/
+double iniparser_getdouble(const dictionary *d, const char *key, double notfound) {
+ const char *str;
+
+ str = iniparser_getstring(d, key, INI_INVALID_KEY);
+ if (str == INI_INVALID_KEY)
+ return notfound;
+ return atof(str);
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key, convert to a boolean
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param notfound Value to return in case of error
+ @return integer
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the notfound value is returned.
+
+ A true boolean is found if one of the following is matched:
+
+ - A string starting with 'y'
+ - A string starting with 'Y'
+ - A string starting with 't'
+ - A string starting with 'T'
+ - A string starting with '1'
+
+ A false boolean is found if one of the following is matched:
+
+ - A string starting with 'n'
+ - A string starting with 'N'
+ - A string starting with 'f'
+ - A string starting with 'F'
+ - A string starting with '0'
+
+ The notfound value returned if no boolean is identified, does not
+ necessarily have to be 0 or 1.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getboolean(const dictionary *d, const char *key, int notfound) {
+ int ret;
+ const char *c;
+
+ c = iniparser_getstring(d, key, INI_INVALID_KEY);
+ if (c == INI_INVALID_KEY)
+ return notfound;
+ if (c[0] == 'y' || c[0] == 'Y' || c[0] == '1' || c[0] == 't' || c[0] == 'T') {
+ ret = 1;
+ } else if (c[0] == 'n' || c[0] == 'N' || c[0] == '0' || c[0] == 'f' || c[0] == 'F') {
+ ret = 0;
+ } else {
+ ret = notfound;
+ }
+ return ret;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Finds out if a given entry exists in a dictionary
+ @param ini Dictionary to search
+ @param entry Name of the entry to look for
+ @return integer 1 if entry exists, 0 otherwise
+
+ Finds out if a given entry exists in the dictionary. Since sections
+ are stored as keys with NULL associated values, this is the only way
+ of querying for the presence of sections in a dictionary.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_find_entry(const dictionary *ini, const char *entry) {
+ int found = 0;
+ if (iniparser_getstring(ini, entry, INI_INVALID_KEY) != INI_INVALID_KEY) {
+ found = 1;
+ }
+ return found;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Set an entry in a dictionary.
+ @param ini Dictionary to modify.
+ @param entry Entry to modify (entry name)
+ @param val New value to associate to the entry.
+ @return int 0 if Ok, -1 otherwise.
+
+ If the given entry can be found in the dictionary, it is modified to
+ contain the provided value. If it cannot be found, the entry is created.
+ It is Ok to set val to NULL.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_set(dictionary *ini, const char *entry, const char *val) {
+ char tmp_str[ASCIILINESZ + 1];
+ return dictionary_set(ini, strlwc(entry, tmp_str, sizeof(tmp_str)), val);
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Delete an entry in a dictionary
+ @param ini Dictionary to modify
+ @param entry Entry to delete (entry name)
+ @return void
+
+ If the given entry can be found, it is deleted from the dictionary.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_unset(dictionary *ini, const char *entry) {
+ char tmp_str[ASCIILINESZ + 1];
+ dictionary_unset(ini, strlwc(entry, tmp_str, sizeof(tmp_str)));
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Load a single line from an INI file
+ @param input_line Input line, may be concatenated multi-line input
+ @param section Output space to store section
+ @param key Output space to store key
+ @param value Output space to store value
+ @return line_status value
+ */
+/*--------------------------------------------------------------------------*/
+static line_status iniparser_line(const char *input_line, char *section, char *key, char *value) {
+ line_status sta;
+ char *line = NULL;
+ size_t len;
+
+ line = xstrdup(input_line);
+ len = strstrip(line);
+
+ sta = LINE_UNPROCESSED;
+ if (len < 1) {
+ /* Empty line */
+ sta = LINE_EMPTY;
+ } else if (line[0] == '#' || line[0] == ';') {
+ /* Comment line */
+ sta = LINE_COMMENT;
+ } else if (line[0] == '[' && line[len - 1] == ']') {
+ /* Section name */
+ sscanf(line, "[%[^]]", section);
+ strstrip(section);
+ strlwc(section, section, len);
+ sta = LINE_SECTION;
+ } else if (sscanf(line, "%[^=] = \"%[^\"]\"", key, value) == 2 ||
+ sscanf(line, "%[^=] = '%[^\']'", key, value) == 2) {
+ /* Usual key=value with quotes, with or without comments */
+ strstrip(key);
+ strlwc(key, key, len);
+ /* Don't strip spaces from values surrounded with quotes */
+ /*
+ * sscanf cannot handle '' or "" as empty values
+ * this is done here
+ */
+ if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) {
+ value[0] = 0;
+ }
+ sta = LINE_VALUE;
+ } else if (sscanf(line, "%[^=] = %[^;#]", key, value) == 2) {
+ /* Usual key=value without quotes, with or without comments */
+ strstrip(key);
+ strlwc(key, key, len);
+ strstrip(value);
+
+ sta = LINE_VALUE;
+ } else if (sscanf(line, "%[^=] = %[;#]", key, value) == 2 ||
+ sscanf(line, "%[^=] %[=]", key, value) == 2) {
+ /*
+ * Special cases:
+ * key=
+ * key=;
+ * key=#
+ */
+ strstrip(key);
+ strlwc(key, key, len);
+ value[0] = 0;
+ sta = LINE_VALUE;
+ } else {
+ /* Generate syntax error */
+ sta = LINE_ERROR;
+ }
+
+ free(line);
+ return sta;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Parse an ini file and return an allocated dictionary object
+ @param ininame Name of the ini file to read.
+ @return Pointer to newly allocated dictionary
+
+ This is the parser for ini files. This function is called, providing
+ the name of the file to be read. It returns a dictionary object that
+ should not be accessed directly, but through accessor functions
+ instead.
+
+ The returned dictionary must be freed using iniparser_freedict().
+ */
+/*--------------------------------------------------------------------------*/
+dictionary *iniparser_load(const char *ininame) {
+ FILE *in;
+
+ char line[ASCIILINESZ + 1];
+ char section[ASCIILINESZ + 1];
+ char key[ASCIILINESZ + 1];
+ char tmp[(ASCIILINESZ * 2) + 1];
+ char val[ASCIILINESZ + 1];
+
+ int last = 0;
+ int len;
+ int lineno = 0;
+ int errs = 0;
+
+ dictionary *dict;
+
+ if ((in = fopen(ininame, "r")) == NULL) {
+ return NULL;
+ }
+
+ dict = dictionary_new(0);
+ if (!dict) {
+ fclose(in);
+ return NULL;
+ }
+
+ memset(line, 0, ASCIILINESZ);
+ memset(section, 0, ASCIILINESZ);
+ memset(key, 0, ASCIILINESZ);
+ memset(val, 0, ASCIILINESZ);
+ last = 0;
+
+ while (fgets(line + last, ASCIILINESZ - last, in) != NULL) {
+ lineno++;
+ len = (int)strlen(line) - 1;
+ if (len == 0)
+ continue;
+ /* Safety check against buffer overflows */
+ if (line[len] != '\n' && !feof(in)) {
+ fprintf(stderr, "iniparser: input line too long in %s (%d)\n", ininame, lineno);
+ dictionary_del(dict);
+ fclose(in);
+ return NULL;
+ }
+ /* Get rid of \n and spaces at end of line */
+ while ((len >= 0) && ((line[len] == '\n') || (isspace(line[len])))) {
+ line[len] = 0;
+ len--;
+ }
+ if (len < 0) { /* Line was entirely \n and/or spaces */
+ len = 0;
+ }
+ /* Detect multi-line */
+ if (line[len] == '\\') {
+ /* Multi-line value */
+ last = len;
+ continue;
+ } else {
+ last = 0;
+ }
+ switch (iniparser_line(line, section, key, val)) {
+ case LINE_EMPTY:
+ case LINE_COMMENT:
+ break;
+
+ case LINE_SECTION:
+ errs = dictionary_set(dict, section, NULL);
+ break;
+
+ case LINE_VALUE:
+ sprintf(tmp, "%s:%s", section, key);
+ errs = dictionary_set(dict, tmp, val);
+ break;
+
+ case LINE_ERROR:
+ fprintf(stderr, "iniparser: syntax error in %s (%d):\n", ininame, lineno);
+ fprintf(stderr, "-> %s\n", line);
+ errs++;
+ break;
+
+ default:
+ break;
+ }
+ memset(line, 0, ASCIILINESZ);
+ last = 0;
+ if (errs < 0) {
+ fprintf(stderr, "iniparser: memory allocation failure\n");
+ break;
+ }
+ }
+ if (errs) {
+ dictionary_del(dict);
+ dict = NULL;
+ }
+ fclose(in);
+ return dict;
+}
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Free all memory associated to an ini dictionary
+ @param d Dictionary to free
+ @return void
+
+ Free all memory associated to an ini dictionary.
+ It is mandatory to call this function before the dictionary object
+ gets out of the current context.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_freedict(dictionary *d) { dictionary_del(d); }
diff --git a/iniparser/src/iniparser.h b/iniparser/src/iniparser.h
new file mode 100644
index 0000000..e76b8ce
--- /dev/null
+++ b/iniparser/src/iniparser.h
@@ -0,0 +1,313 @@
+
+/*-------------------------------------------------------------------------*/
+/**
+ @file iniparser.h
+ @author N. Devillard
+ @brief Parser for ini files.
+*/
+/*--------------------------------------------------------------------------*/
+
+#ifndef _INIPARSER_H_
+#define _INIPARSER_H_
+
+/*---------------------------------------------------------------------------
+ Includes
+ ---------------------------------------------------------------------------*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * The following #include is necessary on many Unixes but not Linux.
+ * It is not needed for Windows platforms.
+ * Uncomment it if needed.
+ */
+/* #include <unistd.h> */
+
+#include "dictionary.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get number of sections in a dictionary
+ @param d Dictionary to examine
+ @return int Number of sections found in dictionary
+
+ This function returns the number of sections found in a dictionary.
+ The test to recognize sections is done on the string stored in the
+ dictionary: a section name is given as "section" whereas a key is
+ stored as "section:key", thus the test looks for entries that do not
+ contain a colon.
+
+ This clearly fails in the case a section name contains a colon, but
+ this should simply be avoided.
+
+ This function returns -1 in case of error.
+ */
+/*--------------------------------------------------------------------------*/
+
+int iniparser_getnsec(const dictionary *d);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get name for section n in a dictionary.
+ @param d Dictionary to examine
+ @param n Section number (from 0 to nsec-1).
+ @return Pointer to char string
+
+ This function locates the n-th section in a dictionary and returns
+ its name as a pointer to a string statically allocated inside the
+ dictionary. Do not free or modify the returned string!
+
+ This function returns NULL in case of error.
+ */
+/*--------------------------------------------------------------------------*/
+
+const char *iniparser_getsecname(const dictionary *d, int n);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Save a dictionary to a loadable ini file
+ @param d Dictionary to dump
+ @param f Opened file pointer to dump to
+ @return void
+
+ This function dumps a given dictionary into a loadable ini file.
+ It is Ok to specify @c stderr or @c stdout as output files.
+ */
+/*--------------------------------------------------------------------------*/
+
+void iniparser_dump_ini(const dictionary *d, FILE *f);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Save a dictionary section to a loadable ini file
+ @param d Dictionary to dump
+ @param s Section name of dictionary to dump
+ @param f Opened file pointer to dump to
+ @return void
+
+ This function dumps a given section of a given dictionary into a loadable ini
+ file. It is Ok to specify @c stderr or @c stdout as output files.
+ */
+/*--------------------------------------------------------------------------*/
+
+void iniparser_dumpsection_ini(const dictionary *d, const char *s, FILE *f);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Dump a dictionary to an opened file pointer.
+ @param d Dictionary to dump.
+ @param f Opened file pointer to dump to.
+ @return void
+
+ This function prints out the contents of a dictionary, one element by
+ line, onto the provided file pointer. It is OK to specify @c stderr
+ or @c stdout as output files. This function is meant for debugging
+ purposes mostly.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_dump(const dictionary *d, FILE *f);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the number of keys in a section of a dictionary.
+ @param d Dictionary to examine
+ @param s Section name of dictionary to examine
+ @return Number of keys in section
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getsecnkeys(const dictionary *d, const char *s);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the number of keys in a section of a dictionary.
+ @param d Dictionary to examine
+ @param s Section name of dictionary to examine
+ @param keys Already allocated array to store the keys in
+ @return The pointer passed as `keys` argument or NULL in case of error
+
+ This function queries a dictionary and finds all keys in a given section.
+ The keys argument should be an array of pointers which size has been
+ determined by calling `iniparser_getsecnkeys` function prior to this one.
+
+ Each pointer in the returned char pointer-to-pointer is pointing to
+ a string allocated in the dictionary; do not free or modify them.
+ */
+/*--------------------------------------------------------------------------*/
+const char **iniparser_getseckeys(const dictionary *d, const char *s, const char **keys);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param def Default value to return if key not found.
+ @return pointer to statically allocated character string
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the pointer passed as 'def' is returned.
+ The returned char pointer is pointing to a string allocated in
+ the dictionary, do not free or modify it.
+ */
+/*--------------------------------------------------------------------------*/
+const char *iniparser_getstring(const dictionary *d, const char *key, const char *def);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key, convert to an int
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param notfound Value to return in case of error
+ @return integer
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the notfound value is returned.
+
+ Supported values for integers include the usual C notation
+ so decimal, octal (starting with 0) and hexadecimal (starting with 0x)
+ are supported. Examples:
+
+ - "42" -> 42
+ - "042" -> 34 (octal -> decimal)
+ - "0x42" -> 66 (hexa -> decimal)
+
+ Warning: the conversion may overflow in various ways. Conversion is
+ totally outsourced to strtol(), see the associated man page for overflow
+ handling.
+
+ Credits: Thanks to A. Becker for suggesting strtol()
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getint(const dictionary *d, const char *key, int notfound);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key, convert to a double
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param notfound Value to return in case of error
+ @return double
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the notfound value is returned.
+ */
+/*--------------------------------------------------------------------------*/
+double iniparser_getdouble(const dictionary *d, const char *key, double notfound);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Get the string associated to a key, convert to a boolean
+ @param d Dictionary to search
+ @param key Key string to look for
+ @param notfound Value to return in case of error
+ @return integer
+
+ This function queries a dictionary for a key. A key as read from an
+ ini file is given as "section:key". If the key cannot be found,
+ the notfound value is returned.
+
+ A true boolean is found if one of the following is matched:
+
+ - A string starting with 'y'
+ - A string starting with 'Y'
+ - A string starting with 't'
+ - A string starting with 'T'
+ - A string starting with '1'
+
+ A false boolean is found if one of the following is matched:
+
+ - A string starting with 'n'
+ - A string starting with 'N'
+ - A string starting with 'f'
+ - A string starting with 'F'
+ - A string starting with '0'
+
+ The notfound value returned if no boolean is identified, does not
+ necessarily have to be 0 or 1.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_getboolean(const dictionary *d, const char *key, int notfound);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Set an entry in a dictionary.
+ @param ini Dictionary to modify.
+ @param entry Entry to modify (entry name)
+ @param val New value to associate to the entry.
+ @return int 0 if Ok, -1 otherwise.
+
+ If the given entry can be found in the dictionary, it is modified to
+ contain the provided value. If it cannot be found, the entry is created.
+ It is Ok to set val to NULL.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_set(dictionary *ini, const char *entry, const char *val);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Delete an entry in a dictionary
+ @param ini Dictionary to modify
+ @param entry Entry to delete (entry name)
+ @return void
+
+ If the given entry can be found, it is deleted from the dictionary.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_unset(dictionary *ini, const char *entry);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Finds out if a given entry exists in a dictionary
+ @param ini Dictionary to search
+ @param entry Name of the entry to look for
+ @return integer 1 if entry exists, 0 otherwise
+
+ Finds out if a given entry exists in the dictionary. Since sections
+ are stored as keys with NULL associated values, this is the only way
+ of querying for the presence of sections in a dictionary.
+ */
+/*--------------------------------------------------------------------------*/
+int iniparser_find_entry(const dictionary *ini, const char *entry);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Parse an ini file and return an allocated dictionary object
+ @param ininame Name of the ini file to read.
+ @return Pointer to newly allocated dictionary
+
+ This is the parser for ini files. This function is called, providing
+ the name of the file to be read. It returns a dictionary object that
+ should not be accessed directly, but through accessor functions
+ instead.
+
+ The returned dictionary must be freed using iniparser_freedict().
+ */
+/*--------------------------------------------------------------------------*/
+dictionary *iniparser_load(const char *ininame);
+
+/*-------------------------------------------------------------------------*/
+/**
+ @brief Free all memory associated to an ini dictionary
+ @param d Dictionary to free
+ @return void
+
+ Free all memory associated to an ini dictionary.
+ It is mandatory to call this function before the dictionary object
+ gets out of the current context.
+ */
+/*--------------------------------------------------------------------------*/
+void iniparser_freedict(dictionary *d);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/input/alsa.c b/input/alsa.c
new file mode 100644
index 0000000..8fcbc28
--- /dev/null
+++ b/input/alsa.c
@@ -0,0 +1,165 @@
+// input: ALSA
+#include "input/alsa.h"
+#include "debug.h"
+#include "input/common.h"
+
+#include <alloca.h>
+#include <alsa/asoundlib.h>
+#include <math.h>
+
+// assuming stereo
+#define CHANNELS_COUNT 2
+#define SAMPLE_RATE 44100
+
+static void initialize_audio_parameters(snd_pcm_t **handle, struct audio_data *audio,
+ snd_pcm_uframes_t *frames) {
+ // alsa: open device to capture audio
+ int err = snd_pcm_open(handle, audio->source, SND_PCM_STREAM_CAPTURE, 0);
+ if (err < 0) {
+ fprintf(stderr, "error opening stream: %s\n", snd_strerror(err));
+ exit(EXIT_FAILURE);
+ } else
+ debug("open stream successful\n");
+
+ snd_pcm_hw_params_t *params;
+ snd_pcm_hw_params_alloca(&params); // assembling params
+ snd_pcm_hw_params_any(*handle, params); // setting defaults or something
+ // interleaved mode right left right left
+ snd_pcm_hw_params_set_access(*handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
+ // trying to set 16bit
+ snd_pcm_hw_params_set_format(*handle, params, SND_PCM_FORMAT_S16_LE);
+ snd_pcm_hw_params_set_channels(*handle, params, CHANNELS_COUNT);
+ unsigned int sample_rate = SAMPLE_RATE;
+ // trying our rate
+ snd_pcm_hw_params_set_rate_near(*handle, params, &sample_rate, NULL);
+ // number of frames pr read
+ snd_pcm_hw_params_set_period_size_near(*handle, params, frames, NULL);
+ err = snd_pcm_hw_params(*handle, params); // attempting to set params
+ if (err < 0) {
+ fprintf(stderr, "unable to set hw parameters: %s\n", snd_strerror(err));
+ exit(EXIT_FAILURE);
+ }
+
+ if ((err = snd_pcm_prepare(*handle)) < 0) {
+ fprintf(stderr, "cannot prepare audio interface for use (%s)\n", snd_strerror(err));
+ exit(EXIT_FAILURE);
+ }
+
+ // getting actual format
+ snd_pcm_hw_params_get_format(params, (snd_pcm_format_t *)&sample_rate);
+ // converting result to number of bits
+ if (sample_rate <= 5)
+ audio->format = 16;
+ else if (sample_rate <= 9)
+ audio->format = 24;
+ else
+ audio->format = 32;
+ snd_pcm_hw_params_get_rate(params, &audio->rate, NULL);
+ snd_pcm_hw_params_get_period_size(params, frames, NULL);
+ // snd_pcm_hw_params_get_period_time(params, &sample_rate, &dir);
+}
+
+static int get_certain_frame(signed char *buffer, int buffer_index, int adjustment) {
+ // using the 10 upper bits this would give me a vert res of 1024, enough...
+ int temp = buffer[buffer_index + adjustment - 1] << 2;
+ int lo = buffer[buffer_index + adjustment - 2] >> 6;
+ if (lo < 0)
+ lo = abs(lo) + 1;
+ if (temp >= 0)
+ temp += lo;
+ else
+ temp -= lo;
+ return temp;
+}
+/*
+static void fill_audio_outs(struct audio_data* audio, signed char* buffer,
+const int size) {
+ int radj = audio->format / 4; // adjustments for interleaved
+ int ladj = audio->format / 8;
+ static int audio_out_buffer_index = 0;
+ // sorting out one channel and only biggest octet
+ for (int buffer_index = 0; buffer_index < size; buffer_index += ladj * 2) {
+ // first channel
+ int tempr = get_certain_frame(buffer, buffer_index, radj);
+ // second channel
+ int templ = get_certain_frame(buffer, buffer_index, ladj);
+
+ // mono: adding channels and storing it in the buffer
+ if (audio->channels == 1)
+ audio->audio_out_l[audio_out_buffer_index] = (templ + tempr) / 2;
+ else { // stereo storing channels in buffer
+ audio->audio_out_l[audio_out_buffer_index] = templ;
+ audio->audio_out_r[audio_out_buffer_index] = tempr;
+ }
+
+ ++audio_out_buffer_index;
+ audio_out_buffer_index %= audio->FFTbufferSize;
+ }
+}
+*/
+#define FRAMES_NUMBER 256
+
+void *input_alsa(void *data) {
+ int err;
+ struct audio_data *audio = (struct audio_data *)data;
+ snd_pcm_t *handle;
+ snd_pcm_uframes_t buffer_size;
+ snd_pcm_uframes_t period_size;
+ snd_pcm_uframes_t frames = FRAMES_NUMBER;
+
+ initialize_audio_parameters(&handle, audio, &frames);
+ snd_pcm_get_params(handle, &buffer_size, &period_size);
+
+ int radj = audio->format / 4; // adjustments for interleaved
+ int ladj = audio->format / 8;
+ int16_t buf[period_size];
+ int32_t buffer32[period_size];
+ frames = period_size / ((audio->format / 8) * CHANNELS_COUNT);
+ // printf("period size: %lu\n", period_size);
+ // exit(0);
+
+ // frames * bits/8 * channels
+ // const int size = frames * (audio->format / 8) * CHANNELS_COUNT;
+ signed char *buffer = malloc(period_size);
+
+ while (!audio->terminate) {
+ switch (audio->format) {
+ case 16:
+ err = snd_pcm_readi(handle, buf, frames);
+ break;
+ case 32:
+ err = snd_pcm_readi(handle, buffer32, frames);
+ for (uint16_t i = 0; i < frames * 2; i++) {
+ buf[i] = buffer32[i] / pow(2, 16);
+ }
+ break;
+ default:
+ err = snd_pcm_readi(handle, buffer, frames);
+ // sorting out one channel and only biggest octet
+ for (uint16_t i = 0; i < period_size * 2; i += ladj * 2) {
+ // first channel
+ buf[i] = get_certain_frame(buffer, i, ladj);
+ // second channel
+ buf[i + 1] = get_certain_frame(buffer, i, radj);
+ }
+ // fill_audio_outs(audio, buffer, period_size);
+ break;
+ }
+
+ if (err == -EPIPE) {
+ /* EPIPE means overrun */
+ debug("overrun occurred\n");
+ snd_pcm_prepare(handle);
+ } else if (err < 0) {
+ debug("error from read: %s\n", snd_strerror(err));
+ } else if (err != (int)frames) {
+ debug("short read, read %d %d frames\n", err, (int)frames);
+ }
+
+ write_to_fftw_input_buffers(buf, frames, data);
+ }
+
+ free(buffer);
+ snd_pcm_close(handle);
+ return NULL;
+}
diff --git a/input/alsa.h b/input/alsa.h
new file mode 100644
index 0000000..daa2f58
--- /dev/null
+++ b/input/alsa.h
@@ -0,0 +1,5 @@
+// header file for alsa, part of cava.
+
+#pragma once
+
+void *input_alsa(void *data);
diff --git a/input/common.c b/input/common.c
new file mode 100644
index 0000000..29fdc89
--- /dev/null
+++ b/input/common.c
@@ -0,0 +1,53 @@
+#include "input/common.h"
+
+#include <string.h>
+
+void reset_output_buffers(struct audio_data *data) {
+ memset(data->in_bass_r, 0, sizeof(double) * 2 * (data->FFTbassbufferSize / 2 + 1));
+ memset(data->in_bass_l, 0, sizeof(double) * 2 * (data->FFTbassbufferSize / 2 + 1));
+ memset(data->in_mid_r, 0, sizeof(double) * 2 * (data->FFTmidbufferSize / 2 + 1));
+ memset(data->in_mid_l, 0, sizeof(double) * 2 * (data->FFTmidbufferSize / 2 + 1));
+ memset(data->in_treble_r, 0, sizeof(double) * 2 * (data->FFTtreblebufferSize / 2 + 1));
+ memset(data->in_treble_l, 0, sizeof(double) * 2 * (data->FFTtreblebufferSize / 2 + 1));
+}
+
+int write_to_fftw_input_buffers(int16_t buf[], int16_t frames, void *data) {
+ struct audio_data *audio = (struct audio_data *)data;
+
+ for (uint16_t i = 0; i < frames * 2; i += 2) {
+ if (audio->channels == 1) {
+ if (audio->average) {
+ audio->in_bass_l[audio->bass_index] = (buf[i] + buf[i + 1]) / 2;
+ }
+ if (audio->left) {
+ audio->in_bass_l[audio->bass_index] = buf[i];
+ }
+ if (audio->right) {
+ audio->in_bass_l[audio->bass_index] = buf[i + 1];
+ }
+ }
+ // stereo storing channels in buffer
+ if (audio->channels == 2) {
+ audio->in_bass_l[audio->bass_index] = buf[i];
+ audio->in_bass_r[audio->bass_index] = buf[i + 1];
+
+ audio->in_mid_r[audio->mid_index] = audio->in_bass_r[audio->bass_index];
+ audio->in_treble_r[audio->treble_index] = audio->in_bass_r[audio->bass_index];
+ }
+
+ audio->in_mid_l[audio->mid_index] = audio->in_bass_l[audio->bass_index];
+ audio->in_treble_l[audio->treble_index] = audio->in_bass_l[audio->bass_index];
+
+ audio->bass_index++;
+ audio->mid_index++;
+ audio->treble_index++;
+ if (audio->bass_index == audio->FFTbassbufferSize - 1)
+ audio->bass_index = 0;
+ if (audio->mid_index == audio->FFTmidbufferSize - 1)
+ audio->mid_index = 0;
+ if (audio->treble_index == audio->FFTtreblebufferSize - 1)
+ audio->treble_index = 0;
+ }
+
+ return 0;
+}
diff --git a/input/common.h b/input/common.h
new file mode 100644
index 0000000..3737f2b
--- /dev/null
+++ b/input/common.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+struct audio_data {
+ int FFTbassbufferSize;
+ int FFTmidbufferSize;
+ int FFTtreblebufferSize;
+ int bass_index;
+ int mid_index;
+ int treble_index;
+ double *in_bass_r, *in_bass_l;
+ double *in_mid_r, *in_mid_l;
+ double *in_treble_r, *in_treble_l;
+ int format;
+ unsigned int rate;
+ char *source; // alsa device, fifo path or pulse source
+ int im; // input mode alsa, fifo or pulse
+ unsigned int channels;
+ bool left, right, average;
+ int terminate; // shared variable used to terminate audio thread
+ char error_message[1024];
+};
+
+void reset_output_buffers(struct audio_data *data);
+
+int write_to_fftw_input_buffers(int16_t buf[], int16_t frames, void *data);
diff --git a/input/fifo.c b/input/fifo.c
new file mode 100644
index 0000000..870785c
--- /dev/null
+++ b/input/fifo.c
@@ -0,0 +1,79 @@
+#include "input/fifo.h"
+#include "input/common.h"
+
+#include <time.h>
+
+#define SAMPLES_PER_BUFFER 512
+
+int open_fifo(const char *path) {
+ int fd = open(path, O_RDONLY);
+ int flags = fcntl(fd, F_GETFL, 0);
+ fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ return fd;
+}
+
+// input: FIFO
+void *input_fifo(void *data) {
+ struct audio_data *audio = (struct audio_data *)data;
+ int bytes_per_sample = audio->format / 8;
+ __attribute__((aligned(sizeof(uint16_t)))) uint8_t buf[SAMPLES_PER_BUFFER * bytes_per_sample];
+ uint16_t *samples =
+ bytes_per_sample == 2 ? (uint16_t *)&buf : calloc(SAMPLES_PER_BUFFER, sizeof(uint16_t));
+
+ int fd = open_fifo(audio->source);
+
+ while (!audio->terminate) {
+ int time_since_last_input = 0;
+ unsigned int offset = 0;
+ do {
+ int num_read = read(fd, buf + offset, sizeof(buf) - offset);
+
+ if (num_read < 1) { // if no bytes read sleep 10ms and zero shared buffer
+ nanosleep(&(struct timespec){.tv_sec = 0, .tv_nsec = 10000000}, NULL);
+ time_since_last_input++;
+
+ if (time_since_last_input > 10) {
+ reset_output_buffers(audio);
+ close(fd);
+
+ fd = open_fifo(audio->source);
+ time_since_last_input = 0;
+ offset = 0;
+ }
+ } else {
+ offset += num_read;
+ time_since_last_input = 0;
+ }
+ } while (offset < sizeof(buf));
+
+ switch (bytes_per_sample) {
+ case 2:
+ // [samples] = [buf] so there's nothing to do here.
+ break;
+ case 3:
+ for (int i = 0; i < SAMPLES_PER_BUFFER; i++) {
+ // Really, a sample is composed of buf[3i + 2] | buf[3i + 1] | buf[3i], but our FFT
+ // only takes 16-bit samples. Since we need to scale them eventually, we can just
+ // do so here by taking the top 2 bytes.
+ samples[i] = (buf[3 * i + 2] << 8) | buf[3 * i + 1];
+ }
+ break;
+ case 4:
+ for (int i = 0; i < SAMPLES_PER_BUFFER; i++) {
+ samples[i] = (buf[4 * i + 3] << 8) | buf[4 * i + 2];
+ }
+ break;
+ }
+
+ // We worked with unsigned ints up until now to save on sign extension, but the FFT wants
+ // signed ints.
+ write_to_fftw_input_buffers((int16_t *)samples, SAMPLES_PER_BUFFER / 2, audio);
+ }
+
+ close(fd);
+ if (bytes_per_sample != 2) {
+ free(samples);
+ }
+
+ return 0;
+}
diff --git a/input/fifo.h b/input/fifo.h
new file mode 100644
index 0000000..52b6ba6
--- /dev/null
+++ b/input/fifo.h
@@ -0,0 +1,5 @@
+// header files for fifo, part of cava
+
+#pragma once
+
+void *input_fifo(void *data);
diff --git a/input/portaudio.c b/input/portaudio.c
new file mode 100644
index 0000000..21ad188
--- /dev/null
+++ b/input/portaudio.c
@@ -0,0 +1,204 @@
+#include "input/portaudio.h"
+#include "input/common.h"
+
+#include <portaudio.h>
+#define PORTBUFSIZE 512
+
+#define SAMPLE_SILENCE -32767
+#define PA_SAMPLE_TYPE paInt16
+typedef short SAMPLE;
+
+typedef struct {
+ int frameIndex; /* Index into sample array. */
+ int maxFrameIndex;
+ SAMPLE *recordedSamples;
+} paTestData;
+
+static struct audio_data *audio;
+// static int n = 0;
+
+static int recordCallback(const void *inputBuffer, void *outputBuffer,
+ unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo,
+ PaStreamCallbackFlags statusFlags, void *userData) {
+ paTestData *data = (paTestData *)userData;
+ SAMPLE *rptr = (SAMPLE *)inputBuffer;
+ long framesToCalc;
+ // long i;
+ int finished;
+ unsigned long framesLeft = data->maxFrameIndex - data->frameIndex;
+ int16_t silence_buffer[PORTBUFSIZE] = {SAMPLE_SILENCE};
+ (void)outputBuffer; // Prevent unused variable warnings.
+ (void)timeInfo;
+ (void)statusFlags;
+ (void)userData;
+
+ if (framesLeft < framesPerBuffer) {
+ framesToCalc = framesLeft;
+ finished = paComplete;
+ } else {
+ framesToCalc = framesPerBuffer;
+ finished = paContinue;
+ }
+
+ if (inputBuffer == NULL) {
+ write_to_fftw_input_buffers(silence_buffer, framesToCalc, audio);
+ /*
+ for(i=0; i<framesToCalc; i++) {
+ if(audio->channels == 1) audio->audio_out_l[n] = SAMPLE_SILENCE;
+ if(audio->channels == 2) {
+ audio->audio_out_l[n] = SAMPLE_SILENCE;
+ audio->audio_out_r[n] = SAMPLE_SILENCE;
+ }
+ if(n == PORTBUFSIZE-1) n = 0;
+ }
+ */
+ } else {
+ write_to_fftw_input_buffers(rptr, framesToCalc, audio);
+ /*
+ for(i=0; i<framesToCalc; i++) {
+ if(audio->channels == 1) {
+ audio->audio_out_l[n] = (rptr[0] + rptr[1]) / 2;
+ rptr += 2;
+ }
+ if(audio->channels == 2) {
+ audio->audio_out_l[n] = *rptr++;
+ audio->audio_out_r[n] = *rptr++;
+ }
+ n++;
+ if(n == PORTBUFSIZE-1) n = 0;
+ }
+ */
+ }
+
+ data->frameIndex += framesToCalc;
+ if (finished == paComplete) {
+ data->frameIndex = 0;
+ finished = paContinue;
+ }
+ return finished;
+}
+
+void portaudio_simple_free(paTestData data) {
+ Pa_Terminate();
+ free(data.recordedSamples);
+}
+
+void *input_portaudio(void *audiodata) {
+ audio = (struct audio_data *)audiodata;
+
+ PaStreamParameters inputParameters;
+ PaStream *stream;
+ PaError err = paNoError;
+ paTestData data;
+
+ // start portaudio
+ err = Pa_Initialize();
+ if (err != paNoError) {
+ fprintf(stderr, "Error: unable to initilize portaudio - %s\n", Pa_GetErrorText(err));
+ exit(EXIT_FAILURE);
+ }
+
+ // get portaudio device
+ int deviceNum = -1, numOfDevices = Pa_GetDeviceCount();
+ if (!strcmp(audio->source, "list")) {
+ if (numOfDevices < 0) {
+ fprintf(stderr, "Error: portaudio was unable to find a audio device! Code: 0x%x\n",
+ numOfDevices);
+ exit(EXIT_FAILURE);
+ }
+ for (int i = 0; i < numOfDevices; i++) {
+ const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
+ printf("Device #%d: %s\n"
+ "\tInput Channels: %d\n"
+ "\tOutput Channels: %d\n"
+ "\tDefault SampleRate: %lf\n",
+ i + 1, deviceInfo->name, deviceInfo->maxInputChannels,
+ deviceInfo->maxOutputChannels, deviceInfo->defaultSampleRate);
+ }
+ exit(EXIT_SUCCESS);
+ } else if (!strcmp(audio->source, "auto")) {
+ deviceNum = Pa_GetDefaultInputDevice();
+
+ if (deviceNum == paNoDevice) {
+ fprintf(stderr, "Error: no portaudio input device found\n");
+ exit(EXIT_FAILURE);
+ }
+ } else if (sscanf(audio->source, "%d", &deviceNum)) {
+ if (deviceNum > numOfDevices) {
+ fprintf(stderr, "Error: Invalid input device!\n");
+ exit(EXIT_FAILURE);
+ }
+ deviceNum--;
+ } else {
+ for (int i = 0; i < numOfDevices; i++) {
+ const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
+ if (!strcmp(audio->source, deviceInfo->name)) {
+ deviceNum = i;
+ break;
+ }
+ }
+ if (deviceNum == -1) {
+ fprintf(stderr, "Error: No such device '%s'!\n", audio->source);
+ exit(EXIT_FAILURE);
+ }
+ }
+ inputParameters.device = deviceNum;
+
+ // set parameters
+ data.maxFrameIndex = PORTBUFSIZE;
+ data.recordedSamples = (SAMPLE *)malloc(2 * PORTBUFSIZE * sizeof(SAMPLE));
+ if (data.recordedSamples == NULL) {
+ fprintf(stderr, "Error: failure in memory allocation!\n");
+ exit(EXIT_FAILURE);
+ } else
+ memset(data.recordedSamples, 0x00, 2 * PORTBUFSIZE);
+
+ inputParameters.channelCount = 2;
+ inputParameters.sampleFormat = PA_SAMPLE_TYPE;
+ inputParameters.suggestedLatency =
+ Pa_GetDeviceInfo(inputParameters.device)->defaultLowInputLatency;
+ inputParameters.hostApiSpecificStreamInfo = NULL;
+
+ // set it to work
+ err = Pa_OpenStream(&stream, &inputParameters, NULL, audio->rate, PORTBUFSIZE, paClipOff,
+ recordCallback, &data);
+ if (err != paNoError) {
+ fprintf(stderr, "Error: failure in opening stream (%x)\n", err);
+ exit(EXIT_FAILURE);
+ }
+
+ // main loop
+ while (1) {
+ // start recording
+ data.frameIndex = 0;
+ err = Pa_StartStream(stream);
+ if (err != paNoError) {
+ fprintf(stderr, "Error: failure in starting stream (%x)\n", err);
+ exit(EXIT_FAILURE);
+ }
+
+ // record
+ while ((err = Pa_IsStreamActive(stream)) == 1) {
+ Pa_Sleep(5);
+ if (audio->terminate == 1)
+ break;
+ }
+ // check for errors
+ if (err < 0) {
+ fprintf(stderr, "Error: failure in recording audio (%x)\n", err);
+ exit(EXIT_FAILURE);
+ }
+
+ // check if it bailed
+ if (audio->terminate == 1)
+ break;
+ }
+ // close stream
+ if ((err = Pa_CloseStream(stream)) != paNoError) {
+ fprintf(stderr, "Error: failure in closing stream (%x)\n", err);
+ exit(EXIT_FAILURE);
+ }
+
+ portaudio_simple_free(data);
+ return 0;
+}
diff --git a/input/portaudio.h b/input/portaudio.h
new file mode 100644
index 0000000..1953e7f
--- /dev/null
+++ b/input/portaudio.h
@@ -0,0 +1,3 @@
+#pragma once
+
+void *input_portaudio(void *audiodata);
diff --git a/input/pulse.c b/input/pulse.c
new file mode 100644
index 0000000..97907db
--- /dev/null
+++ b/input/pulse.c
@@ -0,0 +1,142 @@
+#include "input/pulse.h"
+#include "input/common.h"
+
+#include <pulse/error.h>
+#include <pulse/pulseaudio.h>
+#include <pulse/simple.h>
+
+#define BUFFERSIZE 4096
+
+pa_mainloop *m_pulseaudio_mainloop;
+
+void cb(__attribute__((unused)) pa_context *pulseaudio_context, const pa_server_info *i,
+ void *userdata) {
+
+ // getting default sink name
+ struct audio_data *audio = (struct audio_data *)userdata;
+ audio->source = malloc(sizeof(char) * 1024);
+
+ strcpy(audio->source, i->default_sink_name);
+
+ // appending .monitor suufix
+ audio->source = strcat(audio->source, ".monitor");
+
+ // quiting mainloop
+ pa_context_disconnect(pulseaudio_context);
+ pa_context_unref(pulseaudio_context);
+ pa_mainloop_quit(m_pulseaudio_mainloop, 0);
+ pa_mainloop_free(m_pulseaudio_mainloop);
+}
+
+void pulseaudio_context_state_callback(pa_context *pulseaudio_context, void *userdata) {
+
+ // make sure loop is ready
+ switch (pa_context_get_state(pulseaudio_context)) {
+ case PA_CONTEXT_UNCONNECTED:
+ // printf("UNCONNECTED\n");
+ break;
+ case PA_CONTEXT_CONNECTING:
+ // printf("CONNECTING\n");
+ break;
+ case PA_CONTEXT_AUTHORIZING:
+ // printf("AUTHORIZING\n");
+ break;
+ case PA_CONTEXT_SETTING_NAME:
+ // printf("SETTING_NAME\n");
+ break;
+ case PA_CONTEXT_READY: // extract default sink name
+ // printf("READY\n");
+ pa_operation_unref(pa_context_get_server_info(pulseaudio_context, cb, userdata));
+ break;
+ case PA_CONTEXT_FAILED:
+ printf("failed to connect to pulseaudio server\n");
+ exit(EXIT_FAILURE);
+ break;
+ case PA_CONTEXT_TERMINATED:
+ // printf("TERMINATED\n");
+ pa_mainloop_quit(m_pulseaudio_mainloop, 0);
+ break;
+ }
+}
+
+void getPulseDefaultSink(void *data) {
+
+ struct audio_data *audio = (struct audio_data *)data;
+ pa_mainloop_api *mainloop_api;
+ pa_context *pulseaudio_context;
+ int ret;
+
+ // Create a mainloop API and connection to the default server
+ m_pulseaudio_mainloop = pa_mainloop_new();
+
+ mainloop_api = pa_mainloop_get_api(m_pulseaudio_mainloop);
+ pulseaudio_context = pa_context_new(mainloop_api, "cava device list");
+
+ // This function connects to the pulse server
+ pa_context_connect(pulseaudio_context, NULL, PA_CONTEXT_NOFLAGS, NULL);
+
+ // printf("connecting to server\n");
+
+ // This function defines a callback so the server will tell us its state.
+ pa_context_set_state_callback(pulseaudio_context, pulseaudio_context_state_callback,
+ (void *)audio);
+
+ // starting a mainloop to get default sink
+
+ // starting with one nonblokng iteration in case pulseaudio is not able to run
+ if (!(ret = pa_mainloop_iterate(m_pulseaudio_mainloop, 0, &ret))) {
+ printf("Could not open pulseaudio mainloop to "
+ "find default device name: %d\n"
+ "check if pulseaudio is running\n",
+ ret);
+
+ exit(EXIT_FAILURE);
+ }
+
+ pa_mainloop_run(m_pulseaudio_mainloop, &ret);
+}
+
+void *input_pulse(void *data) {
+
+ struct audio_data *audio = (struct audio_data *)data;
+ int16_t buf[BUFFERSIZE / 2];
+
+ /* The sample type to use */
+ static const pa_sample_spec ss = {.format = PA_SAMPLE_S16LE, .rate = 44100, .channels = 2};
+
+ audio->format = 16;
+
+ static const pa_buffer_attr pb = {.maxlength = (uint32_t)-1, // BUFSIZE * 2,
+ .fragsize = BUFFERSIZE};
+
+ uint16_t frames = BUFFERSIZE / 4;
+
+ pa_simple *s = NULL;
+ int error;
+
+ if (!(s = pa_simple_new(NULL, "cava", PA_STREAM_RECORD, audio->source, "audio for cava", &ss,
+ NULL, &pb, &error))) {
+ sprintf(audio->error_message, __FILE__ ": Could not open pulseaudio source: %s, %s. \
+ To find a list of your pulseaudio sources run 'pacmd list-sources'\n",
+ audio->source, pa_strerror(error));
+
+ audio->terminate = 1;
+ pthread_exit(NULL);
+ }
+
+ while (!audio->terminate) {
+ if (pa_simple_read(s, buf, sizeof(buf), &error) < 0) {
+ sprintf(audio->error_message, __FILE__ ": pa_simple_read() failed: %s\n",
+ pa_strerror(error));
+ audio->terminate = 1;
+ pthread_exit(NULL);
+ }
+
+ // sorting out channels
+
+ write_to_fftw_input_buffers(buf, frames, data);
+ }
+
+ pa_simple_free(s);
+ return 0;
+}
diff --git a/input/pulse.h b/input/pulse.h
new file mode 100644
index 0000000..155da47
--- /dev/null
+++ b/input/pulse.h
@@ -0,0 +1,6 @@
+// header file for pulse, part of cava.
+
+#pragma once
+
+void *input_pulse(void *data);
+void getPulseDefaultSink();
diff --git a/input/shmem.c b/input/shmem.c
new file mode 100644
index 0000000..c7bf040
--- /dev/null
+++ b/input/shmem.c
@@ -0,0 +1,82 @@
+#include "input/shmem.h"
+#include "input/common.h"
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+typedef unsigned int u32_t;
+typedef short s16_t;
+
+// #define BUFSIZE 1024
+#define BUFSIZE 2048
+
+int rc;
+
+#define VIS_BUF_SIZE 16384
+#define VB_OFFSET 8192 + 4096
+
+typedef struct {
+ pthread_rwlock_t rwlock;
+ u32_t buf_size;
+ u32_t buf_index;
+ bool running;
+ u32_t rate;
+ time_t updated;
+ s16_t buffer[VIS_BUF_SIZE];
+} vis_t;
+
+// input: SHMEM
+void *input_shmem(void *data) {
+ struct audio_data *audio = (struct audio_data *)data;
+ vis_t *mmap_area;
+ int fd; /* file descriptor to mmaped area */
+ int mmap_count = sizeof(vis_t);
+
+ printf("input_shmem: source: %s", audio->source);
+
+ fd = shm_open(audio->source, O_RDWR, 0666);
+
+ if (fd < 0) {
+ printf("Could not open source '%s': %s\n", audio->source, strerror(errno));
+ exit(EXIT_FAILURE);
+ } else {
+ mmap_area = mmap(NULL, sizeof(vis_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if ((intptr_t)mmap_area == -1) {
+ printf("mmap failed - check if squeezelite is running with visualization enabled\n");
+ exit(EXIT_FAILURE);
+ }
+ }
+ // printf("bufs: %u / run: %u / rate: %u\n",mmap_area->buf_size, mmap_area->running,
+ // mmap_area->rate);
+ audio->rate = mmap_area->rate;
+
+ while (!audio->terminate) {
+ write_to_fftw_input_buffers(mmap_area->buffer, BUFSIZE, audio);
+ /*
+ for (i = VB_OFFSET; i < BUFSIZE+VB_OFFSET; i += 2) {
+ if (audio->channels == 1) {
+ audio->audio_out_l[n] = (mmap_area->buffer[i] +
+ mmap_area->buffer[i + 1]) / 2; } else if (audio->channels == 2) { audio->audio_out_l[n] =
+ mmap_area->buffer[i]; audio->audio_out_r[n] = mmap_area->buffer[i + 1];
+ }
+ n++;
+ if (n == audio->FFTbufferSize - 1) n = 0;
+ }
+ */
+ }
+
+ // cleanup
+ if (fd > 0) {
+ if (close(fd) != 0) {
+ printf("Could not close file descriptor %d: %s", fd, strerror(errno));
+ }
+ } else {
+ printf("Wrong file descriptor %d", fd);
+ }
+
+ if (munmap(mmap_area, mmap_count) != 0) {
+ printf("Could not munmap() area %p+%d. %s", mmap_area, mmap_count, strerror(errno));
+ }
+ return 0;
+}
diff --git a/input/shmem.h b/input/shmem.h
new file mode 100644
index 0000000..d02da8d
--- /dev/null
+++ b/input/shmem.h
@@ -0,0 +1,5 @@
+// header file for shmem, part of cava.
+
+#pragma once
+
+void *input_shmem(void *data);
diff --git a/input/sndio.c b/input/sndio.c
new file mode 100644
index 0000000..5d13784
--- /dev/null
+++ b/input/sndio.c
@@ -0,0 +1,63 @@
+#include "input/sndio.h"
+#include "input/common.h"
+
+#include <sndio.h>
+
+void *input_sndio(void *data) {
+ struct audio_data *audio = (struct audio_data *)data;
+ struct sio_par par;
+ struct sio_hdl *hdl;
+ int16_t buf[256];
+ unsigned int channels;
+
+ sio_initpar(&par);
+ par.sig = 1;
+ par.bits = 16;
+ par.le = 1;
+ par.rate = 44100;
+ par.rchan = 2;
+ par.appbufsz = sizeof(buf) / par.rchan;
+
+ if ((hdl = sio_open(audio->source, SIO_REC, 0)) == NULL) {
+ fprintf(stderr, __FILE__ ": Could not open sndio source: %s\n", audio->source);
+ exit(EXIT_FAILURE);
+ }
+
+ if (!sio_setpar(hdl, &par) || !sio_getpar(hdl, &par) || par.sig != 1 || par.le != 1 ||
+ par.rate != 44100 || par.rchan != audio->channels) {
+ fprintf(stderr, __FILE__ ": Could not set required audio parameters\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (!sio_start(hdl)) {
+ fprintf(stderr, __FILE__ ": sio_start() failed\n");
+ exit(EXIT_FAILURE);
+ }
+
+ uint16_t frames = (sizeof(buf) / sizeof(buf[0])) / channels;
+ while (audio->terminate != 1) {
+ if (sio_read(hdl, buf, sizeof(buf)) == 0) {
+ fprintf(stderr, __FILE__ ": sio_read() failed: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ write_to_fftw_input_buffers(buf, frames, audio);
+ /*
+ for (i = 0; i < sizeof(buf)/sizeof(buf[0]); i += 2) {
+ if (par.rchan == 1) {
+ // sndiod has already taken care of averaging the samples
+ audio->audio_out_l[n] = buf[i];
+ } else if (par.rchan == 2) {
+ audio->audio_out_l[n] = buf[i];
+ audio->audio_out_r[n] = buf[i + 1];
+ }
+ n = (n + 1) % audio->FFTbufferSize;
+ }
+ */
+ }
+
+ sio_stop(hdl);
+ sio_close(hdl);
+
+ return 0;
+}
diff --git a/input/sndio.h b/input/sndio.h
new file mode 100644
index 0000000..61801a5
--- /dev/null
+++ b/input/sndio.h
@@ -0,0 +1,5 @@
+// header file for sndio, part of cava.
+
+#pragma once
+
+void *input_sndio(void *data);
diff --git a/output/raw.c b/output/raw.c
new file mode 100644
index 0000000..eaef8ee
--- /dev/null
+++ b/output/raw.c
@@ -0,0 +1,48 @@
+#include <math.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+
+int16_t buf_16;
+int8_t buf_8;
+
+int print_raw_out(int bars_count, int fd, int is_binary, int bit_format, int ascii_range,
+ char bar_delim, char frame_delim, int const f[200]) {
+ if (is_binary) {
+ for (int i = 0; i < bars_count; i++) {
+ int f_limited = f[i];
+ if (f_limited > (pow(2, bit_format) - 1))
+ f_limited = pow(2, bit_format) - 1;
+
+ switch (bit_format) {
+ case 16:
+ buf_16 = f_limited;
+ write(fd, &buf_16, sizeof(int16_t));
+ break;
+ case 8:
+ buf_8 = f_limited;
+ write(fd, &buf_8, sizeof(int8_t));
+ break;
+ }
+ }
+ } else { // ascii
+ for (int i = 0; i < bars_count; i++) {
+ int f_ranged = f[i];
+ if (f_ranged > ascii_range)
+ f_ranged = ascii_range;
+
+ // finding size of number-string in byte
+ int bar_height_size = 2; // a number + \0
+ if (f_ranged != 0)
+ bar_height_size += floor(log10(f_ranged));
+
+ char bar_height[bar_height_size];
+ snprintf(bar_height, bar_height_size, "%d", f_ranged);
+
+ write(fd, bar_height, bar_height_size - 1);
+ write(fd, &bar_delim, sizeof(bar_delim));
+ }
+ write(fd, &frame_delim, sizeof(frame_delim));
+ }
+ return 0;
+}
diff --git a/output/raw.h b/output/raw.h
new file mode 100644
index 0000000..f2f50cf
--- /dev/null
+++ b/output/raw.h
@@ -0,0 +1,2 @@
+int print_raw_out(int bars_count, int fd, int is_binary, int bit_format, int ascii_range,
+ char bar_delim, char frame_delim, int const f[200]);
diff --git a/output/terminal_bcircle.c b/output/terminal_bcircle.c
new file mode 100644
index 0000000..90363cb
--- /dev/null
+++ b/output/terminal_bcircle.c
@@ -0,0 +1,85 @@
+#include "output/terminal_bcircle.h"
+
+#include <curses.h>
+#include <locale.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+#ifndef M_PI
+#define M_PI 3.141592
+#endif
+#define DEGTORAD(deg) (deg * (180.0f / M_PI))
+#define DOT 0x2588
+
+int init_terminal_bcircle(int col, int bgcol) {
+
+ initscr();
+ curs_set(0);
+ timeout(0);
+ noecho();
+ start_color();
+ use_default_colors();
+ init_pair(1, col, bgcol);
+ if (bgcol != -1)
+ bkgd(COLOR_PAIR(1));
+ attron(COLOR_PAIR(1));
+ // attron(A_BOLD);
+ return 0;
+}
+
+void get_terminal_dim_bcircle(int *w, int *h) {
+
+ getmaxyx(stdscr, *h, *w);
+ clear(); // clearing in case of resieze
+}
+
+int draw_terminal_bcircle(int tty, int h, int w, int f[200]) {
+
+ const wchar_t *bars[] = {L"\u2581", L"\u2582", L"\u2583", L"\u2584",
+ L"\u2585", L"\u2586", L"\u2587", L"\u2588"};
+
+ // output: check if terminal has been resized
+ if (!tty) {
+ if (LINES != h || COLS != w) {
+ return -1;
+ }
+ }
+
+ float deg, width, height;
+ int y, x;
+
+ /* Convert to int */
+ width = f[1] / 10;
+ height = f[1] / 15;
+
+ int oy, ox;
+ oy = LINES / 2 - height / 2;
+ ox = COLS / 2 - width / 2;
+ for (x = 0; x < COLS; x++) {
+ for (y = 0; y < LINES; y++) {
+ mvaddstr(y, x, " ");
+ }
+ }
+ /* Draw circle */
+ for (deg = 0; deg < 360.0f; deg += 1.0f) {
+ x = ox + width + (int)(width * cos(DEGTORAD(deg)));
+ y = oy + height + (int)(height * sin(DEGTORAD(deg)));
+
+ mvaddwstr(y, x, bars[7]);
+ }
+
+ refresh();
+ return 0;
+}
+
+// general: cleanup
+void cleanup_terminal_bcircle(void) {
+ echo();
+ system("setfont >/dev/null 2>&1");
+ system("setfont /usr/share/consolefonts/Lat2-Fixed16.psf.gz >/dev/null 2>&1");
+ system("setterm -blank 10");
+ endwin();
+ system("clear");
+}
diff --git a/output/terminal_bcircle.h b/output/terminal_bcircle.h
new file mode 100644
index 0000000..87bbe0f
--- /dev/null
+++ b/output/terminal_bcircle.h
@@ -0,0 +1,6 @@
+#pragma once
+
+int init_terminal_bcircle(int col, int bgcol);
+void get_terminal_dim_bcircle(int *w, int *h);
+int draw_terminal_bcircle(int virt, int height, int width, int f[200]);
+void cleanup_terminal_bcircle(void);
diff --git a/output/terminal_ncurses.c b/output/terminal_ncurses.c
new file mode 100644
index 0000000..061a507
--- /dev/null
+++ b/output/terminal_ncurses.c
@@ -0,0 +1,280 @@
+#include "output/terminal_ncurses.h"
+
+#include <curses.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+#include "util.h"
+
+int gradient_size = 64;
+
+struct colors {
+ NCURSES_COLOR_T color;
+ NCURSES_COLOR_T R;
+ NCURSES_COLOR_T G;
+ NCURSES_COLOR_T B;
+};
+
+#define COLOR_REDEFINITION -2
+
+#define MAX_COLOR_REDEFINITION 256
+
+// static struct colors the_color_redefinitions[MAX_COLOR_REDEFINITION];
+
+static void parse_color(char *color_string, struct colors *color) {
+ if (color_string[0] == '#') {
+ if (!can_change_color()) {
+ cleanup_terminal_ncurses();
+ fprintf(stderr, "Your terminal can not change color definitions, "
+ "please use one of the predefined colors.\n");
+ exit(EXIT_FAILURE);
+ }
+ color->color = COLOR_REDEFINITION;
+ sscanf(++color_string, "%02hx%02hx%02hx", &color->R, &color->G, &color->B);
+ }
+}
+/*
+static void remember_color_definition(NCURSES_COLOR_T color_number) {
+ int index = color_number - 1; // array starts from zero and colors - not
+ if(the_color_redefinitions[index].color == 0) {
+ the_color_redefinitions[index].color = color_number;
+ color_content(color_number,
+ &the_color_redefinitions[index].R,
+ &the_color_redefinitions[index].G,
+ &the_color_redefinitions[index].B);
+ }
+}
+*/
+// ncurses use color range [0, 1000], and we - [0, 255]
+#define CURSES_COLOR_COEFFICIENT(X) ((X)*1000.0 / 0xFF + 0.5)
+#define COLORS_STRUCT_NORMALIZE(X) \
+ CURSES_COLOR_COEFFICIENT(X.R), CURSES_COLOR_COEFFICIENT(X.G), CURSES_COLOR_COEFFICIENT(X.B)
+
+static NCURSES_COLOR_T change_color_definition(NCURSES_COLOR_T color_number,
+ char *const color_string,
+ NCURSES_COLOR_T predef_color) {
+ struct colors color = {0};
+ parse_color(color_string, &color);
+ NCURSES_COLOR_T return_color_number = predef_color;
+ if (color.color == COLOR_REDEFINITION) {
+ // remember_color_definition(color_number);
+ init_color(color_number, COLORS_STRUCT_NORMALIZE(color));
+ return_color_number = color_number;
+ }
+ return return_color_number;
+}
+
+void init_terminal_ncurses(char *const fg_color_string, char *const bg_color_string,
+ int predef_fg_color, int predef_bg_color, int gradient,
+ int gradient_count, char **gradient_colors, int *width, int *lines) {
+ initscr();
+ curs_set(0);
+ timeout(0);
+ noecho();
+ start_color();
+ use_default_colors();
+
+ getmaxyx(stdscr, *lines, *width);
+ clear();
+
+ NCURSES_COLOR_T color_pair_number = 16;
+
+ NCURSES_COLOR_T bg_color_number;
+ bg_color_number = change_color_definition(0, bg_color_string, predef_bg_color);
+
+ if (!gradient) {
+
+ NCURSES_COLOR_T fg_color_number;
+ fg_color_number = change_color_definition(1, fg_color_string, predef_fg_color);
+
+ init_pair(color_pair_number, fg_color_number, bg_color_number);
+
+ } else if (gradient) {
+
+ // 0 -> col1, 1-> col1<=>col2, 2 -> col2 and so on
+ short unsigned int rgb[2 * gradient_count - 1][3];
+ char next_color[14];
+
+ gradient_size = *lines;
+
+ if (gradient_size > COLORS)
+ gradient_size = COLORS - 1;
+
+ if (gradient_size > COLOR_PAIRS)
+ gradient_size = COLOR_PAIRS - 1;
+
+ if (gradient_size > MAX_COLOR_REDEFINITION)
+ gradient_size = MAX_COLOR_REDEFINITION - 1;
+
+ for (int i = 0; i < gradient_count; i++) {
+ int col = (i + 1) * 2 - 2;
+ sscanf(gradient_colors[i] + 1, "%02hx%02hx%02hx", &rgb[col][0], &rgb[col][1],
+ &rgb[col][2]);
+ }
+
+ // sscanf(gradient_color_1 + 1, "%02hx%02hx%02hx", &rgb[0][0], &rgb[0][1], &rgb[0][2]);
+ // sscanf(gradient_color_2 + 1, "%02hx%02hx%02hx", &rgb[1][0], &rgb[1][1], &rgb[1][2]);
+
+ int individual_size = gradient_size / (gradient_count - 1);
+
+ for (int i = 0; i < gradient_count - 1; i++) {
+
+ int col = (i + 1) * 2 - 2;
+ if (i == gradient_count - 1)
+ col = 2 * (gradient_count - 1) - 2;
+
+ for (int j = 0; j < individual_size; j++) {
+
+ for (int k = 0; k < 3; k++) {
+ rgb[col + 1][k] = rgb[col][k] + (rgb[col + 2][k] - rgb[col][k]) *
+ (j / (individual_size * 0.85));
+ if (rgb[col + 1][k] > 255)
+ rgb[col][k] = 0;
+ if (j > individual_size * 0.85)
+ rgb[col + 1][k] = rgb[col + 2][k];
+ }
+
+ sprintf(next_color, "#%02x%02x%02x", rgb[col + 1][0], rgb[col + 1][1],
+ rgb[col + 1][2]);
+
+ change_color_definition(color_pair_number, next_color, color_pair_number);
+ init_pair(color_pair_number, color_pair_number, bg_color_number);
+ color_pair_number++;
+ }
+ }
+
+ int left = individual_size * (gradient_count - 1);
+ int col = 2 * (gradient_count)-2;
+ while (left < gradient_size) {
+ sprintf(next_color, "#%02x%02x%02x", rgb[col][0], rgb[col][1], rgb[col][2]);
+ change_color_definition(color_pair_number, next_color, color_pair_number);
+ init_pair(color_pair_number, color_pair_number, bg_color_number);
+ color_pair_number++;
+ left++;
+ }
+ color_pair_number--;
+ }
+
+ attron(COLOR_PAIR(color_pair_number));
+
+ if (bg_color_number != -1)
+ bkgd(COLOR_PAIR(color_pair_number));
+
+ for (int y = 0; y < *lines; y++) {
+ for (int x = 0; x < *width; x++) {
+ mvaddch(y, x, ' ');
+ }
+ }
+ refresh();
+}
+
+void change_colors(int cur_height, int tot_height) {
+ tot_height /= gradient_size;
+ if (tot_height < 1)
+ tot_height = 1;
+ cur_height /= tot_height;
+ if (cur_height > gradient_size - 1)
+ cur_height = gradient_size - 1;
+ attron(COLOR_PAIR(cur_height + 16));
+}
+
+void get_terminal_dim_ncurses(int *width, int *height) {
+ getmaxyx(stdscr, *height, *width);
+ gradient_size = *height;
+ clear(); // clearing in case of resieze
+}
+
+#define TERMINAL_RESIZED -1
+
+int draw_terminal_ncurses(int is_tty, int terminal_height, int terminal_width, int bars_count,
+ int bar_width, int bar_spacing, int rest, const int f[200],
+ int flastd[200], int gradient) {
+ const int height = terminal_height - 1;
+ const wchar_t *bar_heights[] = {L"\u2581", L"\u2582", L"\u2583", L"\u2584",
+ L"\u2585", L"\u2586", L"\u2587", L"\u2588"};
+ int num_bar_heights = (sizeof(bar_heights) / sizeof(bar_heights[0]));
+
+ // output: check if terminal has been resized
+ if (!is_tty) {
+ if (LINES != terminal_height || COLS != terminal_width) {
+ return TERMINAL_RESIZED;
+ }
+ }
+
+ // Compute how much of the screen we possibly need to update ahead-of-time.
+ int max_update_y = 0;
+ for (int bar = 0; bar < bars_count; bar++) {
+ max_update_y = max(max_update_y, max(f[bar], flastd[bar]));
+ }
+
+ max_update_y = (max_update_y + num_bar_heights) / num_bar_heights;
+
+ for (int y = 0; y < max_update_y; y++) {
+ if (gradient) {
+ change_colors(y, height);
+ }
+
+ for (int bar = 0; bar < bars_count; bar++) {
+ if (f[bar] == flastd[bar]) {
+ continue;
+ }
+
+ int cur_col = bar * bar_width + bar * bar_spacing + rest;
+ int f_cell = (f[bar] - 1) / num_bar_heights;
+ int f_last_cell = (flastd[bar] - 1) / num_bar_heights;
+
+ if (f_cell >= y) {
+ int bar_step;
+
+ if (f_cell == y) {
+ // The "cap" of the bar occurs at this [y].
+ bar_step = (f[bar] - 1) % num_bar_heights;
+ } else if (f_last_cell <= y) {
+ // The bar is full at this [y].
+ bar_step = num_bar_heights - 1;
+ } else {
+ // No update necessary since last frame.
+ continue;
+ }
+
+ for (int col = cur_col, i = 0; i < bar_width; i++, col++) {
+ if (is_tty) {
+ mvaddch(height - y, col, '1' + bar_step);
+ } else {
+ mvaddwstr(height - y, col, bar_heights[bar_step]);
+ }
+ }
+ } else if (f_last_cell >= y) {
+ // This bar was taller during the last frame than during this frame, so
+ // clear the excess characters.
+ for (int col = cur_col, i = 0; i < bar_width; i++, col++) {
+ mvaddch(height - y, col, ' ');
+ }
+ }
+ }
+ }
+
+ refresh();
+ return 0;
+}
+
+// general: cleanup
+void cleanup_terminal_ncurses(void) {
+ echo();
+ system("setfont >/dev/null 2>&1");
+ system("setfont /usr/share/consolefonts/Lat2-Fixed16.psf.gz >/dev/null 2>&1");
+ system("setterm -blank 10 >/dev/null 2>&1");
+ /*for(int i = 0; i < gradient_size; ++i) {
+ if(the_color_redefinitions[i].color) {
+ init_color(the_color_redefinitions[i].color,
+ the_color_redefinitions[i].R,
+ the_color_redefinitions[i].G,
+ the_color_redefinitions[i].B);
+ }
+ }
+*/
+ standend();
+ endwin();
+ system("clear");
+}
diff --git a/output/terminal_ncurses.h b/output/terminal_ncurses.h
new file mode 100644
index 0000000..5c36fb2
--- /dev/null
+++ b/output/terminal_ncurses.h
@@ -0,0 +1,8 @@
+void init_terminal_ncurses(char *const fg_color_string, char *const bg_color_string,
+ int predef_fg_color, int predef_bg_color, int gradient,
+ int gradient_count, char **gradient_colors, int *width, int *height);
+void get_terminal_dim_ncurses(int *width, int *height);
+int draw_terminal_ncurses(int is_tty, int terminal_height, int terminal_width, int bars_count,
+ int bar_width, int bar_spacing, int rest, const int f[200],
+ int flastd[200], int gradient);
+void cleanup_terminal_ncurses(void);
diff --git a/output/terminal_noncurses.c b/output/terminal_noncurses.c
new file mode 100644
index 0000000..6789f30
--- /dev/null
+++ b/output/terminal_noncurses.c
@@ -0,0 +1,287 @@
+#include <locale.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <unistd.h>
+#include <wchar.h>
+
+wchar_t *frame_buffer;
+wchar_t *barstring[8];
+wchar_t *spacestring;
+int buf_length;
+char *ttyframe_buffer;
+char *ttybarstring[8];
+char *ttyspacestring;
+int ttybuf_length;
+
+int setecho(int fd, int onoff) {
+
+ struct termios t;
+
+ if (tcgetattr(fd, &t) == -1)
+ return -1;
+
+ if (onoff == 0)
+ t.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
+ else
+ t.c_lflag |= (ECHO | ECHOE | ECHOK | ECHONL);
+
+ if (tcsetattr(fd, TCSANOW, &t) == -1)
+ return -1;
+
+ return 0;
+}
+
+int init_terminal_noncurses(int tty, int col, int bgcol, int width, int lines, int bar_width) {
+
+ if (tty) {
+
+ ttybuf_length = sizeof(char) * width * lines * 10;
+ ttyframe_buffer = (char *)malloc(ttybuf_length);
+ ttyspacestring = (char *)malloc(sizeof(char) * bar_width);
+
+ // clearing barstrings
+ for (int n = 0; n < 8; n++) {
+ ttybarstring[n] = (char *)malloc(sizeof(char) * bar_width);
+ ttybarstring[n][0] = '\0';
+ }
+ ttyspacestring[0] = '\0';
+ ttyframe_buffer[0] = '\0';
+
+ // creating barstrings for drawing
+ for (int n = 0; n < bar_width; n++) {
+ strcat(ttybarstring[0], "8");
+ strcat(ttybarstring[1], "1");
+ strcat(ttybarstring[2], "2");
+ strcat(ttybarstring[3], "3");
+ strcat(ttybarstring[4], "4");
+ strcat(ttybarstring[5], "5");
+ strcat(ttybarstring[6], "6");
+ strcat(ttybarstring[7], "7");
+ strcat(ttyspacestring, " ");
+ }
+ } else if (!tty) {
+
+ buf_length = sizeof(wchar_t) * width * lines * 10;
+ frame_buffer = (wchar_t *)malloc(buf_length);
+ spacestring = (wchar_t *)malloc(sizeof(wchar_t) * bar_width);
+
+ // clearing barstrings
+ for (int n = 0; n < 8; n++) {
+ barstring[n] = (wchar_t *)malloc(sizeof(wchar_t) * bar_width);
+ barstring[n][0] = '\0';
+ }
+ spacestring[0] = '\0';
+ frame_buffer[0] = '\0';
+
+ // creating barstrings for drawing
+ for (int n = 0; n < bar_width; n++) {
+ wcscat(barstring[0], L"\u2588");
+ wcscat(barstring[1], L"\u2581");
+ wcscat(barstring[2], L"\u2582");
+ wcscat(barstring[3], L"\u2583");
+ wcscat(barstring[4], L"\u2584");
+ wcscat(barstring[5], L"\u2585");
+ wcscat(barstring[6], L"\u2586");
+ wcscat(barstring[7], L"\u2587");
+ wcscat(spacestring, L" ");
+ }
+ }
+
+ col += 30;
+
+ system("setterm -cursor off");
+ system("setterm -blank 0");
+
+ // output: reset console
+ printf("\033[0m\n");
+ system("clear");
+
+ if (col)
+ printf("\033[%dm", col); // setting color
+
+ // printf("\033[1m"); // setting "bright" color mode, looks cooler... I think
+
+ if (bgcol != 0) {
+
+ bgcol += 40;
+ printf("\033[%dm", bgcol);
+
+ for (int n = (lines); n >= 0; n--) {
+ for (int i = 0; i < width; i++) {
+
+ printf(" "); // setting backround color
+ }
+ printf("\n");
+ }
+ printf("\033[%dA", lines); // moving cursor back up
+ }
+
+ setecho(STDIN_FILENO, 0);
+
+ return 0;
+}
+
+void get_terminal_dim_noncurses(int *width, int *lines) {
+
+ struct winsize dim;
+
+ ioctl(STDOUT_FILENO, TIOCGWINSZ, &dim);
+
+ *lines = (int)dim.ws_row;
+ *width = (int)dim.ws_col;
+
+ system("clear"); // clearing in case of resieze
+}
+
+int draw_terminal_noncurses(int tty, int lines, int width, int number_of_bars, int bar_width,
+ int bar_spacing, int rest, int bars[200], int previous_frame[200]) {
+
+ int current_cell, prev_cell, same_line, new_line, cx;
+
+ struct winsize dim;
+
+ same_line = 0;
+ new_line = 0;
+ cx = 0;
+ if (!tty) {
+
+ // output: check if terminal has been resized
+ ioctl(STDOUT_FILENO, TIOCGWINSZ, &dim);
+
+ if ((int)dim.ws_row != lines || (int)dim.ws_col != width) {
+ free(frame_buffer);
+ free(spacestring);
+ for (int i = 0; i < 8; i++)
+ free(barstring[i]);
+
+ return -1;
+ }
+ }
+ if (tty)
+ ttyframe_buffer[0] = '\0';
+ else if (!tty)
+ frame_buffer[0] = '\0';
+
+ for (int current_line = lines - 1; current_line >= 0; current_line--) {
+
+ int same_bar = 0;
+ int center_adjusted = 0;
+
+ for (int i = 0; i < number_of_bars; i++) {
+
+ current_cell = bars[i] - current_line * 8;
+ prev_cell = previous_frame[i] - current_line * 8;
+
+ // same as last frame
+ if ((current_cell < 1 && prev_cell < 1) || (current_cell > 7 && prev_cell > 7) ||
+ (current_cell == prev_cell)) {
+ same_bar++;
+ } else {
+ if (tty) {
+ if (same_line > 0) {
+ cx += snprintf(ttyframe_buffer + cx, ttybuf_length - cx, "\033[%dB",
+ same_line); // move down
+ new_line += same_line;
+ same_line = 0;
+ }
+
+ if (same_bar > 0) {
+ cx += snprintf(ttyframe_buffer + cx, ttybuf_length - cx, "\033[%dC",
+ (bar_width + bar_spacing) * same_bar); // move forward
+ same_bar = 0;
+ }
+
+ if (!center_adjusted) {
+ cx += snprintf(ttyframe_buffer + cx, ttybuf_length - cx, "\033[%dC", rest);
+ center_adjusted = 1;
+ }
+
+ if (current_cell < 1)
+ cx += snprintf(ttyframe_buffer + cx, ttybuf_length - cx, "%s",
+ ttyspacestring);
+ else if (current_cell > 7)
+ cx += snprintf(ttyframe_buffer + cx, ttybuf_length - cx, "%s",
+ ttybarstring[0]);
+ else
+ cx += snprintf(ttyframe_buffer + cx, ttybuf_length - cx, "%s",
+ ttybarstring[current_cell]);
+
+ cx +=
+ snprintf(ttyframe_buffer + cx, ttybuf_length - cx, "\033[%dC", bar_spacing);
+ } else if (!tty) {
+ if (same_line > 0) {
+ cx += swprintf(frame_buffer + cx, buf_length - cx, L"\033[%dB",
+ same_line); // move down
+ new_line += same_line;
+ same_line = 0;
+ }
+
+ if (same_bar > 0) {
+ cx += swprintf(frame_buffer + cx, buf_length - cx, L"\033[%dC",
+ (bar_width + bar_spacing) * same_bar); // move forward
+ same_bar = 0;
+ }
+
+ if (!center_adjusted && rest) {
+ cx += swprintf(frame_buffer + cx, buf_length - cx, L"\033[%dC", rest);
+ center_adjusted = 1;
+ }
+
+ if (current_cell < 1)
+ cx += swprintf(frame_buffer + cx, buf_length - cx, spacestring);
+ else if (current_cell > 7)
+ cx += swprintf(frame_buffer + cx, buf_length - cx, barstring[0]);
+ else
+ cx += swprintf(frame_buffer + cx, buf_length - cx, barstring[current_cell]);
+
+ cx += swprintf(frame_buffer + cx, buf_length - cx, L"\033[%dC", bar_spacing);
+ }
+ }
+ }
+
+ if (same_bar != number_of_bars) {
+ if (current_line != 0) {
+ if (tty)
+ cx += snprintf(ttyframe_buffer + cx, ttybuf_length - cx, "\n");
+ else if (!tty)
+ cx += swprintf(frame_buffer + cx, buf_length - cx, L"\n");
+
+ new_line++;
+ }
+ } else {
+ same_line++;
+ }
+ }
+ if (same_line != lines) {
+ if (tty)
+ printf("%s\r\033[%dA", ttyframe_buffer, new_line);
+ else if (!tty)
+ printf("%ls\r\033[%dA", frame_buffer, new_line);
+
+ fflush(stdout);
+ }
+ return 0;
+}
+
+// general: cleanup
+void cleanup_terminal_noncurses(void) {
+ free(frame_buffer);
+ free(ttyframe_buffer);
+ free(spacestring);
+ free(ttyspacestring);
+ for (int i = 0; i < 8; i++) {
+ free(barstring[i]);
+ free(ttybarstring[i]);
+ }
+ setecho(STDIN_FILENO, 1);
+ printf("\033[0m\n");
+ system("setfont >/dev/null 2>&1");
+ system("setfont /usr/share/consolefonts/Lat2-Fixed16.psf.gz >/dev/null 2>&1");
+ system("setterm -cursor on");
+ system("setterm -blank 10");
+ system("clear");
+}
diff --git a/output/terminal_noncurses.h b/output/terminal_noncurses.h
new file mode 100644
index 0000000..542774d
--- /dev/null
+++ b/output/terminal_noncurses.h
@@ -0,0 +1,5 @@
+int init_terminal_noncurses(int tty, int col, int bgcol, int w, int h, int bar_width);
+void get_terminal_dim_noncurses(int *w, int *h);
+int draw_terminal_noncurses(int virt, int height, int width, int bars, int bar_width, int bs,
+ int rest, int f[200], int flastd[200]);
+void cleanup_terminal_noncurses(void);
diff --git a/todo b/todo
new file mode 100644
index 0000000..a7c17f6
--- /dev/null
+++ b/todo
@@ -0,0 +1,11 @@
+TODO
+----
+
+
+* config option print cut-off frequencies on x-axis
+* config option different color for different threshold amplitude
+* possibility to save settings to config file
+* openGL
+* plug-in api
+* new demo video
+* JACK support
diff --git a/util.h b/util.h
new file mode 100644
index 0000000..962faac
--- /dev/null
+++ b/util.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#define max(a, b) \
+ ({ \
+ __typeof__(a) _a = (a); \
+ __typeof__(b) _b = (b); \
+ _a > _b ? _a : _b; \
+ })
+
+#define ARRAY_SIZE(x) ((sizeof(x) / sizeof(0 [x])) / ((size_t)(!(sizeof(x) % sizeof(0 [x])))))