From 6b60469a1c3eff24afb586c1d59457ebd0a92bbf Mon Sep 17 00:00:00 2001 From: Daniel Jones Date: Mon, 21 Oct 2024 16:45:31 +0100 Subject: [PATCH] miniaudio: Switch to using static methods for querying backends/devices --- auxiliary/libs/signalflow_cli/__init__.py | 17 +-- source/include/signalflow/core/graph.h | 4 +- .../signalflow/node/io/output/miniaudio.h | 7 +- source/src/core/graph.cpp | 10 +- source/src/node/io/output/miniaudio.cpp | 76 +++++++------ source/src/python/graph.cpp | 107 +++++++----------- 6 files changed, 102 insertions(+), 119 deletions(-) diff --git a/auxiliary/libs/signalflow_cli/__init__.py b/auxiliary/libs/signalflow_cli/__init__.py index 32b1243e..b84d5766 100755 --- a/auxiliary/libs/signalflow_cli/__init__.py +++ b/auxiliary/libs/signalflow_cli/__init__.py @@ -58,24 +58,17 @@ def run_version(): print(signalflow.__version__) -def run_list_output_device_names(output_backend_name: str = None): - config = AudioGraphConfig() - if output_backend_name: - config.output_backend_name = output_backend_name - # config.output_device_name = "dummy" - graph = AudioGraph(config=config, start=False) +def run_list_output_device_names(backend_name: str = None): + output_device_names = AudioGraph.get_output_device_names(backend_name) print("Available output device names:") - for name in graph.output_device_names: + for name in output_device_names: print(" - %s" % name) def run_list_output_backend_names(): - config = AudioGraphConfig() - config.output_backend_name = "null" - config.output_device_name = "dummy" - graph = AudioGraph(config=config, start=False) + output_backend_names = AudioGraph.get_output_backend_names() print("Available output backend names:") - for name in graph.output_backend_names: + for name in output_backend_names: print(" - %s" % name) diff --git a/source/include/signalflow/core/graph.h b/source/include/signalflow/core/graph.h index 9892aff3..b47c710b 100644 --- a/source/include/signalflow/core/graph.h +++ b/source/include/signalflow/core/graph.h @@ -199,7 +199,7 @@ class AudioGraph * @return The list of device names. * *--------------------------------------------------------------------------------*/ - std::list get_output_device_names(); + static std::list get_output_device_names(std::string backend_name = ""); /**-------------------------------------------------------------------------------- * Returns a list of available audio I/O output backends. @@ -207,7 +207,7 @@ class AudioGraph * @return The list of backend names. * *--------------------------------------------------------------------------------*/ - std::list get_output_backend_names(); + static std::list get_output_backend_names(); /**-------------------------------------------------------------------------------- * Schedule a node for rendering without connecting the node to the graph's output. diff --git a/source/include/signalflow/node/io/output/miniaudio.h b/source/include/signalflow/node/io/output/miniaudio.h index e242209c..9b79cc40 100644 --- a/source/include/signalflow/node/io/output/miniaudio.h +++ b/source/include/signalflow/node/io/output/miniaudio.h @@ -25,16 +25,15 @@ class AudioOut : public AudioOut_Abstract virtual void stop() override; virtual void destroy() override; - std::list get_output_device_names(); - std::list get_output_backend_names(); - int get_default_output_device_index(); + static std::list get_output_device_names(std::string backend_name = ""); + static std::list get_output_backend_names(); private: /*-------------------------------------------------------------------------------- * Initialise a new miniaudio context, using the specified backend name if * present, or the default backend otherwise. *-------------------------------------------------------------------------------*/ - void init_context(ma_context *context); + static void init_context(ma_context *context, std::string backend_name = ""); std::string backend_name; std::string device_name; diff --git a/source/src/core/graph.cpp b/source/src/core/graph.cpp index 3627fb19..65babff8 100644 --- a/source/src/core/graph.cpp +++ b/source/src/core/graph.cpp @@ -587,16 +587,16 @@ std::list AudioGraph::get_outputs() return output->get_inputs(); } -std::list AudioGraph::get_output_device_names() +// static +std::list AudioGraph::get_output_device_names(std::string backend_name) { - AudioOut *output = (AudioOut *) (this->output.get()); - return output->get_output_device_names(); + return AudioOut::get_output_device_names(backend_name); } +// static std::list AudioGraph::get_output_backend_names() { - AudioOut *output = (AudioOut *) (this->output.get()); - return output->get_output_backend_names(); + return AudioOut::get_output_backend_names(); } NodeRef AudioGraph::add_node(NodeRef node) diff --git a/source/src/node/io/output/miniaudio.cpp b/source/src/node/io/output/miniaudio.cpp index d4234bfc..258b0066 100644 --- a/source/src/node/io/output/miniaudio.cpp +++ b/source/src/node/io/output/miniaudio.cpp @@ -92,30 +92,6 @@ AudioOut::AudioOut(const std::string &backend_name, this->init(); } -void AudioOut::init_context(ma_context *context) -{ - if (!this->backend_name.empty()) - { - if (possible_backend_names.find(this->backend_name) == possible_backend_names.end()) - { - throw audio_io_exception("miniaudio: Backend name not recognised: " + this->backend_name); - } - ma_backend backend_name = possible_backend_names[this->backend_name]; - - if (ma_context_init(&backend_name, 1, NULL, context) != MA_SUCCESS) - { - throw audio_io_exception("miniaudio: Error initialising context"); - } - } - else - { - if (ma_context_init(NULL, 0, NULL, context) != MA_SUCCESS) - { - throw audio_io_exception("miniaudio: Error initialising context"); - } - } -} - void AudioOut::init() { ma_device_config config = ma_device_config_init(ma_device_type_playback); @@ -126,7 +102,7 @@ void AudioOut::init() ma_uint32 capture_device_count; ma_result rv; - this->init_context(&this->context); + AudioOut::init_context(&this->context, this->backend_name); rv = ma_context_get_devices(&this->context, &playback_devices, @@ -180,6 +156,18 @@ void AudioOut::init() this->set_channels(device.playback.internalChannels, 0); + /*-------------------------------------------------------------------------------- + * If no specified sample rate was given, update AudioOut's sample rate to + * reflect the actual underlying sample rate. + * + * Otherwise, SignalFlow will use the user-specified sample rate, and miniaudio + * will perform sample-rate conversion. + *-------------------------------------------------------------------------------*/ + if (this->sample_rate == 0) + { + this->sample_rate = device.playback.internalSampleRate; + } + 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 << ")" @@ -215,7 +203,32 @@ void AudioOut::destroy() ma_device_uninit(&device); } -std::list AudioOut::get_output_device_names() +// static +void AudioOut::init_context(ma_context *context, std::string backend_name) +{ + if (!backend_name.empty()) + { + if (possible_backend_names.find(backend_name) == possible_backend_names.end()) + { + throw audio_io_exception("miniaudio: Backend name not recognised: " + backend_name); + } + ma_backend backend = possible_backend_names[backend_name]; + + if (ma_context_init(&backend, 1, NULL, context) != MA_SUCCESS) + { + throw audio_io_exception("miniaudio: Error initialising context"); + } + } + else + { + if (ma_context_init(NULL, 0, NULL, context) != MA_SUCCESS) + { + throw audio_io_exception("miniaudio: Error initialising context"); + } + } +} + +std::list AudioOut::get_output_device_names(std::string backend_name) { std::list device_names; @@ -225,7 +238,8 @@ std::list AudioOut::get_output_device_names() ma_device_info *capture_devices; ma_uint32 capture_device_count; ma_context context; - this->init_context(&context); + + AudioOut::init_context(&context, backend_name); rv = ma_context_get_devices(&context, &playback_devices, @@ -241,13 +255,9 @@ std::list AudioOut::get_output_device_names() device_names.push_back(std::string(playback_devices[i].name)); } - return device_names; -} + ma_context_uninit(&context); -int AudioOut::get_default_output_device_index() -{ - // TODO: Is this even used? - return -1; + return device_names; } std::list AudioOut::get_output_backend_names() diff --git a/source/src/python/graph.cpp b/source/src/python/graph.cpp index a6c82393..70d3a2c0 100644 --- a/source/src/python/graph.cpp +++ b/source/src/python/graph.cpp @@ -36,10 +36,6 @@ void init_python_graph(py::module &m) .def_property_readonly( "outputs", &AudioGraph::get_outputs, R"pbdoc(int: Get the list of Node objects currently connected to the graph's output.)pbdoc") - .def_property_readonly("output_device_names", &AudioGraph::get_output_device_names, - R"pbdoc(list[str]: List the available output device names.)pbdoc") - .def_property_readonly("output_backend_names", &AudioGraph::get_output_backend_names, - R"pbdoc(list[str]: List the available output backend names.)pbdoc") .def_property_readonly( "status", &AudioGraph::get_status, R"pbdoc(int: Get a text representation of the AudioGraph's status (node count, patch count, CPU usage).)pbdoc") @@ -50,41 +46,40 @@ void init_python_graph(py::module &m) R"pbdoc(int: Get/set the graph's sample rate.)pbdoc") .def_property("output", &AudioGraph::get_output, &AudioGraph::set_output) + /*-------------------------------------------------------------------------------- + * Static methods + *-------------------------------------------------------------------------------*/ + .def_static( + "get_output_device_names", [](py::object backend_name) { + std::string backend_name_str = backend_name.is_none() ? "" : backend_name.cast(); + return AudioGraph::get_output_device_names(backend_name_str); + }, + "backend_name"_a = "", R"pbdoc(list[str]: List the available output device names.)pbdoc") + .def_static("get_output_backend_names", &AudioGraph::get_output_backend_names, R"pbdoc(list[str]: List the available output backend names.)pbdoc") + /*-------------------------------------------------------------------------------- * Methods *-------------------------------------------------------------------------------*/ .def("start", &AudioGraph::start, R"pbdoc(Start the AudioGraph processing.)pbdoc") .def( "stop", [](AudioGraph &graph) { graph.stop(); }, R"pbdoc(Stop the AudioGraph processing.)pbdoc") - .def("clear", &AudioGraph::clear, - R"pbdoc(Remove all Node and Patches objects currently in the processing graph.)pbdoc") - .def("destroy", &AudioGraph::destroy, - R"pbdoc(Clear the AudioGraph and deallocate its memory, ready to create a new AudioGraph.)pbdoc") + .def("clear", &AudioGraph::clear, R"pbdoc(Remove all Node and Patches objects currently in the processing graph.)pbdoc") + .def("destroy", &AudioGraph::destroy, R"pbdoc(Clear the AudioGraph and deallocate its memory, ready to create a new AudioGraph.)pbdoc") .def( - "show_structure", [](AudioGraph &graph) { graph.show_structure(); }, - R"pbdoc(Print the AudioGraph's node connectivity structure to stdout.)pbdoc") + "show_structure", [](AudioGraph &graph) { graph.show_structure(); }, R"pbdoc(Print the AudioGraph's node connectivity structure to stdout.)pbdoc") .def( - "poll", [](AudioGraph &graph, float frequency) { graph.poll(frequency); }, "frequency"_a, - R"pbdoc(Begin polling the AudioGraph's status every `frequency` seconds, printing it to stdout.)pbdoc") + "poll", [](AudioGraph &graph, float frequency) { graph.poll(frequency); }, "frequency"_a, R"pbdoc(Begin polling the AudioGraph's status every `frequency` seconds, printing it to stdout.)pbdoc") .def( - "poll", [](AudioGraph &graph) { graph.poll(); }, - R"pbdoc(Begin polling the AudioGraph's status every 1.0 seconds, printing it to stdout.)pbdoc") + "poll", [](AudioGraph &graph) { graph.poll(); }, R"pbdoc(Begin polling the AudioGraph's status every 1.0 seconds, printing it to stdout.)pbdoc") .def( - "render", [](AudioGraph &graph) { graph.render(); }, - R"pbdoc(Render a single block (of `output_buffer_size` frames) of the AudioGraph's output.)pbdoc") + "render", [](AudioGraph &graph) { graph.render(); }, R"pbdoc(Render a single block (of `output_buffer_size` frames) of the AudioGraph's output.)pbdoc") .def( - "render", [](AudioGraph &graph, int num_frames) { graph.render(num_frames); }, "num_frames"_a, - R"pbdoc(Render a specified number of samples of the AudioGraph's output.)pbdoc") + "render", [](AudioGraph &graph, int num_frames) { graph.render(num_frames); }, "num_frames"_a, R"pbdoc(Render a specified number of samples of the AudioGraph's output.)pbdoc") + .def("render_to_buffer", &AudioGraph::render_to_buffer, "buffer"_a, R"pbdoc(Render the graph's output to the specified buffer, for the same number of frames as the buffer's length.)pbdoc") + .def("render_to_new_buffer", &AudioGraph::render_to_new_buffer, "num_frames"_a, R"pbdoc(Render the graph's output for the specified number of frames, and return the resultant buffer.)pbdoc") .def( - "render_to_buffer", &AudioGraph::render_to_buffer, "buffer"_a, - R"pbdoc(Render the graph's output to the specified buffer, for the same number of frames as the buffer's length.)pbdoc") - .def( - "render_to_new_buffer", &AudioGraph::render_to_new_buffer, "num_frames"_a, - R"pbdoc(Render the graph's output for the specified number of frames, and return the resultant buffer.)pbdoc") - .def( - "render_subgraph", - [](AudioGraph &graph, NodeRef node, int num_frames, bool reset) { + "render_subgraph", [](AudioGraph &graph, NodeRef node, int num_frames, bool reset) { if (reset) { graph.reset_subgraph(node); @@ -98,58 +93,44 @@ void init_python_graph(py::module &m) graph.render_subgraph(node); } }, - "node"_a, "num_frames"_a = 0, "reset"_a = false, - R"pbdoc(Recursively render the nodes in the tree starting at `node`. If `reset` is true, call `reset_subgraph` first.)pbdoc") - .def("reset_subgraph", &AudioGraph::reset_subgraph, - R"pbdoc(Reset the `played` status of nodes in the tree starting at `node`.)pbdoc") + "node"_a, "num_frames"_a = 0, "reset"_a = false, R"pbdoc(Recursively render the nodes in the tree starting at `node`. If `reset` is true, call `reset_subgraph` first.)pbdoc") + .def("reset_subgraph", &AudioGraph::reset_subgraph, R"pbdoc(Reset the `played` status of nodes in the tree starting at `node`.)pbdoc") .def( - "play", [](AudioGraph &graph, NodeRef node) { graph.play(node); }, "node"_a, - R"pbdoc(Begin playback of `node` (by connecting it to the output of the graph))pbdoc") + "play", [](AudioGraph &graph, NodeRef node) { graph.play(node); }, "node"_a, R"pbdoc(Begin playback of `node` (by connecting it to the output of the graph))pbdoc") .def( - "play", [](AudioGraph &graph, PatchRef patch) { graph.play(patch); }, "patch"_a, - R"pbdoc(Begin playback of `patch` (by connecting it to the output of the graph))pbdoc") + "play", [](AudioGraph &graph, PatchRef patch) { graph.play(patch); }, "patch"_a, R"pbdoc(Begin playback of `patch` (by connecting it to the output of the graph))pbdoc") .def( - "stop", [](AudioGraph &graph, NodeRef node) { graph.stop(node); }, "node"_a, - R"pbdoc(Stop playback of `node` (by disconnecting it from the output of the graph))pbdoc") + "stop", [](AudioGraph &graph, NodeRef node) { graph.stop(node); }, "node"_a, R"pbdoc(Stop playback of `node` (by disconnecting it from the output of the graph))pbdoc") .def( - "stop", [](AudioGraph &graph, PatchRef patch) { graph.stop(patch); }, "patch"_a, - R"pbdoc(Stop playback of `patch]` (by disconnecting it from the output of the graph))pbdoc") + "stop", [](AudioGraph &graph, PatchRef patch) { graph.stop(patch); }, "patch"_a, R"pbdoc(Stop playback of `patch]` (by disconnecting it from the output of the graph))pbdoc") .def( - "replace", [](AudioGraph &graph, NodeRef node, NodeRef other) { graph.replace(node, other); }, "node"_a, - "other"_a, R"pbdoc(Replace `node` in the graph's output with `other`.)pbdoc") - .def( - "add_node", &AudioGraph::add_node, "node"_a, - R"pbdoc(Add `node` to the graph so that it is processed in future blocks, without connecting it to the graph's output. Useful for non-playback nodes (e.g. BufferRecorder).)pbdoc") - .def("remove_node", &AudioGraph::remove_node, "node"_a, - R"pbdoc(Remove a `node` that has previously been added with `add_node()`)pbdoc") + "replace", [](AudioGraph &graph, NodeRef node, NodeRef other) { graph.replace(node, other); }, "node"_a, "other"_a, R"pbdoc(Replace `node` in the graph's output with `other`.)pbdoc") + .def("add_node", &AudioGraph::add_node, "node"_a, R"pbdoc(Add `node` to the graph so that it is processed in future blocks, without connecting it to the graph's output. Useful for non-playback nodes (e.g. BufferRecorder).)pbdoc") + .def("remove_node", &AudioGraph::remove_node, "node"_a, R"pbdoc(Remove a `node` that has previously been added with `add_node()`)pbdoc") - .def( - "start_recording", &AudioGraph::start_recording, "filename"_a = "", "num_channels"_a = 0, - R"pbdoc(Start recording the graph's output to an audio file, with the same number of channels as the AudioGraph or `num_channels` if specified.)pbdoc") + .def("start_recording", &AudioGraph::start_recording, "filename"_a = "", "num_channels"_a = 0, R"pbdoc(Start recording the graph's output to an audio file, with the same number of channels as the AudioGraph or `num_channels` if specified.)pbdoc") .def("stop_recording", &AudioGraph::stop_recording, R"pbdoc(Stop recording the graph's output.)pbdoc") - .def("wait", - [](AudioGraph &graph) { - /*-------------------------------------------------------------------------------- + .def("wait", [](AudioGraph &graph) { + /*-------------------------------------------------------------------------------- * Interruptible wait * https://pybind11.readthedocs.io/en/stable/faq.html#how-can-i-properly-handle-ctrl-c-in-long-running-functions *-------------------------------------------------------------------------------*/ - for (;;) - { - if (PyErr_CheckSignals() != 0) - throw py::error_already_set(); - /*-------------------------------------------------------------------------------- + for (;;) + { + if (PyErr_CheckSignals() != 0) + throw py::error_already_set(); + /*-------------------------------------------------------------------------------- * Release the GIL so that other threads can do processing. *-------------------------------------------------------------------------------*/ - py::gil_scoped_release release; + py::gil_scoped_release release; - if (graph.has_raised_audio_thread_error()) - break; - } - }) + if (graph.has_raised_audio_thread_error()) + break; + } + }) .def( - "wait", - [](AudioGraph &graph, float timeout_seconds) { + "wait", [](AudioGraph &graph, float timeout_seconds) { timeval tv; gettimeofday(&tv, NULL); double t0 = tv.tv_sec + tv.tv_usec / 1000000.0;