Full Code of keur/prettyeq for AI

master c6d300b9070d cached
52 files
102.5 KB
30.7k tokens
95 symbols
1 requests
Download .txt
Repository: keur/prettyeq
Branch: master
Commit: c6d300b9070d
Files: 52
Total size: 102.5 KB

Directory structure:
gitextract__5e1dd8e/

├── .gitignore
├── LICENSE.txt
├── README.md
├── build_tests.sh
├── equalizer/
│   ├── arena.c
│   ├── arena.h
│   ├── arena_test.c
│   ├── equalizer.pro
│   ├── fft.c
│   ├── fft.h
│   ├── fft_test.c
│   ├── macro.h
│   ├── pretty.c
│   └── pretty.h
├── gui/
│   ├── collisionmanager.cpp
│   ├── collisionmanager.h
│   ├── curvepoint.cpp
│   ├── curvepoint.h
│   ├── eqhoverer.cpp
│   ├── eqhoverer.h
│   ├── filtercurve.cpp
│   ├── filtercurve.h
│   ├── frequencytick.cpp
│   ├── frequencytick.h
│   ├── frequencytickbuilder.cpp
│   ├── frequencytickbuilder.h
│   ├── gui.cpp
│   ├── gui.h
│   ├── gui.pro
│   ├── gui.ui
│   ├── highshelfcurve.cpp
│   ├── highshelfcurve.h
│   ├── lowshelfcurve.cpp
│   ├── lowshelfcurve.h
│   ├── macro.h
│   ├── main.cpp
│   ├── peakingcurve.cpp
│   ├── peakingcurve.h
│   ├── prettygraphicsscene.cpp
│   ├── prettygraphicsscene.h
│   ├── prettyshim.h
│   ├── resources.qrc
│   ├── ringbuffer.h
│   ├── runguard.cpp
│   ├── runguard.h
│   ├── shelfcurve.cpp
│   ├── shelfcurve.h
│   ├── spectrumanalyzer.cpp
│   ├── spectrumanalyzer.h
│   ├── unixsignalhandler.cpp
│   └── unixsignalhandler.h
└── prettyeq.pro

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# C++ objects and libs
*.slo
*.lo
*.o
*.a
*.la
*.lai
*.so
*.so.*
*.dll
*.dylib

# Qt-es
object_script.*.Release
object_script.*.Debug
*_plugin_import.cpp
/.qmake.cache
/.qmake.stash
*.pro.user
*.pro.user.*
*.qbs.user
*.qbs.user.*
*.moc
moc_*.cpp
moc_*.h
qrc_*.cpp
ui_*.h
*.qmlc
*.jsc
Makefile*
*build-*
*.qm
*.prl

# Qt unit tests
target_wrapper.*

# QtCreator
*.autosave

# QtCreator Qml
*.qmlproject.user
*.qmlproject.user.*

# QtCreator CMake
CMakeLists.txt.user*

# QtCreator 4.8< compilation database
compile_commands.json

# QtCreator local machine specific files for imported projects
*creator.user*

*_qmlcache.qrc

/tags
/TAGS
/prettyeq
/build
/.vscode


================================================
FILE: LICENSE.txt
================================================
Copyright 2020 Kevin Kuehler

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


================================================
FILE: README.md
================================================
## PrettyEQ

prettyeq is a system-wide paramateric equalizer for pulseaudio. This software
is in alpha. Use at your own discretion.

![prettyeq demo](https://i.fluffy.cc/0GFcjGmbrtCgnbRSjd4xjDcf7h6qNk4Q.gif)

### Building

```
mkdir build
cd build
qmake CONFIG+=release ..
make -j4
```

### Usage

When the program is executed all pulseaudio streams will be routed through the
equalizer. With no filters activated prettyeq acts as a passthrough sink.

Right now prettyeq only supports two-channel sound.

##### Filter types

prettyeq has three filter types:
* one **low shelf** filter mounted at 20Hz
* one **high shelf** filter mounted at 20kHz
* five **peakingEQ** filters that can move freely

##### Controls

Click and drag points to boost and cut frequency bands. The dB gain range is
±12dB. Filter bandwidth and slope can be changed with the mousewheel.

##### Quitting

If your desktop has a system tray, the close button will hide the GUI but the
equalizer will still be in effect. There are context menus in the application
and tray that have a "exit" option to quit the application.


================================================
FILE: build_tests.sh
================================================
#!/bin/sh
mkdir -p build
gcc -lm -ffast-math -march=skylake -fopenmp -O2 \
  -o build/fft_test \
  equalizer/fft.c equalizer/fft_test.c

gcc -g -o build/arena_test \
  equalizer/arena.c equalizer/arena_test.c


================================================
FILE: equalizer/arena.c
================================================
#include <assert.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>

#include "arena.h"

#define CHUNK_OFFSET(memory) ((void*)(memory)-(sizeof(chunk_t)))

#define MEMORY_OFFSET(chunk) ((void*)(chunk)+(sizeof(chunk_t)))

#define NEXT_CHUNK(chunk, size) (((void*)MEMORY_OFFSET((chunk)))+(size))

arena_t* arena_new(size_t num_chunks, size_t chunk_size) {
    assert(num_chunks > 0);
    assert(chunk_size > 0);

    arena_t *arena = calloc(sizeof(arena_t), 1);
    if (!arena)
        return NULL;

    size_t map_size = num_chunks * (chunk_size + sizeof(chunk_t));
    void *mem = mmap(
            NULL,
            map_size,
            PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON,
            -1, 0);

    if (mem == MAP_FAILED)
        return NULL;

    chunk_t *chunk = mem;
    for (unsigned int i = 0; i < num_chunks - 1; i++) {
        chunk->next = NEXT_CHUNK(chunk, chunk_size);
        chunk = chunk->next;
    }
    chunk->next = NULL;

    arena->chunk_size = chunk_size;
    arena->map_size = map_size;
    arena->mem = mem;
    arena->avail_chunk = mem;

    return arena;
}

void* arena_alloc(arena_t *arena) {
    assert(arena);
    assert(arena->avail_chunk);

    void *p = MEMORY_OFFSET(arena->avail_chunk);
    assert(p);
    arena->avail_chunk = arena->avail_chunk->next;
    return p;
}

void arena_dealloc(arena_t *arena, void *mem) {
    assert(arena);
    assert(mem);

    chunk_t *chunk = CHUNK_OFFSET(mem);
    chunk->next = arena->avail_chunk;
    arena->avail_chunk = chunk;
}

void arena_destroy(arena_t **arena) {
    assert(arena);
    assert((*arena)->mem);
    munmap((*arena)->mem, (*arena)->map_size);
    free(*arena);
    *arena = NULL;
}


================================================
FILE: equalizer/arena.h
================================================
#pragma once

#include <stddef.h>

typedef struct chunk_t chunk_t;
struct chunk_t {
    chunk_t *next;
};

typedef struct arena_t {
    void *mem;
    size_t chunk_size;
    size_t map_size;
    chunk_t *avail_chunk;
} arena_t;

arena_t* arena_new(size_t num_chunks, size_t chunk_size);
void* arena_alloc(arena_t *arena);
void arena_dealloc(arena_t *arena, void *mem);
void arena_destroy(arena_t **arena);


================================================
FILE: equalizer/arena_test.c
================================================
#include <assert.h>
#include <string.h>
#include <stdio.h>

#include "arena.h"

int main(int argc, char **argv) {
    arena_t *arena = arena_new(4, 5);
    assert(arena);
    assert(arena->avail_chunk != NULL);

    char *a = arena_alloc(arena);
    memset(a, 'a', 4);
    a[4] = '\0';

    char *b = arena_alloc(arena);
    memset(b, 'b', 4);
    b[4] = '\0';

    char *c = arena_alloc(arena);
    memset(c, 'c', 4);
    c[4] = '\0';

    char *d = arena_alloc(arena);
    memset(d, 'd', 4);
    d[4] = '\0';

    assert(arena->avail_chunk == NULL);
    assert(memcmp(a, "aaaa", 5) == 0);
    assert(memcmp(b, "bbbb", 5) == 0);
    assert(memcmp(c, "cccc", 5) == 0);
    assert(memcmp(d, "dddd", 5) == 0);

    arena_dealloc(arena, c);
    arena_dealloc(arena, a);

    char *e = arena_alloc(arena);
    memset(e, 'e', 4);
    e[4] = '\0';

    char *f = arena_alloc(arena);
    memset(f, 'f', 4);
    f[4] = '\0';

    assert(memcmp(e, "eeee", 5) == 0);
    assert(memcmp(a, "eeee", 5) == 0);
    assert(memcmp(f, "ffff", 5) == 0);
    assert(memcmp(c, "ffff", 5) == 0);

    arena_dealloc(arena, b);
    arena_dealloc(arena, e);
    arena_dealloc(arena, f);
    arena_dealloc(arena, d);

    assert((void *) arena->avail_chunk == (void *) d - sizeof(chunk_t));
    return 0;
}


================================================
FILE: equalizer/equalizer.pro
================================================
TEMPLATE = lib
QMAKE_CFLAGS += -ffast-math -fopenmp
QMAKE_CFLAGS_WARN_ON += -Wno-unused-parameter
CONFIG += staticlib
HEADERS = \
    arena.h \
    fft.h \
    macro.h \
    pretty.h
    pretty.h
SOURCES = \
    arena.c \
    fft.c \
    pretty.c


================================================
FILE: equalizer/fft.c
================================================
#include <assert.h>
#include <limits.h>
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdalign.h>
#include <string.h>
#include <time.h>

#include "macro.h"
#include "fft.h"

static bool initialized = false;
alignas(32) static complex float omega_vec_log2[MAX_SAMPLES][K];

static inline unsigned int reverse_bits(unsigned int n, unsigned int num_bits) {
    int i, j;
    register unsigned int res = 0;

    i = 0;
    j = num_bits;
    while (i <= j) {
        unsigned int lower_mask = 1 << i;
        unsigned int upper_mask = (1 << num_bits) >> i;
        unsigned int shift = j - i;
        res |= ((n >> shift) & lower_mask) | ((n << shift) & upper_mask);
        i++;
        j--;
    }
    return res;
}

static inline unsigned int get_msb(unsigned int v) {
    return 31 - __builtin_clz(v);
}

void fft_init() {
    for (unsigned int nl = 0; nl < MAX_SAMPLES_LOG_2; nl++) {
        unsigned int n = (1u << nl);
        const double mul_div_n = (-2.0 * M_PI) / n;
        for (unsigned int k = 0; k < K; k++) {
            complex float *res = &omega_vec_log2[nl][k];
            complex double theta = mul_div_n * k;
            _real_(*res) = cosf(theta);
            _imag_(*res) = sinf(theta);
        }
    }
    initialized = true;
}

void fft_run(
        const float *input_data,
        complex float *output_data,
        unsigned int N,
        unsigned int channels) {
    assert(initialized);

    {
        unsigned int msb;

        for (unsigned int i = 0, j = 0; i < N; j++, i+=channels)
            /* Taking just the left channel for now... */
            output_data[j] = input_data[i];

        N = N / channels;
        assert(N <= MAX_SAMPLES);
        msb = get_msb(N);

        if (_unlikely_((N & (N-1)))) {
            /* Pad out so FFT is a power of 2. */
            msb = msb + 1;
            unsigned int new_N = 1 << msb;
            for (unsigned int i = N; i < new_N; i++)
                output_data[i] = 0.0f;

            N = new_N;
        }

        /* Reverse the input array. */
        unsigned int hi_bit = msb - 1;
        for (unsigned int i = 0; i < N; i++) {
            unsigned int r = reverse_bits(i, hi_bit);
            if (i < r)
                SWAP(output_data[i], output_data[r]);
        }
    }

    {
        /* Simple radix-2 DIT FFT */
        unsigned int wingspan = 1;
        unsigned int nl = 1;
        while (wingspan < N) {
            for (unsigned int j = 0; j < N; j+=wingspan*2) {
                for (unsigned int k = 0; k < wingspan; k++) {
                    complex float omega = omega_vec_log2[nl][k];
                    complex float a0 = output_data[k+j];
                    complex float a1 = output_data[k+j+wingspan];
                    output_data[k+j] = a0 + omega*a1;
                    output_data[k+j+wingspan] = a0 - omega*a1;
                }
            }
            nl++;
            wingspan *= 2;
        }
    }
}


================================================
FILE: equalizer/fft.h
================================================
#pragma once
#include <complex.h>
#include <math.h>

#define MAX_SAMPLES_LOG_2 12
#define MAX_SAMPLES (1 << MAX_SAMPLES_LOG_2)
#define K (MAX_SAMPLES / 2)

#define FFT_BUCKET_WIDTH(NUM_SAMPLES) (44100/(NUM_SAMPLES))
#define FFT_SAMPLE_TO_FREQ(NUM_SAMPLES, SAMPLE_INDEX) (44100*(SAMPLE_INDEX)/(NUM_SAMPLES))
#define FFT_FREQ_TO_SAMPLE(NUM_SAMPLES, FREQ) ((int)roundf((FREQ)*(NUM_SAMPLES)/44100))
#define FFT_PSD(SAMPLE) ((float)crealf(cabsf((SAMPLE))))

void fft_init();
void fft_run(
        const float *input_data,
        complex float *output_data,
        unsigned int N,
        unsigned int channels);


================================================
FILE: equalizer/fft_test.c
================================================
#include "fft.h"

#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <time.h>

static const float single_channel_5000HZ_data[] = {
    -0.120544, 0.222534, 0.457336, 0.469727, 0.253632, -0.085846, -0.383575,
    -0.494568, -0.365295, -0.058197, 0.277222, 0.477783, 0.445923, 0.197205,
    -0.147461, -0.420441, -0.488922, -0.319580, 0.005157, 0.327362, 0.490356,
    0.414825, 0.137512, -0.206696, -0.450378, -0.475037, -0.268646, 0.068390,
    0.372162, 0.494537, 0.376892, 0.075592, -0.262482, -0.472900, -0.453339,
    -0.213257, 0.130524, 0.410797, 0.491302, 0.332794, 0.012451, -0.313995,
    -0.487701, -0.424225, -0.154419, 0.190491, 0.442719, 0.479614, 0.283234,
    -0.050934, -0.360352, -0.494476, -0.388123, -0.093018, 0.247345, 0.467377,
    0.460083, 0.229004, -0.113464, -0.400787, -0.493164, -0.345673, -0.030090,
    0.300110, 0.484344, 0.432983, 0.171021, -0.174133, -0.434601, -0.483704,
    -0.297546, 0.033325, 0.347992, 0.493378, 0.398773, 0.110199, -0.231964,
    -0.461334, -0.466339, -0.244507, 0.096191, 0.390137, 0.494293, 0.358032,
    0.047607, -0.285980, -0.480469, -0.441284, -0.187500, 0.157501, 0.425873,
    0.487091, 0.311401, -0.015778, -0.335266, -0.491730, -0.409027, -0.127380,
    0.216217, 0.454590, 0.471893, 0.259644, -0.078888, -0.379059, -0.494568,
    -0.370026, -0.065186, 0.271332, 0.475891, 0.448944, 0.203644, -0.140717,
    -0.416687, -0.489960, -0.324951, -0.001892, 0.322052, 0.489349, 0.418640,
    0.144287, -0.200256, -0.447388, -0.476959, -0.274536, 0.061401, 0.367462,
    0.494537, 0.381439, 0.082550, -0.256500, -0.470795, -0.456116, -0.219604,
    0.123688, 0.406830, 0.492065, 0.337982, 0.019470, -0.308502, -0.486450,
    -0.427826, -0.161102, 0.183960, 0.439545, 0.481293, 0.288971, -0.043915,
    -0.355469, -0.494141, -0.392487, -0.099945, 0.241211, 0.465027, 0.462616,
    0.235229, -0.106598, -0.396576, -0.493683, -0.350677, -0.037109, 0.294495,
    0.482849, 0.436340, 0.177612, -0.167542, -0.431213, -0.485168, -0.303131,
    0.026276, 0.342926, 0.492767, 0.402924, 0.117065, -0.225708, -0.458740,
    -0.468628, -0.250610, 0.089264, 0.385742, 0.494537, 0.362854, 0.054626,
    -0.280182, -0.478729, -0.444458, -0.194000, 0.150787, 0.422241, 0.488281,
    0.316864, -0.008728, -0.330048, -0.490845, -0.412933, -0.134186, 0.209839,
    0.451782, 0.473969, 0.265625, -0.071930, -0.374512, -0.494568, -0.374664,
    -0.072174, 0.265411, 0.473877, 0.451874, 0.210022, -0.133942, -0.412811,
    -0.490906, -0.330231, -0.008972, 0.316650, 0.488251, 0.422363, 0.151031,
    -0.193787, -0.444336, -0.478790, -0.280365, 0.054382, 0.362671, 0.494537,
    0.385895, 0.089508, -0.250427, -0.468567, -0.458801, -0.225922, 0.116852,
    0.402802, 0.492798, 0.343109, 0.026520, -0.302948, -0.485107, -0.431305,
    -0.167755, 0.177399, 0.436249, 0.482910, 0.294678, -0.036896, -0.350525,
    -0.493683, -0.396729, -0.106842, 0.235016, 0.462555, 0.465088, 0.241425,
    -0.099701, -0.392303, -0.494141, -0.355621, -0.044159, 0.288788, 0.481262,
    0.439636, 0.184174, -0.160858, -0.427704, -0.486511, -0.308685, 0.019257,
    0.337830, 0.492065, 0.406982, 0.123932, -0.219421, -0.456024, -0.470856,
    -0.256683, 0.082306, 0.381287, 0.494537, 0.367615, 0.061646, -0.274323,
    -0.476868, -0.447479, -0.200470, 0.144043, 0.418518, 0.489380, 0.322235,
    -0.001678, -0.324768, -0.489899, -0.416779, -0.140961, 0.203400, 0.448822,
    0.475952, 0.271545, -0.064941, -0.369873, -0.494568, -0.379242, -0.079132,
    0.259430, 0.471832, 0.454712, 0.216400, -0.127167, -0.408875, -0.491760,
    -0.335449, -0.016022, 0.311218, 0.487061, 0.425995, 0.157715, -0.187286,
    -0.441193, -0.480530, -0.286163, 0.047363, 0.357849, 0.494293, 0.390289,
    0.096436, -0.244324, -0.466248, -0.461395, -0.232178, 0.109985, 0.398651,
    0.493378, 0.348145, 0.033569, -0.297333, -0.483673, -0.434723, -0.174347,
    0.170807, 0.432861, 0.484406, 0.300323, -0.029846, -0.345520, -0.493134,
    -0.400909, -0.113708, 0.228790, 0.459991, 0.467438, 0.247528, -0.092773,
    -0.388000, -0.494507, -0.360504, -0.051178, 0.283020, 0.479584, 0.442841,
    0.190704, -0.154175, -0.424103, -0.487762, -0.314178, 0.012207, 0.332611,
    0.491241, 0.410950, 0.130737, -0.213074, -0.453247, -0.472992, -0.262695,
    0.075348, 0.376740, 0.494537, 0.372314, 0.068634, -0.268433, -0.474945,
    -0.450470, -0.206909, 0.137299, 0.414703, 0.490387, 0.327545, 0.005402,
    -0.319397, -0.488861, -0.420563, -0.147705, 0.196991, 0.445862, 0.477844,
    0.277435, -0.057953, -0.365143, -0.494568, -0.383728, -0.086090, 0.253418,
    0.469635, 0.457428, 0.222717, -0.120331, -0.404877, -0.492493, -0.340607,
    -0.023071, 0.305695, 0.485748, 0.429535, 0.164398, -0.180725, -0.437958,
    -0.482178, -0.291870, 0.040344, 0.352966, 0.493866, 0.394562, 0.103333,
    -0.238159, -0.463837, -0.463928, -0.238373, 0.103088, 0.394440, 0.493896,
    0.353119, 0.040588, -0.291687, -0.482117, -0.438049, -0.180939, 0.164154,
    0.429413, 0.485809, 0.305878, -0.022797, -0.340424, -0.492462, -0.404999,
    -0.120544, 0.222504, 0.457336, 0.469727, 0.253632, -0.085846, -0.383575,
    -0.494568, -0.365295, -0.058197, 0.277222, 0.477783, 0.445923, 0.197205,
    -0.147461, -0.420410, -0.488922, -0.319580, 0.005127, 0.327362, 0.490356,
    0.414825, 0.137512, -0.206696, -0.450348, -0.475037, -0.268646, 0.068390,
    0.372131, 0.494537, 0.376892, 0.075592, -0.262482, -0.472900, -0.453339,
    -0.213287, 0.130524, 0.410797, 0.491302, 0.332794, 0.012451, -0.313995,
    -0.487701, -0.424225, -0.154419, 0.190491, 0.442719, 0.479645, 0.283234,
    -0.050934, -0.360352, -0.494476, -0.388153, -0.093018, 0.247345, 0.467377,
    0.460083, 0.229004, -0.113464, -0.400757, -0.493164, -0.345673, -0.030090,
    0.300110, 0.484344, 0.432983, 0.171021, -0.174133, -0.434601, -0.483704,
    -0.297546,
};

static const float dual_channel_micro[] = {
    -0.120544, -0.120544, 0.222534, 0.222534,
};

typedef struct TestCases {
    int frequency;
    float expected_psd;
} TestCases;

TestCases cases[] = {
    {
        .frequency = 100,
        .expected_psd = 0.09595596265773097,
    },
    {
        .frequency = 200,
        .expected_psd = 0.09558897341820413,
    },
    {
        .frequency = 1000,
        .expected_psd = 0.10730647827342488,
    },
    {
        .frequency = 2000,
        .expected_psd = 0.14449811458513645,
    },
    {
        .frequency = 5000,
        .expected_psd = 126.14145371375102,
    },
    {
        .frequency = 10000,
        .expected_psd = 0.14292403328440603,
    },
};

static inline bool float_approx_equal(float f1, float f2) {
    return fabsf(f1 - f2) > 0.00001 ? false : true;
}

void test_init() {
    clock_t start, end;
    double cpu_time;
    start = clock();
    fft_init();
    end = clock();
    fprintf(stdout, "[FFT Init %d samples] time: %lf\n", MAX_SAMPLES, (double) (end - start) / CLOCKS_PER_SEC);
}

void test_single_channel() {
    clock_t start, end;
    double cpu_time;
    complex float output[MAX_SAMPLES];
    unsigned int N = sizeof(single_channel_5000HZ_data) / sizeof(single_channel_5000HZ_data[0]);

    start = clock();
    fft_run(single_channel_5000HZ_data, output, N, 1);
    end = clock();

    for (int i = 0; i < sizeof(cases)/sizeof(cases[0]); i++) {
        int sample_index = FFT_FREQ_TO_SAMPLE(N, cases[i].frequency);
        float psd = FFT_PSD(output[sample_index]);
        assert(float_approx_equal(cases[i].expected_psd, FFT_PSD(output[sample_index])));
    }

    fprintf(stdout, "[FFT Run %d samples] time: %lf\n", N, (double) (end - start) / CLOCKS_PER_SEC);
}

void test_dual_channel_micro() {
    clock_t start, end;
    double cpu_time;
    complex float output[MAX_SAMPLES];
    unsigned int N = sizeof(dual_channel_micro) / sizeof(dual_channel_micro[0]);

    start = clock();
    fft_run(dual_channel_micro, output, N, 2);
    end = clock();


    fprintf(stdout, "[FFT Run %d samples] time: %lf\n", N, (double) (end - start) / CLOCKS_PER_SEC);
}

int main(int argc, char **argv) {
    test_init();
    test_single_channel();
    test_dual_channel_micro();
    return 0;
}


================================================
FILE: equalizer/macro.h
================================================
#pragma once

#define PRETTY_EXPORT __attribute__ ((visibility ("default")))
#define MAY_ALIAS __attribute__((__may_alias__))

#define SWAP(a, b)              \
    ({                          \
        typeof(a) _tmp_ = b;    \
        b = a;                  \
        a = _tmp_;              \
    })

#define BOX_USERDATA(var) ((void *) ((uint64_t) (var)))
#define UNBOX_USERDATA(type, var) ((type) ((uint64_t) (var)))

#define _likely_(x)      __builtin_expect(!!(x), 1)
#define _unlikely_(x)    __builtin_expect(!!(x), 0)

#define _real_ __real__
#define _imag_ __imag__


================================================
FILE: equalizer/pretty.c
================================================
#include <assert.h>
#include <complex.h>
#include <errno.h>
#include <math.h>
#include <pthread.h>
#include <pulse/pulseaudio.h>
#include <pulse/sample.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

#include "arena.h"
#include "fft.h"
#include "pretty.h"

#define NUM_FILTERS 7
#define LATENCY_MAX_MS 30

//=============================================================================

static pa_mainloop_api *mainloop_api = NULL;
static pa_context *context = NULL;
static uint32_t prettyeq_module_index = PA_INVALID_INDEX;
static pa_stream *read_stream = NULL, *write_stream = NULL;
static pa_threaded_mainloop *m = NULL;

static const pa_sample_spec sample_spec = {
    // https://freedesktop.org/software/pulseaudio/doxygen/sample_8h.html
    .format = PA_SAMPLE_FLOAT32LE,
    .rate = 44100,
    .channels = 2,
};

//=============================================================================

static _Atomic bool bypass;

static arena_t *arena = NULL;

struct _AudioFFT {
    pthread_spinlock_t lock;
    complex float data[MAX_SAMPLES];
    unsigned int N;
};
static AudioFFT audio_fft = {0};

typedef struct FilterParams {
    float a[3];
    float b[3];
} FilterParams;

struct _PrettyFilter {
    /* Filter parameters used in the audio loop. */
    float xwin[3];
    float ywin[2];

    /* Temporary parameters for compare-exchange. */
    _Atomic (FilterParams*) params;
    FilterParams *storage;
};
static PrettyFilter filters[NUM_FILTERS];
static int user_enabled_filters = 0;

//=============================================================================

static void quit(int ret) {
    fprintf(stderr, "debug: quit(%d) called!", ret);
    if (prettyeq_module_index != PA_INVALID_INDEX)
        pa_operation_unref(pa_context_unload_module(context, prettyeq_module_index, NULL, NULL));
    mainloop_api->quit(mainloop_api, ret);
}

static void cleanup() {
    if (read_stream)
        pa_stream_unref(read_stream);

    if (write_stream)
        pa_stream_unref(write_stream);

    if (context)
        pa_context_unref(context);

    if (m)
        pa_threaded_mainloop_free(m);

    if (arena)
        arena_destroy(&arena);

    if (audio_fft.lock) {
        pthread_spin_destroy(&audio_fft.lock);
    }

    munlockall();
}

//=============================================================================

static void success_callback(pa_context *c, int success, void *userdata) {
    assert(c);

    if ( !success) {
        fprintf(stderr, "Failure: %s", pa_strerror(pa_context_errno(c)));
        quit(1);
    }
}

static void sink_input_callback(pa_context *c, const pa_sink_input_info *i, int is_last, void *userdata) {
    assert(c);

    if (is_last < 0) {
        fprintf(stderr, "Failed to get sink information: %s",
                        pa_strerror(pa_context_errno(c)));
        return;
    }

    if (is_last)
        return;

    assert(i);

    if (strcmp(SINK_NAME, i->name) != 0) {
        const char *app_name;
        app_name = pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME);
        fprintf(stderr, "new sink-input: name=%s, index=%d, sink=%d\n", app_name, i->index, i->sink);
        pa_operation_unref(pa_context_move_sink_input_by_name(c, i->index, SINK_NAME, NULL, NULL));
    } else {
        /* Pulseaudio keeps sink state, so if the user changed the prettyeq
         * playback stream volume, we reset back to 100% here. This is so the
         * equalizer "at rest" doesn't modify the input stream. */
        pa_cvolume volume;
        volume = i->volume;
        pa_cvolume_reset(&volume, i->sample_spec.channels);
        pa_operation_unref(pa_context_set_sink_input_volume(c,
                                                            i->index,
                                                            &volume,
                                                            success_callback,
                                                            NULL));
    }
}

