From 7dac325122067bd8b453c0ec60fc1a768bb6f934 Mon Sep 17 00:00:00 2001 From: stefonzo Date: Mon, 5 Sep 2022 20:17:22 -0500 Subject: adds libwav --- 3rdparty/libwav/.clang-format | 111 +++ 3rdparty/libwav/.gitignore | 41 + 3rdparty/libwav/CHANGELOG.md | 0 3rdparty/libwav/CMakeLists.txt | 116 +++ 3rdparty/libwav/LICENSE | 373 +++++++++ 3rdparty/libwav/README.md | 46 ++ .../libwav/cmake/Modules/wavTargetProperties.cmake | 55 ++ 3rdparty/libwav/cmake/wavConfig.cmake.in | 2 + 3rdparty/libwav/include/wav.h | 225 ++++++ 3rdparty/libwav/src/wav.c | 881 +++++++++++++++++++++ 3rdparty/libwav/tests/write_f32/CMakeLists.txt | 13 + 3rdparty/libwav/tests/write_f32/main.c | 25 + CMakeLists.txt | 3 +- 13 files changed, 1890 insertions(+), 1 deletion(-) create mode 100644 3rdparty/libwav/.clang-format create mode 100644 3rdparty/libwav/.gitignore create mode 100644 3rdparty/libwav/CHANGELOG.md create mode 100644 3rdparty/libwav/CMakeLists.txt create mode 100644 3rdparty/libwav/LICENSE create mode 100644 3rdparty/libwav/README.md create mode 100644 3rdparty/libwav/cmake/Modules/wavTargetProperties.cmake create mode 100644 3rdparty/libwav/cmake/wavConfig.cmake.in create mode 100644 3rdparty/libwav/include/wav.h create mode 100644 3rdparty/libwav/src/wav.c create mode 100644 3rdparty/libwav/tests/write_f32/CMakeLists.txt create mode 100644 3rdparty/libwav/tests/write_f32/main.c diff --git a/3rdparty/libwav/.clang-format b/3rdparty/libwav/.clang-format new file mode 100644 index 0000000..9cb71b7 --- /dev/null +++ b/3rdparty/libwav/.clang-format @@ -0,0 +1,111 @@ +# vim: ft=yaml +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: true +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +# AllowAllArgumentsOnNextLine: true +# AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +# AllowShortLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: false +# AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + # AfterCaseLabel: false + AfterClass: true + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakAfterJavaFieldAnnotations: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +BreakStringLiterals: true +ColumnLimit: 0 +# CommentPragmas: +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +# DisableFormat: true +# ExperimentalAutoDetectBinPacking +FixNamespaceComments: true +# ForEachMacros +IncludeBlocks: Preserve +# IncludeCategories +# IncludeIsMainRegex +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +# JavaImportGroups: ['com.example', 'com', 'org'] +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +# MacroBlockBegin: +# MacroBlockEnd: +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +# NamespaceIndentation: Auto +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: false +# PenaltyBreakAssignment +# PenaltyBreakBeforeFirstCallParameter +# PenaltyBreakComment +# PenaltyBreakFirstLessLess +# PenaltyBreakString +# PenaltyBreakTemplateDeclaration +# PenaltyExcessCharacter +# PenaltyReturnTypeOnItsOwnLine +PointerAlignment: Left +# RawStringFormats: +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +# SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +# StatementMacros: +TabWidth: 4 +UseTab: Never diff --git a/3rdparty/libwav/.gitignore b/3rdparty/libwav/.gitignore new file mode 100644 index 0000000..eb4e76f --- /dev/null +++ b/3rdparty/libwav/.gitignore @@ -0,0 +1,41 @@ +# Object files +*.o +*.ko +*.obj +*.elf + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su + +.ycm_extra_conf.py +.ycm_extra_conf.pyc + +/build/ + +.ccls +.ccls-cache diff --git a/3rdparty/libwav/CHANGELOG.md b/3rdparty/libwav/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/3rdparty/libwav/CMakeLists.txt b/3rdparty/libwav/CMakeLists.txt new file mode 100644 index 0000000..c3faa46 --- /dev/null +++ b/3rdparty/libwav/CMakeLists.txt @@ -0,0 +1,116 @@ +cmake_minimum_required(VERSION 3.14) +list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules") + +# Project Information +project(wav VERSION "0.1.0") + +# CMake variables that affects building +if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") + if(CMAKE_BUILD_TYPE STREQUAL "") + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) + endif() + + if(NOT DEFINED BUILD_SHARED_LIBS) + set(BUILD_SHARED_LIBS ON CACHE BOOL "enable building of shared libraries instead of static ones" FORCE) + endif() + + if(NOT DEFINED CMAKE_POSITION_INDEPENDENT_CODE) + set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE BOOL "enable position independent code" FORCE) + endif() + + include(CTest) + message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") + message(STATUS "CMAKE_INSTALL_PREFIX: ${CMAKE_INSTALL_PREFIX}") + message(STATUS "CMAKE_POSITION_INDEPENDENT_CODE: ${CMAKE_POSITION_INDEPENDENT_CODE}") + message(STATUS "BUILD_SHARED_LIBS: ${BUILD_SHARED_LIBS}") + message(STATUS "BUILD_TESTING: ${BUILD_TESTING}") +endif() + +include(GNUInstallDirs) +include(wavTargetProperties) + +add_library(${PROJECT_NAME} src/wav.c) +add_library(wav::wav ALIAS wav) +target_include_directories(${PROJECT_NAME} + PUBLIC + $ + $ + PRIVATE + $ + ) +target_compile_features(${PROJECT_NAME} PRIVATE ${wav_compile_features}) +target_compile_definitions(${PROJECT_NAME} PRIVATE ${wav_compile_definitions}) +target_compile_options(${PROJECT_NAME} PRIVATE + ${wav_c_flags} + $<$:${wav_compile_options_release}> + $<$:${wav_compile_options_release}> + ) + +if(BUILD_TESTING AND "${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") + add_subdirectory(tests/write_f32) +endif() + +export(TARGETS wav NAMESPACE wav FILE wavTargets.cmake) + +install( + TARGETS ${PROJECT_NAME} + EXPORT wavTargets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) + +export(PACKAGE wav) + +install(DIRECTORY include DESTINATION .) + +install( + FILES README.md CHANGELOG.md LICENSE + DESTINATION ${CMAKE_INSTALL_DATADIR}/doc/${PROJECT_NAME} + ) + +install(EXPORT wavTargets + FILE wavTargets.cmake + NAMESPACE wav:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} + ) + +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + wavConfigVersion.cmake + VERSION ${PACKAGE_VERSION} + COMPATIBILITY SameMajorVersion + ) + +configure_file(cmake/wavConfig.cmake.in wavConfig.cmake @ONLY) +install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/wavConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/wavConfigVersion.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} + ) + +if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") + set(CPACK_PACKAGE_NAME "wav") + set(CPACK_GENERATOR "TXZ") + set(CPACK_SOURCE_IGNORE_FILES + "/\\\\.git/" + "\\\\.git.*" + "/build/" + "/backup/" + "/cmake-build-.*/" + "/\\\\.idea/" + "/\\\\.ycm_extra_conf\\\\..*" + "/GPATH$" + "/GRTAGS$" + "/GSYMS$" + "/GTAGS$" + "\\\\.swp$" + "\\\\.swo$" + ".DS_Store" + ".ccls" + ".ccls-cache" + ) + set(CPACK_SOURCE_GENERATOR "TXZ") + include(CPack) +endif() diff --git a/3rdparty/libwav/LICENSE b/3rdparty/libwav/LICENSE new file mode 100644 index 0000000..14e2f77 --- /dev/null +++ b/3rdparty/libwav/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/3rdparty/libwav/README.md b/3rdparty/libwav/README.md new file mode 100644 index 0000000..00560c5 --- /dev/null +++ b/3rdparty/libwav/README.md @@ -0,0 +1,46 @@ +# libwav + +libwav is a simple and tiny C library for reading or writing PCM wave (.wav) +files. + +## Build and Install + +On Linux and macOS: + + mkdir build + cd build + cmake [-DCMAKE_BUILD_TYPE=] .. + make + sudo make install + +On Windows: + + mkdir build + cd build + cmake .. + cmake --build . + +## CMake Support + +Use `FetchContent`: + + include(FetchContent) + FetchContent_Declare(libwav + GIT_REPOSITORY "https://github.com/brglng/libwav.git" + GIT_SHALLOW ON + ) + FetchContent_MakeAvailable(libwav) + add_executable(yourprogram yourprogram.c) + target_link_libraries(yourprogram wav::wav) + +Use `add_subdirectory`: + + add_subdirectory(libwav) + add_executable(yourprogram yourprogram.c) + target_link_libraries(yourprogram wav::wav) + +Use `find_package`: + + find_package(wav) + add_executable(yourprogram yourprogram.c) + target_link_libraries(yourprogram wav::wav) diff --git a/3rdparty/libwav/cmake/Modules/wavTargetProperties.cmake b/3rdparty/libwav/cmake/Modules/wavTargetProperties.cmake new file mode 100644 index 0000000..21c5657 --- /dev/null +++ b/3rdparty/libwav/cmake/Modules/wavTargetProperties.cmake @@ -0,0 +1,55 @@ +set(wav_compile_features c_std_99) + +set(wav_compile_definitions + __STDC_FORMAT_MACROS + __STDC_LIMIT_MACROS + __STDC_CONSTANT_MACROS + ) + +if(NOT DEFINED wav_c_flags) + set(wav_c_flags "") + include(CheckCCompilerFlag) + if(${CMAKE_C_COMPILER_ID} STREQUAL "MSVC") + elseif(${CMAKE_C_COMPILER_ID} MATCHES "^(GNU|.*Clang)$") + foreach(flag -fno-strict-aliasing + -Wall + -Wcast-align + -Wduplicated-branches + -Wduplicated-cond + -Wextra + -Wformat=2 + -Wmissing-include-dirs + -Wnarrowing + -Wpointer-arith + -Wshadow + -Wuninitialized + -Wwrite-strings + -Wno-multichar + -Wno-format-nonliteral + -Wno-format-truncation + -Werror=discarded-qualifiers + -Werror=ignored-qualifiers + -Werror=implicit + -Werror=implicit-function-declaration + -Werror=implicit-int + -Werror=init-self + -Werror=incompatible-pointer-types + -Werror=int-conversion + -Werror=return-type + -Werror=strict-prototypes + ) + check_c_compiler_flag(${flag} wav_has_c_flag_${flag}) + if(wav_has_c_flag_${flag}) + list(APPEND wav_c_flags ${flag}) + endif() + endforeach() + endif() + set(wav_c_flags ${wav_c_flags} CACHE INTERNAL "C compiler flags") +endif() + +if(${CMAKE_C_COMPILER_ID} STREQUAL "MSVC") +elseif(${CMAKE_C_COMPILER_ID} STREQUAL "GNU") + set(wav_compile_options_release -fomit-frame-pointer -march=native -mtune=native -fvisibility=hidden) +elseif(${CMAKE_C_COMPILER_ID} MATCHES "^.*Clang$") + set(wav_compile_options_release -fomit-frame-pointer -fvisibility=hidden) +endif() diff --git a/3rdparty/libwav/cmake/wavConfig.cmake.in b/3rdparty/libwav/cmake/wavConfig.cmake.in new file mode 100644 index 0000000..d120954 --- /dev/null +++ b/3rdparty/libwav/cmake/wavConfig.cmake.in @@ -0,0 +1,2 @@ +include(CMakeFindDependencyMacro) +include("${CMAKE_CURRENT_LIST_DIR}/wavTargets.cmake") diff --git a/3rdparty/libwav/include/wav.h b/3rdparty/libwav/include/wav.h new file mode 100644 index 0000000..1178622 --- /dev/null +++ b/3rdparty/libwav/include/wav.h @@ -0,0 +1,225 @@ +/** Simple PCM wav file I/O library + * + * Author: Zhaosheng Pan + * + * The API is designed to be similar to stdio. + * + * This library does not support: + * + * - formats other than PCM, IEEE float and log-PCM + * - extra chunks after the data chunk + * - big endian platforms (might be supported in the future) + */ + +#ifndef __WAV_H__ +#define __WAV_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#if !defined(_MSC_VER) || _MSC_VER >= 1800 +#define WAV_INLINE static inline +#define WAV_CONST const +#define WAV_RESTRICT restrict +#else +#define WAV_INLINE static __inline +#define WAV_CONST +#define WAV_RESTRICT __restrict +#endif + +#if defined(__APPLE__) || defined(_MSC_VER) +typedef long long WavI64; +typedef unsigned long long WavU64; +typedef long long WavIntPtr; +typedef unsigned long long WavUIntPtr; +#else +#if defined(_WIN64) || defined(__x86_64) || defined(__amd64) +typedef long WavI64; +typedef unsigned long WavU64; +typedef long WavIntPtr; +typedef unsigned long WavUIntPtr; +#else +typedef long long WavI64; +typedef unsigned long long WavU64; +typedef int WavIntPtr; +typedef unsigned int WavUIntPtr; +#endif +#endif + +#if defined(__cplusplus) && __cplusplus >= 201103L +#define WAV_THREAD_LOCAL thread_local +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L +#define WAV_THREAD_LOCAL _Thread_local +#elif defined(_MSC_VER) +#define WAV_THREAD_LOCAL __declspec(thread) +#else +#define WAV_THREAD_LOCAL __thread +#endif + +typedef int WavBool; +typedef signed char WavI8; +typedef unsigned char WavU8; +typedef short WavI16; +typedef unsigned short WavU16; +typedef int WavI32; +typedef unsigned int WavU32; + +enum { + WAV_FALSE, + WAV_TRUE +}; + +/* wave file format codes */ +#define WAV_FORMAT_PCM ((WavU16)0x0001) +#define WAV_FORMAT_IEEE_FLOAT ((WavU16)0x0003) +#define WAV_FORMAT_ALAW ((WavU16)0x0006) +#define WAV_FORMAT_MULAW ((WavU16)0x0007) +#define WAV_FORMAT_EXTENSIBLE ((WavU16)0xfffe) + +typedef enum { + WAV_OK, /** no error */ + WAV_ERR_OS, /** error when {wave} called a stdio function */ + WAV_ERR_FORMAT, /** not a wave file or unsupported wave format */ + WAV_ERR_MODE, /** incorrect mode when opening the wave file or calling mode-specific API */ + WAV_ERR_PARAM, /** incorrect parameter passed to the API function */ +} WavErrCode; + +typedef struct { + WavErrCode code; + char* message; + int _is_literal; +} WavErr; + +typedef struct { + void* (*malloc)(void *context, size_t size); + void* (*realloc)(void *context, void *p, size_t size); + void (*free)(void *context, void *p); +} WavAllocFuncs; + +void wav_set_allocator(void *context, WAV_CONST WavAllocFuncs *funcs); + +void* wav_malloc(size_t size); +void* wav_realloc(void *p, size_t size); +void wav_free(void *p); + +char* wav_strdup(WAV_CONST char *str); +char* wav_strndup(WAV_CONST char *str, size_t n); +int wav_vasprintf(char **str, WAV_CONST char *format, va_list args); +int wav_asprintf(char **str, WAV_CONST char *format, ...); + +WAV_CONST WavErr* wav_err(void); +void wav_err_clear(void); + +#define WAV_OPEN_READ 1 +#define WAV_OPEN_WRITE 2 +#define WAV_OPEN_APPEND 4 + +typedef struct _WavFile WavFile; + +/** Open a wav file + * + * @param filename The name of the wav file + * @param mode The mode for open (same as {fopen}) + * @return NULL if the memory allocation for the {WavFile} object failed. Non-NULL means the memory allocation succeeded, but there can be other errors, which can be obtained using {wav_errno} or {wav_error}. + */ +WavFile* wav_open(WAV_CONST char* filename, WavU32 mode); +void wav_close(WavFile* self); +WavFile* wav_reopen(WavFile* self, WAV_CONST char* filename, WavU32 mode); + +/** Read a block of samples from the wav file + * + * @param buffer A pointer to a buffer where the data will be placed + * @param count The number of frames (block size) + * @param self The pointer to the {WavFile} structure + * @return The number of frames read. If returned value is less than {count}, either EOF reached or an error occured + * @remarks This API does not support extensible format. For extensible format, use {wave_read_raw} instead. + */ +size_t wav_read(WavFile* self, void *buffer, size_t count); + +/** Write a block of samples to the wav file + * + * @param buffer A pointer to the buffer of data + * @param count The number of frames (block size) + * @param self The pointer to the {WavFile} structure + * @return The number of frames written. If returned value is less than {count}, either EOF reached or an error occured. + * @remarks This API does not support extensible format. For extensible format, use {wave_read_raw} instead. + */ +size_t wav_write(WavFile* self, WAV_CONST void *buffer, size_t count); + +/** Tell the current position in the wav file. + * + * @param self The pointer to the WavFile structure. + * @return The current frame index. + */ +long int wav_tell(WAV_CONST WavFile* self); + +int wav_seek(WavFile* self, long int offset, int origin); +void wav_rewind(WavFile* self); + +/** Tell if the end of the wav file is reached. + * + * @param self The pointer to the WavFile structure. + * @return Non-zero integer if the end of the wav file is reached, otherwise zero. + */ +int wav_eof(WAV_CONST WavFile* self); + +int wav_flush(WavFile* self); + +/** Set the format code + * + * @param self The {WavFile} object + * @param format The format code, which should be one of `WAV_FORMAT_*` + * @remarks All data will be cleared after the call. {wav_errno} can be used to get the error code if there is an error. + */ +void wav_set_format(WavFile* self, WavU16 format); + +/** Set the number of channels + * + * @param self The {WavFile} object + * @param num_channels The number of channels + * @remarks All data will be cleared after the call. {wav_errno} can be used to get the error code if there is an error. + */ +void wav_set_num_channels(WavFile* self, WavU16 num_channels); + +/** Set the sample rate + * + * @param self The {WavFile} object + * @param sample_rate The sample rate + * @remarks All data will be cleared after the call. {wav_errno} can be used to get the error code if there is an error. + */ +void wav_set_sample_rate(WavFile* self, WavU32 sample_rate); + +/** Get the number of valid bits per sample + * + * @param self The {WavFile} object + * @param bits The value of valid bits to set + * @remarks If {bits} is 0 or larger than 8*{sample_size}, an error will occur. All data will be cleared after the call. {wav_errno} can be used to get the error code if there is an error. + */ +void wav_set_valid_bits_per_sample(WavFile* self, WavU16 bits); + +/** Set the size (in bytes) per sample + * + * @param self The WaveFile object + * @param sample_size Number of bytes per sample + * @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. {wav_errno} can be used to get the error code if there is an error. + */ +void wav_set_sample_size(WavFile* self, size_t sample_size); + +WavU16 wav_get_format(WAV_CONST WavFile* self); +WavU16 wav_get_num_channels(WAV_CONST WavFile* self); +WavU32 wav_get_sample_rate(WAV_CONST WavFile* self); +WavU16 wav_get_valid_bits_per_sample(WAV_CONST WavFile* self); +size_t wav_get_sample_size(WAV_CONST WavFile* self); +size_t wav_get_length(WAV_CONST WavFile* self); +WavU32 wav_get_channel_mask(WAV_CONST WavFile* self); +WavU16 wav_get_sub_format(WAV_CONST WavFile* self); + +#ifdef __cplusplus +} +#endif + +#endif /* __WAV_H__ */ diff --git a/3rdparty/libwav/src/wav.c b/3rdparty/libwav/src/wav.c new file mode 100644 index 0000000..a5d04dd --- /dev/null +++ b/3rdparty/libwav/src/wav.c @@ -0,0 +1,881 @@ +#include +#include +#include +#include +#include + +#include "wav.h" + +#if defined(__x86_64) || defined(__amd64) || defined(__i386__) || defined(__x86_64__) || defined(__LITTLE_ENDIAN__) || defined(CORE_CM7) +#define WAV_ENDIAN_LITTLE 1 +#elif defined(__BIG_ENDIAN__) +#define WAV_ENDIAN_BIG 1 +#endif + +#if WAV_ENDIAN_LITTLE +#define WAV_RIFF_CHUNK_ID ((WavU32)'FFIR') +#define WAV_FORMAT_CHUNK_ID ((WavU32)' tmf') +#define WAV_FACT_CHUNK_ID ((WavU32)'tcaf') +#define WAV_DATA_CHUNK_ID ((WavU32)'atad') +#define WAV_WAVE_ID ((WavU32)'EVAW') +#endif + +#if WAV_ENDIAN_BIG +#define WAV_RIFF_CHUNK_ID ((WavU32)'RIFF') +#define WAV_FORMAT_CHUNK_ID ((WavU32)'fmt ') +#define WAV_FACT_CHUNK_ID ((WavU32)'fact') +#define WAV_DATA_CHUNK_ID ((WavU32)'data') +#define WAV_WAVE_ID ((WavU32)'WAVE') +#endif + +WAV_THREAD_LOCAL WavErr g_err = {WAV_OK, (char*)"", 1}; + +static void* wav_default_malloc(void *context, size_t size) +{ + (void)context; + void *p = malloc(size); + assert(p != NULL); + return p; +} + +static void* wav_default_realloc(void *context, void *p, size_t size) +{ + (void)context; + void *ptr = realloc(p, size); + assert(ptr != NULL); + return ptr; +} + +static void wav_default_free(void *context, void *p) +{ + (void)context; + free(p); +} + +static WavAllocFuncs g_default_alloc_funcs = { + &wav_default_malloc, + &wav_default_realloc, + &wav_default_free +}; + +static void *g_alloc_context = NULL; +static WAV_CONST WavAllocFuncs* g_alloc_funcs = &g_default_alloc_funcs; + +void wav_set_allocator(void *context, WAV_CONST WavAllocFuncs *funcs) +{ + g_alloc_context = context; + g_alloc_funcs = funcs; +} + +void* wav_malloc(size_t size) +{ + return g_alloc_funcs->malloc(g_alloc_context, size); +} + +void* wav_realloc(void *p, size_t size) +{ + return g_alloc_funcs->realloc(g_alloc_context, p, size); +} + +void wav_free(void *p) +{ + if (p != NULL) { + g_alloc_funcs->free(g_alloc_context, p); + } +} + +char* wav_strdup(WAV_CONST char *str) +{ + size_t len = strlen(str) + 1; + void *new = wav_malloc(len); + if (new == NULL) + return NULL; + + return memcpy(new, str, len); +} + +char* wav_strndup(WAV_CONST char *str, size_t n) +{ + char *result = wav_malloc(n + 1); + if (result == NULL) + return NULL; + + result[n] = 0; + return memcpy(result, str, n); +} + +int wav_vasprintf(char **str, WAV_CONST char *format, va_list args) +{ + int size = 0; + + va_list args_copy; + va_copy(args_copy, args); + size = vsnprintf(NULL, (size_t)size, format, args_copy); + va_end(args_copy); + + if (size < 0) { + return size; + } + + *str = wav_malloc((size_t)size + 1); + if (*str == NULL) + return -1; + + return vsprintf(*str, format, args); +} + +int wav_asprintf(char **str, WAV_CONST char *format, ...) +{ + va_list args; + va_start(args, format); + int size = wav_vasprintf(str, format, args); + va_end(args); + return size; +} + +WAV_CONST WavErr* wav_err(void) +{ + return &g_err; +} + +void wav_err_clear(void) +{ + if (g_err.code != WAV_OK) { + if (!g_err._is_literal) { + wav_free(g_err.message); + } + g_err.code = WAV_OK; + g_err.message = (char*)""; + g_err._is_literal = 1; + } +} + +WAV_INLINE void wav_err_set(WavErrCode code, WAV_CONST char *format, ...) +{ + assert(g_err.code == WAV_OK); + va_list args; + va_start(args, format); + g_err.code = code; + wav_vasprintf(&g_err.message, format, args); + g_err._is_literal = 0; + va_end(args); +} + +WAV_INLINE void wav_err_set_literal(WavErrCode code, WAV_CONST char *message) +{ + assert(g_err.code == WAV_OK); + g_err.code = code; + g_err.message = (char *)message; + g_err._is_literal = 1; +} + +#pragma pack(push, 1) + +typedef struct { + WavU32 id; + WavU32 size; +} WavChunkHeader; + +typedef struct { + WavChunkHeader header; + + WavU64 offset; + + struct { + WavU16 format_tag; + WavU16 num_channels; + WavU32 sample_rate; + WavU32 avg_bytes_per_sec; + WavU16 block_align; + WavU16 bits_per_sample; + + WavU16 ext_size; + WavU16 valid_bits_per_sample; + WavU32 channel_mask; + + WavU8 sub_format[16]; + } body; +} WavFormatChunk; + +typedef struct { + WavChunkHeader header; + + WavU64 offset; + + struct { + WavU32 sample_length; + } body; +} WavFactChunk; + +typedef struct { + WavChunkHeader header; + WavU64 offset; +} WavDataChunk; + +typedef struct { + WavU32 id; + WavU32 size; + WavU32 wave_id; + WavU64 offset; +} WavMasterChunk; + +#pragma pack(pop) + +#define WAV_CHUNK_MASTER ((WavU32)1) +#define WAV_CHUNK_FORMAT ((WavU32)2) +#define WAV_CHUNK_FACT ((WavU32)4) +#define WAV_CHUNK_DATA ((WavU32)8) + +struct _WavFile { + FILE* fp; + char* filename; + WavU32 mode; + WavBool is_a_new_file; + + WavMasterChunk riff_chunk; + WavFormatChunk format_chunk; + WavFactChunk fact_chunk; + WavDataChunk data_chunk; +}; + +static WAV_CONST WavU8 default_sub_format[16] = { + 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 +}; + +void wav_parse_header(WavFile* self) +{ + size_t read_count; + + read_count = fread(&self->riff_chunk, sizeof(WavChunkHeader), 1, self->fp); + if (read_count != 1) { + wav_err_set_literal(WAV_ERR_FORMAT, "Unexpected EOF"); + return; + } + + if (self->riff_chunk.id != WAV_RIFF_CHUNK_ID) { + wav_err_set_literal(WAV_ERR_FORMAT, "Not a RIFF file"); + return; + } + + read_count = fread(&self->riff_chunk.wave_id, 4, 1, self->fp); + if (read_count != 1) { + wav_err_set_literal(WAV_ERR_FORMAT, "Unexpected EOF"); + return; + } + if (self->riff_chunk.wave_id != WAV_WAVE_ID) { + wav_err_set_literal(WAV_ERR_FORMAT, "Not a WAVE file"); + return; + } + + self->riff_chunk.offset = (WavU64)ftell(self->fp); + + while (self->data_chunk.header.id != WAV_DATA_CHUNK_ID) { + WavChunkHeader header; + + read_count = fread(&header, sizeof(WavChunkHeader), 1, self->fp); + if (read_count != 1) { + wav_err_set_literal(WAV_ERR_FORMAT, "Unexpected EOF"); + return; + } + + switch (header.id) { + case WAV_FORMAT_CHUNK_ID: + self->format_chunk.header = header; + self->format_chunk.offset = (WavU64)ftell(self->fp); + read_count = fread(&self->format_chunk.body, header.size, 1, self->fp); + if (read_count != 1) { + wav_err_set_literal(WAV_ERR_FORMAT, "Unexpected EOF"); + return; + } + if (self->format_chunk.body.format_tag != WAV_FORMAT_PCM && + self->format_chunk.body.format_tag != WAV_FORMAT_IEEE_FLOAT && + self->format_chunk.body.format_tag != WAV_FORMAT_ALAW && + self->format_chunk.body.format_tag != WAV_FORMAT_MULAW) + { + wav_err_set(WAV_ERR_FORMAT, "Unsupported format tag: %#010x", self->format_chunk.body.format_tag); + return; + } + break; + case WAV_FACT_CHUNK_ID: + self->fact_chunk.header = header; + self->fact_chunk.offset = (WavU64)ftell(self->fp); + read_count = fread(&self->fact_chunk.body, header.size, 1, self->fp); + if (read_count != 1) { + wav_err_set(WAV_ERR_FORMAT, "Unexpected EOF"); + } + break; + case WAV_DATA_CHUNK_ID: + self->data_chunk.header = header; + self->data_chunk.offset = (WavU64)ftell(self->fp); + break; + default: + if (fseek(self->fp, header.size, SEEK_CUR) < 0) { + wav_err_set(WAV_ERR_OS, "fseek() failed [errno %d: %s]", errno, strerror(errno)); + return; + } + break; + } + } +} + +void wav_write_header(WavFile* self) +{ + self->riff_chunk.size = + sizeof(self->riff_chunk.wave_id) + + (self->format_chunk.header.id == WAV_FORMAT_CHUNK_ID ? (sizeof(WavChunkHeader) + self->format_chunk.header.size) : 0) + + (self->fact_chunk.header.id == WAV_FACT_CHUNK_ID ? (sizeof(WavChunkHeader) + self->fact_chunk.header.size) : 0) + + (self->data_chunk.header.id == WAV_DATA_CHUNK_ID ? (sizeof(WavChunkHeader) + self->data_chunk.header.size) : 0); + + if (fseek(self->fp, 0, SEEK_SET) != 0) { + wav_err_set(WAV_ERR_OS, "fseek() failed [errno %d: %s]", errno, strerror(errno)); + return; + } + if (fwrite(&self->riff_chunk, sizeof(WavChunkHeader) + 4, 1, self->fp) != 1) { + wav_err_set(WAV_ERR_OS, "Error while writing to %s [errno %d: %s]", self->filename, errno, strerror(errno)); + return; + } + + if (self->format_chunk.header.id == WAV_FORMAT_CHUNK_ID) { + if (fseek(self->fp, (long)(self->format_chunk.offset - sizeof(WavChunkHeader)), SEEK_SET) != 0) { + wav_err_set(WAV_ERR_OS, "fseek() failed [errno %d: %s]", errno, strerror(errno)); + return; + } + if (fwrite(&self->format_chunk.header, sizeof(WavChunkHeader), 1, self->fp) != 1) { + wav_err_set(WAV_ERR_OS, "Error while writing to %s [errno %d: %s]", self->filename, errno, strerror(errno)); + return; + } + if (fwrite(&self->format_chunk.body, self->format_chunk.header.size, 1, self->fp) != 1) { + wav_err_set(WAV_ERR_OS, "Error while writing to %s [errno %d: %s]", self->filename, errno, strerror(errno)); + return; + } + } + + if (self->fact_chunk.header.id == WAV_FACT_CHUNK_ID) { + if (fseek(self->fp, (long)(self->fact_chunk.offset - sizeof(WavChunkHeader)), SEEK_SET) != 0) { + wav_err_set(WAV_ERR_OS, "fseek() failed [errno %d: %s]", errno, strerror(errno)); + return; + } + if (fwrite(&self->fact_chunk.header, sizeof(WavChunkHeader), 1, self->fp) != 1) { + wav_err_set(WAV_ERR_OS, "Error while writing to %s [errno %d: %s]", self->filename, errno, strerror(errno)); + return; + } + if (fwrite(&self->fact_chunk.body, self->fact_chunk.header.size, 1, self->fp) != 1) { + wav_err_set(WAV_ERR_OS, "Error while writing to %s [errno %d: %s]", self->filename, errno, strerror(errno)); + return; + } + } + + if (self->data_chunk.header.id == WAV_DATA_CHUNK_ID) { + if (fseek(self->fp, (long)(self->data_chunk.offset - sizeof(WavChunkHeader)), SEEK_SET) != 0) { + wav_err_set(WAV_ERR_OS, "fseek() failed [errno %d: %s]", errno, strerror(errno)); + return; + } + if (fwrite(&self->data_chunk.header, sizeof(WavChunkHeader), 1, self->fp) != 1) { + wav_err_set(WAV_ERR_OS, "Error while writing to %s [errno %d: %s]", self->filename, errno, strerror(errno)); + return; + } + } +} + +void wav_init(WavFile* self, WAV_CONST char* filename, WavU32 mode) +{ + memset(self, 0, sizeof(WavFile)); + + if (mode & WAV_OPEN_READ) { + if ((mode & WAV_OPEN_WRITE) || (mode & WAV_OPEN_APPEND)) { + self->fp = fopen(filename, "wb+"); + } else { + self->fp = fopen(filename, "rb"); + } + } else { + if ((mode & WAV_OPEN_WRITE) || (mode & WAV_OPEN_APPEND)) { + self->fp = fopen(filename, "wb+"); + } else { + wav_err_set_literal(WAV_ERR_PARAM, "Invalid mode"); + return; + } + } + + if (self->fp == NULL) { + wav_err_set(WAV_ERR_OS, "Error when opening %s [errno %d: %s]", filename, errno, strerror(errno)); + return; + } + + self->filename = wav_strdup(filename); + self->mode = mode; + + if (!(self->mode & WAV_OPEN_WRITE) && !(self->mode & WAV_OPEN_APPEND)) { + wav_parse_header(self); + return; + } + + if (self->mode & WAV_OPEN_APPEND) { + wav_parse_header(self); + if (g_err.code == WAV_OK) { + // If the header parsing was successful, return immediately. + return; + } else { + // Header parsing failed. Regard it as a new file. + wav_err_clear(); + rewind(self->fp); + self->is_a_new_file = WAV_TRUE; + } + } + + // reaches here only if creating a new file + + self->riff_chunk.id = WAV_RIFF_CHUNK_ID; + /* self->chunk.size = calculated by wav_write_header */ + self->riff_chunk.wave_id = WAV_WAVE_ID; + self->riff_chunk.offset = sizeof(WavChunkHeader) + 4; + + self->format_chunk.header.id = WAV_FORMAT_CHUNK_ID; + self->format_chunk.header.size = (WavU32)((WavUIntPtr)&self->format_chunk.body.ext_size - (WavUIntPtr)&self->format_chunk.body); + self->format_chunk.offset = self->riff_chunk.offset + sizeof(WavChunkHeader); + self->format_chunk.body.format_tag = WAV_FORMAT_PCM; + self->format_chunk.body.num_channels = 2; + self->format_chunk.body.sample_rate = 44100; + self->format_chunk.body.avg_bytes_per_sec = 44100 * 2 * 2; + self->format_chunk.body.block_align = 4; + self->format_chunk.body.bits_per_sample = 16; + + memcpy(self->format_chunk.body.sub_format, default_sub_format, 16); + + self->data_chunk.header.id = WAV_DATA_CHUNK_ID; + self->data_chunk.offset = self->format_chunk.offset + self->format_chunk.header.size + sizeof(WavChunkHeader); + + wav_write_header(self); +} + +void wav_finalize(WavFile* self) +{ + int ret; + + wav_free(self->filename); + + if (self->fp == NULL) { + return; + } + + ret = fclose(self->fp); + if (ret != 0) { + fprintf(stderr, "[WARN] [libwav] fclose failed with code %d [errno %d: %s]", ret, errno, strerror(errno)); + return; + } +} + +WavFile* wav_open(WAV_CONST char* filename, WavU32 mode) +{ + WavFile* self = wav_malloc(sizeof(WavFile)); + if (self == NULL) { + return NULL; + } + + wav_init(self, filename, mode); + + return self; +} + +void wav_close(WavFile* self) +{ + wav_finalize(self); + wav_free(self); +} + +WavFile* wav_reopen(WavFile* self, WAV_CONST char* filename, WavU32 mode) +{ + wav_finalize(self); + wav_init(self, filename, mode); + return self; +} + +size_t wav_read(WavFile* self, void *buffer, size_t count) +{ + size_t read_count; + WavU16 n_channels = wav_get_num_channels(self); + size_t sample_size = wav_get_sample_size(self); + size_t len_remain; + + if (!(self->mode & WAV_OPEN_READ)) { + wav_err_set_literal(WAV_ERR_MODE, "This WavFile is not readable"); + return 0; + } + + if (self->format_chunk.body.format_tag == WAV_FORMAT_EXTENSIBLE) { + wav_err_set_literal(WAV_ERR_FORMAT, "Extensible format is not supported"); + return 0; + } + + len_remain = wav_get_length(self) - (size_t)wav_tell(self); + if (g_err.code != WAV_OK) { + return 0; + } + count = (count <= len_remain) ? count : len_remain; + + if (count == 0) { + return 0; + } + + read_count = fread(buffer, sample_size, n_channels * count, self->fp); + if (ferror(self->fp)) { + wav_err_set(WAV_ERR_OS, "Error when reading %s [errno %d: %s]", self->filename, errno, strerror(errno)); + return 0; + } + + return read_count / n_channels; +} + +WAV_INLINE void wav_update_sizes(WavFile *self) +{ + long int save_pos = ftell(self->fp); + if (fseek(self->fp, (long)(sizeof(WavChunkHeader) - 4), SEEK_SET) != 0) { + wav_err_set(WAV_ERR_OS, "fseek() failed [errno %d: %s]", errno, strerror(errno)); + return; + } + if (fwrite(&self->riff_chunk.size, 4, 1, self->fp) != 1) { + wav_err_set(WAV_ERR_OS, "fwrite() failed [errno %d: %s]", errno, strerror(errno)); + return; + } + if (self->fact_chunk.header.id == WAV_FACT_CHUNK_ID) { + if (fseek(self->fp, (long)self->fact_chunk.offset, SEEK_SET) != 0) { + wav_err_set(WAV_ERR_OS, "fseek() failed [errno %d: %s]", errno, strerror(errno)); + return; + } + if (fwrite(&self->fact_chunk.body.sample_length, 4, 1, self->fp) != 1) { + wav_err_set(WAV_ERR_OS, "fwrite() failed [errno %d: %s]", errno, strerror(errno)); + return; + } + } + if (fseek(self->fp, (long)(self->data_chunk.offset - 4), SEEK_SET) != 0) { + wav_err_set(WAV_ERR_OS, "fseek() failed [errno %d: %s]", errno, strerror(errno)); + return; + } + if (fwrite(&self->data_chunk.header.size, 4, 1, self->fp) != 1) { + wav_err_set(WAV_ERR_OS, "fwrite() failed [errno %d: %s]", errno, strerror(errno)); + return; + } + if (fseek(self->fp, save_pos, SEEK_SET) != 0) { + wav_err_set(WAV_ERR_OS, "fseek() failed [errno %d: %s]", errno, strerror(errno)); + return; + } +} + +size_t wav_write(WavFile* self, WAV_CONST void *buffer, size_t count) +{ + size_t write_count; + WavU16 n_channels = wav_get_num_channels(self); + size_t sample_size = wav_get_sample_size(self); + + if (!(self->mode & WAV_OPEN_WRITE) && !(self->mode & WAV_OPEN_APPEND)) { + wav_err_set_literal(WAV_ERR_MODE, "This WavFile is not writable"); + return 0; + } + + if (self->format_chunk.body.format_tag == WAV_FORMAT_EXTENSIBLE) { + wav_err_set_literal(WAV_ERR_FORMAT, "Extensible format is not supported"); + return 0; + } + + if (count == 0) { + return 0; + } + + wav_tell(self); + if (g_err.code != WAV_OK) { + return 0; + } + + if (!(self->mode & WAV_OPEN_READ) && !(self->mode & WAV_OPEN_WRITE)) { + wav_seek(self, 0, SEEK_END); + if (g_err.code != WAV_OK) { + return 0; + } + } + + write_count = fwrite(buffer, sample_size, n_channels * count, self->fp); + if (ferror(self->fp)) { + wav_err_set(WAV_ERR_OS, "Error when writing to %s [errno %d: %s]", self->filename, errno, strerror(errno)); + return 0; + } + + self->riff_chunk.size += write_count * sample_size; + if (self->fact_chunk.header.id == WAV_FACT_CHUNK_ID) { + self->fact_chunk.body.sample_length += write_count / n_channels; + } + self->data_chunk.header.size += write_count * sample_size; + + wav_update_sizes(self); + if (g_err.code != WAV_OK) + return 0; + + return write_count / n_channels; +} + +long int wav_tell(WAV_CONST WavFile* self) +{ + long pos = ftell(self->fp); + + if (pos == -1L) { + wav_err_set(WAV_ERR_OS, "ftell() failed [errno %d: %s]", errno, strerror(errno)); + return -1L; + } + + assert(pos >= (long)self->data_chunk.offset); + + return (long)(((WavU64)pos - self->data_chunk.offset) / (self->format_chunk.body.block_align)); +} + +int wav_seek(WavFile* self, long int offset, int origin) +{ + size_t length = wav_get_length(self); + int ret; + + if (origin == SEEK_CUR) { + offset += (long)wav_tell(self); + } else if (origin == SEEK_END) { + offset += (long)length; + } + + /* POSIX allows seeking beyond end of file */ + if (offset >= 0) { + offset *= self->format_chunk.body.block_align; + } else { + wav_err_set_literal(WAV_ERR_PARAM, "Invalid seek"); + return (int)g_err.code; + } + + ret = fseek(self->fp, (long)self->data_chunk.offset + offset, SEEK_SET); + + if (ret != 0) { + wav_err_set(WAV_ERR_OS, "fseek() failed [errno %d: %s]", errno, strerror(errno)); + return (int)ret; + } + + return 0; +} + +void wav_rewind(WavFile* self) +{ + wav_seek(self, 0, SEEK_SET); +} + +int wav_eof(WAV_CONST WavFile* self) +{ + return feof(self->fp) || ftell(self->fp) == (long)(self->data_chunk.offset + self->data_chunk.header.size); +} + +int wav_flush(WavFile* self) +{ + int ret = fflush(self->fp); + + if (ret != 0) { + wav_err_set(WAV_ERR_OS, "fflush() failed [errno %d: %s]", errno, strerror(errno)); + } + + return ret; +} + +void wav_set_format(WavFile* self, WavU16 format) +{ + if (!(self->mode & WAV_OPEN_WRITE) && !((self->mode & WAV_OPEN_APPEND) && self->is_a_new_file && self->data_chunk.header.size == 0)) { + wav_err_set_literal(WAV_ERR_MODE, "This WavFile is not writable"); + return; + } + + if (format == self->format_chunk.body.format_tag) + return; + + self->format_chunk.body.format_tag = format; + if (format != WAV_FORMAT_PCM && format != WAV_FORMAT_EXTENSIBLE) { + self->format_chunk.body.ext_size = 0; + self->format_chunk.header.size = (WavU32)((WavUIntPtr)&self->format_chunk.body.ext_size - (WavUIntPtr)&self->format_chunk.body); + } else if (format == WAV_FORMAT_EXTENSIBLE) { + self->format_chunk.body.ext_size = 22; + self->format_chunk.header.size = sizeof(WavFormatChunk) - sizeof(WavChunkHeader); + } + + if (format == WAV_FORMAT_ALAW || format == WAV_FORMAT_MULAW) { + WavU16 sample_size = wav_get_sample_size(self); + if (sample_size != 1) { + wav_set_sample_size(self, 1); + } + } else if (format == WAV_FORMAT_IEEE_FLOAT) { + WavU16 sample_size = wav_get_sample_size(self); + if (sample_size != 4 && sample_size != 8) { + wav_set_sample_size(self, 4); + } + } + + wav_write_header(self); +} + +void wav_set_num_channels(WavFile* self, WavU16 num_channels) +{ + if (!(self->mode & WAV_OPEN_WRITE) && !((self->mode & WAV_OPEN_APPEND) && self->is_a_new_file && self->data_chunk.header.size == 0)) { + wav_err_set_literal(WAV_ERR_MODE, "This WavFile is not writable"); + return; + } + + if (num_channels < 1) { + wav_err_set(WAV_ERR_PARAM, "Invalid number of channels: %u", num_channels); + return; + } + + WavU16 old_num_channels = self->format_chunk.body.num_channels; + if (num_channels == old_num_channels) + return; + + self->format_chunk.body.num_channels = num_channels; + self->format_chunk.body.block_align = self->format_chunk.body.block_align / old_num_channels * num_channels; + self->format_chunk.body.avg_bytes_per_sec = self->format_chunk.body.block_align * self->format_chunk.body.sample_rate; + + wav_write_header(self); +} + +void wav_set_sample_rate(WavFile* self, WavU32 sample_rate) +{ + if (!(self->mode & WAV_OPEN_WRITE) && !((self->mode & WAV_OPEN_APPEND) && self->is_a_new_file && self->data_chunk.header.size == 0)) { + wav_err_set_literal(WAV_ERR_MODE, "This WavFile is not writable"); + return; + } + + if (sample_rate == self->format_chunk.body.sample_rate) + return; + + self->format_chunk.body.sample_rate = sample_rate; + self->format_chunk.body.avg_bytes_per_sec = self->format_chunk.body.block_align * self->format_chunk.body.sample_rate; + + wav_write_header(self); +} + +void wav_set_valid_bits_per_sample(WavFile* self, WavU16 bits) +{ + if (!(self->mode & WAV_OPEN_WRITE) && !((self->mode & WAV_OPEN_APPEND) && self->is_a_new_file && self->data_chunk.header.size == 0)) { + wav_err_set_literal(WAV_ERR_MODE, "This WavFile is not writable"); + return; + } + + if (bits < 1 || bits > 8 * self->format_chunk.body.block_align / self->format_chunk.body.num_channels) { + wav_err_set(WAV_ERR_PARAM, "Invalid ValidBitsPerSample: %u", bits); + return; + } + + if ((self->format_chunk.body.format_tag == WAV_FORMAT_ALAW || self->format_chunk.body.format_tag == WAV_FORMAT_MULAW) && bits != 8) { + wav_err_set(WAV_ERR_PARAM, "Invalid ValidBitsPerSample: %u", bits); + return; + } + + if (self->format_chunk.body.format_tag != WAV_FORMAT_EXTENSIBLE) { + self->format_chunk.body.bits_per_sample = bits; + } else { + self->format_chunk.body.bits_per_sample = 8 * self->format_chunk.body.block_align / self->format_chunk.body.num_channels; + self->format_chunk.body.valid_bits_per_sample = bits; + } + + wav_write_header(self); +} + +void wav_set_sample_size(WavFile* self, size_t sample_size) +{ + if (!(self->mode & WAV_OPEN_WRITE) && !((self->mode & WAV_OPEN_APPEND) && self->is_a_new_file && self->data_chunk.header.size == 0)) { + wav_err_set_literal(WAV_ERR_MODE, "This WavFile is not writable"); + return; + } + + if (sample_size < 1) { + wav_err_set(WAV_ERR_PARAM, "Invalid sample size: %zu", sample_size); + return; + } + + self->format_chunk.body.block_align = (WavU16)(sample_size * self->format_chunk.body.num_channels); + self->format_chunk.body.avg_bytes_per_sec = self->format_chunk.body.block_align * self->format_chunk.body.sample_rate; + self->format_chunk.body.bits_per_sample = (WavU16)(sample_size * 8); + if (self->format_chunk.body.format_tag == WAV_FORMAT_EXTENSIBLE) { + self->format_chunk.body.valid_bits_per_sample = (WavU16)(sample_size * 8); + } + + wav_write_header(self); +} + +void wav_set_channel_mask(WavFile* self, WavU32 channel_mask) +{ + if (!(self->mode & WAV_OPEN_WRITE) && !((self->mode & WAV_OPEN_APPEND) && self->is_a_new_file && self->data_chunk.header.size == 0)) { + wav_err_set_literal(WAV_ERR_MODE, "This WavFile is not writable"); + return; + } + + if (self->format_chunk.body.format_tag != WAV_FORMAT_EXTENSIBLE) { + wav_err_set_literal(WAV_ERR_FORMAT, "Extensible format is not supported"); + return; + } + + self->format_chunk.body.channel_mask = channel_mask; + + wav_write_header(self); +} + +void wav_set_sub_format(WavFile* self, WavU16 sub_format) +{ + if (!(self->mode & WAV_OPEN_WRITE) && !((self->mode & WAV_OPEN_APPEND) && self->is_a_new_file && self->data_chunk.header.size == 0)) { + wav_err_set_literal(WAV_ERR_MODE, "This WavFile is not writable"); + return; + } + + if (self->format_chunk.body.format_tag != WAV_FORMAT_EXTENSIBLE) { + wav_err_set_literal(WAV_ERR_FORMAT, "Extensible format is not supported"); + return; + } + + self->format_chunk.body.sub_format[0] = (WavU8)(sub_format & 0xff); + self->format_chunk.body.sub_format[1] = (WavU8)(sub_format >> 8); + + wav_write_header(self); +} + +WavU16 wav_get_format(WAV_CONST WavFile* self) +{ + return self->format_chunk.body.format_tag; +} + +WavU16 wav_get_num_channels(WAV_CONST WavFile* self) +{ + return self->format_chunk.body.num_channels; +} + +WavU32 wav_get_sample_rate(WAV_CONST WavFile* self) +{ + return self->format_chunk.body.sample_rate; +} + +WavU16 wav_get_valid_bits_per_sample(WAV_CONST WavFile* self) +{ + if (self->format_chunk.body.format_tag != WAV_FORMAT_EXTENSIBLE) { + return self->format_chunk.body.bits_per_sample; + } else { + return self->format_chunk.body.valid_bits_per_sample; + } +} + +size_t wav_get_sample_size(WAV_CONST WavFile* self) +{ + return self->format_chunk.body.block_align / self->format_chunk.body.num_channels; +} + +size_t wav_get_length(WAV_CONST WavFile* self) +{ + return self->data_chunk.header.size / (self->format_chunk.body.block_align); +} + +WavU32 wav_get_channel_mask(WAV_CONST WavFile* self) +{ + return self->format_chunk.body.channel_mask; +} + +WavU16 wav_get_sub_format(WAV_CONST WavFile* self) +{ + WavU16 sub_format = self->format_chunk.body.sub_format[1]; + sub_format <<= 8; + sub_format |= self->format_chunk.body.sub_format[0]; + return sub_format; +} diff --git a/3rdparty/libwav/tests/write_f32/CMakeLists.txt b/3rdparty/libwav/tests/write_f32/CMakeLists.txt new file mode 100644 index 0000000..cca1ecf --- /dev/null +++ b/3rdparty/libwav/tests/write_f32/CMakeLists.txt @@ -0,0 +1,13 @@ +add_executable(write-f32 main.c) +target_link_libraries(write-f32 + wav::wav + $<$:m> + ) +target_include_directories(write-f32 PRIVATE ${CMAKE_SOURCE_DIR}/include) +target_compile_features(write-f32 PRIVATE ${wav_compile_features}) +target_compile_definitions(write-f32 PRIVATE ${wav_compile_definitions}) +target_compile_options(write-f32 PRIVATE + ${wav_c_flags} + $<$:${wav_compile_options_release}> + $<$:${wav_compile_options_release}> + ) diff --git a/3rdparty/libwav/tests/write_f32/main.c b/3rdparty/libwav/tests/write_f32/main.c new file mode 100644 index 0000000..14f4f33 --- /dev/null +++ b/3rdparty/libwav/tests/write_f32/main.c @@ -0,0 +1,25 @@ +#include +#include +#include "wav.h" + +void generate_sine_wave(float *x, int sample_rate, int len) +{ + for (int i = 0; i < len; ++i) { + x[i] = 0.5f * cosf(2 * 3.14159265358979323f * 440.0f * i / sample_rate); + } +} + +int main(void) +{ + float *buf = malloc(sizeof(float) * 10 * 44100); + generate_sine_wave(buf, 44100, 10 * 44100); + WavFile *fp = wav_open("out.wav", WAV_OPEN_WRITE); + wav_set_format(fp, WAV_FORMAT_IEEE_FLOAT); + /* wav_set_sample_size(fp, sizeof(float)); */ + wav_set_num_channels(fp, 1); + wav_set_sample_rate(fp, 44100); + wav_write(fp, buf, 10 * 44100); + wav_close(fp); + free(buf); + return 0; +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 16b27d1..e223470 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,7 @@ option(MOSSROSE_BUILD_TESTS "Build the tests" OFF) ######## third-party libraries ######## add_subdirectory(${CMAKE_SOURCE_DIR}/3rdparty/portaudio EXCLUDE_FROM_ALL) add_subdirectory(${CMAKE_SOURCE_DIR}/3rdparty/plibsys EXCLUDE_FROM_ALL) +add_subdirectory(${CMAKE_SOURCE_DIR}/3rdparty/libwav EXCLUDE_FROM_ALL) add_library(mossrose) set_target_properties(mossrose PROPERTIES @@ -21,7 +22,7 @@ set_target_properties(mossrose PROPERTIES VERSION ${PROJECT_VERSION} PUBLIC_HEADER src/mossrose.h ) -target_link_libraries(mossrose portaudio plibsys m) +target_link_libraries(mossrose portaudio plibsys wav::wav m) if (MOSSROSE_BUILD_EXAMPLES) -- cgit v1.2.1