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