static void subscribe_callback(
        pa_context *c,
        pa_subscription_event_type_t t,
        uint32_t idx,
        void *userdata) {
    assert(c);
    assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT);

    if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW)
        pa_operation_unref(pa_context_get_sink_input_info(c, idx, sink_input_callback, NULL));
}

static void null_source_output_callback(pa_context *c, const pa_source_output_info *i, int is_last, void *userdata) {
    uint32_t prettyeq_source = UNBOX_USERDATA(uint32_t, userdata);

    assert(c);

    if (is_last < 0) {
        fprintf(stderr, "Failed to get null source output information: %s\n",
                         pa_strerror(pa_context_errno(c)));
        quit(1);
        return;
    }

    if (is_last)
        return;

    assert(i);

    if (prettyeq_source == i->source) {
        /* The recording stream sometimes starts at 79% instead of 100%, so
         * let's fix that here or else we start at a lower volume. */
        pa_cvolume volume;
        volume = i->volume;
        pa_cvolume_reset(&volume, i->sample_spec.channels);
        pa_operation_unref(pa_context_set_source_output_volume(c,
                                                            i->index,
                                                            &volume,
                                                            success_callback,
                                                            NULL));
    }
}

static void null_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_last, void *userdata) {
    pa_buffer_attr buffer_attr;

    assert(c);

    if (is_last < 0) {
        fprintf(stderr, "Failed to get null sink information: %s\n",
                         pa_strerror(pa_context_errno(c)));
        quit(1);
        return;
    }

    if (is_last)
        return;

    assert(i);

    /* Hook up the read stream to the null sink monitor output. */
    memset(&buffer_attr, 0, sizeof(buffer_attr));
    buffer_attr.maxlength = (uint32_t) -1;
    buffer_attr.prebuf = (uint32_t) -1;
    buffer_attr.fragsize = pa_usec_to_bytes(LATENCY_MAX_MS * PA_USEC_PER_MSEC, &sample_spec);
    buffer_attr.tlength = pa_usec_to_bytes(LATENCY_MAX_MS * PA_USEC_PER_MSEC, &sample_spec);
    if (pa_stream_connect_record(read_stream, i->monitor_source_name, &buffer_attr, PA_STREAM_ADJUST_LATENCY) < 0) {
        fprintf(stderr, "pa_stream_connect_record() failed: %s\n",
                pa_strerror(pa_context_errno(c)));
        quit(1);
    }
    fprintf(stderr, "monitor source name=%s\n", i->monitor_source_name);
    pa_operation_unref(pa_context_get_source_output_info_list(c,
                                                              null_source_output_callback,
                                                              BOX_USERDATA(i->monitor_source)));
}

static void unload_module_callback(pa_context *c, int success, void *userdata) {
    pa_threaded_mainloop *m;
    m = userdata;
    assert(m);

    if (! success)
        fprintf(stderr, "pa_context_unload_module() failed: %s",
                pa_strerror(pa_context_errno(c)));

    pa_threaded_mainloop_signal(m, 0);
}

static void read_stream_callback(pa_stream *s, size_t length, void *userdata) {
    assert(s);
    assert(length > 0);

    while (pa_stream_readable_size(s) > 0) {
        const void *input_data = NULL;
        float *fp = NULL;
        size_t num_samples;

        if (pa_stream_peek(s, &input_data, &length) < 0) {
            fprintf(stderr, "pa_stream_peek() failed: %s\n",
                            pa_strerror(pa_context_errno(context)));
            quit(1);
            return;
        }

        fp = (float *) input_data;
        num_samples = length / sizeof(float);

        if (bypass)
            goto play_frame;

#ifndef EQ_DISABLE
        for (int k = 0; k < NUM_FILTERS; k++) {
            /* Swap in NULL to block a filter param update mid-iteration. */
            FilterParams *params = atomic_exchange(&filters[k].params, NULL);

            float *xwin = filters[k].xwin;
            float *ywin = filters[k].ywin;
            float *a = params->a;
            float *b = params->b;

            for (unsigned long i = 0; i < length / sizeof(float); i++) {
                float f;

                /* Slide x-window */
                xwin[2] = xwin[1];
                xwin[1] = xwin[0];
                xwin[0] = fp[i];

                f = fp[i];
                f = (b[0] / a[0] * xwin[0]) +
                    (b[1] / a[0] * xwin[1]) +
                    (b[2] / a[0] * xwin[2]) -
                    (a[1] / a[0] * ywin[0]) -
                    (a[2] / a[0] * ywin[1]);

                if (PRETTY_IS_DENORMAL(f) || PRETTY_IS_NAN(f))
                    f = 0.0f;

                fp[i] = f;

                /* Slide y-window */
                ywin[1] = ywin[0];
                ywin[0] = f;
            }
            /* Unblock any pending filter param updates. */
            atomic_store(&filters[k].params, params);
        }
#endif // EQ_DISABLE

play_frame:
        for (;;) {
            size_t data_length = length;
            uint8_t *output_data = NULL;

            if (pa_stream_begin_write(write_stream, (void **)&output_data, &data_length) < 0) {
                fprintf(stderr, "pa_stream_begin_write() failed: %s\n",
                                pa_strerror(pa_context_errno(context)));
                quit(1);
                return;
            }

            memcpy(output_data, input_data, data_length);
            if (pa_stream_write(write_stream, output_data, data_length, NULL, 0, PA_SEEK_RELATIVE) < 0) {
                fprintf(stderr, "pa_stream_write() failed: %s\n",
                                pa_strerror(pa_context_errno(context)));
                quit(1);
                return;
            }

            if (data_length >= length)
                break;

            length -= data_length;
            input_data += data_length;
        }

        if (pthread_spin_trylock(&audio_fft.lock) >= 0) {
            /* Sigh... In no world should pulse really be handing us anything more than
             * uint16_t in a rapid callback. Fuck it, we downcast. */
            assert(num_samples <= UINT_MAX);
            fft_run(fp, audio_fft.data, (unsigned int) num_samples, sample_spec.channels);
            audio_fft.N = (unsigned int) num_samples / sample_spec.channels;
            pthread_spin_unlock(&audio_fft.lock);
        }

        if (pa_stream_drop(s) < 0) {
            fprintf(stderr, "pa_stream_drop() failed: %s\n",
                            pa_strerror(pa_context_errno(context)));
            quit(1);
            return;
        }
    }
}

static void load_module_callback(pa_context *c, uint32_t idx, void *userdata) {
    assert(c);

    if (idx == PA_INVALID_INDEX) {
        fprintf(stderr, "Bad index\n");
        quit(1);
        return;
    }

    prettyeq_module_index = idx;
    pa_operation_unref(pa_context_get_sink_input_info_list(c, sink_input_callback, NULL));
    pa_operation_unref(pa_context_get_sink_info_by_name(c, SINK_NAME, null_sink_info_callback, NULL));

    /* Subscribe to new sink-inputs so we can send them through the equalizer. */
    pa_context_set_subscribe_callback(c, subscribe_callback, NULL);
    pa_operation_unref(pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK_INPUT, NULL, NULL));
}

static void module_list_callback(pa_context *c, const pa_module_info *i, int is_last, void *userdata) {
    pa_threaded_mainloop *m = userdata;
    static bool signal_on_last = true;

    assert(m);
    assert(c);

    if (is_last) {
        if (signal_on_last)
            pa_threaded_mainloop_signal(m, 0);
        return;
    }

    assert(i);
    if (strcmp(i->name, "module-null-sink") == 0 && i->argument && strcmp(i->argument, "sink_name=" SINK_NAME) == 0) {
        pa_operation_unref(pa_context_unload_module(c, i->index, unload_module_callback, m));
        signal_on_last = false;
    }

}

static void drain_signal_callback(pa_context *c, void *userdata) {
    pa_threaded_mainloop *m = userdata;

    assert(c);
    assert(m);

    pa_threaded_mainloop_signal(m, 0);
}

