diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 9e4f304..e70ef25 100755 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,18 +1,26 @@ cmake_minimum_required(VERSION 3.1...3.18) -project(StftPitchShiftExample) +project(StftPitchShiftExamples) +# basic example of single file processing add_executable(example "${CMAKE_CURRENT_LIST_DIR}/example.cpp") -# at least C++ 20 is mandatory -target_compile_features(example PRIVATE cxx_std_20) +# advanced example of continuous stream processing +add_executable(realtime "${CMAKE_CURRENT_LIST_DIR}/realtime.cpp") -# just the case of default build e.g. if LibStftPitchShift.cmake is already included -target_link_libraries(example stftpitchshift) +foreach(name example realtime) -# if installed via ppa:jurihock/stftpitchshift on Ubuntu -# target_link_libraries(example stftpitchshift) + # at least C++ 20 is mandatory + target_compile_features(${name} PRIVATE cxx_std_20) -# if installed via vcpkg -# find_package(stftpitchshift CONFIG REQUIRED) -# target_link_libraries(example stftpitchshift::stftpitchshift) + # just the case of default build e.g. if LibStftPitchShift.cmake is already included + target_link_libraries(${name} stftpitchshift) + + # if installed via ppa:jurihock/stftpitchshift on Ubuntu + # target_link_libraries(${name} stftpitchshift) + + # if installed via vcpkg + # find_package(stftpitchshift CONFIG REQUIRED) + # target_link_libraries(${name} stftpitchshift::stftpitchshift) + +endforeach() diff --git a/examples/realtime.cpp b/examples/realtime.cpp new file mode 100644 index 0000000..27a89a4 --- /dev/null +++ b/examples/realtime.cpp @@ -0,0 +1,125 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace stftpitchshift; + +// basic parameters +// - samplerate as required +// - overlap at least 4 +const double samplerate = 44100; +const size_t overlap = 4; + +// analysis and synthesis window size +// power of two each of them +// - for best quality: analysis = synthesis +// - for lower latency: analysis > synthesis +// - synthesis window size corresponds to the audio frame size and vice versa +const std::tuple framesize = { 1024, 1024 }; + +// hop size refers to the synthesis window size +const size_t hopsize = std::get<1>(framesize) / overlap; + +// delay buffers for input and output frames +struct { std::vector input, output; } buffer; + +// modules required for pitch shifting +std::shared_ptr> stft; +std::shared_ptr> core; + +// example of an IO callback +void audio_interface_callback(std::span input, std::span output); + +int main() +{ + // prepare for audio stream processing + + const size_t total_buffer_size = + std::get<0>(framesize) + + std::get<1>(framesize); + + buffer.input.resize(total_buffer_size); + buffer.output.resize(total_buffer_size); + + stft = std::make_shared>(framesize, hopsize); + core = std::make_shared>(framesize, hopsize, samplerate); + + // set pitch shifting parameters as required + + core->factors({ 1 }); + core->quefrency(0 * 1e-3); + core->distortion(1); + core->normalization(false); + + // simulate continuous audio stream processing + + const size_t n = static_cast(samplerate); + const size_t m = std::get<1>(framesize); + + std::vector x(n), y(n); + + for (size_t i = 0; (i + m) < n; i += m) + { + std::cout << "processing audio frame " << (i / m + 1) << " of " << (n / m) << std::endl; + + std::span input = std::span(x.data(), m); + std::span output = std::span(y.data(), m); + + audio_interface_callback(input, output); + } + + return 0; +} + +void audio_interface_callback(std::span input, std::span output) +{ + const auto analysis_window_size = std::get<0>(framesize); + const auto synthesis_window_size = std::get<1>(framesize); + + // this is the expected input and output frame size + assert(input.size() == synthesis_window_size); + assert(output.size() == synthesis_window_size); + + // shift input buffer + std::copy( + buffer.input.begin() + synthesis_window_size, + buffer.input.end(), + buffer.input.begin()); + + // copy new input samples + std::copy( + input.begin(), + input.end(), + buffer.input.begin() + analysis_window_size); + + // apply pitch shifting within the STFT routine + (*stft)(buffer.input, buffer.output, [&](std::span> dft) + { + core->shiftpitch(dft); + }); + + // copy new output samples back + std::copy( + buffer.output.begin() - synthesis_window_size + analysis_window_size, + buffer.output.end() - synthesis_window_size, + output.begin()); + + // shift output buffer + std::copy( + buffer.output.begin() + synthesis_window_size, + buffer.output.end(), + buffer.output.begin()); + + // prepare for the next callback + std::fill( + buffer.output.begin() + analysis_window_size, + buffer.output.end(), + 0); +}