├── .clang-format ├── .gitignore ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── Modules │ └── waveTargetProperties.cmake └── waveConfig.cmake.in ├── include └── wave.h ├── src └── wave.c └── tests └── write_f32 ├── CMakeLists.txt └── main.c /.clang-format: -------------------------------------------------------------------------------- 1 | # vim: ft=yaml 2 | Language: Cpp 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveAssignments: true 6 | AlignConsecutiveDeclarations: true 7 | AlignEscapedNewlines: Left 8 | AlignOperands: true 9 | AlignTrailingComments: true 10 | # AllowAllArgumentsOnNextLine: true 11 | # AllowAllConstructorInitializersOnNextLine: true 12 | AllowAllParametersOfDeclarationOnNextLine: true 13 | AllowShortBlocksOnASingleLine: false 14 | # AllowShortLabelsOnASingleLine: true 15 | AllowShortFunctionsOnASingleLine: Inline 16 | AllowShortIfStatementsOnASingleLine: false 17 | # AllowShortLambdasOnASingleLine: Inline 18 | AllowShortLoopsOnASingleLine: false 19 | AlwaysBreakAfterReturnType: None 20 | AlwaysBreakBeforeMultilineStrings: false 21 | AlwaysBreakTemplateDeclarations: MultiLine 22 | BinPackArguments: true 23 | BinPackParameters: true 24 | BraceWrapping: 25 | # AfterCaseLabel: false 26 | AfterClass: true 27 | AfterControlStatement: false 28 | AfterEnum: false 29 | AfterFunction: false 30 | AfterNamespace: false 31 | AfterObjCDeclaration: false 32 | AfterStruct: false 33 | AfterUnion: false 34 | AfterExternBlock: false 35 | BeforeCatch: false 36 | BeforeElse: false 37 | IndentBraces: false 38 | SplitEmptyFunction: false 39 | SplitEmptyRecord: false 40 | SplitEmptyNamespace: false 41 | BreakAfterJavaFieldAnnotations: true 42 | BreakBeforeBinaryOperators: None 43 | BreakBeforeBraces: Attach 44 | BreakBeforeTernaryOperators: true 45 | BreakConstructorInitializers: BeforeComma 46 | BreakInheritanceList: BeforeComma 47 | BreakStringLiterals: true 48 | ColumnLimit: 0 49 | # CommentPragmas: 50 | CompactNamespaces: false 51 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 52 | ConstructorInitializerIndentWidth: 4 53 | ContinuationIndentWidth: 4 54 | Cpp11BracedListStyle: true 55 | DerivePointerAlignment: false 56 | # DisableFormat: true 57 | # ExperimentalAutoDetectBinPacking 58 | FixNamespaceComments: true 59 | # ForEachMacros 60 | IncludeBlocks: Preserve 61 | # IncludeCategories 62 | # IncludeIsMainRegex 63 | IndentCaseLabels: true 64 | IndentPPDirectives: None 65 | IndentWidth: 4 66 | IndentWrappedFunctionNames: false 67 | # JavaImportGroups: ['com.example', 'com', 'org'] 68 | JavaScriptQuotes: Leave 69 | JavaScriptWrapImports: true 70 | KeepEmptyLinesAtTheStartOfBlocks: false 71 | # MacroBlockBegin: 72 | # MacroBlockEnd: 73 | MaxEmptyLinesToKeep: 1 74 | NamespaceIndentation: None 75 | # NamespaceIndentation: Auto 76 | ObjCBlockIndentWidth: 4 77 | ObjCSpaceAfterProperty: false 78 | ObjCSpaceBeforeProtocolList: false 79 | # PenaltyBreakAssignment 80 | # PenaltyBreakBeforeFirstCallParameter 81 | # PenaltyBreakComment 82 | # PenaltyBreakFirstLessLess 83 | # PenaltyBreakString 84 | # PenaltyBreakTemplateDeclaration 85 | # PenaltyExcessCharacter 86 | # PenaltyReturnTypeOnItsOwnLine 87 | PointerAlignment: Left 88 | # RawStringFormats: 89 | ReflowComments: true 90 | SortIncludes: true 91 | SortUsingDeclarations: true 92 | SpaceAfterCStyleCast: false 93 | # SpaceAfterLogicalNot: false 94 | SpaceAfterTemplateKeyword: false 95 | SpaceBeforeAssignmentOperators: true 96 | SpaceBeforeCpp11BracedList: false 97 | SpaceBeforeCtorInitializerColon: true 98 | SpaceBeforeInheritanceColon: true 99 | SpaceBeforeParens: ControlStatements 100 | SpaceBeforeRangeBasedForLoopColon: true 101 | SpaceInEmptyParentheses: false 102 | SpacesBeforeTrailingComments: 1 103 | SpacesInAngles: false 104 | SpacesInCStyleCastParentheses: false 105 | SpacesInContainerLiterals: false 106 | SpacesInParentheses: false 107 | SpacesInSquareBrackets: false 108 | Standard: Auto 109 | # StatementMacros: 110 | TabWidth: 4 111 | UseTab: Never 112 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | # Debug files 32 | *.dSYM/ 33 | *.su 34 | 35 | .ycm_extra_conf.py 36 | .ycm_extra_conf.pyc 37 | 38 | /build/ 39 | 40 | .ccls 41 | .ccls-cache 42 | .cache 43 | compile_commands.json 44 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brglng/libwave/2da0f3d254d107ceb7ca2fcedf562145231192dc/CHANGELOG.md -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules") 3 | 4 | # Project Information 5 | project(wave VERSION "0.1.0") 6 | 7 | # CMake variables that affects building 8 | if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") 9 | if(CMAKE_BUILD_TYPE STREQUAL "") 10 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) 11 | endif() 12 | 13 | if(NOT DEFINED BUILD_SHARED_LIBS) 14 | set(BUILD_SHARED_LIBS ON CACHE BOOL "enable building of shared libraries instead of static ones" FORCE) 15 | endif() 16 | 17 | if(NOT DEFINED CMAKE_POSITION_INDEPENDENT_CODE) 18 | set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE BOOL "enable position independent code" FORCE) 19 | endif() 20 | 21 | include(CTest) 22 | message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") 23 | message(STATUS "CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}") 24 | message(STATUS "CMAKE_POSITION_INDEPENDENT_CODE: ${CMAKE_POSITION_INDEPENDENT_CODE}") 25 | message(STATUS "BUILD_SHARED_LIBS: ${BUILD_SHARED_LIBS}") 26 | message(STATUS "BUILD_TESTING: ${BUILD_TESTING}") 27 | endif() 28 | 29 | include(GNUInstallDirs) 30 | include(waveTargetProperties) 31 | 32 | add_library(${PROJECT_NAME} src/wave.c) 33 | add_library(wave::wave ALIAS wave) 34 | target_include_directories(${PROJECT_NAME} 35 | PUBLIC 36 | $ 37 | $ 38 | PRIVATE 39 | $ 40 | ) 41 | target_compile_features(${PROJECT_NAME} PRIVATE ${wave_compile_features}) 42 | target_compile_definitions(${PROJECT_NAME} PRIVATE ${wave_compile_definitions}) 43 | target_compile_options(${PROJECT_NAME} PRIVATE 44 | ${wave_c_flags} 45 | $<$:${wave_compile_options_release}> 46 | $<$:${wave_compile_options_release}> 47 | ) 48 | 49 | if(BUILD_TESTING AND "${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") 50 | add_subdirectory(tests/write_f32) 51 | endif() 52 | 53 | export(TARGETS wave NAMESPACE wave FILE waveTargets.cmake) 54 | 55 | install( 56 | TARGETS ${PROJECT_NAME} 57 | EXPORT waveTargets 58 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 59 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 60 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 61 | ) 62 | 63 | export(PACKAGE wave) 64 | 65 | install(DIRECTORY include DESTINATION .) 66 | 67 | install( 68 | FILES README.md CHANGELOG.md LICENSE 69 | DESTINATION ${CMAKE_INSTALL_DATADIR}/doc/${PROJECT_NAME} 70 | ) 71 | 72 | install(EXPORT waveTargets 73 | FILE waveTargets.cmake 74 | NAMESPACE wave:: 75 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} 76 | ) 77 | 78 | include(CMakePackageConfigHelpers) 79 | write_basic_package_version_file( 80 | waveConfigVersion.cmake 81 | VERSION ${PACKAGE_VERSION} 82 | COMPATIBILITY SameMajorVersion 83 | ) 84 | 85 | configure_file(cmake/waveConfig.cmake.in waveConfig.cmake @ONLY) 86 | install( 87 | FILES 88 | "${CMAKE_CURRENT_BINARY_DIR}/waveConfig.cmake" 89 | "${CMAKE_CURRENT_BINARY_DIR}/waveConfigVersion.cmake" 90 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} 91 | ) 92 | 93 | if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") 94 | set(CPACK_PACKAGE_NAME "wave") 95 | set(CPACK_GENERATOR "TXZ") 96 | set(CPACK_SOURCE_IGNORE_FILES 97 | "/\\\\.git/" 98 | "\\\\.git.*" 99 | "/build/" 100 | "/backup/" 101 | "/cmake-build-.*/" 102 | "/\\\\.idea/" 103 | "/\\\\.ycm_extra_conf\\\\..*" 104 | "/GPATH$" 105 | "/GRTAGS$" 106 | "/GSYMS$" 107 | "/GTAGS$" 108 | "\\\\.swp$" 109 | "\\\\.swo$" 110 | ".DS_Store" 111 | ".ccls" 112 | ".ccls-cache" 113 | "compile_commands.json" 114 | ) 115 | set(CPACK_SOURCE_GENERATOR "TXZ") 116 | include(CPack) 117 | endif() 118 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libwave 2 | 3 | libwave is a simple and tiny C library for reading or writing PCM wave (.wav) 4 | files. 5 | 6 | ## Build and Install 7 | 8 | On Linux and macOS: 9 | 10 | mkdir build 11 | cd build 12 | cmake [-DCMAKE_BUILD_TYPE=] .. 13 | make 14 | sudo make install 15 | 16 | On Windows: 17 | 18 | mkdir build 19 | cd build 20 | cmake .. 21 | cmake --build . 22 | 23 | ## CMake Support 24 | 25 | Use `FetchContent`: 26 | 27 | include(FetchContent) 28 | FetchContent_Declare(libwave 29 | GIT_REPOSITORY "https://github.com/brglng/libwave.git" 30 | GIT_SHALLOW ON 31 | ) 32 | FetchContent_MakeAvailable(libwave) 33 | add_executable(yourprogram yourprogram.c) 34 | target_link_libraries(yourprogram wave::wave) 35 | 36 | Use `add_subdirectory`: 37 | 38 | add_subdirectory(libwave) 39 | add_executable(yourprogram yourprogram.c) 40 | target_link_libraries(yourprogram wave::wave) 41 | 42 | Use `find_package`: 43 | 44 | find_package(wave) 45 | add_executable(yourprogram yourprogram.c) 46 | target_link_libraries(yourprogram wave::wave) 47 | -------------------------------------------------------------------------------- /cmake/Modules/waveTargetProperties.cmake: -------------------------------------------------------------------------------- 1 | set(wave_compile_features c_std_99) 2 | 3 | set(wave_compile_definitions 4 | __STDC_FORMAT_MACROS 5 | __STDC_LIMIT_MACROS 6 | __STDC_CONSTANT_MACROS 7 | ) 8 | 9 | if(NOT DEFINED wave_c_flags) 10 | set(wave_c_flags "") 11 | include(CheckCCompilerFlag) 12 | if(${CMAKE_C_COMPILER_ID} STREQUAL "MSVC") 13 | elseif(${CMAKE_C_COMPILER_ID} MATCHES "^(GNU|.*Clang)$") 14 | foreach(flag -fno-strict-aliasing 15 | -Wall 16 | -Wcast-align 17 | -Wduplicated-branches 18 | -Wduplicated-cond 19 | -Wextra 20 | -Wformat=2 21 | -Wmissing-include-dirs 22 | -Wnarrowing 23 | -Wpointer-arith 24 | -Wshadow 25 | -Wuninitialized 26 | -Wwrite-strings 27 | -Wno-multichar 28 | -Wno-format-nonliteral 29 | -Wno-format-truncation 30 | -Werror=discarded-qualifiers 31 | -Werror=ignored-qualifiers 32 | -Werror=implicit 33 | -Werror=implicit-function-declaration 34 | -Werror=implicit-int 35 | -Werror=init-self 36 | -Werror=incompatible-pointer-types 37 | -Werror=int-conversion 38 | -Werror=return-type 39 | -Werror=strict-prototypes 40 | ) 41 | check_c_compiler_flag(${flag} wave_has_c_flag_${flag}) 42 | if(wave_has_c_flag_${flag}) 43 | list(APPEND wave_c_flags ${flag}) 44 | endif() 45 | endforeach() 46 | endif() 47 | set(wave_c_flags ${wave_c_flags} CACHE INTERNAL "C compiler flags") 48 | endif() 49 | 50 | if(${CMAKE_C_COMPILER_ID} STREQUAL "MSVC") 51 | elseif(${CMAKE_C_COMPILER_ID} STREQUAL "GNU") 52 | set(wave_compile_options_release -fomit-frame-pointer -march=native -mtune=native -fvisibility=hidden) 53 | elseif(${CMAKE_C_COMPILER_ID} MATCHES "^.*Clang$") 54 | set(wave_compile_options_release -fomit-frame-pointer -fvisibility=hidden) 55 | endif() 56 | -------------------------------------------------------------------------------- /cmake/waveConfig.cmake.in: -------------------------------------------------------------------------------- 1 | include(CMakeFindDependencyMacro) 2 | include("${CMAKE_CURRENT_LIST_DIR}/waveTargets.cmake") 3 | -------------------------------------------------------------------------------- /include/wave.h: -------------------------------------------------------------------------------- 1 | /** Simple PCM wave file I/O library 2 | * 3 | * Author: Zhaosheng Pan 4 | * 5 | * The API is designed to be similar to stdio. 6 | * 7 | * This library does not support: 8 | * 9 | * - formats other than PCM, IEEE float and log-PCM 10 | * - extra chunks after the data chunk 11 | * - big endian platforms (might be supported in the future) 12 | */ 13 | 14 | #ifndef __WAVE_H__ 15 | #define __WAVE_H__ 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | #include 22 | #include 23 | 24 | #if !defined(_MSC_VER) || _MSC_VER >= 1800 25 | #define WAVE_INLINE static inline 26 | #define WAVE_CONST const 27 | #define WAVE_RESTRICT restrict 28 | #else 29 | #define WAVE_INLINE static __inline 30 | #define WAVE_CONST 31 | #define WAVE_RESTRICT __restrict 32 | #endif 33 | 34 | #if defined(_MSC_VER) 35 | #define WAVE_API __declspec(dllexport) 36 | #else 37 | #define WAVE_API __attribute__((visibility("default"))) 38 | #endif 39 | 40 | #if defined(_WIN32) || defined(_WIN64) || defined(__APPLE__) 41 | typedef long long WaveI64; 42 | typedef unsigned long long WaveU64; 43 | typedef long long WaveIntPtr; 44 | typedef unsigned long long WaveUIntPtr; 45 | #elif defined(__x86_64) || defined(__amd64) || defined(__aarch64__) 46 | typedef long WaveI64; 47 | typedef unsigned long WaveU64; 48 | typedef long WaveIntPtr; 49 | typedef unsigned long WaveUIntPtr; 50 | #else 51 | typedef long long WaveI64; 52 | typedef unsigned long long WaveU64; 53 | typedef long long WaveIntPtr; 54 | typedef unsigned long long WaveUIntPtr; 55 | #endif 56 | 57 | #if defined(__cplusplus) && __cplusplus >= 201103L 58 | #define WAVE_THREAD_LOCAL thread_local 59 | #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L 60 | #define WAVE_THREAD_LOCAL _Thread_local 61 | #elif defined(_MSC_VER) 62 | #define WAVE_THREAD_LOCAL __declspec(thread) 63 | #else 64 | #define WAVE_THREAD_LOCAL __thread 65 | #endif 66 | 67 | typedef int WaveBool; 68 | typedef signed char WaveI8; 69 | typedef unsigned char WaveU8; 70 | typedef short WaveI16; 71 | typedef unsigned short WaveU16; 72 | typedef int WaveI32; 73 | typedef unsigned int WaveU32; 74 | 75 | enum { 76 | WAVE_FALSE, 77 | WAVE_TRUE 78 | }; 79 | 80 | /* wave file format codes */ 81 | 82 | // Windows already has these macros in mmreg.h 83 | #if !((defined(_WIN32) || defined(_WIN64)) && defined(WAVE_FORMAT_PCM)) 84 | #define WAVE_FORMAT_PCM ((WaveU16)0x0001) 85 | #endif 86 | 87 | #if !((defined(_WIN32) || defined(_WIN64)) && defined(WAVE_FORMAT_IEEE_FLOAT)) 88 | #define WAVE_FORMAT_IEEE_FLOAT ((WaveU16)0x0003) 89 | #endif 90 | 91 | #if !((defined(_WIN32) || defined(_WIN64)) && defined(WAVE_FORMAT_ALAW)) 92 | #define WAVE_FORMAT_ALAW ((WaveU16)0x0006) 93 | #endif 94 | 95 | #if !((defined(_WIN32) || defined(_WIN64)) && defined(WAVE_FORMAT_MULAW)) 96 | #define WAVE_FORMAT_MULAW ((WaveU16)0x0007) 97 | #endif 98 | 99 | #if !((defined(_WIN32) || defined(_WIN64)) && defined(WAVE_FORMAT_EXTENSIBLE)) 100 | #define WAVE_FORMAT_EXTENSIBLE ((WaveU16)0xfffe) 101 | #endif 102 | 103 | typedef enum { 104 | WAVE_OK, /** no error */ 105 | WAVE_ERR_OS, /** error when {wave} called a stdio function */ 106 | WAVE_ERR_FORMAT, /** not a wave file or unsupported wave format */ 107 | WAVE_ERR_MODE, /** incorrect mode when opening the wave file or calling mode-specific API */ 108 | WAVE_ERR_PARAM, /** incorrect parameter passed to the API function */ 109 | } WaveErrCode; 110 | 111 | typedef struct { 112 | WaveErrCode code; 113 | char* message; 114 | int _is_literal; 115 | } WaveErr; 116 | 117 | typedef struct { 118 | void* (*malloc)(void *context, size_t size); 119 | void* (*realloc)(void *context, void *p, size_t size); 120 | void (*free)(void *context, void *p); 121 | } WaveAllocFuncs; 122 | 123 | void wave_set_allocator(void *context, WAVE_CONST WaveAllocFuncs *funcs); 124 | 125 | void* wave_malloc(size_t size); 126 | void* wave_realloc(void *p, size_t size); 127 | void wave_free(void *p); 128 | 129 | char* wave_strdup(WAVE_CONST char *str); 130 | char* wave_strndup(WAVE_CONST char *str, size_t n); 131 | int wave_vasprintf(char **str, WAVE_CONST char *format, va_list args); 132 | int wave_asprintf(char **str, WAVE_CONST char *format, ...); 133 | 134 | WAVE_API WAVE_CONST WaveErr* wave_err(void); 135 | WAVE_API void wave_err_clear(void); 136 | 137 | #define WAVE_OPEN_READ 1 138 | #define WAVE_OPEN_WRITE 2 139 | #define WAVE_OPEN_APPEND 4 140 | 141 | typedef struct _WaveFile WaveFile; 142 | 143 | /** Open a wav file 144 | * 145 | * @param filename The name of the wav file 146 | * @param mode The mode for open (same as {fopen}) 147 | * @return NULL if the memory allocation for the {WaveFile} object failed. Non-NULL means the memory allocation succeeded, but there can be other errors, which can be obtained using {wave_err}. 148 | */ 149 | WAVE_API WaveFile* wave_open(WAVE_CONST char* filename, WaveU32 mode); 150 | WAVE_API void wave_close(WaveFile* self); 151 | WAVE_API WaveFile* wave_reopen(WaveFile* self, WAVE_CONST char* filename, WaveU32 mode); 152 | 153 | /** Read a block of samples from the wav file 154 | * 155 | * @param buffer A pointer to a buffer where the data will be placed 156 | * @param count The number of frames (block size) 157 | * @param self The pointer to the {WaveFile} structure 158 | * @return The number of frames read. If returned value is less than {count}, either EOF reached or an error occured 159 | * @remarks This API does not support extensible format. For extensible format, use {wave_read_raw} instead. 160 | */ 161 | WAVE_API size_t wave_read(WaveFile* self, void *buffer, size_t count); 162 | 163 | /** Write a block of samples to the wav file 164 | * 165 | * @param buffer A pointer to the buffer of data 166 | * @param count The number of frames (block size) 167 | * @param self The pointer to the {WaveFile} structure 168 | * @return The number of frames written. If returned value is less than {count}, either EOF reached or an error occured. 169 | * @remarks This API does not support extensible format. For extensible format, use {wave_read_raw} instead. 170 | */ 171 | WAVE_API size_t wave_write(WaveFile* self, WAVE_CONST void *buffer, size_t count); 172 | 173 | /** Tell the current position in the wav file. 174 | * 175 | * @param self The pointer to the WaveFile structure. 176 | * @return The current frame index. 177 | */ 178 | WAVE_API long int wave_tell(WAVE_CONST WaveFile* self); 179 | 180 | WAVE_API int wave_seek(WaveFile* self, long int offset, int origin); 181 | WAVE_API void wave_rewind(WaveFile* self); 182 | 183 | /** Tell if the end of the wav file is reached. 184 | * 185 | * @param self The pointer to the WaveFile structure. 186 | * @return Non-zero integer if the end of the wav file is reached, otherwise zero. 187 | */ 188 | WAVE_API int wave_eof(WAVE_CONST WaveFile* self); 189 | 190 | WAVE_API int wave_flush(WaveFile* self); 191 | 192 | /** Set the format code 193 | * 194 | * @param self The {WaveFile} object 195 | * @param format The format code, which should be one of `WAVE_FORMAT_*` 196 | * @remarks All data will be cleared after the call. {wave_err} can be used to get the error code if there is an error. 197 | */ 198 | WAVE_API void wave_set_format(WaveFile* self, WaveU16 format); 199 | 200 | /** Set the number of channels 201 | * 202 | * @param self The {WaveFile} object 203 | * @param num_channels The number of channels 204 | * @remarks All data will be cleared after the call. {wave_err} can be used to get the error code if there is an error. 205 | */ 206 | WAVE_API void wave_set_num_channels(WaveFile* self, WaveU16 num_channels); 207 | 208 | /** Set the sample rate 209 | * 210 | * @param self The {WaveFile} object 211 | * @param sample_rate The sample rate 212 | * @remarks All data will be cleared after the call. {wave_err} can be used to get the error code if there is an error. 213 | */ 214 | WAVE_API void wave_set_sample_rate(WaveFile* self, WaveU32 sample_rate); 215 | 216 | /** Get the number of valid bits per sample 217 | * 218 | * @param self The {WaveFile} object 219 | * @param bits The value of valid bits to set 220 | * @remarks If {bits} is 0 or larger than 8*{sample_size}, an error will occur. All data will be cleared after the call. {wave_err} can be used to get the error code if there is an error. 221 | */ 222 | WAVE_API void wave_set_valid_bits_per_sample(WaveFile* self, WaveU16 bits); 223 | 224 | /** Set the size (in bytes) per sample 225 | * 226 | * @param self The WaveeFile object 227 | * @param sample_size Number of bytes per sample 228 | * @remarks When this function is called, the {BitsPerSample} and {ValidBitsPerSample} fields in the wav file will be set to 8*{sample_size}. All data will be cleared after the call. {wave_err} can be used to get the error code if there is an error. 229 | */ 230 | WAVE_API void wave_set_sample_size(WaveFile* self, size_t sample_size); 231 | 232 | WAVE_API WaveU16 wave_get_format(WAVE_CONST WaveFile* self); 233 | WAVE_API WaveU16 wave_get_num_channels(WAVE_CONST WaveFile* self); 234 | WAVE_API WaveU32 wave_get_sample_rate(WAVE_CONST WaveFile* self); 235 | WAVE_API WaveU16 wave_get_valid_bits_per_sample(WAVE_CONST WaveFile* self); 236 | WAVE_API size_t wave_get_sample_size(WAVE_CONST WaveFile* self); 237 | WAVE_API size_t wave_get_length(WAVE_CONST WaveFile* self); 238 | WAVE_API WaveU32 wave_get_channel_mask(WAVE_CONST WaveFile* self); 239 | WAVE_API WaveU16 wave_get_sub_format(WAVE_CONST WaveFile* self); 240 | 241 | #ifdef __cplusplus 242 | } 243 | #endif 244 | 245 | #endif /* __WAVE_H__ */ 246 | -------------------------------------------------------------------------------- /src/wave.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "wave.h" 8 | 9 | #define WAVE_ENDIAN_ORDER_LITTLE 0x41424344UL 10 | #define WAVE_ENDIAN_ORDER_BIG 0x44434241UL 11 | #define WAVE_ENDIAN_ORDER_PDP 0x42414443UL 12 | #define WAVE_ENDIAN_ORDER 'ABCD' 13 | 14 | #if WAVE_ENDIAN_ORDER == WAVE_ENDIAN_ORDER_LITTLE 15 | #define WAVE_ENDIAN_LITTLE 1 16 | #define WAVE_ENDIAN_BIG 0 17 | #elif WAVE_ENDIAN_ORDER == WAVE_ENDIAN_ORDER_BIG 18 | #define WAVE_ENDIAN_LITTLE 0 19 | #define WAVE_ENDIAN_BIG 1 20 | #else 21 | #error "unsupported endianess" 22 | #endif 23 | 24 | #if WAVE_ENDIAN_LITTLE 25 | #define WAVE_RIFF_CHUNK_ID ((WaveU32)'FFIR') 26 | #define WAVE_FORMAT_CHUNK_ID ((WaveU32)' tmf') 27 | #define WAVE_FACT_CHUNK_ID ((WaveU32)'tcaf') 28 | #define WAVE_DATA_CHUNK_ID ((WaveU32)'atad') 29 | #define WAVE_WAVE_ID ((WaveU32)'EVAW') 30 | #endif 31 | 32 | #if WAVE_ENDIAN_BIG 33 | #define WAVE_RIFF_CHUNK_ID ((WaveU32)'RIFF') 34 | #define WAVE_FORMAT_CHUNK_ID ((WaveU32)'fmt ') 35 | #define WAVE_FACT_CHUNK_ID ((WaveU32)'fact') 36 | #define WAVE_DATA_CHUNK_ID ((WaveU32)'data') 37 | #define WAVE_WAVE_ID ((WaveU32)'WAVE') 38 | #endif 39 | 40 | WAVE_THREAD_LOCAL WaveErr g_err = {WAVE_OK, (char*)"", 1}; 41 | 42 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 43 | 44 | static void* wave_default_malloc(void *context, size_t size) 45 | { 46 | (void)context; 47 | void *p = malloc(size); 48 | assert(p != NULL); 49 | return p; 50 | } 51 | 52 | static void* wave_default_realloc(void *context, void *p, size_t size) 53 | { 54 | (void)context; 55 | void *ptr = realloc(p, size); 56 | assert(ptr != NULL); 57 | return ptr; 58 | } 59 | 60 | static void wave_default_free(void *context, void *p) 61 | { 62 | (void)context; 63 | free(p); 64 | } 65 | 66 | static WaveAllocFuncs g_default_alloc_funcs = { 67 | &wave_default_malloc, 68 | &wave_default_realloc, 69 | &wave_default_free 70 | }; 71 | 72 | static void *g_alloc_context = NULL; 73 | static WAVE_CONST WaveAllocFuncs* g_alloc_funcs = &g_default_alloc_funcs; 74 | 75 | void wave_set_allocator(void *context, WAVE_CONST WaveAllocFuncs *funcs) 76 | { 77 | g_alloc_context = context; 78 | g_alloc_funcs = funcs; 79 | } 80 | 81 | void* wave_malloc(size_t size) 82 | { 83 | return g_alloc_funcs->malloc(g_alloc_context, size); 84 | } 85 | 86 | void* wave_realloc(void *p, size_t size) 87 | { 88 | return g_alloc_funcs->realloc(g_alloc_context, p, size); 89 | } 90 | 91 | void wave_free(void *p) 92 | { 93 | if (p != NULL) { 94 | g_alloc_funcs->free(g_alloc_context, p); 95 | } 96 | } 97 | 98 | char* wave_strdup(WAVE_CONST char *str) 99 | { 100 | size_t len = strlen(str) + 1; 101 | void *new = wave_malloc(len); 102 | if (new == NULL) 103 | return NULL; 104 | 105 | return memcpy(new, str, len); 106 | } 107 | 108 | char* wave_strndup(WAVE_CONST char *str, size_t n) 109 | { 110 | char *result = wave_malloc(n + 1); 111 | if (result == NULL) 112 | return NULL; 113 | 114 | result[n] = 0; 115 | return memcpy(result, str, n); 116 | } 117 | 118 | int wave_vasprintf(char **str, WAVE_CONST char *format, va_list args) 119 | { 120 | int size = 0; 121 | 122 | va_list args_copy; 123 | va_copy(args_copy, args); 124 | size = vsnprintf(NULL, (size_t)size, format, args_copy); 125 | va_end(args_copy); 126 | 127 | if (size < 0) { 128 | return size; 129 | } 130 | 131 | *str = wave_malloc((size_t)size + 1); 132 | if (*str == NULL) 133 | return -1; 134 | 135 | return vsprintf(*str, format, args); 136 | } 137 | 138 | int wave_asprintf(char **str, WAVE_CONST char *format, ...) 139 | { 140 | va_list args; 141 | va_start(args, format); 142 | int size = wave_vasprintf(str, format, args); 143 | va_end(args); 144 | return size; 145 | } 146 | 147 | WAVE_CONST WaveErr* wave_err(void) 148 | { 149 | return &g_err; 150 | } 151 | 152 | void wave_err_clear(void) 153 | { 154 | if (g_err.code != WAVE_OK) { 155 | if (!g_err._is_literal) { 156 | wave_free(g_err.message); 157 | } 158 | g_err.code = WAVE_OK; 159 | g_err.message = (char*)""; 160 | g_err._is_literal = 1; 161 | } 162 | } 163 | 164 | WAVE_INLINE void wave_err_set(WaveErrCode code, WAVE_CONST char *format, ...) 165 | { 166 | assert(g_err.code == WAVE_OK); 167 | va_list args; 168 | va_start(args, format); 169 | g_err.code = code; 170 | wave_vasprintf(&g_err.message, format, args); 171 | g_err._is_literal = 0; 172 | va_end(args); 173 | } 174 | 175 | WAVE_INLINE void wave_err_set_literal(WaveErrCode code, WAVE_CONST char *message) 176 | { 177 | assert(g_err.code == WAVE_OK); 178 | g_err.code = code; 179 | g_err.message = (char *)message; 180 | g_err._is_literal = 1; 181 | } 182 | 183 | #pragma pack(push, 1) 184 | 185 | typedef struct { 186 | WaveU32 id; 187 | WaveU32 size; 188 | } WaveChunkHeader; 189 | 190 | typedef struct { 191 | WaveChunkHeader header; 192 | 193 | WaveU64 offset; 194 | 195 | struct { 196 | WaveU16 format_tag; 197 | WaveU16 num_channels; 198 | WaveU32 sample_rate; 199 | WaveU32 avg_bytes_per_sec; 200 | WaveU16 block_align; 201 | WaveU16 bits_per_sample; 202 | 203 | WaveU16 ext_size; 204 | WaveU16 valid_bits_per_sample; 205 | WaveU32 channel_mask; 206 | 207 | WaveU8 sub_format[16]; 208 | } body; 209 | } WaveFormatChunk; 210 | 211 | typedef struct { 212 | WaveChunkHeader header; 213 | 214 | WaveU64 offset; 215 | 216 | struct { 217 | WaveU32 sample_length; 218 | } body; 219 | } WaveFactChunk; 220 | 221 | typedef struct { 222 | WaveChunkHeader header; 223 | WaveU64 offset; 224 | } WaveDataChunk; 225 | 226 | typedef struct { 227 | WaveU32 id; 228 | WaveU32 size; 229 | WaveU32 wave_id; 230 | WaveU64 offset; 231 | } WaveMasterChunk; 232 | 233 | #pragma pack(pop) 234 | 235 | #define WAVE_CHUNK_MASTER ((WaveU32)1) 236 | #define WAVE_CHUNK_FORMAT ((WaveU32)2) 237 | #define WAVE_CHUNK_FACT ((WaveU32)4) 238 | #define WAVE_CHUNK_DATA ((WaveU32)8) 239 | 240 | struct _WaveFile { 241 | FILE* fp; 242 | char* filename; 243 | WaveU32 mode; 244 | WaveBool is_a_new_file; 245 | 246 | WaveMasterChunk riff_chunk; 247 | WaveFormatChunk format_chunk; 248 | WaveFactChunk fact_chunk; 249 | WaveDataChunk data_chunk; 250 | }; 251 | 252 | static WAVE_CONST WaveU8 default_sub_format[16] = { 253 | 0x01, 0x00, 254 | 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 255 | }; 256 | 257 | void wave_parse_header(WaveFile* self) 258 | { 259 | size_t read_count; 260 | 261 | read_count = fread(&self->riff_chunk, sizeof(WaveChunkHeader), 1, self->fp); 262 | if (read_count != 1) { 263 | wave_err_set_literal(WAVE_ERR_FORMAT, "Unexpected EOF"); 264 | return; 265 | } 266 | 267 | if (self->riff_chunk.id != WAVE_RIFF_CHUNK_ID) { 268 | wave_err_set_literal(WAVE_ERR_FORMAT, "Not a RIFF file"); 269 | return; 270 | } 271 | 272 | read_count = fread(&self->riff_chunk.wave_id, 4, 1, self->fp); 273 | if (read_count != 1) { 274 | wave_err_set_literal(WAVE_ERR_FORMAT, "Unexpected EOF"); 275 | return; 276 | } 277 | if (self->riff_chunk.wave_id != WAVE_WAVE_ID) { 278 | wave_err_set_literal(WAVE_ERR_FORMAT, "Not a WAVE file"); 279 | return; 280 | } 281 | 282 | self->riff_chunk.offset = (WaveU64)ftell(self->fp); 283 | 284 | while (self->data_chunk.header.id != WAVE_DATA_CHUNK_ID) { 285 | WaveChunkHeader header; 286 | 287 | read_count = fread(&header, sizeof(WaveChunkHeader), 1, self->fp); 288 | if (read_count != 1) { 289 | wave_err_set_literal(WAVE_ERR_FORMAT, "Unexpected EOF"); 290 | return; 291 | } 292 | 293 | switch (header.id) { 294 | case WAVE_FORMAT_CHUNK_ID: 295 | self->format_chunk.header = header; 296 | self->format_chunk.offset = (WaveU64)ftell(self->fp); 297 | read_count = fread(&self->format_chunk.body, MIN(header.size, sizeof(self->format_chunk.body)), 1, self->fp); 298 | if (read_count != 1) { 299 | wave_err_set_literal(WAVE_ERR_FORMAT, "Unexpected EOF"); 300 | return; 301 | } 302 | if (self->format_chunk.body.format_tag != WAVE_FORMAT_PCM && 303 | self->format_chunk.body.format_tag != WAVE_FORMAT_IEEE_FLOAT && 304 | self->format_chunk.body.format_tag != WAVE_FORMAT_ALAW && 305 | self->format_chunk.body.format_tag != WAVE_FORMAT_MULAW) 306 | { 307 | wave_err_set(WAVE_ERR_FORMAT, "Unsupported format tag: %#010x", self->format_chunk.body.format_tag); 308 | return; 309 | } 310 | break; 311 | case WAVE_FACT_CHUNK_ID: 312 | self->fact_chunk.header = header; 313 | self->fact_chunk.offset = (WaveU64)ftell(self->fp); 314 | read_count = fread(&self->fact_chunk.body, MIN(header.size, sizeof(self->fact_chunk.body)), 1, self->fp); 315 | if (read_count != 1) { 316 | wave_err_set(WAVE_ERR_FORMAT, "Unexpected EOF"); 317 | } 318 | break; 319 | case WAVE_DATA_CHUNK_ID: 320 | self->data_chunk.header = header; 321 | self->data_chunk.offset = (WaveU64)ftell(self->fp); 322 | break; 323 | default: 324 | if (fseek(self->fp, header.size, SEEK_CUR) < 0) { 325 | wave_err_set(WAVE_ERR_OS, "fseek() failed [errno %d: %s]", errno, strerror(errno)); 326 | return; 327 | } 328 | break; 329 | } 330 | } 331 | } 332 | 333 | void wave_write_header(WaveFile* self) 334 | { 335 | self->riff_chunk.size = 336 | sizeof(self->riff_chunk.wave_id) + 337 | (self->format_chunk.header.id == WAVE_FORMAT_CHUNK_ID ? (sizeof(WaveChunkHeader) + self->format_chunk.header.size) : 0) + 338 | (self->fact_chunk.header.id == WAVE_FACT_CHUNK_ID ? (sizeof(WaveChunkHeader) + self->fact_chunk.header.size) : 0) + 339 | (self->data_chunk.header.id == WAVE_DATA_CHUNK_ID ? (sizeof(WaveChunkHeader) + self->data_chunk.header.size) : 0); 340 | 341 | if (fseek(self->fp, 0, SEEK_SET) != 0) { 342 | wave_err_set(WAVE_ERR_OS, "fseek() failed [errno %d: %s]", errno, strerror(errno)); 343 | return; 344 | } 345 | if (fwrite(&self->riff_chunk, sizeof(WaveChunkHeader) + 4, 1, self->fp) != 1) { 346 | wave_err_set(WAVE_ERR_OS, "Error while writing to %s [errno %d: %s]", self->filename, errno, strerror(errno)); 347 | return; 348 | } 349 | 350 | if (self->format_chunk.header.id == WAVE_FORMAT_CHUNK_ID) { 351 | if (fseek(self->fp, (long)(self->format_chunk.offset - sizeof(WaveChunkHeader)), SEEK_SET) != 0) { 352 | wave_err_set(WAVE_ERR_OS, "fseek() failed [errno %d: %s]", errno, strerror(errno)); 353 | return; 354 | } 355 | if (fwrite(&self->format_chunk.header, sizeof(WaveChunkHeader), 1, self->fp) != 1) { 356 | wave_err_set(WAVE_ERR_OS, "Error while writing to %s [errno %d: %s]", self->filename, errno, strerror(errno)); 357 | return; 358 | } 359 | if (fwrite(&self->format_chunk.body, self->format_chunk.header.size, 1, self->fp) != 1) { 360 | wave_err_set(WAVE_ERR_OS, "Error while writing to %s [errno %d: %s]", self->filename, errno, strerror(errno)); 361 | return; 362 | } 363 | } 364 | 365 | if (self->fact_chunk.header.id == WAVE_FACT_CHUNK_ID) { 366 | if (fseek(self->fp, (long)(self->fact_chunk.offset - sizeof(WaveChunkHeader)), SEEK_SET) != 0) { 367 | wave_err_set(WAVE_ERR_OS, "fseek() failed [errno %d: %s]", errno, strerror(errno)); 368 | return; 369 | } 370 | if (fwrite(&self->fact_chunk.header, sizeof(WaveChunkHeader), 1, self->fp) != 1) { 371 | wave_err_set(WAVE_ERR_OS, "Error while writing to %s [errno %d: %s]", self->filename, errno, strerror(errno)); 372 | return; 373 | } 374 | if (fwrite(&self->fact_chunk.body, self->fact_chunk.header.size, 1, self->fp) != 1) { 375 | wave_err_set(WAVE_ERR_OS, "Error while writing to %s [errno %d: %s]", self->filename, errno, strerror(errno)); 376 | return; 377 | } 378 | } 379 | 380 | if (self->data_chunk.header.id == WAVE_DATA_CHUNK_ID) { 381 | if (fseek(self->fp, (long)(self->data_chunk.offset - sizeof(WaveChunkHeader)), SEEK_SET) != 0) { 382 | wave_err_set(WAVE_ERR_OS, "fseek() failed [errno %d: %s]", errno, strerror(errno)); 383 | return; 384 | } 385 | if (fwrite(&self->data_chunk.header, sizeof(WaveChunkHeader), 1, self->fp) != 1) { 386 | wave_err_set(WAVE_ERR_OS, "Error while writing to %s [errno %d: %s]", self->filename, errno, strerror(errno)); 387 | return; 388 | } 389 | } 390 | } 391 | 392 | void wave_init(WaveFile* self, WAVE_CONST char* filename, WaveU32 mode) 393 | { 394 | memset(self, 0, sizeof(WaveFile)); 395 | 396 | if (mode & WAVE_OPEN_READ) { 397 | if ((mode & WAVE_OPEN_WRITE) || (mode & WAVE_OPEN_APPEND)) { 398 | self->fp = fopen(filename, "wb+"); 399 | } else { 400 | self->fp = fopen(filename, "rb"); 401 | } 402 | } else { 403 | if ((mode & WAVE_OPEN_WRITE) || (mode & WAVE_OPEN_APPEND)) { 404 | self->fp = fopen(filename, "wb+"); 405 | } else { 406 | wave_err_set_literal(WAVE_ERR_PARAM, "Invalid mode"); 407 | return; 408 | } 409 | } 410 | 411 | if (self->fp == NULL) { 412 | wave_err_set(WAVE_ERR_OS, "Error when opening %s [errno %d: %s]", filename, errno, strerror(errno)); 413 | return; 414 | } 415 | 416 | self->filename = wave_strdup(filename); 417 | self->mode = mode; 418 | 419 | if (!(self->mode & WAVE_OPEN_WRITE) && !(self->mode & WAVE_OPEN_APPEND)) { 420 | wave_parse_header(self); 421 | return; 422 | } 423 | 424 | if (self->mode & WAVE_OPEN_APPEND) { 425 | wave_parse_header(self); 426 | if (g_err.code == WAVE_OK) { 427 | // If the header parsing was successful, return immediately. 428 | return; 429 | } else { 430 | // Header parsing failed. Regard it as a new file. 431 | wave_err_clear(); 432 | rewind(self->fp); 433 | self->is_a_new_file = WAVE_TRUE; 434 | } 435 | } 436 | 437 | // reaches here only if creating a new file 438 | 439 | self->riff_chunk.id = WAVE_RIFF_CHUNK_ID; 440 | /* self->chunk.size = calculated by wave_write_header */ 441 | self->riff_chunk.wave_id = WAVE_WAVE_ID; 442 | self->riff_chunk.offset = sizeof(WaveChunkHeader) + 4; 443 | 444 | self->format_chunk.header.id = WAVE_FORMAT_CHUNK_ID; 445 | self->format_chunk.header.size = (WaveU32)((WaveUIntPtr)&self->format_chunk.body.ext_size - (WaveUIntPtr)&self->format_chunk.body); 446 | self->format_chunk.offset = self->riff_chunk.offset + sizeof(WaveChunkHeader); 447 | self->format_chunk.body.format_tag = WAVE_FORMAT_PCM; 448 | self->format_chunk.body.num_channels = 2; 449 | self->format_chunk.body.sample_rate = 44100; 450 | self->format_chunk.body.avg_bytes_per_sec = 44100 * 2 * 2; 451 | self->format_chunk.body.block_align = 4; 452 | self->format_chunk.body.bits_per_sample = 16; 453 | 454 | memcpy(self->format_chunk.body.sub_format, default_sub_format, 16); 455 | 456 | self->data_chunk.header.id = WAVE_DATA_CHUNK_ID; 457 | self->data_chunk.offset = self->format_chunk.offset + self->format_chunk.header.size + sizeof(WaveChunkHeader); 458 | 459 | wave_write_header(self); 460 | } 461 | 462 | void wave_finalize(WaveFile* self) 463 | { 464 | int ret; 465 | 466 | wave_free(self->filename); 467 | 468 | if (self->fp == NULL) { 469 | return; 470 | } 471 | 472 | ret = fclose(self->fp); 473 | if (ret != 0) { 474 | fprintf(stderr, "[WARN] [libwav] fclose failed with code %d [errno %d: %s]", ret, errno, strerror(errno)); 475 | return; 476 | } 477 | } 478 | 479 | WaveFile* wave_open(WAVE_CONST char* filename, WaveU32 mode) 480 | { 481 | WaveFile* self = wave_malloc(sizeof(WaveFile)); 482 | if (self == NULL) { 483 | return NULL; 484 | } 485 | 486 | wave_init(self, filename, mode); 487 | 488 | return self; 489 | } 490 | 491 | void wave_close(WaveFile* self) 492 | { 493 | wave_finalize(self); 494 | wave_free(self); 495 | } 496 | 497 | WaveFile* wave_reopen(WaveFile* self, WAVE_CONST char* filename, WaveU32 mode) 498 | { 499 | wave_finalize(self); 500 | wave_init(self, filename, mode); 501 | return self; 502 | } 503 | 504 | size_t wave_read(WaveFile* self, void *buffer, size_t count) 505 | { 506 | size_t read_count; 507 | WaveU16 n_channels = wave_get_num_channels(self); 508 | size_t sample_size = wave_get_sample_size(self); 509 | size_t len_remain; 510 | 511 | if (!(self->mode & WAVE_OPEN_READ)) { 512 | wave_err_set_literal(WAVE_ERR_MODE, "This WaveFile is not readable"); 513 | return 0; 514 | } 515 | 516 | if (self->format_chunk.body.format_tag == WAVE_FORMAT_EXTENSIBLE) { 517 | wave_err_set_literal(WAVE_ERR_FORMAT, "Extensible format is not supported"); 518 | return 0; 519 | } 520 | 521 | len_remain = wave_get_length(self) - (size_t)wave_tell(self); 522 | if (g_err.code != WAVE_OK) { 523 | return 0; 524 | } 525 | count = (count <= len_remain) ? count : len_remain; 526 | 527 | if (count == 0) { 528 | return 0; 529 | } 530 | 531 | read_count = fread(buffer, sample_size, n_channels * count, self->fp); 532 | if (ferror(self->fp)) { 533 | wave_err_set(WAVE_ERR_OS, "Error when reading %s [errno %d: %s]", self->filename, errno, strerror(errno)); 534 | return 0; 535 | } 536 | 537 | return read_count / n_channels; 538 | } 539 | 540 | WAVE_INLINE void wave_update_sizes(WaveFile *self) 541 | { 542 | long int save_pos = ftell(self->fp); 543 | if (fseek(self->fp, (long)(sizeof(WaveChunkHeader) - 4), SEEK_SET) != 0) { 544 | wave_err_set(WAVE_ERR_OS, "fseek() failed [errno %d: %s]", errno, strerror(errno)); 545 | return; 546 | } 547 | if (fwrite(&self->riff_chunk.size, 4, 1, self->fp) != 1) { 548 | wave_err_set(WAVE_ERR_OS, "fwrite() failed [errno %d: %s]", errno, strerror(errno)); 549 | return; 550 | } 551 | if (self->fact_chunk.header.id == WAVE_FACT_CHUNK_ID) { 552 | if (fseek(self->fp, (long)self->fact_chunk.offset, SEEK_SET) != 0) { 553 | wave_err_set(WAVE_ERR_OS, "fseek() failed [errno %d: %s]", errno, strerror(errno)); 554 | return; 555 | } 556 | if (fwrite(&self->fact_chunk.body.sample_length, 4, 1, self->fp) != 1) { 557 | wave_err_set(WAVE_ERR_OS, "fwrite() failed [errno %d: %s]", errno, strerror(errno)); 558 | return; 559 | } 560 | } 561 | if (fseek(self->fp, (long)(self->data_chunk.offset - 4), SEEK_SET) != 0) { 562 | wave_err_set(WAVE_ERR_OS, "fseek() failed [errno %d: %s]", errno, strerror(errno)); 563 | return; 564 | } 565 | if (fwrite(&self->data_chunk.header.size, 4, 1, self->fp) != 1) { 566 | wave_err_set(WAVE_ERR_OS, "fwrite() failed [errno %d: %s]", errno, strerror(errno)); 567 | return; 568 | } 569 | if (fseek(self->fp, save_pos, SEEK_SET) != 0) { 570 | wave_err_set(WAVE_ERR_OS, "fseek() failed [errno %d: %s]", errno, strerror(errno)); 571 | return; 572 | } 573 | } 574 | 575 | size_t wave_write(WaveFile* self, WAVE_CONST void *buffer, size_t count) 576 | { 577 | size_t write_count; 578 | WaveU16 n_channels = wave_get_num_channels(self); 579 | size_t sample_size = wave_get_sample_size(self); 580 | 581 | if (!(self->mode & WAVE_OPEN_WRITE) && !(self->mode & WAVE_OPEN_APPEND)) { 582 | wave_err_set_literal(WAVE_ERR_MODE, "This WaveFile is not writable"); 583 | return 0; 584 | } 585 | 586 | if (self->format_chunk.body.format_tag == WAVE_FORMAT_EXTENSIBLE) { 587 | wave_err_set_literal(WAVE_ERR_FORMAT, "Extensible format is not supported"); 588 | return 0; 589 | } 590 | 591 | if (count == 0) { 592 | return 0; 593 | } 594 | 595 | wave_tell(self); 596 | if (g_err.code != WAVE_OK) { 597 | return 0; 598 | } 599 | 600 | if (!(self->mode & WAVE_OPEN_READ) && !(self->mode & WAVE_OPEN_WRITE)) { 601 | wave_seek(self, 0, SEEK_END); 602 | if (g_err.code != WAVE_OK) { 603 | return 0; 604 | } 605 | } 606 | 607 | write_count = fwrite(buffer, sample_size, n_channels * count, self->fp); 608 | if (ferror(self->fp)) { 609 | wave_err_set(WAVE_ERR_OS, "Error when writing to %s [errno %d: %s]", self->filename, errno, strerror(errno)); 610 | return 0; 611 | } 612 | 613 | self->riff_chunk.size += write_count * sample_size; 614 | if (self->fact_chunk.header.id == WAVE_FACT_CHUNK_ID) { 615 | self->fact_chunk.body.sample_length += write_count / n_channels; 616 | } 617 | self->data_chunk.header.size += write_count * sample_size; 618 | 619 | wave_update_sizes(self); 620 | if (g_err.code != WAVE_OK) 621 | return 0; 622 | 623 | return write_count / n_channels; 624 | } 625 | 626 | long int wave_tell(WAVE_CONST WaveFile* self) 627 | { 628 | long pos = ftell(self->fp); 629 | 630 | if (pos == -1L) { 631 | wave_err_set(WAVE_ERR_OS, "ftell() failed [errno %d: %s]", errno, strerror(errno)); 632 | return -1L; 633 | } 634 | 635 | assert(pos >= (long)self->data_chunk.offset); 636 | 637 | return (long)(((WaveU64)pos - self->data_chunk.offset) / (self->format_chunk.body.block_align)); 638 | } 639 | 640 | int wave_seek(WaveFile* self, long int offset, int origin) 641 | { 642 | size_t length = wave_get_length(self); 643 | int ret; 644 | 645 | if (origin == SEEK_CUR) { 646 | offset += (long)wave_tell(self); 647 | } else if (origin == SEEK_END) { 648 | offset += (long)length; 649 | } 650 | 651 | /* POSIX allows seeking beyond end of file */ 652 | if (offset >= 0) { 653 | offset *= self->format_chunk.body.block_align; 654 | } else { 655 | wave_err_set_literal(WAVE_ERR_PARAM, "Invalid seek"); 656 | return (int)g_err.code; 657 | } 658 | 659 | ret = fseek(self->fp, (long)self->data_chunk.offset + offset, SEEK_SET); 660 | 661 | if (ret != 0) { 662 | wave_err_set(WAVE_ERR_OS, "fseek() failed [errno %d: %s]", errno, strerror(errno)); 663 | return (int)ret; 664 | } 665 | 666 | return 0; 667 | } 668 | 669 | void wave_rewind(WaveFile* self) 670 | { 671 | wave_seek(self, 0, SEEK_SET); 672 | } 673 | 674 | int wave_eof(WAVE_CONST WaveFile* self) 675 | { 676 | return feof(self->fp) || ftell(self->fp) == (long)(self->data_chunk.offset + self->data_chunk.header.size); 677 | } 678 | 679 | int wave_flush(WaveFile* self) 680 | { 681 | int ret = fflush(self->fp); 682 | 683 | if (ret != 0) { 684 | wave_err_set(WAVE_ERR_OS, "fflush() failed [errno %d: %s]", errno, strerror(errno)); 685 | } 686 | 687 | return ret; 688 | } 689 | 690 | void wave_set_format(WaveFile* self, WaveU16 format) 691 | { 692 | if (!(self->mode & WAVE_OPEN_WRITE) && !((self->mode & WAVE_OPEN_APPEND) && self->is_a_new_file && self->data_chunk.header.size == 0)) { 693 | wave_err_set_literal(WAVE_ERR_MODE, "This WaveFile is not writable"); 694 | return; 695 | } 696 | 697 | if (format == self->format_chunk.body.format_tag) 698 | return; 699 | 700 | self->format_chunk.body.format_tag = format; 701 | if (format != WAVE_FORMAT_PCM && format != WAVE_FORMAT_EXTENSIBLE) { 702 | self->format_chunk.body.ext_size = 0; 703 | self->format_chunk.header.size = (WaveU32)((WaveUIntPtr)&self->format_chunk.body.ext_size - (WaveUIntPtr)&self->format_chunk.body); 704 | } else if (format == WAVE_FORMAT_EXTENSIBLE) { 705 | self->format_chunk.body.ext_size = 22; 706 | self->format_chunk.header.size = sizeof(WaveFormatChunk) - sizeof(WaveChunkHeader); 707 | } 708 | 709 | if (format == WAVE_FORMAT_ALAW || format == WAVE_FORMAT_MULAW) { 710 | WaveU16 sample_size = wave_get_sample_size(self); 711 | if (sample_size != 1) { 712 | wave_set_sample_size(self, 1); 713 | } 714 | } else if (format == WAVE_FORMAT_IEEE_FLOAT) { 715 | WaveU16 sample_size = wave_get_sample_size(self); 716 | if (sample_size != 4 && sample_size != 8) { 717 | wave_set_sample_size(self, 4); 718 | } 719 | } 720 | 721 | wave_write_header(self); 722 | } 723 | 724 | void wave_set_num_channels(WaveFile* self, WaveU16 num_channels) 725 | { 726 | if (!(self->mode & WAVE_OPEN_WRITE) && !((self->mode & WAVE_OPEN_APPEND) && self->is_a_new_file && self->data_chunk.header.size == 0)) { 727 | wave_err_set_literal(WAVE_ERR_MODE, "This WaveFile is not writable"); 728 | return; 729 | } 730 | 731 | if (num_channels < 1) { 732 | wave_err_set(WAVE_ERR_PARAM, "Invalid number of channels: %u", num_channels); 733 | return; 734 | } 735 | 736 | WaveU16 old_num_channels = self->format_chunk.body.num_channels; 737 | if (num_channels == old_num_channels) 738 | return; 739 | 740 | self->format_chunk.body.num_channels = num_channels; 741 | self->format_chunk.body.block_align = self->format_chunk.body.block_align / old_num_channels * num_channels; 742 | self->format_chunk.body.avg_bytes_per_sec = self->format_chunk.body.block_align * self->format_chunk.body.sample_rate; 743 | 744 | wave_write_header(self); 745 | } 746 | 747 | void wave_set_sample_rate(WaveFile* self, WaveU32 sample_rate) 748 | { 749 | if (!(self->mode & WAVE_OPEN_WRITE) && !((self->mode & WAVE_OPEN_APPEND) && self->is_a_new_file && self->data_chunk.header.size == 0)) { 750 | wave_err_set_literal(WAVE_ERR_MODE, "This WaveFile is not writable"); 751 | return; 752 | } 753 | 754 | if (sample_rate == self->format_chunk.body.sample_rate) 755 | return; 756 | 757 | self->format_chunk.body.sample_rate = sample_rate; 758 | self->format_chunk.body.avg_bytes_per_sec = self->format_chunk.body.block_align * self->format_chunk.body.sample_rate; 759 | 760 | wave_write_header(self); 761 | } 762 | 763 | void wave_set_valid_bits_per_sample(WaveFile* self, WaveU16 bits) 764 | { 765 | if (!(self->mode & WAVE_OPEN_WRITE) && !((self->mode & WAVE_OPEN_APPEND) && self->is_a_new_file && self->data_chunk.header.size == 0)) { 766 | wave_err_set_literal(WAVE_ERR_MODE, "This WaveFile is not writable"); 767 | return; 768 | } 769 | 770 | if (bits < 1 || bits > 8 * self->format_chunk.body.block_align / self->format_chunk.body.num_channels) { 771 | wave_err_set(WAVE_ERR_PARAM, "Invalid ValidBitsPerSample: %u", bits); 772 | return; 773 | } 774 | 775 | if ((self->format_chunk.body.format_tag == WAVE_FORMAT_ALAW || self->format_chunk.body.format_tag == WAVE_FORMAT_MULAW) && bits != 8) { 776 | wave_err_set(WAVE_ERR_PARAM, "Invalid ValidBitsPerSample: %u", bits); 777 | return; 778 | } 779 | 780 | if (self->format_chunk.body.format_tag != WAVE_FORMAT_EXTENSIBLE) { 781 | self->format_chunk.body.bits_per_sample = bits; 782 | } else { 783 | self->format_chunk.body.bits_per_sample = 8 * self->format_chunk.body.block_align / self->format_chunk.body.num_channels; 784 | self->format_chunk.body.valid_bits_per_sample = bits; 785 | } 786 | 787 | wave_write_header(self); 788 | } 789 | 790 | void wave_set_sample_size(WaveFile* self, size_t sample_size) 791 | { 792 | if (!(self->mode & WAVE_OPEN_WRITE) && !((self->mode & WAVE_OPEN_APPEND) && self->is_a_new_file && self->data_chunk.header.size == 0)) { 793 | wave_err_set_literal(WAVE_ERR_MODE, "This WaveFile is not writable"); 794 | return; 795 | } 796 | 797 | if (sample_size < 1) { 798 | wave_err_set(WAVE_ERR_PARAM, "Invalid sample size: %zu", sample_size); 799 | return; 800 | } 801 | 802 | self->format_chunk.body.block_align = (WaveU16)(sample_size * self->format_chunk.body.num_channels); 803 | self->format_chunk.body.avg_bytes_per_sec = self->format_chunk.body.block_align * self->format_chunk.body.sample_rate; 804 | self->format_chunk.body.bits_per_sample = (WaveU16)(sample_size * 8); 805 | if (self->format_chunk.body.format_tag == WAVE_FORMAT_EXTENSIBLE) { 806 | self->format_chunk.body.valid_bits_per_sample = (WaveU16)(sample_size * 8); 807 | } 808 | 809 | wave_write_header(self); 810 | } 811 | 812 | void wave_set_channel_mask(WaveFile* self, WaveU32 channel_mask) 813 | { 814 | if (!(self->mode & WAVE_OPEN_WRITE) && !((self->mode & WAVE_OPEN_APPEND) && self->is_a_new_file && self->data_chunk.header.size == 0)) { 815 | wave_err_set_literal(WAVE_ERR_MODE, "This WaveFile is not writable"); 816 | return; 817 | } 818 | 819 | if (self->format_chunk.body.format_tag != WAVE_FORMAT_EXTENSIBLE) { 820 | wave_err_set_literal(WAVE_ERR_FORMAT, "Extensible format is not supported"); 821 | return; 822 | } 823 | 824 | self->format_chunk.body.channel_mask = channel_mask; 825 | 826 | wave_write_header(self); 827 | } 828 | 829 | void wave_set_sub_format(WaveFile* self, WaveU16 sub_format) 830 | { 831 | if (!(self->mode & WAVE_OPEN_WRITE) && !((self->mode & WAVE_OPEN_APPEND) && self->is_a_new_file && self->data_chunk.header.size == 0)) { 832 | wave_err_set_literal(WAVE_ERR_MODE, "This WaveFile is not writable"); 833 | return; 834 | } 835 | 836 | if (self->format_chunk.body.format_tag != WAVE_FORMAT_EXTENSIBLE) { 837 | wave_err_set_literal(WAVE_ERR_FORMAT, "Extensible format is not supported"); 838 | return; 839 | } 840 | 841 | self->format_chunk.body.sub_format[0] = (WaveU8)(sub_format & 0xff); 842 | self->format_chunk.body.sub_format[1] = (WaveU8)(sub_format >> 8); 843 | 844 | wave_write_header(self); 845 | } 846 | 847 | WaveU16 wave_get_format(WAVE_CONST WaveFile* self) 848 | { 849 | return self->format_chunk.body.format_tag; 850 | } 851 | 852 | WaveU16 wave_get_num_channels(WAVE_CONST WaveFile* self) 853 | { 854 | return self->format_chunk.body.num_channels; 855 | } 856 | 857 | WaveU32 wave_get_sample_rate(WAVE_CONST WaveFile* self) 858 | { 859 | return self->format_chunk.body.sample_rate; 860 | } 861 | 862 | WaveU16 wave_get_valid_bits_per_sample(WAVE_CONST WaveFile* self) 863 | { 864 | if (self->format_chunk.body.format_tag != WAVE_FORMAT_EXTENSIBLE) { 865 | return self->format_chunk.body.bits_per_sample; 866 | } else { 867 | return self->format_chunk.body.valid_bits_per_sample; 868 | } 869 | } 870 | 871 | size_t wave_get_sample_size(WAVE_CONST WaveFile* self) 872 | { 873 | return self->format_chunk.body.block_align / self->format_chunk.body.num_channels; 874 | } 875 | 876 | size_t wave_get_length(WAVE_CONST WaveFile* self) 877 | { 878 | return self->data_chunk.header.size / (self->format_chunk.body.block_align); 879 | } 880 | 881 | WaveU32 wave_get_channel_mask(WAVE_CONST WaveFile* self) 882 | { 883 | return self->format_chunk.body.channel_mask; 884 | } 885 | 886 | WaveU16 wave_get_sub_format(WAVE_CONST WaveFile* self) 887 | { 888 | WaveU16 sub_format = self->format_chunk.body.sub_format[1]; 889 | sub_format <<= 8; 890 | sub_format |= self->format_chunk.body.sub_format[0]; 891 | return sub_format; 892 | } 893 | -------------------------------------------------------------------------------- /tests/write_f32/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(write-f32 main.c) 2 | target_link_libraries(write-f32 3 | wave::wave 4 | $<$:m> 5 | ) 6 | target_include_directories(write-f32 PRIVATE ${CMAKE_SOURCE_DIR}/include) 7 | target_compile_features(write-f32 PRIVATE ${wave_compile_features}) 8 | target_compile_definitions(write-f32 PRIVATE ${wave_compile_definitions}) 9 | target_compile_options(write-f32 PRIVATE 10 | ${wave_c_flags} 11 | $<$:${wave_compile_options_release}> 12 | $<$:${wave_compile_options_release}> 13 | ) 14 | -------------------------------------------------------------------------------- /tests/write_f32/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "wave.h" 4 | 5 | void generate_sine_wave(float *x, int sample_rate, int len) 6 | { 7 | for (int i = 0; i < len; ++i) { 8 | x[i] = 0.5f * cosf(2 * 3.14159265358979323f * 440.0f * i / sample_rate); 9 | } 10 | } 11 | 12 | int main(void) 13 | { 14 | float *buf = malloc(sizeof(float) * 10 * 44100); 15 | generate_sine_wave(buf, 44100, 10 * 44100); 16 | WaveFile *fp = wave_open("out.wav", WAVE_OPEN_WRITE); 17 | wave_set_format(fp, WAVE_FORMAT_IEEE_FLOAT); 18 | /* wave_set_sample_size(fp, sizeof(float)); */ 19 | wave_set_num_channels(fp, 1); 20 | wave_set_sample_rate(fp, 44100); 21 | wave_write(fp, buf, 10 * 44100); 22 | wave_close(fp); 23 | free(buf); 24 | return 0; 25 | } 26 | --------------------------------------------------------------------------------