static void context_state_callback(pa_context *c, void *userdata) {
    switch (pa_context_get_state(c)) {
        case PA_CONTEXT_UNCONNECTED:
        case PA_CONTEXT_CONNECTING:
        case PA_CONTEXT_AUTHORIZING:
        case PA_CONTEXT_SETTING_NAME:
            break;

        case PA_CONTEXT_READY: {

            if ( !(read_stream = pa_stream_new(c, "prettyeq", &sample_spec, NULL))) {
                fprintf(stderr, "pa_stream_new() failed.\n");
                quit(1);
                return;
            }

            if ( !(write_stream = pa_stream_new(c, "prettyeq", &sample_spec, NULL))) {
                fprintf(stderr, "pa_stream_new() failed.\n");
                quit(1);
                return;
            }

            /* Setup the playback stream. We create the recording stream in the null sink callback. */
            if (pa_stream_connect_playback(write_stream, NULL, NULL, 0, NULL, NULL) < 0) {
                fprintf(stderr, "pa_stream_connect_playback() failed: %s\n",
                        pa_strerror(pa_context_errno(c)));
                quit(1);
                return;
            }

            /* Audio event callback functions. */
            pa_stream_set_read_callback(read_stream, read_stream_callback, NULL);
            pa_threaded_mainloop_signal(m, 0);

            fprintf(stderr, "context is ready!\n");
            break;
        }
        case PA_CONTEXT_FAILED:
            fprintf(stderr, "Unclean termination\n");
            quit(1);
            break;
        case PA_CONTEXT_TERMINATED:
            fprintf(stderr, "Clean termination\n");
            quit(0);
            break;

    }
}

//=============================================================================

int pretty_init() {
    int r;

    /* Initialize the fft and user exposed data structures. */
    fft_init();
    audio_fft.N = 0;
    r = pthread_spin_init(&audio_fft.lock, PTHREAD_PROCESS_PRIVATE);
    if (r < 0) {
        fprintf(stderr, "Could not pthread_spin_init()");
        goto err;
    }

    /* Initialize the memory arena used to create new filters. */
    arena = arena_new(NUM_FILTERS * 2, sizeof(FilterParams));
    if (!arena) {
        fprintf(stderr, "Could not arena_new()");
        r = -ENOMEM;
        goto err;
    }

    /* Initialize filters. */
    for (int i = 0; i < NUM_FILTERS; i++) {
        FilterParams *start_params;
        PrettyFilter *filter = &filters[i];

        filter->xwin[0] = 0.0f;
        filter->xwin[1] = 0.0f;
        filter->xwin[2] = 0.0f;
        filter->ywin[0] = 0.0f;
        filter->ywin[1] = 0.0f;

        start_params = arena_alloc(arena);
        atomic_store(&filter->params, start_params);
        filter->storage = start_params;

        /* We don't want to page fault reading filters in the audio loop. */
        r = mlock(&filter, sizeof(PrettyFilter));
        if (r < 0) {
            fprintf(stderr, "mlock() failed: %s.", strerror(errno));
            goto err;
        }
    }

    /* Start with bypass mode disabled. */
    atomic_store(&bypass, false);

    /* Initialize pulseaudio mainloop. */
    if ( !(m = pa_threaded_mainloop_new())) {
        fprintf(stderr, "pa_mainloop_new() failed.");
        r = -1;
        goto err;
    }

    mainloop_api = pa_threaded_mainloop_get_api(m);
    if ( !(context = pa_context_new_with_proplist(mainloop_api, NULL, NULL))) {
        fprintf(stderr, "pa_context_new() failed.");
        r = -1;
        goto err;
    }

    pa_context_set_state_callback(context, context_state_callback, NULL);
    if (pa_context_connect(context, NULL, 0, NULL) < 0) {
        fprintf(stderr, "pa_context_connect() failed.");
        r = -1;
        goto err;
    }

    r = pa_threaded_mainloop_start(m);
    if (r < 0) {
        fprintf(stderr, "pa_threaded_mainloop_start() failed.");
        goto err;
    }

    return 0;

err:
    cleanup();
    return r;
}

void pretty_setup_sink_io() {
    pa_operation *o;

    pa_threaded_mainloop_lock(m);

    /* Wait until context is ready. */
    while(pa_context_get_state(context) != PA_CONTEXT_READY)
        pa_threaded_mainloop_wait(m);

    /* Handle an unclean termination and cleanup an existing prettyeq module. */
    o = pa_context_get_module_info_list(context, module_list_callback, m);
    while (pa_operation_get_state(o) == PA_OPERATION_RUNNING)
        pa_threaded_mainloop_wait(m);
    pa_operation_cancel(o);
    pa_operation_unref(o);

    /* Drain the context. TODO(keur): Not sure if we actually need this. Poorly documented function */
    if ((o = pa_context_drain(context, drain_signal_callback, m))) {
        while (pa_operation_get_state(o) == PA_OPERATION_RUNNING)
            pa_threaded_mainloop_wait(m);
        pa_operation_unref(o);
    }

    pa_threaded_mainloop_unlock(m);

    /* Load in the null sink to act as audio IO. */
    pa_operation_unref(pa_context_load_module(context,
                                              "module-null-sink",
                                              "sink_name=" SINK_NAME,
                                              load_module_callback,
                                              NULL));
}

int pretty_exit() {
    if (m) {
        pa_operation *o;
        if (prettyeq_module_index != PA_INVALID_INDEX) {
            pa_threaded_mainloop_lock(m);
            o = pa_context_unload_module(context, prettyeq_module_index, unload_module_callback, m);
            while (pa_operation_get_state(o) == PA_OPERATION_RUNNING)
                pa_threaded_mainloop_wait(m);
            pa_operation_unref(o);
            pa_threaded_mainloop_unlock(m);
        }
        pa_threaded_mainloop_stop(m);
    }
    cleanup();

    return 0;
}

int pretty_new_filter(PrettyFilter **filter) {
    assert(user_enabled_filters < NUM_FILTERS);
    *filter = &filters[user_enabled_filters++];
    return 0;
}

static inline void safe_update_audio_loop(PrettyFilter *filter, FilterParams *new_params) {
    FilterParams *expected;

    assert(filter);
    assert(new_params);

    do {
        expected = filter->storage;
        atomic_compare_exchange_strong(&filter->params, &expected, new_params);
    } while (!expected);

    arena_dealloc(arena, filter->storage);
    filter->storage = new_params;
}

void pretty_set_peaking_eq(PrettyFilter *filter, float f0, float bandwidth, float db_gain) {
    FilterParams *new_params;
    float A, alpha, w0, sinw0, cosw0;

    new_params = arena_alloc(arena);

    A = powf(10, db_gain/40);
    w0 = 2*M_PI*f0/sample_spec.rate;
    sinw0 = sinf(w0);
    cosw0 = cosf(w0);
    alpha = sinw0*sinhf(logf(2)/2 * bandwidth * w0/sinw0);
    new_params->b[0] = 1 + alpha*A;
    new_params->b[1] = -2 * cosw0;
    new_params->b[2] = 1 - alpha*A;
    new_params->a[0] = 1 + alpha/A;
    new_params->a[1] = -2 * cosw0;
    new_params->a[2] = 1 - alpha/A;

    safe_update_audio_loop(filter, new_params);
}

void pretty_set_low_shelf(PrettyFilter *filter, float f0, float S, float db_gain) {
    FilterParams *new_params;
    float A, alpha, w0, sinw0, cosw0, sqrtA;

    new_params = arena_alloc(arena);

    A = powf(10, db_gain / 40);
    w0 = 2*M_PI*f0/sample_spec.rate;
    sinw0 = sinf(w0);
    cosw0 = cosf(w0);
    sqrtA = sqrtf(A);
    alpha = sinw0/2 * sqrtf((A + 1/A) * (1/S - 1) + 2);
    new_params->b[0] = A*((A + 1) - (A - 1)*cosw0 + 2*sqrtA*alpha);
    new_params->b[1] = 2*A*((A - 1) - (A + 1)*cosw0);
    new_params->b[2] = A*((A + 1) - (A - 1)*cosw0 - 2*sqrtA*alpha);
    new_params->a[0] = (A + 1) + (A - 1)*cosw0 + 2*sqrtA*alpha;
    new_params->a[1] = -2*((A - 1) + (A + 1)*cosw0);
    new_params->a[2] = (A + 1) + (A - 1)*cosw0 - 2*sqrtA*alpha;

    safe_update_audio_loop(filter, new_params);
}

void pretty_set_high_shelf(PrettyFilter *filter, float f0, float S, float db_gain) {
    FilterParams *new_params;
    float A, alpha, w0, sinw0, cosw0, sqrtA;

    new_params = arena_alloc(arena);

    A = powf(10, db_gain / 40);
    w0 = 2*M_PI*f0/sample_spec.rate;
    sinw0 = sinf(w0);
    cosw0 = cosf(w0);
    sqrtA = sqrtf(A);
    alpha = sinw0/2 * sqrtf((A + 1/A) * (1/S - 1) + 2);
    new_params->b[0] = A*((A + 1) + (A - 1)*cosw0 + 2*sqrtA*alpha);
    new_params->b[1] = -2*A*((A - 1) + (A + 1)*cosw0);
    new_params->b[2] = A*((A + 1) + (A - 1)*cosw0 - 2*sqrtA*alpha);
    new_params->a[0] = (A + 1) - (A - 1)*cosw0 + 2*sqrtA*alpha;
    new_params->a[1] = 2*((A - 1) - (A + 1)*cosw0);
    new_params->a[2] = (A + 1) - (A - 1)*cosw0 - 2*sqrtA*alpha;

    safe_update_audio_loop(filter, new_params);
}

void pretty_enable_bypass(bool should_bypass)
{
    atomic_store(&bypass, should_bypass);
}

void pretty_acquire_audio_data(complex float **data, unsigned int *N) {
    int r = pthread_spin_lock(&audio_fft.lock);
    assert(r == 0); /* No deadlock. */
    *data = audio_fft.data;
    *N = audio_fft.N;
}

void pretty_release_audio_data() {
    int r = pthread_spin_unlock(&audio_fft.lock);
    assert(r == 0); /* No deadlock. */
}


================================================
FILE: equalizer/pretty.h
================================================
#pragma once

#include "macro.h"

#include <complex.h>
#include <stdbool.h>
#include <stdint.h>

#define SINK_NAME "prettyeq"

#define DRAIN_NO_CB(c) (pa_operation_unref(pa_context_drain(c, NULL, NULL)))

/* with -ffast-math the standard macros have undefined behavior. */
#define PRETTY_IS_DENORMAL(f)   \
    ({                          \
        typeof(f) *_f_ = &(f);  \
        ((*(unsigned int *)_f_) & 0x7f800000u) == 0 || ((*(unsigned int *)_f_) & 0xff800000u) == 0; \
    })

#define PRETTY_IS_NAN(f)  		\
    ({                          \
        typeof(f) *_f_ = &(f);  \
        ((*(unsigned int *)_f_) << 1) > 0xff000000u; \
    })

/* accomodating the stupid C++ template redefinition... */
#ifdef __cplusplus
    typedef std::complex<float>* FFTComplexCompat;
#else
    typedef complex float* FFTComplexCompat;
#endif

#ifdef __cplusplus
extern "C" {
#endif

typedef struct _PrettyFilter PrettyFilter;

typedef struct _AudioFFT AudioFFT;

PRETTY_EXPORT
int pretty_init();

PRETTY_EXPORT
int pretty_exit();

PRETTY_EXPORT
void pretty_setup_sink_io();

PRETTY_EXPORT
int pretty_new_filter(PrettyFilter **filter);

PRETTY_EXPORT
void pretty_set_peaking_eq(PrettyFilter *filter, float f0, float bandwidth, float db_gain);

PRETTY_EXPORT
void pretty_set_low_shelf(PrettyFilter *filter, float f0, float S, float db_gain);

PRETTY_EXPORT
void pretty_set_high_shelf(PrettyFilter *filter, float f0, float S, float db_gain);

PRETTY_EXPORT
void pretty_enable_bypass(bool should_bypass);

PRETTY_EXPORT
void pretty_acquire_audio_data(FFTComplexCompat *data, unsigned int *N);

PRETTY_EXPORT
void pretty_release_audio_data();

#ifdef __cplusplus
}
#endif


================================================
FILE: gui/collisionmanager.cpp
================================================
#include "collisionmanager.h"
#include "eqhoverer.h"
#include "filtercurve.h"

#include <QDebug>

CollisionManager::CollisionManager() : numItems(0)
{

}

void CollisionManager::addEqHoverer(EqHoverer *hover)
{
    Q_ASSERT(hover);
    hoverItems[numItems++] = hover;
}

void CollisionManager::notifyFriends()
{
    Q_ASSERT(numItems == NUM_FILTERS);

    for (int i = 0; i < numItems; i++)
        hoverItems[i]->collisionStateChanged();
}


================================================
FILE: gui/collisionmanager.h
================================================
#ifndef COLLISIONMANAGER_H
#define COLLISIONMANAGER_H

#define NUM_FILTERS 7

class EqHoverer;
class FilterCurve;

class CollisionManager
{
public:
    explicit CollisionManager();
    void addEqHoverer(EqHoverer *hover);
    void notifyFriends();

private:
    int numItems;
    EqHoverer *hoverItems[NUM_FILTERS];
};

#endif // COLLISIONMANAGER_H


================================================
FILE: gui/curvepoint.cpp
================================================
#include "curvepoint.h"
#include <QDebug>
#include <QGraphicsItem>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#define RADIUS 40
#define ESCALE 0.60

CurvePoint::CurvePoint(QBrush normalBrush, QBrush lightBrush, QObject *parent)
    : QObject(parent),
      normalBrush(normalBrush),
      lightBrush(lightBrush)
{
    setZValue(100001);
    hide();
    setFlags(GraphicsItemFlag::ItemIsSelectable | GraphicsItemFlag::ItemIsMovable);
}

QRectF CurvePoint::boundingRect() const
{
    return QRectF(-RADIUS/2, -RADIUS/2, RADIUS, RADIUS);
}

void CurvePoint::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    painter->setRenderHint(QPainter::Antialiasing, true);
    painter->setPen(Qt::NoPen);

    /* outer circle */
    painter->setBrush(lightBrush);
    painter->drawEllipse(-RADIUS/2, -RADIUS/2, RADIUS, RADIUS);

    /* inner circle */
    painter->scale(ESCALE, ESCALE);
    painter->setBrush(normalBrush);
    painter->drawEllipse(-RADIUS/2, -RADIUS/2, RADIUS, RADIUS);

}

int CurvePoint::type() const
{
    /* Make this type work with qgraphicsitem_cast */
    return Type;
}

void CurvePoint::setResetPos(QPointF resetPoint)
{
    sceneResetPoint = resetPoint;
    setPos(sceneResetPoint);
}

void CurvePoint::reset()
{
    setPos(sceneResetPoint);
    emit pointPositionChanged(this);
}

void CurvePoint::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    QGraphicsItem::mouseMoveEvent(event);
    if (scene() && event->type() == QGraphicsSceneMouseEvent::GraphicsSceneMouseMove) {

        /* x bounds detection */
        if (scene()->sceneRect().x() >= mapToScene(boundingRect().center()).x())
            setPos(scene()->sceneRect().x(), pos().y());
        else if (scene()->sceneRect().x() + scene()->width() <= mapToScene(boundingRect().center()).x())
            setPos(scene()->sceneRect().x() + scene()->width(), pos().y());

        /* y bounds detection */
        if (scene()->sceneRect().y() >= mapToScene(boundingRect().center()).y())
            setPos(pos().x(), scene()->sceneRect().y());
        else if (scene()->sceneRect().y() + scene()->height() <= mapToScene(boundingRect().center()).y())
            setPos(pos().x(), scene()->sceneRect().y() + scene()->sceneRect().height());

        emit pointPositionChanged(this);
    }
}

void CurvePoint::wheelEvent(QGraphicsSceneWheelEvent *event)
{
    QGraphicsItem::wheelEvent(event);
    wheelDeltaSum += event->delta();
    if (wheelDeltaSum >= 120) {
        wheelDeltaSum = 0;
        emit pointSlopeChanged(-1);
    } else if (wheelDeltaSum <= -120) {
        wheelDeltaSum = 0;
        emit pointSlopeChanged(1);
    }
}


================================================
FILE: gui/curvepoint.h
================================================
#ifndef CURVEPOINT_H
#define CURVEPOINT_H

#include "filtercurve.h"
#include <QBrush>
#include <QGraphicsItem>
#include <QObject>

class CurvePoint : public QObject, public QGraphicsItem
{
    Q_OBJECT
    Q_INTERFACES(QGraphicsItem)
public:
    explicit CurvePoint(QBrush normalBrush, QBrush lightBrush, QObject *parent = nullptr);
    QRectF boundingRect() const override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
    enum { Type = UserType + 2 };
    int type() const override;

    void setResetPos(QPointF resetPoint);
    void reset();
protected:
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
    void wheelEvent(QGraphicsSceneWheelEvent *event) override;

signals:
    void pointPositionChanged(CurvePoint *point);
    void pointSlopeChanged(int delta);

private:
    QBrush normalBrush, lightBrush;
    QPointF sceneResetPoint = QPointF(0, 0);
    int wheelDeltaSum = 0;
};

#endif // CURVEPOINT_H


================================================
FILE: gui/eqhoverer.cpp
================================================
#include "collisionmanager.h"
#include "eqhoverer.h"
#include <QGraphicsScene>
#include <QDebug>
#include <QGraphicsSceneHoverEvent>
#include <QPainter>

static const unsigned int Default     = 1;
static const unsigned int Collision   = 1 << 1;
static const unsigned int Hover       = 1 << 2;
static const unsigned int ContextMenu = 1 << 3;

EqHoverer::EqHoverer(CollisionManager *mgr, FilterCurve *curve, CurvePoint *point, QObject *parent)
    : QObject(parent), curve(curve), point(point), pointState(0), mgr(mgr)
{
    Q_ASSERT(curve);
    Q_ASSERT(point);
    setAcceptHoverEvents(true);
    setFlag(QGraphicsItem::ItemHasNoContents, true);
    reset();
}


QRectF EqHoverer::boundingRect() const
{
    QRectF r = curve->boundingRect();
    if (scene()) {
        r.setY(scene()->sceneRect().y());
        r.setHeight(scene()->height());
        return r;
    } else
        return QRectF(QPointF(0, 0), QPointF(0, 0));
}

void EqHoverer::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
#if 0
    // debugging boundingRect()
    QPen wpen(Qt::white);
    wpen.setWidth(3);
    painter->save();
    painter->setPen(wpen);
    painter->drawRect(boundingRect());
    painter->restore();
#endif

}

