From e32549e8eba28bc1a2c9dc7eb7cd11942a031306 Mon Sep 17 00:00:00 2001 From: Daniel Jones Date: Sun, 20 Oct 2024 10:33:36 +0100 Subject: [PATCH] Initial integration of miniaudio --- .../signalflow/node/io/output/miniaudio.h | 42 ++++ source/include/signalflow/signalflow.h | 3 +- source/src/CMakeLists.txt | 3 +- source/src/core/graph.cpp | 6 +- source/src/node/io/input/soundio.cpp | 4 +- source/src/node/io/output/miniaudio.cpp | 208 ++++++++++++++++++ 6 files changed, 259 insertions(+), 7 deletions(-) create mode 100644 source/include/signalflow/node/io/output/miniaudio.h create mode 100644 source/src/node/io/output/miniaudio.cpp diff --git a/source/include/signalflow/node/io/output/miniaudio.h b/source/include/signalflow/node/io/output/miniaudio.h new file mode 100644 index 00000000..31d04862 --- /dev/null +++ b/source/include/signalflow/node/io/output/miniaudio.h @@ -0,0 +1,42 @@ +#pragma once + +#define AudioOut AudioOut_MiniAudio + +#include +#include + +#include "abstract.h" + +#include "miniaudio-library.h" +#include "signalflow/core/graph.h" +#include "signalflow/node/node.h" + +namespace signalflow +{ + +class AudioOut_MiniAudio : public AudioOut_Abstract +{ +public: + AudioOut_MiniAudio(const std::string &backend_name = "", + const std::string &device_name = "", + unsigned int sample_rate = 0, + unsigned int buffer_size = 0); + + virtual int init() override; + virtual int start() override; + virtual int stop() override; + virtual int destroy() override; + + std::list get_output_device_names(); + std::list get_output_backend_names(); + int get_default_output_device_index(); + +private: + std::string backend_name; + std::string device_name; + ma_device device; +}; + +REGISTER(AudioOut_MiniAudio, "audioout-miniaudio") + +} // namespace signalflow diff --git a/source/include/signalflow/signalflow.h b/source/include/signalflow/signalflow.h index 59937362..8c1f4c53 100644 --- a/source/include/signalflow/signalflow.h +++ b/source/include/signalflow/signalflow.h @@ -68,7 +68,8 @@ #include #include #include -#include +#include +//#include #include #include diff --git a/source/src/CMakeLists.txt b/source/src/CMakeLists.txt index 7576c3d9..55813326 100644 --- a/source/src/CMakeLists.txt +++ b/source/src/CMakeLists.txt @@ -86,7 +86,8 @@ set(SRC ${SRC} ${CMAKE_CURRENT_SOURCE_DIR}/node/io/input/soundio.cpp ${CMAKE_CURRENT_SOURCE_DIR}/node/io/output/abstract.cpp ${CMAKE_CURRENT_SOURCE_DIR}/node/io/output/dummy.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/node/io/output/soundio.cpp +# ${CMAKE_CURRENT_SOURCE_DIR}/node/io/output/soundio.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/node/io/output/miniaudio.cpp ${CMAKE_CURRENT_SOURCE_DIR}/node/operators/add.cpp ${CMAKE_CURRENT_SOURCE_DIR}/node/operators/amplitude-to-decibels.cpp ${CMAKE_CURRENT_SOURCE_DIR}/node/operators/bus.cpp diff --git a/source/src/core/graph.cpp b/source/src/core/graph.cpp index 6b2c941d..ca93ec80 100644 --- a/source/src/core/graph.cpp +++ b/source/src/core/graph.cpp @@ -4,7 +4,7 @@ #include "signalflow/node/io/output/abstract.h" #include "signalflow/node/io/output/dummy.h" #include "signalflow/node/io/output/ios.h" -#include "signalflow/node/io/output/soundio.h" +#include "signalflow/node/io/output/miniaudio.h" #include "signalflow/node/node.h" #include "signalflow/node/oscillators/constant.h" #include "signalflow/patch/patch.h" @@ -590,13 +590,13 @@ std::list AudioGraph::get_outputs() std::list AudioGraph::get_output_device_names() { - AudioOut_SoundIO *output = (AudioOut_SoundIO *) (this->output.get()); + AudioOut_MiniAudio *output = (AudioOut_MiniAudio *) (this->output.get()); return output->get_output_device_names(); } std::list AudioGraph::get_output_backend_names() { - AudioOut_SoundIO *output = (AudioOut_SoundIO *) (this->output.get()); + AudioOut_MiniAudio *output = (AudioOut_MiniAudio *) (this->output.get()); return output->get_output_backend_names(); } diff --git a/source/src/node/io/input/soundio.cpp b/source/src/node/io/input/soundio.cpp index fa529c5d..030eb528 100644 --- a/source/src/node/io/input/soundio.cpp +++ b/source/src/node/io/input/soundio.cpp @@ -5,7 +5,7 @@ #define SIGNALFLOW_AUDIO_IN_DEFAULT_BUFFER_SIZE 1024 #include "signalflow/core/graph.h" -#include "signalflow/node/io/output/soundio.h" +#include "signalflow/node/io/output/miniaudio.h" #include #include @@ -92,7 +92,7 @@ int AudioIn_SoundIO::init() { int err; - this->soundio = ((AudioOut_SoundIO *) this->graph->get_output().get())->soundio; + this->soundio = NULL; // ((AudioOut_MiniAudio *) this->graph->get_output().get())->soundio; if (!this->soundio) throw audio_io_exception("libsoundio init error: No output node found in graph (initialising input before output?)"); diff --git a/source/src/node/io/output/miniaudio.cpp b/source/src/node/io/output/miniaudio.cpp new file mode 100644 index 00000000..a1324921 --- /dev/null +++ b/source/src/node/io/output/miniaudio.cpp @@ -0,0 +1,208 @@ +#include "signalflow/node/io/output/miniaudio.h" + +#define MINIAUDIO_IMPLEMENTATION +#include "signalflow/node/io/output/miniaudio-library.h" + +#ifdef HAVE_SOUNDIO + +#include "signalflow/core/graph.h" + +#include +#include +#include +#include +#include +#include + +static bool is_processing = false; + +namespace signalflow +{ + +extern AudioGraph *shared_graph; + +void data_callback(ma_device *pDevice, + void *pOutput, + const void *pInput, + ma_uint32 frame_count) +{ + is_processing = true; + int channel_count = pDevice->playback.channels; + + /*-----------------------------------------------------------------------* + * Return if the shared_graph hasn't been initialized yet. + * (The libsoundio Pulse Audio driver calls the write_callback once + * on initialization, so this may happen legitimately.) + *-----------------------------------------------------------------------*/ + if (!shared_graph || !shared_graph->get_output()) + { + return; + } + + float *output_pointer = (float *) pOutput; + + try + { + shared_graph->render(frame_count); + } + catch (const std::exception &e) + { + std::cerr << "Exception in AudioGraph: " << e.what() << std::endl; + exit(1); + } + + NodeRef output = shared_graph->get_output(); + for (int frame = 0; frame < frame_count; frame++) + { + for (int channel = 0; channel < channel_count; channel += 1) + { + output_pointer[channel_count * frame + channel] = output->out[channel][frame]; + } + } + + is_processing = false; +} + +AudioOut_MiniAudio::AudioOut_MiniAudio(const std::string &backend_name, + const std::string &device_name, + unsigned int sample_rate, + unsigned int buffer_size) + : AudioOut_Abstract() +{ + this->backend_name = backend_name; + this->device_name = device_name; + this->sample_rate = sample_rate; + this->buffer_size = buffer_size; + this->name = "audioout-soundio"; + + this->init(); +} + +int AudioOut_MiniAudio::init() +{ + ma_device_config config = ma_device_config_init(ma_device_type_playback); + + ma_device_info *playback_devices; + ma_uint32 playback_device_count; + ma_device_info *capture_devices; + ma_uint32 capture_device_count; + ma_context context; + ma_result rv; + + if (ma_context_init(NULL, 0, NULL, &context) != MA_SUCCESS) + { + // Error. + printf("Error initialising context\n"); + return -1; + } + + rv = ma_context_get_devices(&context, + &playback_devices, + &playback_device_count, + &capture_devices, + &capture_device_count); + int selected_device_index = -1; + if (!this->device_name.empty()) + { + for (int i = 0; i < playback_device_count; i++) + { + printf(" - (%d) %s\n", i, playback_devices[i].name); + if (strcmp(playback_devices[i].name, device_name.c_str()) == 0) + { + selected_device_index = i; + } + } + if (selected_device_index == -1) + { + printf("Couldn't find device\n"); + return -1; + } + + config.playback.pDeviceID = &playback_devices[selected_device_index].id; + } + + // Set to ma_format_unknown to use the device's native format. + config.playback.format = ma_format_f32; + + // Set to 0 to use the device's native channel count / buffer size. + config.playback.channels = 0; + config.periodSizeInFrames = buffer_size; + + // Note that the underlying connection always uses the device's native sample rate. + // Setting values other than zero instantiates miniaudio's internal resampler. + config.sampleRate = sample_rate; + config.dataCallback = data_callback; + + // Buffer blocks into a fixed number of frames + config.noFixedSizedCallback = 1; + + rv = ma_device_init(NULL, &config, &device); + if (rv != MA_SUCCESS) + { + printf("Error initialising device\n"); + return -1; + } + + this->sample_rate = device.playback.internalSampleRate; + this->set_channels(device.playback.internalChannels, 0); + + std::string s = device.playback.internalChannels == 1 ? "" : "s"; + + std::cerr << "[MINIAUDIO] Output device: " << std::string(device.playback.name) << " (" << device.playback.internalSampleRate << "Hz, " + << "buffer size " << device.playback.internalPeriodSizeInFrames << " samples, " << device.playback.internalChannels << " channel" << s << ")" + << std::endl; + + // do we need to set num_output channels to allocate the right number of output buffers? + this->set_channels(device.playback.internalChannels, 0); + + return 0; +} + +int AudioOut_MiniAudio::start() +{ + ma_result rv = ma_device_start(&device); + if (rv != MA_SUCCESS) + { + printf("Error starting device\n"); + return -1; + } + this->set_state(SIGNALFLOW_NODE_STATE_ACTIVE); + return 0; +} + +int AudioOut_MiniAudio::stop() +{ + // TODO + this->set_state(SIGNALFLOW_NODE_STATE_STOPPED); + return 0; +} + +int AudioOut_MiniAudio::destroy() +{ + while (is_processing) + { + } + + ma_device_uninit(&device); + + return 0; +} + +std::list AudioOut_MiniAudio::get_output_device_names() +{ + return {}; +} + +int AudioOut_MiniAudio::get_default_output_device_index() +{ + return 0; +} + +std::list AudioOut_MiniAudio::get_output_backend_names() +{ + return {}; +} + +} // namespace signalflow + +#endif