├── cmake ├── utuConfig.cmake.in ├── ProjectConfig.cmake.in ├── Doxygen.cmake ├── version.h.in ├── SourcesAndHeaders.cmake ├── Vcpkg.cmake ├── StaticAnalyzers.cmake ├── Conan.cmake ├── Utils.cmake ├── StandardSettings.cmake ├── BuildLoris.cmake └── CompilerWarnings.cmake ├── lib ├── include │ └── utu │ │ ├── utu.h │ │ ├── Partial.h │ │ ├── PartialIO.h │ │ └── PartialData.h ├── README.md ├── partials.example.json ├── LICENSE ├── src │ ├── PartialIO.cpp │ └── SerializerImpl.h ├── test │ ├── CMakeLists.txt │ └── src │ │ └── test_json.cpp └── partials.schema.json ├── cmd └── src │ ├── Marshal.h │ ├── AudioFile.h │ ├── Marshal.cpp │ ├── AudioFile.cpp │ ├── AudioPlayer.h │ └── main.cpp ├── .gitignore ├── LICENSE ├── .gitmodules ├── README.md ├── .clang-format ├── CMakeLists.txt └── COPYING /cmake/utuConfig.cmake.in: -------------------------------------------------------------------------------- 1 | option(INSTALL_GTEST "Enable installation of googletest. (Projects embedding googletest may want to turn this OFF.)" OFF) 2 | -------------------------------------------------------------------------------- /lib/include/utu/utu.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2021 Greg Wuller. 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include -------------------------------------------------------------------------------- /cmake/ProjectConfig.cmake.in: -------------------------------------------------------------------------------- 1 | set(@PROJECT_NAME@_VERSION @PROJECT_VERSION@) 2 | 3 | @PACKAGE_INIT@ 4 | 5 | set_and_check(@PROJECT_NAME@_INCLUDE_DIR "@CMAKE_INSTALL_FULL_INCLUDEDIR@") 6 | 7 | include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") 8 | 9 | check_required_components(@PROJECT_NAME@) 10 | -------------------------------------------------------------------------------- /lib/README.md: -------------------------------------------------------------------------------- 1 | # lib 2 | 3 | This directory contains library used by the analysis tool to represent partial 4 | data in memory as well as read and write that data to a JSON based file format. 5 | 6 | _**NOTE**: the library code is provided under the more permissive MIT license in 7 | order to facilitate using it in close source products._ -------------------------------------------------------------------------------- /cmd/src/Marshal.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2021 Greg Wuller. 3 | // 4 | // SPDX-License-Identifier: GPL-2.0-or-later 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | 12 | struct Marshal { 13 | static utu::PartialData from(const Loris::PartialList& p); 14 | static Loris::PartialList from(const utu::PartialData& p); 15 | }; -------------------------------------------------------------------------------- /cmake/Doxygen.cmake: -------------------------------------------------------------------------------- 1 | if(${PROJECT_NAME}_ENABLE_DOXYGEN) 2 | set(DOXYGEN_CALLER_GRAPH YES) 3 | set(DOXYGEN_CALL_GRAPH YES) 4 | set(DOXYGEN_EXTRACT_ALL YES) 5 | set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/docs) 6 | 7 | find_package(Doxygen REQUIRED dot) 8 | doxygen_add_docs(doxygen-docs ${PROJECT_SOURCE_DIR}) 9 | 10 | verbose_message("Doxygen has been setup and documentation is now available.") 11 | endif() 12 | -------------------------------------------------------------------------------- /cmake/version.h.in: -------------------------------------------------------------------------------- 1 | #ifndef @PROJECT_NAME_UPPERCASE@_VERSION_H_ 2 | #define @PROJECT_NAME_UPPERCASE@_VERSION_H_ 3 | 4 | #define PROJECT_NAME "@PROJECT_NAME@" 5 | 6 | #define PROJECT_VERSION "@PROJECT_VERSION@" 7 | 8 | #define PROJECT_MAJOR_VERSION @PROJECT_VERSION_MAJOR@ 9 | #define PROJECT_MINOR_VERSION @PROJECT_VERSION_MINOR@ 10 | #define PROJECT_PATCH_VERSION @PROJECT_VERSION_PATCH@ 11 | 12 | #endif // @PROJECT_NAME_UPPERCASE@_VERSION_H_ 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Reference 2 | _* 3 | 4 | # Temporaries 5 | *~ 6 | *# 7 | build/ 8 | 9 | # Prerequisites 10 | *.d 11 | 12 | # Compiled Object files 13 | *.slo 14 | *.lo 15 | *.o 16 | *.obj 17 | 18 | # Precompiled Headers 19 | *.gch 20 | *.pch 21 | 22 | # Compiled Dynamic libraries 23 | *.so 24 | *.dylib 25 | *.dll 26 | 27 | # Fortran module files 28 | *.mod 29 | *.smod 30 | 31 | # Compiled Static libraries 32 | *.lai 33 | *.la 34 | *.a 35 | *.lib 36 | 37 | # Executables 38 | *.exe 39 | *.out 40 | *.app 41 | 42 | # Misc 43 | .DS_Store 44 | 45 | -------------------------------------------------------------------------------- /cmake/SourcesAndHeaders.cmake: -------------------------------------------------------------------------------- 1 | set(lib_sources 2 | lib/src/PartialIO.cpp 3 | ) 4 | 5 | set(exe_sources 6 | cmd/src/AudioPlayer.h 7 | cmd/src/AudioFile.cpp 8 | cmd/src/AudioFile.h 9 | cmd/src/Marshal.cpp 10 | cmd/src/Marshal.h 11 | cmd/src/main.cpp 12 | ${lib_sources} 13 | ) 14 | 15 | set(lib_headers 16 | lib/include/utu/utu.h 17 | lib/include/utu/Partial.h 18 | lib/include/utu/PartialData.h 19 | lib/include/utu/PartialIO.h 20 | lib/src/SerializerImpl.h 21 | ) 22 | 23 | set(test_sources 24 | src/test_json.cpp 25 | ) 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | COPYRIGHT AND LICENSE: 2 | 3 | Copyright (c) 2021 Greg Wuller 4 | 5 | Utu is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY, without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | file COPYING or the GNU General Public License for more details. -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dep/docopt"] 2 | path = dep/docopt 3 | url = https://github.com/docopt/docopt.cpp.git 4 | [submodule "dep/loris"] 5 | path = dep/loris 6 | url = ssh://git@github.com/ngwese/loris.git 7 | [submodule "dep/json"] 8 | path = dep/json 9 | url = https://github.com/nlohmann/json.git 10 | [submodule "dep/googletest"] 11 | path = dep/googletest 12 | url = https://github.com/google/googletest.git 13 | [submodule "dep/libsndfile"] 14 | path = dep/libsndfile 15 | url = https://github.com/libsndfile/libsndfile.git 16 | [submodule "dep/rtaudio"] 17 | path = dep/rtaudio 18 | url = https://github.com/thestk/rtaudio.git 19 | [submodule "dep/libsamplerate"] 20 | path = dep/libsamplerate 21 | url = https://github.com/libsndfile/libsamplerate.git 22 | -------------------------------------------------------------------------------- /cmake/Vcpkg.cmake: -------------------------------------------------------------------------------- 1 | if(${PROJECT_NAME}_ENABLE_VCPKG) 2 | # 3 | # If `vcpkg.cmake` (from https://github.com/microsoft/vcpkg) does not exist, download it. 4 | # 5 | if(NOT EXISTS "${CMAKE_BINARY_DIR}/vcpkg.cmake") 6 | message( 7 | STATUS 8 | "Downloading `vcpkg.cmake` from https://github.com/microsoft/vcpkg..." 9 | ) 10 | file(DOWNLOAD "https://github.com/microsoft/vcpkg/raw/master/scripts/buildsystems/vcpkg.cmake" 11 | "${CMAKE_BINARY_DIR}/vcpkg.cmake" 12 | ) 13 | message(STATUS "Vcpkg config downloaded succesfully.") 14 | endif() 15 | 16 | if(${PROJECT_NAME}_VERBOSE_OUTPUT) 17 | set(VCPKG_VERBOSE ON) 18 | endif() 19 | set(CMAKE_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}" "${CMAKE_BINARY_DIR}/vcpkg.cmake") 20 | endif() 21 | -------------------------------------------------------------------------------- /lib/include/utu/Partial.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2021 Greg Wuller. 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | constexpr char kTimeName[] = "time"; 15 | constexpr char kFrequencyName[] = "frequency"; 16 | constexpr char kAmplitudeName[] = "amplitude"; 17 | constexpr char kBandwidthName[] = "bandwidth"; 18 | constexpr char kPhaseName[] = "phase"; 19 | 20 | namespace utu 21 | { 22 | 23 | struct Partial { 24 | using Samples = std::vector; 25 | using Parameters = std::unordered_map; 26 | 27 | std::optional label; 28 | Parameters parameters; 29 | }; 30 | 31 | } // namespace utu 32 | -------------------------------------------------------------------------------- /cmake/StaticAnalyzers.cmake: -------------------------------------------------------------------------------- 1 | if(${PROJECT_NAME}_ENABLE_CLANG_TIDY) 2 | find_program(CLANGTIDY clang-tidy) 3 | if(CLANGTIDY) 4 | set(CMAKE_CXX_CLANG_TIDY ${CLANGTIDY} -extra-arg=-Wno-unknown-warning-option) 5 | message("Clang-Tidy finished setting up.") 6 | else() 7 | message(SEND_ERROR "Clang-Tidy requested but executable not found.") 8 | endif() 9 | endif() 10 | 11 | if(${PROJECT_NAME}_ENABLE_CPPCHECK) 12 | find_program(CPPCHECK cppcheck) 13 | if(CPPCHECK) 14 | set(CMAKE_CXX_CPPCHECK ${CPPCHECK} --suppress=missingInclude --enable=all 15 | --inline-suppr --inconclusive -i ${CMAKE_SOURCE_DIR}/imgui/lib) 16 | message("Cppcheck finished setting up.") 17 | else() 18 | message(SEND_ERROR "Cppcheck requested but executable not found.") 19 | endif() 20 | endif() 21 | -------------------------------------------------------------------------------- /lib/include/utu/PartialIO.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2021 Greg Wuller. 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace utu 16 | { 17 | 18 | template 19 | struct Reader { 20 | using ValueType = T; 21 | static std::optional read(const std::string& jsonData); 22 | static std::optional read(std::istream& is); 23 | }; 24 | 25 | template 26 | struct Writer { 27 | using ValueType = T; 28 | static std::optional write(const T& value); 29 | static void write(const T& value, std::ostream& os); 30 | }; 31 | 32 | typedef Reader PartialReader; 33 | typedef Writer PartialWriter; 34 | 35 | } // namespace utu -------------------------------------------------------------------------------- /lib/partials.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "file_info": { 3 | "kind": "utu-partial-data", 4 | "version": 1 5 | }, 6 | "source": { 7 | "location": "/some/path/on/disk.aiff" 8 | }, 9 | "layout": ["time", "frequency", "amplitude", "bandwidth", "phase"], 10 | "partials": [ 11 | { 12 | "label": "component-1", 13 | "breakpoints": [ 14 | [0, 440, 0.3, 0.2, 0], 15 | [0.2, 440, 0.3, 0.2, 0], 16 | [0.5, 440, 0.3, 0.2, 0], 17 | [1.5, 440, 0.3, 0.2, 0], 18 | [3.0, 440, 0.3, 0.2, 0] 19 | ] 20 | }, 21 | { 22 | "label": "component-2", 23 | "breakpoints": [ 24 | [0.3, 440, 0.3, 0.2, 0], 25 | [3.0, 440, 0.3, 0.2, 0], 26 | [4.2, 440, 0.3, 0.2, 0] 27 | ] 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /lib/include/utu/PartialData.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2021 Greg Wuller. 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "utu/Partial.h" 15 | 16 | namespace utu 17 | { 18 | 19 | struct PartialData { 20 | using Parameters = std::vector; 21 | using Partials = std::vector; 22 | 23 | struct Source { 24 | std::string location; 25 | std::optional fingerprint; 26 | }; 27 | 28 | std::optional description; 29 | std::optional source; 30 | 31 | Parameters parameters; 32 | Partials partials; 33 | 34 | bool push_back(Partial& partial) 35 | { 36 | // Ensure the incoming partial has all the expected parameters 37 | for (const auto& param : parameters) { 38 | if (partial.parameters.find(param) == partial.parameters.end()) { 39 | return false; 40 | } 41 | } 42 | partials.push_back(partial); 43 | return true; 44 | } 45 | }; 46 | 47 | } // namespace utu -------------------------------------------------------------------------------- /lib/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Greg Wuller 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | -------------------------------------------------------------------------------- /cmake/Conan.cmake: -------------------------------------------------------------------------------- 1 | if(${PROJECT_NAME}_ENABLE_CONAN) 2 | # 3 | # Setup Conan requires and options here: 4 | # 5 | 6 | set(${PROJECT_NAME}_CONAN_REQUIRES "") 7 | set(${PROJECT_NAME}_CONAN_OPTIONS "") 8 | 9 | # 10 | # If `conan.cmake` (from https://github.com/conan-io/cmake-conan) does not exist, download it. 11 | # 12 | if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake") 13 | message( 14 | STATUS 15 | "Downloading conan.cmake from https://github.com/conan-io/cmake-conan..." 16 | ) 17 | file( 18 | DOWNLOAD "https://github.com/conan-io/cmake-conan/raw/v0.15/conan.cmake" 19 | "${CMAKE_BINARY_DIR}/conan.cmake" 20 | ) 21 | message(STATUS "Cmake-Conan downloaded succesfully.") 22 | endif() 23 | 24 | include(${CMAKE_BINARY_DIR}/conan.cmake) 25 | 26 | conan_add_remote( 27 | NAME bincrafters 28 | URL 29 | https://api.bintray.com/conan/bincrafters/public-conan 30 | ) 31 | 32 | conan_cmake_run( 33 | REQUIRES 34 | ${${PROJECT_NAME}_CONAN_REQUIRES} 35 | OPTIONS 36 | ${${PROJECT_NAME}_CONAN_OPTIONS} 37 | BASIC_SETUP 38 | CMAKE_TARGETS # Individual targets to link to 39 | BUILD 40 | missing 41 | ) 42 | 43 | conan_basic_setup() 44 | 45 | verbose_message("Conan is setup and all requires have been installed.") 46 | endif() 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # utu 2 | 3 | `utu` uses the [loris](http://www.cerlsoundgroup.org/Loris/) library to provide 4 | sound analysis, synthesis, and morphing functionality. `utu` defines an 5 | alternative JSON based file format for partial data to allow the analysis output 6 | to be used in a wide number of environments. 7 | 8 | ## building 9 | 10 | `utu` requires a C++17 capable compiler, tested compilers include: 11 | 12 | | compiler | version | platform | os | 13 | | -------- | ------- | -------- | -- | 14 | | gcc | 10.2.1 20210110 | aarch64-linux-gnu | Debian Bullseye | 15 | | clang | Apple clang version 13.0.0 | arm64-apple-darwin21.2.0 | Monterey | 16 | | clang | Apple clang version 13.0.0 | x86_64-apple-darwin20.6.0 | Big Sur | 17 | 18 | 19 | ### dependencies 20 | 21 | `utu` builds and statically links several third party libraries from source 22 | which requires installation of the GNU autotools suite. 23 | 24 | on linux: 25 | 26 | ``` 27 | sudo apt install cmake autotools-dev automake autoconf m4 libtool libasound2-dev 28 | ``` 29 | 30 | on macos: 31 | 32 | ``` 33 | brew install cmake ninja automake autoconf m4 libtool 34 | ``` 35 | 36 | ### building 37 | 38 | once dependencies are installed building `utu` itself can be done as follows: 39 | 40 | ``` 41 | git clone ssh://git@github.com/madronalabs/utu.git 42 | cd utu 43 | git submodule update --init --depth 1 44 | mkdir build 45 | cd build 46 | cmake .. 47 | cmake --build . 48 | ./bin/Debug/utu --help 49 | ``` 50 | -------------------------------------------------------------------------------- /cmake/Utils.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Print a message only if the `VERBOSE_OUTPUT` option is on 3 | # 4 | 5 | function(verbose_message content) 6 | if(${PROJECT_NAME}_VERBOSE_OUTPUT) 7 | message(STATUS ${content}) 8 | endif() 9 | endfunction() 10 | 11 | # 12 | # Add a target for formating the project using `clang-format` (i.e: cmake --build build --target clang-format) 13 | # 14 | 15 | function(add_clang_format_target) 16 | if(NOT ${PROJECT_NAME}_CLANG_FORMAT_BINARY) 17 | find_program(${PROJECT_NAME}_CLANG_FORMAT_BINARY clang-format) 18 | endif() 19 | 20 | if(${PROJECT_NAME}_CLANG_FORMAT_BINARY) 21 | if(${PROJECT_NAME}_BUILD_EXECUTABLE) 22 | add_custom_target(clang-format 23 | COMMAND ${${PROJECT_NAME}_CLANG_FORMAT_BINARY} 24 | -i ${exe_sources} ${lib_headers} 25 | WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) 26 | elseif(${PROJECT_NAME}_BUILD_HEADERS_ONLY) 27 | add_custom_target(clang-format 28 | COMMAND ${${PROJECT_NAME}_CLANG_FORMAT_BINARY} 29 | -i ${lib_headers} 30 | WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) 31 | else() 32 | add_custom_target(clang-format 33 | COMMAND ${${PROJECT_NAME}_CLANG_FORMAT_BINARY} 34 | -i ${lib_sources} ${lib_headers} 35 | WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) 36 | endif() 37 | 38 | message(STATUS "Format the project using the `clang-format` target (i.e: cmake --build build --target clang-format).\n") 39 | endif() 40 | endfunction() 41 | -------------------------------------------------------------------------------- /cmd/src/AudioFile.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2021 Greg Wuller. 3 | // 4 | // SPDX-License-Identifier: GPL-2.0-or-later 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | class AudioFile final 16 | { 17 | public: 18 | using Samples = std::vector; 19 | 20 | enum Mode { 21 | READ, 22 | WRITE, 23 | }; 24 | 25 | enum Format { WAV, AIFF, CAF }; 26 | 27 | enum Encoding { 28 | PCM_16, 29 | PCM_24, 30 | PCM_32, 31 | FLOAT, 32 | DOUBLE, 33 | }; 34 | 35 | static std::optional inferFormat(const std::filesystem::path& p); 36 | static std::optional inferEncoding(const std::string& s); 37 | 38 | static AudioFile forRead(const std::filesystem::path& p); 39 | static AudioFile forWrite(const std::filesystem::path& p, uint32_t sampleRate, 40 | uint16_t channels = 1, Format format = Format::WAV, 41 | Encoding encoding = Encoding::PCM_24); 42 | 43 | ~AudioFile(); 44 | 45 | AudioFile(const AudioFile&) = delete; 46 | AudioFile& operator=(const AudioFile&) = delete; 47 | 48 | AudioFile(AudioFile&& other); 49 | AudioFile& operator=(AudioFile&& other); 50 | 51 | const std::filesystem::path& path() const { return _path; }; 52 | Mode mode() const { return _mode; }; 53 | 54 | Samples& samples(); 55 | 56 | int sampleRate() const; 57 | int channels() const; 58 | int64_t frames() const; 59 | 60 | void write(); 61 | void write(const Samples& samples); 62 | void close(); 63 | 64 | private: 65 | AudioFile(const std::filesystem::path& p, Mode m) : _path(p), _mode(m), _file(nullptr){}; 66 | 67 | void _loadSamples(); 68 | 69 | std::filesystem::path _path; 70 | Mode _mode; 71 | Samples _samples; 72 | 73 | SNDFILE* _file; 74 | SF_INFO _info; 75 | }; 76 | -------------------------------------------------------------------------------- /lib/src/PartialIO.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2021 Greg Wuller. 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "SerializerImpl.h" 14 | 15 | constexpr uint8_t kIndentWidth = 2; 16 | 17 | constexpr uint16_t kFileVersion = 1; 18 | constexpr char kFileKind[] = "utu-partial-data"; 19 | 20 | namespace 21 | { 22 | 23 | using json = nlohmann::json; 24 | using namespace utu; 25 | 26 | std::optional _read(json& j) 27 | { 28 | FileInfo info = j["file_info"].get(); 29 | 30 | // TODO: validate header and choose the appropriate version of the PartialData 31 | // structure to read. 32 | 33 | PartialData data = j; 34 | std::optional result(data); 35 | 36 | return result; 37 | } 38 | 39 | void _addFileInfo(json& j) 40 | { 41 | FileInfo info({kFileKind, kFileVersion}); 42 | j["file_info"] = info; 43 | } 44 | 45 | } // namespace 46 | 47 | namespace utu 48 | { 49 | 50 | using json = nlohmann::json; 51 | 52 | template <> 53 | std::optional PartialReader::read(const std::string& jsonData) 54 | { 55 | json j = json::parse(jsonData, nullptr, true /* allow exceptions */, true /* allow comments */); 56 | return _read(j); 57 | } 58 | 59 | template <> 60 | std::optional PartialReader::read(std::istream& is) 61 | { 62 | json j; 63 | is >> j; 64 | return _read(j); 65 | } 66 | 67 | template <> 68 | std::optional PartialWriter::write(const PartialData& value) 69 | { 70 | json j = value; 71 | _addFileInfo(j); 72 | std::optional result(j.dump(kIndentWidth)); 73 | return result; 74 | } 75 | 76 | template <> 77 | void PartialWriter::write(const PartialData& value, std::ostream& os) 78 | { 79 | // TODO: better error reporting 80 | json j = value; 81 | _addFileInfo(j); 82 | os << std::setw(kIndentWidth) << j << std::endl; 83 | } 84 | 85 | } // namespace utu -------------------------------------------------------------------------------- /cmd/src/Marshal.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2021 Greg Wuller. 3 | // 4 | // SPDX-License-Identifier: GPL-2.0-or-later 5 | // 6 | 7 | #include "Marshal.h" 8 | 9 | utu::PartialData Marshal::from(const Loris::PartialList& partials) 10 | { 11 | utu::PartialData result; 12 | 13 | result.parameters = { 14 | kTimeName, kFrequencyName, kAmplitudeName, kBandwidthName, kPhaseName, 15 | }; 16 | 17 | for (const auto& ip : partials) { 18 | utu::Partial::Samples time; 19 | utu::Partial::Samples frequency; 20 | utu::Partial::Samples amplitude; 21 | utu::Partial::Samples bandwidth; 22 | utu::Partial::Samples phase; 23 | 24 | for (auto it = ip.begin(); it != ip.end(); it++) { 25 | time.push_back(it.time()); 26 | frequency.push_back(it->frequency()); 27 | amplitude.push_back(it->amplitude()); 28 | bandwidth.push_back(it->bandwidth()); 29 | phase.push_back(it->phase()); 30 | } 31 | 32 | utu::Partial op; 33 | op.parameters[kTimeName] = time; 34 | op.parameters[kFrequencyName] = frequency; 35 | op.parameters[kAmplitudeName] = amplitude; 36 | op.parameters[kBandwidthName] = bandwidth; 37 | op.parameters[kPhaseName] = phase; 38 | 39 | result.push_back(op); // FIXME: check for errors 40 | } 41 | 42 | return result; 43 | } 44 | 45 | Loris::PartialList Marshal::from(const utu::PartialData& data) 46 | { 47 | // TODO: validate the required parameters are present 48 | 49 | Loris::PartialList result; 50 | 51 | for (auto partial : data.partials) { 52 | auto times = partial.parameters[kTimeName]; 53 | auto frequencies = partial.parameters[kFrequencyName]; 54 | auto amplitudes = partial.parameters[kAmplitudeName]; 55 | auto bandwidths = partial.parameters[kBandwidthName]; 56 | auto phases = partial.parameters[kPhaseName]; 57 | 58 | auto t = times.cbegin(); 59 | auto f = frequencies.cbegin(); 60 | auto a = amplitudes.cbegin(); 61 | auto b = bandwidths.cbegin(); 62 | auto p = phases.cbegin(); 63 | 64 | Loris::Partial out; 65 | 66 | for (; t != times.end() and f != frequencies.end() and a != amplitudes.end() and 67 | b != bandwidths.end() and p != phases.end(); 68 | ++t, ++f, ++a, ++b, ++p) { 69 | out.insert(*t, Loris::Breakpoint(*f, *a, *b, *p)); 70 | } 71 | 72 | result.push_back(out); 73 | } 74 | 75 | return result; 76 | } 77 | -------------------------------------------------------------------------------- /lib/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | # 4 | # Project details 5 | # 6 | 7 | project( 8 | ${CMAKE_PROJECT_NAME}Tests 9 | LANGUAGES CXX 10 | ) 11 | 12 | verbose_message("Adding tests under ${CMAKE_PROJECT_NAME}Tests...") 13 | 14 | foreach(file ${test_sources}) 15 | string(REGEX REPLACE "(.*/)([a-zA-Z0-9_ ]+)(\.cpp)" "\\2" test_name ${file}) 16 | add_executable(${test_name}_Tests ${file}) 17 | 18 | # 19 | # Set the compiler standard 20 | # 21 | 22 | target_compile_features(${test_name}_Tests PUBLIC cxx_std_17) 23 | 24 | # 25 | # Setup code coverage if enabled 26 | # 27 | 28 | if (${CMAKE_PROJECT_NAME}_ENABLE_CODE_COVERAGE) 29 | target_compile_options(${CMAKE_PROJECT_NAME} PUBLIC -O0 -g -fprofile-arcs -ftest-coverage) 30 | target_link_options(${CMAKE_PROJECT_NAME} PUBLIC -fprofile-arcs -ftest-coverage) 31 | verbose_message("Code coverage is enabled and provided with GCC.") 32 | endif() 33 | 34 | # 35 | # Load the desired unit testing framework 36 | # 37 | # Currently supported: GoogleTest (and GoogleMock), Catch2. 38 | 39 | if(${CMAKE_PROJECT_NAME}_BUILD_EXECUTABLE) 40 | set(${CMAKE_PROJECT_NAME}_TEST_LIB ${CMAKE_PROJECT_NAME}_LIB) 41 | else() 42 | set(${CMAKE_PROJECT_NAME}_TEST_LIB ${CMAKE_PROJECT_NAME}) 43 | endif() 44 | 45 | if(${CMAKE_PROJECT_NAME}_USE_GTEST) 46 | # NOTE: googletest is added directly as a subdirectory for avoid the need to 47 | # install, commenting out the find_package call as a result. 48 | # 49 | # find_package(GTest REQUIRED) 50 | 51 | if(${CMAKE_PROJECT_NAME}_USE_GOOGLE_MOCK) 52 | set(GOOGLE_MOCK_LIBRARIES GTest::gmock GTest::gmock_main) 53 | endif() 54 | 55 | # NOTE: this treats tests as project internal and allows access to private headers 56 | set_property(TARGET ${test_name}_Tests PROPERTY ${CMAKE_PROJECT_NAME}_INTERNAL 1) 57 | 58 | target_link_libraries( 59 | ${test_name}_Tests 60 | PUBLIC 61 | # GTest::GTest 62 | # GTest::Main 63 | gtest_main 64 | ${GOOGLE_MOCK_LIBRARIES} 65 | ${${CMAKE_PROJECT_NAME}_TEST_LIB} 66 | ) 67 | elseif(${CMAKE_PROJECT_NAME}_USE_CATCH2) 68 | find_package(Catch2 REQUIRED) 69 | target_link_libraries( 70 | ${test_name}_Tests 71 | PUBLIC 72 | Catch2::Catch2 73 | ${${CMAKE_PROJECT_NAME}_TEST_LIB} 74 | ) 75 | else() 76 | message(FATAL_ERROR "Unknown testing library. Please setup your desired unit testing library by using `target_link_libraries`.") 77 | endif() 78 | 79 | # 80 | # Add the unit tests 81 | # 82 | 83 | add_test( 84 | NAME 85 | ${test_name} 86 | COMMAND 87 | ${test_name}_Tests 88 | ) 89 | endforeach() 90 | 91 | verbose_message("Finished adding unit tests for ${CMAKE_PROJECT_NAME}.") 92 | -------------------------------------------------------------------------------- /lib/partials.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://raw.githubusercontent.com/ngwese/utu/main/partials.schema.json", 4 | "title": "utu sample analysis output", 5 | "description": "", 6 | "type": "object", 7 | "properties": { 8 | "file_info": { 9 | "description": "file format metadata", 10 | "type": "object", 11 | "properties": { 12 | "kind": { 13 | "type": "string" 14 | }, 15 | "version": { 16 | "type": "integer" 17 | } 18 | }, 19 | "required": ["kind", "version"] 20 | }, 21 | "description": { 22 | "description": "Optional description of the analysis data", 23 | "type": "string" 24 | }, 25 | "source": { 26 | "description": "Optional details on the audio data which was analyzed", 27 | "type": "object", 28 | "properties": { 29 | "location": { 30 | "description": "Path/URL to the source audio file", 31 | "type": "string" 32 | }, 33 | "fingerprint": { 34 | "description": "Optional sha256 fingerprint of the file which was analyzed", 35 | "type": "string" 36 | } 37 | }, 38 | "required": ["location"] 39 | }, 40 | "parameters": { 41 | "description": "Optional analysis parameters used to compute partials", 42 | "type": "object" 43 | }, 44 | "layout": { 45 | "description": "Describes the name and order of parameters recorded for each breakpoint", 46 | "type": "array", 47 | "items": { 48 | "type": "string" 49 | }, 50 | "minItems": 2, 51 | "uniqueItems": true 52 | }, 53 | "partials": { 54 | "description": "Array of partials", 55 | "type": "array", 56 | "items": { 57 | "description": "A partial", 58 | "type": "object", 59 | "properties": { 60 | "label": { 61 | "type": "string" 62 | }, 63 | "breakpoints": { 64 | "description": "Breakpoints for a given partial", 65 | "type": "array", 66 | "items": { 67 | "description": "Parameters for a given breakpoint", 68 | "type": "array", 69 | "items": { 70 | "type": "number" 71 | } 72 | } 73 | } 74 | }, 75 | "required": ["breakpoints"] 76 | } 77 | } 78 | }, 79 | "required": [ "file_info", "layout", "partials" ] 80 | } -------------------------------------------------------------------------------- /lib/src/SerializerImpl.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2021 Greg Wuller. 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #include 8 | 9 | #include 10 | 11 | namespace utu 12 | { 13 | struct FileInfo { 14 | std::string kind; 15 | uint16_t version; 16 | 17 | NLOHMANN_DEFINE_TYPE_INTRUSIVE(FileInfo, kind, version) 18 | }; 19 | } // namespace utu 20 | 21 | // 22 | // JSON serialize/deserialize helpers, implemented here to avoid exposing the 23 | // chosen JSON library to API consumers and to speed compilation. 24 | // 25 | // https://json.nlohmann.me/features/arbitrary_types/#how-do-i-convert-third-party-types 26 | // 27 | 28 | namespace nlohmann 29 | { 30 | 31 | template 32 | struct adl_serializer> { 33 | static void to_json(json& j, const std::optional& v) 34 | { 35 | if (v) { 36 | j = *v; 37 | } else { 38 | j = nullptr; 39 | } 40 | } 41 | 42 | static void from_json(const json& j, std::optional& v) 43 | { 44 | if (j.is_null()) { 45 | v = {}; 46 | } else { 47 | v = j.get(); 48 | } 49 | } 50 | }; 51 | 52 | template <> 53 | struct adl_serializer { 54 | static void to_json(json& j, const utu::Partial& p) 55 | { 56 | if (p.label) { 57 | j["label"] = *p.label; 58 | } 59 | j["parameters"] = p.parameters; 60 | } 61 | 62 | static void from_json(const json& j, utu::Partial& p) 63 | { 64 | p.label = j.value("label", std::optional({})); 65 | p.parameters = j["parameters"].get(); 66 | // TODO: ensure there is at least one envelope 67 | } 68 | }; 69 | 70 | template <> 71 | struct adl_serializer { 72 | static void to_json(json& j, const utu::PartialData::Source& s) 73 | { 74 | j["location"] = s.location; 75 | if (s.fingerprint) { 76 | j["fingerprint"] = *s.fingerprint; 77 | } 78 | } 79 | 80 | static void from_json(const json& j, utu::PartialData::Source& s) 81 | { 82 | s.location = j["location"].get(); 83 | s.fingerprint = j.value("fingerprint", std::optional({})); 84 | } 85 | }; 86 | 87 | template <> 88 | struct adl_serializer { 89 | static void to_json(json& j, const utu::PartialData& d) 90 | { 91 | if (d.description) { 92 | j["description"] = *d.description; 93 | } 94 | if (d.source) { 95 | j["source"] = *d.source; 96 | } 97 | j["parameters"] = d.parameters; 98 | j["partials"] = d.partials; 99 | } 100 | 101 | static void from_json(const json& j, utu::PartialData& d) 102 | { 103 | d.description = j.value("description", std::optional({})); 104 | d.source = j.value("source", std::optional({})); 105 | d.parameters = j["parameters"].get>(); 106 | 107 | // FIXME: should validate that parameters match up 108 | d.partials = j["partials"].get>(); 109 | } 110 | }; 111 | 112 | } // namespace nlohmann 113 | -------------------------------------------------------------------------------- /cmake/StandardSettings.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Project settings 3 | # 4 | 5 | option(${PROJECT_NAME}_BUILD_EXECUTABLE "Build the project as an executable, rather than a library." ON) 6 | option(${PROJECT_NAME}_BUILD_HEADERS_ONLY "Build the project as a header-only library." OFF) 7 | option(${PROJECT_NAME}_USE_ALT_NAMES "Use alternative names for the project, such as naming the include directory all lowercase." ON) 8 | 9 | # 10 | # Compiler options 11 | # 12 | 13 | option(${PROJECT_NAME}_WARNINGS_AS_ERRORS "Treat compiler warnings as errors." OFF) 14 | 15 | # 16 | # Package managers 17 | # 18 | # Currently supporting: Conan, Vcpkg. 19 | 20 | option(${PROJECT_NAME}_ENABLE_CONAN "Enable the Conan package manager for this project." OFF) 21 | option(${PROJECT_NAME}_ENABLE_VCPKG "Enable the Vcpkg package manager for this project." OFF) 22 | 23 | # 24 | # Unit testing 25 | # 26 | # Currently supporting: GoogleTest, Catch2. 27 | 28 | option(${PROJECT_NAME}_ENABLE_UNIT_TESTING "Enable unit tests for the projects (from the `test` subfolder)." ON) 29 | 30 | option(${PROJECT_NAME}_USE_GTEST "Use the GoogleTest project for creating unit tests." ON) 31 | option(${PROJECT_NAME}_USE_GOOGLE_MOCK "Use the GoogleMock project for extending the unit tests." ON) 32 | 33 | option(${PROJECT_NAME}_USE_CATCH2 "Use the Catch2 project for creating unit tests." OFF) 34 | 35 | # 36 | # Static analyzers 37 | # 38 | # Currently supporting: Clang-Tidy, Cppcheck. 39 | 40 | option(${PROJECT_NAME}_ENABLE_CLANG_TIDY "Enable static analysis with Clang-Tidy." OFF) 41 | option(${PROJECT_NAME}_ENABLE_CPPCHECK "Enable static analysis with Cppcheck." OFF) 42 | 43 | # 44 | # Code coverage 45 | # 46 | 47 | option(${PROJECT_NAME}_ENABLE_CODE_COVERAGE "Enable code coverage through GCC." OFF) 48 | 49 | # 50 | # Doxygen 51 | # 52 | 53 | option(${PROJECT_NAME}_ENABLE_DOXYGEN "Enable Doxygen documentation builds of source." OFF) 54 | 55 | # 56 | # Miscelanious options 57 | # 58 | 59 | # Generate compile_commands.json for clang based tools 60 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 61 | 62 | option(${PROJECT_NAME}_VERBOSE_OUTPUT "Enable verbose output, allowing for a better understanding of each step taken." ON) 63 | option(${PROJECT_NAME}_GENERATE_EXPORT_HEADER "Create a `project_export.h` file containing all exported symbols." OFF) 64 | 65 | # Export all symbols when building a shared library 66 | if(BUILD_SHARED_LIBS) 67 | set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS OFF) 68 | set(CMAKE_CXX_VISIBILITY_PRESET hidden) 69 | set(CMAKE_VISIBILITY_INLINES_HIDDEN 1) 70 | endif() 71 | 72 | option(${PROJECT_NAME}_ENABLE_LTO "Enable Interprocedural Optimization, aka Link Time Optimization (LTO)." OFF) 73 | if(${PROJECT_NAME}_ENABLE_LTO) 74 | include(CheckIPOSupported) 75 | check_ipo_supported(RESULT result OUTPUT output) 76 | if(result) 77 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) 78 | else() 79 | message(SEND_ERROR "IPO is not supported: ${output}.") 80 | endif() 81 | endif() 82 | 83 | 84 | option(${PROJECT_NAME}_ENABLE_CCACHE "Enable the usage of Ccache, in order to speed up rebuild times." ON) 85 | find_program(CCACHE_FOUND ccache) 86 | if(CCACHE_FOUND) 87 | set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) 88 | set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) 89 | endif() 90 | -------------------------------------------------------------------------------- /cmake/BuildLoris.cmake: -------------------------------------------------------------------------------- 1 | include(ExternalProject) 2 | include(FindPkgConfig) 3 | 4 | set(loris_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/dep/loris) 5 | 6 | find_program(AUTOUPDATE_COMMAND NAMES autoupdate) 7 | find_program(AUTORECONF_COMMAND NAMES autoreconf) 8 | 9 | pkg_check_modules(FFTW fftw3 IMPORTED_TARGET) 10 | 11 | # The following attempts to work around old crusty autotools, specifically 12 | # 13 | # (1) loris configure does not offer a way to specify an alternate prefix for 14 | # the libraries it wants to leverage, instead one must add set CPPFLAGS/LDFLAGS 15 | # in order for the configure tests to find the needed bits. 16 | # 17 | # (2) Locating fftw is done via pkg-config which will work on macOS (homebrew) 18 | # and Linux, possibly Windows. If pkg-config finds fftw then we inject the 19 | # needed bits into the environment when configuring 20 | # 21 | # (3) "cmake -E env" can't pass environment variables where the values contain 22 | # spaces to a subcommand. The suggested workaround was to generate a script and 23 | # then execute the script. 24 | # 25 | if(FFTW_FOUND) 26 | ExternalProject_Add(loris 27 | SOURCE_DIR ${loris_SOURCE_DIR} 28 | UPDATE_DISCONNECTED true # do not attempt to update source on rebuild 29 | PATCH_COMMAND ${CMAKE_COMMAND} -E echo "CPPFLAGS=\"${FFTW_STATIC_CFLAGS}\" LDFLAGS=\"${FFTW_STATIC_LDFLAGS}\" ${loris_SOURCE_DIR}/configure --prefix= --with-python=NO --with-csound=NO --with-utils=NO" > ${CMAKE_CURRENT_BINARY_DIR}/configure-loris.sh 30 | CONFIGURE_COMMAND sh ${CMAKE_CURRENT_BINARY_DIR}/configure-loris.sh 31 | BUILD_COMMAND make -j${Ncpu} 32 | BUILD_BYPRODUCTS ${loris_LIBRARY} 33 | ) 34 | else() 35 | ExternalProject_Add(loris 36 | SOURCE_DIR ${loris_SOURCE_DIR} 37 | UPDATE_DISCONNECTED true # do not attempt to update source on rebuild 38 | CONFIGURE_COMMAND ${loris_SOURCE_DIR}/configure --prefix= --with-python=NO --with-csound=NO --with-utils=NO --with-fftw=NO 39 | BUILD_COMMAND make -j${Ncpu} 40 | BUILD_BYPRODUCTS ${loris_LIBRARY} 41 | ) 42 | endif() 43 | 44 | ExternalProject_Get_Property(loris SOURCE_DIR) 45 | 46 | ExternalProject_Add_Step(loris 47 | autoupdate 48 | COMMAND ${AUTOUPDATE_COMMAND} 49 | DEPENDEES download 50 | WORKING_DIRECTORY ${SOURCE_DIR} 51 | ) 52 | 53 | ExternalProject_Add_Step(loris 54 | touch-changelog 55 | COMMAND touch ChangeLog 56 | DEPENDEES download 57 | WORKING_DIRECTORY ${SOURCE_DIR} 58 | ) 59 | 60 | ExternalProject_Add_Step(loris 61 | bootstrap 62 | COMMAND ${AUTORECONF_COMMAND} --install 63 | DEPENDEES autoupdate touch-changelog 64 | DEPENDERS configure 65 | WORKING_DIRECTORY ${SOURCE_DIR} 66 | ) 67 | 68 | # HACK: create the install directories so that the target_include_directories function does not error during configure. 69 | file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/loris-prefix/include) # avoid race condition 70 | file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/loris-prefix/lib) # avoid race condition 71 | 72 | add_library(loris::loris INTERFACE IMPORTED GLOBAL) 73 | target_include_directories(loris::loris INTERFACE 74 | "${PROJECT_BINARY_DIR}/loris-prefix/include" 75 | ) 76 | 77 | set(loris_libs 78 | "${PROJECT_BINARY_DIR}/loris-prefix/lib/${CMAKE_STATIC_LIBRARY_PREFIX}loris${CMAKE_STATIC_LIBRARY_SUFFIX}" 79 | ) 80 | 81 | # TODO: Figure out how to force static linking of fftw 82 | if(FFTW_FOUND) 83 | # ensure consumers of the loris target link against the needed libraries 84 | list(APPEND loris_libs PkgConfig::FFTW) 85 | endif() 86 | target_link_libraries(loris::loris INTERFACE ${loris_libs}) 87 | 88 | add_dependencies(loris::loris loris) 89 | -------------------------------------------------------------------------------- /cmake/CompilerWarnings.cmake: -------------------------------------------------------------------------------- 1 | # from here: 2 | # 3 | # https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Avai 4 | # lable.md 5 | # Courtesy of Jason Turner 6 | 7 | function(set_project_warnings project_name) 8 | set(MSVC_WARNINGS 9 | /W4 # Baseline reasonable warnings 10 | /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss 11 | # of data 12 | /w14254 # 'operator': conversion from 'type1:field_bits' to 13 | # 'type2:field_bits', possible loss of data 14 | /w14263 # 'function': member function does not override any base class 15 | # virtual member function 16 | /w14265 # 'classname': class has virtual functions, but destructor is not 17 | # virtual instances of this class may not be destructed correctly 18 | /w14287 # 'operator': unsigned/negative constant mismatch 19 | /we4289 # nonstandard extension used: 'variable': loop control variable 20 | # declared in the for-loop is used outside the for-loop scope 21 | /w14296 # 'operator': expression is always 'boolean_value' 22 | /w14311 # 'variable': pointer truncation from 'type1' to 'type2' 23 | /w14545 # expression before comma evaluates to a function which is missing 24 | # an argument list 25 | /w14546 # function call before comma missing argument list 26 | /w14547 # 'operator': operator before comma has no effect; expected 27 | # operator with side-effect 28 | /w14549 # 'operator': operator before comma has no effect; did you intend 29 | # 'operator'? 30 | /w14555 # expression has no effect; expected expression with side- effect 31 | /w14619 # pragma warning: there is no warning number 'number' 32 | /w14640 # Enable warning on thread un-safe static member initialization 33 | /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may 34 | # cause unexpected runtime behavior. 35 | /w14905 # wide string literal cast to 'LPSTR' 36 | /w14906 # string literal cast to 'LPWSTR' 37 | /w14928 # illegal copy-initialization; more than one user-defined 38 | # conversion has been implicitly applied 39 | /permissive- # standards conformance mode for MSVC compiler. 40 | ) 41 | 42 | set(CLANG_WARNINGS 43 | -Wall 44 | -Wextra # reasonable and standard 45 | -Wshadow # warn the user if a variable declaration shadows one from a 46 | # parent context 47 | -Wnon-virtual-dtor # warn the user if a class with virtual functions has a 48 | # non-virtual destructor. This helps catch hard to 49 | # track down memory errors 50 | -Wold-style-cast # warn for c-style casts 51 | -Wcast-align # warn for potential performance problem casts 52 | -Wunused # warn on anything being unused 53 | -Woverloaded-virtual # warn if you overload (not override) a virtual 54 | # function 55 | -Wpedantic # warn if non-standard C++ is used 56 | -Wconversion # warn on type conversions that may lose data 57 | -Wsign-conversion # warn on sign conversions 58 | -Wnull-dereference # warn if a null dereference is detected 59 | -Wdouble-promotion # warn if float is implicit promoted to double 60 | -Wformat=2 # warn on security issues around functions that format output 61 | # (ie printf) 62 | ) 63 | 64 | if (${PROJECT_NAME}_WARNINGS_AS_ERRORS) 65 | set(CLANG_WARNINGS ${CLANG_WARNINGS} -Werror) 66 | set(MSVC_WARNINGS ${MSVC_WARNINGS} /WX) 67 | endif() 68 | 69 | set(GCC_WARNINGS 70 | ${CLANG_WARNINGS} 71 | -Wmisleading-indentation # warn if indentation implies blocks where blocks 72 | # do not exist 73 | -Wduplicated-cond # warn if if / else chain has duplicated conditions 74 | -Wduplicated-branches # warn if if / else branches have duplicated code 75 | -Wlogical-op # warn about logical operations being used where bitwise were 76 | # probably wanted 77 | -Wuseless-cast # warn if you perform a cast to the same type 78 | ) 79 | 80 | if(MSVC) 81 | set(PROJECT_WARNINGS ${MSVC_WARNINGS}) 82 | elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 83 | set(PROJECT_WARNINGS ${CLANG_WARNINGS}) 84 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 85 | set(PROJECT_WARNINGS ${GCC_WARNINGS}) 86 | else() 87 | message(AUTHOR_WARNING "No compiler warnings set for '${CMAKE_CXX_COMPILER_ID}' compiler.") 88 | endif() 89 | 90 | if(${PROJECT_NAME}_BUILD_HEADERS_ONLY) 91 | target_compile_options(${project_name} INTERFACE ${PROJECT_WARNINGS}) 92 | else() 93 | target_compile_options(${project_name} PUBLIC ${PROJECT_WARNINGS}) 94 | endif() 95 | 96 | if(NOT TARGET ${project_name}) 97 | message(AUTHOR_WARNING "${project_name} is not a target, thus no compiler warning were added.") 98 | endif() 99 | endfunction() 100 | -------------------------------------------------------------------------------- /lib/test/src/test_json.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2021 Greg Wuller. 3 | // 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include "SerializerImpl.h" 15 | 16 | using json = nlohmann::json; 17 | 18 | // TODO: Add tests for non-happy path cases 19 | 20 | TEST(json, CanReadOptional) 21 | { 22 | json j1 = {{"key", "value"}}; 23 | auto v1 = j1["key"].get>(); 24 | EXPECT_TRUE(v1.has_value()); 25 | EXPECT_EQ(*v1, "value"); 26 | 27 | json j2 = {{"key", {}}}; 28 | auto v2 = j2["key"].get>(); 29 | EXPECT_FALSE(v2.has_value()); 30 | } 31 | 32 | TEST(json, CanReadPartial) 33 | { 34 | json e1 = R"({ 35 | "parameters": { 36 | "amplitude": [0.2, 0.4, -0.2, -0.3] 37 | } 38 | })"_json; 39 | 40 | utu::Partial p1 = e1; 41 | EXPECT_FALSE(p1.label); 42 | EXPECT_EQ(p1.parameters.size(), 1); 43 | EXPECT_NE(p1.parameters.find("amplitude"), p1.parameters.end()); 44 | EXPECT_EQ(p1.parameters["amplitude"].size(), 4); 45 | 46 | json e2 = R"({ 47 | "label": "component-1", 48 | "parameters": { 49 | "time": [0, 0.1, 0.2, 0.3], 50 | "frequency": [440.0, 440.5, 462.2, 439.8] 51 | } 52 | })"_json; 53 | 54 | utu::Partial p2 = e2; 55 | EXPECT_EQ(*p2.label, "component-1"); 56 | EXPECT_EQ(p2.parameters.size(), 2); 57 | EXPECT_EQ(p2.parameters["frequency"].size(), 4); 58 | EXPECT_DOUBLE_EQ(p2.parameters["frequency"][2], 462.2); 59 | } 60 | 61 | TEST(json, CanReadPartialDataSource) 62 | { 63 | json s1 = { 64 | {"location", "file://foo/bar/baz"} 65 | }; 66 | 67 | utu::PartialData::Source v1 = s1; 68 | EXPECT_EQ(v1.location, "file://foo/bar/baz"); 69 | EXPECT_FALSE(v1.fingerprint); 70 | 71 | json s2 = { 72 | {"location", "path/to/source.aiff"}, 73 | {"fingerprint", "aa3d7a675a011631f8a1c5402403496245fd822cdc15a8c385e1bdb5e6f1f0a8"} 74 | }; 75 | 76 | utu::PartialData::Source v2 = s2; 77 | EXPECT_EQ(v2.location, "path/to/source.aiff"); 78 | EXPECT_TRUE(v2.fingerprint.has_value()); 79 | EXPECT_EQ(*v2.fingerprint, "aa3d7a675a011631f8a1c5402403496245fd822cdc15a8c385e1bdb5e6f1f0a8"); 80 | } 81 | 82 | TEST(json, CanReadPartialData) 83 | { 84 | json j1 = R"({ 85 | "parameters": ["foo", "bar", "baz"], 86 | "partials": [ 87 | { 88 | "parameters": { 89 | "time": [0, 0.1, 0.2, 0.3], 90 | "frequency": [440.0, 440.5, 462.2, 439.8] 91 | } 92 | } 93 | ] 94 | })"_json; 95 | 96 | utu::PartialData minimal = j1; 97 | EXPECT_FALSE(minimal.source); 98 | EXPECT_FALSE(minimal.description); 99 | EXPECT_EQ(minimal.partials.size(), 1); 100 | 101 | json j2 = R"({ 102 | "description": "something", 103 | "source": { 104 | "location": "file.wav" 105 | }, 106 | "parameters": ["foo", "bar", "baz"], 107 | "partials": [ 108 | { 109 | "parameters": { 110 | "time": [0, 0.1, 0.2, 0.3], 111 | "frequency": [440.0, 440.5, 462.2, 439.8] 112 | } 113 | }, 114 | { 115 | "label": "a component label", 116 | "parameters": { 117 | "foo": [0, 0.1, 0.2, 0.3], 118 | "bar": [1, 2] 119 | } 120 | } 121 | ] 122 | })"_json; 123 | 124 | utu::PartialData maximal = j2; 125 | EXPECT_EQ(*maximal.description, "something"); 126 | EXPECT_TRUE(maximal.source); 127 | EXPECT_EQ(maximal.parameters.size(), 3); 128 | EXPECT_EQ(maximal.parameters[2], "baz"); 129 | 130 | const utu::Partial& p2 = maximal.partials[1]; 131 | EXPECT_NE(p2.parameters.find("bar"), p2.parameters.end()); 132 | 133 | const utu::Partial::Samples& e = p2.parameters.find("bar")->second; 134 | EXPECT_DOUBLE_EQ(e[1], 2); 135 | } 136 | 137 | TEST(json, PartialReader) 138 | { 139 | std::string data = R"({ 140 | "file_info": { 141 | "kind": "utu-partial-data", 142 | "version": 1 143 | }, 144 | "source": { 145 | "location": "/some/path/on/disk.aiff" 146 | }, 147 | "parameters": ["time", "frequency", "amplitude", "bandwidth", "phase"], 148 | "partials": [ 149 | { 150 | "label": "component-1", 151 | "parameters": { 152 | "time": [0, 440, 0.3, 0.2, 0], 153 | "frequency": [0.2, 440, 0.3, 0.2, 0], 154 | "amplitude": [0.5, 440, 0.3, 0.2, 0], 155 | "bandwith": [1.5, 440, 0.3, 0.2, 0], 156 | "phase": [3.0, 440, 0.3, 0.2, 0] 157 | } 158 | }, 159 | { 160 | "parameters": { 161 | "time": [0.3, 440, 0.3, 0.2, 0], 162 | "frequency": [3.0, 440, 0.3, 0.2, 0], 163 | "amplitude": [4.2, 440, 0.3, 0.2, 0] 164 | } 165 | } 166 | ] 167 | })"; 168 | 169 | std::optional d = utu::PartialReader::read(data); 170 | 171 | } 172 | 173 | int main(int argc, char **argv) 174 | { 175 | ::testing::InitGoogleTest(&argc, argv); 176 | return RUN_ALL_TESTS(); 177 | } 178 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Google 4 | AccessModifierOffset: -1 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveMacros: false 7 | AlignConsecutiveAssignments: false 8 | AlignConsecutiveDeclarations: false 9 | AlignEscapedNewlines: Left 10 | AlignOperands: true 11 | AlignTrailingComments: true 12 | AllowAllArgumentsOnNextLine: true 13 | AllowAllConstructorInitializersOnNextLine: true 14 | AllowAllParametersOfDeclarationOnNextLine: true 15 | AllowShortBlocksOnASingleLine: Never 16 | AllowShortCaseLabelsOnASingleLine: false 17 | AllowShortFunctionsOnASingleLine: All 18 | AllowShortLambdasOnASingleLine: All 19 | AllowShortIfStatementsOnASingleLine: WithoutElse 20 | AllowShortLoopsOnASingleLine: true 21 | AlwaysBreakAfterDefinitionReturnType: None 22 | AlwaysBreakAfterReturnType: None 23 | AlwaysBreakBeforeMultilineStrings: true 24 | AlwaysBreakTemplateDeclarations: Yes 25 | BinPackArguments: true 26 | BinPackParameters: true 27 | BraceWrapping: 28 | AfterCaseLabel: false 29 | AfterClass: false 30 | AfterControlStatement: false 31 | AfterEnum: false 32 | AfterFunction: false 33 | AfterNamespace: false 34 | AfterObjCDeclaration: false 35 | AfterStruct: false 36 | AfterUnion: false 37 | AfterExternBlock: false 38 | BeforeCatch: false 39 | BeforeElse: false 40 | IndentBraces: false 41 | SplitEmptyFunction: true 42 | SplitEmptyRecord: true 43 | SplitEmptyNamespace: true 44 | BreakBeforeBinaryOperators: None 45 | BreakBeforeBraces: Linux 46 | BreakBeforeInheritanceComma: false 47 | BreakInheritanceList: BeforeColon 48 | BreakBeforeTernaryOperators: true 49 | BreakConstructorInitializersBeforeComma: false 50 | BreakConstructorInitializers: BeforeColon 51 | BreakAfterJavaFieldAnnotations: false 52 | BreakStringLiterals: true 53 | ColumnLimit: 100 54 | CommentPragmas: '^ IWYU pragma:' 55 | CompactNamespaces: false 56 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 57 | ConstructorInitializerIndentWidth: 4 58 | ContinuationIndentWidth: 4 59 | Cpp11BracedListStyle: true 60 | DeriveLineEnding: true 61 | DerivePointerAlignment: true 62 | DisableFormat: false 63 | ExperimentalAutoDetectBinPacking: false 64 | FixNamespaceComments: true 65 | ForEachMacros: 66 | - foreach 67 | - Q_FOREACH 68 | - BOOST_FOREACH 69 | IncludeBlocks: Regroup 70 | IncludeCategories: 71 | - Regex: '^' 72 | Priority: 2 73 | SortPriority: 0 74 | - Regex: '^<.*\.h>' 75 | Priority: 1 76 | SortPriority: 0 77 | - Regex: '^<.*' 78 | Priority: 2 79 | SortPriority: 0 80 | - Regex: '.*' 81 | Priority: 3 82 | SortPriority: 0 83 | IncludeIsMainRegex: '([-_](test|unittest))?$' 84 | IncludeIsMainSourceRegex: '' 85 | IndentCaseLabels: true 86 | IndentGotoLabels: true 87 | IndentPPDirectives: None 88 | IndentWidth: 2 89 | IndentWrappedFunctionNames: false 90 | JavaScriptQuotes: Leave 91 | JavaScriptWrapImports: true 92 | KeepEmptyLinesAtTheStartOfBlocks: false 93 | MacroBlockBegin: '' 94 | MacroBlockEnd: '' 95 | MaxEmptyLinesToKeep: 1 96 | NamespaceIndentation: None 97 | ObjCBinPackProtocolList: Never 98 | ObjCBlockIndentWidth: 2 99 | ObjCSpaceAfterProperty: false 100 | ObjCSpaceBeforeProtocolList: true 101 | PenaltyBreakAssignment: 2 102 | PenaltyBreakBeforeFirstCallParameter: 1 103 | PenaltyBreakComment: 300 104 | PenaltyBreakFirstLessLess: 120 105 | PenaltyBreakString: 1000 106 | PenaltyBreakTemplateDeclaration: 10 107 | PenaltyExcessCharacter: 1000000 108 | PenaltyReturnTypeOnItsOwnLine: 200 109 | PointerAlignment: Left 110 | RawStringFormats: 111 | - Language: Cpp 112 | Delimiters: 113 | - cc 114 | - CC 115 | - cpp 116 | - Cpp 117 | - CPP 118 | - 'c++' 119 | - 'C++' 120 | CanonicalDelimiter: '' 121 | BasedOnStyle: google 122 | - Language: TextProto 123 | Delimiters: 124 | - pb 125 | - PB 126 | - proto 127 | - PROTO 128 | EnclosingFunctions: 129 | - EqualsProto 130 | - EquivToProto 131 | - PARSE_PARTIAL_TEXT_PROTO 132 | - PARSE_TEST_PROTO 133 | - PARSE_TEXT_PROTO 134 | - ParseTextOrDie 135 | - ParseTextProtoOrDie 136 | CanonicalDelimiter: '' 137 | BasedOnStyle: google 138 | ReflowComments: true 139 | SortIncludes: true 140 | SortUsingDeclarations: true 141 | SpaceAfterCStyleCast: false 142 | SpaceAfterLogicalNot: false 143 | SpaceAfterTemplateKeyword: true 144 | SpaceBeforeAssignmentOperators: true 145 | SpaceBeforeCpp11BracedList: false 146 | SpaceBeforeCtorInitializerColon: true 147 | SpaceBeforeInheritanceColon: true 148 | SpaceBeforeParens: ControlStatements 149 | SpaceBeforeRangeBasedForLoopColon: true 150 | SpaceInEmptyBlock: false 151 | SpaceInEmptyParentheses: false 152 | SpacesBeforeTrailingComments: 2 153 | SpacesInAngles: false 154 | SpacesInConditionalStatement: false 155 | SpacesInContainerLiterals: true 156 | SpacesInCStyleCastParentheses: false 157 | SpacesInParentheses: false 158 | SpacesInSquareBrackets: false 159 | SpaceBeforeSquareBrackets: false 160 | Standard: Auto 161 | StatementMacros: 162 | - Q_UNUSED 163 | - QT_REQUIRE_VERSION 164 | TabWidth: 8 165 | UseCRLF: false 166 | UseTab: Never 167 | ... 168 | 169 | -------------------------------------------------------------------------------- /cmd/src/AudioFile.cpp: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // Copyright (c) 2021 Greg Wuller. 4 | // 5 | // SPDX-License-Identifier: GPL-2.0-or-later 6 | // 7 | 8 | #include "AudioFile.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | std::optional AudioFile::inferFormat(const std::filesystem::path& p) 15 | { 16 | std::string e = p.extension(); 17 | if (e == ".wav") { 18 | return AudioFile::Format::WAV; 19 | } 20 | if (e == ".aiff") { 21 | return AudioFile::Format::AIFF; 22 | } 23 | if (e == ".caf") { 24 | return AudioFile::Format::CAF; 25 | } 26 | 27 | return {}; 28 | } 29 | 30 | std::optional AudioFile::inferEncoding(const std::string& s) 31 | { 32 | if (s == "16") { 33 | return AudioFile::Encoding::PCM_16; 34 | } 35 | if (s == "24") { 36 | return AudioFile::Encoding::PCM_24; 37 | } 38 | if (s == "32") { 39 | return AudioFile::Encoding::PCM_32; 40 | } 41 | if (s == "f32") { 42 | return AudioFile::Encoding::FLOAT; 43 | } 44 | if (s == "f64") { 45 | return AudioFile::Encoding::DOUBLE; 46 | } 47 | 48 | return {}; 49 | } 50 | 51 | AudioFile AudioFile::forRead(const std::filesystem::path& p) 52 | { 53 | AudioFile file(p, Mode::READ); 54 | 55 | file._info.format = 0; 56 | file._file = sf_open(p.c_str(), SFM_READ, &file._info); 57 | // FIXME: need proper error handling 58 | assert(file._file != nullptr); 59 | 60 | return file; 61 | } 62 | 63 | AudioFile AudioFile::forWrite(const std::filesystem::path& p, uint32_t sampleRate, 64 | uint16_t channels, Format format, Encoding encoding) 65 | { 66 | AudioFile file(p, Mode::WRITE); 67 | 68 | int fmt = 0; 69 | 70 | switch (format) { 71 | case Format::WAV: 72 | fmt = SF_FORMAT_WAV; 73 | break; 74 | case Format::AIFF: 75 | fmt = SF_FORMAT_AIFF; 76 | break; 77 | case Format::CAF: 78 | fmt = SF_FORMAT_CAF; 79 | break; 80 | } 81 | 82 | switch (encoding) { 83 | case Encoding::PCM_16: 84 | fmt |= SF_FORMAT_PCM_16; 85 | break; 86 | case Encoding::PCM_24: 87 | fmt |= SF_FORMAT_PCM_24; 88 | break; 89 | case Encoding::PCM_32: 90 | fmt |= SF_FORMAT_PCM_32; 91 | break; 92 | case Encoding::FLOAT: 93 | fmt |= SF_FORMAT_FLOAT; 94 | break; 95 | case Encoding::DOUBLE: 96 | fmt |= SF_FORMAT_DOUBLE; 97 | break; 98 | } 99 | 100 | assert(fmt != 0); 101 | file._info.format = fmt; 102 | 103 | assert(sampleRate <= std::numeric_limits::max()); 104 | file._info.samplerate = static_cast(sampleRate); 105 | file._info.channels = channels; 106 | 107 | file._file = sf_open(p.c_str(), SFM_WRITE, &file._info); 108 | assert(file._file != nullptr); 109 | 110 | return file; 111 | } 112 | 113 | AudioFile::~AudioFile() { close(); } 114 | 115 | AudioFile::AudioFile(AudioFile&& other) 116 | { 117 | _path = other._path; 118 | _mode = other._mode; 119 | _file = other._file; 120 | _info = other._info; 121 | _samples = other._samples; 122 | 123 | other._file = nullptr; 124 | memset(&other._info, 0, sizeof(SF_INFO)); 125 | } 126 | 127 | AudioFile& AudioFile::operator=(AudioFile&& other) 128 | { 129 | if (this != &other) { 130 | // close any file handle which might be open in this instance 131 | close(); 132 | 133 | _path = other._path; 134 | _mode = other._mode; 135 | _file = other._file; 136 | _info = other._info; 137 | _samples = other._samples; 138 | 139 | other._file = nullptr; 140 | memset(&other._info, 0, sizeof(SF_INFO)); 141 | } 142 | 143 | return *this; 144 | } 145 | 146 | std::vector& AudioFile::samples() 147 | { 148 | if (_mode == Mode::READ && _samples.size() == 0) { 149 | _loadSamples(); 150 | } 151 | return _samples; 152 | } 153 | 154 | int AudioFile::sampleRate() const { return _file ? _info.samplerate : 0; } 155 | 156 | int AudioFile::channels() const { return _file ? _info.channels : 0; } 157 | 158 | int64_t AudioFile::frames() const { return _file ? _info.frames : 0; } 159 | 160 | void AudioFile::write(const Samples& samples) 161 | { 162 | if (_file) { 163 | sf_count_t sampleCount = static_cast(samples.size()); 164 | sf_count_t wrote = sf_write_double(_file, samples.data(), sampleCount); 165 | assert(wrote == sampleCount); 166 | } 167 | } 168 | 169 | void AudioFile::write() { write(_samples); } 170 | 171 | void AudioFile::close() 172 | { 173 | if (_file) { 174 | // FIXME: check for errors 175 | sf_close(_file); 176 | } 177 | } 178 | 179 | void AudioFile::_loadSamples() 180 | { 181 | if (_file) { 182 | // TODO: generalize to handle files with multiple channels 183 | assert(_info.channels == 1); 184 | 185 | // ensure the down cast to reserve size will not overflow 186 | assert(_info.frames >= 0); 187 | assert(std::numeric_limits::max() <= 188 | std::numeric_limits::max()); 189 | 190 | // assign (as opposed to reserve) so that the vector size reflects the size of the data being 191 | // written into the backing memory 192 | _samples.assign(static_cast(_info.frames), 0.0); 193 | 194 | sf_count_t read = 195 | sf_read_double(_file, _samples.data(), static_cast(_samples.capacity())); 196 | assert(read == _info.frames); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /cmd/src/AudioPlayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2021 Greg Wuller. 3 | // 4 | // SPDX-License-Identifier: GPL-2.0-or-later 5 | // 6 | 7 | #pragma once 8 | 9 | #pragma GCC diagnostic push 10 | #pragma GCC diagnostic ignored "-Wold-style-cast" 11 | #include 12 | #pragma GCC diagnostic pop 13 | 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | class AudioPlayer final 26 | { 27 | public: 28 | using Samples64 = std::vector; 29 | using Samples32 = std::vector; 30 | 31 | static std::vector getOutputDeviceDescriptions() 32 | { 33 | std::vector descriptions; 34 | 35 | RtAudio dac(RtAudio::UNSPECIFIED, &_errorCallback); 36 | 37 | auto deviceCount = dac.getDeviceCount(); 38 | for (unsigned int i = 0; i < deviceCount; i++) { 39 | auto info = dac.getDeviceInfo(i); 40 | descriptions.push_back(info.name); 41 | } 42 | 43 | return descriptions; 44 | } 45 | 46 | AudioPlayer(const Samples64& samples, uint32_t sampleRate) 47 | : _samples(samples), 48 | _sampleRate(sampleRate), 49 | _playbackOffset(0), 50 | _blocksOutput(0), 51 | _convertedSamples({}) 52 | { 53 | } 54 | 55 | int play(std::optional outputDevice = {}, bool verbose = true, bool src = true) 56 | { 57 | using namespace std::chrono_literals; 58 | 59 | _playbackOffset = 0; 60 | _blocksOutput = 0; 61 | 62 | int status = 0; 63 | 64 | RtAudio dac(RtAudio::UNSPECIFIED, &_errorCallback); 65 | 66 | if (dac.getDeviceCount() < 1) { 67 | std::cout << "error: No audio devices found\n"; 68 | return -1; 69 | } 70 | 71 | dac.showWarnings(); 72 | 73 | RtAudio::StreamParameters params; 74 | params.deviceId = outputDevice ? *outputDevice : dac.getDefaultOutputDevice(); 75 | params.nChannels = 1; 76 | params.firstChannel = 0; 77 | 78 | RtAudio::StreamOptions options; 79 | options.flags = 0; 80 | options.flags |= RTAUDIO_SCHEDULE_REALTIME; 81 | 82 | unsigned int bufferFrames = 512; // size of output block 83 | 84 | auto info = dac.getDeviceInfo(params.deviceId); 85 | if (verbose) { 86 | std::cout << "Output Device: " << info.name << " (sr: " << info.preferredSampleRate 87 | << " ch: " << info.outputChannels << ")\n"; 88 | } 89 | 90 | if (info.preferredSampleRate != _sampleRate) { 91 | if (!_convertedSamples && !src) { 92 | // NOTE: bail if the sample rate of the playback device doesn't match the 93 | // input. Forcing the audio device to change sample rate causes problems 94 | // anywhere from disruption of playback in other applications to 95 | // destabalizing the audio system if the host is not in control of the 96 | // sample rate for external hardware. 97 | std::cout << "error: Output device sr: " << info.preferredSampleRate 98 | << " does not match sample rate of playback material\n"; 99 | return -1; 100 | } else { 101 | _convert(info.preferredSampleRate); 102 | if (verbose) { 103 | std::cout << "Converted sr: " << _sampleRate << " source to " << info.preferredSampleRate 104 | << " for playback\n"; 105 | } 106 | } 107 | } 108 | 109 | RtAudioFormat bufferFormat = _convertedSamples ? RTAUDIO_FLOAT32 : RTAUDIO_FLOAT64; 110 | if (dac.openStream(¶ms, nullptr /* input options */, bufferFormat, info.preferredSampleRate, 111 | &bufferFrames, &_audioCallback, this, &options)) { 112 | status = -200; 113 | goto cleanup; 114 | } 115 | 116 | if (!dac.isStreamOpen()) { 117 | status = -201; 118 | goto cleanup; 119 | } 120 | 121 | if (dac.startStream()) { 122 | status = -202; 123 | goto cleanup; 124 | } 125 | 126 | std::cerr << "Playing..."; 127 | while (dac.isStreamRunning()) { 128 | std::this_thread::sleep_for(500ms); 129 | std::cerr << "."; 130 | } 131 | std::cerr << "done. (blocks: " << _blocksOutput << ")\n"; 132 | 133 | cleanup: 134 | if (dac.isStreamOpen()) { 135 | dac.closeStream(); 136 | } 137 | 138 | return status; 139 | } 140 | 141 | private: 142 | const Samples64& _samples; 143 | const uint32_t _sampleRate; 144 | 145 | uint64_t _playbackOffset; 146 | uint64_t _blocksOutput; 147 | 148 | std::optional _convertedSamples; 149 | uint32_t _convertedRate; 150 | 151 | void _convert(uint32_t desiredRate) 152 | { 153 | if (_convertedSamples && _convertedRate == desiredRate) { 154 | return; // nothing to do 155 | } 156 | 157 | // determine size of converted audio 158 | double conversionRatio = static_cast(desiredRate) / static_cast(_sampleRate); 159 | uint32_t outputFrames = 160 | static_cast(std::ceil(static_cast(_samples.size()) * conversionRatio)); 161 | 162 | // NOTE: libsamplerate does not support double precision samples so 163 | // unfortunately a single precision copy of the input samples needs to be 164 | // created to feed conversion. 165 | Samples32 original(_samples.begin(), _samples.end()); 166 | 167 | // allocate converted audio buffer 168 | Samples32 converted; 169 | converted.assign(outputFrames, 0); 170 | 171 | SRC_DATA params; 172 | params.data_in = original.data(); 173 | params.data_out = converted.data(); 174 | params.input_frames = static_cast(original.size()); 175 | params.output_frames = static_cast(outputFrames); 176 | params.src_ratio = conversionRatio; 177 | 178 | int status = src_simple(¶ms, SRC_SINC_BEST_QUALITY, 1); 179 | if (status != 0) { 180 | std::cerr << "error: " << src_strerror(status) << std::endl; 181 | return; 182 | } 183 | 184 | _convertedSamples = converted; 185 | _convertedRate = desiredRate; 186 | } 187 | 188 | template 189 | inline int _outputSamples(void* outputBuffer, unsigned int nFrames, const T& outputSamples) 190 | { 191 | uint64_t framesRemaining = outputSamples.size() - _playbackOffset; 192 | uint64_t framesToCopy = std::min(static_cast(nFrames), framesRemaining); 193 | 194 | std::memcpy(outputBuffer, &outputSamples[_playbackOffset], 195 | framesToCopy * sizeof(typename T::value_type)); 196 | 197 | _playbackOffset += framesToCopy; 198 | framesRemaining = outputSamples.size() - _playbackOffset; 199 | _blocksOutput += 1; 200 | return framesRemaining > 0 ? 0 /* keep requesting */ : 1 /* drain the buffer and stop */; 201 | } 202 | 203 | int _output(void* outputBuffer, unsigned int nFrames) 204 | { 205 | if (_convertedSamples) { 206 | return _outputSamples(outputBuffer, nFrames, *_convertedSamples); 207 | } 208 | return _outputSamples(outputBuffer, nFrames, _samples); 209 | } 210 | 211 | static void _errorCallback(RtAudioErrorType /* type */, const std::string& error) 212 | { 213 | std::cerr << "error: " << error << "\n"; 214 | } 215 | 216 | static int _audioCallback(void* outputBuffer, void* /* inputBuffer */, unsigned int nFrames, 217 | double /* streamTime */, RtAudioStreamStatus /* status */, 218 | void* userData) 219 | { 220 | return reinterpret_cast(userData)->_output(outputBuffer, nFrames); 221 | } 222 | }; 223 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required (VERSION 3.16) 3 | 4 | # set deployment target before first project invocation 5 | set(CMAKE_OSX_DEPLOYMENT_TARGET "11" CACHE STRING "Minimum OS X deployment version") 6 | 7 | 8 | 9 | # 10 | # Project details 11 | # 12 | 13 | project("utu" 14 | VERSION 0.1.0 15 | LANGUAGES CXX 16 | ) 17 | 18 | # 19 | # Set project options 20 | # 21 | 22 | include(cmake/StandardSettings.cmake) 23 | include(cmake/StaticAnalyzers.cmake) 24 | include(cmake/Utils.cmake) 25 | if(NOT CMAKE_BUILD_TYPE) 26 | set(CMAKE_BUILD_TYPE "Debug") 27 | endif() 28 | message(STATUS "Started CMake for ${PROJECT_NAME} v${PROJECT_VERSION}...\n") 29 | 30 | if (UNIX) 31 | add_compile_options("$<$:-D_DEBUG>") #this will allow to use same _DEBUG macro available in both Linux as well as Windows - MSCV environment. Easy to put Debug specific code. 32 | endif (UNIX) 33 | 34 | 35 | # 36 | # Setup alternative names 37 | # 38 | 39 | if(${PROJECT_NAME}_USE_ALT_NAMES) 40 | string(TOLOWER ${PROJECT_NAME} PROJECT_NAME_LOWERCASE) 41 | string(TOUPPER ${PROJECT_NAME} PROJECT_NAME_UPPERCASE) 42 | else() 43 | set(PROJECT_NAME_LOWERCASE ${PROJECT_NAME}) 44 | set(PROJECT_NAME_UPPERCASE ${PROJECT_NAME}) 45 | endif() 46 | 47 | # 48 | # Prevent building in the source directory 49 | # 50 | 51 | if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) 52 | message(FATAL_ERROR "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there.\n") 53 | endif() 54 | 55 | # 56 | # Enable package managers 57 | # 58 | 59 | include(cmake/Conan.cmake) 60 | include(cmake/Vcpkg.cmake) 61 | 62 | # 63 | # Dependendencies 64 | # 65 | 66 | # docopt 67 | if(${PROJECT_NAME}_BUILD_EXECUTABLE) 68 | add_subdirectory(dep/docopt) 69 | endif() 70 | 71 | # loris 72 | include(cmake/BuildLoris.cmake) 73 | 74 | # nlohmann json 75 | set(JSON_BuildTests OFF CACHE INTERNAL "") 76 | add_subdirectory(dep/json) 77 | 78 | # libsndfile 79 | if(MSVC) 80 | set(ENABLE_STATIC_RUNTIME OFF CACHE INTERNAL "") 81 | endif() 82 | set(ENABLE_EXTERNAL_LIBS OFF CACHE INTERNAL "") 83 | set(ENABLE_CPACK OFF CACHE INTERNAL "") 84 | set(ENABLE_PACKAGE_CONFIG OFF CACHE INTERNAL "") 85 | set(BUILD_TESTING OFF CACHE INTERNAL "") 86 | set(BUILD_PROGRAMS OFF CACHE INTERNAL "") 87 | set(BUILD_EXAMPLES OFF CACHE INTERNAL "") 88 | add_subdirectory(dep/libsndfile) 89 | 90 | # rtaudio 91 | add_subdirectory(dep/rtaudio) 92 | 93 | # libsamplerate 94 | add_subdirectory(dep/libsamplerate) 95 | 96 | # gtest 97 | add_subdirectory(dep/googletest) 98 | 99 | 100 | # 101 | # Create library, setup header and source files 102 | # 103 | 104 | set(exe_dependencies 105 | docopt_s 106 | loris::loris 107 | nlohmann_json::nlohmann_json 108 | SndFile::sndfile 109 | SampleRate::samplerate 110 | rtaudio 111 | ) 112 | 113 | set(lib_dependencies 114 | loris::loris 115 | nlohmann_json::nlohmann_json 116 | ) 117 | 118 | set(test_dependencies 119 | ${lib_dependencies} 120 | ) 121 | 122 | # Find all headers and implementation files 123 | include(cmake/SourcesAndHeaders.cmake) 124 | 125 | if(${PROJECT_NAME}_BUILD_EXECUTABLE) 126 | add_executable(${PROJECT_NAME} ${exe_sources}) 127 | target_link_libraries(${PROJECT_NAME} ${exe_dependencies}) 128 | 129 | if(${PROJECT_NAME}_VERBOSE_OUTPUT) 130 | verbose_message("Found the following sources:") 131 | foreach(source IN LISTS exe_sources) 132 | verbose_message("* ${source}") 133 | endforeach() 134 | endif() 135 | 136 | if(${PROJECT_NAME}_ENABLE_UNIT_TESTING) 137 | add_library(${PROJECT_NAME}_LIB ${lib_headers} ${lib_sources}) 138 | target_link_libraries(${PROJECT_NAME}_LIB ${lib_dependencies}) 139 | 140 | if(${PROJECT_NAME}_VERBOSE_OUTPUT) 141 | verbose_message("Found the following headers:") 142 | foreach(header IN LISTS headers) 143 | verbose_message("* ${header}") 144 | endforeach() 145 | endif() 146 | endif() 147 | elseif(${PROJECT_NAME}_BUILD_HEADERS_ONLY) 148 | add_library(${PROJECT_NAME} INTERFACE) 149 | 150 | if(${PROJECT_NAME}_VERBOSE_OUTPUT) 151 | verbose_message("Found the following headers:") 152 | foreach(header IN LIST headers) 153 | verbose_message("* ${header}") 154 | endforeach() 155 | endif() 156 | else() 157 | add_library( 158 | ${PROJECT_NAME} 159 | ${lib_headers} 160 | ${lib_sources} 161 | ) 162 | 163 | if(${PROJECT_NAME}_VERBOSE_OUTPUT) 164 | verbose_message("Found the following sources:") 165 | foreach(source IN LISTS sources) 166 | verbose_message("* ${source}") 167 | endforeach() 168 | verbose_message("Found the following headers:") 169 | foreach(header IN LISTS headers) 170 | verbose_message("* ${header}") 171 | endforeach() 172 | endif() 173 | endif() 174 | 175 | set_target_properties( 176 | ${PROJECT_NAME} 177 | PROPERTIES 178 | ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}" 179 | LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}" 180 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE}" 181 | ) 182 | if(${PROJECT_NAME}_BUILD_EXECUTABLE AND ${PROJECT_NAME}_ENABLE_UNIT_TESTING) 183 | set_target_properties( 184 | ${PROJECT_NAME}_LIB 185 | PROPERTIES 186 | ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}" 187 | LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}" 188 | OUTPUT_NAME ${PROJECT_NAME} 189 | ) 190 | endif() 191 | 192 | message(STATUS "Added all header and implementation files.\n") 193 | 194 | # 195 | # Set the project standard and warnings 196 | # 197 | 198 | if(${PROJECT_NAME}_BUILD_HEADERS_ONLY) 199 | target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_17) 200 | else() 201 | target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) 202 | 203 | if(${PROJECT_NAME}_BUILD_EXECUTABLE AND ${PROJECT_NAME}_ENABLE_UNIT_TESTING) 204 | target_compile_features(${PROJECT_NAME}_LIB PUBLIC cxx_std_17) 205 | endif() 206 | endif() 207 | include(cmake/CompilerWarnings.cmake) 208 | set_project_warnings(${PROJECT_NAME}) 209 | 210 | verbose_message("Applied compiler warnings. Using standard ${CMAKE_CXX_STANDARD}.\n") 211 | 212 | # 213 | # Enable Doxygen 214 | # 215 | 216 | include(cmake/Doxygen.cmake) 217 | 218 | # 219 | # Model project dependencies 220 | # 221 | 222 | # Identify and link with the specific "packages" the project uses 223 | #find_package(package_name package_version REQUIRED package_type [other_options]) 224 | #target_link_libraries( 225 | # ${PROJECT_NAME} 226 | # PUBLIC 227 | # dependency1 ... 228 | # PRIVATE 229 | # dependency2 ... 230 | # ${PROJECT_NAME}_PROJECT_OPTIONS 231 | # ${PROJECT_NAME}_PROJECT_WARNINGS 232 | #) 233 | #if(${PROJECT_NAME}_BUILD_EXECUTABLE AND ${PROJECT_NAME}_ENABLE_UNIT_TESTING) 234 | # target_link_libraries( 235 | # ${PROJECT_NAME}_LIB 236 | # PUBLIC 237 | # dependency1 ... 238 | # ) 239 | #endif() 240 | 241 | # For Windows, it is necessary to link with the MultiThreaded library. 242 | # Depending on how the rest of the project's dependencies are linked, it might be necessary 243 | # to change the line to statically link with the library. 244 | # 245 | # This is done as follows: 246 | # 247 | # set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 248 | # 249 | # On Linux and Mac this variable is ignored. If any issues rise from it, try commenting it out 250 | # and letting CMake decide how to link with it. 251 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") 252 | 253 | verbose_message("Successfully added all dependencies and linked against them.") 254 | 255 | # 256 | # Set the build/user include directories 257 | # 258 | 259 | # Allow usage of header files in the `src` directory, but only for utilities 260 | if(${PROJECT_NAME}_BUILD_HEADERS_ONLY) 261 | target_include_directories( 262 | ${PROJECT_NAME} 263 | INTERFACE 264 | $ 265 | $ 266 | $ 267 | ) 268 | else() 269 | target_include_directories( 270 | ${PROJECT_NAME} 271 | PUBLIC 272 | $ 273 | $ 274 | $ 275 | # Adds library internal source dir to include path if target has utu_INTERNAL property set to true 276 | # https://stackoverflow.com/questions/54652349/how-to-unit-test-private-features-of-library-tdd-with-cmake 277 | $>:${CMAKE_CURRENT_SOURCE_DIR}/src>> 278 | PRIVATE 279 | ${CMAKE_CURRENT_SOURCE_DIR}/lib/src 280 | ) 281 | if(${PROJECT_NAME}_BUILD_EXECUTABLE AND ${PROJECT_NAME}_ENABLE_UNIT_TESTING) 282 | target_include_directories( 283 | ${PROJECT_NAME}_LIB 284 | PUBLIC 285 | $ 286 | $ 287 | $ 288 | # Adds library internal source dir to include path if target has PROJECT_INTERNAL property set to true 289 | $>:${CMAKE_CURRENT_SOURCE_DIR}/lib/src>> 290 | PRIVATE 291 | ${CMAKE_CURRENT_SOURCE_DIR}/lib/src 292 | ) 293 | endif() 294 | endif() 295 | 296 | message(STATUS "Finished setting up include directories.") 297 | 298 | # 299 | # Provide alias to library for 300 | # 301 | 302 | if(${PROJECT_NAME}_BUILD_EXECUTABLE) 303 | add_executable(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) 304 | else() 305 | add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) 306 | endif() 307 | 308 | verbose_message("Project is now aliased as ${PROJECT_NAME}::${PROJECT_NAME}.\n") 309 | 310 | # 311 | # Format the project using the `clang-format` target (i.e: cmake --build build --target clang-format) 312 | # 313 | 314 | add_clang_format_target() 315 | 316 | # 317 | # Install library for easy downstream inclusion 318 | # 319 | 320 | include(GNUInstallDirs) 321 | install( 322 | TARGETS 323 | ${PROJECT_NAME} 324 | EXPORT 325 | ${PROJECT_NAME}Targets 326 | LIBRARY DESTINATION 327 | ${CMAKE_INSTALL_LIBDIR} 328 | RUNTIME DESTINATION 329 | ${CMAKE_INSTALL_BINDIR} 330 | ARCHIVE DESTINATION 331 | ${CMAKE_INSTALL_LIBDIR} 332 | INCLUDES DESTINATION 333 | include 334 | PUBLIC_HEADER DESTINATION 335 | include 336 | ) 337 | 338 | install( 339 | EXPORT 340 | ${PROJECT_NAME}Targets 341 | FILE 342 | ${PROJECT_NAME}Targets.cmake 343 | NAMESPACE 344 | ${PROJECT_NAME}:: 345 | DESTINATION 346 | ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} 347 | ) 348 | 349 | # 350 | # Add version header 351 | # 352 | 353 | configure_file( 354 | ${CMAKE_CURRENT_LIST_DIR}/cmake/version.h.in 355 | lib/include/${PROJECT_NAME_LOWERCASE}/version.h 356 | @ONLY 357 | ) 358 | 359 | install( 360 | FILES 361 | ${CMAKE_CURRENT_BINARY_DIR}/include/${PROJECT_NAME_LOWERCASE}/version.h 362 | DESTINATION 363 | lib/include/${PROJECT_NAME_LOWERCASE} 364 | ) 365 | 366 | # 367 | # Install the `include` directory 368 | # 369 | 370 | install( 371 | DIRECTORY 372 | lib/include/${PROJECT_NAME_LOWERCASE} 373 | DESTINATION 374 | include 375 | ) 376 | 377 | verbose_message("Install targets succesfully build. Install with `cmake --build --target install --config `.") 378 | 379 | # 380 | # Quick `ConfigVersion.cmake` creation 381 | # 382 | 383 | include(CMakePackageConfigHelpers) 384 | write_basic_package_version_file( 385 | ${PROJECT_NAME}ConfigVersion.cmake 386 | VERSION 387 | ${PROJECT_VERSION} 388 | COMPATIBILITY 389 | SameMajorVersion 390 | ) 391 | 392 | configure_package_config_file( 393 | ${CMAKE_CURRENT_LIST_DIR}/cmake/${PROJECT_NAME}Config.cmake.in 394 | ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake 395 | INSTALL_DESTINATION 396 | ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} 397 | ) 398 | 399 | install( 400 | FILES 401 | ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake 402 | ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake 403 | DESTINATION 404 | ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} 405 | ) 406 | 407 | # 408 | # Generate export header if specified 409 | # 410 | 411 | if(${PROJECT_NAME}_GENERATE_EXPORT_HEADER) 412 | include(GenerateExportHeader) 413 | generate_export_header(${PROJECT_NAME}) 414 | install( 415 | FILES 416 | ${PROJECT_BINARY_DIR}/${PROJECT_NAME_LOWERCASE}_export.h 417 | DESTINATION 418 | include 419 | ) 420 | 421 | message(STATUS "Generated the export header `${PROJECT_NAME_LOWERCASE}_export.h` and installed it.") 422 | endif() 423 | 424 | message(STATUS "Finished building requirements for installing the package.\n") 425 | 426 | # 427 | # Unit testing setup 428 | # 429 | 430 | if(${PROJECT_NAME}_ENABLE_UNIT_TESTING) 431 | enable_testing() 432 | message(STATUS "Build unit tests for the project. Tests should always be found in the test folder\n") 433 | add_subdirectory(lib/test) 434 | endif() 435 | -------------------------------------------------------------------------------- /cmd/src/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2021 Greg Wuller. 3 | // 4 | // SPDX-License-Identifier: GPL-2.0-or-later 5 | // 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "AudioFile.h" 24 | #include "AudioPlayer.h" 25 | #include "Marshal.h" 26 | #include "utu/version.h" 27 | 28 | using Args = std::map; 29 | 30 | std::optional vtod(const docopt::value& v) noexcept; 31 | 32 | template 33 | T check(std::optional n, Predicate predicate, const char* message); 34 | template 35 | T checkRangeInclusive(std::optional n, T minimum, T maximum, const char* message); 36 | template 37 | T checkAboveZero(std::optional n, const char* message); 38 | 39 | int AnalyzeCommand(Args& args); 40 | int SynthCommand(Args& args); 41 | int SynthCommandListOutputDevices(Args& args); 42 | int ConvertCommand(Args& args); 43 | 44 | static const char USAGE[] = 45 | R"(utu 46 | 47 | Usage: 48 | utu analyze [options] [--output=] 49 | utu synth [options] [--output=] 50 | utu synth --list-devices 51 | utu convert ( | ) 52 | utu (-h | --help) 53 | utu --version 54 | 55 | General Options: 56 | -o, --output= write analysis/synthesis result 57 | -h --help Show this screen. 58 | --quiet Suppress normal output. 59 | --version Show version. 60 | 61 | Analyze Options: 62 | --freq-res= minimum instantaneous frequency 63 | difference [default: 332] 64 | --freq-drift= maximum allowable frequency difference 65 | between consecutive breakpoints [default: 30] 66 | --freq-floor= minimum instantaneous partial frequency 67 | --amp-floor= lowest detected spectral amplitude [default: -90] 68 | --crop-time= 69 | --hop-time= approximate average density of breakpoints 70 | --lobe-level= sidelobe attenuation level for Kaiser analysis 71 | window in positive dB 72 | --window-width= frequency domain lobe width [default: 664] 73 | --no-phase-correct 74 | 75 | Synth Options: 76 | --pitch-shift= shift the pitch partials [default: 0] 77 | --sample-rate= sample rate [default: 44100] 78 | --sample-type=(16|24|32|f32|f64) sample type [default: 24] 79 | --audition play result out given audio interface 80 | --device= play out device other than default output 81 | --list-devices list output devices for auditioning 82 | )"; 83 | 84 | int main(int argc, const char** argv) 85 | { 86 | Args args = docopt::docopt(USAGE, {argv + 1, argv + argc}, 87 | true, // show help if requested 88 | PROJECT_VERSION); // version string 89 | 90 | #if 0 91 | for (auto const& arg : args) { 92 | std::cout << arg.first << " " << arg.second << std::endl; 93 | } 94 | #endif 95 | 96 | if (args["analyze"].asBool()) { 97 | return AnalyzeCommand(args); 98 | } else if (args["synth"].asBool()) { 99 | return SynthCommand(args); 100 | } else if (args["convert"].asBool()) { 101 | return ConvertCommand(args); 102 | } 103 | 104 | return -1; 105 | } 106 | 107 | // 108 | // analyze subcommand 109 | // 110 | 111 | int AnalyzeCommand(Args& args) 112 | { 113 | bool quietOutput = args["--quiet"].asBool(); 114 | 115 | docopt::value outputPath = args["--output"]; 116 | if (outputPath && outputPath.asString() == "-") { 117 | // if writing to std::out suppress any logging 118 | quietOutput = true; 119 | } 120 | 121 | double resolutionHz = 122 | checkAboveZero(vtod(args["--freq-res"]), "--freq-res must be greater than 0"); 123 | double windowWidthHz = vtod(args["--window-width"]).value(); 124 | 125 | Loris::Analyzer a(resolutionHz, windowWidthHz); 126 | 127 | // 128 | // configure analysis options 129 | // 130 | 131 | auto freqDrift = args["--freq-drift"]; 132 | if (freqDrift) { 133 | a.setFreqDrift(checkAboveZero(vtod(freqDrift), "--freq-drift must be greater than 0")); 134 | } 135 | 136 | auto freqFloor = args["--freq-floor"]; 137 | if (freqFloor) { 138 | a.setFreqFloor(checkAboveZero(vtod(freqFloor), "--freq-floor must be greater than 0")); 139 | } 140 | 141 | auto ampFloor = args["--amp-floor"]; 142 | if (ampFloor) { 143 | a.setAmpFloor(vtod(ampFloor).value()); 144 | } 145 | 146 | auto hopTime = args["--hop-time"]; 147 | if (hopTime) { 148 | a.setHopTime(vtod(hopTime).value()); 149 | } 150 | 151 | auto cropTime = args["--crop-time"]; 152 | if (cropTime) { 153 | a.setCropTime(vtod(cropTime).value()); 154 | } 155 | 156 | auto lobeLevel = args["--lobe-level"]; 157 | if (lobeLevel) { 158 | a.setSidelobeLevel(vtod(lobeLevel).value()); 159 | } 160 | 161 | if (args["--no-phase-correct"]) { 162 | a.setPhaseCorrect(false); 163 | } 164 | 165 | std::string sourcePath = args[""].asString(); 166 | AudioFile f = AudioFile::forRead(sourcePath); 167 | if (!quietOutput) { 168 | std::cout << "Source: " << sourcePath << " ch: " << f.channels() << " sr: " << f.sampleRate() 169 | << " frames: " << f.frames() << std::endl; 170 | } 171 | 172 | // 173 | // perform analysis 174 | // 175 | 176 | Loris::PartialList partials = a.analyze(f.samples(), f.sampleRate()); 177 | Loris::FrequencyReference partialsRef(partials.begin(), partials.end(), 415 * 0.8, 415 * 1.2, 50); 178 | Loris::Channelizer::channelize(partials, partialsRef, 1); 179 | Loris::Distiller::distill(partials, 0.001); 180 | 181 | if (!quietOutput) { 182 | std::cout << "Partials: " << partials.size() << std::endl; 183 | } 184 | 185 | // 186 | // output results 187 | // 188 | 189 | if (outputPath) { 190 | if (std::filesystem::path(outputPath.asString()).extension() == ".sdif") { 191 | // output native Loris SDIF files 192 | Loris::SdifFile::Export(outputPath.asString(), partials); 193 | } else { 194 | // output JSON format 195 | utu::PartialData data = Marshal::from(partials); 196 | data.source = utu::PartialData::Source({std::filesystem::canonical(sourcePath), {}}); 197 | 198 | if (outputPath.asString() == "-") { 199 | utu::PartialWriter::write(data, std::cout); 200 | } else { 201 | std::ofstream os(outputPath.asString(), std::ios::binary); 202 | utu::PartialWriter::write(data, os); 203 | } 204 | } 205 | 206 | if (!quietOutput) { 207 | std::cout << "Wrote: " << outputPath << std::endl; 208 | } 209 | } 210 | 211 | return 0; 212 | } 213 | 214 | // 215 | // synth subcommand 216 | // 217 | 218 | int SynthCommand(Args& args) 219 | { 220 | // special case, --list-devices 221 | if (args["--list-devices"].asBool()) { 222 | return SynthCommandListOutputDevices(args); 223 | } 224 | 225 | std::string partialPath = args[""].asString(); 226 | 227 | bool quietOutput = args["--quiet"].asBool(); 228 | 229 | Loris::PartialList partials; 230 | std::optional data; 231 | 232 | if (partialPath == "-") { 233 | data = utu::PartialReader::read(std::cin); 234 | } else if (std::filesystem::path(partialPath).extension() == ".sdif") { 235 | Loris::SdifFile in(partialPath); 236 | partials = in.partials(); 237 | } else { 238 | // assume JSON format 239 | std::ifstream is(partialPath, std::ios::binary); 240 | data = utu::PartialReader::read(is); 241 | partials = Marshal::from(*data); 242 | } 243 | 244 | if (!quietOutput) { 245 | std::cout << "Partials: " << partials.size() << std::endl; 246 | } 247 | 248 | std::optional pitchShift = vtod(args["--pitch-shift"]); 249 | if (pitchShift && *pitchShift != 0) { 250 | if (!quietOutput) { 251 | std::cout << "Shifting pitch by " << *pitchShift << " cents\n"; 252 | } 253 | Loris::PartialUtils::shiftPitch(partials.begin(), partials.end(), *pitchShift); 254 | } 255 | 256 | auto sr = static_cast(args["--sample-rate"].asLong()); 257 | 258 | // configure Loris synthesizer paramters 259 | Loris::Synthesizer::Parameters params; 260 | params.sampleRate = sr; 261 | // TODO: fade time 262 | 263 | // perform synthesis 264 | std::vector samples; 265 | Loris::Synthesizer synth(params, samples); 266 | synth.synthesize(partials.begin(), partials.end()); 267 | 268 | if (!quietOutput) { 269 | std::cout << "Calculated: " << samples.size() << " frames, sr: " << sr << std::endl; 270 | } 271 | 272 | docopt::value outputPath = args["--output"]; 273 | if (outputPath) { 274 | std::optional format = AudioFile::inferFormat(outputPath.asString()); 275 | if (!format) { 276 | std::cout << "error: Unsupported output format; must be .wav, .aiff, or .caf\n"; 277 | return -1; 278 | } 279 | 280 | docopt::value sampleType = args["--sample-type"]; 281 | std::optional encoding = AudioFile::inferEncoding(sampleType.asString()); 282 | if (!encoding) { 283 | std::cout << "error: Unsupported sample type; must be 16, 24, 32, f32, or f64\n"; 284 | return -1; 285 | } 286 | 287 | AudioFile f = 288 | AudioFile::forWrite(outputPath.asString(), sr, 1 /* channel */, *format, *encoding); 289 | f.write(samples); 290 | 291 | if (!quietOutput) { 292 | std::cout << "Wrote: " << outputPath.asString() << std::endl; 293 | } 294 | } 295 | 296 | docopt::value audition = args["--audition"]; 297 | if (audition.asBool()) { 298 | std::optional outputDevice; 299 | if (args["--device"]) { 300 | outputDevice = static_cast(args["--device"].asLong()); 301 | } 302 | 303 | AudioPlayer player(samples, sr); 304 | player.play(outputDevice); 305 | } 306 | 307 | return 0; 308 | } 309 | 310 | int SynthCommandListOutputDevices(Args& /* args */) 311 | { 312 | auto descriptions = AudioPlayer::getOutputDeviceDescriptions(); 313 | 314 | if (descriptions.size() == 0) { 315 | std::cerr << "error: No output devices found\n"; 316 | return -1; 317 | } 318 | 319 | std::cout << "Available devices:\n\n"; 320 | for (unsigned int index = 0; index < descriptions.size(); ++index) { 321 | std::cerr << " " << index << ": " << descriptions[index] << std::endl; 322 | } 323 | 324 | return 0; 325 | } 326 | 327 | // 328 | // convert command 329 | // 330 | 331 | int ConvertCommand(Args& args) 332 | { 333 | // NOTE: Conversion is lossy, the markers stored in SDIF files are not carried 334 | // over to the JSON format. 335 | 336 | docopt::value inSdif = args[""]; 337 | docopt::value outJson = args[""]; 338 | if (inSdif && outJson) { 339 | Loris::SdifFile in(inSdif.asString()); 340 | 341 | utu::PartialData data = Marshal::from(in.partials()); 342 | data.source = utu::PartialData::Source({std::filesystem::canonical(inSdif.asString()), {}}); 343 | std::ofstream os(outJson.asString(), std::ios::binary); 344 | utu::PartialWriter::write(data, os); 345 | 346 | return 0; 347 | } 348 | 349 | docopt::value inJson = args[""]; 350 | docopt::value outSdif = args[""]; 351 | if (inJson && outSdif) { 352 | std::ifstream is(inJson.asString(), std::ios::binary); 353 | std::optional data = utu::PartialReader::read(is); 354 | 355 | Loris::PartialList partials = Marshal::from(*data); 356 | Loris::SdifFile::Export(outSdif.asString(), partials); 357 | 358 | return 0; 359 | } 360 | 361 | std::cout << "error: expected input/output file paths for partial data\n"; 362 | return -1; 363 | } 364 | 365 | // 366 | // Helpers 367 | // 368 | 369 | std::optional vtod(const docopt::value& v) noexcept 370 | { 371 | try { 372 | return std::stod(v.asString()); 373 | } catch (...) { 374 | } 375 | return {}; 376 | } 377 | 378 | template 379 | T check(std::optional n, Predicate predicate, const char* message) 380 | { 381 | if (n && predicate(n.value())) { 382 | return n.value(); 383 | } 384 | std::cerr << message << std::endl; 385 | exit(-1); 386 | } 387 | 388 | template 389 | T checkRangeInclusive(std::optional n, T minimum, T maximum, const char* message) 390 | { 391 | if (n && n.value() >= minimum && n.value() <= maximum) { 392 | return n.value(); 393 | } 394 | std::cerr << message << std::endl; 395 | exit(-1); 396 | } 397 | 398 | template 399 | T checkAboveZero(std::optional n, const char* message) 400 | { 401 | return check( 402 | n, [](const T& v) { return v > 0; }, message); 403 | } 404 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 675 Mass Ave, Cambridge, MA 02139, USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | Appendix: How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 19yy 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License 307 | along with this program; if not, write to the Free Software 308 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) 19yy name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Library General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------