int EqHoverer::type() const
{
    /* Make this type work with qgraphicsitem_cast */
    return Type;
}

void EqHoverer::collisionStateChanged()
{
    pointState &= ~Collision;
    for(QGraphicsItem *i : collidingItems()) {
        EqHoverer *cc = qgraphicsitem_cast<EqHoverer*>(i);
        if (cc && cc != this) {
            if (isUnderMouse() || cc->isUnderMouse()) {
                pointState |= Collision;
                break;
            }
        }
    }
    maybeShowPoint();
}

void EqHoverer::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
{
#if 0
    qDebug() << "hoverEnterEvent();";
#endif
    pointState |= Hover;
    mgr->notifyFriends();
}

void EqHoverer::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
#if 0
    qDebug() << "hoverLeaveEvent();";
#endif
    pointState &= ~Hover;
    mgr->notifyFriends();
}

void EqHoverer::contextMenuToggle(bool on) {
    int contextBit = (ContextMenu & static_cast<int>(on));
    pointState |= contextBit;
    pointState &= (~ContextMenu | contextBit);
    maybeShowPoint();
    mgr->notifyFriends();
}

void EqHoverer::reset()
{
    /* Order of these calls *does* matter here because
     * resetting the point signals resync. */
    point->reset();
    curve->reset();
    pointState |= Default;
    maybeShowPoint();
}

void EqHoverer::maybeShowPoint()
{
    if (pointState == 0) {
        point->hide();
        curve->setColorState(DefaultState);
    } else {
        point->show();
        curve->setColorState(PrettyState);
    }
}

void EqHoverer::resync(FilterCurve *curve)
{
    pointState &= ~Default;
    prepareGeometryChange();
    mgr->notifyFriends();
}


================================================
FILE: gui/eqhoverer.h
================================================
#ifndef EQHOVERER_H
#define EQHOVERER_H

#include "curvepoint.h"
#include "filtercurve.h"
#include <QGraphicsItem>
#include <QObject>

class CollisionManager;

class EqHoverer : public QObject, public QGraphicsItem
{
    Q_OBJECT
    Q_INTERFACES(QGraphicsItem)
public:
    explicit EqHoverer(CollisionManager *mgr, FilterCurve *curve, CurvePoint *point, QObject *parent = nullptr);
    QRectF boundingRect() const override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
    enum { Type  = UserType + 1 };
    int type() const override;

    void collisionStateChanged();
    void contextMenuToggle(bool on = false);
    void reset();
protected:
    void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
    void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
private:
    void maybeShowPoint();

public slots:
    void resync(FilterCurve *curve);

signals:

public:
    FilterCurve *curve;
    CurvePoint *point;

private:
    unsigned int pointState;
    CollisionManager *mgr;
};

#endif // EQHOVERER_H


================================================
FILE: gui/filtercurve.cpp
================================================
#include "filtercurve.h"
#include <QGraphicsScene>

FilterCurve::FilterCurve(QPen togglePen, QBrush toggleBrush, bool guiOnly) : filter(nullptr)
{
    colorState = 0;
    pens[0] = defaultPen;
    pens[1] = togglePen;
    brushes[0] = defaultBrush;
    brushes[1] = toggleBrush;

    if (!guiOnly) {
        /* initialize the filter */
        PrettyShim::getInstance().new_filter(&filter);
        PrettyShim::getInstance().set_peaking_eq(filter, 100, 100, 0);
    }
}

FilterCurve::~FilterCurve()
{

}

void FilterCurve::setColorState(ColorState state)
{
    colorState = state;
    this->update();
}

const QPen& FilterCurve::getActivePen() const
{
    return pens[colorState];
}

const QBrush& FilterCurve::getActiveBrush() const
{
    return brushes[colorState];
}


================================================
FILE: gui/filtercurve.h
================================================
#ifndef FILTERCURVE_H
#define FILTERCURVE_H

#include "prettyshim.h"

#include <QGraphicsItem>
#include <QObject>
#include <QPen>
#include <QBrush>

static QPen defaultPen = Qt::NoPen;
static QBrush defaultBrush = QBrush(QColor(73, 137, 196, 255));

typedef enum ColorState {
    DefaultState = 0,
    PrettyState,
} ColorState;

class FilterCurve : public QGraphicsItem
{
public:
    explicit FilterCurve(QPen togglePen, QBrush toggleBrush, bool guiOnly = false);
    virtual ~FilterCurve();
    virtual QPointF controlPoint() const = 0;
    virtual void reset() = 0;
    void setColorState(ColorState state);

signals:

protected:
    const QPen& getActivePen() const;
    const QBrush& getActiveBrush() const;

protected:
    ShimFilterPtr filter;

private:
    QPen pens[2];
    QBrush brushes[2];
    int colorState;
};

#endif // FILTERCURVE_H


================================================
FILE: gui/frequencytick.cpp
================================================
#include "frequencytick.h"
#include "macro.h"

#include <QDebug>
#include <QFont>
#include <QFontDatabase>
#include <QPainter>

FrequencyTick::FrequencyTick(QGraphicsScene *scene, int x, int y0, int y1, int frequency) :
    scene(scene), x(x), y0(y0), y1(y1), frequency(frequency) {
    QFont ff = QFontDatabase::systemFont(QFontDatabase::FixedFont);
    ff.setPixelSize(20);

    /* Setup line */
    line = new QGraphicsLineItem(x, y0, x, y1);
    line->setPen(QPen(Qt::white));
    line->setOpacity(0.3);

    /* Setup text */
    text = new QGraphicsTextItem(toQString());
    text->setDefaultTextColor(Qt::white);
    text->setFont(ff);
    auto fm = QFontMetrics(text->font());
    if (frequency == FMAX) {
        int offset = fm.horizontalAdvance(QLatin1Char('K')) * 4;
        text->setPos(x - offset, 30);
    } else if (frequency == FMIN) {
        text->setPos(x, 30);
    } else {
        int offset = fm.horizontalAdvance(QLatin1Char('K')) * toQString().length() + 7;
        text->setPos(x - offset / 2, 30);
    }
    text->setZValue(1000);

    scene->addItem(text);
    scene->addItem(line);
}

FrequencyTick::~FrequencyTick()
{
    delete line;
    delete text;
}

QString FrequencyTick::toQString()
{
    if (frequency >= 1000)
        return QString::number(frequency / 1000) + QString("K");
    else
        return QString::number(frequency);
}

qreal FrequencyTick::getFrequency() const
{
    return static_cast<qreal>(frequency);
}

qreal FrequencyTick::getX() const
{
    return x;
}


================================================
FILE: gui/frequencytick.h
================================================
#ifndef FREQUENCYTICK_H
#define FREQUENCYTICK_H

#include <QGraphicsScene>
#include <QGraphicsLineItem>

class FrequencyTick
{
public:
    FrequencyTick(QGraphicsScene *scene, int x, int y0, int y1, int frequency);
    ~FrequencyTick();
    QString toQString();
    qreal getFrequency() const;
    qreal getX() const;

private:
    QGraphicsScene *scene;
    int x, y0, y1;
    int frequency;
    QGraphicsLineItem *line;
    QGraphicsTextItem *text;
};

#endif // FREQUENCYTICK_H


================================================
FILE: gui/frequencytickbuilder.cpp
================================================
#include "frequencytick.h"
#include "frequencytickbuilder.h"
#include "macro.h"

#include <QGraphicsScene>

FrequencyTickBuilder::FrequencyTickBuilder(QGraphicsScene *scene, int width, int xmin, int xmax, int ymin, int ymax)
{
    /* y-axis frequency markers */
    int tickWidth = width / (NUM_TICKS - 1);
    tick[0] = new FrequencyTick(scene, xmin,                 ymin, ymax, F1);
    tick[1] = new FrequencyTick(scene, xmin + tickWidth * 1, ymin, ymax, F2);
    tick[2] = new FrequencyTick(scene, xmin + tickWidth * 2, ymin, ymax, F3);
    tick[3] = new FrequencyTick(scene, xmin + tickWidth * 3, ymin, ymax, F4);
    tick[4] = new FrequencyTick(scene, xmin + tickWidth * 4, ymin, ymax, F5);
    tick[5] = new FrequencyTick(scene, xmin + tickWidth * 5, ymin, ymax, F6);
    tick[6] = new FrequencyTick(scene, xmin + tickWidth * 6, ymin, ymax, F7);
    tick[7] = new FrequencyTick(scene, xmin + tickWidth * 7, ymin, ymax, F8);
    tick[8] = new FrequencyTick(scene, xmin + tickWidth * 8, ymin, ymax, F9);
    tick[9] = new FrequencyTick(scene, xmax,                 ymin, ymax, F10);
}

qreal FrequencyTickBuilder::lerpTick(qreal x)
{
    FrequencyTick *tp, *tq;
    for (int i = 1; i < NUM_TICKS; i++) {
        tp = tick[i-1];
        tq = tick[i];
        if (x >= tp->getX() && x <= tq->getX())
            break;
    }
    return LINEAR_REMAP(x, tp->getX(), tq->getX(), tp->getFrequency(), tq->getFrequency());
}

qreal FrequencyTickBuilder::unlerpTick(qreal f)
{
    FrequencyTick *tp, *tq;
    for (int i = 1; i < NUM_TICKS; i++) {
        tp = tick[i-1];
        tq = tick[i];
        if (f >= tp->getFrequency() && f <= tq->getFrequency())
            break;
    }
    return LINEAR_REMAP(f, tp->getFrequency(), tq->getFrequency(), tp->getX(), tq->getX());
}

FrequencyTickBuilder::~FrequencyTickBuilder()
{
    for (int i = 0; i < NUM_TICKS; i++)
        delete tick[i];
}


================================================
FILE: gui/frequencytickbuilder.h
================================================
#ifndef FREQUENCYTICKBUILDER_H
#define FREQUENCYTICKBUILDER_H

#include <QtCore>
#include <QGraphicsScene>
#define NUM_TICKS 10

class FrequencyTick;

class FrequencyTickBuilder
{
public:
    FrequencyTickBuilder(QGraphicsScene *scene, int width, int xmin, int xmax, int ymin, int ymax);
    ~FrequencyTickBuilder();
    qreal lerpTick(qreal x);
    qreal unlerpTick(qreal f);
private:
    FrequencyTick *tick[NUM_TICKS];
};

#endif // FREQUENCYTICKBUILDER_H


================================================
FILE: gui/gui.cpp
================================================
#include "collisionmanager.h"
#include "curvepoint.h"
#include "eqhoverer.h"
#include "frequencytick.h"
#include "frequencytickbuilder.h"
#include "gui.h"
#include "highshelfcurve.h"
#include "lowshelfcurve.h"
#include "macro.h"
#include "peakingcurve.h"
#include "prettygraphicsscene.h"
#include "prettyshim.h"
#include "spectrumanalyzer.h"
#include "ui_gui.h"

#include <QBrush>
#include <QComboBox>
#include <QDebug>
#include <QGraphicsEllipseItem>
#include <QGraphicsLineItem>
#include <QPainter>
#include <QPen>
#include <QRadialGradient>
#include <QResizeEvent>
#include <QScrollBar>
#include <QSystemTrayIcon>
#include <QtMath>

#define WIDTH 2000
#define HEIGHT 1000
#define XMIN -1000
#define YMIN -500
#define XMAX (XMIN + WIDTH)
#define YMAX (YMIN + HEIGHT)

static QPen GreenFilterPen = QPen(QColor(138, 237, 152), 3);
static QBrush GreenFilterBrush = QBrush(QColor(31, 204, 57, 127));
static QBrush GreenInnerRadiusBrush = QBrush(QColor(31, 204, 57));
static QBrush GreenOuterRadiusBrush = QBrush(QColor(31, 204, 57, 50));

static QPen RedFilterPen = QPen(QColor(231, 123, 131), 3);
static QBrush RedFilterBrush = QBrush(QColor(236, 85, 95, 127));
static QBrush RedInnerRadiusBrush = QBrush(QColor(223, 59, 70));
static QBrush RedOuterRadiusBRush = QBrush(QColor(233, 59, 70, 50));

static QPen YellowFilterPen = QPen(QColor(231, 229, 123), 3);
static QBrush YellowFilterBrush = QBrush(QColor(236, 225, 85, 127));
static QBrush YellowInnerRadiusBrush = QBrush(QColor(236, 225, 85));
static QBrush YellowOuterRadiusBrush = QBrush(QColor(236, 225, 85, 50));

static QPen PinkFilterPen = QPen(QColor(231, 123, 217), 3);
static QBrush PinkFilterBrush = QBrush(QColor(236, 85, 216, 127));
static QBrush PinkInnerRadiusBrush = QBrush(QColor(236, 85, 216));
static QBrush PinkOuterRadiusBrush = QBrush(QColor(236, 85, 216, 50));

static QPen BlueFilterPen = QPen(QColor(123, 231, 191), 3);
static QBrush BlueFilterBrush = QBrush(QColor(85, 236, 193, 127));
static QBrush BlueInnerRadiusBrush = QBrush(QColor(85, 236, 193));
static QBrush BlueOuterRadiusBrush = QBrush(QColor(85, 236, 193, 50));

static QPen OrangeFilterPen = QPen(QColor(231, 175, 123), 3);
static QBrush OrangeFilterBrush = QBrush(QColor(236, 151, 85, 127));
static QBrush OrangeInnerRadiusBrush = QBrush(QColor(236, 151, 85));
static QBrush OrangeOuterRadiusBrush = QBrush(QColor(236, 151, 85, 50));

static QPen PurpleFilterPen = QPen(QColor(177, 123, 231), 3);
static QBrush PurpleFilterBrush = QBrush(QColor(159, 85, 236, 127));
static QBrush PurpleInnerRadiusBrush = QBrush(QColor(159, 85, 236));
static QBrush PurpleOuterRadiusBrush = QBrush(QColor(159, 85, 236, 50));

Gui::Gui(QWidget *parent)
    : QDialog(parent)
    , ui(new Ui::Gui)
{

    PrettyShim::getInstance().init();
    PrettyShim::getInstance().setup_sink_io();
    ui->setupUi(this);

    QRadialGradient backgroundGradient(QPoint(0, 0), WIDTH);
    backgroundGradient.setSpread(QGradient::ReflectSpread);
    backgroundGradient.setColorAt(0.15, QColor(4, 38, 69));
    backgroundGradient.setColorAt(0.85, QColor(6, 17, 43));
    scene = new PrettyGraphicsScene(ui->graphicsView);
    scene->setSceneRect(XMIN, YMIN, WIDTH, HEIGHT);
    scene->setBackgroundBrush(QBrush(backgroundGradient));

    ui->graphicsView->setRenderHint(QPainter::Antialiasing);
    ui->graphicsView->setRenderHint(QPainter::TextAntialiasing);
    ui->graphicsView->setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
    ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    ui->graphicsView->setResizeAnchor(QGraphicsView::AnchorViewCenter);
    ui->graphicsView->setScene(scene);

    /* x-axis (boost/cut) */
    auto xaxis = new QGraphicsLineItem(XMIN, scene->sceneRect().center().y(), XMAX, scene->sceneRect().center().y());
    xaxis->setPen(QPen(Qt::white));
    xaxis->setOpacity(0.3);
    scene->addItem(xaxis);

    /* x-axis frequency markers */
    xTickBuilder = new FrequencyTickBuilder(scene, WIDTH, XMIN, XMAX, YMIN, YMAX);
    addSpectrumAnalyzer();

    collisionMgr = new CollisionManager();

    addLowShelf (GreenFilterPen,  GreenFilterBrush,  GreenInnerRadiusBrush,  GreenOuterRadiusBrush);
    addHighShelf(OrangeFilterPen, OrangeFilterBrush, OrangeInnerRadiusBrush, OrangeOuterRadiusBrush);

    addPeakingEq(150,  RedFilterPen, 	RedFilterBrush, 	RedInnerRadiusBrush, 	RedOuterRadiusBRush);
    addPeakingEq(350,  YellowFilterPen, YellowFilterBrush, 	YellowInnerRadiusBrush, YellowOuterRadiusBrush);
    addPeakingEq(750,  PinkFilterPen, 	PinkFilterBrush, 	PinkInnerRadiusBrush, 	PinkOuterRadiusBrush);
    addPeakingEq(1500, BlueFilterPen, 	BlueFilterBrush, 	BlueInnerRadiusBrush, 	BlueOuterRadiusBrush);
    addPeakingEq(3500, PurpleFilterPen, PurpleFilterBrush, 	PurpleInnerRadiusBrush, PurpleOuterRadiusBrush);

    connectBypassButton();
    maybeShowInSystemTray();
}

//=============================================================================

void Gui::addFilterItem(QGraphicsItemGroup *group, FilterCurve *curve, CurvePoint *point, EqHoverer *hover)
{
    Q_ASSERT(itemCount < NUM_FILTERS);
    items[itemCount].group = group;
    items[itemCount].curve = curve;
    items[itemCount].point = point;
    items[itemCount].hover = hover;
    itemCount++;
}

void Gui::addPeakingEq(int frequency, QPen curvePen, QBrush filterBrush, QBrush innerRadiusBrush, QBrush outerRadiusBrush) {
    Q_ASSERT(collisionMgr);
    PeakingCurve *curve = new PeakingCurve(curvePen, filterBrush);
    CurvePoint *point = new CurvePoint(innerRadiusBrush, outerRadiusBrush);
    EqHoverer *hover = new EqHoverer(collisionMgr, curve, point);
    collisionMgr->addEqHoverer(hover);

    /* point signals */
    QObject::connect(point,
                  SIGNAL(pointPositionChanged(CurvePoint*)),
                  curve,
                  SLOT(pointPositionChanged(CurvePoint*)));
    QObject::connect(point,
                  SIGNAL(pointSlopeChanged(int)),
                  curve,
                  SLOT(pointSlopeChanged(int)));

    /* curve signals */
    QObject::connect(curve,
                  SIGNAL(resync(FilterCurve*)),
                  hover,
                  SLOT(resync(FilterCurve*)));
    QObject::connect(curve,
                     SIGNAL(filterParamsChanged(ShimFilterPtr, PeakingCurve*)),
                     this,
                     SLOT(peakingFilterParamsChanged(ShimFilterPtr, PeakingCurve*)));

    QGraphicsItemGroup *group = new QGraphicsItemGroup();
    group->addToGroup(curve);
    group->addToGroup(hover);
    group->setHandlesChildEvents(false);
    group->setPos(xTickBuilder->unlerpTick(frequency) - group->boundingRect().width() / 2, 0);
    point->setResetPos(curve->controlPoint());
    scene->addItem(group);
    scene->addItem(point);
    addFilterItem(group, curve, point, hover);
}

