├── .gitignore ├── LICENSE.txt ├── README.md ├── build_tests.sh ├── equalizer ├── arena.c ├── arena.h ├── arena_test.c ├── equalizer.pro ├── fft.c ├── fft.h ├── fft_test.c ├── macro.h ├── pretty.c └── pretty.h ├── gui ├── collisionmanager.cpp ├── collisionmanager.h ├── curvepoint.cpp ├── curvepoint.h ├── eqhoverer.cpp ├── eqhoverer.h ├── filtercurve.cpp ├── filtercurve.h ├── frequencytick.cpp ├── frequencytick.h ├── frequencytickbuilder.cpp ├── frequencytickbuilder.h ├── gui.cpp ├── gui.h ├── gui.pro ├── gui.ui ├── highshelfcurve.cpp ├── highshelfcurve.h ├── images │ └── prettyeq.png ├── lowshelfcurve.cpp ├── lowshelfcurve.h ├── macro.h ├── main.cpp ├── peakingcurve.cpp ├── peakingcurve.h ├── prettygraphicsscene.cpp ├── prettygraphicsscene.h ├── prettyshim.h ├── resources.qrc ├── ringbuffer.h ├── runguard.cpp ├── runguard.h ├── shelfcurve.cpp ├── shelfcurve.h ├── spectrumanalyzer.cpp ├── spectrumanalyzer.h ├── unixsignalhandler.cpp └── unixsignalhandler.h └── prettyeq.pro /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | *.slo 3 | *.lo 4 | *.o 5 | *.a 6 | *.la 7 | *.lai 8 | *.so 9 | *.so.* 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | object_script.*.Release 15 | object_script.*.Debug 16 | *_plugin_import.cpp 17 | /.qmake.cache 18 | /.qmake.stash 19 | *.pro.user 20 | *.pro.user.* 21 | *.qbs.user 22 | *.qbs.user.* 23 | *.moc 24 | moc_*.cpp 25 | moc_*.h 26 | qrc_*.cpp 27 | ui_*.h 28 | *.qmlc 29 | *.jsc 30 | Makefile* 31 | *build-* 32 | *.qm 33 | *.prl 34 | 35 | # Qt unit tests 36 | target_wrapper.* 37 | 38 | # QtCreator 39 | *.autosave 40 | 41 | # QtCreator Qml 42 | *.qmlproject.user 43 | *.qmlproject.user.* 44 | 45 | # QtCreator CMake 46 | CMakeLists.txt.user* 47 | 48 | # QtCreator 4.8< compilation database 49 | compile_commands.json 50 | 51 | # QtCreator local machine specific files for imported projects 52 | *creator.user* 53 | 54 | *_qmlcache.qrc 55 | 56 | /tags 57 | /TAGS 58 | /prettyeq 59 | /build 60 | /.vscode 61 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2020 Kevin Kuehler 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors 14 | may be used to endorse or promote products derived from this software without 15 | specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## PrettyEQ 2 | 3 | prettyeq is a system-wide paramateric equalizer for pulseaudio. This software 4 | is in alpha. Use at your own discretion. 5 | 6 | ![prettyeq demo](https://i.fluffy.cc/0GFcjGmbrtCgnbRSjd4xjDcf7h6qNk4Q.gif) 7 | 8 | ### Building 9 | 10 | ``` 11 | mkdir build 12 | cd build 13 | qmake CONFIG+=release .. 14 | make -j4 15 | ``` 16 | 17 | ### Usage 18 | 19 | When the program is executed all pulseaudio streams will be routed through the 20 | equalizer. With no filters activated prettyeq acts as a passthrough sink. 21 | 22 | Right now prettyeq only supports two-channel sound. 23 | 24 | ##### Filter types 25 | 26 | prettyeq has three filter types: 27 | * one **low shelf** filter mounted at 20Hz 28 | * one **high shelf** filter mounted at 20kHz 29 | * five **peakingEQ** filters that can move freely 30 | 31 | ##### Controls 32 | 33 | Click and drag points to boost and cut frequency bands. The dB gain range is 34 | ±12dB. Filter bandwidth and slope can be changed with the mousewheel. 35 | 36 | ##### Quitting 37 | 38 | If your desktop has a system tray, the close button will hide the GUI but the 39 | equalizer will still be in effect. There are context menus in the application 40 | and tray that have a "exit" option to quit the application. 41 | -------------------------------------------------------------------------------- /build_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | mkdir -p build 3 | gcc -lm -ffast-math -march=skylake -fopenmp -O2 \ 4 | -o build/fft_test \ 5 | equalizer/fft.c equalizer/fft_test.c 6 | 7 | gcc -g -o build/arena_test \ 8 | equalizer/arena.c equalizer/arena_test.c 9 | -------------------------------------------------------------------------------- /equalizer/arena.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "arena.h" 8 | 9 | #define CHUNK_OFFSET(memory) ((void*)(memory)-(sizeof(chunk_t))) 10 | 11 | #define MEMORY_OFFSET(chunk) ((void*)(chunk)+(sizeof(chunk_t))) 12 | 13 | #define NEXT_CHUNK(chunk, size) (((void*)MEMORY_OFFSET((chunk)))+(size)) 14 | 15 | arena_t* arena_new(size_t num_chunks, size_t chunk_size) { 16 | assert(num_chunks > 0); 17 | assert(chunk_size > 0); 18 | 19 | arena_t *arena = calloc(sizeof(arena_t), 1); 20 | if (!arena) 21 | return NULL; 22 | 23 | size_t map_size = num_chunks * (chunk_size + sizeof(chunk_t)); 24 | void *mem = mmap( 25 | NULL, 26 | map_size, 27 | PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, 28 | -1, 0); 29 | 30 | if (mem == MAP_FAILED) 31 | return NULL; 32 | 33 | chunk_t *chunk = mem; 34 | for (unsigned int i = 0; i < num_chunks - 1; i++) { 35 | chunk->next = NEXT_CHUNK(chunk, chunk_size); 36 | chunk = chunk->next; 37 | } 38 | chunk->next = NULL; 39 | 40 | arena->chunk_size = chunk_size; 41 | arena->map_size = map_size; 42 | arena->mem = mem; 43 | arena->avail_chunk = mem; 44 | 45 | return arena; 46 | } 47 | 48 | void* arena_alloc(arena_t *arena) { 49 | assert(arena); 50 | assert(arena->avail_chunk); 51 | 52 | void *p = MEMORY_OFFSET(arena->avail_chunk); 53 | assert(p); 54 | arena->avail_chunk = arena->avail_chunk->next; 55 | return p; 56 | } 57 | 58 | void arena_dealloc(arena_t *arena, void *mem) { 59 | assert(arena); 60 | assert(mem); 61 | 62 | chunk_t *chunk = CHUNK_OFFSET(mem); 63 | chunk->next = arena->avail_chunk; 64 | arena->avail_chunk = chunk; 65 | } 66 | 67 | void arena_destroy(arena_t **arena) { 68 | assert(arena); 69 | assert((*arena)->mem); 70 | munmap((*arena)->mem, (*arena)->map_size); 71 | free(*arena); 72 | *arena = NULL; 73 | } 74 | -------------------------------------------------------------------------------- /equalizer/arena.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef struct chunk_t chunk_t; 6 | struct chunk_t { 7 | chunk_t *next; 8 | }; 9 | 10 | typedef struct arena_t { 11 | void *mem; 12 | size_t chunk_size; 13 | size_t map_size; 14 | chunk_t *avail_chunk; 15 | } arena_t; 16 | 17 | arena_t* arena_new(size_t num_chunks, size_t chunk_size); 18 | void* arena_alloc(arena_t *arena); 19 | void arena_dealloc(arena_t *arena, void *mem); 20 | void arena_destroy(arena_t **arena); 21 | -------------------------------------------------------------------------------- /equalizer/arena_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "arena.h" 6 | 7 | int main(int argc, char **argv) { 8 | arena_t *arena = arena_new(4, 5); 9 | assert(arena); 10 | assert(arena->avail_chunk != NULL); 11 | 12 | char *a = arena_alloc(arena); 13 | memset(a, 'a', 4); 14 | a[4] = '\0'; 15 | 16 | char *b = arena_alloc(arena); 17 | memset(b, 'b', 4); 18 | b[4] = '\0'; 19 | 20 | char *c = arena_alloc(arena); 21 | memset(c, 'c', 4); 22 | c[4] = '\0'; 23 | 24 | char *d = arena_alloc(arena); 25 | memset(d, 'd', 4); 26 | d[4] = '\0'; 27 | 28 | assert(arena->avail_chunk == NULL); 29 | assert(memcmp(a, "aaaa", 5) == 0); 30 | assert(memcmp(b, "bbbb", 5) == 0); 31 | assert(memcmp(c, "cccc", 5) == 0); 32 | assert(memcmp(d, "dddd", 5) == 0); 33 | 34 | arena_dealloc(arena, c); 35 | arena_dealloc(arena, a); 36 | 37 | char *e = arena_alloc(arena); 38 | memset(e, 'e', 4); 39 | e[4] = '\0'; 40 | 41 | char *f = arena_alloc(arena); 42 | memset(f, 'f', 4); 43 | f[4] = '\0'; 44 | 45 | assert(memcmp(e, "eeee", 5) == 0); 46 | assert(memcmp(a, "eeee", 5) == 0); 47 | assert(memcmp(f, "ffff", 5) == 0); 48 | assert(memcmp(c, "ffff", 5) == 0); 49 | 50 | arena_dealloc(arena, b); 51 | arena_dealloc(arena, e); 52 | arena_dealloc(arena, f); 53 | arena_dealloc(arena, d); 54 | 55 | assert((void *) arena->avail_chunk == (void *) d - sizeof(chunk_t)); 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /equalizer/equalizer.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | QMAKE_CFLAGS += -ffast-math -fopenmp 3 | QMAKE_CFLAGS_WARN_ON += -Wno-unused-parameter 4 | CONFIG += staticlib 5 | HEADERS = \ 6 | arena.h \ 7 | fft.h \ 8 | macro.h \ 9 | pretty.h 10 | pretty.h 11 | SOURCES = \ 12 | arena.c \ 13 | fft.c \ 14 | pretty.c 15 | -------------------------------------------------------------------------------- /equalizer/fft.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "macro.h" 13 | #include "fft.h" 14 | 15 | static bool initialized = false; 16 | alignas(32) static complex float omega_vec_log2[MAX_SAMPLES][K]; 17 | 18 | static inline unsigned int reverse_bits(unsigned int n, unsigned int num_bits) { 19 | int i, j; 20 | register unsigned int res = 0; 21 | 22 | i = 0; 23 | j = num_bits; 24 | while (i <= j) { 25 | unsigned int lower_mask = 1 << i; 26 | unsigned int upper_mask = (1 << num_bits) >> i; 27 | unsigned int shift = j - i; 28 | res |= ((n >> shift) & lower_mask) | ((n << shift) & upper_mask); 29 | i++; 30 | j--; 31 | } 32 | return res; 33 | } 34 | 35 | static inline unsigned int get_msb(unsigned int v) { 36 | return 31 - __builtin_clz(v); 37 | } 38 | 39 | void fft_init() { 40 | for (unsigned int nl = 0; nl < MAX_SAMPLES_LOG_2; nl++) { 41 | unsigned int n = (1u << nl); 42 | const double mul_div_n = (-2.0 * M_PI) / n; 43 | for (unsigned int k = 0; k < K; k++) { 44 | complex float *res = &omega_vec_log2[nl][k]; 45 | complex double theta = mul_div_n * k; 46 | _real_(*res) = cosf(theta); 47 | _imag_(*res) = sinf(theta); 48 | } 49 | } 50 | initialized = true; 51 | } 52 | 53 | void fft_run( 54 | const float *input_data, 55 | complex float *output_data, 56 | unsigned int N, 57 | unsigned int channels) { 58 | assert(initialized); 59 | 60 | { 61 | unsigned int msb; 62 | 63 | for (unsigned int i = 0, j = 0; i < N; j++, i+=channels) 64 | /* Taking just the left channel for now... */ 65 | output_data[j] = input_data[i]; 66 | 67 | N = N / channels; 68 | assert(N <= MAX_SAMPLES); 69 | msb = get_msb(N); 70 | 71 | if (_unlikely_((N & (N-1)))) { 72 | /* Pad out so FFT is a power of 2. */ 73 | msb = msb + 1; 74 | unsigned int new_N = 1 << msb; 75 | for (unsigned int i = N; i < new_N; i++) 76 | output_data[i] = 0.0f; 77 | 78 | N = new_N; 79 | } 80 | 81 | /* Reverse the input array. */ 82 | unsigned int hi_bit = msb - 1; 83 | for (unsigned int i = 0; i < N; i++) { 84 | unsigned int r = reverse_bits(i, hi_bit); 85 | if (i < r) 86 | SWAP(output_data[i], output_data[r]); 87 | } 88 | } 89 | 90 | { 91 | /* Simple radix-2 DIT FFT */ 92 | unsigned int wingspan = 1; 93 | unsigned int nl = 1; 94 | while (wingspan < N) { 95 | for (unsigned int j = 0; j < N; j+=wingspan*2) { 96 | for (unsigned int k = 0; k < wingspan; k++) { 97 | complex float omega = omega_vec_log2[nl][k]; 98 | complex float a0 = output_data[k+j]; 99 | complex float a1 = output_data[k+j+wingspan]; 100 | output_data[k+j] = a0 + omega*a1; 101 | output_data[k+j+wingspan] = a0 - omega*a1; 102 | } 103 | } 104 | nl++; 105 | wingspan *= 2; 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /equalizer/fft.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #define MAX_SAMPLES_LOG_2 12 6 | #define MAX_SAMPLES (1 << MAX_SAMPLES_LOG_2) 7 | #define K (MAX_SAMPLES / 2) 8 | 9 | #define FFT_BUCKET_WIDTH(NUM_SAMPLES) (44100/(NUM_SAMPLES)) 10 | #define FFT_SAMPLE_TO_FREQ(NUM_SAMPLES, SAMPLE_INDEX) (44100*(SAMPLE_INDEX)/(NUM_SAMPLES)) 11 | #define FFT_FREQ_TO_SAMPLE(NUM_SAMPLES, FREQ) ((int)roundf((FREQ)*(NUM_SAMPLES)/44100)) 12 | #define FFT_PSD(SAMPLE) ((float)crealf(cabsf((SAMPLE)))) 13 | 14 | void fft_init(); 15 | void fft_run( 16 | const float *input_data, 17 | complex float *output_data, 18 | unsigned int N, 19 | unsigned int channels); 20 | -------------------------------------------------------------------------------- /equalizer/fft_test.c: -------------------------------------------------------------------------------- 1 | #include "fft.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static const float single_channel_5000HZ_data[] = { 9 | -0.120544, 0.222534, 0.457336, 0.469727, 0.253632, -0.085846, -0.383575, 10 | -0.494568, -0.365295, -0.058197, 0.277222, 0.477783, 0.445923, 0.197205, 11 | -0.147461, -0.420441, -0.488922, -0.319580, 0.005157, 0.327362, 0.490356, 12 | 0.414825, 0.137512, -0.206696, -0.450378, -0.475037, -0.268646, 0.068390, 13 | 0.372162, 0.494537, 0.376892, 0.075592, -0.262482, -0.472900, -0.453339, 14 | -0.213257, 0.130524, 0.410797, 0.491302, 0.332794, 0.012451, -0.313995, 15 | -0.487701, -0.424225, -0.154419, 0.190491, 0.442719, 0.479614, 0.283234, 16 | -0.050934, -0.360352, -0.494476, -0.388123, -0.093018, 0.247345, 0.467377, 17 | 0.460083, 0.229004, -0.113464, -0.400787, -0.493164, -0.345673, -0.030090, 18 | 0.300110, 0.484344, 0.432983, 0.171021, -0.174133, -0.434601, -0.483704, 19 | -0.297546, 0.033325, 0.347992, 0.493378, 0.398773, 0.110199, -0.231964, 20 | -0.461334, -0.466339, -0.244507, 0.096191, 0.390137, 0.494293, 0.358032, 21 | 0.047607, -0.285980, -0.480469, -0.441284, -0.187500, 0.157501, 0.425873, 22 | 0.487091, 0.311401, -0.015778, -0.335266, -0.491730, -0.409027, -0.127380, 23 | 0.216217, 0.454590, 0.471893, 0.259644, -0.078888, -0.379059, -0.494568, 24 | -0.370026, -0.065186, 0.271332, 0.475891, 0.448944, 0.203644, -0.140717, 25 | -0.416687, -0.489960, -0.324951, -0.001892, 0.322052, 0.489349, 0.418640, 26 | 0.144287, -0.200256, -0.447388, -0.476959, -0.274536, 0.061401, 0.367462, 27 | 0.494537, 0.381439, 0.082550, -0.256500, -0.470795, -0.456116, -0.219604, 28 | 0.123688, 0.406830, 0.492065, 0.337982, 0.019470, -0.308502, -0.486450, 29 | -0.427826, -0.161102, 0.183960, 0.439545, 0.481293, 0.288971, -0.043915, 30 | -0.355469, -0.494141, -0.392487, -0.099945, 0.241211, 0.465027, 0.462616, 31 | 0.235229, -0.106598, -0.396576, -0.493683, -0.350677, -0.037109, 0.294495, 32 | 0.482849, 0.436340, 0.177612, -0.167542, -0.431213, -0.485168, -0.303131, 33 | 0.026276, 0.342926, 0.492767, 0.402924, 0.117065, -0.225708, -0.458740, 34 | -0.468628, -0.250610, 0.089264, 0.385742, 0.494537, 0.362854, 0.054626, 35 | -0.280182, -0.478729, -0.444458, -0.194000, 0.150787, 0.422241, 0.488281, 36 | 0.316864, -0.008728, -0.330048, -0.490845, -0.412933, -0.134186, 0.209839, 37 | 0.451782, 0.473969, 0.265625, -0.071930, -0.374512, -0.494568, -0.374664, 38 | -0.072174, 0.265411, 0.473877, 0.451874, 0.210022, -0.133942, -0.412811, 39 | -0.490906, -0.330231, -0.008972, 0.316650, 0.488251, 0.422363, 0.151031, 40 | -0.193787, -0.444336, -0.478790, -0.280365, 0.054382, 0.362671, 0.494537, 41 | 0.385895, 0.089508, -0.250427, -0.468567, -0.458801, -0.225922, 0.116852, 42 | 0.402802, 0.492798, 0.343109, 0.026520, -0.302948, -0.485107, -0.431305, 43 | -0.167755, 0.177399, 0.436249, 0.482910, 0.294678, -0.036896, -0.350525, 44 | -0.493683, -0.396729, -0.106842, 0.235016, 0.462555, 0.465088, 0.241425, 45 | -0.099701, -0.392303, -0.494141, -0.355621, -0.044159, 0.288788, 0.481262, 46 | 0.439636, 0.184174, -0.160858, -0.427704, -0.486511, -0.308685, 0.019257, 47 | 0.337830, 0.492065, 0.406982, 0.123932, -0.219421, -0.456024, -0.470856, 48 | -0.256683, 0.082306, 0.381287, 0.494537, 0.367615, 0.061646, -0.274323, 49 | -0.476868, -0.447479, -0.200470, 0.144043, 0.418518, 0.489380, 0.322235, 50 | -0.001678, -0.324768, -0.489899, -0.416779, -0.140961, 0.203400, 0.448822, 51 | 0.475952, 0.271545, -0.064941, -0.369873, -0.494568, -0.379242, -0.079132, 52 | 0.259430, 0.471832, 0.454712, 0.216400, -0.127167, -0.408875, -0.491760, 53 | -0.335449, -0.016022, 0.311218, 0.487061, 0.425995, 0.157715, -0.187286, 54 | -0.441193, -0.480530, -0.286163, 0.047363, 0.357849, 0.494293, 0.390289, 55 | 0.096436, -0.244324, -0.466248, -0.461395, -0.232178, 0.109985, 0.398651, 56 | 0.493378, 0.348145, 0.033569, -0.297333, -0.483673, -0.434723, -0.174347, 57 | 0.170807, 0.432861, 0.484406, 0.300323, -0.029846, -0.345520, -0.493134, 58 | -0.400909, -0.113708, 0.228790, 0.459991, 0.467438, 0.247528, -0.092773, 59 | -0.388000, -0.494507, -0.360504, -0.051178, 0.283020, 0.479584, 0.442841, 60 | 0.190704, -0.154175, -0.424103, -0.487762, -0.314178, 0.012207, 0.332611, 61 | 0.491241, 0.410950, 0.130737, -0.213074, -0.453247, -0.472992, -0.262695, 62 | 0.075348, 0.376740, 0.494537, 0.372314, 0.068634, -0.268433, -0.474945, 63 | -0.450470, -0.206909, 0.137299, 0.414703, 0.490387, 0.327545, 0.005402, 64 | -0.319397, -0.488861, -0.420563, -0.147705, 0.196991, 0.445862, 0.477844, 65 | 0.277435, -0.057953, -0.365143, -0.494568, -0.383728, -0.086090, 0.253418, 66 | 0.469635, 0.457428, 0.222717, -0.120331, -0.404877, -0.492493, -0.340607, 67 | -0.023071, 0.305695, 0.485748, 0.429535, 0.164398, -0.180725, -0.437958, 68 | -0.482178, -0.291870, 0.040344, 0.352966, 0.493866, 0.394562, 0.103333, 69 | -0.238159, -0.463837, -0.463928, -0.238373, 0.103088, 0.394440, 0.493896, 70 | 0.353119, 0.040588, -0.291687, -0.482117, -0.438049, -0.180939, 0.164154, 71 | 0.429413, 0.485809, 0.305878, -0.022797, -0.340424, -0.492462, -0.404999, 72 | -0.120544, 0.222504, 0.457336, 0.469727, 0.253632, -0.085846, -0.383575, 73 | -0.494568, -0.365295, -0.058197, 0.277222, 0.477783, 0.445923, 0.197205, 74 | -0.147461, -0.420410, -0.488922, -0.319580, 0.005127, 0.327362, 0.490356, 75 | 0.414825, 0.137512, -0.206696, -0.450348, -0.475037, -0.268646, 0.068390, 76 | 0.372131, 0.494537, 0.376892, 0.075592, -0.262482, -0.472900, -0.453339, 77 | -0.213287, 0.130524, 0.410797, 0.491302, 0.332794, 0.012451, -0.313995, 78 | -0.487701, -0.424225, -0.154419, 0.190491, 0.442719, 0.479645, 0.283234, 79 | -0.050934, -0.360352, -0.494476, -0.388153, -0.093018, 0.247345, 0.467377, 80 | 0.460083, 0.229004, -0.113464, -0.400757, -0.493164, -0.345673, -0.030090, 81 | 0.300110, 0.484344, 0.432983, 0.171021, -0.174133, -0.434601, -0.483704, 82 | -0.297546, 83 | }; 84 | 85 | static const float dual_channel_micro[] = { 86 | -0.120544, -0.120544, 0.222534, 0.222534, 87 | }; 88 | 89 | typedef struct TestCases { 90 | int frequency; 91 | float expected_psd; 92 | } TestCases; 93 | 94 | TestCases cases[] = { 95 | { 96 | .frequency = 100, 97 | .expected_psd = 0.09595596265773097, 98 | }, 99 | { 100 | .frequency = 200, 101 | .expected_psd = 0.09558897341820413, 102 | }, 103 | { 104 | .frequency = 1000, 105 | .expected_psd = 0.10730647827342488, 106 | }, 107 | { 108 | .frequency = 2000, 109 | .expected_psd = 0.14449811458513645, 110 | }, 111 | { 112 | .frequency = 5000, 113 | .expected_psd = 126.14145371375102, 114 | }, 115 | { 116 | .frequency = 10000, 117 | .expected_psd = 0.14292403328440603, 118 | }, 119 | }; 120 | 121 | static inline bool float_approx_equal(float f1, float f2) { 122 | return fabsf(f1 - f2) > 0.00001 ? false : true; 123 | } 124 | 125 | void test_init() { 126 | clock_t start, end; 127 | double cpu_time; 128 | start = clock(); 129 | fft_init(); 130 | end = clock(); 131 | fprintf(stdout, "[FFT Init %d samples] time: %lf\n", MAX_SAMPLES, (double) (end - start) / CLOCKS_PER_SEC); 132 | } 133 | 134 | void test_single_channel() { 135 | clock_t start, end; 136 | double cpu_time; 137 | complex float output[MAX_SAMPLES]; 138 | unsigned int N = sizeof(single_channel_5000HZ_data) / sizeof(single_channel_5000HZ_data[0]); 139 | 140 | start = clock(); 141 | fft_run(single_channel_5000HZ_data, output, N, 1); 142 | end = clock(); 143 | 144 | for (int i = 0; i < sizeof(cases)/sizeof(cases[0]); i++) { 145 | int sample_index = FFT_FREQ_TO_SAMPLE(N, cases[i].frequency); 146 | float psd = FFT_PSD(output[sample_index]); 147 | assert(float_approx_equal(cases[i].expected_psd, FFT_PSD(output[sample_index]))); 148 | } 149 | 150 | fprintf(stdout, "[FFT Run %d samples] time: %lf\n", N, (double) (end - start) / CLOCKS_PER_SEC); 151 | } 152 | 153 | void test_dual_channel_micro() { 154 | clock_t start, end; 155 | double cpu_time; 156 | complex float output[MAX_SAMPLES]; 157 | unsigned int N = sizeof(dual_channel_micro) / sizeof(dual_channel_micro[0]); 158 | 159 | start = clock(); 160 | fft_run(dual_channel_micro, output, N, 2); 161 | end = clock(); 162 | 163 | 164 | fprintf(stdout, "[FFT Run %d samples] time: %lf\n", N, (double) (end - start) / CLOCKS_PER_SEC); 165 | } 166 | 167 | int main(int argc, char **argv) { 168 | test_init(); 169 | test_single_channel(); 170 | test_dual_channel_micro(); 171 | return 0; 172 | } 173 | -------------------------------------------------------------------------------- /equalizer/macro.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define PRETTY_EXPORT __attribute__ ((visibility ("default"))) 4 | #define MAY_ALIAS __attribute__((__may_alias__)) 5 | 6 | #define SWAP(a, b) \ 7 | ({ \ 8 | typeof(a) _tmp_ = b; \ 9 | b = a; \ 10 | a = _tmp_; \ 11 | }) 12 | 13 | #define BOX_USERDATA(var) ((void *) ((uint64_t) (var))) 14 | #define UNBOX_USERDATA(type, var) ((type) ((uint64_t) (var))) 15 | 16 | #define _likely_(x) __builtin_expect(!!(x), 1) 17 | #define _unlikely_(x) __builtin_expect(!!(x), 0) 18 | 19 | #define _real_ __real__ 20 | #define _imag_ __imag__ 21 | -------------------------------------------------------------------------------- /equalizer/pretty.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "arena.h" 15 | #include "fft.h" 16 | #include "pretty.h" 17 | 18 | #define NUM_FILTERS 7 19 | #define LATENCY_MAX_MS 30 20 | 21 | //============================================================================= 22 | 23 | static pa_mainloop_api *mainloop_api = NULL; 24 | static pa_context *context = NULL; 25 | static uint32_t prettyeq_module_index = PA_INVALID_INDEX; 26 | static pa_stream *read_stream = NULL, *write_stream = NULL; 27 | static pa_threaded_mainloop *m = NULL; 28 | 29 | static const pa_sample_spec sample_spec = { 30 | // https://freedesktop.org/software/pulseaudio/doxygen/sample_8h.html 31 | .format = PA_SAMPLE_FLOAT32LE, 32 | .rate = 44100, 33 | .channels = 2, 34 | }; 35 | 36 | //============================================================================= 37 | 38 | static _Atomic bool bypass; 39 | 40 | static arena_t *arena = NULL; 41 | 42 | struct _AudioFFT { 43 | pthread_spinlock_t lock; 44 | complex float data[MAX_SAMPLES]; 45 | unsigned int N; 46 | }; 47 | static AudioFFT audio_fft = {0}; 48 | 49 | typedef struct FilterParams { 50 | float a[3]; 51 | float b[3]; 52 | } FilterParams; 53 | 54 | struct _PrettyFilter { 55 | /* Filter parameters used in the audio loop. */ 56 | float xwin[3]; 57 | float ywin[2]; 58 | 59 | /* Temporary parameters for compare-exchange. */ 60 | _Atomic (FilterParams*) params; 61 | FilterParams *storage; 62 | }; 63 | static PrettyFilter filters[NUM_FILTERS]; 64 | static int user_enabled_filters = 0; 65 | 66 | //============================================================================= 67 | 68 | static void quit(int ret) { 69 | fprintf(stderr, "debug: quit(%d) called!", ret); 70 | if (prettyeq_module_index != PA_INVALID_INDEX) 71 | pa_operation_unref(pa_context_unload_module(context, prettyeq_module_index, NULL, NULL)); 72 | mainloop_api->quit(mainloop_api, ret); 73 | } 74 | 75 | static void cleanup() { 76 | if (read_stream) 77 | pa_stream_unref(read_stream); 78 | 79 | if (write_stream) 80 | pa_stream_unref(write_stream); 81 | 82 | if (context) 83 | pa_context_unref(context); 84 | 85 | if (m) 86 | pa_threaded_mainloop_free(m); 87 | 88 | if (arena) 89 | arena_destroy(&arena); 90 | 91 | if (audio_fft.lock) { 92 | pthread_spin_destroy(&audio_fft.lock); 93 | } 94 | 95 | munlockall(); 96 | } 97 | 98 | //============================================================================= 99 | 100 | static void success_callback(pa_context *c, int success, void *userdata) { 101 | assert(c); 102 | 103 | if ( !success) { 104 | fprintf(stderr, "Failure: %s", pa_strerror(pa_context_errno(c))); 105 | quit(1); 106 | } 107 | } 108 | 109 | static void sink_input_callback(pa_context *c, const pa_sink_input_info *i, int is_last, void *userdata) { 110 | assert(c); 111 | 112 | if (is_last < 0) { 113 | fprintf(stderr, "Failed to get sink information: %s", 114 | pa_strerror(pa_context_errno(c))); 115 | return; 116 | } 117 | 118 | if (is_last) 119 | return; 120 | 121 | assert(i); 122 | 123 | if (strcmp(SINK_NAME, i->name) != 0) { 124 | const char *app_name; 125 | app_name = pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME); 126 | fprintf(stderr, "new sink-input: name=%s, index=%d, sink=%d\n", app_name, i->index, i->sink); 127 | pa_operation_unref(pa_context_move_sink_input_by_name(c, i->index, SINK_NAME, NULL, NULL)); 128 | } else { 129 | /* Pulseaudio keeps sink state, so if the user changed the prettyeq 130 | * playback stream volume, we reset back to 100% here. This is so the 131 | * equalizer "at rest" doesn't modify the input stream. */ 132 | pa_cvolume volume; 133 | volume = i->volume; 134 | pa_cvolume_reset(&volume, i->sample_spec.channels); 135 | pa_operation_unref(pa_context_set_sink_input_volume(c, 136 | i->index, 137 | &volume, 138 | success_callback, 139 | NULL)); 140 | } 141 | } 142 | 143 | static void subscribe_callback( 144 | pa_context *c, 145 | pa_subscription_event_type_t t, 146 | uint32_t idx, 147 | void *userdata) { 148 | assert(c); 149 | assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT); 150 | 151 | if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) 152 | pa_operation_unref(pa_context_get_sink_input_info(c, idx, sink_input_callback, NULL)); 153 | } 154 | 155 | static void null_source_output_callback(pa_context *c, const pa_source_output_info *i, int is_last, void *userdata) { 156 | uint32_t prettyeq_source = UNBOX_USERDATA(uint32_t, userdata); 157 | 158 | assert(c); 159 | 160 | if (is_last < 0) { 161 | fprintf(stderr, "Failed to get null source output information: %s\n", 162 | pa_strerror(pa_context_errno(c))); 163 | quit(1); 164 | return; 165 | } 166 | 167 | if (is_last) 168 | return; 169 | 170 | assert(i); 171 | 172 | if (prettyeq_source == i->source) { 173 | /* The recording stream sometimes starts at 79% instead of 100%, so 174 | * let's fix that here or else we start at a lower volume. */ 175 | pa_cvolume volume; 176 | volume = i->volume; 177 | pa_cvolume_reset(&volume, i->sample_spec.channels); 178 | pa_operation_unref(pa_context_set_source_output_volume(c, 179 | i->index, 180 | &volume, 181 | success_callback, 182 | NULL)); 183 | } 184 | } 185 | 186 | static void null_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_last, void *userdata) { 187 | pa_buffer_attr buffer_attr; 188 | 189 | assert(c); 190 | 191 | if (is_last < 0) { 192 | fprintf(stderr, "Failed to get null sink information: %s\n", 193 | pa_strerror(pa_context_errno(c))); 194 | quit(1); 195 | return; 196 | } 197 | 198 | if (is_last) 199 | return; 200 | 201 | assert(i); 202 | 203 | /* Hook up the read stream to the null sink monitor output. */ 204 | memset(&buffer_attr, 0, sizeof(buffer_attr)); 205 | buffer_attr.maxlength = (uint32_t) -1; 206 | buffer_attr.prebuf = (uint32_t) -1; 207 | buffer_attr.fragsize = pa_usec_to_bytes(LATENCY_MAX_MS * PA_USEC_PER_MSEC, &sample_spec); 208 | buffer_attr.tlength = pa_usec_to_bytes(LATENCY_MAX_MS * PA_USEC_PER_MSEC, &sample_spec); 209 | if (pa_stream_connect_record(read_stream, i->monitor_source_name, &buffer_attr, PA_STREAM_ADJUST_LATENCY) < 0) { 210 | fprintf(stderr, "pa_stream_connect_record() failed: %s\n", 211 | pa_strerror(pa_context_errno(c))); 212 | quit(1); 213 | } 214 | fprintf(stderr, "monitor source name=%s\n", i->monitor_source_name); 215 | pa_operation_unref(pa_context_get_source_output_info_list(c, 216 | null_source_output_callback, 217 | BOX_USERDATA(i->monitor_source))); 218 | } 219 | 220 | static void unload_module_callback(pa_context *c, int success, void *userdata) { 221 | pa_threaded_mainloop *m; 222 | m = userdata; 223 | assert(m); 224 | 225 | if (! success) 226 | fprintf(stderr, "pa_context_unload_module() failed: %s", 227 | pa_strerror(pa_context_errno(c))); 228 | 229 | pa_threaded_mainloop_signal(m, 0); 230 | } 231 | 232 | static void read_stream_callback(pa_stream *s, size_t length, void *userdata) { 233 | assert(s); 234 | assert(length > 0); 235 | 236 | while (pa_stream_readable_size(s) > 0) { 237 | const void *input_data = NULL; 238 | float *fp = NULL; 239 | size_t num_samples; 240 | 241 | if (pa_stream_peek(s, &input_data, &length) < 0) { 242 | fprintf(stderr, "pa_stream_peek() failed: %s\n", 243 | pa_strerror(pa_context_errno(context))); 244 | quit(1); 245 | return; 246 | } 247 | 248 | fp = (float *) input_data; 249 | num_samples = length / sizeof(float); 250 | 251 | if (bypass) 252 | goto play_frame; 253 | 254 | #ifndef EQ_DISABLE 255 | for (int k = 0; k < NUM_FILTERS; k++) { 256 | /* Swap in NULL to block a filter param update mid-iteration. */ 257 | FilterParams *params = atomic_exchange(&filters[k].params, NULL); 258 | 259 | float *xwin = filters[k].xwin; 260 | float *ywin = filters[k].ywin; 261 | float *a = params->a; 262 | float *b = params->b; 263 | 264 | for (unsigned long i = 0; i < length / sizeof(float); i++) { 265 | float f; 266 | 267 | /* Slide x-window */ 268 | xwin[2] = xwin[1]; 269 | xwin[1] = xwin[0]; 270 | xwin[0] = fp[i]; 271 | 272 | f = fp[i]; 273 | f = (b[0] / a[0] * xwin[0]) + 274 | (b[1] / a[0] * xwin[1]) + 275 | (b[2] / a[0] * xwin[2]) - 276 | (a[1] / a[0] * ywin[0]) - 277 | (a[2] / a[0] * ywin[1]); 278 | 279 | if (PRETTY_IS_DENORMAL(f) || PRETTY_IS_NAN(f)) 280 | f = 0.0f; 281 | 282 | fp[i] = f; 283 | 284 | /* Slide y-window */ 285 | ywin[1] = ywin[0]; 286 | ywin[0] = f; 287 | } 288 | /* Unblock any pending filter param updates. */ 289 | atomic_store(&filters[k].params, params); 290 | } 291 | #endif // EQ_DISABLE 292 | 293 | play_frame: 294 | for (;;) { 295 | size_t data_length = length; 296 | uint8_t *output_data = NULL; 297 | 298 | if (pa_stream_begin_write(write_stream, (void **)&output_data, &data_length) < 0) { 299 | fprintf(stderr, "pa_stream_begin_write() failed: %s\n", 300 | pa_strerror(pa_context_errno(context))); 301 | quit(1); 302 | return; 303 | } 304 | 305 | memcpy(output_data, input_data, data_length); 306 | if (pa_stream_write(write_stream, output_data, data_length, NULL, 0, PA_SEEK_RELATIVE) < 0) { 307 | fprintf(stderr, "pa_stream_write() failed: %s\n", 308 | pa_strerror(pa_context_errno(context))); 309 | quit(1); 310 | return; 311 | } 312 | 313 | if (data_length >= length) 314 | break; 315 | 316 | length -= data_length; 317 | input_data += data_length; 318 | } 319 | 320 | if (pthread_spin_trylock(&audio_fft.lock) >= 0) { 321 | /* Sigh... In no world should pulse really be handing us anything more than 322 | * uint16_t in a rapid callback. Fuck it, we downcast. */ 323 | assert(num_samples <= UINT_MAX); 324 | fft_run(fp, audio_fft.data, (unsigned int) num_samples, sample_spec.channels); 325 | audio_fft.N = (unsigned int) num_samples / sample_spec.channels; 326 | pthread_spin_unlock(&audio_fft.lock); 327 | } 328 | 329 | if (pa_stream_drop(s) < 0) { 330 | fprintf(stderr, "pa_stream_drop() failed: %s\n", 331 | pa_strerror(pa_context_errno(context))); 332 | quit(1); 333 | return; 334 | } 335 | } 336 | } 337 | 338 | static void load_module_callback(pa_context *c, uint32_t idx, void *userdata) { 339 | assert(c); 340 | 341 | if (idx == PA_INVALID_INDEX) { 342 | fprintf(stderr, "Bad index\n"); 343 | quit(1); 344 | return; 345 | } 346 | 347 | prettyeq_module_index = idx; 348 | pa_operation_unref(pa_context_get_sink_input_info_list(c, sink_input_callback, NULL)); 349 | pa_operation_unref(pa_context_get_sink_info_by_name(c, SINK_NAME, null_sink_info_callback, NULL)); 350 | 351 | /* Subscribe to new sink-inputs so we can send them through the equalizer. */ 352 | pa_context_set_subscribe_callback(c, subscribe_callback, NULL); 353 | pa_operation_unref(pa_context_subscribe(c, PA_SUBSCRIPTION_MASK_SINK_INPUT, NULL, NULL)); 354 | } 355 | 356 | static void module_list_callback(pa_context *c, const pa_module_info *i, int is_last, void *userdata) { 357 | pa_threaded_mainloop *m = userdata; 358 | static bool signal_on_last = true; 359 | 360 | assert(m); 361 | assert(c); 362 | 363 | if (is_last) { 364 | if (signal_on_last) 365 | pa_threaded_mainloop_signal(m, 0); 366 | return; 367 | } 368 | 369 | assert(i); 370 | if (strcmp(i->name, "module-null-sink") == 0 && i->argument && strcmp(i->argument, "sink_name=" SINK_NAME) == 0) { 371 | pa_operation_unref(pa_context_unload_module(c, i->index, unload_module_callback, m)); 372 | signal_on_last = false; 373 | } 374 | 375 | } 376 | 377 | static void drain_signal_callback(pa_context *c, void *userdata) { 378 | pa_threaded_mainloop *m = userdata; 379 | 380 | assert(c); 381 | assert(m); 382 | 383 | pa_threaded_mainloop_signal(m, 0); 384 | } 385 | 386 | static void context_state_callback(pa_context *c, void *userdata) { 387 | switch (pa_context_get_state(c)) { 388 | case PA_CONTEXT_UNCONNECTED: 389 | case PA_CONTEXT_CONNECTING: 390 | case PA_CONTEXT_AUTHORIZING: 391 | case PA_CONTEXT_SETTING_NAME: 392 | break; 393 | 394 | case PA_CONTEXT_READY: { 395 | 396 | if ( !(read_stream = pa_stream_new(c, "prettyeq", &sample_spec, NULL))) { 397 | fprintf(stderr, "pa_stream_new() failed.\n"); 398 | quit(1); 399 | return; 400 | } 401 | 402 | if ( !(write_stream = pa_stream_new(c, "prettyeq", &sample_spec, NULL))) { 403 | fprintf(stderr, "pa_stream_new() failed.\n"); 404 | quit(1); 405 | return; 406 | } 407 | 408 | /* Setup the playback stream. We create the recording stream in the null sink callback. */ 409 | if (pa_stream_connect_playback(write_stream, NULL, NULL, 0, NULL, NULL) < 0) { 410 | fprintf(stderr, "pa_stream_connect_playback() failed: %s\n", 411 | pa_strerror(pa_context_errno(c))); 412 | quit(1); 413 | return; 414 | } 415 | 416 | /* Audio event callback functions. */ 417 | pa_stream_set_read_callback(read_stream, read_stream_callback, NULL); 418 | pa_threaded_mainloop_signal(m, 0); 419 | 420 | fprintf(stderr, "context is ready!\n"); 421 | break; 422 | } 423 | case PA_CONTEXT_FAILED: 424 | fprintf(stderr, "Unclean termination\n"); 425 | quit(1); 426 | break; 427 | case PA_CONTEXT_TERMINATED: 428 | fprintf(stderr, "Clean termination\n"); 429 | quit(0); 430 | break; 431 | 432 | } 433 | } 434 | 435 | //============================================================================= 436 | 437 | int pretty_init() { 438 | int r; 439 | 440 | /* Initialize the fft and user exposed data structures. */ 441 | fft_init(); 442 | audio_fft.N = 0; 443 | r = pthread_spin_init(&audio_fft.lock, PTHREAD_PROCESS_PRIVATE); 444 | if (r < 0) { 445 | fprintf(stderr, "Could not pthread_spin_init()"); 446 | goto err; 447 | } 448 | 449 | /* Initialize the memory arena used to create new filters. */ 450 | arena = arena_new(NUM_FILTERS * 2, sizeof(FilterParams)); 451 | if (!arena) { 452 | fprintf(stderr, "Could not arena_new()"); 453 | r = -ENOMEM; 454 | goto err; 455 | } 456 | 457 | /* Initialize filters. */ 458 | for (int i = 0; i < NUM_FILTERS; i++) { 459 | FilterParams *start_params; 460 | PrettyFilter *filter = &filters[i]; 461 | 462 | filter->xwin[0] = 0.0f; 463 | filter->xwin[1] = 0.0f; 464 | filter->xwin[2] = 0.0f; 465 | filter->ywin[0] = 0.0f; 466 | filter->ywin[1] = 0.0f; 467 | 468 | start_params = arena_alloc(arena); 469 | atomic_store(&filter->params, start_params); 470 | filter->storage = start_params; 471 | 472 | /* We don't want to page fault reading filters in the audio loop. */ 473 | r = mlock(&filter, sizeof(PrettyFilter)); 474 | if (r < 0) { 475 | fprintf(stderr, "mlock() failed: %s.", strerror(errno)); 476 | goto err; 477 | } 478 | } 479 | 480 | /* Start with bypass mode disabled. */ 481 | atomic_store(&bypass, false); 482 | 483 | /* Initialize pulseaudio mainloop. */ 484 | if ( !(m = pa_threaded_mainloop_new())) { 485 | fprintf(stderr, "pa_mainloop_new() failed."); 486 | r = -1; 487 | goto err; 488 | } 489 | 490 | mainloop_api = pa_threaded_mainloop_get_api(m); 491 | if ( !(context = pa_context_new_with_proplist(mainloop_api, NULL, NULL))) { 492 | fprintf(stderr, "pa_context_new() failed."); 493 | r = -1; 494 | goto err; 495 | } 496 | 497 | pa_context_set_state_callback(context, context_state_callback, NULL); 498 | if (pa_context_connect(context, NULL, 0, NULL) < 0) { 499 | fprintf(stderr, "pa_context_connect() failed."); 500 | r = -1; 501 | goto err; 502 | } 503 | 504 | r = pa_threaded_mainloop_start(m); 505 | if (r < 0) { 506 | fprintf(stderr, "pa_threaded_mainloop_start() failed."); 507 | goto err; 508 | } 509 | 510 | return 0; 511 | 512 | err: 513 | cleanup(); 514 | return r; 515 | } 516 | 517 | void pretty_setup_sink_io() { 518 | pa_operation *o; 519 | 520 | pa_threaded_mainloop_lock(m); 521 | 522 | /* Wait until context is ready. */ 523 | while(pa_context_get_state(context) != PA_CONTEXT_READY) 524 | pa_threaded_mainloop_wait(m); 525 | 526 | /* Handle an unclean termination and cleanup an existing prettyeq module. */ 527 | o = pa_context_get_module_info_list(context, module_list_callback, m); 528 | while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) 529 | pa_threaded_mainloop_wait(m); 530 | pa_operation_cancel(o); 531 | pa_operation_unref(o); 532 | 533 | /* Drain the context. TODO(keur): Not sure if we actually need this. Poorly documented function */ 534 | if ((o = pa_context_drain(context, drain_signal_callback, m))) { 535 | while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) 536 | pa_threaded_mainloop_wait(m); 537 | pa_operation_unref(o); 538 | } 539 | 540 | pa_threaded_mainloop_unlock(m); 541 | 542 | /* Load in the null sink to act as audio IO. */ 543 | pa_operation_unref(pa_context_load_module(context, 544 | "module-null-sink", 545 | "sink_name=" SINK_NAME, 546 | load_module_callback, 547 | NULL)); 548 | } 549 | 550 | int pretty_exit() { 551 | if (m) { 552 | pa_operation *o; 553 | if (prettyeq_module_index != PA_INVALID_INDEX) { 554 | pa_threaded_mainloop_lock(m); 555 | o = pa_context_unload_module(context, prettyeq_module_index, unload_module_callback, m); 556 | while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) 557 | pa_threaded_mainloop_wait(m); 558 | pa_operation_unref(o); 559 | pa_threaded_mainloop_unlock(m); 560 | } 561 | pa_threaded_mainloop_stop(m); 562 | } 563 | cleanup(); 564 | 565 | return 0; 566 | } 567 | 568 | int pretty_new_filter(PrettyFilter **filter) { 569 | assert(user_enabled_filters < NUM_FILTERS); 570 | *filter = &filters[user_enabled_filters++]; 571 | return 0; 572 | } 573 | 574 | static inline void safe_update_audio_loop(PrettyFilter *filter, FilterParams *new_params) { 575 | FilterParams *expected; 576 | 577 | assert(filter); 578 | assert(new_params); 579 | 580 | do { 581 | expected = filter->storage; 582 | atomic_compare_exchange_strong(&filter->params, &expected, new_params); 583 | } while (!expected); 584 | 585 | arena_dealloc(arena, filter->storage); 586 | filter->storage = new_params; 587 | } 588 | 589 | void pretty_set_peaking_eq(PrettyFilter *filter, float f0, float bandwidth, float db_gain) { 590 | FilterParams *new_params; 591 | float A, alpha, w0, sinw0, cosw0; 592 | 593 | new_params = arena_alloc(arena); 594 | 595 | A = powf(10, db_gain/40); 596 | w0 = 2*M_PI*f0/sample_spec.rate; 597 | sinw0 = sinf(w0); 598 | cosw0 = cosf(w0); 599 | alpha = sinw0*sinhf(logf(2)/2 * bandwidth * w0/sinw0); 600 | new_params->b[0] = 1 + alpha*A; 601 | new_params->b[1] = -2 * cosw0; 602 | new_params->b[2] = 1 - alpha*A; 603 | new_params->a[0] = 1 + alpha/A; 604 | new_params->a[1] = -2 * cosw0; 605 | new_params->a[2] = 1 - alpha/A; 606 | 607 | safe_update_audio_loop(filter, new_params); 608 | } 609 | 610 | void pretty_set_low_shelf(PrettyFilter *filter, float f0, float S, float db_gain) { 611 | FilterParams *new_params; 612 | float A, alpha, w0, sinw0, cosw0, sqrtA; 613 | 614 | new_params = arena_alloc(arena); 615 | 616 | A = powf(10, db_gain / 40); 617 | w0 = 2*M_PI*f0/sample_spec.rate; 618 | sinw0 = sinf(w0); 619 | cosw0 = cosf(w0); 620 | sqrtA = sqrtf(A); 621 | alpha = sinw0/2 * sqrtf((A + 1/A) * (1/S - 1) + 2); 622 | new_params->b[0] = A*((A + 1) - (A - 1)*cosw0 + 2*sqrtA*alpha); 623 | new_params->b[1] = 2*A*((A - 1) - (A + 1)*cosw0); 624 | new_params->b[2] = A*((A + 1) - (A - 1)*cosw0 - 2*sqrtA*alpha); 625 | new_params->a[0] = (A + 1) + (A - 1)*cosw0 + 2*sqrtA*alpha; 626 | new_params->a[1] = -2*((A - 1) + (A + 1)*cosw0); 627 | new_params->a[2] = (A + 1) + (A - 1)*cosw0 - 2*sqrtA*alpha; 628 | 629 | safe_update_audio_loop(filter, new_params); 630 | } 631 | 632 | void pretty_set_high_shelf(PrettyFilter *filter, float f0, float S, float db_gain) { 633 | FilterParams *new_params; 634 | float A, alpha, w0, sinw0, cosw0, sqrtA; 635 | 636 | new_params = arena_alloc(arena); 637 | 638 | A = powf(10, db_gain / 40); 639 | w0 = 2*M_PI*f0/sample_spec.rate; 640 | sinw0 = sinf(w0); 641 | cosw0 = cosf(w0); 642 | sqrtA = sqrtf(A); 643 | alpha = sinw0/2 * sqrtf((A + 1/A) * (1/S - 1) + 2); 644 | new_params->b[0] = A*((A + 1) + (A - 1)*cosw0 + 2*sqrtA*alpha); 645 | new_params->b[1] = -2*A*((A - 1) + (A + 1)*cosw0); 646 | new_params->b[2] = A*((A + 1) + (A - 1)*cosw0 - 2*sqrtA*alpha); 647 | new_params->a[0] = (A + 1) - (A - 1)*cosw0 + 2*sqrtA*alpha; 648 | new_params->a[1] = 2*((A - 1) - (A + 1)*cosw0); 649 | new_params->a[2] = (A + 1) - (A - 1)*cosw0 - 2*sqrtA*alpha; 650 | 651 | safe_update_audio_loop(filter, new_params); 652 | } 653 | 654 | void pretty_enable_bypass(bool should_bypass) 655 | { 656 | atomic_store(&bypass, should_bypass); 657 | } 658 | 659 | void pretty_acquire_audio_data(complex float **data, unsigned int *N) { 660 | int r = pthread_spin_lock(&audio_fft.lock); 661 | assert(r == 0); /* No deadlock. */ 662 | *data = audio_fft.data; 663 | *N = audio_fft.N; 664 | } 665 | 666 | void pretty_release_audio_data() { 667 | int r = pthread_spin_unlock(&audio_fft.lock); 668 | assert(r == 0); /* No deadlock. */ 669 | } 670 | -------------------------------------------------------------------------------- /equalizer/pretty.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "macro.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #define SINK_NAME "prettyeq" 10 | 11 | #define DRAIN_NO_CB(c) (pa_operation_unref(pa_context_drain(c, NULL, NULL))) 12 | 13 | /* with -ffast-math the standard macros have undefined behavior. */ 14 | #define PRETTY_IS_DENORMAL(f) \ 15 | ({ \ 16 | typeof(f) *_f_ = &(f); \ 17 | ((*(unsigned int *)_f_) & 0x7f800000u) == 0 || ((*(unsigned int *)_f_) & 0xff800000u) == 0; \ 18 | }) 19 | 20 | #define PRETTY_IS_NAN(f) \ 21 | ({ \ 22 | typeof(f) *_f_ = &(f); \ 23 | ((*(unsigned int *)_f_) << 1) > 0xff000000u; \ 24 | }) 25 | 26 | /* accomodating the stupid C++ template redefinition... */ 27 | #ifdef __cplusplus 28 | typedef std::complex* FFTComplexCompat; 29 | #else 30 | typedef complex float* FFTComplexCompat; 31 | #endif 32 | 33 | #ifdef __cplusplus 34 | extern "C" { 35 | #endif 36 | 37 | typedef struct _PrettyFilter PrettyFilter; 38 | 39 | typedef struct _AudioFFT AudioFFT; 40 | 41 | PRETTY_EXPORT 42 | int pretty_init(); 43 | 44 | PRETTY_EXPORT 45 | int pretty_exit(); 46 | 47 | PRETTY_EXPORT 48 | void pretty_setup_sink_io(); 49 | 50 | PRETTY_EXPORT 51 | int pretty_new_filter(PrettyFilter **filter); 52 | 53 | PRETTY_EXPORT 54 | void pretty_set_peaking_eq(PrettyFilter *filter, float f0, float bandwidth, float db_gain); 55 | 56 | PRETTY_EXPORT 57 | void pretty_set_low_shelf(PrettyFilter *filter, float f0, float S, float db_gain); 58 | 59 | PRETTY_EXPORT 60 | void pretty_set_high_shelf(PrettyFilter *filter, float f0, float S, float db_gain); 61 | 62 | PRETTY_EXPORT 63 | void pretty_enable_bypass(bool should_bypass); 64 | 65 | PRETTY_EXPORT 66 | void pretty_acquire_audio_data(FFTComplexCompat *data, unsigned int *N); 67 | 68 | PRETTY_EXPORT 69 | void pretty_release_audio_data(); 70 | 71 | #ifdef __cplusplus 72 | } 73 | #endif 74 | -------------------------------------------------------------------------------- /gui/collisionmanager.cpp: -------------------------------------------------------------------------------- 1 | #include "collisionmanager.h" 2 | #include "eqhoverer.h" 3 | #include "filtercurve.h" 4 | 5 | #include 6 | 7 | CollisionManager::CollisionManager() : numItems(0) 8 | { 9 | 10 | } 11 | 12 | void CollisionManager::addEqHoverer(EqHoverer *hover) 13 | { 14 | Q_ASSERT(hover); 15 | hoverItems[numItems++] = hover; 16 | } 17 | 18 | void CollisionManager::notifyFriends() 19 | { 20 | Q_ASSERT(numItems == NUM_FILTERS); 21 | 22 | for (int i = 0; i < numItems; i++) 23 | hoverItems[i]->collisionStateChanged(); 24 | } 25 | -------------------------------------------------------------------------------- /gui/collisionmanager.h: -------------------------------------------------------------------------------- 1 | #ifndef COLLISIONMANAGER_H 2 | #define COLLISIONMANAGER_H 3 | 4 | #define NUM_FILTERS 7 5 | 6 | class EqHoverer; 7 | class FilterCurve; 8 | 9 | class CollisionManager 10 | { 11 | public: 12 | explicit CollisionManager(); 13 | void addEqHoverer(EqHoverer *hover); 14 | void notifyFriends(); 15 | 16 | private: 17 | int numItems; 18 | EqHoverer *hoverItems[NUM_FILTERS]; 19 | }; 20 | 21 | #endif // COLLISIONMANAGER_H 22 | -------------------------------------------------------------------------------- /gui/curvepoint.cpp: -------------------------------------------------------------------------------- 1 | #include "curvepoint.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #define RADIUS 40 8 | #define ESCALE 0.60 9 | 10 | CurvePoint::CurvePoint(QBrush normalBrush, QBrush lightBrush, QObject *parent) 11 | : QObject(parent), 12 | normalBrush(normalBrush), 13 | lightBrush(lightBrush) 14 | { 15 | setZValue(100001); 16 | hide(); 17 | setFlags(GraphicsItemFlag::ItemIsSelectable | GraphicsItemFlag::ItemIsMovable); 18 | } 19 | 20 | QRectF CurvePoint::boundingRect() const 21 | { 22 | return QRectF(-RADIUS/2, -RADIUS/2, RADIUS, RADIUS); 23 | } 24 | 25 | void CurvePoint::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) 26 | { 27 | painter->setRenderHint(QPainter::Antialiasing, true); 28 | painter->setPen(Qt::NoPen); 29 | 30 | /* outer circle */ 31 | painter->setBrush(lightBrush); 32 | painter->drawEllipse(-RADIUS/2, -RADIUS/2, RADIUS, RADIUS); 33 | 34 | /* inner circle */ 35 | painter->scale(ESCALE, ESCALE); 36 | painter->setBrush(normalBrush); 37 | painter->drawEllipse(-RADIUS/2, -RADIUS/2, RADIUS, RADIUS); 38 | 39 | } 40 | 41 | int CurvePoint::type() const 42 | { 43 | /* Make this type work with qgraphicsitem_cast */ 44 | return Type; 45 | } 46 | 47 | void CurvePoint::setResetPos(QPointF resetPoint) 48 | { 49 | sceneResetPoint = resetPoint; 50 | setPos(sceneResetPoint); 51 | } 52 | 53 | void CurvePoint::reset() 54 | { 55 | setPos(sceneResetPoint); 56 | emit pointPositionChanged(this); 57 | } 58 | 59 | void CurvePoint::mouseMoveEvent(QGraphicsSceneMouseEvent *event) 60 | { 61 | QGraphicsItem::mouseMoveEvent(event); 62 | if (scene() && event->type() == QGraphicsSceneMouseEvent::GraphicsSceneMouseMove) { 63 | 64 | /* x bounds detection */ 65 | if (scene()->sceneRect().x() >= mapToScene(boundingRect().center()).x()) 66 | setPos(scene()->sceneRect().x(), pos().y()); 67 | else if (scene()->sceneRect().x() + scene()->width() <= mapToScene(boundingRect().center()).x()) 68 | setPos(scene()->sceneRect().x() + scene()->width(), pos().y()); 69 | 70 | /* y bounds detection */ 71 | if (scene()->sceneRect().y() >= mapToScene(boundingRect().center()).y()) 72 | setPos(pos().x(), scene()->sceneRect().y()); 73 | else if (scene()->sceneRect().y() + scene()->height() <= mapToScene(boundingRect().center()).y()) 74 | setPos(pos().x(), scene()->sceneRect().y() + scene()->sceneRect().height()); 75 | 76 | emit pointPositionChanged(this); 77 | } 78 | } 79 | 80 | void CurvePoint::wheelEvent(QGraphicsSceneWheelEvent *event) 81 | { 82 | QGraphicsItem::wheelEvent(event); 83 | wheelDeltaSum += event->delta(); 84 | if (wheelDeltaSum >= 120) { 85 | wheelDeltaSum = 0; 86 | emit pointSlopeChanged(-1); 87 | } else if (wheelDeltaSum <= -120) { 88 | wheelDeltaSum = 0; 89 | emit pointSlopeChanged(1); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /gui/curvepoint.h: -------------------------------------------------------------------------------- 1 | #ifndef CURVEPOINT_H 2 | #define CURVEPOINT_H 3 | 4 | #include "filtercurve.h" 5 | #include 6 | #include 7 | #include 8 | 9 | class CurvePoint : public QObject, public QGraphicsItem 10 | { 11 | Q_OBJECT 12 | Q_INTERFACES(QGraphicsItem) 13 | public: 14 | explicit CurvePoint(QBrush normalBrush, QBrush lightBrush, QObject *parent = nullptr); 15 | QRectF boundingRect() const override; 16 | void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; 17 | enum { Type = UserType + 2 }; 18 | int type() const override; 19 | 20 | void setResetPos(QPointF resetPoint); 21 | void reset(); 22 | protected: 23 | void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; 24 | void wheelEvent(QGraphicsSceneWheelEvent *event) override; 25 | 26 | signals: 27 | void pointPositionChanged(CurvePoint *point); 28 | void pointSlopeChanged(int delta); 29 | 30 | private: 31 | QBrush normalBrush, lightBrush; 32 | QPointF sceneResetPoint = QPointF(0, 0); 33 | int wheelDeltaSum = 0; 34 | }; 35 | 36 | #endif // CURVEPOINT_H 37 | -------------------------------------------------------------------------------- /gui/eqhoverer.cpp: -------------------------------------------------------------------------------- 1 | #include "collisionmanager.h" 2 | #include "eqhoverer.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static const unsigned int Default = 1; 9 | static const unsigned int Collision = 1 << 1; 10 | static const unsigned int Hover = 1 << 2; 11 | static const unsigned int ContextMenu = 1 << 3; 12 | 13 | EqHoverer::EqHoverer(CollisionManager *mgr, FilterCurve *curve, CurvePoint *point, QObject *parent) 14 | : QObject(parent), curve(curve), point(point), pointState(0), mgr(mgr) 15 | { 16 | Q_ASSERT(curve); 17 | Q_ASSERT(point); 18 | setAcceptHoverEvents(true); 19 | setFlag(QGraphicsItem::ItemHasNoContents, true); 20 | reset(); 21 | } 22 | 23 | 24 | QRectF EqHoverer::boundingRect() const 25 | { 26 | QRectF r = curve->boundingRect(); 27 | if (scene()) { 28 | r.setY(scene()->sceneRect().y()); 29 | r.setHeight(scene()->height()); 30 | return r; 31 | } else 32 | return QRectF(QPointF(0, 0), QPointF(0, 0)); 33 | } 34 | 35 | void EqHoverer::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) 36 | { 37 | #if 0 38 | // debugging boundingRect() 39 | QPen wpen(Qt::white); 40 | wpen.setWidth(3); 41 | painter->save(); 42 | painter->setPen(wpen); 43 | painter->drawRect(boundingRect()); 44 | painter->restore(); 45 | #endif 46 | 47 | } 48 | 49 | int EqHoverer::type() const 50 | { 51 | /* Make this type work with qgraphicsitem_cast */ 52 | return Type; 53 | } 54 | 55 | void EqHoverer::collisionStateChanged() 56 | { 57 | pointState &= ~Collision; 58 | for(QGraphicsItem *i : collidingItems()) { 59 | EqHoverer *cc = qgraphicsitem_cast(i); 60 | if (cc && cc != this) { 61 | if (isUnderMouse() || cc->isUnderMouse()) { 62 | pointState |= Collision; 63 | break; 64 | } 65 | } 66 | } 67 | maybeShowPoint(); 68 | } 69 | 70 | void EqHoverer::hoverEnterEvent(QGraphicsSceneHoverEvent *event) 71 | { 72 | #if 0 73 | qDebug() << "hoverEnterEvent();"; 74 | #endif 75 | pointState |= Hover; 76 | mgr->notifyFriends(); 77 | } 78 | 79 | void EqHoverer::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) 80 | { 81 | #if 0 82 | qDebug() << "hoverLeaveEvent();"; 83 | #endif 84 | pointState &= ~Hover; 85 | mgr->notifyFriends(); 86 | } 87 | 88 | void EqHoverer::contextMenuToggle(bool on) { 89 | int contextBit = (ContextMenu & static_cast(on)); 90 | pointState |= contextBit; 91 | pointState &= (~ContextMenu | contextBit); 92 | maybeShowPoint(); 93 | mgr->notifyFriends(); 94 | } 95 | 96 | void EqHoverer::reset() 97 | { 98 | /* Order of these calls *does* matter here because 99 | * resetting the point signals resync. */ 100 | point->reset(); 101 | curve->reset(); 102 | pointState |= Default; 103 | maybeShowPoint(); 104 | } 105 | 106 | void EqHoverer::maybeShowPoint() 107 | { 108 | if (pointState == 0) { 109 | point->hide(); 110 | curve->setColorState(DefaultState); 111 | } else { 112 | point->show(); 113 | curve->setColorState(PrettyState); 114 | } 115 | } 116 | 117 | void EqHoverer::resync(FilterCurve *curve) 118 | { 119 | pointState &= ~Default; 120 | prepareGeometryChange(); 121 | mgr->notifyFriends(); 122 | } 123 | -------------------------------------------------------------------------------- /gui/eqhoverer.h: -------------------------------------------------------------------------------- 1 | #ifndef EQHOVERER_H 2 | #define EQHOVERER_H 3 | 4 | #include "curvepoint.h" 5 | #include "filtercurve.h" 6 | #include 7 | #include 8 | 9 | class CollisionManager; 10 | 11 | class EqHoverer : public QObject, public QGraphicsItem 12 | { 13 | Q_OBJECT 14 | Q_INTERFACES(QGraphicsItem) 15 | public: 16 | explicit EqHoverer(CollisionManager *mgr, FilterCurve *curve, CurvePoint *point, QObject *parent = nullptr); 17 | QRectF boundingRect() const override; 18 | void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; 19 | enum { Type = UserType + 1 }; 20 | int type() const override; 21 | 22 | void collisionStateChanged(); 23 | void contextMenuToggle(bool on = false); 24 | void reset(); 25 | protected: 26 | void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; 27 | void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; 28 | private: 29 | void maybeShowPoint(); 30 | 31 | public slots: 32 | void resync(FilterCurve *curve); 33 | 34 | signals: 35 | 36 | public: 37 | FilterCurve *curve; 38 | CurvePoint *point; 39 | 40 | private: 41 | unsigned int pointState; 42 | CollisionManager *mgr; 43 | }; 44 | 45 | #endif // EQHOVERER_H 46 | -------------------------------------------------------------------------------- /gui/filtercurve.cpp: -------------------------------------------------------------------------------- 1 | #include "filtercurve.h" 2 | #include 3 | 4 | FilterCurve::FilterCurve(QPen togglePen, QBrush toggleBrush, bool guiOnly) : filter(nullptr) 5 | { 6 | colorState = 0; 7 | pens[0] = defaultPen; 8 | pens[1] = togglePen; 9 | brushes[0] = defaultBrush; 10 | brushes[1] = toggleBrush; 11 | 12 | if (!guiOnly) { 13 | /* initialize the filter */ 14 | PrettyShim::getInstance().new_filter(&filter); 15 | PrettyShim::getInstance().set_peaking_eq(filter, 100, 100, 0); 16 | } 17 | } 18 | 19 | FilterCurve::~FilterCurve() 20 | { 21 | 22 | } 23 | 24 | void FilterCurve::setColorState(ColorState state) 25 | { 26 | colorState = state; 27 | this->update(); 28 | } 29 | 30 | const QPen& FilterCurve::getActivePen() const 31 | { 32 | return pens[colorState]; 33 | } 34 | 35 | const QBrush& FilterCurve::getActiveBrush() const 36 | { 37 | return brushes[colorState]; 38 | } 39 | -------------------------------------------------------------------------------- /gui/filtercurve.h: -------------------------------------------------------------------------------- 1 | #ifndef FILTERCURVE_H 2 | #define FILTERCURVE_H 3 | 4 | #include "prettyshim.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | static QPen defaultPen = Qt::NoPen; 12 | static QBrush defaultBrush = QBrush(QColor(73, 137, 196, 255)); 13 | 14 | typedef enum ColorState { 15 | DefaultState = 0, 16 | PrettyState, 17 | } ColorState; 18 | 19 | class FilterCurve : public QGraphicsItem 20 | { 21 | public: 22 | explicit FilterCurve(QPen togglePen, QBrush toggleBrush, bool guiOnly = false); 23 | virtual ~FilterCurve(); 24 | virtual QPointF controlPoint() const = 0; 25 | virtual void reset() = 0; 26 | void setColorState(ColorState state); 27 | 28 | signals: 29 | 30 | protected: 31 | const QPen& getActivePen() const; 32 | const QBrush& getActiveBrush() const; 33 | 34 | protected: 35 | ShimFilterPtr filter; 36 | 37 | private: 38 | QPen pens[2]; 39 | QBrush brushes[2]; 40 | int colorState; 41 | }; 42 | 43 | #endif // FILTERCURVE_H 44 | -------------------------------------------------------------------------------- /gui/frequencytick.cpp: -------------------------------------------------------------------------------- 1 | #include "frequencytick.h" 2 | #include "macro.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | FrequencyTick::FrequencyTick(QGraphicsScene *scene, int x, int y0, int y1, int frequency) : 10 | scene(scene), x(x), y0(y0), y1(y1), frequency(frequency) { 11 | QFont ff = QFontDatabase::systemFont(QFontDatabase::FixedFont); 12 | ff.setPixelSize(20); 13 | 14 | /* Setup line */ 15 | line = new QGraphicsLineItem(x, y0, x, y1); 16 | line->setPen(QPen(Qt::white)); 17 | line->setOpacity(0.3); 18 | 19 | /* Setup text */ 20 | text = new QGraphicsTextItem(toQString()); 21 | text->setDefaultTextColor(Qt::white); 22 | text->setFont(ff); 23 | auto fm = QFontMetrics(text->font()); 24 | if (frequency == FMAX) { 25 | int offset = fm.horizontalAdvance(QLatin1Char('K')) * 4; 26 | text->setPos(x - offset, 30); 27 | } else if (frequency == FMIN) { 28 | text->setPos(x, 30); 29 | } else { 30 | int offset = fm.horizontalAdvance(QLatin1Char('K')) * toQString().length() + 7; 31 | text->setPos(x - offset / 2, 30); 32 | } 33 | text->setZValue(1000); 34 | 35 | scene->addItem(text); 36 | scene->addItem(line); 37 | } 38 | 39 | FrequencyTick::~FrequencyTick() 40 | { 41 | delete line; 42 | delete text; 43 | } 44 | 45 | QString FrequencyTick::toQString() 46 | { 47 | if (frequency >= 1000) 48 | return QString::number(frequency / 1000) + QString("K"); 49 | else 50 | return QString::number(frequency); 51 | } 52 | 53 | qreal FrequencyTick::getFrequency() const 54 | { 55 | return static_cast(frequency); 56 | } 57 | 58 | qreal FrequencyTick::getX() const 59 | { 60 | return x; 61 | } 62 | -------------------------------------------------------------------------------- /gui/frequencytick.h: -------------------------------------------------------------------------------- 1 | #ifndef FREQUENCYTICK_H 2 | #define FREQUENCYTICK_H 3 | 4 | #include 5 | #include 6 | 7 | class FrequencyTick 8 | { 9 | public: 10 | FrequencyTick(QGraphicsScene *scene, int x, int y0, int y1, int frequency); 11 | ~FrequencyTick(); 12 | QString toQString(); 13 | qreal getFrequency() const; 14 | qreal getX() const; 15 | 16 | private: 17 | QGraphicsScene *scene; 18 | int x, y0, y1; 19 | int frequency; 20 | QGraphicsLineItem *line; 21 | QGraphicsTextItem *text; 22 | }; 23 | 24 | #endif // FREQUENCYTICK_H 25 | -------------------------------------------------------------------------------- /gui/frequencytickbuilder.cpp: -------------------------------------------------------------------------------- 1 | #include "frequencytick.h" 2 | #include "frequencytickbuilder.h" 3 | #include "macro.h" 4 | 5 | #include 6 | 7 | FrequencyTickBuilder::FrequencyTickBuilder(QGraphicsScene *scene, int width, int xmin, int xmax, int ymin, int ymax) 8 | { 9 | /* y-axis frequency markers */ 10 | int tickWidth = width / (NUM_TICKS - 1); 11 | tick[0] = new FrequencyTick(scene, xmin, ymin, ymax, F1); 12 | tick[1] = new FrequencyTick(scene, xmin + tickWidth * 1, ymin, ymax, F2); 13 | tick[2] = new FrequencyTick(scene, xmin + tickWidth * 2, ymin, ymax, F3); 14 | tick[3] = new FrequencyTick(scene, xmin + tickWidth * 3, ymin, ymax, F4); 15 | tick[4] = new FrequencyTick(scene, xmin + tickWidth * 4, ymin, ymax, F5); 16 | tick[5] = new FrequencyTick(scene, xmin + tickWidth * 5, ymin, ymax, F6); 17 | tick[6] = new FrequencyTick(scene, xmin + tickWidth * 6, ymin, ymax, F7); 18 | tick[7] = new FrequencyTick(scene, xmin + tickWidth * 7, ymin, ymax, F8); 19 | tick[8] = new FrequencyTick(scene, xmin + tickWidth * 8, ymin, ymax, F9); 20 | tick[9] = new FrequencyTick(scene, xmax, ymin, ymax, F10); 21 | } 22 | 23 | qreal FrequencyTickBuilder::lerpTick(qreal x) 24 | { 25 | FrequencyTick *tp, *tq; 26 | for (int i = 1; i < NUM_TICKS; i++) { 27 | tp = tick[i-1]; 28 | tq = tick[i]; 29 | if (x >= tp->getX() && x <= tq->getX()) 30 | break; 31 | } 32 | return LINEAR_REMAP(x, tp->getX(), tq->getX(), tp->getFrequency(), tq->getFrequency()); 33 | } 34 | 35 | qreal FrequencyTickBuilder::unlerpTick(qreal f) 36 | { 37 | FrequencyTick *tp, *tq; 38 | for (int i = 1; i < NUM_TICKS; i++) { 39 | tp = tick[i-1]; 40 | tq = tick[i]; 41 | if (f >= tp->getFrequency() && f <= tq->getFrequency()) 42 | break; 43 | } 44 | return LINEAR_REMAP(f, tp->getFrequency(), tq->getFrequency(), tp->getX(), tq->getX()); 45 | } 46 | 47 | FrequencyTickBuilder::~FrequencyTickBuilder() 48 | { 49 | for (int i = 0; i < NUM_TICKS; i++) 50 | delete tick[i]; 51 | } 52 | -------------------------------------------------------------------------------- /gui/frequencytickbuilder.h: -------------------------------------------------------------------------------- 1 | #ifndef FREQUENCYTICKBUILDER_H 2 | #define FREQUENCYTICKBUILDER_H 3 | 4 | #include 5 | #include 6 | #define NUM_TICKS 10 7 | 8 | class FrequencyTick; 9 | 10 | class FrequencyTickBuilder 11 | { 12 | public: 13 | FrequencyTickBuilder(QGraphicsScene *scene, int width, int xmin, int xmax, int ymin, int ymax); 14 | ~FrequencyTickBuilder(); 15 | qreal lerpTick(qreal x); 16 | qreal unlerpTick(qreal f); 17 | private: 18 | FrequencyTick *tick[NUM_TICKS]; 19 | }; 20 | 21 | #endif // FREQUENCYTICKBUILDER_H 22 | -------------------------------------------------------------------------------- /gui/gui.cpp: -------------------------------------------------------------------------------- 1 | #include "collisionmanager.h" 2 | #include "curvepoint.h" 3 | #include "eqhoverer.h" 4 | #include "frequencytick.h" 5 | #include "frequencytickbuilder.h" 6 | #include "gui.h" 7 | #include "highshelfcurve.h" 8 | #include "lowshelfcurve.h" 9 | #include "macro.h" 10 | #include "peakingcurve.h" 11 | #include "prettygraphicsscene.h" 12 | #include "prettyshim.h" 13 | #include "spectrumanalyzer.h" 14 | #include "ui_gui.h" 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #define WIDTH 2000 30 | #define HEIGHT 1000 31 | #define XMIN -1000 32 | #define YMIN -500 33 | #define XMAX (XMIN + WIDTH) 34 | #define YMAX (YMIN + HEIGHT) 35 | 36 | static QPen GreenFilterPen = QPen(QColor(138, 237, 152), 3); 37 | static QBrush GreenFilterBrush = QBrush(QColor(31, 204, 57, 127)); 38 | static QBrush GreenInnerRadiusBrush = QBrush(QColor(31, 204, 57)); 39 | static QBrush GreenOuterRadiusBrush = QBrush(QColor(31, 204, 57, 50)); 40 | 41 | static QPen RedFilterPen = QPen(QColor(231, 123, 131), 3); 42 | static QBrush RedFilterBrush = QBrush(QColor(236, 85, 95, 127)); 43 | static QBrush RedInnerRadiusBrush = QBrush(QColor(223, 59, 70)); 44 | static QBrush RedOuterRadiusBRush = QBrush(QColor(233, 59, 70, 50)); 45 | 46 | static QPen YellowFilterPen = QPen(QColor(231, 229, 123), 3); 47 | static QBrush YellowFilterBrush = QBrush(QColor(236, 225, 85, 127)); 48 | static QBrush YellowInnerRadiusBrush = QBrush(QColor(236, 225, 85)); 49 | static QBrush YellowOuterRadiusBrush = QBrush(QColor(236, 225, 85, 50)); 50 | 51 | static QPen PinkFilterPen = QPen(QColor(231, 123, 217), 3); 52 | static QBrush PinkFilterBrush = QBrush(QColor(236, 85, 216, 127)); 53 | static QBrush PinkInnerRadiusBrush = QBrush(QColor(236, 85, 216)); 54 | static QBrush PinkOuterRadiusBrush = QBrush(QColor(236, 85, 216, 50)); 55 | 56 | static QPen BlueFilterPen = QPen(QColor(123, 231, 191), 3); 57 | static QBrush BlueFilterBrush = QBrush(QColor(85, 236, 193, 127)); 58 | static QBrush BlueInnerRadiusBrush = QBrush(QColor(85, 236, 193)); 59 | static QBrush BlueOuterRadiusBrush = QBrush(QColor(85, 236, 193, 50)); 60 | 61 | static QPen OrangeFilterPen = QPen(QColor(231, 175, 123), 3); 62 | static QBrush OrangeFilterBrush = QBrush(QColor(236, 151, 85, 127)); 63 | static QBrush OrangeInnerRadiusBrush = QBrush(QColor(236, 151, 85)); 64 | static QBrush OrangeOuterRadiusBrush = QBrush(QColor(236, 151, 85, 50)); 65 | 66 | static QPen PurpleFilterPen = QPen(QColor(177, 123, 231), 3); 67 | static QBrush PurpleFilterBrush = QBrush(QColor(159, 85, 236, 127)); 68 | static QBrush PurpleInnerRadiusBrush = QBrush(QColor(159, 85, 236)); 69 | static QBrush PurpleOuterRadiusBrush = QBrush(QColor(159, 85, 236, 50)); 70 | 71 | Gui::Gui(QWidget *parent) 72 | : QDialog(parent) 73 | , ui(new Ui::Gui) 74 | { 75 | 76 | PrettyShim::getInstance().init(); 77 | PrettyShim::getInstance().setup_sink_io(); 78 | ui->setupUi(this); 79 | 80 | QRadialGradient backgroundGradient(QPoint(0, 0), WIDTH); 81 | backgroundGradient.setSpread(QGradient::ReflectSpread); 82 | backgroundGradient.setColorAt(0.15, QColor(4, 38, 69)); 83 | backgroundGradient.setColorAt(0.85, QColor(6, 17, 43)); 84 | scene = new PrettyGraphicsScene(ui->graphicsView); 85 | scene->setSceneRect(XMIN, YMIN, WIDTH, HEIGHT); 86 | scene->setBackgroundBrush(QBrush(backgroundGradient)); 87 | 88 | ui->graphicsView->setRenderHint(QPainter::Antialiasing); 89 | ui->graphicsView->setRenderHint(QPainter::TextAntialiasing); 90 | ui->graphicsView->setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate); 91 | ui->graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 92 | ui->graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 93 | ui->graphicsView->setResizeAnchor(QGraphicsView::AnchorViewCenter); 94 | ui->graphicsView->setScene(scene); 95 | 96 | /* x-axis (boost/cut) */ 97 | auto xaxis = new QGraphicsLineItem(XMIN, scene->sceneRect().center().y(), XMAX, scene->sceneRect().center().y()); 98 | xaxis->setPen(QPen(Qt::white)); 99 | xaxis->setOpacity(0.3); 100 | scene->addItem(xaxis); 101 | 102 | /* x-axis frequency markers */ 103 | xTickBuilder = new FrequencyTickBuilder(scene, WIDTH, XMIN, XMAX, YMIN, YMAX); 104 | addSpectrumAnalyzer(); 105 | 106 | collisionMgr = new CollisionManager(); 107 | 108 | addLowShelf (GreenFilterPen, GreenFilterBrush, GreenInnerRadiusBrush, GreenOuterRadiusBrush); 109 | addHighShelf(OrangeFilterPen, OrangeFilterBrush, OrangeInnerRadiusBrush, OrangeOuterRadiusBrush); 110 | 111 | addPeakingEq(150, RedFilterPen, RedFilterBrush, RedInnerRadiusBrush, RedOuterRadiusBRush); 112 | addPeakingEq(350, YellowFilterPen, YellowFilterBrush, YellowInnerRadiusBrush, YellowOuterRadiusBrush); 113 | addPeakingEq(750, PinkFilterPen, PinkFilterBrush, PinkInnerRadiusBrush, PinkOuterRadiusBrush); 114 | addPeakingEq(1500, BlueFilterPen, BlueFilterBrush, BlueInnerRadiusBrush, BlueOuterRadiusBrush); 115 | addPeakingEq(3500, PurpleFilterPen, PurpleFilterBrush, PurpleInnerRadiusBrush, PurpleOuterRadiusBrush); 116 | 117 | connectBypassButton(); 118 | maybeShowInSystemTray(); 119 | } 120 | 121 | //============================================================================= 122 | 123 | void Gui::addFilterItem(QGraphicsItemGroup *group, FilterCurve *curve, CurvePoint *point, EqHoverer *hover) 124 | { 125 | Q_ASSERT(itemCount < NUM_FILTERS); 126 | items[itemCount].group = group; 127 | items[itemCount].curve = curve; 128 | items[itemCount].point = point; 129 | items[itemCount].hover = hover; 130 | itemCount++; 131 | } 132 | 133 | void Gui::addPeakingEq(int frequency, QPen curvePen, QBrush filterBrush, QBrush innerRadiusBrush, QBrush outerRadiusBrush) { 134 | Q_ASSERT(collisionMgr); 135 | PeakingCurve *curve = new PeakingCurve(curvePen, filterBrush); 136 | CurvePoint *point = new CurvePoint(innerRadiusBrush, outerRadiusBrush); 137 | EqHoverer *hover = new EqHoverer(collisionMgr, curve, point); 138 | collisionMgr->addEqHoverer(hover); 139 | 140 | /* point signals */ 141 | QObject::connect(point, 142 | SIGNAL(pointPositionChanged(CurvePoint*)), 143 | curve, 144 | SLOT(pointPositionChanged(CurvePoint*))); 145 | QObject::connect(point, 146 | SIGNAL(pointSlopeChanged(int)), 147 | curve, 148 | SLOT(pointSlopeChanged(int))); 149 | 150 | /* curve signals */ 151 | QObject::connect(curve, 152 | SIGNAL(resync(FilterCurve*)), 153 | hover, 154 | SLOT(resync(FilterCurve*))); 155 | QObject::connect(curve, 156 | SIGNAL(filterParamsChanged(ShimFilterPtr, PeakingCurve*)), 157 | this, 158 | SLOT(peakingFilterParamsChanged(ShimFilterPtr, PeakingCurve*))); 159 | 160 | QGraphicsItemGroup *group = new QGraphicsItemGroup(); 161 | group->addToGroup(curve); 162 | group->addToGroup(hover); 163 | group->setHandlesChildEvents(false); 164 | group->setPos(xTickBuilder->unlerpTick(frequency) - group->boundingRect().width() / 2, 0); 165 | point->setResetPos(curve->controlPoint()); 166 | scene->addItem(group); 167 | scene->addItem(point); 168 | addFilterItem(group, curve, point, hover); 169 | } 170 | 171 | void Gui::addLowShelf(QPen curvePen, QBrush filterBrush, QBrush innerRadiusBrush, QBrush outerRadiusBrush) 172 | { 173 | Q_ASSERT(collisionMgr); 174 | ShelfCurve *curve = new LowShelfCurve(curvePen, filterBrush); 175 | CurvePoint *point = new CurvePoint(innerRadiusBrush, outerRadiusBrush); 176 | EqHoverer *hover = new EqHoverer(collisionMgr, curve, point); 177 | collisionMgr->addEqHoverer(hover); 178 | 179 | /* point signals */ 180 | QObject::connect(point, 181 | SIGNAL(pointPositionChanged(CurvePoint*)), 182 | curve, 183 | SLOT(pointPositionChanged(CurvePoint*))); 184 | QObject::connect(point, 185 | SIGNAL(pointSlopeChanged(int)), 186 | curve, 187 | SLOT(pointSlopeChanged(int))); 188 | 189 | /* curve signals */ 190 | QObject::connect(curve, 191 | SIGNAL(resync(FilterCurve*)), 192 | hover, 193 | SLOT(resync(FilterCurve*))); 194 | QObject::connect(curve, 195 | SIGNAL(filterParamsChanged(ShimFilterPtr, ShelfCurve*)), 196 | this, 197 | SLOT(lowshelfFilterParamsChanged(ShimFilterPtr, ShelfCurve*))); 198 | 199 | QGraphicsItemGroup *group = new QGraphicsItemGroup(); 200 | group->setHandlesChildEvents(false); 201 | group->addToGroup(curve); 202 | group->addToGroup(hover); 203 | group->setPos(XMIN, 0); 204 | point->setResetPos(curve->controlPoint()); 205 | scene->addItem(group); 206 | scene->addItem(point); 207 | addFilterItem(group, curve, point, hover); 208 | } 209 | 210 | void Gui::addHighShelf(QPen curvePen, QBrush filterBrush, QBrush innerRadiusBrush, QBrush outerRadiusBrush) 211 | { 212 | Q_ASSERT(collisionMgr); 213 | ShelfCurve *curve = new HighShelfCurve(curvePen, filterBrush); 214 | CurvePoint *point = new CurvePoint(innerRadiusBrush, outerRadiusBrush); 215 | EqHoverer *hover = new EqHoverer(collisionMgr, curve, point); 216 | collisionMgr->addEqHoverer(hover); 217 | 218 | /* point signals */ 219 | QObject::connect(point, 220 | SIGNAL(pointPositionChanged(CurvePoint*)), 221 | curve, 222 | SLOT(pointPositionChanged(CurvePoint*))); 223 | QObject::connect(point, 224 | SIGNAL(pointSlopeChanged(int)), 225 | curve, 226 | SLOT(pointSlopeChanged(int))); 227 | 228 | /* curve signals */ 229 | QObject::connect(curve, 230 | SIGNAL(resync(FilterCurve*)), 231 | hover, 232 | SLOT(resync(FilterCurve*))); 233 | QObject::connect(curve, 234 | SIGNAL(filterParamsChanged(ShimFilterPtr, ShelfCurve*)), 235 | this, 236 | SLOT(highshelfFilterParamsChanged(ShimFilterPtr, ShelfCurve*))); 237 | 238 | QGraphicsItemGroup *group = new QGraphicsItemGroup(); 239 | group->setHandlesChildEvents(false); 240 | group->addToGroup(curve); 241 | group->addToGroup(hover); 242 | group->setPos(XMAX, 0); 243 | point->setResetPos(curve->controlPoint()); 244 | scene->addItem(group); 245 | scene->addItem(point); 246 | addFilterItem(group, curve, point, hover); 247 | } 248 | 249 | void Gui::peakingFilterParamsChanged(ShimFilterPtr filter, PeakingCurve *curve) 250 | { 251 | QPointF c = curve->controlPoint(); 252 | QRectF r = curve->sceneBoundingRect(); 253 | qreal f0 = xTickBuilder->lerpTick(c.x()); 254 | qreal bw = xTickBuilder->lerpTick(r.bottomRight().x()) / xTickBuilder->lerpTick(r.bottomLeft().x()) / 2; 255 | qreal db_gain = LINEAR_REMAP( 256 | c.y(), 257 | scene->sceneRect().y(), scene->sceneRect().y() + scene->sceneRect().height(), 258 | DB_GAIN_MAX, -DB_GAIN_MAX); 259 | PrettyShim::getInstance().set_peaking_eq(filter, f0, bw, db_gain); 260 | } 261 | 262 | void Gui::lowshelfFilterParamsChanged(ShimFilterPtr filter, ShelfCurve *curve) 263 | { 264 | QPointF c = curve->controlPoint(); 265 | qreal f0 = xTickBuilder->lerpTick(c.x()); 266 | qreal S = curve->slope(); 267 | qreal db_gain = LINEAR_REMAP( 268 | c.y(), 269 | scene->sceneRect().y(), scene->sceneRect().y() + scene->sceneRect().height(), 270 | DB_GAIN_MAX, -DB_GAIN_MAX); 271 | PrettyShim::getInstance().set_low_shelf(filter, f0, S, db_gain); 272 | } 273 | 274 | void Gui::highshelfFilterParamsChanged(ShimFilterPtr filter, ShelfCurve *curve) 275 | { 276 | 277 | QPointF c = curve->controlPoint(); 278 | qreal f0 = xTickBuilder->lerpTick(c.x()); 279 | qreal S = curve->slope(); 280 | qreal db_gain = LINEAR_REMAP( 281 | c.y(), 282 | scene->sceneRect().y(), scene->sceneRect().y() + scene->sceneRect().height(), 283 | DB_GAIN_MAX, -DB_GAIN_MAX); 284 | PrettyShim::getInstance().set_high_shelf(filter, f0, S, db_gain); 285 | } 286 | 287 | void Gui::connectBypassButton() 288 | { 289 | QObject::connect(ui->pushButton, &QAbstractButton::clicked, this, [&](bool checked){ 290 | PrettyShim::getInstance().enable_bypass(checked); 291 | }); 292 | } 293 | 294 | void Gui::addSpectrumAnalyzer() 295 | { 296 | Q_ASSERT(xTickBuilder); 297 | spectrumAnalyzer = new SpectrumAnalyzer(xTickBuilder); 298 | spectrumAnalyzer->setPos(-scene->sceneRect().width() / 2, -scene->sceneRect().height() / 8); 299 | scene->addItem(spectrumAnalyzer); 300 | spectrumUpdateTimer = new QTimer(this); 301 | spectrumUpdateTimer->setInterval(1000 / 60); 302 | QObject::connect(spectrumUpdateTimer, &QTimer::timeout, [&]() { 303 | spectrumAnalyzer->updateFrameDelta(); 304 | spectrumAnalyzer->update(); 305 | }); 306 | spectrumUpdateTimer->start(); 307 | } 308 | 309 | //============================================================================= 310 | 311 | void Gui::maybeShowInSystemTray() 312 | { 313 | if (!QSystemTrayIcon::isSystemTrayAvailable()) 314 | return; 315 | 316 | trayMenu = new QMenu(); 317 | quitAct = new QAction("Quit"); 318 | trayMenu->addAction(quitAct); 319 | QObject::connect(quitAct, &QAction::triggered, qApp, QCoreApplication::quit); 320 | 321 | trayIcon = new QSystemTrayIcon(this); 322 | trayIcon->setContextMenu(trayMenu); 323 | trayIcon->setIcon(QIcon(":/images/images/prettyeq.png")); 324 | trayIcon->setContextMenu(trayMenu); 325 | QObject::connect(trayIcon, 326 | SIGNAL(activated(QSystemTrayIcon::ActivationReason)), 327 | this, 328 | SLOT(trayActivated(QSystemTrayIcon::ActivationReason))); 329 | 330 | trayIcon->show(); 331 | } 332 | 333 | //============================================================================= 334 | 335 | void Gui::resizeEvent(QResizeEvent *event) { 336 | QDialog::resizeEvent(event); 337 | qDebug() << this->size(); 338 | if (isVisible()) 339 | ui->graphicsView->fitInView(scene->sceneRect(), Qt::KeepAspectRatio); 340 | //ui->graphicsView->centerOn(0, 0); 341 | } 342 | 343 | void Gui::showEvent(QShowEvent *event) 344 | { 345 | ui->graphicsView->fitInView(scene->sceneRect(), Qt::KeepAspectRatio); 346 | } 347 | 348 | void Gui::on_actionQuit_triggered() 349 | { 350 | QCoreApplication::exit(); 351 | } 352 | 353 | void Gui::trayActivated(QSystemTrayIcon::ActivationReason reason) 354 | { 355 | this->show(); 356 | this->raise(); 357 | this->activateWindow(); 358 | } 359 | 360 | //============================================================================= 361 | 362 | Gui::~Gui() 363 | { 364 | delete collisionMgr; 365 | for (int i = 0; i < NUM_FILTERS; i++) { 366 | delete items[i].curve; 367 | delete items[i].hover; 368 | delete items[i].point; 369 | delete items[i].group; 370 | } 371 | 372 | if (QSystemTrayIcon::isSystemTrayAvailable()) { 373 | delete trayIcon; 374 | delete trayMenu; 375 | delete quitAct; 376 | } 377 | spectrumUpdateTimer->stop(); 378 | delete spectrumUpdateTimer; 379 | delete spectrumAnalyzer; 380 | delete ui; 381 | } 382 | 383 | void Gui::cleanup() 384 | { 385 | PrettyShim::getInstance().exit(); 386 | } 387 | -------------------------------------------------------------------------------- /gui/gui.h: -------------------------------------------------------------------------------- 1 | #ifndef GUI_H 2 | #define GUI_H 3 | 4 | #include "prettyshim.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define DB_GAIN_MAX 12 13 | #define NUM_FILTERS 7 14 | 15 | QT_BEGIN_NAMESPACE 16 | namespace Ui { class Gui; } 17 | QT_END_NAMESPACE 18 | 19 | class CollisionManager; 20 | class CurvePoint; 21 | class EqHoverer; 22 | class FilterCurve; 23 | class FrequencyTick; 24 | class FrequencyTickBuilder; 25 | class PeakingCurve; 26 | class ShelfCurve; 27 | class SpectrumAnalyzer; 28 | 29 | typedef struct FilterItem { 30 | QGraphicsItemGroup *group; 31 | FilterCurve *curve; 32 | CurvePoint *point; 33 | EqHoverer *hover; 34 | } FilterItem; 35 | 36 | class Gui : public QDialog 37 | { 38 | Q_OBJECT 39 | 40 | public: 41 | Gui(QWidget *parent = nullptr); 42 | ~Gui(); 43 | 44 | /* Equalizer slots */ 45 | public slots: 46 | void cleanup(); 47 | void peakingFilterParamsChanged(ShimFilterPtr filter, PeakingCurve *curve); 48 | void lowshelfFilterParamsChanged(ShimFilterPtr filter, ShelfCurve *curve); 49 | void highshelfFilterParamsChanged(ShimFilterPtr filter, ShelfCurve *curve); 50 | 51 | /* Menu Bar slots */ 52 | private slots: 53 | void on_actionQuit_triggered(); 54 | 55 | /* System Tray slots */ 56 | private slots: 57 | void trayActivated(QSystemTrayIcon::ActivationReason reason); 58 | 59 | protected: 60 | void resizeEvent(QResizeEvent *event) override; 61 | void showEvent(QShowEvent *event) override; 62 | 63 | 64 | private: 65 | void addFilterItem(QGraphicsItemGroup *group, FilterCurve *curve, CurvePoint *point, EqHoverer *hover); 66 | qreal lerpTick(qreal x); 67 | qreal unlerpTick(qreal f); 68 | void addLowShelf(QPen curvePen, QBrush filterBrush, QBrush innerRadiusBrush, QBrush outerRadiusBrush); 69 | void addHighShelf(QPen curvePen, QBrush filterBrush, QBrush innerRadiusBrush, QBrush outerRadiusBrush); 70 | void addPeakingEq(int frequency, QPen curvePen, QBrush filterBrush, QBrush innerRadiusBrush, QBrush outerRadiusBrush); 71 | void connectBypassButton(); 72 | void addSpectrumAnalyzer(); 73 | 74 | void maybeShowInSystemTray(); 75 | 76 | private: 77 | Ui::Gui *ui; 78 | QGraphicsScene *scene; 79 | CollisionManager *collisionMgr = nullptr;; 80 | FilterItem items[NUM_FILTERS]; 81 | SpectrumAnalyzer *spectrumAnalyzer = nullptr; 82 | FrequencyTickBuilder *xTickBuilder = nullptr; 83 | QTimer *spectrumUpdateTimer = nullptr; 84 | int itemCount = 0; 85 | 86 | /* System tray stuff. */ 87 | QMenu *trayMenu; 88 | QAction *quitAct; 89 | QSystemTrayIcon *trayIcon; 90 | }; 91 | #endif // GUI_H 92 | -------------------------------------------------------------------------------- /gui/gui.pro: -------------------------------------------------------------------------------- 1 | QT += core gui 2 | 3 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 4 | 5 | CONFIG += c++11 6 | 7 | # The following define makes your compiler emit warnings if you use 8 | # any Qt feature that has been marked deprecated (the exact warnings 9 | # depend on your compiler). Please consult the documentation of the 10 | # deprecated API in order to know how to port your code away from it. 11 | DEFINES += QT_DEPRECATED_WARNINGS 12 | 13 | # You can also make your code fail to compile if it uses deprecated APIs. 14 | # In order to do so, uncomment the following line. 15 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 16 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 17 | 18 | SOURCES += \ 19 | collisionmanager.cpp \ 20 | curvepoint.cpp \ 21 | eqhoverer.cpp \ 22 | filtercurve.cpp \ 23 | frequencytick.cpp \ 24 | frequencytickbuilder.cpp \ 25 | gui.cpp \ 26 | highshelfcurve.cpp \ 27 | lowshelfcurve.cpp \ 28 | main.cpp \ 29 | peakingcurve.cpp \ 30 | prettygraphicsscene.cpp \ 31 | runguard.cpp \ 32 | shelfcurve.cpp \ 33 | spectrumanalyzer.cpp \ 34 | unixsignalhandler.cpp 35 | 36 | HEADERS += \ 37 | collisionmanager.h \ 38 | curvepoint.h \ 39 | eqhoverer.h \ 40 | filtercurve.h \ 41 | frequencytick.h \ 42 | frequencytickbuilder.h \ 43 | gui.h \ 44 | highshelfcurve.h \ 45 | lowshelfcurve.h \ 46 | macro.h \ 47 | peakingcurve.h \ 48 | prettygraphicsscene.h \ 49 | prettyshim.h \ 50 | ringbuffer.h \ 51 | runguard.h \ 52 | shelfcurve.h \ 53 | spectrumanalyzer.h \ 54 | unixsignalhandler.h 55 | 56 | FORMS += \ 57 | gui.ui 58 | 59 | QMAKE_CXXFLAGS_WARN_ON += -Wno-unused-parameter 60 | INCLUDEPATH += '../equalizer' 61 | unix:LIBS += -L ../equalizer -lequalizer -lm -lpulse -lpthread -ffast-math -fopenmp 62 | TARGET = ../prettyeq 63 | 64 | # Default rules for deployment. 65 | qnx: target.path = /tmp/$${TARGET}/bin 66 | else: unix:!android: target.path = /opt/$${TARGET}/bin 67 | !isEmpty(target.path): INSTALLS += target 68 | 69 | RESOURCES += \ 70 | resources.qrc 71 | -------------------------------------------------------------------------------- /gui/gui.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Gui 4 | 5 | 6 | 7 | 0 8 | 0 9 | 1152 10 | 648 11 | 12 | 13 | 14 | 15 | 1152 16 | 648 17 | 18 | 19 | 20 | 21 | 1152 22 | 648 23 | 24 | 25 | 26 | Gui 27 | 28 | 29 | false 30 | 31 | 32 | QWidget { 33 | background: #25282b; 34 | } 35 | 36 | QPushButton { 37 | color: white 38 | } 39 | 40 | 41 | 42 | 43 | 44 | true 45 | 46 | 47 | 48 | Options 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 115 60 | 0 61 | 62 | 63 | 64 | 65 | 100 66 | 16777215 67 | 68 | 69 | 70 | 71 | Source Code Pro Semibold 72 | 75 73 | true 74 | 75 | 76 | 77 | Qt::LeftToRight 78 | 79 | 80 | false 81 | 82 | 83 | BYPASS 84 | 85 | 86 | true 87 | 88 | 89 | false 90 | 91 | 92 | true 93 | 94 | 95 | 96 | 97 | 98 | 99 | false 100 | 101 | 102 | 103 | 104 | 105 | 106 | Quit 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /gui/highshelfcurve.cpp: -------------------------------------------------------------------------------- 1 | #include "highshelfcurve.h" 2 | 3 | HighShelfCurve::HighShelfCurve(QPen pen, QBrush brush, bool guiOnly, QObject *parent) 4 | : ShelfCurve(pen, brush, guiOnly, parent) 5 | { 6 | reset(); 7 | updateCurveGeometry(); 8 | } 9 | 10 | HighShelfCurve::~HighShelfCurve() 11 | { 12 | 13 | } 14 | 15 | QPointF HighShelfCurve::clampP2() const 16 | { 17 | return (p2.x() > 0) ? QPointF(0, p2.y()) : p2; 18 | } 19 | 20 | void HighShelfCurve::pointSlopeChanged(int delta) 21 | { 22 | bool reduce = delta > 0; 23 | int offset = -delta * SLOPE_DELTA; 24 | if ((qAbs(p3.x() - p2.x()) - offset < SLOPE_MAX || !reduce) && (p2.x() - offset > p3.x() || reduce)) { 25 | p2.setX(p2.x() - offset); 26 | updateCurveGeometry(); 27 | this->update(); 28 | emit resync(this); 29 | emit filterParamsChanged(filter, this); 30 | } 31 | } 32 | 33 | void HighShelfCurve::reset() 34 | { 35 | p0 = QPointF(0, 0); 36 | p3 = QPointF(-330, 0); 37 | p1 = QPointF((p3.x() - p0.x()) * 0.3, p0.y()); 38 | p2 = QPointF((p1.x() + p3.x())/ 2, p3.y()); 39 | updateCurveGeometry(); 40 | this->update(); 41 | } 42 | -------------------------------------------------------------------------------- /gui/highshelfcurve.h: -------------------------------------------------------------------------------- 1 | #ifndef HIGHSHELFCURVE_H 2 | #define HIGHSHELFCURVE_H 3 | 4 | #include "shelfcurve.h" 5 | 6 | class HighShelfCurve : public ShelfCurve 7 | { 8 | public: 9 | explicit HighShelfCurve(QPen pen, QBrush brush, bool guiOnly = false, QObject *parent = nullptr); 10 | ~HighShelfCurve(); 11 | 12 | // ShelfCurve interface 13 | protected: 14 | QPointF clampP2() const override; 15 | 16 | // ShelfCurve interface 17 | public slots: 18 | void pointSlopeChanged(int delta) override; 19 | 20 | // FilterCurve interface 21 | public: 22 | void reset() override; 23 | }; 24 | 25 | #endif // HIGHSHELFCURVE_H 26 | -------------------------------------------------------------------------------- /gui/images/prettyeq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kkuehlz/prettyeq/c6d300b9070da3a35c4c534120361feb920c98ef/gui/images/prettyeq.png -------------------------------------------------------------------------------- /gui/lowshelfcurve.cpp: -------------------------------------------------------------------------------- 1 | #include "lowshelfcurve.h" 2 | 3 | LowShelfCurve::LowShelfCurve(QPen pen, QBrush brush, bool guiOnly, QObject *parent) 4 | : ShelfCurve(pen, brush, guiOnly, parent) 5 | { 6 | reset(); 7 | updateCurveGeometry(); 8 | } 9 | 10 | LowShelfCurve::~LowShelfCurve() 11 | { 12 | 13 | } 14 | 15 | QPointF LowShelfCurve::clampP2() const 16 | { 17 | return p2.x() < 0 ? QPointF(0, p2.y()) : p2; 18 | } 19 | 20 | void LowShelfCurve::pointSlopeChanged(int delta) 21 | { 22 | bool reduce = delta > 0; 23 | int offset = delta * SLOPE_DELTA; 24 | if ((p3.x() - p2.x() - offset < SLOPE_MAX || !reduce) && (p2.x() - offset <= p3.x() || reduce)) { 25 | p2.setX(p2.x() - offset); 26 | updateCurveGeometry(); 27 | this->update(); 28 | emit resync(this); 29 | emit filterParamsChanged(filter, this); 30 | } 31 | } 32 | 33 | void LowShelfCurve::reset() 34 | { 35 | p0 = QPointF(0, 0); 36 | p3 = QPointF(330, 0); 37 | p1 = QPointF((p3.x() - p0.x()) * 0.3, p0.y()); 38 | p2 = QPointF((p1.x() + p3.x())/ 2, p3.y()); 39 | updateCurveGeometry(); 40 | this->update(); 41 | } 42 | -------------------------------------------------------------------------------- /gui/lowshelfcurve.h: -------------------------------------------------------------------------------- 1 | #ifndef LOWSHELFCURVE_H 2 | #define LOWSHELFCURVE_H 3 | 4 | #include "shelfcurve.h" 5 | #include 6 | #include 7 | 8 | class LowShelfCurve : public ShelfCurve 9 | { 10 | public: 11 | explicit LowShelfCurve(QPen pen, QBrush brush, bool guiOnly = false, QObject *parent = nullptr); 12 | ~LowShelfCurve(); 13 | 14 | protected: 15 | QPointF clampP2() const override; 16 | 17 | public slots: 18 | void pointSlopeChanged(int delta) override; 19 | 20 | // FilterCurve interface 21 | public: 22 | void reset() override; 23 | }; 24 | 25 | #endif // LOWSHELFCURVE_H 26 | -------------------------------------------------------------------------------- /gui/macro.h: -------------------------------------------------------------------------------- 1 | #ifndef MACRO_H 2 | #define MACRO_H 3 | 4 | #include 5 | 6 | #define F1 20 7 | #define F2 50 8 | #define F3 100 9 | #define F4 200 10 | #define F5 500 11 | #define F6 1000 12 | #define F7 2000 13 | #define F8 5000 14 | #define F9 10000 15 | #define F10 20000 16 | #define FMIN F1 17 | #define FMAX F10 18 | 19 | #define UNLERP( v, min, max ) ( ( (v) - (min) ) / ( (max) - (min) ) ) 20 | 21 | #define LERP( n, min, max ) ( (min) + (n) * ( (max) - (min) ) ) 22 | 23 | #define LINEAR_REMAP( i, imin, imax, omin, omax ) ( LERP( UNLERP( i, imin, imax ), omin, omax ) ) 24 | 25 | static inline QPointF cubic_bezier(qreal t, QPointF p0, QPointF p1, QPointF p2, QPointF p3) { 26 | return (1-t)*(1-t)*(1-t)*p0 + 27 | 3*(1-t)*(1-t)*t*p1 + 28 | 3*(1-t)*t*t*p2 + 29 | t*t*t*p3; 30 | } 31 | 32 | #define CUBIC_BEZIER cubic_bezier 33 | 34 | #endif // MACRO_H 35 | -------------------------------------------------------------------------------- /gui/main.cpp: -------------------------------------------------------------------------------- 1 | #include "gui.h" 2 | #include "runguard.h" 3 | #include "unixsignalhandler.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | static void setupUnixSignalHandlers(QList exitSignals) { 11 | struct sigaction sa; 12 | sigset_t blocking_mask; 13 | int r; 14 | 15 | sigemptyset(&blocking_mask); 16 | for (int sig : exitSignals) { 17 | r = sigaddset(&blocking_mask, sig); 18 | if (r < 0) 19 | qFatal("could not sigaddset(): %s", strerror(r)); 20 | } 21 | 22 | sa.sa_handler = UnixSignalHandler::getInstance().exitHandler; 23 | sa.sa_mask = blocking_mask; 24 | sa.sa_flags = 0; 25 | sa.sa_flags |= SA_RESTART; 26 | 27 | for (int sig : exitSignals) { 28 | r = sigaction(sig, &sa, nullptr); 29 | if (r < 0) 30 | qFatal("could not sigaction(): %s", strerror(r)); 31 | } 32 | } 33 | 34 | int main(int argc, char *argv[]) 35 | { 36 | RunGuard guard; 37 | if (guard.isRunning()) { 38 | qWarning() << "prettyeq is already running. Quitting!"; 39 | return 0; 40 | } 41 | QApplication a(argc, argv); 42 | setupUnixSignalHandlers(QList{SIGINT, SIGTERM, SIGQUIT, SIGABRT, SIGHUP}); 43 | Gui w; 44 | QObject::connect(&a, SIGNAL(aboutToQuit()), &w, SLOT(cleanup())); 45 | w.show(); 46 | return a.exec(); 47 | } 48 | -------------------------------------------------------------------------------- /gui/peakingcurve.cpp: -------------------------------------------------------------------------------- 1 | #include "peakingcurve.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #define MIN_WIDTH 40 8 | #define MAX_WIDTH 500 9 | #define SLOPE 10 10 | #define SPLINE_CAPACITY 16 11 | 12 | PeakingCurve::PeakingCurve(QPen pen, QBrush brush, bool guiOnly, QObject *parent) : QObject(parent), FilterCurve(pen, brush, guiOnly) 13 | { 14 | setZValue(100000); 15 | 16 | lineSpline = std::unique_ptr(new QPainterPath()); 17 | fillSpline = std::unique_ptr(new QPainterPath()); 18 | 19 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) 20 | lineSpline->reserve(SPLINE_CAPACITY); 21 | fillSpline->reserve(SPLINE_CAPACITY); 22 | #endif 23 | reset(); 24 | updateSplineGeometry(); 25 | } 26 | 27 | QRectF PeakingCurve::boundingRect() const 28 | { 29 | return fillSpline->boundingRect(); 30 | } 31 | 32 | void PeakingCurve::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) 33 | { 34 | painter->setRenderHint(QPainter::Antialiasing, true); 35 | painter->setPen(getActivePen()); 36 | painter->drawPath(*lineSpline); 37 | painter->fillPath(*fillSpline, getActiveBrush()); 38 | 39 | #if 0 40 | // debugging - draw control points 41 | QPen rpen(Qt::red); 42 | rpen.setWidth(10); 43 | painter->save(); 44 | painter->setPen(rpen); 45 | painter->drawPoint(c1); 46 | painter->drawPoint(c2); 47 | painter->restore(); 48 | #endif 49 | 50 | #if 0 51 | // debugging - draw boundingRect() 52 | QPen rpen(Qt::red); 53 | rpen.setWidth(2); 54 | painter->save(); 55 | painter->setPen(rpen); 56 | painter->drawRect(boundingRect()); 57 | painter->restore(); 58 | #endif 59 | } 60 | 61 | QPointF PeakingCurve::controlPoint() const 62 | { 63 | return mapToScene(ip); 64 | } 65 | 66 | void PeakingCurve::reset() 67 | { 68 | p0 = QPointF(0, 0); 69 | ip = QPointF(100, 0); 70 | p1 = QPointF(200, 0); 71 | c1 = QPointF((p0.x() + ip.x()) / 2, ip.y()); 72 | c2 = QPointF((ip.x() + p1.x()) / 2, ip.y()); 73 | updateSplineGeometry(); 74 | prepareGeometryChange(); 75 | this->update(); 76 | } 77 | 78 | void PeakingCurve::updateSplineGeometry() 79 | { 80 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) 81 | lineSpline->clear(); 82 | #else 83 | lineSpline.reset(new QPainterPath()); 84 | #endif 85 | lineSpline->moveTo(p0); 86 | lineSpline->quadTo(c1, ip); 87 | lineSpline->moveTo(ip); 88 | lineSpline->quadTo(c2, p1); 89 | Q_ASSERT(lineSpline->elementCount() == 8); 90 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) 91 | Q_ASSERT(lineSpline->capacity() == SPLINE_CAPACITY); 92 | #endif 93 | 94 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) 95 | fillSpline->clear(); 96 | #else 97 | fillSpline.reset(new QPainterPath()); 98 | #endif 99 | fillSpline->moveTo(p0); 100 | fillSpline->quadTo(c1, ip); 101 | fillSpline->moveTo(ip); 102 | fillSpline->quadTo(c2, p1); 103 | fillSpline->lineTo(p0); 104 | Q_ASSERT(fillSpline->elementCount() == 9); 105 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) 106 | Q_ASSERT(fillSpline->capacity() == SPLINE_CAPACITY); 107 | #endif 108 | } 109 | 110 | void PeakingCurve::pointPositionChanged(CurvePoint *point) 111 | { 112 | QPointF curvePoint = mapFromScene(point->pos()); 113 | QPointF delta = curvePoint - ip; 114 | ip = curvePoint; 115 | p0.setX(p0.x() + delta.x()); 116 | p1.setX(p1.x() + delta.x()); 117 | c1 = QPointF((p0.x() + ip.x()) / 2, ip.y()); 118 | c2 = QPointF((ip.x() + p1.x()) / 2, ip.y()); 119 | updateSplineGeometry(); 120 | prepareGeometryChange(); 121 | this->update(); 122 | emit resync(this); 123 | emit filterParamsChanged(filter, this); 124 | } 125 | 126 | void PeakingCurve::pointSlopeChanged(int delta) 127 | { 128 | qreal p0x = p0.x() + SLOPE*delta; 129 | qreal p1x = p1.x() - SLOPE*delta; 130 | if (p1x - p0x >= MIN_WIDTH && p1x - p0x <= MAX_WIDTH) { 131 | p0.setX(p0x); 132 | p1.setX(p1x); 133 | c1 = QPointF((p0.x() + ip.x()) / 2, ip.y()); 134 | c2 = QPointF((ip.x() + p1.x()) / 2, ip.y()); 135 | updateSplineGeometry(); 136 | prepareGeometryChange(); 137 | this->update(); 138 | emit resync(this); 139 | emit filterParamsChanged(filter, this); 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /gui/peakingcurve.h: -------------------------------------------------------------------------------- 1 | #ifndef PEAKINGCURVE_H 2 | #define PEAKINGCURVE_H 3 | 4 | #include "curvepoint.h" 5 | #include "filtercurve.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | typedef enum SplinePart { 15 | SplineLeft, 16 | SplineRight, 17 | } SplinePart; 18 | 19 | class PeakingCurve : public QObject, public FilterCurve 20 | { 21 | Q_OBJECT 22 | Q_INTERFACES(QGraphicsItem) 23 | public: 24 | explicit PeakingCurve(QPen pen, QBrush brush, bool guiOnly = false, QObject *parent = nullptr); 25 | QRectF boundingRect() const override; 26 | void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; 27 | 28 | QPointF controlPoint() const override; 29 | 30 | // FilterCurve interface 31 | public: 32 | void reset() override; 33 | 34 | private: 35 | void updateSplineGeometry(); 36 | 37 | public slots: 38 | void pointPositionChanged(CurvePoint *point); 39 | void pointSlopeChanged(int delta); 40 | signals: 41 | void resync(FilterCurve *curve); 42 | void filterParamsChanged(ShimFilterPtr filter, PeakingCurve *curve); 43 | 44 | private: 45 | QPointF p0, p1, c1, c2, ip; 46 | std::unique_ptr lineSpline, fillSpline; 47 | }; 48 | 49 | #endif // PEAKINGCURVE_H 50 | -------------------------------------------------------------------------------- /gui/prettygraphicsscene.cpp: -------------------------------------------------------------------------------- 1 | #include "curvepoint.h" 2 | #include "eqhoverer.h" 3 | #include "prettygraphicsscene.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | PrettyGraphicsScene::PrettyGraphicsScene(QObject *parent) : QGraphicsScene(parent) {} 10 | 11 | void PrettyGraphicsScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) 12 | { 13 | CurvePoint *point = nullptr; 14 | for (auto item : items()) { 15 | point = qgraphicsitem_cast(item); 16 | if ( !point) 17 | continue; 18 | 19 | if (point->sceneBoundingRect().contains(event->scenePos())) 20 | break; 21 | } 22 | 23 | if (point) { 24 | EqHoverer *hover = nullptr; 25 | for (auto item : items()) { 26 | hover = qgraphicsitem_cast(item); 27 | if (! hover) 28 | continue; 29 | 30 | if (point == hover->point) { 31 | hover->contextMenuToggle(true); 32 | break; 33 | } 34 | } 35 | 36 | QMenu menu(event->widget()); 37 | menu.addAction("Reset"); 38 | if (menu.exec(event->screenPos())) { 39 | Q_ASSERT(hover); 40 | hover->reset(); 41 | } 42 | 43 | for (auto item : items()) { 44 | EqHoverer *hover = qgraphicsitem_cast(item); 45 | if (! hover) 46 | continue; 47 | 48 | if (point == hover->point) { 49 | hover->contextMenuToggle(false); 50 | break; 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /gui/prettygraphicsscene.h: -------------------------------------------------------------------------------- 1 | #ifndef PRETTYGRAPHICSSCENE_H 2 | #define PRETTYGRAPHICSSCENE_H 3 | 4 | #include 5 | 6 | class PrettyGraphicsScene : public QGraphicsScene 7 | { 8 | public: 9 | PrettyGraphicsScene(QObject *parent = nullptr); 10 | 11 | protected: 12 | void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; 13 | }; 14 | 15 | #endif // PRETTYGRAPHICSSCENE_H 16 | -------------------------------------------------------------------------------- /gui/prettyshim.h: -------------------------------------------------------------------------------- 1 | #ifndef PRETTYSHIM_H 2 | #define PRETTYSHIM_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "pretty.h" 10 | 11 | typedef PrettyFilter* ShimFilterPtr; 12 | 13 | /* The shim is for making C callbacks idiomatic with Qt signals/slots */ 14 | 15 | class PrettyShim : public QObject 16 | { 17 | Q_OBJECT 18 | 19 | public: 20 | static PrettyShim& getInstance() { 21 | static PrettyShim instance; 22 | return instance; 23 | } 24 | 25 | private: 26 | explicit PrettyShim(QObject *parent = nullptr) {} 27 | static PrettyShim *instance; 28 | 29 | public: 30 | PrettyShim(PrettyShim const&) = delete; 31 | void operator=(PrettyShim const&) = delete; 32 | 33 | void init() { 34 | qRegisterMetaType("uint32_t"); 35 | int r = pretty_init(); 36 | Q_ASSERT(r == 0); 37 | } 38 | 39 | void setup_sink_io() { 40 | pretty_setup_sink_io(); 41 | } 42 | 43 | void exit() { 44 | pretty_exit(); 45 | } 46 | 47 | void new_filter(ShimFilterPtr *filter) { 48 | int r = pretty_new_filter(filter); 49 | Q_ASSERT(*filter && r >= 0); 50 | } 51 | 52 | void set_peaking_eq(ShimFilterPtr filter, float f0, float bandwidth, float db_gain) { 53 | pretty_set_peaking_eq(filter, f0, bandwidth, db_gain); 54 | } 55 | 56 | void set_low_shelf(PrettyFilter *filter, float f0, float S, float db_gain) { 57 | pretty_set_low_shelf(filter, f0, S, db_gain); 58 | } 59 | 60 | void set_high_shelf(PrettyFilter *filter, float f0, float S, float db_gain) { 61 | pretty_set_high_shelf(filter, f0, S, db_gain); 62 | } 63 | 64 | void enable_bypass(bool should_bypass) { 65 | pretty_enable_bypass(should_bypass); 66 | } 67 | 68 | std::complex* get_audio_data(unsigned int *N) { 69 | std::complex *data; 70 | pretty_acquire_audio_data(&data, N); 71 | return std::move(data); 72 | } 73 | 74 | void release_audio_data() { 75 | pretty_release_audio_data(); 76 | } 77 | 78 | private: 79 | AudioFFT *audio_fft; 80 | 81 | }; 82 | 83 | #endif // PRETTYSHIM_H 84 | -------------------------------------------------------------------------------- /gui/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | images/prettyeq.png 4 | 5 | 6 | -------------------------------------------------------------------------------- /gui/ringbuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef RINGBUFFER_H 2 | #define RINGBUFFER_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | template 9 | class RingBuffer 10 | { 11 | public: 12 | RingBuffer() : pos(0) {}; 13 | 14 | const T& at(int i) { 15 | Q_ASSERT(i < size); 16 | return ring_buffer.at(i); 17 | } 18 | 19 | const std::array& buffer() { 20 | return ring_buffer; 21 | } 22 | 23 | void append(const T& value) { 24 | ring_buffer[pos] = value; 25 | pos = (pos + 1) % size; 26 | } 27 | 28 | private: 29 | int pos; 30 | std::array ring_buffer; 31 | }; 32 | 33 | #endif // RINGBUFFER_H 34 | -------------------------------------------------------------------------------- /gui/runguard.cpp: -------------------------------------------------------------------------------- 1 | #include "runguard.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | RunGuard::RunGuard() 10 | { 11 | ::memset(&addr, 0, sizeof(struct sockaddr_un)); 12 | addr.sun_family = AF_UNIX; 13 | 14 | #pragma GCC diagnostic push 15 | #pragma GCC diagnostic ignored "-Wstringop-truncation" 16 | ::strncpy(&addr.sun_path[1], PRETTY_ABSTRACT_SOCK, sizeof(PRETTY_ABSTRACT_SOCK) - 2); 17 | #pragma GCC diagnostic pop 18 | 19 | sockfd = ::socket(AF_UNIX, SOCK_STREAM, 0); 20 | if (sockfd < 0) 21 | qFatal("socket() failed: %s", strerror(sockfd)); 22 | } 23 | 24 | bool RunGuard::isRunning() 25 | { 26 | int r = ::bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)); 27 | if (r < 0) { 28 | if (errno == EADDRINUSE) 29 | return true; 30 | else 31 | qFatal("bind() failed: %s", strerror(errno)); 32 | } 33 | return false; 34 | } 35 | -------------------------------------------------------------------------------- /gui/runguard.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNGUARD_H 2 | #define RUNGUARD_H 3 | 4 | #include 5 | #define PRETTY_ABSTRACT_SOCK "prettyeq" 6 | 7 | /* Linux only implementation of a single application guard. 8 | * We bind to an abstract socket so cleanup is handled entirely 9 | * by the kernel. */ 10 | class RunGuard 11 | { 12 | public: 13 | RunGuard(); 14 | bool isRunning(); 15 | 16 | private: 17 | int sockfd; 18 | struct sockaddr_un addr; 19 | }; 20 | 21 | #endif // RUNGUARD_H 22 | -------------------------------------------------------------------------------- /gui/shelfcurve.cpp: -------------------------------------------------------------------------------- 1 | #include "macro.h" 2 | #include "shelfcurve.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #define CURVE_CAPACITY 16 9 | 10 | ShelfCurve::ShelfCurve(QPen pen, QBrush brush, bool guiOnly, QObject *parent) 11 | : QObject(parent), FilterCurve(pen, brush, guiOnly) 12 | { 13 | setZValue(100000); 14 | 15 | lineCurve = std::unique_ptr(new QPainterPath()); 16 | fillCurve = std::unique_ptr(new QPainterPath()); 17 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) 18 | lineCurve->reserve(CURVE_CAPACITY); 19 | fillCurve->reserve(CURVE_CAPACITY); 20 | #endif 21 | } 22 | 23 | ShelfCurve::~ShelfCurve() 24 | { 25 | 26 | } 27 | 28 | void ShelfCurve::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) 29 | { 30 | painter->setRenderHint(QPainter::Antialiasing, true); 31 | painter->setPen(getActivePen()); 32 | painter->drawPath(*lineCurve); 33 | painter->fillPath(*fillCurve, getActiveBrush()); 34 | #if 0 35 | // debugging - draw control points 36 | QPen rpen(Qt::red); 37 | QPen ypen(Qt::yellow); 38 | QPen gpen(Qt::green); 39 | QPen bpen(Qt::blue); 40 | rpen.setWidth(10); 41 | ypen.setWidth(10); 42 | bpen.setWidth(10); 43 | gpen.setWidth(10); 44 | painter->save(); 45 | painter->setPen(rpen); 46 | painter->drawPoint(p0); 47 | painter->setPen(ypen); 48 | painter->drawPoint(p1); 49 | painter->setPen(gpen); 50 | painter->drawPoint(p2); 51 | painter->setPen(bpen); 52 | painter->drawPoint(p3); 53 | painter->restore(); 54 | #endif 55 | 56 | #if 0 57 | // debugging - draw boundingRect() 58 | QPen rpen(Qt::red); 59 | rpen.setWidth(10); 60 | QPen bpen(Qt::blue); 61 | bpen.setWidth(10); 62 | QPen gpen(Qt::green); 63 | gpen.setWidth(10); 64 | QPen ypen(Qt::yellow); 65 | ypen.setWidth(10); 66 | painter->save(); 67 | painter->setPen(rpen); 68 | painter->drawPoint(boundingRect().bottomLeft()); 69 | painter->setPen(bpen); 70 | painter->drawPoint(boundingRect().topLeft()); 71 | painter->setPen(gpen); 72 | painter->drawPoint(boundingRect().bottomRight()); 73 | painter->setPen(ypen); 74 | painter->drawPoint(boundingRect().topRight()); 75 | painter->restore(); 76 | #endif 77 | 78 | } 79 | 80 | QPointF ShelfCurve::controlPoint() const 81 | { 82 | return mapToScene(p1); 83 | } 84 | 85 | qreal ShelfCurve::slope() const 86 | { 87 | /* The i have no idea what the fuck I'm doing equation. */ 88 | return qAbs(bezierPainter().slopeAtPercent(0.7)) + 0.5; 89 | } 90 | 91 | void ShelfCurve::pointPositionChanged(CurvePoint *point) { 92 | QPointF curvePoint = mapFromScene(point->pos()); 93 | QPointF delta = curvePoint - p1; 94 | p0.setY(p0.y() + delta.y()); 95 | p1 = curvePoint; 96 | p2.setX(p2.x() + delta.x()); 97 | p3.setX(p3.x() + delta.x()); 98 | updateCurveGeometry(); 99 | prepareGeometryChange(); 100 | this->update(); 101 | emit resync(this); 102 | emit filterParamsChanged(filter, this); 103 | } 104 | 105 | QPainterPath ShelfCurve::bezierPainter() const 106 | { 107 | QPainterPath shelf; 108 | shelf.moveTo(p0); 109 | shelf.cubicTo(p1, clampP2(), p3); 110 | return shelf; 111 | } 112 | 113 | void ShelfCurve::updateCurveGeometry() 114 | { 115 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) 116 | lineCurve->clear(); 117 | #else 118 | lineCurve.reset(new QPainterPath()); 119 | #endif 120 | lineCurve->moveTo(p0); 121 | lineCurve->cubicTo(p1, clampP2(), p3); 122 | Q_ASSERT(lineCurve->elementCount() == 4); 123 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) 124 | Q_ASSERT(lineCurve->capacity() == CURVE_CAPACITY); 125 | #endif 126 | 127 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) 128 | fillCurve->clear(); 129 | #else 130 | fillCurve.reset(new QPainterPath()); 131 | #endif 132 | fillCurve->moveTo(p0); 133 | fillCurve->cubicTo(p1, clampP2(), p3); 134 | fillCurve->lineTo(0, 0); 135 | fillCurve->lineTo(p0); 136 | Q_ASSERT(fillCurve->elementCount() <= 6); 137 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) 138 | Q_ASSERT(fillCurve->capacity() == CURVE_CAPACITY); 139 | #endif 140 | } 141 | 142 | QRectF ShelfCurve::boundingRect() const 143 | { 144 | return bezierPainter().boundingRect(); 145 | } 146 | -------------------------------------------------------------------------------- /gui/shelfcurve.h: -------------------------------------------------------------------------------- 1 | #ifndef SHELFCURVE_H 2 | #define SHELFCURVE_H 3 | 4 | #include "curvepoint.h" 5 | #include "filtercurve.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #define SLOPE_DELTA 20 15 | #define SLOPE_MAX 330 16 | 17 | class LowShelfCurve; 18 | class HighShelfCurve; 19 | 20 | class ShelfCurve : public QObject, public FilterCurve 21 | { 22 | Q_OBJECT 23 | Q_INTERFACES(QGraphicsItem) 24 | public: 25 | explicit ShelfCurve(QPen pen, QBrush brush, bool guiOnly = false, QObject *parent = nullptr); 26 | virtual ~ShelfCurve(); 27 | QRectF boundingRect() const override; 28 | void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; 29 | 30 | QPointF controlPoint() const override; 31 | qreal slope() const; 32 | 33 | protected: 34 | virtual QPointF clampP2() const = 0; 35 | 36 | private: 37 | QPainterPath bezierPainter() const; 38 | void updateCurveGeometry(); 39 | 40 | public slots: 41 | void pointPositionChanged(CurvePoint *point); 42 | virtual void pointSlopeChanged(int delta) = 0; 43 | signals: 44 | void resync(FilterCurve *curve); 45 | void filterParamsChanged(ShimFilterPtr filter, ShelfCurve *curve); 46 | 47 | private: 48 | QPointF p0, p1, p2, p3; 49 | std::unique_ptr lineCurve, fillCurve; 50 | friend class LowShelfCurve; 51 | friend class HighShelfCurve; 52 | }; 53 | 54 | #endif // SHELFCURVE_H 55 | -------------------------------------------------------------------------------- /gui/spectrumanalyzer.cpp: -------------------------------------------------------------------------------- 1 | #include "frequencytickbuilder.h" 2 | #include "macro.h" 3 | #include "prettyshim.h" 4 | #include "spectrumanalyzer.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define FFT_SAMPLE_TO_FREQ(NUM_SAMPLES, SAMPLE_INDEX) (44100*(SAMPLE_INDEX)/(NUM_SAMPLES)) 15 | #define FFT_FREQ_TO_SAMPLE(NUM_SAMPLES, FREQ) ((int)roundf((FREQ)*(NUM_SAMPLES)/44100)) 16 | #define FFT_BUCKET_WIDTH(NUM_SAMPLES) (44100/(NUM_SAMPLES)) 17 | 18 | static inline qreal dampen(qreal start, qreal end, qreal smoothing_factor, qint64 dt) { 19 | return LERP(1 - qPow(smoothing_factor, dt), start, end); 20 | } 21 | 22 | SpectrumAnalyzer::SpectrumAnalyzer(FrequencyTickBuilder *xTickBuilder) : xTickBuilder(xTickBuilder), last_frame_time(0) 23 | { 24 | setZValue(-1); 25 | for (unsigned int i = 0; i < MAX_SAMPLES; i++) 26 | last_psds[i] = 0.0; 27 | } 28 | 29 | QRectF SpectrumAnalyzer::boundingRect() const 30 | { 31 | auto x = scene()->sceneRect().width(); 32 | auto y = scene()->sceneRect().height(); 33 | return QRectF(0, 0, x, y / 4); 34 | } 35 | 36 | inline QLineF SpectrumAnalyzer::pointForSample(qreal frequency, qreal max_psd, qreal max_psd_moving_avg) { 37 | qreal sceneX = xTickBuilder->unlerpTick(frequency); 38 | qreal startX = mapFromScene(sceneX, 0xbeefcafe).x(); 39 | qreal startY = qMax(boundingRect().top(), LINEAR_REMAP(max_psd, 0, max_psd_moving_avg, 0, boundingRect().height() / 2)); 40 | QLineF line(QPointF(startX, boundingRect().center().y()), QPointF(startX, boundingRect().center().y() - startY)); 41 | return line; 42 | } 43 | 44 | qint64 SpectrumAnalyzer::frame_dt() 45 | { 46 | return QDateTime::currentMSecsSinceEpoch() - last_frame_time; 47 | } 48 | 49 | void SpectrumAnalyzer::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) 50 | { 51 | painter->setRenderHint(QPainter::Antialiasing, true); 52 | #if 0 53 | QPen rpen(Qt::red); 54 | rpen.setWidth(5); 55 | QPen bpen(Qt::blue); 56 | bpen.setWidth(5); 57 | painter->setPen(rpen); 58 | painter->drawPoint(boundingRect().center()); 59 | painter->drawRect(boundingRect()); 60 | painter->drawPoint(boundingRect().topLeft()); 61 | auto line = QLineF(QPointF(100, 0), QPointF(100, boundingRect().height() / 2)); 62 | painter->drawLine(line); 63 | line.translate(0, boundingRect().height() / 2); 64 | painter->setPen(bpen); 65 | painter->drawLine(line); 66 | #else 67 | qreal max_psd_moving_avg = 0.0; 68 | static auto brush = QBrush(QColor(127, 153, 176, 128)); 69 | static auto pen = QPen(brush, 3); 70 | painter->setPen(pen); 71 | { 72 | auto &buf = max_psds.buffer(); 73 | for (auto it = buf.begin(); it != buf.end(); it++) 74 | max_psd_moving_avg += *it; 75 | max_psd_moving_avg /= MOVING_AVG_PERIOD; 76 | } 77 | 78 | { 79 | qint64 delta = frame_dt(); 80 | unsigned int N; 81 | auto data = PrettyShim::getInstance().get_audio_data(&N); 82 | Q_ASSERT(N < MAX_SAMPLES); 83 | qreal max_psd = 0.0; 84 | for (unsigned int i = FFT_FREQ_TO_SAMPLE(N, FMIN); i < N / 2; i++) { 85 | qreal raw_psd = (qreal) std::abs(data[i]); 86 | qreal min = qMin(raw_psd, last_psds[i]); 87 | qreal max = qMax(raw_psd, last_psds[i]); 88 | qreal dampening_factor = 1 - min/max; 89 | dampening_factor = qMax(0.5, dampening_factor); 90 | dampening_factor = qMin(0.95, dampening_factor); 91 | qreal smoothed_psd = dampen(raw_psd, last_psds[i], dampening_factor, delta); 92 | last_psds[i] = smoothed_psd; 93 | max_psd = qMax(max_psd, smoothed_psd); 94 | qreal frequency = FFT_SAMPLE_TO_FREQ(N, (qreal) i); 95 | qreal sceneX = xTickBuilder->unlerpTick(frequency); 96 | qreal startX = mapFromScene(sceneX, 0xbeefcafe).x(); 97 | qreal startY = LINEAR_REMAP(smoothed_psd, 0, max_psd_moving_avg, 0, boundingRect().height() / 2); 98 | startY = qMin(startY, boundingRect().height() / 2); 99 | QPointF p1 = QPointF(startX, boundingRect().center().y() - startY); 100 | QPointF p2 = QPointF(startX, boundingRect().center().y() + startY); 101 | lines[i].setP1(p1); 102 | lines[i].setP2(p2); 103 | } 104 | 105 | if (max_psd_moving_avg > 0.01) 106 | painter->drawLines(lines, N / 2); 107 | 108 | PrettyShim::getInstance().release_audio_data(); 109 | max_psds.append(max_psd); 110 | } 111 | #endif 112 | } 113 | 114 | void SpectrumAnalyzer::updateFrameDelta() 115 | { 116 | last_frame_time = QDateTime::currentMSecsSinceEpoch(); 117 | } 118 | -------------------------------------------------------------------------------- /gui/spectrumanalyzer.h: -------------------------------------------------------------------------------- 1 | #ifndef SPECTRUMANALYZER_H 2 | #define SPECTRUMANALYZER_H 3 | 4 | #include 5 | #include "ringbuffer.h" 6 | 7 | #define MOVING_AVG_PERIOD 128 8 | #define MAX_SAMPLES 4096 9 | 10 | class FrequencyTickBuilder; 11 | 12 | class SpectrumAnalyzer : public QGraphicsItem 13 | { 14 | public: 15 | SpectrumAnalyzer(FrequencyTickBuilder *xTickBuilder); 16 | 17 | public: 18 | QRectF boundingRect() const; 19 | void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); 20 | 21 | void updateFrameDelta(); 22 | private: 23 | QLineF pointForSample(qreal frequency, qreal max_psd, qreal max_psd_moving_avg); 24 | inline qint64 frame_dt(); 25 | 26 | private: 27 | FrequencyTickBuilder *xTickBuilder; 28 | RingBuffer max_psds; 29 | QLineF lines[MAX_SAMPLES]; 30 | qreal last_psds[MAX_SAMPLES]; 31 | qint64 last_frame_time; 32 | }; 33 | 34 | #endif // SPECTRUMANALYZER_H 35 | -------------------------------------------------------------------------------- /gui/unixsignalhandler.cpp: -------------------------------------------------------------------------------- 1 | #include "unixsignalhandler.h" 2 | #include "prettyshim.h" 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | int UnixSignalHandler::socketVector[2]; 10 | 11 | UnixSignalHandler &UnixSignalHandler::getInstance() { 12 | static UnixSignalHandler instance; 13 | return instance; 14 | } 15 | 16 | UnixSignalHandler::UnixSignalHandler(QObject *parent) : QObject(parent) 17 | { 18 | if (::socketpair(AF_UNIX, SOCK_STREAM, 0, socketVector)) 19 | qFatal("Could not socketpair();"); 20 | 21 | sn = new QSocketNotifier(socketVector[1], QSocketNotifier::Read, this); 22 | QObject::connect(sn, SIGNAL(activated(QSocketDescriptor, QSocketNotifier::Type)), this, SLOT(safeHandleExit())); 23 | } 24 | 25 | void UnixSignalHandler::exitHandler(int sig) 26 | { 27 | /* Write junk to the socket to trigger Qt signal */ 28 | char junk = 1; 29 | ::write(socketVector[0], &junk, sizeof(char)); 30 | } 31 | 32 | void UnixSignalHandler::safeHandleExit() 33 | { 34 | sn->setEnabled(false); 35 | char junk; 36 | ::read(socketVector[1], &junk, sizeof(char)); 37 | QCoreApplication::quit(); 38 | sn->setEnabled(true); 39 | } 40 | 41 | UnixSignalHandler::~UnixSignalHandler() 42 | { 43 | delete sn; 44 | } 45 | -------------------------------------------------------------------------------- /gui/unixsignalhandler.h: -------------------------------------------------------------------------------- 1 | #ifndef UNIXSIGNALHANDLER_H 2 | #define UNIXSIGNALHANDLER_H 3 | 4 | #include 5 | #include 6 | 7 | class UnixSignalHandler : public QObject 8 | { 9 | Q_OBJECT 10 | public: 11 | static UnixSignalHandler& getInstance(); 12 | ~UnixSignalHandler(); 13 | UnixSignalHandler(UnixSignalHandler const&) = delete; 14 | void operator=(UnixSignalHandler const&) = delete; 15 | 16 | private: 17 | explicit UnixSignalHandler(QObject *parent = nullptr); 18 | 19 | public: 20 | /* SIGTERM, SIGINT, SIGQUIT, SIGABRT */ 21 | static void exitHandler(int sig); 22 | 23 | public slots: 24 | void safeHandleExit(); 25 | 26 | private: 27 | static UnixSignalHandler instance; 28 | /* All signals share the same socketpair and will mask each other. */ 29 | static int socketVector[2]; 30 | QSocketNotifier *sn; 31 | }; 32 | 33 | #endif // UNIXSIGNALHANDLER_H 34 | -------------------------------------------------------------------------------- /prettyeq.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | CONFIG += ordered 3 | SUBDIRS = \ 4 | equalizer \ 5 | gui 6 | 7 | gui.depends = equalizer 8 | --------------------------------------------------------------------------------