├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── docs ├── LICENSE.md ├── README.md ├── advanced.md ├── anim.gif ├── example0.PNG ├── example1.png ├── example2.png ├── example3.png ├── example4.png ├── example5.png ├── example6.PNG ├── example7.PNG └── example8.PNG ├── libs └── catch2 │ └── include │ └── catch2 │ └── catch.hpp ├── libs_win ├── glew-2.1.0 │ ├── LICENSE.txt │ ├── glew32s.lib │ └── include │ │ └── GL │ │ ├── eglew.h │ │ ├── glew.h │ │ ├── glxew.h │ │ └── wglew.h └── glfw-3.2.1 │ ├── COPYING.txt │ ├── glfw3.lib │ └── include │ └── GLFW │ ├── glfw3.h │ └── glfw3native.h ├── music_visualizer.sln ├── music_visualizer.vcxproj ├── music_visualizer.vcxproj.user ├── notes.txt ├── src ├── AudioProcess.h ├── AudioStreams │ ├── AudioStream.h │ ├── LinuxAudioStream.cpp │ ├── LinuxAudioStream.h │ ├── ProceduralAudioStream.h │ ├── WavAudioStream.cpp │ ├── WavAudioStream.h │ ├── WindowsAudioStream.cpp │ ├── WindowsAudioStream.h │ └── pulse_misc.h ├── FileWatcher.h ├── Renderer.cpp ├── Renderer.h ├── ShaderConfig.cpp ├── ShaderConfig.h ├── ShaderPrograms.cpp ├── ShaderPrograms.h ├── Window.cpp ├── Window.h ├── filesystem.h ├── main.cpp ├── noise.cpp ├── noise.h └── shaders │ ├── A.frag │ ├── A.geom │ ├── B.frag │ ├── blocky_fft │ ├── A.frag │ ├── A.geom │ ├── image.frag │ └── shader.json │ ├── blocky_osc │ ├── A.frag │ ├── A.geom │ ├── image.frag │ └── shader.json │ ├── dots │ ├── a.frag │ └── image.frag │ ├── dual_oscilloscope │ ├── A.frag │ ├── A.geom │ ├── B.frag │ ├── image.frag │ └── shader.json │ ├── dual_waves │ └── image.frag │ ├── fft │ ├── A.frag │ ├── A.geom │ ├── B.frag │ ├── image.frag │ └── shader.json │ ├── fftPlane │ └── image.frag │ ├── fft_and_wave │ ├── A.frag │ ├── A.geom │ ├── B.frag │ ├── image.frag │ └── shader.json │ ├── image.frag │ ├── lissajous │ ├── A.frag │ ├── A.geom │ ├── B.frag │ ├── image.frag │ └── shader.json │ ├── oscilloscope │ ├── A.frag │ ├── A.geom │ ├── B.frag │ ├── image.frag │ └── shader.json │ ├── retrowave │ ├── a.frag │ └── image.frag │ ├── shader.json │ ├── spectrogram │ ├── a.frag │ └── image.frag │ └── star │ └── image.frag └── tests ├── fake_clock.cpp ├── fake_clock.h ├── test_audio_process.cpp ├── test_audio_utilities.cpp ├── test_shader_config.cpp ├── tests.vcxproj └── tests.vcxproj.user /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | 3 | build 4 | build_result 5 | 6 | libs/ffts/* 7 | libs/rapidjson/* 8 | libs/SimpleFileWatcher/* 9 | 10 | tests/Debug 11 | tests/x64 12 | 13 | x64 14 | 15 | misc 16 | 17 | \.vscode/ 18 | 19 | *.exe 20 | 21 | *.wav 22 | 23 | src/shaders/shadertoy 24 | 25 | Release/ 26 | 27 | src/shaders/scratch/ 28 | 29 | src/shaders/myInversionThing/ 30 | 31 | src/shaders/myVolumeKiffs/ 32 | 33 | src/shaders/scraps/ptrace/ 34 | 35 | src/shaders/scraps/scratch/ 36 | 37 | src/shaders/scraps/scratch2/ 38 | 39 | src/shaders/crop_circles/ 40 | 41 | src/shaders/conformal_map/ 42 | 43 | src/shaders/my_path_trace/ 44 | 45 | src/shaders/pathbackup/ 46 | /visualizer.srctrlbm 47 | /visualizer.srctrldb 48 | /visualizer.srctrlprj 49 | 50 | enc_temp_folder 51 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libs/ffts"] 2 | path = libs/ffts 3 | url = https://github.com/xdaimon/ffts.git 4 | [submodule "libs/SimpleFileWatcher"] 5 | path = libs/SimpleFileWatcher 6 | url = https://github.com/xdaimon/SimpleFileWatcher.git 7 | [submodule "libs/rapidjson"] 8 | path = libs/rapidjson 9 | url = https://github.com/Tencent/rapidjson.git 10 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.3) 2 | project(main) 3 | 4 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -std=c++17 -D_REENTRANT -DLINUX") 5 | 6 | set(SOURCE_FILES 7 | src/main.cpp 8 | src/Window.cpp 9 | src/ShaderConfig.cpp 10 | src/ShaderPrograms.cpp 11 | src/Renderer.cpp 12 | src/AudioStreams/LinuxAudioStream.cpp 13 | src/AudioStreams/WavAudioStream.cpp 14 | ) 15 | 16 | include_directories( 17 | . 18 | src 19 | libs/ffts/include/ 20 | libs/rapidjson/include/ 21 | libs/SimpleFileWatcher/include/ 22 | ) 23 | 24 | add_subdirectory(libs/ffts) 25 | add_subdirectory(libs/SimpleFileWatcher) 26 | 27 | add_executable(main ${SOURCE_FILES}) 28 | add_dependencies(main ffts) 29 | add_dependencies(main SimpleFileWatcher) 30 | 31 | TARGET_LINK_LIBRARIES(main glfw GLEW GLU GL pulse-simple pulse pthread ${CMAKE_SOURCE_DIR}/build/libs/ffts/libffts.a ${CMAKE_SOURCE_DIR}/build/libs/SimpleFileWatcher/libSimpleFileWatcher.a stdc++fs) 32 | 33 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # About 2 | Welcome to the project page for my music visualizer :) 3 | 4 | Here is a comparison between what my app looks like and what other music 5 | visualizers might look like when visualizing the same sound. 6 | 7 | ![](/docs/anim.gif) 8 | 9 | # Examples 10 | 11 | 12 | 13 | 14 | 15 | # Usage 16 | 17 | The user writes a .frag file that renders to a window sized quad. If the user wants multipass buffers, then multiple .frag files should be written. When a frag file is saved the app automatically reloads the changes. If the frag file compiles correctly, then the changes are presented to the user otherwise the app ignores the changes. 18 | 19 | The name of a buffer is the file name of the frag file without the .frag extension. A buffer's output is available in all buffers as i{Filename w/o extension}. So if the files A.frag and B.frag exist, then buffer B can access the contents of buffer A by doing texture(iA, pos);. 20 | 21 | Every shader must contain an image.frag file, just like shadertoy. 22 | 23 | Buffers are rendered in alphabetical order and image.frag is always rendered last. If two buffers have the same name but different case, such as A.frag and a.frag, then the render order is unspecified. Do not use non ascii characters in file names ( I use tolower in the code to alphabetize the buffer file list ). 24 | 25 | Code for a shader should be located in a folder named shaders that is in the same directory as the executable. Subdirectories of shaders/ can also contain code but that code will not be considered a part of the currently rendered shader. 26 | 27 | A shadertoy like shader might have the following folder layout 28 | 29 | shader_viewer.exe 30 | shaders/ 31 | image.frag 32 | buffA.frag 33 | buffB.frag 34 | 35 | See [here](/docs/advanced.md) for details on how to configure the rendering process ( clear colors, render size, render order, render same buffer multiple times, geometry shaders, audio system toggle ). 36 | 37 | Here is a list of uniforms available in all buffers 38 | ``` 39 | vec2 iMouse; 40 | bool iMouseDown; // whether left mouse button is down, in range [0, iRes] 41 | vec2 iMouseDownPos; // position of mouse when left mouse button was pressed down 42 | vec2 iRes; // resolution of window 43 | vec2 iBuffRes; // resolution of currently rendering buffer 44 | float iTime; 45 | int iFrame; 46 | float iNumGeomIters; // how many times the geometry shader executed, useful for advanced mode rendering 47 | sampler1D iSoundR; // audio data, each element is in the range [-1, 1] 48 | sampler1D iSoundL; 49 | sampler1D iFreqR; // each element is >= zero for frequency data, you many need to scale this in shader 50 | sampler1D iFreqL; 51 | 52 | // Samplers for your buffers, for example 53 | sampler2D iMyBuff; 54 | 55 | // Constant uniforms specified in shader.json, for example 56 | uniform vec4 color_set_by_script; 57 | ``` 58 | 59 | By default, the program uses the shader defined in the `shaders` directory (relative to the current working directory). You can override this by providing a different directory as the first argument, e.g. 60 | ``` 61 | cd build_result 62 | ./music_visualizer shaders/retrowave 63 | ``` 64 | 65 | # Building 66 | 67 | First get the sources: 68 | ``` 69 | git clone --recursive https://github.com/xdaimon/music_visualizer.git 70 | ``` 71 | Then to build on Ubuntu with gcc version >= 5.5: 72 | ``` 73 | sudo apt install cmake libglfw3-dev libglew-dev libpulse-dev 74 | cd music_visualizer 75 | mkdir build 76 | mkdir build_result 77 | cd build 78 | cmake .. 79 | && make -j4 80 | && mv main ../build_result/music_visualizer 81 | && cp -r ../src/shaders ../build_result/shaders 82 | ``` 83 | and on Windows 10 with Visual Studio 2017: 84 | ``` 85 | build the x64 Release configuration 86 | ``` 87 | 88 | # Contact 89 | 90 | Feel free to use the issues page as a general communication channel. 91 | 92 | You can also message me on reddit at /u/xdaimon 93 | 94 | # Thanks To 95 | 96 | ffts 97 | Fast fft library
98 | cava 99 | Pulseaudio setup code
100 | Oscilloscope 101 | Shader code for drawing smooth lines
102 | SimpleFileWatcher 103 | Asyncronous recursive file watcher
104 | RapidJson 105 | Fast json file reader
106 | Catch2 107 | Convenient testing framework
108 | -------------------------------------------------------------------------------- /docs/advanced.md: -------------------------------------------------------------------------------- 1 | If the user modifies the current shader's files while the app is running, then the app will load the changes, recompile the shaders, and render the new shaders if everything compiled correctly. 2 | 3 | More advanced usage is supported by giving the user access to geometry shaders. The user could render a glittery sphere, for example, using only geometry shaders and a very simple frag shader. To do this the user would write a geometry shader, say buffname.geom, which would be executed a number of times and on each execution would output a triangle or quad to be shaded by a buffname.frag. The geometry shader knows which execution it is currently on and so can decide where to place the output geometry (and how to apply a perspective transform) so that a sphere is generated. 4 | 5 | The size of each framebuffer can also be configured so that unnecessary compute can be avoided. For example, shader games could be implemented where there is a state buffer and a separate rendering buffer. The state framebuffer could be of size 2x100, if for instance the user is simulating a hundred 2D balls moving around. The geometry shader would execute once and draw a full buffer quad. The fragment shader would then shade each pixel in this quad where a pixel is one of the two coordinates for one of the one hundred 2D balls.. Another approach would have the geometry shader output two one-pixel sized quads (or triangles?). The geometry shader could do all the work of updating each ball's state and pass the new state to an 'assignment' fragment shader to be written into the framebuffer. In this case the geometry shader would execute 100 times. 6 | 7 | A shader using geometry shaders might expect this folder layout 8 | 9 | executable 10 | shaders/ 11 | image.frag 12 | buffA.frag 13 | buffA.geom 14 | shader.json 15 | 16 | # Configuration 17 | 18 | If you provide .geom shaders or want to change certain options, then you should have a shader.json file in shaders/. 19 | 20 | If options in shader.json are not given, then for most options the default values are assumed. 21 | 22 | Here is a shader.json with all the options described: 23 | 24 | { 25 | // If you want to specify options in "image", "buffers", "render_order", "blend", then set "shader_mode" to "advanced". 26 | // Otherwise default values are assumed and buffers are created for any .frag file in shaders/ 27 | // Defaults to "easy" 28 | "shader_mode":"advanced", 29 | 30 | // initial window size 31 | // Defaults to [400,300] 32 | "initial_window_size":[500,250], 33 | 34 | // image buffer is always the size of the window 35 | "image": { 36 | // Defaults to 1 37 | "geom_iters":1, 38 | 39 | // Defaults to [0,0,0] 40 | "clear_color":[0,0,0] 41 | }, 42 | 43 | // In addition to drawing an image buffer you can define other buffers to draw here 44 | // Available as iBuffName in all shaders. 45 | "buffers": { 46 | "A": { 47 | // Defaults to "window_size" 48 | "size": "window_size", 49 | 50 | // How many times the geometry shader will execute 51 | "geom_iters":1024, 52 | 53 | // RGB values from the interval [0, 1] 54 | // Defaults to [0,0,0] 55 | "clear_color":[0, 0, 0] 56 | }, 57 | "B": { 58 | "size": [100,3], 59 | "geom_iters":1, 60 | "clear_color":[0, 0, 0] 61 | } 62 | }, 63 | 64 | // Whether glBlend is enabled 65 | // Defaults to false 66 | "blend":true, 67 | 68 | // Render A then B and then B again 69 | // Every buffer has access to the most recent output of all buffers except image 70 | // Defaults to the order of the buffers in "buffers" 71 | "render_order":["A", "B", "B"], 72 | 73 | // Defaults to true 74 | "audio_enabled":true, 75 | "audio_options": { 76 | 77 | // Defaults to true 78 | "fft_sync":true, 79 | 80 | // whether the cross correlation sync is enabled 81 | // Defaults to true 82 | "xcorr_sync":true, 83 | 84 | // how much to mix the prev fft buff with the new fft buff 85 | // Defaults to 1 86 | "fft_smooth":0.5, // not implemented yet 87 | 88 | // how much to mix the prev wave buff with the new wave buff 89 | // Defaults to 0.8 90 | "wave_smooth":0.8 91 | }, 92 | 93 | // TODO just write these in as const variables into the shader? 94 | // Useful for setting colors from external scripts. 95 | // Available as UniformName in all buffers. 96 | "uniforms": { 97 | "my_uni": [10, 123, 42], 98 | "your_uni":[25, 20, 1], 99 | "his_uni":[1.0, 2.0, 3.0, 4] 100 | } 101 | } 102 | 103 | -------------------------------------------------------------------------------- /docs/anim.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradleybauer/music_visualizer/3226d3885aea0c87e8286e6703c4cf293f339741/docs/anim.gif -------------------------------------------------------------------------------- /docs/example0.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradleybauer/music_visualizer/3226d3885aea0c87e8286e6703c4cf293f339741/docs/example0.PNG -------------------------------------------------------------------------------- /docs/example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradleybauer/music_visualizer/3226d3885aea0c87e8286e6703c4cf293f339741/docs/example1.png -------------------------------------------------------------------------------- /docs/example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradleybauer/music_visualizer/3226d3885aea0c87e8286e6703c4cf293f339741/docs/example2.png -------------------------------------------------------------------------------- /docs/example3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradleybauer/music_visualizer/3226d3885aea0c87e8286e6703c4cf293f339741/docs/example3.png -------------------------------------------------------------------------------- /docs/example4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradleybauer/music_visualizer/3226d3885aea0c87e8286e6703c4cf293f339741/docs/example4.png -------------------------------------------------------------------------------- /docs/example5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradleybauer/music_visualizer/3226d3885aea0c87e8286e6703c4cf293f339741/docs/example5.png -------------------------------------------------------------------------------- /docs/example6.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradleybauer/music_visualizer/3226d3885aea0c87e8286e6703c4cf293f339741/docs/example6.PNG -------------------------------------------------------------------------------- /docs/example7.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradleybauer/music_visualizer/3226d3885aea0c87e8286e6703c4cf293f339741/docs/example7.PNG -------------------------------------------------------------------------------- /docs/example8.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradleybauer/music_visualizer/3226d3885aea0c87e8286e6703c4cf293f339741/docs/example8.PNG -------------------------------------------------------------------------------- /libs_win/glew-2.1.0/LICENSE.txt: -------------------------------------------------------------------------------- 1 | The OpenGL Extension Wrangler Library 2 | Copyright (C) 2002-2007, Milan Ikits 3 | Copyright (C) 2002-2007, Marcelo E. Magallon 4 | Copyright (C) 2002, Lev Povalahev 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, 11 | this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | * The name of the author may be used to endorse or promote products 16 | derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 28 | THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | 31 | Mesa 3-D graphics library 32 | Version: 7.0 33 | 34 | Copyright (C) 1999-2007 Brian Paul All Rights Reserved. 35 | 36 | Permission is hereby granted, free of charge, to any person obtaining a 37 | copy of this software and associated documentation files (the "Software"), 38 | to deal in the Software without restriction, including without limitation 39 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 40 | and/or sell copies of the Software, and to permit persons to whom the 41 | Software is furnished to do so, subject to the following conditions: 42 | 43 | The above copyright notice and this permission notice shall be included 44 | in all copies or substantial portions of the Software. 45 | 46 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 47 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 48 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 49 | BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 50 | AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 51 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 52 | 53 | 54 | Copyright (c) 2007 The Khronos Group Inc. 55 | 56 | Permission is hereby granted, free of charge, to any person obtaining a 57 | copy of this software and/or associated documentation files (the 58 | "Materials"), to deal in the Materials without restriction, including 59 | without limitation the rights to use, copy, modify, merge, publish, 60 | distribute, sublicense, and/or sell copies of the Materials, and to 61 | permit persons to whom the Materials are furnished to do so, subject to 62 | the following conditions: 63 | 64 | The above copyright notice and this permission notice shall be included 65 | in all copies or substantial portions of the Materials. 66 | 67 | THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 68 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 69 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 70 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 71 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 72 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 73 | MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. 74 | -------------------------------------------------------------------------------- /libs_win/glew-2.1.0/glew32s.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradleybauer/music_visualizer/3226d3885aea0c87e8286e6703c4cf293f339741/libs_win/glew-2.1.0/glew32s.lib -------------------------------------------------------------------------------- /libs_win/glfw-3.2.1/COPYING.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2002-2006 Marcus Geelnard 2 | Copyright (c) 2006-2016 Camilla Berglund 3 | 4 | This software is provided 'as-is', without any express or implied 5 | warranty. In no event will the authors be held liable for any damages 6 | arising from the use of this software. 7 | 8 | Permission is granted to anyone to use this software for any purpose, 9 | including commercial applications, and to alter it and redistribute it 10 | freely, subject to the following restrictions: 11 | 12 | 1. The origin of this software must not be misrepresented; you must not 13 | claim that you wrote the original software. If you use this software 14 | in a product, an acknowledgment in the product documentation would 15 | be appreciated but is not required. 16 | 17 | 2. Altered source versions must be plainly marked as such, and must not 18 | be misrepresented as being the original software. 19 | 20 | 3. This notice may not be removed or altered from any source 21 | distribution. 22 | 23 | -------------------------------------------------------------------------------- /libs_win/glfw-3.2.1/glfw3.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bradleybauer/music_visualizer/3226d3885aea0c87e8286e6703c4cf293f339741/libs_win/glfw-3.2.1/glfw3.lib -------------------------------------------------------------------------------- /libs_win/glfw-3.2.1/include/GLFW/glfw3native.h: -------------------------------------------------------------------------------- 1 | /************************************************************************* 2 | * GLFW 3.2 - www.glfw.org 3 | * A library for OpenGL, window and input 4 | *------------------------------------------------------------------------ 5 | * Copyright (c) 2002-2006 Marcus Geelnard 6 | * Copyright (c) 2006-2016 Camilla Berglund 7 | * 8 | * This software is provided 'as-is', without any express or implied 9 | * warranty. In no event will the authors be held liable for any damages 10 | * arising from the use of this software. 11 | * 12 | * Permission is granted to anyone to use this software for any purpose, 13 | * including commercial applications, and to alter it and redistribute it 14 | * freely, subject to the following restrictions: 15 | * 16 | * 1. The origin of this software must not be misrepresented; you must not 17 | * claim that you wrote the original software. If you use this software 18 | * in a product, an acknowledgment in the product documentation would 19 | * be appreciated but is not required. 20 | * 21 | * 2. Altered source versions must be plainly marked as such, and must not 22 | * be misrepresented as being the original software. 23 | * 24 | * 3. This notice may not be removed or altered from any source 25 | * distribution. 26 | * 27 | *************************************************************************/ 28 | 29 | #ifndef _glfw3_native_h_ 30 | #define _glfw3_native_h_ 31 | 32 | #ifdef __cplusplus 33 | extern "C" { 34 | #endif 35 | 36 | 37 | /************************************************************************* 38 | * Doxygen documentation 39 | *************************************************************************/ 40 | 41 | /*! @file glfw3native.h 42 | * @brief The header of the native access functions. 43 | * 44 | * This is the header file of the native access functions. See @ref native for 45 | * more information. 46 | */ 47 | /*! @defgroup native Native access 48 | * 49 | * **By using the native access functions you assert that you know what you're 50 | * doing and how to fix problems caused by using them. If you don't, you 51 | * shouldn't be using them.** 52 | * 53 | * Before the inclusion of @ref glfw3native.h, you may define exactly one 54 | * window system API macro and zero or more context creation API macros. 55 | * 56 | * The chosen backends must match those the library was compiled for. Failure 57 | * to do this will cause a link-time error. 58 | * 59 | * The available window API macros are: 60 | * * `GLFW_EXPOSE_NATIVE_WIN32` 61 | * * `GLFW_EXPOSE_NATIVE_COCOA` 62 | * * `GLFW_EXPOSE_NATIVE_X11` 63 | * * `GLFW_EXPOSE_NATIVE_WAYLAND` 64 | * * `GLFW_EXPOSE_NATIVE_MIR` 65 | * 66 | * The available context API macros are: 67 | * * `GLFW_EXPOSE_NATIVE_WGL` 68 | * * `GLFW_EXPOSE_NATIVE_NSGL` 69 | * * `GLFW_EXPOSE_NATIVE_GLX` 70 | * * `GLFW_EXPOSE_NATIVE_EGL` 71 | * 72 | * These macros select which of the native access functions that are declared 73 | * and which platform-specific headers to include. It is then up your (by 74 | * definition platform-specific) code to handle which of these should be 75 | * defined. 76 | */ 77 | 78 | 79 | /************************************************************************* 80 | * System headers and types 81 | *************************************************************************/ 82 | 83 | #if defined(GLFW_EXPOSE_NATIVE_WIN32) 84 | // This is a workaround for the fact that glfw3.h needs to export APIENTRY (for 85 | // example to allow applications to correctly declare a GL_ARB_debug_output 86 | // callback) but windows.h assumes no one will define APIENTRY before it does 87 | #undef APIENTRY 88 | #include 89 | #elif defined(GLFW_EXPOSE_NATIVE_COCOA) 90 | #include 91 | #if defined(__OBJC__) 92 | #import 93 | #else 94 | typedef void* id; 95 | #endif 96 | #elif defined(GLFW_EXPOSE_NATIVE_X11) 97 | #include 98 | #include 99 | #elif defined(GLFW_EXPOSE_NATIVE_WAYLAND) 100 | #include 101 | #elif defined(GLFW_EXPOSE_NATIVE_MIR) 102 | #include 103 | #endif 104 | 105 | #if defined(GLFW_EXPOSE_NATIVE_WGL) 106 | /* WGL is declared by windows.h */ 107 | #endif 108 | #if defined(GLFW_EXPOSE_NATIVE_NSGL) 109 | /* NSGL is declared by Cocoa.h */ 110 | #endif 111 | #if defined(GLFW_EXPOSE_NATIVE_GLX) 112 | #include 113 | #endif 114 | #if defined(GLFW_EXPOSE_NATIVE_EGL) 115 | #include 116 | #endif 117 | 118 | 119 | /************************************************************************* 120 | * Functions 121 | *************************************************************************/ 122 | 123 | #if defined(GLFW_EXPOSE_NATIVE_WIN32) 124 | /*! @brief Returns the adapter device name of the specified monitor. 125 | * 126 | * @return The UTF-8 encoded adapter device name (for example `\\.\DISPLAY1`) 127 | * of the specified monitor, or `NULL` if an [error](@ref error_handling) 128 | * occurred. 129 | * 130 | * @thread_safety This function may be called from any thread. Access is not 131 | * synchronized. 132 | * 133 | * @since Added in version 3.1. 134 | * 135 | * @ingroup native 136 | */ 137 | GLFWAPI const char* glfwGetWin32Adapter(GLFWmonitor* monitor); 138 | 139 | /*! @brief Returns the display device name of the specified monitor. 140 | * 141 | * @return The UTF-8 encoded display device name (for example 142 | * `\\.\DISPLAY1\Monitor0`) of the specified monitor, or `NULL` if an 143 | * [error](@ref error_handling) occurred. 144 | * 145 | * @thread_safety This function may be called from any thread. Access is not 146 | * synchronized. 147 | * 148 | * @since Added in version 3.1. 149 | * 150 | * @ingroup native 151 | */ 152 | GLFWAPI const char* glfwGetWin32Monitor(GLFWmonitor* monitor); 153 | 154 | /*! @brief Returns the `HWND` of the specified window. 155 | * 156 | * @return The `HWND` of the specified window, or `NULL` if an 157 | * [error](@ref error_handling) occurred. 158 | * 159 | * @thread_safety This function may be called from any thread. Access is not 160 | * synchronized. 161 | * 162 | * @since Added in version 3.0. 163 | * 164 | * @ingroup native 165 | */ 166 | GLFWAPI HWND glfwGetWin32Window(GLFWwindow* window); 167 | #endif 168 | 169 | #if defined(GLFW_EXPOSE_NATIVE_WGL) 170 | /*! @brief Returns the `HGLRC` of the specified window. 171 | * 172 | * @return The `HGLRC` of the specified window, or `NULL` if an 173 | * [error](@ref error_handling) occurred. 174 | * 175 | * @thread_safety This function may be called from any thread. Access is not 176 | * synchronized. 177 | * 178 | * @since Added in version 3.0. 179 | * 180 | * @ingroup native 181 | */ 182 | GLFWAPI HGLRC glfwGetWGLContext(GLFWwindow* window); 183 | #endif 184 | 185 | #if defined(GLFW_EXPOSE_NATIVE_COCOA) 186 | /*! @brief Returns the `CGDirectDisplayID` of the specified monitor. 187 | * 188 | * @return The `CGDirectDisplayID` of the specified monitor, or 189 | * `kCGNullDirectDisplay` if an [error](@ref error_handling) occurred. 190 | * 191 | * @thread_safety This function may be called from any thread. Access is not 192 | * synchronized. 193 | * 194 | * @since Added in version 3.1. 195 | * 196 | * @ingroup native 197 | */ 198 | GLFWAPI CGDirectDisplayID glfwGetCocoaMonitor(GLFWmonitor* monitor); 199 | 200 | /*! @brief Returns the `NSWindow` of the specified window. 201 | * 202 | * @return The `NSWindow` of the specified window, or `nil` if an 203 | * [error](@ref error_handling) occurred. 204 | * 205 | * @thread_safety This function may be called from any thread. Access is not 206 | * synchronized. 207 | * 208 | * @since Added in version 3.0. 209 | * 210 | * @ingroup native 211 | */ 212 | GLFWAPI id glfwGetCocoaWindow(GLFWwindow* window); 213 | #endif 214 | 215 | #if defined(GLFW_EXPOSE_NATIVE_NSGL) 216 | /*! @brief Returns the `NSOpenGLContext` of the specified window. 217 | * 218 | * @return The `NSOpenGLContext` of the specified window, or `nil` if an 219 | * [error](@ref error_handling) occurred. 220 | * 221 | * @thread_safety This function may be called from any thread. Access is not 222 | * synchronized. 223 | * 224 | * @since Added in version 3.0. 225 | * 226 | * @ingroup native 227 | */ 228 | GLFWAPI id glfwGetNSGLContext(GLFWwindow* window); 229 | #endif 230 | 231 | #if defined(GLFW_EXPOSE_NATIVE_X11) 232 | /*! @brief Returns the `Display` used by GLFW. 233 | * 234 | * @return The `Display` used by GLFW, or `NULL` if an 235 | * [error](@ref error_handling) occurred. 236 | * 237 | * @thread_safety This function may be called from any thread. Access is not 238 | * synchronized. 239 | * 240 | * @since Added in version 3.0. 241 | * 242 | * @ingroup native 243 | */ 244 | GLFWAPI Display* glfwGetX11Display(void); 245 | 246 | /*! @brief Returns the `RRCrtc` of the specified monitor. 247 | * 248 | * @return The `RRCrtc` of the specified monitor, or `None` if an 249 | * [error](@ref error_handling) occurred. 250 | * 251 | * @thread_safety This function may be called from any thread. Access is not 252 | * synchronized. 253 | * 254 | * @since Added in version 3.1. 255 | * 256 | * @ingroup native 257 | */ 258 | GLFWAPI RRCrtc glfwGetX11Adapter(GLFWmonitor* monitor); 259 | 260 | /*! @brief Returns the `RROutput` of the specified monitor. 261 | * 262 | * @return The `RROutput` of the specified monitor, or `None` if an 263 | * [error](@ref error_handling) occurred. 264 | * 265 | * @thread_safety This function may be called from any thread. Access is not 266 | * synchronized. 267 | * 268 | * @since Added in version 3.1. 269 | * 270 | * @ingroup native 271 | */ 272 | GLFWAPI RROutput glfwGetX11Monitor(GLFWmonitor* monitor); 273 | 274 | /*! @brief Returns the `Window` of the specified window. 275 | * 276 | * @return The `Window` of the specified window, or `None` if an 277 | * [error](@ref error_handling) occurred. 278 | * 279 | * @thread_safety This function may be called from any thread. Access is not 280 | * synchronized. 281 | * 282 | * @since Added in version 3.0. 283 | * 284 | * @ingroup native 285 | */ 286 | GLFWAPI Window glfwGetX11Window(GLFWwindow* window); 287 | #endif 288 | 289 | #if defined(GLFW_EXPOSE_NATIVE_GLX) 290 | /*! @brief Returns the `GLXContext` of the specified window. 291 | * 292 | * @return The `GLXContext` of the specified window, or `NULL` if an 293 | * [error](@ref error_handling) occurred. 294 | * 295 | * @thread_safety This function may be called from any thread. Access is not 296 | * synchronized. 297 | * 298 | * @since Added in version 3.0. 299 | * 300 | * @ingroup native 301 | */ 302 | GLFWAPI GLXContext glfwGetGLXContext(GLFWwindow* window); 303 | 304 | /*! @brief Returns the `GLXWindow` of the specified window. 305 | * 306 | * @return The `GLXWindow` of the specified window, or `None` if an 307 | * [error](@ref error_handling) occurred. 308 | * 309 | * @thread_safety This function may be called from any thread. Access is not 310 | * synchronized. 311 | * 312 | * @since Added in version 3.2. 313 | * 314 | * @ingroup native 315 | */ 316 | GLFWAPI GLXWindow glfwGetGLXWindow(GLFWwindow* window); 317 | #endif 318 | 319 | #if defined(GLFW_EXPOSE_NATIVE_WAYLAND) 320 | /*! @brief Returns the `struct wl_display*` used by GLFW. 321 | * 322 | * @return The `struct wl_display*` used by GLFW, or `NULL` if an 323 | * [error](@ref error_handling) occurred. 324 | * 325 | * @thread_safety This function may be called from any thread. Access is not 326 | * synchronized. 327 | * 328 | * @since Added in version 3.2. 329 | * 330 | * @ingroup native 331 | */ 332 | GLFWAPI struct wl_display* glfwGetWaylandDisplay(void); 333 | 334 | /*! @brief Returns the `struct wl_output*` of the specified monitor. 335 | * 336 | * @return The `struct wl_output*` of the specified monitor, or `NULL` if an 337 | * [error](@ref error_handling) occurred. 338 | * 339 | * @thread_safety This function may be called from any thread. Access is not 340 | * synchronized. 341 | * 342 | * @since Added in version 3.2. 343 | * 344 | * @ingroup native 345 | */ 346 | GLFWAPI struct wl_output* glfwGetWaylandMonitor(GLFWmonitor* monitor); 347 | 348 | /*! @brief Returns the main `struct wl_surface*` of the specified window. 349 | * 350 | * @return The main `struct wl_surface*` of the specified window, or `NULL` if 351 | * an [error](@ref error_handling) occurred. 352 | * 353 | * @thread_safety This function may be called from any thread. Access is not 354 | * synchronized. 355 | * 356 | * @since Added in version 3.2. 357 | * 358 | * @ingroup native 359 | */ 360 | GLFWAPI struct wl_surface* glfwGetWaylandWindow(GLFWwindow* window); 361 | #endif 362 | 363 | #if defined(GLFW_EXPOSE_NATIVE_MIR) 364 | /*! @brief Returns the `MirConnection*` used by GLFW. 365 | * 366 | * @return The `MirConnection*` used by GLFW, or `NULL` if an 367 | * [error](@ref error_handling) occurred. 368 | * 369 | * @thread_safety This function may be called from any thread. Access is not 370 | * synchronized. 371 | * 372 | * @since Added in version 3.2. 373 | * 374 | * @ingroup native 375 | */ 376 | GLFWAPI MirConnection* glfwGetMirDisplay(void); 377 | 378 | /*! @brief Returns the Mir output ID of the specified monitor. 379 | * 380 | * @return The Mir output ID of the specified monitor, or zero if an 381 | * [error](@ref error_handling) occurred. 382 | * 383 | * @thread_safety This function may be called from any thread. Access is not 384 | * synchronized. 385 | * 386 | * @since Added in version 3.2. 387 | * 388 | * @ingroup native 389 | */ 390 | GLFWAPI int glfwGetMirMonitor(GLFWmonitor* monitor); 391 | 392 | /*! @brief Returns the `MirSurface*` of the specified window. 393 | * 394 | * @return The `MirSurface*` of the specified window, or `NULL` if an 395 | * [error](@ref error_handling) occurred. 396 | * 397 | * @thread_safety This function may be called from any thread. Access is not 398 | * synchronized. 399 | * 400 | * @since Added in version 3.2. 401 | * 402 | * @ingroup native 403 | */ 404 | GLFWAPI MirSurface* glfwGetMirWindow(GLFWwindow* window); 405 | #endif 406 | 407 | #if defined(GLFW_EXPOSE_NATIVE_EGL) 408 | /*! @brief Returns the `EGLDisplay` used by GLFW. 409 | * 410 | * @return The `EGLDisplay` used by GLFW, or `EGL_NO_DISPLAY` if an 411 | * [error](@ref error_handling) occurred. 412 | * 413 | * @thread_safety This function may be called from any thread. Access is not 414 | * synchronized. 415 | * 416 | * @since Added in version 3.0. 417 | * 418 | * @ingroup native 419 | */ 420 | GLFWAPI EGLDisplay glfwGetEGLDisplay(void); 421 | 422 | /*! @brief Returns the `EGLContext` of the specified window. 423 | * 424 | * @return The `EGLContext` of the specified window, or `EGL_NO_CONTEXT` if an 425 | * [error](@ref error_handling) occurred. 426 | * 427 | * @thread_safety This function may be called from any thread. Access is not 428 | * synchronized. 429 | * 430 | * @since Added in version 3.0. 431 | * 432 | * @ingroup native 433 | */ 434 | GLFWAPI EGLContext glfwGetEGLContext(GLFWwindow* window); 435 | 436 | /*! @brief Returns the `EGLSurface` of the specified window. 437 | * 438 | * @return The `EGLSurface` of the specified window, or `EGL_NO_SURFACE` if an 439 | * [error](@ref error_handling) occurred. 440 | * 441 | * @thread_safety This function may be called from any thread. Access is not 442 | * synchronized. 443 | * 444 | * @since Added in version 3.0. 445 | * 446 | * @ingroup native 447 | */ 448 | GLFWAPI EGLSurface glfwGetEGLSurface(GLFWwindow* window); 449 | #endif 450 | 451 | #ifdef __cplusplus 452 | } 453 | #endif 454 | 455 | #endif /* _glfw3_native_h_ */ 456 | 457 | -------------------------------------------------------------------------------- /music_visualizer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2010 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "music_visualizer", "music_visualizer.vcxproj", "{D6550F63-9286-4450-A866-89587427FCCB}" 7 | ProjectSection(ProjectDependencies) = postProject 8 | {F7863593-CFF2-3268-A73E-63239BFAECDC} = {F7863593-CFF2-3268-A73E-63239BFAECDC} 9 | {7DF02CE1-E906-2B77-327E-B6989ED3FBFE} = {7DF02CE1-E906-2B77-327E-B6989ED3FBFE} 10 | EndProjectSection 11 | EndProject 12 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "music_visualizer_tests", "tests\tests.vcxproj", "{65705339-3F83-42BB-B4C1-5C8AF2C23CDD}" 13 | EndProject 14 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ffts_static", "libs\ffts\build\ffts_static.vcxproj", "{F7863593-CFF2-3268-A73E-63239BFAECDC}" 15 | EndProject 16 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SimpleFileWatcher", "libs\SimpleFileWatcher\build\vs2013\SimpleFileWatcher\SimpleFileWatcher.vcxproj", "{7DF02CE1-E906-2B77-327E-B6989ED3FBFE}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|x64 = Debug|x64 21 | Debug|x86 = Debug|x86 22 | MinSizeRel|x64 = MinSizeRel|x64 23 | MinSizeRel|x86 = MinSizeRel|x86 24 | Release|x64 = Release|x64 25 | Release|x86 = Release|x86 26 | RelWithDebInfo|x64 = RelWithDebInfo|x64 27 | RelWithDebInfo|x86 = RelWithDebInfo|x86 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {D6550F63-9286-4450-A866-89587427FCCB}.Debug|x64.ActiveCfg = Debug|x64 31 | {D6550F63-9286-4450-A866-89587427FCCB}.Debug|x64.Build.0 = Debug|x64 32 | {D6550F63-9286-4450-A866-89587427FCCB}.Debug|x86.ActiveCfg = Debug|Win32 33 | {D6550F63-9286-4450-A866-89587427FCCB}.Debug|x86.Build.0 = Debug|Win32 34 | {D6550F63-9286-4450-A866-89587427FCCB}.MinSizeRel|x64.ActiveCfg = Release|x64 35 | {D6550F63-9286-4450-A866-89587427FCCB}.MinSizeRel|x64.Build.0 = Release|x64 36 | {D6550F63-9286-4450-A866-89587427FCCB}.MinSizeRel|x86.ActiveCfg = Release|Win32 37 | {D6550F63-9286-4450-A866-89587427FCCB}.MinSizeRel|x86.Build.0 = Release|Win32 38 | {D6550F63-9286-4450-A866-89587427FCCB}.Release|x64.ActiveCfg = Release|x64 39 | {D6550F63-9286-4450-A866-89587427FCCB}.Release|x64.Build.0 = Release|x64 40 | {D6550F63-9286-4450-A866-89587427FCCB}.Release|x86.ActiveCfg = Release|Win32 41 | {D6550F63-9286-4450-A866-89587427FCCB}.Release|x86.Build.0 = Release|Win32 42 | {D6550F63-9286-4450-A866-89587427FCCB}.RelWithDebInfo|x64.ActiveCfg = Release|x64 43 | {D6550F63-9286-4450-A866-89587427FCCB}.RelWithDebInfo|x64.Build.0 = Release|x64 44 | {D6550F63-9286-4450-A866-89587427FCCB}.RelWithDebInfo|x86.ActiveCfg = Release|Win32 45 | {D6550F63-9286-4450-A866-89587427FCCB}.RelWithDebInfo|x86.Build.0 = Release|Win32 46 | {65705339-3F83-42BB-B4C1-5C8AF2C23CDD}.Debug|x64.ActiveCfg = Debug|x64 47 | {65705339-3F83-42BB-B4C1-5C8AF2C23CDD}.Debug|x64.Build.0 = Debug|x64 48 | {65705339-3F83-42BB-B4C1-5C8AF2C23CDD}.Debug|x86.ActiveCfg = Debug|Win32 49 | {65705339-3F83-42BB-B4C1-5C8AF2C23CDD}.Debug|x86.Build.0 = Debug|Win32 50 | {65705339-3F83-42BB-B4C1-5C8AF2C23CDD}.MinSizeRel|x64.ActiveCfg = Release|x64 51 | {65705339-3F83-42BB-B4C1-5C8AF2C23CDD}.MinSizeRel|x64.Build.0 = Release|x64 52 | {65705339-3F83-42BB-B4C1-5C8AF2C23CDD}.MinSizeRel|x86.ActiveCfg = Release|Win32 53 | {65705339-3F83-42BB-B4C1-5C8AF2C23CDD}.MinSizeRel|x86.Build.0 = Release|Win32 54 | {65705339-3F83-42BB-B4C1-5C8AF2C23CDD}.Release|x64.ActiveCfg = Release|x64 55 | {65705339-3F83-42BB-B4C1-5C8AF2C23CDD}.Release|x64.Build.0 = Release|x64 56 | {65705339-3F83-42BB-B4C1-5C8AF2C23CDD}.Release|x86.ActiveCfg = Release|Win32 57 | {65705339-3F83-42BB-B4C1-5C8AF2C23CDD}.Release|x86.Build.0 = Release|Win32 58 | {65705339-3F83-42BB-B4C1-5C8AF2C23CDD}.RelWithDebInfo|x64.ActiveCfg = Release|x64 59 | {65705339-3F83-42BB-B4C1-5C8AF2C23CDD}.RelWithDebInfo|x64.Build.0 = Release|x64 60 | {65705339-3F83-42BB-B4C1-5C8AF2C23CDD}.RelWithDebInfo|x86.ActiveCfg = Release|Win32 61 | {65705339-3F83-42BB-B4C1-5C8AF2C23CDD}.RelWithDebInfo|x86.Build.0 = Release|Win32 62 | {F7863593-CFF2-3268-A73E-63239BFAECDC}.Debug|x64.ActiveCfg = Debug|x64 63 | {F7863593-CFF2-3268-A73E-63239BFAECDC}.Debug|x64.Build.0 = Debug|x64 64 | {F7863593-CFF2-3268-A73E-63239BFAECDC}.Debug|x86.ActiveCfg = Debug|x64 65 | {F7863593-CFF2-3268-A73E-63239BFAECDC}.MinSizeRel|x64.ActiveCfg = MinSizeRel|x64 66 | {F7863593-CFF2-3268-A73E-63239BFAECDC}.MinSizeRel|x64.Build.0 = MinSizeRel|x64 67 | {F7863593-CFF2-3268-A73E-63239BFAECDC}.MinSizeRel|x86.ActiveCfg = MinSizeRel|x64 68 | {F7863593-CFF2-3268-A73E-63239BFAECDC}.Release|x64.ActiveCfg = Release|x64 69 | {F7863593-CFF2-3268-A73E-63239BFAECDC}.Release|x64.Build.0 = Release|x64 70 | {F7863593-CFF2-3268-A73E-63239BFAECDC}.Release|x86.ActiveCfg = Release|x64 71 | {F7863593-CFF2-3268-A73E-63239BFAECDC}.RelWithDebInfo|x64.ActiveCfg = RelWithDebInfo|x64 72 | {F7863593-CFF2-3268-A73E-63239BFAECDC}.RelWithDebInfo|x64.Build.0 = RelWithDebInfo|x64 73 | {F7863593-CFF2-3268-A73E-63239BFAECDC}.RelWithDebInfo|x86.ActiveCfg = RelWithDebInfo|x64 74 | {7DF02CE1-E906-2B77-327E-B6989ED3FBFE}.Debug|x64.ActiveCfg = Debug|x64 75 | {7DF02CE1-E906-2B77-327E-B6989ED3FBFE}.Debug|x64.Build.0 = Debug|x64 76 | {7DF02CE1-E906-2B77-327E-B6989ED3FBFE}.Debug|x86.ActiveCfg = Debug|Win32 77 | {7DF02CE1-E906-2B77-327E-B6989ED3FBFE}.Debug|x86.Build.0 = Debug|Win32 78 | {7DF02CE1-E906-2B77-327E-B6989ED3FBFE}.MinSizeRel|x64.ActiveCfg = Release|x64 79 | {7DF02CE1-E906-2B77-327E-B6989ED3FBFE}.MinSizeRel|x64.Build.0 = Release|x64 80 | {7DF02CE1-E906-2B77-327E-B6989ED3FBFE}.MinSizeRel|x86.ActiveCfg = Release|Win32 81 | {7DF02CE1-E906-2B77-327E-B6989ED3FBFE}.MinSizeRel|x86.Build.0 = Release|Win32 82 | {7DF02CE1-E906-2B77-327E-B6989ED3FBFE}.Release|x64.ActiveCfg = Release|x64 83 | {7DF02CE1-E906-2B77-327E-B6989ED3FBFE}.Release|x64.Build.0 = Release|x64 84 | {7DF02CE1-E906-2B77-327E-B6989ED3FBFE}.Release|x86.ActiveCfg = Release|Win32 85 | {7DF02CE1-E906-2B77-327E-B6989ED3FBFE}.Release|x86.Build.0 = Release|Win32 86 | {7DF02CE1-E906-2B77-327E-B6989ED3FBFE}.RelWithDebInfo|x64.ActiveCfg = Release|x64 87 | {7DF02CE1-E906-2B77-327E-B6989ED3FBFE}.RelWithDebInfo|x64.Build.0 = Release|x64 88 | {7DF02CE1-E906-2B77-327E-B6989ED3FBFE}.RelWithDebInfo|x86.ActiveCfg = Release|Win32 89 | {7DF02CE1-E906-2B77-327E-B6989ED3FBFE}.RelWithDebInfo|x86.Build.0 = Release|Win32 90 | EndGlobalSection 91 | GlobalSection(SolutionProperties) = preSolution 92 | HideSolutionNode = FALSE 93 | EndGlobalSection 94 | GlobalSection(ExtensibilityGlobals) = postSolution 95 | SolutionGuid = {1B3A0ED1-8D50-4973-A384-29C984064B6D} 96 | EndGlobalSection 97 | EndGlobal 98 | -------------------------------------------------------------------------------- /music_visualizer.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | {D6550F63-9286-4450-A866-89587427FCCB} 47 | Win32Proj 48 | 10.0 49 | 50 | 51 | 52 | Application 53 | true 54 | v141 55 | 56 | 57 | Application 58 | false 59 | v141 60 | 61 | 62 | Application 63 | true 64 | v142 65 | false 66 | 67 | 68 | Application 69 | false 70 | v142 71 | true 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | true 93 | $(VC_IncludePath);$(WindowsSDK_IncludePath) 94 | $(SolutionDir)libs\ffts\build\Debug;$(SolutionDir)libs\SimpleFileWatcher\lib\Debug;$(SolutionDir)libs_win\glfw-3.2.1\;$(SolutionDir)libs_win\glew-2.1.0\;$(LibraryPath) 95 | 96 | 97 | true 98 | $(VC_IncludePath);$(WindowsSDK_IncludePath) 99 | $(SolutionDir)libs\ffts\build\Release;$(SolutionDir)libs\SimpleFileWatcher\lib\Release;$(SolutionDir)libs_win\glfw-3.2.1\;$(SolutionDir)libs_win\glew-2.1.0\;$(LibraryPath) 100 | 101 | 102 | $(SolutionDir)libs\ffts\build\Debug;$(SolutionDir)libs\SimpleFileWatcher\lib\Debug;$(SolutionDir)libs_win\glfw-3.2.1\;$(SolutionDir)libs_win\glew-2.1.0\;$(LibraryPath) 103 | $(VC_IncludePath);$(WindowsSDK_IncludePath) 104 | 105 | 106 | $(SolutionDir)libs\ffts\build\Release;$(SolutionDir)libs\SimpleFileWatcher\lib\Release;$(SolutionDir)libs_win\glfw-3.2.1\;$(SolutionDir)libs_win\glew-2.1.0\;$(LibraryPath) 107 | $(VC_IncludePath);$(WindowsSDK_IncludePath) 108 | 109 | 110 | 111 | GLEW_STATIC;WIN32;_DEBUG;_CONSOLE;WINDOWS;%(PreprocessorDefinitions) 112 | MultiThreadedDebugDLL 113 | Level3 114 | ProgramDatabase 115 | Disabled 116 | $(SolutionDir)src;$(SolutionDir)libs\ffts\include;$(SolutionDir)libs\rapidjson\include;$(SolutionDir)libs\SimpleFileWatcher\include;$(SolutionDir)libs_win\glew-2.1.0\include;$(SolutionDir)libs_win\glfw-3.2.1\include 117 | stdcpp17 118 | false 119 | 120 | 121 | MachineX86 122 | true 123 | Console 124 | ffts_staticd.lib;opengl32.lib;glew32s.lib;glfw3.lib;SimpleFileWatcher.lib;%(AdditionalDependencies) 125 | 126 | 127 | 128 | 129 | GLEW_STATIC;WIN32;NDEBUG;_CONSOLE;WINDOWS;%(PreprocessorDefinitions) 130 | MultiThreadedDLL 131 | Level3 132 | ProgramDatabase 133 | stdcpp17 134 | false 135 | $(SolutionDir)src;$(SolutionDir)libs\ffts\include;$(SolutionDir)libs\rapidjson\include;$(SolutionDir)libs\SimpleFileWatcher\include;$(SolutionDir)libs_win\glew-2.1.0\include;$(SolutionDir)libs_win\glfw-3.2.1\include 136 | Full 137 | true 138 | 139 | 140 | MachineX86 141 | true 142 | Console 143 | true 144 | true 145 | ffts_static.lib;opengl32.lib;glew32s.lib;glfw3.lib;SimpleFileWatcher.lib;%(AdditionalDependencies) 146 | 147 | 148 | 149 | 150 | $(SolutionDir)src;$(SolutionDir)libs\ffts\include;$(SolutionDir)libs\rapidjson\include;$(SolutionDir)libs\SimpleFileWatcher\include;$(SolutionDir)libs_win\glew-2.1.0\include;$(SolutionDir)libs_win\glfw-3.2.1\include 151 | ProgramDatabase 152 | GLEW_STATIC;WIN32;_DEBUG;_CONSOLE;WINDOWS;%(PreprocessorDefinitions) 153 | Disabled 154 | EnableFastChecks 155 | stdcpp17 156 | false 157 | Level3 158 | 159 | 160 | ffts_staticd.lib;opengl32.lib;glew32s.lib;glfw3.lib;SimpleFileWatcher.lib;%(AdditionalDependencies) 161 | 162 | 163 | Default 164 | 165 | 166 | 167 | 168 | Full 169 | GLEW_STATIC;WIN32;NDEBUG;_CONSOLE;WINDOWS;%(PreprocessorDefinitions) 170 | $(SolutionDir)src;$(SolutionDir)libs\ffts\include;$(SolutionDir)libs\rapidjson\include;$(SolutionDir)libs\SimpleFileWatcher\include;$(SolutionDir)libs_win\glew-2.1.0\include;$(SolutionDir)libs_win\glfw-3.2.1\include 171 | MultiThreadedDLL 172 | stdcpp17 173 | false 174 | Level3 175 | 176 | 177 | ffts_static.lib;opengl32.lib;glew32s.lib;glfw3.lib;SimpleFileWatcher.lib;%(AdditionalDependencies) 178 | 179 | 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /music_visualizer.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(SolutionDir)src 5 | WindowsLocalDebugger 6 | 7 | 8 | $(SolutionDir)src 9 | WindowsLocalDebugger 10 | 11 | -------------------------------------------------------------------------------- /notes.txt: -------------------------------------------------------------------------------- 1 | Please see the following links for more information on geometry shaders. 2 | https://open.gl/geometry 3 | https://www.khronos.org/opengl/wiki/Geometry_Shader 4 | http://www.informit.com/articles/article.aspx?p=2120983&seqNum=2 5 | 6 | The vertex shader runs once per vertex (geom_iters). 7 | The geometry shader runs once per primitive received from the vertex shader and has access to the vertices of that primitive. 8 | This program's vertex shader always outputs point primatives. 9 | 10 | out can have these primitive types 11 | points 12 | line_strip 13 | triangle_strip -------------------------------------------------------------------------------- /src/AudioStreams/AudioStream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class AudioStream { 4 | public: 5 | virtual void get_next_pcm(float* buff_l, float* buff_r, int buff_size) = 0; 6 | virtual int get_sample_rate() = 0; 7 | virtual int get_max_buff_size() = 0; 8 | }; 9 | -------------------------------------------------------------------------------- /src/AudioStreams/LinuxAudioStream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using std::cout; using std::endl; 3 | 4 | #include "LinuxAudioStream.h" 5 | 6 | LinuxAudioStream::LinuxAudioStream() { 7 | std::string sink_name; 8 | getPulseDefaultSink((void*)&sink_name); 9 | sink_name += ".monitor"; 10 | 11 | buf_interlaced = new float[sample_rate * channels]; 12 | 13 | pa_sample_spec pulseSampleSpec; 14 | pulseSampleSpec.channels = channels; 15 | pulseSampleSpec.rate = sample_rate; 16 | pulseSampleSpec.format = PA_SAMPLE_FLOAT32NE; 17 | pa_buffer_attr pb; 18 | pb.fragsize = max_buff_size*channels*sizeof(float) / 2; 19 | pb.maxlength = max_buff_size*channels*sizeof(float); 20 | pulseState = pa_simple_new(NULL, "Music Visualizer", PA_STREAM_RECORD, sink_name.c_str(), "Music Visualizer", &pulseSampleSpec, NULL, 21 | &pb, &pulseError); 22 | if (!pulseState) { 23 | cout << "Could not open pulseaudio source: " << sink_name.c_str() << " " << pa_strerror(pulseError) 24 | << ". To find a list of your pulseaudio sources run 'pacmd list-sources'" << endl; 25 | exit(EXIT_FAILURE); 26 | } 27 | } 28 | 29 | LinuxAudioStream::~LinuxAudioStream() { 30 | pa_simple_free(pulseState); 31 | delete[] buf_interlaced; 32 | } 33 | 34 | void LinuxAudioStream::get_next_pcm(float * buff_l, float * buff_r, int size) { 35 | if (max_buff_size < size) 36 | cout << "get_next_pcm called with size > max_buff_size" << endl; 37 | if (pa_simple_read(pulseState, buf_interlaced, size*channels*sizeof(float), &pulseError) < 0) { 38 | cout << "pa_simple_read() failed: " << pa_strerror(pulseError) << endl; 39 | exit(EXIT_FAILURE); 40 | } 41 | for (int i = 0; i < size; i++) { 42 | buff_l[i] = buf_interlaced[i * channels + 0]; 43 | buff_r[i] = buf_interlaced[i * channels + 1]; 44 | } 45 | } 46 | 47 | int LinuxAudioStream::get_sample_rate() { 48 | return sample_rate; 49 | } 50 | 51 | int LinuxAudioStream::get_max_buff_size() { 52 | return max_buff_size; 53 | } 54 | -------------------------------------------------------------------------------- /src/AudioStreams/LinuxAudioStream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AudioStream.h" 4 | #include "pulse_misc.h" 5 | 6 | class LinuxAudioStream : public AudioStream { 7 | public: 8 | LinuxAudioStream(); 9 | ~LinuxAudioStream(); 10 | void get_next_pcm(float* buff_l, float* buff_r, int size); 11 | int get_sample_rate(); 12 | int get_max_buff_size(); 13 | 14 | private: 15 | const int sample_rate = 48000; 16 | const int max_buff_size = 512; 17 | const int channels = 2; 18 | 19 | float* buf_interlaced; 20 | int pulseError; 21 | pa_simple* pulseState; 22 | }; 23 | -------------------------------------------------------------------------------- /src/AudioStreams/ProceduralAudioStream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "AudioStream.h" 7 | 8 | // max is a define in winmindef.h 9 | #ifdef max 10 | #undef max 11 | #endif 12 | 13 | class ProceduralAudioStream : public AudioStream { 14 | public: 15 | ProceduralAudioStream(std::function lambda) : lambda(lambda) {} 16 | void get_next_pcm(float* buff_l, float* buff_r, int buff_size) { 17 | lambda(buff_l, buff_r, buff_size); 18 | } 19 | int get_sample_rate() { 20 | return sample_rate; 21 | }; 22 | int get_max_buff_size() { 23 | return max_buff_size; 24 | } 25 | private: 26 | const int sample_rate = 48000; 27 | int max_buff_size = std::numeric_limits::max(); 28 | std::function lambda; 29 | }; 30 | -------------------------------------------------------------------------------- /src/AudioStreams/WavAudioStream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using std::cout; using std::endl; 3 | #include 4 | using std::ifstream; 5 | using std::ios; 6 | #include 7 | #include 8 | #include // memory stuff 9 | 10 | #include "WavAudioStream.h" 11 | 12 | // Create a WavStreamer backed by a CircularBuffer? 13 | 14 | // https://github.com/tkaczenko/WavReader/blob/master/WavReader/WavReader.cpp 15 | //Wav Header 16 | struct wav_header_t { 17 | char chunkID[4]; //"RIFF" = 0x46464952 18 | unsigned long chunkSize; //28 [+ sizeof(wExtraFormatBytes) + wExtraFormatBytes] + sum(sizeof(chunk.id) + sizeof(chunk.size) + chunk.size) 19 | char format[4]; //"WAVE" = 0x45564157 20 | char subchunk1ID[4]; //"fmt " = 0x20746D66 21 | unsigned long subchunk1Size; //16 [+ sizeof(wExtraFormatBytes) + wExtraFormatBytes] 22 | unsigned short audioFormat; 23 | unsigned short numChannels; 24 | unsigned long sampleRate; 25 | unsigned long byteRate; 26 | unsigned short blockAlign; 27 | unsigned short bitsPerSample; 28 | //[WORD wExtraFormatBytes;] 29 | //[Extra format bytes] 30 | }; 31 | 32 | //Chunks 33 | struct chunk_t { 34 | char ID[4]; //"data" = 0x61746164 35 | unsigned long size; //Chunk data bytes 36 | }; 37 | 38 | // Reads entire file into buffer 39 | WavAudioStream::WavAudioStream(const filesys::path &wav_path) { 40 | if (!filesys::exists(wav_path)) 41 | throw std::runtime_error("WavAudioStream: wav file not found"); 42 | ifstream fin(wav_path.string(), std::ios::binary); 43 | if (!fin.is_open()) 44 | throw std::runtime_error("WavAudioStream: file did not open" ); 45 | fin.unsetf(ios::skipws); 46 | 47 | //Read WAV header 48 | wav_header_t header; 49 | fin.read((char*)&header, sizeof(header)); 50 | 51 | //Print WAV header 52 | /* 53 | cout << "WAV File Header read:" << endl; 54 | cout << "File Type: " << header.chunkID << endl; 55 | cout << "File Size: " << header.chunkSize << endl; 56 | cout << "WAV Marker: " << header.format << endl; 57 | cout << "Format Name: " << header.subchunk1ID << endl; 58 | cout << "Format Length: " << header.subchunk1Size << endl; 59 | cout << "Format Type: " << header.audioFormat << endl; 60 | cout << "Number of Channels: " << header.numChannels << endl; 61 | cout << "Sample Rate: " << header.sampleRate << endl; 62 | cout << "Sample Rate * Bits/Sample * Channels / 8: " << header.byteRate << endl; 63 | cout << "Bits per Sample * Channels / 8.1: " << header.blockAlign << endl; 64 | cout << "Bits per Sample: " << header.bitsPerSample << endl; 65 | */ 66 | sample_rate = header.sampleRate; 67 | 68 | //Reading file 69 | chunk_t chunk; 70 | //go to data chunk 71 | while (true) { 72 | fin.read((char*)&chunk, sizeof(chunk)); 73 | if (*(unsigned int *)&chunk.ID == 0x61746164) 74 | break; 75 | //skip chunk data bytes 76 | fin.seekg(chunk.size, ios::cur); 77 | } 78 | 79 | //Number of samples 80 | int sample_size = header.bitsPerSample / 8; 81 | int samples_count = chunk.size * 8 / header.bitsPerSample; 82 | 83 | buf_interlaced = new short[samples_count]; 84 | memset(buf_interlaced, 0, sizeof(short) * samples_count); 85 | 86 | //Reading data 87 | for (int i = 0; i < samples_count; ++i) { 88 | fin.read((char*)&buf_interlaced[i], sample_size); 89 | } 90 | 91 | // TODO do not load the whole file to memory 92 | } 93 | 94 | WavAudioStream::~WavAudioStream() { 95 | if (buf_interlaced) 96 | delete[] buf_interlaced; 97 | } 98 | 99 | void WavAudioStream::get_next_pcm(float * buff_l, float * buff_r, int buff_size) { 100 | // TODO 101 | cout << "WavAudioStream.get_next_pcm not implemented yet" << endl; 102 | exit(1); 103 | } 104 | 105 | int WavAudioStream::get_sample_rate() { 106 | return sample_rate; 107 | } 108 | 109 | int WavAudioStream::get_max_buff_size() { 110 | return max_buff_size; 111 | } 112 | -------------------------------------------------------------------------------- /src/AudioStreams/WavAudioStream.h: -------------------------------------------------------------------------------- 1 | #include "AudioStream.h" 2 | 3 | #include "filesystem.h" 4 | 5 | class WavAudioStream : public AudioStream { 6 | public: 7 | WavAudioStream(const filesys::path & wav_path); 8 | ~WavAudioStream(); 9 | void get_next_pcm(float* buff_l, float* buff_r, int buff_size); 10 | int get_sample_rate(); 11 | int get_max_buff_size(); 12 | private: 13 | int sample_rate; 14 | const int max_buff_size = 512; 15 | short* buf_interlaced; 16 | }; 17 | -------------------------------------------------------------------------------- /src/AudioStreams/WindowsAudioStream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using std::cout; 3 | using std::endl; 4 | #include 5 | namespace chrono = std::chrono; 6 | #include 7 | using std::runtime_error; 8 | 9 | #include "WindowsAudioStream.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define CHECK(call) if (HRESULT x = call; x) { cout << "Windows audio error code " << x << " at line " << __LINE__ << endl; exit(x); } 17 | 18 | WindowsAudioStream::WindowsAudioStream() { 19 | // --Difficulties arise-- 20 | // PCM CAPTURE CONTROL FLOW 21 | // PCM SAMPLE RATE 22 | // pulse audio's function that gives me pcm blocks until the amount i've asked for has been delivered 23 | // window's function tells me how much I get, gives me that much and then returns. 24 | // if I want the apis to act the same I need to wrap window's function with a loop until the amount 25 | // I ask for has been given. But then I have to cache the excess and use it in the next call. 26 | // 27 | // Unless windows happens to give me the amount i need every single time I ask for some data 28 | // also pulse allows me to ask for a certain sample rate 29 | // windows tells me what sample rate i'll get 30 | 31 | CoInitialize(nullptr); 32 | 33 | const int m_refTimesPerSec = 10000000; 34 | REFERENCE_TIME hnsRequestedDuration = m_refTimesPerSec; 35 | 36 | CHECK(CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL,CLSCTX_ALL, __uuidof(IMMDeviceEnumerator),(void**)&m_pEnumerator)); 37 | 38 | CHECK(m_pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &m_pDevice)); 39 | 40 | CHECK(m_pDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL,NULL, (void**)&m_pAudioClient)); 41 | 42 | WAVEFORMATEX* m_pwfx = NULL; 43 | CHECK(m_pAudioClient->GetMixFormat(&m_pwfx)); 44 | sample_rate = m_pwfx->nSamplesPerSec; 45 | const int bits_per_byte = 8; 46 | m_pwfx->wBitsPerSample = sizeof(short) * bits_per_byte; 47 | m_pwfx->nBlockAlign = 4; 48 | m_pwfx->wFormatTag = 1; 49 | m_pwfx->cbSize = 0; 50 | m_pwfx->nAvgBytesPerSec = m_pwfx->nSamplesPerSec * m_pwfx->nBlockAlign; 51 | 52 | CHECK(m_pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED,AUDCLNT_STREAMFLAGS_LOOPBACK,hnsRequestedDuration,0,m_pwfx,NULL)); 53 | 54 | CHECK(m_pAudioClient->GetService(__uuidof(IAudioCaptureClient),(void**)&m_pCaptureClient)); 55 | 56 | // Start capturing audio 57 | CHECK(m_pAudioClient->Start()); 58 | } 59 | 60 | WindowsAudioStream::~WindowsAudioStream() { 61 | CHECK(m_pAudioClient->Stop()); 62 | } 63 | 64 | void WindowsAudioStream::get_next_pcm(float * buff_l, float * buff_r, int buff_size) { 65 | const int channels = 2; 66 | 67 | UINT32 packetLength = 0; 68 | 69 | short* pData; 70 | DWORD flags; 71 | UINT32 numFramesAvailable; 72 | 73 | // if frame_cache_fill != 0 74 | // use frame_cache 75 | // remove frame_cache 76 | // if more needed 77 | // use system provided 78 | // grow frame_cache 79 | 80 | int i = 0; 81 | if (frame_cache_fill) { 82 | int read = frame_cache_fill > buff_size ? buff_size : frame_cache_fill; 83 | for (; i < read; i++) { 84 | buff_l[i] = frame_cache[i * channels + 0]/32768.f; 85 | buff_r[i] = frame_cache[i * channels + 1]/32768.f; 86 | } 87 | frame_cache_fill -= read; 88 | // Remove from the fifo the frames we've just read 89 | memcpy(frame_cache, frame_cache + channels*read, sizeof(short)*channels*frame_cache_fill); 90 | } 91 | 92 | // if i == buff_size, then we can just return. (audio_buff completely filled from frame_cache) 93 | // We do not lose some section of the system audio stream if we return here, because we've not called GetBuffer yet. 94 | // So we do not need to fill the frame_cache with anything here if we return here. 95 | // We only need to fill the frame_cache with the frames we don't use after calling GetBuffer 96 | 97 | auto start = chrono::steady_clock::now(); 98 | while (i < buff_size) { 99 | 100 | // TODOFPS 101 | if (chrono::steady_clock::now() - start > chrono::milliseconds(144)) { 102 | std::fill(buff_l, buff_l + buff_size, 0.f); 103 | std::fill(buff_r, buff_r + buff_size, 0.f); 104 | break; 105 | } 106 | 107 | CHECK(m_pCaptureClient->GetNextPacketSize(&packetLength)); 108 | if (packetLength != 0) { 109 | // Get the available data in the shared buffer. 110 | CHECK(m_pCaptureClient->GetBuffer((BYTE**)&pData, &numFramesAvailable, &flags, NULL, NULL)); 111 | 112 | // Copy the available capture data to the audio sink. 113 | int j = 0; // j==1 is 1 frame 114 | for (; i + j < buff_size && j < numFramesAvailable; j++) { 115 | buff_l[i + j] = pData[j * channels + 0] / 32768.f; 116 | buff_r[i + j] = pData[j * channels + 1] / 32768.f; 117 | } 118 | i += j; 119 | 120 | // If we didn't use all the frames returned by the system 121 | if (j != numFramesAvailable) { 122 | if (frame_cache_fill + (numFramesAvailable - j) >= CACHE_SIZE) { 123 | cout << "WindowsAudioStream::get_next_pcm fifo overflow" << endl; 124 | exit(-1); 125 | } 126 | // Then copy what we didn't use to the frame_cache 127 | memcpy(frame_cache + channels * frame_cache_fill, pData + j * channels, sizeof(short)*channels*(numFramesAvailable - j)); 128 | frame_cache_fill += numFramesAvailable - j; 129 | } 130 | 131 | CHECK(m_pCaptureClient->ReleaseBuffer(numFramesAvailable)); 132 | } 133 | } 134 | } 135 | 136 | int WindowsAudioStream::get_sample_rate() { 137 | return sample_rate; 138 | } 139 | 140 | int WindowsAudioStream::get_max_buff_size() { 141 | return max_buff_size; 142 | } 143 | -------------------------------------------------------------------------------- /src/AudioStreams/WindowsAudioStream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AudioStream.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class WindowsAudioStream : public AudioStream { 11 | public: 12 | WindowsAudioStream(); 13 | ~WindowsAudioStream(); 14 | void get_next_pcm(float* buff_l, float* buff_r, int buff_size); 15 | int get_sample_rate(); 16 | int get_max_buff_size(); 17 | 18 | private: 19 | int sample_rate; 20 | const int max_buff_size = 512; 21 | 22 | IAudioClient* m_pAudioClient = NULL; 23 | IAudioCaptureClient* m_pCaptureClient = NULL; 24 | IMMDeviceEnumerator* m_pEnumerator = NULL; 25 | IMMDevice* m_pDevice = NULL; 26 | 27 | static const int m_refTimesPerMS = 10000; 28 | static const int CACHE_SIZE = 10000; 29 | 30 | int frame_cache_fill = 0; 31 | short frame_cache[WindowsAudioStream::CACHE_SIZE]; 32 | }; 33 | -------------------------------------------------------------------------------- /src/AudioStreams/pulse_misc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | // pulseaudio code from github: karlstav/cava 8 | 9 | static pa_mainloop* m_pulseaudio_mainloop; 10 | static void cb(pa_context* pulseaudio_context, const pa_server_info* i, void* data) { 11 | std::string* sink_name = (std::string*) data; 12 | *sink_name = std::string(i->default_sink_name); 13 | pa_mainloop_quit(m_pulseaudio_mainloop, 0); 14 | } 15 | static void pulseaudio_context_state_callback(pa_context* pulseaudio_context, void* data) { 16 | switch (pa_context_get_state(pulseaudio_context)) { 17 | case PA_CONTEXT_UNCONNECTED: 18 | // std::cout << "UNCONNECTED" << std::endl; 19 | break; 20 | case PA_CONTEXT_CONNECTING: 21 | // std::cout << "CONNECTING" << std::endl; 22 | break; 23 | case PA_CONTEXT_AUTHORIZING: 24 | // std::cout << "AUTHORIZING" << std::endl; 25 | break; 26 | case PA_CONTEXT_SETTING_NAME: 27 | // std::cout << "SETTING_NAME" << std::endl; 28 | break; 29 | case PA_CONTEXT_READY: // extract default sink name 30 | // std::cout << "READY" << std::endl; 31 | pa_operation_unref(pa_context_get_server_info(pulseaudio_context, cb, data)); 32 | break; 33 | case PA_CONTEXT_FAILED: 34 | // std::cout << "FAILED" << std::endl; 35 | break; 36 | case PA_CONTEXT_TERMINATED: 37 | // std::cout << "TERMINATED" << std::endl; 38 | pa_mainloop_quit(m_pulseaudio_mainloop, 0); 39 | break; 40 | } 41 | } 42 | static void getPulseDefaultSink(void* data) { 43 | pa_mainloop_api* mainloop_api; 44 | pa_context* pulseaudio_context; 45 | int ret; 46 | // Create a mainloop API and connection to the default server 47 | m_pulseaudio_mainloop = pa_mainloop_new(); 48 | mainloop_api = pa_mainloop_get_api(m_pulseaudio_mainloop); 49 | pulseaudio_context = pa_context_new(mainloop_api, "APPNAME device list"); 50 | // This function connects to the pulse server 51 | pa_context_connect(pulseaudio_context, NULL, PA_CONTEXT_NOFLAGS, NULL); 52 | // This function defines a callback so the server will tell us its state. 53 | pa_context_set_state_callback(pulseaudio_context, pulseaudio_context_state_callback, data); 54 | // starting a mainloop to get default sink 55 | if (pa_mainloop_run(m_pulseaudio_mainloop, &ret) < 0) { 56 | std::cout << "Could not open pulseaudio mainloop to find default device name: %d" << ret << std::endl; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/FileWatcher.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "filesystem.h" 7 | 8 | #include "FileWatcher/FileWatcher.h" 9 | 10 | class FileWatcher : FW::FileWatchListener { 11 | public: 12 | FileWatcher(filesys::path shader_folder) : shader_folder(shader_folder), shaders_changed(false), last_event_time() 13 | { 14 | file_watcher.addWatch(shader_folder.string(), (FW::FileWatchListener*)this, false); 15 | } 16 | 17 | ~FileWatcher() { 18 | file_watcher.removeWatch(shader_folder.string()); 19 | } 20 | 21 | // More than one event can be delivered by the editor from a single save command. 22 | // So sleep a few millis and then process the event and set the last process time. 23 | // If new event is within 100 ms of last process time, then ignore it. 24 | // If shader.json or any frag or geom file has changed, then set shaders_changed. 25 | void handleFileAction(FW::WatchID watchid, const FW::String& dir, const FW::String& filename_str, FW::Action action) 26 | { 27 | if (FW::Action::Delete == action) 28 | return; 29 | if (dir != shader_folder) 30 | return; 31 | std::string extension = filesys::path(filename_str).extension().string(); 32 | if (extension != ".json" && extension != ".geom" && extension != ".frag") 33 | return; 34 | if (std::chrono::steady_clock::now() - last_event_time < std::chrono::milliseconds(100)) 35 | return; 36 | 37 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 38 | 39 | shaders_changed = true; 40 | last_event_time = std::chrono::steady_clock::now(); 41 | } 42 | 43 | bool files_changed() { 44 | if (shaders_changed) { 45 | shaders_changed = false; 46 | return true; 47 | } 48 | return false; 49 | } 50 | 51 | private: 52 | bool shaders_changed; 53 | std::chrono::steady_clock::time_point last_event_time; 54 | 55 | filesys::path shader_folder; 56 | FW::AsyncFileWatcher file_watcher; 57 | }; 58 | -------------------------------------------------------------------------------- /src/Renderer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using std::cout; 3 | using std::endl; 4 | #include 5 | namespace chrono = std::chrono; 6 | using ClockT = std::chrono::steady_clock; 7 | 8 | #include "Renderer.h" 9 | 10 | void GLAPIENTRY MessageCallback(GLenum source, 11 | GLenum type, 12 | GLuint id, 13 | GLenum severity, 14 | GLsizei length, 15 | const GLchar* message, 16 | const void* userParam) { 17 | fprintf(stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", 18 | (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""), type, severity, message); 19 | } 20 | 21 | // TODO Test the output of the shaders. Use dummy data in AudioData. Compute similarity between 22 | // expected images and produced images. 23 | 24 | // TODO add a previously rendered uniform so that a single buffer can be repetitvely applied 25 | 26 | // TODO buffer.size option is ShaderConfig is not rendered correctly, rendering to half res and then upscaling in image.frag doesn't work as expected 27 | 28 | Renderer& Renderer::operator=(Renderer&& o) { 29 | glDeleteFramebuffers(fbos.size(), fbos.data()); 30 | // The default window's fbo is now bound 31 | 32 | glDeleteTextures(fbo_textures.size(), fbo_textures.data()); 33 | // Textures in fbo_textures are unboud from their targets 34 | 35 | glDeleteTextures(audio_textures.size(), audio_textures.data()); 36 | 37 | fbos = std::move(o.fbos); 38 | fbo_textures = std::move(o.fbo_textures); 39 | audio_textures = std::move(o.audio_textures); 40 | buffers_last_drawn = std::move(o.buffers_last_drawn); 41 | num_user_buffers = o.num_user_buffers; 42 | frame_counter = o.frame_counter; 43 | elapsed_time = o.elapsed_time; 44 | 45 | o.fbos.clear(); 46 | o.fbo_textures.clear(); 47 | o.audio_textures.clear(); 48 | o.buffers_last_drawn.clear(); 49 | o.num_user_buffers = 0; 50 | o.frame_counter = 0; 51 | o.elapsed_time = 0; 52 | 53 | return *this; 54 | } 55 | 56 | Renderer::Renderer(const ShaderConfig& config, const Window& window) 57 | : config(config), window(window), frame_counter(0), num_user_buffers(0), buffers_last_drawn(config.mBuffers.size(), 0) { 58 | #ifdef _DEBUG 59 | glEnable(GL_DEBUG_OUTPUT); 60 | glDebugMessageCallback(MessageCallback, 0); 61 | #endif 62 | 63 | if (config.mBlend) { 64 | // I chose the following blending func because it allows the user to completely 65 | // replace the colors in the buffer by setting their output alpha to 1. 66 | // unfortunately it forces the user to make one of three choices: 67 | // 1) replace color in the framebuffer 68 | // 2) leave framebuffer unchanged 69 | // 3) mix new color with old color 70 | glEnable(GL_BLEND); 71 | // mix(old_color.rgb, new_color.rgb, new_color_alpha) 72 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 73 | } 74 | else { 75 | glDisable(GL_BLEND); 76 | } 77 | 78 | //glGetIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES, &max_output_vertices); 79 | //glEnable(GL_DEPTH_TEST); // maybe allow as option so that geom shaders are more useful 80 | glDisable(GL_DEPTH_TEST); 81 | 82 | // Required by gl but unused. 83 | GLuint vao; 84 | glGenVertexArrays(1, &vao); 85 | glBindVertexArray(vao); 86 | 87 | num_user_buffers = int(config.mBuffers.size()); 88 | 89 | // Create framebuffers and textures 90 | for (int i = 0; i < num_user_buffers; ++i) { 91 | GLuint tex1, tex2, fbo; 92 | 93 | Buffer buff = config.mBuffers[i]; 94 | if (buff.is_window_size) { 95 | buff.width = window.width; 96 | buff.height = window.height; 97 | } 98 | 99 | glActiveTexture(GL_TEXTURE0 + i); 100 | glGenTextures(1, &tex1); 101 | glBindTexture(GL_TEXTURE_2D, tex1); 102 | glTexImage2D(GL_TEXTURE_2D, // which binding point on the current active texture 103 | 0, 104 | GL_RGBA32F, // how is the data stored on the gfx card 105 | buff.width, buff.height, 0, 106 | GL_RGBA, GL_FLOAT, nullptr); // describes how the data is stored on the cpu 107 | // TODO parameterize wrap behavior 108 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 109 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 110 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 111 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 112 | 113 | glGenTextures(1, &tex2); 114 | glGenTextures(1, &tex2); 115 | glBindTexture(GL_TEXTURE_2D, tex2); 116 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, buff.width, buff.height, 0, GL_RGBA, GL_FLOAT, nullptr); 117 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 118 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 119 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 120 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 121 | 122 | glGenFramebuffers(1, &fbo); 123 | glBindFramebuffer(GL_FRAMEBUFFER, fbo); 124 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex1, 0); 125 | 126 | fbo_textures.push_back(tex1); 127 | fbo_textures.push_back(tex2); 128 | fbos.push_back(fbo); 129 | } 130 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 131 | 132 | // Generate audio textures 133 | for (int i = 0; i < 4; ++i) { 134 | GLuint tex; 135 | glGenTextures(1, &tex); 136 | glActiveTexture(GL_TEXTURE0 + i); 137 | glBindTexture(GL_TEXTURE_1D, tex); 138 | glTexImage1D(GL_TEXTURE_1D, 0, GL_R32F, VISUALIZER_BUFSIZE, 0, GL_RED, GL_FLOAT, nullptr); 139 | glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT); 140 | glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_REPEAT); 141 | glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 142 | glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 143 | audio_textures.push_back(tex); 144 | } 145 | 146 | start_time = ClockT::now(); 147 | } 148 | 149 | Renderer::~Renderer() { 150 | // revert opengl state 151 | 152 | glDeleteFramebuffers(fbos.size(), fbos.data()); 153 | // The default window's fbo is now bound 154 | 155 | glDeleteTextures(fbo_textures.size(), fbo_textures.data()); 156 | // Textures in fbo_textures are unboud from their targets 157 | 158 | glDeleteTextures(audio_textures.size(), audio_textures.data()); 159 | } 160 | 161 | void Renderer::update(AudioData& data) { 162 | // Update audio textures 163 | // glActivateTexture activates a certain texture unit. 164 | // each texture unit holds one texture of each dimension of texture 165 | // {GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_CUBEMAP} 166 | // because I'm using four 1D textures I need to store them in separate texture units 167 | // 168 | // glUniform1i(textureLoc, int) sets what texture unit the sampler in the shader reads from 169 | // 170 | // A texture binding created with glBindTexture remains active until a different texture is 171 | // bound to the same target (in the active unit? I think), or until the bound texture is deleted 172 | // with glDeleteTextures. So I do not need to rebind 173 | // glBindTexture(GL_TEXTURE_1D, tex[X]); 174 | data.mtx.lock(); 175 | glActiveTexture(GL_TEXTURE0 + 0); 176 | glTexSubImage1D(GL_TEXTURE_1D, 0, 0, VISUALIZER_BUFSIZE, GL_RED, GL_FLOAT, data.audio_r); 177 | glActiveTexture(GL_TEXTURE0 + 1); 178 | glTexSubImage1D(GL_TEXTURE_1D, 0, 0, VISUALIZER_BUFSIZE, GL_RED, GL_FLOAT, data.audio_l); 179 | glActiveTexture(GL_TEXTURE0 + 2); 180 | glTexSubImage1D(GL_TEXTURE_1D, 0, 0, VISUALIZER_BUFSIZE, GL_RED, GL_FLOAT, data.freq_r); 181 | glActiveTexture(GL_TEXTURE0 + 3); 182 | glTexSubImage1D(GL_TEXTURE_1D, 0, 0, VISUALIZER_BUFSIZE, GL_RED, GL_FLOAT, data.freq_l); 183 | data.mtx.unlock(); 184 | 185 | update(); 186 | } 187 | 188 | void Renderer::update() { 189 | if (window.size_changed) { 190 | // Resize textures for window sized buffers 191 | for (int i = 0; i < config.mBuffers.size(); ++i) { 192 | Buffer buff = config.mBuffers[i]; 193 | if (buff.is_window_size) { 194 | buff.width = window.width; 195 | buff.height = window.height; 196 | } 197 | glBindTexture(GL_TEXTURE_2D, fbo_textures[2 * i]); 198 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, buff.width, buff.height, 0, GL_RGBA, GL_FLOAT, nullptr); 199 | glBindTexture(GL_TEXTURE_2D, fbo_textures[2 * i + 1]); 200 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, buff.width, buff.height, 0, GL_RGBA, GL_FLOAT, nullptr); 201 | } 202 | frame_counter = 0; 203 | start_time = ClockT::now(); 204 | // for (int& bld : buffers_last_drawn) { 205 | // bld = 0; 206 | // } 207 | } 208 | } 209 | 210 | void Renderer::render() { 211 | auto now = ClockT::now(); 212 | elapsed_time = (now - start_time).count() / 1e9f; 213 | //if (elapsed_time > 0) 214 | // cout << (frame_counter / elapsed_time) << " " << frame_counter << " " << elapsed_time << endl; 215 | 216 | // Render buffers 217 | for (const int r : config.mRender_order) { 218 | Buffer buff = config.mBuffers[r]; 219 | if (buff.is_window_size) { 220 | buff.width = window.width; 221 | buff.height = window.height; 222 | } 223 | shaders->use_program(r); 224 | upload_uniforms(buff, r); 225 | glActiveTexture(GL_TEXTURE0 + r); 226 | glBindTexture(GL_TEXTURE_2D, fbo_textures[2 * r + buffers_last_drawn[r]]); 227 | glBindFramebuffer(GL_FRAMEBUFFER, fbos[r]); 228 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo_textures[2 * r + (buffers_last_drawn[r] + 1) % 2], 0); 229 | glViewport(0, 0, buff.width, buff.height); 230 | glClearColor(buff.clear_color[0], buff.clear_color[1], buff.clear_color[2], 1.f); 231 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 232 | glDrawArrays(GL_POINTS, 0, buff.geom_iters); 233 | buffers_last_drawn[r] += 1; 234 | buffers_last_drawn[r] %= 2; 235 | // bind most recently drawn texture to texture unit r so other buffers can use it 236 | glBindTexture(GL_TEXTURE_2D, fbo_textures[2 * r + buffers_last_drawn[r]]); 237 | } 238 | 239 | // Render image 240 | shaders->use_program(num_user_buffers); 241 | Buffer buff = config.mImage; 242 | if (buff.is_window_size) { 243 | buff.width = window.width; 244 | buff.height = window.height; 245 | } 246 | upload_uniforms(buff, num_user_buffers); 247 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 248 | glViewport(0, 0, buff.width, buff.height); 249 | glClearColor(buff.clear_color[0], buff.clear_color[1], buff.clear_color[2], 1.f); 250 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 251 | glDrawArrays(GL_POINTS, 0, buff.geom_iters); 252 | frame_counter++; 253 | } 254 | 255 | void Renderer::set_programs(const ShaderPrograms* progs) { 256 | shaders = progs; 257 | } 258 | 259 | void Renderer::upload_uniforms(const Buffer& buff, const int buff_index) const { 260 | // Builtin uniforms 261 | for (const auto& u : shaders->builtin_uniforms) 262 | u.update(buff_index, buff); 263 | int uniform_offset = shaders->builtin_uniforms.size(); 264 | 265 | // Point user's samplers to texture units 266 | for (int i = 0; i < num_user_buffers; ++i) 267 | glUniform1i(shaders->get_uniform_loc(buff_index, uniform_offset + i), i); 268 | uniform_offset += num_user_buffers; 269 | 270 | // TODO remove this functionality? simplify. 271 | // User's uniforms 272 | for (int i = 0; i < config.mUniforms.size(); ++i) { 273 | const std::vector& uv = config.mUniforms[i].values; 274 | switch (uv.size()) { 275 | case 1: 276 | glUniform1f(shaders->get_uniform_loc(buff_index, uniform_offset + i), uv[0]); 277 | break; 278 | case 2: 279 | glUniform2f(shaders->get_uniform_loc(buff_index, uniform_offset + i), uv[0], uv[1]); 280 | break; 281 | case 3: 282 | glUniform3f(shaders->get_uniform_loc(buff_index, uniform_offset + i), uv[0], uv[1], uv[2]); 283 | break; 284 | case 4: 285 | glUniform4f(shaders->get_uniform_loc(buff_index, uniform_offset + i), uv[0], uv[1], uv[2], uv[3]); 286 | break; 287 | } 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /src/Renderer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "ShaderConfig.h" 6 | #include "Window.h" 7 | 8 | #include "AudioProcess.h" 9 | 10 | class ShaderPrograms; 11 | 12 | class Renderer { 13 | friend class ShaderPrograms; // for uploading uniform values 14 | public: 15 | Renderer(const ShaderConfig& config, const Window& window); 16 | Renderer& operator=(Renderer&& o); 17 | ~Renderer(); 18 | 19 | void update(AudioData &data); 20 | void update(); 21 | void render(); 22 | void set_programs(const ShaderPrograms* shaders); 23 | 24 | private: 25 | Renderer(Renderer&) = delete; 26 | Renderer(Renderer&&) = delete; 27 | Renderer& operator=(Renderer& o) = delete; 28 | 29 | const ShaderConfig& config; 30 | const ShaderPrograms* shaders; 31 | const Window& window; 32 | 33 | void upload_uniforms(const Buffer& buff, const int buff_index) const; 34 | 35 | std::chrono::steady_clock::time_point start_time; 36 | float elapsed_time; 37 | 38 | int frame_counter; 39 | int num_user_buffers; 40 | std::vector buffers_last_drawn; 41 | std::vector fbos; // n * num_user_buffs 42 | std::vector fbo_textures; // 2n * num_user_buffs 43 | std::vector audio_textures; // 2n * num_user_buffs 44 | }; 45 | 46 | #include "ShaderPrograms.h" 47 | -------------------------------------------------------------------------------- /src/ShaderConfig.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using std::ifstream; 3 | #include 4 | using std::stringstream; 5 | #include 6 | using std::sort; 7 | #include 8 | using std::cout; 9 | using std::endl; 10 | #include 11 | using std::string; 12 | using std::to_string; 13 | #include 14 | using std::set; 15 | #include 16 | using std::vector; 17 | #include // isalpha 18 | #include 19 | using std::runtime_error; 20 | 21 | #include "ShaderConfig.h" 22 | #include "rapidjson/document.h" 23 | #include "rapidjson/error/en.h" 24 | namespace rj = rapidjson; 25 | 26 | // TODO throw for unknown configuration options 27 | // TODO check for valid glsl names for buffers and uniforms 28 | // TODO fail if options are given more than once 29 | 30 | 31 | static const string WINDOW_SIZE_OPTION("window_size"); 32 | static const string EASY_SHADER_MODE_OPTION("easy"); 33 | static const string ADVANCED_SHADER_MODE_OPTION("advanced"); 34 | 35 | static AudioOptions parse_audio_options(rj::Document& user_conf) { 36 | AudioOptions ao; 37 | 38 | rj::Value& audio_options = user_conf["audio_options"]; 39 | if (!audio_options.IsObject()) 40 | throw runtime_error("Audio options must be a json object"); 41 | 42 | if (audio_options.HasMember("fft_smooth")) { 43 | rj::Value& fft_smooth = audio_options["fft_smooth"]; 44 | if (!fft_smooth.IsNumber()) 45 | throw runtime_error("fft_smooth must be a number between in the interval [0, 1]"); 46 | ao.fft_smooth = fft_smooth.GetFloat(); 47 | if (ao.fft_smooth < 0 || ao.fft_smooth > 1) 48 | throw runtime_error("fft_smooth must be in the interval [0, 1]"); 49 | } 50 | if (audio_options.HasMember("wave_smooth")) { 51 | rj::Value& wave_smooth = audio_options["wave_smooth"]; 52 | if (!wave_smooth.IsNumber()) 53 | throw runtime_error("wave_smooth must be a number between in the interval [0, 1]"); 54 | ao.wave_smooth = wave_smooth.GetFloat(); 55 | if (ao.wave_smooth < 0 || ao.wave_smooth > 1) 56 | throw runtime_error("wave_smooth must be in the interval [0, 1]"); 57 | } 58 | if (audio_options.HasMember("fft_sync")) { 59 | rj::Value& fft_sync = audio_options["fft_sync"]; 60 | if (!fft_sync.IsBool()) 61 | throw runtime_error("fft_sync must be true or false"); 62 | ao.fft_sync = fft_sync.GetBool(); 63 | } 64 | if (audio_options.HasMember("xcorr_sync")) { 65 | rj::Value& xcorr_sync = audio_options["xcorr_sync"]; 66 | if (!xcorr_sync.IsBool()) 67 | throw runtime_error("xcorr_sync must be true or false"); 68 | ao.xcorr_sync = xcorr_sync.GetBool(); 69 | } 70 | 71 | return ao; 72 | } 73 | 74 | static Buffer parse_image_buffer(rj::Document& user_conf) { 75 | Buffer image_buffer; 76 | image_buffer.name = "image"; 77 | 78 | rj::Value& image = user_conf["image"]; 79 | if (!image.IsObject()) 80 | throw runtime_error("image is not a json object"); 81 | if (!image.HasMember("geom_iters")) 82 | throw runtime_error("image does not contain the geom_iters option"); 83 | 84 | rj::Value& geom_iters = image["geom_iters"]; 85 | 86 | if (!geom_iters.IsInt() || geom_iters.GetInt() <= 0) 87 | throw runtime_error("image.geom_iters must be a positive integer"); 88 | image_buffer.geom_iters = geom_iters.GetInt(); 89 | 90 | if (image.HasMember("clear_color")) { 91 | rj::Value& clear_color = image["clear_color"]; 92 | if (!(clear_color.IsArray() && clear_color.Size() == 3)) 93 | throw runtime_error("image.clear_color must be an array of 3 real numbers each between 0 and 1"); 94 | for (int i = 0; i < 3; ++i) { 95 | if (clear_color[i].IsNumber()) 96 | image_buffer.clear_color[i] = clear_color[i].GetFloat(); 97 | else 98 | throw runtime_error("image.clear_color must be an array of 3 real numbers each between 0 and 1"); 99 | } 100 | } 101 | 102 | return image_buffer; 103 | } 104 | 105 | static Buffer parse_buffer(rj::Value& buffer, string buffer_name, set& buffer_names) { 106 | Buffer b; 107 | 108 | b.name = buffer_name; 109 | if (b.name == string("")) 110 | throw runtime_error("Buffer must have a name"); 111 | if (buffer_names.count(b.name)) 112 | throw runtime_error("Buffer name " + b.name + " already used (buffers must have unique names)"); 113 | buffer_names.insert(b.name); 114 | if (!(std::isalpha(b.name[0]) || b.name[0] == '_')) 115 | throw runtime_error("Invalid buffer name: " + b.name + " buffer names must start with either a letter or an underscore"); 116 | if (b.name == string("image")) 117 | throw runtime_error("Cannot name buffer image"); 118 | 119 | if (!buffer.IsObject()) 120 | throw runtime_error("Buffer " + b.name + " is not a json object"); 121 | 122 | if (buffer.HasMember("clear_color")) { 123 | rj::Value& b_clear_color = buffer["clear_color"]; 124 | if (!b_clear_color.IsArray() || b_clear_color.Size() != 3) { 125 | throw runtime_error(b.name + ".clear_color must be an array of 3 real numbers each between 0 and 1"); 126 | } 127 | for (int i = 0; i < 3; ++i) { 128 | if (b_clear_color[i].IsNumber()) 129 | b.clear_color[i] = b_clear_color[i].GetFloat(); 130 | else 131 | throw runtime_error(b.name + ".clear_color must be an array of 3 real numbers each between 0 and 1"); 132 | // else if (b_clear_color[i].IsInt()) 133 | // b.clear_color[i] = b_clear_color[i].GetFloat()/256.f; 134 | } 135 | } 136 | 137 | 138 | if (!buffer.HasMember("size")) { 139 | cout << "Buffer: " << b.name << " does not have a size option, assuming the size of the buffer to be the window size" << endl; 140 | } 141 | else { 142 | rj::Value& b_size = buffer["size"]; 143 | if (b_size.IsArray() && b_size.Size() == 2) { 144 | if (!b_size[0].IsInt() || !b_size[1].IsInt()) 145 | throw runtime_error(b.name + ".size must be an array of two positive integers"); 146 | b.width = b_size[0].GetInt(); 147 | b.height = b_size[1].GetInt(); 148 | b.is_window_size = false; 149 | } 150 | else if (!b_size.IsString() || b_size.GetString() != WINDOW_SIZE_OPTION) { 151 | throw runtime_error(b.name + ".size must be an array of two positive integers"); 152 | } 153 | } 154 | 155 | if (!buffer.HasMember("geom_iters")) { 156 | cout << "Buffer: " << b.name << " does not have a geom_iters option, assuming the number of times to execute geometry shader is 1" << endl; 157 | } 158 | else { 159 | rj::Value& b_geom_iters = buffer["geom_iters"]; 160 | if (!b_geom_iters.IsInt() || b_geom_iters.GetInt() == 0) 161 | throw runtime_error(b.name + ".geom_iters must be a positive integer"); 162 | b.geom_iters = b_geom_iters.GetInt(); 163 | } 164 | 165 | return b; 166 | } 167 | 168 | static vector parse_buffers(rj::Document& user_conf) { 169 | vector buffers_vec; 170 | 171 | rj::Value& buffers = user_conf["buffers"]; 172 | if (!buffers.IsObject()) 173 | throw runtime_error("buffers is not a json object"); 174 | 175 | if (buffers.MemberCount() == 0) { 176 | return {}; 177 | } 178 | 179 | // Catch buffers with the same name 180 | set buffer_names; 181 | 182 | for (auto memb = buffers.MemberBegin(); memb != buffers.MemberEnd(); memb++) { 183 | buffers_vec.push_back(parse_buffer(memb->value, memb->name.GetString(), buffer_names)); 184 | } 185 | 186 | return buffers_vec; 187 | } 188 | 189 | static vector parse_render_order(rj::Document& user_conf, vector& buffers) { 190 | vector ro; 191 | 192 | if (!user_conf.HasMember("render_order")) { 193 | for (int i = 0; i < buffers.size(); ++i) 194 | ro.push_back(i); 195 | return ro; 196 | } 197 | 198 | if (!buffers.size()) { 199 | return {}; 200 | } 201 | 202 | rj::Value& render_order = user_conf["render_order"]; 203 | if (!render_order.IsArray() || render_order.Size() == 0) 204 | throw runtime_error("render_order must be an array of strings (buffer names) with length > 0"); 205 | for (unsigned int i = 0; i < render_order.Size(); ++i) { 206 | if (!render_order[i].IsString()) 207 | throw runtime_error("render_order can only contain strings (buffer names)"); 208 | 209 | string b_name = render_order[i].GetString(); 210 | int index = -1; 211 | for (int j = 0; j < buffers.size(); ++j) { 212 | if (buffers[j].name == b_name) { 213 | index = j; 214 | break; 215 | } 216 | } 217 | if (index == -1) 218 | throw runtime_error("render_order member \"" + b_name + "\" must be the name of a buffer in \"buffers\""); 219 | 220 | // mRender_order contains indices into mBuffers 221 | ro.push_back(index); 222 | } 223 | 224 | return ro; 225 | } 226 | 227 | static void delete_unused_buffers(vector& buffers, vector& render_order) { 228 | // Only keep the buffers that are used to render 229 | set used_buff_indices; 230 | vector used_buffs; 231 | for (int i = 0; i < render_order.size(); i++) { 232 | if (!used_buff_indices.count(render_order[i])) { 233 | used_buffs.push_back(buffers[render_order[i]]); 234 | used_buff_indices.insert(render_order[i]); 235 | } 236 | } 237 | buffers = used_buffs; 238 | } 239 | 240 | Uniform parse_uniform(rj::Value& uniform, string uniform_name, set& uniform_names) { 241 | Uniform u; 242 | 243 | if (uniform_names.count(uniform_name)) 244 | throw runtime_error("Uniform name " + uniform_name + " already used (uniforms must have unique names)"); 245 | uniform_names.insert(uniform_name); 246 | u.name = uniform_name; 247 | 248 | if (uniform.IsArray()) { 249 | if (uniform.Size() > 4) 250 | throw runtime_error("Uniform " + u.name + " must have dimension less than or equal to 4"); 251 | for (unsigned int i = 0; i < uniform.Size(); ++i) { 252 | if (!uniform[i].IsNumber()) 253 | throw runtime_error("Uniform " + u.name + " contains a non-numeric value."); 254 | u.values.push_back(uniform[i].GetFloat()); 255 | } 256 | } 257 | else if (uniform.IsNumber()) { 258 | u.values.push_back(uniform.GetFloat()); 259 | } 260 | else { 261 | throw runtime_error("Uniform " + u.name + " must be either a number or an array of numbers."); 262 | } 263 | return u; 264 | } 265 | 266 | static vector parse_uniforms(rj::Document& user_conf) { 267 | vector uniform_vec; 268 | 269 | rj::Value& uniforms = user_conf["uniforms"]; 270 | if (!uniforms.IsObject()) 271 | throw runtime_error("Uniforms must be a json object."); 272 | 273 | if (uniforms.MemberCount() == 0) { 274 | return {}; 275 | } 276 | 277 | // Catch uniforms with the same name 278 | set uniform_names; 279 | 280 | for (auto memb = uniforms.MemberBegin(); memb != uniforms.MemberEnd(); memb++) { 281 | uniform_vec.push_back(parse_uniform(memb->value, memb->name.GetString(), uniform_names)); 282 | } 283 | 284 | return uniform_vec; 285 | } 286 | 287 | void ShaderConfig::parse_config_from_string(const std::string & json_str) { 288 | rj::Document user_conf; 289 | rj::ParseResult ok = 290 | user_conf.Parse(json_str.c_str()); 291 | if (!ok) 292 | throw runtime_error("JSON parse error: " + string(rj::GetParseError_En(ok.Code())) + 293 | " At char offset (" + to_string(ok.Offset()) + ")"); 294 | 295 | if (!user_conf.IsObject()) 296 | throw runtime_error("Invalid json file"); 297 | 298 | if (user_conf.HasMember("initial_window_size")) { 299 | rj::Value& window_size = user_conf["initial_window_size"]; 300 | if (!(window_size.IsArray() && window_size.Size() == 2 && window_size[0].IsInt() && window_size[1].IsInt())) 301 | throw runtime_error("initial_window_size must be an array of 2 positive integers"); 302 | mInitWinSize.width = window_size[0].GetInt(); 303 | mInitWinSize.height = window_size[1].GetInt(); 304 | } 305 | 306 | if (user_conf.HasMember("audio_enabled")) { 307 | rj::Value& audio_enabled = user_conf["audio_enabled"]; 308 | if (!audio_enabled.IsBool()) 309 | throw runtime_error("audio_enabled must be true or false"); 310 | mAudio_enabled = audio_enabled.GetBool(); 311 | } 312 | 313 | if (user_conf.HasMember("audio_options")) { 314 | mAudio_ops = parse_audio_options(user_conf); 315 | } 316 | 317 | if (user_conf.HasMember("shader_mode")) { 318 | if (!user_conf["shader_mode"].IsString()) 319 | throw runtime_error("shader_mode must be either \"easy\" or \"advanced\""); 320 | if (user_conf["shader_mode"].GetString() == EASY_SHADER_MODE_OPTION) { 321 | mSimpleMode = true; 322 | } 323 | else if (user_conf["shader_mode"].GetString() == ADVANCED_SHADER_MODE_OPTION) { 324 | mSimpleMode = false; 325 | } 326 | else { 327 | throw runtime_error("shader_mode must be either \"easy\" or \"advanced\""); 328 | } 329 | } 330 | else { 331 | mSimpleMode = true; 332 | } 333 | 334 | if (user_conf.HasMember("blend")) { 335 | if (!user_conf["blend"].IsBool()) 336 | throw runtime_error("blend must be true or false"); 337 | mBlend = user_conf["blend"].GetBool(); 338 | } 339 | 340 | if (user_conf.HasMember("image")) { 341 | mImage = parse_image_buffer(user_conf); 342 | } 343 | else { 344 | mImage.name = "image"; 345 | } 346 | 347 | if (user_conf.HasMember("buffers")) { 348 | mBuffers = parse_buffers(user_conf); 349 | mRender_order = parse_render_order(user_conf, mBuffers); 350 | delete_unused_buffers(mBuffers, mRender_order); 351 | } 352 | 353 | if (user_conf.HasMember("uniforms")) { 354 | mUniforms = parse_uniforms(user_conf); 355 | } 356 | } 357 | 358 | void ShaderConfig::parse_simple_config(const filesys::path & shader_folder) { 359 | // ignore mBuffers, mRender_order, and mImage options that have been parsed from json 360 | if (mBuffers.size() != 0) { 361 | cout << "Warning: ignoring \"buffers\" because shader_mode is set to " << EASY_SHADER_MODE_OPTION << endl; 362 | } 363 | mBuffers.clear(); 364 | mRender_order.clear(); 365 | mImage = Buffer{}; 366 | 367 | // parse file list to decide if frag files should have custom or default geometry shader 368 | vector frag_filenames; 369 | for (auto & p : filesys::directory_iterator(shader_folder)) { 370 | if (!filesys::is_regular_file(p)) 371 | continue; 372 | 373 | const filesys::path &path = p.path(); 374 | if (path.stem().string() == "image") 375 | continue; 376 | if (path.extension().string() == ".frag") 377 | frag_filenames.push_back(path.stem().string()); 378 | } 379 | 380 | // simple mode renders in alphabetical order 381 | auto compare_lowercase = [](string l, string r) { 382 | for (char& c : l) c = tolower(c); 383 | for (char& c : r) c = tolower(c); 384 | return l < r; 385 | }; 386 | sort(frag_filenames.begin(), frag_filenames.end(), compare_lowercase); 387 | 388 | mBlend = false; 389 | 390 | mImage.name = "image"; 391 | for (auto& buffer_name : frag_filenames) { 392 | mRender_order.push_back(mBuffers.size()); 393 | Buffer b; 394 | b.name = buffer_name; 395 | mBuffers.push_back(b); 396 | } 397 | } 398 | 399 | ShaderConfig::ShaderConfig(const filesys::path& shader_folder, const filesys::path& conf_file_path) { 400 | if (filesys::exists(conf_file_path)) { 401 | ifstream file(conf_file_path.string()); 402 | stringstream str; 403 | str << file.rdbuf(); 404 | parse_config_from_string(str.str()); 405 | } 406 | else { 407 | mSimpleMode = true; 408 | } 409 | 410 | if (mSimpleMode) { 411 | cout << "shader_mode set to simple" << endl; 412 | parse_simple_config(shader_folder); 413 | } 414 | 415 | if (filesys::exists(filesys::path(shader_folder / (mImage.name + ".geom")))) { 416 | mImage.uses_default_geometry_shader = false; 417 | } 418 | else if (mImage.geom_iters > 1) { 419 | cout << "Warning: Buffer " << mImage.name << " is using default geometry shader and has geom_iters > 1 set. Performance could suffer." << endl; 420 | } 421 | for (Buffer& b : mBuffers) { 422 | if (filesys::exists(filesys::path(shader_folder / (b.name + ".geom")))) { 423 | b.uses_default_geometry_shader = false; 424 | } 425 | else if (b.geom_iters > 1) { 426 | cout << "Warning: Buffer " << b.name << " is using default geometry shader and has geom_iters > 1 set. Performance could suffer." << endl; 427 | } 428 | } 429 | } 430 | 431 | ShaderConfig::ShaderConfig(const string& json_str) { 432 | parse_config_from_string(json_str); 433 | } 434 | -------------------------------------------------------------------------------- /src/ShaderConfig.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "filesystem.h" 7 | 8 | struct Buffer { 9 | std::string name; 10 | int width = 0; 11 | int height = 0; 12 | bool is_window_size = true; 13 | bool uses_default_geometry_shader = true; 14 | int geom_iters = 1; 15 | std::array clear_color; 16 | // Enables building w/ g++-5 17 | Buffer() { clear_color = {0}; } 18 | }; 19 | 20 | struct Uniform { 21 | std::string name; 22 | std::vector values; 23 | }; 24 | 25 | struct AudioOptions { 26 | bool fft_sync = true; 27 | bool xcorr_sync = true; 28 | float fft_smooth = 1.f; 29 | float wave_smooth = .8f; 30 | }; 31 | 32 | class ShaderConfig { 33 | public: 34 | ShaderConfig(const filesys::path& shader_folder, const filesys::path& conf_file_path); 35 | ShaderConfig(const std::string &json_str); // used in testing 36 | 37 | struct { 38 | int width = 400; 39 | int height = 300; 40 | } mInitWinSize; 41 | bool mSimpleMode; 42 | bool mBlend = false; 43 | bool mAudio_enabled = true; 44 | Buffer mImage; 45 | std::vector mBuffers; 46 | std::vector mRender_order; // render_order[n] is an index into buffers 47 | std::vector mUniforms; 48 | AudioOptions mAudio_ops; 49 | 50 | #ifdef TEST 51 | ShaderConfig() {}; // For generating mock instances 52 | #endif 53 | 54 | private: 55 | void parse_config_from_string(const std::string&); 56 | void parse_simple_config(const filesys::path& shader_folder); 57 | }; 58 | -------------------------------------------------------------------------------- /src/ShaderPrograms.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using std::cout; 3 | using std::endl; 4 | #include 5 | using std::vector; 6 | #include 7 | #include 8 | using std::string; 9 | using std::to_string; 10 | #include 11 | using std::stringstream; 12 | #include 13 | using std::runtime_error; 14 | 15 | #include "ShaderPrograms.h" 16 | 17 | ShaderPrograms::ShaderPrograms(const ShaderConfig& config, 18 | const Renderer& renderer, 19 | const Window& window, 20 | const filesys::path& shader_folder) { 21 | #define lambda [&](const int p, const Buffer& b) 22 | builtin_uniforms = { 23 | {"vec2","iMouse", lambda{ glUniform2f(get_uniform_loc(p, 0), window.mouse.x, window.mouse.y); }}, 24 | {"bool","iMouseDown", lambda{ glUniform1i(get_uniform_loc(p, 1), window.mouse.down); } }, 25 | {"vec2","iMouseDownPos", lambda{ glUniform2f(get_uniform_loc(p, 2), window.mouse.last_down_x, window.mouse.last_down_y); }}, 26 | {"vec2","iRes", lambda{ glUniform2f(get_uniform_loc(p, 3), window.width, window.height); }}, 27 | {"float","iTime", lambda{ glUniform1f(get_uniform_loc(p, 4), renderer.elapsed_time); }}, 28 | {"int","iFrame", lambda{ glUniform1i(get_uniform_loc(p, 5), renderer.frame_counter); }}, 29 | {"float","iNumGeomIters", lambda{ glUniform1f(get_uniform_loc(p, 6), float(b.geom_iters)); }}, 30 | {"sampler1D", "iSoundR", lambda{ glUniform1i(get_uniform_loc(p, 7), 0); }}, // texture_unit 0 31 | {"sampler1D", "iSoundL", lambda{ glUniform1i(get_uniform_loc(p, 8), 1); }}, // texture_unit 1 32 | {"sampler1D", "iFreqR", lambda{ glUniform1i(get_uniform_loc(p, 9), 2); }}, // texture_unit 2 33 | {"sampler1D", "iFreqL", lambda{ glUniform1i(get_uniform_loc(p, 10), 3); }}, // texture_unit 3 34 | {"vec2", "iBuffRes", lambda{ glUniform2f(get_uniform_loc(p, 11), float(b.width), float(b.height)); }} 35 | }; 36 | #undef lambda 37 | 38 | stringstream uniform_header; 39 | for (const uniform_info& uniform : builtin_uniforms) { 40 | uniform_header << "uniform " << uniform.type << " " << uniform.name << ";\n"; 41 | } 42 | uniform_header << "#define iResolution iRes\n"; 43 | 44 | // Put samplers for user buffers in header 45 | for (const Buffer& b : config.mBuffers) { 46 | uniform_header << "uniform sampler2D i" << b.name << ";\n"; 47 | } 48 | 49 | // Put user's uniforms in header 50 | for (const Uniform& uniform : config.mUniforms) { 51 | string type; 52 | if (uniform.values.size() == 1) // ShaderConfig ensures size is in [1,4] 53 | type = "float"; 54 | else 55 | type = "vec" + to_string(uniform.values.size()); 56 | 57 | uniform_header << "uniform " << type << " " << uniform.name << ";\n"; 58 | } 59 | 60 | // make error message line numbers correspond to line numbers in my text editor 61 | uniform_header << "#line 0\n"; 62 | 63 | for (const Buffer& b : config.mBuffers) 64 | compile_buffer_shaders(shader_folder, b.name, uniform_header.str(), b.uses_default_geometry_shader); 65 | compile_buffer_shaders(shader_folder, config.mImage.name, uniform_header.str(), config.mImage.uses_default_geometry_shader); 66 | 67 | // get uniform locations for each program 68 | for (GLuint p : mPrograms) { 69 | vector uniform_locs; 70 | for (const uniform_info& u : builtin_uniforms) 71 | uniform_locs.push_back(glGetUniformLocation(p, u.name.c_str())); 72 | for (const Buffer& b : config.mBuffers) 73 | uniform_locs.push_back(glGetUniformLocation(p, ("i" + b.name).c_str())); 74 | for (const Uniform& u : config.mUniforms) 75 | uniform_locs.push_back(glGetUniformLocation(p, u.name.c_str())); 76 | mUniformLocs.push_back(std::move(uniform_locs)); 77 | } 78 | } 79 | 80 | ShaderPrograms & ShaderPrograms::operator=(ShaderPrograms && o) { 81 | // Delete my shaders 82 | for (auto p : mPrograms) 83 | glDeleteProgram(p); 84 | 85 | // Move other's shaders 86 | mPrograms = std::move(o.mPrograms); 87 | mUniformLocs = std::move(o.mUniformLocs); 88 | 89 | return *this; 90 | } 91 | 92 | ShaderPrograms::~ShaderPrograms() { 93 | for (auto p : mPrograms) 94 | glDeleteProgram(p); 95 | } 96 | 97 | void ShaderPrograms::use_program(int i) const { 98 | if (i < mPrograms.size()) 99 | glUseProgram(mPrograms[i]); 100 | else 101 | cout << "i = " + to_string(i) + " is not a program index" << endl; 102 | } 103 | 104 | GLint ShaderPrograms::get_uniform_loc(int program_i, int uniform_i) const { 105 | if (program_i >= mPrograms.size()) { 106 | cout << "program_i = " + to_string(program_i) + " is not a program index" << endl; 107 | return 0; 108 | } 109 | if (uniform_i >= mUniformLocs[program_i].size()) { 110 | cout << "uniform_i = " + to_string(uniform_i) + " is not a uniform index" << endl; 111 | return 0; 112 | } 113 | return mUniformLocs[program_i][uniform_i]; 114 | } 115 | 116 | // TODO always report warnings 117 | bool ShaderPrograms::compile_shader(const GLchar* s, GLuint& sn, GLenum stype) { 118 | sn = glCreateShader(stype); 119 | glShaderSource(sn, 1, &s, NULL); 120 | glCompileShader(sn); 121 | GLint isCompiled = 0; 122 | glGetShaderiv(sn, GL_COMPILE_STATUS, &isCompiled); 123 | if (isCompiled == GL_FALSE) { 124 | GLint maxLength = 0; 125 | glGetShaderiv(sn, GL_INFO_LOG_LENGTH, &maxLength); 126 | vector errorLog(maxLength); 127 | glGetShaderInfoLog(sn, maxLength, &maxLength, &errorLog[0]); 128 | for (GLchar c : errorLog) 129 | cout << c; 130 | cout << endl; 131 | glDeleteShader(sn); 132 | return false; 133 | } 134 | return true; 135 | } 136 | 137 | bool ShaderPrograms::link_program(GLuint& pn, GLuint vs, GLuint gs, GLuint fs) { 138 | pn = glCreateProgram(); 139 | glAttachShader(pn, gs); 140 | glAttachShader(pn, fs); 141 | glAttachShader(pn, vs); 142 | glLinkProgram(pn); 143 | GLint isLinked = 0; 144 | glGetProgramiv(pn, GL_LINK_STATUS, &isLinked); 145 | if (isLinked == GL_FALSE) { 146 | GLint maxLength = 0; 147 | glGetProgramiv(pn, GL_INFO_LOG_LENGTH, &maxLength); 148 | vector infoLog(maxLength); 149 | glGetProgramInfoLog(pn, maxLength, &maxLength, &infoLog[0]); 150 | for (GLchar c : infoLog) 151 | cout << c; 152 | cout << endl; 153 | glDeleteShader(vs); 154 | glDeleteShader(fs); 155 | glDeleteShader(gs); 156 | glDeleteProgram(pn); // automatically detaches shaders 157 | return false; 158 | } 159 | // Always detach and delete shaders after a successful link. 160 | glDeleteShader(vs); 161 | glDeleteShader(fs); 162 | glDeleteShader(gs); 163 | glDetachShader(pn, vs); 164 | glDetachShader(pn, fs); 165 | glDetachShader(pn, gs); 166 | return true; 167 | } 168 | 169 | static const std::string default_geom_shader = R"( 170 | layout(points) in; 171 | layout(triangle_strip, max_vertices = 6) out; 172 | out vec2 geom_p; 173 | void main() { 174 | /* 1------3 175 | | \ | 176 | | \ | 177 | | \| 178 | 0------2 */ 179 | const vec2 p0 = vec2(-1., -1.); 180 | const vec2 p1 = vec2(-1., 1.); 181 | gl_Position = vec4(p0, 0., 1.); 182 | geom_p = p0 * .5 + .5; 183 | EmitVertex(); // 0 184 | gl_Position = vec4(p1, 0., 1.); 185 | geom_p = p1 * .5 + .5; 186 | EmitVertex(); // 1 187 | gl_Position = vec4(-p1, 0., 1.); 188 | geom_p = -p1 * .5 + .5; 189 | EmitVertex(); // 2 190 | EndPrimitive(); 191 | 192 | gl_Position = vec4(-p1, 0., 1.); 193 | geom_p = -p1 * .5 + .5; 194 | EmitVertex(); // 2 195 | gl_Position = vec4(p1, 0., 1.); 196 | geom_p = p1 * .5 + .5; 197 | EmitVertex(); // 1 198 | gl_Position = vec4(-p0, 0., 1.); 199 | geom_p = -p0 * .5 + .5; 200 | EmitVertex(); // 3 201 | EndPrimitive(); 202 | } 203 | )"; 204 | 205 | void ShaderPrograms::compile_buffer_shaders(const filesys::path& shader_folder, const string& buff_name, const string& uniform_header, const bool uses_default_geometry_shader) { 206 | cout << "Compiling shaders for buffer: " << buff_name << endl; 207 | 208 | filesys::path filepath; 209 | std::ifstream shader_file; 210 | stringstream geom_str; 211 | stringstream frag_str; 212 | 213 | string version_header = R"( 214 | #version 330 215 | precision highp float; 216 | )"; 217 | 218 | if (uses_default_geometry_shader) { 219 | geom_str << version_header; 220 | geom_str << uniform_header; 221 | geom_str << default_geom_shader; 222 | } 223 | else { 224 | filepath = filesys::path(shader_folder / (buff_name + ".geom")); 225 | if (! filesys::exists(filepath)) 226 | throw runtime_error("\tGeometry shader does not exist."); 227 | if (! filesys::is_regular_file(filepath)) 228 | throw runtime_error("\t" + buff_name + ".geom is not a regular file."); 229 | shader_file = std::ifstream(filepath.string()); 230 | if (! shader_file.is_open()) 231 | throw runtime_error("\tError opening geometry shader."); 232 | geom_str << version_header; 233 | geom_str << uniform_header; 234 | geom_str << string("layout(points) in;\n #define iGeomIter (float(gl_PrimitiveIDIn)) \n"); 235 | geom_str << shader_file.rdbuf(); 236 | if (shader_file.is_open()) 237 | shader_file.close(); 238 | } 239 | 240 | filepath = filesys::path(shader_folder / (buff_name + ".frag")); 241 | if (! filesys::exists(filepath)) 242 | throw runtime_error("\tFragment shader does not exist."); 243 | if (! filesys::is_regular_file(filepath)) 244 | throw runtime_error("\t" + buff_name + ".frag is not a regular file."); 245 | shader_file = std::ifstream(filepath.string()); 246 | if (! shader_file.is_open()) 247 | throw runtime_error("\tError opening fragment shader."); 248 | frag_str << version_header; 249 | frag_str << uniform_header; 250 | frag_str << shader_file.rdbuf(); 251 | if (frag_str.str().find("mainImage", uniform_header.size() + version_header.size()) != std::string::npos) 252 | frag_str << "\nout vec4 asdsfasdFDSDf; void main() {mainImage(asdsfasdFDSDf, gl_FragCoord.xy);}"; 253 | 254 | string vertex_shader = version_header + string("void main(){}"); 255 | GLuint vs, gs, fs; 256 | bool ok = compile_shader(vertex_shader.c_str(), vs, GL_VERTEX_SHADER); 257 | if (!ok) 258 | throw runtime_error("\tInternal error: vertex shader didn't compile."); 259 | cout << "Compiling " + buff_name + ".geom" << endl; 260 | ok = compile_shader(geom_str.str().c_str(), gs, GL_GEOMETRY_SHADER); 261 | if (!ok) 262 | throw runtime_error("Failed to compile geometry shader."); 263 | cout << "Compiling " + buff_name + ".frag" << endl; 264 | ok = compile_shader(frag_str.str().c_str(), fs, GL_FRAGMENT_SHADER); 265 | if (!ok) 266 | throw runtime_error("Failed to compile fragment shader."); 267 | GLuint program; 268 | ok = link_program(program, vs, gs, fs); 269 | if (!ok) 270 | throw runtime_error("Failed to link program."); 271 | 272 | mPrograms.push_back(program); 273 | } 274 | 275 | -------------------------------------------------------------------------------- /src/ShaderPrograms.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include "filesystem.h" 7 | 8 | #include 9 | #include "ShaderConfig.h" 10 | #include "Renderer.h" 11 | #include "Window.h" 12 | 13 | // Programs 14 | // program for buffer n is in mPrograms[n] 15 | // program for image shader is in mPrograms.back() 16 | 17 | class ShaderPrograms { 18 | public: 19 | ShaderPrograms(const ShaderConfig& config, 20 | const Renderer& renderer, 21 | const Window& window, 22 | const filesys::path& shader_folder); 23 | ShaderPrograms& operator=(ShaderPrograms&&); 24 | ~ShaderPrograms(); 25 | 26 | void use_program(int i) const; 27 | //void upload_uniforms(const Buffer& buff, const int buff_index) const; 28 | GLint get_uniform_loc(int program_i, int uniform_i) const; 29 | 30 | struct uniform_info { 31 | std::string type; 32 | std::string name; 33 | std::function update; 34 | }; 35 | std::vector builtin_uniforms; 36 | 37 | private: 38 | ShaderPrograms(ShaderPrograms&) = delete; 39 | ShaderPrograms(ShaderPrograms&&) = delete; 40 | ShaderPrograms& operator=(ShaderPrograms&) = delete; 41 | 42 | bool compile_shader(const GLchar* s, GLuint& sn, GLenum stype); 43 | bool link_program(GLuint& pn, GLuint vs, GLuint gs, GLuint fs); 44 | void compile_buffer_shaders(const filesys::path& shader_folder, const std::string& buff_name, const std::string& uniform_header, const bool uses_default_geometry_shader); 45 | 46 | std::vector mPrograms; 47 | std::vector> mUniformLocs; 48 | }; 49 | -------------------------------------------------------------------------------- /src/Window.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using std::cout; 3 | using std::endl; 4 | #include 5 | using std::runtime_error; 6 | 7 | #include "Window.h" 8 | #include "AudioProcess.h" // VISUALIZER_BUFSIZE 9 | 10 | Window::Window(int _width, int _height) : width(_width), height(_height), size_changed(true), mouse() { 11 | glfwInit(); 12 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 13 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); 14 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 15 | //glfwWindowHint(GLFW_DECORATED, false); 16 | 17 | //glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_FALSE); 18 | window = glfwCreateWindow(width, height, "Music Visualizer", NULL, NULL); 19 | if (window == NULL) throw runtime_error("GLFW window creation failed."); 20 | 21 | glfwMakeContextCurrent(window); 22 | glfwSetWindowUserPointer(window, this); 23 | auto mouse_button_func = [](GLFWwindow * window, int button, int action, int mods) { 24 | static_cast(glfwGetWindowUserPointer(window))->mouse_button_callback(button, action, mods); 25 | }; 26 | auto cursor_pos_func = [](GLFWwindow * window, double xpos, double ypos) { 27 | static_cast(glfwGetWindowUserPointer(window))->cursor_position_callback(xpos, ypos); 28 | }; 29 | auto window_size_func = [](GLFWwindow * window, int width, int height) { 30 | static_cast(glfwGetWindowUserPointer(window))->window_size_callback(width, height); 31 | }; 32 | auto keyboard_func = [](GLFWwindow* window, int key, int scancode, int action, int mods) { 33 | static_cast(glfwGetWindowUserPointer(window))->keyboard_callback(key, scancode, action, mods); 34 | }; 35 | glfwSetKeyCallback(window, keyboard_func); 36 | glfwSetCursorPosCallback(window, cursor_pos_func); 37 | glfwSetMouseButtonCallback(window, mouse_button_func); 38 | glfwSetWindowSizeCallback(window, window_size_func); 39 | 40 | glewExperimental = GL_TRUE; 41 | glewInit(); 42 | const GLubyte* renderer = glGetString(GL_RENDERER); 43 | const GLubyte* version = glGetString(GL_VERSION); 44 | cout << "Renderer: " << renderer << endl; 45 | cout << "OpenGL version supported "<< version << endl; 46 | 47 | glfwSwapInterval(0); 48 | } 49 | 50 | Window::~Window() { 51 | // If we're being destroyed, then the app is shutting down. 52 | glfwDestroyWindow(window); 53 | glfwTerminate(); 54 | } 55 | 56 | void Window::window_size_callback(int _width, int _height) { 57 | width = _width; 58 | height = _height; 59 | size_changed = true; 60 | } 61 | 62 | void Window::cursor_position_callback(double xpos, double ypos) { 63 | mouse.x = float(xpos); 64 | mouse.y = height - float(ypos); 65 | if (mouse.down) { 66 | mouse.last_down_x = mouse.x; 67 | mouse.last_down_y = mouse.y; 68 | } 69 | } 70 | 71 | void Window::mouse_button_callback(int button, int action, int mods) { 72 | if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) 73 | mouse.down = true; 74 | if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_RELEASE) 75 | mouse.down = false; 76 | if (mouse.down) { 77 | mouse.last_down_x = mouse.x; 78 | mouse.last_down_y = mouse.y; 79 | } 80 | } 81 | 82 | void Window::keyboard_callback(int key, int scancode, int action, int mods) { 83 | if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) 84 | glfwSetWindowShouldClose(window, GL_TRUE); 85 | if (key == GLFW_KEY_Q && action == GLFW_PRESS) 86 | glfwSetWindowShouldClose(window, GL_TRUE); 87 | } 88 | 89 | bool Window::is_alive() { 90 | return !glfwWindowShouldClose(window); 91 | } 92 | 93 | void Window::poll_events() { 94 | // size_changed should've been noticed by renderer this frame, so reset 95 | size_changed = false; 96 | glfwPollEvents(); 97 | } 98 | 99 | void Window::swap_buffers() { 100 | glfwSwapBuffers(window); 101 | } -------------------------------------------------------------------------------- /src/Window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class Window { 7 | public: 8 | Window(int width, int height); 9 | ~Window(); 10 | 11 | void poll_events(); 12 | void swap_buffers(); 13 | bool is_alive(); 14 | 15 | int width; 16 | int height; 17 | bool size_changed; 18 | struct { 19 | float x; 20 | float y; 21 | bool down; 22 | float last_down_x; 23 | float last_down_y; 24 | } mouse; 25 | 26 | private: 27 | GLFWwindow* window; 28 | 29 | void window_size_callback(int width, int height); 30 | void cursor_position_callback(double xpos, double ypos); 31 | void mouse_button_callback(int button, int action, int mods); 32 | void keyboard_callback(int key, int scancode, int action, int mods); 33 | }; 34 | -------------------------------------------------------------------------------- /src/filesystem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef WINDOWS 4 | #include 5 | namespace filesys = std::filesystem; 6 | #else 7 | #include 8 | namespace filesys = std::experimental::filesystem; 9 | #endif 10 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using std::cout; 3 | using std::endl; 4 | #include 5 | #include 6 | using std::string; 7 | #include 8 | using ClockT = std::chrono::steady_clock; 9 | #include 10 | using std::ifstream; 11 | #include 12 | using std::runtime_error; 13 | 14 | #include "filesystem.h" 15 | #include "FileWatcher.h" 16 | 17 | #include "Window.h" 18 | #include "ShaderConfig.h" 19 | #include "ShaderPrograms.h" 20 | #include "Renderer.h" 21 | 22 | #include "AudioProcess.h" 23 | #ifdef WINDOWS 24 | #include "AudioStreams/WindowsAudioStream.h" 25 | #include "AudioStreams/ProceduralAudioStream.h" 26 | using AudioStreamT = WindowsAudioStream; 27 | //using AudioStreamT = ProceduralAudioStream; 28 | #else 29 | #include "AudioStreams/LinuxAudioStream.h" 30 | using AudioStreamT = LinuxAudioStream; 31 | #endif 32 | using AudioProcessT = AudioProcess; 33 | 34 | // TODO rename to shader player (like vmware player) ? 35 | // TODO adding builtin uniforms should be as easy as adding an entry to a list 36 | 37 | #if defined(WINDOWS) && defined(DEBUG) 38 | int WinMain() { 39 | #else 40 | int main(int argc, char* argv[]) { 41 | #endif 42 | 43 | // TODO add no system window border/title bar option? 44 | // TODO add stay on top of all other windows option? 45 | // TODO could i use some kind of neural net to maximize temporal consistency but also minimize difference between displayed signal and actual signal? 46 | 47 | // TODO find a better way to get a decent fps 48 | // fps is also used in AudioProcess.h. Search the project for TODOFPS. 49 | static const int fps = 144; 50 | 51 | filesys::path shader_folder("shaders"); 52 | if (argc > 1) { 53 | shader_folder = filesys::path(argv[1]); 54 | } 55 | 56 | // TODO should this be here or in ShaderConfig? 57 | filesys::path shader_config_path = shader_folder / "shader.json"; 58 | 59 | FileWatcher watcher(shader_folder); 60 | 61 | ShaderConfig *shader_config = nullptr; 62 | ShaderPrograms *shader_programs = nullptr; 63 | Renderer* renderer = nullptr; 64 | Window *window = nullptr; 65 | // TODO extract to get_valid_config(&, &, &, &) 66 | while (!(shader_config && shader_programs && window)) { 67 | try { 68 | shader_config = new ShaderConfig(shader_folder, shader_config_path); 69 | window = new Window(shader_config->mInitWinSize.width, shader_config->mInitWinSize.height); 70 | renderer = new Renderer(*shader_config, *window); 71 | shader_programs = new ShaderPrograms(*shader_config, *renderer, *window, shader_folder); 72 | renderer->set_programs(shader_programs); 73 | } 74 | catch (runtime_error &msg) { 75 | cout << msg.what() << endl; 76 | 77 | // something failed so reset state 78 | delete shader_config; 79 | delete shader_programs; 80 | delete window; 81 | delete renderer; 82 | shader_config = nullptr; 83 | shader_programs = nullptr; 84 | window = nullptr; 85 | renderer = nullptr; 86 | 87 | while (!watcher.files_changed()) { 88 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 89 | } 90 | } 91 | } 92 | cout << "Successfully compiled shaders." << endl; 93 | 94 | //AudioStreamT audio_stream(); // Most Vexing Parse 95 | AudioStreamT audio_stream; 96 | AudioProcessT audio_process{audio_stream, shader_config->mAudio_ops}; 97 | std::thread audio_thread = std::thread(&AudioProcess::start, &audio_process); 98 | if (shader_config->mAudio_enabled) 99 | audio_process.start_audio_system(); 100 | 101 | auto update_shader = [&]() { 102 | cout << "Updating shaders." << endl; 103 | try { 104 | ShaderConfig new_shader_config(shader_folder, shader_config_path); 105 | Renderer new_renderer(new_shader_config, *window); 106 | ShaderPrograms new_shader_programs(new_shader_config, new_renderer, *window, shader_folder); 107 | *shader_config = new_shader_config; 108 | *shader_programs = std::move(new_shader_programs); 109 | *renderer = std::move(new_renderer); 110 | renderer->set_programs(shader_programs); 111 | } 112 | catch (runtime_error &msg) { 113 | cout << msg.what() << endl; 114 | cout << "Failed to update shaders." << endl << endl; 115 | return; 116 | } 117 | if (shader_config->mAudio_enabled) { 118 | audio_process.start_audio_system(); 119 | audio_process.set_audio_options(shader_config->mAudio_ops); 120 | } 121 | else { 122 | audio_process.pause_audio_system(); 123 | } 124 | cout << "Successfully updated shaders." << endl << endl; 125 | }; 126 | 127 | auto lastFrameTime = ClockT::now(); 128 | auto frameRateDuration = std::chrono::microseconds(int64_t(1 / 144.0 * 1000000)); 129 | while (window->is_alive()) { 130 | auto now = ClockT::now(); 131 | if ((now - lastFrameTime) > frameRateDuration) { 132 | if (watcher.files_changed()) 133 | update_shader(); 134 | auto now = ClockT::now(); 135 | renderer->update(audio_process.get_audio_data()); 136 | renderer->render(); 137 | window->swap_buffers(); 138 | window->poll_events(); 139 | lastFrameTime = now; 140 | } 141 | } 142 | 143 | audio_process.exit_audio_system(); 144 | audio_thread.join(); 145 | 146 | return 0; 147 | } 148 | -------------------------------------------------------------------------------- /src/noise.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static float fract(float x) { 4 | return x - std::floor(x); 5 | } 6 | 7 | static float mix(float x, float y, float m) { 8 | return (1.f - m) * x + (m) * y; 9 | } 10 | 11 | static float hash11(float p) { 12 | return fract(std::sin(p) * 43758.5453123f); 13 | } 14 | 15 | static float noise(float x) { 16 | float u = std::floor(x); 17 | float v = fract(x); 18 | v = v * v * (3.f - 2.f * v); 19 | return mix(hash11(u), hash11(u + 1.f), v); 20 | } 21 | 22 | float fbm(float p) { 23 | float f = .5f * noise(p); 24 | p *= 2.01f; 25 | f += .25f * noise(p); 26 | p *= 2.02f; 27 | f += .25f * noise(p); 28 | p *= 2.03f; 29 | f += .25f * noise(p); 30 | p *= 2.04f; 31 | f += .25f * noise(p); 32 | return f * .7f; 33 | } 34 | -------------------------------------------------------------------------------- /src/noise.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | float fbm(float); 4 | -------------------------------------------------------------------------------- /src/shaders/A.frag: -------------------------------------------------------------------------------- 1 | const float TAU = 6.283185307179586; 2 | const float TAUR = 2.5066282746310002; 3 | const float SQRT2 = 1.4142135623730951; 4 | 5 | in vec3 uvl; 6 | in float width; 7 | in float intensity; 8 | in float min_intensity; 9 | 10 | // A standard gaussian function, used for weighting samples 11 | float gaussian(float x, float sigma) { 12 | return exp(-(x * x) / (2.0 * sigma * sigma)) / (TAUR * sigma); 13 | } 14 | 15 | // This approximates the error function, needed for the gaussian integral 16 | float erf(float x) { 17 | float s = sign(x), a = abs(x); 18 | x = 1.0 + (0.278393 + (0.230389 + (0.000972 + 0.078108 * a) * a) * a) * a; 19 | x *= x; 20 | return s - s / (x * x); 21 | } 22 | 23 | out vec4 C; 24 | void main() { 25 | float len = uvl.z; 26 | vec2 xy = uvl.xy; 27 | float alpha; 28 | 29 | float sigma = width/(2. + 2000.*width/50.); 30 | alpha = erf(xy.x/SQRT2/sigma) - erf((xy.x-len)/SQRT2/sigma); 31 | alpha *= exp(-xy.y*xy.y/(2.0*sigma*sigma))/2.0/len*width; 32 | 33 | alpha = pow(alpha,1.0-min_intensity)*(0.01+min(0.99,intensity*3.0)); 34 | C = vec4(vec3(1.), alpha); 35 | } 36 | -------------------------------------------------------------------------------- /src/shaders/A.geom: -------------------------------------------------------------------------------- 1 | layout(triangle_strip, max_vertices=24) out; 2 | 3 | out float width; 4 | out float intensity; 5 | out float min_intensity; 6 | out vec3 uvl; 7 | 8 | void quad(vec2 P0, vec2 P1, float thickness) { 9 | /* 10 | 1------3 11 | | \ | 12 | | \ | 13 | | \| 14 | 0------2 15 | */ 16 | vec2 dir = P1-P0; 17 | float dl = length(dir); 18 | // If the segment is too short, just draw a square 19 | dir = normalize(dir); 20 | vec2 norm = vec2(-dir.y, dir.x); 21 | 22 | uvl = vec3(dl+thickness, -thickness, dl); 23 | gl_Position = vec4(P0+(-dir-norm)*thickness, 0., 1.); 24 | EmitVertex(); // 0 25 | 26 | uvl = vec3(dl+thickness, thickness, dl); 27 | gl_Position = vec4(P0+(-dir+norm)*thickness, 0., 1.); 28 | EmitVertex(); // 1 29 | 30 | uvl = vec3(-thickness, -thickness, dl); 31 | gl_Position = vec4(P1+(dir-norm)*thickness, 0., 1.); 32 | EmitVertex(); // 2 33 | EndPrimitive(); 34 | 35 | uvl = vec3(-thickness, -thickness, dl); 36 | gl_Position = vec4(P1+(dir-norm)*thickness, 0., 1.); 37 | EmitVertex(); // 2 38 | 39 | uvl = vec3(dl+thickness, thickness, dl); 40 | gl_Position = vec4(P0+(-dir+norm)*thickness, 0., 1.); 41 | EmitVertex(); // 1 42 | 43 | uvl = vec3(-thickness, thickness, dl); 44 | gl_Position = vec4(P1+(dir+norm)*thickness, 0., 1.); 45 | EmitVertex(); // 3 46 | EndPrimitive(); 47 | } 48 | 49 | float sample_freq(float x) { 50 | return max(-1., .1*log(8.*texture(iFreqR, .5*pow(mix(x, 1., .13), 3.)).r) - .4); 51 | //return max(-1., sqrt(1.2*texture(iFreqR, pow(mix(x, 1., .18), 4.)).r) - 1.); 52 | } 53 | 54 | float smooth_freq(float x) { 55 | float sum = 0.; 56 | const float width = 6.; 57 | for (float i = -width; i <= width; i+=1.) { 58 | sum += sample_freq(x + i / iNumGeomIters); 59 | } 60 | return sum / (2. * width + 1.); 61 | } 62 | 63 | float smooth_wave(float x) { 64 | float sum = 0.; 65 | const float width = 7.; 66 | for (float i = - width; i <= width; i += 1.) 67 | sum += .55 * texture(iSoundR, x + i / iNumGeomIters).r + .5; 68 | return sum / (2. * width + 1.); 69 | } 70 | 71 | void main() { 72 | float t0 = (iGeomIter+0)/iNumGeomIters; 73 | float t1 = (iGeomIter+1)/iNumGeomIters; 74 | 75 | width = .005; 76 | intensity = .15; 77 | min_intensity = .0; 78 | 79 | float f0 = smooth_freq(t0); 80 | float f1 = smooth_freq(t1); 81 | float sr0 = smooth_wave(t0); 82 | float sr1 = smooth_wave(t1); 83 | 84 | t0 = t0 * 2. - 1.; 85 | t1 = t1 * 2. - 1.; 86 | 87 | vec2 P0 = vec2(t0, f0); 88 | vec2 P1 = vec2(t1, f1); 89 | quad(P0, P1, width); 90 | 91 | P0 = vec2(t0, sr0); 92 | P1 = vec2(t1, sr1);; 93 | quad(P0, P1, width); 94 | } 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/shaders/B.frag: -------------------------------------------------------------------------------- 1 | //vec4 fg = vec4(1., 2., 3., 1.); 2 | vec4 fg = vec4(1); 3 | vec4 bg = vec4(0); 4 | 5 | const float lag = 10/144.; 6 | 7 | in vec2 geom_p; 8 | out vec4 c; 9 | 10 | void main() { 11 | if (iFrame == 0) { 12 | c = fg; 13 | return; 14 | } 15 | 16 | float al = texture(iA, geom_p).r; 17 | 18 | if (geom_p.y > .5) 19 | al *= 1.3; 20 | 21 | vec4 new_color = 4.*mix(bg, fg, al); 22 | vec4 old_color = texture(iB, geom_p); 23 | 24 | c = mix(old_color, new_color, lag); 25 | c.a = 1.; // Replaces color 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/shaders/blocky_fft/A.frag: -------------------------------------------------------------------------------- 1 | const vec4 fg = vec4(1); 2 | const vec4 bg = vec4(.1); 3 | 4 | in float is_background; 5 | out vec4 c; 6 | void main() { 7 | if (is_background > .5) 8 | c = bg; 9 | else 10 | c = fg; 11 | } 12 | -------------------------------------------------------------------------------- /src/shaders/blocky_fft/A.geom: -------------------------------------------------------------------------------- 1 | layout(triangle_strip, max_vertices=24) out; 2 | 3 | out vec2 P; 4 | out float is_background; 5 | 6 | float integ(float x) { 7 | float f = texture(iFreqL, x).r*2.; 8 | f += texture(iFreqL, x+1./2048.).r*.5; 9 | f += texture(iFreqL, x-1./2048.).r*.5; 10 | f += texture(iFreqL, x+2./2048.).r*.5; 11 | f += texture(iFreqL, x-2./2048.).r*.5; 12 | f += texture(iFreqL, x+3./2048.).r*.5; 13 | f += texture(iFreqL, x-3./2048.).r*.5; 14 | return f/5.; 15 | } 16 | 17 | void quad(vec2 P0, vec2 P1, float thickness) { 18 | /* 19 | 1------3 20 | | \ | 21 | | \ | 22 | | \| 23 | 0------2 24 | */ 25 | vec2 dir = P1-P0; 26 | float dl = length(dir); 27 | // If the segment is too short, just draw a square 28 | dir = normalize(dir); 29 | vec2 norm = vec2(-dir.y, dir.x); 30 | 31 | gl_Position = vec4(P0+(-dir-norm)*thickness, 0., 1.); 32 | EmitVertex(); // 0 33 | 34 | gl_Position = vec4(P0+(-dir+norm)*thickness, 0., 1.); 35 | EmitVertex(); // 1 36 | 37 | gl_Position = vec4(P1+(dir-norm)*thickness, 0., 1.); 38 | EmitVertex(); // 2 39 | EndPrimitive(); 40 | 41 | gl_Position = vec4(P1+(dir-norm)*thickness, 0., 1.); 42 | EmitVertex(); // 2 43 | 44 | gl_Position = vec4(P0+(-dir+norm)*thickness, 0., 1.); 45 | EmitVertex(); // 1 46 | 47 | gl_Position = vec4(P1+(dir+norm)*thickness, 0., 1.); 48 | EmitVertex(); // 3 49 | EndPrimitive(); 50 | } 51 | 52 | void main() { 53 | float n = iGeomIter/iNumGeomIters; 54 | 55 | float width = 0.014; 56 | 57 | n += width/4.; 58 | 59 | float stretch = 100.; 60 | float f = integ(pow(stretch, n-1.)-(1.-n)/stretch); 61 | 62 | // LOG 63 | vec2 p = vec2(n*2.-1., log(40.*f)/4.+.4); 64 | 65 | // SQRT 66 | // vec2 p = vec2(n*2.-1., .5*sqrt(f)); 67 | 68 | float height = .5; 69 | 70 | is_background = 1.; 71 | if (iGeomIter < 1.) 72 | quad(vec2(-1, -1), vec2(1,1), 2.); 73 | 74 | is_background = 0.; 75 | quad(vec2(p.x + width / 2., -1.), vec2(p.x + width / 2., p.y), width / 2.); 76 | } 77 | -------------------------------------------------------------------------------- /src/shaders/blocky_fft/image.frag: -------------------------------------------------------------------------------- 1 | in vec2 geom_p; 2 | out vec4 c; 3 | void main() { 4 | c = texture(iA, geom_p); 5 | } 6 | -------------------------------------------------------------------------------- /src/shaders/blocky_fft/shader.json: -------------------------------------------------------------------------------- 1 | { 2 | "shader_mode":"advanced", 3 | "initial_window_size":[600,200], 4 | // Please use only valid glsl names for buffers and uniforms 5 | // Please do not specify options more than once 6 | 7 | // Every shader has an image buffer, just like shadertoy 8 | // Size is always the same as the window size 9 | "image": { 10 | "geom_iters":1, 11 | "clear_color":[0,0,0] 12 | }, 13 | 14 | // In addition to drawing an image buffer you can define other buffers to draw here 15 | // Available as iBuffName in all shaders. 16 | "buffers": { 17 | "A": { 18 | // Buffer will have the same size as the window size 19 | "size": "window_size", 20 | // How many times the geometry shader will execute 21 | "geom_iters":50, 22 | } 23 | }, 24 | "audio_enabled":true 25 | } 26 | -------------------------------------------------------------------------------- /src/shaders/blocky_osc/A.frag: -------------------------------------------------------------------------------- 1 | const vec4 fg = vec4(0,1,1,1); 2 | const vec4 bg = vec4(.1); 3 | 4 | in float is_background; 5 | out vec4 c; 6 | void main() { 7 | if (is_background > .5) 8 | c = bg; 9 | else 10 | c = fg; 11 | } 12 | -------------------------------------------------------------------------------- /src/shaders/blocky_osc/A.geom: -------------------------------------------------------------------------------- 1 | layout(triangle_strip, max_vertices=24) out; 2 | 3 | out vec2 P; 4 | out float is_background; 5 | 6 | void quad(vec2 p, float thickness, float height) { 7 | /* 8 | 1------3 9 | | \ | 10 | | \ | 11 | | \| 12 | 0------2 13 | */ 14 | height /= 2.; // half height 15 | if (height < .1) height = .1; 16 | 17 | gl_Position = vec4(p.x, p.y - height, 0., 1.); 18 | EmitVertex(); // 0 19 | 20 | gl_Position = vec4(p.x, p.y + height, 0., 1.); 21 | EmitVertex(); // 1 22 | 23 | gl_Position = vec4(p.x + thickness, p.y - height, 0., 1.); 24 | EmitVertex(); // 2 25 | EndPrimitive(); 26 | 27 | gl_Position = vec4(p.x + thickness, p.y - height, 0., 1.); 28 | EmitVertex(); // 2 29 | 30 | gl_Position = vec4(p.x, p.y + height, 0., 1.); 31 | EmitVertex(); // 1 32 | 33 | gl_Position = vec4(p.x + thickness, p.y + height, 0., 1.); 34 | EmitVertex(); // 3 35 | EndPrimitive(); 36 | } 37 | 38 | void main() { 39 | float n = iGeomIter/iNumGeomIters; 40 | 41 | float width = 0.014; 42 | 43 | n+=width/4.; 44 | 45 | float s0 = texture(iSoundL, n).r; 46 | float s1 = texture(iSoundL, n+1./iNumGeomIters).r; 47 | 48 | vec2 p = vec2(n*2.-1., s0); 49 | 50 | is_background = 1.; 51 | if (iGeomIter < 1.) 52 | quad(vec2(-1, 0), 2., 2.); 53 | 54 | is_background = 0.; 55 | float height = abs(s0-s1); 56 | quad(p, width, height); 57 | } 58 | -------------------------------------------------------------------------------- /src/shaders/blocky_osc/image.frag: -------------------------------------------------------------------------------- 1 | in vec2 geom_p; 2 | out vec4 c; 3 | void main() { 4 | c = texture(iA, geom_p); 5 | } 6 | -------------------------------------------------------------------------------- /src/shaders/blocky_osc/shader.json: -------------------------------------------------------------------------------- 1 | { 2 | "shader_mode":"advanced", 3 | "initial_window_size":[600,200], 4 | // Please use only valid glsl names for buffers and uniforms 5 | // Please do not specify options more than once 6 | 7 | // Every shader has an image buffer, just like shadertoy 8 | // Size is always the same as the window size 9 | "image": { 10 | "geom_iters":1 11 | }, 12 | 13 | // In addition to drawing an image buffer you can define other buffers to draw here 14 | // Available as iBuffName in all shaders. 15 | "buffers": { 16 | "A": { 17 | // Buffer will have the same size as the window size 18 | "size": "window_size", 19 | // How many times the geometry shader will execute 20 | "geom_iters":60 21 | } 22 | }, 23 | 24 | "audio_enabled":true, 25 | "audio_options": { 26 | "fft_sync":true, 27 | "xcorr_sync":true, 28 | "wave_smooth":0.7, 29 | "fft_smooth":1.0 30 | }, 31 | 32 | // Do you want glEnable(GL_BLEND) ? 33 | // blend func (srcAlpha, 1-srcAlpha) 34 | // Default is false 35 | "blend":false 36 | } 37 | -------------------------------------------------------------------------------- /src/shaders/dots/a.frag: -------------------------------------------------------------------------------- 1 | in vec2 geom_p; 2 | out vec4 c; 3 | void main() { 4 | float wave = texture(iSoundL, geom_p.x).r*.5+.5; 5 | float dist = abs(geom_p.y - wave); 6 | dist *= sqrt(dist); 7 | c = vec4(clamp(0.004 / dist, 0., 1.)); 8 | } 9 | -------------------------------------------------------------------------------- /src/shaders/dots/image.frag: -------------------------------------------------------------------------------- 1 | vec4 fg = vec4(0,1,1,1); 2 | vec4 bg = vec4(0); 3 | 4 | in vec2 geom_p; 5 | out vec4 c; 6 | 7 | void main() { 8 | const float freq = 30.; 9 | const float bezel = .7; 10 | 11 | // read from texture at grid 12 | vec2 uv = geom_p; 13 | uv.x *= iRes.x / iRes.y; 14 | 15 | vec2 grid = floor(uv * freq) / freq; 16 | uv = fract(uv * freq); 17 | 18 | float separation = step(bezel, 1. - abs(uv.x - .5)); 19 | separation *= step(bezel, 1. - abs(uv.y - .5)); 20 | 21 | grid.x *= iRes.y / iRes.x; 22 | float x = texture(ia, grid).r; 23 | c = mix(bg, fg, x) * separation; 24 | } 25 | -------------------------------------------------------------------------------- /src/shaders/dual_oscilloscope/A.frag: -------------------------------------------------------------------------------- 1 | const float TAU = 6.283185307179586; 2 | const float TAUR = 2.5066282746310002; 3 | const float SQRT2 = 1.4142135623730951; 4 | 5 | in vec3 uvl; 6 | in float width; 7 | in float intensity; 8 | in float min_intensity; 9 | 10 | // A standard gaussian function, used for weighting samples 11 | float gaussian(float x, float sigma) { 12 | return exp(-(x * x) / (2.0 * sigma * sigma)) / (TAUR * sigma); 13 | } 14 | 15 | // This approximates the error function, needed for the gaussian integral 16 | float erf(float x) { 17 | float s = sign(x), a = abs(x); 18 | x = 1.0 + (0.278393 + (0.230389 + (0.000972 + 0.078108 * a) * a) * a) * a; 19 | x *= x; 20 | return s - s / (x * x); 21 | } 22 | 23 | out vec4 C; 24 | void main() { 25 | float len = uvl.z; 26 | vec2 xy = uvl.xy; 27 | float alpha; 28 | 29 | float sigma = width/(2. + 2000.*width/50.); 30 | alpha = erf(xy.x/SQRT2/sigma) - erf((xy.x-len)/SQRT2/sigma); 31 | alpha *= exp(-xy.y*xy.y/(2.0*sigma*sigma))/2.0/len*width; 32 | 33 | alpha = pow(alpha,1.0-min_intensity)*(0.01+min(0.99,intensity*3.0)); 34 | C = vec4(vec3(1.), alpha); 35 | } 36 | -------------------------------------------------------------------------------- /src/shaders/dual_oscilloscope/A.geom: -------------------------------------------------------------------------------- 1 | layout(triangle_strip, max_vertices=12) out; 2 | 3 | out float width; 4 | out float intensity; 5 | out float min_intensity; 6 | out vec3 uvl; 7 | 8 | void quad(vec2 P0, vec2 P1, float thickness) { 9 | /* 10 | 1------3 11 | | \ | 12 | | \ | 13 | | \| 14 | 0------2 15 | */ 16 | vec2 dir = P1-P0; 17 | float dl = length(dir); 18 | // If the segment is too short, just draw a square 19 | dir = normalize(dir); 20 | vec2 norm = vec2(-dir.y, dir.x); 21 | 22 | uvl = vec3(dl+thickness, -thickness, dl); 23 | gl_Position = vec4(P0+(-dir-norm)*thickness, 0., 1.); 24 | EmitVertex(); // 0 25 | 26 | uvl = vec3(dl+thickness, thickness, dl); 27 | gl_Position = vec4(P0+(-dir+norm)*thickness, 0., 1.); 28 | EmitVertex(); // 1 29 | 30 | uvl = vec3(-thickness, -thickness, dl); 31 | gl_Position = vec4(P1+(dir-norm)*thickness, 0., 1.); 32 | EmitVertex(); // 2 33 | EndPrimitive(); 34 | 35 | uvl = vec3(-thickness, -thickness, dl); 36 | gl_Position = vec4(P1+(dir-norm)*thickness, 0., 1.); 37 | EmitVertex(); // 2 38 | 39 | uvl = vec3(dl+thickness, thickness, dl); 40 | gl_Position = vec4(P0+(-dir+norm)*thickness, 0., 1.); 41 | EmitVertex(); // 1 42 | 43 | uvl = vec3(-thickness, thickness, dl); 44 | gl_Position = vec4(P1+(dir+norm)*thickness, 0., 1.); 45 | EmitVertex(); // 3 46 | EndPrimitive(); 47 | } 48 | 49 | void main() { 50 | float t0 = (iGeomIter+0)/iNumGeomIters; 51 | float t1 = (iGeomIter+1)/iNumGeomIters; 52 | 53 | width = .006; 54 | intensity = .15; 55 | min_intensity = 0.0; 56 | 57 | float sr0 = texture(iSoundR, t0).r * .5 - .5; 58 | float sr1 = texture(iSoundR, t1).r * .5 - .5; 59 | float sl0 = texture(iSoundL, t0).r * .5 + .5; 60 | float sl1 = texture(iSoundL, t1).r * .5 + .5; 61 | t0 = t0 * 2. - 1.; 62 | t1 = t1 * 2. - 1.; 63 | vec2 P0 = vec2(t0, sr0); 64 | vec2 P1 = vec2(t1, sr1); 65 | quad(P0, P1, width); 66 | vec2 P2 = vec2(t0, sl0); 67 | vec2 P3 = vec2(t1, sl1); 68 | quad(P2, P3, width); 69 | } 70 | -------------------------------------------------------------------------------- /src/shaders/dual_oscilloscope/B.frag: -------------------------------------------------------------------------------- 1 | vec4 fg = vec4(1); 2 | vec4 bg = vec4(0); 3 | 4 | //vec4 bg = .9*vec4(52./256., 9./256., 38./256., 1.); 5 | //vec4 fg = 1.1*vec4(1.,195./256.,31./256.,1.); 6 | 7 | // vec4 bg = vec4(vec3(10, 23, 42)/77., 1.); 8 | // vec4 fg = vec4(1.5); 9 | 10 | in vec2 geom_p; 11 | out vec4 c; 12 | 13 | void main() { 14 | if (iFrame == 0) { 15 | c = fg; 16 | return; 17 | } 18 | 19 | float al = texture(iA, geom_p).r; 20 | al *= 4.; 21 | vec4 new_color = mix(bg, fg, al); 22 | vec4 old_color = texture(iB, geom_p); 23 | 24 | c = mix(old_color, new_color, .3333); 25 | c.a = 1.; // Replaces color 26 | } 27 | -------------------------------------------------------------------------------- /src/shaders/dual_oscilloscope/image.frag: -------------------------------------------------------------------------------- 1 | in vec2 geom_p; 2 | out vec4 c; 3 | void main() { 4 | c=texture(iB, geom_p); 5 | } 6 | -------------------------------------------------------------------------------- /src/shaders/dual_oscilloscope/shader.json: -------------------------------------------------------------------------------- 1 | { 2 | "shader_mode": "advanced", 3 | // TODO implement these options 4 | // interpolation -> default of gl_nearest 5 | // edge_behaviour -> default of gl_repeat 6 | 7 | "initial_window_size":[500,400], 8 | // Please use only valid glsl names for buffers and uniforms 9 | // Please do not specify options more than once 10 | 11 | // Every shader has an image buffer, just like shadertoy 12 | // Size is always the same as the window size 13 | "image": { 14 | "geom_iters":1, 15 | "clear_color":[0,0,0] 16 | }, 17 | 18 | // In addition to drawing an image buffer you can define other buffers to draw here 19 | // Available as iBuffName in all shaders. 20 | "buffers": { 21 | "A": { 22 | // Buffer will have the same size as the window size 23 | "size": "window_size", 24 | // How many times the geometry shader will execute 25 | "geom_iters":2048, 26 | // RGB values from the interval [0, 1] 27 | // Default is [0,0,0] 28 | "clear_color":[0, 0, 0] 29 | }, 30 | "B": { 31 | "size": "window_size", 32 | "geom_iters":1, 33 | "clear_color":[0, 0, 0] 34 | } 35 | }, 36 | 37 | // Render A then B 38 | // Every buffer has access to the most recent output of all other buffers 39 | // No buffer has access to the output of the image buffer 40 | // Default is the order the buffers are declared in buffers 41 | "render_order":["A", "B"], 42 | 43 | // Do you want glEnable(GL_BLEND) ? 44 | // blend func (srcAlpha, 1-srcAlpha) 45 | // Default is false 46 | "blend":true, 47 | 48 | // Defaults are true, true, .75, .75 49 | "audio_enabled":true, 50 | "audio_options": { 51 | "fft_sync":true, 52 | "xcorr_sync":true, 53 | "fft_smooth":1.0, 54 | "wave_smooth":0.7 55 | }, 56 | 57 | // Useful for setting colors from external scripts. 58 | // Available as UniformName in all buffers. 59 | "uniforms": { 60 | "my_uni": [10, 123, 42], 61 | "your_uni":[25, 20, 1], 62 | "his_uni":[1.0, 2.0, 3.0, 4] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/shaders/dual_waves/image.frag: -------------------------------------------------------------------------------- 1 | vec4 fg = vec4(0,1,1,1); 2 | vec4 bg = vec4(0.06); 3 | 4 | in vec2 geom_p; 5 | out vec4 C; 6 | void main() { 7 | vec2 U = geom_p; 8 | 9 | U.x/=2.; 10 | U.y = 2.*U.y-1.; 11 | 12 | float sl = texture(iSoundL, U.x).r; 13 | float sr = texture(iSoundR, U.x).r; 14 | sl = sl/1.75 + .5; 15 | sr = sr/1.75 - .5; 16 | 17 | C = bg; 18 | if (abs(U.y - sl) < .005) 19 | C = fg; 20 | if (abs(U.y - sr) < .005) 21 | C = fg; 22 | C.a = 1.; 23 | } 24 | -------------------------------------------------------------------------------- /src/shaders/fft/A.frag: -------------------------------------------------------------------------------- 1 | const float TAU = 6.283185307179586; 2 | const float TAUR = 2.5066282746310002; 3 | const float SQRT2 = 1.4142135623730951; 4 | 5 | in vec3 uvl; 6 | in float width; 7 | in float intensity; 8 | in float min_intensity; 9 | 10 | // A standard gaussian function, used for weighting samples 11 | float gaussian(float x, float sigma) { 12 | return exp(-(x * x) / (2.0 * sigma * sigma)) / (TAUR * sigma); 13 | } 14 | 15 | // This approximates the error function, needed for the gaussian integral 16 | float erf(float x) { 17 | float s = sign(x), a = abs(x); 18 | x = 1.0 + (0.278393 + (0.230389 + (0.000972 + 0.078108 * a) * a) * a) * a; 19 | x *= x; 20 | return s - s / (x * x); 21 | } 22 | 23 | out vec4 C; 24 | void main() { 25 | float len = uvl.z; 26 | vec2 xy = uvl.xy; 27 | float alpha; 28 | 29 | float sigma = width/(2. + 2000.*width/50.); 30 | alpha = erf(xy.x/SQRT2/sigma) - erf((xy.x-len)/SQRT2/sigma); 31 | alpha *= exp(-xy.y*xy.y/(2.0*sigma*sigma))/2.0/len*width; 32 | 33 | alpha = pow(alpha,1.0-min_intensity)*(0.01+min(0.99,intensity*3.0)); 34 | C = vec4(vec3(1.), alpha); 35 | } 36 | -------------------------------------------------------------------------------- /src/shaders/fft/A.geom: -------------------------------------------------------------------------------- 1 | layout(triangle_strip, max_vertices=24) out; 2 | 3 | out float width; 4 | out float intensity; 5 | out float min_intensity; 6 | out vec3 uvl; 7 | 8 | void quad(vec2 P0, vec2 P1, float thickness) { 9 | /* 10 | 1------3 11 | | \ | 12 | | \ | 13 | | \| 14 | 0------2 15 | */ 16 | vec2 dir = P1-P0; 17 | float dl = length(dir); 18 | // If the segment is too short, just draw a square 19 | if (dl < .001) 20 | dir = vec2(1., 0.); 21 | else 22 | dir = normalize(dir); 23 | vec2 norm = vec2(-dir.y, dir.x); 24 | 25 | uvl = vec3(dl+thickness, -thickness, dl); 26 | gl_Position = vec4(P0+(-dir-norm)*thickness, 0., 1.); 27 | EmitVertex(); // 0 28 | 29 | uvl = vec3(dl+thickness, thickness, dl); 30 | gl_Position = vec4(P0+(-dir+norm)*thickness, 0., 1.); 31 | EmitVertex(); // 1 32 | 33 | uvl = vec3(-thickness, -thickness, dl); 34 | gl_Position = vec4(P1+(dir-norm)*thickness, 0., 1.); 35 | EmitVertex(); // 2 36 | EndPrimitive(); 37 | 38 | uvl = vec3(-thickness, -thickness, dl); 39 | gl_Position = vec4(P1+(dir-norm)*thickness, 0., 1.); 40 | EmitVertex(); // 2 41 | 42 | uvl = vec3(dl+thickness, thickness, dl); 43 | gl_Position = vec4(P0+(-dir+norm)*thickness, 0., 1.); 44 | EmitVertex(); // 1 45 | 46 | uvl = vec3(-thickness, thickness, dl); 47 | gl_Position = vec4(P1+(dir+norm)*thickness, 0., 1.); 48 | EmitVertex(); // 3 49 | EndPrimitive(); 50 | } 51 | 52 | float integ(float x) { 53 | float f = texture(iFreqL, x).r*2.; 54 | f += texture(iFreqL, x+1./2048.).r*.5; 55 | f += texture(iFreqL, x-1./2048.).r*.5; 56 | f += texture(iFreqL, x+2./2048.).r*.5; 57 | f += texture(iFreqL, x-2./2048.).r*.5; 58 | f += texture(iFreqL, x+3./2048.).r*.5; 59 | f += texture(iFreqL, x-3./2048.).r*.5; 60 | f += texture(iFreqL, x+4./2048.).r*.5; 61 | f += texture(iFreqL, x-4./2048.).r*.5; 62 | return f/6.; 63 | } 64 | 65 | void main() { 66 | float t0 = (iGeomIter+0)/iNumGeomIters; 67 | float t1 = (iGeomIter+1)/iNumGeomIters; 68 | 69 | width = .015; 70 | intensity = .1; 71 | min_intensity = .01; 72 | 73 | const float stretch = 100.; 74 | float ft0 = integ(pow(stretch, t0-1.)-(1.-t0)/stretch); 75 | float ft1 = integ(pow(stretch, t1-1.)-(1.-t1)/stretch); 76 | 77 | vec2 P0; 78 | vec2 P1; 79 | 80 | // LOG 81 | P0 = vec2(t0*2.-1., log(ft0+0.002)/4.8+.12); 82 | P1 = vec2(t1*2.-1., log(ft1+0.002)/4.8+.12); 83 | 84 | // SQRT 85 | // P0 = vec2(t0*2.-1., .56*sqrt(ft0)-.9); 86 | // P1 = vec2(t1*2.-1., .56*sqrt(ft1)-.9); 87 | 88 | // NORMAL FFT 89 | // P0 = vec2(t0*2.-1., .5*ft0-1.); 90 | // P1 = vec2(t1*2.-1., .5*ft1-1.); 91 | 92 | quad(P0, P1, width); 93 | } 94 | -------------------------------------------------------------------------------- /src/shaders/fft/B.frag: -------------------------------------------------------------------------------- 1 | in vec2 geom_p; 2 | out vec4 c; 3 | 4 | vec4 bg = vec4(52./256., 9./256., 38./256., 1.); 5 | vec4 fg = vec4(1.,195./256.,31./256.,1.); 6 | 7 | //vec4 fg = vec4(248./256.,73./256.,52./256.,1.); 8 | //vec4 bg = vec4(77./256., 94./256., 95./256., 1.); 9 | 10 | //vec4 fg = vec4(221./256.,249./256.,30./256.,1.); 11 | //vec4 bg = vec4(246./256., 69./256., 114./256., 1.); 12 | 13 | //vec4 fg = vec4(1); 14 | //vec4 bg = vec4(0); 15 | 16 | //vec4 fg = vec4(1,vec3(0)); 17 | //vec4 bg = vec4(0); 18 | 19 | void main() { 20 | if (iFrame == 0) { 21 | c = fg; 22 | return; 23 | } 24 | 25 | float al = texture(iA, geom_p).r; 26 | al *= 5.; 27 | vec4 new_color = mix(bg, fg, al); 28 | vec4 old_color = texture(iB, geom_p); 29 | 30 | c = mix(new_color, old_color, .75); 31 | c.a = 1.; // Replaces color 32 | } 33 | -------------------------------------------------------------------------------- /src/shaders/fft/image.frag: -------------------------------------------------------------------------------- 1 | in vec2 geom_p; 2 | out vec4 c; 3 | void main() { 4 | c=texture(iB, geom_p); 5 | } 6 | -------------------------------------------------------------------------------- /src/shaders/fft/shader.json: -------------------------------------------------------------------------------- 1 | { 2 | "shader_mode":"advanced", 3 | // Please use only valid glsl names for buffers and uniforms 4 | // Please do not specify options more than once 5 | 6 | // Every shader has an image buffer, just like shadertoy 7 | // Size is always the same as the window size 8 | "image": { 9 | "geom_iters":1, 10 | "clear_color":[0,0,0] 11 | }, 12 | 13 | // In addition to drawing an image buffer you can define other buffers to draw here 14 | // Available as iBuffName in all shaders. 15 | "buffers": { 16 | "A": { 17 | // Buffer will have the same size as the window size 18 | "size": "window_size", 19 | // How many times the geometry shader will execute 20 | "geom_iters":1024, 21 | // RGB values from the interval [0, 1] 22 | "clear_color":[0, 0, 0] 23 | }, 24 | "B": { 25 | "size": "window_size", 26 | "geom_iters":1, 27 | "clear_color":[0, 0, 0] 28 | } 29 | }, 30 | 31 | // Render A then B 32 | // Every buffer has access to the most recent output of all buffers except image 33 | "render_order":["A", "B"], 34 | 35 | "audio_enabled":true, 36 | "audio_options": { 37 | "fft_sync":true, 38 | "xcorr_sync":true, 39 | "fft_smooth":1.0, 40 | "wave_smooth":0.8 41 | }, 42 | 43 | // Useful for setting colors from external scripts. 44 | // Available as UniformName in all buffers. 45 | "uniforms": { 46 | "my_uni": [10, 123, 42], 47 | "your_uni":[25, 20, 1], 48 | "his_uni":[1.0, 2.0, 3.0, 4] 49 | }, 50 | 51 | "blend":true 52 | } 53 | -------------------------------------------------------------------------------- /src/shaders/fftPlane/image.frag: -------------------------------------------------------------------------------- 1 | vec4 fg = vec4(1); 2 | vec4 bg = vec4(0); 3 | 4 | in vec2 geom_p; 5 | out vec4 C; 6 | 7 | float dbox(vec3 p, vec3 dim) { 8 | return length(max(abs(p) - dim, vec3(0))); 9 | } 10 | 11 | vec2 box(vec3 ro, vec3 rd, vec3 dim) { 12 | float t = 0.; 13 | float d; 14 | for (int i = 0; i < 16; ++i) { 15 | vec3 p = ro + rd * t; 16 | d = dbox(p, dim); 17 | t += d; 18 | } 19 | return vec2(t, d); 20 | } 21 | 22 | float dtl(vec3 p, vec3 lo, vec3 ld, float l) { 23 | vec3 x = p - lo; // x's origin is now at lo 24 | float t = clamp(dot(ld, x), 0., l); 25 | return length(x - ld * t); 26 | } 27 | 28 | float tx(sampler1D tex, float x) { 29 | return .333*texture(tex, x).r + 30 | .333*texture(tex, x+.005).r + 31 | .333*texture(tex, x-.005).r; 32 | } 33 | 34 | float get_sound(vec2 x) { 35 | float sl = tx(iSoundR, x.x); 36 | float sr = tx(iSoundR, x.y); 37 | return .2*(sl+sr); 38 | } 39 | 40 | float get_freq(vec2 x) { 41 | x = max(vec2(0), x*.9 + (1.-.9)); 42 | x *= x; 43 | x *= .2; 44 | float fl = tx(iFreqL, x.x); 45 | float fr = tx(iFreqR, x.y); 46 | return 6.*sqrt(fl*fr); 47 | } 48 | 49 | float plane(vec3 p, vec3 pb1, vec3 pb2, vec3 pn, vec2 bound) { 50 | mat3 r3_to_plane_basis = transpose(mat3(pb1, pn, pb2)); 51 | vec3 x = r3_to_plane_basis * p; 52 | //float frq = .4*sqrt(get_freq((x.xz / bound*.5 + .5)*.75)); 53 | float frq = .3*sqrt(get_freq(abs(x.xz) / bound)); 54 | return x.y - frq + .125; 55 | } 56 | 57 | vec3 box_dim = vec3(.5,.25,.5); 58 | 59 | float map(vec3 p) { 60 | float d = plane(p, vec3(0,0,1), vec3(1,0,0), vec3(0,1,0), vec2(.5)); 61 | d = max(d, dbox(p,box_dim)); 62 | return d; 63 | } 64 | 65 | vec2 sphere(vec3 p, vec3 ro, vec3 rd, float r) { 66 | float a = dot(rd, rd); 67 | float b = 2.0 * dot(ro - p, rd); 68 | float c = dot(ro - p, ro - p) - r*r; 69 | float h = b*b - 4.0*a*c; 70 | if (h < 0.) 71 | discard; 72 | h = sqrt(h); 73 | return vec2(-b - h, -b + h); 74 | } 75 | 76 | float tri_min(vec3 x) { 77 | return min(x.x,min(x.y,x.z)); 78 | } 79 | 80 | void main() { 81 | vec2 uv = geom_p * 2. - 1.; 82 | uv.x *= iRes.x / iRes.y; 83 | 84 | uv /= 2.; 85 | 86 | C = bg; 87 | 88 | float away = 8.; 89 | float th = - iMouse.x / 80. + iTime / 10.; float cs = cos(th); float sn = sin(th); 90 | vec3 ro = away*vec3(cs, .5, sn); 91 | vec3 ta = vec3(0,-.07,0); 92 | vec3 forward = normalize(ta - ro); 93 | vec3 up = vec3(0,1,0); 94 | vec3 right = vec3(-sn, 0., cs); // deriv of ro, which also is (-z,0,x) 95 | //vec3 right = normalize(cross(forward,up)); 96 | vec3 rd = normalize(vec3(mat3(right, up, forward) * vec3(uv.x, uv.y, away))); 97 | 98 | vec2 bound = box(ro,rd,box_dim); 99 | if (bound.y > .03) 100 | discard; 101 | float t = bound.x; 102 | vec3 p; 103 | float i = 0.; 104 | for (i = 0.; i < 128.; i+=1.) { 105 | p = ro+rd*t; 106 | float d = map(p); 107 | if (d < .003) { 108 | C = 2.7*(p.y+.07) * fg; 109 | break; 110 | } 111 | else if (tri_min(abs(p) - box_dim) > 0.) { 112 | //C = vec4(1.,0,0,1); 113 | break; 114 | } 115 | t += d*.1; 116 | } 117 | //C = vec4(float(i > 100.)); 118 | } 119 | 120 | -------------------------------------------------------------------------------- /src/shaders/fft_and_wave/A.frag: -------------------------------------------------------------------------------- 1 | const float TAU = 6.283185307179586; 2 | const float TAUR = 2.5066282746310002; 3 | const float SQRT2 = 1.4142135623730951; 4 | 5 | in vec3 uvl; 6 | in float width; 7 | in float intensity; 8 | in float min_intensity; 9 | 10 | // A standard gaussian function, used for weighting samples 11 | float gaussian(float x, float sigma) { 12 | return exp(-(x * x) / (2.0 * sigma * sigma)) / (TAUR * sigma); 13 | } 14 | 15 | // This approximates the error function, needed for the gaussian integral 16 | float erf(float x) { 17 | float s = sign(x), a = abs(x); 18 | x = 1.0 + (0.278393 + (0.230389 + (0.000972 + 0.078108 * a) * a) * a) * a; 19 | x *= x; 20 | return s - s / (x * x); 21 | } 22 | 23 | out vec4 C; 24 | void main() { 25 | float len = uvl.z; 26 | vec2 xy = uvl.xy; 27 | float alpha; 28 | 29 | float sigma = width/(2. + 2000.*width/50.); 30 | alpha = erf(xy.x/SQRT2/sigma) - erf((xy.x-len)/SQRT2/sigma); 31 | alpha *= exp(-xy.y*xy.y/(2.0*sigma*sigma))/2.0/len*width; 32 | 33 | alpha = pow(alpha,1.0-min_intensity)*(0.01+min(0.99,intensity*3.0)); 34 | C = vec4(vec3(1.), alpha); 35 | } 36 | -------------------------------------------------------------------------------- /src/shaders/fft_and_wave/A.geom: -------------------------------------------------------------------------------- 1 | layout(triangle_strip, max_vertices=24) out; 2 | 3 | out float width; 4 | out float intensity; 5 | out float min_intensity; 6 | out vec3 uvl; 7 | 8 | void quad(vec2 P0, vec2 P1, float thickness) { 9 | /* 10 | 1------3 11 | | \ | 12 | | \ | 13 | | \| 14 | 0------2 15 | */ 16 | vec2 dir = P1-P0; 17 | float dl = length(dir); 18 | // If the segment is too short, just draw a square 19 | dir = normalize(dir); 20 | vec2 norm = vec2(-dir.y, dir.x); 21 | 22 | uvl = vec3(dl+thickness, -thickness, dl); 23 | gl_Position = vec4(P0+(-dir-norm)*thickness, 0., 1.); 24 | EmitVertex(); // 0 25 | 26 | uvl = vec3(dl+thickness, thickness, dl); 27 | gl_Position = vec4(P0+(-dir+norm)*thickness, 0., 1.); 28 | EmitVertex(); // 1 29 | 30 | uvl = vec3(-thickness, -thickness, dl); 31 | gl_Position = vec4(P1+(dir-norm)*thickness, 0., 1.); 32 | EmitVertex(); // 2 33 | EndPrimitive(); 34 | 35 | uvl = vec3(-thickness, -thickness, dl); 36 | gl_Position = vec4(P1+(dir-norm)*thickness, 0., 1.); 37 | EmitVertex(); // 2 38 | 39 | uvl = vec3(dl+thickness, thickness, dl); 40 | gl_Position = vec4(P0+(-dir+norm)*thickness, 0., 1.); 41 | EmitVertex(); // 1 42 | 43 | uvl = vec3(-thickness, thickness, dl); 44 | gl_Position = vec4(P1+(dir+norm)*thickness, 0., 1.); 45 | EmitVertex(); // 3 46 | EndPrimitive(); 47 | } 48 | 49 | float sample_freq(float x) { 50 | return max(-1., .1*log(8.*texture(iFreqR, .5*pow(mix(x, 1., .13), 3.)).r) - .4); 51 | //return max(-1., sqrt(1.2*texture(iFreqR, pow(mix(x, 1., .18), 4.)).r) - 1.); 52 | } 53 | 54 | float smooth_freq(float x) { 55 | float sum = 0.; 56 | const float width = 6.; 57 | for (float i = -width; i <= width; i+=1.) { 58 | sum += sample_freq(x + i / iNumGeomIters); 59 | } 60 | return sum / (2. * width + 1.); 61 | } 62 | 63 | float smooth_wave(float x) { 64 | float sum = 0.; 65 | const float width = 7.; 66 | for (float i = - width; i <= width; i += 1.) 67 | sum += .55 * texture(iSoundR, x + i / iNumGeomIters).r + .5; 68 | return sum / (2. * width + 1.); 69 | } 70 | 71 | void main() { 72 | float t0 = (iGeomIter+0)/iNumGeomIters; 73 | float t1 = (iGeomIter+1)/iNumGeomIters; 74 | 75 | width = .005; 76 | intensity = .15; 77 | min_intensity = .0; 78 | 79 | float f0 = smooth_freq(t0); 80 | float f1 = smooth_freq(t1); 81 | float sr0 = smooth_wave(t0); 82 | float sr1 = smooth_wave(t1); 83 | 84 | t0 = t0 * 2. - 1.; 85 | t1 = t1 * 2. - 1.; 86 | 87 | vec2 P0 = vec2(t0, f0); 88 | vec2 P1 = vec2(t1, f1); 89 | quad(P0, P1, width); 90 | 91 | P0 = vec2(t0, sr0); 92 | P1 = vec2(t1, sr1);; 93 | quad(P0, P1, width); 94 | } 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/shaders/fft_and_wave/B.frag: -------------------------------------------------------------------------------- 1 | vec4 fg = vec4(1., 2., 3., 1.); 2 | vec4 bg = vec4(0); 3 | 4 | const float lag = 10/144.; 5 | 6 | in vec2 geom_p; 7 | out vec4 c; 8 | 9 | void main() { 10 | if (iFrame == 0) { 11 | c = fg; 12 | return; 13 | } 14 | 15 | float al = texture(iA, geom_p).r; 16 | 17 | if (geom_p.y > .5) 18 | al *= 1.3; 19 | 20 | vec4 new_color = 4.*mix(bg, fg, al); 21 | vec4 old_color = texture(iB, geom_p); 22 | 23 | c = mix(old_color, new_color, lag); 24 | c.a = 1.; // Replaces color 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/shaders/fft_and_wave/image.frag: -------------------------------------------------------------------------------- 1 | in vec2 geom_p; 2 | out vec4 c; 3 | void main() { 4 | c=texture(iB, geom_p); 5 | } 6 | -------------------------------------------------------------------------------- /src/shaders/fft_and_wave/shader.json: -------------------------------------------------------------------------------- 1 | { 2 | "shader_mode": "advanced", 3 | // TODO implement these options 4 | // interpolation -> default of gl_nearest 5 | // edge_behaviour -> default of gl_repeat 6 | 7 | "initial_window_size":[500,400], 8 | // Please use only valid glsl names for buffers and uniforms 9 | // Please do not specify options more than once 10 | 11 | // Every shader has an image buffer, just like shadertoy 12 | // Size is always the same as the window size 13 | "image": { 14 | "geom_iters":1, 15 | "clear_color":[0,0,0] 16 | }, 17 | 18 | // In addition to drawing an image buffer you can define other buffers to draw here 19 | // Available as iBuffName in all shaders. 20 | "buffers": { 21 | "A": { 22 | // Buffer will have the same size as the window size 23 | "size": "window_size", 24 | // How many times the geometry shader will execute 25 | "geom_iters":2048, 26 | // RGB values from the interval [0, 1] 27 | // Default is [0,0,0] 28 | "clear_color":[0, 0, 0] 29 | }, 30 | "B": { 31 | "size": "window_size", 32 | "geom_iters":1, 33 | "clear_color":[0, 0, 0] 34 | } 35 | }, 36 | 37 | // Render A then B 38 | // Every buffer has access to the most recent output of all other buffers 39 | // No buffer has access to the output of the image buffer 40 | // Default is the order the buffers are declared in buffers 41 | "render_order":["A", "B"], 42 | 43 | // Do you want glEnable(GL_BLEND) ? 44 | // blend func (srcAlpha, 1-srcAlpha) 45 | // Default is false 46 | "blend":true, 47 | 48 | // Defaults are true, true, .75, .75 49 | "audio_enabled":true, 50 | "audio_options": { 51 | "fft_sync":true, 52 | "xcorr_sync":true, 53 | "fft_smooth":1.0, 54 | "wave_smooth":0.6 55 | }, 56 | 57 | // Useful for setting colors from external scripts. 58 | // Available as UniformName in all buffers. 59 | "uniforms": { 60 | "my_uni": [10, 123, 42], 61 | "your_uni":[25, 20, 1], 62 | "his_uni":[1.0, 2.0, 3.0, 4] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/shaders/image.frag: -------------------------------------------------------------------------------- 1 | in vec2 geom_p; 2 | out vec4 c; 3 | void main() { 4 | c=texture(iB, geom_p); 5 | } 6 | -------------------------------------------------------------------------------- /src/shaders/lissajous/A.frag: -------------------------------------------------------------------------------- 1 | const float TAU = 6.283185307179586; 2 | const float TAUR = 2.5066282746310002; 3 | const float SQRT2 = 1.4142135623730951; 4 | 5 | in vec3 uvl; 6 | in float width; 7 | in float intensity; 8 | in float min_intensity; 9 | 10 | // A standard gaussian function, used for weighting samples 11 | float gaussian(float x, float sigma) { 12 | return exp(-(x * x) / (2.0 * sigma * sigma)) / (TAUR * sigma); 13 | } 14 | 15 | // This approximates the error function, needed for the gaussian integral 16 | float erf(float x) { 17 | float s = sign(x), a = abs(x); 18 | x = 1.0 + (0.278393 + (0.230389 + (0.000972 + 0.078108 * a) * a) * a) * a; 19 | x *= x; 20 | return s - s / (x * x); 21 | } 22 | 23 | out vec4 C; 24 | void main() { 25 | float len = uvl.z; 26 | vec2 xy = uvl.xy; 27 | float alpha; 28 | 29 | float sigma = width/(2. + 2000.*width/50.); 30 | alpha = erf(xy.x/SQRT2/sigma) - erf((xy.x-len)/SQRT2/sigma); 31 | alpha *= exp(-xy.y*xy.y/(2.0*sigma*sigma))/2.0/len*width; 32 | 33 | alpha = pow(alpha,1.0-min_intensity)*(0.01+min(0.99,intensity*3.0)); 34 | C = vec4(vec3(1.), alpha); 35 | } 36 | -------------------------------------------------------------------------------- /src/shaders/lissajous/A.geom: -------------------------------------------------------------------------------- 1 | layout(triangle_strip, max_vertices=24) out; 2 | 3 | out float width; 4 | out float intensity; 5 | out float min_intensity; 6 | out vec3 uvl; 7 | 8 | void quad(vec2 P0, vec2 P1, float thickness) { 9 | /* 10 | 1------3 11 | | \ | 12 | | \ | 13 | | \| 14 | 0------2 15 | */ 16 | vec2 dir = P1-P0; 17 | float dl = length(dir); 18 | // If the segment is too short, just draw a square 19 | dir = normalize(dir); 20 | vec2 norm = vec2(-dir.y, dir.x); 21 | 22 | uvl = vec3(dl+thickness, -thickness, dl); 23 | gl_Position = vec4(P0+(-dir-norm)*thickness, 0., 1.); 24 | EmitVertex(); // 0 25 | 26 | uvl = vec3(dl+thickness, thickness, dl); 27 | gl_Position = vec4(P0+(-dir+norm)*thickness, 0., 1.); 28 | EmitVertex(); // 1 29 | 30 | uvl = vec3(-thickness, -thickness, dl); 31 | gl_Position = vec4(P1+(dir-norm)*thickness, 0., 1.); 32 | EmitVertex(); // 2 33 | EndPrimitive(); 34 | 35 | uvl = vec3(-thickness, -thickness, dl); 36 | gl_Position = vec4(P1+(dir-norm)*thickness, 0., 1.); 37 | EmitVertex(); // 2 38 | 39 | uvl = vec3(dl+thickness, thickness, dl); 40 | gl_Position = vec4(P0+(-dir+norm)*thickness, 0., 1.); 41 | EmitVertex(); // 1 42 | 43 | uvl = vec3(-thickness, thickness, dl); 44 | gl_Position = vec4(P1+(dir+norm)*thickness, 0., 1.); 45 | EmitVertex(); // 3 46 | EndPrimitive(); 47 | } 48 | 49 | void main() { 50 | float t0 = (iGeomIter+0)/iNumGeomIters; 51 | float t1 = (iGeomIter+1)/iNumGeomIters; 52 | 53 | width = .003; 54 | intensity = 1; 55 | min_intensity = .75; 56 | 57 | float sl0 = texture(iSoundL, t0).r; 58 | float sl1 = texture(iSoundL, t1).r; 59 | float sr0 = texture(iSoundR, t0).r; 60 | float sr1 = texture(iSoundR, t1).r; 61 | 62 | vec2 P0 = vec2(sl0, sr0); 63 | vec2 P1 = vec2(sl1, sr1); 64 | quad(P0, P1, width); 65 | } 66 | -------------------------------------------------------------------------------- /src/shaders/lissajous/B.frag: -------------------------------------------------------------------------------- 1 | in vec2 geom_p; 2 | out vec4 c; 3 | // vec4 bg = .9*vec4(52./256., 9./256., 38./256., 1.); 4 | // vec4 fg = 1.1*vec4(1.,195./256.,31./256.,1.); 5 | 6 | vec4 fg = vec4(1); 7 | vec4 bg = vec4(0); 8 | 9 | //vec4 fg = vec4(1,vec3(0)); 10 | //vec4 bg = vec4(0); 11 | 12 | void main() { 13 | if (iFrame == 0) { 14 | c = fg; 15 | return; 16 | } 17 | float al = texture(iA, geom_p).r; 18 | al *= 2.; 19 | vec4 new_color = mix(bg, fg, al); 20 | vec4 old_color = texture(iB, geom_p); 21 | 22 | c = mix(old_color, new_color, .05); 23 | c.a = 1.; // Replaces color 24 | } 25 | -------------------------------------------------------------------------------- /src/shaders/lissajous/image.frag: -------------------------------------------------------------------------------- 1 | in vec2 geom_p; 2 | out vec4 c; 3 | void main() { 4 | c=texture(iB, geom_p); 5 | } 6 | -------------------------------------------------------------------------------- /src/shaders/lissajous/shader.json: -------------------------------------------------------------------------------- 1 | { 2 | "shader_mode":"advanced", 3 | // Please use only valid glsl names for buffers and uniforms 4 | // Please do not specify options more than once 5 | 6 | // Every shader has an image buffer, just like shadertoy 7 | // Size is always the same as the window size 8 | "image": { 9 | "geom_iters":1, 10 | "clear_color":[0,0,0] 11 | }, 12 | 13 | // In addition to drawing an image buffer you can define other buffers to draw here 14 | // Available as iBuffName in all shaders. 15 | "buffers": { 16 | "A": { 17 | // Buffer will have the same size as the window size 18 | "size": "window_size", 19 | // How many times the geometry shader will execute 20 | "geom_iters":1024, 21 | // RGB values from the interval [0, 1] 22 | "clear_color":[0, 0, 0] 23 | }, 24 | "B": { 25 | "size": "window_size", 26 | "geom_iters":1, 27 | "clear_color":[0, 0, 0] 28 | } 29 | }, 30 | 31 | // Render A then B 32 | // Every buffer has access to the most recent output of all buffers except image 33 | "render_order":["A", "B"], 34 | 35 | "audio_enabled":true, 36 | "audio_options": { 37 | "fft_sync":true, 38 | "xcorr_sync":true, 39 | "fft_smooth":1.0, 40 | "wave_smooth":0.8 41 | }, 42 | 43 | // Useful for setting colors from external scripts. 44 | // Available as UniformName in all buffers. 45 | "uniforms": { 46 | "my_uni": [10, 123, 42], 47 | "your_uni":[25, 20, 1], 48 | "his_uni":[1.0, 2.0, 3.0, 4] 49 | }, 50 | "blend":true 51 | } 52 | -------------------------------------------------------------------------------- /src/shaders/oscilloscope/A.frag: -------------------------------------------------------------------------------- 1 | const float TAU = 6.283185307179586; 2 | const float TAUR = 2.5066282746310002; 3 | const float SQRT2 = 1.4142135623730951; 4 | 5 | in vec3 uvl; 6 | in float width; 7 | in float intensity; 8 | in float min_intensity; 9 | 10 | // A standard gaussian function, used for weighting samples 11 | float gaussian(float x, float sigma) { 12 | return exp(-(x * x) / (2.0 * sigma * sigma)) / (TAUR * sigma); 13 | } 14 | 15 | // This approximates the error function, needed for the gaussian integral 16 | float erf(float x) { 17 | float s = sign(x), a = abs(x); 18 | x = 1.0 + (0.278393 + (0.230389 + (0.000972 + 0.078108 * a) * a) * a) * a; 19 | x *= x; 20 | return s - s / (x * x); 21 | } 22 | 23 | out vec4 C; 24 | void main() { 25 | float len = uvl.z; 26 | vec2 xy = uvl.xy; 27 | float alpha; 28 | 29 | float sigma = width/(2. + 2000.*width/50.); 30 | alpha = erf(xy.x/SQRT2/sigma) - erf((xy.x-len)/SQRT2/sigma); 31 | alpha *= exp(-xy.y*xy.y/(2.0*sigma*sigma))/2.0/len*width; 32 | 33 | alpha = pow(alpha,1.0-min_intensity)*(0.01+min(0.99,intensity*3.0)); 34 | C = vec4(vec3(1.), alpha); 35 | } 36 | -------------------------------------------------------------------------------- /src/shaders/oscilloscope/A.geom: -------------------------------------------------------------------------------- 1 | layout(triangle_strip, max_vertices=24) out; 2 | 3 | out float width; 4 | out float intensity; 5 | out float min_intensity; 6 | out vec3 uvl; 7 | 8 | void quad(vec2 P0, vec2 P1, float thickness) { 9 | /* 10 | 1------3 11 | | \ | 12 | | \ | 13 | | \| 14 | 0------2 15 | */ 16 | vec2 dir = P1-P0; 17 | float dl = length(dir); 18 | // If the segment is too short, just draw a square 19 | dir = normalize(dir); 20 | vec2 norm = vec2(-dir.y, dir.x); 21 | 22 | uvl = vec3(dl+thickness, -thickness, dl); 23 | gl_Position = vec4(P0+(-dir-norm)*thickness, 0., 1.); 24 | EmitVertex(); // 0 25 | 26 | uvl = vec3(dl+thickness, thickness, dl); 27 | gl_Position = vec4(P0+(-dir+norm)*thickness, 0., 1.); 28 | EmitVertex(); // 1 29 | 30 | uvl = vec3(-thickness, -thickness, dl); 31 | gl_Position = vec4(P1+(dir-norm)*thickness, 0., 1.); 32 | EmitVertex(); // 2 33 | EndPrimitive(); 34 | 35 | uvl = vec3(-thickness, -thickness, dl); 36 | gl_Position = vec4(P1+(dir-norm)*thickness, 0., 1.); 37 | EmitVertex(); // 2 38 | 39 | uvl = vec3(dl+thickness, thickness, dl); 40 | gl_Position = vec4(P0+(-dir+norm)*thickness, 0., 1.); 41 | EmitVertex(); // 1 42 | 43 | uvl = vec3(-thickness, thickness, dl); 44 | gl_Position = vec4(P1+(dir+norm)*thickness, 0., 1.); 45 | EmitVertex(); // 3 46 | EndPrimitive(); 47 | } 48 | 49 | void main() { 50 | float t0 = (iGeomIter+0)/iNumGeomIters; 51 | float t1 = (iGeomIter+1)/iNumGeomIters; 52 | 53 | width = .015; 54 | intensity = .1; 55 | min_intensity = .01; 56 | 57 | float sr0 = texture(iSoundR, t0).r; 58 | float sr1 = texture(iSoundR, t1).r; 59 | vec2 P0 = vec2(t0*2.-1., sr0); 60 | vec2 P1 = vec2(t1*2.-1., sr1); 61 | quad(P0, P1, width); 62 | } 63 | -------------------------------------------------------------------------------- /src/shaders/oscilloscope/B.frag: -------------------------------------------------------------------------------- 1 | in vec2 geom_p; 2 | out vec4 c; 3 | 4 | vec4 bg = .9*vec4(52./256., 9./256., 38./256., 1.); 5 | vec4 fg = 1.1*vec4(1.,195./256.,31./256.,1.); 6 | 7 | // vec4 bg = vec4(vec3(10, 23, 42)/77., 1.); 8 | // vec4 fg = vec4(1.5); 9 | 10 | // vec4 fg = vec4(1); 11 | // vec4 bg = vec4(0); 12 | 13 | void main() { 14 | if (iFrame == 0) { 15 | c = fg; 16 | return; 17 | } 18 | 19 | float al = texture(iA, geom_p).r; 20 | al *= 5.; 21 | vec4 new_color = mix(bg, fg, al); 22 | vec4 old_color = texture(iB, geom_p); 23 | 24 | c = mix(new_color, old_color, .8); 25 | c.a = 1.; // Replaces color 26 | } 27 | -------------------------------------------------------------------------------- /src/shaders/oscilloscope/image.frag: -------------------------------------------------------------------------------- 1 | in vec2 geom_p; 2 | out vec4 c; 3 | void main() { 4 | c=texture(iB, geom_p); 5 | } 6 | -------------------------------------------------------------------------------- /src/shaders/oscilloscope/shader.json: -------------------------------------------------------------------------------- 1 | { 2 | "shader_mode":"advanced", 3 | // TODO implement these options 4 | // interpolation -> default of gl_nearest 5 | // edge_behaviour -> default of gl_repeat 6 | 7 | "initial_window_size":[500,250], 8 | // Please use only valid glsl names for buffers and uniforms 9 | // Please do not specify options more than once 10 | 11 | // Every shader has an image buffer, just like shadertoy 12 | // Size is always the same as the window size 13 | "image": { 14 | "geom_iters":1, 15 | "clear_color":[0,0,0] 16 | }, 17 | 18 | // In addition to drawing an image buffer you can define other buffers to draw here 19 | // Available as iBuffName in all shaders. 20 | "buffers": { 21 | "A": { 22 | // Buffer will have the same size as the window size 23 | "size": "window_size", 24 | // How many times the geometry shader will execute 25 | "geom_iters":1024, 26 | // RGB values from the interval [0, 1] 27 | // Default is [0,0,0] 28 | "clear_color":[0, 0, 0] 29 | }, 30 | "B": { 31 | "size": "window_size", 32 | "geom_iters":1, 33 | "clear_color":[0, 0, 0] 34 | } 35 | }, 36 | 37 | // Render A then B 38 | // Every buffer has access to the most recent output of all other buffers 39 | // No buffer has access to the output of the image buffer 40 | // Default is the order the buffers are declared in buffers 41 | "render_order":["A", "B"], 42 | 43 | // Do you want glEnable(GL_BLEND) ? 44 | // blend func (srcAlpha, 1-srcAlpha) 45 | // Default is false 46 | "blend":true, 47 | 48 | // Defaults are true, true, .75, .75 49 | "audio_enabled":true, 50 | "audio_options": { 51 | "fft_sync":true, 52 | "xcorr_sync":true, 53 | "fft_smooth":1.0, 54 | "wave_smooth":0.8 55 | }, 56 | 57 | // Useful for setting colors from external scripts. 58 | // Available as UniformName in all buffers. 59 | "uniforms": { 60 | "my_uni": [10, 123, 42], 61 | "your_uni":[25, 20, 1], 62 | "his_uni":[1.0, 2.0, 3.0, 4] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/shaders/retrowave/a.frag: -------------------------------------------------------------------------------- 1 | in vec2 geom_p; 2 | out vec4 c; 3 | void main () { 4 | float wave = texture(iSoundL, geom_p.x).r * .5 + .5; 5 | 6 | vec4 prev_col = texture(ia, geom_p); 7 | float dist = abs(geom_p.y - wave); 8 | dist *= dist; 9 | 10 | /* 11 | c = vec4(clamp(.002 / dist, 0., 1.)); 12 | /*/ 13 | c = prev_col*.5 + vec4(clamp(.0005 / dist, 0., 1.)); 14 | c = clamp(c, vec4(0), vec4(1.5)); 15 | //*/ 16 | } 17 | -------------------------------------------------------------------------------- /src/shaders/retrowave/image.frag: -------------------------------------------------------------------------------- 1 | in vec2 geom_p; 2 | out vec4 c; 3 | 4 | float hash(float p) { 5 | vec3 p3 = fract(vec3(p) * 443.8975); 6 | p3 += dot(p3, p3.yzx + 19.19); 7 | return fract((p3.x + p3.y) * p3.z); 8 | } 9 | 10 | void main () { 11 | // use manhatten dist in voronoi to get the blocky effect. 12 | // todo look into plane tilings 13 | 14 | // todo parallelogram is neat but a diagonal high tech (possibly mini voronoi) 15 | // pattern might be cooler 16 | 17 | vec2 uv = geom_p; 18 | uv.x *= iRes.x / iRes.y; 19 | 20 | // background 21 | vec2 rep_p = fract(uv*sqrt(iRes.y*14.)); 22 | float bgmesh_bezel = .4 - .08 * smoothstep(500., 900., iRes.y); 23 | vec4 bgmesh = 1.5*vec4(.1, .092, .07, 1.) * step(bgmesh_bezel, rep_p.x) * step(bgmesh_bezel, rep_p.y); 24 | 25 | // main grid division 26 | const float freq = 33.; 27 | const float bezel = .7; 28 | const float skew = .5; 29 | 30 | // give pixel a skewed lattice point 31 | uv = vec2(uv.x-skew*uv.y, uv.y); 32 | vec2 lattice = floor(uv * freq) / freq; 33 | // unskew latice point 34 | lattice.x += floor(skew * uv.y * freq)/freq; 35 | 36 | // grid lines 37 | uv = fract(uv * freq); 38 | float grid = step(bezel, 1. - abs(uv.x - .5)) * step(bezel, 1. - abs(uv.y - .5)); 39 | 40 | const float pinkness = .7; 41 | const float blueness = .8; 42 | const float brightness = 2.; 43 | 44 | vec4 pink = 1.4 * vec4(255./256., 20./256., 144./256., 1); 45 | vec4 blue = vec4(0., 204./256., 1., 1); 46 | vec4 bg = vec4(0); 47 | 48 | lattice.x *= iRes.y / iRes.x; // put lattice.x in range [0, 1] 49 | float x = texture(ia, lattice).r; 50 | c = mix(mix(bg, blue, smoothstep(.0, .3, blueness*x)), pink, pinkness*x); 51 | c *= grid * brightness; 52 | c = mix(bgmesh, c, smoothstep(.05, 1., length(c))); 53 | 54 | // vignette 55 | c.rgb *= pow(16.*geom_p.x*geom_p.y*(1.-geom_p.x)*(1.-geom_p.y), .3); 56 | 57 | // a bit of grain 58 | c *= 1. - .5*sqrt(hash(geom_p.x*geom_p.y+2.)); 59 | 60 | c.a = 1.; 61 | } 62 | -------------------------------------------------------------------------------- /src/shaders/shader.json: -------------------------------------------------------------------------------- 1 | { 2 | "shader_mode": "advanced", 3 | // TODO implement these options 4 | // interpolation -> default of gl_nearest 5 | // edge_behaviour -> default of gl_repeat 6 | 7 | "initial_window_size":[500,400], 8 | // Please use only valid glsl names for buffers and uniforms 9 | // Please do not specify options more than once 10 | 11 | // Every shader has an image buffer, just like shadertoy 12 | // Size is always the same as the window size 13 | "image": { 14 | "geom_iters":1, 15 | "clear_color":[0,0,0] 16 | }, 17 | 18 | // In addition to drawing an image buffer you can define other buffers to draw here 19 | // Available as iBuffName in all shaders. 20 | "buffers": { 21 | "A": { 22 | // Buffer will have the same size as the window size 23 | "size": "window_size", 24 | // How many times the geometry shader will execute 25 | "geom_iters":2048, 26 | // RGB values from the interval [0, 1] 27 | // Default is [0,0,0] 28 | "clear_color":[0, 0, 0] 29 | }, 30 | "B": { 31 | "size": "window_size", 32 | "geom_iters":1, 33 | "clear_color":[0, 0, 0] 34 | } 35 | }, 36 | 37 | // Render A then B 38 | // Every buffer has access to the most recent output of all other buffers 39 | // No buffer has access to the output of the image buffer 40 | // Default is the order the buffers are declared in buffers 41 | "render_order":["A", "B"], 42 | 43 | // Do you want glEnable(GL_BLEND) ? 44 | // blend func (srcAlpha, 1-srcAlpha) 45 | // Default is false 46 | "blend":true, 47 | 48 | // Defaults are true, true, .75, .75 49 | "audio_enabled":true, 50 | "audio_options": { 51 | "fft_sync":true, 52 | "xcorr_sync":true, 53 | "fft_smooth":1.0, 54 | "wave_smooth":0.6 55 | }, 56 | 57 | // Useful for setting colors from external scripts. 58 | // Available as UniformName in all buffers. 59 | "uniforms": { 60 | "my_uni": [10, 123, 42], 61 | "your_uni":[25, 20, 1], 62 | "his_uni":[1.0, 2.0, 3.0, 4] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/shaders/spectrogram/a.frag: -------------------------------------------------------------------------------- 1 | in vec2 geom_p; 2 | out vec4 c; 3 | void main () { 4 | if (gl_FragCoord.x >= iRes.x-1) { 5 | float freq = log(1. + 10.*texture(iFreqL, (exp2(geom_p.y / 1.5) - 1.) ).r); 6 | c = vec4(freq); 7 | } 8 | else { 9 | vec4 adjcol = texture(ia, geom_p + vec2(1. / iRes.x, 0.)); 10 | c = vec4(adjcol); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/shaders/spectrogram/image.frag: -------------------------------------------------------------------------------- 1 | in vec2 geom_p; 2 | out vec4 c; 3 | 4 | float hash(float p) { 5 | vec3 p3 = fract(vec3(p) * 443.8975); 6 | p3 += dot(p3, p3.yzx + 19.19); 7 | return fract((p3.x + p3.y) * p3.z); 8 | } 9 | 10 | void main () { 11 | c = texture(ia, geom_p); 12 | c.a = 1.; 13 | } 14 | -------------------------------------------------------------------------------- /src/shaders/star/image.frag: -------------------------------------------------------------------------------- 1 | 2 | out vec4 C; 3 | 4 | // vec4 bg = .9*vec4(52./256., 9./256., 38./256., 1.); 5 | // vec4 fg = 1.1*vec4(1.,195./256.,31./256.,1.); 6 | 7 | // vec4 fg = vec4(248./256.,73./256.,52./256.,1.); 8 | // vec4 bg = .3*vec4(77./256., 94./256., 95./256., 1.); 9 | 10 | vec4 fg = vec4(1); 11 | vec4 bg = vec4(0); 12 | 13 | float f(float x) { 14 | x /= 4.; 15 | return .3*texture(iFreqL, x).r; 16 | } 17 | 18 | #define SPIRAL 19 | 20 | void main() { 21 | if (iFrame == 0) { 22 | C = fg; 23 | return; 24 | } 25 | const float PI = 3.141592; 26 | float threshold = .2; 27 | float time = iTime/100.; 28 | 29 | vec2 p = gl_FragCoord.xy/iRes*2.-1.; 30 | #ifdef SPIRAL 31 | p*=1.5; 32 | p-=vec2(.0,-.4); 33 | #endif 34 | float aspect = iRes.x/iRes.y; 35 | p.y /= aspect; 36 | p *= 1. + fract(max(aspect, .7))/2.; 37 | 38 | float theta = atan(p.y,p.x); 39 | float len = length(p); 40 | 41 | // Make star fish 42 | 43 | // bass 44 | // float bump = (f(0.01)+f(0.02)+f(0.03))*.05; 45 | float bump = f(0.02)*.07; 46 | 47 | // Set the fishies parameters 48 | float fish_number_legs = 9.; 49 | 50 | // Make the fish twirl around 51 | float fish_spin = -time*20.; 52 | 53 | // Make the fish get bigger 54 | float fish_leg_len = .1+.1*bump; 55 | 56 | // Make the fish move its legs 57 | float fish_leg_bend = 0.1*sin(40.*time+len)*(2.*PI); 58 | 59 | // Put the fish togeter 60 | float fish = fish_leg_len*sin(fish_leg_bend + fish_spin + fish_number_legs*theta); 61 | 62 | // Make the fish jump a little 63 | float fish_jump = .25*bump; // just a soft bump 64 | 65 | #ifdef SPIRAL 66 | float spiral = (theta-PI/2.)/PI; 67 | float fish_dist = 1.-len*(.8+fish_jump+fish)+spiral; 68 | float fish_swim = -time*4.; 69 | float fish_school = fract(fish_dist*.5 + fish_swim); 70 | fish_school = pow(fish_school, 1.3); 71 | float v = f(fish_school); 72 | v = log(v+1.); 73 | v *= smoothstep(0.,0.08,len*(fish_school)); 74 | // fish is a bit limp on linux so boost it up some 75 | v *= 10.; 76 | #else 77 | // Pixel distance to fish 78 | float fish_dist = 1.-len*(.8+fish_jump+fish); 79 | 80 | // Fishes swim away 81 | float fish_swim = time/4.; 82 | 83 | // Fishes are packed fin to fin and gill to gill 84 | float fish_school = abs(fract(.5*fish_dist + fish_swim)-.5); 85 | fish_school += 0.05; // remove the always zero frequencies. 86 | fish_school = fract(fish_school*.8); 87 | 88 | // Eat the fish 89 | float v = f(fish_school); 90 | v = log(2.*v+1.); 91 | v *= smoothstep(0.,.07, len*(.8+fish)); 92 | // fish is a bit limp on linux so boost it up some 93 | v *= 10.; 94 | #endif 95 | 96 | C = mix(bg, fg, smoothstep(0.,1.,v)); 97 | C.a = 1.; 98 | } 99 | 100 | -------------------------------------------------------------------------------- /tests/fake_clock.cpp: -------------------------------------------------------------------------------- 1 | // https://raw.githubusercontent.com/korfuri/fake_clock/master/fake_clock.cc 2 | #include "fake_clock.h" 3 | 4 | fake_clock::time_point fake_clock::now_us_; 5 | const bool fake_clock::is_steady = false; 6 | 7 | void fake_clock::advance(duration d) noexcept { 8 | now_us_ += d; 9 | } 10 | 11 | void fake_clock::reset_to_epoch() noexcept { 12 | now_us_ -= (now_us_ - time_point()); 13 | } 14 | 15 | fake_clock::time_point fake_clock::now() noexcept { 16 | return now_us_; 17 | } -------------------------------------------------------------------------------- /tests/fake_clock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // https://raw.githubusercontent.com/korfuri/fake_clock/master/fake_clock.cc 3 | // A clock class that is convenient for testing. 4 | // 5 | // This class satisfies the TrivialClock requirement 6 | // (http://en.cppreference.com/w/cpp/concept/TrivialClock) and as such 7 | // can be used in place of any standard clock 8 | // (e.g. std::chrono::system_clock). 9 | // 10 | // The clock uses an uint64_t internally, so it can store all 11 | // nanoseconds in a century. This is consistent with the precision 12 | // required of std::chrono::nanoseconds in C++11. 13 | // 14 | // Example usage: 15 | // 16 | // fake_clock::time_point t1 = fake_clock::now(); 17 | // fake_clock::advance(std::chrono::milliseconds(100)); 18 | // fake_clock::time_point t2 = fake_clock::now(); 19 | // auto elapsed_us = std::chrono::duration_cast< 20 | // std::chrono::microseconds>(t2 - t1).count(); 21 | // assert(100000 == elapsed_us); 22 | 23 | #include 24 | #include 25 | 26 | class fake_clock { 27 | public: 28 | typedef uint64_t rep; 29 | typedef std::ratio<1l, 1000000000l> period; 30 | typedef std::chrono::duration duration; 31 | typedef std::chrono::time_point time_point; 32 | 33 | static void advance(duration d) noexcept; 34 | static void reset_to_epoch() noexcept; 35 | static time_point now() noexcept; 36 | 37 | private: 38 | fake_clock() = delete; 39 | ~fake_clock() = delete; 40 | fake_clock(fake_clock const&) = delete; 41 | 42 | static time_point now_us_; 43 | static const bool is_steady; 44 | }; 45 | -------------------------------------------------------------------------------- /tests/test_audio_process.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using std::cout; 3 | using std::endl; 4 | #include 5 | using std::ifstream; 6 | #include 7 | using std::vector; 8 | #include 9 | #include 10 | namespace chrono = std::chrono; 11 | 12 | #include "fake_clock.h" 13 | #include "AudioProcess.h" 14 | #include "AudioStreams/WavAudioStream.h" 15 | #include "AudioStreams/ProceduralAudioStream.h" 16 | using AudioStreamT = ProceduralAudioStream; 17 | #include "noise.h" 18 | 19 | #include "catch2/catch.hpp" 20 | 21 | static float correlation(vector>& previous_buffers, int frame_id) { 22 | float sum = 0.; 23 | const float* current_buffer = previous_buffers[frame_id % HISTORY_NUM_FRAMES].data(); 24 | for (int b = 0; b < HISTORY_NUM_FRAMES; ++b) { 25 | const int cur_buf = (frame_id + b) % HISTORY_NUM_FRAMES; 26 | for (int i = 0; i < HISTORY_BUFF_SIZE; ++i) { 27 | sum += previous_buffers[cur_buf][i] * current_buffer[i]; 28 | } 29 | } 30 | return sum; 31 | } 32 | 33 | static float test_for_audio_options(AudioOptions ao) { 34 | vector> previous_buffers(HISTORY_NUM_FRAMES, vector(HISTORY_BUFF_SIZE, 0)); 35 | 36 | float freq_modulator = 0.; 37 | float freq = 0.; 38 | float t = 0.; 39 | AudioStreamT as([&](float* l, float* r, int s) { 40 | // set a frequency for this buffer's wave 41 | freq = 2.f * 3.1415926f / s * (1.f + 2.f*(.5f + .5f*sin(freq_modulator))); 42 | 43 | for (int i = 0; i < s; ++i) { 44 | //l[i] = r[i] = sin(t); 45 | //t += freq; 46 | 47 | l[i] = r[i] = .5f*sin(t) + (2.f*fbm(t) - 1.f); 48 | t += freq; 49 | 50 | //l[i] = r[i] = 2.*fbm(t) - 1.; 51 | //t += freq; 52 | } 53 | }); 54 | 55 | AudioProcess ap(as, ao); 56 | 57 | /* Simulation notes 58 | The purpose is to simulate the behavior of the audio loop in a test environment. 59 | I thought it would be interesting to model the probability that the stall time in the system specific get_pcm function is x (then use uniform distribution & inverse CDF). 60 | 61 | On average the gfx loop accesses AudioData roughly every 16.7 ms. Considering that the average stall in get_pcm is 62 | about equal to the half of the time between gfx thread accesses we can simply step the audio thread twice in 63 | the below loop for a good approximation. 64 | */ 65 | float sum = 0.; 66 | const auto avg_get_pcm_stall_time = chrono::microseconds(8650); 67 | for (int frame_id = 0; frame_id < 60 * 30; ++frame_id) { 68 | ap.step(); 69 | fake_clock::advance(avg_get_pcm_stall_time); 70 | ap.step(); 71 | fake_clock::advance(avg_get_pcm_stall_time); 72 | 73 | const AudioData& ad = ap.get_audio_data(); 74 | std::copy(ad.audio_l, ad.audio_l + HISTORY_BUFF_SIZE, previous_buffers[frame_id % HISTORY_NUM_FRAMES].data()); 75 | 76 | sum += correlation(previous_buffers, frame_id); 77 | } 78 | return sum; 79 | } 80 | 81 | TEST_CASE("Optimization performance test") { 82 | AudioOptions ao; 83 | ao.xcorr_sync = false; 84 | ao.fft_sync = false; 85 | ao.wave_smooth = 1.0f; 86 | ao.fft_smooth = 1.0f; 87 | 88 | // none of the wave stabilization optims enabled 89 | const float baseline_perf = test_for_audio_options(ao); 90 | 91 | ao.fft_sync = true; 92 | const float fft_perf = test_for_audio_options(ao); 93 | CHECK(fft_perf > baseline_perf); 94 | CHECK(fft_perf >= 809800); 95 | 96 | ao.xcorr_sync = true; 97 | const float xcorr_perf = test_for_audio_options(ao); 98 | CHECK(xcorr_perf > baseline_perf); 99 | CHECK(xcorr_perf > fft_perf); 100 | CHECK(xcorr_perf >= 850400); 101 | } -------------------------------------------------------------------------------- /tests/test_audio_utilities.cpp: -------------------------------------------------------------------------------- 1 | #include "Test.h" 2 | #include "AudioProcess.h" 3 | 4 | #include "catch2/catch.hpp" 5 | 6 | // TODO finish writing these tests. 7 | 8 | using std::cout; 9 | using std::endl; 10 | 11 | template 12 | T min(T a, T b) { 13 | if (a < b) return a; 14 | return b; 15 | } 16 | 17 | bool AudioUtilityTest::adjust_reader() { 18 | // adjust_reader attempts to separate two points r (reader) and w (writer) 19 | // in a circular buffer by distance tbl/2.f by moving r in steps of size step_size 20 | // adjust_reader will fail if |w - r| is more than step_size units away from tbl / 2 21 | 22 | typedef AudioProcessor ap; 23 | bool ok = true; 24 | 25 | auto test = [](int r, int w, int step_size, int tbl) -> bool { 26 | int delta = ap::adjust_reader(r, w, step_size, tbl); 27 | r = ap::move_index(r, delta, tbl); 28 | int df = ap::dist_forward(w, r, tbl); 29 | int db = ap::dist_backward(w, r, tbl); 30 | int closest_dist = min(df, db); 31 | 32 | if (std::abs(closest_dist - tbl / 2) >= step_size) { 33 | cout << "\t" << "closest_dist: " << closest_dist << endl; 34 | cout << "\t" << " tbl/2: " << tbl / 2 << endl; 35 | cout << "\t" << "|dist-tbl/2|: " << std::abs(closest_dist - tbl / 2) << endl; 36 | cout << "\t" << " step_size: " << step_size << endl; 37 | return false; 38 | } 39 | return true; 40 | }; 41 | 42 | int tbl; 43 | int step_size, w, r; 44 | 45 | tbl = 512 * 16; 46 | step_size = 1000; 47 | //step_size = .75; 48 | //step_size = 1.; 49 | w = 0; 50 | r = tbl; 51 | ok &= test(r, w, step_size, tbl); 52 | 53 | tbl = 52 * 16; 54 | step_size = 10; 55 | w = 0; 56 | r = tbl; 57 | ok &= test(r, w, step_size, tbl); 58 | 59 | if (ok) cout << PASS_MSG << endl; 60 | else cout << FAIL_MSG << endl; 61 | return ok; 62 | } 63 | 64 | bool AudioUtilityTest::advance_index() { 65 | // Test that the advance_index function moves the supplied index forwared given 66 | // the input frequency and the reader/writer positions 67 | // Test that the reader is placed such that reading VL samples will not read past the writer 68 | 69 | typedef audio_processor ap; 70 | bool ok; 71 | 72 | int tbl = 512*16; 73 | int w = 0; 74 | int r = 0; 75 | 76 | // A 93.75hz wave, since SR == 48000 and ABL = 512 77 | // Each pcm_getter could would return 1 cycle of the wave 78 | float freq = SR / float(ABL); 79 | int r_new = ap::advance_index(w, r, freq, tbl); 80 | 81 | // Check that r_new moved according to wave_len 82 | int wave_len = ABL; // == SR / freq; 83 | // Check that dist(r, w) is great enough 84 | int d = min(ap::dist_backward(r_new, r, tbl), ap::dist_forward(r_new, r, tbl)); 85 | if (d % wave_len != 0) { 86 | cout << "reader not moved according to wave_len" << endl; 87 | ok = false; 88 | } 89 | d = ap::dist_forward(r_new, w, tbl); 90 | if (d >= VL) { 91 | cout << "Reader will read discontinuity" << endl; 92 | ok = false; 93 | } 94 | 95 | if (ok) cout << PASS_MSG << endl; 96 | else cout << FAIL_MSG << endl; 97 | return ok; 98 | } 99 | 100 | bool AudioUtilityTest::get_harmonic_less_than() { 101 | // if get_harmonic_less_than does not return its input multiplied by some non-positive integer power of two, 102 | // then fail 103 | // if get_harmonic_less_than does not return a numeber less than or equal to its second argument, 104 | // then fail 105 | 106 | typedef audio_processor ap; 107 | float new_freq, freq, power; 108 | 109 | freq = 61.f; 110 | new_freq = ap::get_harmonic_less_than(freq, 121.f); 111 | power = std::log2(new_freq / freq); 112 | 113 | if (std::fabs(power - std::floor(power)) > 0.000001) { 114 | cout << FAIL_MSG << endl; 115 | return false; 116 | } 117 | if (power > 0.000001f) { 118 | cout << FAIL_MSG << endl; 119 | return false; 120 | } 121 | if (freq > 121.f) { 122 | cout << FAIL_MSG << endl; 123 | return false; 124 | } 125 | cout << PASS_MSG << endl; 126 | return true; 127 | } 128 | 129 | bool AudioUtilityTest::test() { 130 | bool ok; 131 | 132 | cout << "adjust_reader test: " << endl; 133 | ok = adjust_reader(); 134 | 135 | cout << "advance_index test: " << endl; 136 | ok &= advance_index(); 137 | 138 | cout << "get_harmonic_less_than test: " << endl; 139 | ok &= get_harmonic_less_than(); 140 | 141 | return ok; 142 | } 143 | -------------------------------------------------------------------------------- /tests/tests.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 15.0 37 | {65705339-3F83-42BB-B4C1-5C8AF2C23CDD} 38 | Win32Proj 39 | tests 40 | 10.0 41 | music_visualizer_tests 42 | 43 | 44 | 45 | Application 46 | true 47 | v141 48 | Unicode 49 | 50 | 51 | Application 52 | false 53 | v141 54 | true 55 | Unicode 56 | 57 | 58 | Application 59 | true 60 | v141 61 | Unicode 62 | 63 | 64 | Application 65 | false 66 | v142 67 | true 68 | Unicode 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | true 90 | $(SolutionDir)libs\ffts\build\Debug;$(SolutionDir)libs\SimpleFileWatcher\lib\Debug;$(SolutionDir)libs_win\glfw-3.2.1;$(SolutionDir)libs_win\glew-2.1.0;$(LibraryPath) 91 | 92 | 93 | true 94 | $(SolutionDir)libs\ffts\build\Debug;$(SolutionDir)libs\SimpleFileWatcher\lib\Debug;$(SolutionDir)libs_win\glfw-3.2.1;$(SolutionDir)libs_win\glew-2.1.0;$(LibraryPath) 95 | $(SolutionDir)$(Platform)\$(Configuration)\ 96 | $(Platform)\$(Configuration)\ 97 | 98 | 99 | false 100 | $(SolutionDir)libs\SimpleFileWatcher\lib\Release;$(SolutionDir)libs_win\glfw-3.2.1;$(SolutionDir)libs_win\glew-2.1.0;$(SolutionDir)libs\ffts\build\Release;$(LibraryPath) 101 | $(Platform)\$(Configuration)\ 102 | $(SolutionDir)$(Platform)\$(Configuration)\ 103 | 104 | 105 | false 106 | $(SolutionDir)libs\SimpleFileWatcher\lib\Release;$(SolutionDir)libs_win\glfw-3.2.1;$(SolutionDir)libs_win\glew-2.1.0;$(SolutionDir)libs\ffts\build\Release;$(LibraryPath) 107 | 108 | 109 | 110 | NotUsing 111 | Level3 112 | Disabled 113 | true 114 | GLEW_STATIC;WIN32;_DEBUG;_CONSOLE;WINDOWS;%(PreprocessorDefinitions) 115 | $(SolutionDir)src;$(SolutionDir)libs\catch2\include;$(SolutionDir)libs\ffts\include;$(SolutionDir)libs\rapidjson\include;$(SolutionDir)libs\SimpleFileWatcher\include 116 | stdcpp17 117 | false 118 | 119 | 120 | Console 121 | true 122 | ffts_staticd.lib;opengl32.lib;glew32s.lib;glfw3.lib;%(AdditionalDependencies) 123 | 124 | 125 | 126 | 127 | 128 | 129 | NotUsing 130 | Level3 131 | Disabled 132 | true 133 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 134 | stdcpp17 135 | false 136 | $(SolutionDir)src;$(SolutionDir)libs\catch2\include;$(SolutionDir)libs\ffts\include;$(SolutionDir)libs\rapidjson\include;$(SolutionDir)libs\SimpleFileWatcher\include 137 | 138 | 139 | Console 140 | true 141 | ffts_staticd.lib;opengl32.lib;glew32s.lib;glfw3.lib;%(AdditionalDependencies) 142 | 143 | 144 | 145 | 146 | NotUsing 147 | Level3 148 | MaxSpeed 149 | true 150 | true 151 | true 152 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 153 | stdcpp17 154 | false 155 | $(SolutionDir)src;$(SolutionDir)libs\catch2\include;$(SolutionDir)libs\ffts\include;$(SolutionDir)libs\rapidjson\include;$(SolutionDir)libs\SimpleFileWatcher\include 156 | 157 | 158 | Console 159 | true 160 | true 161 | true 162 | ffts_static.lib;opengl32.lib;glew32s.lib;glfw3.lib;%(AdditionalDependencies) 163 | 164 | 165 | 166 | 167 | NotUsing 168 | Level3 169 | MaxSpeed 170 | true 171 | true 172 | true 173 | GLEW_STATIC;WIN32;NDEBUG;_CONSOLE;WINDOWS;%(PreprocessorDefinitions) 174 | $(SolutionDir)src;$(SolutionDir)libs\catch2\include;$(SolutionDir)libs\ffts\include;$(SolutionDir)libs\rapidjson\include;$(SolutionDir)libs\SimpleFileWatcher\include 175 | MultiThreadedDLL 176 | stdcpp17 177 | false 178 | 179 | 180 | Console 181 | true 182 | true 183 | true 184 | %(AdditionalLibraryDirectories) 185 | ffts_static.lib;opengl32.lib;glew32s.lib;glfw3.lib;%(AdditionalDependencies) 186 | 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /tests/tests.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | --------------------------------------------------------------------------------