void Gui::addLowShelf(QPen curvePen, QBrush filterBrush, QBrush innerRadiusBrush, QBrush outerRadiusBrush)
{
    Q_ASSERT(collisionMgr);
    ShelfCurve *curve = new LowShelfCurve(curvePen, filterBrush);
    CurvePoint *point = new CurvePoint(innerRadiusBrush, outerRadiusBrush);
    EqHoverer *hover = new EqHoverer(collisionMgr, curve, point);
    collisionMgr->addEqHoverer(hover);

    /* point signals */
    QObject::connect(point,
                     SIGNAL(pointPositionChanged(CurvePoint*)),
                     curve,
                     SLOT(pointPositionChanged(CurvePoint*)));
    QObject::connect(point,
                     SIGNAL(pointSlopeChanged(int)),
                     curve,
                     SLOT(pointSlopeChanged(int)));

    /* curve signals */
    QObject::connect(curve,
                  SIGNAL(resync(FilterCurve*)),
                  hover,
                  SLOT(resync(FilterCurve*)));
    QObject::connect(curve,
                     SIGNAL(filterParamsChanged(ShimFilterPtr, ShelfCurve*)),
                     this,
                     SLOT(lowshelfFilterParamsChanged(ShimFilterPtr, ShelfCurve*)));

    QGraphicsItemGroup *group = new QGraphicsItemGroup();
    group->setHandlesChildEvents(false);
    group->addToGroup(curve);
    group->addToGroup(hover);
    group->setPos(XMIN, 0);
    point->setResetPos(curve->controlPoint());
    scene->addItem(group);
    scene->addItem(point);
    addFilterItem(group, curve, point, hover);
}

void Gui::addHighShelf(QPen curvePen, QBrush filterBrush, QBrush innerRadiusBrush, QBrush outerRadiusBrush)
{
    Q_ASSERT(collisionMgr);
    ShelfCurve *curve = new HighShelfCurve(curvePen, filterBrush);
    CurvePoint *point = new CurvePoint(innerRadiusBrush, outerRadiusBrush);
    EqHoverer *hover = new EqHoverer(collisionMgr, curve, point);
    collisionMgr->addEqHoverer(hover);

    /* point signals */
    QObject::connect(point,
                     SIGNAL(pointPositionChanged(CurvePoint*)),
                     curve,
                     SLOT(pointPositionChanged(CurvePoint*)));
    QObject::connect(point,
                     SIGNAL(pointSlopeChanged(int)),
                     curve,
                     SLOT(pointSlopeChanged(int)));

    /* curve signals */
    QObject::connect(curve,
                  SIGNAL(resync(FilterCurve*)),
                  hover,
                  SLOT(resync(FilterCurve*)));
    QObject::connect(curve,
                     SIGNAL(filterParamsChanged(ShimFilterPtr, ShelfCurve*)),
                     this,
                     SLOT(highshelfFilterParamsChanged(ShimFilterPtr, ShelfCurve*)));

    QGraphicsItemGroup *group = new QGraphicsItemGroup();
    group->setHandlesChildEvents(false);
    group->addToGroup(curve);
    group->addToGroup(hover);
    group->setPos(XMAX, 0);
    point->setResetPos(curve->controlPoint());
    scene->addItem(group);
    scene->addItem(point);
    addFilterItem(group, curve, point, hover);
}

void Gui::peakingFilterParamsChanged(ShimFilterPtr filter, PeakingCurve *curve)
{
    QPointF c = curve->controlPoint();
    QRectF r = curve->sceneBoundingRect();
    qreal f0 = xTickBuilder->lerpTick(c.x());
    qreal bw = xTickBuilder->lerpTick(r.bottomRight().x()) / xTickBuilder->lerpTick(r.bottomLeft().x()) / 2;
    qreal db_gain = LINEAR_REMAP(
                c.y(),
                scene->sceneRect().y(), scene->sceneRect().y() + scene->sceneRect().height(),
                DB_GAIN_MAX, -DB_GAIN_MAX);
    PrettyShim::getInstance().set_peaking_eq(filter, f0, bw, db_gain);
}

void Gui::lowshelfFilterParamsChanged(ShimFilterPtr filter, ShelfCurve *curve)
{
    QPointF c = curve->controlPoint();
    qreal f0 = xTickBuilder->lerpTick(c.x());
    qreal S =  curve->slope();
    qreal db_gain = LINEAR_REMAP(
                c.y(),
                scene->sceneRect().y(), scene->sceneRect().y() + scene->sceneRect().height(),
                DB_GAIN_MAX, -DB_GAIN_MAX);
    PrettyShim::getInstance().set_low_shelf(filter, f0, S, db_gain);
}

void Gui::highshelfFilterParamsChanged(ShimFilterPtr filter, ShelfCurve *curve)
{

    QPointF c = curve->controlPoint();
    qreal f0 = xTickBuilder->lerpTick(c.x());
    qreal S =  curve->slope();
    qreal db_gain = LINEAR_REMAP(
                c.y(),
                scene->sceneRect().y(), scene->sceneRect().y() + scene->sceneRect().height(),
                DB_GAIN_MAX, -DB_GAIN_MAX);
    PrettyShim::getInstance().set_high_shelf(filter, f0, S, db_gain);
}

void Gui::connectBypassButton()
{
    QObject::connect(ui->pushButton, &QAbstractButton::clicked, this, [&](bool checked){
        PrettyShim::getInstance().enable_bypass(checked);
    });
}

void Gui::addSpectrumAnalyzer()
{
    Q_ASSERT(xTickBuilder);
    spectrumAnalyzer = new SpectrumAnalyzer(xTickBuilder);
    spectrumAnalyzer->setPos(-scene->sceneRect().width() / 2, -scene->sceneRect().height() / 8);
    scene->addItem(spectrumAnalyzer);
    spectrumUpdateTimer = new QTimer(this);
    spectrumUpdateTimer->setInterval(1000 / 60);
    QObject::connect(spectrumUpdateTimer, &QTimer::timeout, [&]() {
        spectrumAnalyzer->updateFrameDelta();
        spectrumAnalyzer->update();
    });
    spectrumUpdateTimer->start();
}

//=============================================================================

void Gui::maybeShowInSystemTray()
{
    if (!QSystemTrayIcon::isSystemTrayAvailable())
        return;

    trayMenu = new QMenu();
    quitAct = new QAction("Quit");
    trayMenu->addAction(quitAct);
    QObject::connect(quitAct, &QAction::triggered, qApp, QCoreApplication::quit);

    trayIcon = new QSystemTrayIcon(this);
    trayIcon->setContextMenu(trayMenu);
    trayIcon->setIcon(QIcon(":/images/images/prettyeq.png"));
    trayIcon->setContextMenu(trayMenu);
    QObject::connect(trayIcon,
                     SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
                     this,
                     SLOT(trayActivated(QSystemTrayIcon::ActivationReason)));

    trayIcon->show();
}

//=============================================================================

void Gui::resizeEvent(QResizeEvent *event) {
    QDialog::resizeEvent(event);
    qDebug() << this->size();
    if (isVisible())
        ui->graphicsView->fitInView(scene->sceneRect(), Qt::KeepAspectRatio);
    //ui->graphicsView->centerOn(0, 0);
}

void Gui::showEvent(QShowEvent *event)
{
    ui->graphicsView->fitInView(scene->sceneRect(), Qt::KeepAspectRatio);
}

void Gui::on_actionQuit_triggered()
{
    QCoreApplication::exit();
}

void Gui::trayActivated(QSystemTrayIcon::ActivationReason reason)
{
    this->show();
    this->raise();
    this->activateWindow();
}

//=============================================================================

Gui::~Gui()
{
    delete collisionMgr;
    for (int i = 0; i < NUM_FILTERS; i++) {
        delete items[i].curve;
        delete items[i].hover;
        delete items[i].point;
        delete items[i].group;
    }

    if (QSystemTrayIcon::isSystemTrayAvailable()) {
        delete trayIcon;
        delete trayMenu;
        delete quitAct;
    }
    spectrumUpdateTimer->stop();
    delete spectrumUpdateTimer;
    delete spectrumAnalyzer;
    delete ui;
}

void Gui::cleanup()
{
    PrettyShim::getInstance().exit();
}


================================================
FILE: gui/gui.h
================================================
#ifndef GUI_H
#define GUI_H

#include "prettyshim.h"
#include <QDialog>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QMenu>
#include <QMetaType>
#include <QSystemTrayIcon>

#define DB_GAIN_MAX 12
#define NUM_FILTERS 7

QT_BEGIN_NAMESPACE
namespace Ui { class Gui; }
QT_END_NAMESPACE

class CollisionManager;
class CurvePoint;
class EqHoverer;
class FilterCurve;
class FrequencyTick;
class FrequencyTickBuilder;
class PeakingCurve;
class ShelfCurve;
class SpectrumAnalyzer;

typedef struct FilterItem {
    QGraphicsItemGroup *group;
    FilterCurve *curve;
    CurvePoint *point;
    EqHoverer *hover;
} FilterItem;

class Gui : public QDialog
{
    Q_OBJECT

public:
    Gui(QWidget *parent = nullptr);
    ~Gui();

/* Equalizer slots */
public slots:
    void cleanup();
    void peakingFilterParamsChanged(ShimFilterPtr filter, PeakingCurve *curve);
    void lowshelfFilterParamsChanged(ShimFilterPtr filter, ShelfCurve *curve);
    void highshelfFilterParamsChanged(ShimFilterPtr filter, ShelfCurve *curve);

/* Menu Bar slots */
private slots:
    void on_actionQuit_triggered();

/* System Tray slots */
private slots:
    void trayActivated(QSystemTrayIcon::ActivationReason reason);

protected:
    void resizeEvent(QResizeEvent *event) override;
    void showEvent(QShowEvent *event) override;


private:
    void addFilterItem(QGraphicsItemGroup *group, FilterCurve *curve, CurvePoint *point, EqHoverer *hover);
    qreal lerpTick(qreal x);
    qreal unlerpTick(qreal f);
    void addLowShelf(QPen curvePen, QBrush filterBrush, QBrush innerRadiusBrush, QBrush outerRadiusBrush);
    void addHighShelf(QPen curvePen, QBrush filterBrush, QBrush innerRadiusBrush, QBrush outerRadiusBrush);
    void addPeakingEq(int frequency, QPen curvePen, QBrush filterBrush, QBrush innerRadiusBrush, QBrush outerRadiusBrush);
    void connectBypassButton();
    void addSpectrumAnalyzer();

    void maybeShowInSystemTray();

private:
    Ui::Gui *ui;
    QGraphicsScene *scene;
    CollisionManager *collisionMgr = nullptr;;
    FilterItem items[NUM_FILTERS];
    SpectrumAnalyzer *spectrumAnalyzer = nullptr;
    FrequencyTickBuilder *xTickBuilder = nullptr;
    QTimer *spectrumUpdateTimer = nullptr;
    int itemCount = 0;

    /* System tray stuff. */
    QMenu *trayMenu;
    QAction *quitAct;
    QSystemTrayIcon *trayIcon;
};
#endif // GUI_H


================================================
FILE: gui/gui.pro
================================================
QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    collisionmanager.cpp \
    curvepoint.cpp \
    eqhoverer.cpp \
    filtercurve.cpp \
    frequencytick.cpp \
    frequencytickbuilder.cpp \
    gui.cpp \
    highshelfcurve.cpp \
    lowshelfcurve.cpp \
    main.cpp \
    peakingcurve.cpp \
    prettygraphicsscene.cpp \
    runguard.cpp \
    shelfcurve.cpp \
    spectrumanalyzer.cpp \
    unixsignalhandler.cpp

HEADERS += \
    collisionmanager.h \
    curvepoint.h \
    eqhoverer.h \
    filtercurve.h \
    frequencytick.h \
    frequencytickbuilder.h \
    gui.h \
    highshelfcurve.h \
    lowshelfcurve.h \
    macro.h \
    peakingcurve.h \
    prettygraphicsscene.h \
    prettyshim.h \
    ringbuffer.h \
    runguard.h \
    shelfcurve.h \
    spectrumanalyzer.h \
    unixsignalhandler.h

FORMS += \
    gui.ui

QMAKE_CXXFLAGS_WARN_ON += -Wno-unused-parameter
INCLUDEPATH += '../equalizer'
unix:LIBS += -L ../equalizer -lequalizer -lm -lpulse -lpthread -ffast-math -fopenmp
TARGET = ../prettyeq

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

RESOURCES += \
    resources.qrc


================================================
FILE: gui/gui.ui
================================================
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Gui</class>
 <widget class="QDialog" name="Gui">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>1152</width>
    <height>648</height>
   </rect>
  </property>
  <property name="minimumSize">
   <size>
    <width>1152</width>
    <height>648</height>
   </size>
  </property>
  <property name="maximumSize">
   <size>
    <width>1152</width>
    <height>648</height>
   </size>
  </property>
  <property name="windowTitle">
   <string>Gui</string>
  </property>
  <property name="autoFillBackground">
   <bool>false</bool>
  </property>
  <property name="styleSheet">
   <string notr="true">QWidget {
  background: #25282b;
}

QPushButton {
  color: white
}</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout_3">
   <item>
    <widget class="QMenuBar" name="menubar">
     <property name="enabled">
      <bool>true</bool>
     </property>
     <widget class="QMenu" name="menuOptions">
      <property name="title">
       <string>Options</string>
      </property>
      <addaction name="actionQuit"/>
     </widget>
     <addaction name="menuOptions"/>
    </widget>
   </item>
   <item alignment="Qt::AlignRight">
    <widget class="QPushButton" name="pushButton">
     <property name="minimumSize">
      <size>
       <width>115</width>
       <height>0</height>
      </size>
     </property>
     <property name="maximumSize">
      <size>
       <width>100</width>
       <height>16777215</height>
      </size>
     </property>
     <property name="font">
      <font>
       <family>Source Code Pro Semibold</family>
       <weight>75</weight>
       <bold>true</bold>
      </font>
     </property>
     <property name="layoutDirection">
      <enum>Qt::LeftToRight</enum>
     </property>
     <property name="autoFillBackground">
      <bool>false</bool>
     </property>
     <property name="text">
      <string>BYPASS</string>
     </property>
     <property name="checkable">
      <bool>true</bool>
     </property>
     <property name="checked">
      <bool>false</bool>
     </property>
     <property name="flat">
      <bool>true</bool>
     </property>
    </widget>
   </item>
   <item>
    <widget class="QGraphicsView" name="graphicsView">
     <property name="acceptDrops">
      <bool>false</bool>
     </property>
    </widget>
   </item>
  </layout>
  <action name="actionQuit">
   <property name="text">
    <string>Quit</string>
   </property>
  </action>
 </widget>
 <resources/>
 <connections/>
</ui>


================================================
FILE: gui/highshelfcurve.cpp
================================================
#include "highshelfcurve.h"

HighShelfCurve::HighShelfCurve(QPen pen, QBrush brush, bool guiOnly, QObject *parent)
    : ShelfCurve(pen, brush, guiOnly, parent)
{
    reset();
    updateCurveGeometry();
}

HighShelfCurve::~HighShelfCurve()
{

}

QPointF HighShelfCurve::clampP2() const
{
    return (p2.x() > 0) ? QPointF(0, p2.y()) : p2;
}

void HighShelfCurve::pointSlopeChanged(int delta)
{
    bool reduce = delta > 0;
    int offset = -delta * SLOPE_DELTA;
    if ((qAbs(p3.x() - p2.x()) - offset < SLOPE_MAX  || !reduce) && (p2.x() - offset > p3.x() || reduce)) {
        p2.setX(p2.x() - offset);
        updateCurveGeometry();
        this->update();
        emit resync(this);
        emit filterParamsChanged(filter, this);
    }
}

void HighShelfCurve::reset()
{
    p0 = QPointF(0, 0);
    p3 = QPointF(-330, 0);
    p1 = QPointF((p3.x() - p0.x()) * 0.3, p0.y());
    p2 = QPointF((p1.x() + p3.x())/ 2, p3.y());
    updateCurveGeometry();
    this->update();
}


================================================
FILE: gui/highshelfcurve.h
================================================
#ifndef HIGHSHELFCURVE_H
#define HIGHSHELFCURVE_H

#include "shelfcurve.h"

class HighShelfCurve : public ShelfCurve
{
public:
    explicit HighShelfCurve(QPen pen, QBrush brush, bool guiOnly = false, QObject *parent = nullptr);
    ~HighShelfCurve();

// ShelfCurve interface
protected:
    QPointF clampP2() const override;

    // ShelfCurve interface
public slots:
    void pointSlopeChanged(int delta) override;

    // FilterCurve interface
public:
    void reset() override;
};

#endif // HIGHSHELFCURVE_H


================================================
FILE: gui/lowshelfcurve.cpp
================================================
#include "lowshelfcurve.h"

LowShelfCurve::LowShelfCurve(QPen pen, QBrush brush, bool guiOnly, QObject *parent)
    : ShelfCurve(pen, brush, guiOnly, parent)
{
    reset();
    updateCurveGeometry();
}

LowShelfCurve::~LowShelfCurve()
{

}

QPointF LowShelfCurve::clampP2() const
{
    return p2.x() < 0 ? QPointF(0, p2.y()) : p2;
}

void LowShelfCurve::pointSlopeChanged(int delta)
{
    bool reduce = delta > 0;
    int offset = delta * SLOPE_DELTA;
    if ((p3.x() - p2.x() - offset < SLOPE_MAX  || !reduce) && (p2.x() - offset <= p3.x() || reduce)) {
        p2.setX(p2.x() - offset);
        updateCurveGeometry();
        this->update();
        emit resync(this);
        emit filterParamsChanged(filter, this);
    }
}

