Skip to content

Commit

Permalink
AudioGraphConfig: Add auto_record config parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
ideoforms committed Sep 11, 2024
1 parent dd0f40a commit 2212a3c
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 21 deletions.
34 changes: 21 additions & 13 deletions docs/graph/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@

## Graph configuration

There are a number of graph configuration parameters that can be used to change the global behaviour of the audio system. This can be done [programmatically](#configuring-the-graph-programmatically), [via a config file](#configuring-the-graph-via-signalflowconfig), or [via environmental variables](#configuring-the-graph-via-environmental-variables).

| Parameter | Description |
|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| output_backend_name | The name of the audio output backend to use, which can be one of: `jack`, `alsa`, `pulseaudio`, `coreaudio`, `wasapi`, `dummy`. Defaults to the first of these found on the system. Typically only required for Linux. |
| output_device_name | The name of the audio output device to use. This must precisely match the device's name in your system. If not found, `DeviceNotFoundException` is thrown when instantiating the graph. |
There are a number of graph configuration parameters that can be used to change the global behaviour of the audio
system. This can be
done [programmatically](#configuring-the-graph-programmatically), [via a config file](#configuring-the-graph-via-signalflowconfig),
or [via environmental variables](#configuring-the-graph-via-environmental-variables).

| Parameter | Description |
|---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| output_backend_name | The name of the audio output backend to use, which can be one of: `jack`, `alsa`, `pulseaudio`, `coreaudio`, `wasapi`, `dummy`. Defaults to the first of these found on the system. Typically only required for Linux. |
| output_device_name | The name of the audio output device to use. This must precisely match the device's name in your system. If not found, `DeviceNotFoundException` is thrown when instantiating the graph. |
| output_buffer_size | The size of the hardware output audio buffer, in samples. A larger buffer reduces the chance of buffer overflows and glitches, but at the cost of higher latency. Note that this config option merely specifies the **preferred** output buffer size, which may not be available in the system hardware. To check the actual buffer size used by the AudioGraph, query `graph.output_buffer_size` after instantiation. |
| input_device_name | The name of the input device to use. |
| input_buffer_size | The size of the hardware input audio buffer. |
| sample_rate | The audio sample rate to use. |
| cpu_usage_limit | Imposes a hard limit on the CPU usage permitted by SignalFlow. If the estimated (single-core) CPU usage exceeds this value, no more nodes or patches can be created until it returns to below the limit. Floating-point value between 0..1, where 0.5 means 50% CPU. |
| input_device_name | The name of the input device to use. |
| input_buffer_size | The size of the hardware input audio buffer. |
| sample_rate | The audio sample rate to use. |
| cpu_usage_limit | Imposes a hard limit on the CPU usage permitted by SignalFlow. If the estimated (single-core) CPU usage exceeds this value, no more nodes or patches can be created until it returns to below the limit. Floating-point value between 0..1, where 0.5 means 50% CPU. |
| auto_record | If true, automatically records the graph's audio output to a timestamped file in `~/.signalflow/recordings`. A true value can be any of "true", "yes", "on", or 1. |

---

Expand All @@ -33,7 +37,8 @@ graph = AudioGraph(config)

## Configuring the graph via ~/.signalflow/config

To specify a configuration that is used by all future SignalFlow sessions, create a file `~/.signalflow/config`, containing one or more of the "Graph configuration" fields listed above.
To specify a configuration that is used by all future SignalFlow sessions, create a file `~/.signalflow/config`,
containing one or more of the "Graph configuration" fields listed above.

For example:

Expand All @@ -48,7 +53,8 @@ input_device_name = "MacBook Pro Microphone"

All fields are optional.

A quick and easy way to edit your config, or create a new config file, is by using the `signalflow` command-line utility:
A quick and easy way to edit your config, or create a new config file, is by using the `signalflow` command-line
utility:

```
signalflow configure
Expand All @@ -60,11 +66,13 @@ This will use your default `$EDITOR` to open the configuration, or `pico` if no

## Configuring the graph via environmental variables

SignalFlow config can also be set by setting an environmental variable in your shell. Variable names are identical to the upper-case version of the config string, prefixed with `SIGNALFLOW_`. For example:
SignalFlow config can also be set by setting an environmental variable in your shell. Variable names are identical to
the upper-case version of the config string, prefixed with `SIGNALFLOW_`. For example:

```
export SIGNALFLOW_OUTPUT_DEVICE_NAME="MacBook Pro Speakers"
export SIGNALFLOW_OUTPUT_BUFFER_SIZE=1024
export SIGNALFLOW_AUTO_RECORD=true
```

---
Expand Down
19 changes: 19 additions & 0 deletions source/include/signalflow/core/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,24 @@ class AudioGraphConfig
*--------------------------------------------------------------------------------*/
void set_cpu_usage_limit(float limit);

/**--------------------------------------------------------------------------------
* Get the current auto_record mode.
*
* @returns true or false
*
*--------------------------------------------------------------------------------*/
bool get_auto_record() const;

/**--------------------------------------------------------------------------------
* Set the auto_record mode.
* If true, the AudioGraph automatically continuously records its output to a
* timestamped .wav file in ~/.signalflow/recordings.
*
* @param on If true, automatically records when the graph is running.
*
*--------------------------------------------------------------------------------*/
void set_auto_record(bool on);

/**--------------------------------------------------------------------------------
* Print the current config to stdout.
*
Expand All @@ -174,6 +192,7 @@ class AudioGraphConfig
std::string output_device_name;
std::string output_backend_name;
float cpu_usage_limit = 0.0;
bool auto_record = false;
};

} /* namespace signalflow */
4 changes: 2 additions & 2 deletions source/include/signalflow/core/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,9 @@ typedef RingBuffer<sample> SampleRingBuffer;
}

#ifdef _WIN32
#define SIGNALFLOW_USER_DIR std::string(getenv("HOMEPATH")) + "/.signalflow"
#define SIGNALFLOW_USER_DIR (std::string(getenv("HOMEPATH")) + "/.signalflow")
#else
#define SIGNALFLOW_USER_DIR std::string(getenv("HOME")) + "/.signalflow"
#define SIGNALFLOW_USER_DIR (std::string(getenv("HOME")) + "/.signalflow")
#endif

/**------------------------------------------------------------------------
Expand Down
25 changes: 25 additions & 0 deletions source/src/core/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,23 @@ void AudioGraphConfig::parse_file(std::ifstream &input)
{
this->cpu_usage_limit = std::stof(parameter_value);
}
else if (parameter_name == "auto_record")
{
std::vector<std::string> true_values = { "yes", "on", "true", "1" };
std::vector<std::string> false_values = { "no", "off", "false", "0" };
if (std::find(true_values.begin(), true_values.end(), parameter_value) != true_values.end())
{
this->auto_record = true;
}
else if (std::find(false_values.begin(), false_values.end(), parameter_value) != false_values.end())
{
this->auto_record = false;
}
else
{
throw std::runtime_error("Invalid value for auto_record: " + parameter_value);
}
}
else
{
throw std::runtime_error("Invalid section parameter name: " + section_name + " > "
Expand Down Expand Up @@ -198,6 +215,10 @@ void AudioGraphConfig::parse_env()
{
this->cpu_usage_limit = atof(getenv("SIGNALFLOW_CPU_USAGE_LIMIT"));
}
if (getenv("SIGNALFLOW_AUTO_RECORD"))
{
this->auto_record = (bool) atoi(getenv("SIGNALFLOW_AUTO_RECORD"));
}
}

unsigned int AudioGraphConfig::get_sample_rate() const { return this->sample_rate; }
Expand Down Expand Up @@ -228,6 +249,10 @@ float AudioGraphConfig::get_cpu_usage_limit() const { return this->cpu_usage_lim

void AudioGraphConfig::set_cpu_usage_limit(float limit) { this->cpu_usage_limit = limit; }

bool AudioGraphConfig::get_auto_record() const { return this->auto_record; }

void AudioGraphConfig::set_auto_record(bool on) { this->auto_record = on; }

void AudioGraphConfig::print() const
{
std::cout << "output_backend_name = " << this->output_backend_name << std::endl;
Expand Down
54 changes: 49 additions & 5 deletions source/src/core/graph.cpp
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
#include "signalflow/core/core.h"
#include "signalflow/core/graph-monitor.h"
#include "signalflow/core/graph.h"
#include "signalflow/node/node.h"
#include "signalflow/node/oscillators/constant.h"

#include "signalflow/patch/patch.h"

#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/node.h"
#include "signalflow/node/oscillators/constant.h"
#include "signalflow/patch/patch.h"

#include <iomanip>
#include <limits.h>
#include <math.h>
#include <sstream>
#include <string.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

namespace signalflow
{

Expand All @@ -31,6 +33,13 @@ AudioGraph::AudioGraph(AudioGraphConfig *config, std::string output_device, bool
{
if (shared_graph)
{
/*--------------------------------------------------------------------------------
* TODO: At some point, it would be nice to allow the creation of multiple
* AudioGraphs, or recreating the shared AudioGraph (perhaps given some
* config option). However, this may have unforeseen circumstances and
* would need memory/CPU to be correctly cleaned up, which does not happen
* right now.
*--------------------------------------------------------------------------------*/
throw graph_already_created_exception();
}
shared_graph = this;
Expand Down Expand Up @@ -140,12 +149,47 @@ void AudioGraph::start()
{
AudioOut_Abstract *audio_out = (AudioOut_Abstract *) this->output.get();
audio_out->start();

if (this->config.get_auto_record())
{
std::time_t now = std::time(nullptr);
char timestamp[100];
std::strftime(timestamp, sizeof(timestamp), "%Y-%m-%d-%H%M%S", std::localtime(&now));
std::string timestamp_str(timestamp);
std::string recordings_dir = SIGNALFLOW_USER_DIR + "/recordings";
std::string recording_filename = recordings_dir + "/signalflow-" + timestamp_str + ".wav";

// TODO: This is all very POSIX-specific and won't work on Windows
struct stat st;
if (stat(SIGNALFLOW_USER_DIR.c_str(), &st) == -1)
{
int rv = mkdir(SIGNALFLOW_USER_DIR.c_str(), 0755);
if (rv != 0)
{
throw std::runtime_error("AudioGraph: Failed creating user directory for auto_record (" + SIGNALFLOW_USER_DIR + ")");
}
}
if (stat(recordings_dir.c_str(), &st) == -1)
{
int rv = mkdir(recordings_dir.c_str(), 0755);
if (rv != 0)
{
throw std::runtime_error("AudioGraph: Failed creating recordings directory for auto_record (" + recordings_dir + ")");
}
}
this->start_recording(recording_filename, this->output->get_num_input_channels());
}
}

void AudioGraph::stop()
{
AudioOut_Abstract *audioout = (AudioOut_Abstract *) this->output.get();
audioout->stop();

if (this->config.get_auto_record())
{
this->stop_recording();
}
}

bool AudioGraph::has_raised_audio_thread_error()
Expand Down
3 changes: 2 additions & 1 deletion source/src/python/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ void init_python_config(py::module &m)
.def_property("input_device_name", &AudioGraphConfig::get_input_device_name, &AudioGraphConfig::set_input_device_name)
.def_property("output_device_name", &AudioGraphConfig::get_output_device_name, &AudioGraphConfig::set_output_device_name)
.def_property("output_backend_name", &AudioGraphConfig::get_output_backend_name, &AudioGraphConfig::set_output_backend_name)
.def_property("cpu_usage_limit", &AudioGraphConfig::get_cpu_usage_limit, &AudioGraphConfig::set_cpu_usage_limit);
.def_property("cpu_usage_limit", &AudioGraphConfig::get_cpu_usage_limit, &AudioGraphConfig::set_cpu_usage_limit)
.def_property("auto_record", &AudioGraphConfig::get_auto_record, &AudioGraphConfig::set_auto_record);
}

0 comments on commit 2212a3c

Please sign in to comment.