├── .gitignore ├── src ├── tester │ ├── 12kHz-100dB.scsa │ ├── impulse.48000.44100.scsa │ ├── 10kHz-100dB.scsa │ ├── 10kHz-140dB.scsa │ ├── impulse.44100.48000.scsa │ ├── sin10k_44100i16_d99.scsa │ ├── sin10k_44100i8_d99.scsa │ ├── sin10k_48000i16_d99.scsa │ ├── sin10k_48000i8_d99.scsa │ ├── sin10k_44100i8_d0.scsa │ ├── sin10k_48000i8_d0.scsa │ ├── sin10k_44100i16_d0.scsa │ ├── sin10k_44100i16_d98.scsa │ ├── sin10k_44100i8_d98.scsa │ ├── sin10k_48000i16_d0.scsa │ ├── sin10k_48000i16_d98.scsa │ ├── sin10k_48000i8_d98.scsa │ ├── sin10k_44100i8_d6.scsa │ ├── sin10k_48000i8_d6.scsa │ ├── sin10k_44100i16_d6.scsa │ ├── sin10k_48000i16_d6.scsa │ ├── execute_commands.cmake │ ├── test_cppapi.cpp │ ├── cmpwav.cpp │ ├── test_oneshot.c │ ├── test_soxrapi.c │ └── test_api.cmake ├── libshibatchdsp │ ├── xdr_wav.cpp │ ├── shibatchdsp.pc.in │ ├── CMakeLists.txt │ ├── BGExecutor.hpp │ ├── BlockingQueue.hpp │ ├── ObjectCache.hpp │ ├── ArrayQueue.hpp │ ├── FastPP.hpp │ ├── Dither.hpp │ ├── ChannelMixer.hpp │ ├── Minrceps.hpp │ ├── WavWriter.hpp │ ├── WavReader.hpp │ ├── DFTFilter.hpp │ ├── RNG.hpp │ ├── Kaiser.hpp │ ├── PartDFTFilter.hpp │ ├── PartDFTFilterMT.hpp │ ├── SRC.hpp │ ├── libssrc.cpp │ └── ssrcsoxr.cpp ├── CMakeLists.txt ├── cli │ ├── CMakeLists.txt │ ├── scsa.1 │ ├── ssrc.1 │ └── scsa.cpp └── include │ └── shibatch │ ├── ssrcsoxr.h │ └── ssrc.hpp ├── .gitmodules ├── NOTICE.txt ├── winbuild-msvc.bat ├── winbuild-clang.bat ├── LICENSE.txt ├── licenses └── LICENSE-dr_libs.txt ├── CONTRIBUTING.md ├── Jenkinsfile ├── CMakeLists.txt ├── SUSTAINABILITY.md └── ALGORITHM.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore build artifacts 2 | build/ 3 | install/ 4 | -------------------------------------------------------------------------------- /src/tester/12kHz-100dB.scsa: -------------------------------------------------------------------------------- 1 | 10 11900 < -100 2 | 12100 22050 < -100 3 | -------------------------------------------------------------------------------- /src/tester/impulse.48000.44100.scsa: -------------------------------------------------------------------------------- 1 | 100 21900 < -80 2 | 100 21900 > -90 3 | -------------------------------------------------------------------------------- /src/tester/10kHz-100dB.scsa: -------------------------------------------------------------------------------- 1 | 10 9900 < -100 2 | 9900 10100 ^ -8 3 | 10100 22050 < -100 4 | -------------------------------------------------------------------------------- /src/tester/10kHz-140dB.scsa: -------------------------------------------------------------------------------- 1 | 10 9900 < -140 2 | 9900 10100 ^ -8 3 | 10100 22050 < -140 4 | -------------------------------------------------------------------------------- /src/tester/impulse.44100.48000.scsa: -------------------------------------------------------------------------------- 1 | 100 21500 < -80 2 | 100 21500 > -90 3 | 22600 24000 < -200 -------------------------------------------------------------------------------- /src/tester/sin10k_44100i16_d99.scsa: -------------------------------------------------------------------------------- 1 | 10 9900 < -120 2 | 9900 10100 ^ -8 3 | 10100 22050 < -120 4 | -------------------------------------------------------------------------------- /src/tester/sin10k_44100i8_d99.scsa: -------------------------------------------------------------------------------- 1 | 10 9900 < -72 2 | 9900 10100 ^ -8 3 | 10100 22050 < -72 4 | -------------------------------------------------------------------------------- /src/tester/sin10k_48000i16_d99.scsa: -------------------------------------------------------------------------------- 1 | 10 9900 < -120 2 | 9900 10100 ^ -8 3 | 10100 24000 < -120 4 | -------------------------------------------------------------------------------- /src/tester/sin10k_48000i8_d99.scsa: -------------------------------------------------------------------------------- 1 | 10 9900 < -72 2 | 9900 10100 ^ -8 3 | 10100 24000 < -72 4 | -------------------------------------------------------------------------------- /src/tester/sin10k_44100i8_d0.scsa: -------------------------------------------------------------------------------- 1 | 10 6000 < -77 2 | 6000 9900 < -72 3 | 9900 10100 ^ -8 4 | 10100 22050 < -62 5 | -------------------------------------------------------------------------------- /src/tester/sin10k_48000i8_d0.scsa: -------------------------------------------------------------------------------- 1 | 10 6000 < -77 2 | 6000 9900 < -72 3 | 9900 10100 ^ -8 4 | 10100 22050 < -62 5 | -------------------------------------------------------------------------------- /src/tester/sin10k_44100i16_d0.scsa: -------------------------------------------------------------------------------- 1 | 10 6000 < -125 2 | 6000 9900 < -120 3 | 9900 10100 ^ -8 4 | 10100 22050 < -110 5 | -------------------------------------------------------------------------------- /src/tester/sin10k_44100i16_d98.scsa: -------------------------------------------------------------------------------- 1 | 10 2000 < -133 2 | 2000 9900 < -120 3 | 9900 10100 ^ -8 4 | 10100 22050 < -110 5 | -------------------------------------------------------------------------------- /src/tester/sin10k_44100i8_d98.scsa: -------------------------------------------------------------------------------- 1 | 10 2000 < -85 2 | 2000 9900 < -72 3 | 9900 10100 ^ -8 4 | 10100 22050 < -62 5 | -------------------------------------------------------------------------------- /src/tester/sin10k_48000i16_d0.scsa: -------------------------------------------------------------------------------- 1 | 10 6000 < -125 2 | 6000 9900 < -120 3 | 9900 10100 ^ -8 4 | 10100 22050 < -110 5 | -------------------------------------------------------------------------------- /src/tester/sin10k_48000i16_d98.scsa: -------------------------------------------------------------------------------- 1 | 10 6000 < -123 2 | 6000 9900 < -120 3 | 9900 10100 ^ -8 4 | 10100 22050 < -110 5 | -------------------------------------------------------------------------------- /src/tester/sin10k_48000i8_d98.scsa: -------------------------------------------------------------------------------- 1 | 10 6000 < -75 2 | 6000 9900 < -72 3 | 9900 10100 ^ -8 4 | 10100 22050 < -62 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "submodules/sleef"] 2 | path = submodules/sleef 3 | url = https://github.com/shibatch/sleef 4 | -------------------------------------------------------------------------------- /src/libshibatchdsp/xdr_wav.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace dr_wav { 4 | #define DR_WAV_IMPLEMENTATION 5 | #include "xdr_wav.h" 6 | } 7 | -------------------------------------------------------------------------------- /src/tester/sin10k_44100i8_d6.scsa: -------------------------------------------------------------------------------- 1 | 10 3000 < -80 2 | 3000 5000 < -92 3 | 5000 9900 < -82 4 | 9900 10100 ^ -8 5 | 10100 12000 < -82 6 | 12000 14000 < -72 7 | 14000 16000 < -52 8 | 16000 22050 < -32 9 | -------------------------------------------------------------------------------- /src/tester/sin10k_48000i8_d6.scsa: -------------------------------------------------------------------------------- 1 | 10 3000 < -82 2 | 3000 6000 < -92 3 | 6000 9900 < -87 4 | 9900 10100 ^ -8 5 | 10100 12000 < -87 6 | 12000 14000 < -77 7 | 14000 16000 < -62 8 | 16000 24000 < -37 9 | -------------------------------------------------------------------------------- /src/tester/sin10k_44100i16_d6.scsa: -------------------------------------------------------------------------------- 1 | 10 3000 < -130 2 | 3000 5000 < -140 3 | 5000 9900 < -130 4 | 9900 10100 ^ -8 5 | 10100 12000 < -130 6 | 12000 14000 < -120 7 | 14000 16000 < -100 8 | 16000 22050 < -80 9 | -------------------------------------------------------------------------------- /src/tester/sin10k_48000i16_d6.scsa: -------------------------------------------------------------------------------- 1 | 10 3000 < -130 2 | 3000 6000 < -140 3 | 6000 9900 < -135 4 | 9900 10100 ^ -8 5 | 10100 12000 < -135 6 | 12000 14000 < -125 7 | 14000 16000 < -110 8 | 16000 24000 < -85 9 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory("libshibatchdsp") 2 | if (BUILD_CLI) 3 | add_subdirectory("cli") 4 | endif() 5 | if (BUILD_TESTS) 6 | add_subdirectory("tester") # tester has to come at last 7 | endif() 8 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | This application uses the following third-party software. 2 | 3 | dr_wav 4 | 5 | * License: MIT No Attribution License 6 | * Source: https://github.com/mackron/dr_libs 7 | * Copyright: Copyright (c) 2016, David Reid 8 | -------------------------------------------------------------------------------- /src/libshibatchdsp/shibatchdsp.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | libdir=@CMAKE_INSTALL_FULL_LIBDIR@ 3 | includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ 4 | 5 | Name: SSRC 6 | Description: Shibatch Sample Rate Converter 7 | Version: @SSRC_VERSION@ 8 | Cflags: -I${includedir} 9 | Libs: -L${libdir} -lshibatchdsp 10 | -------------------------------------------------------------------------------- /src/tester/execute_commands.cmake: -------------------------------------------------------------------------------- 1 | set(COUNTER 0) 2 | 3 | while(1) 4 | string(CONCAT COMMAND_VAR_NAME "COMMAND" ${COUNTER} "_TO_EXECUTE") 5 | 6 | if(DEFINED ${COMMAND_VAR_NAME}) 7 | execute_process( 8 | COMMAND ${${COMMAND_VAR_NAME}} 9 | COMMAND_ERROR_IS_FATAL ANY 10 | COMMAND_ECHO STDOUT 11 | ) 12 | math(EXPR COUNTER "${COUNTER} + 1") 13 | else() 14 | break() 15 | endif() 16 | endwhile() 17 | -------------------------------------------------------------------------------- /src/cli/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(ssrc cli.cpp) 2 | target_link_libraries(ssrc shibatchdsp ${SLEEF_LIBRARIES}) 3 | target_include_directories(ssrc PRIVATE "${PROJECT_SOURCE_DIR}/src/libshibatchdsp") 4 | 5 | add_executable(scsa scsa.cpp) 6 | target_link_libraries(scsa shibatchdsp ${SLEEF_LIBRARIES}) 7 | target_include_directories(scsa PRIVATE "${PROJECT_SOURCE_DIR}/src/libshibatchdsp") 8 | 9 | install( 10 | TARGETS ssrc scsa 11 | DESTINATION "${INSTALL_BINDIR}" 12 | COMPONENT Runtime 13 | ) 14 | 15 | install( 16 | FILES ssrc.1 scsa.1 17 | DESTINATION "${CMAKE_INSTALL_MANDIR}/man1" 18 | COMPONENT Runtime 19 | ) 20 | -------------------------------------------------------------------------------- /winbuild-msvc.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | if "%INSTALLDIR%"=="" set INSTALLDIR=..\..\ssrc_install 3 | 4 | if NOT exist winbuild-msvc.bat exit /b 255 5 | 6 | if "%VSCMD_ARG_HOST_ARCH%"=="" ( 7 | echo Run this batch file from Developer Command Prompt for VS 20XX 8 | exit /b 255 9 | ) 10 | 11 | if %VSCMD_ARG_HOST_ARCH%==x86 call "%VCINSTALLDIR%Auxiliary\Build\vcvars64.bat" 12 | 13 | if exist build\ rmdir /S /Q build 14 | mkdir build 15 | cd build 16 | if exist %INSTALLDIR%\ rmdir /S /Q %INSTALLDIR% 17 | cmake .. -GNinja -DCMAKE_INSTALL_PREFIX=%INSTALLDIR% %* 18 | if not errorlevel 0 exit /b 255 19 | cmake -E time ninja 20 | if not errorlevel 0 exit /b 255 21 | ninja install 22 | -------------------------------------------------------------------------------- /src/libshibatchdsp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(shibatchdsp libssrc.cpp ssrcsoxr.cpp xdr_wav.cpp) 2 | add_dependencies(shibatchdsp ext_sleef) 3 | 4 | set_target_properties(shibatchdsp PROPERTIES 5 | VERSION ${SSRC_VERSION} 6 | SOVERSION ${SSRC_SOVERSION} 7 | PUBLIC_HEADER "${PROJECT_SOURCE_DIR}/src/include/shibatch/ssrc.hpp;${PROJECT_SOURCE_DIR}/src/include/shibatch/shapercoefs.h;${PROJECT_SOURCE_DIR}/src/include/shibatch/ssrcsoxr.h" 8 | ) 9 | 10 | configure_file("${CMAKE_CURRENT_SOURCE_DIR}/shibatchdsp.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/shibatchdsp.pc" @ONLY) 11 | install( 12 | FILES "${CMAKE_CURRENT_BINARY_DIR}/shibatchdsp.pc" 13 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" 14 | COMPONENT shibatch_Development 15 | ) 16 | 17 | install( 18 | TARGETS shibatchdsp 19 | DESTINATION "${INSTALL_LIBDIR}" 20 | PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/shibatch" 21 | COMPONENT shibatch_Development 22 | ) 23 | -------------------------------------------------------------------------------- /src/libshibatchdsp/BGExecutor.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BGEXECUTOR_HPP 2 | #define BGEXECUTOR_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace shibatch { 8 | class Runnable { 9 | class BGExecutor* belongsTo = nullptr; 10 | public: 11 | virtual void run() = 0; 12 | virtual void postProcess(void *) = 0; 13 | virtual ~Runnable() {} 14 | 15 | friend class BGExecutor; 16 | friend class BGExecutorStatic; 17 | 18 | static class std::shared_ptr factory(std::function run, void *p=nullptr, 19 | std::function post = nullptr, void *q=nullptr); 20 | }; 21 | 22 | class BGExecutor { 23 | std::queue> que; 24 | size_t size_ = 0; 25 | public: 26 | void push(std::shared_ptr job); 27 | std::shared_ptr pop(); 28 | size_t size(); 29 | 30 | friend class BGExecutorStatic; 31 | }; 32 | } 33 | #endif // #ifndef BGEXECUTOR_HPP 34 | -------------------------------------------------------------------------------- /winbuild-clang.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | if "%INSTALLDIR%"=="" set INSTALLDIR=..\..\ssrc_install 3 | 4 | if NOT exist winbuild-clang.bat exit /b 255 5 | 6 | if "%VSCMD_ARG_HOST_ARCH%"=="" ( 7 | echo Run this batch file from Developer Command Prompt for VS 20XX 8 | exit /b 255 9 | ) 10 | 11 | if "%CLANGINSTALLDIR%"=="" set CLANGINSTALLDIR=%VCINSTALLDIR%Tools\Llvm\x64 12 | 13 | if NOT exist "%CLANGINSTALLDIR%\bin\clang.exe" ( 14 | echo Cannot find "%CLANGINSTALLDIR%\bin\clang.exe" 15 | echo Please set CLANGINSTALLDIR correctly. 16 | exit /b 255 17 | ) 18 | 19 | if %VSCMD_ARG_HOST_ARCH%==x86 call "%VCINSTALLDIR%Auxiliary\Build\vcvars64.bat" 20 | 21 | if exist build\ rmdir /S /Q build 22 | mkdir build 23 | cd build 24 | if exist %INSTALLDIR%\ rmdir /S /Q %INSTALLDIR% 25 | cmake -GNinja .. -DCMAKE_C_COMPILER:PATH="%CLANGINSTALLDIR%\bin\clang.exe" -DCMAKE_CXX_COMPILER:PATH="%CLANGINSTALLDIR%\bin\clang++.exe" -DCMAKE_INSTALL_PREFIX=%INSTALLDIR% %* 26 | if not errorlevel 0 exit /b 255 27 | cmake -E time ninja 28 | if not errorlevel 0 exit /b 255 29 | ninja install 30 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /src/libshibatchdsp/BlockingQueue.hpp: -------------------------------------------------------------------------------- 1 | #ifndef BLOCKINGQUEUE_HPP 2 | #define BLOCKINGQUEUE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace shibatch { 10 | /** 11 | * @brief A class that implements a thread-safe queue. pop() method 12 | * can be used to retrieve an item from the queue, and this method 13 | * blocks until an item becomes available. 14 | */ 15 | template class BlockingQueue { 16 | std::queue que; 17 | std::mutex mtx; 18 | std::condition_variable condVar; 19 | public: 20 | bool empty() { 21 | std::unique_lock lock(mtx); 22 | return que.empty(); 23 | } 24 | 25 | size_t size() { 26 | std::unique_lock lock(mtx); 27 | return que.size(); 28 | } 29 | 30 | void push(const T& val) { 31 | std::unique_lock lock(mtx); 32 | que.push(val); 33 | if (que.size() == 1) condVar.notify_all(); 34 | } 35 | 36 | void push(T&& val) { 37 | std::unique_lock lock(mtx); 38 | que.push(std::move(val)); 39 | if (que.size() == 1) condVar.notify_all(); 40 | } 41 | 42 | /** @brief This method blocks until an item becomes available. */ 43 | T pop() { 44 | std::unique_lock lock(mtx); 45 | while(que.empty()) condVar.wait(lock); 46 | T ret = std::move(que.front()); 47 | que.pop(); 48 | return ret; 49 | } 50 | 51 | void clear() { 52 | std::unique_lock lock(mtx); 53 | while(!que.empty()) que.pop(); 54 | } 55 | 56 | BlockingQueue() {} 57 | BlockingQueue(const BlockingQueue &p) = delete; 58 | BlockingQueue(BlockingQueue &p) = delete; 59 | BlockingQueue& operator=(const BlockingQueue &) = delete; 60 | }; 61 | } 62 | #endif // #ifndef BLOCKINGQUEUE_HPP 63 | -------------------------------------------------------------------------------- /src/tester/test_cppapi.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "shibatch/ssrc.hpp" 6 | 7 | void convert_file(const std::string& in_path, const std::string& out_path, int dstRate) { 8 | try { 9 | // 1. Set up the reader for single-precision floats 10 | auto reader = std::make_shared>(in_path); 11 | ssrc::WavFormat srcFormat = reader->getFormat(); 12 | 13 | // 2. Define destination format and conversion parameters 14 | int dstBits = 24; 15 | 16 | ssrc::WavFormat dstFormat(ssrc::WavFormat::PCM, srcFormat.channels, dstRate, dstBits); 17 | ssrc::ContainerFormat dstContainer(ssrc::ContainerFormat::RIFF); 18 | 19 | // 3. Create a resampler for each channel 20 | std::vector>> outlets; 21 | for (int i = 0; i < srcFormat.channels; ++i) { 22 | auto resampler = std::make_shared>( 23 | reader->getOutlet(i), 24 | srcFormat.sampleRate, 25 | dstRate, 26 | 14, // log2dftfilterlen for "standard" profile 27 | 145, // aa for "standard" profile 28 | 2.0 // guard for "standard" profile 29 | ); 30 | outlets.push_back(resampler); 31 | } 32 | 33 | // 4. Set up the writer 34 | auto writer = std::make_shared>(out_path, dstFormat, dstContainer, outlets); 35 | 36 | // 5. Execute the entire process 37 | std::cout << "Converting " << in_path << " to " << out_path << "..." << std::endl; 38 | writer->execute(); 39 | std::cout << "Conversion complete." << std::endl; 40 | 41 | } catch (const std::exception& e) { 42 | std::cerr << "Error: " << e.what() << std::endl; 43 | } 44 | } 45 | 46 | int main(int argc, char **argv) { 47 | if (argc == 4) { 48 | convert_file(argv[1], argv[2], atoi(argv[3])); 49 | return 0; 50 | } 51 | 52 | std::cerr << "Usage : " << argv[0] << " " << std::endl; 53 | 54 | return -1; 55 | } 56 | -------------------------------------------------------------------------------- /src/libshibatchdsp/ObjectCache.hpp: -------------------------------------------------------------------------------- 1 | #ifndef OBJECTCACHE_HPP 2 | #define OBJECTCACHE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace ssrc { 13 | template 14 | class ObjectCache { 15 | struct Internal { 16 | std::unordered_map> theCache; 17 | std::mutex mtx; 18 | }; 19 | static Internal internal; 20 | public: 21 | static size_t count(const std::string &key) { 22 | std::unique_lock lock(internal.mtx); 23 | return internal.theCache.count(key); 24 | } 25 | 26 | static std::shared_ptr at(const std::string &key) { 27 | std::unique_lock lock(internal.mtx); 28 | if (internal.theCache.count(key) == 0) return nullptr; 29 | return internal.theCache.at(key); 30 | } 31 | 32 | static void insert(const std::string &key, std::shared_ptr value) { 33 | std::unique_lock lock(internal.mtx); 34 | internal.theCache[key] = value; 35 | } 36 | 37 | static void erase(const std::string &key) { 38 | std::unique_lock lock(internal.mtx); 39 | internal.theCache.erase(key); 40 | } 41 | }; 42 | 43 | template ObjectCache::Internal ssrc::ObjectCache::internal; 44 | 45 | namespace { 46 | template::value), int>::type = 0> 47 | SleefDFT *SleefDFT_init(uint64_t mode, uint32_t n) { 48 | return SleefDFT_double_init1d(n, NULL, NULL, mode); 49 | } 50 | 51 | template::value), int>::type = 0> 52 | SleefDFT *SleefDFT_init(uint64_t mode, uint32_t n) { 53 | return SleefDFT_float_init1d(n, NULL, NULL, mode); 54 | } 55 | } 56 | 57 | template::value || std::is_same::value), int>::type = 0> 58 | std::shared_ptr constructSleefDFT(uint64_t mode, uint32_t n) { 59 | std::string key = "SleefDFT<" + std::string(typeid(T).name()) + ">(" + std::to_string(mode) + ", " + std::to_string(n) + ")"; 60 | std::shared_ptr ret = ObjectCache::at(key); 61 | 62 | if (!ret) { 63 | ret = std::shared_ptr(SleefDFT_init(mode, n), SleefDFT_dispose);; 64 | ObjectCache::insert(key, ret); 65 | } 66 | 67 | return ret; 68 | } 69 | } 70 | #endif //#ifndef OBJECTCACHE_HPP 71 | -------------------------------------------------------------------------------- /src/libshibatchdsp/ArrayQueue.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ARRAYQUEUE_HPP 2 | #define ARRAYQUEUE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace shibatch { 12 | template 13 | class ArrayQueue { 14 | std::deque> queue; 15 | size_t pos = 0, sumsize = 0; 16 | 17 | public: 18 | size_t size() { return sumsize - pos; } 19 | 20 | void write(std::vector &&v) { 21 | sumsize += v.size(); 22 | queue.push_back(std::move(v)); 23 | } 24 | 25 | void write(T *ptr, size_t n) { 26 | std::vector v(n); 27 | memcpy(v.data(), ptr, n * sizeof(T)); 28 | write(std::move(v)); 29 | } 30 | 31 | size_t read(T *ptr, size_t n) { 32 | size_t s = std::min(size(), n); 33 | 34 | for(size_t r = s;r > 0;) { 35 | size_t cs = std::min(queue.front().size() - pos, r); 36 | memcpy(ptr, queue.front().data() + pos, cs * sizeof(T)); 37 | pos += cs; 38 | ptr += cs; 39 | r -= cs; 40 | if (pos >= queue.front().size()) { 41 | sumsize -= queue.front().size(); 42 | queue.pop_front(); 43 | pos = 0; 44 | } 45 | } 46 | 47 | return s; 48 | } 49 | }; 50 | 51 | template 52 | class BlockingArrayQueue { 53 | const size_t capacity; 54 | bool closed = false; 55 | ArrayQueue aq; 56 | std::mutex mtx; 57 | std::condition_variable condVar; 58 | 59 | public: 60 | BlockingArrayQueue(size_t c) : capacity(c) {} 61 | 62 | size_t size() { return aq.size(); } 63 | 64 | void close() { 65 | std::unique_lock lock(mtx); 66 | closed = true; 67 | condVar.notify_all(); 68 | } 69 | 70 | void write(std::vector &&v) { 71 | std::unique_lock lock(mtx); 72 | while(size() >= capacity && !closed) condVar.wait(lock); 73 | if (closed) return; 74 | condVar.notify_all(); 75 | aq.write(std::move(v)); 76 | } 77 | 78 | size_t write(T *ptr, size_t n) { 79 | std::unique_lock lock(mtx); 80 | while(size() >= capacity && !closed) condVar.wait(lock); 81 | if (closed) return 0; 82 | size_t z = capacity - size(); 83 | aq.write(ptr, z); 84 | condVar.notify_all(); 85 | return z; 86 | } 87 | 88 | size_t read(T *ptr, size_t n) { 89 | std::unique_lock lock(mtx); 90 | while(size() == 0 && !closed) condVar.wait(lock); 91 | condVar.notify_all(); 92 | return aq.read(ptr, n); 93 | } 94 | }; 95 | } 96 | #endif //#ifndef ARRAYQUEUE_HPP 97 | -------------------------------------------------------------------------------- /src/libshibatchdsp/FastPP.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FASTPP_HPP 2 | #define FASTPP_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "shibatch/ssrc.hpp" 10 | 11 | namespace shibatch { 12 | template 13 | class FastPP : public ssrc::StageOutlet { 14 | const size_t N = 65536; 15 | std::shared_ptr> inlet; 16 | const size_t sfs, lcmfs, dfs, sstep, dstep, firlen; 17 | 18 | std::vector buf; 19 | std::vector> fircoef; 20 | size_t dpos = 0, ssize = 0, dsize = 0, buflast = 0; 21 | 22 | public: 23 | FastPP(std::shared_ptr> in_, int64_t sfs_, int64_t lcmfs_, int64_t dfs_, const REAL *fircoef_, size_t firlen_) : 24 | inlet(in_), sfs(sfs_), lcmfs(lcmfs_), dfs(dfs_), sstep(lcmfs / sfs), dstep(lcmfs / dfs), firlen(firlen_) { 25 | fircoef.resize(sstep); 26 | for(size_t i=0;i= dsize; 34 | } 35 | 36 | size_t read(REAL *out, size_t nSamples) { 37 | size_t nOut = 0; 38 | 39 | while(nSamples > 0) { 40 | size_t nRead = inlet->read(buf.data() + buflast, buf.size() - buflast); 41 | ssize += nRead; 42 | dsize = ssize * sstep / dstep; 43 | 44 | bool endReached = nRead == 0; 45 | 46 | if (dpos >= dsize) return nOut; 47 | 48 | buflast += nRead; 49 | memset(buf.data() + buflast, 0, (buf.size() - buflast) * sizeof(REAL)); 50 | 51 | const size_t sorg = (dpos * dstep + sstep - 1) / sstep; 52 | const size_t bs = std::min(nSamples, N); 53 | 54 | for(size_t i=0;i= buflast && !endReached) break; 59 | 60 | REAL sum = 0; 61 | for(size_t p = 0;p < (firlen + sstep - 1) / sstep;p++) { 62 | sum += fircoef[filterpos][p] * buf[p + (spos - sorg)]; 63 | } 64 | 65 | *out++ = sum; 66 | dpos++; 67 | nOut++; 68 | nSamples--; 69 | } 70 | 71 | const size_t slast = (dpos * dstep + sstep - 1) / sstep; 72 | memmove(buf.data(), buf.data() + (slast - sorg), (buf.size() - (slast - sorg)) * sizeof(REAL)); 73 | buflast -= slast - sorg; 74 | } 75 | 76 | return nOut; 77 | } 78 | }; 79 | } 80 | #endif // #ifndef FASTPP_HPP 81 | -------------------------------------------------------------------------------- /src/tester/cmpwav.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "dr_wav.hpp" 7 | 8 | using namespace std; 9 | using namespace dr_wav; 10 | 11 | string container_to_string(drwav_container container) { 12 | if (container == drwav_container_riff) return "riff"; 13 | if (container == drwav_container_w64) return "w64"; 14 | if (container == drwav_container_rf64) return "rf64"; 15 | if (container == drwav_container_aiff) return "aiff"; 16 | return "unknown"; 17 | } 18 | 19 | double compare(const string& file0, const string& file1) { 20 | static const size_t N = 4096; 21 | 22 | WavFile wav0(file0), wav1(file1); 23 | 24 | if (wav0.getNChannels() != wav1.getNChannels()) throw(runtime_error("Number of channels does not match")); 25 | if (wav0.getSampleRate() != wav1.getSampleRate()) throw(runtime_error("Sample rates do not match")); 26 | if (wav0.getNFrames() != wav1.getNFrames()) 27 | throw(runtime_error(("Number of frames does not match : " + file0 + ":" + to_string(wav0.getNFrames()) + 28 | " vs. " + file1 + ":" + to_string(wav1.getNFrames())).c_str())); 29 | 30 | const unsigned nch = wav0.getNChannels(); 31 | 32 | vector buf0(N * nch), buf1(N * nch); 33 | 34 | double maxDif = 0; 35 | 36 | for(;;) { 37 | size_t nr0 = wav0.readPCM(buf0.data(), N), nr1 = wav1.readPCM(buf1.data(), N); 38 | if (nr0 != nr1) throw(runtime_error("File lengths do not match")); 39 | 40 | for(size_t i=0;i " << endl; 65 | cerr << " or " << argv[0] << " --check-channels <# of channels>" << endl; 66 | cerr << " or " << argv[0] << " --check-container " << endl; 67 | } 68 | 69 | return -1; 70 | } 71 | -------------------------------------------------------------------------------- /src/cli/scsa.1: -------------------------------------------------------------------------------- 1 | .\" Man page for scsa 2 | .TH scsa 1 "September 2025" "SSRC" "User Commands" 3 | .SH NAME 4 | scsa \- command-line spectrum analyzer for automated testing 5 | .SH SYNOPSIS 6 | .B scsa 7 | [\fIoptions\fR] \fI\fR \fI\fR \fI\fR \fI\fR 8 | .SH DESCRIPTION 9 | .B scsa 10 | is a command-line spectrum analyzer. While it can be used as a general-purpose analyzer, it is primarily designed for automated testing and verification, for example in a CI environment. 11 | .P 12 | Its main purpose is to check audio spectra against predefined criteria, making it ideal for automated quality assurance in a CI/CD pipeline. It is cross-platform and dependency-free. When a test fails, 13 | .B scsa 14 | can generate an SVG image of the spectrum for debugging. 15 | .P 16 | Unlike many standard analyzers, all internal processing is performed in double precision. This minimizes the impact of floating-point noise, allowing for highly accurate measurements. It also uses a 7-term Blackman-Harris window function, which provides excellent dynamic range and frequency resolution. 17 | .SH OPTIONS 18 | .TP 19 | \fB--log2dftlen \fR 20 | Set the log2 of the DFT length. Default: 12. 21 | .TP 22 | \fB--check \fR 23 | Specify a file containing spectrum check criteria. 24 | .TP 25 | \fB--svgout \fR 26 | Specify the output SVG file name for the spectrum graph. An SVG is generated if a check file is provided and the test fails, or if no check file is provided at all. 27 | .TP 28 | \fB--debug\fR 29 | Print detailed debugging information during processing. 30 | .SH "CHECK FILE FORMAT" 31 | The check file is a plain text file that defines the spectral criteria. Each line specifies a single constraint. 32 | .P 33 | .B Format: 34 | 35 | .IP 36 | .B 37 | The lower bound of the frequency range in Hz. 38 | .IP 39 | .B 40 | The upper bound of the frequency range in Hz. 41 | .IP 42 | .B 43 | The comparison operator. Can be one of the following: 44 | .RS 45 | .TP 46 | \fB<\fR 47 | Checks if all frequency components in the range are less than the threshold. 48 | .TP 49 | \fB>\fR 50 | Checks if all frequency components in the range are greater than the threshold. 51 | .TP 52 | \fB^\fR 53 | Checks if the peak frequency component in the range is greater than or equal to the threshold. 54 | .RE 55 | .IP 56 | .B 57 | The threshold value in decibels. 58 | .P 59 | Lines starting with a # character are treated as comments. For a check to pass, all frequency components within the range must satisfy the condition. 60 | .SH AUTHOR 61 | Naoki Shibata and contributors. 62 | .SH "SEE ALSO" 63 | .BR ssrc (1) 64 | -------------------------------------------------------------------------------- /licenses/LICENSE-dr_libs.txt: -------------------------------------------------------------------------------- 1 | This software is available as a choice of the following licenses. Choose 2 | whichever you prefer. 3 | 4 | =============================================================================== 5 | ALTERNATIVE 1 - Public Domain (www.unlicense.org) 6 | =============================================================================== 7 | This is free and unencumbered software released into the public domain. 8 | 9 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 10 | software, either in source code form or as a compiled binary, for any purpose, 11 | commercial or non-commercial, and by any means. 12 | 13 | In jurisdictions that recognize copyright laws, the author or authors of this 14 | software dedicate any and all copyright interest in the software to the public 15 | domain. We make this dedication for the benefit of the public at large and to 16 | the detriment of our heirs and successors. We intend this dedication to be an 17 | overt act of relinquishment in perpetuity of all present and future rights to 18 | this software under copyright law. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 24 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | For more information, please refer to 28 | 29 | =============================================================================== 30 | ALTERNATIVE 2 - MIT No Attribution 31 | =============================================================================== 32 | Copyright 2020 David Reid 33 | 34 | Permission is hereby granted, free of charge, to any person obtaining a copy of 35 | this software and associated documentation files (the "Software"), to deal in 36 | the Software without restriction, including without limitation the rights to 37 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 38 | of the Software, and to permit persons to whom the Software is furnished to do 39 | so. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 42 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 43 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 44 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 45 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 46 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 47 | SOFTWARE. 48 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to this project 2 | 3 | Thank you for your interest in contributing to this project. We welcome contributions from the community to help improve this project. 4 | 5 | However, to ensure the sustainability of the project and fairness among stakeholders, we have established specific policies for corporate contributors. 6 | 7 | ## Policy for Corporate Contributors 8 | 9 | **In principle, organizations that meet the criteria below are required to agree to the following Corporate Contributor License Agreement (CCLA).** 10 | 11 | Many corporations routinely require their business partners to agree to similar provisions (such as unlimited liability and complete transfer of intellectual property). In fact, this project has explicitly been asked by corporate users of our products to agree to these exact types of clauses in the past. 12 | 13 | Therefore, we recognize that agreeing to such clauses is a standard practice in business-to-business transactions and should **pose no obstacle** for corporate users wishing to contribute to this project. 14 | 15 | ### Scope of Application 16 | 17 | This policy applies to contributions made on behalf of organizations that: 18 | 19 | 1. Incorporate this project's deliverables into their products or services. 20 | 2. Derive annual revenue exceeding **US $1 million** from those products or services. 21 | 22 | **NOTE:** Individual contributors and small businesses that do not meet the above criteria are **NOT** required to agree to this CCLA. They may continue to contribute under the standard project license. 23 | 24 | --- 25 | 26 | ### Corporate Contributor License Agreement (CCLA) Terms: 27 | 28 | 1. **Transfer of Intellectual Property:** 29 | You hereby assign to the Project Maintainer all right, title, and interest in and to this contribution, including all Intellectual Property Rights therein. The Project Maintainer shall be the sole and exclusive owner of the contribution. 30 | 31 | 2. **Warranties:** 32 | You represent and warrant that this contribution is free from any defects, fit for the particular purpose of the Project, and does not infringe upon the intellectual property rights of any third party. 33 | 34 | 3. **Indemnification & Unlimited Liability:** 35 | You agree to defend, indemnify, and hold harmless the Project Maintainer from and against any and all claims, damages, losses, and expenses arising out of or resulting from this contribution. **Your liability under this agreement is UNLIMITED.** 36 | 37 | --- 38 | 39 | ### Authorized Exemption 40 | 41 | We understand that some organizations may prefer not to agree to the terms above. 42 | 43 | Organizations that enter into a separate Maintenance Support Agreement (which includes fair liability caps and mutual respect for IP) with the Project Maintainer are granted an **"Authorized Exemption"** status. 44 | 45 | For inquiries regarding CCLA exemption, please contact the maintainers. 46 | -------------------------------------------------------------------------------- /src/libshibatchdsp/Dither.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DITHER_HPP 2 | #define DITHER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "shibatch/ssrc.hpp" 11 | #include "RNG.hpp" 12 | 13 | template class ssrc::Dither::DitherImpl { 14 | public: 15 | virtual ~DitherImpl() = default; 16 | }; 17 | 18 | namespace shibatch { 19 | class TriangularDoubleRNG : public ssrc::DoubleRNG { 20 | const double peak; 21 | std::shared_ptr rng; 22 | public: 23 | TriangularDoubleRNG(double peak_, std::shared_ptr rng_ = std::make_shared()) : 24 | peak(peak_), rng(rng_) {} 25 | double nextDouble() { return rng->nextTriangularDouble(peak); } 26 | ~TriangularDoubleRNG() {} 27 | }; 28 | 29 | template 30 | class DitherStage : public ssrc::StageOutlet, public ssrc::Dither::DitherImpl { 31 | public: 32 | std::shared_ptr> inlet; 33 | const double gain; 34 | const int32_t offset, clipMin, clipMax; 35 | const ssrc::NoiseShaperCoef *coef; 36 | std::shared_ptr rng; 37 | std::vector buf, in; 38 | std::vector rndbuf; 39 | 40 | DitherStage(std::shared_ptr> in_, double gain_, int32_t offset_, int32_t clipMin_, int32_t clipMax_, 41 | const ssrc::NoiseShaperCoef *coef_, std::shared_ptr rng_) : 42 | inlet(in_), gain(gain_), offset(offset_), clipMin(clipMin_), clipMax(clipMax_), coef(coef_), rng(rng_) { 43 | buf.resize(coef->len); 44 | } 45 | 46 | bool atEnd() { return inlet->atEnd(); } 47 | 48 | size_t read(OUTTYPE *out, size_t nSamples) { 49 | const double *shaperCoefs = coef->coefs; 50 | const int shaperLen = coef->len; 51 | 52 | if (in.size() < nSamples) in.resize(nSamples); 53 | nSamples = inlet->read(in.data(), nSamples); 54 | 55 | rndbuf.resize(std::max(rndbuf.size(), nSamples)); 56 | rng->fill(rndbuf.data(), nSamples); 57 | 58 | if (shaperLen != 0) { 59 | for(size_t p=0;p=0;i--) { 63 | h += shaperCoefs[i] * buf[i]; 64 | buf[i+1] = buf[i]; 65 | } 66 | 67 | double x = gain * in[p] + offset + h; 68 | double q = rint(x + rndbuf[p]); 69 | buf[0] = q - x; 70 | 71 | if (q < clipMin || q > clipMax) { 72 | if (q < clipMin) q = clipMin; 73 | if (q > clipMax) q = clipMax; 74 | buf[0] = q - x; 75 | if (buf[0] < -1) buf[0] = -1; 76 | if (buf[0] > 1) buf[0] = 1; 77 | } 78 | 79 | out[p] = q; 80 | } 81 | } else { 82 | for(size_t p=0;p 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "shibatch/ssrc.hpp" 11 | #include "ArrayQueue.hpp" 12 | 13 | template class ssrc::ChannelMixer::ChannelMixerImpl { 14 | public: 15 | virtual ~ChannelMixerImpl() = default; 16 | }; 17 | 18 | namespace shibatch { 19 | template 20 | class ChannelMixerStage : public ssrc::OutletProvider, public ssrc::ChannelMixer::ChannelMixerImpl { 21 | class Outlet : public ssrc::StageOutlet { 22 | ChannelMixerStage &parent; 23 | shibatch::ArrayQueue queue; 24 | public: 25 | Outlet(ChannelMixerStage &parent_) : parent(parent_) {} 26 | 27 | bool atEnd() { 28 | std::unique_lock lock(parent.mtx); 29 | return queue.size() == 0 && parent.allInputAtEnd(); 30 | } 31 | 32 | size_t read(T *ptr, size_t n) { 33 | std::unique_lock lock(parent.mtx); 34 | size_t s = queue.size(); 35 | if (s < n) s += parent.refill(n - s); 36 | if (s > n) s = n; 37 | return queue.read(ptr, s); 38 | } 39 | 40 | friend ChannelMixerStage; 41 | }; 42 | 43 | std::shared_ptr> in; 44 | const std::vector> matrix; 45 | ssrc::WavFormat format; 46 | const unsigned snch, dnch; 47 | std::vector> out; 48 | std::vector> buf; 49 | std::mutex mtx; 50 | 51 | size_t refill(size_t n) { 52 | for(unsigned c=0;cgetOutlet(ic)->read(buf[ic].data(), n); 57 | memset(buf[ic].data() + z, 0, (n - z) * sizeof(T)); 58 | nRead = std::max(nRead, z); 59 | } 60 | 61 | std::vector v(dnch); 62 | for(size_t pos = 0;pos < nRead;pos++) { 63 | for(unsigned oc = 0;oc < dnch;oc++) { 64 | double s = 0; 65 | for(unsigned ic = 0;ic < snch;ic++) s += buf[ic][pos] * matrix[oc][ic]; 66 | v[oc] = s; 67 | } 68 | for(unsigned oc = 0;oc < dnch;oc++) buf[oc][pos] = v[oc]; 69 | } 70 | 71 | for(unsigned oc = 0;oc < dnch;oc++) out[oc]->queue.write(buf[oc].data(), nRead); 72 | 73 | return nRead; 74 | } 75 | 76 | bool allInputAtEnd() { 77 | for(unsigned ic = 0;ic < snch;ic++) if (!in->getOutlet(ic)->atEnd()) return false; 78 | return true; 79 | } 80 | public: 81 | ChannelMixerStage(std::shared_ptr> in_, const std::vector>& matrix_) : 82 | in(in_), matrix(matrix_), format(in_->getFormat()), snch(format.channels), dnch(matrix.size()) { 83 | format.channels = dnch; 84 | buf.resize(std::max(snch, dnch)); 85 | for(unsigned i=0;i(*this)); 86 | } 87 | 88 | std::shared_ptr> getOutlet(uint32_t c) { return out[c]; } 89 | ssrc::WavFormat getFormat() { return format; } 90 | }; 91 | } 92 | #endif // #ifndef MIXER_HPP 93 | -------------------------------------------------------------------------------- /src/libshibatchdsp/Minrceps.hpp: -------------------------------------------------------------------------------- 1 | #ifndef MINRCEPS_HPP 2 | #define MINRCEPS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "ObjectCache.hpp" 10 | 11 | #ifndef M_PI 12 | #define M_PI 3.1415926535897932384626433832795028842 13 | #endif 14 | 15 | namespace shibatch { 16 | class Minrceps { 17 | static constexpr const size_t toPow2(size_t n) { 18 | size_t ret = 1; 19 | for(;ret < n && ret != 0;ret *= 2) ; 20 | return ret; 21 | } 22 | 23 | std::vector createWindow(size_t N) { 24 | // 7-term Blackman-Harris 25 | // https://dsp.stackexchange.com/questions/51095/seven-term-blackman-harris-window 26 | static const double coef[] = { 27 | .27105140069342, -0.43329793923448, 0.21812299954311, -0.06592544638803, 28 | 0.01081174209837, -0.00077658482522, 0.00001388721735 29 | }; 30 | 31 | std::vector v(N); 32 | 33 | for(size_t n=0;n dftf, dftb; 42 | double *dftbuf = nullptr; 43 | 44 | public: 45 | Minrceps(unsigned L_) : L(toPow2(L_)) { 46 | dftf = ssrc::constructSleefDFT(SLEEF_MODE_REAL | SLEEF_MODE_ALT | SLEEF_MODE_FORWARD | SLEEF_MODE_NO_MT, L); 47 | dftb = ssrc::constructSleefDFT(SLEEF_MODE_REAL | SLEEF_MODE_ALT | SLEEF_MODE_BACKWARD | SLEEF_MODE_NO_MT, L); 48 | dftbuf = (double *)Sleef_malloc(L * sizeof(double)); 49 | } 50 | 51 | ~Minrceps() { 52 | Sleef_free(dftbuf); 53 | } 54 | 55 | // Smith AD, Ferguson RJ. Minimum-phase signal calculation using the real cepstrum. CREWES Res. Report. 2014;26(72). 56 | 57 | template 58 | std::shared_ptr> execute(std::shared_ptr> in, const double alpha = 1.0 - ldexp(1, -20)) { 59 | REAL *inp = in->data(); 60 | auto window = createWindow(in->size()); 61 | 62 | memset(dftbuf, 0, L * sizeof(double)); 63 | 64 | double a = 1.0, ein = 0; 65 | for(unsigned i=0;isize();i++) { dftbuf[i] = inp[i] * a; ein += inp[i]; a *= alpha; } 66 | 67 | SleefDFT_execute(dftf.get(), dftbuf, dftbuf); 68 | 69 | for(unsigned i=0;i>(in->size()); 79 | REAL *outp = out->data(); 80 | 81 | outp[0] = exp(dftbuf[0] / 2) * window[0]; 82 | double eout = outp[0] * outp[0]; 83 | a = 1.0 / alpha; 84 | for(unsigned n=1;nsize();n++) { 85 | double sum = 0; 86 | for(unsigned k=1;k<=n;k++) sum += k * (1.0 / n) * dftbuf[k] * outp[n - k]; 87 | outp[n] = sum * a * window[n]; 88 | eout += outp[n]; 89 | a *= (1.0 / alpha); 90 | } 91 | 92 | for(unsigned n=0;nsize();n++) outp[n] *= ein / eout; 93 | 94 | return out; 95 | } 96 | }; 97 | } 98 | #endif // #ifndef MINRCEPS_HPP 99 | -------------------------------------------------------------------------------- /src/libshibatchdsp/WavWriter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef WAVWRITER_HPP 2 | #define WAVWRITER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "shibatch/ssrc.hpp" 10 | #include "dr_wav.hpp" 11 | #include "BGExecutor.hpp" 12 | #include "BlockingQueue.hpp" 13 | 14 | template class ssrc::WavWriter::WavWriterImpl { 15 | public: 16 | virtual ~WavWriterImpl() = default; 17 | }; 18 | 19 | namespace shibatch { 20 | template 21 | class WavWriterStage : public ssrc::WavWriter::WavWriterImpl { 22 | const size_t N; 23 | dr_wav::WavFile wav; 24 | const std::vector>> in; 25 | const bool mt; 26 | std::shared_ptr bgExecutor; 27 | BlockingQueue> queue; 28 | std::shared_ptr th; 29 | 30 | void thEntry() { 31 | const unsigned nch = wav.getNChannels(); 32 | 33 | for(;;) { 34 | auto v = queue.pop(); 35 | if (v.size() == 0) break; 36 | wav.writePCM(v.data(), v.size() / nch); 37 | } 38 | } 39 | 40 | public: 41 | WavWriterStage(const std::string &filename, const dr_wav::drwav_fmt &fmt, const dr_wav::Container& container, 42 | const std::vector>> &in_, uint64_t nFrames = 0, size_t bufsize = 65536, bool mt_ = true) : 43 | N(bufsize), wav(filename.c_str(), fmt, container, nFrames), in(in_), mt(mt_) { 44 | if (fmt.channels != in.size()) throw(std::runtime_error("WavWriterStage::WavWriterStage fmt.channels != in.size()")); 45 | if (mt) bgExecutor = std::make_shared(); 46 | } 47 | 48 | ~WavWriterStage() { 49 | if (th) { 50 | queue.push(std::vector(0)); 51 | th->join(); 52 | th = nullptr; 53 | } 54 | } 55 | 56 | void execute() { 57 | const unsigned nch = wav.getNChannels(); 58 | 59 | if (!mt) { 60 | std::vector cbuf(N), fbuf(N * nch); 61 | for(;;) { 62 | size_t zmax = 0; 63 | for(unsigned c=0;cread(cbuf.data(), N); 65 | zmax = std::max(z, zmax); 66 | for(size_t i=0;i(&WavWriterStage::thEntry, this); 74 | std::vector fbuf(N * nch); 75 | std::vector> vz(2); 76 | 77 | std::vector>> cbuf(2); 78 | for(unsigned p=0;p<2;p++) { 79 | vz[p].resize(nch); 80 | cbuf[p].resize(nch); 81 | for(unsigned c=0;cpush(Runnable::factory([&, c](void *ptr) { 86 | vz[0][c] = in[c]->read((T*)ptr, N); 87 | }, cbuf[0][c].data())); 88 | } 89 | 90 | for(unsigned p=0;;p ^= 1) { 91 | for(unsigned c=0;cpop(); 92 | 93 | for(unsigned c=0;cpush(Runnable::factory([&, p, c](void *ptr) { 95 | vz[p ^ 1][c] = in[c]->read((T*)ptr, N); 96 | }, cbuf[p ^ 1][c].data())); 97 | } 98 | 99 | size_t zmax = 0; 100 | for(unsigned c=0;c v(zmax * nch); 108 | memcpy(v.data(), fbuf.data(), zmax * nch * sizeof(T)); 109 | queue.push(std::move(v)); 110 | } 111 | 112 | for(unsigned c=0;cpop(); 113 | } 114 | } 115 | }; 116 | } 117 | #endif // #ifndef WAVWRITER_HPP 118 | -------------------------------------------------------------------------------- /src/tester/test_oneshot.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define DR_WAV_IMPLEMENTATION 6 | 7 | #ifdef USE_SOXR 8 | #include 9 | #include "dr_wav.h" 10 | #else 11 | #define SSRC_LIBSOXR_EMULATION 12 | #include "shibatch/ssrcsoxr.h" 13 | #include "xdr_wav.h" 14 | #endif 15 | 16 | void print_usage(char *argv0) { 17 | fprintf(stderr, "Usage: %s \n", argv0); 18 | } 19 | 20 | int main(int argc, char *argv[]) { 21 | if (argc < 4) { 22 | print_usage(argv[0]); 23 | return 1; 24 | } 25 | 26 | const char* in_filename = argv[1]; 27 | const char* out_filename = argv[2]; 28 | unsigned int out_rate = atoi(argv[3]); 29 | 30 | if (out_rate == 0) { 31 | fprintf(stderr, "Error: Invalid output sample rate.\n"); 32 | return 1; 33 | } 34 | 35 | unsigned int channels; 36 | unsigned int in_rate; 37 | drwav_uint64 total_frame_count; 38 | float* p_input_samples = drwav_open_file_and_read_pcm_frames_f32(in_filename, &channels, &in_rate, &total_frame_count, NULL); 39 | 40 | if (p_input_samples == NULL) { 41 | fprintf(stderr, "Error: Failed to open and read WAV file: %s\n", in_filename); 42 | return 1; 43 | } 44 | 45 | fprintf(stderr, "Input file: %s\n", in_filename); 46 | fprintf(stderr, " - Channels: %u\n", channels); 47 | fprintf(stderr, " - Sample Rate: %u Hz\n", in_rate); 48 | fprintf(stderr, " - Total Frames: %llu\n", total_frame_count); 49 | 50 | if (in_rate == out_rate) { 51 | fprintf(stderr, "Input and output sample rates are the same. No conversion needed.\n"); 52 | } 53 | 54 | drwav_uint64 output_frame_count = (drwav_uint64)((double)total_frame_count * (double)out_rate / (double)in_rate + 0.5) * 2; 55 | float* p_output_samples = (float*)malloc(sizeof(float) * output_frame_count * channels); 56 | if (p_output_samples == NULL) { 57 | fprintf(stderr, "Error: Failed to allocate memory for output buffer.\n"); 58 | drwav_free(p_input_samples, NULL); 59 | return 1; 60 | } 61 | 62 | size_t odone; 63 | soxr_error_t error; 64 | 65 | soxr_io_spec_t io_spec = soxr_io_spec(SOXR_FLOAT32_I, SOXR_FLOAT32_I); 66 | soxr_quality_spec_t q_spec = soxr_quality_spec(SOXR_MQ, 0); 67 | 68 | fprintf(stderr, "\nStarting resampling...\n"); 69 | fprintf(stderr, " - From: %u Hz\n", in_rate); 70 | fprintf(stderr, " - To: %u Hz\n", out_rate); 71 | 72 | error = soxr_oneshot(in_rate, out_rate, channels, 73 | p_input_samples, total_frame_count, NULL, 74 | p_output_samples, output_frame_count, &odone, 75 | &io_spec, &q_spec, NULL); 76 | 77 | drwav_free(p_input_samples, NULL); 78 | 79 | if (error) { 80 | fprintf(stderr, "Error: soxr_oneshot failed: %s\n", soxr_strerror(error)); 81 | free(p_output_samples); 82 | return 1; 83 | } 84 | 85 | fprintf(stderr, "Resampling complete. Output frames: %zu\n", odone); 86 | 87 | drwav wav_out; 88 | drwav_data_format format; 89 | format.container = drwav_container_riff; 90 | format.format = DR_WAVE_FORMAT_IEEE_FLOAT; 91 | format.channels = channels; 92 | format.sampleRate = out_rate; 93 | format.bitsPerSample = 32; 94 | 95 | if (!drwav_init_file_write(&wav_out, out_filename, &format, NULL)) { 96 | fprintf(stderr, "Error: Failed to initialize output WAV file: %s\n", out_filename); 97 | free(p_output_samples); 98 | return 1; 99 | } 100 | 101 | drwav_uint64 frames_written = drwav_write_pcm_frames(&wav_out, odone, p_output_samples); 102 | drwav_uninit(&wav_out); 103 | free(p_output_samples); 104 | 105 | if (frames_written != odone) { 106 | fprintf(stderr, "Error: Failed to write all frames to output file.\n"); 107 | return 1; 108 | } 109 | 110 | fprintf(stderr, "\nSuccessfully created resampled file: %s\n", out_filename); 111 | 112 | return 0; 113 | } 114 | -------------------------------------------------------------------------------- /src/libshibatchdsp/WavReader.hpp: -------------------------------------------------------------------------------- 1 | #ifndef WAVREADER_HPP 2 | #define WAVREADER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "shibatch/ssrc.hpp" 12 | #include "ArrayQueue.hpp" 13 | #include "dr_wav.hpp" 14 | 15 | template class ssrc::WavReader::WavReaderImpl { 16 | public: 17 | virtual ~WavReaderImpl() = default; 18 | }; 19 | 20 | namespace shibatch { 21 | template 22 | class WavReaderStage : public ssrc::WavReader::WavReaderImpl { 23 | class WavOutlet : public ssrc::StageOutlet { 24 | WavReaderStage &reader; 25 | const uint32_t ch; 26 | ArrayQueue queue; 27 | 28 | public: 29 | WavOutlet(WavReaderStage &reader_, int ch_) : reader(reader_), ch(ch_) {} 30 | ~WavOutlet() {} 31 | bool atEnd() { 32 | std::unique_lock lock(reader.mtx); 33 | return queue.size() == 0 && reader.atEnd(); 34 | } 35 | 36 | size_t read(T *ptr, size_t n) { 37 | std::unique_lock lock(reader.mtx); 38 | 39 | size_t s = queue.size(); 40 | 41 | if (s < n) s += reader.refill(n - s); 42 | 43 | if (s > n) s = n; 44 | 45 | return queue.read(ptr, s); 46 | } 47 | 48 | friend WavReaderStage; 49 | }; 50 | 51 | static const size_t N = 1024 * 1024; 52 | 53 | std::mutex mtx; 54 | dr_wav::WavFile wav; 55 | const bool mt; 56 | std::vector>> outlet; 57 | std::vector buf; 58 | std::shared_ptr th; 59 | BlockingArrayQueue baq; 60 | 61 | size_t refill(size_t n) { 62 | buf.resize(std::max(n * getNChannels(), buf.size())); 63 | size_t z; 64 | if (!mt) { 65 | z = wav.readPCM(buf.data(), n); 66 | } else { 67 | z = baq.read(buf.data(), n * getNChannels()) / getNChannels(); 68 | } 69 | unsigned nc = getNChannels(); 70 | 71 | for(unsigned c=0;c(outlet[c]); 73 | 74 | std::vector v(z); 75 | for(size_t i=0;iqueue.write(std::move(v)); 77 | } 78 | 79 | return z; 80 | } 81 | 82 | void thEntry() { 83 | for(;;) { 84 | std::vector buf(N * getNChannels()); 85 | size_t z = wav.readPCM(buf.data(), N); 86 | if (z == 0) { 87 | baq.close(); 88 | break; 89 | } 90 | buf.resize(z * getNChannels()); 91 | baq.write(std::move(buf)); 92 | } 93 | } 94 | 95 | public: 96 | WavReaderStage(const std::string &filename, bool mt_) : wav(filename.c_str()), mt(mt_), baq(N * wav.getNChannels()) { 97 | outlet.resize(getNChannels()); 98 | for(unsigned ch=0;ch(*this, ch); 100 | if (mt) th = std::make_shared(&WavReaderStage::thEntry, this); 101 | } 102 | 103 | WavReaderStage(bool mt_) : wav(), mt(mt_), baq(N * wav.getNChannels()) { 104 | outlet.resize(getNChannels()); 105 | for(unsigned ch=0;ch(*this, ch); 107 | if (mt) th = std::make_shared(&WavReaderStage::thEntry, this); 108 | } 109 | 110 | dr_wav::drwav getWav() const { return wav.getWav(); } 111 | dr_wav::drwav_fmt getFmt() const { return wav.getFmt(); } 112 | dr_wav::drwav_container getContainer() const { return wav.getContainer(); } 113 | uint32_t getSampleRate() const { return wav.getSampleRate(); } 114 | uint16_t getNBitsPerSample() const { return wav.getNBitsPerSample(); } 115 | uint32_t getNChannels() const { return wav.getNChannels(); } 116 | uint32_t getNFrames() { return wav.getNFrames(); } 117 | bool isFloat() { return wav.isFloat(); } 118 | 119 | size_t getPosition() { return wav.getNFrames(); } 120 | bool atEnd() { return wav.atEnd(); } 121 | 122 | std::shared_ptr> getOutlet(uint32_t channel) { 123 | if (channel >= outlet.size()) throw(std::runtime_error("WavReaderStage::getOutlet channel too large")); 124 | return outlet[channel]; 125 | } 126 | 127 | ~WavReaderStage() { 128 | baq.close(); 129 | if (th) th->join(); 130 | } 131 | }; 132 | } 133 | #endif // #ifndef WAVREADER_HPP 134 | -------------------------------------------------------------------------------- /src/libshibatchdsp/DFTFilter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DFTFILTER_HPP 2 | #define DFTFILTER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "ObjectCache.hpp" 9 | 10 | #include "shibatch/ssrc.hpp" 11 | 12 | #ifndef _MSC_VER 13 | #define RESTRICT __restrict__ 14 | #else 15 | #define RESTRICT 16 | #endif 17 | 18 | namespace shibatch { 19 | template 20 | class DFTFilter : public ssrc::StageOutlet { 21 | static constexpr const size_t toPow2(size_t n) { 22 | size_t ret = 1; 23 | for(;ret < n && ret != 0;ret *= 2) ; 24 | return ret; 25 | } 26 | 27 | std::shared_ptr> in; 28 | const size_t firlen, dftleno2, dftlen; 29 | 30 | size_t nDispose; 31 | std::shared_ptr dftf, dftb; 32 | REAL *RESTRICT dftfilter = nullptr, *RESTRICT dftbuf = nullptr; 33 | 34 | std::vector overlapbuf, fractionBuf; 35 | size_t fractionLen = 0, nZeroPadding = 0; 36 | bool endReached = false; 37 | 38 | public: 39 | DFTFilter(std::shared_ptr> in_, const REAL *fircoef_, size_t firlen_) : 40 | in(in_), firlen(firlen_), dftleno2(toPow2(firlen_)), dftlen(dftleno2 * 2) { 41 | 42 | dftf = ssrc::constructSleefDFT(SLEEF_MODE_REAL | SLEEF_MODE_ALT | SLEEF_MODE_FORWARD | SLEEF_MODE_NO_MT, dftlen); 43 | dftb = ssrc::constructSleefDFT(SLEEF_MODE_REAL | SLEEF_MODE_ALT | SLEEF_MODE_BACKWARD | SLEEF_MODE_NO_MT, dftlen); 44 | 45 | dftfilter = (REAL *)Sleef_malloc(dftlen * sizeof(REAL)); 46 | dftbuf = (REAL *)Sleef_malloc(dftlen * sizeof(REAL)); 47 | 48 | memset(dftfilter, 0, dftlen * sizeof(REAL)); 49 | for(size_t z=0;z 0 || !endReached; } 63 | 64 | size_t read(REAL *RESTRICT out, size_t nSamples) { 65 | size_t ret = 0; 66 | 67 | if (fractionLen > 0) { 68 | size_t nOut = std::min(fractionLen, nSamples); 69 | memcpy(out, fractionBuf.data(), nOut * sizeof(REAL)); 70 | memmove(fractionBuf.data(), fractionBuf.data() + nOut, (fractionLen - nOut) * sizeof(REAL)); 71 | fractionLen -= nOut; 72 | nSamples -= nOut; 73 | out += nOut; 74 | ret += nOut; 75 | } 76 | 77 | while(nSamples > 0 && (!endReached || nZeroPadding != 0)) { 78 | size_t nRead = 0; 79 | 80 | while(nRead < dftleno2) { 81 | if (!endReached) { 82 | size_t r = in->read(dftbuf + nRead, dftleno2 - nRead); 83 | if (r == 0) { 84 | endReached = true; 85 | nZeroPadding = firlen; 86 | } 87 | nRead += r; 88 | } else { 89 | size_t r = std::min(dftleno2 - nRead, nZeroPadding); 90 | memset(dftbuf + nRead, 0, r * sizeof(REAL)); 91 | nRead += r; 92 | nZeroPadding -= r; 93 | if (nZeroPadding == 0) break; 94 | } 95 | } 96 | 97 | memset(dftbuf + nRead, 0, (dftlen - nRead) * sizeof(REAL)); 98 | 99 | // 100 | 101 | SleefDFT_execute(dftf.get(), dftbuf, dftbuf); 102 | 103 | dftbuf[0] = dftfilter[0] * dftbuf[0]; 104 | dftbuf[1] = dftfilter[1] * dftbuf[1]; 105 | 106 | for(unsigned i=1;i 0) break; 134 | } 135 | 136 | if (nSamples > 0) { 137 | size_t nOut = std::min(fractionLen, nSamples); 138 | memcpy(out, fractionBuf.data(), nOut * sizeof(REAL)); 139 | memmove(fractionBuf.data(), fractionBuf.data() + nOut, (fractionLen - nOut) * sizeof(REAL)); 140 | fractionLen -= nOut; 141 | nSamples -= nOut; 142 | out += nOut; 143 | ret += nOut; 144 | } 145 | 146 | return ret; 147 | } 148 | }; 149 | } 150 | #endif // #ifndef DFTFILTER_HPP 151 | -------------------------------------------------------------------------------- /src/libshibatchdsp/RNG.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RNG_HPP 2 | #define RNG_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace shibatch { 9 | class RNG { 10 | uint64_t res0 = 0, res1 = 0; 11 | uint32_t nBitsInRes = 0; 12 | public: 13 | virtual uint64_t next64() = 0; 14 | virtual uint32_t next32() { return (uint32_t)next(32); } 15 | 16 | uint64_t next(uint32_t bits) { 17 | if (bits == 64) return next64(); 18 | if (bits > nBitsInRes) { 19 | uint64_t u = next64(); 20 | res0 |= u << nBitsInRes; 21 | res1 = nBitsInRes == 0 ? 0 : (u >> (64 - nBitsInRes)); 22 | nBitsInRes += 64; 23 | } 24 | uint64_t ret = res0 & ((uint64_t(1) << bits) - 1); 25 | res0 >>= bits; 26 | res0 |= res1 << (64 - bits); 27 | res1 >>= bits; 28 | nBitsInRes -= bits; 29 | return ret; 30 | } 31 | 32 | void nextBytes(unsigned char *dst, size_t len) { 33 | while(len >= 8) { 34 | uint64_t u = next64(); 35 | for(int i=0;i<8;i++) dst[i] = (u >> (i * 8)) & 0xff; 36 | dst += 8; 37 | len -= 8; 38 | } 39 | uint64_t u = next(uint32_t(len * 8)); 40 | for(int i=0;i<(int)len;i++) dst[i] = (u >> (i * 8)) & 0xff; 41 | } 42 | 43 | #if !defined(__BYTE_ORDER__) || (__BYTE_ORDER__ != __ORDER_BIG_ENDIAN__) 44 | void nextBytesW(unsigned char *dst, size_t len) { nextBytes(dst, len); } 45 | #else 46 | void nextBytesW(unsigned char * const dst, const size_t len_) { 47 | int len = len_, index = 0; 48 | while(len >= 8) { 49 | uint64_t u = next64(); 50 | for(int i=0;i<8;i++) dst[len_-1-i-index] = (u >> (i * 8)) & 0xff; 51 | index += 8; 52 | len -= 8; 53 | } 54 | uint64_t u = next(uint32_t(len * 8)); 55 | for(int i=0;i<(int)len;i++) dst[len_-1-i-index] = (u >> (i * 8)) & 0xff; 56 | } 57 | #endif 58 | 59 | unsigned clz64(uint64_t u) { 60 | unsigned z = 0; 61 | if (u & 0xffffffff00000000ULL) u >>= 32; else z += 32; 62 | if (u & 0x00000000ffff0000ULL) u >>= 16; else z += 16; 63 | if (u & 0x000000000000ff00ULL) u >>= 8; else z += 8; 64 | if (u & 0x00000000000000f0ULL) u >>= 4; else z += 4; 65 | if (u & 0x000000000000000cULL) u >>= 2; else z += 2; 66 | if (u & 0x0000000000000002ULL) u >>= 1; else z += 1; 67 | if (!u) z++; 68 | return z; 69 | } 70 | 71 | uint64_t nextLT(uint64_t bound) { 72 | if (bound == 0) return 0; 73 | 74 | unsigned b = sizeof(uint64_t)*8 - clz64(bound - 1); 75 | uint64_t r = next(b), u = uint64_t(1) << b; 76 | 77 | while(r >= bound) { 78 | r -= bound; 79 | u -= bound; 80 | while(u < bound) { 81 | r = (r << 1) | next(1); 82 | u *= 2; 83 | } 84 | } 85 | return r; 86 | } 87 | 88 | double nextDouble_1_2() { 89 | uint64_t u; 90 | u = next(52); 91 | u |= 0x3ff0000000000000ULL; 92 | double d; 93 | memcpy(&d, &u, sizeof(double)); 94 | return d; 95 | } 96 | 97 | double nextDouble_0_1() { return nextDouble_1_2() - 1.0; } 98 | 99 | double nextRectangularDouble(double min, double max) { 100 | return min + nextDouble_0_1() * (max-min); 101 | } 102 | 103 | double nextTriangularDouble(double peak) { 104 | return (nextDouble_0_1() - nextDouble_0_1()) * peak; 105 | } 106 | 107 | bool nextBool() { return next(1); } 108 | 109 | double nextTwoLevelDouble(double peak) { 110 | return nextBool() ? -peak : peak; 111 | } 112 | 113 | virtual ~RNG() {} 114 | }; 115 | 116 | class LCG64 : public RNG { 117 | uint64_t state = 1; 118 | public: 119 | LCG64(uint64_t seed) { 120 | state = seed; 121 | for(int i=0;i<10;i++) next32(); 122 | } 123 | 124 | LCG64() { 125 | state = std::chrono::high_resolution_clock::now().time_since_epoch().count(); 126 | for(int i=0;i<10;i++) next32(); 127 | } 128 | 129 | uint32_t next32() { 130 | state = state * 6364136223846793005ULL + 1442695040888963407ULL; 131 | return uint32_t(state >> 32); 132 | } 133 | uint64_t next64() { 134 | uint32_t u = next32(); 135 | return u | (uint64_t(next32()) << 32); 136 | } 137 | }; 138 | 139 | class TLCG64 : public RNG { 140 | uint64_t state = 1; 141 | public: 142 | TLCG64() { 143 | state = std::chrono::high_resolution_clock::now().time_since_epoch().count(); 144 | for(int i=0;i<10;i++) next32(); 145 | } 146 | 147 | uint32_t next32() { 148 | uint64_t t = std::chrono::high_resolution_clock::now().time_since_epoch().count(); 149 | state = state * 6364136223846793005ULL + (t << 1) + 1; 150 | return uint32_t(state >> 32); 151 | } 152 | uint64_t next64() { 153 | uint32_t u = next32(); 154 | return u | (uint64_t(next32()) << 32); 155 | } 156 | }; 157 | } 158 | #endif // #ifndef RNG_HPP 159 | -------------------------------------------------------------------------------- /src/libshibatchdsp/Kaiser.hpp: -------------------------------------------------------------------------------- 1 | #ifndef KAISER_HPP 2 | #define KAISER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #ifndef M_PI 9 | #define M_PI 3.1415926535897932384626433832795028842 10 | #endif 11 | 12 | namespace shibatch { 13 | class KaiserWindow { 14 | static std::vector factVector(unsigned n) { 15 | if (n == 0) { 16 | return std::vector{ 1 }; 17 | } else { 18 | std::vector v = factVector(n-1); 19 | v.push_back(tgamma(n+1)); 20 | return v; 21 | } 22 | } 23 | 24 | public: 25 | static double sinc(double x) { return x == 0 ? 1 : (sin(x) / x); } 26 | 27 | /** 28 | * @param aa : stop band attenuation (dB) 29 | */ 30 | static double alpha(double aa) { 31 | if (aa <= 21) return 0; 32 | if (aa <= 50) return 0.5842 * pow(aa - 21, 0.4) + 0.07886 * (aa - 21); 33 | return 0.1102 * (aa - 8.7); 34 | } 35 | 36 | static double izero(double x, int M = 30) { 37 | static std::vector factorial = factVector(180); 38 | 39 | double ret = 1; 40 | for(int m = M;m >= 1;m--) { 41 | double t = pow(x/2, m) / factorial[m]; 42 | ret += t * t; 43 | } 44 | return ret; 45 | } 46 | 47 | /** 48 | * @param aa : stop band attenuation (dB) 49 | * @param fs : sampleing frequency (Hz) 50 | * @param df : transition band width (Hz) 51 | */ 52 | static int length(double aa, double fs, double df) { 53 | double d = aa <= 21 ? 0.9222 : (aa-7.95)/14.36; 54 | int len = fs * d / df + 1; 55 | if ((len & 1) == 0) len++; 56 | return len; 57 | } 58 | 59 | static double transitionBandWidth(double aa, double fs, int length) { 60 | double d = aa <= 21 ? 0.9222 : (aa-7.95)/14.36; 61 | return (fs * d) / (length - 1); 62 | } 63 | 64 | static double window(int n, int len, double alp, double iza) { 65 | if (n > len - 1) return 0; 66 | return izero(alp * sqrt(1 - 4.0*n*n/((len-1.0)*(len-1.0)))) / iza; 67 | } 68 | 69 | /** 70 | * @param fp : pass-band edge frequency (Hz) 71 | * @param fs : sampleing frequency (Hz) 72 | */ 73 | static double hn_lpf(int n, double fp, double fs) { 74 | double t = 1.0 / fs; 75 | double omega = 2 * M_PI * fp; 76 | return 2 * fp * t * sinc(n * omega * t); 77 | } 78 | 79 | /** 80 | * @param fs : sampleing frequency (Hz) 81 | * @param fp : pass-band edge frequency (Hz) 82 | * @param df : transition band width (Hz) 83 | * @param aa : stop band attenuation (dB) 84 | */ 85 | template 86 | static std::shared_ptr> makeLPF(double fs, double fp, double df, double aa, double gain = 1) { 87 | double alp = alpha(aa), iza = izero(alp); 88 | int64_t len = length(aa, fs, df); 89 | auto filter = std::make_shared>(len); 90 | REAL *filterData = filter->data(); 91 | for(int i=0;i 102 | static std::shared_ptr> makeLPF(double fs, double fp, int64_t len, double aa, double gain = 1) { 103 | double alp = alpha(aa), iza = izero(alp); 104 | if ((len & 1) == 0) len++; 105 | auto filter = std::make_shared>(len); 106 | REAL *filterData = filter->data(); 107 | for(int i=0;i<=len/2;i++) 108 | filterData[len/2 + i] = filterData[len/2 - i] = window(i, len, alp, iza) * hn_lpf(i, fp, fs) * gain; 109 | return filter; 110 | } 111 | 112 | static double hn_bpf(int n, double fp0, double g0, double fp1, double g1, double fs, int K) { 113 | double sum = 0; 114 | for(int k=0;k 124 | static std::vector makeBPF(double fs, double fp0, double g0, double fp1, double g1, int64_t len, double aa, int K, double gain = 1) { 125 | double alp = alpha(aa), iza = izero(alp); 126 | if ((len & 1) == 0) len++; 127 | std::vector filter(len); 128 | for(int i=0;i<=len/2;i++) 129 | filter[len/2 + i] = filter[len/2 - i] = window(i, len, alp, iza) * hn_bpf(i, fp0, g0, fp1, g1, fs, K) * gain; 130 | return filter; 131 | } 132 | }; 133 | } 134 | #endif // #ifndef KAISER_HPP 135 | -------------------------------------------------------------------------------- /src/tester/test_soxrapi.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define DR_WAV_IMPLEMENTATION 6 | 7 | #ifdef USE_SOXR 8 | #include 9 | #include "dr_wav.h" 10 | #else 11 | #define SSRC_LIBSOXR_EMULATION 12 | #include "shibatch/ssrcsoxr.h" 13 | #include "xdr_wav.h" 14 | #endif 15 | 16 | #define BUFFER_FRAMES 3000 17 | 18 | void print_usage(const char *prog_name) { 19 | printf("Usage: %s [input2.wav] ...\n", prog_name); 20 | printf(" Concatenates and resamples multiple WAV files into a single output file.\n"); 21 | } 22 | 23 | int main(int argc, char *argv[]) { 24 | if (argc < 4) { 25 | print_usage(argv[0]); 26 | return 1; 27 | } 28 | 29 | double out_rate = atof(argv[1]); 30 | const char* out_filename = argv[2]; 31 | int minPhase = out_rate < 0; 32 | 33 | if (out_rate <= 0) out_rate = -out_rate; 34 | 35 | drwav first_wav_in; 36 | if (!drwav_init_file(&first_wav_in, argv[3], NULL)) { 37 | fprintf(stderr, "Failed to open initial input file: %s\n", argv[3]); 38 | return 1; 39 | } 40 | double current_in_rate = (double)first_wav_in.sampleRate; 41 | unsigned int const num_channels = first_wav_in.channels; 42 | drwav_uninit(&first_wav_in); 43 | 44 | fprintf(stderr, "Output format will be: %.0f Hz, %u channels\n", out_rate, num_channels); 45 | fprintf(stderr, "----------------------------------------\n"); 46 | 47 | // 48 | 49 | soxr_error_t error; 50 | soxr_io_spec_t io_spec = soxr_io_spec(SOXR_FLOAT32_I, SOXR_FLOAT32_I); 51 | soxr_quality_spec_t q_spec = soxr_quality_spec(SOXR_MQ, minPhase ? SOXR_MINIMUM_PHASE : 0); 52 | soxr_t soxr = soxr_create(current_in_rate, out_rate, num_channels, &error, &io_spec, &q_spec, NULL); 53 | if (!soxr) { 54 | fprintf(stderr, "soxr_create failed: %s\n", soxr_strerror(error)); 55 | return 1; 56 | } 57 | 58 | drwav_data_format format; 59 | format.container = drwav_container_riff; 60 | format.format = DR_WAVE_FORMAT_IEEE_FLOAT; 61 | format.channels = num_channels; 62 | format.sampleRate = (drwav_uint32)out_rate; 63 | format.bitsPerSample = 32; 64 | 65 | drwav wav_out; 66 | if (!drwav_init_file_write(&wav_out, out_filename, &format, NULL)) { 67 | fprintf(stderr, "Failed to open output file: %s\n", out_filename); 68 | soxr_delete(soxr); 69 | return 1; 70 | } 71 | 72 | float* in_buffer = (float*)malloc(sizeof(float) * BUFFER_FRAMES * num_channels); 73 | size_t out_buffer_capacity = (size_t)(BUFFER_FRAMES * (out_rate / 8000.0 > 1.0 ? out_rate / 8000.0 : 1.0) + 0.5) * 2; 74 | float* out_buffer = (float*)malloc(sizeof(float) * out_buffer_capacity * num_channels); 75 | 76 | for (int i = 3; i < argc; ++i) { 77 | const char* in_filename = argv[i]; 78 | fprintf(stderr, "Processing: %s\n", in_filename); 79 | 80 | drwav wav_in; 81 | if (!drwav_init_file(&wav_in, in_filename, NULL)) { 82 | fprintf(stderr, " -> Failed to open. Skipping.\n"); 83 | continue; 84 | } 85 | 86 | if (wav_in.channels != num_channels) { 87 | fprintf(stderr, " -> Channel count mismatch (%u channels, expected %u). Skipping.\n", wav_in.channels, num_channels); 88 | drwav_uninit(&wav_in); 89 | continue; 90 | } 91 | 92 | if ((double)wav_in.sampleRate != current_in_rate) { 93 | current_in_rate = (double)wav_in.sampleRate; 94 | fprintf(stderr, " -> Sample rate is %.0f Hz. Recreating resampler.\n", current_in_rate); 95 | soxr_delete(soxr); 96 | soxr = soxr_create(current_in_rate, out_rate, num_channels, &error, &io_spec, &q_spec, NULL); 97 | if (!soxr) { 98 | fprintf(stderr, "soxr_create failed during recreation. Aborting.\n"); 99 | drwav_uninit(&wav_in); 100 | break; 101 | } 102 | } else if (i > 3) { 103 | fprintf(stderr, " -> Same sample rate. Clearing resampler state.\n"); 104 | soxr_clear(soxr); 105 | } 106 | 107 | size_t frames_read; 108 | while ((frames_read = drwav_read_pcm_frames_f32(&wav_in, BUFFER_FRAMES, in_buffer)) > 0) { 109 | size_t frames_produced; 110 | error = soxr_process(soxr, in_buffer, frames_read, NULL, out_buffer, out_buffer_capacity, &frames_produced); 111 | if (error) { 112 | fprintf(stderr, "soxr_process error: %s\n", soxr_strerror(error)); 113 | exit(-1); 114 | } 115 | if (frames_produced > 0) { 116 | drwav_write_pcm_frames(&wav_out, frames_produced, out_buffer); 117 | } 118 | } 119 | 120 | size_t frames_produced; 121 | do { 122 | error = soxr_process(soxr, NULL, 0, NULL, out_buffer, out_buffer_capacity, &frames_produced); 123 | if (error) fprintf(stderr, "soxr_process (flush) error: %s\n", soxr_strerror(error)); 124 | if (frames_produced > 0) { 125 | drwav_write_pcm_frames(&wav_out, frames_produced, out_buffer); 126 | } 127 | } while (frames_produced > 0); 128 | 129 | drwav_uninit(&wav_in); 130 | } 131 | 132 | free(in_buffer); 133 | free(out_buffer); 134 | soxr_delete(soxr); 135 | drwav_uninit(&wav_out); 136 | 137 | fprintf(stderr, "----------------------------------------\n"); 138 | fprintf(stderr, "Successfully created %s\n", out_filename); 139 | return 0; 140 | } 141 | -------------------------------------------------------------------------------- /src/include/shibatch/ssrcsoxr.h: -------------------------------------------------------------------------------- 1 | // Soxr-ish C API for Shibatch Sample Rate Converter 2 | 3 | #ifndef SSRC_SOXR_H_INCLUDED 4 | #define SSRC_SOXR_H_INCLUDED 5 | 6 | #include 7 | #include 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | typedef char const * ssrc_soxr_error_t; 14 | 15 | typedef enum { 16 | SSRC_SOXR_FLOAT32, SSRC_SOXR_FLOAT32_I = SSRC_SOXR_FLOAT32, 17 | SSRC_SOXR_FLOAT64, SSRC_SOXR_FLOAT64_I = SSRC_SOXR_FLOAT64, 18 | } ssrc_soxr_datatype_t; 19 | 20 | // io_spec 21 | 22 | typedef struct ssrc_soxr_io_spec ssrc_soxr_io_spec_t; 23 | 24 | struct ssrc_soxr_io_spec { 25 | ssrc_soxr_datatype_t itype, otype; 26 | unsigned ditherType; 27 | unsigned long flags; 28 | }; 29 | 30 | ssrc_soxr_io_spec_t ssrc_soxr_io_spec( 31 | ssrc_soxr_datatype_t itype, 32 | ssrc_soxr_datatype_t otype); 33 | 34 | #define SSRC_SOXR_TPDF 0 35 | #define SSRC_SOXR_NO_DITHER 8 36 | 37 | // quality_spec 38 | 39 | typedef struct ssrc_soxr_quality_spec ssrc_soxr_quality_spec_t; 40 | 41 | struct ssrc_soxr_quality_spec { 42 | unsigned log2dftfilterlen; 43 | double aa, guard; 44 | ssrc_soxr_datatype_t dataType; 45 | unsigned long flags; 46 | }; 47 | 48 | ssrc_soxr_quality_spec_t ssrc_soxr_quality_spec( 49 | unsigned long recipe, 50 | unsigned long flags); 51 | 52 | #define SSRC_SOXR_QQ 0 53 | #define SSRC_SOXR_LQ 1 54 | #define SSRC_SOXR_MQ 2 55 | #define SSRC_SOXR_HQ 4 56 | #define SSRC_SOXR_VHQ 6 57 | 58 | #define SSRC_SOXR_MINIMUM_PHASE 0x30 59 | 60 | // runtime_spec 61 | 62 | typedef struct ssrc_soxr_runtime_spec ssrc_soxr_runtime_spec_t; 63 | 64 | struct ssrc_soxr_runtime_spec { 65 | unsigned num_threads; 66 | }; 67 | 68 | // 69 | 70 | struct ssrc_soxr *ssrc_soxr_create( 71 | double input_rate, double output_rate, unsigned num_channels, 72 | ssrc_soxr_error_t *eptr, ssrc_soxr_io_spec_t const *iospec, 73 | ssrc_soxr_quality_spec_t const *qspec, 74 | ssrc_soxr_runtime_spec_t const *rtspec); 75 | 76 | ssrc_soxr_error_t ssrc_soxr_process( 77 | struct ssrc_soxr *thiz, 78 | void const *in, size_t ilen, size_t *idone, 79 | void *out, size_t olen, size_t *odone); 80 | 81 | ssrc_soxr_error_t ssrc_soxr_clear(struct ssrc_soxr *thiz); 82 | void ssrc_soxr_delete(struct ssrc_soxr *thiz); 83 | 84 | double ssrc_soxr_delay(struct ssrc_soxr *thiz); 85 | 86 | ssrc_soxr_error_t ssrc_soxr_oneshot(double in_rate, double out_rate, unsigned num_channels, 87 | void const *in, size_t in_len, size_t *in_rd, 88 | void *out, size_t out_len, size_t *out_wr, 89 | ssrc_soxr_io_spec_t const *io_spec, 90 | ssrc_soxr_quality_spec_t const *q_spec, 91 | ssrc_soxr_runtime_spec_t const *runtime_spec); 92 | 93 | // 94 | 95 | #ifdef SSRC_LIBSOXR_EMULATION 96 | typedef ssrc_soxr_error_t soxr_error_t; 97 | typedef ssrc_soxr_io_spec_t soxr_io_spec_t; 98 | typedef ssrc_soxr_quality_spec_t soxr_quality_spec_t; 99 | typedef ssrc_soxr_runtime_spec_t soxr_runtime_spec_t; 100 | typedef struct ssrc_soxr * soxr_t; 101 | 102 | typedef enum { 103 | SOXR_FLOAT32 = SSRC_SOXR_FLOAT32, 104 | SOXR_FLOAT32_I = SSRC_SOXR_FLOAT32, 105 | SOXR_FLOAT64 = SSRC_SOXR_FLOAT64, 106 | SOXR_FLOAT64_I = SSRC_SOXR_FLOAT64, 107 | } soxr_datatype_t; 108 | 109 | static inline soxr_io_spec_t soxr_io_spec(soxr_datatype_t itype, soxr_datatype_t otype) { 110 | return ssrc_soxr_io_spec((ssrc_soxr_datatype_t)itype, (ssrc_soxr_datatype_t)otype); 111 | } 112 | 113 | static inline soxr_quality_spec_t soxr_quality_spec(unsigned long recipe, unsigned long flags) { 114 | return ssrc_soxr_quality_spec(recipe, flags); 115 | } 116 | 117 | static inline soxr_t soxr_create( 118 | double input_rate, double output_rate, unsigned num_channels, 119 | ssrc_soxr_error_t *eptr, ssrc_soxr_io_spec_t const *iospec, ssrc_soxr_quality_spec_t const *qspec, 120 | void const *rtspec) { 121 | return ssrc_soxr_create(input_rate, output_rate, num_channels, eptr, iospec, qspec, rtspec); 122 | } 123 | 124 | static inline soxr_error_t soxr_process( 125 | struct ssrc_soxr *thiz, 126 | void const *in, size_t ilen, size_t *idone, 127 | void *out, size_t olen, size_t *odone) { 128 | return ssrc_soxr_process(thiz, in, ilen, idone, out, olen, odone); 129 | } 130 | 131 | static inline soxr_error_t soxr_oneshot(double in_rate, double out_rate, unsigned num_channels, 132 | void const *in, size_t in_len, size_t *in_rd, 133 | void *out, size_t out_len, size_t *out_wr, 134 | soxr_io_spec_t const *io_spec, 135 | soxr_quality_spec_t const *q_spec, 136 | soxr_runtime_spec_t const *runtime_spec) { 137 | return ssrc_soxr_oneshot(in_rate, out_rate, num_channels, 138 | in, in_len, in_rd, out, out_len, out_wr, 139 | io_spec, q_spec, runtime_spec); 140 | } 141 | 142 | static inline soxr_error_t soxr_clear(struct ssrc_soxr *thiz) { return ssrc_soxr_clear(thiz); } 143 | static inline void soxr_delete(struct ssrc_soxr *thiz) { ssrc_soxr_delete(thiz); } 144 | static inline double soxr_delay(struct ssrc_soxr *thiz) { return ssrc_soxr_delay(thiz); } 145 | 146 | #define SOXR_TPDF SSRC_SOXR_TPDF 147 | #define SOXR_NO_DITHER SSRC_SOXR_NO_DITHER 148 | 149 | #define SOXR_QQ SSRC_SOXR_QQ 150 | #define SOXR_LQ SSRC_SOXR_LQ 151 | #define SOXR_MQ SSRC_SOXR_MQ 152 | #define SOXR_HQ SSRC_SOXR_HQ 153 | #define SOXR_VHQ SSRC_SOXR_VHQ 154 | 155 | #define SOXR_MINIMUM_PHASE SSRC_SOXR_MINIMUM_PHASE 156 | 157 | #define soxr_strerror(e) ((e)?(e):"no error") 158 | 159 | #endif 160 | 161 | #ifdef __cplusplus 162 | } // extern "C" { 163 | #endif 164 | 165 | #endif // #ifndef SSRC_SOXR_H_INCLUDED 166 | -------------------------------------------------------------------------------- /src/tester/test_api.cmake: -------------------------------------------------------------------------------- 1 | if (NOT WIN32) 2 | execute_process( 3 | COMMAND "${TARGET_FILE_ssrc}" --rate 48000 --bits 24 --stdout "${TMP_DIR_PATH}/noise.44100.wav" 4 | OUTPUT_FILE "${TMP_DIR_PATH}/noise.ssrc.44100.48000.24.wav" 5 | COMMAND_ERROR_IS_FATAL ANY 6 | COMMAND_ECHO STDOUT 7 | ) 8 | execute_process( 9 | COMMAND "${TARGET_FILE_ssrc}" --rate 44100 --bits 24 --stdin "${TMP_DIR_PATH}/noise.ssrc.48000.44100.24.wav" 10 | INPUT_FILE "${TMP_DIR_PATH}/noise.48000.wav" 11 | COMMAND_ERROR_IS_FATAL ANY 12 | COMMAND_ECHO STDOUT 13 | ) 14 | execute_process( 15 | COMMAND "${TARGET_FILE_ssrc}" --rate 48000 --bits -32 "${TMP_DIR_PATH}/noise.44100.wav" "${TMP_DIR_PATH}/noise.ssrc.44100.48000.-32.wav" 16 | COMMAND_ERROR_IS_FATAL ANY 17 | COMMAND_ECHO STDOUT 18 | ) 19 | execute_process( 20 | COMMAND "${TARGET_FILE_ssrc}" --rate 44100 --bits -32 --stdin --stdout 21 | INPUT_FILE "${TMP_DIR_PATH}/noise.48000.wav" 22 | OUTPUT_FILE "${TMP_DIR_PATH}/noise.ssrc.48000.44100.-32.wav" 23 | COMMAND_ERROR_IS_FATAL ANY 24 | COMMAND_ECHO STDOUT 25 | ) 26 | execute_process( 27 | COMMAND "${TARGET_FILE_ssrc}" --rate 44100 --bits -32 --minPhase --stdin --stdout 28 | INPUT_FILE "${TMP_DIR_PATH}/noise.48000.wav" 29 | OUTPUT_FILE "${TMP_DIR_PATH}/noise.ssrc.48000.44100.-32.minPhase.wav" 30 | COMMAND_ERROR_IS_FATAL ANY 31 | COMMAND_ECHO STDOUT 32 | ) 33 | else() 34 | execute_process( 35 | COMMAND "${TARGET_FILE_ssrc}" --rate 48000 --bits 24 "${TMP_DIR_PATH}/noise.44100.wav" "${TMP_DIR_PATH}/noise.ssrc.44100.48000.24.wav" 36 | COMMAND_ERROR_IS_FATAL ANY 37 | COMMAND_ECHO STDOUT 38 | ) 39 | execute_process( 40 | COMMAND "${TARGET_FILE_ssrc}" --rate 44100 --bits 24 "${TMP_DIR_PATH}/noise.48000.wav" "${TMP_DIR_PATH}/noise.ssrc.48000.44100.24.wav" 41 | COMMAND_ERROR_IS_FATAL ANY 42 | COMMAND_ECHO STDOUT 43 | ) 44 | execute_process( 45 | COMMAND "${TARGET_FILE_ssrc}" --rate 48000 --bits -32 "${TMP_DIR_PATH}/noise.44100.wav" "${TMP_DIR_PATH}/noise.ssrc.44100.48000.-32.wav" 46 | COMMAND_ERROR_IS_FATAL ANY 47 | COMMAND_ECHO STDOUT 48 | ) 49 | execute_process( 50 | COMMAND "${TARGET_FILE_ssrc}" --rate 44100 --bits -32 "${TMP_DIR_PATH}/noise.48000.wav" "${TMP_DIR_PATH}/noise.ssrc.48000.44100.-32.wav" 51 | COMMAND_ERROR_IS_FATAL ANY 52 | COMMAND_ECHO STDOUT 53 | ) 54 | execute_process( 55 | COMMAND "${TARGET_FILE_ssrc}" --rate 44100 --bits -32 --minPhase "${TMP_DIR_PATH}/noise.48000.wav" "${TMP_DIR_PATH}/noise.ssrc.48000.44100.-32.minPhase.wav" 56 | COMMAND_ERROR_IS_FATAL ANY 57 | COMMAND_ECHO STDOUT 58 | ) 59 | endif() 60 | 61 | execute_process( 62 | COMMAND "${TARGET_FILE_test_cppapi}" "${TMP_DIR_PATH}/noise.44100.wav" "${TMP_DIR_PATH}/noise.test_cppapi.44100.48000.24.wav" 48000 63 | COMMAND_ERROR_IS_FATAL ANY 64 | COMMAND_ECHO STDOUT 65 | ) 66 | execute_process( 67 | COMMAND "${TARGET_FILE_test_cppapi}" "${TMP_DIR_PATH}/noise.48000.wav" "${TMP_DIR_PATH}/noise.test_cppapi.48000.44100.24.wav" 44100 68 | COMMAND_ERROR_IS_FATAL ANY 69 | COMMAND_ECHO STDOUT 70 | ) 71 | execute_process( 72 | COMMAND "${TARGET_FILE_test_soxrapi}" 48000 "${TMP_DIR_PATH}/noise.test_soxrapi.44100.48000.-32.wav" "${TMP_DIR_PATH}/noise.44100.wav" 73 | COMMAND_ERROR_IS_FATAL ANY 74 | COMMAND_ECHO STDOUT 75 | ) 76 | execute_process( 77 | COMMAND "${TARGET_FILE_test_soxrapi}" -44100 "${TMP_DIR_PATH}/noise.test_soxrapi.48000.44100.-32.minPhase.wav" "${TMP_DIR_PATH}/noise.48000.wav" 78 | COMMAND_ERROR_IS_FATAL ANY 79 | COMMAND_ECHO STDOUT 80 | ) 81 | execute_process( 82 | COMMAND "${TARGET_FILE_test_oneshot}" "${TMP_DIR_PATH}/noise.44100.wav" "${TMP_DIR_PATH}/noise.test_oneshot.44100.48000.-32.wav" 48000 83 | COMMAND_ERROR_IS_FATAL ANY 84 | COMMAND_ECHO STDOUT 85 | ) 86 | execute_process( 87 | COMMAND "${TARGET_FILE_test_oneshot}" "${TMP_DIR_PATH}/noise.48000.wav" "${TMP_DIR_PATH}/noise.test_oneshot.48000.44100.-32.wav" 44100 88 | COMMAND_ERROR_IS_FATAL ANY 89 | COMMAND_ECHO STDOUT 90 | ) 91 | execute_process( 92 | COMMAND "${TARGET_FILE_test_soxrapi}" 48000 "${TMP_DIR_PATH}/sin10k12k.test_soxrapi.44100.48000.-32.wav" "${TMP_DIR_PATH}/sin10k.44100.wav" "${TMP_DIR_PATH}/sin12k.44100.wav" 93 | COMMAND_ERROR_IS_FATAL ANY 94 | COMMAND_ECHO STDOUT 95 | ) 96 | execute_process( 97 | COMMAND "${TARGET_FILE_cmpwav}" "${TMP_DIR_PATH}/noise.ssrc.44100.48000.24.wav" "${TMP_DIR_PATH}/noise.test_cppapi.44100.48000.24.wav" 0.0001 98 | COMMAND "${TARGET_FILE_cmpwav}" "${TMP_DIR_PATH}/noise.ssrc.48000.44100.24.wav" "${TMP_DIR_PATH}/noise.test_cppapi.48000.44100.24.wav" 0.0001 99 | COMMAND "${TARGET_FILE_cmpwav}" "${TMP_DIR_PATH}/noise.ssrc.44100.48000.-32.wav" "${TMP_DIR_PATH}/noise.test_soxrapi.44100.48000.-32.wav" 0.0001 100 | COMMAND "${TARGET_FILE_cmpwav}" "${TMP_DIR_PATH}/noise.ssrc.48000.44100.-32.minPhase.wav" "${TMP_DIR_PATH}/noise.test_soxrapi.48000.44100.-32.minPhase.wav" 0.0001 101 | COMMAND "${TARGET_FILE_cmpwav}" "${TMP_DIR_PATH}/noise.ssrc.44100.48000.-32.wav" "${TMP_DIR_PATH}/noise.test_oneshot.44100.48000.-32.wav" 0.0001 102 | COMMAND "${TARGET_FILE_cmpwav}" "${TMP_DIR_PATH}/noise.ssrc.48000.44100.-32.wav" "${TMP_DIR_PATH}/noise.test_oneshot.48000.44100.-32.wav" 0.0001 103 | COMMAND "${TARGET_FILE_scsa}" "--check" "${CMAKE_CURRENT_LIST_DIR}/10kHz-100dB.scsa" "${TMP_DIR_PATH}/sin10k12k.test_soxrapi.44100.48000.-32.wav" 100000 300000 10000 104 | COMMAND "${TARGET_FILE_scsa}" "--check" "${CMAKE_CURRENT_LIST_DIR}/12kHz-100dB.scsa" "${TMP_DIR_PATH}/sin10k12k.test_soxrapi.44100.48000.-32.wav" 500000 800000 10000 105 | COMMAND_ERROR_IS_FATAL ANY 106 | COMMAND_ECHO STDOUT 107 | ) 108 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { label 'jenkinsfile' } 3 | 4 | stages { 5 | stage('Preamble') { 6 | parallel { 7 | stage('x86_64 linux clang-18') { 8 | agent { label 'x86_64 && ubuntu24' } 9 | options { skipDefaultCheckout() } 10 | steps { 11 | cleanWs() 12 | checkout scm 13 | sh ''' 14 | echo "x86_64 clang-18 on" `hostname` 15 | export CC=clang-18 16 | export CXX=clang++-18 17 | mkdir build 18 | cd build 19 | cmake -GNinja -DCMAKE_INSTALL_PREFIX=../../install -DENABLE_ASAN=True .. 20 | cmake -E time ninja 21 | export CTEST_OUTPUT_ON_FAILURE=TRUE 22 | ctest -j `nproc` 23 | ''' 24 | } 25 | } 26 | 27 | stage('x86_64 linux gcc-13') { 28 | agent { label 'x86_64 && ubuntu24' } 29 | options { skipDefaultCheckout() } 30 | steps { 31 | cleanWs() 32 | checkout scm 33 | sh ''' 34 | echo "x86_64 gcc-13 on" `hostname` 35 | export CC=gcc-13 36 | export CXX=g++-13 37 | mkdir build 38 | cd build 39 | cmake -GNinja -DCMAKE_INSTALL_PREFIX=../../install -DENABLE_ASAN=True .. 40 | cmake -E time ninja 41 | export CTEST_OUTPUT_ON_FAILURE=TRUE 42 | ctest -j `nproc` 43 | ''' 44 | } 45 | } 46 | 47 | stage('aarch64 linux gcc-14') { 48 | agent { label 'aarch64 && ubuntu24' } 49 | options { skipDefaultCheckout() } 50 | steps { 51 | cleanWs() 52 | checkout scm 53 | sh ''' 54 | echo "aarch64 gcc-14 on" `hostname` 55 | export CC=gcc-14 56 | export CXX=g++-14 57 | mkdir build 58 | cd build 59 | cmake -GNinja -DCMAKE_INSTALL_PREFIX=../../install -DENABLE_ASAN=True .. 60 | cmake -E time oomstaller ninja -j `nproc` 61 | export CTEST_OUTPUT_ON_FAILURE=TRUE 62 | ctest -j `nproc` 63 | ''' 64 | } 65 | } 66 | 67 | stage('aarch64 linux clang-18') { 68 | agent { label 'aarch64 && ubuntu24' } 69 | options { skipDefaultCheckout() } 70 | steps { 71 | cleanWs() 72 | checkout scm 73 | sh ''' 74 | echo "aarch64 clang-18 on" `hostname` 75 | export CC=clang 76 | export CXX=clang++ 77 | mkdir build 78 | cd build 79 | cmake -GNinja -DCMAKE_INSTALL_PREFIX=../../install .. 80 | cmake -E time oomstaller ninja -j `nproc` 81 | export CTEST_OUTPUT_ON_FAILURE=TRUE 82 | ctest -j `nproc` 83 | ''' 84 | } 85 | } 86 | 87 | stage('x86_64 windows clang') { 88 | agent { label 'windows11 && vs2022' } 89 | options { skipDefaultCheckout() } 90 | steps { 91 | cleanWs() 92 | checkout scm 93 | bat """ 94 | call "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat" 95 | if not %ERRORLEVEL% == 0 exit /b %ERRORLEVEL% 96 | call "winbuild-clang.bat" -DCMAKE_BUILD_TYPE=Release 97 | if not %ERRORLEVEL% == 0 exit /b %ERRORLEVEL% 98 | ctest -j 4 --output-on-failure 99 | exit /b %ERRORLEVEL% 100 | """ 101 | } 102 | } 103 | 104 | stage('x86_64 windows msvc') { 105 | agent { label 'windows11 && vs2022' } 106 | options { skipDefaultCheckout() } 107 | steps { 108 | cleanWs() 109 | checkout scm 110 | bat """ 111 | call "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat" 112 | if not %ERRORLEVEL% == 0 exit /b %ERRORLEVEL% 113 | call "winbuild-msvc.bat" -DCMAKE_BUILD_TYPE=Release 114 | if not %ERRORLEVEL% == 0 exit /b %ERRORLEVEL% 115 | ctest -j 4 --output-on-failure 116 | exit /b %ERRORLEVEL% 117 | """ 118 | } 119 | } 120 | 121 | stage('aarch64 macos clang-18') { 122 | agent { label 'macos' } 123 | options { skipDefaultCheckout() } 124 | steps { 125 | cleanWs() 126 | checkout scm 127 | sh ''' 128 | eval "$(/opt/homebrew/bin/brew shellenv)" 129 | export CC=/opt/homebrew/opt/llvm@18/bin/clang-18 130 | export CXX=/opt/homebrew/opt/llvm@18/bin/clang++ 131 | mkdir build 132 | cd build 133 | cmake -GNinja -DCMAKE_INSTALL_PREFIX=../../install .. 134 | cmake -E time ninja 135 | export CTEST_OUTPUT_ON_FAILURE=TRUE 136 | ctest -j 8 137 | ''' 138 | } 139 | } 140 | 141 | stage('aarch64 macos AppleClang') { 142 | agent { label 'macos' } 143 | options { skipDefaultCheckout() } 144 | steps { 145 | cleanWs() 146 | checkout scm 147 | sh ''' 148 | eval "$(/opt/homebrew/bin/brew shellenv)" 149 | mkdir build 150 | cd build 151 | cmake -GNinja -DCMAKE_INSTALL_PREFIX=../../install .. 152 | cmake -E time ninja 153 | export CTEST_OUTPUT_ON_FAILURE=TRUE 154 | ctest -j 8 155 | ''' 156 | } 157 | } 158 | } 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/cli/ssrc.1: -------------------------------------------------------------------------------- 1 | .\" Man page for ssrc 2 | .TH ssrc 1 "September 2025" "SSRC" "User Commands" 3 | .SH NAME 4 | ssrc \- An audiophile-grade sample rate converter 5 | .SH SYNOPSIS 6 | .B ssrc 7 | [\fIoptions\fR] \fI\fR \fI\fR 8 | .br 9 | .B cat 10 | \fIinput.wav\fR | \fBssrc\fR \fB--stdin\fR [\fIoptions\fR] \fB--stdout\fR > \fIoutput.wav\fR 11 | .SH DESCRIPTION 12 | Shibatch Sample Rate Converter (SSRC) is a fast and high-quality sample rate converter for PCM WAV files. It is designed to efficiently handle the conversion between commonly used sampling rates such as 44.1kHz and 48kHz while ensuring minimal sound quality degradation. 13 | .P 14 | SSRC achieves its balance of speed and audio fidelity through a unique FFT-based algorithm, leveraging the SleefDFT library for high-speed Fourier transforms and SIMD (AVX-512) optimization for accelerated processing. 15 | .P 16 | Key features: 17 | .IP \(bu 2 18 | \fBHigh-Quality Conversion\fR: Achieves excellent audio quality with minimal artifacts using specialized high-order filters. 19 | .IP \(bu 2 20 | \fBSelectable Conversion Profile\fR: Allows selection of filter lengths and computing precision (single or double) to balance quality and speed. 21 | .IP \(bu 2 22 | \fBDithering Functionality\fR: Supports various dithering techniques, including noise shaping based on the absolute threshold of hearing (ATH) curve. 23 | .IP \(bu 2 24 | \fBLow-Latency Real-Time Processing\fR: Suitable for demanding real-time applications by combining minimum-phase filters with an efficient partitioned convolution algorithm. 25 | .SH OPTIONS 26 | .TP 27 | \fB--rate \fR 28 | Specify the output sampling rate in Hz. Example: \fB48000\fR. 29 | .TP 30 | \fB--att \fR 31 | Attenuate the output signal in decibels (dB). Default: \fB0\fR. 32 | .TP 33 | \fB--bits \fR 34 | Specify the output quantization bit depth. Common values are \fB16\fR, \fB24\fR, \fB32\fR. Use \fB-32\fR or \fB-64\fR for 32-bit or 64-bit IEEE floating-point output. Default: \fB16\fR. 35 | .TP 36 | \fB--dither \fR 37 | Select a dithering/noise shaping algorithm by ID. Use \fB--dither help\fR to see all available types. 38 | .TP 39 | \fB--mixChannels \fR 40 | Mix, re-route, or change the number of channels. See the "CHANNEL MIXING" section below for details and examples. 41 | .TP 42 | \fB--minPhase\fR 43 | Use minimum-phase filters instead of the default linear-phase filters, which makes the processing delay negligible. 44 | .TP 45 | \fB--partConv \fR 46 | Divide a long filter into smaller sub-filters so that they can be applied without significant processing delays. 47 | .TP 48 | \fB--st\fR 49 | Disable multithreading (enabled by default). 50 | .TP 51 | \fB--pdf []\fR 52 | Select a Probability Distribution Function (PDF) for dithering. \fB0\fR: Rectangular, \fB1\fR: Triangular. Default: \fB0\fR. 53 | .TP 54 | \fB--profile \fR 55 | Select a conversion quality/speed profile. Use \fB--profile help\fR for details. Default: \fBstandard\fR. 56 | .TP 57 | \fB--dstContainer \fR 58 | Specify the output file container type (\fBriff\fR, \fBw64\fR, \fBrf64\fR, etc.). Use \fB--dstContainer help\fR for options. Defaults to the source container or \fBriff\fR. 59 | .TP 60 | \fB--genImpulse ...\fR 61 | For testing. Generate an impulse signal instead of reading a file. 62 | .TP 63 | \fB--genSweep ...\fR 64 | For testing. Generate a sweep signal instead of reading a file. 65 | .TP 66 | \fB--stdin\fR 67 | Read audio data from standard input. 68 | .TP 69 | \fB--stdout\fR 70 | Write audio data to standard output. 71 | .TP 72 | \fB--quiet\fR 73 | Suppress informational messages. 74 | .TP 75 | \fB--debug\fR 76 | Print detailed debugging information during processing. 77 | .TP 78 | \fB--seed \fR 79 | Set the random seed for dithering to ensure reproducible results. 80 | .SH "CONVERSION PROFILES" 81 | Profiles allow you to balance between conversion speed and quality (stop-band attenuation and filter length). Use \fBssrc --profile help\fR to see all profiles and their technical details. 82 | .TP 83 | \fBinsane\fR 84 | FFT: 262144, Attenuation: 200 dB, Precision: double. Highest possible quality, very slow. 85 | .TP 86 | \fBhigh\fR 87 | FFT: 65536, Attenuation: 170 dB, Precision: double. Excellent quality for audiophiles. 88 | .TP 89 | \fBlong\fR 90 | FFT: 32768, Attenuation: 145 dB, Precision: double. Superb quality. 91 | .TP 92 | \fBstandard\fR 93 | FFT: 16384, Attenuation: 145 dB, Precision: single. Great quality, default setting. 94 | .TP 95 | \fBshort\fR 96 | FFT: 4096, Attenuation: 96 dB, Precision: single. Good quality. 97 | .TP 98 | \fBfast\fR 99 | FFT: 1024, Attenuation: 96 dB, Precision: single. Good quality, suitable for most uses. 100 | .TP 101 | \fBlightning\fR 102 | FFT: 256, Attenuation: 96 dB, Precision: single. Low latency, suitable for real-time uses. 103 | .SH "CHANNEL MIXING" 104 | The \fB--mixChannels\fR option allows you to mix, re-route, or change the number of channels using a matrix string. 105 | .P 106 | The matrix string is a series of numbers separated by commas (,) and semicolons (;). Commas separate the gain values for each column in a row, and semicolons separate the rows. The number of rows in the matrix defines the number of output channels, and the number of columns must match the number of input channels. 107 | .SS "Example 1: Stereo to Mono Downmix" 108 | To combine a 2-channel stereo input into a 1-channel mono output, use a 1-row, 2-column matrix. The standard formula is Mono = 0.5 * Left + 0.5 * Right. 109 | .IP 110 | .B --mixChannels '0.5,0.5' 111 | .SS "Example 2: Mono to Stereo" 112 | To duplicate a 1-channel mono input into a 2-channel stereo output, use a 2-row, 1-column matrix. 113 | .IP 114 | .B --mixChannels '1;1' 115 | .SS "Example 3: Swapping Stereo Channels" 116 | To swap the left and right channels of a stereo file, you need a 2x2 matrix. 117 | .IP 118 | .B --mixChannels '0,1;1,0' 119 | .P 120 | The first row \fB0,1\fR means Output0 = (0 * Input0) + (1 * Input1). The second row \fB1,0\fR means Output1 = (1 * Input0) + (0 * Input1). 121 | .SH EXAMPLE 122 | Convert a WAV file from 44.1kHz to 48kHz with dithering: 123 | .P 124 | .B ssrc --rate 48000 --dither 0 input.wav output.wav 125 | .SH AUTHOR 126 | Naoki Shibata and contributors. 127 | .SH "SEE ALSO" 128 | .BR scsa (1) 129 | -------------------------------------------------------------------------------- /src/include/shibatch/ssrc.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SHIBATCH_SSRC_HPP 2 | #define SHIBATCH_SSRC_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace ssrc { 13 | struct WavFormat { 14 | static const inline uint16_t PCM = 0x0001, IEEE_FLOAT = 0x0003, EXTENSIBLE = 0xfffe; 15 | 16 | static const inline uint8_t KSDATAFORMAT_SUBTYPE_PCM[] = { 17 | 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 18 | }; 19 | static const inline uint8_t KSDATAFORMAT_SUBTYPE_IEEE_FLOAT[] = { 20 | 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 21 | }; 22 | 23 | uint16_t formatTag, channels; 24 | uint32_t sampleRate, avgBytesPerSec; 25 | uint16_t blockAlign, bitsPerSample, extendedSize, validBitsPerSample; 26 | uint32_t channelMask; 27 | uint8_t subFormat[16]; 28 | 29 | WavFormat() = default; 30 | 31 | WavFormat(const WavFormat &w) : 32 | formatTag(w.formatTag), channels(w.channels), sampleRate(w.sampleRate), avgBytesPerSec(w.avgBytesPerSec), 33 | blockAlign(w.blockAlign), bitsPerSample(w.bitsPerSample), extendedSize(w.extendedSize), 34 | validBitsPerSample(w.validBitsPerSample), channelMask(w.channelMask) { 35 | memcpy(subFormat, w.subFormat, sizeof(subFormat)); 36 | } 37 | 38 | WavFormat(uint16_t formatTag_, uint16_t channels_, uint32_t sampleRate_, uint16_t bitsPerSample_, uint32_t channelMask_ = 0, const uint8_t *subFormat_ = nullptr) : 39 | formatTag(formatTag_), channels(channels_), sampleRate(sampleRate_), avgBytesPerSec(0), 40 | blockAlign((uint32_t)channels_ * bitsPerSample_ / 8), bitsPerSample(bitsPerSample_), 41 | extendedSize(0), validBitsPerSample(0), channelMask(channelMask_) { 42 | if (subFormat_) memcpy(subFormat, subFormat_, sizeof(subFormat)); 43 | } 44 | }; 45 | 46 | struct ContainerFormat { 47 | static const inline uint16_t RIFF = 0x1000, RIFX = 0x1001, W64 = 0x1002; 48 | static const inline uint16_t RF64 = 0x1003, AIFF = 0x1004; 49 | 50 | uint16_t c; 51 | 52 | ContainerFormat(uint16_t c_) : c(c_) {} 53 | operator uint16_t() const { return c; } 54 | }; 55 | 56 | inline std::string to_string(ContainerFormat c) { 57 | switch(c.c) { 58 | case ContainerFormat::RIFF: return "RIFF"; 59 | case ContainerFormat::RIFX: return "RIFX"; 60 | case ContainerFormat::W64: return "W64"; 61 | case ContainerFormat::RF64: return "RF64"; 62 | case ContainerFormat::AIFF: return "AIFF"; 63 | default: return "N/A"; 64 | } 65 | } 66 | 67 | struct NoiseShaperCoef { 68 | const int fs, id; 69 | const char *name; 70 | const int len; 71 | const double coefs[64]; 72 | }; 73 | 74 | template 75 | class StageOutlet { 76 | public: 77 | virtual ~StageOutlet() = default; 78 | virtual bool atEnd() = 0; 79 | 80 | /** 81 | * Returns 0 only when EOF. 82 | * If not EOF and no data is available for reading, it must block. 83 | */ 84 | virtual size_t read(T *ptr, size_t n) = 0; 85 | }; 86 | 87 | template 88 | class OutletProvider { 89 | public: 90 | virtual ~OutletProvider() = default; 91 | virtual std::shared_ptr> getOutlet(uint32_t channel) = 0; 92 | virtual WavFormat getFormat() = 0; 93 | virtual ContainerFormat getContainer() { return ContainerFormat(0); } 94 | }; 95 | 96 | class DoubleRNG { 97 | public: 98 | virtual double nextDouble() = 0; 99 | virtual void fill(double *ptr, size_t n) { 100 | for(;n>0;n--) *ptr++ = nextDouble(); 101 | } 102 | virtual ~DoubleRNG() = default; 103 | }; 104 | 105 | template 106 | class SSRC : public StageOutlet { 107 | public: 108 | class SSRCImpl; 109 | SSRC(std::shared_ptr> inlet_, int64_t sfs_, int64_t dfs_, 110 | unsigned log2dftfilterlen_ = 10, double aa_ = 80, double guard_ = 1, double gain_ = 1, 111 | bool minPhase_ = false, unsigned l2mindftflen_ = 0, bool mt_ = true); 112 | ~SSRC(); 113 | bool atEnd(); 114 | size_t read(REAL *ptr, size_t n); 115 | double getDelay(); 116 | private: 117 | std::shared_ptr impl; 118 | }; 119 | 120 | template 121 | class WavReader : public OutletProvider { 122 | public: 123 | class WavReaderImpl; 124 | WavReader(const std::string &filename, bool mt_ = true); 125 | WavReader(bool mt_ = true); 126 | ~WavReader(); 127 | std::shared_ptr> getOutlet(uint32_t channel); 128 | WavFormat getFormat(); 129 | ContainerFormat getContainer(); 130 | private: 131 | std::shared_ptr impl; 132 | }; 133 | 134 | template 135 | class WavWriter { 136 | public: 137 | class WavWriterImpl; 138 | WavWriter(const std::string &filename, const WavFormat& fmt, const ContainerFormat& cont_, 139 | const std::vector>> &in_, uint64_t nFrames = 0, size_t bufsize_ = 65536, bool mt_ = true); 140 | ~WavWriter(); 141 | void execute(); 142 | private: 143 | std::shared_ptr impl; 144 | }; 145 | 146 | std::shared_ptr createTriangularRNG(double peak = 1.0, 147 | uint64_t seed = std::chrono::high_resolution_clock::now().time_since_epoch().count()); 148 | 149 | template 150 | class Dither : public StageOutlet { 151 | public: 152 | class DitherImpl; 153 | Dither(std::shared_ptr> in_, double gain_, int32_t offset_, int32_t clipMin_, int32_t clipMax_, 154 | const ssrc::NoiseShaperCoef *coef_, std::shared_ptr rng_ = createTriangularRNG()); 155 | ~Dither(); 156 | bool atEnd(); 157 | size_t read(OUTTYPE *ptr, size_t n); 158 | private: 159 | std::shared_ptr impl; 160 | }; 161 | 162 | template 163 | class ChannelMixer : public OutletProvider { 164 | public: 165 | class ChannelMixerImpl; 166 | ChannelMixer(std::shared_ptr> in_, const std::vector>& matrix_); 167 | ~ChannelMixer(); 168 | std::shared_ptr> getOutlet(uint32_t c); 169 | WavFormat getFormat(); 170 | private: 171 | std::shared_ptr impl; 172 | }; 173 | 174 | std::string versionString(); 175 | std::string buildInfo(); 176 | } 177 | #endif // #ifndef SHIBATCH_SSRC_HPP 178 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | option(BUILD_TESTS "Build tests" ON) 2 | option(BUILD_CLI "Build utilities" ON) 3 | option(ENABLE_ASAN "Enable ASAN" OFF) 4 | 5 | # To build with "-march=native" option 6 | # CC=gcc-12 CXX=g++-12 CFLAGS="-march=native" CXXFLAGS="-march=native" cmake .. 7 | 8 | # To use clang-19 to Build with LTO enabled 9 | # CXX=clang++-19 CC=clang-19 cmake .. -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=True -DCMAKE_CXX_COMPILER_AR=/usr/bin/llvm-ar-19 -DCMAKE_CXX_COMPILER_RANLIB=/usr/bin/llvm-ranlib-19 10 | 11 | # To build shared library 12 | # cmake .. -DBUILD_SHARED_LIBS=True -DCMAKE_POSITION_INDEPENDENT_CODE=ON 13 | 14 | ## 15 | 16 | set(SSRC_VERSION 2.4.2) 17 | 18 | message(STATUS "Configuring Shibatch Sample Rate Converter ${SSRC_VERSION}") 19 | 20 | cmake_minimum_required(VERSION 3.18) 21 | project(SSRC VERSION ${SSRC_VERSION} LANGUAGES C CXX) 22 | 23 | add_compile_definitions(SSRC_VERSION="${SSRC_VERSION}") 24 | set(SSRC_SOVERSION ${SSRC_VERSION_MAJOR}) 25 | 26 | # 27 | 28 | enable_language(CXX) 29 | set(CMAKE_CXX_STANDARD 20) 30 | 31 | # CMake configuration 32 | 33 | if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") 34 | message(FATAL_ERROR "In-source build not supported") 35 | endif() 36 | 37 | set(LIBRARY_OUTPUT_PATH "${CMAKE_BINARY_DIR}/lib") 38 | set(EXECUTABLE_OUTPUT_PATH "${CMAKE_BINARY_DIR}/bin") 39 | 40 | include_directories("${PROJECT_SOURCE_DIR}/src/include") 41 | 42 | if (NOT EXISTS ${CMAKE_BINARY_DIR}/CMakeCache.txt) 43 | if (NOT CMAKE_BUILD_TYPE) 44 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING "" FORCE) 45 | endif() 46 | endif() 47 | 48 | string(TIMESTAMP BUILDDATE "%Y-%m-%dT%H:%M:%SZ" UTC) 49 | add_compile_definitions(BUILDINFO="${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} ${BUILDDATE} ${CMAKE_BUILD_TYPE}") 50 | 51 | include(GNUInstallDirs) 52 | 53 | include(CTest) 54 | 55 | # Compiler options 56 | 57 | if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 58 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fcompare-debug-second") 59 | set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -Og") 60 | if (ENABLE_ASAN) 61 | if(CMAKE_SYSTEM_PROCESSOR MATCHES "(arm64|aarch64)") 62 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=hwaddress -static-libasan") 63 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=hwaddress -static-libasan") 64 | else() 65 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -static-libasan") 66 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -static-libasan") 67 | endif() 68 | endif() 69 | elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT WIN32) 70 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fno-math-errno") 71 | set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -Og") 72 | if (ENABLE_ASAN) 73 | if(CMAKE_SYSTEM_PROCESSOR MATCHES "(arm64|aarch64)") 74 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=hwaddress") 75 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=hwaddress") 76 | else() 77 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address") 78 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address") 79 | endif() 80 | endif() 81 | elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND WIN32) 82 | get_filename_component(COMPILER_BASENAME "${CMAKE_CXX_COMPILER}" NAME_WE) 83 | string(TOLOWER "${COMPILER_BASENAME}" LC_COMPILER_BASENAME) 84 | set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS True) 85 | elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 86 | set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS True) 87 | endif() 88 | 89 | string(TOLOWER "${CMAKE_BUILD_TYPE}" LC_CMAKE_BUILD_TYPE) 90 | 91 | if (WIN32) 92 | add_compile_definitions(_CRT_SECURE_NO_WARNINGS=1 _CRT_NONSTDC_NO_DEPRECATE=1) 93 | endif() 94 | 95 | # Include submodule 96 | 97 | set(SLEEF_SUBMODULE_INSTALL_DIR "${CMAKE_BINARY_DIR}/submodules") 98 | 99 | include(ExternalProject) 100 | include(FindPkgConfig) 101 | 102 | if (NOT EXISTS "${PROJECT_SOURCE_DIR}/submodules") 103 | file(MAKE_DIRECTORY "${PROJECT_SOURCE_DIR}/submodules") 104 | endif() 105 | 106 | # Include SLEEF as a submodule 107 | 108 | set(SLEEF_MINIMUM_VERSION 4.0.0) 109 | set(SLEEF_GIT_TAG "0c063a8f0e01c22fa1e473effd2e7a0c69b4963a") 110 | 111 | set(SLEEF_SOURCE_DIR "${PROJECT_SOURCE_DIR}/submodules/sleef") 112 | set(SLEEF_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}") 113 | 114 | set(SLEEF_CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${SLEEF_INSTALL_DIR} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DSLEEF_BUILD_DFT=True -DSLEEF_ENFORCE_DFT=True -DSLEEFDFT_ENABLE_PARALLELFOR=True -DSLEEF_BUILD_TESTS=False -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=${CMAKE_POSITION_INDEPENDENT_CODE} -DBUILD_SHARED_LIBS:BOOL=${BUILD_SHARED_LIBS}) 115 | 116 | if (CMAKE_C_COMPILER) 117 | list(APPEND SLEEF_CMAKE_ARGS -DCMAKE_C_COMPILER:PATH=${CMAKE_C_COMPILER}) 118 | endif() 119 | 120 | if (CMAKE_CXX_COMPILER) 121 | list(APPEND SLEEF_CMAKE_ARGS -DCMAKE_CXX_COMPILER:PATH=${CMAKE_CXX_COMPILER}) 122 | endif() 123 | 124 | if (CMAKE_TOOLCHAIN_FILE) 125 | list(APPEND SLEEF_CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}) 126 | endif() 127 | 128 | if (EXISTS "${SLEEF_SOURCE_DIR}/CMakeLists.txt") 129 | # If the source code of sleef is already downloaded, use it 130 | ExternalProject_Add(ext_sleef 131 | SOURCE_DIR "${SLEEF_SOURCE_DIR}" 132 | CMAKE_ARGS ${SLEEF_CMAKE_ARGS} 133 | UPDATE_DISCONNECTED TRUE 134 | ) 135 | include_directories(BEFORE "${SLEEF_INSTALL_DIR}/include") 136 | link_directories(BEFORE "${SLEEF_INSTALL_DIR}/lib") 137 | set(SLEEF_LIBRARIES "sleefdft" "sleef" "tlfloat") 138 | else() 139 | pkg_search_module(SLEEF sleef) 140 | 141 | if (SLEEF_FOUND AND SLEEF_VERSION VERSION_GREATER_EQUAL SLEEF_MINIMUM_VERSION) 142 | # If sleef is installed on the system 143 | add_custom_target(ext_sleef ALL) 144 | include_directories(BEFORE "${SLEEF_INCLUDE_DIRS}") 145 | link_directories(BEFORE "${SLEEF_LIBDIR}") 146 | message(STATUS "Found installed TLFloat " ${SLEEF_VERSION}) 147 | else() 148 | # Otherwise, download the source code 149 | find_package(Git REQUIRED) 150 | ExternalProject_Add(ext_sleef 151 | GIT_REPOSITORY https://github.com/shibatch/sleef 152 | GIT_TAG "${SLEEF_GIT_TAG}" 153 | SOURCE_DIR "${SLEEF_SOURCE_DIR}" 154 | CMAKE_ARGS ${SLEEF_CMAKE_ARGS} 155 | UPDATE_DISCONNECTED TRUE 156 | ) 157 | 158 | include_directories(BEFORE "${SLEEF_INSTALL_DIR}/include") 159 | link_directories(BEFORE "${SLEEF_INSTALL_DIR}/lib") 160 | set(SLEEF_LIBRARIES "sleefdft" "sleef" "tlfloat") 161 | endif() 162 | endif() 163 | 164 | if (NOT BUILD_SHARED_LIBS) 165 | add_compile_definitions(SLEEF_STATIC_LIBS=1) 166 | endif() 167 | 168 | # 169 | 170 | if (NOT "${LC_CMAKE_BUILD_TYPE}" STREQUAL "release") 171 | add_compile_definitions(DEBUG=1) 172 | endif() 173 | 174 | # 175 | 176 | add_subdirectory("src") 177 | 178 | # Show status 179 | 180 | if(NOT "${CMAKE_BUILD_TYPE}" STREQUAL "") 181 | message(STATUS "CMAKE_BUILD_TYPE : " ${CMAKE_BUILD_TYPE}) 182 | endif() 183 | -------------------------------------------------------------------------------- /SUSTAINABILITY.md: -------------------------------------------------------------------------------- 1 | #### Building a Sustainable Future for Our Open Source Projects 2 | 3 | We believe that Free and Open Source Software (FOSS) is a wonderful 4 | ecosystem that allows anyone to use software freely. However, to 5 | maintain and enhance its value over the long term, continuous 6 | maintenance and improvement are essential. 7 | 8 | Like many FOSS projects, we face the challenge that long-term 9 | sustainability is difficult to achieve through the goodwill and 10 | efforts of developers alone. While the outputs of open-source projects 11 | are incorporated into the products of many companies and their value 12 | is rightfully recognized, the developers who create these outputs are 13 | not always treated as equal partners in the business world. 14 | 15 | A license guarantees the "freedom to use," but the spirit of the FOSS 16 | ecosystem is based on a culture of mutual respect and contribution 17 | built upon that freedom. We believe that accepting the "value" of a 18 | project's output while unilaterally refusing dialogue with its 19 | creators simply because they are individuals undermines the 20 | sustainability of this ecosystem. Such companies should not turn a 21 | blind eye to the reality that someone must bear the costs to make the 22 | cycle sustainable. 23 | 24 | This issue is not just about corporations; it reflects a deeper 25 | cultural expectation within the FOSS ecosystem itself. Over time, we 26 | have come to take for granted that everything in open source should be 27 | provided for free - not only the code, but also the ongoing effort to 28 | maintain and improve it. However, FOSS licenses guarantee the freedom 29 | to use and modify software; **they do not impose an obligation on 30 | developers to offer perpetual, unpaid maintenance**. When this 31 | distinction is overlooked, maintainers can end up burdened with work 32 | that was never meant to be an open-ended personal commitment. Such an 33 | imbalance not only discourages openness, but also undermines the 34 | sustainability of an ecosystem that has become a vital part of modern 35 | society. 36 | 37 | To explain the phenomenon occurring across the entire ecosystem: 38 | Developers write code they find useful and release it as FOSS. It 39 | gains popularity, and soon large corporations incorporate it into 40 | their products, reaping substantial profits. Requests for new features 41 | and fixes flood in, yet no financial support accompanies 42 | them. Eventually, the maintainer realizes there is no personal or 43 | professional benefit in responding to these unpaid demands. The skills 44 | required to develop popular FOSS are often in high demand in other 45 | fields as well. Ultimately, the maintainer burns out and the project 46 | is abandoned. This is the unsustainable cycle we are tackling. 47 | 48 | Within this unsustainable cycle, adopting FOSS into products while 49 | fully aware of this situation is hardly beneficial for either 50 | companies or the society at large. To make the cycle sustainable, 51 | everyone must recognize the reality that someone must bear the costs, 52 | and these costs are equivalent to what companies would need to develop 53 | and maintain comparable products. This project specifically requests 54 | companies profiting from our deliverables to contribute to maintaining 55 | the project. 56 | 57 | To be clear, **this is not a request for charity**; it is a proposal 58 | to manage the operational risk. This is a systemic challenge 59 | originating not from the developers, but from within the organizations 60 | that consume and whose business continuity depends on FOSS. Should a 61 | project be abandoned due to this unresolved problem, **the primary 62 | victims will be you, the company** that built its product on top of an 63 | unmaintained foundation, not the developers who can move on to other 64 | opportunities. 65 | 66 | #### Our Request for Support 67 | 68 | We request ongoing financial support from organizations that 69 | incorporate our project's deliverables into their products or services 70 | and derive **annual revenue exceeding US $1 million** from those 71 | products and services, to help cover the costs of maintenance and the 72 | development of new features. While this support is not a legal 73 | obligation, let us be clear: the license is a grant of permission to 74 | use our work, not a service contract obligating us to provide 75 | perpetual, unpaid labor. We consider it a fundamental business 76 | principle that to profit from a critical dependency while contributing 77 | nothing to its stability is an extractive and unsustainable practice. 78 | 79 | It is also crucial to recognize what "maintenance" truly entails. In a 80 | living software project, it is not merely about preserving the status 81 | quo of the current version. It is the continuous effort that leads to 82 | security patches, compatibility with new environments, and the very 83 | features that define future versions. Therefore, to claim satisfaction 84 | with an older version as a reason to decline support, while 85 | simultaneously benefiting from the ongoing development that produces 86 | newer, better versions, is a logically inconsistent position. 87 | 88 | This support must not be intended to benefit any particular company, 89 | but must support maintaining the project as a shared infrastructure 90 | that **benefits all users and the broader community**. Furthermore, 91 | this threshold is designed so that **individual developers, 92 | small-scale projects, and the majority of our users are not asked to 93 | pay**, while seeking appropriate support from companies that derive 94 | significant value from our project. 95 | 96 | We understand that corporate procurement processes were not designed 97 | with FOSS sustainability in mind. We are committed to finding a 98 | practical path forward, but your partnership is essential in 99 | structuring a financial relationship that aligns with your standard 100 | corporate procedures. We simply ask for fairness: do not demand 101 | contractual terms from maintainers that your own legal department 102 | would refuse to accept. Our mutual goal is to treat this partnership 103 | as a conventional operational expense, removing your internal barriers 104 | and making sustainability a straightforward business practice. 105 | 106 | Our goal is to maintain this project stably over the long term and 107 | make it even more valuable for all users. In an industry where many 108 | projects are forced to abandon FOSS licenses, our preference is to 109 | continue offering this project under a true open-source 110 | license. However, the long-term viability of this FOSS-first approach 111 | depends directly on the willingness of our commercial beneficiaries to 112 | invest in the ecosystem they rely on. We hope our collaborative 113 | approach can contribute to shaping a more balanced and enduring future 114 | for FOSS. 115 | 116 | For details, please see our [Code of 117 | Conduct](https://github.com/shibatch/nofreelunch?tab=coc-ov-file) or 118 | its [introduction video](https://youtu.be/wsh2yKGwK4s). 119 | 120 | --- 121 | 122 | This sustainability statement is copyrighted by Naoki Shibata 2025. 123 | Licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). 124 | 125 | If you agree with the purpose of this statement, you can copy the text 126 | into your project's README to help spread awareness. When doing so, 127 | please also include a file named `SUSTAINABILITY.md` within your 128 | repository that contains the statement and attribution. 129 | -------------------------------------------------------------------------------- /src/libshibatchdsp/PartDFTFilter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PARTDFTFILTER_HPP 2 | #define PARTDFTFILTER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "ObjectCache.hpp" 9 | 10 | #include "shibatch/ssrc.hpp" 11 | 12 | #ifndef _MSC_VER 13 | #define RESTRICT __restrict__ 14 | #else 15 | #define RESTRICT 16 | #endif 17 | 18 | namespace shibatch { 19 | template 20 | class PartDFTFilter : public ssrc::StageOutlet { 21 | static constexpr const size_t toPow2(size_t n) { 22 | size_t ret = 1; 23 | for(;ret < n && ret != 0;ret *= 2) ; 24 | return ret; 25 | } 26 | 27 | static constexpr const size_t ilog2(size_t n) { 28 | size_t ret = 1; 29 | for(;n > (1ULL << ret) && ret < 64;ret++) ; 30 | return ret; 31 | } 32 | 33 | // 34 | 35 | std::shared_ptr> in; 36 | const size_t firlen, maxdftleno2, maxdftlen, l2maxdftlen, mindftlen, mindftleno2, l2mindftlen; 37 | 38 | std::vector inBuf, overlapBuf, fractionBuf; 39 | size_t overlapLen = 0, fractionLen = 0, nZeroPadding = 0; 40 | bool endReached = false; 41 | 42 | std::vector> dftf, dftb; 43 | 44 | std::vector> dftfilter_; 45 | std::vector dftfilter; 46 | 47 | std::shared_ptr dftfilter0_, dftbuf_; 48 | REAL *dftfilter0, *dftbuf; 49 | 50 | size_t dftCount = 0; 51 | 52 | public: 53 | PartDFTFilter(std::shared_ptr> in_, const REAL *fircoef_, size_t firlen_, size_t mindftlen_) : 54 | in(in_), firlen(firlen_), maxdftleno2(toPow2(firlen_)/2), maxdftlen(maxdftleno2 * 2), l2maxdftlen(ilog2(maxdftlen)), 55 | mindftlen(toPow2(mindftlen_)), mindftleno2(mindftlen / 2), l2mindftlen(ilog2(mindftlen)) { 56 | 57 | inBuf.resize(maxdftleno2 + mindftleno2); 58 | overlapBuf.resize(maxdftlen); 59 | fractionBuf.resize(mindftleno2 + maxdftlen); 60 | 61 | dftf.resize(l2maxdftlen+1); 62 | dftb.resize(l2maxdftlen+1); 63 | dftfilter_.resize(l2maxdftlen+1); 64 | dftfilter.resize(l2maxdftlen+1); 65 | dftfilter0_ = std::shared_ptr(Sleef_malloc(mindftlen * sizeof(REAL)), Sleef_free); 66 | dftbuf_ = std::shared_ptr(Sleef_malloc(maxdftlen * sizeof(REAL)), Sleef_free); 67 | dftfilter0 = (REAL *)dftfilter0_.get(); 68 | dftbuf = (REAL *)dftbuf_.get(); 69 | 70 | { 71 | const REAL *p = fircoef_; 72 | size_t r = std::min(firlen_ - (p - fircoef_), mindftleno2); 73 | for(size_t z=0;z(m | SLEEF_MODE_FORWARD , dftlen); 83 | dftb[l2dftlen] = ssrc::constructSleefDFT(m | SLEEF_MODE_BACKWARD, dftlen); 84 | dftfilter_[l2dftlen] = std::shared_ptr(Sleef_malloc(dftlen * sizeof(REAL)), Sleef_free); 85 | dftfilter[l2dftlen] = (REAL *)dftfilter_[l2dftlen].get(); 86 | 87 | r = std::min(firlen_ - (p - fircoef_), dftleno2); 88 | for(size_t z=0;z 0 || !endReached; } 100 | 101 | size_t read(REAL *RESTRICT out, size_t nSamples) { 102 | size_t ret = 0; 103 | 104 | if (fractionLen > 0) { 105 | size_t nOut = std::min(fractionLen, nSamples); 106 | memcpy(out, fractionBuf.data(), nOut * sizeof(REAL)); 107 | memmove(fractionBuf.data(), fractionBuf.data() + nOut, (fractionLen - nOut) * sizeof(REAL)); 108 | fractionLen -= nOut; 109 | nSamples -= nOut; 110 | out += nOut; 111 | ret += nOut; 112 | } 113 | 114 | while(nSamples > 0 && (!endReached || nZeroPadding != 0)) { 115 | size_t nRead = 0; 116 | 117 | { 118 | REAL *RESTRICT ptrRead = inBuf.data() + inBuf.size() - mindftleno2; 119 | 120 | while(nRead < mindftleno2) { 121 | if (!endReached) { 122 | size_t r = in->read(ptrRead + nRead, mindftleno2 - nRead); 123 | if (r == 0) { 124 | endReached = true; 125 | nZeroPadding = firlen; 126 | } 127 | nRead += r; 128 | } else { 129 | size_t r = std::min(mindftleno2 - nRead, nZeroPadding); 130 | memset(ptrRead + nRead, 0, r * sizeof(REAL)); 131 | nRead += r; 132 | nZeroPadding -= r; 133 | if (nZeroPadding == 0) break; 134 | } 135 | } 136 | 137 | memset(ptrRead + nRead, 0, (mindftleno2 - nRead) * sizeof(REAL)); 138 | 139 | // 140 | 141 | memcpy(dftbuf , ptrRead, mindftleno2 * sizeof(REAL)); 142 | memset(dftbuf + mindftleno2, 0 , mindftleno2 * sizeof(REAL)); 143 | 144 | SleefDFT_execute(dftf[l2mindftlen].get(), dftbuf, dftbuf); 145 | 146 | dftbuf[0] = dftfilter0[0] * dftbuf[0]; 147 | dftbuf[1] = dftfilter0[1] * dftbuf[1]; 148 | 149 | for(unsigned i=1;i= mindftleno2) overlapLen -= mindftleno2; else overlapLen = 0; 210 | 211 | out += nOut; 212 | nSamples -= nOut; 213 | ret += nOut; 214 | 215 | dftCount++; 216 | 217 | if (fractionLen > 0) break; 218 | } 219 | 220 | if (nSamples > 0) { 221 | size_t nOut = std::min(fractionLen, nSamples); 222 | memcpy(out, fractionBuf.data(), nOut * sizeof(REAL)); 223 | memmove(fractionBuf.data(), fractionBuf.data() + nOut, (fractionLen - nOut) * sizeof(REAL)); 224 | fractionLen -= nOut; 225 | nSamples -= nOut; 226 | out += nOut; 227 | ret += nOut; 228 | } 229 | 230 | return ret; 231 | } 232 | }; 233 | } 234 | #endif // #ifndef PARTDFTFILTER_HPP 235 | -------------------------------------------------------------------------------- /src/libshibatchdsp/PartDFTFilterMT.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PARTDFTFILTERMT_HPP 2 | #define PARTDFTFILTERMT_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "ObjectCache.hpp" 9 | 10 | #include "shibatch/ssrc.hpp" 11 | 12 | #ifndef _MSC_VER 13 | #define RESTRICT __restrict__ 14 | #else 15 | #define RESTRICT 16 | #endif 17 | 18 | namespace shibatch { 19 | template 20 | class PartDFTFilterMT : public ssrc::StageOutlet { 21 | static constexpr const size_t toPow2(size_t n) { 22 | size_t ret = 1; 23 | for(;ret < n && ret != 0;ret *= 2) ; 24 | return ret; 25 | } 26 | 27 | static constexpr const size_t ilog2(size_t n) { 28 | size_t ret = 1; 29 | for(;n > (1ULL << ret) && ret < 64;ret++) ; 30 | return ret; 31 | } 32 | 33 | // 34 | 35 | std::shared_ptr> in; 36 | const size_t firlen, maxdftleno2, maxdftlen, l2maxdftlen, mindftlen, mindftleno2, l2mindftlen; 37 | 38 | std::vector inBuf, overlapBuf, fractionBuf; 39 | size_t overlapLen = 0, fractionLen = 0, nZeroPadding = 0; 40 | bool endReached = false; 41 | 42 | std::vector> dftf, dftb; 43 | 44 | std::vector> dftfilter_; 45 | std::vector dftfilter; 46 | 47 | std::shared_ptr dftfilter0_, dftbuf_; 48 | REAL *dftfilter0, *dftbuf; 49 | 50 | size_t dftCount = 0; 51 | 52 | public: 53 | PartDFTFilterMT(std::shared_ptr> in_, const REAL *fircoef_, size_t firlen_, size_t mindftlen_) : 54 | in(in_), firlen(firlen_), maxdftleno2(toPow2(firlen_)/2), maxdftlen(maxdftleno2 * 2), l2maxdftlen(ilog2(maxdftlen)), 55 | mindftlen(toPow2(mindftlen_)), mindftleno2(mindftlen / 2), l2mindftlen(ilog2(mindftlen)) { 56 | 57 | inBuf.resize(maxdftleno2 + mindftleno2); 58 | overlapBuf.resize(maxdftlen); 59 | fractionBuf.resize(mindftleno2 + maxdftlen); 60 | 61 | dftf.resize(l2maxdftlen+1); 62 | dftb.resize(l2maxdftlen+1); 63 | dftfilter_.resize(l2maxdftlen+1); 64 | dftfilter.resize(l2maxdftlen+1); 65 | dftfilter0_ = std::shared_ptr(Sleef_malloc(mindftlen * sizeof(REAL)), Sleef_free); 66 | dftbuf_ = std::shared_ptr(Sleef_malloc(maxdftlen * sizeof(REAL)), Sleef_free); 67 | dftfilter0 = (REAL *)dftfilter0_.get(); 68 | dftbuf = (REAL *)dftbuf_.get(); 69 | 70 | { 71 | const REAL *p = fircoef_; 72 | size_t r = std::min(firlen_ - (p - fircoef_), mindftleno2); 73 | for(size_t z=0;z(m | SLEEF_MODE_FORWARD , dftlen); 83 | dftb[l2dftlen] = ssrc::constructSleefDFT(m | SLEEF_MODE_BACKWARD, dftlen); 84 | dftfilter_[l2dftlen] = std::shared_ptr(Sleef_malloc(dftlen * sizeof(REAL)), Sleef_free); 85 | dftfilter[l2dftlen] = (REAL *)dftfilter_[l2dftlen].get(); 86 | 87 | r = std::min(firlen_ - (p - fircoef_), dftleno2); 88 | for(size_t z=0;z 0 || !endReached; } 100 | 101 | size_t read(REAL *RESTRICT out, size_t nSamples) { 102 | size_t ret = 0; 103 | 104 | if (fractionLen > 0) { 105 | size_t nOut = std::min(fractionLen, nSamples); 106 | memcpy(out, fractionBuf.data(), nOut * sizeof(REAL)); 107 | memmove(fractionBuf.data(), fractionBuf.data() + nOut, (fractionLen - nOut) * sizeof(REAL)); 108 | fractionLen -= nOut; 109 | nSamples -= nOut; 110 | out += nOut; 111 | ret += nOut; 112 | } 113 | 114 | while(nSamples > 0 && (!endReached || nZeroPadding != 0)) { 115 | size_t nRead = 0; 116 | 117 | { 118 | REAL *RESTRICT ptrRead = inBuf.data() + inBuf.size() - mindftleno2; 119 | 120 | while(nRead < mindftleno2) { 121 | if (!endReached) { 122 | size_t r = in->read(ptrRead + nRead, mindftleno2 - nRead); 123 | if (r == 0) { 124 | endReached = true; 125 | nZeroPadding = firlen; 126 | } 127 | nRead += r; 128 | } else { 129 | size_t r = std::min(mindftleno2 - nRead, nZeroPadding); 130 | memset(ptrRead + nRead, 0, r * sizeof(REAL)); 131 | nRead += r; 132 | nZeroPadding -= r; 133 | if (nZeroPadding == 0) break; 134 | } 135 | } 136 | 137 | memset(ptrRead + nRead, 0, (mindftleno2 - nRead) * sizeof(REAL)); 138 | 139 | // 140 | 141 | memcpy(dftbuf , ptrRead, mindftleno2 * sizeof(REAL)); 142 | memset(dftbuf + mindftleno2, 0 , mindftleno2 * sizeof(REAL)); 143 | 144 | SleefDFT_execute(dftf[l2mindftlen].get(), dftbuf, dftbuf); 145 | 146 | dftbuf[0] = dftfilter0[0] * dftbuf[0]; 147 | dftbuf[1] = dftfilter0[1] * dftbuf[1]; 148 | 149 | for(unsigned i=1;i= mindftleno2) overlapLen -= mindftleno2; else overlapLen = 0; 210 | 211 | out += nOut; 212 | nSamples -= nOut; 213 | ret += nOut; 214 | 215 | dftCount++; 216 | 217 | if (fractionLen > 0) break; 218 | } 219 | 220 | if (nSamples > 0) { 221 | size_t nOut = std::min(fractionLen, nSamples); 222 | memcpy(out, fractionBuf.data(), nOut * sizeof(REAL)); 223 | memmove(fractionBuf.data(), fractionBuf.data() + nOut, (fractionLen - nOut) * sizeof(REAL)); 224 | fractionLen -= nOut; 225 | nSamples -= nOut; 226 | out += nOut; 227 | ret += nOut; 228 | } 229 | 230 | return ret; 231 | } 232 | }; 233 | } 234 | #endif // #ifndef PARTDFTFILTERMT_HPP 235 | -------------------------------------------------------------------------------- /src/libshibatchdsp/SRC.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_HPP 2 | #define SRC_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "Kaiser.hpp" 9 | #include "FastPP.hpp" 10 | #include "DFTFilter.hpp" 11 | #include "PartDFTFilter.hpp" 12 | #include "PartDFTFilterMT.hpp" 13 | #include "Minrceps.hpp" 14 | #include "ObjectCache.hpp" 15 | 16 | #include "shibatch/ssrc.hpp" 17 | 18 | template class ssrc::SSRC::SSRCImpl { 19 | public: 20 | virtual ~SSRCImpl() = default; 21 | }; 22 | 23 | namespace shibatch { 24 | template 25 | class SSRCStage : public ssrc::StageOutlet, public ssrc::SSRC::SSRCImpl { 26 | static int64_t gcd(int64_t x, int64_t y) { 27 | while (y != 0) { int64_t t = x % y; x = y; y = t; } 28 | return x; 29 | } 30 | 31 | class Oversample : public ssrc::StageOutlet { 32 | std::shared_ptr> inlet; 33 | const int64_t sfs, dfs, m; 34 | size_t remaining = 0; 35 | 36 | const size_t N = 65536; 37 | std::vector buf; 38 | bool endReached = false; 39 | public: 40 | Oversample(std::shared_ptr> in_, int64_t sfs_, int64_t dfs_) : inlet(in_), sfs(sfs_), dfs(dfs_), m(dfs_ / sfs_), buf(N) {} 41 | 42 | bool atEnd() { return endReached; } 43 | 44 | size_t read(REAL *out, size_t nSamples) { 45 | size_t ret = 0; 46 | 47 | while(nSamples > 0 && remaining > 0) { 48 | *out++ = 0; 49 | ret++; 50 | nSamples--; 51 | remaining--; 52 | } 53 | 54 | while(nSamples > 0) { 55 | size_t nRead = inlet->read(buf.data(), std::min(size_t((nSamples + m - 1) / m), N)); 56 | if (nRead == 0) { endReached = true; break; } 57 | 58 | for(size_t i=0;i < nRead-1;i++) { 59 | *out++ = buf[i]; 60 | for(int j=0;j < m-1;j++) *out++ = 0; 61 | } 62 | 63 | ret += (nRead - 1) * m; 64 | nSamples -= (nRead - 1) * m; 65 | 66 | *out++ = buf[nRead-1]; 67 | ret++; 68 | nSamples--; 69 | 70 | for(int j=0;j < m-1;j++) { 71 | if (nSamples == 0) { 72 | remaining = m - 1 - j; 73 | break; 74 | } 75 | *out++ = 0; 76 | ret++; 77 | nSamples--; 78 | } 79 | } 80 | 81 | return ret; 82 | } 83 | }; 84 | 85 | class Undersample : public ssrc::StageOutlet { 86 | std::shared_ptr> inlet; 87 | const int64_t sfs, dfs, m; 88 | bool endReached = false; 89 | 90 | const size_t N = 65536; 91 | std::vector buf; 92 | 93 | public: 94 | Undersample(std::shared_ptr> in_, int64_t sfs_, int64_t dfs_) : inlet(in_), sfs(sfs_), dfs(dfs_), m(sfs_ / dfs_), buf(N * m) {} 95 | 96 | bool atEnd() { return endReached; } 97 | 98 | size_t read(REAL *out, size_t nSamples) { 99 | REAL *origin = out; 100 | 101 | while(nSamples > 0 && !endReached) { 102 | int64_t nRead = 0, toBeRead = std::min(N, nSamples) * m; 103 | 104 | while(nRead < toBeRead) { 105 | size_t r = inlet->read(buf.data() + nRead, toBeRead - nRead); 106 | if (r == 0) { 107 | endReached = true; 108 | break; 109 | } 110 | nRead += r; 111 | } 112 | 113 | for(int64_t i = 0;i < nRead;i += m) { 114 | *out++ = buf[i]; 115 | nSamples--; 116 | } 117 | } 118 | 119 | return out - origin; 120 | } 121 | }; 122 | 123 | std::shared_ptr> inlet; 124 | const int64_t sfs, dfs, fslcm, lfs, hfs; 125 | 126 | const int64_t dftflen, mindftflen; 127 | const double aa, guard, gain; 128 | const bool minPhase, mt; 129 | double delay = 0; 130 | 131 | int64_t osm, fsos; 132 | 133 | std::shared_ptr> ppf; 134 | std::shared_ptr> dftf; 135 | std::shared_ptr> pdftf; 136 | std::shared_ptr> pdftfmt; 137 | std::shared_ptr oversample; 138 | std::shared_ptr undersample; 139 | 140 | public: 141 | SSRCStage(std::shared_ptr> inlet_, int64_t sfs_, int64_t dfs_, 142 | unsigned l2dftflen_ = 12, double aa_ = 96, double guard_ = 1, double gain_ = 1, 143 | bool minPhase_ = false, unsigned l2mindftflen_ = 0, bool mt_ = true) : 144 | inlet(inlet_), sfs(sfs_), dfs(dfs_), fslcm(sfs_ / gcd(sfs_, dfs_) * dfs_), 145 | lfs(std::min(sfs_, dfs_)), hfs(std::max(sfs_, dfs_)), 146 | dftflen(1LL << l2dftflen_), mindftflen(l2mindftflen_ == 0 ? 0 : (1LL << l2mindftflen_)), 147 | aa(aa_), guard(guard_), gain(gain_), minPhase(minPhase_), mt(mt_) { 148 | 149 | if (l2mindftflen_ > l2dftflen_) throw(std::runtime_error("SSRCStage::SSRCStage l2mindftflen > l2dftflen")); 150 | 151 | if (fslcm/hfs == 1) osm = 1; 152 | else if (fslcm/hfs % 2 == 0) osm = 2; 153 | else if (fslcm/hfs % 3 == 0) osm = 3; 154 | else { 155 | std::string s = "Resampling from " + std::to_string(sfs_) + " to " + std::to_string(dfs_) + " is not supported. "; 156 | s += std::to_string(lfs) + " / gcd(" + std::to_string(sfs_) + ", "; 157 | s += std::to_string(dfs_) + ") must be divided by 2 or 3."; 158 | throw(std::runtime_error(s.c_str())); 159 | } 160 | fsos = hfs * osm; 161 | 162 | std::shared_ptr> ppfv, dftfv; 163 | 164 | if (dfs != sfs) { 165 | // sampling frequency (fslcm) : lcm(lfs, hfs) (Hz) 166 | // pass-band edge frequency (fp) : (fsos + (lfs - fsos)/(1.0 + guard)) / 2 (Hz) 167 | // : guard = 0 => lfs/2 , guard = 1 => (lfs + fsos)/2, guard = inf => fsos / 2 168 | // transition band width (df) : (fsos - lfs) / (1.0 + guard) (Hz) 169 | // : guard = 0 => fsos - lfs, guard = 1 => (fsos - lfs)/2, guard = inf => 0 170 | // gain : fslcm / (double)sfs 171 | 172 | std::string keyPP = "KaiserWindow::makeLPF<" + std::string(typeid(REAL).name()) + ">(" + 173 | std::to_string(fslcm) + ", " + std::to_string((fsos + (lfs - fsos)/(1.0 + guard)) / 2) + ", " + 174 | std::to_string((fsos - lfs) / (1.0 + guard)) + ", " + std::to_string(aa) + ", " + std::to_string(fslcm / (double)sfs) + ")"; 175 | 176 | ppfv = ssrc::ObjectCache>::at(keyPP); 177 | 178 | if (!ppfv) { 179 | ppfv = KaiserWindow::makeLPF(fslcm, (fsos + (lfs - fsos)/(1.0 + guard)) / 2, (fsos - lfs) / (1.0 + guard), aa, fslcm / (double)sfs); 180 | ssrc::ObjectCache>::insert(keyPP, ppfv); 181 | } 182 | 183 | // sampling frequency (fsos) : hfs * osm (Hz) 184 | // pass-band edge frequency (fp2) : (lfs / 2 - df) (Hz) 185 | // length : dftflen - 1 (dftflen must be 2^N) 186 | // gain : 1.0 187 | 188 | double df = KaiserWindow::transitionBandWidth(aa, hfs * osm, dftflen - 1); 189 | 190 | std::string keyDF = "KaiserWindow::makeLPF<" + std::string(typeid(REAL).name()) + ">(" + 191 | std::to_string(fsos) + ", " + std::to_string(lfs/2) + ", " + 192 | std::to_string(dftflen - 1) + ", " + std::to_string(aa) + ", " + std::to_string(gain) + ")"; 193 | 194 | dftfv = ssrc::ObjectCache>::at(keyDF); 195 | 196 | if (!dftfv) { 197 | dftfv = KaiserWindow::makeLPF(fsos, lfs / 2 - df, dftflen - 1, aa, gain); 198 | ssrc::ObjectCache>::insert(keyDF, dftfv); 199 | } 200 | 201 | delay = ((ppfv->size() * 0.5 - 1) / fslcm + (dftfv->size() * 0.5 - 1) / (hfs * osm)) * dfs; 202 | 203 | if (minPhase) { 204 | Minrceps minrceps(dftfv->size() * 8); 205 | 206 | keyPP = "Minrceps(" + std::to_string(dftfv->size() * 8) + ") " + keyPP; 207 | 208 | if (ssrc::ObjectCache>::count(keyPP) == 0) { 209 | ppfv = minrceps.execute(ppfv); 210 | ssrc::ObjectCache>::insert(keyPP, ppfv); 211 | } else { 212 | ppfv = ssrc::ObjectCache>::at(keyPP); 213 | } 214 | 215 | keyDF = "Minrceps(" + std::to_string(dftfv->size() * 8) + ") " + keyDF; 216 | 217 | if (ssrc::ObjectCache>::count(keyDF) == 0) { 218 | dftfv = minrceps.execute(dftfv); 219 | ssrc::ObjectCache>::insert(keyDF, dftfv); 220 | } else { 221 | dftfv = ssrc::ObjectCache>::at(keyDF); 222 | } 223 | 224 | delay = 0; 225 | } 226 | } 227 | 228 | if (dfs > sfs) { 229 | ppf = std::make_shared>(inlet, sfs, fslcm, fsos, ppfv->data(), ppfv->size()); 230 | if (mindftflen == 0) { 231 | dftf = std::make_shared>(ppf, dftfv->data(), dftfv->size()); 232 | undersample = std::make_shared(dftf, fsos, dfs); 233 | } else if (!mt) { 234 | pdftf = std::make_shared>(ppf, dftfv->data(), dftfv->size(), mindftflen); 235 | undersample = std::make_shared(pdftf, fsos, dfs); 236 | } else { 237 | pdftfmt = std::make_shared>(ppf, dftfv->data(), dftfv->size(), mindftflen); 238 | undersample = std::make_shared(pdftfmt, fsos, dfs); 239 | } 240 | } else if (dfs < sfs) { 241 | oversample = std::make_shared(inlet, sfs, fsos); 242 | if (mindftflen == 0) { 243 | dftf = std::make_shared>(oversample, dftfv->data(), dftfv->size()); 244 | ppf = std::make_shared>(dftf, fsos, fslcm, dfs, ppfv->data(), ppfv->size()); 245 | } else if (!mt) { 246 | pdftf = std::make_shared>(oversample, dftfv->data(), dftfv->size(), mindftflen); 247 | ppf = std::make_shared>(pdftf, fsos, fslcm, dfs, ppfv->data(), ppfv->size()); 248 | } else { 249 | pdftfmt = std::make_shared>(oversample, dftfv->data(), dftfv->size(), mindftflen); 250 | ppf = std::make_shared>(pdftfmt, fsos, fslcm, dfs, ppfv->data(), ppfv->size()); 251 | } 252 | } 253 | } 254 | 255 | bool atEnd() { 256 | if (dfs > sfs) { 257 | return undersample->atEnd(); 258 | } else if (dfs < sfs) { 259 | return ppf->atEnd(); 260 | } else { 261 | return inlet->atEnd(); 262 | } 263 | } 264 | 265 | size_t read(REAL *out, size_t nSamples) { 266 | if (dfs > sfs) { 267 | return undersample->read(out, nSamples); 268 | } else if (dfs < sfs) { 269 | return ppf->read(out, nSamples); 270 | } else { 271 | size_t nr = inlet->read(out, nSamples); 272 | for(size_t i=0;i 2 | #include 3 | #include 4 | #include 5 | #include "SRC.hpp" 6 | #include "WavReader.hpp" 7 | #include "WavWriter.hpp" 8 | #include "Dither.hpp" 9 | #include "ChannelMixer.hpp" 10 | #include "BGExecutor.hpp" 11 | #include "ObjectCache.hpp" 12 | 13 | #ifndef SSRC_VERSION 14 | #error SSRC_VERSION not defined 15 | #endif 16 | 17 | #ifndef BUILDINFO 18 | #error BULIDINFO not defined 19 | #endif 20 | 21 | using namespace std; 22 | using namespace ssrc; 23 | using namespace shibatch; 24 | 25 | // 26 | 27 | namespace ssrc { 28 | string versionString() { return SSRC_VERSION; } 29 | string buildInfo() { return BUILDINFO; } 30 | } 31 | 32 | namespace shibatch { 33 | class LambdaRunner : public Runnable { 34 | const function f; 35 | const function g; 36 | void *ptrf, *ptrg; 37 | public: 38 | LambdaRunner(function f_, void *ptrf_, 39 | function g_, void *ptrg_) : f(f_), g(g_), ptrf(ptrf_), ptrg(ptrg_) {} 40 | ~LambdaRunner() {} 41 | void run() { f(ptrf); } 42 | void postProcess(void *p) { g(ptrg, p); } 43 | }; 44 | 45 | class BGExecutorStatic { 46 | int nIdleWorkers = 0; 47 | vector> vth; 48 | unordered_set thIds; 49 | queue> que; 50 | mutex mtx; 51 | condition_variable condVar; 52 | 53 | shared_ptr pop() { 54 | auto ret = std::move(que.front()); 55 | que.pop(); 56 | return ret; 57 | } 58 | 59 | void thEntry() { 60 | { 61 | unique_lock lock(mtx); 62 | thIds.insert(this_thread::get_id()); 63 | } 64 | 65 | shared_ptr r = nullptr; 66 | 67 | for(;;) { 68 | if (r) r->run(); 69 | 70 | unique_lock lock(mtx); 71 | 72 | if (r) { 73 | r->belongsTo->que.push(r); 74 | condVar.notify_all(); 75 | } 76 | 77 | nIdleWorkers++; 78 | while(que.empty()) condVar.wait(lock); 79 | nIdleWorkers--; 80 | r = pop(); 81 | if (!r) break; 82 | } 83 | } 84 | 85 | void addWorkerIfNecessary() { 86 | if (nIdleWorkers == 0 && vth.size() < thread::hardware_concurrency()) 87 | vth.push_back(make_shared(&BGExecutorStatic::thEntry, this)); 88 | } 89 | 90 | public: 91 | ~BGExecutorStatic() { 92 | { 93 | unique_lock lock(mtx); 94 | for(unsigned i=0;i < vth.size();i++) que.push(nullptr); 95 | condVar.notify_all(); 96 | } 97 | for(auto t : vth) t->join(); 98 | } 99 | 100 | friend class shibatch::BGExecutor; 101 | }; 102 | 103 | BGExecutorStatic bgExecutorStatic; 104 | 105 | shared_ptr Runnable::factory(function f, void *p, 106 | function g, void *q) { 107 | return make_shared(f, p, g, q); 108 | } 109 | 110 | void BGExecutor::push(shared_ptr job) { 111 | job->belongsTo = this; 112 | unique_lock lock(bgExecutorStatic.mtx); 113 | bgExecutorStatic.addWorkerIfNecessary(); 114 | bgExecutorStatic.que.push(job); 115 | bgExecutorStatic.condVar.notify_all(); 116 | size_++; 117 | } 118 | 119 | shared_ptr BGExecutor::pop() { 120 | if (bgExecutorStatic.thIds.count(this_thread::get_id()) == 0) { 121 | unique_lock lock(bgExecutorStatic.mtx); 122 | 123 | while(que.empty()) bgExecutorStatic.condVar.wait(lock); 124 | 125 | auto r = std::move(que.front()); 126 | que.pop(); 127 | size_--; 128 | 129 | return r; 130 | } 131 | 132 | for(;;) { 133 | shared_ptr r = nullptr; 134 | 135 | { 136 | unique_lock lock(bgExecutorStatic.mtx); 137 | 138 | bgExecutorStatic.nIdleWorkers++; 139 | while(que.empty() && bgExecutorStatic.que.empty()) bgExecutorStatic.condVar.wait(lock); 140 | bgExecutorStatic.nIdleWorkers--; 141 | 142 | if (!que.empty()) { 143 | auto ret = std::move(que.front()); 144 | que.pop(); 145 | size_--; 146 | return ret; 147 | } 148 | 149 | r = bgExecutorStatic.pop(); 150 | } 151 | 152 | r->run(); 153 | } 154 | } 155 | 156 | size_t BGExecutor::size() { 157 | unique_lock lock(bgExecutorStatic.mtx); 158 | return size_; 159 | } 160 | } 161 | 162 | // 163 | 164 | template SSRC::SSRC(shared_ptr> inlet_, int64_t sfs_, int64_t dfs_, 165 | unsigned l2dftflen_, double aa_, double guard_, double gain_, 166 | bool minPhase_, unsigned l2mindftflen_, bool mt_) : 167 | impl(make_shared>(inlet_, sfs_, dfs_, l2dftflen_, aa_, guard_, gain_, minPhase_, l2mindftflen_, mt_)) {} 168 | 169 | template SSRC::~SSRC() {} 170 | 171 | template size_t SSRC::read(REAL *ptr, size_t n) { 172 | return dynamic_pointer_cast>(impl)->read(ptr, n); 173 | } 174 | 175 | template bool SSRC::atEnd() { 176 | return dynamic_pointer_cast>(impl)->atEnd(); 177 | } 178 | 179 | template double SSRC::getDelay() { 180 | return dynamic_pointer_cast>(impl)->getDelay(); 181 | } 182 | 183 | // 184 | 185 | template SSRC::SSRC(shared_ptr>, int64_t, int64_t, unsigned, double, double, double, bool, unsigned, bool); 186 | template SSRC::~SSRC(); 187 | template size_t SSRC::read(float *ptr, size_t n); 188 | template bool SSRC::atEnd(); 189 | template double SSRC::getDelay(); 190 | 191 | template SSRC::SSRC(shared_ptr>, int64_t, int64_t, unsigned, double, double, double, bool, unsigned, bool); 192 | template SSRC::~SSRC(); 193 | template size_t SSRC::read(double *ptr, size_t n); 194 | template bool SSRC::atEnd(); 195 | template double SSRC::getDelay(); 196 | 197 | // 198 | 199 | template WavReader::WavReader(const string &filename, bool mt_) : 200 | impl(make_shared>(filename, mt_)) {} 201 | 202 | template WavReader::WavReader(bool mt_) : 203 | impl(make_shared>(mt_)) {} 204 | 205 | template WavReader::~WavReader() {} 206 | 207 | template shared_ptr> WavReader::getOutlet(uint32_t channel) { 208 | return dynamic_pointer_cast>(impl)->getOutlet(channel); 209 | } 210 | 211 | template WavFormat WavReader::getFormat() { 212 | dr_wav::drwav_fmt fmt = dynamic_pointer_cast>(impl)->getFmt(); 213 | ssrc::WavFormat ret; 214 | 215 | ret.formatTag = fmt.formatTag; 216 | ret.channels = fmt.channels; 217 | ret.sampleRate = fmt.sampleRate; 218 | ret.avgBytesPerSec = fmt.avgBytesPerSec; 219 | ret.blockAlign = fmt.blockAlign; 220 | ret.bitsPerSample = fmt.bitsPerSample; 221 | ret.extendedSize = fmt.extendedSize; 222 | ret.validBitsPerSample = fmt.validBitsPerSample; 223 | ret.channelMask = fmt.channelMask; 224 | memcpy(&ret.subFormat, &fmt.subFormat, sizeof(ret.subFormat)); 225 | 226 | return ret; 227 | } 228 | 229 | template ContainerFormat WavReader::getContainer() { 230 | return ContainerFormat((uint16_t)dr_wav::Container(dynamic_pointer_cast>(impl)->getContainer())); 231 | } 232 | 233 | // 234 | 235 | template WavReader::WavReader(const string &filename, bool mt_); 236 | template WavReader::WavReader(bool mt_); 237 | template WavReader::~WavReader(); 238 | template shared_ptr> WavReader::getOutlet(uint32_t); 239 | template WavFormat WavReader::getFormat(); 240 | template ContainerFormat WavReader::getContainer(); 241 | 242 | template WavReader::WavReader(const string &filename, bool mt_); 243 | template WavReader::WavReader(bool mt_); 244 | template WavReader::~WavReader(); 245 | template shared_ptr> WavReader::getOutlet(uint32_t); 246 | template WavFormat WavReader::getFormat(); 247 | template ContainerFormat WavReader::getContainer(); 248 | 249 | // 250 | 251 | template WavWriter::WavWriter(const string &filename, 252 | const ssrc::WavFormat& fmt_, const ssrc::ContainerFormat& cont_, 253 | const vector>> &in_, 254 | uint64_t nFrames, size_t bufsize_, bool mt_) { 255 | dr_wav::drwav_fmt fmt; 256 | memcpy(&fmt, &fmt_, sizeof(fmt)); 257 | impl = make_shared>(filename, fmt, dr_wav::Container(cont_.c), in_, nFrames, bufsize_, mt_); 258 | } 259 | 260 | template WavWriter::~WavWriter() {} 261 | 262 | template void WavWriter::execute() { 263 | dynamic_pointer_cast>(impl)->execute(); 264 | } 265 | 266 | // 267 | 268 | template WavWriter::WavWriter(const string &, const ssrc::WavFormat&, const ssrc::ContainerFormat&, 269 | const vector>> &, uint64_t, size_t, bool); 270 | template WavWriter::~WavWriter(); 271 | template void WavWriter::execute(); 272 | 273 | template WavWriter::WavWriter(const string &, const ssrc::WavFormat&, const ssrc::ContainerFormat&, 274 | const vector>> &, uint64_t, size_t, bool); 275 | template WavWriter::~WavWriter(); 276 | template void WavWriter::execute(); 277 | 278 | template WavWriter::WavWriter(const string &, const ssrc::WavFormat&, const ssrc::ContainerFormat&, 279 | const vector>> &, uint64_t, size_t, bool); 280 | template WavWriter::~WavWriter(); 281 | template void WavWriter::execute(); 282 | 283 | // 284 | 285 | template 286 | Dither::Dither(shared_ptr> in_, double gain_, int32_t offset_, 287 | int32_t clipMin_, int32_t clipMax_, 288 | const ssrc::NoiseShaperCoef *coef_, shared_ptr rng_) : 289 | impl(make_shared>(in_, gain_, offset_, clipMin_, clipMax_, coef_, rng_)) {} 290 | 291 | template Dither::~Dither() {} 292 | 293 | template bool Dither::atEnd() { 294 | return dynamic_pointer_cast>(impl)->atEnd(); 295 | } 296 | 297 | template size_t Dither::read(OUTTYPE *ptr, size_t n) { 298 | return dynamic_pointer_cast>(impl)->read(ptr, n); 299 | } 300 | 301 | // 302 | 303 | template Dither::Dither(shared_ptr> in_, double gain_, int32_t offset_, 304 | int32_t clipMin_, int32_t clipMax_, 305 | const ssrc::NoiseShaperCoef *coef_, shared_ptr rng_); 306 | template Dither::~Dither(); 307 | template size_t Dither::read(int32_t *ptr, size_t n); 308 | template bool Dither::atEnd(); 309 | 310 | template Dither::Dither(shared_ptr> in_, double gain_, int32_t offset_, 311 | int32_t clipMin_, int32_t clipMax_, 312 | const ssrc::NoiseShaperCoef *coef_, shared_ptr rng_); 313 | template Dither::~Dither(); 314 | template size_t Dither::read(int32_t *ptr, size_t n); 315 | template bool Dither::atEnd(); 316 | 317 | // 318 | 319 | shared_ptr ssrc::createTriangularRNG(double peak, uint64_t seed) { 320 | return make_shared(peak, make_shared(seed)); 321 | } 322 | 323 | // 324 | 325 | template ChannelMixer::ChannelMixer(shared_ptr> in_, 326 | const vector>& matrix_) : 327 | impl(make_shared>(in_, matrix_)) {} 328 | 329 | template ChannelMixer::~ChannelMixer() {} 330 | 331 | template shared_ptr> ChannelMixer::getOutlet(uint32_t c) { 332 | return dynamic_pointer_cast>(impl)->getOutlet(c); 333 | } 334 | 335 | template WavFormat ChannelMixer::getFormat() { 336 | return dynamic_pointer_cast>(impl)->getFormat(); 337 | } 338 | 339 | // 340 | 341 | template ChannelMixer::ChannelMixer(shared_ptr> in_, 342 | const vector>& matrix_); 343 | template ChannelMixer::~ChannelMixer(); 344 | template shared_ptr> ChannelMixer::getOutlet(uint32_t c); 345 | template WavFormat ChannelMixer::getFormat(); 346 | 347 | template ChannelMixer::ChannelMixer(shared_ptr> in_, 348 | const vector>& matrix_); 349 | template ChannelMixer::~ChannelMixer(); 350 | template shared_ptr> ChannelMixer::getOutlet(uint32_t c); 351 | template WavFormat ChannelMixer::getFormat(); 352 | 353 | // 354 | 355 | template ObjectCache::Internal ssrc::ObjectCache::internal; 356 | template ObjectCache::Internal ssrc::ObjectCache::internal; 357 | 358 | template ObjectCache::Internal ssrc::ObjectCache::internal; 359 | -------------------------------------------------------------------------------- /ALGORITHM.md: -------------------------------------------------------------------------------- 1 | ## Overview of the Algorithm 2 | 3 | ### 1. Introduction 4 | 5 | One of the key challenges in digital audio processing is converting PCM signals between different sampling frequencies. This is particularly difficult for rational sample rate conversion, where the ratio is a rational number L/M (e.g., converting 44,100 Hz to 48,000 Hz involves a ratio of 147/160). 6 | 7 | Theoretically, the ideal method is to: 8 | 1. Upsample the source signal to the least common multiple (LCM) of the two frequencies. 9 | 2. Apply a single, sharp, ideal low-pass filter (LPF) to remove unwanted spectral content. 10 | 3. Downsample (or decimate) the result to the target frequency. 11 | 12 | However, the LCM of common audio frequencies is often enormous (e.g., lcm(44100, 48000) = 7,840,000 Hz). This makes the required filter order and computational load impractically large for a single-stage approach. 13 | 14 | To solve this, the proposed algorithm employs **two-stage filtering**. It combines two different types of FIR filters—one implemented as a **polyphase filter** and the other using **FFT-based fast convolution**—to achieve high-fidelity conversion within feasible computational limits. 15 | 16 | ### 2. The Two-Stage Process 17 | 18 | The core of the algorithm is a two-stage filtering process that uses an intermediate sampling frequency, *fsos*. This architecture decomposes the complex filtering problem into two more manageable steps. The order of operations depends on whether we are upsampling or downsampling. 19 | 20 | #### 2.1. Upsampling Process (Low Frequency *lfs* → High Frequency *hfs*) 21 | 22 | The goal of upsampling is to interpolate new sample values while suppressing unwanted high-frequency **spectral images** (copies of the original signal's spectrum at higher frequencies) that are created during the process. 23 | 24 | **Conceptual Flow:** 25 | 26 | `Input (@lfs)` → `Polyphase Filter` → `Intermediate (@fsos)` → `Fast Conv. FIR` → `Output (@hfs)` 27 | 28 | **Steps:** 29 | 30 | 1. **Stage 1: Polyphase Filter:** The input signal is first processed by a polyphase filter. 31 | * Conceptually, this step involves upsampling the signal to the LCM frequency (*fslcm*) and applying a low-pass filter. 32 | * In practice, the polyphase structure is a highly efficient implementation that combines upsampling, filtering, and downsampling into a single operation. It avoids explicitly generating the massive intermediate signal at *fslcm*, thus saving significant computation. 33 | * The output of this stage is a signal at the intermediate sampling frequency *fsos* = *hfs* · *osm*. 34 | 35 | 2. **Stage 2: Fast Convolution FIR Filter:** The signal at *fsos* is then filtered by a very high-order, sharp FIR low-pass filter. 36 | * This filter is implemented using a **fast convolution** algorithm, which leverages FFTs for efficiency. 37 | * Its purpose is to definitively remove all frequency components above the original signal's Nyquist frequency (*lfs*/2), eliminating any remaining spectral images with near-ideal precision. 38 | 39 | 3. **Final Decimation:** The clean signal from Stage 2 is decimated (downsampled) to the target frequency *hfs*. 40 | 41 | #### 2.2. Downsampling Process (High Frequency *hfs* → Low Frequency *lfs*) 42 | 43 | For downsampling, the primary challenge is to prevent **aliasing**, where high-frequency content folds down into the audible low-frequency band after decimation. The filter order is reversed to address this. 44 | 45 | **Conceptual Flow:** 46 | 47 | `Input (@hfs)` → `Fast Conv. FIR` → `Intermediate (@fsos)` → `Polyphase Filter` → `Output (@lfs)` 48 | 49 | **Steps:** 50 | 51 | 1. **Stage 1: Fast Convolution FIR Filter:** The input signal is first upsampled to the intermediate frequency *fsos* and then processed by the sharp FIR filter. 52 | * This filter acts as a very effective **anti-aliasing filter**. It sharply cuts off all frequencies above the target Nyquist frequency (*lfs*/2) *before* the final decimation occurs. 53 | 54 | 2. **Stage 2: Polyphase Filter:** The filtered signal at *fsos* is then passed to the polyphase filter, which efficiently decimates it down to the final target frequency *lfs*. 55 | 56 | ### 3. Filter Characteristics and Roles 57 | 58 | The efficacy of this algorithm stems from the complementary strengths of the two chosen filter implementations. 59 | 60 | * **FIR Filter with Fast Convolution**: This is a standard Finite Impulse Response (FIR) filter implemented using a **fast convolution** algorithm. This technique allows for the efficient application of a very high-order filter, enabling a near-ideal LPF with an extremely sharp transition band and high stopband attenuation. It is perfect for precise frequency separation, serving as the primary anti-imaging (for upsampling) or anti-aliasing (for downsampling) filter. 61 | * **Polyphase Filter**: The polyphase structure is inherently optimized for the mechanics of sample rate conversion. While it cannot typically achieve the same extreme filter order as the fast convolution method, it excels at the computational task by efficiently combining the operations of upsampling, filtering, and downsampling. 62 | 63 | ### 4. Filter Design and Parameters 64 | 65 | The quality of the conversion is determined by the design of the two FIR filters. This implementation uses a Kaiser window to design the low-pass filters, guided by a few key parameters. The default values are selected to achieve a good balance between audio conversion quality and conversion speed. 66 | 67 | * Stopband Attenuation (*aa*): Defines the attenuation in the stopband. The default value is 96 dB, which is sufficient for converting 16-bit PCM data. 68 | * DFT Filter Length (*dftflen*): The length of the FIR filter implemented with fast convolution. The default is 4096 taps. A longer filter allows for a sharper transition band. 69 | * Guard Factor (*guard*): A parameter used to adjust the transition band of the polyphase filter. The default is 1. 70 | 71 | #### Polyphase Filter Design 72 | 73 | The polyphase filter acts as the first stage in upsampling and the second in downsampling. Its characteristics are defined by the following formulas: 74 | * **Transition Band Width**: (*fsos* - *lfs*) / (1.0 + *guard*) 75 | * **Pass-band Edge Frequency**: (*fsos* + (*lfs* - *fsos*)/(1.0 + *guard*)) / 2 76 | 77 | These formulas show how the *guard* parameter helps define the cutoff characteristics relative to the low frequency (*lfs*) and the intermediate frequency (*fsos*). 78 | 79 | #### Fast Convolution FIR Filter Design 80 | 81 | This filter provides the final, sharp filtering. Its design is based on achieving the target stopband attenuation (*aa*) given its length (*dftflen*). 82 | 1. First, the required transition band width (*df*) for the filter is calculated based on *aa*, *fsos*, and *dftflen*. 83 | 2. The pass-band edge frequency is then set to (*lfs* / 2 - *df*). This ensures that the filter's transition band starts just below the Nyquist frequency of the lower-rate signal, providing a very sharp cutoff that prevents aliasing (in downsampling) and removes spectral images (in upsampling) with high precision. 84 | 85 | ### 5. The *osm* Parameter and Implementation Constraints 86 | 87 | The intermediate frequency *fsos* is defined by the parameter *osm*, where *fsos* = *hfs* · *osm*. The parameter *osm* is the smallest positive integer (*osm* ≥ 1) that satisfies the following condition: 88 | 89 | lcm(*lfs*, *hfs*) / (*hfs* · *osm*) ∈ Z 90 | 91 | In simpler terms, this condition ensures that the decimation factor from the conceptual LCM frequency down to the intermediate frequency *fsos* is a whole number. This keeps sample positions on a regular grid, simplifying the process. 92 | 93 | However, the efficiency of the fast convolution stage degrades as *fsos* (and thus *osm*) increases. To manage this trade-off, this implementation imposes a constraint: **only combinations of *lfs* and *hfs* that result in *osm* ≤ 3 are permitted.** This constraint means the converter is not universal; it cannot, in principle, convert between any two arbitrary frequencies. In practice, however, this design choice covers all common sampling frequencies used in audio. It is a trade-off, sacrificing absolute universality for optimized performance in the most common use cases. 94 | 95 | ### 6. Partitioned Convolution Implementation Details 96 | 97 | One of the primary goals of this sample rate converter is to be suitable for real-time applications. In such use cases, processing latency is a critical factor; a long delay between input and output can make an application unusable. The high-order FIR filters required for high-quality conversion inherently introduce significant latency. To overcome this, this implementation employs a dual strategy: using **minimum-phase filters** to reduce the intrinsic filter delay, and using **Partitioned Convolution** to reduce the delay from block-based processing. The combination of these techniques allows the converter to meet the stringent demands of real-time use. 98 | 99 | #### The Challenge: Latency in Fast Convolution 100 | 101 | Standard FFT-based fast convolution is very efficient for applying long filters. However, it introduces a significant delay (latency). To convolve a signal, the algorithm must collect a full block of input samples (e.g., 4096 samples) before it can perform the FFT, multiply the frequency-domain representations, and perform the inverse FFT. The output is only available after this entire block is processed, resulting in a latency of at least the block size. This latency cannot be reduced no matter how fast the computer is. For real-time audio, this delay can be unacceptable. 102 | 103 | #### Solution: Partitioned Convolution 104 | 105 | Partitioned convolution solves the latency problem. Instead of viewing the long FIR filter as one monolithic block, it is **split into smaller sub-filters called partitions**. 106 | 107 | The input signal is also processed in much smaller blocks. For each new block of the input signal, a convolution is performed with the *first* partition of the filter. The result of this can be output almost immediately, drastically reducing latency. Convolutions with the remaining, longer partitions are performed and their results are combined over time. This way, the low-latency output is generated quickly, while the full, high-precision filtering effect is achieved as more blocks are processed. 108 | 109 | The main benefit is **low latency**. It allows for the use of very long, high-quality filters (which require large FFTs for efficiency) without the associated long processing delay. 110 | 111 | #### Optimization: Non-Uniform Partitioning 112 | 113 | This implementation takes the concept a step further by using **non-uniform partitions**. This is an optimization that provides an even better trade-off between latency and computational load. 114 | 115 | The filter's impulse response is partitioned into blocks of *different sizes*: 116 | - The **beginning** of the impulse response, which has the most significant impact on initial latency, is split into **many small partitions**. These are processed frequently with small, fast FFTs. 117 | - The **tail** of the impulse response is grouped into a **few large partitions**. These are processed less frequently, which is more computationally efficient as it requires fewer FFT operations overall. 118 | 119 | This hybrid approach allows the filter to achieve both the extremely low latency of short filters and the high frequency precision and computational efficiency of long filters. The `PartDFTFilter` class efficiently performs this complex processing by exponentially increasing the lengths of the applied filters. The underlying DFT calculations are accelerated using the `SleefDFT` library, which leverages SIMD instructions for high-speed processing. 120 | 121 | ### 7. Internal Execution Framework (`BGExecutor`) 122 | 123 | To efficiently execute the conversion process, especially computationally intensive tasks like partitioned convolution, SSRC includes an internal multi-threaded execution framework. The core of this framework is the `BGExecutor` class. This system is used for parallelizing computational tasks, separate from the dedicated threads used for file I/O (reading and writing). 124 | 125 | - **Job Submission and Retrieval**: A user creates an instance of the `BGExecutor` class and `push`es jobs (implementing the `Runnable` interface) to it to request background execution. By calling `pop` on the same instance, the user can retrieve the results of the job (the completed `Runnable` object). Each `BGExecutor` instance is independent; a job `push`ed to one instance cannot be `pop`ped from another. 126 | - **Global Worker Pool**: Internally, a singleton class named `BGExecutorStatic` manages all worker threads globally. Jobs `push`ed from any `BGExecutor` instance are sent to this singleton's queue and assigned to waiting worker threads. 127 | - **Deadlock Avoidance**: This architecture is robust against deadlocks. In this framework, worker threads only enter a waiting state when no executable jobs are available. Therefore, as long as executable jobs exist, at least one job is always running. Consequently, if the number of jobs is finite, job execution will eventually complete. 128 | -------------------------------------------------------------------------------- /src/libshibatchdsp/ssrcsoxr.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "shibatch/ssrc.hpp" 11 | #include "shibatch/ssrcsoxr.h" 12 | #include "ArrayQueue.hpp" 13 | 14 | using namespace std; 15 | using namespace ssrc; 16 | 17 | namespace shibatch { 18 | template 19 | class Soxifier : OutletProvider { 20 | class Outlet : public StageOutlet { 21 | Soxifier &parent; 22 | mutex mtx; 23 | condition_variable condVar; 24 | const uint32_t ch; 25 | shared_ptr th; 26 | ArrayQueue inQueue; 27 | ArrayQueue outQueue; 28 | bool finished = false; 29 | 30 | void thEntry() { 31 | vector buf(parent.N); 32 | 33 | while(!parent.shuttingDown) { 34 | size_t z = parent.tail[ch]->read(buf.data(), parent.N); 35 | if (z == 0) break; 36 | 37 | unique_lock lock(mtx); 38 | outQueue.write(buf.data(), z); 39 | } 40 | 41 | while(parent.tail[ch]->read(buf.data(), parent.N)) ; 42 | 43 | unique_lock lock(mtx); 44 | finished = true; 45 | condVar.notify_all(); 46 | } 47 | 48 | public: 49 | Outlet(Soxifier& parent_, int ch_) : parent(parent_), ch(ch_) {} 50 | 51 | bool atEnd() { 52 | unique_lock lock(mtx); 53 | return inQueue.size() == 0 && parent.isDraining(); 54 | } 55 | 56 | size_t read(INTYPE *ptr, size_t n) { 57 | unique_lock lock(mtx); 58 | 59 | while(!(inQueue.size() != 0 || parent.isDraining())) condVar.wait(lock); 60 | 61 | size_t z = inQueue.read(ptr, min(n, inQueue.size())); 62 | 63 | if (inQueue.size() == 0) condVar.notify_all(); 64 | 65 | return z; 66 | } 67 | 68 | friend Soxifier; 69 | }; 70 | 71 | // 72 | 73 | enum { INIT, CLAMPED, STARTED, DRAINING, STOPPED } state = INIT; 74 | 75 | const unsigned nch; 76 | const size_t N; 77 | bool shuttingDown = false; 78 | 79 | WavFormat format; 80 | vector> outlet; 81 | vector>> tail; 82 | 83 | size_t collectOutput(OUTTYPE *obuf, size_t z) { 84 | for(unsigned ch=0;chmtx); 86 | z = min(z, outlet[ch]->outQueue.size()); 87 | } 88 | 89 | vector buf(z); 90 | 91 | for(unsigned ch=0;chmtx); 94 | outlet[ch]->outQueue.read(buf.data(), z); 95 | } 96 | for(size_t i=0;i(*this, ch)); 109 | } 110 | 111 | ~Soxifier() { 112 | { 113 | shuttingDown = true; 114 | 115 | for(unsigned c=0;cmtx); 117 | outlet[c]->condVar.notify_all(); 118 | } 119 | } 120 | 121 | for(unsigned ch=0;chth) outlet[ch]->th->join(); 123 | } 124 | } 125 | 126 | shared_ptr> getOutlet(uint32_t channel) { 127 | if (channel >= outlet.size()) throw(runtime_error("Soxifier::getOutlet channel too large")); 128 | return outlet[channel]; 129 | } 130 | 131 | void clamp(const vector>> &in_) { 132 | if (state != INIT) throw(runtime_error("Soxifier::clamp state != INIT")); 133 | tail = in_; 134 | state = CLAMPED; 135 | } 136 | 137 | WavFormat getFormat() { return format; } 138 | 139 | uint32_t getNChannels() const { return format.channels; } 140 | 141 | // 142 | 143 | void start(const WavFormat &format_) { 144 | if (state != CLAMPED) throw(runtime_error("Soxifier::start state != CLAMPED")); 145 | if (format_.channels != nch) throw(runtime_error("Soxifier::start format.channels != nch")); 146 | 147 | format = format_; 148 | 149 | for(unsigned ch=0;chth = make_shared(&shibatch::Soxifier::Outlet::thEntry, outlet[ch]); 151 | 152 | state = STARTED; 153 | } 154 | 155 | void flow(const INTYPE *ibuf, OUTTYPE *obuf, size_t *inframe, size_t *onframe) { 156 | if (state != STARTED && state != DRAINING) throw(runtime_error("Soxifier::flow state != STARTED")); 157 | 158 | size_t ilen = *inframe, olen = *onframe; 159 | 160 | { 161 | size_t z = collectOutput(obuf, olen); 162 | olen -= z; 163 | obuf += z * nch; 164 | } 165 | 166 | for(unsigned c=0;c v(ilen); 168 | for(size_t i=0;imtx); 171 | outlet[c]->inQueue.write(std::move(v)); 172 | outlet[c]->condVar.notify_all(); 173 | } 174 | 175 | for(unsigned c=0;cmtx); 177 | while(outlet[c]->inQueue.size() != 0) outlet[c]->condVar.wait(lock); 178 | } 179 | 180 | { 181 | size_t z = collectOutput(obuf, olen); 182 | olen -= z; 183 | obuf += z * nch; 184 | } 185 | 186 | *onframe = *onframe - olen; 187 | } 188 | 189 | void drain(OUTTYPE *obuf, size_t *onframe) { 190 | if (state != STARTED && state != DRAINING) throw(runtime_error("Soxifier::drain state != STARTED && state != DRAINING")); 191 | 192 | if (state != DRAINING) { 193 | state = DRAINING; 194 | 195 | for(unsigned c=0;cmtx); 197 | outlet[c]->condVar.notify_all(); 198 | while(!outlet[c]->finished) outlet[c]->condVar.wait(lock); 199 | } 200 | } 201 | 202 | size_t z = 0; 203 | flow(nullptr, obuf, &z, onframe); 204 | } 205 | 206 | void stop() { 207 | if (state != STARTED && state != DRAINING) throw(runtime_error("Soxifier::stop state != STARTED && state != DRAINING")); 208 | 209 | state = STOPPED; 210 | 211 | for(unsigned c=0;cmtx); 213 | outlet[c]->condVar.notify_all(); 214 | } 215 | } 216 | }; 217 | 218 | static const uint64_t MAGIC = 0x8046b5efb58216fcULL; 219 | 220 | mutex mtxErrorString; 221 | unordered_map errorString; 222 | } 223 | 224 | using namespace shibatch; 225 | 226 | struct ssrc_soxr { 227 | uint64_t magic = MAGIC; 228 | ssrc_soxr_datatype_t itype, otype; 229 | double delay; 230 | 231 | // 232 | 233 | double input_rate, output_rate; 234 | unsigned num_channels; 235 | ssrc_soxr_io_spec_t iospec; 236 | ssrc_soxr_quality_spec_t qspec; 237 | ssrc_soxr_runtime_spec_t rtspec; 238 | 239 | // 240 | 241 | shared_ptr> f32f32; 242 | shared_ptr> f64f64; 243 | }; 244 | 245 | ssrc_soxr_io_spec_t ssrc_soxr_io_spec(ssrc_soxr_datatype_t itype, ssrc_soxr_datatype_t otype) { 246 | ssrc_soxr_io_spec_t ret; 247 | ret.itype = itype; 248 | ret.otype = otype; 249 | ret.ditherType = 0; 250 | ret.flags = 0; 251 | return ret; 252 | } 253 | 254 | ssrc_soxr_quality_spec_t ssrc_soxr_quality_spec(unsigned long recipe, unsigned long flags) { 255 | ssrc_soxr_quality_spec_t ret; 256 | ret.flags = flags; 257 | switch(recipe) { 258 | case SSRC_SOXR_QQ: ret.log2dftfilterlen = 10; ret.aa = 96; ret.guard = 1; ret.dataType = SSRC_SOXR_FLOAT32; break; 259 | case SSRC_SOXR_LQ: ret.log2dftfilterlen = 12; ret.aa = 96; ret.guard = 1; ret.dataType = SSRC_SOXR_FLOAT32; break; 260 | case SSRC_SOXR_MQ: ret.log2dftfilterlen = 14; ret.aa = 145; ret.guard = 2; ret.dataType = SSRC_SOXR_FLOAT32; break; 261 | case SSRC_SOXR_HQ: ret.log2dftfilterlen = 15; ret.aa = 145; ret.guard = 4; ret.dataType = SSRC_SOXR_FLOAT64; break; 262 | case SSRC_SOXR_VHQ: ret.log2dftfilterlen = 16; ret.aa = 170; ret.guard = 4; ret.dataType = SSRC_SOXR_FLOAT64; break; 263 | default: 264 | cerr << "ssrc_soxr_quality_spec : Unknown recipe" << endl; 265 | abort(); 266 | } 267 | return ret; 268 | } 269 | 270 | struct ssrc_soxr *ssrc_soxr_create(double input_rate, double output_rate, unsigned num_channels, 271 | ssrc_soxr_error_t *eptr, 272 | ssrc_soxr_io_spec_t const *iospec, 273 | ssrc_soxr_quality_spec_t const *qspec, 274 | ssrc_soxr_runtime_spec_t const *rtspec) { 275 | if (rint(input_rate) != input_rate || rint(output_rate) != output_rate) { 276 | *eptr = "ssrc_soxr_create : Unsupported sample rate"; 277 | return nullptr; 278 | } 279 | if (num_channels == 0) { 280 | *eptr = "ssrc_soxr_create : Unsupported num_channels"; 281 | return nullptr; 282 | } 283 | if (!iospec || iospec->itype != SSRC_SOXR_FLOAT32 || iospec->otype != SSRC_SOXR_FLOAT32 || iospec->ditherType != 0) { 284 | *eptr = "ssrc_soxr_create : Unsupported iospec"; 285 | return nullptr; 286 | } 287 | 288 | ssrc_soxr *thiz = nullptr; 289 | 290 | try { 291 | thiz = new ssrc_soxr(); 292 | 293 | // 294 | 295 | thiz->itype = iospec->itype; 296 | thiz->otype = iospec->otype; 297 | 298 | thiz->input_rate = input_rate; 299 | thiz->output_rate = output_rate; 300 | thiz->num_channels = num_channels; 301 | thiz->iospec = *iospec; 302 | 303 | ssrc_soxr_quality_spec_t q = { 14, 145, 2, SSRC_SOXR_FLOAT32, 0 }; 304 | if (qspec) q = *qspec; 305 | thiz->qspec = q; 306 | 307 | ssrc_soxr_runtime_spec_t rt = { 1 }; 308 | if (rtspec) rt = *rtspec; 309 | thiz->rtspec = rt; 310 | 311 | // 312 | 313 | auto xifier = make_shared>(num_channels); 314 | 315 | thiz->f32f32 = xifier; 316 | 317 | vector>> out(num_channels); 318 | 319 | for(unsigned i=0;i>(xifier->getOutlet(i), (int64_t)input_rate, (int64_t)output_rate, q.log2dftfilterlen, q.aa, q.guard, 1.0, 321 | (q.flags & SSRC_SOXR_MINIMUM_PHASE) == SSRC_SOXR_MINIMUM_PHASE, 0, rt.num_threads == 0); 322 | thiz->delay = ssrc->getDelay(); 323 | out[i] = ssrc; 324 | } 325 | 326 | xifier->clamp(out); 327 | 328 | xifier->start(WavFormat(WavFormat::IEEE_FLOAT, num_channels, output_rate, 32)); 329 | 330 | return thiz; 331 | } catch(exception &ex) { 332 | if (thiz) delete thiz; 333 | 334 | unique_lock lock(mtxErrorString); 335 | errorString[this_thread::get_id()] = ex.what(); 336 | *eptr = errorString[this_thread::get_id()].c_str(); 337 | 338 | return nullptr; 339 | } 340 | } 341 | 342 | ssrc_soxr_error_t ssrc_soxr_process(struct ssrc_soxr *thiz, 343 | void const *in, size_t ilen, size_t *idone, 344 | void *out, size_t olen, size_t *odone) { 345 | if (thiz->magic != MAGIC) { 346 | cerr << "ssrc_soxr_process : thiz->magic != MAGIC" << endl; 347 | abort(); 348 | } 349 | 350 | try { 351 | auto xifier = thiz->f32f32; 352 | 353 | if (in) { 354 | size_t isamp = ilen, osamp = olen; 355 | 356 | xifier->flow((const float *)in, (float *)out, &isamp, &osamp); 357 | 358 | if (idone) *idone = isamp; 359 | if (odone) *odone = osamp; 360 | } else { 361 | size_t osamp = olen; 362 | 363 | xifier->drain((float *)out, &osamp); 364 | 365 | if (odone) *odone = osamp; 366 | } 367 | 368 | return nullptr; 369 | } catch(exception &ex) { 370 | unique_lock lock(mtxErrorString); 371 | errorString[this_thread::get_id()] = ex.what(); 372 | 373 | return errorString[this_thread::get_id()].c_str(); 374 | } 375 | } 376 | 377 | ssrc_soxr_error_t ssrc_soxr_clear(struct ssrc_soxr *thiz) { 378 | if (thiz->magic != MAGIC) { 379 | cerr << "ssrc_soxr_clear : thiz->magic != MAGIC" << endl; 380 | abort(); 381 | } 382 | 383 | try { 384 | auto xifier = make_shared>(thiz->num_channels); 385 | 386 | thiz->f32f32 = xifier; 387 | 388 | vector>> out(thiz->num_channels); 389 | 390 | for(unsigned i=0;inum_channels;i++) { 391 | auto ssrc = make_shared>(xifier->getOutlet(i), (int64_t)thiz->input_rate, (int64_t)thiz->output_rate, 392 | thiz->qspec.log2dftfilterlen, thiz->qspec.aa, thiz->qspec.guard, 1.0, 393 | false, 0, thiz->rtspec.num_threads == 0); 394 | thiz->delay = ssrc->getDelay(); 395 | out[i] = ssrc; 396 | } 397 | 398 | xifier->clamp(out); 399 | 400 | xifier->start(WavFormat(WavFormat::IEEE_FLOAT, thiz->num_channels, thiz->output_rate, 32)); 401 | } catch(exception &ex) { 402 | } 403 | 404 | return nullptr; 405 | } 406 | 407 | void ssrc_soxr_delete(struct ssrc_soxr *thiz) { 408 | if (thiz->magic != MAGIC) { 409 | cerr << "ssrc_soxr_delete : thiz->magic != MAGIC" << endl; 410 | abort(); 411 | } 412 | thiz->magic = 0; 413 | delete thiz; 414 | } 415 | 416 | ssrc_soxr_error_t ssrc_soxr_oneshot(double in_rate, double out_rate, unsigned num_channels, 417 | void const * in, size_t in_len, size_t * in_rd, 418 | void * out, size_t out_len, size_t * out_wr, 419 | ssrc_soxr_io_spec_t const * io_spec, 420 | ssrc_soxr_quality_spec_t const * q_spec, 421 | ssrc_soxr_runtime_spec_t const * runtime_spec) { 422 | if (in_rd) *in_rd = 0; 423 | if (out_wr) *out_wr = 0; 424 | 425 | ssrc_soxr_error_t error; 426 | ssrc_soxr *soxr = ssrc_soxr_create(in_rate, out_rate, num_channels, &error, io_spec, q_spec, runtime_spec); 427 | 428 | if (!soxr) return error; 429 | 430 | size_t total_frames_read = 0; 431 | size_t total_frames_written = 0; 432 | 433 | char * current_out_ptr = (char *)out; 434 | size_t remaining_out_capacity_frames = out_len; 435 | 436 | unsigned bytes_per_frame = 0; 437 | switch(io_spec->otype) { 438 | case SSRC_SOXR_FLOAT32_I: bytes_per_frame = sizeof(float) * num_channels; break; 439 | case SSRC_SOXR_FLOAT64_I: bytes_per_frame = sizeof(double) * num_channels; break; 440 | default: return "Unsupported otype"; 441 | } 442 | 443 | size_t frames_consumed = 0; 444 | size_t frames_produced = 0; 445 | 446 | if (in && in_len > 0) { 447 | error = ssrc_soxr_process(soxr, in, in_len, &frames_consumed, 448 | current_out_ptr, remaining_out_capacity_frames, &frames_produced); 449 | 450 | total_frames_read += frames_consumed; 451 | total_frames_written += frames_produced; 452 | 453 | current_out_ptr += frames_produced * bytes_per_frame; 454 | if (remaining_out_capacity_frames >= frames_produced) { 455 | remaining_out_capacity_frames -= frames_produced; 456 | } else { 457 | remaining_out_capacity_frames = 0; 458 | } 459 | } 460 | 461 | if (!error) { 462 | do { 463 | if (remaining_out_capacity_frames == 0) break; 464 | 465 | error = ssrc_soxr_process(soxr, NULL, 0, NULL, 466 | current_out_ptr, remaining_out_capacity_frames, &frames_produced); 467 | 468 | total_frames_written += frames_produced; 469 | current_out_ptr += frames_produced * bytes_per_frame; 470 | if (remaining_out_capacity_frames >= frames_produced) { 471 | remaining_out_capacity_frames -= frames_produced; 472 | } else { 473 | remaining_out_capacity_frames = 0; 474 | } 475 | } while (!error && frames_produced > 0); 476 | } 477 | 478 | ssrc_soxr_delete(soxr); 479 | 480 | if (in_rd) *in_rd = total_frames_read; 481 | if (out_wr) *out_wr = total_frames_written; 482 | 483 | return error; 484 | } 485 | 486 | double ssrc_soxr_delay(struct ssrc_soxr *thiz) { 487 | if (thiz->magic != MAGIC) { 488 | cerr << "ssrc_soxr_delay : thiz->magic != MAGIC" << endl; 489 | abort(); 490 | } 491 | return thiz->delay; 492 | } 493 | -------------------------------------------------------------------------------- /src/cli/scsa.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | #include "shibatch/ssrc.hpp" 16 | #include "dr_wav.hpp" 17 | 18 | #ifndef M_PI 19 | #define M_PI 3.1415926535897932384626433832795028842 20 | #endif 21 | 22 | using namespace std; 23 | 24 | namespace { 25 | struct SpectrumCheckItem { 26 | const double lf, hf; 27 | const char op; 28 | const double thres; 29 | 30 | SpectrumCheckItem(double lf_, double hf_, char op_, double thres_) : 31 | lf(lf_), hf(hf_), op(op_), thres(thres_) {} 32 | 33 | #if 0 34 | friend ostream& operator<<(ostream& os, const SpectrumCheckItem& si) { 35 | return os << "[" << si.lf << "Hz ... " << si.hf << "Hz " << si.op << " " << si.thres << "dB]"; 36 | } 37 | #endif 38 | }; 39 | 40 | // 41 | 42 | class Color { 43 | public: 44 | const double r, g, b; 45 | 46 | Color(double r_, double g_, double b_) : r(r_), g(g_), b(b_) {} 47 | 48 | friend ostream& operator<<(ostream& os, const Color& c) { 49 | vector s(10); 50 | unsigned ri = max(min((int)rint(c.r * 0xff), 0xff), 0); 51 | unsigned gi = max(min((int)rint(c.g * 0xff), 0xff), 0); 52 | unsigned bi = max(min((int)rint(c.b * 0xff), 0xff), 0); 53 | snprintf(s.data(), s.size(), "#%02x%02x%02x", ri, gi, bi); 54 | return os << string(s.data()); 55 | } 56 | }; 57 | 58 | class StrokeStyle { 59 | public: 60 | const Color color; 61 | const double width; 62 | 63 | StrokeStyle(const Color& color_, double width_) : color(color_), width(width_) {} 64 | 65 | friend ostream& operator<<(ostream& os, const StrokeStyle& ss) { 66 | return os << "stroke:" << ss.color << "; stroke-width:" << ss.width << "; "; 67 | } 68 | }; 69 | 70 | class FillStyle { 71 | public: 72 | const Color color; 73 | const double opacity; 74 | 75 | FillStyle(const Color& color_, double opacity_ = 1) : color(color_), opacity(opacity_) {} 76 | 77 | friend ostream& operator<<(ostream& os, const FillStyle& fs) { 78 | os << "fill:" << fs.color << "; "; 79 | if (fs.opacity != 1.0) os << "fill-opacity:" << fs.opacity << "; "; 80 | return os; 81 | } 82 | }; 83 | 84 | class Font { 85 | public: 86 | const string family; 87 | const double size; 88 | const string weight, style; 89 | 90 | Font(double size_, const string &family_="sans-serif", const string &weight_="normal", const string &style_="normal") : 91 | family(family_), size(size_), weight(weight_), style(style_) {} 92 | 93 | friend ostream& operator<<(ostream& os, const Font& f) { 94 | os << "font-family:" << f.family << "; font-size:" << f.size << "; "; 95 | os << "font-weight:" << f.weight << "; font-style:" << f.style << "; "; 96 | return os; 97 | } 98 | }; 99 | 100 | class TextAnchor { 101 | public: 102 | const string textAnchor, dominantBaseline; 103 | 104 | TextAnchor(const string& textAnchor_, const string& dominantBaseline_) : 105 | textAnchor(textAnchor_), dominantBaseline(dominantBaseline_) {} 106 | 107 | friend ostream& operator<<(ostream& os, const TextAnchor& ta) { 108 | os << "text-anchor=\"" << ta.textAnchor << "\" "; 109 | os << "dominant-baseline=\"" << ta.dominantBaseline << "\" "; 110 | return os; 111 | } 112 | }; 113 | 114 | static const TextAnchor TOP {"middle", "hanging"}; 115 | static const TextAnchor BOTTOM {"middle", "text-bottom"}; 116 | static const TextAnchor CENTER {"middle", "central"}; 117 | static const TextAnchor RIGHT {"end", "middle"}; 118 | static const TextAnchor LEFT {"start", "middle"}; 119 | static const TextAnchor BOTTOMLEFT {"start", "text-bottom"}; 120 | static const TextAnchor BOTTOMRIGHT {"end", "text-bottom"}; 121 | static const TextAnchor TOPLEFT {"start", "hanging"}; 122 | static const TextAnchor TOPRIGHT {"end", "hanging"}; 123 | 124 | class SVGCanvas { 125 | ostream &os; 126 | public: 127 | const double width, height; 128 | 129 | SVGCanvas(ostream &os_, double width_, double height_) : 130 | os(os_), width(width_), height(height_) { 131 | os << "" << endl; 132 | os << "" << endl; 133 | os << "" << endl; 135 | os << "" << endl; 136 | os << "" << endl; 137 | } 138 | 139 | ~SVGCanvas() { 140 | os << "" << endl; 141 | } 142 | 143 | void drawRect(double x, double y, double w, double h, const FillStyle& fs, const StrokeStyle& ss, const string& clipID = "") { 144 | os << "" << endl; 148 | } 149 | 150 | void drawRect(double x, double y, double w, double h, const FillStyle& fs) { 151 | os << "" << endl; 153 | } 154 | 155 | void drawRect(double x, double y, double w, double h, const StrokeStyle& ss=StrokeStyle(Color(0,0,0), 0), const string& clipID = "") { 156 | os << "" << endl; 160 | } 161 | 162 | void drawText(double x, double y, const string &str, const FillStyle &fs=Color(1, 1, 1), const Font& f=Font(10), const TextAnchor &ta=BOTTOMLEFT) { 163 | os << ""; 165 | os << str << "" << endl; 166 | } 167 | 168 | void drawLine(double x1, double y1, double x2, double y2, const StrokeStyle& ss) { 169 | os << "" << endl; 171 | } 172 | 173 | void defineClip(string id, double x, double y, double w, double h) { 174 | os << " " << endl; 175 | } 176 | 177 | void drawPolyline(const vector> points, const StrokeStyle& ss) { 178 | os << "" << endl; 181 | } 182 | 183 | void drawPolyline(const vector> points, const StrokeStyle& ss, const string& clipID = "") { 184 | os << "" << endl; 189 | } 190 | }; 191 | 192 | class SpectrumDisplay { 193 | const double width, height, topMargin = 30, bottomMargin = 40, leftMargin = 60, rightMargin = 20; 194 | const double rangekHz, rangedB, intervalX, intervalY, gw, gh; 195 | SVGCanvas c; 196 | 197 | public: 198 | SpectrumDisplay(ostream &os_, double width_, double height_, double rangekHz_, double rangedB_, 199 | double intervalX_, double intervalY_, bool logFreq_) : 200 | width(width_), height(height_), rangekHz(rangekHz_), rangedB(rangedB_), 201 | intervalX(intervalX_), intervalY(intervalY_), gw(width - leftMargin - rightMargin), gh(height - topMargin - bottomMargin), 202 | c(os_, width_, height_) { 203 | 204 | c.drawRect(0, 0, width, height, StrokeStyle(Color(0,0,0),1)); 205 | 206 | c.drawRect(leftMargin, topMargin, gw, gh, StrokeStyle(Color(.5,.5,.5),1)); 207 | 208 | for(double y=intervalY;y> data, const StrokeStyle ss = StrokeStyle(Color(0,0,0), 1)) { 232 | vector> proj(data.size()); 233 | 234 | double m = -10000; 235 | for(size_t i = 0;i items, const FillStyle fs = FillStyle(Color(0,0,0), 0.1)) { 245 | for(auto e : items) { 246 | double l = leftMargin + gw * e.lf / (rangekHz * 1000), r = leftMargin + gw * e.hf / (rangekHz * 1000); 247 | double y = topMargin + gh * -e.thres / rangedB; 248 | if (e.op == '>') { 249 | c.drawRect(l, y, r-l, gh + topMargin - y, fs, StrokeStyle(Color(0,0,0), 1), "graph"); 250 | } else if (e.op == '<') { 251 | c.drawRect(l, topMargin, r-l, y - topMargin, fs, StrokeStyle(Color(0,0,0), 1), "graph"); 252 | } else if (e.op == '^') { 253 | c.drawRect(l, topMargin, r-l, y - topMargin, StrokeStyle(Color(0,0,0), 1), "graph"); 254 | } 255 | } 256 | } 257 | }; 258 | 259 | vector loadCheckItems(const string& fn) { 260 | vector ret; 261 | 262 | ifstream f(fn); 263 | if (!f.is_open()) throw(runtime_error(("Could not open file " + fn).c_str())); 264 | 265 | string line; 266 | int ln = 1; 267 | while(getline(f, line)) { 268 | double lf, hf, thres; 269 | char c; 270 | if (line.find_first_not_of(' ') == string::npos || line[0] == '#') { ln++; continue; } 271 | if (sscanf(line.c_str(), "%lf %lf %c %lf", &lf, &hf, &c, &thres) == 4) { 272 | if (!(c == '>' || c == '<' || c == '^')) throw(runtime_error((fn + ":" + to_string(ln) + " : error <>^").c_str())); 273 | ret.push_back(SpectrumCheckItem(lf, hf, c, thres)); 274 | } else throw(runtime_error((fn + ":" + to_string(ln) + " : error").c_str())); 275 | ln++; 276 | } 277 | f.close(); 278 | 279 | return ret; 280 | } 281 | 282 | class SpectrumAnalyzer { 283 | const double fs; 284 | const size_t dftlen; 285 | const vector window; 286 | 287 | SleefDFT *dft; 288 | double *dftbuf = nullptr; 289 | public: 290 | SpectrumAnalyzer(double fs_, unsigned log2dftlen_) : 291 | fs(fs_), dftlen(1ULL << log2dftlen_), window(createWindow(dftlen)) { 292 | dft = SleefDFT_double_init1d(dftlen, NULL, NULL, SLEEF_MODE_REAL | SLEEF_MODE_ALT | SLEEF_MODE_FORWARD | SLEEF_MODE_NO_MT); 293 | dftbuf = (double *)Sleef_malloc(dftlen * sizeof(double)); 294 | } 295 | 296 | ~SpectrumAnalyzer() { 297 | Sleef_free(dftbuf); 298 | SleefDFT_dispose(dft); 299 | } 300 | 301 | virtual vector createWindow(size_t N) { 302 | // 7-term Blackman-Harris 303 | // https://dsp.stackexchange.com/questions/51095/seven-term-blackman-harris-window 304 | static const double coef[] = { 305 | .27105140069342, -0.43329793923448, 0.21812299954311, -0.06592544638803, 306 | 0.01081174209837, -0.00077658482522, 0.00001388721735 307 | }; 308 | 309 | vector v(N); 310 | 311 | for(size_t n=0;n> doAnalysis(const double* data) { 320 | for(size_t i=0;i> ret(dftlen/2); 325 | 326 | for(unsigned i=0;i& checkItems, vector> analysis) { 334 | for(auto p : analysis) { 335 | for(auto it : checkItems) { 336 | if (p.first < it.lf || it.hf < p.first) continue; 337 | if (it.op == '>') { 338 | if (!(p.second > it.thres)) return false; 339 | } else if (it.op == '<') { 340 | if (!(p.second < it.thres)) return false; 341 | } 342 | } 343 | } 344 | for(auto it : checkItems) { 345 | if (it.op != '^') continue; 346 | double peak = -10000; 347 | for(auto p : analysis) { 348 | if (p.first < it.lf || it.hf < p.first) continue; 349 | peak = max(peak, p.second); 350 | } 351 | if (peak < it.thres) return false; 352 | } 353 | return true; 354 | } 355 | 356 | bool check(const vector& checkItems, const double* data) { 357 | return checkCompliance(checkItems, doAnalysis(data)); 358 | } 359 | }; 360 | } 361 | 362 | using namespace dr_wav; 363 | 364 | void showUsage(const string& argv0, const string& mes = "") { 365 | cerr << "Shibatch command-line spectrum analyzer (accompanying SSRC Version " << ssrc::versionString() << ")" << endl; 366 | cerr << endl; 367 | cerr << "usage: " << argv0 << " [] " << endl; 368 | cerr << endl; 369 | cerr << "options : --log2dftlen " << endl; 370 | cerr << " --check " << endl; 371 | cerr << " --svgout " << endl; 372 | cerr << endl; 373 | cerr << "If you like this tool, visit https://github.com/shibatch/ssrc and give it a star." << endl; 374 | cerr << endl; 375 | 376 | if (mes != "") cerr << "Error : " << mes << endl; 377 | 378 | exit(-1); 379 | } 380 | 381 | bool analyzeAndCheck(WavFile &wav, SpectrumAnalyzer &ana, vector &checkItems, size_t dftlen, size_t pos, const string &svgoutfn) { 382 | const unsigned nch = wav.getNChannels(); 383 | 384 | wav.seek(pos - dftlen / 2); 385 | vector wavbuf(dftlen * nch); 386 | wav.readPCM(wavbuf.data(), dftlen); 387 | 388 | vector chbuf(dftlen); 389 | 390 | bool compliant = true; 391 | 392 | if (checkItems.size() != 0) { 393 | for(unsigned ch = 0;ch < nch;ch++) { 394 | for(size_t i=0;i= argc) showUsage(argv[0]); 434 | char *p; 435 | log2dftlen = strtoul(argv[nextArg+1], &p, 0); 436 | if (p == argv[nextArg+1] || *p) 437 | showUsage(argv[0], "A non-negative integer is expected after --log2dftlen."); 438 | nextArg++; 439 | } else if (string(argv[nextArg]) == "--check") { 440 | if (nextArg+1 >= argc) showUsage(argv[0], "Specify a check file name after --check"); 441 | checkfn = argv[nextArg+1]; 442 | nextArg++; 443 | } else if (string(argv[nextArg]) == "--svgout") { 444 | if (nextArg+1 >= argc) showUsage(argv[0], "Specify a SVG file name after --svgout"); 445 | svgoutfn = argv[nextArg+1]; 446 | nextArg++; 447 | } else if (string(argv[nextArg]) == "--debug") { 448 | debug = true; 449 | } else if (string(argv[nextArg]).substr(0, 2) == "--") { 450 | showUsage(argv[0], string("Unrecognized option : ") + argv[nextArg]); 451 | } else { 452 | break; 453 | } 454 | } 455 | 456 | if (nextArg < argc) srcfn = argv[nextArg]; else showUsage(argv[0], "Specify a WAV file name."); 457 | nextArg++; 458 | 459 | if (nextArg < argc) { 460 | char *p; 461 | start = strtoull(argv[nextArg], &p, 0); 462 | if (p == argv[nextArg] || *p) 463 | showUsage(argv[0], "Specify the position for checking."); 464 | nextArg++; 465 | } else showUsage(argv[0], "Specify the position for checking."); 466 | 467 | if (nextArg < argc) { 468 | char *p; 469 | end = strtoull(argv[nextArg], &p, 0); 470 | if (p == argv[nextArg] || *p) 471 | showUsage(argv[0], "Specify the ending position for checking."); 472 | nextArg++; 473 | } 474 | 475 | if (nextArg < argc) { 476 | char *p; 477 | interval = strtoull(argv[nextArg], &p, 0); 478 | if (p == argv[nextArg] || *p) 479 | showUsage(argv[0], "Specify the interval for checking."); 480 | nextArg++; 481 | } 482 | 483 | if (nextArg != argc) showUsage(argv[0], "Extra arguments detected."); 484 | 485 | const size_t dftlen = 1ULL << log2dftlen; 486 | 487 | if (debug) { 488 | cerr << "log2dftlen = " << log2dftlen << endl; 489 | cerr << "dftlen = " << dftlen << endl; 490 | cerr << "srcfn = " << srcfn << endl; 491 | cerr << "start = " << start << endl; 492 | cerr << "end = " << end << endl; 493 | cerr << "interval = " << interval << endl; 494 | cerr << "checkfn = " << checkfn << endl; 495 | cerr << "svgoutfn = " << svgoutfn << endl; 496 | } 497 | 498 | if (end != 0 && end <= start) showUsage(argv[0], "The ending position must be greater than the starting position."); 499 | if (end != 0 && interval == 0) showUsage(argv[0], "You must specify an interval."); 500 | if (checkfn == "" && end != 0) showUsage(argv[0], "You must specify a check file."); 501 | if (checkfn == "" && svgoutfn == "") showUsage(argv[0], "You must specify an SVG file name."); 502 | if (start < dftlen / 2) showUsage(argv[0], "Start position must be greater than dftlen/2."); 503 | 504 | try { 505 | vector checkItems; 506 | 507 | if (checkfn != "") checkItems = loadCheckItems(checkfn); 508 | 509 | const size_t dftlen = 1ULL << log2dftlen; 510 | 511 | WavFile wav(srcfn); 512 | 513 | if (start > wav.getNFrames() - dftlen / 2) showUsage(argv[0], "Start position must be smaller than (nFrames - dftlen/2)."); 514 | 515 | SpectrumAnalyzer ana(wav.getSampleRate(), log2dftlen); 516 | 517 | if (end == 0) { 518 | if (!analyzeAndCheck(wav, ana, checkItems, dftlen, start, svgoutfn)) { 519 | cerr << "NG" << endl; 520 | return -1; 521 | } 522 | 523 | return 0; 524 | } else { 525 | for(size_t pos = start;pos <= end;pos += interval) { 526 | if (!analyzeAndCheck(wav, ana, checkItems, dftlen, pos, svgoutfn)) { 527 | cerr << "NG" << endl; 528 | return -1; 529 | } 530 | } 531 | 532 | return 0; 533 | } 534 | } catch(exception &ex) { 535 | cerr << argv[0] << " Error : " << ex.what() << endl; 536 | return -1; 537 | } 538 | } 539 | --------------------------------------------------------------------------------