Skip to content

Commit

Permalink
miniaudio: Switch to using static methods for querying backends/devices
Browse files Browse the repository at this point in the history
  • Loading branch information
ideoforms committed Oct 21, 2024
1 parent bf47436 commit 6b60469
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 119 deletions.
17 changes: 5 additions & 12 deletions auxiliary/libs/signalflow_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand Down
4 changes: 2 additions & 2 deletions source/include/signalflow/core/graph.h
Original file line number Diff line number Diff line change
Expand Up @@ -199,15 +199,15 @@ class AudioGraph
* @return The list of device names.
*
*--------------------------------------------------------------------------------*/
std::list<std::string> get_output_device_names();
static std::list<std::string> get_output_device_names(std::string backend_name = "");

/**--------------------------------------------------------------------------------
* Returns a list of available audio I/O output backends.
*
* @return The list of backend names.
*
*--------------------------------------------------------------------------------*/
std::list<std::string> get_output_backend_names();
static std::list<std::string> get_output_backend_names();

/**--------------------------------------------------------------------------------
* Schedule a node for rendering without connecting the node to the graph's output.
Expand Down
7 changes: 3 additions & 4 deletions source/include/signalflow/node/io/output/miniaudio.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,15 @@ class AudioOut : public AudioOut_Abstract
virtual void stop() override;
virtual void destroy() override;

std::list<std::string> get_output_device_names();
std::list<std::string> get_output_backend_names();
int get_default_output_device_index();
static std::list<std::string> get_output_device_names(std::string backend_name = "");
static std::list<std::string> 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;
Expand Down
10 changes: 5 additions & 5 deletions source/src/core/graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -587,16 +587,16 @@ std::list<NodeRef> AudioGraph::get_outputs()
return output->get_inputs();
}

std::list<std::string> AudioGraph::get_output_device_names()
// static
std::list<std::string> 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<std::string> 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)
Expand Down
76 changes: 43 additions & 33 deletions source/src/node/io/output/miniaudio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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,
Expand Down Expand Up @@ -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 << ")"
Expand Down Expand Up @@ -215,7 +203,32 @@ void AudioOut::destroy()
ma_device_uninit(&device);
}

std::list<std::string> 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<std::string> AudioOut::get_output_device_names(std::string backend_name)
{
std::list<std::string> device_names;

Expand All @@ -225,7 +238,8 @@ std::list<std::string> 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,
Expand All @@ -241,13 +255,9 @@ std::list<std::string> 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<std::string> AudioOut::get_output_backend_names()
Expand Down
107 changes: 44 additions & 63 deletions source/src/python/graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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<std::string>();
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);
Expand All @@ -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;
Expand Down

0 comments on commit 6b60469

Please sign in to comment.