From aaa17f9730c399b39458d03b90824363a97d1072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Micha=C3=ABl=20Celerier?= Date: Tue, 26 Nov 2024 11:21:58 -0500 Subject: [PATCH] examples: add an audio-to-osc example --- cmake/avendish.examples.cmake | 9 ++ examples/Advanced/Utilities/AudioToOSC.hpp | 165 +++++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 examples/Advanced/Utilities/AudioToOSC.hpp diff --git a/cmake/avendish.examples.cmake b/cmake/avendish.examples.cmake index aa5fada..095bcf3 100644 --- a/cmake/avendish.examples.cmake +++ b/cmake/avendish.examples.cmake @@ -285,6 +285,15 @@ foreach(theTarget ${OSSIA_EXAMPLES}) ) endforeach() +###### Advanced examples +avnd_make_all( + TARGET AudioToOSC + MAIN_FILE examples/Advanced/Utilities/AudioToOSC.hpp + MAIN_CLASS ad::AudioToOSC + C_NAME ad_audio_to_osc + LINK_LIBRARIES samplerate ossia::ossia +) + ###### Ports ## Essentia diff --git a/examples/Advanced/Utilities/AudioToOSC.hpp b/examples/Advanced/Utilities/AudioToOSC.hpp new file mode 100644 index 0000000..680d837 --- /dev/null +++ b/examples/Advanced/Utilities/AudioToOSC.hpp @@ -0,0 +1,165 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace ad +{ +struct resampler +{ + explicit resampler() noexcept + : src{src_new(SRC_SINC_BEST_QUALITY, 1, nullptr)} + , data(1024) + { + } + resampler(resampler&&) noexcept = default; + resampler& operator=(resampler&&) noexcept = default; + resampler(const resampler&) noexcept = delete; + resampler& operator=(const resampler&) noexcept = delete; + + ~resampler() { } + + using src_handle + = std::unique_ptr; + src_handle src; + moodycamel::BlockingReaderWriterCircularBuffer data; +}; + +struct osc_thread +{ + explicit osc_thread( + std::string host, uint16_t port, std::span> resamplers) + : m_config{.mode = conf::MIRROR, .version = conf::OSC1_1, .transport = ossia::net::udp_configuration{{.local = std::nullopt, .remote = ossia::net::send_socket_configuration{{host, port}}}}} + , m_device{ossia::net::make_osc_protocol(m_ctx, m_config), "P"} + , m_resamplers{resamplers.begin(), resamplers.end()} + , m_sema{0} + { + auto& root = m_device.get_root_node(); + for(int i = 0; i < resamplers.size(); i++) + { + auto channel + = ossia::create_parameter(root, fmt::format("/channel.{}", i), "float"); + m_channels.push_back(channel); + } + + m_thread = std::thread([this] { + while(!m_stop_flag.test(std::memory_order_acquire)) + { + for(int k = 0; k < m_resamplers.size(); k++) + { + auto& queue = m_resamplers[k]->data; + auto& osc = *m_channels[k]; + float sample{}; + if(queue.try_dequeue(sample)) + { + osc.push_value(sample); + } + } + } + m_sema.release(); + }); + } + + ~osc_thread() + { + m_stop_flag.test_and_set(std::memory_order_release); + m_sema.acquire(); + m_thread.join(); + } + + using conf = ossia::net::osc_protocol_configuration; + conf m_config; + + std::shared_ptr m_ctx + = std::make_shared(); + ossia::net::generic_device m_device{}; + std::vector m_channels; + + std::vector> m_resamplers; + + std::thread m_thread; + std::atomic_flag m_stop_flag = ATOMIC_FLAG_INIT; + std::binary_semaphore m_sema; +}; + +// The core audio object +struct AudioToOSC +{ +public: + halp_meta(name, "Audio to OSC") + halp_meta(c_name, "audio_to_osc") + halp_meta(author, "Jean-Michaƫl Celerier") + halp_meta(category, "Audio/Effects") + halp_meta(description, "Convert audio to OSC at a specific rate") + halp_meta(uuid, "e525a387-b2ce-43b1-83c6-e308a4d7fd2f") + + struct + { + halp::dynamic_audio_bus<"In", float> audio; + halp::knob_f32<"Frequency", halp::range{1., 1000., 401.2}> frequency; + } inputs; + + struct outputs + { + + } outputs; + + halp::setup s; + void prepare(halp::setup info) noexcept + { + s = info; + + m_osc.reset(); + m_resamplers.clear(); + for(int i = 0; i < info.input_channels; i++) + m_resamplers.push_back(std::make_shared()); + m_osc = std::make_shared("127.0.0.1", 1234, m_resamplers); + } + + void operator()(int frames) noexcept + { + if(s.rate <= 0.) + return; + + m_buffer.resize(4 * frames, boost::container::default_init); + + for(std::size_t i = 0; i < s.input_channels; ++i) + { + SRC_DATA data; + auto& src = this->m_resamplers[i]->src; + data.data_in = inputs.audio.samples[i]; + data.data_out = m_buffer.data(); + data.input_frames = frames; + data.output_frames = m_buffer.size(); + data.input_frames_used = 0; + data.output_frames_gen = 0; + data.src_ratio = inputs.frequency.value / s.rate; + data.end_of_input = 0; + + // Resample + src_process(src.get(), &data); + + // Put in ring buffer + auto& queue = m_resamplers[i]->data; + for(auto sample : std::span(m_buffer.data(), data.output_frames_gen)) + queue.try_enqueue(sample); + } + } + +private: + boost::container::vector m_buffer; + std::vector> m_resamplers; + std::shared_ptr m_osc; +}; +}