void LowShelfCurve::reset()
{
    p0 = QPointF(0, 0);
    p3 = QPointF(330, 0);
    p1 = QPointF((p3.x() - p0.x()) * 0.3, p0.y());
    p2 = QPointF((p1.x() + p3.x())/ 2, p3.y());
    updateCurveGeometry();
    this->update();
}


================================================
FILE: gui/lowshelfcurve.h
================================================
#ifndef LOWSHELFCURVE_H
#define LOWSHELFCURVE_H

#include "shelfcurve.h"
#include <QBrush>
#include <QPen>

class LowShelfCurve : public ShelfCurve
{
public:
    explicit LowShelfCurve(QPen pen, QBrush brush, bool guiOnly = false, QObject *parent = nullptr);
    ~LowShelfCurve();

protected:
    QPointF clampP2() const override;

public slots:
    void pointSlopeChanged(int delta) override;

    // FilterCurve interface
public:
    void reset() override;
};

#endif // LOWSHELFCURVE_H


================================================
FILE: gui/macro.h
================================================
#ifndef MACRO_H
#define MACRO_H

#include <QPointF>

#define F1   20
#define F2   50
#define F3   100
#define F4   200
#define F5   500
#define F6   1000
#define F7   2000
#define F8   5000
#define F9   10000
#define F10  20000
#define FMIN F1
#define FMAX F10

#define UNLERP( v, min, max ) ( ( (v) - (min) ) / ( (max) - (min) ) )

#define LERP( n, min, max ) ( (min) + (n) * ( (max) - (min) ) )

#define LINEAR_REMAP( i, imin, imax, omin, omax ) ( LERP( UNLERP( i, imin, imax ), omin, omax ) )

static inline QPointF cubic_bezier(qreal t, QPointF p0, QPointF p1, QPointF p2, QPointF p3) {
    return (1-t)*(1-t)*(1-t)*p0 +
            3*(1-t)*(1-t)*t*p1 +
            3*(1-t)*t*t*p2 +
            t*t*t*p3;
}

#define CUBIC_BEZIER cubic_bezier

#endif // MACRO_H


================================================
FILE: gui/main.cpp
================================================
#include "gui.h"
#include "runguard.h"
#include "unixsignalhandler.h"
#include <QApplication>
#include <QList>
#include <QObject>
#include <error.h>
#include <signal.h>

static void setupUnixSignalHandlers(QList<int> exitSignals) {
    struct sigaction sa;
    sigset_t blocking_mask;
    int r;

    sigemptyset(&blocking_mask);
    for (int sig : exitSignals) {
        r = sigaddset(&blocking_mask, sig);
        if (r < 0)
            qFatal("could not sigaddset(): %s", strerror(r));
    }

    sa.sa_handler = UnixSignalHandler::getInstance().exitHandler;
    sa.sa_mask = blocking_mask;
    sa.sa_flags = 0;
    sa.sa_flags |= SA_RESTART;

    for (int sig : exitSignals) {
        r = sigaction(sig, &sa, nullptr);
        if (r < 0)
            qFatal("could not sigaction(): %s", strerror(r));
    }
}

int main(int argc, char *argv[])
{
    RunGuard guard;
    if (guard.isRunning()) {
        qWarning() << "prettyeq is already running. Quitting!";
        return 0;
    }
    QApplication a(argc, argv);
    setupUnixSignalHandlers(QList<int>{SIGINT, SIGTERM, SIGQUIT, SIGABRT, SIGHUP});
    Gui w;
    QObject::connect(&a, SIGNAL(aboutToQuit()), &w, SLOT(cleanup()));
    w.show();
    return a.exec();
}


================================================
FILE: gui/peakingcurve.cpp
================================================
#include "peakingcurve.h"
#include <QtGlobal>
#include <QDebug>
#include <QGraphicsScene>
#include <QPainter>
#include <QTransform>
#define MIN_WIDTH 40
#define MAX_WIDTH 500
#define SLOPE 10
#define SPLINE_CAPACITY 16

PeakingCurve::PeakingCurve(QPen pen, QBrush brush, bool guiOnly, QObject *parent) : QObject(parent), FilterCurve(pen, brush, guiOnly)
{
    setZValue(100000);

    lineSpline = std::unique_ptr<QPainterPath>(new QPainterPath());
    fillSpline = std::unique_ptr<QPainterPath>(new QPainterPath());

#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
    lineSpline->reserve(SPLINE_CAPACITY);
    fillSpline->reserve(SPLINE_CAPACITY);
#endif
    reset();
    updateSplineGeometry();
}

QRectF PeakingCurve::boundingRect() const
{
    return fillSpline->boundingRect();
}

void PeakingCurve::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    painter->setRenderHint(QPainter::Antialiasing, true);
    painter->setPen(getActivePen());
    painter->drawPath(*lineSpline);
    painter->fillPath(*fillSpline, getActiveBrush());

#if 0
    // debugging - draw control points
    QPen rpen(Qt::red);
    rpen.setWidth(10);
    painter->save();
    painter->setPen(rpen);
    painter->drawPoint(c1);
    painter->drawPoint(c2);
    painter->restore();
#endif

#if 0
    // debugging - draw boundingRect()
    QPen rpen(Qt::red);
    rpen.setWidth(2);
    painter->save();
    painter->setPen(rpen);
    painter->drawRect(boundingRect());
    painter->restore();
#endif
}

QPointF PeakingCurve::controlPoint() const
{
    return mapToScene(ip);
}

void PeakingCurve::reset()
{
    p0 = QPointF(0, 0);
    ip = QPointF(100, 0);
    p1 = QPointF(200, 0);
    c1 = QPointF((p0.x() + ip.x()) / 2, ip.y());
    c2 = QPointF((ip.x() + p1.x()) / 2, ip.y());
    updateSplineGeometry();
    prepareGeometryChange();
    this->update();
}

void PeakingCurve::updateSplineGeometry()
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
    lineSpline->clear();
#else
    lineSpline.reset(new QPainterPath());
#endif
    lineSpline->moveTo(p0);
    lineSpline->quadTo(c1, ip);
    lineSpline->moveTo(ip);
    lineSpline->quadTo(c2, p1);
    Q_ASSERT(lineSpline->elementCount() == 8);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
    Q_ASSERT(lineSpline->capacity() == SPLINE_CAPACITY);
#endif

#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
    fillSpline->clear();
#else
    fillSpline.reset(new QPainterPath());
#endif
    fillSpline->moveTo(p0);
    fillSpline->quadTo(c1, ip);
    fillSpline->moveTo(ip);
    fillSpline->quadTo(c2, p1);
    fillSpline->lineTo(p0);
    Q_ASSERT(fillSpline->elementCount() == 9);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
    Q_ASSERT(fillSpline->capacity() == SPLINE_CAPACITY);
#endif
}

void PeakingCurve::pointPositionChanged(CurvePoint *point)
{
    QPointF curvePoint = mapFromScene(point->pos());
    QPointF delta = curvePoint - ip;
    ip = curvePoint;
    p0.setX(p0.x() + delta.x());
    p1.setX(p1.x() + delta.x());
    c1 = QPointF((p0.x() + ip.x()) / 2, ip.y());
    c2 = QPointF((ip.x() + p1.x()) / 2, ip.y());
    updateSplineGeometry();
    prepareGeometryChange();
    this->update();
    emit resync(this);
    emit filterParamsChanged(filter, this);
}

void PeakingCurve::pointSlopeChanged(int delta)
{
    qreal p0x = p0.x() + SLOPE*delta;
    qreal p1x = p1.x() - SLOPE*delta;
    if (p1x - p0x >= MIN_WIDTH && p1x - p0x <= MAX_WIDTH) {
        p0.setX(p0x);
        p1.setX(p1x);
        c1 = QPointF((p0.x() + ip.x()) / 2, ip.y());
        c2 = QPointF((ip.x() + p1.x()) / 2, ip.y());
        updateSplineGeometry();
        prepareGeometryChange();
        this->update();
        emit resync(this);
        emit filterParamsChanged(filter, this);
    }

}


================================================
FILE: gui/peakingcurve.h
================================================
#ifndef PEAKINGCURVE_H
#define PEAKINGCURVE_H

#include "curvepoint.h"
#include "filtercurve.h"

#include <QBrush>
#include <QGraphicsItem>
#include <QObject>
#include <QPen>

#include <memory>

typedef enum SplinePart {
    SplineLeft,
    SplineRight,
} SplinePart;

class PeakingCurve : public QObject, public FilterCurve
{
    Q_OBJECT
    Q_INTERFACES(QGraphicsItem)
public:
    explicit PeakingCurve(QPen pen, QBrush brush, bool guiOnly = false, QObject *parent = nullptr);
    QRectF boundingRect() const override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;

    QPointF controlPoint() const override;

    // FilterCurve interface
public:
    void reset() override;

private:
    void updateSplineGeometry();

public slots:
    void pointPositionChanged(CurvePoint *point);
    void pointSlopeChanged(int delta);
signals:
    void resync(FilterCurve *curve);
    void filterParamsChanged(ShimFilterPtr filter, PeakingCurve *curve);

private:
    QPointF p0, p1, c1, c2, ip;
    std::unique_ptr<QPainterPath> lineSpline, fillSpline;
};

#endif // PEAKINGCURVE_H


================================================
FILE: gui/prettygraphicsscene.cpp
================================================
#include "curvepoint.h"
#include "eqhoverer.h"
#include "prettygraphicsscene.h"
#include <QDebug>
#include <QGraphicsItem>
#include <QGraphicsSceneContextMenuEvent>
#include <QMenu>

PrettyGraphicsScene::PrettyGraphicsScene(QObject *parent) : QGraphicsScene(parent) {}

void PrettyGraphicsScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
    CurvePoint *point = nullptr;
    for (auto item : items()) {
        point = qgraphicsitem_cast<CurvePoint*>(item);
        if ( !point)
            continue;

        if (point->sceneBoundingRect().contains(event->scenePos()))
            break;
    }

    if (point) {
        EqHoverer *hover = nullptr;
        for (auto item : items()) {
            hover = qgraphicsitem_cast<EqHoverer*>(item);
            if (! hover)
                continue;

            if (point == hover->point) {
                hover->contextMenuToggle(true);
                break;
            }
        }

        QMenu menu(event->widget());
        menu.addAction("Reset");
        if (menu.exec(event->screenPos())) {
            Q_ASSERT(hover);
            hover->reset();
        }

        for (auto item : items()) {
            EqHoverer *hover = qgraphicsitem_cast<EqHoverer*>(item);
            if (! hover)
                continue;

            if (point == hover->point) {
                hover->contextMenuToggle(false);
                break;
            }
        }
    }
}


================================================
FILE: gui/prettygraphicsscene.h
================================================
#ifndef PRETTYGRAPHICSSCENE_H
#define PRETTYGRAPHICSSCENE_H

#include <QGraphicsScene>

class PrettyGraphicsScene : public QGraphicsScene
{
public:
    PrettyGraphicsScene(QObject *parent = nullptr);

protected:
    void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
};

#endif // PRETTYGRAPHICSSCENE_H


================================================
FILE: gui/prettyshim.h
================================================
#ifndef PRETTYSHIM_H
#define PRETTYSHIM_H

#include <QObject>
#include <QString>
#include <QDebug>
#include <complex>

#include "pretty.h"

typedef PrettyFilter* ShimFilterPtr;

/*  The shim is for making C callbacks idiomatic with Qt signals/slots  */

class PrettyShim : public QObject
{
    Q_OBJECT

public:
    static PrettyShim& getInstance() {
        static PrettyShim instance;
        return instance;
    }

private:
    explicit PrettyShim(QObject *parent = nullptr) {}
    static PrettyShim *instance;

public:
    PrettyShim(PrettyShim const&) 	  = delete;
    void operator=(PrettyShim const&) = delete;

    void init() {
        qRegisterMetaType<uint32_t>("uint32_t");
        int r = pretty_init();
        Q_ASSERT(r == 0);
    }

    void setup_sink_io()  {
        pretty_setup_sink_io();
    }

    void exit() {
        pretty_exit();
    }

    void new_filter(ShimFilterPtr *filter) {
        int r = pretty_new_filter(filter);
        Q_ASSERT(*filter && r >= 0);
    }

    void set_peaking_eq(ShimFilterPtr filter, float f0, float bandwidth, float db_gain) {
        pretty_set_peaking_eq(filter, f0, bandwidth, db_gain);
    }

    void set_low_shelf(PrettyFilter *filter, float f0, float S, float db_gain) {
        pretty_set_low_shelf(filter, f0, S, db_gain);
    }

    void set_high_shelf(PrettyFilter *filter, float f0, float S, float db_gain) {
        pretty_set_high_shelf(filter, f0, S, db_gain);
    }

    void enable_bypass(bool should_bypass) {
        pretty_enable_bypass(should_bypass);
    }

    std::complex<float>* get_audio_data(unsigned int *N) {
        std::complex<float> *data;
        pretty_acquire_audio_data(&data, N);
        return std::move(data);
    }

    void release_audio_data() {
        pretty_release_audio_data();
    }

private:
    AudioFFT *audio_fft;

};

#endif // PRETTYSHIM_H


================================================
FILE: gui/resources.qrc
================================================
<RCC>
    <qresource prefix="/images">
        <file>images/prettyeq.png</file>
    </qresource>
</RCC>


================================================
FILE: gui/ringbuffer.h
================================================
#ifndef RINGBUFFER_H
#define RINGBUFFER_H

#include <QtCore>

#include <stdlib.h>

template <class T, int size>
class RingBuffer
{
public:
    RingBuffer() : pos(0) {};

    const T& at(int i) {
        Q_ASSERT(i < size);
        return ring_buffer.at(i);
    }

    const std::array<T, size>& buffer() {
        return ring_buffer;
    }

    void append(const T& value) {
        ring_buffer[pos] = value;
        pos = (pos + 1) % size;
    }

private:
    int pos;
    std::array<T, size> ring_buffer;
};

#endif // RINGBUFFER_H


================================================
FILE: gui/runguard.cpp
================================================
#include "runguard.h"
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <error.h>
#include <QtGlobal>
#include <QDebug>

RunGuard::RunGuard()
{
    ::memset(&addr, 0, sizeof(struct sockaddr_un));
    addr.sun_family = AF_UNIX;

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-truncation"
    ::strncpy(&addr.sun_path[1], PRETTY_ABSTRACT_SOCK, sizeof(PRETTY_ABSTRACT_SOCK) - 2);
#pragma GCC diagnostic pop

    sockfd = ::socket(AF_UNIX, SOCK_STREAM, 0);
    if (sockfd < 0)
        qFatal("socket() failed: %s", strerror(sockfd));
}

bool RunGuard::isRunning()
{
    int r = ::bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un));
    if (r < 0) {
        if (errno == EADDRINUSE)
            return true;
        else
            qFatal("bind() failed: %s", strerror(errno));
    }
    return false;
}


================================================
FILE: gui/runguard.h
================================================
#ifndef RUNGUARD_H
#define RUNGUARD_H

#include <sys/un.h>
#define PRETTY_ABSTRACT_SOCK "prettyeq"

/* Linux only implementation of a single application guard.
 * We bind to an abstract socket so cleanup is handled entirely
 * by the kernel. */
class RunGuard
{
public:
    RunGuard();
    bool isRunning();

private:
    int sockfd;
    struct sockaddr_un addr;
};

#endif // RUNGUARD_H


================================================
FILE: gui/shelfcurve.cpp
================================================
#include "macro.h"
#include "shelfcurve.h"
#include <QtGlobal>
#include <QDebug>
#include <QGraphicsScene>
#include <QPainter>
#include <QPainterPath>
#define CURVE_CAPACITY 16

ShelfCurve::ShelfCurve(QPen pen, QBrush brush, bool guiOnly, QObject *parent)
    : QObject(parent), FilterCurve(pen, brush, guiOnly)
{
    setZValue(100000);

    lineCurve = std::unique_ptr<QPainterPath>(new QPainterPath());
    fillCurve = std::unique_ptr<QPainterPath>(new QPainterPath());
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
    lineCurve->reserve(CURVE_CAPACITY);
    fillCurve->reserve(CURVE_CAPACITY);
#endif
}

ShelfCurve::~ShelfCurve()
{

}

void ShelfCurve::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    painter->setRenderHint(QPainter::Antialiasing, true);
    painter->setPen(getActivePen());
    painter->drawPath(*lineCurve);
    painter->fillPath(*fillCurve, getActiveBrush());
#if 0
    // debugging - draw control points
    QPen rpen(Qt::red);
    QPen ypen(Qt::yellow);
    QPen gpen(Qt::green);
    QPen bpen(Qt::blue);
    rpen.setWidth(10);
    ypen.setWidth(10);
    bpen.setWidth(10);
    gpen.setWidth(10);
    painter->save();
    painter->setPen(rpen);
    painter->drawPoint(p0);
    painter->setPen(ypen);
    painter->drawPoint(p1);
    painter->setPen(gpen);
    painter->drawPoint(p2);
    painter->setPen(bpen);
    painter->drawPoint(p3);
    painter->restore();
#endif

#if 0
    // debugging - draw boundingRect()
    QPen rpen(Qt::red);
    rpen.setWidth(10);
    QPen bpen(Qt::blue);
    bpen.setWidth(10);
    QPen gpen(Qt::green);
    gpen.setWidth(10);
    QPen ypen(Qt::yellow);
    ypen.setWidth(10);
    painter->save();
    painter->setPen(rpen);
    painter->drawPoint(boundingRect().bottomLeft());
    painter->setPen(bpen);
    painter->drawPoint(boundingRect().topLeft());
    painter->setPen(gpen);
    painter->drawPoint(boundingRect().bottomRight());
    painter->setPen(ypen);
    painter->drawPoint(boundingRect().topRight());
    painter->restore();
#endif

}

QPointF ShelfCurve::controlPoint() const
{
    return mapToScene(p1);
}

qreal ShelfCurve::slope() const
{
    /* The i have no idea what the fuck I'm doing equation. */
    return qAbs(bezierPainter().slopeAtPercent(0.7)) + 0.5;
}

void ShelfCurve::pointPositionChanged(CurvePoint *point) {
    QPointF curvePoint = mapFromScene(point->pos());
    QPointF delta = curvePoint - p1;
    p0.setY(p0.y() + delta.y());
    p1 = curvePoint;
    p2.setX(p2.x() + delta.x());
    p3.setX(p3.x() + delta.x());
    updateCurveGeometry();
    prepareGeometryChange();
    this->update();
    emit resync(this);
    emit filterParamsChanged(filter, this);
}

