├── .gitignore
├── CMakeLists.txt
├── README.md
├── demos
├── SoundWaveTests.vcxproj
├── SoundWaveTests.vcxproj.filters
├── SoundWaveTests.vcxproj.user
├── Test_001_BasicCompilation.cpp
├── Test_002_PGE_BasicSynth.cpp
├── Test_003_PGE_ClickToPlayWaves.cpp
├── Test_004_PGE_RawDataPlayback.cpp
├── assets
│ ├── SampleA.wav
│ ├── SampleB.wav
│ ├── SampleC.wav
│ ├── Sample_Test_44100.wav
│ └── Sample_Test_48000.wav
└── olcPixelGameEngine.h
├── olcSoundWaveEngine.h
└── source
├── olcSoundWaveEngine.h
├── olcSoundWaveEngine.sln
├── olcSoundWaveEngine.vcxproj
├── olcSoundWaveEngine.vcxproj.filters
├── olcSoundWaveEngine.vcxproj.user
├── olc_swe_template.h
├── swe_dummy.h
├── swe_prefix.h
├── swe_synth_envelope.cpp
├── swe_synth_envelope.h
├── swe_synth_modular.cpp
├── swe_synth_modular.h
├── swe_synth_osc.cpp
├── swe_synth_osc.h
├── swe_system_alsa.cpp
├── swe_system_alsa.h
├── swe_system_base.cpp
├── swe_system_base.h
├── swe_system_openal.cpp
├── swe_system_openal.h
├── swe_system_pulse.cpp
├── swe_system_pulse.h
├── swe_system_sdlmixer.cpp
├── swe_system_sdlmixer.h
├── swe_system_wasapi.cpp
├── swe_system_wasapi.h
├── swe_system_winmm.cpp
├── swe_system_winmm.h
├── swe_test_basics.cpp
├── swe_wave_engine.cpp
├── swe_wave_engine.h
├── swe_wave_file.cpp
├── swe_wave_file.h
├── swe_wave_view.cpp
├── swe_wave_view.h
├── swe_wave_wave.cpp
└── swe_wave_wave.h
/.gitignore:
--------------------------------------------------------------------------------
1 | # Prerequisites
2 | *.d
3 |
4 | # Compiled Object files
5 | *.slo
6 | *.lo
7 | *.o
8 | *.obj
9 |
10 | # Precompiled Headers
11 | *.gch
12 | *.pch
13 |
14 | # Compiled Dynamic libraries
15 | *.so
16 | *.dylib
17 | *.dll
18 |
19 | # Fortran module files
20 | *.mod
21 | *.smod
22 |
23 | # Compiled Static libraries
24 | *.lai
25 | *.la
26 | *.a
27 | *.lib
28 |
29 | # Executables
30 | *.exe
31 | *.out
32 | *.app
33 | /.vs
34 | .vscode
35 | /demos/x64/Debug
36 | /source/.vs/olcSoundWaveEngine
37 | /source/x64/Debug/olcSound.4ae3b973.tlog
38 | /source/x64/Debug
39 |
40 | # ignore binary of tools
41 | tools/headerify
42 | tools/headerify.exe
43 |
44 | # CMake Intermediates
45 | build
46 | CMakeCache.txt
47 | CMakeFiles
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # require version 3.10 or higher
2 | cmake_minimum_required(VERSION 3.10)
3 |
4 | project(olcSoundWaveEngine)
5 | option(USE_ALSA "Force using ALSA as audio backend (Linux-only)")
6 | option(USE_PULSEAUDIO "Force using PulseAudio as audio backend (Linux-only)")
7 | option(USE_SDL2_MIXER "Force using SDL2_mixer as audio backend")
8 |
9 | # Set C++ Standards
10 | set(CMAKE_CXX_STANDARD 17)
11 | set(CMAKE_CXX_STANDARD_REQUIRED ON)
12 | set(CMAKE_CXX_EXTENSIONS OFF)
13 |
14 | ######################################################################
15 | # Directories
16 |
17 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
18 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
19 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
20 |
21 | # We need to specify the output for each configuration to make it work
22 | # on Visual Studio solutions.
23 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/lib")
24 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/lib")
25 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/bin")
26 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/lib")
27 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/lib")
28 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/bin")
29 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/lib")
30 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/lib")
31 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin")
32 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_PROFILE "${CMAKE_BINARY_DIR}/lib")
33 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_PROFILE "${CMAKE_BINARY_DIR}/lib")
34 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_PROFILE "${CMAKE_BINARY_DIR}/bin")
35 |
36 | # set(SOURCE_CXX_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
37 |
38 | set(SOURCE_CXX_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/demos)
39 | set(SOURCE_DATA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/demos/assets)
40 |
41 | # Source Files are Curated Here
42 | file(
43 | GLOB_RECURSE _demoSourceFiles
44 | "${SOURCE_CXX_SRC_DIR}/*.cpp"
45 | )
46 |
47 | foreach(_demoSource IN LISTS _demoSourceFiles) #######################
48 |
49 | get_filename_component(OutputExecutable ${_demoSource} NAME_WE)
50 | add_executable(${OutputExecutable} ${_demoSource})
51 |
52 | ######################################################################
53 | # MacOS
54 | ######################################################################
55 | if(APPLE)
56 |
57 | # OpenGL
58 | set(OpenGL_GL_PREFERENCE LEGACY)
59 | find_package(OpenGL REQUIRED)
60 | include_directories(${OpenGL_INCLUDE_DIRS})
61 | target_link_libraries(${OutputExecutable} ${OpenGL_LIBRARIES} OpenGL::GL)
62 |
63 | # Carbon
64 | FIND_LIBRARY(CARBON_LIBRARY Carbon)
65 | target_link_libraries(${OutputExecutable} ${CARBON_LIBRARY})
66 |
67 | # GLUT
68 | find_package(GLUT REQUIRED)
69 | target_link_libraries(${OutputExecutable} ${GLUT_LIBRARIES})
70 |
71 | # Threads
72 | find_package(Threads REQUIRED)
73 | target_link_libraries(${OutputExecutable} Threads::Threads)
74 | include_directories(${Threads_INCLUDE_DIRS})
75 |
76 | # SDL2_mixer
77 | set(USE_SDL2_MIXER ON)
78 |
79 | find_package(PNG REQUIRED)
80 | target_link_libraries(${OutputExecutable} PNG::PNG)
81 | include_directories(${PNG_INCLUDE_DIRS})
82 |
83 | endif()
84 |
85 | ######################################################################
86 | # Windows: MinGW
87 | ######################################################################
88 | if(WIN32 AND MINGW)
89 |
90 | # OpenGL
91 | set(OpenGL_GL_PREFERENCE LEGACY)
92 | find_package(OpenGL REQUIRED)
93 | include_directories(${OpenGL_INCLUDE_DIRS})
94 | target_link_libraries(${OutputExecutable} ${OpenGL_LIBRARIES} OpenGL::GL)
95 |
96 | # GDI+
97 | set(GDIPLUS_LIBRARY gdiplus)
98 | target_link_libraries(${OutputExecutable} ${GDIPLUS_LIBRARY})
99 |
100 | # Shlwapi
101 | set(SHLWAPI_LIBRARY shlwapi)
102 | target_link_libraries(${OutputExecutable} ${SHLWAPI_LIBRARY})
103 |
104 | # Dwmapi
105 | set(DWMAPI_LIBRARY dwmapi)
106 | target_link_libraries(${OutputExecutable} ${DWMAPI_LIBRARY})
107 |
108 | if(NOT USE_SDL2_MIXER)
109 |
110 | # winmm
111 | set(WINMM_LIBRARY winmm)
112 | target_link_libraries(${OutputExecutable} ${WINMM_LIBRARY})
113 |
114 | endif() # NOT USE_SDL2_MIXER
115 |
116 |
117 | # stdc++fs
118 | target_link_libraries(${OutputExecutable} stdc++fs)
119 |
120 | endif() # WIN32 AND MINGW
121 |
122 | ######################################################################
123 | # Windows: Visual Studio / MSVC
124 | ######################################################################
125 | if(WIN32 AND MSVC)
126 |
127 | # OpenGL
128 | set(OpenGL_GL_PREFERENCE LEGACY)
129 | find_package(OpenGL REQUIRED)
130 | include_directories(${OpenGL_INCLUDE_DIRS})
131 | target_link_libraries(${OutputExecutable} ${OpenGL_LIBRARIES} OpenGL::GL)
132 |
133 | # set the startup project to the target executable instead of ALL_BUILD
134 | set_property(
135 | DIRECTORY
136 | ${CMAKE_CURRENT_SOURCE_DIR}
137 | PROPERTY
138 | VS_STARTUP_PROJECT
139 | ${OutputExecutable}
140 | )
141 |
142 | # set working directory for Visual Studio Debugger
143 | set_target_properties(
144 | ${OutputExecutable} PROPERTIES
145 | VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
146 | )
147 |
148 | # GDI+
149 | set(GDIPLUS_LIBRARY gdiplus)
150 | target_link_libraries(${OutputExecutable} ${GDIPLUS_LIBRARY})
151 |
152 | # Shlwapi
153 | set(SHLWAPI_LIBRARY shlwapi)
154 | target_link_libraries(${OutputExecutable} ${SHLWAPI_LIBRARY})
155 |
156 | # Dwmapi
157 | set(DWMAPI_LIBRARY dwmapi)
158 | target_link_libraries(${OutputExecutable} ${DWMAPI_LIBRARY})
159 |
160 | if(NOT USE_SDL2_MIXER)
161 |
162 | # winmm
163 | set(WINMM_LIBRARY winmm)
164 | target_link_libraries(${OutputExecutable} ${WINMM_LIBRARY})
165 |
166 | endif()
167 |
168 | endif() # WIN32 AND MSVC
169 |
170 | ######################################################################
171 | # Linux: using anything?
172 | ######################################################################
173 | if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN)
174 |
175 | # OpenGL
176 | set(OpenGL_GL_PREFERENCE LEGACY)
177 | find_package(OpenGL REQUIRED)
178 | include_directories(${OpenGL_INCLUDE_DIRS})
179 | target_link_libraries(${OutputExecutable} ${OpenGL_LIBRARIES} OpenGL::GL)
180 |
181 | # X11
182 | find_package(X11 REQUIRED)
183 | target_link_libraries(${OutputExecutable} X11::X11)
184 |
185 | include_directories(${X11_INCLUDE_DIRS})
186 |
187 | # Threads
188 | find_package(Threads REQUIRED)
189 | target_link_libraries(${OutputExecutable} Threads::Threads)
190 | include_directories(${Threads_INCLUDE_DIRS})
191 |
192 | # TODO: sanity checks
193 |
194 | if(USE_ALSA)
195 |
196 | # ALSA
197 | find_package(ALSA REQUIRED)
198 | target_link_libraries(${OutputExecutable} ALSA::ALSA)
199 | include_directories(${ALSA_INCLUDE_DIRS})
200 | add_compile_definitions(SOUNDWAVE_USING_ALSA=1)
201 |
202 | elseif(USE_SDL2_MIXER)
203 |
204 | # Because SDL2_mixer can be used on multiple platforms, we
205 | # defer it's inclusion until outside of the platform/toolchain
206 | # selection logic.
207 |
208 | else() # PulseAudio is Default
209 |
210 | # PulseAudio
211 | find_package(PulseAudio REQUIRED)
212 | target_link_libraries(${OutputExecutable} ${PULSEAUDIO_LIBRARY} pulse-simple)
213 | include_directories(${PULSEAUDIO_INCLUDE_DIR})
214 |
215 | add_compile_definitions(SOUNDWAVE_USING_PULSE=1)
216 |
217 | endif()
218 |
219 | find_package(PNG REQUIRED)
220 | target_link_libraries(${OutputExecutable} PNG::PNG)
221 | include_directories(${PNG_INCLUDE_DIRS})
222 |
223 | # stdc++fs
224 | target_link_libraries(${OutputExecutable} stdc++fs)
225 |
226 | endif() # Linux
227 |
228 | ######################################################################
229 | # Emscripten
230 | ######################################################################
231 | if (EMSCRIPTEN)
232 |
233 | # Generate an HTML file
234 | set(CMAKE_EXECUTABLE_SUFFIX .html)
235 |
236 | # SDL2_mixer: build cache
237 | execute_process(COMMAND "${EMSCRIPTEN_ROOT_PATH}/embuilder${EMCC_SUFFIX}" build sdl2_mixer)
238 |
239 | # libpng: build cache
240 | execute_process(COMMAND "${EMSCRIPTEN_ROOT_PATH}/embuilder${EMCC_SUFFIX}" build libpng)
241 |
242 | # zlib, for libpng: build cache
243 | execute_process(COMMAND "${EMSCRIPTEN_ROOT_PATH}/embuilder${EMCC_SUFFIX}" build zlib)
244 |
245 | # require libpng
246 | find_package(PNG REQUIRED)
247 | target_link_libraries(${OutputExecutable} PNG::PNG)
248 | include_directories(${PNG_INCLUDE_DIRS})
249 |
250 | if(EXISTS "${SOURCE_DATA_DIR}" AND IS_DIRECTORY "${SOURCE_DATA_DIR}")
251 | target_link_options(
252 | ${OutputExecutable}
253 | PRIVATE
254 | -sALLOW_MEMORY_GROWTH=1
255 | -sMAX_WEBGL_VERSION=2
256 | -sMIN_WEBGL_VERSION=2
257 | -sUSE_LIBPNG=1
258 | -sUSE_SDL_MIXER=2 # thanks for the s, cstd
259 | -sLLD_REPORT_UNDEFINED
260 | --preload-file ${SOURCE_DATA_DIR}@assets)
261 | else()
262 | target_link_options(
263 | ${OutputExecutable}
264 | PRIVATE
265 | -sALLOW_MEMORY_GROWTH=1
266 | -sMAX_WEBGL_VERSION=2
267 | -sMIN_WEBGL_VERSION=2
268 | -sUSE_LIBPNG=1
269 | -sUSE_SDL_MIXER=2 # thanks for the s, cstd
270 | -sLLD_REPORT_UNDEFINED)
271 | endif()
272 |
273 | endif() # Emscripten
274 |
275 |
276 | if(USE_SDL2_MIXER AND NOT EMSCRIPTEN)
277 |
278 | # SDL2_mixer
279 | find_package(SDL2_mixer REQUIRED)
280 | target_link_libraries(${OutputExecutable} SDL2_mixer::SDL2_mixer)
281 |
282 | add_compile_definitions(SOUNDWAVE_USING_SDLMIXER=1)
283 |
284 | endif() # USE_SDL2_MIXER
285 |
286 |
287 | ######################################################################
288 | # Set include directory
289 | ######################################################################
290 | if(IS_DIRECTORY ${SOURCE_CXX_INCLUDE_DIR})
291 | include_directories(${SOURCE_CXX_INCLUDE_DIR})
292 | endif()
293 |
294 | endforeach() #########################################################
295 |
296 |
297 | ######################################################################
298 | # Copy assets/ directory target
299 | ######################################################################
300 | set(DATA_OUTPUT_DIR ${CMAKE_BINARY_DIR}/bin/assets)
301 |
302 | file(GLOB_RECURSE src_data_files
303 | RELATIVE ${SOURCE_DATA_DIR}/ "${SOURCE_DATA_DIR}/*.*" "${SOURCE_DATA_DIR}/*")
304 | foreach(fn ${src_data_files})
305 | add_custom_command(
306 | OUTPUT ${DATA_OUTPUT_DIR}/${fn}
307 | COMMAND ${CMAKE_COMMAND} -E copy ${SOURCE_DATA_DIR}/${fn} ${DATA_OUTPUT_DIR}/${fn}
308 | MAIN_DEPENDENCY ${SOURCE_DATA_DIR}/${fn})
309 | list(APPEND out_data_files ${DATA_OUTPUT_DIR}/${fn})
310 | endforeach()
311 |
312 | add_custom_target(copy_data DEPENDS ${out_data_files})
313 |
314 | # Copy Asset Files, if not Emscripten
315 | if (NOT EMSCRIPTEN)
316 | foreach(_demoSource IN LISTS _demoSourceFiles) #######################
317 | get_filename_component(OutputExecutable ${_demoSource} NAME_WE)
318 | add_dependencies(${OutputExecutable} copy_data)
319 | endforeach()
320 | endif()
321 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # olcSoundWaveEngine
2 | Audio playback can be a bit tricky. This framework simplifies playback by
3 | * Reducing to single file include
4 | * Cross platform compatible
5 | * Compiles using emscripten for browser audio
6 | * Provides tools to playback wave files
7 | * Provides tools to synthesize sounds
8 |
9 | # License (OLC-3)
10 | Copyright 2018-2022 OneLoneCoder.com
11 |
12 | Redistribution and use in source and binary forms, with or without
13 | modification, are permitted provided that the following conditions
14 | are met:
15 |
16 | 1. Redistributions or derivations of source code must retain the above
17 | copyright notice, this list of conditions and the following disclaimer.
18 |
19 | 2. Redistributions or derivative works in binary form must reproduce
20 | the above copyright notice. This list of conditions and the following
21 | disclaimer must be reproduced in the documentation and/or other
22 | materials provided with the distribution.
23 |
24 | 3. Neither the name of the copyright holder nor the names of its
25 | contributors may be used to endorse or promote products derived
26 | from this software without specific prior written permission.
27 |
28 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
32 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
34 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
36 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 |
--------------------------------------------------------------------------------
/demos/SoundWaveTests.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 | true
28 | true
29 | true
30 | true
31 |
32 |
33 | true
34 | true
35 | true
36 | true
37 |
38 |
39 | true
40 | true
41 | true
42 | true
43 |
44 |
45 |
46 |
47 | 16.0
48 | Win32Proj
49 | {1eed794d-8b25-40d9-9650-4a4819ffa69c}
50 | SoundWaveTests
51 | 10.0
52 |
53 |
54 |
55 | Application
56 | true
57 | v143
58 | Unicode
59 |
60 |
61 | Application
62 | false
63 | v143
64 | true
65 | Unicode
66 |
67 |
68 | Application
69 | true
70 | v143
71 | Unicode
72 |
73 |
74 | Application
75 | false
76 | v143
77 | true
78 | Unicode
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | true
100 |
101 |
102 | false
103 |
104 |
105 | true
106 |
107 |
108 | false
109 |
110 |
111 |
112 | Level3
113 | true
114 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
115 | true
116 | stdcpp17
117 |
118 |
119 | Console
120 | true
121 |
122 |
123 | HeaderMaker.exe ..\source\olc_swe_template.h olcSoundWaveEngine.h
124 | Creating Deployable Single Header - olcSoundWaveEngine.h
125 |
126 |
127 | copy olcSoundWaveEngine.h ..\olcSoundWaveEngine.h
128 |
129 |
130 |
131 |
132 | Level3
133 | true
134 | true
135 | true
136 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
137 | true
138 | stdcpp17
139 |
140 |
141 | Console
142 | true
143 | true
144 | true
145 |
146 |
147 | HeaderMaker.exe ..\source\olc_swe_template.h olcSoundWaveEngine.h
148 | Creating Deployable Single Header - olcSoundWaveEngine.h
149 |
150 |
151 | copy olcSoundWaveEngine.h ..\olcSoundWaveEngine.h
152 |
153 |
154 |
155 |
156 | Level3
157 | true
158 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
159 | true
160 | stdcpp17
161 |
162 |
163 | Console
164 | true
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 | Level3
180 | true
181 | true
182 | true
183 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
184 | true
185 | stdcpp17
186 |
187 |
188 | Console
189 | true
190 | true
191 | true
192 |
193 |
194 | HeaderMaker.exe ..\source\olc_swe_template.h olcSoundWaveEngine.h
195 | Creating Deployable Single Header - olcSoundWaveEngine.h
196 |
197 |
198 | copy olcSoundWaveEngine.h ..\olcSoundWaveEngine.h
199 |
200 |
201 |
202 |
203 |
204 |
--------------------------------------------------------------------------------
/demos/SoundWaveTests.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
7 |
8 |
9 | {93995380-89BD-4b04-88EB-625FBE52EBFB}
10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
11 |
12 |
13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
15 |
16 |
17 |
18 |
19 | Header Files
20 |
21 |
22 | Header Files
23 |
24 |
25 |
26 |
27 | Source Files
28 |
29 |
30 | Source Files
31 |
32 |
33 | Source Files
34 |
35 |
36 | Source Files
37 |
38 |
39 |
--------------------------------------------------------------------------------
/demos/SoundWaveTests.vcxproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/demos/Test_001_BasicCompilation.cpp:
--------------------------------------------------------------------------------
1 |
2 | #define OLC_SOUNDWAVE
3 | #include "../olcSoundWaveEngine.h"
4 |
5 | olc::sound::WaveEngine engine;
6 |
7 | int main()
8 | {
9 | engine.InitialiseAudio();
10 | olc::sound::Wave w("./assets/SampleA.wav");
11 |
12 |
13 | w.file.data()[23] = 8;
14 |
15 |
16 | olc::sound::Wave wavCustom(2, sizeof(int16_t), 44100, 44100);
17 | for (size_t i = 0; i < 44100; i++)
18 | {
19 | double dt = 1.0 / 44100.0;
20 | double t = double(i) * dt;
21 | wavCustom.file.data()[i * wavCustom.file.channels() + 0] = float(0.5 * sin(2.0 * 440.0 * 3.14159 * t));
22 | wavCustom.file.data()[i * wavCustom.file.channels() + 1] = float(0.5 * sin(2.0 * 800.0 * 3.14159 * t));
23 | }
24 |
25 |
26 |
27 | engine.PlayWaveform(&wavCustom);
28 |
29 | using namespace std::chrono_literals;
30 | std::this_thread::sleep_for(5s);
31 |
32 |
33 | return 0;
34 | }
--------------------------------------------------------------------------------
/demos/Test_002_PGE_BasicSynth.cpp:
--------------------------------------------------------------------------------
1 | #define OLC_PGE_APPLICATION
2 | #include "olcPixelGameEngine.h"
3 |
4 | #define OLC_SOUNDWAVE
5 | #include "../olcSoundWaveEngine.h"
6 |
7 |
8 | class BasicSynthesizer : public olc::PixelGameEngine
9 | {
10 | public:
11 | BasicSynthesizer()
12 | {
13 | sAppName = "Basic Synthesizer";
14 | }
15 |
16 | protected: // Sound Specific
17 | olc::sound::WaveEngine engine;
18 |
19 | olc::sound::Wave sample1;
20 |
21 | // Make a modular synthesizer!
22 | olc::sound::synth::ModularSynth synth;
23 | olc::sound::synth::modules::Oscillator osc1;
24 | olc::sound::synth::modules::Oscillator osc2;
25 | olc::sound::synth::modules::Oscillator osc3;
26 |
27 | // Called once per device "whole sample" i.e. all channels
28 | // Useful for sequencing, controlling and visualisation
29 | void Synthesizer_OnNewCycleRequest(double dTime)
30 | {
31 | synth.UpdatePatches();
32 | }
33 |
34 | // Called individually per sample per channel
35 | // Useful for actual sound synthesis
36 | float Synthesizer_OnGetSample(uint32_t nChannel, double dTime)
37 | {
38 | synth.Update(nChannel, dTime, 1.0 / 44100.0);
39 | return osc1.output.value;
40 | }
41 |
42 | public:
43 | bool OnUserCreate() override
44 | {
45 | // Construct Modular Synthesizer
46 |
47 | sample1.LoadAudioWaveform("./assets/SampleB.wav");
48 |
49 | // Primary oscillator
50 | osc1.waveform = olc::sound::synth::modules::Oscillator::Type::Wave;
51 |
52 | osc1.pWave = &sample1;
53 |
54 | osc1.frequency = 5.0 / 20000.0;
55 | osc1.amplitude = 0.5;
56 | osc1.parameter = 0.5;
57 |
58 | // Secondary one acts as tremelo for primary
59 | osc2.frequency = 10.0 / 20000.0;
60 | osc2.amplitude = 0.5;
61 |
62 | // Third oscillator acts as LFO for primary
63 | osc3.frequency = 5.0 / 20000.0;
64 | osc3.amplitude = 0.0;
65 |
66 | synth.AddModule(&osc1);
67 | synth.AddModule(&osc2);
68 | synth.AddModule(&osc3);
69 | synth.AddPatch(&osc2.output, &osc1.amplitude);
70 | synth.AddPatch(&osc3.output, &osc1.lfo_input);
71 |
72 |
73 | engine.InitialiseAudio();
74 |
75 | engine.SetCallBack_NewSample([this](double dTime) {return Synthesizer_OnNewCycleRequest(dTime); });
76 | engine.SetCallBack_SynthFunction([this](uint32_t nChannel, double dTime) {return Synthesizer_OnGetSample(nChannel, dTime); });
77 |
78 | return true;
79 | }
80 |
81 | bool OnUserUpdate(float fElapsedTime) override
82 | {
83 | if (GetKey(olc::Key::Q).bHeld)
84 | osc1.frequency.value *= 1.0 + 1.0 * fElapsedTime;
85 |
86 | if (GetKey(olc::Key::A).bHeld)
87 | osc1.frequency.value *= 1.0 - 1.0 * fElapsedTime;
88 |
89 | if (GetKey(olc::Key::W).bHeld)
90 | osc2.frequency.value *= 1.0 + 1.0 * fElapsedTime;
91 |
92 | if (GetKey(olc::Key::S).bHeld)
93 | osc2.frequency.value *= 1.0 - 1.0 * fElapsedTime;
94 |
95 | if (GetKey(olc::Key::E).bHeld)
96 | osc3.frequency.value += +0.01 * fElapsedTime;
97 |
98 | if (GetKey(olc::Key::D).bHeld)
99 | osc3.frequency.value += -0.01 * fElapsedTime;
100 |
101 |
102 | Clear(olc::BLACK);
103 | DrawString({ 4, 4 }, "OSC1 Frequency [+Q,-A]: " + std::to_string(osc1.frequency.value * 20000.0));
104 | DrawString({ 4, 14 }, "OSC2 Frequency [+W,-S]: " + std::to_string(osc2.frequency.value * 20000.0));
105 | DrawString({ 4, 24 }, "OSC3 Frequency [+E,-D]: " + std::to_string(osc3.frequency.value * 20000.0));
106 | return true;
107 | }
108 | };
109 |
110 |
111 |
112 | int main()
113 | {
114 | BasicSynthesizer demo;
115 | if (demo.Construct(640, 480, 2, 2))
116 | demo.Start();
117 | return 0;
118 | }
--------------------------------------------------------------------------------
/demos/Test_003_PGE_ClickToPlayWaves.cpp:
--------------------------------------------------------------------------------
1 | #define OLC_PGE_APPLICATION
2 | #include "olcPixelGameEngine.h"
3 |
4 | #define OLC_SOUNDWAVE
5 | #include "../olcSoundWaveEngine.h"
6 |
7 |
8 | class Example003 : public olc::PixelGameEngine
9 | {
10 | public:
11 | Example003()
12 | {
13 | sAppName = "Testing Sample Rates";
14 | }
15 |
16 | protected: // Sound Specific
17 | olc::sound::WaveEngine engine;
18 |
19 | olc::sound::Wave sample_44100;
20 | olc::sound::Wave sample_48000;
21 |
22 |
23 | public:
24 | bool OnUserCreate() override
25 | {
26 | sample_44100.LoadAudioWaveform("./assets/Sample_Test_44100.wav");
27 | sample_48000.LoadAudioWaveform("./assets/Sample_Test_48000.wav");
28 | engine.InitialiseAudio(44100,2);
29 | return true;
30 | }
31 |
32 | bool OnUserUpdate(float fElapsedTime) override
33 | {
34 | if (GetKey(olc::Key::K1).bPressed)
35 | engine.PlayWaveform(&sample_44100);
36 |
37 | if (GetKey(olc::Key::K2).bPressed)
38 | engine.PlayWaveform(&sample_48000);
39 |
40 |
41 | Clear(olc::BLACK);
42 | DrawString({ 4, 4 }, "[1] Play 44100Hz x 16-bit Sample");
43 | DrawString({ 4, 14 }, "[2] Play 48000Hz x 24-bit Sample");
44 | return true;
45 | }
46 | };
47 |
48 |
49 |
50 | int main()
51 | {
52 | Example003 demo;
53 | if (demo.Construct(640, 480, 2, 2))
54 | demo.Start();
55 | return 0;
56 | }
--------------------------------------------------------------------------------
/demos/Test_004_PGE_RawDataPlayback.cpp:
--------------------------------------------------------------------------------
1 | #define OLC_PGE_APPLICATION
2 | #include "olcPixelGameEngine.h"
3 |
4 | #define OLC_SOUNDWAVE
5 | #include "../olcSoundWaveEngine.h"
6 |
7 |
8 | class Example004 : public olc::PixelGameEngine
9 | {
10 | public:
11 | Example004()
12 | {
13 | sAppName = "Testing Custom Wave Gen";
14 | }
15 |
16 | protected: // Sound Specific
17 | olc::sound::WaveEngine engine;
18 |
19 | olc::sound::Wave custom_wave;
20 |
21 | public:
22 | bool OnUserCreate() override
23 | {
24 | engine.InitialiseAudio(44100, 1);
25 |
26 | custom_wave = olc::sound::Wave(1, sizeof(uint8_t), 44100, 44100);
27 |
28 | for (size_t i = 0; i < 44100; i++)
29 | {
30 | double dt = 1.0 / 44100.0;
31 | double t = double(i) * dt;
32 | custom_wave.file.data()[i] = float(0.5 * sin(2.0 * 440.0 * 3.14159 * t));
33 | }
34 |
35 | return true;
36 | }
37 |
38 | bool OnUserUpdate(float fElapsedTime) override
39 | {
40 | if (GetKey(olc::Key::K1).bPressed)
41 | engine.PlayWaveform(&custom_wave);
42 |
43 | Clear(olc::BLACK);
44 | DrawString({ 4, 4 }, "[1] Play 44100Hz x 8-bit Custom Sample");
45 | return true;
46 | }
47 | };
48 |
49 |
50 |
51 | int main()
52 | {
53 | Example004 demo;
54 | if (demo.Construct(640, 480, 2, 2))
55 | demo.Start();
56 | return 0;
57 | }
--------------------------------------------------------------------------------
/demos/assets/SampleA.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneLoneCoder/olcSoundWaveEngine/e0e454c1e6316e51346206f3f3aef3fec2d20866/demos/assets/SampleA.wav
--------------------------------------------------------------------------------
/demos/assets/SampleB.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneLoneCoder/olcSoundWaveEngine/e0e454c1e6316e51346206f3f3aef3fec2d20866/demos/assets/SampleB.wav
--------------------------------------------------------------------------------
/demos/assets/SampleC.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneLoneCoder/olcSoundWaveEngine/e0e454c1e6316e51346206f3f3aef3fec2d20866/demos/assets/SampleC.wav
--------------------------------------------------------------------------------
/demos/assets/Sample_Test_44100.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneLoneCoder/olcSoundWaveEngine/e0e454c1e6316e51346206f3f3aef3fec2d20866/demos/assets/Sample_Test_44100.wav
--------------------------------------------------------------------------------
/demos/assets/Sample_Test_48000.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneLoneCoder/olcSoundWaveEngine/e0e454c1e6316e51346206f3f3aef3fec2d20866/demos/assets/Sample_Test_48000.wav
--------------------------------------------------------------------------------
/demos/olcPixelGameEngine.h:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OneLoneCoder/olcSoundWaveEngine/e0e454c1e6316e51346206f3f3aef3fec2d20866/demos/olcPixelGameEngine.h
--------------------------------------------------------------------------------
/olcSoundWaveEngine.h:
--------------------------------------------------------------------------------
1 | /*
2 | +-------------------------------------------------------------+
3 | | OneLoneCoder Sound Wave Engine v0.02 |
4 | | "You wanted noise? Well is this loud enough?" - javidx9 |
5 | +-------------------------------------------------------------+
6 |
7 | What is this?
8 | ~~~~~~~~~~~~~
9 | olc::SoundWaveEngine is a single file, cross platform audio
10 | interface for lightweight applications that just need a bit of
11 | easy audio manipulation.
12 |
13 | It's origins started in the olcNoiseMaker file that accompanied
14 | javidx9's "Code-It-Yourself: Synthesizer" series. It was refactored
15 | and absorbed into the "olcConsoleGameEngine.h" file, and then
16 | refactored again into olcPGEX_Sound.h, that was an extension to
17 | the awesome "olcPixelGameEngine.h" file.
18 |
19 | Alas, it went underused and began to rot, with many myths circulating
20 | that "it doesnt work" and "it shouldn't be used". These untruths
21 | made javidx9 feel sorry for the poor file, and he decided to breathe
22 | some new life into it, in anticipation of new videos!
23 |
24 | License (OLC-3)
25 | ~~~~~~~~~~~~~~~
26 |
27 | Copyright 2018 - 2022 OneLoneCoder.com
28 |
29 | Redistribution and use in source and binary forms, with or without
30 | modification, are permitted provided that the following conditions
31 | are met :
32 |
33 | 1. Redistributions or derivations of source code must retain the above
34 | copyright notice, this list of conditionsand the following disclaimer.
35 |
36 | 2. Redistributions or derivative works in binary form must reproduce
37 | the above copyright notice.This list of conditions and the following
38 | disclaimer must be reproduced in the documentation and /or other
39 | materials provided with the distribution.
40 |
41 | 3. Neither the name of the copyright holder nor the names of its
42 | contributors may be used to endorse or promote products derived
43 | from this software without specific prior written permission.
44 |
45 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
46 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
47 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
48 | A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT
49 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
50 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT
51 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
52 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
53 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
54 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
55 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
56 |
57 | Links
58 | ~~~~~
59 | YouTube: https://www.youtube.com/javidx9
60 | Discord: https://discord.gg/WhwHUMV
61 | Twitter: https://www.twitter.com/javidx9
62 | Twitch: https://www.twitch.tv/javidx9
63 | GitHub: https://www.github.com/onelonecoder
64 | Homepage: https://www.onelonecoder.com
65 | Patreon: https://www.patreon.com/javidx9
66 |
67 | Thanks
68 | ~~~~~~
69 | Gorbit99, Dragoneye, Puol
70 |
71 | Authors
72 | ~~~~~~~
73 | slavka, MaGetzUb, cstd, Moros1138 & javidx9
74 |
75 | (c)OneLoneCoder 2019, 2020, 2021, 2022
76 | */
77 |
78 |
79 | /*
80 | Using & Installing On Microsoft Windows
81 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
82 |
83 | Microsoft Visual Studio
84 | ~~~~~~~~~~~~~~~~~~~~~~~
85 | 1) Include the header file "olcSoundWaveEngine.h" from a .cpp file in your project.
86 | 2) That's it!
87 |
88 |
89 | Code::Blocks
90 | ~~~~~~~~~~~~
91 | 1) Make sure your compiler toolchain is NOT the default one installed with Code::Blocks. That
92 | one is old, out of date, and a general mess. Instead, use MSYS2 to install a recent and
93 | decent GCC toolchain, then configure Code::Blocks to use it
94 |
95 | Guide for installing recent GCC for Windows:
96 | https://www.msys2.org/
97 | Guide for configuring code::blocks:
98 | https://solarianprogrammer.com/2019/11/05/install-gcc-windows/
99 | https://solarianprogrammer.com/2019/11/16/install-codeblocks-gcc-windows-build-c-cpp-fortran-programs/
100 |
101 | 2) Include the header file "olcSoundWaveEngine.h" from a .cpp file in your project.
102 | 3) Add these libraries to "Linker Options": user32 winmm
103 | 4) Set this "Compiler Option": -std=c++17
104 | */
105 |
106 | /*
107 | Using & Installing On Linux
108 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
109 |
110 | GNU Compiler Collection (GCC)
111 | ~~~~~~~~~~~~~~~~~~~~~~~
112 | 1) Include the header file "olcSoundWaveEngine.h" from a .cpp file in your project.
113 | 2) Build with the following command:
114 |
115 | g++ olcSoundWaveEngineExample.cpp -o olcSoundWaveEngineExample -lpulse -lpulse-simple -std=c++17
116 |
117 | 3) That's it!
118 |
119 | */
120 |
121 | /*
122 | Using in multiple-file projects
123 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
124 | If you intend to use olcSoundWaveEngine across multiple files, it's important to only have one
125 | instance of the implementation. This is done using the compiler preprocessor definition: OLC_SOUNDWAVE
126 |
127 | This is defined typically before the header is included in teh translation unit you wish the implementation
128 | to be associated with. To avoid things getting messy I recommend you create a file "olcSoundWaveEngine.cpp"
129 | and that file includes ONLY the following code:
130 |
131 | #define OLC_SOUNDWAVE
132 | #include "olcSoundWaveEngine.h"
133 | */
134 |
135 | /*
136 | 0.01: olcPGEX_Sound.h reworked
137 | +Changed timekeeping to double, added accuracy fix - Thanks scripticuk
138 | +Concept of audio drivers and interface
139 | +All internal timing now double precision
140 | +All internal sampling now single precsion
141 | +Loading form WAV files
142 | +LERPed sampling from all buffers
143 | +Multi-channel audio support
144 | 0.02: +Support multi-channel wave files
145 | +Support for 24-bit wave files
146 | +Wave files are now sample rate invariant
147 | +Linux PulseAudio Updated
148 | +Linux ALSA Updated
149 | +WinMM Updated
150 | +CMake Compatibility
151 | =Fix wave format durations preventing playback
152 | =Various bug fixes
153 | */
154 |
155 | #pragma once
156 | #ifndef OLC_SOUNDWAVE_H
157 | #define OLC_SOUNDWAVE_H
158 |
159 | #include
160 | #include
161 | #include
162 | #include
163 | #include
164 | #include
165 | #include
166 | #include
167 | #include
168 | #include
169 | #include
170 | #include
171 | #include
172 | #include
173 |
174 | // Compiler/System Sensitivity
175 | #if !defined(SOUNDWAVE_USING_WINMM) && !defined(SOUNDWAVE_USING_WASAPI) && \
176 | !defined(SOUNDWAVE_USING_XAUDIO) && !defined(SOUNDWAVE_USING_OPENAL) && \
177 | !defined(SOUNDWAVE_USING_ALSA) && !defined(SOUNDWAVE_USING_SDLMIXER) && \
178 | !defined(SOUNDWAVE_USING_PULSE) \
179 |
180 | #if defined(_WIN32)
181 | #define SOUNDWAVE_USING_WINMM
182 | #endif
183 | #if defined(__linux__)
184 | #define SOUNDWAVE_USING_PULSE
185 | #endif
186 | #if defined(__APPLE__)
187 | #define SOUNDWAVE_USING_SDLMIXER
188 | #endif
189 | #if defined(__EMSCRIPTEN__)
190 | #define SOUNDWAVE_USING_SDLMIXER
191 | #endif
192 |
193 | #endif
194 |
195 | namespace olc::sound
196 | {
197 |
198 | namespace wave
199 | {
200 | // Physically represents a .WAV file, but the data is stored
201 | // as normalised floating point values
202 | template
203 | class File
204 | {
205 | public:
206 | File() = default;
207 |
208 | File(const size_t nChannels, const size_t nSampleSize, const size_t nSampleRate, const size_t nSamples)
209 | {
210 | m_nChannels = nChannels;
211 | m_nSampleSize = nSampleSize;
212 | m_nSamples = nSamples;
213 | m_nSampleRate = nSampleRate;
214 | m_dDuration = double(m_nSamples) / double(m_nSampleRate);
215 | m_dDurationInSamples = double(m_nSamples);
216 |
217 | m_pRawData = std::make_unique(m_nSamples * m_nChannels);
218 | }
219 |
220 | public:
221 | T* data() const
222 | {
223 | return m_pRawData.get();
224 | }
225 |
226 | size_t samples() const
227 | {
228 | return m_nSamples;
229 | }
230 |
231 | size_t channels() const
232 | {
233 | return m_nChannels;
234 | }
235 |
236 | size_t samplesize() const
237 | {
238 | return m_nSampleSize;
239 | }
240 |
241 | size_t samplerate() const
242 | {
243 | return m_nSampleRate;
244 | }
245 |
246 | double duration() const
247 | {
248 | return m_dDuration;
249 | }
250 |
251 | double durationInSamples() const
252 | {
253 | return m_dDurationInSamples;
254 | }
255 |
256 | bool LoadFile(const std::string& sFilename)
257 | {
258 | std::ifstream ifs(sFilename, std::ios::binary);
259 | if (!ifs.is_open())
260 | return false;
261 |
262 | struct WaveFormatHeader
263 | {
264 | uint16_t wFormatTag; /* format type */
265 | uint16_t nChannels; /* number of channels (i.e. mono, stereo...) */
266 | uint32_t nSamplesPerSec; /* sample rate */
267 | uint32_t nAvgBytesPerSec; /* for buffer estimation */
268 | uint16_t nBlockAlign; /* block size of data */
269 | uint16_t wBitsPerSample; /* number of bits per sample of mono data */
270 | };
271 |
272 | WaveFormatHeader header{ 0 };
273 |
274 | m_pRawData.reset();
275 |
276 | char dump[4];
277 | ifs.read(dump, sizeof(uint8_t) * 4); // Read "RIFF"
278 | if (strncmp(dump, "RIFF", 4) != 0) return false;
279 |
280 | ifs.read(dump, sizeof(uint8_t) * 4); // Not Interested
281 | ifs.read(dump, sizeof(uint8_t) * 4); // Read "WAVE"
282 | if (strncmp(dump, "WAVE", 4) != 0) return false;
283 |
284 | // Read Wave description chunk
285 | ifs.read(dump, sizeof(uint8_t) * 4); // Read "fmt "
286 | ifs.read(dump, sizeof(uint8_t) * 4); // Not Interested
287 | ifs.read((char*)&header, sizeof(WaveFormatHeader)); // Read Wave Format Structure chunk
288 |
289 | // Search for audio data chunk
290 | int32_t nChunksize = 0;
291 | ifs.read(dump, sizeof(uint8_t) * 4); // Read chunk header
292 | ifs.read((char*)&nChunksize, sizeof(uint32_t)); // Read chunk size
293 |
294 | while (strncmp(dump, "data", 4) != 0)
295 | {
296 | // Not audio data, so just skip it
297 | ifs.seekg(nChunksize, std::ios::cur);
298 | ifs.read(dump, sizeof(uint8_t) * 4); // Read next chunk header
299 | ifs.read((char*)&nChunksize, sizeof(uint32_t)); // Read next chunk size
300 | }
301 |
302 | // Finally got to data, so read it all in and convert to float samples
303 | m_nSampleSize = header.wBitsPerSample >> 3;
304 | m_nSamples = nChunksize / (header.nChannels * m_nSampleSize);
305 | m_nChannels = header.nChannels;
306 | m_nSampleRate = header.nSamplesPerSec;
307 | m_pRawData = std::make_unique(m_nSamples * m_nChannels);
308 | m_dDuration = double(m_nSamples) / double(m_nSampleRate);
309 | m_dDurationInSamples = double(m_nSamples);
310 |
311 | T* pSample = m_pRawData.get();
312 |
313 | // Read in audio data and normalise
314 | for (long i = 0; i < m_nSamples; i++)
315 | {
316 | for (int c = 0; c < m_nChannels; c++)
317 | {
318 | switch (m_nSampleSize)
319 | {
320 | case 1:
321 | {
322 | int8_t s = 0;
323 | ifs.read((char*)&s, sizeof(int8_t));
324 | *pSample = T(s) / T(std::numeric_limits::max());
325 | }
326 | break;
327 |
328 | case 2:
329 | {
330 | int16_t s = 0;
331 | ifs.read((char*)&s, sizeof(int16_t));
332 | *pSample = T(s) / T(std::numeric_limits::max());
333 | }
334 | break;
335 |
336 | case 3: // 24-bit
337 | {
338 | int32_t s = 0;
339 | ifs.read((char*)&s, 3);
340 | if (s & (1 << 23)) s |= 0xFF000000;
341 | *pSample = T(s) / T(std::pow(2, 23)-1);
342 | }
343 | break;
344 |
345 | case 4:
346 | {
347 | int32_t s = 0;
348 | ifs.read((char*)&s, sizeof(int32_t));
349 | *pSample = T(s) / T(std::numeric_limits::max());
350 | }
351 | break;
352 | }
353 |
354 | pSample++;
355 | }
356 | }
357 | return true;
358 | }
359 |
360 | bool SaveFile(const std::string& sFilename)
361 | {
362 | return false;
363 | }
364 |
365 |
366 | protected:
367 | std::unique_ptr m_pRawData;
368 | size_t m_nSamples = 0;
369 | size_t m_nChannels = 0;
370 | size_t m_nSampleRate = 0;
371 | size_t m_nSampleSize = 0;
372 | double m_dDuration = 0.0;
373 | double m_dDurationInSamples = 0.0;
374 | };
375 |
376 | template
377 | class View
378 | {
379 | public:
380 | View() = default;
381 |
382 | View(const T* pData, const size_t nSamples)
383 | {
384 | SetData(pData, nSamples);
385 | }
386 |
387 | public:
388 | void SetData(T const* pData, const size_t nSamples, const size_t nStride = 1, const size_t nOffset = 0)
389 | {
390 | m_pData = pData;
391 | m_nSamples = nSamples;
392 | m_nStride = nStride;
393 | m_nOffset = nOffset;
394 | }
395 |
396 | double GetSample(const double dSample) const
397 | {
398 | double d1 = std::floor(dSample);
399 | size_t p1 = static_cast(d1);
400 | size_t p2 = p1 + 1;
401 |
402 | double t = dSample - d1;
403 | double a = GetValue(p1);
404 | double b = GetValue(p2);
405 |
406 | return a + t * (b - a); // std::lerp in C++20
407 | }
408 |
409 | std::pair GetRange(const double dSample1, const double dSample2) const
410 | {
411 | if (dSample1 < 0 || dSample2 < 0)
412 | return { 0,0 };
413 |
414 | if (dSample1 > m_nSamples && dSample2 > m_nSamples)
415 | return { 0,0 };
416 |
417 | double dMin, dMax;
418 |
419 | double d = GetSample(dSample1);
420 | dMin = dMax = d;
421 |
422 | size_t n1 = static_cast(std::ceil(dSample1));
423 | size_t n2 = static_cast(std::floor(dSample2));
424 | for (size_t n = n1; n < n2; n++)
425 | {
426 | d = GetValue(n);
427 | dMin = std::min(dMin, d);
428 | dMax = std::max(dMax, d);
429 | }
430 |
431 | d = GetSample(dSample2);
432 | dMin = std::min(dMin, d);
433 | dMax = std::max(dMax, d);
434 |
435 | return { dMin, dMax };
436 | }
437 |
438 | T GetValue(const size_t nSample) const
439 | {
440 | if (nSample >= m_nSamples)
441 | return 0;
442 | else
443 | return m_pData[m_nOffset + nSample * m_nStride];
444 | }
445 |
446 | private:
447 | const T* m_pData = nullptr;
448 | size_t m_nSamples = 0;
449 | size_t m_nStride = 1;
450 | size_t m_nOffset = 0;
451 | };
452 | }
453 |
454 | template
455 | class Wave_generic
456 | {
457 | public:
458 | Wave_generic() = default;
459 | Wave_generic(std::string sWavFile) { LoadAudioWaveform(sWavFile); }
460 | Wave_generic(std::istream& sStream) { LoadAudioWaveform(sStream); }
461 | Wave_generic(const char* pData, const size_t nBytes) { LoadAudioWaveform(pData, nBytes); }
462 |
463 | Wave_generic(const size_t nChannels, const size_t nSampleSize, const size_t nSampleRate, const size_t nSamples)
464 | {
465 | vChannelView.clear();
466 | file = wave::File(nChannels, nSampleSize, nSampleRate, nSamples);
467 | vChannelView.resize(file.channels());
468 | for (uint32_t c = 0; c < file.channels(); c++)
469 | vChannelView[c].SetData(file.data(), file.samples(), file.channels(), c);
470 | }
471 |
472 | bool LoadAudioWaveform(std::string sWavFile)
473 | {
474 | vChannelView.clear();
475 |
476 | if (file.LoadFile(sWavFile))
477 | {
478 | // Setup views for each channel
479 | vChannelView.resize(file.channels());
480 | for (uint32_t c = 0; c < file.channels(); c++)
481 | vChannelView[c].SetData(file.data(), file.samples(), file.channels(), c);
482 |
483 | return true;
484 | }
485 |
486 | return false;
487 | }
488 |
489 |
490 |
491 | bool LoadAudioWaveform(std::istream& sStream) { return false; }
492 | bool LoadAudioWaveform(const char* pData, const size_t nBytes) { return false; }
493 |
494 | std::vector> vChannelView;
495 | wave::File file;
496 | };
497 |
498 | typedef Wave_generic Wave;
499 |
500 | struct WaveInstance
501 | {
502 | Wave* pWave = nullptr;
503 | double dInstanceTime = 0.0;
504 | double dDuration = 0.0;
505 | double dSpeedModifier = 1.0;
506 | bool bFinished = false;
507 | bool bLoop = false;
508 | bool bFlagForStop = false;
509 | };
510 |
511 | typedef std::list::iterator PlayingWave;
512 |
513 | namespace driver
514 | {
515 | class Base;
516 | }
517 |
518 | // Container class for Basic Sound Manipulation
519 | class WaveEngine
520 | {
521 |
522 | public:
523 | WaveEngine();
524 | virtual ~WaveEngine();
525 |
526 | // Configure Audio Hardware
527 | bool InitialiseAudio(uint32_t nSampleRate = 44100, uint32_t nChannels = 1, uint32_t nBlocks = 8, uint32_t nBlockSamples = 512);
528 |
529 | // Release Audio Hardware
530 | bool DestroyAudio();
531 |
532 | // Call to get the names of all the devices capable of audio output - DACs. An entry
533 | // from the returned collection can be specified as the device to use in UseOutputDevice()
534 | std::vector GetOutputDevices();
535 |
536 | // Specify a device for audio output prior to calling InitialiseAudio()
537 | void UseOutputDevice(const std::string& sDeviceOut);
538 |
539 | // Call to get the names of all the devices capable of audio input - ADCs. An entry
540 | // from the returned collection can be specified as the device to use in UseInputDevice()
541 | std::vector GetInputDevices();
542 |
543 | // Specify a device for audio input prior to calling InitialiseAudio()
544 | void UseInputDevice(const std::string& sDeviceOut);
545 |
546 |
547 | void SetCallBack_NewSample(std::function func);
548 | void SetCallBack_SynthFunction(std::function func);
549 | void SetCallBack_FilterFunction(std::function func);
550 |
551 | public:
552 | void SetOutputVolume(const float fVolume);
553 |
554 |
555 |
556 | PlayingWave PlayWaveform(Wave* pWave, bool bLoop = false, double dSpeed = 1.0);
557 | void StopWaveform(const PlayingWave& w);
558 | void StopAll();
559 |
560 | private:
561 | uint32_t FillOutputBuffer(std::vector& vBuffer, const uint32_t nBufferOffset, const uint32_t nRequiredSamples);
562 |
563 | private:
564 | std::unique_ptr m_driver;
565 | std::function m_funcNewSample;
566 | std::function m_funcUserSynth;
567 | std::function m_funcUserFilter;
568 |
569 |
570 | private:
571 | uint32_t m_nSampleRate = 44100;
572 | uint32_t m_nChannels = 1;
573 | uint32_t m_nBlocks = 8;
574 | uint32_t m_nBlockSamples = 512;
575 | double m_dSamplePerTime = 44100.0;
576 | double m_dTimePerSample = 1.0 / 44100;
577 | double m_dGlobalTime = 0.0;
578 | float m_fOutputVolume = 1.0;
579 |
580 | std::string m_sInputDevice;
581 | std::string m_sOutputDevice;
582 |
583 | private:
584 | std::list m_listWaves;
585 |
586 | public:
587 | uint32_t GetSampleRate() const;
588 | uint32_t GetChannels() const;
589 | uint32_t GetBlocks() const;
590 | uint32_t GetBlockSampleCount() const;
591 | double GetTimePerSample() const;
592 |
593 |
594 | // Friends, for access to FillOutputBuffer from Drivers
595 | friend class driver::Base;
596 |
597 | };
598 |
599 | namespace driver
600 | {
601 | // DRIVER DEVELOPERS ONLY!!!
602 | //
603 | // This interface allows SoundWave to exchange data with OS audio systems. It
604 | // is not intended of use by regular users.
605 | class Base
606 | {
607 | public:
608 | Base(WaveEngine* pHost);
609 | virtual ~Base();
610 |
611 | public:
612 | // [IMPLEMENT] Opens a connection to the hardware device, returns true if success
613 | virtual bool Open(const std::string& sOutputDevice, const std::string& sInputDevice);
614 | // [IMPLEMENT] Starts a process that repeatedly requests audio, returns true if success
615 | virtual bool Start();
616 | // [IMPLEMENT] Stops a process form requesting audio
617 | virtual void Stop();
618 | // [IMPLEMENT] Closes any connections to hardware devices
619 | virtual void Close();
620 |
621 | virtual std::vector EnumerateOutputDevices();
622 | virtual std::vector EnumerateInputDevices();
623 |
624 | protected:
625 | // [IMPLEMENT IF REQUIRED] Called by driver to exchange data with SoundWave System. Your
626 | // implementation will call this function providing a "DAC" buffer to be filled by
627 | // SoundWave from a buffer of floats filled by the user.
628 | void ProcessOutputBlock(std::vector& vFloatBuffer, std::vector& vDACBuffer);
629 |
630 | // [IMPLEMENT IF REQUIRED] Called by driver to exchange data with SoundWave System.
631 | void GetFullOutputBlock(std::vector& vFloatBuffer);
632 |
633 | // Handle to SoundWave, to interrogate optons, and get user data
634 | WaveEngine* m_pHost = nullptr;
635 | };
636 | }
637 |
638 |
639 | namespace synth
640 | {
641 | class Property
642 | {
643 | public:
644 | double value = 0.0f;
645 |
646 | public:
647 | Property() = default;
648 | Property(double f);
649 |
650 | public:
651 | Property& operator =(const double f);
652 | };
653 |
654 |
655 | class Trigger
656 | {
657 |
658 | };
659 |
660 |
661 | class Module
662 | {
663 | public:
664 | virtual void Update(uint32_t nChannel, double dTime, double dTimeStep) = 0;
665 | };
666 |
667 |
668 | class ModularSynth
669 | {
670 | public:
671 | ModularSynth();
672 |
673 | public:
674 | bool AddModule(Module* pModule);
675 | bool RemoveModule(Module* pModule);
676 | bool AddPatch(Property* pInput, Property* pOutput);
677 | bool RemovePatch(Property* pInput, Property* pOutput);
678 |
679 |
680 | public:
681 | void UpdatePatches();
682 | void Update(uint32_t nChannel, double dTime, double dTimeStep);
683 |
684 | protected:
685 | std::vector m_vModules;
686 | std::vector> m_vPatches;
687 | };
688 |
689 |
690 | namespace modules
691 | {
692 | class Oscillator : public Module
693 | {
694 | public:
695 | enum class Type
696 | {
697 | Sine,
698 | Saw,
699 | Square,
700 | Triangle,
701 | PWM,
702 | Wave,
703 | Noise,
704 | };
705 |
706 | public:
707 | // Primary frequency of oscillation
708 | Property frequency = 0.0f;
709 | // Primary amplitude of output
710 | Property amplitude = 1.0f;
711 | // LFO input if required
712 | Property lfo_input = 0.0f;
713 | // Primary Output
714 | Property output;
715 | // Tweakable Parameter
716 | Property parameter = 0.0;
717 |
718 | Type waveform = Type::Sine;
719 |
720 | Wave* pWave = nullptr;
721 |
722 | private:
723 | double phase_acc = 0.0f;
724 | double max_frequency = 20000.0;
725 | uint32_t random_seed = 0xB00B1E5;
726 |
727 | double rndDouble(double min, double max);
728 | uint32_t rnd();
729 |
730 |
731 | public:
732 | virtual void Update(uint32_t nChannel, double dTime, double dTimeStep) override;
733 |
734 | };
735 | }
736 | }
737 |
738 |
739 | }
740 |
741 | #if defined(SOUNDWAVE_USING_WINMM)
742 | #define _WIN32_LEAN_AND_MEAN
743 | #include
744 | #undef min
745 | #undef max
746 |
747 | namespace olc::sound::driver
748 | {
749 | class WinMM : public Base
750 | {
751 | public:
752 | WinMM(WaveEngine* pHost);
753 | ~WinMM();
754 |
755 | protected:
756 | bool Open(const std::string& sOutputDevice, const std::string& sInputDevice) override;
757 | bool Start() override;
758 | void Stop() override;
759 | void Close() override;
760 |
761 | private:
762 | void DriverLoop();
763 | void FreeAudioBlock();
764 | static void CALLBACK waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2);
765 | HWAVEOUT m_hwDevice = nullptr;
766 | std::thread m_thDriverLoop;
767 | std::atomic m_bDriverLoopActive{ false };
768 | std::unique_ptr[]> m_pvBlockMemory;
769 | std::unique_ptr m_pWaveHeaders;
770 | std::atomic m_nBlockFree = 0;
771 | std::condition_variable m_cvBlockNotZero;
772 | std::mutex m_muxBlockNotZero;
773 | uint32_t m_nBlockCurrent = 0;
774 | };
775 | }
776 | #endif // SOUNDWAVE_USING_WINMM
777 |
778 | #if defined(SOUNDWAVE_USING_SDLMIXER)
779 |
780 | #if defined(__EMSCRIPTEN__)
781 | #include
782 | #else
783 | #include
784 | #endif
785 |
786 | namespace olc::sound::driver
787 | {
788 | class SDLMixer final : public Base
789 | {
790 | public:
791 | explicit SDLMixer(WaveEngine* pHost);
792 | ~SDLMixer() final;
793 |
794 | protected:
795 | bool Open(const std::string& sOutputDevice, const std::string& sInputDevice) final;
796 | bool Start() final;
797 | void Stop() final;
798 | void Close() final;
799 |
800 | private:
801 | void FillChunkBuffer(const std::vector& userData) const;
802 |
803 | static void SDLMixerCallback(int channel);
804 |
805 | private:
806 | bool m_keepRunning = false;
807 | Uint16 m_haveFormat = AUDIO_F32SYS;
808 | std::vector audioBuffer;
809 | Mix_Chunk audioChunk;
810 |
811 | static SDLMixer* instance;
812 | };
813 | }
814 |
815 | #endif // SOUNDWAVE_USING_SDLMIXER
816 |
817 | #if defined(SOUNDWAVE_USING_ALSA)
818 | #include
819 | #include
820 | #include
821 |
822 | namespace olc::sound::driver
823 | {
824 | // Not thread-safe
825 | template
826 | class RingBuffer
827 | {
828 | public:
829 | RingBuffer()
830 | { }
831 |
832 | void Resize(unsigned int bufnum = 0, unsigned int buflen = 0)
833 | {
834 | m_vBuffers.resize(bufnum);
835 | for (auto &vBuffer : m_vBuffers)
836 | vBuffer.resize(buflen);
837 | }
838 |
839 | std::vector& GetFreeBuffer()
840 | {
841 | assert(!IsFull());
842 |
843 | std::vector& result = m_vBuffers[m_nTail];
844 | m_nTail = Next(m_nTail);
845 | return result;
846 | }
847 |
848 | std::vector& GetFullBuffer()
849 | {
850 | assert(!IsEmpty());
851 |
852 | std::vector& result = m_vBuffers[m_nHead];
853 | m_nHead = Next(m_nHead);
854 | return result;
855 | }
856 |
857 | bool IsEmpty()
858 | {
859 | return m_nHead == m_nTail;
860 | }
861 |
862 | bool IsFull()
863 | {
864 | return m_nHead == Next(m_nTail);
865 | }
866 |
867 | private:
868 | unsigned int Next(unsigned int current)
869 | {
870 | return (current + 1) % m_vBuffers.size();
871 | }
872 |
873 | std::vector> m_vBuffers;
874 | unsigned int m_nHead = 0;
875 | unsigned int m_nTail = 0;
876 | };
877 |
878 | class ALSA : public Base
879 | {
880 | public:
881 | ALSA(WaveEngine* pHost);
882 | ~ALSA();
883 |
884 | protected:
885 | bool Open(const std::string& sOutputDevice, const std::string& sInputDevice) override;
886 | bool Start() override;
887 | void Stop() override;
888 | void Close() override;
889 |
890 | private:
891 | void DriverLoop();
892 |
893 | snd_pcm_t *m_pPCM;
894 | RingBuffer m_rBuffers;
895 | std::atomic m_bDriverLoopActive{ false };
896 | std::thread m_thDriverLoop;
897 | };
898 | }
899 | #endif // SOUNDWAVE_USING_ALSA
900 |
901 | #if defined(SOUNDWAVE_USING_PULSE)
902 | #include
903 |
904 | namespace olc::sound::driver
905 | {
906 | class PulseAudio : public Base
907 | {
908 | public:
909 | PulseAudio(WaveEngine* pHost);
910 | ~PulseAudio();
911 |
912 | protected:
913 | bool Open(const std::string& sOutputDevice, const std::string& sInputDevice) override;
914 | bool Start() override;
915 | void Stop() override;
916 | void Close() override;
917 |
918 | private:
919 | void DriverLoop();
920 |
921 | pa_simple *m_pPA;
922 | std::atomic m_bDriverLoopActive{ false };
923 | std::thread m_thDriverLoop;
924 | };
925 | }
926 | #endif // SOUNDWAVE_USING_PULSE
927 |
928 | #ifdef OLC_SOUNDWAVE
929 | #undef OLC_SOUNDWAVE
930 |
931 | namespace olc::sound
932 | {
933 | WaveEngine::WaveEngine()
934 | {
935 | m_sInputDevice = "NONE";
936 | m_sOutputDevice = "DEFAULT";
937 |
938 | #if defined(SOUNDWAVE_USING_WINMM)
939 | m_driver = std::make_unique(this);
940 | #endif
941 |
942 | #if defined(SOUNDWAVE_USING_WASAPI)
943 | m_driver = std::make_unique(this);
944 | #endif
945 |
946 | #if defined(SOUNDWAVE_USING_XAUDIO)
947 | m_driver = std::make_unique(this);
948 | #endif
949 |
950 | #if defined(SOUNDWAVE_USING_OPENAL)
951 | m_driver = std::make_unique(this);
952 | #endif
953 |
954 | #if defined(SOUNDWAVE_USING_ALSA)
955 | m_driver = std::make_unique(this);
956 | #endif
957 |
958 | #if defined(SOUNDWAVE_USING_SDLMIXER)
959 | m_driver = std::make_unique(this);
960 | #endif
961 |
962 | #if defined(SOUNDWAVE_USING_PULSE)
963 | m_driver = std::make_unique(this);
964 | #endif
965 | }
966 |
967 | WaveEngine::~WaveEngine()
968 | {
969 | DestroyAudio();
970 | }
971 |
972 | std::vector WaveEngine::GetOutputDevices()
973 | {
974 | return { "XXX" };
975 | }
976 |
977 |
978 | void WaveEngine::UseOutputDevice(const std::string& sDeviceOut)
979 | {
980 | m_sOutputDevice = sDeviceOut;
981 | }
982 |
983 | std::vector WaveEngine::GetInputDevices()
984 | {
985 | return { "XXX" };
986 | }
987 |
988 | void WaveEngine::UseInputDevice(const std::string& sDeviceIn)
989 | {
990 | m_sInputDevice = sDeviceIn;
991 | }
992 |
993 | bool WaveEngine::InitialiseAudio(uint32_t nSampleRate, uint32_t nChannels, uint32_t nBlocks, uint32_t nBlockSamples)
994 | {
995 | m_nSampleRate = nSampleRate;
996 | m_nChannels = nChannels;
997 | m_nBlocks = nBlocks;
998 | m_nBlockSamples = nBlockSamples;
999 | m_dSamplePerTime = double(nSampleRate);
1000 | m_dTimePerSample = 1.0 / double(nSampleRate);
1001 | m_driver->Open(m_sOutputDevice, m_sInputDevice);
1002 | m_driver->Start();
1003 | return false;
1004 | }
1005 |
1006 |
1007 | bool WaveEngine::DestroyAudio()
1008 | {
1009 | StopAll();
1010 | m_driver->Stop();
1011 | m_driver->Close();
1012 | return false;
1013 | }
1014 |
1015 | void WaveEngine::SetCallBack_NewSample(std::function func)
1016 | {
1017 | m_funcNewSample = func;
1018 | }
1019 |
1020 | void WaveEngine::SetCallBack_SynthFunction(std::function func)
1021 | {
1022 | m_funcUserSynth = func;
1023 | }
1024 |
1025 | void WaveEngine::SetCallBack_FilterFunction(std::function func)
1026 | {
1027 | m_funcUserFilter = func;
1028 | }
1029 |
1030 | PlayingWave WaveEngine::PlayWaveform(Wave* pWave, bool bLoop, double dSpeed)
1031 | {
1032 | WaveInstance wi;
1033 | wi.bLoop = bLoop;
1034 | wi.pWave = pWave;
1035 | wi.dSpeedModifier = dSpeed * double(pWave->file.samplerate()) / m_dSamplePerTime;
1036 | wi.dDuration = pWave->file.duration() / dSpeed;
1037 | wi.dInstanceTime = m_dGlobalTime;
1038 | m_listWaves.push_back(wi);
1039 | return std::prev(m_listWaves.end());
1040 | }
1041 |
1042 | void WaveEngine::StopWaveform(const PlayingWave& w)
1043 | {
1044 | w->bFlagForStop = true;
1045 | }
1046 |
1047 | void WaveEngine::StopAll()
1048 | {
1049 | for (auto& wave : m_listWaves)
1050 | {
1051 | wave.bFlagForStop = true;
1052 | }
1053 | }
1054 |
1055 | void WaveEngine::SetOutputVolume(const float fVolume)
1056 | {
1057 | m_fOutputVolume = std::clamp(fVolume, 0.0f, 1.0f);
1058 | }
1059 |
1060 | uint32_t WaveEngine::FillOutputBuffer(std::vector& vBuffer, const uint32_t nBufferOffset, const uint32_t nRequiredSamples)
1061 | {
1062 | for (uint32_t nSample = 0; nSample < nRequiredSamples; nSample++)
1063 | {
1064 | double dSampleTime = m_dGlobalTime + nSample * m_dTimePerSample;
1065 |
1066 | if (m_funcNewSample)
1067 | m_funcNewSample(dSampleTime);
1068 |
1069 | for (uint32_t nChannel = 0; nChannel < m_nChannels; nChannel++)
1070 | {
1071 | // Construct the sample
1072 | float fSample = 0.0f;
1073 |
1074 | // 1) Sample any active waves
1075 | for (auto& wave : m_listWaves)
1076 | {
1077 | // Is wave instance flagged for stopping?
1078 | if (wave.bFlagForStop)
1079 | {
1080 | wave.bFinished = true;
1081 | }
1082 | else
1083 | {
1084 | // Calculate offset into wave instance
1085 | double dTimeOffset = dSampleTime - wave.dInstanceTime;
1086 |
1087 | // If offset is larger than wave then...
1088 | if (dTimeOffset >= wave.dDuration)
1089 | {
1090 | if (wave.bLoop)
1091 | {
1092 | // ...if looping, reset the wave instance
1093 | wave.dInstanceTime = dSampleTime;
1094 | }
1095 | else
1096 | {
1097 | // ...if not looping, flag wave instance as dead
1098 | wave.bFinished = true;
1099 | }
1100 | }
1101 | else
1102 | {
1103 | // OR, sample the waveform from the correct channel
1104 | fSample += float(wave.pWave->vChannelView[nChannel % wave.pWave->file.channels()].GetSample(dTimeOffset * m_dSamplePerTime * wave.dSpeedModifier));
1105 | }
1106 | }
1107 | }
1108 |
1109 | // Remove waveform instances that have finished
1110 | m_listWaves.remove_if([](const WaveInstance& wi) {return wi.bFinished; });
1111 |
1112 |
1113 | // 2) If user is synthesizing, request sample
1114 | if (m_funcUserSynth)
1115 | fSample += m_funcUserSynth(nChannel, dSampleTime);
1116 |
1117 | // 3) Apply global filters
1118 |
1119 |
1120 | // 4) If user is filtering, allow manipulation of output
1121 | if (m_funcUserFilter)
1122 | fSample = m_funcUserFilter(nChannel, dSampleTime, fSample);
1123 |
1124 | // Place sample in buffer
1125 | vBuffer[nBufferOffset + nSample * m_nChannels + nChannel] = fSample * m_fOutputVolume;
1126 | }
1127 | }
1128 |
1129 | // UPdate global time, accounting for error (thanks scripticuk)
1130 | m_dGlobalTime += nRequiredSamples * m_dTimePerSample;
1131 | return nRequiredSamples;
1132 | }
1133 |
1134 |
1135 | uint32_t WaveEngine::GetSampleRate() const
1136 | {
1137 | return m_nSampleRate;
1138 | }
1139 |
1140 | uint32_t WaveEngine::GetChannels() const
1141 | {
1142 | return m_nChannels;
1143 | }
1144 |
1145 | uint32_t WaveEngine::GetBlocks() const
1146 | {
1147 | return m_nBlocks;
1148 | }
1149 |
1150 | uint32_t WaveEngine::GetBlockSampleCount() const
1151 | {
1152 | return m_nBlockSamples;
1153 | }
1154 |
1155 | double WaveEngine::GetTimePerSample() const
1156 | {
1157 | return m_dTimePerSample;
1158 | }
1159 |
1160 | namespace driver
1161 | {
1162 | Base::Base(olc::sound::WaveEngine* pHost) : m_pHost(pHost)
1163 | {}
1164 |
1165 | Base::~Base()
1166 | {}
1167 |
1168 | bool Base::Open(const std::string& sOutputDevice, const std::string& sInputDevice)
1169 | {
1170 | return false;
1171 | }
1172 |
1173 | bool Base::Start()
1174 | {
1175 | return false;
1176 | }
1177 |
1178 | void Base::Stop()
1179 | {
1180 | }
1181 |
1182 | void Base::Close()
1183 | {
1184 | }
1185 |
1186 | std::vector Base::EnumerateOutputDevices()
1187 | {
1188 | return { "DEFAULT" };
1189 | }
1190 |
1191 | std::vector Base::EnumerateInputDevices()
1192 | {
1193 | return { "NONE" };
1194 | }
1195 |
1196 | void Base::ProcessOutputBlock(std::vector& vFloatBuffer, std::vector& vDACBuffer)
1197 | {
1198 | constexpr float fMaxSample = float(std::numeric_limits::max());
1199 | constexpr float fMinSample = float(std::numeric_limits::min());
1200 |
1201 | // So... why not use vFloatBuffer.size()? Well with this implementation
1202 | // we can, but i suspect there may be some platforms that request a
1203 | // specific number of samples per "loop" rather than this block architecture
1204 | uint32_t nSamplesToProcess = m_pHost->GetBlockSampleCount();
1205 | uint32_t nSampleOffset = 0;
1206 | while (nSamplesToProcess > 0)
1207 | {
1208 | uint32_t nSamplesGathered = m_pHost->FillOutputBuffer(vFloatBuffer, nSampleOffset, nSamplesToProcess);
1209 |
1210 | // Vector is in float32 format, so convert to hardware required format
1211 | for (uint32_t n = 0; n < nSamplesGathered; n++)
1212 | {
1213 | for (uint32_t c = 0; c < m_pHost->GetChannels(); c++)
1214 | {
1215 | size_t nSampleID = nSampleOffset + (n * m_pHost->GetChannels() + c);
1216 | vDACBuffer[nSampleID] = short(std::clamp(vFloatBuffer[nSampleID] * fMaxSample, fMinSample, fMaxSample));
1217 | }
1218 | }
1219 |
1220 | nSampleOffset += nSamplesGathered;
1221 | nSamplesToProcess -= nSamplesGathered;
1222 | }
1223 | }
1224 |
1225 | void Base::GetFullOutputBlock(std::vector& vFloatBuffer)
1226 | {
1227 | uint32_t nSamplesToProcess = m_pHost->GetBlockSampleCount();
1228 | uint32_t nSampleOffset = 0;
1229 | while (nSamplesToProcess > 0)
1230 | {
1231 | uint32_t nSamplesGathered = m_pHost->FillOutputBuffer(vFloatBuffer, nSampleOffset, nSamplesToProcess);
1232 |
1233 | nSampleOffset += nSamplesGathered;
1234 | nSamplesToProcess -= nSamplesGathered;
1235 | }
1236 | }
1237 | }
1238 |
1239 | namespace synth
1240 | {
1241 | Property::Property(double f)
1242 | {
1243 | value = std::clamp(f, -1.0, 1.0);
1244 | }
1245 |
1246 | Property& Property::operator =(const double f)
1247 | {
1248 | value = std::clamp(f, -1.0, 1.0);
1249 | return *this;
1250 | }
1251 |
1252 |
1253 | ModularSynth::ModularSynth()
1254 | {
1255 |
1256 | }
1257 |
1258 | bool ModularSynth::AddModule(Module* pModule)
1259 | {
1260 | // Check if module already added
1261 | if (std::find(m_vModules.begin(), m_vModules.end(), pModule) == std::end(m_vModules))
1262 | {
1263 | m_vModules.push_back(pModule);
1264 | return true;
1265 | }
1266 |
1267 | return false;
1268 | }
1269 |
1270 | bool ModularSynth::RemoveModule(Module* pModule)
1271 | {
1272 | if (std::find(m_vModules.begin(), m_vModules.end(), pModule) == std::end(m_vModules))
1273 | {
1274 | m_vModules.erase(std::remove(m_vModules.begin(), m_vModules.end(), pModule), m_vModules.end());
1275 | return true;
1276 | }
1277 |
1278 | return false;
1279 | }
1280 |
1281 | bool ModularSynth::AddPatch(Property* pInput, Property* pOutput)
1282 | {
1283 | // Does patch exist?
1284 | std::pair newPatch = std::pair(pInput, pOutput);
1285 |
1286 | if (std::find(m_vPatches.begin(), m_vPatches.end(), newPatch) == std::end(m_vPatches))
1287 | {
1288 | // Patch doesnt exist, now check if either are null
1289 | if (pInput != nullptr && pOutput != nullptr)
1290 | {
1291 | m_vPatches.push_back(newPatch);
1292 | return true;
1293 | }
1294 | }
1295 |
1296 | return false;
1297 | }
1298 |
1299 | bool ModularSynth::RemovePatch(Property* pInput, Property* pOutput)
1300 | {
1301 | std::pair newPatch = std::pair(pInput, pOutput);
1302 |
1303 | if (std::find(m_vPatches.begin(), m_vPatches.end(), newPatch) == std::end(m_vPatches))
1304 | {
1305 | m_vPatches.erase(std::remove(m_vPatches.begin(), m_vPatches.end(), newPatch), m_vPatches.end());
1306 | return true;
1307 | }
1308 |
1309 | return false;
1310 | }
1311 |
1312 | void ModularSynth::UpdatePatches()
1313 | {
1314 | // Update patches
1315 | for (auto& patch : m_vPatches)
1316 | {
1317 | patch.second->value = patch.first->value;
1318 | }
1319 | }
1320 |
1321 |
1322 | void ModularSynth::Update(uint32_t nChannel, double dTime, double dTimeStep)
1323 | {
1324 | // Now update synth
1325 | for (auto& pModule : m_vModules)
1326 | {
1327 | pModule->Update(nChannel, dTime, dTimeStep);
1328 | }
1329 | }
1330 |
1331 |
1332 | namespace modules
1333 | {
1334 | void Oscillator::Update(uint32_t nChannel, double dTime, double dTimeStep)
1335 | {
1336 | // We use phase accumulation to combat change in parameter glitches
1337 | double w = frequency.value * max_frequency * dTimeStep;
1338 | phase_acc += w + lfo_input.value * frequency.value;
1339 | if (phase_acc >= 2.0) phase_acc -= 2.0;
1340 |
1341 | switch (waveform)
1342 | {
1343 | case Type::Sine:
1344 | output = amplitude.value * sin(phase_acc * 2.0 * 3.14159);
1345 | break;
1346 |
1347 | case Type::Saw:
1348 | output = amplitude.value * (phase_acc - 1.0) * 2.0;
1349 | break;
1350 |
1351 | case Type::Square:
1352 | output = amplitude.value * (phase_acc >= 1.0) ? 1.0 : -1.0;
1353 | break;
1354 |
1355 | case Type::Triangle:
1356 | output = amplitude.value * (phase_acc < 1.0) ? (phase_acc * 0.5) : (1.0 - phase_acc * 0.5);
1357 | break;
1358 |
1359 | case Type::PWM:
1360 | output = amplitude.value * (phase_acc >= (parameter.value + 1.0)) ? 1.0 : -1.0;
1361 | break;
1362 |
1363 | case Type::Wave:
1364 | if(pWave != nullptr)
1365 | output = amplitude.value * pWave->vChannelView[nChannel].GetSample(phase_acc * 0.5 * pWave->file.durationInSamples());
1366 | break;
1367 |
1368 | case Type::Noise:
1369 | output = amplitude.value * rndDouble(-1.0, 1.0);
1370 | break;
1371 |
1372 | }
1373 | }
1374 |
1375 | double Oscillator::rndDouble(double min, double max)
1376 | {
1377 | return ((double)rnd() / (double)(0x7FFFFFFF)) * (max - min) + min;
1378 | }
1379 |
1380 | uint32_t Oscillator::rnd()
1381 | {
1382 | random_seed += 0xe120fc15;
1383 | uint64_t tmp;
1384 | tmp = (uint64_t)random_seed * 0x4a39b70d;
1385 | uint32_t m1 = uint32_t(((tmp >> 32) ^ tmp) & 0xFFFFFFFF);
1386 | tmp = (uint64_t)m1 * 0x12fad5c9;
1387 | uint32_t m2 = uint32_t(((tmp >> 32) ^ tmp) & 0xFFFFFFFF);
1388 | return m2;
1389 | }
1390 | }
1391 | }
1392 | }
1393 |
1394 |
1395 |
1396 | #if defined(SOUNDWAVE_USING_WINMM)
1397 | // WinMM Driver Implementation
1398 | namespace olc::sound::driver
1399 | {
1400 | #pragma comment(lib, "winmm.lib")
1401 |
1402 | WinMM::WinMM(WaveEngine* pHost) : Base(pHost)
1403 | { }
1404 |
1405 | WinMM::~WinMM()
1406 | {
1407 | Stop();
1408 | Close();
1409 | }
1410 |
1411 | bool WinMM::Open(const std::string& sOutputDevice, const std::string& sInputDevice)
1412 | {
1413 | // Device is available
1414 | WAVEFORMATEX waveFormat;
1415 | waveFormat.wFormatTag = WAVE_FORMAT_PCM;
1416 | waveFormat.nSamplesPerSec = m_pHost->GetSampleRate();
1417 | waveFormat.wBitsPerSample = sizeof(short) * 8;
1418 | waveFormat.nChannels = m_pHost->GetChannels();
1419 | waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels;
1420 | waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
1421 | waveFormat.cbSize = 0;
1422 |
1423 | // Open Device if valid
1424 | if (waveOutOpen(&m_hwDevice, WAVE_MAPPER, &waveFormat, (DWORD_PTR)WinMM::waveOutProc, (DWORD_PTR)this, CALLBACK_FUNCTION) != S_OK)
1425 | return false;
1426 |
1427 | // Allocate array of wave header objects, one per block
1428 | m_pWaveHeaders = std::make_unique(m_pHost->GetBlocks());
1429 |
1430 | // Allocate block memory - I dont like vector of vectors, so going with this mess instead
1431 | // My std::vector's content will change, but their size never will - they are basically array now
1432 | m_pvBlockMemory = std::make_unique[]>(m_pHost->GetBlocks());
1433 | for (size_t i = 0; i < m_pHost->GetBlocks(); i++)
1434 | m_pvBlockMemory[i].resize(m_pHost->GetBlockSampleCount() * m_pHost->GetChannels(), 0);
1435 |
1436 | // Link headers to block memory - clever, so we only move headers about
1437 | // rather than memory...
1438 | for (unsigned int n = 0; n < m_pHost->GetBlocks(); n++)
1439 | {
1440 | m_pWaveHeaders[n].dwBufferLength = DWORD(m_pvBlockMemory[0].size() * sizeof(short));
1441 | m_pWaveHeaders[n].lpData = (LPSTR)(m_pvBlockMemory[n].data());
1442 | }
1443 |
1444 | // To begin with, all blocks are free
1445 | m_nBlockFree = m_pHost->GetBlocks();
1446 | return true;
1447 | }
1448 |
1449 | bool WinMM::Start()
1450 | {
1451 | // Prepare driver thread for activity
1452 | m_bDriverLoopActive = true;
1453 | // and get it going!
1454 | m_thDriverLoop = std::thread(&WinMM::DriverLoop, this);
1455 | return true;
1456 | }
1457 |
1458 | void WinMM::Stop()
1459 | {
1460 | // Signal the driver loop to exit
1461 | m_bDriverLoopActive = false;
1462 |
1463 | // Wait for driver thread to exit gracefully
1464 | if (m_thDriverLoop.joinable())
1465 | m_thDriverLoop.join();
1466 | }
1467 |
1468 | void WinMM::Close()
1469 | {
1470 | waveOutClose(m_hwDevice);
1471 | }
1472 |
1473 | // Static Callback wrapper - specific instance is specified
1474 | void CALLBACK WinMM::waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2)
1475 | {
1476 | // All sorts of messages may be pinged here, but we're only interested
1477 | // in audio block is finished...
1478 | if (uMsg != WOM_DONE) return;
1479 |
1480 | // ...which has happened so allow driver object to free resource
1481 | WinMM* driver = (WinMM*)dwInstance;
1482 | driver->FreeAudioBlock();
1483 | }
1484 |
1485 | void WinMM::FreeAudioBlock()
1486 | {
1487 | // Audio subsystem is done with the block it was using, thus
1488 | // making it available again
1489 | m_nBlockFree++;
1490 |
1491 | // Signal to driver loop that a block is now available. It
1492 | // could have been suspended waiting for one
1493 | std::unique_lock lm(m_muxBlockNotZero);
1494 | m_cvBlockNotZero.notify_one();
1495 | }
1496 |
1497 | void WinMM::DriverLoop()
1498 | {
1499 | // We will be using this vector to transfer to the host for filling, with
1500 | // user sound data (float32, -1.0 --> +1.0)
1501 | std::vector vFloatBuffer(m_pHost->GetBlockSampleCount() * m_pHost->GetChannels(), 0.0f);
1502 |
1503 | // While the system is active, start requesting audio data
1504 | while (m_bDriverLoopActive)
1505 | {
1506 | // Are there any blocks available to fill? ...
1507 | if (m_nBlockFree == 0)
1508 | {
1509 | // ...no, So wait until one is available
1510 | std::unique_lock lm(m_muxBlockNotZero);
1511 | while (m_nBlockFree == 0) // sometimes, Windows signals incorrectly
1512 | {
1513 | // This thread will suspend until this CV is signalled
1514 | // from FreeAudioBlock.
1515 | m_cvBlockNotZero.wait(lm);
1516 | }
1517 | }
1518 |
1519 | // ...yes, so use next one, by indicating one fewer
1520 | // block is available
1521 | m_nBlockFree--;
1522 |
1523 | // Prepare block for processing, by oddly, marking it as unprepared :P
1524 | if (m_pWaveHeaders[m_nBlockCurrent].dwFlags & WHDR_PREPARED)
1525 | {
1526 | waveOutUnprepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR));
1527 | }
1528 |
1529 | // Give the userland the opportunity to fill the buffer. Note that the driver
1530 | // doesnt give a hoot about timing. Thats up to the SoundWave interface to
1531 | // maintain
1532 |
1533 | // Userland will populate a float buffer, that gets cleanly converted to
1534 | // a buffer of shorts for DAC
1535 | ProcessOutputBlock(vFloatBuffer, m_pvBlockMemory[m_nBlockCurrent]);
1536 |
1537 | // Send block to sound device
1538 | waveOutPrepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR));
1539 | waveOutWrite(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR));
1540 | m_nBlockCurrent++;
1541 | m_nBlockCurrent %= m_pHost->GetBlocks();
1542 | }
1543 | }
1544 | } // WinMM Driver Implementation
1545 | #endif
1546 | #if defined(SOUNDWAVE_USING_SDLMIXER)
1547 |
1548 | namespace olc::sound::driver
1549 | {
1550 |
1551 | SDLMixer* SDLMixer::instance = nullptr;
1552 |
1553 | SDLMixer::SDLMixer(olc::sound::WaveEngine* pHost)
1554 | : Base(pHost)
1555 | {
1556 | instance = this;
1557 | }
1558 |
1559 | SDLMixer::~SDLMixer()
1560 | {
1561 | Stop();
1562 | Close();
1563 | }
1564 |
1565 | bool SDLMixer::Open(const std::string& sOutputDevice, const std::string& sInputDevice)
1566 | {
1567 | auto errc = Mix_OpenAudioDevice(static_cast(m_pHost->GetSampleRate()),
1568 | AUDIO_F32,
1569 | static_cast(m_pHost->GetChannels()),
1570 | static_cast(m_pHost->GetBlockSampleCount()),
1571 | sOutputDevice == "DEFAULT" ? nullptr : sOutputDevice.c_str(),
1572 | SDL_AUDIO_ALLOW_FORMAT_CHANGE);
1573 |
1574 | // Query the actual format of the audio device, as we have allowed it to be changed.
1575 | if (errc || !Mix_QuerySpec(nullptr, &m_haveFormat, nullptr))
1576 | {
1577 | std::cerr << "Failed to open audio device '" << sOutputDevice << "'" << std::endl;
1578 | return false;
1579 | }
1580 |
1581 | // Compute the Mix_Chunk buffer's size according to the format of the audio device
1582 | Uint32 bufferSize = 0;
1583 | switch (m_haveFormat)
1584 | {
1585 | case AUDIO_F32:
1586 | case AUDIO_S32:
1587 | bufferSize = m_pHost->GetBlockSampleCount() * m_pHost->GetChannels() * 4;
1588 | break;
1589 | case AUDIO_S16:
1590 | case AUDIO_U16:
1591 | bufferSize = m_pHost->GetBlockSampleCount() * m_pHost->GetChannels() * 2;
1592 | break;
1593 | case AUDIO_S8:
1594 | case AUDIO_U8:
1595 | bufferSize = m_pHost->GetBlockSampleCount() * m_pHost->GetChannels() * 1;
1596 | break;
1597 | default:
1598 | std::cerr << "Audio format of device '" << sOutputDevice << "' is not supported" << std::endl;
1599 | return false;
1600 | }
1601 |
1602 | // Allocate the buffer once. The size will never change after this
1603 | audioBuffer.resize(bufferSize);
1604 | audioChunk = {
1605 | 0, // 0, as the chunk does not own the array
1606 | audioBuffer.data(), // Pointer to data array
1607 | bufferSize, // Size in bytes
1608 | 128 // Volume; max by default as it's not controlled by the driver.
1609 | };
1610 |
1611 | return true;
1612 | }
1613 |
1614 | template
1615 | void ConvertFloatTo(const std::vector& fromArr, Int* toArr)
1616 | {
1617 | static auto minVal = static_cast(std::numeric_limits::min());
1618 | static auto maxVal = static_cast(std::numeric_limits::max());
1619 | for (size_t i = 0; i != fromArr.size(); ++i)
1620 | {
1621 | toArr[i] = static_cast(std::clamp(fromArr[i] * maxVal, minVal, maxVal));
1622 | }
1623 | }
1624 |
1625 | void SDLMixer::FillChunkBuffer(const std::vector& userData) const
1626 | {
1627 | // Since the audio device might have changed the format we need to provide,
1628 | // we convert the wave data from the user to that format.
1629 | switch (m_haveFormat)
1630 | {
1631 | case AUDIO_F32:
1632 | memcpy(audioChunk.abuf, userData.data(), audioChunk.alen);
1633 | break;
1634 | case AUDIO_S32:
1635 | ConvertFloatTo(userData, reinterpret_cast(audioChunk.abuf));
1636 | break;
1637 | case AUDIO_S16:
1638 | ConvertFloatTo(userData, reinterpret_cast(audioChunk.abuf));
1639 | break;
1640 | case AUDIO_U16:
1641 | ConvertFloatTo(userData, reinterpret_cast(audioChunk.abuf));
1642 | break;
1643 | case AUDIO_S8:
1644 | ConvertFloatTo(userData, reinterpret_cast(audioChunk.abuf));
1645 | break;
1646 | case AUDIO_U8:
1647 | ConvertFloatTo(userData, audioChunk.abuf);
1648 | break;
1649 | }
1650 | }
1651 |
1652 | void SDLMixer::SDLMixerCallback(int channel)
1653 | {
1654 | static std::vector userData(instance->m_pHost->GetBlockSampleCount() * instance->m_pHost->GetChannels());
1655 |
1656 | if (channel != 0)
1657 | {
1658 | std::cerr << "Unexpected channel number" << std::endl;
1659 | }
1660 |
1661 | // Don't add another chunk if we should not keep running
1662 | if (!instance->m_keepRunning)
1663 | return;
1664 |
1665 | instance->GetFullOutputBlock(userData);
1666 | instance->FillChunkBuffer(userData);
1667 |
1668 | if (Mix_PlayChannel(0, &instance->audioChunk, 0) == -1)
1669 | {
1670 | std::cerr << "Error while playing Chunk" << std::endl;
1671 | }
1672 | }
1673 |
1674 | bool SDLMixer::Start()
1675 | {
1676 | m_keepRunning = true;
1677 |
1678 | // Kickoff the audio driver
1679 | SDLMixerCallback(0);
1680 |
1681 | // SDLMixer handles all other calls to reinsert user data
1682 | Mix_ChannelFinished(SDLMixerCallback);
1683 | return true;
1684 | }
1685 |
1686 | void SDLMixer::Stop()
1687 | {
1688 | m_keepRunning = false;
1689 |
1690 | // Stop might be called multiple times, so we check whether the device is already closed
1691 | if (Mix_QuerySpec(nullptr, nullptr, nullptr))
1692 | {
1693 | for (int i = 0; i != m_pHost->GetChannels(); ++i)
1694 | {
1695 | if (Mix_Playing(i))
1696 | Mix_HaltChannel(i);
1697 | }
1698 | }
1699 | }
1700 |
1701 | void SDLMixer::Close()
1702 | {
1703 | Mix_CloseAudio();
1704 | }
1705 | }
1706 |
1707 | #endif // SOUNDWAVE_USING_SDLMIXER
1708 | #if defined(SOUNDWAVE_USING_ALSA)
1709 | // ALSA Driver Implementation
1710 | namespace olc::sound::driver
1711 | {
1712 | ALSA::ALSA(WaveEngine* pHost) : Base(pHost)
1713 | { }
1714 |
1715 | ALSA::~ALSA()
1716 | {
1717 | Stop();
1718 | Close();
1719 | }
1720 |
1721 | bool ALSA::Open(const std::string& sOutputDevice, const std::string& sInputDevice)
1722 | {
1723 | // Open PCM stream
1724 | int rc = snd_pcm_open(&m_pPCM, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
1725 |
1726 | // Clear global cache.
1727 | // This won't affect users who don't want to create multiple instances of this driver,
1728 | // but it will prevent valgrind from whining about "possibly lost" memory.
1729 | // If the user's ALSA setup uses a PulseAudio plugin, then valgrind will still compain
1730 | // about some "still reachable" data used by that plugin. TODO?
1731 | snd_config_update_free_global();
1732 |
1733 | if (rc < 0)
1734 | return false;
1735 |
1736 | // Prepare the parameter structure and set default parameters
1737 | snd_pcm_hw_params_t *params;
1738 | snd_pcm_hw_params_alloca(¶ms);
1739 | snd_pcm_hw_params_any(m_pPCM, params);
1740 |
1741 | // Set other parameters
1742 | snd_pcm_hw_params_set_access(m_pPCM, params, SND_PCM_ACCESS_RW_INTERLEAVED);
1743 | snd_pcm_hw_params_set_format(m_pPCM, params, SND_PCM_FORMAT_FLOAT);
1744 | snd_pcm_hw_params_set_rate(m_pPCM, params, m_pHost->GetSampleRate(), 0);
1745 | snd_pcm_hw_params_set_channels(m_pPCM, params, m_pHost->GetChannels());
1746 | snd_pcm_hw_params_set_period_size(m_pPCM, params, m_pHost->GetBlockSampleCount(), 0);
1747 | snd_pcm_hw_params_set_periods(m_pPCM, params, m_pHost->GetBlocks(), 0);
1748 |
1749 | // Save these parameters
1750 | rc = snd_pcm_hw_params(m_pPCM, params);
1751 | if (rc < 0)
1752 | return false;
1753 |
1754 | return true;
1755 | }
1756 |
1757 | bool ALSA::Start()
1758 | {
1759 | // Unsure if really needed, helped prevent underrun on my setup
1760 | std::vector vSilence(m_pHost->GetBlockSampleCount() * m_pHost->GetChannels(), 0.0f);
1761 | snd_pcm_start(m_pPCM);
1762 | for (unsigned int i = 0; i < m_pHost->GetBlocks(); i++)
1763 | snd_pcm_writei(m_pPCM, vSilence.data(), m_pHost->GetBlockSampleCount());
1764 |
1765 | m_rBuffers.Resize(m_pHost->GetBlocks(), m_pHost->GetBlockSampleCount() * m_pHost->GetChannels());
1766 |
1767 | snd_pcm_start(m_pPCM);
1768 | m_bDriverLoopActive = true;
1769 | m_thDriverLoop = std::thread(&ALSA::DriverLoop, this);
1770 |
1771 | return true;
1772 | }
1773 |
1774 | void ALSA::Stop()
1775 | {
1776 | // Signal the driver loop to exit
1777 | m_bDriverLoopActive = false;
1778 |
1779 | // Wait for driver thread to exit gracefully
1780 | if (m_thDriverLoop.joinable())
1781 | m_thDriverLoop.join();
1782 |
1783 | if (m_pPCM != nullptr)
1784 | snd_pcm_drop(m_pPCM);
1785 | }
1786 |
1787 | void ALSA::Close()
1788 | {
1789 | if (m_pPCM != nullptr)
1790 | {
1791 | snd_pcm_close(m_pPCM);
1792 | m_pPCM = nullptr;
1793 | }
1794 | // Clear the global cache again for good measure
1795 | snd_config_update_free_global();
1796 | }
1797 |
1798 | void ALSA::DriverLoop()
1799 | {
1800 | const uint32_t nFrames = m_pHost->GetBlockSampleCount();
1801 |
1802 | int err;
1803 | std::vector vFDs;
1804 |
1805 | int nFDs = snd_pcm_poll_descriptors_count(m_pPCM);
1806 | if (nFDs < 0)
1807 | {
1808 | std::cerr << "snd_pcm_poll_descriptors_count returned " << nFDs << "\n";
1809 | std::cerr << "disabling polling\n";
1810 | nFDs = 0;
1811 | }
1812 | else
1813 | {
1814 | vFDs.resize(nFDs);
1815 |
1816 | err = snd_pcm_poll_descriptors(m_pPCM, vFDs.data(), vFDs.size());
1817 | if (err < 0)
1818 | {
1819 | std::cerr << "snd_pcm_poll_descriptors returned " << err << "\n";
1820 | std::cerr << "disabling polling\n";
1821 | vFDs = {};
1822 | }
1823 | }
1824 |
1825 | // While the system is active, start requesting audio data
1826 | while (m_bDriverLoopActive)
1827 | {
1828 | if (!m_rBuffers.IsFull())
1829 | {
1830 | // Grab some audio data
1831 | auto& vFreeBuffer = m_rBuffers.GetFreeBuffer();
1832 | GetFullOutputBlock(vFreeBuffer);
1833 | }
1834 |
1835 | // Wait a bit if our buffer is full
1836 | auto avail = snd_pcm_avail_update(m_pPCM);
1837 | while (m_rBuffers.IsFull() && avail < nFrames)
1838 | {
1839 | if (vFDs.size() == 0) break;
1840 |
1841 | err = poll(vFDs.data(), vFDs.size(), -1);
1842 | if (err < 0)
1843 | std::cerr << "poll returned " << err << "\n";
1844 |
1845 | unsigned short revents;
1846 | err = snd_pcm_poll_descriptors_revents(m_pPCM, vFDs.data(), vFDs.size(), &revents);
1847 | if (err < 0)
1848 | std::cerr << "snd_pcm_poll_descriptors_revents returned " << err << "\n";
1849 |
1850 | if (revents & POLLERR)
1851 | std::cerr << "POLLERR\n";
1852 |
1853 | avail = snd_pcm_avail_update(m_pPCM);
1854 | }
1855 |
1856 | // Write whatever we can
1857 | while (!m_rBuffers.IsEmpty() && avail >= nFrames)
1858 | {
1859 | auto vFullBuffer = m_rBuffers.GetFullBuffer();
1860 | uint32_t nWritten = 0;
1861 |
1862 | while (nWritten < nFrames)
1863 | {
1864 | auto err = snd_pcm_writei(m_pPCM, vFullBuffer.data() + nWritten, nFrames - nWritten);
1865 | if (err > 0)
1866 | nWritten += err;
1867 | else
1868 | {
1869 | std::cerr << "snd_pcm_writei returned " << err << "\n";
1870 | break;
1871 | }
1872 | }
1873 | avail = snd_pcm_avail_update(m_pPCM);
1874 | }
1875 | }
1876 | }
1877 | } // ALSA Driver Implementation
1878 | #endif
1879 | #if defined(SOUNDWAVE_USING_PULSE)
1880 | // PULSE Driver Implementation
1881 | #include
1882 | #include
1883 |
1884 | namespace olc::sound::driver
1885 | {
1886 | PulseAudio::PulseAudio(WaveEngine* pHost) : Base(pHost)
1887 | { }
1888 |
1889 | PulseAudio::~PulseAudio()
1890 | {
1891 | Stop();
1892 | Close();
1893 | }
1894 |
1895 | bool PulseAudio::Open(const std::string& sOutputDevice, const std::string& sInputDevice)
1896 | {
1897 | pa_sample_spec ss {
1898 | PA_SAMPLE_FLOAT32, m_pHost->GetSampleRate(), (uint8_t)m_pHost->GetChannels()
1899 | };
1900 |
1901 | m_pPA = pa_simple_new(NULL, "olcSoundWaveEngine", PA_STREAM_PLAYBACK, NULL,
1902 | "Output Stream", &ss, NULL, NULL, NULL);
1903 |
1904 | if (m_pPA == NULL)
1905 | return false;
1906 |
1907 | return true;
1908 | }
1909 |
1910 | bool PulseAudio::Start()
1911 | {
1912 | m_bDriverLoopActive = true;
1913 | m_thDriverLoop = std::thread(&PulseAudio::DriverLoop, this);
1914 |
1915 | return true;
1916 | }
1917 |
1918 | void PulseAudio::Stop()
1919 | {
1920 | // Signal the driver loop to exit
1921 | m_bDriverLoopActive = false;
1922 |
1923 | // Wait for driver thread to exit gracefully
1924 | if (m_thDriverLoop.joinable())
1925 | m_thDriverLoop.join();
1926 | }
1927 |
1928 | void PulseAudio::Close()
1929 | {
1930 | if (m_pPA != nullptr)
1931 | {
1932 | pa_simple_free(m_pPA);
1933 | m_pPA = nullptr;
1934 | }
1935 | }
1936 |
1937 | void PulseAudio::DriverLoop()
1938 | {
1939 | // We will be using this vector to transfer to the host for filling, with
1940 | // user sound data (float32, -1.0 --> +1.0)
1941 | std::vector vFloatBuffer(m_pHost->GetBlockSampleCount() * m_pHost->GetChannels(), 0.0f);
1942 |
1943 | // While the system is active, start requesting audio data
1944 | while (m_bDriverLoopActive)
1945 | {
1946 | // Grab audio data from user
1947 | GetFullOutputBlock(vFloatBuffer);
1948 |
1949 | // Fill PulseAudio data buffer
1950 | int error;
1951 | if (pa_simple_write(m_pPA, vFloatBuffer.data(),
1952 | vFloatBuffer.size() * sizeof(float), &error) < 0)
1953 | {
1954 | std::cerr << "Failed to feed data to PulseAudio: " << pa_strerror(error) << "\n";
1955 | }
1956 | }
1957 | }
1958 | } // PulseAudio Driver Implementation
1959 | #endif
1960 |
1961 | #endif // OLC_SOUNDWAVE IMPLEMENTATION
1962 | #endif // OLC_SOUNDWAVE_H
1963 |
1964 |
--------------------------------------------------------------------------------
/source/olcSoundWaveEngine.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.32112.339
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "olcSoundWaveEngine", "olcSoundWaveEngine.vcxproj", "{4AE3B973-FDDC-4EFE-8955-299B51DFD990}"
7 | EndProject
8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SoundWaveTests", "..\demos\SoundWaveTests.vcxproj", "{1EED794D-8B25-40D9-9650-4A4819FFA69C}"
9 | ProjectSection(ProjectDependencies) = postProject
10 | {4AE3B973-FDDC-4EFE-8955-299B51DFD990} = {4AE3B973-FDDC-4EFE-8955-299B51DFD990}
11 | EndProjectSection
12 | EndProject
13 | Global
14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
15 | Debug|x64 = Debug|x64
16 | Debug|x86 = Debug|x86
17 | Release|x64 = Release|x64
18 | Release|x86 = Release|x86
19 | EndGlobalSection
20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
21 | {4AE3B973-FDDC-4EFE-8955-299B51DFD990}.Debug|x64.ActiveCfg = Debug|x64
22 | {4AE3B973-FDDC-4EFE-8955-299B51DFD990}.Debug|x64.Build.0 = Debug|x64
23 | {4AE3B973-FDDC-4EFE-8955-299B51DFD990}.Debug|x86.ActiveCfg = Debug|Win32
24 | {4AE3B973-FDDC-4EFE-8955-299B51DFD990}.Debug|x86.Build.0 = Debug|Win32
25 | {4AE3B973-FDDC-4EFE-8955-299B51DFD990}.Release|x64.ActiveCfg = Release|x64
26 | {4AE3B973-FDDC-4EFE-8955-299B51DFD990}.Release|x64.Build.0 = Release|x64
27 | {4AE3B973-FDDC-4EFE-8955-299B51DFD990}.Release|x86.ActiveCfg = Release|Win32
28 | {4AE3B973-FDDC-4EFE-8955-299B51DFD990}.Release|x86.Build.0 = Release|Win32
29 | {1EED794D-8B25-40D9-9650-4A4819FFA69C}.Debug|x64.ActiveCfg = Debug|x64
30 | {1EED794D-8B25-40D9-9650-4A4819FFA69C}.Debug|x64.Build.0 = Debug|x64
31 | {1EED794D-8B25-40D9-9650-4A4819FFA69C}.Debug|x86.ActiveCfg = Debug|Win32
32 | {1EED794D-8B25-40D9-9650-4A4819FFA69C}.Debug|x86.Build.0 = Debug|Win32
33 | {1EED794D-8B25-40D9-9650-4A4819FFA69C}.Release|x64.ActiveCfg = Release|x64
34 | {1EED794D-8B25-40D9-9650-4A4819FFA69C}.Release|x64.Build.0 = Release|x64
35 | {1EED794D-8B25-40D9-9650-4A4819FFA69C}.Release|x86.ActiveCfg = Release|Win32
36 | {1EED794D-8B25-40D9-9650-4A4819FFA69C}.Release|x86.Build.0 = Release|Win32
37 | EndGlobalSection
38 | GlobalSection(SolutionProperties) = preSolution
39 | HideSolutionNode = FALSE
40 | EndGlobalSection
41 | GlobalSection(ExtensibilityGlobals) = postSolution
42 | SolutionGuid = {5462FB37-451F-4072-973F-D320C569FDE0}
43 | EndGlobalSection
44 | EndGlobal
45 |
--------------------------------------------------------------------------------
/source/olcSoundWaveEngine.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 | 16.0
23 | Win32Proj
24 | {4ae3b973-fddc-4efe-8955-299b51dfd990}
25 | olcSoundWaveEngine
26 | 10.0
27 |
28 |
29 |
30 | Application
31 | true
32 | v143
33 | Unicode
34 |
35 |
36 | Application
37 | false
38 | v143
39 | true
40 | Unicode
41 |
42 |
43 | Application
44 | true
45 | v143
46 | Unicode
47 |
48 |
49 | Application
50 | false
51 | v143
52 | true
53 | Unicode
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | true
75 |
76 |
77 | false
78 |
79 |
80 | true
81 |
82 |
83 | false
84 |
85 |
86 |
87 | Level3
88 | true
89 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
90 | true
91 | stdcpp17
92 |
93 |
94 | Console
95 | true
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | ..\Demos\HeaderMaker.exe olc_swe_template.h olcSoundWaveEngine.h
107 |
108 |
109 | Creating Deployable Single Header - olcSoundWaveEngine.h
110 |
111 |
112 |
113 |
114 | Level3
115 | true
116 | true
117 | true
118 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
119 | true
120 | stdcpp17
121 |
122 |
123 | Console
124 | true
125 | true
126 | true
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 | ..\Demos\HeaderMaker.exe olc_swe_template.h olcSoundWaveEngine.h
138 |
139 |
140 | Creating Deployable Single Header - olcSoundWaveEngine.h
141 |
142 |
143 |
144 |
145 | Level3
146 | true
147 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
148 | true
149 | stdcpp17
150 |
151 |
152 | Console
153 | true
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 | ..\Demos\HeaderMaker.exe olc_swe_template.h olcSoundWaveEngine.h
165 | copy olcSoundWaveEngine.h ..\olcSoundWaveEngine.h
166 |
167 |
168 | Creating Deployable Single Header - olcSoundWaveEngine.h
169 |
170 |
171 |
172 |
173 | Level3
174 | true
175 | true
176 | true
177 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
178 | true
179 | stdcpp17
180 |
181 |
182 | Console
183 | true
184 | true
185 | true
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 | ..\Demos\HeaderMaker.exe olc_swe_template.h olcSoundWaveEngine.h
197 |
198 |
199 | Creating Deployable Single Header - olcSoundWaveEngine.h
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
--------------------------------------------------------------------------------
/source/olcSoundWaveEngine.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
7 |
8 |
9 | {93995380-89BD-4b04-88EB-625FBE52EBFB}
10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
11 |
12 |
13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
15 |
16 |
17 |
18 |
19 | Header Files
20 |
21 |
22 | Header Files
23 |
24 |
25 | Header Files
26 |
27 |
28 | Header Files
29 |
30 |
31 | Header Files
32 |
33 |
34 | Header Files
35 |
36 |
37 | Header Files
38 |
39 |
40 | Header Files
41 |
42 |
43 | Header Files
44 |
45 |
46 | Header Files
47 |
48 |
49 | Header Files
50 |
51 |
52 | Header Files
53 |
54 |
55 | Header Files
56 |
57 |
58 | Header Files
59 |
60 |
61 | Header Files
62 |
63 |
64 | Header Files
65 |
66 |
67 | Header Files
68 |
69 |
70 | Header Files
71 |
72 |
73 |
74 |
75 | Source Files
76 |
77 |
78 | Source Files
79 |
80 |
81 | Source Files
82 |
83 |
84 | Source Files
85 |
86 |
87 | Source Files
88 |
89 |
90 | Source Files
91 |
92 |
93 | Source Files
94 |
95 |
96 | Source Files
97 |
98 |
99 | Source Files
100 |
101 |
102 | Source Files
103 |
104 |
105 | Source Files
106 |
107 |
108 | Source Files
109 |
110 |
111 | Source Files
112 |
113 |
114 | Source Files
115 |
116 |
117 | Source Files
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/source/olcSoundWaveEngine.vcxproj.user:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/source/olc_swe_template.h:
--------------------------------------------------------------------------------
1 | /*
2 | +-------------------------------------------------------------+
3 | | OneLoneCoder Sound Wave Engine v0.02 |
4 | | "You wanted noise? Well is this loud enough?" - javidx9 |
5 | +-------------------------------------------------------------+
6 |
7 | What is this?
8 | ~~~~~~~~~~~~~
9 | olc::SoundWaveEngine is a single file, cross platform audio
10 | interface for lightweight applications that just need a bit of
11 | easy audio manipulation.
12 |
13 | It's origins started in the olcNoiseMaker file that accompanied
14 | javidx9's "Code-It-Yourself: Synthesizer" series. It was refactored
15 | and absorbed into the "olcConsoleGameEngine.h" file, and then
16 | refactored again into olcPGEX_Sound.h, that was an extension to
17 | the awesome "olcPixelGameEngine.h" file.
18 |
19 | Alas, it went underused and began to rot, with many myths circulating
20 | that "it doesnt work" and "it shouldn't be used". These untruths
21 | made javidx9 feel sorry for the poor file, and he decided to breathe
22 | some new life into it, in anticipation of new videos!
23 |
24 | License (OLC-3)
25 | ~~~~~~~~~~~~~~~
26 |
27 | Copyright 2018 - 2022 OneLoneCoder.com
28 |
29 | Redistribution and use in source and binary forms, with or without
30 | modification, are permitted provided that the following conditions
31 | are met :
32 |
33 | 1. Redistributions or derivations of source code must retain the above
34 | copyright notice, this list of conditionsand the following disclaimer.
35 |
36 | 2. Redistributions or derivative works in binary form must reproduce
37 | the above copyright notice.This list of conditions and the following
38 | disclaimer must be reproduced in the documentation and /or other
39 | materials provided with the distribution.
40 |
41 | 3. Neither the name of the copyright holder nor the names of its
42 | contributors may be used to endorse or promote products derived
43 | from this software without specific prior written permission.
44 |
45 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
46 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
47 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
48 | A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT
49 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
50 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT
51 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
52 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
53 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
54 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
55 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
56 |
57 | Links
58 | ~~~~~
59 | YouTube: https://www.youtube.com/javidx9
60 | Discord: https://discord.gg/WhwHUMV
61 | Twitter: https://www.twitter.com/javidx9
62 | Twitch: https://www.twitch.tv/javidx9
63 | GitHub: https://www.github.com/onelonecoder
64 | Homepage: https://www.onelonecoder.com
65 | Patreon: https://www.patreon.com/javidx9
66 |
67 | Thanks
68 | ~~~~~~
69 | Gorbit99, Dragoneye, Puol
70 |
71 | Authors
72 | ~~~~~~~
73 | slavka, MaGetzUb, cstd, Moros1138 & javidx9
74 |
75 | (c)OneLoneCoder 2019, 2020, 2021, 2022
76 | */
77 |
78 |
79 | /*
80 | Using & Installing On Microsoft Windows
81 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
82 |
83 | Microsoft Visual Studio
84 | ~~~~~~~~~~~~~~~~~~~~~~~
85 | 1) Include the header file "olcSoundWaveEngine.h" from a .cpp file in your project.
86 | 2) That's it!
87 |
88 |
89 | Code::Blocks
90 | ~~~~~~~~~~~~
91 | 1) Make sure your compiler toolchain is NOT the default one installed with Code::Blocks. That
92 | one is old, out of date, and a general mess. Instead, use MSYS2 to install a recent and
93 | decent GCC toolchain, then configure Code::Blocks to use it
94 |
95 | Guide for installing recent GCC for Windows:
96 | https://www.msys2.org/
97 | Guide for configuring code::blocks:
98 | https://solarianprogrammer.com/2019/11/05/install-gcc-windows/
99 | https://solarianprogrammer.com/2019/11/16/install-codeblocks-gcc-windows-build-c-cpp-fortran-programs/
100 |
101 | 2) Include the header file "olcSoundWaveEngine.h" from a .cpp file in your project.
102 | 3) Add these libraries to "Linker Options": user32 winmm
103 | 4) Set this "Compiler Option": -std=c++17
104 | */
105 |
106 | /*
107 | Using & Installing On Linux
108 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
109 |
110 | GNU Compiler Collection (GCC)
111 | ~~~~~~~~~~~~~~~~~~~~~~~
112 | 1) Include the header file "olcSoundWaveEngine.h" from a .cpp file in your project.
113 | 2) Build with the following command:
114 |
115 | g++ olcSoundWaveEngineExample.cpp -o olcSoundWaveEngineExample -lpulse -lpulse-simple -std=c++17
116 |
117 | 3) That's it!
118 |
119 | */
120 |
121 | /*
122 | Using in multiple-file projects
123 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
124 | If you intend to use olcSoundWaveEngine across multiple files, it's important to only have one
125 | instance of the implementation. This is done using the compiler preprocessor definition: OLC_SOUNDWAVE
126 |
127 | This is defined typically before the header is included in teh translation unit you wish the implementation
128 | to be associated with. To avoid things getting messy I recommend you create a file "olcSoundWaveEngine.cpp"
129 | and that file includes ONLY the following code:
130 |
131 | #define OLC_SOUNDWAVE
132 | #include "olcSoundWaveEngine.h"
133 | */
134 |
135 | /*
136 | 0.01: olcPGEX_Sound.h reworked
137 | +Changed timekeeping to double, added accuracy fix - Thanks scripticuk
138 | +Concept of audio drivers and interface
139 | +All internal timing now double precision
140 | +All internal sampling now single precsion
141 | +Loading form WAV files
142 | +LERPed sampling from all buffers
143 | +Multi-channel audio support
144 | 0.02: +Support multi-channel wave files
145 | +Support for 24-bit wave files
146 | +Wave files are now sample rate invariant
147 | +Linux PulseAudio Updated
148 | +Linux ALSA Updated
149 | +WinMM Updated
150 | +CMake Compatibility
151 | =Fix wave format durations preventing playback
152 | =Various bug fixes
153 | */
154 |
155 | #pragma once
156 | #ifndef OLC_SOUNDWAVE_H
157 | #define OLC_SOUNDWAVE_H
158 |
159 | ///[OLC_HM] INSERT swe_prefix.h STD_INCLUDES
160 |
161 | ///[OLC_HM] INSERT swe_prefix.h COMPILER_SWITCHES
162 |
163 | namespace olc::sound
164 | {
165 |
166 | namespace wave
167 | {
168 | ///[OLC_HM] INSERT swe_wave_file.h WAVE_FILE_TEMPLATE
169 |
170 | ///[OLC_HM] INSERT swe_wave_view.h WAVE_VIEW_TEMPLATE
171 | }
172 |
173 | ///[OLC_HM] INSERT swe_wave_wave.h WAVE_GENERIC_TEMPLATE
174 |
175 | ///[OLC_HM] INSERT swe_wave_engine.h WAVE_ENGINE_H
176 |
177 | namespace driver
178 | {
179 | ///[OLC_HM] INSERT swe_system_base.h SYSTEM_BASE_H
180 | }
181 |
182 |
183 | namespace synth
184 | {
185 | ///[OLC_HM] INSERT swe_synth_modular.h SYNTH_MODULAR_H
186 |
187 | ///[OLC_HM] INSERT swe_synth_osc.h SYNTH_OSCILLATOR_H
188 | }
189 |
190 |
191 | }
192 |
193 | ///[OLC_HM] INSERT swe_system_winmm.h WINMM_H
194 |
195 | ///[OLC_HM] INSERT swe_system_sdlmixer.h SDLMIXER_H
196 |
197 | ///[OLC_HM] INSERT swe_system_alsa.h ALSA_H
198 |
199 | ///[OLC_HM] INSERT swe_system_pulse.h PULSE_H
200 |
201 | #ifdef OLC_SOUNDWAVE
202 | #undef OLC_SOUNDWAVE
203 |
204 | namespace olc::sound
205 | {
206 | ///[OLC_HM] INSERT swe_wave_engine.cpp WAVE_ENGINE_CPP
207 |
208 | namespace driver
209 | {
210 | ///[OLC_HM] INSERT swe_system_base.cpp SYSTEM_BASE_CPP
211 | }
212 |
213 | namespace synth
214 | {
215 | ///[OLC_HM] INSERT swe_synth_modular.cpp SYNTH_MODULAR_CPP
216 |
217 | ///[OLC_HM] INSERT swe_synth_osc.cpp SYNTH_OSCILLATOR_CPP
218 | }
219 | }
220 |
221 |
222 |
223 | ///[OLC_HM] INSERT swe_system_winmm.cpp WINMM_CPP
224 | ///[OLC_HM] INSERT swe_system_sdlmixer.cpp SDLMIXER_CPP
225 | ///[OLC_HM] INSERT swe_system_alsa.cpp ALSA_CPP
226 | ///[OLC_HM] INSERT swe_system_pulse.cpp PULSE_CPP
227 |
228 | #endif // OLC_SOUNDWAVE IMPLEMENTATION
229 | #endif // OLC_SOUNDWAVE_H
230 |
--------------------------------------------------------------------------------
/source/swe_dummy.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "swe_wave_file.h"
4 | #include "swe_wave_view.h"
5 | #include "swe_wave_wave.h"
6 | #include "swe_system_base.h"
7 | #include "swe_system_winmm.h"
8 | #include "swe_system_wasapi.h"
9 | #include "swe_system_openal.h"
10 | #include "swe_system_alsa.h"
11 | #include "swe_system_sdlmixer.h"
12 | #include "swe_wave_engine.h"
13 |
--------------------------------------------------------------------------------
/source/swe_prefix.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | ///[OLC_HM] START STD_INCLUDES
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | ///[OLC_HM] END STD_INCLUDES
19 |
20 | ///[OLC_HM] START COMPILER_SWITCHES
21 | // Compiler/System Sensitivity
22 | #if !defined(SOUNDWAVE_USING_WINMM) && !defined(SOUNDWAVE_USING_WASAPI) && \
23 | !defined(SOUNDWAVE_USING_XAUDIO) && !defined(SOUNDWAVE_USING_OPENAL) && \
24 | !defined(SOUNDWAVE_USING_ALSA) && !defined(SOUNDWAVE_USING_SDLMIXER) && \
25 | !defined(SOUNDWAVE_USING_PULSE) \
26 |
27 | #if defined(_WIN32)
28 | #define SOUNDWAVE_USING_WINMM
29 | #endif
30 | #if defined(__linux__)
31 | #define SOUNDWAVE_USING_PULSE
32 | #endif
33 | #if defined(__APPLE__)
34 | #define SOUNDWAVE_USING_SDLMIXER
35 | #endif
36 | #if defined(__EMSCRIPTEN__)
37 | #define SOUNDWAVE_USING_SDLMIXER
38 | #endif
39 |
40 | #endif
41 | ///[OLC_HM] END COMPILER_SWITCHES
42 |
--------------------------------------------------------------------------------
/source/swe_synth_envelope.cpp:
--------------------------------------------------------------------------------
1 | #include "swe_synth_envelope.h"
2 |
--------------------------------------------------------------------------------
/source/swe_synth_envelope.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | class swe_synth_envelope
3 | {
4 | };
5 |
6 |
--------------------------------------------------------------------------------
/source/swe_synth_modular.cpp:
--------------------------------------------------------------------------------
1 | #include "swe_synth_modular.h"
2 |
3 |
4 |
5 |
6 |
7 | namespace olc::sound::synth
8 | {
9 | ///[OLC_HM] START SYNTH_MODULAR_CPP
10 | Property::Property(double f)
11 | {
12 | value = std::clamp(f, -1.0, 1.0);
13 | }
14 |
15 | Property& Property::operator =(const double f)
16 | {
17 | value = std::clamp(f, -1.0, 1.0);
18 | return *this;
19 | }
20 |
21 |
22 | ModularSynth::ModularSynth()
23 | {
24 |
25 | }
26 |
27 | bool ModularSynth::AddModule(Module* pModule)
28 | {
29 | // Check if module already added
30 | if (std::find(m_vModules.begin(), m_vModules.end(), pModule) == std::end(m_vModules))
31 | {
32 | m_vModules.push_back(pModule);
33 | return true;
34 | }
35 |
36 | return false;
37 | }
38 |
39 | bool ModularSynth::RemoveModule(Module* pModule)
40 | {
41 | if (std::find(m_vModules.begin(), m_vModules.end(), pModule) == std::end(m_vModules))
42 | {
43 | m_vModules.erase(std::remove(m_vModules.begin(), m_vModules.end(), pModule), m_vModules.end());
44 | return true;
45 | }
46 |
47 | return false;
48 | }
49 |
50 | bool ModularSynth::AddPatch(Property* pInput, Property* pOutput)
51 | {
52 | // Does patch exist?
53 | std::pair newPatch = std::pair(pInput, pOutput);
54 |
55 | if (std::find(m_vPatches.begin(), m_vPatches.end(), newPatch) == std::end(m_vPatches))
56 | {
57 | // Patch doesnt exist, now check if either are null
58 | if (pInput != nullptr && pOutput != nullptr)
59 | {
60 | m_vPatches.push_back(newPatch);
61 | return true;
62 | }
63 | }
64 |
65 | return false;
66 | }
67 |
68 | bool ModularSynth::RemovePatch(Property* pInput, Property* pOutput)
69 | {
70 | std::pair newPatch = std::pair(pInput, pOutput);
71 |
72 | if (std::find(m_vPatches.begin(), m_vPatches.end(), newPatch) == std::end(m_vPatches))
73 | {
74 | m_vPatches.erase(std::remove(m_vPatches.begin(), m_vPatches.end(), newPatch), m_vPatches.end());
75 | return true;
76 | }
77 |
78 | return false;
79 | }
80 |
81 | void ModularSynth::UpdatePatches()
82 | {
83 | // Update patches
84 | for (auto& patch : m_vPatches)
85 | {
86 | patch.second->value = patch.first->value;
87 | }
88 | }
89 |
90 |
91 | void ModularSynth::Update(uint32_t nChannel, double dTime, double dTimeStep)
92 | {
93 | // Now update synth
94 | for (auto& pModule : m_vModules)
95 | {
96 | pModule->Update(nChannel, dTime, dTimeStep);
97 | }
98 | }
99 |
100 | ///[OLC_HM] END SYNTH_MODULAR_CPP
101 | }
102 |
103 |
--------------------------------------------------------------------------------
/source/swe_synth_modular.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "swe_prefix.h"
3 |
4 |
5 | namespace olc::sound
6 | {
7 |
8 | namespace synth
9 | {
10 | ///[OLC_HM] START SYNTH_MODULAR_H
11 | class Property
12 | {
13 | public:
14 | double value = 0.0f;
15 |
16 | public:
17 | Property() = default;
18 | Property(double f);
19 |
20 | public:
21 | Property& operator =(const double f);
22 | };
23 |
24 |
25 | class Trigger
26 | {
27 |
28 | };
29 |
30 |
31 | class Module
32 | {
33 | public:
34 | virtual void Update(uint32_t nChannel, double dTime, double dTimeStep) = 0;
35 | };
36 |
37 |
38 | class ModularSynth
39 | {
40 | public:
41 | ModularSynth();
42 |
43 | public:
44 | bool AddModule(Module* pModule);
45 | bool RemoveModule(Module* pModule);
46 | bool AddPatch(Property* pInput, Property* pOutput);
47 | bool RemovePatch(Property* pInput, Property* pOutput);
48 |
49 |
50 | public:
51 | void UpdatePatches();
52 | void Update(uint32_t nChannel, double dTime, double dTimeStep);
53 |
54 | protected:
55 | std::vector m_vModules;
56 | std::vector> m_vPatches;
57 | };
58 |
59 | ///[OLC_HM] END SYNTH_MODULAR_H
60 | }
61 | }
--------------------------------------------------------------------------------
/source/swe_synth_osc.cpp:
--------------------------------------------------------------------------------
1 | #include "swe_synth_osc.h"
2 |
3 |
4 | namespace olc::sound::synth
5 | {
6 | ///[OLC_HM] START SYNTH_OSCILLATOR_CPP
7 | namespace modules
8 | {
9 | void Oscillator::Update(uint32_t nChannel, double dTime, double dTimeStep)
10 | {
11 | // We use phase accumulation to combat change in parameter glitches
12 | double w = frequency.value * max_frequency * dTimeStep;
13 | phase_acc += w + lfo_input.value * frequency.value;
14 | if (phase_acc >= 2.0) phase_acc -= 2.0;
15 |
16 | switch (waveform)
17 | {
18 | case Type::Sine:
19 | output = amplitude.value * sin(phase_acc * 2.0 * 3.14159);
20 | break;
21 |
22 | case Type::Saw:
23 | output = amplitude.value * (phase_acc - 1.0) * 2.0;
24 | break;
25 |
26 | case Type::Square:
27 | output = amplitude.value * (phase_acc >= 1.0) ? 1.0 : -1.0;
28 | break;
29 |
30 | case Type::Triangle:
31 | output = amplitude.value * (phase_acc < 1.0) ? (phase_acc * 0.5) : (1.0 - phase_acc * 0.5);
32 | break;
33 |
34 | case Type::PWM:
35 | output = amplitude.value * (phase_acc >= (parameter.value + 1.0)) ? 1.0 : -1.0;
36 | break;
37 |
38 | case Type::Wave:
39 | if(pWave != nullptr)
40 | output = amplitude.value * pWave->vChannelView[nChannel].GetSample(phase_acc * 0.5 * pWave->file.durationInSamples());
41 | break;
42 |
43 | case Type::Noise:
44 | output = amplitude.value * rndDouble(-1.0, 1.0);
45 | break;
46 |
47 | }
48 | }
49 |
50 | double Oscillator::rndDouble(double min, double max)
51 | {
52 | return ((double)rnd() / (double)(0x7FFFFFFF)) * (max - min) + min;
53 | }
54 |
55 | uint32_t Oscillator::rnd()
56 | {
57 | random_seed += 0xe120fc15;
58 | uint64_t tmp;
59 | tmp = (uint64_t)random_seed * 0x4a39b70d;
60 | uint32_t m1 = uint32_t(((tmp >> 32) ^ tmp) & 0xFFFFFFFF);
61 | tmp = (uint64_t)m1 * 0x12fad5c9;
62 | uint32_t m2 = uint32_t(((tmp >> 32) ^ tmp) & 0xFFFFFFFF);
63 | return m2;
64 | }
65 | }
66 | ///[OLC_HM] END SYNTH_OSCILLATOR_CPP
67 | }
--------------------------------------------------------------------------------
/source/swe_synth_osc.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "swe_prefix.h"
3 | #include "swe_synth_modular.h"
4 | #include "swe_wave_wave.h"
5 |
6 | namespace olc::sound::synth
7 | {
8 | ///[OLC_HM] START SYNTH_OSCILLATOR_H
9 | namespace modules
10 | {
11 | class Oscillator : public Module
12 | {
13 | public:
14 | enum class Type
15 | {
16 | Sine,
17 | Saw,
18 | Square,
19 | Triangle,
20 | PWM,
21 | Wave,
22 | Noise,
23 | };
24 |
25 | public:
26 | // Primary frequency of oscillation
27 | Property frequency = 0.0f;
28 | // Primary amplitude of output
29 | Property amplitude = 1.0f;
30 | // LFO input if required
31 | Property lfo_input = 0.0f;
32 | // Primary Output
33 | Property output;
34 | // Tweakable Parameter
35 | Property parameter = 0.0;
36 |
37 | Type waveform = Type::Sine;
38 |
39 | Wave* pWave = nullptr;
40 |
41 | private:
42 | double phase_acc = 0.0f;
43 | double max_frequency = 20000.0;
44 | uint32_t random_seed = 0xB00B1E5;
45 |
46 | double rndDouble(double min, double max);
47 | uint32_t rnd();
48 |
49 |
50 | public:
51 | virtual void Update(uint32_t nChannel, double dTime, double dTimeStep) override;
52 |
53 | };
54 | }
55 | ///[OLC_HM] END SYNTH_OSCILLATOR_H
56 | }
--------------------------------------------------------------------------------
/source/swe_system_alsa.cpp:
--------------------------------------------------------------------------------
1 | #include "swe_system_alsa.h"
2 |
3 | #include "swe_wave_engine.h"
4 |
5 |
6 | ///[OLC_HM] START ALSA_CPP
7 | #if defined(SOUNDWAVE_USING_ALSA)
8 | // ALSA Driver Implementation
9 | namespace olc::sound::driver
10 | {
11 | ALSA::ALSA(WaveEngine* pHost) : Base(pHost)
12 | { }
13 |
14 | ALSA::~ALSA()
15 | {
16 | Stop();
17 | Close();
18 | }
19 |
20 | bool ALSA::Open(const std::string& sOutputDevice, const std::string& sInputDevice)
21 | {
22 | // Open PCM stream
23 | int rc = snd_pcm_open(&m_pPCM, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
24 |
25 | // Clear global cache.
26 | // This won't affect users who don't want to create multiple instances of this driver,
27 | // but it will prevent valgrind from whining about "possibly lost" memory.
28 | // If the user's ALSA setup uses a PulseAudio plugin, then valgrind will still compain
29 | // about some "still reachable" data used by that plugin. TODO?
30 | snd_config_update_free_global();
31 |
32 | if (rc < 0)
33 | return false;
34 |
35 | // Prepare the parameter structure and set default parameters
36 | snd_pcm_hw_params_t *params;
37 | snd_pcm_hw_params_alloca(¶ms);
38 | snd_pcm_hw_params_any(m_pPCM, params);
39 |
40 | // Set other parameters
41 | snd_pcm_hw_params_set_access(m_pPCM, params, SND_PCM_ACCESS_RW_INTERLEAVED);
42 | snd_pcm_hw_params_set_format(m_pPCM, params, SND_PCM_FORMAT_FLOAT);
43 | snd_pcm_hw_params_set_rate(m_pPCM, params, m_pHost->GetSampleRate(), 0);
44 | snd_pcm_hw_params_set_channels(m_pPCM, params, m_pHost->GetChannels());
45 | snd_pcm_hw_params_set_period_size(m_pPCM, params, m_pHost->GetBlockSampleCount(), 0);
46 | snd_pcm_hw_params_set_periods(m_pPCM, params, m_pHost->GetBlocks(), 0);
47 |
48 | // Save these parameters
49 | rc = snd_pcm_hw_params(m_pPCM, params);
50 | if (rc < 0)
51 | return false;
52 |
53 | return true;
54 | }
55 |
56 | bool ALSA::Start()
57 | {
58 | // Unsure if really needed, helped prevent underrun on my setup
59 | std::vector vSilence(m_pHost->GetBlockSampleCount() * m_pHost->GetChannels(), 0.0f);
60 | snd_pcm_start(m_pPCM);
61 | for (unsigned int i = 0; i < m_pHost->GetBlocks(); i++)
62 | snd_pcm_writei(m_pPCM, vSilence.data(), m_pHost->GetBlockSampleCount());
63 |
64 | m_rBuffers.Resize(m_pHost->GetBlocks(), m_pHost->GetBlockSampleCount() * m_pHost->GetChannels());
65 |
66 | snd_pcm_start(m_pPCM);
67 | m_bDriverLoopActive = true;
68 | m_thDriverLoop = std::thread(&ALSA::DriverLoop, this);
69 |
70 | return true;
71 | }
72 |
73 | void ALSA::Stop()
74 | {
75 | // Signal the driver loop to exit
76 | m_bDriverLoopActive = false;
77 |
78 | // Wait for driver thread to exit gracefully
79 | if (m_thDriverLoop.joinable())
80 | m_thDriverLoop.join();
81 |
82 | if (m_pPCM != nullptr)
83 | snd_pcm_drop(m_pPCM);
84 | }
85 |
86 | void ALSA::Close()
87 | {
88 | if (m_pPCM != nullptr)
89 | {
90 | snd_pcm_close(m_pPCM);
91 | m_pPCM = nullptr;
92 | }
93 | // Clear the global cache again for good measure
94 | snd_config_update_free_global();
95 | }
96 |
97 | void ALSA::DriverLoop()
98 | {
99 | const uint32_t nFrames = m_pHost->GetBlockSampleCount();
100 |
101 | int err;
102 | std::vector vFDs;
103 |
104 | int nFDs = snd_pcm_poll_descriptors_count(m_pPCM);
105 | if (nFDs < 0)
106 | {
107 | std::cerr << "snd_pcm_poll_descriptors_count returned " << nFDs << "\n";
108 | std::cerr << "disabling polling\n";
109 | nFDs = 0;
110 | }
111 | else
112 | {
113 | vFDs.resize(nFDs);
114 |
115 | err = snd_pcm_poll_descriptors(m_pPCM, vFDs.data(), vFDs.size());
116 | if (err < 0)
117 | {
118 | std::cerr << "snd_pcm_poll_descriptors returned " << err << "\n";
119 | std::cerr << "disabling polling\n";
120 | vFDs = {};
121 | }
122 | }
123 |
124 | // While the system is active, start requesting audio data
125 | while (m_bDriverLoopActive)
126 | {
127 | if (!m_rBuffers.IsFull())
128 | {
129 | // Grab some audio data
130 | auto& vFreeBuffer = m_rBuffers.GetFreeBuffer();
131 | GetFullOutputBlock(vFreeBuffer);
132 | }
133 |
134 | // Wait a bit if our buffer is full
135 | auto avail = snd_pcm_avail_update(m_pPCM);
136 | while (m_rBuffers.IsFull() && avail < nFrames)
137 | {
138 | if (vFDs.size() == 0) break;
139 |
140 | err = poll(vFDs.data(), vFDs.size(), -1);
141 | if (err < 0)
142 | std::cerr << "poll returned " << err << "\n";
143 |
144 | unsigned short revents;
145 | err = snd_pcm_poll_descriptors_revents(m_pPCM, vFDs.data(), vFDs.size(), &revents);
146 | if (err < 0)
147 | std::cerr << "snd_pcm_poll_descriptors_revents returned " << err << "\n";
148 |
149 | if (revents & POLLERR)
150 | std::cerr << "POLLERR\n";
151 |
152 | avail = snd_pcm_avail_update(m_pPCM);
153 | }
154 |
155 | // Write whatever we can
156 | while (!m_rBuffers.IsEmpty() && avail >= nFrames)
157 | {
158 | auto vFullBuffer = m_rBuffers.GetFullBuffer();
159 | uint32_t nWritten = 0;
160 |
161 | while (nWritten < nFrames)
162 | {
163 | auto err = snd_pcm_writei(m_pPCM, vFullBuffer.data() + nWritten, nFrames - nWritten);
164 | if (err > 0)
165 | nWritten += err;
166 | else
167 | {
168 | std::cerr << "snd_pcm_writei returned " << err << "\n";
169 | break;
170 | }
171 | }
172 | avail = snd_pcm_avail_update(m_pPCM);
173 | }
174 | }
175 | }
176 | } // ALSA Driver Implementation
177 | #endif
178 | ///[OLC_HM] END ALSA_CPP
179 |
--------------------------------------------------------------------------------
/source/swe_system_alsa.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "swe_system_base.h"
3 |
4 | ///[OLC_HM] START ALSA_H
5 | #if defined(SOUNDWAVE_USING_ALSA)
6 | #include
7 | #include
8 | #include
9 |
10 | namespace olc::sound::driver
11 | {
12 | // Not thread-safe
13 | template
14 | class RingBuffer
15 | {
16 | public:
17 | RingBuffer()
18 | { }
19 |
20 | void Resize(unsigned int bufnum = 0, unsigned int buflen = 0)
21 | {
22 | m_vBuffers.resize(bufnum);
23 | for (auto &vBuffer : m_vBuffers)
24 | vBuffer.resize(buflen);
25 | }
26 |
27 | std::vector& GetFreeBuffer()
28 | {
29 | assert(!IsFull());
30 |
31 | std::vector& result = m_vBuffers[m_nTail];
32 | m_nTail = Next(m_nTail);
33 | return result;
34 | }
35 |
36 | std::vector& GetFullBuffer()
37 | {
38 | assert(!IsEmpty());
39 |
40 | std::vector& result = m_vBuffers[m_nHead];
41 | m_nHead = Next(m_nHead);
42 | return result;
43 | }
44 |
45 | bool IsEmpty()
46 | {
47 | return m_nHead == m_nTail;
48 | }
49 |
50 | bool IsFull()
51 | {
52 | return m_nHead == Next(m_nTail);
53 | }
54 |
55 | private:
56 | unsigned int Next(unsigned int current)
57 | {
58 | return (current + 1) % m_vBuffers.size();
59 | }
60 |
61 | std::vector> m_vBuffers;
62 | unsigned int m_nHead = 0;
63 | unsigned int m_nTail = 0;
64 | };
65 |
66 | class ALSA : public Base
67 | {
68 | public:
69 | ALSA(WaveEngine* pHost);
70 | ~ALSA();
71 |
72 | protected:
73 | bool Open(const std::string& sOutputDevice, const std::string& sInputDevice) override;
74 | bool Start() override;
75 | void Stop() override;
76 | void Close() override;
77 |
78 | private:
79 | void DriverLoop();
80 |
81 | snd_pcm_t *m_pPCM;
82 | RingBuffer m_rBuffers;
83 | std::atomic m_bDriverLoopActive{ false };
84 | std::thread m_thDriverLoop;
85 | };
86 | }
87 | #endif // SOUNDWAVE_USING_ALSA
88 | ///[OLC_HM] END ALSA_H
89 |
--------------------------------------------------------------------------------
/source/swe_system_base.cpp:
--------------------------------------------------------------------------------
1 | #include "swe_system_base.h"
2 |
3 | #include "swe_wave_engine.h"
4 |
5 | namespace olc::sound::driver
6 | {
7 | ///[OLC_HM] START SYSTEM_BASE_CPP
8 | Base::Base(olc::sound::WaveEngine* pHost) : m_pHost(pHost)
9 | {}
10 |
11 | Base::~Base()
12 | {}
13 |
14 | bool Base::Open(const std::string& sOutputDevice, const std::string& sInputDevice)
15 | {
16 | return false;
17 | }
18 |
19 | bool Base::Start()
20 | {
21 | return false;
22 | }
23 |
24 | void Base::Stop()
25 | {
26 | }
27 |
28 | void Base::Close()
29 | {
30 | }
31 |
32 | std::vector Base::EnumerateOutputDevices()
33 | {
34 | return { "DEFAULT" };
35 | }
36 |
37 | std::vector Base::EnumerateInputDevices()
38 | {
39 | return { "NONE" };
40 | }
41 |
42 | void Base::ProcessOutputBlock(std::vector& vFloatBuffer, std::vector& vDACBuffer)
43 | {
44 | constexpr float fMaxSample = float(std::numeric_limits::max());
45 | constexpr float fMinSample = float(std::numeric_limits::min());
46 |
47 | // So... why not use vFloatBuffer.size()? Well with this implementation
48 | // we can, but i suspect there may be some platforms that request a
49 | // specific number of samples per "loop" rather than this block architecture
50 | uint32_t nSamplesToProcess = m_pHost->GetBlockSampleCount();
51 | uint32_t nSampleOffset = 0;
52 | while (nSamplesToProcess > 0)
53 | {
54 | uint32_t nSamplesGathered = m_pHost->FillOutputBuffer(vFloatBuffer, nSampleOffset, nSamplesToProcess);
55 |
56 | // Vector is in float32 format, so convert to hardware required format
57 | for (uint32_t n = 0; n < nSamplesGathered; n++)
58 | {
59 | for (uint32_t c = 0; c < m_pHost->GetChannels(); c++)
60 | {
61 | size_t nSampleID = nSampleOffset + (n * m_pHost->GetChannels() + c);
62 | vDACBuffer[nSampleID] = short(std::clamp(vFloatBuffer[nSampleID] * fMaxSample, fMinSample, fMaxSample));
63 | }
64 | }
65 |
66 | nSampleOffset += nSamplesGathered;
67 | nSamplesToProcess -= nSamplesGathered;
68 | }
69 | }
70 |
71 | void Base::GetFullOutputBlock(std::vector& vFloatBuffer)
72 | {
73 | uint32_t nSamplesToProcess = m_pHost->GetBlockSampleCount();
74 | uint32_t nSampleOffset = 0;
75 | while (nSamplesToProcess > 0)
76 | {
77 | uint32_t nSamplesGathered = m_pHost->FillOutputBuffer(vFloatBuffer, nSampleOffset, nSamplesToProcess);
78 |
79 | nSampleOffset += nSamplesGathered;
80 | nSamplesToProcess -= nSamplesGathered;
81 | }
82 | }
83 | ///[OLC_HM] END SYSTEM_BASE_CPP
84 | } // AudioDriver Base Implementation
--------------------------------------------------------------------------------
/source/swe_system_base.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "swe_prefix.h"
4 |
5 | namespace olc::sound
6 | {
7 | class WaveEngine;
8 | }
9 |
10 | namespace olc::sound::driver
11 | {
12 | ///[OLC_HM] START SYSTEM_BASE_H
13 | // DRIVER DEVELOPERS ONLY!!!
14 | //
15 | // This interface allows SoundWave to exchange data with OS audio systems. It
16 | // is not intended of use by regular users.
17 | class Base
18 | {
19 | public:
20 | Base(WaveEngine* pHost);
21 | virtual ~Base();
22 |
23 | public:
24 | // [IMPLEMENT] Opens a connection to the hardware device, returns true if success
25 | virtual bool Open(const std::string& sOutputDevice, const std::string& sInputDevice);
26 | // [IMPLEMENT] Starts a process that repeatedly requests audio, returns true if success
27 | virtual bool Start();
28 | // [IMPLEMENT] Stops a process form requesting audio
29 | virtual void Stop();
30 | // [IMPLEMENT] Closes any connections to hardware devices
31 | virtual void Close();
32 |
33 | virtual std::vector EnumerateOutputDevices();
34 | virtual std::vector EnumerateInputDevices();
35 |
36 | protected:
37 | // [IMPLEMENT IF REQUIRED] Called by driver to exchange data with SoundWave System. Your
38 | // implementation will call this function providing a "DAC" buffer to be filled by
39 | // SoundWave from a buffer of floats filled by the user.
40 | void ProcessOutputBlock(std::vector& vFloatBuffer, std::vector& vDACBuffer);
41 |
42 | // [IMPLEMENT IF REQUIRED] Called by driver to exchange data with SoundWave System.
43 | void GetFullOutputBlock(std::vector& vFloatBuffer);
44 |
45 | // Handle to SoundWave, to interrogate optons, and get user data
46 | WaveEngine* m_pHost = nullptr;
47 | };
48 | ///[OLC_HM] END SYSTEM_BASE_H
49 | } // End Driver Declaration
50 |
51 |
--------------------------------------------------------------------------------
/source/swe_system_openal.cpp:
--------------------------------------------------------------------------------
1 | #include "swe_system_openal.h"
2 |
--------------------------------------------------------------------------------
/source/swe_system_openal.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | class system_openal
3 | {
4 | };
5 |
6 |
--------------------------------------------------------------------------------
/source/swe_system_pulse.cpp:
--------------------------------------------------------------------------------
1 | #include "swe_system_pulse.h"
2 |
3 | #include "swe_wave_engine.h"
4 |
5 |
6 | ///[OLC_HM] START PULSE_CPP
7 | #if defined(SOUNDWAVE_USING_PULSE)
8 | // PULSE Driver Implementation
9 | #include
10 | #include
11 |
12 | namespace olc::sound::driver
13 | {
14 | PulseAudio::PulseAudio(WaveEngine* pHost) : Base(pHost)
15 | { }
16 |
17 | PulseAudio::~PulseAudio()
18 | {
19 | Stop();
20 | Close();
21 | }
22 |
23 | bool PulseAudio::Open(const std::string& sOutputDevice, const std::string& sInputDevice)
24 | {
25 | pa_sample_spec ss {
26 | PA_SAMPLE_FLOAT32, m_pHost->GetSampleRate(), (uint8_t)m_pHost->GetChannels()
27 | };
28 |
29 | m_pPA = pa_simple_new(NULL, "olcSoundWaveEngine", PA_STREAM_PLAYBACK, NULL,
30 | "Output Stream", &ss, NULL, NULL, NULL);
31 |
32 | if (m_pPA == NULL)
33 | return false;
34 |
35 | return true;
36 | }
37 |
38 | bool PulseAudio::Start()
39 | {
40 | m_bDriverLoopActive = true;
41 | m_thDriverLoop = std::thread(&PulseAudio::DriverLoop, this);
42 |
43 | return true;
44 | }
45 |
46 | void PulseAudio::Stop()
47 | {
48 | // Signal the driver loop to exit
49 | m_bDriverLoopActive = false;
50 |
51 | // Wait for driver thread to exit gracefully
52 | if (m_thDriverLoop.joinable())
53 | m_thDriverLoop.join();
54 | }
55 |
56 | void PulseAudio::Close()
57 | {
58 | if (m_pPA != nullptr)
59 | {
60 | pa_simple_free(m_pPA);
61 | m_pPA = nullptr;
62 | }
63 | }
64 |
65 | void PulseAudio::DriverLoop()
66 | {
67 | // We will be using this vector to transfer to the host for filling, with
68 | // user sound data (float32, -1.0 --> +1.0)
69 | std::vector vFloatBuffer(m_pHost->GetBlockSampleCount() * m_pHost->GetChannels(), 0.0f);
70 |
71 | // While the system is active, start requesting audio data
72 | while (m_bDriverLoopActive)
73 | {
74 | // Grab audio data from user
75 | GetFullOutputBlock(vFloatBuffer);
76 |
77 | // Fill PulseAudio data buffer
78 | int error;
79 | if (pa_simple_write(m_pPA, vFloatBuffer.data(),
80 | vFloatBuffer.size() * sizeof(float), &error) < 0)
81 | {
82 | std::cerr << "Failed to feed data to PulseAudio: " << pa_strerror(error) << "\n";
83 | }
84 | }
85 | }
86 | } // PulseAudio Driver Implementation
87 | #endif
88 | ///[OLC_HM] END PULSE_CPP
89 |
--------------------------------------------------------------------------------
/source/swe_system_pulse.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "swe_system_base.h"
3 |
4 | ///[OLC_HM] START PULSE_H
5 | #if defined(SOUNDWAVE_USING_PULSE)
6 | #include
7 |
8 | namespace olc::sound::driver
9 | {
10 | class PulseAudio : public Base
11 | {
12 | public:
13 | PulseAudio(WaveEngine* pHost);
14 | ~PulseAudio();
15 |
16 | protected:
17 | bool Open(const std::string& sOutputDevice, const std::string& sInputDevice) override;
18 | bool Start() override;
19 | void Stop() override;
20 | void Close() override;
21 |
22 | private:
23 | void DriverLoop();
24 |
25 | pa_simple *m_pPA;
26 | std::atomic m_bDriverLoopActive{ false };
27 | std::thread m_thDriverLoop;
28 | };
29 | }
30 | #endif // SOUNDWAVE_USING_PULSE
31 | ///[OLC_HM] END PULSE_H
32 |
--------------------------------------------------------------------------------
/source/swe_system_sdlmixer.cpp:
--------------------------------------------------------------------------------
1 | #include "swe_system_sdlmixer.h"
2 |
3 | #include "swe_wave_engine.h"
4 |
5 | ///[OLC_HM] START SDLMIXER_CPP
6 | #if defined(SOUNDWAVE_USING_SDLMIXER)
7 |
8 | namespace olc::sound::driver
9 | {
10 |
11 | SDLMixer* SDLMixer::instance = nullptr;
12 |
13 | SDLMixer::SDLMixer(olc::sound::WaveEngine* pHost)
14 | : Base(pHost)
15 | {
16 | instance = this;
17 | }
18 |
19 | SDLMixer::~SDLMixer()
20 | {
21 | Stop();
22 | Close();
23 | }
24 |
25 | bool SDLMixer::Open(const std::string& sOutputDevice, const std::string& sInputDevice)
26 | {
27 | auto errc = Mix_OpenAudioDevice(static_cast(m_pHost->GetSampleRate()),
28 | AUDIO_F32,
29 | static_cast(m_pHost->GetChannels()),
30 | static_cast(m_pHost->GetBlockSampleCount()),
31 | sOutputDevice == "DEFAULT" ? nullptr : sOutputDevice.c_str(),
32 | SDL_AUDIO_ALLOW_FORMAT_CHANGE);
33 |
34 | // Query the actual format of the audio device, as we have allowed it to be changed.
35 | if (errc || !Mix_QuerySpec(nullptr, &m_haveFormat, nullptr))
36 | {
37 | std::cerr << "Failed to open audio device '" << sOutputDevice << "'" << std::endl;
38 | return false;
39 | }
40 |
41 | // Compute the Mix_Chunk buffer's size according to the format of the audio device
42 | Uint32 bufferSize = 0;
43 | switch (m_haveFormat)
44 | {
45 | case AUDIO_F32:
46 | case AUDIO_S32:
47 | bufferSize = m_pHost->GetBlockSampleCount() * m_pHost->GetChannels() * 4;
48 | break;
49 | case AUDIO_S16:
50 | case AUDIO_U16:
51 | bufferSize = m_pHost->GetBlockSampleCount() * m_pHost->GetChannels() * 2;
52 | break;
53 | case AUDIO_S8:
54 | case AUDIO_U8:
55 | bufferSize = m_pHost->GetBlockSampleCount() * m_pHost->GetChannels() * 1;
56 | break;
57 | default:
58 | std::cerr << "Audio format of device '" << sOutputDevice << "' is not supported" << std::endl;
59 | return false;
60 | }
61 |
62 | // Allocate the buffer once. The size will never change after this
63 | audioBuffer.resize(bufferSize);
64 | audioChunk = {
65 | 0, // 0, as the chunk does not own the array
66 | audioBuffer.data(), // Pointer to data array
67 | bufferSize, // Size in bytes
68 | 128 // Volume; max by default as it's not controlled by the driver.
69 | };
70 |
71 | return true;
72 | }
73 |
74 | template
75 | void ConvertFloatTo(const std::vector& fromArr, Int* toArr)
76 | {
77 | static auto minVal = static_cast(std::numeric_limits::min());
78 | static auto maxVal = static_cast(std::numeric_limits::max());
79 | for (size_t i = 0; i != fromArr.size(); ++i)
80 | {
81 | toArr[i] = static_cast(std::clamp(fromArr[i] * maxVal, minVal, maxVal));
82 | }
83 | }
84 |
85 | void SDLMixer::FillChunkBuffer(const std::vector& userData) const
86 | {
87 | // Since the audio device might have changed the format we need to provide,
88 | // we convert the wave data from the user to that format.
89 | switch (m_haveFormat)
90 | {
91 | case AUDIO_F32:
92 | memcpy(audioChunk.abuf, userData.data(), audioChunk.alen);
93 | break;
94 | case AUDIO_S32:
95 | ConvertFloatTo(userData, reinterpret_cast(audioChunk.abuf));
96 | break;
97 | case AUDIO_S16:
98 | ConvertFloatTo(userData, reinterpret_cast(audioChunk.abuf));
99 | break;
100 | case AUDIO_U16:
101 | ConvertFloatTo(userData, reinterpret_cast(audioChunk.abuf));
102 | break;
103 | case AUDIO_S8:
104 | ConvertFloatTo(userData, reinterpret_cast(audioChunk.abuf));
105 | break;
106 | case AUDIO_U8:
107 | ConvertFloatTo(userData, audioChunk.abuf);
108 | break;
109 | }
110 | }
111 |
112 | void SDLMixer::SDLMixerCallback(int channel)
113 | {
114 | static std::vector userData(instance->m_pHost->GetBlockSampleCount() * instance->m_pHost->GetChannels());
115 |
116 | if (channel != 0)
117 | {
118 | std::cerr << "Unexpected channel number" << std::endl;
119 | }
120 |
121 | // Don't add another chunk if we should not keep running
122 | if (!instance->m_keepRunning)
123 | return;
124 |
125 | instance->GetFullOutputBlock(userData);
126 | instance->FillChunkBuffer(userData);
127 |
128 | if (Mix_PlayChannel(0, &instance->audioChunk, 0) == -1)
129 | {
130 | std::cerr << "Error while playing Chunk" << std::endl;
131 | }
132 | }
133 |
134 | bool SDLMixer::Start()
135 | {
136 | m_keepRunning = true;
137 |
138 | // Kickoff the audio driver
139 | SDLMixerCallback(0);
140 |
141 | // SDLMixer handles all other calls to reinsert user data
142 | Mix_ChannelFinished(SDLMixerCallback);
143 | return true;
144 | }
145 |
146 | void SDLMixer::Stop()
147 | {
148 | m_keepRunning = false;
149 |
150 | // Stop might be called multiple times, so we check whether the device is already closed
151 | if (Mix_QuerySpec(nullptr, nullptr, nullptr))
152 | {
153 | for (int i = 0; i != m_pHost->GetChannels(); ++i)
154 | {
155 | if (Mix_Playing(i))
156 | Mix_HaltChannel(i);
157 | }
158 | }
159 | }
160 |
161 | void SDLMixer::Close()
162 | {
163 | Mix_CloseAudio();
164 | }
165 | }
166 |
167 | #endif // SOUNDWAVE_USING_SDLMIXER
168 | ///[OLC_HM] END SDLMIXER_CPP
--------------------------------------------------------------------------------
/source/swe_system_sdlmixer.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "swe_system_base.h"
3 |
4 | ///[OLC_HM] START SDLMIXER_H
5 | #if defined(SOUNDWAVE_USING_SDLMIXER)
6 |
7 | #if defined(__EMSCRIPTEN__)
8 | #include
9 | #else
10 | #include
11 | #endif
12 |
13 | namespace olc::sound::driver
14 | {
15 | class SDLMixer final : public Base
16 | {
17 | public:
18 | explicit SDLMixer(WaveEngine* pHost);
19 | ~SDLMixer() final;
20 |
21 | protected:
22 | bool Open(const std::string& sOutputDevice, const std::string& sInputDevice) final;
23 | bool Start() final;
24 | void Stop() final;
25 | void Close() final;
26 |
27 | private:
28 | void FillChunkBuffer(const std::vector& userData) const;
29 |
30 | static void SDLMixerCallback(int channel);
31 |
32 | private:
33 | bool m_keepRunning = false;
34 | Uint16 m_haveFormat = AUDIO_F32SYS;
35 | std::vector audioBuffer;
36 | Mix_Chunk audioChunk;
37 |
38 | static SDLMixer* instance;
39 | };
40 | }
41 |
42 | #endif // SOUNDWAVE_USING_SDLMIXER
43 | ///[OLC_HM] END SDLMIXER_H
--------------------------------------------------------------------------------
/source/swe_system_wasapi.cpp:
--------------------------------------------------------------------------------
1 | #include "swe_system_wasapi.h"
2 |
--------------------------------------------------------------------------------
/source/swe_system_wasapi.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | class system_wasapi
3 | {
4 | };
5 |
6 |
--------------------------------------------------------------------------------
/source/swe_system_winmm.cpp:
--------------------------------------------------------------------------------
1 | #include "swe_system_winmm.h"
2 |
3 | #include "swe_wave_engine.h"
4 |
5 |
6 | ///[OLC_HM] START WINMM_CPP
7 | #if defined(SOUNDWAVE_USING_WINMM)
8 | // WinMM Driver Implementation
9 | namespace olc::sound::driver
10 | {
11 | #pragma comment(lib, "winmm.lib")
12 |
13 | WinMM::WinMM(WaveEngine* pHost) : Base(pHost)
14 | { }
15 |
16 | WinMM::~WinMM()
17 | {
18 | Stop();
19 | Close();
20 | }
21 |
22 | bool WinMM::Open(const std::string& sOutputDevice, const std::string& sInputDevice)
23 | {
24 | // Device is available
25 | WAVEFORMATEX waveFormat;
26 | waveFormat.wFormatTag = WAVE_FORMAT_PCM;
27 | waveFormat.nSamplesPerSec = m_pHost->GetSampleRate();
28 | waveFormat.wBitsPerSample = sizeof(short) * 8;
29 | waveFormat.nChannels = m_pHost->GetChannels();
30 | waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels;
31 | waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
32 | waveFormat.cbSize = 0;
33 |
34 | // Open Device if valid
35 | if (waveOutOpen(&m_hwDevice, WAVE_MAPPER, &waveFormat, (DWORD_PTR)WinMM::waveOutProc, (DWORD_PTR)this, CALLBACK_FUNCTION) != S_OK)
36 | return false;
37 |
38 | // Allocate array of wave header objects, one per block
39 | m_pWaveHeaders = std::make_unique(m_pHost->GetBlocks());
40 |
41 | // Allocate block memory - I dont like vector of vectors, so going with this mess instead
42 | // My std::vector's content will change, but their size never will - they are basically array now
43 | m_pvBlockMemory = std::make_unique[]>(m_pHost->GetBlocks());
44 | for (size_t i = 0; i < m_pHost->GetBlocks(); i++)
45 | m_pvBlockMemory[i].resize(m_pHost->GetBlockSampleCount() * m_pHost->GetChannels(), 0);
46 |
47 | // Link headers to block memory - clever, so we only move headers about
48 | // rather than memory...
49 | for (unsigned int n = 0; n < m_pHost->GetBlocks(); n++)
50 | {
51 | m_pWaveHeaders[n].dwBufferLength = DWORD(m_pvBlockMemory[0].size() * sizeof(short));
52 | m_pWaveHeaders[n].lpData = (LPSTR)(m_pvBlockMemory[n].data());
53 | }
54 |
55 | // To begin with, all blocks are free
56 | m_nBlockFree = m_pHost->GetBlocks();
57 | return true;
58 | }
59 |
60 | bool WinMM::Start()
61 | {
62 | // Prepare driver thread for activity
63 | m_bDriverLoopActive = true;
64 | // and get it going!
65 | m_thDriverLoop = std::thread(&WinMM::DriverLoop, this);
66 | return true;
67 | }
68 |
69 | void WinMM::Stop()
70 | {
71 | // Signal the driver loop to exit
72 | m_bDriverLoopActive = false;
73 |
74 | // Wait for driver thread to exit gracefully
75 | if (m_thDriverLoop.joinable())
76 | m_thDriverLoop.join();
77 | }
78 |
79 | void WinMM::Close()
80 | {
81 | waveOutClose(m_hwDevice);
82 | }
83 |
84 | // Static Callback wrapper - specific instance is specified
85 | void CALLBACK WinMM::waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2)
86 | {
87 | // All sorts of messages may be pinged here, but we're only interested
88 | // in audio block is finished...
89 | if (uMsg != WOM_DONE) return;
90 |
91 | // ...which has happened so allow driver object to free resource
92 | WinMM* driver = (WinMM*)dwInstance;
93 | driver->FreeAudioBlock();
94 | }
95 |
96 | void WinMM::FreeAudioBlock()
97 | {
98 | // Audio subsystem is done with the block it was using, thus
99 | // making it available again
100 | m_nBlockFree++;
101 |
102 | // Signal to driver loop that a block is now available. It
103 | // could have been suspended waiting for one
104 | std::unique_lock lm(m_muxBlockNotZero);
105 | m_cvBlockNotZero.notify_one();
106 | }
107 |
108 | void WinMM::DriverLoop()
109 | {
110 | // We will be using this vector to transfer to the host for filling, with
111 | // user sound data (float32, -1.0 --> +1.0)
112 | std::vector vFloatBuffer(m_pHost->GetBlockSampleCount() * m_pHost->GetChannels(), 0.0f);
113 |
114 | // While the system is active, start requesting audio data
115 | while (m_bDriverLoopActive)
116 | {
117 | // Are there any blocks available to fill? ...
118 | if (m_nBlockFree == 0)
119 | {
120 | // ...no, So wait until one is available
121 | std::unique_lock lm(m_muxBlockNotZero);
122 | while (m_nBlockFree == 0) // sometimes, Windows signals incorrectly
123 | {
124 | // This thread will suspend until this CV is signalled
125 | // from FreeAudioBlock.
126 | m_cvBlockNotZero.wait(lm);
127 | }
128 | }
129 |
130 | // ...yes, so use next one, by indicating one fewer
131 | // block is available
132 | m_nBlockFree--;
133 |
134 | // Prepare block for processing, by oddly, marking it as unprepared :P
135 | if (m_pWaveHeaders[m_nBlockCurrent].dwFlags & WHDR_PREPARED)
136 | {
137 | waveOutUnprepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR));
138 | }
139 |
140 | // Give the userland the opportunity to fill the buffer. Note that the driver
141 | // doesnt give a hoot about timing. Thats up to the SoundWave interface to
142 | // maintain
143 |
144 | // Userland will populate a float buffer, that gets cleanly converted to
145 | // a buffer of shorts for DAC
146 | ProcessOutputBlock(vFloatBuffer, m_pvBlockMemory[m_nBlockCurrent]);
147 |
148 | // Send block to sound device
149 | waveOutPrepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR));
150 | waveOutWrite(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR));
151 | m_nBlockCurrent++;
152 | m_nBlockCurrent %= m_pHost->GetBlocks();
153 | }
154 | }
155 | } // WinMM Driver Implementation
156 | #endif
157 | ///[OLC_HM] END WINMM_CPP
--------------------------------------------------------------------------------
/source/swe_system_winmm.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "swe_system_base.h"
3 |
4 | ///[OLC_HM] START WINMM_H
5 | #if defined(SOUNDWAVE_USING_WINMM)
6 | #define _WIN32_LEAN_AND_MEAN
7 | #include
8 | #undef min
9 | #undef max
10 |
11 | namespace olc::sound::driver
12 | {
13 | class WinMM : public Base
14 | {
15 | public:
16 | WinMM(WaveEngine* pHost);
17 | ~WinMM();
18 |
19 | protected:
20 | bool Open(const std::string& sOutputDevice, const std::string& sInputDevice) override;
21 | bool Start() override;
22 | void Stop() override;
23 | void Close() override;
24 |
25 | private:
26 | void DriverLoop();
27 | void FreeAudioBlock();
28 | static void CALLBACK waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD_PTR dwInstance, DWORD dwParam1, DWORD dwParam2);
29 | HWAVEOUT m_hwDevice = nullptr;
30 | std::thread m_thDriverLoop;
31 | std::atomic m_bDriverLoopActive{ false };
32 | std::unique_ptr[]> m_pvBlockMemory;
33 | std::unique_ptr m_pWaveHeaders;
34 | std::atomic m_nBlockFree = 0;
35 | std::condition_variable m_cvBlockNotZero;
36 | std::mutex m_muxBlockNotZero;
37 | uint32_t m_nBlockCurrent = 0;
38 | };
39 | }
40 | #endif // SOUNDWAVE_USING_WINMM
41 | ///[OLC_HM] END WINMM_H
42 |
--------------------------------------------------------------------------------
/source/swe_test_basics.cpp:
--------------------------------------------------------------------------------
1 |
2 |
3 | #include "swe_dummy.h"
4 |
5 |
6 |
7 | olc::sound::WaveEngine engine;
8 |
9 | int main()
10 | {
11 | engine.InitialiseAudio();
12 |
13 | olc::sound::Wave w("./assets/SampleA.wav");
14 |
15 |
16 | return 0;
17 | }
--------------------------------------------------------------------------------
/source/swe_wave_engine.cpp:
--------------------------------------------------------------------------------
1 | #include "swe_wave_engine.h"
2 |
3 | namespace olc::sound
4 | {
5 | ///[OLC_HM] START WAVE_ENGINE_CPP
6 | WaveEngine::WaveEngine()
7 | {
8 | m_sInputDevice = "NONE";
9 | m_sOutputDevice = "DEFAULT";
10 |
11 | #if defined(SOUNDWAVE_USING_WINMM)
12 | m_driver = std::make_unique(this);
13 | #endif
14 |
15 | #if defined(SOUNDWAVE_USING_WASAPI)
16 | m_driver = std::make_unique(this);
17 | #endif
18 |
19 | #if defined(SOUNDWAVE_USING_XAUDIO)
20 | m_driver = std::make_unique(this);
21 | #endif
22 |
23 | #if defined(SOUNDWAVE_USING_OPENAL)
24 | m_driver = std::make_unique(this);
25 | #endif
26 |
27 | #if defined(SOUNDWAVE_USING_ALSA)
28 | m_driver = std::make_unique(this);
29 | #endif
30 |
31 | #if defined(SOUNDWAVE_USING_SDLMIXER)
32 | m_driver = std::make_unique(this);
33 | #endif
34 |
35 | #if defined(SOUNDWAVE_USING_PULSE)
36 | m_driver = std::make_unique(this);
37 | #endif
38 | }
39 |
40 | WaveEngine::~WaveEngine()
41 | {
42 | DestroyAudio();
43 | }
44 |
45 | std::vector WaveEngine::GetOutputDevices()
46 | {
47 | return { "XXX" };
48 | }
49 |
50 |
51 | void WaveEngine::UseOutputDevice(const std::string& sDeviceOut)
52 | {
53 | m_sOutputDevice = sDeviceOut;
54 | }
55 |
56 | std::vector WaveEngine::GetInputDevices()
57 | {
58 | return { "XXX" };
59 | }
60 |
61 | void WaveEngine::UseInputDevice(const std::string& sDeviceIn)
62 | {
63 | m_sInputDevice = sDeviceIn;
64 | }
65 |
66 | bool WaveEngine::InitialiseAudio(uint32_t nSampleRate, uint32_t nChannels, uint32_t nBlocks, uint32_t nBlockSamples)
67 | {
68 | m_nSampleRate = nSampleRate;
69 | m_nChannels = nChannels;
70 | m_nBlocks = nBlocks;
71 | m_nBlockSamples = nBlockSamples;
72 | m_dSamplePerTime = double(nSampleRate);
73 | m_dTimePerSample = 1.0 / double(nSampleRate);
74 | m_driver->Open(m_sOutputDevice, m_sInputDevice);
75 | m_driver->Start();
76 | return false;
77 | }
78 |
79 |
80 | bool WaveEngine::DestroyAudio()
81 | {
82 | StopAll();
83 | m_driver->Stop();
84 | m_driver->Close();
85 | return false;
86 | }
87 |
88 | void WaveEngine::SetCallBack_NewSample(std::function func)
89 | {
90 | m_funcNewSample = func;
91 | }
92 |
93 | void WaveEngine::SetCallBack_SynthFunction(std::function func)
94 | {
95 | m_funcUserSynth = func;
96 | }
97 |
98 | void WaveEngine::SetCallBack_FilterFunction(std::function func)
99 | {
100 | m_funcUserFilter = func;
101 | }
102 |
103 | PlayingWave WaveEngine::PlayWaveform(Wave* pWave, bool bLoop, double dSpeed)
104 | {
105 | WaveInstance wi;
106 | wi.bLoop = bLoop;
107 | wi.pWave = pWave;
108 | wi.dSpeedModifier = dSpeed * double(pWave->file.samplerate()) / m_dSamplePerTime;
109 | wi.dDuration = pWave->file.duration() / dSpeed;
110 | wi.dInstanceTime = m_dGlobalTime;
111 | m_listWaves.push_back(wi);
112 | return std::prev(m_listWaves.end());
113 | }
114 |
115 | void WaveEngine::StopWaveform(const PlayingWave& w)
116 | {
117 | w->bFlagForStop = true;
118 | }
119 |
120 | void WaveEngine::StopAll()
121 | {
122 | for (auto& wave : m_listWaves)
123 | {
124 | wave.bFlagForStop = true;
125 | }
126 | }
127 |
128 | void WaveEngine::SetOutputVolume(const float fVolume)
129 | {
130 | m_fOutputVolume = std::clamp(fVolume, 0.0f, 1.0f);
131 | }
132 |
133 | uint32_t WaveEngine::FillOutputBuffer(std::vector& vBuffer, const uint32_t nBufferOffset, const uint32_t nRequiredSamples)
134 | {
135 | for (uint32_t nSample = 0; nSample < nRequiredSamples; nSample++)
136 | {
137 | double dSampleTime = m_dGlobalTime + nSample * m_dTimePerSample;
138 |
139 | if (m_funcNewSample)
140 | m_funcNewSample(dSampleTime);
141 |
142 | for (uint32_t nChannel = 0; nChannel < m_nChannels; nChannel++)
143 | {
144 | // Construct the sample
145 | float fSample = 0.0f;
146 |
147 | // 1) Sample any active waves
148 | for (auto& wave : m_listWaves)
149 | {
150 | // Is wave instance flagged for stopping?
151 | if (wave.bFlagForStop)
152 | {
153 | wave.bFinished = true;
154 | }
155 | else
156 | {
157 | // Calculate offset into wave instance
158 | double dTimeOffset = dSampleTime - wave.dInstanceTime;
159 |
160 | // If offset is larger than wave then...
161 | if (dTimeOffset >= wave.dDuration)
162 | {
163 | if (wave.bLoop)
164 | {
165 | // ...if looping, reset the wave instance
166 | wave.dInstanceTime = dSampleTime;
167 | }
168 | else
169 | {
170 | // ...if not looping, flag wave instance as dead
171 | wave.bFinished = true;
172 | }
173 | }
174 | else
175 | {
176 | // OR, sample the waveform from the correct channel
177 | fSample += float(wave.pWave->vChannelView[nChannel % wave.pWave->file.channels()].GetSample(dTimeOffset * m_dSamplePerTime * wave.dSpeedModifier));
178 | }
179 | }
180 | }
181 |
182 | // Remove waveform instances that have finished
183 | m_listWaves.remove_if([](const WaveInstance& wi) {return wi.bFinished; });
184 |
185 |
186 | // 2) If user is synthesizing, request sample
187 | if (m_funcUserSynth)
188 | fSample += m_funcUserSynth(nChannel, dSampleTime);
189 |
190 | // 3) Apply global filters
191 |
192 |
193 | // 4) If user is filtering, allow manipulation of output
194 | if (m_funcUserFilter)
195 | fSample = m_funcUserFilter(nChannel, dSampleTime, fSample);
196 |
197 | // Place sample in buffer
198 | vBuffer[nBufferOffset + nSample * m_nChannels + nChannel] = fSample * m_fOutputVolume;
199 | }
200 | }
201 |
202 | // UPdate global time, accounting for error (thanks scripticuk)
203 | m_dGlobalTime += nRequiredSamples * m_dTimePerSample;
204 | return nRequiredSamples;
205 | }
206 |
207 |
208 | uint32_t WaveEngine::GetSampleRate() const
209 | {
210 | return m_nSampleRate;
211 | }
212 |
213 | uint32_t WaveEngine::GetChannels() const
214 | {
215 | return m_nChannels;
216 | }
217 |
218 | uint32_t WaveEngine::GetBlocks() const
219 | {
220 | return m_nBlocks;
221 | }
222 |
223 | uint32_t WaveEngine::GetBlockSampleCount() const
224 | {
225 | return m_nBlockSamples;
226 | }
227 |
228 | double WaveEngine::GetTimePerSample() const
229 | {
230 | return m_dTimePerSample;
231 | }
232 | ///[OLC_HM] END WAVE_ENGINE_CPP
233 | } // SoundWave Interface Implementation - Driver agnostic
234 |
--------------------------------------------------------------------------------
/source/swe_wave_engine.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "swe_prefix.h"
4 | #include "swe_wave_wave.h"
5 | #include "swe_system_base.h"
6 | #include "swe_system_winmm.h"
7 | #include "swe_system_wasapi.h"
8 | #include "swe_system_openal.h"
9 | #include "swe_system_alsa.h"
10 | #include "swe_system_sdlmixer.h"
11 |
12 | namespace olc::sound
13 | {
14 | ///[OLC_HM] START WAVE_ENGINE_H
15 | struct WaveInstance
16 | {
17 | Wave* pWave = nullptr;
18 | double dInstanceTime = 0.0;
19 | double dDuration = 0.0;
20 | double dSpeedModifier = 1.0;
21 | bool bFinished = false;
22 | bool bLoop = false;
23 | bool bFlagForStop = false;
24 | };
25 |
26 | typedef std::list::iterator PlayingWave;
27 |
28 | namespace driver
29 | {
30 | class Base;
31 | }
32 |
33 | // Container class for Basic Sound Manipulation
34 | class WaveEngine
35 | {
36 |
37 | public:
38 | WaveEngine();
39 | virtual ~WaveEngine();
40 |
41 | // Configure Audio Hardware
42 | bool InitialiseAudio(uint32_t nSampleRate = 44100, uint32_t nChannels = 1, uint32_t nBlocks = 8, uint32_t nBlockSamples = 512);
43 |
44 | // Release Audio Hardware
45 | bool DestroyAudio();
46 |
47 | // Call to get the names of all the devices capable of audio output - DACs. An entry
48 | // from the returned collection can be specified as the device to use in UseOutputDevice()
49 | std::vector GetOutputDevices();
50 |
51 | // Specify a device for audio output prior to calling InitialiseAudio()
52 | void UseOutputDevice(const std::string& sDeviceOut);
53 |
54 | // Call to get the names of all the devices capable of audio input - ADCs. An entry
55 | // from the returned collection can be specified as the device to use in UseInputDevice()
56 | std::vector GetInputDevices();
57 |
58 | // Specify a device for audio input prior to calling InitialiseAudio()
59 | void UseInputDevice(const std::string& sDeviceOut);
60 |
61 |
62 | void SetCallBack_NewSample(std::function func);
63 | void SetCallBack_SynthFunction(std::function func);
64 | void SetCallBack_FilterFunction(std::function func);
65 |
66 | public:
67 | void SetOutputVolume(const float fVolume);
68 |
69 |
70 |
71 | PlayingWave PlayWaveform(Wave* pWave, bool bLoop = false, double dSpeed = 1.0);
72 | void StopWaveform(const PlayingWave& w);
73 | void StopAll();
74 |
75 | private:
76 | uint32_t FillOutputBuffer(std::vector& vBuffer, const uint32_t nBufferOffset, const uint32_t nRequiredSamples);
77 |
78 | private:
79 | std::unique_ptr m_driver;
80 | std::function m_funcNewSample;
81 | std::function m_funcUserSynth;
82 | std::function m_funcUserFilter;
83 |
84 |
85 | private:
86 | uint32_t m_nSampleRate = 44100;
87 | uint32_t m_nChannels = 1;
88 | uint32_t m_nBlocks = 8;
89 | uint32_t m_nBlockSamples = 512;
90 | double m_dSamplePerTime = 44100.0;
91 | double m_dTimePerSample = 1.0 / 44100;
92 | double m_dGlobalTime = 0.0;
93 | float m_fOutputVolume = 1.0;
94 |
95 | std::string m_sInputDevice;
96 | std::string m_sOutputDevice;
97 |
98 | private:
99 | std::list m_listWaves;
100 |
101 | public:
102 | uint32_t GetSampleRate() const;
103 | uint32_t GetChannels() const;
104 | uint32_t GetBlocks() const;
105 | uint32_t GetBlockSampleCount() const;
106 | double GetTimePerSample() const;
107 |
108 |
109 | // Friends, for access to FillOutputBuffer from Drivers
110 | friend class driver::Base;
111 |
112 | };
113 | ///[OLC_HM] END WAVE_ENGINE_H
114 | }
115 |
--------------------------------------------------------------------------------
/source/swe_wave_file.cpp:
--------------------------------------------------------------------------------
1 | #include "swe_wave_file.h"
2 |
--------------------------------------------------------------------------------
/source/swe_wave_file.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "swe_prefix.h"
4 |
5 | // Templates for manipulating wave data
6 | namespace olc::sound::wave
7 | {
8 | ///[OLC_HM] START WAVE_FILE_TEMPLATE
9 | // Physically represents a .WAV file, but the data is stored
10 | // as normalised floating point values
11 | template
12 | class File
13 | {
14 | public:
15 | File() = default;
16 |
17 | File(const size_t nChannels, const size_t nSampleSize, const size_t nSampleRate, const size_t nSamples)
18 | {
19 | m_nChannels = nChannels;
20 | m_nSampleSize = nSampleSize;
21 | m_nSamples = nSamples;
22 | m_nSampleRate = nSampleRate;
23 | m_dDuration = double(m_nSamples) / double(m_nSampleRate);
24 | m_dDurationInSamples = double(m_nSamples);
25 |
26 | m_pRawData = std::make_unique(m_nSamples * m_nChannels);
27 | }
28 |
29 | public:
30 | T* data() const
31 | {
32 | return m_pRawData.get();
33 | }
34 |
35 | size_t samples() const
36 | {
37 | return m_nSamples;
38 | }
39 |
40 | size_t channels() const
41 | {
42 | return m_nChannels;
43 | }
44 |
45 | size_t samplesize() const
46 | {
47 | return m_nSampleSize;
48 | }
49 |
50 | size_t samplerate() const
51 | {
52 | return m_nSampleRate;
53 | }
54 |
55 | double duration() const
56 | {
57 | return m_dDuration;
58 | }
59 |
60 | double durationInSamples() const
61 | {
62 | return m_dDurationInSamples;
63 | }
64 |
65 | bool LoadFile(const std::string& sFilename)
66 | {
67 | std::ifstream ifs(sFilename, std::ios::binary);
68 | if (!ifs.is_open())
69 | return false;
70 |
71 | struct WaveFormatHeader
72 | {
73 | uint16_t wFormatTag; /* format type */
74 | uint16_t nChannels; /* number of channels (i.e. mono, stereo...) */
75 | uint32_t nSamplesPerSec; /* sample rate */
76 | uint32_t nAvgBytesPerSec; /* for buffer estimation */
77 | uint16_t nBlockAlign; /* block size of data */
78 | uint16_t wBitsPerSample; /* number of bits per sample of mono data */
79 | };
80 |
81 | WaveFormatHeader header{ 0 };
82 |
83 | m_pRawData.reset();
84 |
85 | char dump[4];
86 | ifs.read(dump, sizeof(uint8_t) * 4); // Read "RIFF"
87 | if (strncmp(dump, "RIFF", 4) != 0) return false;
88 |
89 | ifs.read(dump, sizeof(uint8_t) * 4); // Not Interested
90 | ifs.read(dump, sizeof(uint8_t) * 4); // Read "WAVE"
91 | if (strncmp(dump, "WAVE", 4) != 0) return false;
92 |
93 | // Read Wave description chunk
94 | ifs.read(dump, sizeof(uint8_t) * 4); // Read "fmt "
95 | ifs.read(dump, sizeof(uint8_t) * 4); // Not Interested
96 | ifs.read((char*)&header, sizeof(WaveFormatHeader)); // Read Wave Format Structure chunk
97 |
98 | // Search for audio data chunk
99 | int32_t nChunksize = 0;
100 | ifs.read(dump, sizeof(uint8_t) * 4); // Read chunk header
101 | ifs.read((char*)&nChunksize, sizeof(uint32_t)); // Read chunk size
102 |
103 | while (strncmp(dump, "data", 4) != 0)
104 | {
105 | // Not audio data, so just skip it
106 | ifs.seekg(nChunksize, std::ios::cur);
107 | ifs.read(dump, sizeof(uint8_t) * 4); // Read next chunk header
108 | ifs.read((char*)&nChunksize, sizeof(uint32_t)); // Read next chunk size
109 | }
110 |
111 | // Finally got to data, so read it all in and convert to float samples
112 | m_nSampleSize = header.wBitsPerSample >> 3;
113 | m_nSamples = nChunksize / (header.nChannels * m_nSampleSize);
114 | m_nChannels = header.nChannels;
115 | m_nSampleRate = header.nSamplesPerSec;
116 | m_pRawData = std::make_unique(m_nSamples * m_nChannels);
117 | m_dDuration = double(m_nSamples) / double(m_nSampleRate);
118 | m_dDurationInSamples = double(m_nSamples);
119 |
120 | T* pSample = m_pRawData.get();
121 |
122 | // Read in audio data and normalise
123 | for (long i = 0; i < m_nSamples; i++)
124 | {
125 | for (int c = 0; c < m_nChannels; c++)
126 | {
127 | switch (m_nSampleSize)
128 | {
129 | case 1:
130 | {
131 | int8_t s = 0;
132 | ifs.read((char*)&s, sizeof(int8_t));
133 | *pSample = T(s) / T(std::numeric_limits::max());
134 | }
135 | break;
136 |
137 | case 2:
138 | {
139 | int16_t s = 0;
140 | ifs.read((char*)&s, sizeof(int16_t));
141 | *pSample = T(s) / T(std::numeric_limits::max());
142 | }
143 | break;
144 |
145 | case 3: // 24-bit
146 | {
147 | int32_t s = 0;
148 | ifs.read((char*)&s, 3);
149 | if (s & (1 << 23)) s |= 0xFF000000;
150 | *pSample = T(s) / T(std::pow(2, 23)-1);
151 | }
152 | break;
153 |
154 | case 4:
155 | {
156 | int32_t s = 0;
157 | ifs.read((char*)&s, sizeof(int32_t));
158 | *pSample = T(s) / T(std::numeric_limits::max());
159 | }
160 | break;
161 | }
162 |
163 | pSample++;
164 | }
165 | }
166 | return true;
167 | }
168 |
169 | bool SaveFile(const std::string& sFilename)
170 | {
171 | return false;
172 | }
173 |
174 |
175 | protected:
176 | std::unique_ptr m_pRawData;
177 | size_t m_nSamples = 0;
178 | size_t m_nChannels = 0;
179 | size_t m_nSampleRate = 0;
180 | size_t m_nSampleSize = 0;
181 | double m_dDuration = 0.0;
182 | double m_dDurationInSamples = 0.0;
183 | };
184 | ///[OLC_HM] END WAVE_FILE_TEMPLATE
185 | }
--------------------------------------------------------------------------------
/source/swe_wave_view.cpp:
--------------------------------------------------------------------------------
1 | #include "swe_wave_view.h"
2 |
--------------------------------------------------------------------------------
/source/swe_wave_view.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "swe_prefix.h"
4 |
5 | namespace olc::sound::wave
6 | {
7 | ///[OLC_HM] START WAVE_VIEW_TEMPLATE
8 | template
9 | class View
10 | {
11 | public:
12 | View() = default;
13 |
14 | View(const T* pData, const size_t nSamples)
15 | {
16 | SetData(pData, nSamples);
17 | }
18 |
19 | public:
20 | void SetData(T const* pData, const size_t nSamples, const size_t nStride = 1, const size_t nOffset = 0)
21 | {
22 | m_pData = pData;
23 | m_nSamples = nSamples;
24 | m_nStride = nStride;
25 | m_nOffset = nOffset;
26 | }
27 |
28 | double GetSample(const double dSample) const
29 | {
30 | double d1 = std::floor(dSample);
31 | size_t p1 = static_cast(d1);
32 | size_t p2 = p1 + 1;
33 |
34 | double t = dSample - d1;
35 | double a = GetValue(p1);
36 | double b = GetValue(p2);
37 |
38 | return a + t * (b - a); // std::lerp in C++20
39 | }
40 |
41 | std::pair GetRange(const double dSample1, const double dSample2) const
42 | {
43 | if (dSample1 < 0 || dSample2 < 0)
44 | return { 0,0 };
45 |
46 | if (dSample1 > m_nSamples && dSample2 > m_nSamples)
47 | return { 0,0 };
48 |
49 | double dMin, dMax;
50 |
51 | double d = GetSample(dSample1);
52 | dMin = dMax = d;
53 |
54 | size_t n1 = static_cast(std::ceil(dSample1));
55 | size_t n2 = static_cast(std::floor(dSample2));
56 | for (size_t n = n1; n < n2; n++)
57 | {
58 | d = GetValue(n);
59 | dMin = std::min(dMin, d);
60 | dMax = std::max(dMax, d);
61 | }
62 |
63 | d = GetSample(dSample2);
64 | dMin = std::min(dMin, d);
65 | dMax = std::max(dMax, d);
66 |
67 | return { dMin, dMax };
68 | }
69 |
70 | T GetValue(const size_t nSample) const
71 | {
72 | if (nSample >= m_nSamples)
73 | return 0;
74 | else
75 | return m_pData[m_nOffset + nSample * m_nStride];
76 | }
77 |
78 | private:
79 | const T* m_pData = nullptr;
80 | size_t m_nSamples = 0;
81 | size_t m_nStride = 1;
82 | size_t m_nOffset = 0;
83 | };
84 | ///[OLC_HM] END WAVE_VIEW_TEMPLATE
85 | }
86 |
--------------------------------------------------------------------------------
/source/swe_wave_wave.cpp:
--------------------------------------------------------------------------------
1 | #include "swe_wave_wave.h"
2 |
--------------------------------------------------------------------------------
/source/swe_wave_wave.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "swe_prefix.h"
4 | #include "swe_wave_view.h"
5 | #include "swe_wave_file.h"
6 |
7 | namespace olc::sound
8 | {
9 | ///[OLC_HM] START WAVE_GENERIC_TEMPLATE
10 | template
11 | class Wave_generic
12 | {
13 | public:
14 | Wave_generic() = default;
15 | Wave_generic(std::string sWavFile) { LoadAudioWaveform(sWavFile); }
16 | Wave_generic(std::istream& sStream) { LoadAudioWaveform(sStream); }
17 | Wave_generic(const char* pData, const size_t nBytes) { LoadAudioWaveform(pData, nBytes); }
18 |
19 | Wave_generic(const size_t nChannels, const size_t nSampleSize, const size_t nSampleRate, const size_t nSamples)
20 | {
21 | vChannelView.clear();
22 | file = wave::File(nChannels, nSampleSize, nSampleRate, nSamples);
23 | vChannelView.resize(file.channels());
24 | for (uint32_t c = 0; c < file.channels(); c++)
25 | vChannelView[c].SetData(file.data(), file.samples(), file.channels(), c);
26 | }
27 |
28 | bool LoadAudioWaveform(std::string sWavFile)
29 | {
30 | vChannelView.clear();
31 |
32 | if (file.LoadFile(sWavFile))
33 | {
34 | // Setup views for each channel
35 | vChannelView.resize(file.channels());
36 | for (uint32_t c = 0; c < file.channels(); c++)
37 | vChannelView[c].SetData(file.data(), file.samples(), file.channels(), c);
38 |
39 | return true;
40 | }
41 |
42 | return false;
43 | }
44 |
45 |
46 |
47 | bool LoadAudioWaveform(std::istream& sStream) { return false; }
48 | bool LoadAudioWaveform(const char* pData, const size_t nBytes) { return false; }
49 |
50 | std::vector> vChannelView;
51 | wave::File file;
52 | };
53 |
54 | typedef Wave_generic Wave;
55 | ///[OLC_HM] END WAVE_GENERIC_TEMPLATE
56 | }
--------------------------------------------------------------------------------