├── .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 | 
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 |
--------------------------------------------------------------------------------