QPainterPath ShelfCurve::bezierPainter() const
{
    QPainterPath shelf;
    shelf.moveTo(p0);
    shelf.cubicTo(p1, clampP2(), p3);
    return shelf;
}

void ShelfCurve::updateCurveGeometry()
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
    lineCurve->clear();
#else
    lineCurve.reset(new QPainterPath());
#endif
    lineCurve->moveTo(p0);
    lineCurve->cubicTo(p1, clampP2(), p3);
    Q_ASSERT(lineCurve->elementCount() == 4);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
    Q_ASSERT(lineCurve->capacity() == CURVE_CAPACITY);
#endif

#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
    fillCurve->clear();
#else
    fillCurve.reset(new QPainterPath());
#endif
    fillCurve->moveTo(p0);
    fillCurve->cubicTo(p1, clampP2(), p3);
    fillCurve->lineTo(0, 0);
    fillCurve->lineTo(p0);
    Q_ASSERT(fillCurve->elementCount() <= 6);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
    Q_ASSERT(fillCurve->capacity() == CURVE_CAPACITY);
#endif
}

QRectF ShelfCurve::boundingRect() const
{
    return bezierPainter().boundingRect();
}


================================================
FILE: gui/shelfcurve.h
================================================
#ifndef SHELFCURVE_H
#define SHELFCURVE_H

#include "curvepoint.h"
#include "filtercurve.h"

#include <QBrush>
#include <QGraphicsItem>
#include <QObject>
#include <QPen>

#include <memory>

#define SLOPE_DELTA 20
#define SLOPE_MAX 330

class LowShelfCurve;
class HighShelfCurve;

class ShelfCurve : public QObject, public FilterCurve
{
    Q_OBJECT
    Q_INTERFACES(QGraphicsItem)
public:
    explicit ShelfCurve(QPen pen, QBrush brush, bool guiOnly = false, QObject *parent = nullptr);
    virtual ~ShelfCurve();
    QRectF boundingRect() const override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;

    QPointF controlPoint() const override;
    qreal slope() const;

protected:
    virtual QPointF clampP2() const = 0;

private:
    QPainterPath bezierPainter() const;
    void updateCurveGeometry();

public slots:
    void pointPositionChanged(CurvePoint *point);
    virtual void pointSlopeChanged(int delta) = 0;
signals:
    void resync(FilterCurve *curve);
    void filterParamsChanged(ShimFilterPtr filter, ShelfCurve *curve);

private:
    QPointF p0, p1, p2, p3;
    std::unique_ptr<QPainterPath> lineCurve, fillCurve;
    friend class LowShelfCurve;
    friend class HighShelfCurve;
};

#endif // SHELFCURVE_H


================================================
FILE: gui/spectrumanalyzer.cpp
================================================
#include "frequencytickbuilder.h"
#include "macro.h"
#include "prettyshim.h"
#include "spectrumanalyzer.h"

#include <QDebug>
#include <QGraphicsScene>
#include <QPainter>
#include <QPainterPath>
#include <QtCore>
#include <complex>
#include <string.h>

#define FFT_SAMPLE_TO_FREQ(NUM_SAMPLES, SAMPLE_INDEX) (44100*(SAMPLE_INDEX)/(NUM_SAMPLES))
#define FFT_FREQ_TO_SAMPLE(NUM_SAMPLES, FREQ) ((int)roundf((FREQ)*(NUM_SAMPLES)/44100))
#define FFT_BUCKET_WIDTH(NUM_SAMPLES) (44100/(NUM_SAMPLES))

static inline qreal dampen(qreal start, qreal end, qreal smoothing_factor, qint64 dt) {
    return LERP(1 - qPow(smoothing_factor, dt), start, end);
}

SpectrumAnalyzer::SpectrumAnalyzer(FrequencyTickBuilder *xTickBuilder) : xTickBuilder(xTickBuilder), last_frame_time(0)
{
    setZValue(-1);
    for (unsigned int i = 0; i < MAX_SAMPLES; i++)
        last_psds[i] = 0.0;
}

QRectF SpectrumAnalyzer::boundingRect() const
{
    auto x = scene()->sceneRect().width();
    auto y = scene()->sceneRect().height();
    return QRectF(0, 0, x, y / 4);
}

inline QLineF SpectrumAnalyzer::pointForSample(qreal frequency, qreal max_psd, qreal max_psd_moving_avg) {
    qreal sceneX = xTickBuilder->unlerpTick(frequency);
    qreal startX = mapFromScene(sceneX, 0xbeefcafe).x();
    qreal startY = qMax(boundingRect().top(), LINEAR_REMAP(max_psd, 0, max_psd_moving_avg, 0, boundingRect().height() / 2));
    QLineF line(QPointF(startX, boundingRect().center().y()), QPointF(startX, boundingRect().center().y() - startY));
    return line;
}

qint64 SpectrumAnalyzer::frame_dt()
{
    return QDateTime::currentMSecsSinceEpoch() - last_frame_time;
}

void SpectrumAnalyzer::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    painter->setRenderHint(QPainter::Antialiasing, true);
#if 0
    QPen rpen(Qt::red);
    rpen.setWidth(5);
    QPen bpen(Qt::blue);
    bpen.setWidth(5);
    painter->setPen(rpen);
    painter->drawPoint(boundingRect().center());
    painter->drawRect(boundingRect());
    painter->drawPoint(boundingRect().topLeft());
    auto line = QLineF(QPointF(100, 0), QPointF(100, boundingRect().height() / 2));
    painter->drawLine(line);
    line.translate(0, boundingRect().height() / 2);
    painter->setPen(bpen);
    painter->drawLine(line);
#else
    qreal max_psd_moving_avg = 0.0;
    static auto brush = QBrush(QColor(127, 153, 176, 128));
    static auto pen = QPen(brush, 3);
    painter->setPen(pen);
    {
        auto &buf = max_psds.buffer();
        for (auto it = buf.begin(); it != buf.end(); it++)
            max_psd_moving_avg += *it;
        max_psd_moving_avg /= MOVING_AVG_PERIOD;
    }

    {
        qint64 delta = frame_dt();
        unsigned int N;
        auto data = PrettyShim::getInstance().get_audio_data(&N);
        Q_ASSERT(N < MAX_SAMPLES);
        qreal max_psd = 0.0;
        for (unsigned int i = FFT_FREQ_TO_SAMPLE(N, FMIN); i < N / 2; i++) {
            qreal raw_psd = (qreal) std::abs(data[i]);
            qreal min = qMin(raw_psd, last_psds[i]);
            qreal max = qMax(raw_psd, last_psds[i]);
            qreal dampening_factor = 1 - min/max;
            dampening_factor = qMax(0.5, dampening_factor);
            dampening_factor = qMin(0.95, dampening_factor);
            qreal smoothed_psd = dampen(raw_psd, last_psds[i], dampening_factor, delta);
            last_psds[i] = smoothed_psd;
            max_psd = qMax(max_psd, smoothed_psd);
            qreal frequency = FFT_SAMPLE_TO_FREQ(N, (qreal) i);
            qreal sceneX = xTickBuilder->unlerpTick(frequency);
            qreal startX = mapFromScene(sceneX, 0xbeefcafe).x();
            qreal startY = LINEAR_REMAP(smoothed_psd, 0, max_psd_moving_avg, 0, boundingRect().height() / 2);
            startY = qMin(startY, boundingRect().height() / 2);
            QPointF p1 = QPointF(startX, boundingRect().center().y() - startY);
            QPointF p2 = QPointF(startX, boundingRect().center().y() + startY);
            lines[i].setP1(p1);
            lines[i].setP2(p2);
        }

        if (max_psd_moving_avg > 0.01)
            painter->drawLines(lines, N / 2);

        PrettyShim::getInstance().release_audio_data();
        max_psds.append(max_psd);
    }
#endif
}

void SpectrumAnalyzer::updateFrameDelta()
{
    last_frame_time = QDateTime::currentMSecsSinceEpoch();
}


================================================
FILE: gui/spectrumanalyzer.h
================================================
#ifndef SPECTRUMANALYZER_H
#define SPECTRUMANALYZER_H

#include <QGraphicsItem>
#include "ringbuffer.h"

#define MOVING_AVG_PERIOD 128
#define MAX_SAMPLES 4096

class FrequencyTickBuilder;

class SpectrumAnalyzer : public QGraphicsItem
{
public:
    SpectrumAnalyzer(FrequencyTickBuilder *xTickBuilder);

public:
    QRectF boundingRect() const;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);

    void updateFrameDelta();
private:
    QLineF pointForSample(qreal frequency, qreal max_psd, qreal max_psd_moving_avg);
    inline qint64 frame_dt();

private:
    FrequencyTickBuilder *xTickBuilder;
    RingBuffer<qreal, MOVING_AVG_PERIOD> max_psds;
    QLineF lines[MAX_SAMPLES];
    qreal last_psds[MAX_SAMPLES];
    qint64 last_frame_time;
};

#endif // SPECTRUMANALYZER_H


================================================
FILE: gui/unixsignalhandler.cpp
================================================
#include "unixsignalhandler.h"
#include "prettyshim.h"
#include <QDebug>
#include <QCoreApplication>

#include <sys/socket.h>
#include <unistd.h>

int UnixSignalHandler::socketVector[2];

UnixSignalHandler &UnixSignalHandler::getInstance() {
    static UnixSignalHandler instance;
    return instance;
}

UnixSignalHandler::UnixSignalHandler(QObject *parent) : QObject(parent)
{
    if (::socketpair(AF_UNIX, SOCK_STREAM, 0, socketVector))
        qFatal("Could not socketpair();");

    sn = new QSocketNotifier(socketVector[1], QSocketNotifier::Read, this);
    QObject::connect(sn, SIGNAL(activated(QSocketDescriptor, QSocketNotifier::Type)), this, SLOT(safeHandleExit()));
}

void UnixSignalHandler::exitHandler(int sig)
{
    /* Write junk to the socket to trigger Qt signal */
    char junk = 1;
    ::write(socketVector[0], &junk, sizeof(char));
}

void UnixSignalHandler::safeHandleExit()
{
    sn->setEnabled(false);
    char junk;
    ::read(socketVector[1], &junk, sizeof(char));
    QCoreApplication::quit();
    sn->setEnabled(true);
}

UnixSignalHandler::~UnixSignalHandler()
{
    delete sn;
}


================================================
FILE: gui/unixsignalhandler.h
================================================
#ifndef UNIXSIGNALHANDLER_H
#define UNIXSIGNALHANDLER_H

#include <QObject>
#include <QSocketNotifier>

class UnixSignalHandler : public QObject
{
    Q_OBJECT
public:
    static UnixSignalHandler& getInstance();
    ~UnixSignalHandler();
    UnixSignalHandler(UnixSignalHandler const&) = delete;
    void operator=(UnixSignalHandler const&)    = delete;

private:
    explicit UnixSignalHandler(QObject *parent = nullptr);

public:
    /* SIGTERM, SIGINT, SIGQUIT, SIGABRT */
    static void exitHandler(int sig);

public slots:
    void safeHandleExit();

private:
    static UnixSignalHandler instance;
    /* All signals share the same socketpair and will mask each other. */
    static int socketVector[2];
    QSocketNotifier *sn;
};

#endif // UNIXSIGNALHANDLER_H


================================================
FILE: prettyeq.pro
================================================
TEMPLATE = subdirs
CONFIG +=  ordered
SUBDIRS = \
          equalizer \
          gui

gui.depends = equalizer
Download .txt
gitextract__5e1dd8e/

├── .gitignore
├── LICENSE.txt
├── README.md
├── build_tests.sh
├── equalizer/
│   ├── arena.c
│   ├── arena.h
│   ├── arena_test.c
│   ├── equalizer.pro
│   ├── fft.c
│   ├── fft.h
│   ├── fft_test.c
│   ├── macro.h
│   ├── pretty.c
│   └── pretty.h
├── gui/
│   ├── collisionmanager.cpp
│   ├── collisionmanager.h
│   ├── curvepoint.cpp
│   ├── curvepoint.h
│   ├── eqhoverer.cpp
│   ├── eqhoverer.h
│   ├── filtercurve.cpp
│   ├── filtercurve.h
│   ├── frequencytick.cpp
│   ├── frequencytick.h
│   ├── frequencytickbuilder.cpp
│   ├── frequencytickbuilder.h
│   ├── gui.cpp
│   ├── gui.h
│   ├── gui.pro
│   ├── gui.ui
│   ├── highshelfcurve.cpp
│   ├── highshelfcurve.h
│   ├── lowshelfcurve.cpp
│   ├── lowshelfcurve.h
│   ├── macro.h
│   ├── main.cpp
│   ├── peakingcurve.cpp
│   ├── peakingcurve.h
│   ├── prettygraphicsscene.cpp
│   ├── prettygraphicsscene.h
│   ├── prettyshim.h
│   ├── resources.qrc
│   ├── ringbuffer.h
│   ├── runguard.cpp
│   ├── runguard.h
│   ├── shelfcurve.cpp
│   ├── shelfcurve.h
│   ├── spectrumanalyzer.cpp
│   ├── spectrumanalyzer.h
│   ├── unixsignalhandler.cpp
│   └── unixsignalhandler.h
└── prettyeq.pro
Download .txt
SYMBOL INDEX (95 symbols across 35 files)

