diff --git a/docs/graph/config.md b/docs/graph/config.md index 583e568a..3daf5b63 100644 --- a/docs/graph/config.md +++ b/docs/graph/config.md @@ -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. | --- @@ -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: @@ -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 @@ -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 ``` --- diff --git a/source/include/signalflow/core/config.h b/source/include/signalflow/core/config.h index 83980898..0c1212f1 100644 --- a/source/include/signalflow/core/config.h +++ b/source/include/signalflow/core/config.h @@ -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. * @@ -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 */ diff --git a/source/include/signalflow/core/constants.h b/source/include/signalflow/core/constants.h index cdc11a35..a7765dd8 100644 --- a/source/include/signalflow/core/constants.h +++ b/source/include/signalflow/core/constants.h @@ -138,9 +138,9 @@ typedef RingBuffer 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 /**------------------------------------------------------------------------ diff --git a/source/src/core/config.cpp b/source/src/core/config.cpp index a3167e8b..9a57ff46 100644 --- a/source/src/core/config.cpp +++ b/source/src/core/config.cpp @@ -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 true_values = { "yes", "on", "true", "1" }; + std::vector 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 + " > " @@ -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; } @@ -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; diff --git a/source/src/core/graph.cpp b/source/src/core/graph.cpp index 74039c83..cfde7a58 100644 --- a/source/src/core/graph.cpp +++ b/source/src/core/graph.cpp @@ -1,15 +1,13 @@ #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 #include @@ -17,6 +15,10 @@ #include #include +#include +#include +#include + namespace signalflow { @@ -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; @@ -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() diff --git a/source/src/python/config.cpp b/source/src/python/config.cpp index c62aeeff..434df38e 100644 --- a/source/src/python/config.cpp +++ b/source/src/python/config.cpp @@ -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); }