[
  {
    "path": ".gitignore",
    "content": "# 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_script.*.Debug\n*_plugin_import.cpp\n/.qmake.cache\n/.qmake.stash\n*.pro.user\n*.pro.user.*\n*.qbs.user\n*.qbs.user.*\n*.moc\nmoc_*.cpp\nmoc_*.h\nqrc_*.cpp\nui_*.h\n*.qmlc\n*.jsc\nMakefile*\n*build-*\n*.qm\n*.prl\n\n# Qt unit tests\ntarget_wrapper.*\n\n# QtCreator\n*.autosave\n\n# QtCreator Qml\n*.qmlproject.user\n*.qmlproject.user.*\n\n# QtCreator CMake\nCMakeLists.txt.user*\n\n# QtCreator 4.8< compilation database\ncompile_commands.json\n\n# QtCreator local machine specific files for imported projects\n*creator.user*\n\n*_qmlcache.qrc\n\n/tags\n/TAGS\n/prettyeq\n/build\n/.vscode\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "Copyright 2020 Kevin Kuehler\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\nlist of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\nthis list of conditions and the following disclaimer in the documentation\nand/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its contributors\nmay be used to endorse or promote products derived from this software without\nspecific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "## PrettyEQ\n\nprettyeq is a system-wide paramateric equalizer for pulseaudio. This software\nis in alpha. Use at your own discretion.\n\n![prettyeq demo](https://i.fluffy.cc/0GFcjGmbrtCgnbRSjd4xjDcf7h6qNk4Q.gif)\n\n### Building\n\n```\nmkdir build\ncd build\nqmake CONFIG+=release ..\nmake -j4\n```\n\n### Usage\n\nWhen the program is executed all pulseaudio streams will be routed through the\nequalizer. With no filters activated prettyeq acts as a passthrough sink.\n\nRight now prettyeq only supports two-channel sound.\n\n##### Filter types\n\nprettyeq has three filter types:\n* one **low shelf** filter mounted at 20Hz\n* one **high shelf** filter mounted at 20kHz\n* five **peakingEQ** filters that can move freely\n\n##### Controls\n\nClick and drag points to boost and cut frequency bands. The dB gain range is\n±12dB. Filter bandwidth and slope can be changed with the mousewheel.\n\n##### Quitting\n\nIf your desktop has a system tray, the close button will hide the GUI but the\nequalizer will still be in effect. There are context menus in the application\nand tray that have a \"exit\" option to quit the application.\n"
  },
  {
    "path": "build_tests.sh",
    "content": "#!/bin/sh\nmkdir -p build\ngcc -lm -ffast-math -march=skylake -fopenmp -O2 \\\n  -o build/fft_test \\\n  equalizer/fft.c equalizer/fft_test.c\n\ngcc -g -o build/arena_test \\\n  equalizer/arena.c equalizer/arena_test.c\n"
  },
  {
    "path": "equalizer/arena.c",
    "content": "#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\"\n\n#define CHUNK_OFFSET(memory) ((void*)(memory)-(sizeof(chunk_t)))\n\n#define MEMORY_OFFSET(chunk) ((void*)(chunk)+(sizeof(chunk_t)))\n\n#define NEXT_CHUNK(chunk, size) (((void*)MEMORY_OFFSET((chunk)))+(size))\n\narena_t* arena_new(size_t num_chunks, size_t chunk_size) {\n    assert(num_chunks > 0);\n    assert(chunk_size > 0);\n\n    arena_t *arena = calloc(sizeof(arena_t), 1);\n    if (!arena)\n        return NULL;\n\n    size_t map_size = num_chunks * (chunk_size + sizeof(chunk_t));\n    void *mem = mmap(\n            NULL,\n            map_size,\n            PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON,\n            -1, 0);\n\n    if (mem == MAP_FAILED)\n        return NULL;\n\n    chunk_t *chunk = mem;\n    for (unsigned int i = 0; i < num_chunks - 1; i++) {\n        chunk->next = NEXT_CHUNK(chunk, chunk_size);\n        chunk = chunk->next;\n    }\n    chunk->next = NULL;\n\n    arena->chunk_size = chunk_size;\n    arena->map_size = map_size;\n    arena->mem = mem;\n    arena->avail_chunk = mem;\n\n    return arena;\n}\n\nvoid* arena_alloc(arena_t *arena) {\n    assert(arena);\n    assert(arena->avail_chunk);\n\n    void *p = MEMORY_OFFSET(arena->avail_chunk);\n    assert(p);\n    arena->avail_chunk = arena->avail_chunk->next;\n    return p;\n}\n\nvoid arena_dealloc(arena_t *arena, void *mem) {\n    assert(arena);\n    assert(mem);\n\n    chunk_t *chunk = CHUNK_OFFSET(mem);\n    chunk->next = arena->avail_chunk;\n    arena->avail_chunk = chunk;\n}\n\nvoid arena_destroy(arena_t **arena) {\n    assert(arena);\n    assert((*arena)->mem);\n    munmap((*arena)->mem, (*arena)->map_size);\n    free(*arena);\n    *arena = NULL;\n}\n"
  },
  {
    "path": "equalizer/arena.h",
    "content": "#pragma once\n\n#include <stddef.h>\n\ntypedef struct chunk_t chunk_t;\nstruct chunk_t {\n    chunk_t *next;\n};\n\ntypedef struct arena_t {\n    void *mem;\n    size_t chunk_size;\n    size_t map_size;\n    chunk_t *avail_chunk;\n} arena_t;\n\narena_t* arena_new(size_t num_chunks, size_t chunk_size);\nvoid* arena_alloc(arena_t *arena);\nvoid arena_dealloc(arena_t *arena, void *mem);\nvoid arena_destroy(arena_t **arena);\n"
  },
  {
    "path": "equalizer/arena_test.c",
    "content": "#include <assert.h>\n#include <string.h>\n#include <stdio.h>\n\n#include \"arena.h\"\n\nint main(int argc, char **argv) {\n    arena_t *arena = arena_new(4, 5);\n    assert(arena);\n    assert(arena->avail_chunk != NULL);\n\n    char *a = arena_alloc(arena);\n    memset(a, 'a', 4);\n    a[4] = '\\0';\n\n    char *b = arena_alloc(arena);\n    memset(b, 'b', 4);\n    b[4] = '\\0';\n\n    char *c = arena_alloc(arena);\n    memset(c, 'c', 4);\n    c[4] = '\\0';\n\n    char *d = arena_alloc(arena);\n    memset(d, 'd', 4);\n    d[4] = '\\0';\n\n    assert(arena->avail_chunk == NULL);\n    assert(memcmp(a, \"aaaa\", 5) == 0);\n    assert(memcmp(b, \"bbbb\", 5) == 0);\n    assert(memcmp(c, \"cccc\", 5) == 0);\n    assert(memcmp(d, \"dddd\", 5) == 0);\n\n    arena_dealloc(arena, c);\n    arena_dealloc(arena, a);\n\n    char *e = arena_alloc(arena);\n    memset(e, 'e', 4);\n    e[4] = '\\0';\n\n    char *f = arena_alloc(arena);\n    memset(f, 'f', 4);\n    f[4] = '\\0';\n\n    assert(memcmp(e, \"eeee\", 5) == 0);\n    assert(memcmp(a, \"eeee\", 5) == 0);\n    assert(memcmp(f, \"ffff\", 5) == 0);\n    assert(memcmp(c, \"ffff\", 5) == 0);\n\n    arena_dealloc(arena, b);\n    arena_dealloc(arena, e);\n    arena_dealloc(arena, f);\n    arena_dealloc(arena, d);\n\n    assert((void *) arena->avail_chunk == (void *) d - sizeof(chunk_t));\n    return 0;\n}\n"
  },
  {
    "path": "equalizer/equalizer.pro",
    "content": "TEMPLATE = lib\nQMAKE_CFLAGS += -ffast-math -fopenmp\nQMAKE_CFLAGS_WARN_ON += -Wno-unused-parameter\nCONFIG += staticlib\nHEADERS = \\\n    arena.h \\\n    fft.h \\\n    macro.h \\\n    pretty.h\n    pretty.h\nSOURCES = \\\n    arena.c \\\n    fft.c \\\n    pretty.c\n"
  },
  {
    "path": "equalizer/fft.c",
    "content": "#include <assert.h>\n#include <limits.h>\n#include <math.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdalign.h>\n#include <string.h>\n#include <time.h>\n\n#include \"macro.h\"\n#include \"fft.h\"\n\nstatic bool initialized = false;\nalignas(32) static complex float omega_vec_log2[MAX_SAMPLES][K];\n\nstatic inline unsigned int reverse_bits(unsigned int n, unsigned int num_bits) {\n    int i, j;\n    register unsigned int res = 0;\n\n    i = 0;\n    j = num_bits;\n    while (i <= j) {\n        unsigned int lower_mask = 1 << i;\n        unsigned int upper_mask = (1 << num_bits) >> i;\n        unsigned int shift = j - i;\n        res |= ((n >> shift) & lower_mask) | ((n << shift) & upper_mask);\n        i++;\n        j--;\n    }\n    return res;\n}\n\nstatic inline unsigned int get_msb(unsigned int v) {\n    return 31 - __builtin_clz(v);\n}\n\nvoid fft_init() {\n    for (unsigned int nl = 0; nl < MAX_SAMPLES_LOG_2; nl++) {\n        unsigned int n = (1u << nl);\n        const double mul_div_n = (-2.0 * M_PI) / n;\n        for (unsigned int k = 0; k < K; k++) {\n            complex float *res = &omega_vec_log2[nl][k];\n            complex double theta = mul_div_n * k;\n            _real_(*res) = cosf(theta);\n            _imag_(*res) = sinf(theta);\n        }\n    }\n    initialized = true;\n}\n\nvoid fft_run(\n        const float *input_data,\n        complex float *output_data,\n        unsigned int N,\n        unsigned int channels) {\n    assert(initialized);\n\n    {\n        unsigned int msb;\n\n        for (unsigned int i = 0, j = 0; i < N; j++, i+=channels)\n            /* Taking just the left channel for now... */\n            output_data[j] = input_data[i];\n\n        N = N / channels;\n        assert(N <= MAX_SAMPLES);\n        msb = get_msb(N);\n\n        if (_unlikely_((N & (N-1)))) {\n            /* Pad out so FFT is a power of 2. */\n            msb = msb + 1;\n            unsigned int new_N = 1 << msb;\n            for (unsigned int i = N; i < new_N; i++)\n                output_data[i] = 0.0f;\n\n            N = new_N;\n        }\n\n        /* Reverse the input array. */\n        unsigned int hi_bit = msb - 1;\n        for (unsigned int i = 0; i < N; i++) {\n            unsigned int r = reverse_bits(i, hi_bit);\n            if (i < r)\n                SWAP(output_data[i], output_data[r]);\n        }\n    }\n\n    {\n        /* Simple radix-2 DIT FFT */\n        unsigned int wingspan = 1;\n        unsigned int nl = 1;\n        while (wingspan < N) {\n            for (unsigned int j = 0; j < N; j+=wingspan*2) {\n                for (unsigned int k = 0; k < wingspan; k++) {\n                    complex float omega = omega_vec_log2[nl][k];\n                    complex float a0 = output_data[k+j];\n                    complex float a1 = output_data[k+j+wingspan];\n                    output_data[k+j] = a0 + omega*a1;\n                    output_data[k+j+wingspan] = a0 - omega*a1;\n                }\n            }\n            nl++;\n            wingspan *= 2;\n        }\n    }\n}\n"
  },
  {
    "path": "equalizer/fft.h",
    "content": "#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_LOG_2)\n#define K (MAX_SAMPLES / 2)\n\n#define FFT_BUCKET_WIDTH(NUM_SAMPLES) (44100/(NUM_SAMPLES))\n#define FFT_SAMPLE_TO_FREQ(NUM_SAMPLES, SAMPLE_INDEX) (44100*(SAMPLE_INDEX)/(NUM_SAMPLES))\n#define FFT_FREQ_TO_SAMPLE(NUM_SAMPLES, FREQ) ((int)roundf((FREQ)*(NUM_SAMPLES)/44100))\n#define FFT_PSD(SAMPLE) ((float)crealf(cabsf((SAMPLE))))\n\nvoid fft_init();\nvoid fft_run(\n        const float *input_data,\n        complex float *output_data,\n        unsigned int N,\n        unsigned int channels);\n"
  },
  {
    "path": "equalizer/fft_test.c",
    "content": "#include \"fft.h\"\n\n#include <assert.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <time.h>\n\nstatic const float single_channel_5000HZ_data[] = {\n    -0.120544, 0.222534, 0.457336, 0.469727, 0.253632, -0.085846, -0.383575,\n    -0.494568, -0.365295, -0.058197, 0.277222, 0.477783, 0.445923, 0.197205,\n    -0.147461, -0.420441, -0.488922, -0.319580, 0.005157, 0.327362, 0.490356,\n    0.414825, 0.137512, -0.206696, -0.450378, -0.475037, -0.268646, 0.068390,\n    0.372162, 0.494537, 0.376892, 0.075592, -0.262482, -0.472900, -0.453339,\n    -0.213257, 0.130524, 0.410797, 0.491302, 0.332794, 0.012451, -0.313995,\n    -0.487701, -0.424225, -0.154419, 0.190491, 0.442719, 0.479614, 0.283234,\n    -0.050934, -0.360352, -0.494476, -0.388123, -0.093018, 0.247345, 0.467377,\n    0.460083, 0.229004, -0.113464, -0.400787, -0.493164, -0.345673, -0.030090,\n    0.300110, 0.484344, 0.432983, 0.171021, -0.174133, -0.434601, -0.483704,\n    -0.297546, 0.033325, 0.347992, 0.493378, 0.398773, 0.110199, -0.231964,\n    -0.461334, -0.466339, -0.244507, 0.096191, 0.390137, 0.494293, 0.358032,\n    0.047607, -0.285980, -0.480469, -0.441284, -0.187500, 0.157501, 0.425873,\n    0.487091, 0.311401, -0.015778, -0.335266, -0.491730, -0.409027, -0.127380,\n    0.216217, 0.454590, 0.471893, 0.259644, -0.078888, -0.379059, -0.494568,\n    -0.370026, -0.065186, 0.271332, 0.475891, 0.448944, 0.203644, -0.140717,\n    -0.416687, -0.489960, -0.324951, -0.001892, 0.322052, 0.489349, 0.418640,\n    0.144287, -0.200256, -0.447388, -0.476959, -0.274536, 0.061401, 0.367462,\n    0.494537, 0.381439, 0.082550, -0.256500, -0.470795, -0.456116, -0.219604,\n    0.123688, 0.406830, 0.492065, 0.337982, 0.019470, -0.308502, -0.486450,\n    -0.427826, -0.161102, 0.183960, 0.439545, 0.481293, 0.288971, -0.043915,\n    -0.355469, -0.494141, -0.392487, -0.099945, 0.241211, 0.465027, 0.462616,\n    0.235229, -0.106598, -0.396576, -0.493683, -0.350677, -0.037109, 0.294495,\n    0.482849, 0.436340, 0.177612, -0.167542, -0.431213, -0.485168, -0.303131,\n    0.026276, 0.342926, 0.492767, 0.402924, 0.117065, -0.225708, -0.458740,\n    -0.468628, -0.250610, 0.089264, 0.385742, 0.494537, 0.362854, 0.054626,\n    -0.280182, -0.478729, -0.444458, -0.194000, 0.150787, 0.422241, 0.488281,\n    0.316864, -0.008728, -0.330048, -0.490845, -0.412933, -0.134186, 0.209839,\n    0.451782, 0.473969, 0.265625, -0.071930, -0.374512, -0.494568, -0.374664,\n    -0.072174, 0.265411, 0.473877, 0.451874, 0.210022, -0.133942, -0.412811,\n    -0.490906, -0.330231, -0.008972, 0.316650, 0.488251, 0.422363, 0.151031,\n    -0.193787, -0.444336, -0.478790, -0.280365, 0.054382, 0.362671, 0.494537,\n    0.385895, 0.089508, -0.250427, -0.468567, -0.458801, -0.225922, 0.116852,\n    0.402802, 0.492798, 0.343109, 0.026520, -0.302948, -0.485107, -0.431305,\n    -0.167755, 0.177399, 0.436249, 0.482910, 0.294678, -0.036896, -0.350525,\n    -0.493683, -0.396729, -0.106842, 0.235016, 0.462555, 0.465088, 0.241425,\n    -0.099701, -0.392303, -0.494141, -0.355621, -0.044159, 0.288788, 0.481262,\n    0.439636, 0.184174, -0.160858, -0.427704, -0.486511, -0.308685, 0.019257,\n    0.337830, 0.492065, 0.406982, 0.123932, -0.219421, -0.456024, -0.470856,\n    -0.256683, 0.082306, 0.381287, 0.494537, 0.367615, 0.061646, -0.274323,\n    -0.476868, -0.447479, -0.200470, 0.144043, 0.418518, 0.489380, 0.322235,\n    -0.001678, -0.324768, -0.489899, -0.416779, -0.140961, 0.203400, 0.448822,\n    0.475952, 0.271545, -0.064941, -0.369873, -0.494568, -0.379242, -0.079132,\n    0.259430, 0.471832, 0.454712, 0.216400, -0.127167, -0.408875, -0.491760,\n    -0.335449, -0.016022, 0.311218, 0.487061, 0.425995, 0.157715, -0.187286,\n    -0.441193, -0.480530, -0.286163, 0.047363, 0.357849, 0.494293, 0.390289,\n    0.096436, -0.244324, -0.466248, -0.461395, -0.232178, 0.109985, 0.398651,\n    0.493378, 0.348145, 0.033569, -0.297333, -0.483673, -0.434723, -0.174347,\n    0.170807, 0.432861, 0.484406, 0.300323, -0.029846, -0.345520, -0.493134,\n    -0.400909, -0.113708, 0.228790, 0.459991, 0.467438, 0.247528, -0.092773,\n    -0.388000, -0.494507, -0.360504, -0.051178, 0.283020, 0.479584, 0.442841,\n    0.190704, -0.154175, -0.424103, -0.487762, -0.314178, 0.012207, 0.332611,\n    0.491241, 0.410950, 0.130737, -0.213074, -0.453247, -0.472992, -0.262695,\n    0.075348, 0.376740, 0.494537, 0.372314, 0.068634, -0.268433, -0.474945,\n    -0.450470, -0.206909, 0.137299, 0.414703, 0.490387, 0.327545, 0.005402,\n    -0.319397, -0.488861, -0.420563, -0.147705, 0.196991, 0.445862, 0.477844,\n    0.277435, -0.057953, -0.365143, -0.494568, -0.383728, -0.086090, 0.253418,\n    0.469635, 0.457428, 0.222717, -0.120331, -0.404877, -0.492493, -0.340607,\n    -0.023071, 0.305695, 0.485748, 0.429535, 0.164398, -0.180725, -0.437958,\n    -0.482178, -0.291870, 0.040344, 0.352966, 0.493866, 0.394562, 0.103333,\n    -0.238159, -0.463837, -0.463928, -0.238373, 0.103088, 0.394440, 0.493896,\n    0.353119, 0.040588, -0.291687, -0.482117, -0.438049, -0.180939, 0.164154,\n    0.429413, 0.485809, 0.305878, -0.022797, -0.340424, -0.492462, -0.404999,\n    -0.120544, 0.222504, 0.457336, 0.469727, 0.253632, -0.085846, -0.383575,\n    -0.494568, -0.365295, -0.058197, 0.277222, 0.477783, 0.445923, 0.197205,\n    -0.147461, -0.420410, -0.488922, -0.319580, 0.005127, 0.327362, 0.490356,\n    0.414825, 0.137512, -0.206696, -0.450348, -0.475037, -0.268646, 0.068390,\n    0.372131, 0.494537, 0.376892, 0.075592, -0.262482, -0.472900, -0.453339,\n    -0.213287, 0.130524, 0.410797, 0.491302, 0.332794, 0.012451, -0.313995,\n    -0.487701, -0.424225, -0.154419, 0.190491, 0.442719, 0.479645, 0.283234,\n    -0.050934, -0.360352, -0.494476, -0.388153, -0.093018, 0.247345, 0.467377,\n    0.460083, 0.229004, -0.113464, -0.400757, -0.493164, -0.345673, -0.030090,\n    0.300110, 0.484344, 0.432983, 0.171021, -0.174133, -0.434601, -0.483704,\n    -0.297546,\n};\n\nstatic const float dual_channel_micro[] = {\n    -0.120544, -0.120544, 0.222534, 0.222534,\n};\n\ntypedef struct TestCases {\n    int frequency;\n    float expected_psd;\n} TestCases;\n\nTestCases cases[] = {\n    {\n        .frequency = 100,\n        .expected_psd = 0.09595596265773097,\n    },\n    {\n        .frequency = 200,\n        .expected_psd = 0.09558897341820413,\n    },\n    {\n        .frequency = 1000,\n        .expected_psd = 0.10730647827342488,\n    },\n    {\n        .frequency = 2000,\n        .expected_psd = 0.14449811458513645,\n    },\n    {\n        .frequency = 5000,\n        .expected_psd = 126.14145371375102,\n    },\n    {\n        .frequency = 10000,\n        .expected_psd = 0.14292403328440603,\n    },\n};\n\nstatic inline bool float_approx_equal(float f1, float f2) {\n    return fabsf(f1 - f2) > 0.00001 ? false : true;\n}\n\nvoid test_init() {\n    clock_t start, end;\n    double cpu_time;\n    start = clock();\n    fft_init();\n    end = clock();\n    fprintf(stdout, \"[FFT Init %d samples] time: %lf\\n\", MAX_SAMPLES, (double) (end - start) / CLOCKS_PER_SEC);\n}\n\nvoid test_single_channel() {\n    clock_t start, end;\n    double cpu_time;\n    complex float output[MAX_SAMPLES];\n    unsigned int N = sizeof(single_channel_5000HZ_data) / sizeof(single_channel_5000HZ_data[0]);\n\n    start = clock();\n    fft_run(single_channel_5000HZ_data, output, N, 1);\n    end = clock();\n\n    for (int i = 0; i < sizeof(cases)/sizeof(cases[0]); i++) {\n        int sample_index = FFT_FREQ_TO_SAMPLE(N, cases[i].frequency);\n        float psd = FFT_PSD(output[sample_index]);\n        assert(float_approx_equal(cases[i].expected_psd, FFT_PSD(output[sample_index])));\n    }\n\n    fprintf(stdout, \"[FFT Run %d samples] time: %lf\\n\", N, (double) (end - start) / CLOCKS_PER_SEC);\n}\n\nvoid test_dual_channel_micro() {\n    clock_t start, end;\n    double cpu_time;\n    complex float output[MAX_SAMPLES];\n    unsigned int N = sizeof(dual_channel_micro) / sizeof(dual_channel_micro[0]);\n\n    start = clock();\n    fft_run(dual_channel_micro, output, N, 2);\n    end = clock();\n\n\n    fprintf(stdout, \"[FFT Run %d samples] time: %lf\\n\", N, (double) (end - start) / CLOCKS_PER_SEC);\n}\n\nint main(int argc, char **argv) {\n    test_init();\n    test_single_channel();\n    test_dual_channel_micro();\n    return 0;\n}\n"
  },
  {
    "path": "equalizer/macro.h",
    "content": "#pragma once\n\n#define PRETTY_EXPORT __attribute__ ((visibility (\"default\")))\n#define MAY_ALIAS __attribute__((__may_alias__))\n\n#define SWAP(a, b)              \\\n    ({                          \\\n        typeof(a) _tmp_ = b;    \\\n        b = a;                  \\\n        a = _tmp_;              \\\n    })\n\n#define BOX_USERDATA(var) ((void *) ((uint64_t) (var)))\n#define UNBOX_USERDATA(type, var) ((type) ((uint64_t) (var)))\n\n#define _likely_(x)      __builtin_expect(!!(x), 1)\n#define _unlikely_(x)    __builtin_expect(!!(x), 0)\n\n#define _real_ __real__\n#define _imag_ __imag__\n"
  },
  {
    "path": "equalizer/pretty.c",
    "content": "#include <assert.h>\n#include <complex.h>\n#include <errno.h>\n#include <math.h>\n#include <pthread.h>\n#include <pulse/pulseaudio.h>\n#include <pulse/sample.h>\n#include <stdatomic.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <string.h>\n#include <sys/mman.h>\n\n#include \"arena.h\"\n#include \"fft.h\"\n#include \"pretty.h\"\n\n#define NUM_FILTERS 7\n#define LATENCY_MAX_MS 30\n\n//=============================================================================\n\nstatic pa_mainloop_api *mainloop_api = NULL;\nstatic pa_context *context = NULL;\nstatic uint32_t prettyeq_module_index = PA_INVALID_INDEX;\nstatic pa_stream *read_stream = NULL, *write_stream = NULL;\nstatic pa_threaded_mainloop *m = NULL;\n\nstatic const pa_sample_spec sample_spec = {\n    // https://freedesktop.org/software/pulseaudio/doxygen/sample_8h.html\n    .format = PA_SAMPLE_FLOAT32LE,\n    .rate = 44100,\n    .channels = 2,\n};\n\n//=============================================================================\n\nstatic _Atomic bool bypass;\n\nstatic arena_t *arena = NULL;\n\nstruct _AudioFFT {\n    pthread_spinlock_t lock;\n    complex float data[MAX_SAMPLES];\n    unsigned int N;\n};\nstatic AudioFFT audio_fft = {0};\n\ntypedef struct FilterParams {\n    float a[3];\n    float b[3];\n} FilterParams;\n\nstruct _PrettyFilter {\n    /* Filter parameters used in the audio loop. */\n    float xwin[3];\n    float ywin[2];\n\n    /* Temporary parameters for compare-exchange. */\n    _Atomic (FilterParams*) params;\n    FilterParams *storage;\n};\nstatic PrettyFilter filters[NUM_FILTERS];\nstatic int user_enabled_filters = 0;\n\n//=============================================================================\n\nstatic void quit(int ret) {\n    fprintf(stderr, \"debug: quit(%d) called!\", ret);\n    if (prettyeq_module_index != PA_INVALID_INDEX)\n        pa_operation_unref(pa_context_unload_module(context, prettyeq_module_index, NULL, NULL));\n    mainloop_api->quit(mainloop_api, ret);\n}\n\nstatic void cleanup() {\n    if (read_stream)\n        pa_stream_unref(read_stream);\n\n    if (write_stream)\n        pa_stream_unref(write_stream);\n\n    if (context)\n        pa_context_unref(context);\n\n    if (m)\n        pa_threaded_mainloop_free(m);\n\n    if (arena)\n        arena_destroy(&arena);\n\n    if (audio_fft.lock) {\n        pthread_spin_destroy(&audio_fft.lock);\n    }\n\n    munlockall();\n}\n\n//=============================================================================\n\nstatic void success_callback(pa_context *c, int success, void *userdata) {\n    assert(c);\n\n    if ( !success) {\n        fprintf(stderr, \"Failure: %s\", pa_strerror(pa_context_errno(c)));\n        quit(1);\n    }\n}\n\nstatic void sink_input_callback(pa_context *c, const pa_sink_input_info *i, int is_last, void *userdata) {\n    assert(c);\n\n    if (is_last < 0) {\n        fprintf(stderr, \"Failed to get sink information: %s\",\n                        pa_strerror(pa_context_errno(c)));\n        return;\n    }\n\n    if (is_last)\n        return;\n\n    assert(i);\n\n    if (strcmp(SINK_NAME, i->name) != 0) {\n        const char *app_name;\n        app_name = pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME);\n        fprintf(stderr, \"new sink-input: name=%s, index=%d, sink=%d\\n\", app_name, i->index, i->sink);\n        pa_operation_unref(pa_context_move_sink_input_by_name(c, i->index, SINK_NAME, NULL, NULL));\n    } else {\n        /* Pulseaudio keeps sink state, so if the user changed the prettyeq\n         * playback stream volume, we reset back to 100% here. This is so the\n         * equalizer \"at rest\" doesn't modify the input stream. */\n        pa_cvolume volume;\n        volume = i->volume;\n        pa_cvolume_reset(&volume, i->sample_spec.channels);\n        pa_operation_unref(pa_context_set_sink_input_volume(c,\n                                                            i->index,\n                                                            &volume,\n                                                            success_callback,\n                                                            NULL));\n    }\n}\n\nstatic void subscribe_callback(\n        pa_context *c,\n        pa_subscription_event_type_t t,\n        uint32_t idx,\n        void *userdata) {\n    assert(c);\n    assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT);\n\n    if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW)\n        pa_operation_unref(pa_context_get_sink_input_info(c, idx, sink_input_callback, NULL));\n}\n\nstatic void null_source_output_callback(pa_context *c, const pa_source_output_info *i, int is_last, void *userdata) {\n    uint32_t prettyeq_source = UNBOX_USERDATA(uint32_t, userdata);\n\n    assert(c);\n\n    if (is_last < 0) {\n        fprintf(stderr, \"Failed to get null source output information: %s\\n\",\n                         pa_strerror(pa_context_errno(c)));\n        quit(1);\n        return;\n    }\n\n    if (is_last)\n        return;\n\n    assert(i);\n\n    if (prettyeq_source == i->source) {\n        /* The recording stream sometimes starts at 79% instead of 100%, so\n         * let's fix that here or else we start at a lower volume. */\n        pa_cvolume volume;\n        volume = i->volume;\n        pa_cvolume_reset(&volume, i->sample_spec.channels);\n        pa_operation_unref(pa_context_set_source_output_volume(c,\n                                                            i->index,\n                                                            &volume,\n                                                            success_callback,\n                                                            NULL));\n    }\n}\n\nstatic void null_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_last, void *userdata) {\n    pa_buffer_attr buffer_attr;\n\n    assert(c);\n\n    if (is_last < 0) {\n        fprintf(stderr, \"Failed to get null sink information: %s\\n\",\n                         pa_strerror(pa_context_errno(c)));\n        quit(1);\n        return;\n    }\n\n    if (is_last)\n        return;\n\n    assert(i);\n\n    /* Hook up the read stream to the null sink monitor output. */\n    memset(&buffer_attr, 0, sizeof(buffer_attr));\n    buffer_attr.maxlength = (uint32_t) -1;\n    buffer_attr.prebuf = (uint32_t) -1;\n    buffer_attr.fragsize = pa_usec_to_bytes(LATENCY_MAX_MS * PA_USEC_PER_MSEC, &sample_spec);\n    buffer_attr.tlength = pa_usec_to_bytes(LATENCY_MAX_MS * PA_USEC_PER_MSEC, &sample_spec);\n    if (pa_stream_connect_record(read_stream, i->monitor_source_name, &buffer_attr, PA_STREAM_ADJUST_LATENCY) < 0) {\n        fprintf(stderr, \"pa_stream_connect_record() failed: %s\\n\",\n                pa_strerror(pa_context_errno(c)));\n        quit(1);\n    }\n    fprintf(stderr, \"monitor source name=%s\\n\", i->monitor_source_name);\n    pa_operation_unref(pa_context_get_source_output_info_list(c,\n                                                              null_source_output_callback,\n                                                              BOX_USERDATA(i->monitor_source)));\n}\n\nstatic void unload_module_callback(pa_context *c, int success, void *userdata) {\n    pa_threaded_mainloop *m;\n    m = userdata;\n    assert(m);\n\n    if (! success)\n        fprintf(stderr, \"pa_context_unload_module() failed: %s\",\n                pa_strerror(pa_context_errno(c)));\n\n    pa_threaded_mainloop_signal(m, 0);\n}\n\nstatic void read_stream_callback(pa_stream *s, size_t length, void *userdata) {\n    assert(s);\n    assert(length > 0);\n\n    while (pa_stream_readable_size(s) > 0) {\n        const void *input_data = NULL;\n        float *fp = NULL;\n        size_t num_samples;\n\n        if (pa_stream_peek(s, &input_data, &length) < 0) {\n            fprintf(stderr, \"pa_stream_peek() failed: %s\\n\",\n                            pa_strerror(pa_context_errno(context)));\n            quit(1);\n            return;\n        }\n\n        fp = (float *) input_data;\n        num_samples = length / sizeof(float);\n\n        if (bypass)\n            goto play_frame;\n\n#ifndef EQ_DISABLE\n        for (int k = 0; k < NUM_FILTERS; k++) {\n            /* Swap in NULL to block a filter param update mid-iteration. */\n            FilterParams *params = atomic_exchange(&filters[k].params, NULL);\n\n            float *xwin = filters[k].xwin;\n            float *ywin = filters[k].ywin;\n            float *a = params->a;\n            float *b = params->b;\n\n            for (unsigned long i = 0; i < length / sizeof(float); i++) {\n                float f;\n\n                /* Slide x-window */\n                xwin[2] = xwin[1];\n                xwin[1] = xwin[0];\n                xwin[0] = fp[i];\n\n                f = fp[i];\n                f = (b[0] / a[0] * xwin[0]) +\n                    (b[1] / a[0] * xwin[1]) +\n                    (b[2] / a[0] * xwin[2]) -\n                    (a[1] / a[0] * ywin[0]) -\n                    (a[2] / a[0] * ywin[1]);\n\n                if (PRETTY_IS_DENORMAL(f) || PRETTY_IS_NAN(f))\n                    f = 0.0f;\n\n                fp[i] = f;\n\n                /* Slide y-window */\n                ywin[1] = ywin[0];\n                ywin[0] = f;\n            }\n            /* Unblock any pending filter param updates. */\n            atomic_store(&filters[k].params, params);\n        }\n#endif // EQ_DISABLE\n\nplay_frame:\n        for (;;) {\n            size_t data_length = length;\n            uint8_t *output_data = NULL;\n\n            if (pa_stream_begin_write(write_stream, (void **)&output_data, &data_length) < 0) {\n                fprintf(stderr, \"pa_stream_begin_write() failed: %s\\n\",\n                                pa_strerror(pa_context_errno(context)));\n                quit(1);\n                return;\n            }\n\n            memcpy(output_data, input_data, data_length);\n            if (pa_stream_write(write_stream, output_data, data_length, NULL, 0, PA_SEEK_RELATIVE) < 0) {\n                fprintf(stderr, \"pa_stream_write() failed: %s\\n\",\n                                pa_strerror(pa_context_errno(context)));\n                quit(1);\n                return;\n            }\n\n            if (data_length >= length)\n                break;\n\n            length -= data_length;\n            input_data += data_length;\n        }\n\n        if (pthread_spin_trylock(&audio_fft.lock) >= 0) {\n            /* Sigh... In no world should pulse really be handing us anything more than\n             * uint16_t in a rapid callback. Fuck it, we downcast. */\n            assert(num_samples <= UINT_MAX);\n            fft_run(fp, audio_fft.data, (unsigned int) num_samples, sample_spec.channels);\n            audio_fft.N = (unsigned int) num_samples / sample_spec.channels;\n            pthread_spin_unlock(&audio_fft.lock);\n        }\n\n        if (pa_stream_drop(s) < 0) {\n            fprintf(stderr, \"pa_stream_drop() failed: %s\\n\",\n                            pa_strerror(pa_context_errno(context)));\n            quit(1);\n            return;\n        }\n    }\n}\n\nstatic void load_module_callback(pa_context *c, uint32_t idx, void *userdata) {\n    assert(c);\n\n    if (idx == PA_INVALID_INDEX) {\n        fprintf(stderr, \"Bad index\\n\");\n        quit(1);\n        return;\n    }\n\n    prettyeq_module_index = idx;\n    pa_operation_unref(pa_context_get_sink_input_info_list(c, sink_input_callback, NULL));\n    pa_operation_unref(pa_context_get_sink_info_by_name(c, SINK_NAME, null_sink_info_callback, NULL));\n\n    /* Subscribe to new sink-inputs so we can send them through the equalizer. */\n    pa_context_set_subscribe_callback(c, subscribe_callback, NULL);\n    pa_operation_unref(pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK_INPUT, NULL, NULL));\n}\n\nstatic void module_list_callback(pa_context *c, const pa_module_info *i, int is_last, void *userdata) {\n    pa_threaded_mainloop *m = userdata;\n    static bool signal_on_last = true;\n\n    assert(m);\n    assert(c);\n\n    if (is_last) {\n        if (signal_on_last)\n            pa_threaded_mainloop_signal(m, 0);\n        return;\n    }\n\n    assert(i);\n    if (strcmp(i->name, \"module-null-sink\") == 0 && i->argument && strcmp(i->argument, \"sink_name=\" SINK_NAME) == 0) {\n        pa_operation_unref(pa_context_unload_module(c, i->index, unload_module_callback, m));\n        signal_on_last = false;\n    }\n\n}\n\nstatic void drain_signal_callback(pa_context *c, void *userdata) {\n    pa_threaded_mainloop *m = userdata;\n\n    assert(c);\n    assert(m);\n\n    pa_threaded_mainloop_signal(m, 0);\n}\n\nstatic void context_state_callback(pa_context *c, void *userdata) {\n    switch (pa_context_get_state(c)) {\n        case PA_CONTEXT_UNCONNECTED:\n        case PA_CONTEXT_CONNECTING:\n        case PA_CONTEXT_AUTHORIZING:\n        case PA_CONTEXT_SETTING_NAME:\n            break;\n\n        case PA_CONTEXT_READY: {\n\n            if ( !(read_stream = pa_stream_new(c, \"prettyeq\", &sample_spec, NULL))) {\n                fprintf(stderr, \"pa_stream_new() failed.\\n\");\n                quit(1);\n                return;\n            }\n\n            if ( !(write_stream = pa_stream_new(c, \"prettyeq\", &sample_spec, NULL))) {\n                fprintf(stderr, \"pa_stream_new() failed.\\n\");\n                quit(1);\n                return;\n            }\n\n            /* Setup the playback stream. We create the recording stream in the null sink callback. */\n            if (pa_stream_connect_playback(write_stream, NULL, NULL, 0, NULL, NULL) < 0) {\n                fprintf(stderr, \"pa_stream_connect_playback() failed: %s\\n\",\n                        pa_strerror(pa_context_errno(c)));\n                quit(1);\n                return;\n            }\n\n            /* Audio event callback functions. */\n            pa_stream_set_read_callback(read_stream, read_stream_callback, NULL);\n            pa_threaded_mainloop_signal(m, 0);\n\n            fprintf(stderr, \"context is ready!\\n\");\n            break;\n        }\n        case PA_CONTEXT_FAILED:\n            fprintf(stderr, \"Unclean termination\\n\");\n            quit(1);\n            break;\n        case PA_CONTEXT_TERMINATED:\n            fprintf(stderr, \"Clean termination\\n\");\n            quit(0);\n            break;\n\n    }\n}\n\n//=============================================================================\n\nint pretty_init() {\n    int r;\n\n    /* Initialize the fft and user exposed data structures. */\n    fft_init();\n    audio_fft.N = 0;\n    r = pthread_spin_init(&audio_fft.lock, PTHREAD_PROCESS_PRIVATE);\n    if (r < 0) {\n        fprintf(stderr, \"Could not pthread_spin_init()\");\n        goto err;\n    }\n\n    /* Initialize the memory arena used to create new filters. */\n    arena = arena_new(NUM_FILTERS * 2, sizeof(FilterParams));\n    if (!arena) {\n        fprintf(stderr, \"Could not arena_new()\");\n        r = -ENOMEM;\n        goto err;\n    }\n\n    /* Initialize filters. */\n    for (int i = 0; i < NUM_FILTERS; i++) {\n        FilterParams *start_params;\n        PrettyFilter *filter = &filters[i];\n\n        filter->xwin[0] = 0.0f;\n        filter->xwin[1] = 0.0f;\n        filter->xwin[2] = 0.0f;\n        filter->ywin[0] = 0.0f;\n        filter->ywin[1] = 0.0f;\n\n        start_params = arena_alloc(arena);\n        atomic_store(&filter->params, start_params);\n        filter->storage = start_params;\n\n        /* We don't want to page fault reading filters in the audio loop. */\n        r = mlock(&filter, sizeof(PrettyFilter));\n        if (r < 0) {\n            fprintf(stderr, \"mlock() failed: %s.\", strerror(errno));\n            goto err;\n        }\n    }\n\n    /* Start with bypass mode disabled. */\n    atomic_store(&bypass, false);\n\n    /* Initialize pulseaudio mainloop. */\n    if ( !(m = pa_threaded_mainloop_new())) {\n        fprintf(stderr, \"pa_mainloop_new() failed.\");\n        r = -1;\n        goto err;\n    }\n\n    mainloop_api = pa_threaded_mainloop_get_api(m);\n    if ( !(context = pa_context_new_with_proplist(mainloop_api, NULL, NULL))) {\n        fprintf(stderr, \"pa_context_new() failed.\");\n        r = -1;\n        goto err;\n    }\n\n    pa_context_set_state_callback(context, context_state_callback, NULL);\n    if (pa_context_connect(context, NULL, 0, NULL) < 0) {\n        fprintf(stderr, \"pa_context_connect() failed.\");\n        r = -1;\n        goto err;\n    }\n\n    r = pa_threaded_mainloop_start(m);\n    if (r < 0) {\n        fprintf(stderr, \"pa_threaded_mainloop_start() failed.\");\n        goto err;\n    }\n\n    return 0;\n\nerr:\n    cleanup();\n    return r;\n}\n\nvoid pretty_setup_sink_io() {\n    pa_operation *o;\n\n    pa_threaded_mainloop_lock(m);\n\n    /* Wait until context is ready. */\n    while(pa_context_get_state(context) != PA_CONTEXT_READY)\n        pa_threaded_mainloop_wait(m);\n\n    /* Handle an unclean termination and cleanup an existing prettyeq module. */\n    o = pa_context_get_module_info_list(context, module_list_callback, m);\n    while (pa_operation_get_state(o) == PA_OPERATION_RUNNING)\n        pa_threaded_mainloop_wait(m);\n    pa_operation_cancel(o);\n    pa_operation_unref(o);\n\n    /* Drain the context. TODO(keur): Not sure if we actually need this. Poorly documented function */\n    if ((o = pa_context_drain(context, drain_signal_callback, m))) {\n        while (pa_operation_get_state(o) == PA_OPERATION_RUNNING)\n            pa_threaded_mainloop_wait(m);\n        pa_operation_unref(o);\n    }\n\n    pa_threaded_mainloop_unlock(m);\n\n    /* Load in the null sink to act as audio IO. */\n    pa_operation_unref(pa_context_load_module(context,\n                                              \"module-null-sink\",\n                                              \"sink_name=\" SINK_NAME,\n                                              load_module_callback,\n                                              NULL));\n}\n\nint pretty_exit() {\n    if (m) {\n        pa_operation *o;\n        if (prettyeq_module_index != PA_INVALID_INDEX) {\n            pa_threaded_mainloop_lock(m);\n            o = pa_context_unload_module(context, prettyeq_module_index, unload_module_callback, m);\n            while (pa_operation_get_state(o) == PA_OPERATION_RUNNING)\n                pa_threaded_mainloop_wait(m);\n            pa_operation_unref(o);\n            pa_threaded_mainloop_unlock(m);\n        }\n        pa_threaded_mainloop_stop(m);\n    }\n    cleanup();\n\n    return 0;\n}\n\nint pretty_new_filter(PrettyFilter **filter) {\n    assert(user_enabled_filters < NUM_FILTERS);\n    *filter = &filters[user_enabled_filters++];\n    return 0;\n}\n\nstatic inline void safe_update_audio_loop(PrettyFilter *filter, FilterParams *new_params) {\n    FilterParams *expected;\n\n    assert(filter);\n    assert(new_params);\n\n    do {\n        expected = filter->storage;\n        atomic_compare_exchange_strong(&filter->params, &expected, new_params);\n    } while (!expected);\n\n    arena_dealloc(arena, filter->storage);\n    filter->storage = new_params;\n}\n\nvoid pretty_set_peaking_eq(PrettyFilter *filter, float f0, float bandwidth, float db_gain) {\n    FilterParams *new_params;\n    float A, alpha, w0, sinw0, cosw0;\n\n    new_params = arena_alloc(arena);\n\n    A = powf(10, db_gain/40);\n    w0 = 2*M_PI*f0/sample_spec.rate;\n    sinw0 = sinf(w0);\n    cosw0 = cosf(w0);\n    alpha = sinw0*sinhf(logf(2)/2 * bandwidth * w0/sinw0);\n    new_params->b[0] = 1 + alpha*A;\n    new_params->b[1] = -2 * cosw0;\n    new_params->b[2] = 1 - alpha*A;\n    new_params->a[0] = 1 + alpha/A;\n    new_params->a[1] = -2 * cosw0;\n    new_params->a[2] = 1 - alpha/A;\n\n    safe_update_audio_loop(filter, new_params);\n}\n\nvoid pretty_set_low_shelf(PrettyFilter *filter, float f0, float S, float db_gain) {\n    FilterParams *new_params;\n    float A, alpha, w0, sinw0, cosw0, sqrtA;\n\n    new_params = arena_alloc(arena);\n\n    A = powf(10, db_gain / 40);\n    w0 = 2*M_PI*f0/sample_spec.rate;\n    sinw0 = sinf(w0);\n    cosw0 = cosf(w0);\n    sqrtA = sqrtf(A);\n    alpha = sinw0/2 * sqrtf((A + 1/A) * (1/S - 1) + 2);\n    new_params->b[0] = A*((A + 1) - (A - 1)*cosw0 + 2*sqrtA*alpha);\n    new_params->b[1] = 2*A*((A - 1) - (A + 1)*cosw0);\n    new_params->b[2] = A*((A + 1) - (A - 1)*cosw0 - 2*sqrtA*alpha);\n    new_params->a[0] = (A + 1) + (A - 1)*cosw0 + 2*sqrtA*alpha;\n    new_params->a[1] = -2*((A - 1) + (A + 1)*cosw0);\n    new_params->a[2] = (A + 1) + (A - 1)*cosw0 - 2*sqrtA*alpha;\n\n    safe_update_audio_loop(filter, new_params);\n}\n\nvoid pretty_set_high_shelf(PrettyFilter *filter, float f0, float S, float db_gain) {\n    FilterParams *new_params;\n    float A, alpha, w0, sinw0, cosw0, sqrtA;\n\n    new_params = arena_alloc(arena);\n\n    A = powf(10, db_gain / 40);\n    w0 = 2*M_PI*f0/sample_spec.rate;\n    sinw0 = sinf(w0);\n    cosw0 = cosf(w0);\n    sqrtA = sqrtf(A);\n    alpha = sinw0/2 * sqrtf((A + 1/A) * (1/S - 1) + 2);\n    new_params->b[0] = A*((A + 1) + (A - 1)*cosw0 + 2*sqrtA*alpha);\n    new_params->b[1] = -2*A*((A - 1) + (A + 1)*cosw0);\n    new_params->b[2] = A*((A + 1) + (A - 1)*cosw0 - 2*sqrtA*alpha);\n    new_params->a[0] = (A + 1) - (A - 1)*cosw0 + 2*sqrtA*alpha;\n    new_params->a[1] = 2*((A - 1) - (A + 1)*cosw0);\n    new_params->a[2] = (A + 1) - (A - 1)*cosw0 - 2*sqrtA*alpha;\n\n    safe_update_audio_loop(filter, new_params);\n}\n\nvoid pretty_enable_bypass(bool should_bypass)\n{\n    atomic_store(&bypass, should_bypass);\n}\n\nvoid pretty_acquire_audio_data(complex float **data, unsigned int *N) {\n    int r = pthread_spin_lock(&audio_fft.lock);\n    assert(r == 0); /* No deadlock. */\n    *data = audio_fft.data;\n    *N = audio_fft.N;\n}\n\nvoid pretty_release_audio_data() {\n    int r = pthread_spin_unlock(&audio_fft.lock);\n    assert(r == 0); /* No deadlock. */\n}\n"
  },
  {
    "path": "equalizer/pretty.h",
    "content": "#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 \"prettyeq\"\n\n#define DRAIN_NO_CB(c) (pa_operation_unref(pa_context_drain(c, NULL, NULL)))\n\n/* with -ffast-math the standard macros have undefined behavior. */\n#define PRETTY_IS_DENORMAL(f)   \\\n    ({                          \\\n        typeof(f) *_f_ = &(f);  \\\n        ((*(unsigned int *)_f_) & 0x7f800000u) == 0 || ((*(unsigned int *)_f_) & 0xff800000u) == 0; \\\n    })\n\n#define PRETTY_IS_NAN(f)  \t\t\\\n    ({                          \\\n        typeof(f) *_f_ = &(f);  \\\n        ((*(unsigned int *)_f_) << 1) > 0xff000000u; \\\n    })\n\n/* accomodating the stupid C++ template redefinition... */\n#ifdef __cplusplus\n    typedef std::complex<float>* FFTComplexCompat;\n#else\n    typedef complex float* FFTComplexCompat;\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef struct _PrettyFilter PrettyFilter;\n\ntypedef struct _AudioFFT AudioFFT;\n\nPRETTY_EXPORT\nint pretty_init();\n\nPRETTY_EXPORT\nint pretty_exit();\n\nPRETTY_EXPORT\nvoid pretty_setup_sink_io();\n\nPRETTY_EXPORT\nint pretty_new_filter(PrettyFilter **filter);\n\nPRETTY_EXPORT\nvoid pretty_set_peaking_eq(PrettyFilter *filter, float f0, float bandwidth, float db_gain);\n\nPRETTY_EXPORT\nvoid pretty_set_low_shelf(PrettyFilter *filter, float f0, float S, float db_gain);\n\nPRETTY_EXPORT\nvoid pretty_set_high_shelf(PrettyFilter *filter, float f0, float S, float db_gain);\n\nPRETTY_EXPORT\nvoid pretty_enable_bypass(bool should_bypass);\n\nPRETTY_EXPORT\nvoid pretty_acquire_audio_data(FFTComplexCompat *data, unsigned int *N);\n\nPRETTY_EXPORT\nvoid pretty_release_audio_data();\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "gui/collisionmanager.cpp",
    "content": "#include \"collisionmanager.h\"\n#include \"eqhoverer.h\"\n#include \"filtercurve.h\"\n\n#include <QDebug>\n\nCollisionManager::CollisionManager() : numItems(0)\n{\n\n}\n\nvoid CollisionManager::addEqHoverer(EqHoverer *hover)\n{\n    Q_ASSERT(hover);\n    hoverItems[numItems++] = hover;\n}\n\nvoid CollisionManager::notifyFriends()\n{\n    Q_ASSERT(numItems == NUM_FILTERS);\n\n    for (int i = 0; i < numItems; i++)\n        hoverItems[i]->collisionStateChanged();\n}\n"
  },
  {
    "path": "gui/collisionmanager.h",
    "content": "#ifndef COLLISIONMANAGER_H\n#define COLLISIONMANAGER_H\n\n#define NUM_FILTERS 7\n\nclass EqHoverer;\nclass FilterCurve;\n\nclass CollisionManager\n{\npublic:\n    explicit CollisionManager();\n    void addEqHoverer(EqHoverer *hover);\n    void notifyFriends();\n\nprivate:\n    int numItems;\n    EqHoverer *hoverItems[NUM_FILTERS];\n};\n\n#endif // COLLISIONMANAGER_H\n"
  },
  {
    "path": "gui/curvepoint.cpp",
    "content": "#include \"curvepoint.h\"\n#include <QDebug>\n#include <QGraphicsItem>\n#include <QGraphicsScene>\n#include <QGraphicsSceneMouseEvent>\n#include <QPainter>\n#define RADIUS 40\n#define ESCALE 0.60\n\nCurvePoint::CurvePoint(QBrush normalBrush, QBrush lightBrush, QObject *parent)\n    : QObject(parent),\n      normalBrush(normalBrush),\n      lightBrush(lightBrush)\n{\n    setZValue(100001);\n    hide();\n    setFlags(GraphicsItemFlag::ItemIsSelectable | GraphicsItemFlag::ItemIsMovable);\n}\n\nQRectF CurvePoint::boundingRect() const\n{\n    return QRectF(-RADIUS/2, -RADIUS/2, RADIUS, RADIUS);\n}\n\nvoid CurvePoint::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)\n{\n    painter->setRenderHint(QPainter::Antialiasing, true);\n    painter->setPen(Qt::NoPen);\n\n    /* outer circle */\n    painter->setBrush(lightBrush);\n    painter->drawEllipse(-RADIUS/2, -RADIUS/2, RADIUS, RADIUS);\n\n    /* inner circle */\n    painter->scale(ESCALE, ESCALE);\n    painter->setBrush(normalBrush);\n    painter->drawEllipse(-RADIUS/2, -RADIUS/2, RADIUS, RADIUS);\n\n}\n\nint CurvePoint::type() const\n{\n    /* Make this type work with qgraphicsitem_cast */\n    return Type;\n}\n\nvoid CurvePoint::setResetPos(QPointF resetPoint)\n{\n    sceneResetPoint = resetPoint;\n    setPos(sceneResetPoint);\n}\n\nvoid CurvePoint::reset()\n{\n    setPos(sceneResetPoint);\n    emit pointPositionChanged(this);\n}\n\nvoid CurvePoint::mouseMoveEvent(QGraphicsSceneMouseEvent *event)\n{\n    QGraphicsItem::mouseMoveEvent(event);\n    if (scene() && event->type() == QGraphicsSceneMouseEvent::GraphicsSceneMouseMove) {\n\n        /* x bounds detection */\n        if (scene()->sceneRect().x() >= mapToScene(boundingRect().center()).x())\n            setPos(scene()->sceneRect().x(), pos().y());\n        else if (scene()->sceneRect().x() + scene()->width() <= mapToScene(boundingRect().center()).x())\n            setPos(scene()->sceneRect().x() + scene()->width(), pos().y());\n\n        /* y bounds detection */\n        if (scene()->sceneRect().y() >= mapToScene(boundingRect().center()).y())\n            setPos(pos().x(), scene()->sceneRect().y());\n        else if (scene()->sceneRect().y() + scene()->height() <= mapToScene(boundingRect().center()).y())\n            setPos(pos().x(), scene()->sceneRect().y() + scene()->sceneRect().height());\n\n        emit pointPositionChanged(this);\n    }\n}\n\nvoid CurvePoint::wheelEvent(QGraphicsSceneWheelEvent *event)\n{\n    QGraphicsItem::wheelEvent(event);\n    wheelDeltaSum += event->delta();\n    if (wheelDeltaSum >= 120) {\n        wheelDeltaSum = 0;\n        emit pointSlopeChanged(-1);\n    } else if (wheelDeltaSum <= -120) {\n        wheelDeltaSum = 0;\n        emit pointSlopeChanged(1);\n    }\n}\n"
  },
  {
    "path": "gui/curvepoint.h",
    "content": "#ifndef CURVEPOINT_H\n#define CURVEPOINT_H\n\n#include \"filtercurve.h\"\n#include <QBrush>\n#include <QGraphicsItem>\n#include <QObject>\n\nclass CurvePoint : public QObject, public QGraphicsItem\n{\n    Q_OBJECT\n    Q_INTERFACES(QGraphicsItem)\npublic:\n    explicit CurvePoint(QBrush normalBrush, QBrush lightBrush, QObject *parent = nullptr);\n    QRectF boundingRect() const override;\n    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;\n    enum { Type = UserType + 2 };\n    int type() const override;\n\n    void setResetPos(QPointF resetPoint);\n    void reset();\nprotected:\n    void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;\n    void wheelEvent(QGraphicsSceneWheelEvent *event) override;\n\nsignals:\n    void pointPositionChanged(CurvePoint *point);\n    void pointSlopeChanged(int delta);\n\nprivate:\n    QBrush normalBrush, lightBrush;\n    QPointF sceneResetPoint = QPointF(0, 0);\n    int wheelDeltaSum = 0;\n};\n\n#endif // CURVEPOINT_H\n"
  },
  {
    "path": "gui/eqhoverer.cpp",
    "content": "#include \"collisionmanager.h\"\n#include \"eqhoverer.h\"\n#include <QGraphicsScene>\n#include <QDebug>\n#include <QGraphicsSceneHoverEvent>\n#include <QPainter>\n\nstatic const unsigned int Default     = 1;\nstatic const unsigned int Collision   = 1 << 1;\nstatic const unsigned int Hover       = 1 << 2;\nstatic const unsigned int ContextMenu = 1 << 3;\n\nEqHoverer::EqHoverer(CollisionManager *mgr, FilterCurve *curve, CurvePoint *point, QObject *parent)\n    : QObject(parent), curve(curve), point(point), pointState(0), mgr(mgr)\n{\n    Q_ASSERT(curve);\n    Q_ASSERT(point);\n    setAcceptHoverEvents(true);\n    setFlag(QGraphicsItem::ItemHasNoContents, true);\n    reset();\n}\n\n\nQRectF EqHoverer::boundingRect() const\n{\n    QRectF r = curve->boundingRect();\n    if (scene()) {\n        r.setY(scene()->sceneRect().y());\n        r.setHeight(scene()->height());\n        return r;\n    } else\n        return QRectF(QPointF(0, 0), QPointF(0, 0));\n}\n\nvoid EqHoverer::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)\n{\n#if 0\n    // debugging boundingRect()\n    QPen wpen(Qt::white);\n    wpen.setWidth(3);\n    painter->save();\n    painter->setPen(wpen);\n    painter->drawRect(boundingRect());\n    painter->restore();\n#endif\n\n}\n\nint EqHoverer::type() const\n{\n    /* Make this type work with qgraphicsitem_cast */\n    return Type;\n}\n\nvoid EqHoverer::collisionStateChanged()\n{\n    pointState &= ~Collision;\n    for(QGraphicsItem *i : collidingItems()) {\n        EqHoverer *cc = qgraphicsitem_cast<EqHoverer*>(i);\n        if (cc && cc != this) {\n            if (isUnderMouse() || cc->isUnderMouse()) {\n                pointState |= Collision;\n                break;\n            }\n        }\n    }\n    maybeShowPoint();\n}\n\nvoid EqHoverer::hoverEnterEvent(QGraphicsSceneHoverEvent *event)\n{\n#if 0\n    qDebug() << \"hoverEnterEvent();\";\n#endif\n    pointState |= Hover;\n    mgr->notifyFriends();\n}\n\nvoid EqHoverer::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)\n{\n#if 0\n    qDebug() << \"hoverLeaveEvent();\";\n#endif\n    pointState &= ~Hover;\n    mgr->notifyFriends();\n}\n\nvoid EqHoverer::contextMenuToggle(bool on) {\n    int contextBit = (ContextMenu & static_cast<int>(on));\n    pointState |= contextBit;\n    pointState &= (~ContextMenu | contextBit);\n    maybeShowPoint();\n    mgr->notifyFriends();\n}\n\nvoid EqHoverer::reset()\n{\n    /* Order of these calls *does* matter here because\n     * resetting the point signals resync. */\n    point->reset();\n    curve->reset();\n    pointState |= Default;\n    maybeShowPoint();\n}\n\nvoid EqHoverer::maybeShowPoint()\n{\n    if (pointState == 0) {\n        point->hide();\n        curve->setColorState(DefaultState);\n    } else {\n        point->show();\n        curve->setColorState(PrettyState);\n    }\n}\n\nvoid EqHoverer::resync(FilterCurve *curve)\n{\n    pointState &= ~Default;\n    prepareGeometryChange();\n    mgr->notifyFriends();\n}\n"
  },
  {
    "path": "gui/eqhoverer.h",
    "content": "#ifndef EQHOVERER_H\n#define EQHOVERER_H\n\n#include \"curvepoint.h\"\n#include \"filtercurve.h\"\n#include <QGraphicsItem>\n#include <QObject>\n\nclass CollisionManager;\n\nclass EqHoverer : public QObject, public QGraphicsItem\n{\n    Q_OBJECT\n    Q_INTERFACES(QGraphicsItem)\npublic:\n    explicit EqHoverer(CollisionManager *mgr, FilterCurve *curve, CurvePoint *point, QObject *parent = nullptr);\n    QRectF boundingRect() const override;\n    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;\n    enum { Type  = UserType + 1 };\n    int type() const override;\n\n    void collisionStateChanged();\n    void contextMenuToggle(bool on = false);\n    void reset();\nprotected:\n    void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;\n    void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;\nprivate:\n    void maybeShowPoint();\n\npublic slots:\n    void resync(FilterCurve *curve);\n\nsignals:\n\npublic:\n    FilterCurve *curve;\n    CurvePoint *point;\n\nprivate:\n    unsigned int pointState;\n    CollisionManager *mgr;\n};\n\n#endif // EQHOVERER_H\n"
  },
  {
    "path": "gui/filtercurve.cpp",
    "content": "#include \"filtercurve.h\"\n#include <QGraphicsScene>\n\nFilterCurve::FilterCurve(QPen togglePen, QBrush toggleBrush, bool guiOnly) : filter(nullptr)\n{\n    colorState = 0;\n    pens[0] = defaultPen;\n    pens[1] = togglePen;\n    brushes[0] = defaultBrush;\n    brushes[1] = toggleBrush;\n\n    if (!guiOnly) {\n        /* initialize the filter */\n        PrettyShim::getInstance().new_filter(&filter);\n        PrettyShim::getInstance().set_peaking_eq(filter, 100, 100, 0);\n    }\n}\n\nFilterCurve::~FilterCurve()\n{\n\n}\n\nvoid FilterCurve::setColorState(ColorState state)\n{\n    colorState = state;\n    this->update();\n}\n\nconst QPen& FilterCurve::getActivePen() const\n{\n    return pens[colorState];\n}\n\nconst QBrush& FilterCurve::getActiveBrush() const\n{\n    return brushes[colorState];\n}\n"
  },
  {
    "path": "gui/filtercurve.h",
    "content": "#ifndef FILTERCURVE_H\n#define FILTERCURVE_H\n\n#include \"prettyshim.h\"\n\n#include <QGraphicsItem>\n#include <QObject>\n#include <QPen>\n#include <QBrush>\n\nstatic QPen defaultPen = Qt::NoPen;\nstatic QBrush defaultBrush = QBrush(QColor(73, 137, 196, 255));\n\ntypedef enum ColorState {\n    DefaultState = 0,\n    PrettyState,\n} ColorState;\n\nclass FilterCurve : public QGraphicsItem\n{\npublic:\n    explicit FilterCurve(QPen togglePen, QBrush toggleBrush, bool guiOnly = false);\n    virtual ~FilterCurve();\n    virtual QPointF controlPoint() const = 0;\n    virtual void reset() = 0;\n    void setColorState(ColorState state);\n\nsignals:\n\nprotected:\n    const QPen& getActivePen() const;\n    const QBrush& getActiveBrush() const;\n\nprotected:\n    ShimFilterPtr filter;\n\nprivate:\n    QPen pens[2];\n    QBrush brushes[2];\n    int colorState;\n};\n\n#endif // FILTERCURVE_H\n"
  },
  {
    "path": "gui/frequencytick.cpp",
    "content": "#include \"frequencytick.h\"\n#include \"macro.h\"\n\n#include <QDebug>\n#include <QFont>\n#include <QFontDatabase>\n#include <QPainter>\n\nFrequencyTick::FrequencyTick(QGraphicsScene *scene, int x, int y0, int y1, int frequency) :\n    scene(scene), x(x), y0(y0), y1(y1), frequency(frequency) {\n    QFont ff = QFontDatabase::systemFont(QFontDatabase::FixedFont);\n    ff.setPixelSize(20);\n\n    /* Setup line */\n    line = new QGraphicsLineItem(x, y0, x, y1);\n    line->setPen(QPen(Qt::white));\n    line->setOpacity(0.3);\n\n    /* Setup text */\n    text = new QGraphicsTextItem(toQString());\n    text->setDefaultTextColor(Qt::white);\n    text->setFont(ff);\n    auto fm = QFontMetrics(text->font());\n    if (frequency == FMAX) {\n        int offset = fm.horizontalAdvance(QLatin1Char('K')) * 4;\n        text->setPos(x - offset, 30);\n    } else if (frequency == FMIN) {\n        text->setPos(x, 30);\n    } else {\n        int offset = fm.horizontalAdvance(QLatin1Char('K')) * toQString().length() + 7;\n        text->setPos(x - offset / 2, 30);\n    }\n    text->setZValue(1000);\n\n    scene->addItem(text);\n    scene->addItem(line);\n}\n\nFrequencyTick::~FrequencyTick()\n{\n    delete line;\n    delete text;\n}\n\nQString FrequencyTick::toQString()\n{\n    if (frequency >= 1000)\n        return QString::number(frequency / 1000) + QString(\"K\");\n    else\n        return QString::number(frequency);\n}\n\nqreal FrequencyTick::getFrequency() const\n{\n    return static_cast<qreal>(frequency);\n}\n\nqreal FrequencyTick::getX() const\n{\n    return x;\n}\n"
  },
  {
    "path": "gui/frequencytick.h",
    "content": "#ifndef FREQUENCYTICK_H\n#define FREQUENCYTICK_H\n\n#include <QGraphicsScene>\n#include <QGraphicsLineItem>\n\nclass FrequencyTick\n{\npublic:\n    FrequencyTick(QGraphicsScene *scene, int x, int y0, int y1, int frequency);\n    ~FrequencyTick();\n    QString toQString();\n    qreal getFrequency() const;\n    qreal getX() const;\n\nprivate:\n    QGraphicsScene *scene;\n    int x, y0, y1;\n    int frequency;\n    QGraphicsLineItem *line;\n    QGraphicsTextItem *text;\n};\n\n#endif // FREQUENCYTICK_H\n"
  },
  {
    "path": "gui/frequencytickbuilder.cpp",
    "content": "#include \"frequencytick.h\"\n#include \"frequencytickbuilder.h\"\n#include \"macro.h\"\n\n#include <QGraphicsScene>\n\nFrequencyTickBuilder::FrequencyTickBuilder(QGraphicsScene *scene, int width, int xmin, int xmax, int ymin, int ymax)\n{\n    /* y-axis frequency markers */\n    int tickWidth = width / (NUM_TICKS - 1);\n    tick[0] = new FrequencyTick(scene, xmin,                 ymin, ymax, F1);\n    tick[1] = new FrequencyTick(scene, xmin + tickWidth * 1, ymin, ymax, F2);\n    tick[2] = new FrequencyTick(scene, xmin + tickWidth * 2, ymin, ymax, F3);\n    tick[3] = new FrequencyTick(scene, xmin + tickWidth * 3, ymin, ymax, F4);\n    tick[4] = new FrequencyTick(scene, xmin + tickWidth * 4, ymin, ymax, F5);\n    tick[5] = new FrequencyTick(scene, xmin + tickWidth * 5, ymin, ymax, F6);\n    tick[6] = new FrequencyTick(scene, xmin + tickWidth * 6, ymin, ymax, F7);\n    tick[7] = new FrequencyTick(scene, xmin + tickWidth * 7, ymin, ymax, F8);\n    tick[8] = new FrequencyTick(scene, xmin + tickWidth * 8, ymin, ymax, F9);\n    tick[9] = new FrequencyTick(scene, xmax,                 ymin, ymax, F10);\n}\n\nqreal FrequencyTickBuilder::lerpTick(qreal x)\n{\n    FrequencyTick *tp, *tq;\n    for (int i = 1; i < NUM_TICKS; i++) {\n        tp = tick[i-1];\n        tq = tick[i];\n        if (x >= tp->getX() && x <= tq->getX())\n            break;\n    }\n    return LINEAR_REMAP(x, tp->getX(), tq->getX(), tp->getFrequency(), tq->getFrequency());\n}\n\nqreal FrequencyTickBuilder::unlerpTick(qreal f)\n{\n    FrequencyTick *tp, *tq;\n    for (int i = 1; i < NUM_TICKS; i++) {\n        tp = tick[i-1];\n        tq = tick[i];\n        if (f >= tp->getFrequency() && f <= tq->getFrequency())\n            break;\n    }\n    return LINEAR_REMAP(f, tp->getFrequency(), tq->getFrequency(), tp->getX(), tq->getX());\n}\n\nFrequencyTickBuilder::~FrequencyTickBuilder()\n{\n    for (int i = 0; i < NUM_TICKS; i++)\n        delete tick[i];\n}\n"
  },
  {
    "path": "gui/frequencytickbuilder.h",
    "content": "#ifndef FREQUENCYTICKBUILDER_H\n#define FREQUENCYTICKBUILDER_H\n\n#include <QtCore>\n#include <QGraphicsScene>\n#define NUM_TICKS 10\n\nclass FrequencyTick;\n\nclass FrequencyTickBuilder\n{\npublic:\n    FrequencyTickBuilder(QGraphicsScene *scene, int width, int xmin, int xmax, int ymin, int ymax);\n    ~FrequencyTickBuilder();\n    qreal lerpTick(qreal x);\n    qreal unlerpTick(qreal f);\nprivate:\n    FrequencyTick *tick[NUM_TICKS];\n};\n\n#endif // FREQUENCYTICKBUILDER_H\n"
  },
  {
    "path": "gui/gui.cpp",
    "content": "#include \"collisionmanager.h\"\n#include \"curvepoint.h\"\n#include \"eqhoverer.h\"\n#include \"frequencytick.h\"\n#include \"frequencytickbuilder.h\"\n#include \"gui.h\"\n#include \"highshelfcurve.h\"\n#include \"lowshelfcurve.h\"\n#include \"macro.h\"\n#include \"peakingcurve.h\"\n#include \"prettygraphicsscene.h\"\n#include \"prettyshim.h\"\n#include \"spectrumanalyzer.h\"\n#include \"ui_gui.h\"\n\n#include <QBrush>\n#include <QComboBox>\n#include <QDebug>\n#include <QGraphicsEllipseItem>\n#include <QGraphicsLineItem>\n#include <QPainter>\n#include <QPen>\n#include <QRadialGradient>\n#include <QResizeEvent>\n#include <QScrollBar>\n#include <QSystemTrayIcon>\n#include <QtMath>\n\n#define WIDTH 2000\n#define HEIGHT 1000\n#define XMIN -1000\n#define YMIN -500\n#define XMAX (XMIN + WIDTH)\n#define YMAX (YMIN + HEIGHT)\n\nstatic QPen GreenFilterPen = QPen(QColor(138, 237, 152), 3);\nstatic QBrush GreenFilterBrush = QBrush(QColor(31, 204, 57, 127));\nstatic QBrush GreenInnerRadiusBrush = QBrush(QColor(31, 204, 57));\nstatic QBrush GreenOuterRadiusBrush = QBrush(QColor(31, 204, 57, 50));\n\nstatic QPen RedFilterPen = QPen(QColor(231, 123, 131), 3);\nstatic QBrush RedFilterBrush = QBrush(QColor(236, 85, 95, 127));\nstatic QBrush RedInnerRadiusBrush = QBrush(QColor(223, 59, 70));\nstatic QBrush RedOuterRadiusBRush = QBrush(QColor(233, 59, 70, 50));\n\nstatic QPen YellowFilterPen = QPen(QColor(231, 229, 123), 3);\nstatic QBrush YellowFilterBrush = QBrush(QColor(236, 225, 85, 127));\nstatic QBrush YellowInnerRadiusBrush = QBrush(QColor(236, 225, 85));\nstatic QBrush YellowOuterRadiusBrush = QBrush(QColor(236, 225, 85, 50));\n\nstatic QPen PinkFilterPen = QPen(QColor(231, 123, 217), 3);\nstatic QBrush PinkFilterBrush = QBrush(QColor(236, 85, 216, 127));\nstatic QBrush PinkInnerRadiusBrush = QBrush(QColor(236, 85, 216));\nstatic QBrush PinkOuterRadiusBrush = QBrush(QColor(236, 85, 216, 50));\n\nstatic QPen BlueFilterPen = QPen(QColor(123, 231, 191), 3);\nstatic QBrush BlueFilterBrush = QBrush(QColor(85, 236, 193, 127));\nstatic QBrush BlueInnerRadiusBrush = QBrush(QColor(85, 236, 193));\nstatic QBrush BlueOuterRadiusBrush = QBrush(QColor(85, 236, 193, 50));\n\nstatic QPen OrangeFilterPen = QPen(QColor(231, 175, 123), 3);\nstatic QBrush OrangeFilterBrush = QBrush(QColor(236, 151, 85, 127));\nstatic QBrush OrangeInnerRadiusBrush = QBrush(QColor(236, 151, 85));\nstatic QBrush OrangeOuterRadiusBrush = QBrush(QColor(236, 151, 85, 50));\n\nstatic QPen PurpleFilterPen = QPen(QColor(177, 123, 231), 3);\nstatic QBrush PurpleFilterBrush = QBrush(QColor(159, 85, 236, 127));\nstatic QBrush PurpleInnerRadiusBrush = QBrush(QColor(159, 85, 236));\nstatic QBrush PurpleOuterRadiusBrush = QBrush(QColor(159, 85, 236, 50));\n\nGui::Gui(QWidget *parent)\n    : QDialog(parent)\n    , ui(new Ui::Gui)\n{\n\n    PrettyShim::getInstance().init();\n    PrettyShim::getInstance().setup_sink_io();\n    ui->setupUi(this);\n\n    QRadialGradient backgroundGradient(QPoint(0, 0), WIDTH);\n    backgroundGradient.setSpread(QGradient::ReflectSpread);\n    backgroundGradient.setColorAt(0.15, QColor(4, 38, 69));\n    backgroundGradient.setColorAt(0.85, QColor(6, 17, 43));\n    scene = new PrettyGraphicsScene(ui->graphicsView);\n    scene->setSceneRect(XMIN, YMIN, WIDTH, HEIGHT);\n    scene->setBackgroundBrush(QBrush(backgroundGradient));\n\n    ui->graphicsView->setRenderHint(QPainter::Antialiasing);\n    ui->graphicsView->setRenderHint(QPainter::TextAntialiasing);\n    ui->graphicsView->setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);\n    ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);\n    ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);\n    ui->graphicsView->setResizeAnchor(QGraphicsView::AnchorViewCenter);\n    ui->graphicsView->setScene(scene);\n\n    /* x-axis (boost/cut) */\n    auto xaxis = new QGraphicsLineItem(XMIN, scene->sceneRect().center().y(), XMAX, scene->sceneRect().center().y());\n    xaxis->setPen(QPen(Qt::white));\n    xaxis->setOpacity(0.3);\n    scene->addItem(xaxis);\n\n    /* x-axis frequency markers */\n    xTickBuilder = new FrequencyTickBuilder(scene, WIDTH, XMIN, XMAX, YMIN, YMAX);\n    addSpectrumAnalyzer();\n\n    collisionMgr = new CollisionManager();\n\n    addLowShelf (GreenFilterPen,  GreenFilterBrush,  GreenInnerRadiusBrush,  GreenOuterRadiusBrush);\n    addHighShelf(OrangeFilterPen, OrangeFilterBrush, OrangeInnerRadiusBrush, OrangeOuterRadiusBrush);\n\n    addPeakingEq(150,  RedFilterPen, \tRedFilterBrush, \tRedInnerRadiusBrush, \tRedOuterRadiusBRush);\n    addPeakingEq(350,  YellowFilterPen, YellowFilterBrush, \tYellowInnerRadiusBrush, YellowOuterRadiusBrush);\n    addPeakingEq(750,  PinkFilterPen, \tPinkFilterBrush, \tPinkInnerRadiusBrush, \tPinkOuterRadiusBrush);\n    addPeakingEq(1500, BlueFilterPen, \tBlueFilterBrush, \tBlueInnerRadiusBrush, \tBlueOuterRadiusBrush);\n    addPeakingEq(3500, PurpleFilterPen, PurpleFilterBrush, \tPurpleInnerRadiusBrush, PurpleOuterRadiusBrush);\n\n    connectBypassButton();\n    maybeShowInSystemTray();\n}\n\n//=============================================================================\n\nvoid Gui::addFilterItem(QGraphicsItemGroup *group, FilterCurve *curve, CurvePoint *point, EqHoverer *hover)\n{\n    Q_ASSERT(itemCount < NUM_FILTERS);\n    items[itemCount].group = group;\n    items[itemCount].curve = curve;\n    items[itemCount].point = point;\n    items[itemCount].hover = hover;\n    itemCount++;\n}\n\nvoid Gui::addPeakingEq(int frequency, QPen curvePen, QBrush filterBrush, QBrush innerRadiusBrush, QBrush outerRadiusBrush) {\n    Q_ASSERT(collisionMgr);\n    PeakingCurve *curve = new PeakingCurve(curvePen, filterBrush);\n    CurvePoint *point = new CurvePoint(innerRadiusBrush, outerRadiusBrush);\n    EqHoverer *hover = new EqHoverer(collisionMgr, curve, point);\n    collisionMgr->addEqHoverer(hover);\n\n    /* point signals */\n    QObject::connect(point,\n                  SIGNAL(pointPositionChanged(CurvePoint*)),\n                  curve,\n                  SLOT(pointPositionChanged(CurvePoint*)));\n    QObject::connect(point,\n                  SIGNAL(pointSlopeChanged(int)),\n                  curve,\n                  SLOT(pointSlopeChanged(int)));\n\n    /* curve signals */\n    QObject::connect(curve,\n                  SIGNAL(resync(FilterCurve*)),\n                  hover,\n                  SLOT(resync(FilterCurve*)));\n    QObject::connect(curve,\n                     SIGNAL(filterParamsChanged(ShimFilterPtr, PeakingCurve*)),\n                     this,\n                     SLOT(peakingFilterParamsChanged(ShimFilterPtr, PeakingCurve*)));\n\n    QGraphicsItemGroup *group = new QGraphicsItemGroup();\n    group->addToGroup(curve);\n    group->addToGroup(hover);\n    group->setHandlesChildEvents(false);\n    group->setPos(xTickBuilder->unlerpTick(frequency) - group->boundingRect().width() / 2, 0);\n    point->setResetPos(curve->controlPoint());\n    scene->addItem(group);\n    scene->addItem(point);\n    addFilterItem(group, curve, point, hover);\n}\n\nvoid Gui::addLowShelf(QPen curvePen, QBrush filterBrush, QBrush innerRadiusBrush, QBrush outerRadiusBrush)\n{\n    Q_ASSERT(collisionMgr);\n    ShelfCurve *curve = new LowShelfCurve(curvePen, filterBrush);\n    CurvePoint *point = new CurvePoint(innerRadiusBrush, outerRadiusBrush);\n    EqHoverer *hover = new EqHoverer(collisionMgr, curve, point);\n    collisionMgr->addEqHoverer(hover);\n\n    /* point signals */\n    QObject::connect(point,\n                     SIGNAL(pointPositionChanged(CurvePoint*)),\n                     curve,\n                     SLOT(pointPositionChanged(CurvePoint*)));\n    QObject::connect(point,\n                     SIGNAL(pointSlopeChanged(int)),\n                     curve,\n                     SLOT(pointSlopeChanged(int)));\n\n    /* curve signals */\n    QObject::connect(curve,\n                  SIGNAL(resync(FilterCurve*)),\n                  hover,\n                  SLOT(resync(FilterCurve*)));\n    QObject::connect(curve,\n                     SIGNAL(filterParamsChanged(ShimFilterPtr, ShelfCurve*)),\n                     this,\n                     SLOT(lowshelfFilterParamsChanged(ShimFilterPtr, ShelfCurve*)));\n\n    QGraphicsItemGroup *group = new QGraphicsItemGroup();\n    group->setHandlesChildEvents(false);\n    group->addToGroup(curve);\n    group->addToGroup(hover);\n    group->setPos(XMIN, 0);\n    point->setResetPos(curve->controlPoint());\n    scene->addItem(group);\n    scene->addItem(point);\n    addFilterItem(group, curve, point, hover);\n}\n\nvoid Gui::addHighShelf(QPen curvePen, QBrush filterBrush, QBrush innerRadiusBrush, QBrush outerRadiusBrush)\n{\n    Q_ASSERT(collisionMgr);\n    ShelfCurve *curve = new HighShelfCurve(curvePen, filterBrush);\n    CurvePoint *point = new CurvePoint(innerRadiusBrush, outerRadiusBrush);\n    EqHoverer *hover = new EqHoverer(collisionMgr, curve, point);\n    collisionMgr->addEqHoverer(hover);\n\n    /* point signals */\n    QObject::connect(point,\n                     SIGNAL(pointPositionChanged(CurvePoint*)),\n                     curve,\n                     SLOT(pointPositionChanged(CurvePoint*)));\n    QObject::connect(point,\n                     SIGNAL(pointSlopeChanged(int)),\n                     curve,\n                     SLOT(pointSlopeChanged(int)));\n\n    /* curve signals */\n    QObject::connect(curve,\n                  SIGNAL(resync(FilterCurve*)),\n                  hover,\n                  SLOT(resync(FilterCurve*)));\n    QObject::connect(curve,\n                     SIGNAL(filterParamsChanged(ShimFilterPtr, ShelfCurve*)),\n                     this,\n                     SLOT(highshelfFilterParamsChanged(ShimFilterPtr, ShelfCurve*)));\n\n    QGraphicsItemGroup *group = new QGraphicsItemGroup();\n    group->setHandlesChildEvents(false);\n    group->addToGroup(curve);\n    group->addToGroup(hover);\n    group->setPos(XMAX, 0);\n    point->setResetPos(curve->controlPoint());\n    scene->addItem(group);\n    scene->addItem(point);\n    addFilterItem(group, curve, point, hover);\n}\n\nvoid Gui::peakingFilterParamsChanged(ShimFilterPtr filter, PeakingCurve *curve)\n{\n    QPointF c = curve->controlPoint();\n    QRectF r = curve->sceneBoundingRect();\n    qreal f0 = xTickBuilder->lerpTick(c.x());\n    qreal bw = xTickBuilder->lerpTick(r.bottomRight().x()) / xTickBuilder->lerpTick(r.bottomLeft().x()) / 2;\n    qreal db_gain = LINEAR_REMAP(\n                c.y(),\n                scene->sceneRect().y(), scene->sceneRect().y() + scene->sceneRect().height(),\n                DB_GAIN_MAX, -DB_GAIN_MAX);\n    PrettyShim::getInstance().set_peaking_eq(filter, f0, bw, db_gain);\n}\n\nvoid Gui::lowshelfFilterParamsChanged(ShimFilterPtr filter, ShelfCurve *curve)\n{\n    QPointF c = curve->controlPoint();\n    qreal f0 = xTickBuilder->lerpTick(c.x());\n    qreal S =  curve->slope();\n    qreal db_gain = LINEAR_REMAP(\n                c.y(),\n                scene->sceneRect().y(), scene->sceneRect().y() + scene->sceneRect().height(),\n                DB_GAIN_MAX, -DB_GAIN_MAX);\n    PrettyShim::getInstance().set_low_shelf(filter, f0, S, db_gain);\n}\n\nvoid Gui::highshelfFilterParamsChanged(ShimFilterPtr filter, ShelfCurve *curve)\n{\n\n    QPointF c = curve->controlPoint();\n    qreal f0 = xTickBuilder->lerpTick(c.x());\n    qreal S =  curve->slope();\n    qreal db_gain = LINEAR_REMAP(\n                c.y(),\n                scene->sceneRect().y(), scene->sceneRect().y() + scene->sceneRect().height(),\n                DB_GAIN_MAX, -DB_GAIN_MAX);\n    PrettyShim::getInstance().set_high_shelf(filter, f0, S, db_gain);\n}\n\nvoid Gui::connectBypassButton()\n{\n    QObject::connect(ui->pushButton, &QAbstractButton::clicked, this, [&](bool checked){\n        PrettyShim::getInstance().enable_bypass(checked);\n    });\n}\n\nvoid Gui::addSpectrumAnalyzer()\n{\n    Q_ASSERT(xTickBuilder);\n    spectrumAnalyzer = new SpectrumAnalyzer(xTickBuilder);\n    spectrumAnalyzer->setPos(-scene->sceneRect().width() / 2, -scene->sceneRect().height() / 8);\n    scene->addItem(spectrumAnalyzer);\n    spectrumUpdateTimer = new QTimer(this);\n    spectrumUpdateTimer->setInterval(1000 / 60);\n    QObject::connect(spectrumUpdateTimer, &QTimer::timeout, [&]() {\n        spectrumAnalyzer->updateFrameDelta();\n        spectrumAnalyzer->update();\n    });\n    spectrumUpdateTimer->start();\n}\n\n//=============================================================================\n\nvoid Gui::maybeShowInSystemTray()\n{\n    if (!QSystemTrayIcon::isSystemTrayAvailable())\n        return;\n\n    trayMenu = new QMenu();\n    quitAct = new QAction(\"Quit\");\n    trayMenu->addAction(quitAct);\n    QObject::connect(quitAct, &QAction::triggered, qApp, QCoreApplication::quit);\n\n    trayIcon = new QSystemTrayIcon(this);\n    trayIcon->setContextMenu(trayMenu);\n    trayIcon->setIcon(QIcon(\":/images/images/prettyeq.png\"));\n    trayIcon->setContextMenu(trayMenu);\n    QObject::connect(trayIcon,\n                     SIGNAL(activated(QSystemTrayIcon::ActivationReason)),\n                     this,\n                     SLOT(trayActivated(QSystemTrayIcon::ActivationReason)));\n\n    trayIcon->show();\n}\n\n//=============================================================================\n\nvoid Gui::resizeEvent(QResizeEvent *event) {\n    QDialog::resizeEvent(event);\n    qDebug() << this->size();\n    if (isVisible())\n        ui->graphicsView->fitInView(scene->sceneRect(), Qt::KeepAspectRatio);\n    //ui->graphicsView->centerOn(0, 0);\n}\n\nvoid Gui::showEvent(QShowEvent *event)\n{\n    ui->graphicsView->fitInView(scene->sceneRect(), Qt::KeepAspectRatio);\n}\n\nvoid Gui::on_actionQuit_triggered()\n{\n    QCoreApplication::exit();\n}\n\nvoid Gui::trayActivated(QSystemTrayIcon::ActivationReason reason)\n{\n    this->show();\n    this->raise();\n    this->activateWindow();\n}\n\n//=============================================================================\n\nGui::~Gui()\n{\n    delete collisionMgr;\n    for (int i = 0; i < NUM_FILTERS; i++) {\n        delete items[i].curve;\n        delete items[i].hover;\n        delete items[i].point;\n        delete items[i].group;\n    }\n\n    if (QSystemTrayIcon::isSystemTrayAvailable()) {\n        delete trayIcon;\n        delete trayMenu;\n        delete quitAct;\n    }\n    spectrumUpdateTimer->stop();\n    delete spectrumUpdateTimer;\n    delete spectrumAnalyzer;\n    delete ui;\n}\n\nvoid Gui::cleanup()\n{\n    PrettyShim::getInstance().exit();\n}\n"
  },
  {
    "path": "gui/gui.h",
    "content": "#ifndef GUI_H\n#define GUI_H\n\n#include \"prettyshim.h\"\n#include <QDialog>\n#include <QGraphicsScene>\n#include <QGraphicsView>\n#include <QMenu>\n#include <QMetaType>\n#include <QSystemTrayIcon>\n\n#define DB_GAIN_MAX 12\n#define NUM_FILTERS 7\n\nQT_BEGIN_NAMESPACE\nnamespace Ui { class Gui; }\nQT_END_NAMESPACE\n\nclass CollisionManager;\nclass CurvePoint;\nclass EqHoverer;\nclass FilterCurve;\nclass FrequencyTick;\nclass FrequencyTickBuilder;\nclass PeakingCurve;\nclass ShelfCurve;\nclass SpectrumAnalyzer;\n\ntypedef struct FilterItem {\n    QGraphicsItemGroup *group;\n    FilterCurve *curve;\n    CurvePoint *point;\n    EqHoverer *hover;\n} FilterItem;\n\nclass Gui : public QDialog\n{\n    Q_OBJECT\n\npublic:\n    Gui(QWidget *parent = nullptr);\n    ~Gui();\n\n/* Equalizer slots */\npublic slots:\n    void cleanup();\n    void peakingFilterParamsChanged(ShimFilterPtr filter, PeakingCurve *curve);\n    void lowshelfFilterParamsChanged(ShimFilterPtr filter, ShelfCurve *curve);\n    void highshelfFilterParamsChanged(ShimFilterPtr filter, ShelfCurve *curve);\n\n/* Menu Bar slots */\nprivate slots:\n    void on_actionQuit_triggered();\n\n/* System Tray slots */\nprivate slots:\n    void trayActivated(QSystemTrayIcon::ActivationReason reason);\n\nprotected:\n    void resizeEvent(QResizeEvent *event) override;\n    void showEvent(QShowEvent *event) override;\n\n\nprivate:\n    void addFilterItem(QGraphicsItemGroup *group, FilterCurve *curve, CurvePoint *point, EqHoverer *hover);\n    qreal lerpTick(qreal x);\n    qreal unlerpTick(qreal f);\n    void addLowShelf(QPen curvePen, QBrush filterBrush, QBrush innerRadiusBrush, QBrush outerRadiusBrush);\n    void addHighShelf(QPen curvePen, QBrush filterBrush, QBrush innerRadiusBrush, QBrush outerRadiusBrush);\n    void addPeakingEq(int frequency, QPen curvePen, QBrush filterBrush, QBrush innerRadiusBrush, QBrush outerRadiusBrush);\n    void connectBypassButton();\n    void addSpectrumAnalyzer();\n\n    void maybeShowInSystemTray();\n\nprivate:\n    Ui::Gui *ui;\n    QGraphicsScene *scene;\n    CollisionManager *collisionMgr = nullptr;;\n    FilterItem items[NUM_FILTERS];\n    SpectrumAnalyzer *spectrumAnalyzer = nullptr;\n    FrequencyTickBuilder *xTickBuilder = nullptr;\n    QTimer *spectrumUpdateTimer = nullptr;\n    int itemCount = 0;\n\n    /* System tray stuff. */\n    QMenu *trayMenu;\n    QAction *quitAct;\n    QSystemTrayIcon *trayIcon;\n};\n#endif // GUI_H\n"
  },
  {
    "path": "gui/gui.pro",
    "content": "QT       += core gui\n\ngreaterThan(QT_MAJOR_VERSION, 4): QT += widgets\n\nCONFIG += c++11\n\n# The following define makes your compiler emit warnings if you use\n# any Qt feature that has been marked deprecated (the exact warnings\n# depend on your compiler). Please consult the documentation of the\n# deprecated API in order to know how to port your code away from it.\nDEFINES += QT_DEPRECATED_WARNINGS\n\n# You can also make your code fail to compile if it uses deprecated APIs.\n# In order to do so, uncomment the following line.\n# You can also select to disable deprecated APIs only up to a certain version of Qt.\n#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0\n\nSOURCES += \\\n    collisionmanager.cpp \\\n    curvepoint.cpp \\\n    eqhoverer.cpp \\\n    filtercurve.cpp \\\n    frequencytick.cpp \\\n    frequencytickbuilder.cpp \\\n    gui.cpp \\\n    highshelfcurve.cpp \\\n    lowshelfcurve.cpp \\\n    main.cpp \\\n    peakingcurve.cpp \\\n    prettygraphicsscene.cpp \\\n    runguard.cpp \\\n    shelfcurve.cpp \\\n    spectrumanalyzer.cpp \\\n    unixsignalhandler.cpp\n\nHEADERS += \\\n    collisionmanager.h \\\n    curvepoint.h \\\n    eqhoverer.h \\\n    filtercurve.h \\\n    frequencytick.h \\\n    frequencytickbuilder.h \\\n    gui.h \\\n    highshelfcurve.h \\\n    lowshelfcurve.h \\\n    macro.h \\\n    peakingcurve.h \\\n    prettygraphicsscene.h \\\n    prettyshim.h \\\n    ringbuffer.h \\\n    runguard.h \\\n    shelfcurve.h \\\n    spectrumanalyzer.h \\\n    unixsignalhandler.h\n\nFORMS += \\\n    gui.ui\n\nQMAKE_CXXFLAGS_WARN_ON += -Wno-unused-parameter\nINCLUDEPATH += '../equalizer'\nunix:LIBS += -L ../equalizer -lequalizer -lm -lpulse -lpthread -ffast-math -fopenmp\nTARGET = ../prettyeq\n\n# Default rules for deployment.\nqnx: target.path = /tmp/$${TARGET}/bin\nelse: unix:!android: target.path = /opt/$${TARGET}/bin\n!isEmpty(target.path): INSTALLS += target\n\nRESOURCES += \\\n    resources.qrc\n"
  },
  {
    "path": "gui/gui.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>Gui</class>\n <widget class=\"QDialog\" name=\"Gui\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>1152</width>\n    <height>648</height>\n   </rect>\n  </property>\n  <property name=\"minimumSize\">\n   <size>\n    <width>1152</width>\n    <height>648</height>\n   </size>\n  </property>\n  <property name=\"maximumSize\">\n   <size>\n    <width>1152</width>\n    <height>648</height>\n   </size>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Gui</string>\n  </property>\n  <property name=\"autoFillBackground\">\n   <bool>false</bool>\n  </property>\n  <property name=\"styleSheet\">\n   <string notr=\"true\">QWidget {\n  background: #25282b;\n}\n\nQPushButton {\n  color: white\n}</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout_3\">\n   <item>\n    <widget class=\"QMenuBar\" name=\"menubar\">\n     <property name=\"enabled\">\n      <bool>true</bool>\n     </property>\n     <widget class=\"QMenu\" name=\"menuOptions\">\n      <property name=\"title\">\n       <string>Options</string>\n      </property>\n      <addaction name=\"actionQuit\"/>\n     </widget>\n     <addaction name=\"menuOptions\"/>\n    </widget>\n   </item>\n   <item alignment=\"Qt::AlignRight\">\n    <widget class=\"QPushButton\" name=\"pushButton\">\n     <property name=\"minimumSize\">\n      <size>\n       <width>115</width>\n       <height>0</height>\n      </size>\n     </property>\n     <property name=\"maximumSize\">\n      <size>\n       <width>100</width>\n       <height>16777215</height>\n      </size>\n     </property>\n     <property name=\"font\">\n      <font>\n       <family>Source Code Pro Semibold</family>\n       <weight>75</weight>\n       <bold>true</bold>\n      </font>\n     </property>\n     <property name=\"layoutDirection\">\n      <enum>Qt::LeftToRight</enum>\n     </property>\n     <property name=\"autoFillBackground\">\n      <bool>false</bool>\n     </property>\n     <property name=\"text\">\n      <string>BYPASS</string>\n     </property>\n     <property name=\"checkable\">\n      <bool>true</bool>\n     </property>\n     <property name=\"checked\">\n      <bool>false</bool>\n     </property>\n     <property name=\"flat\">\n      <bool>true</bool>\n     </property>\n    </widget>\n   </item>\n   <item>\n    <widget class=\"QGraphicsView\" name=\"graphicsView\">\n     <property name=\"acceptDrops\">\n      <bool>false</bool>\n     </property>\n    </widget>\n   </item>\n  </layout>\n  <action name=\"actionQuit\">\n   <property name=\"text\">\n    <string>Quit</string>\n   </property>\n  </action>\n </widget>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "gui/highshelfcurve.cpp",
    "content": "#include \"highshelfcurve.h\"\n\nHighShelfCurve::HighShelfCurve(QPen pen, QBrush brush, bool guiOnly, QObject *parent)\n    : ShelfCurve(pen, brush, guiOnly, parent)\n{\n    reset();\n    updateCurveGeometry();\n}\n\nHighShelfCurve::~HighShelfCurve()\n{\n\n}\n\nQPointF HighShelfCurve::clampP2() const\n{\n    return (p2.x() > 0) ? QPointF(0, p2.y()) : p2;\n}\n\nvoid HighShelfCurve::pointSlopeChanged(int delta)\n{\n    bool reduce = delta > 0;\n    int offset = -delta * SLOPE_DELTA;\n    if ((qAbs(p3.x() - p2.x()) - offset < SLOPE_MAX  || !reduce) && (p2.x() - offset > p3.x() || reduce)) {\n        p2.setX(p2.x() - offset);\n        updateCurveGeometry();\n        this->update();\n        emit resync(this);\n        emit filterParamsChanged(filter, this);\n    }\n}\n\nvoid HighShelfCurve::reset()\n{\n    p0 = QPointF(0, 0);\n    p3 = QPointF(-330, 0);\n    p1 = QPointF((p3.x() - p0.x()) * 0.3, p0.y());\n    p2 = QPointF((p1.x() + p3.x())/ 2, p3.y());\n    updateCurveGeometry();\n    this->update();\n}\n"
  },
  {
    "path": "gui/highshelfcurve.h",
    "content": "#ifndef HIGHSHELFCURVE_H\n#define HIGHSHELFCURVE_H\n\n#include \"shelfcurve.h\"\n\nclass HighShelfCurve : public ShelfCurve\n{\npublic:\n    explicit HighShelfCurve(QPen pen, QBrush brush, bool guiOnly = false, QObject *parent = nullptr);\n    ~HighShelfCurve();\n\n// ShelfCurve interface\nprotected:\n    QPointF clampP2() const override;\n\n    // ShelfCurve interface\npublic slots:\n    void pointSlopeChanged(int delta) override;\n\n    // FilterCurve interface\npublic:\n    void reset() override;\n};\n\n#endif // HIGHSHELFCURVE_H\n"
  },
  {
    "path": "gui/lowshelfcurve.cpp",
    "content": "#include \"lowshelfcurve.h\"\n\nLowShelfCurve::LowShelfCurve(QPen pen, QBrush brush, bool guiOnly, QObject *parent)\n    : ShelfCurve(pen, brush, guiOnly, parent)\n{\n    reset();\n    updateCurveGeometry();\n}\n\nLowShelfCurve::~LowShelfCurve()\n{\n\n}\n\nQPointF LowShelfCurve::clampP2() const\n{\n    return p2.x() < 0 ? QPointF(0, p2.y()) : p2;\n}\n\nvoid LowShelfCurve::pointSlopeChanged(int delta)\n{\n    bool reduce = delta > 0;\n    int offset = delta * SLOPE_DELTA;\n    if ((p3.x() - p2.x() - offset < SLOPE_MAX  || !reduce) && (p2.x() - offset <= p3.x() || reduce)) {\n        p2.setX(p2.x() - offset);\n        updateCurveGeometry();\n        this->update();\n        emit resync(this);\n        emit filterParamsChanged(filter, this);\n    }\n}\n\nvoid LowShelfCurve::reset()\n{\n    p0 = QPointF(0, 0);\n    p3 = QPointF(330, 0);\n    p1 = QPointF((p3.x() - p0.x()) * 0.3, p0.y());\n    p2 = QPointF((p1.x() + p3.x())/ 2, p3.y());\n    updateCurveGeometry();\n    this->update();\n}\n"
  },
  {
    "path": "gui/lowshelfcurve.h",
    "content": "#ifndef LOWSHELFCURVE_H\n#define LOWSHELFCURVE_H\n\n#include \"shelfcurve.h\"\n#include <QBrush>\n#include <QPen>\n\nclass LowShelfCurve : public ShelfCurve\n{\npublic:\n    explicit LowShelfCurve(QPen pen, QBrush brush, bool guiOnly = false, QObject *parent = nullptr);\n    ~LowShelfCurve();\n\nprotected:\n    QPointF clampP2() const override;\n\npublic slots:\n    void pointSlopeChanged(int delta) override;\n\n    // FilterCurve interface\npublic:\n    void reset() override;\n};\n\n#endif // LOWSHELFCURVE_H\n"
  },
  {
    "path": "gui/macro.h",
    "content": "#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#define F5   500\n#define F6   1000\n#define F7   2000\n#define F8   5000\n#define F9   10000\n#define F10  20000\n#define FMIN F1\n#define FMAX F10\n\n#define UNLERP( v, min, max ) ( ( (v) - (min) ) / ( (max) - (min) ) )\n\n#define LERP( n, min, max ) ( (min) + (n) * ( (max) - (min) ) )\n\n#define LINEAR_REMAP( i, imin, imax, omin, omax ) ( LERP( UNLERP( i, imin, imax ), omin, omax ) )\n\nstatic inline QPointF cubic_bezier(qreal t, QPointF p0, QPointF p1, QPointF p2, QPointF p3) {\n    return (1-t)*(1-t)*(1-t)*p0 +\n            3*(1-t)*(1-t)*t*p1 +\n            3*(1-t)*t*t*p2 +\n            t*t*t*p3;\n}\n\n#define CUBIC_BEZIER cubic_bezier\n\n#endif // MACRO_H\n"
  },
  {
    "path": "gui/main.cpp",
    "content": "#include \"gui.h\"\n#include \"runguard.h\"\n#include \"unixsignalhandler.h\"\n#include <QApplication>\n#include <QList>\n#include <QObject>\n#include <error.h>\n#include <signal.h>\n\nstatic void setupUnixSignalHandlers(QList<int> exitSignals) {\n    struct sigaction sa;\n    sigset_t blocking_mask;\n    int r;\n\n    sigemptyset(&blocking_mask);\n    for (int sig : exitSignals) {\n        r = sigaddset(&blocking_mask, sig);\n        if (r < 0)\n            qFatal(\"could not sigaddset(): %s\", strerror(r));\n    }\n\n    sa.sa_handler = UnixSignalHandler::getInstance().exitHandler;\n    sa.sa_mask = blocking_mask;\n    sa.sa_flags = 0;\n    sa.sa_flags |= SA_RESTART;\n\n    for (int sig : exitSignals) {\n        r = sigaction(sig, &sa, nullptr);\n        if (r < 0)\n            qFatal(\"could not sigaction(): %s\", strerror(r));\n    }\n}\n\nint main(int argc, char *argv[])\n{\n    RunGuard guard;\n    if (guard.isRunning()) {\n        qWarning() << \"prettyeq is already running. Quitting!\";\n        return 0;\n    }\n    QApplication a(argc, argv);\n    setupUnixSignalHandlers(QList<int>{SIGINT, SIGTERM, SIGQUIT, SIGABRT, SIGHUP});\n    Gui w;\n    QObject::connect(&a, SIGNAL(aboutToQuit()), &w, SLOT(cleanup()));\n    w.show();\n    return a.exec();\n}\n"
  },
  {
    "path": "gui/peakingcurve.cpp",
    "content": "#include \"peakingcurve.h\"\n#include <QtGlobal>\n#include <QDebug>\n#include <QGraphicsScene>\n#include <QPainter>\n#include <QTransform>\n#define MIN_WIDTH 40\n#define MAX_WIDTH 500\n#define SLOPE 10\n#define SPLINE_CAPACITY 16\n\nPeakingCurve::PeakingCurve(QPen pen, QBrush brush, bool guiOnly, QObject *parent) : QObject(parent), FilterCurve(pen, brush, guiOnly)\n{\n    setZValue(100000);\n\n    lineSpline = std::unique_ptr<QPainterPath>(new QPainterPath());\n    fillSpline = std::unique_ptr<QPainterPath>(new QPainterPath());\n\n#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))\n    lineSpline->reserve(SPLINE_CAPACITY);\n    fillSpline->reserve(SPLINE_CAPACITY);\n#endif\n    reset();\n    updateSplineGeometry();\n}\n\nQRectF PeakingCurve::boundingRect() const\n{\n    return fillSpline->boundingRect();\n}\n\nvoid PeakingCurve::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)\n{\n    painter->setRenderHint(QPainter::Antialiasing, true);\n    painter->setPen(getActivePen());\n    painter->drawPath(*lineSpline);\n    painter->fillPath(*fillSpline, getActiveBrush());\n\n#if 0\n    // debugging - draw control points\n    QPen rpen(Qt::red);\n    rpen.setWidth(10);\n    painter->save();\n    painter->setPen(rpen);\n    painter->drawPoint(c1);\n    painter->drawPoint(c2);\n    painter->restore();\n#endif\n\n#if 0\n    // debugging - draw boundingRect()\n    QPen rpen(Qt::red);\n    rpen.setWidth(2);\n    painter->save();\n    painter->setPen(rpen);\n    painter->drawRect(boundingRect());\n    painter->restore();\n#endif\n}\n\nQPointF PeakingCurve::controlPoint() const\n{\n    return mapToScene(ip);\n}\n\nvoid PeakingCurve::reset()\n{\n    p0 = QPointF(0, 0);\n    ip = QPointF(100, 0);\n    p1 = QPointF(200, 0);\n    c1 = QPointF((p0.x() + ip.x()) / 2, ip.y());\n    c2 = QPointF((ip.x() + p1.x()) / 2, ip.y());\n    updateSplineGeometry();\n    prepareGeometryChange();\n    this->update();\n}\n\nvoid PeakingCurve::updateSplineGeometry()\n{\n#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))\n    lineSpline->clear();\n#else\n    lineSpline.reset(new QPainterPath());\n#endif\n    lineSpline->moveTo(p0);\n    lineSpline->quadTo(c1, ip);\n    lineSpline->moveTo(ip);\n    lineSpline->quadTo(c2, p1);\n    Q_ASSERT(lineSpline->elementCount() == 8);\n#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))\n    Q_ASSERT(lineSpline->capacity() == SPLINE_CAPACITY);\n#endif\n\n#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))\n    fillSpline->clear();\n#else\n    fillSpline.reset(new QPainterPath());\n#endif\n    fillSpline->moveTo(p0);\n    fillSpline->quadTo(c1, ip);\n    fillSpline->moveTo(ip);\n    fillSpline->quadTo(c2, p1);\n    fillSpline->lineTo(p0);\n    Q_ASSERT(fillSpline->elementCount() == 9);\n#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))\n    Q_ASSERT(fillSpline->capacity() == SPLINE_CAPACITY);\n#endif\n}\n\nvoid PeakingCurve::pointPositionChanged(CurvePoint *point)\n{\n    QPointF curvePoint = mapFromScene(point->pos());\n    QPointF delta = curvePoint - ip;\n    ip = curvePoint;\n    p0.setX(p0.x() + delta.x());\n    p1.setX(p1.x() + delta.x());\n    c1 = QPointF((p0.x() + ip.x()) / 2, ip.y());\n    c2 = QPointF((ip.x() + p1.x()) / 2, ip.y());\n    updateSplineGeometry();\n    prepareGeometryChange();\n    this->update();\n    emit resync(this);\n    emit filterParamsChanged(filter, this);\n}\n\nvoid PeakingCurve::pointSlopeChanged(int delta)\n{\n    qreal p0x = p0.x() + SLOPE*delta;\n    qreal p1x = p1.x() - SLOPE*delta;\n    if (p1x - p0x >= MIN_WIDTH && p1x - p0x <= MAX_WIDTH) {\n        p0.setX(p0x);\n        p1.setX(p1x);\n        c1 = QPointF((p0.x() + ip.x()) / 2, ip.y());\n        c2 = QPointF((ip.x() + p1.x()) / 2, ip.y());\n        updateSplineGeometry();\n        prepareGeometryChange();\n        this->update();\n        emit resync(this);\n        emit filterParamsChanged(filter, this);\n    }\n\n}\n"
  },
  {
    "path": "gui/peakingcurve.h",
    "content": "#ifndef PEAKINGCURVE_H\n#define PEAKINGCURVE_H\n\n#include \"curvepoint.h\"\n#include \"filtercurve.h\"\n\n#include <QBrush>\n#include <QGraphicsItem>\n#include <QObject>\n#include <QPen>\n\n#include <memory>\n\ntypedef enum SplinePart {\n    SplineLeft,\n    SplineRight,\n} SplinePart;\n\nclass PeakingCurve : public QObject, public FilterCurve\n{\n    Q_OBJECT\n    Q_INTERFACES(QGraphicsItem)\npublic:\n    explicit PeakingCurve(QPen pen, QBrush brush, bool guiOnly = false, QObject *parent = nullptr);\n    QRectF boundingRect() const override;\n    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;\n\n    QPointF controlPoint() const override;\n\n    // FilterCurve interface\npublic:\n    void reset() override;\n\nprivate:\n    void updateSplineGeometry();\n\npublic slots:\n    void pointPositionChanged(CurvePoint *point);\n    void pointSlopeChanged(int delta);\nsignals:\n    void resync(FilterCurve *curve);\n    void filterParamsChanged(ShimFilterPtr filter, PeakingCurve *curve);\n\nprivate:\n    QPointF p0, p1, c1, c2, ip;\n    std::unique_ptr<QPainterPath> lineSpline, fillSpline;\n};\n\n#endif // PEAKINGCURVE_H\n"
  },
  {
    "path": "gui/prettygraphicsscene.cpp",
    "content": "#include \"curvepoint.h\"\n#include \"eqhoverer.h\"\n#include \"prettygraphicsscene.h\"\n#include <QDebug>\n#include <QGraphicsItem>\n#include <QGraphicsSceneContextMenuEvent>\n#include <QMenu>\n\nPrettyGraphicsScene::PrettyGraphicsScene(QObject *parent) : QGraphicsScene(parent) {}\n\nvoid PrettyGraphicsScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)\n{\n    CurvePoint *point = nullptr;\n    for (auto item : items()) {\n        point = qgraphicsitem_cast<CurvePoint*>(item);\n        if ( !point)\n            continue;\n\n        if (point->sceneBoundingRect().contains(event->scenePos()))\n            break;\n    }\n\n    if (point) {\n        EqHoverer *hover = nullptr;\n        for (auto item : items()) {\n            hover = qgraphicsitem_cast<EqHoverer*>(item);\n            if (! hover)\n                continue;\n\n            if (point == hover->point) {\n                hover->contextMenuToggle(true);\n                break;\n            }\n        }\n\n        QMenu menu(event->widget());\n        menu.addAction(\"Reset\");\n        if (menu.exec(event->screenPos())) {\n            Q_ASSERT(hover);\n            hover->reset();\n        }\n\n        for (auto item : items()) {\n            EqHoverer *hover = qgraphicsitem_cast<EqHoverer*>(item);\n            if (! hover)\n                continue;\n\n            if (point == hover->point) {\n                hover->contextMenuToggle(false);\n                break;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "gui/prettygraphicsscene.h",
    "content": "#ifndef PRETTYGRAPHICSSCENE_H\n#define PRETTYGRAPHICSSCENE_H\n\n#include <QGraphicsScene>\n\nclass PrettyGraphicsScene : public QGraphicsScene\n{\npublic:\n    PrettyGraphicsScene(QObject *parent = nullptr);\n\nprotected:\n    void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;\n};\n\n#endif // PRETTYGRAPHICSSCENE_H\n"
  },
  {
    "path": "gui/prettyshim.h",
    "content": "#ifndef PRETTYSHIM_H\n#define PRETTYSHIM_H\n\n#include <QObject>\n#include <QString>\n#include <QDebug>\n#include <complex>\n\n#include \"pretty.h\"\n\ntypedef PrettyFilter* ShimFilterPtr;\n\n/*  The shim is for making C callbacks idiomatic with Qt signals/slots  */\n\nclass PrettyShim : public QObject\n{\n    Q_OBJECT\n\npublic:\n    static PrettyShim& getInstance() {\n        static PrettyShim instance;\n        return instance;\n    }\n\nprivate:\n    explicit PrettyShim(QObject *parent = nullptr) {}\n    static PrettyShim *instance;\n\npublic:\n    PrettyShim(PrettyShim const&) \t  = delete;\n    void operator=(PrettyShim const&) = delete;\n\n    void init() {\n        qRegisterMetaType<uint32_t>(\"uint32_t\");\n        int r = pretty_init();\n        Q_ASSERT(r == 0);\n    }\n\n    void setup_sink_io()  {\n        pretty_setup_sink_io();\n    }\n\n    void exit() {\n        pretty_exit();\n    }\n\n    void new_filter(ShimFilterPtr *filter) {\n        int r = pretty_new_filter(filter);\n        Q_ASSERT(*filter && r >= 0);\n    }\n\n    void set_peaking_eq(ShimFilterPtr filter, float f0, float bandwidth, float db_gain) {\n        pretty_set_peaking_eq(filter, f0, bandwidth, db_gain);\n    }\n\n    void set_low_shelf(PrettyFilter *filter, float f0, float S, float db_gain) {\n        pretty_set_low_shelf(filter, f0, S, db_gain);\n    }\n\n    void set_high_shelf(PrettyFilter *filter, float f0, float S, float db_gain) {\n        pretty_set_high_shelf(filter, f0, S, db_gain);\n    }\n\n    void enable_bypass(bool should_bypass) {\n        pretty_enable_bypass(should_bypass);\n    }\n\n    std::complex<float>* get_audio_data(unsigned int *N) {\n        std::complex<float> *data;\n        pretty_acquire_audio_data(&data, N);\n        return std::move(data);\n    }\n\n    void release_audio_data() {\n        pretty_release_audio_data();\n    }\n\nprivate:\n    AudioFFT *audio_fft;\n\n};\n\n#endif // PRETTYSHIM_H\n"
  },
  {
    "path": "gui/resources.qrc",
    "content": "<RCC>\n    <qresource prefix=\"/images\">\n        <file>images/prettyeq.png</file>\n    </qresource>\n</RCC>\n"
  },
  {
    "path": "gui/ringbuffer.h",
    "content": "#ifndef RINGBUFFER_H\n#define RINGBUFFER_H\n\n#include <QtCore>\n\n#include <stdlib.h>\n\ntemplate <class T, int size>\nclass RingBuffer\n{\npublic:\n    RingBuffer() : pos(0) {};\n\n    const T& at(int i) {\n        Q_ASSERT(i < size);\n        return ring_buffer.at(i);\n    }\n\n    const std::array<T, size>& buffer() {\n        return ring_buffer;\n    }\n\n    void append(const T& value) {\n        ring_buffer[pos] = value;\n        pos = (pos + 1) % size;\n    }\n\nprivate:\n    int pos;\n    std::array<T, size> ring_buffer;\n};\n\n#endif // RINGBUFFER_H\n"
  },
  {
    "path": "gui/runguard.cpp",
    "content": "#include \"runguard.h\"\n#include <string.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <error.h>\n#include <QtGlobal>\n#include <QDebug>\n\nRunGuard::RunGuard()\n{\n    ::memset(&addr, 0, sizeof(struct sockaddr_un));\n    addr.sun_family = AF_UNIX;\n\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wstringop-truncation\"\n    ::strncpy(&addr.sun_path[1], PRETTY_ABSTRACT_SOCK, sizeof(PRETTY_ABSTRACT_SOCK) - 2);\n#pragma GCC diagnostic pop\n\n    sockfd = ::socket(AF_UNIX, SOCK_STREAM, 0);\n    if (sockfd < 0)\n        qFatal(\"socket() failed: %s\", strerror(sockfd));\n}\n\nbool RunGuard::isRunning()\n{\n    int r = ::bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un));\n    if (r < 0) {\n        if (errno == EADDRINUSE)\n            return true;\n        else\n            qFatal(\"bind() failed: %s\", strerror(errno));\n    }\n    return false;\n}\n"
  },
  {
    "path": "gui/runguard.h",
    "content": "#ifndef RUNGUARD_H\n#define RUNGUARD_H\n\n#include <sys/un.h>\n#define PRETTY_ABSTRACT_SOCK \"prettyeq\"\n\n/* Linux only implementation of a single application guard.\n * We bind to an abstract socket so cleanup is handled entirely\n * by the kernel. */\nclass RunGuard\n{\npublic:\n    RunGuard();\n    bool isRunning();\n\nprivate:\n    int sockfd;\n    struct sockaddr_un addr;\n};\n\n#endif // RUNGUARD_H\n"
  },
  {
    "path": "gui/shelfcurve.cpp",
    "content": "#include \"macro.h\"\n#include \"shelfcurve.h\"\n#include <QtGlobal>\n#include <QDebug>\n#include <QGraphicsScene>\n#include <QPainter>\n#include <QPainterPath>\n#define CURVE_CAPACITY 16\n\nShelfCurve::ShelfCurve(QPen pen, QBrush brush, bool guiOnly, QObject *parent)\n    : QObject(parent), FilterCurve(pen, brush, guiOnly)\n{\n    setZValue(100000);\n\n    lineCurve = std::unique_ptr<QPainterPath>(new QPainterPath());\n    fillCurve = std::unique_ptr<QPainterPath>(new QPainterPath());\n#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))\n    lineCurve->reserve(CURVE_CAPACITY);\n    fillCurve->reserve(CURVE_CAPACITY);\n#endif\n}\n\nShelfCurve::~ShelfCurve()\n{\n\n}\n\nvoid ShelfCurve::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)\n{\n    painter->setRenderHint(QPainter::Antialiasing, true);\n    painter->setPen(getActivePen());\n    painter->drawPath(*lineCurve);\n    painter->fillPath(*fillCurve, getActiveBrush());\n#if 0\n    // debugging - draw control points\n    QPen rpen(Qt::red);\n    QPen ypen(Qt::yellow);\n    QPen gpen(Qt::green);\n    QPen bpen(Qt::blue);\n    rpen.setWidth(10);\n    ypen.setWidth(10);\n    bpen.setWidth(10);\n    gpen.setWidth(10);\n    painter->save();\n    painter->setPen(rpen);\n    painter->drawPoint(p0);\n    painter->setPen(ypen);\n    painter->drawPoint(p1);\n    painter->setPen(gpen);\n    painter->drawPoint(p2);\n    painter->setPen(bpen);\n    painter->drawPoint(p3);\n    painter->restore();\n#endif\n\n#if 0\n    // debugging - draw boundingRect()\n    QPen rpen(Qt::red);\n    rpen.setWidth(10);\n    QPen bpen(Qt::blue);\n    bpen.setWidth(10);\n    QPen gpen(Qt::green);\n    gpen.setWidth(10);\n    QPen ypen(Qt::yellow);\n    ypen.setWidth(10);\n    painter->save();\n    painter->setPen(rpen);\n    painter->drawPoint(boundingRect().bottomLeft());\n    painter->setPen(bpen);\n    painter->drawPoint(boundingRect().topLeft());\n    painter->setPen(gpen);\n    painter->drawPoint(boundingRect().bottomRight());\n    painter->setPen(ypen);\n    painter->drawPoint(boundingRect().topRight());\n    painter->restore();\n#endif\n\n}\n\nQPointF ShelfCurve::controlPoint() const\n{\n    return mapToScene(p1);\n}\n\nqreal ShelfCurve::slope() const\n{\n    /* The i have no idea what the fuck I'm doing equation. */\n    return qAbs(bezierPainter().slopeAtPercent(0.7)) + 0.5;\n}\n\nvoid ShelfCurve::pointPositionChanged(CurvePoint *point) {\n    QPointF curvePoint = mapFromScene(point->pos());\n    QPointF delta = curvePoint - p1;\n    p0.setY(p0.y() + delta.y());\n    p1 = curvePoint;\n    p2.setX(p2.x() + delta.x());\n    p3.setX(p3.x() + delta.x());\n    updateCurveGeometry();\n    prepareGeometryChange();\n    this->update();\n    emit resync(this);\n    emit filterParamsChanged(filter, this);\n}\n\nQPainterPath ShelfCurve::bezierPainter() const\n{\n    QPainterPath shelf;\n    shelf.moveTo(p0);\n    shelf.cubicTo(p1, clampP2(), p3);\n    return shelf;\n}\n\nvoid ShelfCurve::updateCurveGeometry()\n{\n#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))\n    lineCurve->clear();\n#else\n    lineCurve.reset(new QPainterPath());\n#endif\n    lineCurve->moveTo(p0);\n    lineCurve->cubicTo(p1, clampP2(), p3);\n    Q_ASSERT(lineCurve->elementCount() == 4);\n#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))\n    Q_ASSERT(lineCurve->capacity() == CURVE_CAPACITY);\n#endif\n\n#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))\n    fillCurve->clear();\n#else\n    fillCurve.reset(new QPainterPath());\n#endif\n    fillCurve->moveTo(p0);\n    fillCurve->cubicTo(p1, clampP2(), p3);\n    fillCurve->lineTo(0, 0);\n    fillCurve->lineTo(p0);\n    Q_ASSERT(fillCurve->elementCount() <= 6);\n#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))\n    Q_ASSERT(fillCurve->capacity() == CURVE_CAPACITY);\n#endif\n}\n\nQRectF ShelfCurve::boundingRect() const\n{\n    return bezierPainter().boundingRect();\n}\n"
  },
  {
    "path": "gui/shelfcurve.h",
    "content": "#ifndef SHELFCURVE_H\n#define SHELFCURVE_H\n\n#include \"curvepoint.h\"\n#include \"filtercurve.h\"\n\n#include <QBrush>\n#include <QGraphicsItem>\n#include <QObject>\n#include <QPen>\n\n#include <memory>\n\n#define SLOPE_DELTA 20\n#define SLOPE_MAX 330\n\nclass LowShelfCurve;\nclass HighShelfCurve;\n\nclass ShelfCurve : public QObject, public FilterCurve\n{\n    Q_OBJECT\n    Q_INTERFACES(QGraphicsItem)\npublic:\n    explicit ShelfCurve(QPen pen, QBrush brush, bool guiOnly = false, QObject *parent = nullptr);\n    virtual ~ShelfCurve();\n    QRectF boundingRect() const override;\n    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;\n\n    QPointF controlPoint() const override;\n    qreal slope() const;\n\nprotected:\n    virtual QPointF clampP2() const = 0;\n\nprivate:\n    QPainterPath bezierPainter() const;\n    void updateCurveGeometry();\n\npublic slots:\n    void pointPositionChanged(CurvePoint *point);\n    virtual void pointSlopeChanged(int delta) = 0;\nsignals:\n    void resync(FilterCurve *curve);\n    void filterParamsChanged(ShimFilterPtr filter, ShelfCurve *curve);\n\nprivate:\n    QPointF p0, p1, p2, p3;\n    std::unique_ptr<QPainterPath> lineCurve, fillCurve;\n    friend class LowShelfCurve;\n    friend class HighShelfCurve;\n};\n\n#endif // SHELFCURVE_H\n"
  },
  {
    "path": "gui/spectrumanalyzer.cpp",
    "content": "#include \"frequencytickbuilder.h\"\n#include \"macro.h\"\n#include \"prettyshim.h\"\n#include \"spectrumanalyzer.h\"\n\n#include <QDebug>\n#include <QGraphicsScene>\n#include <QPainter>\n#include <QPainterPath>\n#include <QtCore>\n#include <complex>\n#include <string.h>\n\n#define FFT_SAMPLE_TO_FREQ(NUM_SAMPLES, SAMPLE_INDEX) (44100*(SAMPLE_INDEX)/(NUM_SAMPLES))\n#define FFT_FREQ_TO_SAMPLE(NUM_SAMPLES, FREQ) ((int)roundf((FREQ)*(NUM_SAMPLES)/44100))\n#define FFT_BUCKET_WIDTH(NUM_SAMPLES) (44100/(NUM_SAMPLES))\n\nstatic inline qreal dampen(qreal start, qreal end, qreal smoothing_factor, qint64 dt) {\n    return LERP(1 - qPow(smoothing_factor, dt), start, end);\n}\n\nSpectrumAnalyzer::SpectrumAnalyzer(FrequencyTickBuilder *xTickBuilder) : xTickBuilder(xTickBuilder), last_frame_time(0)\n{\n    setZValue(-1);\n    for (unsigned int i = 0; i < MAX_SAMPLES; i++)\n        last_psds[i] = 0.0;\n}\n\nQRectF SpectrumAnalyzer::boundingRect() const\n{\n    auto x = scene()->sceneRect().width();\n    auto y = scene()->sceneRect().height();\n    return QRectF(0, 0, x, y / 4);\n}\n\ninline QLineF SpectrumAnalyzer::pointForSample(qreal frequency, qreal max_psd, qreal max_psd_moving_avg) {\n    qreal sceneX = xTickBuilder->unlerpTick(frequency);\n    qreal startX = mapFromScene(sceneX, 0xbeefcafe).x();\n    qreal startY = qMax(boundingRect().top(), LINEAR_REMAP(max_psd, 0, max_psd_moving_avg, 0, boundingRect().height() / 2));\n    QLineF line(QPointF(startX, boundingRect().center().y()), QPointF(startX, boundingRect().center().y() - startY));\n    return line;\n}\n\nqint64 SpectrumAnalyzer::frame_dt()\n{\n    return QDateTime::currentMSecsSinceEpoch() - last_frame_time;\n}\n\nvoid SpectrumAnalyzer::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)\n{\n    painter->setRenderHint(QPainter::Antialiasing, true);\n#if 0\n    QPen rpen(Qt::red);\n    rpen.setWidth(5);\n    QPen bpen(Qt::blue);\n    bpen.setWidth(5);\n    painter->setPen(rpen);\n    painter->drawPoint(boundingRect().center());\n    painter->drawRect(boundingRect());\n    painter->drawPoint(boundingRect().topLeft());\n    auto line = QLineF(QPointF(100, 0), QPointF(100, boundingRect().height() / 2));\n    painter->drawLine(line);\n    line.translate(0, boundingRect().height() / 2);\n    painter->setPen(bpen);\n    painter->drawLine(line);\n#else\n    qreal max_psd_moving_avg = 0.0;\n    static auto brush = QBrush(QColor(127, 153, 176, 128));\n    static auto pen = QPen(brush, 3);\n    painter->setPen(pen);\n    {\n        auto &buf = max_psds.buffer();\n        for (auto it = buf.begin(); it != buf.end(); it++)\n            max_psd_moving_avg += *it;\n        max_psd_moving_avg /= MOVING_AVG_PERIOD;\n    }\n\n    {\n        qint64 delta = frame_dt();\n        unsigned int N;\n        auto data = PrettyShim::getInstance().get_audio_data(&N);\n        Q_ASSERT(N < MAX_SAMPLES);\n        qreal max_psd = 0.0;\n        for (unsigned int i = FFT_FREQ_TO_SAMPLE(N, FMIN); i < N / 2; i++) {\n            qreal raw_psd = (qreal) std::abs(data[i]);\n            qreal min = qMin(raw_psd, last_psds[i]);\n            qreal max = qMax(raw_psd, last_psds[i]);\n            qreal dampening_factor = 1 - min/max;\n            dampening_factor = qMax(0.5, dampening_factor);\n            dampening_factor = qMin(0.95, dampening_factor);\n            qreal smoothed_psd = dampen(raw_psd, last_psds[i], dampening_factor, delta);\n            last_psds[i] = smoothed_psd;\n            max_psd = qMax(max_psd, smoothed_psd);\n            qreal frequency = FFT_SAMPLE_TO_FREQ(N, (qreal) i);\n            qreal sceneX = xTickBuilder->unlerpTick(frequency);\n            qreal startX = mapFromScene(sceneX, 0xbeefcafe).x();\n            qreal startY = LINEAR_REMAP(smoothed_psd, 0, max_psd_moving_avg, 0, boundingRect().height() / 2);\n            startY = qMin(startY, boundingRect().height() / 2);\n            QPointF p1 = QPointF(startX, boundingRect().center().y() - startY);\n            QPointF p2 = QPointF(startX, boundingRect().center().y() + startY);\n            lines[i].setP1(p1);\n            lines[i].setP2(p2);\n        }\n\n        if (max_psd_moving_avg > 0.01)\n            painter->drawLines(lines, N / 2);\n\n        PrettyShim::getInstance().release_audio_data();\n        max_psds.append(max_psd);\n    }\n#endif\n}\n\nvoid SpectrumAnalyzer::updateFrameDelta()\n{\n    last_frame_time = QDateTime::currentMSecsSinceEpoch();\n}\n"
  },
  {
    "path": "gui/spectrumanalyzer.h",
    "content": "#ifndef SPECTRUMANALYZER_H\n#define SPECTRUMANALYZER_H\n\n#include <QGraphicsItem>\n#include \"ringbuffer.h\"\n\n#define MOVING_AVG_PERIOD 128\n#define MAX_SAMPLES 4096\n\nclass FrequencyTickBuilder;\n\nclass SpectrumAnalyzer : public QGraphicsItem\n{\npublic:\n    SpectrumAnalyzer(FrequencyTickBuilder *xTickBuilder);\n\npublic:\n    QRectF boundingRect() const;\n    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);\n\n    void updateFrameDelta();\nprivate:\n    QLineF pointForSample(qreal frequency, qreal max_psd, qreal max_psd_moving_avg);\n    inline qint64 frame_dt();\n\nprivate:\n    FrequencyTickBuilder *xTickBuilder;\n    RingBuffer<qreal, MOVING_AVG_PERIOD> max_psds;\n    QLineF lines[MAX_SAMPLES];\n    qreal last_psds[MAX_SAMPLES];\n    qint64 last_frame_time;\n};\n\n#endif // SPECTRUMANALYZER_H\n"
  },
  {
    "path": "gui/unixsignalhandler.cpp",
    "content": "#include \"unixsignalhandler.h\"\n#include \"prettyshim.h\"\n#include <QDebug>\n#include <QCoreApplication>\n\n#include <sys/socket.h>\n#include <unistd.h>\n\nint UnixSignalHandler::socketVector[2];\n\nUnixSignalHandler &UnixSignalHandler::getInstance() {\n    static UnixSignalHandler instance;\n    return instance;\n}\n\nUnixSignalHandler::UnixSignalHandler(QObject *parent) : QObject(parent)\n{\n    if (::socketpair(AF_UNIX, SOCK_STREAM, 0, socketVector))\n        qFatal(\"Could not socketpair();\");\n\n    sn = new QSocketNotifier(socketVector[1], QSocketNotifier::Read, this);\n    QObject::connect(sn, SIGNAL(activated(QSocketDescriptor, QSocketNotifier::Type)), this, SLOT(safeHandleExit()));\n}\n\nvoid UnixSignalHandler::exitHandler(int sig)\n{\n    /* Write junk to the socket to trigger Qt signal */\n    char junk = 1;\n    ::write(socketVector[0], &junk, sizeof(char));\n}\n\nvoid UnixSignalHandler::safeHandleExit()\n{\n    sn->setEnabled(false);\n    char junk;\n    ::read(socketVector[1], &junk, sizeof(char));\n    QCoreApplication::quit();\n    sn->setEnabled(true);\n}\n\nUnixSignalHandler::~UnixSignalHandler()\n{\n    delete sn;\n}\n"
  },
  {
    "path": "gui/unixsignalhandler.h",
    "content": "#ifndef UNIXSIGNALHANDLER_H\n#define UNIXSIGNALHANDLER_H\n\n#include <QObject>\n#include <QSocketNotifier>\n\nclass UnixSignalHandler : public QObject\n{\n    Q_OBJECT\npublic:\n    static UnixSignalHandler& getInstance();\n    ~UnixSignalHandler();\n    UnixSignalHandler(UnixSignalHandler const&) = delete;\n    void operator=(UnixSignalHandler const&)    = delete;\n\nprivate:\n    explicit UnixSignalHandler(QObject *parent = nullptr);\n\npublic:\n    /* SIGTERM, SIGINT, SIGQUIT, SIGABRT */\n    static void exitHandler(int sig);\n\npublic slots:\n    void safeHandleExit();\n\nprivate:\n    static UnixSignalHandler instance;\n    /* All signals share the same socketpair and will mask each other. */\n    static int socketVector[2];\n    QSocketNotifier *sn;\n};\n\n#endif // UNIXSIGNALHANDLER_H\n"
  },
  {
    "path": "prettyeq.pro",
    "content": "TEMPLATE = subdirs\nCONFIG +=  ordered\nSUBDIRS = \\\n          equalizer \\\n          gui\n\ngui.depends = equalizer\n"
  }
]