FILE: equalizer/arena.c
  function arena_t (line 15) | arena_t* arena_new(size_t num_chunks, size_t chunk_size) {
  function arena_dealloc (line 58) | void arena_dealloc(arena_t *arena, void *mem) {
  function arena_destroy (line 67) | void arena_destroy(arena_t **arena) {

FILE: equalizer/arena.h
  type chunk_t (line 5) | typedef struct chunk_t chunk_t;
  type chunk_t (line 6) | struct chunk_t {
  type arena_t (line 10) | typedef struct arena_t {

FILE: equalizer/arena_test.c
  function main (line 7) | int main(int argc, char **argv) {

FILE: equalizer/fft.c
  function reverse_bits (line 18) | static inline unsigned int reverse_bits(unsigned int n, unsigned int num...
  function get_msb (line 35) | static inline unsigned int get_msb(unsigned int v) {
  function fft_init (line 39) | void fft_init() {
  function fft_run (line 53) | void fft_run(

FILE: equalizer/fft_test.c
  type TestCases (line 89) | typedef struct TestCases {
  function float_approx_equal (line 121) | static inline bool float_approx_equal(float f1, float f2) {
  function test_init (line 125) | void test_init() {
  function test_single_channel (line 134) | void test_single_channel() {
  function test_dual_channel_micro (line 153) | void test_dual_channel_micro() {
  function main (line 167) | int main(int argc, char **argv) {

FILE: equalizer/pretty.c
  type _AudioFFT (line 42) | struct _AudioFFT {
  type FilterParams (line 49) | typedef struct FilterParams {
  type _PrettyFilter (line 54) | struct _PrettyFilter {
  function quit (line 68) | static void quit(int ret) {
  function cleanup (line 75) | static void cleanup() {
  function success_callback (line 100) | static void success_callback(pa_context *c, int success, void *userdata) {
  function sink_input_callback (line 109) | static void sink_input_callback(pa_context *c, const pa_sink_input_info ...
  function subscribe_callback (line 143) | static void subscribe_callback(
  function null_source_output_callback (line 155) | static void null_source_output_callback(pa_context *c, const pa_source_o...
  function null_sink_info_callback (line 186) | static void null_sink_info_callback(pa_context *c, const pa_sink_info *i...
  function unload_module_callback (line 220) | static void unload_module_callback(pa_context *c, int success, void *use...
  function read_stream_callback (line 232) | static void read_stream_callback(pa_stream *s, size_t length, void *user...
  function load_module_callback (line 338) | static void load_module_callback(pa_context *c, uint32_t idx, void *user...
  function module_list_callback (line 356) | static void module_list_callback(pa_context *c, const pa_module_info *i,...
  function drain_signal_callback (line 377) | static void drain_signal_callback(pa_context *c, void *userdata) {
  function context_state_callback (line 386) | static void context_state_callback(pa_context *c, void *userdata) {
  function pretty_init (line 437) | int pretty_init() {
  function pretty_setup_sink_io (line 517) | void pretty_setup_sink_io() {
  function pretty_exit (line 550) | int pretty_exit() {
  function pretty_new_filter (line 568) | int pretty_new_filter(PrettyFilter **filter) {
  function safe_update_audio_loop (line 574) | static inline void safe_update_audio_loop(PrettyFilter *filter, FilterPa...
  function pretty_set_peaking_eq (line 589) | void pretty_set_peaking_eq(PrettyFilter *filter, float f0, float bandwid...
  function pretty_set_low_shelf (line 610) | void pretty_set_low_shelf(PrettyFilter *filter, float f0, float S, float...
  function pretty_set_high_shelf (line 632) | void pretty_set_high_shelf(PrettyFilter *filter, float f0, float S, floa...
  function pretty_enable_bypass (line 654) | void pretty_enable_bypass(bool should_bypass)
  function pretty_acquire_audio_data (line 659) | void pretty_acquire_audio_data(complex float **data, unsigned int *N) {
  function pretty_release_audio_data (line 666) | void pretty_release_audio_data() {

FILE: equalizer/pretty.h
  type std (line 28) | typedef std::complex<float>* FFTComplexCompat;
  type complex (line 30) | typedef complex float* FFTComplexCompat;
  type PrettyFilter (line 37) | typedef struct _PrettyFilter PrettyFilter;
  type AudioFFT (line 39) | typedef struct _AudioFFT AudioFFT;

FILE: gui/collisionmanager.h
  function class (line 9) | class CollisionManager

FILE: gui/curvepoint.cpp
  function QRectF (line 20) | QRectF CurvePoint::boundingRect() const

FILE: gui/eqhoverer.cpp
  function QRectF (line 24) | QRectF EqHoverer::boundingRect() const

FILE: gui/filtercurve.cpp
  function QPen (line 30) | const QPen& FilterCurve::getActivePen() const
  function QBrush (line 35) | const QBrush& FilterCurve::getActiveBrush() const

FILE: gui/filtercurve.h
  type ColorState (line 14) | typedef enum ColorState {
  function class (line 19) | class FilterCurve : public QGraphicsItem

FILE: gui/frequencytick.cpp
  function QString (line 45) | QString FrequencyTick::toQString()
  function qreal (line 53) | qreal FrequencyTick::getFrequency() const
  function qreal (line 58) | qreal FrequencyTick::getX() const

FILE: gui/frequencytick.h
  function class (line 7) | class FrequencyTick

FILE: gui/frequencytickbuilder.cpp
  function qreal (line 23) | qreal FrequencyTickBuilder::lerpTick(qreal x)
  function qreal (line 35) | qreal FrequencyTickBuilder::unlerpTick(qreal f)

FILE: gui/frequencytickbuilder.h
  function class (line 10) | class FrequencyTickBuilder

FILE: gui/gui.h
  function QT_BEGIN_NAMESPACE (line 15) | QT_BEGIN_NAMESPACE
  type FilterItem (line 29) | typedef struct FilterItem {
  function class (line 36) | class Gui : public QDialog

FILE: gui/highshelfcurve.cpp
  function QPointF (line 15) | QPointF HighShelfCurve::clampP2() const

FILE: gui/highshelfcurve.h
  function class (line 6) | class HighShelfCurve : public ShelfCurve

FILE: gui/lowshelfcurve.cpp
  function QPointF (line 15) | QPointF LowShelfCurve::clampP2() const

FILE: gui/lowshelfcurve.h
  function class (line 8) | class LowShelfCurve : public ShelfCurve

FILE: gui/macro.h
  function QPointF (line 25) | static inline QPointF cubic_bezier(qreal t, QPointF p0, QPointF p1, QPoi...

FILE: gui/main.cpp
  function setupUnixSignalHandlers (line 10) | static void setupUnixSignalHandlers(QList<int> exitSignals) {
  function main (line 34) | int main(int argc, char *argv[])

FILE: gui/peakingcurve.cpp
  function QRectF (line 27) | QRectF PeakingCurve::boundingRect() const
  function QPointF (line 61) | QPointF PeakingCurve::controlPoint() const

FILE: gui/peakingcurve.h
  type SplinePart (line 14) | typedef enum SplinePart {

FILE: gui/prettygraphicsscene.h
  function class (line 6) | class PrettyGraphicsScene : public QGraphicsScene

FILE: gui/prettyshim.h
  type PrettyFilter (line 11) | typedef PrettyFilter* ShimFilterPtr;
  function class (line 15) | class PrettyShim : public QObject

FILE: gui/ringbuffer.h
  function T (line 14) | const T& at(int i) {
  function append (line 23) | void append(const T& value) {

FILE: gui/runguard.cpp
  type sockaddr_un (line 11) | struct sockaddr_un
  type sockaddr (line 26) | struct sockaddr
  type sockaddr_un (line 26) | struct sockaddr_un

FILE: gui/runguard.h
  function class (line 10) | class RunGuard

FILE: gui/shelfcurve.cpp
  function QPointF (line 80) | QPointF ShelfCurve::controlPoint() const
  function qreal (line 85) | qreal ShelfCurve::slope() const
  function QPainterPath (line 105) | QPainterPath ShelfCurve::bezierPainter() const
  function QRectF (line 142) | QRectF ShelfCurve::boundingRect() const

FILE: gui/spectrumanalyzer.cpp
  function qreal (line 18) | static inline qreal dampen(qreal start, qreal end, qreal smoothing_facto...
  function QRectF (line 29) | QRectF SpectrumAnalyzer::boundingRect() const
  function QLineF (line 36) | inline QLineF SpectrumAnalyzer::pointForSample(qreal frequency, qreal ma...
  function qint64 (line 44) | qint64 SpectrumAnalyzer::frame_dt()

FILE: gui/spectrumanalyzer.h
  function class (line 12) | class SpectrumAnalyzer : public QGraphicsItem

FILE: gui/unixsignalhandler.cpp
  function UnixSignalHandler (line 11) | UnixSignalHandler &UnixSignalHandler::getInstance() {

FILE: gui/unixsignalhandler.h
  function class (line 7) | class UnixSignalHandler : public QObject
Condensed preview — 52 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (112K chars).
[
  {
    "path": ".gitignore",
    "chars": 662,
    "preview": "# C++ objects and libs\n*.slo\n*.lo\n*.o\n*.a\n*.la\n*.lai\n*.so\n*.so.*\n*.dll\n*.dylib\n\n# Qt-es\nobject_script.*.Release\nobject_s"
  },
  {
    "path": "LICENSE.txt",
    "chars": 1458,
    "preview": "Copyright 2020 Kevin Kuehler\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permi"
  },
  {
    "path": "README.md",
    "chars": 1093,
    "preview": "## PrettyEQ\n\nprettyeq is a system-wide paramateric equalizer for pulseaudio. This software\nis in alpha. Use at your own "
  },
  {
    "path": "build_tests.sh",
    "chars": 209,
    "preview": "#!/bin/sh\nmkdir -p build\ngcc -lm -ffast-math -march=skylake -fopenmp -O2 \\\n  -o build/fft_test \\\n  equalizer/fft.c equal"
  },
  {
    "path": "equalizer/arena.c",
    "chars": 1714,
    "preview": "#include <assert.h>\n#include <stdlib.h>\n#include <sys/mman.h>\n#include <unistd.h>\n#include <stdio.h>\n\n#include \"arena.h\""
  },
  {
    "path": "equalizer/arena.h",
    "chars": 406,
    "preview": "#pragma once\n\n#include <stddef.h>\n\ntypedef struct chunk_t chunk_t;\nstruct chunk_t {\n    chunk_t *next;\n};\n\ntypedef struc"
  },
  {
    "path": "equalizer/arena_test.c",
    "chars": 1281,
    "preview": "#include <assert.h>\n#include <string.h>\n#include <stdio.h>\n\n#include \"arena.h\"\n\nint main(int argc, char **argv) {\n    ar"
  },
  {
    "path": "equalizer/equalizer.pro",
    "chars": 247,
    "preview": "TEMPLATE = lib\nQMAKE_CFLAGS += -ffast-math -fopenmp\nQMAKE_CFLAGS_WARN_ON += -Wno-unused-parameter\nCONFIG += staticlib\nHE"
  },
  {
    "path": "equalizer/fft.c",
    "chars": 2985,
    "preview": "#include <assert.h>\n#include <limits.h>\n#include <math.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdio.h>\n#i"
  },
  {
    "path": "equalizer/fft.h",
    "chars": 609,
    "preview": "#pragma once\n#include <complex.h>\n#include <math.h>\n\n#define MAX_SAMPLES_LOG_2 12\n#define MAX_SAMPLES (1 << MAX_SAMPLES_"
  },
  {
    "path": "equalizer/fft_test.c",
    "chars": 8097,
    "preview": "#include \"fft.h\"\n\n#include <assert.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <time.h>\n\nstatic const float sing"
  },
  {
    "path": "equalizer/macro.h",
    "chars": 577,
    "preview": "#pragma once\n\n#define PRETTY_EXPORT __attribute__ ((visibility (\"default\")))\n#define MAY_ALIAS __attribute__((__may_alia"
  },
  {
    "path": "equalizer/pretty.c",
    "chars": 21191,
    "preview": "#include <assert.h>\n#include <complex.h>\n#include <errno.h>\n#include <math.h>\n#include <pthread.h>\n#include <pulse/pulse"
  },
  {
    "path": "equalizer/pretty.h",
    "chars": 1658,
    "preview": "#pragma once\n\n#include \"macro.h\"\n\n#include <complex.h>\n#include <stdbool.h>\n#include <stdint.h>\n\n#define SINK_NAME \"pret"
  },
  {
    "path": "gui/collisionmanager.cpp",
    "chars": 441,
    "preview": "#include \"collisionmanager.h\"\n#include \"eqhoverer.h\"\n#include \"filtercurve.h\"\n\n#include <QDebug>\n\nCollisionManager::Coll"
  },
  {
    "path": "gui/collisionmanager.h",
    "chars": 349,
    "preview": "#ifndef COLLISIONMANAGER_H\n#define COLLISIONMANAGER_H\n\n#define NUM_FILTERS 7\n\nclass EqHoverer;\nclass FilterCurve;\n\nclass"
  },
  {
    "path": "gui/curvepoint.cpp",
    "chars": 2684,
    "preview": "#include \"curvepoint.h\"\n#include <QDebug>\n#include <QGraphicsItem>\n#include <QGraphicsScene>\n#include <QGraphicsSceneMou"
  },
  {
    "path": "gui/curvepoint.h",
    "chars": 987,
    "preview": "#ifndef CURVEPOINT_H\n#define CURVEPOINT_H\n\n#include \"filtercurve.h\"\n#include <QBrush>\n#include <QGraphicsItem>\n#include "
  },
  {
    "path": "gui/eqhoverer.cpp",
    "chars": 2868,
    "preview": "#include \"collisionmanager.h\"\n#include \"eqhoverer.h\"\n#include <QGraphicsScene>\n#include <QDebug>\n#include <QGraphicsScen"
  },
  {
    "path": "gui/eqhoverer.h",
    "chars": 1083,
    "preview": "#ifndef EQHOVERER_H\n#define EQHOVERER_H\n\n#include \"curvepoint.h\"\n#include \"filtercurve.h\"\n#include <QGraphicsItem>\n#incl"
  },
  {
    "path": "gui/filtercurve.cpp",
    "chars": 770,
    "preview": "#include \"filtercurve.h\"\n#include <QGraphicsScene>\n\nFilterCurve::FilterCurve(QPen togglePen, QBrush toggleBrush, bool gu"
  },
  {
    "path": "gui/filtercurve.h",
    "chars": 850,
    "preview": "#ifndef FILTERCURVE_H\n#define FILTERCURVE_H\n\n#include \"prettyshim.h\"\n\n#include <QGraphicsItem>\n#include <QObject>\n#inclu"
  },
  {
    "path": "gui/frequencytick.cpp",
    "chars": 1509,
    "preview": "#include \"frequencytick.h\"\n#include \"macro.h\"\n\n#include <QDebug>\n#include <QFont>\n#include <QFontDatabase>\n#include <QPa"
  },
  {
    "path": "gui/frequencytick.h",
    "chars": 481,
    "preview": "#ifndef FREQUENCYTICK_H\n#define FREQUENCYTICK_H\n\n#include <QGraphicsScene>\n#include <QGraphicsLineItem>\n\nclass Frequency"
  },
  {
    "path": "gui/frequencytickbuilder.cpp",
    "chars": 1887,
    "preview": "#include \"frequencytick.h\"\n#include \"frequencytickbuilder.h\"\n#include \"macro.h\"\n\n#include <QGraphicsScene>\n\nFrequencyTic"
  },
  {
    "path": "gui/frequencytickbuilder.h",
    "chars": 459,
    "preview": "#ifndef FREQUENCYTICKBUILDER_H\n#define FREQUENCYTICKBUILDER_H\n\n#include <QtCore>\n#include <QGraphicsScene>\n#define NUM_T"
  },
  {
    "path": "gui/gui.cpp",
    "chars": 14145,
    "preview": "#include \"collisionmanager.h\"\n#include \"curvepoint.h\"\n#include \"eqhoverer.h\"\n#include \"frequencytick.h\"\n#include \"freque"
  },
  {
    "path": "gui/gui.h",
    "chars": 2360,
    "preview": "#ifndef GUI_H\n#define GUI_H\n\n#include \"prettyshim.h\"\n#include <QDialog>\n#include <QGraphicsScene>\n#include <QGraphicsVie"
  },
  {
    "path": "gui/gui.pro",
    "chars": 1895,
    "preview": "QT       += core gui\n\ngreaterThan(QT_MAJOR_VERSION, 4): QT += widgets\n\nCONFIG += c++11\n\n# The following define makes you"
  },
  {
    "path": "gui/gui.ui",
    "chars": 2553,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>Gui</class>\n <widget class=\"QDialog\" name=\"Gui\">\n  <pr"
  },
  {
    "path": "gui/highshelfcurve.cpp",
    "chars": 973,
    "preview": "#include \"highshelfcurve.h\"\n\nHighShelfCurve::HighShelfCurve(QPen pen, QBrush brush, bool guiOnly, QObject *parent)\n    :"
  },
  {
    "path": "gui/highshelfcurve.h",
    "chars": 513,
    "preview": "#ifndef HIGHSHELFCURVE_H\n#define HIGHSHELFCURVE_H\n\n#include \"shelfcurve.h\"\n\nclass HighShelfCurve : public ShelfCurve\n{\np"
  },
  {
    "path": "gui/lowshelfcurve.cpp",
    "chars": 956,
    "preview": "#include \"lowshelfcurve.h\"\n\nLowShelfCurve::LowShelfCurve(QPen pen, QBrush brush, bool guiOnly, QObject *parent)\n    : Sh"
  },
  {
    "path": "gui/lowshelfcurve.h",
    "chars": 489,
    "preview": "#ifndef LOWSHELFCURVE_H\n#define LOWSHELFCURVE_H\n\n#include \"shelfcurve.h\"\n#include <QBrush>\n#include <QPen>\n\nclass LowShe"
  },
  {
    "path": "gui/macro.h",
    "chars": 765,
    "preview": "#ifndef MACRO_H\n#define MACRO_H\n\n#include <QPointF>\n\n#define F1   20\n#define F2   50\n#define F3   100\n#define F4   200\n#"
  },
  {
    "path": "gui/main.cpp",
    "chars": 1219,
    "preview": "#include \"gui.h\"\n#include \"runguard.h\"\n#include \"unixsignalhandler.h\"\n#include <QApplication>\n#include <QList>\n#include "
  },
  {
    "path": "gui/peakingcurve.cpp",
    "chars": 3750,
    "preview": "#include \"peakingcurve.h\"\n#include <QtGlobal>\n#include <QDebug>\n#include <QGraphicsScene>\n#include <QPainter>\n#include <"
  },
  {
    "path": "gui/peakingcurve.h",
    "chars": 1127,
    "preview": "#ifndef PEAKINGCURVE_H\n#define PEAKINGCURVE_H\n\n#include \"curvepoint.h\"\n#include \"filtercurve.h\"\n\n#include <QBrush>\n#incl"
  },
  {
    "path": "gui/prettygraphicsscene.cpp",
    "chars": 1433,
    "preview": "#include \"curvepoint.h\"\n#include \"eqhoverer.h\"\n#include \"prettygraphicsscene.h\"\n#include <QDebug>\n#include <QGraphicsIte"
  },
  {
    "path": "gui/prettygraphicsscene.h",
    "chars": 323,
    "preview": "#ifndef PRETTYGRAPHICSSCENE_H\n#define PRETTYGRAPHICSSCENE_H\n\n#include <QGraphicsScene>\n\nclass PrettyGraphicsScene : publ"
  },
  {
    "path": "gui/prettyshim.h",
    "chars": 1857,
    "preview": "#ifndef PRETTYSHIM_H\n#define PRETTYSHIM_H\n\n#include <QObject>\n#include <QString>\n#include <QDebug>\n#include <complex>\n\n#"
  },
  {
    "path": "gui/resources.qrc",
    "chars": 104,
    "preview": "<RCC>\n    <qresource prefix=\"/images\">\n        <file>images/prettyeq.png</file>\n    </qresource>\n</RCC>\n"
  },
  {
    "path": "gui/ringbuffer.h",
    "chars": 534,
    "preview": "#ifndef RINGBUFFER_H\n#define RINGBUFFER_H\n\n#include <QtCore>\n\n#include <stdlib.h>\n\ntemplate <class T, int size>\nclass Ri"
  },
  {
    "path": "gui/runguard.cpp",
    "chars": 866,
    "preview": "#include \"runguard.h\"\n#include <string.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <error.h>\n#include <Qt"
  },
  {
    "path": "gui/runguard.h",
    "chars": 388,
    "preview": "#ifndef RUNGUARD_H\n#define RUNGUARD_H\n\n#include <sys/un.h>\n#define PRETTY_ABSTRACT_SOCK \"prettyeq\"\n\n/* Linux only implem"
  },
  {
    "path": "gui/shelfcurve.cpp",
    "chars": 3738,
    "preview": "#include \"macro.h\"\n#include \"shelfcurve.h\"\n#include <QtGlobal>\n#include <QDebug>\n#include <QGraphicsScene>\n#include <QPa"
  },
  {
    "path": "gui/shelfcurve.h",
    "chars": 1281,
    "preview": "#ifndef SHELFCURVE_H\n#define SHELFCURVE_H\n\n#include \"curvepoint.h\"\n#include \"filtercurve.h\"\n\n#include <QBrush>\n#include "
  },
  {
    "path": "gui/spectrumanalyzer.cpp",
    "chars": 4331,
    "preview": "#include \"frequencytickbuilder.h\"\n#include \"macro.h\"\n#include \"prettyshim.h\"\n#include \"spectrumanalyzer.h\"\n\n#include <QD"
  },
  {
    "path": "gui/spectrumanalyzer.h",
    "chars": 819,
    "preview": "#ifndef SPECTRUMANALYZER_H\n#define SPECTRUMANALYZER_H\n\n#include <QGraphicsItem>\n#include \"ringbuffer.h\"\n\n#define MOVING_"
  },
  {
    "path": "gui/unixsignalhandler.cpp",
    "chars": 1109,
    "preview": "#include \"unixsignalhandler.h\"\n#include \"prettyshim.h\"\n#include <QDebug>\n#include <QCoreApplication>\n\n#include <sys/sock"
  },
  {
    "path": "gui/unixsignalhandler.h",
    "chars": 771,
    "preview": "#ifndef UNIXSIGNALHANDLER_H\n#define UNIXSIGNALHANDLER_H\n\n#include <QObject>\n#include <QSocketNotifier>\n\nclass UnixSignal"
  },
  {
    "path": "prettyeq.pro",
    "chars": 111,
    "preview": "TEMPLATE = subdirs\nCONFIG +=  ordered\nSUBDIRS = \\\n          equalizer \\\n          gui\n\ngui.depends = equalizer\n"
  }
]

About this extraction

This page contains the full source code of the keur/prettyeq GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 52 files (102.5 KB), approximately 30.7k tokens, and a symbol index with 95 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!