SDRPlusPlus/misc_modules/recorder/src/main.cpp

627 wiersze
23 KiB
C++
Czysty Zwykły widok Historia

2020-08-17 22:56:51 +00:00
#include <imgui.h>
2020-12-22 19:00:51 +00:00
#include <module.h>
2020-08-17 22:56:51 +00:00
#include <dsp/types.h>
#include <dsp/stream.h>
2022-06-17 16:47:09 +00:00
#include <dsp/bench/peak_level_meter.h>
2022-06-17 15:34:23 +00:00
#include <dsp/sink/handler_sink.h>
#include <dsp/routing/splitter.h>
#include <dsp/audio/volume.h>
2023-01-01 02:20:49 +00:00
#include <dsp/convert/stereo_to_mono.h>
2020-08-17 22:56:51 +00:00
#include <thread>
#include <ctime>
2020-10-01 11:46:12 +00:00
#include <gui/gui.h>
#include <filesystem>
2020-10-22 10:53:46 +00:00
#include <signal_path/signal_path.h>
#include <config.h>
#include <gui/style.h>
2021-02-06 20:28:27 +00:00
#include <gui/widgets/volume_meter.h>
#include <regex>
#include <gui/widgets/folder_select.h>
#include <recorder_interface.h>
#include <core.h>
2022-09-24 23:01:07 +00:00
#include <utils/optionlist.h>
2023-02-07 07:33:47 +00:00
#include <utils/wav.h>
#include <radio_interface.h>
2022-09-24 23:01:07 +00:00
2020-08-17 22:56:51 +00:00
#define CONCAT(a, b) ((std::string(a) + b).c_str())
#define SILENCE_LVL 10e-6
2020-08-17 22:56:51 +00:00
SDRPP_MOD_INFO{
2020-12-08 03:36:37 +00:00
/* Name: */ "recorder",
/* Description: */ "Recorder module for SDR++",
/* Author: */ "Ryzerth",
2022-09-24 23:01:07 +00:00
/* Version: */ 0, 3, 0,
2020-12-08 03:36:37 +00:00
/* Max instances */ -1
};
2021-02-28 15:32:57 +00:00
ConfigManager config;
2020-08-17 22:56:51 +00:00
2021-02-06 20:28:27 +00:00
class RecorderModule : public ModuleManager::Instance {
public:
RecorderModule(std::string name) : folderSelect("%ROOT%/recordings") {
this->name = name;
2022-02-24 20:01:51 +00:00
root = (std::string)core::args["root"];
2023-01-02 00:24:30 +00:00
strcpy(nameTemplate, "$t_$f_$h-$m-$s_$d-$M-$y");
2022-09-24 23:01:07 +00:00
// Define option lists
2023-01-01 02:20:49 +00:00
containers.define("WAV", wav::FORMAT_WAV);
// containers.define("RF64", wav::FORMAT_RF64); // Disabled for now
2022-12-06 13:52:42 +00:00
sampleTypes.define(wav::SAMP_TYPE_UINT8, "Uint8", wav::SAMP_TYPE_UINT8);
sampleTypes.define(wav::SAMP_TYPE_INT16, "Int16", wav::SAMP_TYPE_INT16);
sampleTypes.define(wav::SAMP_TYPE_INT32, "Int32", wav::SAMP_TYPE_INT32);
sampleTypes.define(wav::SAMP_TYPE_FLOAT32, "Float32", wav::SAMP_TYPE_FLOAT32);
2022-09-24 23:01:07 +00:00
// Load default config for option lists
2023-01-01 02:20:49 +00:00
containerId = containers.valueId(wav::FORMAT_WAV);
2022-12-06 13:52:42 +00:00
sampleTypeId = sampleTypes.valueId(wav::SAMP_TYPE_INT16);
2022-09-24 23:01:07 +00:00
2021-04-13 23:07:54 +00:00
// Load config
config.acquire();
2022-09-24 23:01:07 +00:00
if (config.conf[name].contains("mode")) {
recMode = config.conf[name]["mode"];
2021-04-13 23:07:54 +00:00
}
2022-09-24 23:01:07 +00:00
if (config.conf[name].contains("recPath")) {
folderSelect.setPath(config.conf[name]["recPath"]);
2021-11-23 21:35:09 +00:00
}
2023-01-02 00:24:30 +00:00
if (config.conf[name].contains("container") && containers.keyExists(config.conf[name]["container"])) {
containerId = containers.keyId(config.conf[name]["container"]);
2022-06-26 01:42:22 +00:00
}
2022-12-06 13:52:42 +00:00
if (config.conf[name].contains("sampleType") && sampleTypes.keyExists(config.conf[name]["sampleType"])) {
sampleTypeId = sampleTypes.keyId(config.conf[name]["sampleType"]);
2022-09-24 23:01:07 +00:00
}
if (config.conf[name].contains("audioStream")) {
selectedStreamName = config.conf[name]["audioStream"];
}
if (config.conf[name].contains("audioVolume")) {
audioVolume = config.conf[name]["audioVolume"];
}
if (config.conf[name].contains("stereo")) {
stereo = config.conf[name]["stereo"];
}
2022-09-24 23:01:07 +00:00
if (config.conf[name].contains("ignoreSilence")) {
ignoreSilence = config.conf[name]["ignoreSilence"];
}
2023-01-02 00:24:30 +00:00
if (config.conf[name].contains("nameTemplate")) {
std::string _nameTemplate = config.conf[name]["nameTemplate"];
if (_nameTemplate.length() > sizeof(nameTemplate)-1) {
_nameTemplate = _nameTemplate.substr(0, sizeof(nameTemplate)-1);
}
strcpy(nameTemplate, _nameTemplate.c_str());
}
2022-09-24 23:01:07 +00:00
config.release();
2021-11-23 21:35:09 +00:00
2022-09-26 12:57:06 +00:00
// Init audio path
2022-09-26 15:04:24 +00:00
volume.init(NULL, audioVolume, false);
splitter.init(&volume.out);
2022-09-26 12:57:06 +00:00
splitter.bindStream(&meterStream);
meter.init(&meterStream);
s2m.init(&stereoStream);
2022-09-26 12:57:06 +00:00
2022-09-24 23:01:07 +00:00
// Init sinks
basebandSink.init(NULL, complexHandler, this);
stereoSink.init(&stereoStream, stereoHandler, this);
2023-01-01 02:20:49 +00:00
monoSink.init(&s2m.out, monoHandler, this);
2021-02-06 20:28:27 +00:00
2020-10-01 11:46:12 +00:00
gui::menu.registerEntry(name, menuHandler, this);
2023-01-02 00:24:30 +00:00
core::modComManager.registerInterface("recorder", name, moduleInterfaceHandler, this);
2020-10-01 11:46:12 +00:00
}
2020-08-17 22:56:51 +00:00
2020-10-01 11:46:12 +00:00
~RecorderModule() {
2023-01-02 00:24:30 +00:00
std::lock_guard<std::recursive_mutex> lck(recMtx);
core::modComManager.unregisterInterface(name);
gui::menu.removeEntry(name);
2022-09-26 12:57:06 +00:00
stop();
deselectStream();
2023-01-02 00:24:30 +00:00
sigpath::sinkManager.onStreamRegistered.unbindHandler(&onStreamRegisteredHandler);
sigpath::sinkManager.onStreamUnregister.unbindHandler(&onStreamUnregisterHandler);
2022-09-26 12:57:06 +00:00
meter.stop();
2020-08-17 22:56:51 +00:00
}
2021-07-26 01:11:51 +00:00
void postInit() {
2022-11-23 09:22:29 +00:00
// Enumerate streams
audioStreams.clear();
auto names = sigpath::sinkManager.getStreamNames();
for (const auto& name : names) {
audioStreams.define(name, name, name);
}
// Bind stream register/unregister handlers
onStreamRegisteredHandler.ctx = this;
onStreamRegisteredHandler.handler = streamRegisteredHandler;
sigpath::sinkManager.onStreamRegistered.bindHandler(&onStreamRegisteredHandler);
onStreamUnregisterHandler.ctx = this;
onStreamUnregisterHandler.handler = streamUnregisterHandler;
sigpath::sinkManager.onStreamUnregister.bindHandler(&onStreamUnregisterHandler);
// Select the stream
2022-10-11 15:40:38 +00:00
selectStream(selectedStreamName);
2021-07-26 01:11:51 +00:00
}
2020-12-08 03:36:37 +00:00
void enable() {
enabled = true;
}
void disable() {
enabled = false;
}
bool isEnabled() {
return enabled;
}
2022-09-24 23:01:07 +00:00
void start() {
std::lock_guard<std::recursive_mutex> lck(recMtx);
if (recording) { return; }
2021-07-26 01:11:51 +00:00
2022-09-24 23:01:07 +00:00
// Configure the wav writer
if (recMode == RECORDER_MODE_AUDIO) {
2022-11-25 19:32:12 +00:00
if (selectedStreamName.empty()) { return; }
samplerate = sigpath::sinkManager.getStreamSampleRate(selectedStreamName);
2022-09-24 23:01:07 +00:00
}
else {
samplerate = sigpath::iqFrontEnd.getSampleRate();
}
2023-01-01 02:20:49 +00:00
writer.setFormat(containers[containerId]);
2022-09-24 23:01:07 +00:00
writer.setChannels((recMode == RECORDER_MODE_AUDIO && !stereo) ? 1 : 2);
2022-12-06 13:52:42 +00:00
writer.setSampleType(sampleTypes[sampleTypeId]);
2022-09-24 23:01:07 +00:00
writer.setSamplerate(samplerate);
// Open file
2023-01-02 00:24:30 +00:00
std::string type = (recMode == RECORDER_MODE_AUDIO) ? "audio" : "baseband";
std::string vfoName = (recMode == RECORDER_MODE_AUDIO) ? selectedStreamName : "";
2023-01-02 00:24:30 +00:00
std::string extension = ".wav";
std::string expandedPath = expandString(folderSelect.path + "/" + genFileName(nameTemplate, type, vfoName) + extension);
2022-09-24 23:01:07 +00:00
if (!writer.open(expandedPath)) {
flog::error("Failed to open file for recording: {0}", expandedPath);
2022-09-24 23:01:07 +00:00
return;
}
2021-04-13 23:07:54 +00:00
2022-09-24 23:01:07 +00:00
// Open audio stream or baseband
if (recMode == RECORDER_MODE_AUDIO) {
// Start correct path depending on
2023-01-01 02:20:49 +00:00
if (stereo) {
stereoSink.start();
}
else {
s2m.start();
monoSink.start();
}
splitter.bindStream(&stereoStream);
2022-09-24 23:01:07 +00:00
}
else {
// Create and bind IQ stream
basebandStream = new dsp::stream<dsp::complex_t>();
basebandSink.setInput(basebandStream);
basebandSink.start();
sigpath::iqFrontEnd.bindIQStream(basebandStream);
2021-02-06 20:28:27 +00:00
}
2022-09-24 23:01:07 +00:00
recording = true;
2021-02-06 20:28:27 +00:00
}
2022-09-24 23:01:07 +00:00
void stop() {
std::lock_guard<std::recursive_mutex> lck(recMtx);
if (!recording) { return; }
// Close audio stream or baseband
if (recMode == RECORDER_MODE_AUDIO) {
splitter.unbindStream(&stereoStream);
2023-01-01 02:20:49 +00:00
monoSink.stop();
2022-09-24 23:01:07 +00:00
stereoSink.stop();
2023-01-01 02:20:49 +00:00
s2m.stop();
2021-07-26 01:11:51 +00:00
}
2022-09-24 23:01:07 +00:00
else {
// Unbind and destroy IQ stream
sigpath::iqFrontEnd.unbindIQStream(basebandStream);
basebandSink.stop();
delete basebandStream;
2021-07-26 01:11:51 +00:00
}
2021-02-06 20:28:27 +00:00
2022-09-24 23:01:07 +00:00
// Close file
writer.close();
recording = false;
2021-02-06 20:28:27 +00:00
}
2022-09-24 23:01:07 +00:00
private:
2020-10-01 11:46:12 +00:00
static void menuHandler(void* ctx) {
RecorderModule* _this = (RecorderModule*)ctx;
2022-09-24 23:01:07 +00:00
float menuWidth = ImGui::GetContentRegionAvail().x;
2020-08-20 16:29:23 +00:00
2021-02-06 20:28:27 +00:00
// Recording mode
if (_this->recording) { style::beginDisabled(); }
ImGui::BeginGroup();
2022-09-24 23:01:07 +00:00
ImGui::Columns(2, CONCAT("RecorderModeColumns##_", _this->name), false);
if (ImGui::RadioButton(CONCAT("Baseband##_recorder_mode_", _this->name), _this->recMode == RECORDER_MODE_BASEBAND)) {
_this->recMode = RECORDER_MODE_BASEBAND;
config.acquire();
2021-04-13 23:07:54 +00:00
config.conf[_this->name]["mode"] = _this->recMode;
config.release(true);
2020-08-20 16:29:23 +00:00
}
2021-02-06 20:28:27 +00:00
ImGui::NextColumn();
2022-09-24 23:01:07 +00:00
if (ImGui::RadioButton(CONCAT("Audio##_recorder_mode_", _this->name), _this->recMode == RECORDER_MODE_AUDIO)) {
_this->recMode = RECORDER_MODE_AUDIO;
config.acquire();
2021-04-13 23:07:54 +00:00
config.conf[_this->name]["mode"] = _this->recMode;
config.release(true);
2020-08-20 16:29:23 +00:00
}
2022-09-24 23:01:07 +00:00
ImGui::Columns(1, CONCAT("EndRecorderModeColumns##_", _this->name), false);
2021-02-06 20:28:27 +00:00
ImGui::EndGroup();
if (_this->recording) { style::endDisabled(); }
2020-10-01 11:46:12 +00:00
2021-02-06 20:28:27 +00:00
// Recording path
if (_this->folderSelect.render("##_recorder_fold_" + _this->name)) {
if (_this->folderSelect.pathIsValid()) {
config.acquire();
2021-04-13 23:07:54 +00:00
config.conf[_this->name]["recPath"] = _this->folderSelect.path;
config.release(true);
}
}
2020-10-22 10:53:46 +00:00
2023-01-02 00:24:30 +00:00
ImGui::LeftLabel("Name template");
ImGui::FillWidth();
if (ImGui::InputText(CONCAT("##_recorder_name_template_", _this->name), _this->nameTemplate, 1023)) {
config.acquire();
config.conf[_this->name]["nameTemplate"] = _this->nameTemplate;
config.release(true);
}
2023-01-01 02:20:49 +00:00
ImGui::LeftLabel("Container");
2022-09-24 23:01:07 +00:00
ImGui::FillWidth();
2023-01-01 02:20:49 +00:00
if (ImGui::Combo(CONCAT("##_recorder_container_", _this->name), &_this->containerId, _this->containers.txt)) {
2022-09-24 23:01:07 +00:00
config.acquire();
2023-01-01 02:20:49 +00:00
config.conf[_this->name]["container"] = _this->containers.key(_this->containerId);
2022-09-24 23:01:07 +00:00
config.release(true);
2021-04-13 23:07:54 +00:00
}
2022-12-06 13:52:42 +00:00
ImGui::LeftLabel("Sample type");
2022-09-24 23:01:07 +00:00
ImGui::FillWidth();
2022-12-06 13:52:42 +00:00
if (ImGui::Combo(CONCAT("##_recorder_st_", _this->name), &_this->sampleTypeId, _this->sampleTypes.txt)) {
config.acquire();
2022-12-06 13:52:42 +00:00
config.conf[_this->name]["sampleType"] = _this->sampleTypes.key(_this->sampleTypeId);
2021-04-13 23:07:54 +00:00
config.release(true);
2021-02-06 20:28:27 +00:00
}
2022-09-24 23:01:07 +00:00
// Show additional audio options
2022-11-23 09:22:29 +00:00
if (_this->recMode == RECORDER_MODE_AUDIO) {
ImGui::LeftLabel("Stream");
ImGui::FillWidth();
if (ImGui::Combo(CONCAT("##_recorder_stream_", _this->name), &_this->streamId, _this->audioStreams.txt)) {
_this->selectStream(_this->audioStreams.value(_this->streamId));
config.acquire();
config.conf[_this->name]["audioStream"] = _this->audioStreams.key(_this->streamId);
config.release(true);
}
2022-09-24 23:01:07 +00:00
_this->updateAudioMeter(_this->audioLvl);
ImGui::FillWidth();
ImGui::VolumeMeter(_this->audioLvl.l, _this->audioLvl.l, -60, 10);
ImGui::FillWidth();
ImGui::VolumeMeter(_this->audioLvl.r, _this->audioLvl.r, -60, 10);
2022-06-17 15:34:23 +00:00
2022-09-24 23:01:07 +00:00
ImGui::FillWidth();
if (ImGui::SliderFloat(CONCAT("##_recorder_vol_", _this->name), &_this->audioVolume, 0, 1, "")) {
2022-09-26 15:04:24 +00:00
_this->volume.setVolume(_this->audioVolume);
2022-09-24 23:01:07 +00:00
config.acquire();
config.conf[_this->name]["audioVolume"] = _this->audioVolume;
config.release(true);
}
2021-02-06 20:28:27 +00:00
2022-09-24 23:01:07 +00:00
if (_this->recording) { style::beginDisabled(); }
if (ImGui::Checkbox(CONCAT("Stereo##_recorder_stereo_", _this->name), &_this->stereo)) {
2022-09-26 15:04:24 +00:00
config.acquire();
2022-09-24 23:01:07 +00:00
config.conf[_this->name]["stereo"] = _this->stereo;
config.release(true);
}
if (_this->recording) { style::endDisabled(); }
2021-02-06 20:28:27 +00:00
if (ImGui::Checkbox(CONCAT("Ignore silence##_recorder_ignore_silence_", _this->name), &_this->ignoreSilence)) {
config.acquire();
config.conf[_this->name]["ignoreSilence"] = _this->ignoreSilence;
config.release(true);
}
2022-06-26 01:42:22 +00:00
}
2022-09-24 23:01:07 +00:00
// Record button
bool canRecord = _this->folderSelect.pathIsValid();
if (_this->recMode == RECORDER_MODE_AUDIO) { canRecord &= !_this->selectedStreamName.empty(); }
if (!_this->recording) {
if (ImGui::Button(CONCAT("Record##_recorder_rec_", _this->name), ImVec2(menuWidth, 0))) {
_this->start();
2020-10-22 10:53:46 +00:00
}
2021-02-06 20:28:27 +00:00
ImGui::TextColored(ImGui::GetStyleColorVec4(ImGuiCol_Text), "Idle --:--:--");
}
else {
2022-09-24 23:01:07 +00:00
if (ImGui::Button(CONCAT("Stop##_recorder_rec_", _this->name), ImVec2(menuWidth, 0))) {
_this->stop();
2020-10-01 11:46:12 +00:00
}
2022-09-24 23:01:07 +00:00
uint64_t seconds = _this->writer.getSamplesWritten() / _this->samplerate;
2021-02-06 20:28:27 +00:00
time_t diff = seconds;
tm* dtm = gmtime(&diff);
if (_this->ignoreSilence && _this->ignoringSilence) {
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "Paused %02d:%02d:%02d", dtm->tm_hour, dtm->tm_min, dtm->tm_sec);
}
else {
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "Recording %02d:%02d:%02d", dtm->tm_hour, dtm->tm_min, dtm->tm_sec);
}
2020-08-17 22:56:51 +00:00
}
}
2020-10-01 11:46:12 +00:00
2022-10-11 15:40:38 +00:00
void selectStream(std::string name) {
std::lock_guard<std::recursive_mutex> lck(recMtx);
deselectStream();
2022-11-23 09:22:29 +00:00
if (audioStreams.empty()) {
selectedStreamName.clear();
return;
}
else if (!audioStreams.keyExists(name)) {
selectStream(audioStreams.key(0));
return;
}
2022-10-11 15:40:38 +00:00
audioStream = sigpath::sinkManager.bindStream(name);
if (!audioStream) { return; }
selectedStreamName = name;
2022-11-23 09:22:29 +00:00
streamId = audioStreams.keyId(name);
2022-10-11 15:40:38 +00:00
volume.setInput(audioStream);
startAudioPath();
}
void deselectStream() {
std::lock_guard<std::recursive_mutex> lck(recMtx);
2022-11-23 09:22:29 +00:00
if (selectedStreamName.empty() || !audioStream) {
selectedStreamName.clear();
return;
}
2022-10-11 15:40:38 +00:00
if (recording && recMode == RECORDER_MODE_AUDIO) { stop(); }
stopAudioPath();
sigpath::sinkManager.unbindStream(selectedStreamName, audioStream);
2022-11-23 09:22:29 +00:00
selectedStreamName.clear();
2022-10-11 15:40:38 +00:00
audioStream = NULL;
}
2022-09-26 15:04:24 +00:00
void startAudioPath() {
volume.start();
splitter.start();
meter.start();
}
void stopAudioPath() {
volume.stop();
splitter.stop();
meter.stop();
}
2022-11-23 09:22:29 +00:00
static void streamRegisteredHandler(std::string name, void* ctx) {
RecorderModule* _this = (RecorderModule*)ctx;
// Add new stream to the list
_this->audioStreams.define(name, name, name);
// If no stream is selected, select new stream. If not, update the menu ID.
if (_this->selectedStreamName.empty()) {
_this->selectStream(name);
}
else {
_this->streamId = _this->audioStreams.keyId(_this->selectedStreamName);
}
}
static void streamUnregisterHandler(std::string name, void* ctx) {
RecorderModule* _this = (RecorderModule*)ctx;
// Remove stream from list
_this->audioStreams.undefineKey(name);
// If the stream is in used, deselect it and reselect default. Otherwise, update ID.
if (_this->selectedStreamName == name) {
_this->selectStream("");
}
else {
_this->streamId = _this->audioStreams.keyId(_this->selectedStreamName);
}
}
2022-09-24 23:01:07 +00:00
void updateAudioMeter(dsp::stereo_t& lvl) {
// Note: Yes, using the natural log is on purpose, it just gives a more beautiful result.
double frameTime = 1.0 / ImGui::GetIO().Framerate;
lvl.l = std::clamp<float>(lvl.l - (frameTime * 50.0), -90.0f, 10.0f);
lvl.r = std::clamp<float>(lvl.r - (frameTime * 50.0), -90.0f, 10.0f);
2022-09-26 15:04:24 +00:00
dsp::stereo_t rawLvl = meter.getLevel();
meter.resetLevel();
2022-09-24 23:01:07 +00:00
dsp::stereo_t dbLvl = { 10.0f * logf(rawLvl.l), 10.0f * logf(rawLvl.r) };
if (dbLvl.l > lvl.l) { lvl.l = dbLvl.l; }
if (dbLvl.r > lvl.r) { lvl.r = dbLvl.r; }
2020-08-17 22:56:51 +00:00
}
2020-10-01 11:46:12 +00:00
std::map<int, const char*> radioModeToString = {
{ RADIO_IFACE_MODE_NFM, "NFM" },
{ RADIO_IFACE_MODE_WFM, "WFM" },
{ RADIO_IFACE_MODE_AM, "AM" },
{ RADIO_IFACE_MODE_DSB, "DSB" },
{ RADIO_IFACE_MODE_USB, "USB" },
{ RADIO_IFACE_MODE_CW, "CW" },
{ RADIO_IFACE_MODE_LSB, "LSB" },
{ RADIO_IFACE_MODE_RAW, "RAW" }
};
2023-01-02 00:24:30 +00:00
std::string genFileName(std::string templ, std::string type, std::string name) {
// Get data
2022-09-24 23:01:07 +00:00
time_t now = time(0);
tm* ltm = localtime(&now);
char buf[1024];
double freq = gui::waterfall.getCenterFrequency();
2023-01-02 00:24:30 +00:00
if (gui::waterfall.vfos.find(name) != gui::waterfall.vfos.end()) {
2022-09-24 23:01:07 +00:00
freq += gui::waterfall.vfos[name]->generalOffset;
}
2023-01-02 00:24:30 +00:00
// Format to string
char freqStr[128];
char hourStr[128];
char minStr[128];
char secStr[128];
char dayStr[128];
char monStr[128];
char yearStr[128];
const char* modeStr = "Unknown";
2023-01-02 00:24:30 +00:00
sprintf(freqStr, "%.0lfHz", freq);
sprintf(hourStr, "%02d", ltm->tm_hour);
sprintf(minStr, "%02d", ltm->tm_min);
sprintf(secStr, "%02d", ltm->tm_sec);
sprintf(dayStr, "%02d", ltm->tm_mday);
sprintf(monStr, "%02d", ltm->tm_mon + 1);
sprintf(yearStr, "%02d", ltm->tm_year + 1900);
if (core::modComManager.getModuleName(name) == "radio") {
2024-04-22 19:54:01 +00:00
int mode = -1;
core::modComManager.callInterface(name, RADIO_IFACE_CMD_GET_MODE, NULL, &mode);
2024-04-22 19:54:01 +00:00
if (mode >= 0) { modeStr = radioModeToString[mode]; };
}
2023-01-02 00:24:30 +00:00
// Replace in template
templ = std::regex_replace(templ, std::regex("\\$t"), type);
templ = std::regex_replace(templ, std::regex("\\$f"), freqStr);
templ = std::regex_replace(templ, std::regex("\\$h"), hourStr);
templ = std::regex_replace(templ, std::regex("\\$m"), minStr);
templ = std::regex_replace(templ, std::regex("\\$s"), secStr);
templ = std::regex_replace(templ, std::regex("\\$d"), dayStr);
templ = std::regex_replace(templ, std::regex("\\$M"), monStr);
templ = std::regex_replace(templ, std::regex("\\$y"), yearStr);
templ = std::regex_replace(templ, std::regex("\\$r"), modeStr);
2023-01-02 00:24:30 +00:00
return templ;
2020-10-22 10:53:46 +00:00
}
2023-01-02 00:24:30 +00:00
2022-09-24 23:01:07 +00:00
std::string expandString(std::string input) {
input = std::regex_replace(input, std::regex("%ROOT%"), root);
return std::regex_replace(input, std::regex("//"), "/");
}
2022-09-24 23:01:07 +00:00
static void complexHandler(dsp::complex_t* data, int count, void* ctx) {
RecorderModule* _this = (RecorderModule*)ctx;
_this->writer.write((float*)data, count);
}
2022-09-24 23:01:07 +00:00
static void stereoHandler(dsp::stereo_t* data, int count, void* ctx) {
RecorderModule* _this = (RecorderModule*)ctx;
if (_this->ignoreSilence) {
2023-03-02 14:19:16 +00:00
float absMax = 0.0f;
float* _data = (float*)data;
int _count = count * 2;
for (int i = 0; i < _count; i++) {
float val = fabsf(_data[i]);
if (val > absMax) { absMax = val; }
}
_this->ignoringSilence = (absMax < SILENCE_LVL);
if (_this->ignoringSilence) { return; }
}
_this->writer.write((float*)data, count);
}
2022-09-24 23:01:07 +00:00
static void monoHandler(float* data, int count, void* ctx) {
2021-07-26 01:11:51 +00:00
RecorderModule* _this = (RecorderModule*)ctx;
if (_this->ignoreSilence) {
2023-03-02 14:19:16 +00:00
float absMax = 0.0f;
for (int i = 0; i < count; i++) {
float val = fabsf(data[i]);
if (val > absMax) { absMax = val; }
}
_this->ignoringSilence = (absMax < SILENCE_LVL);
if (_this->ignoringSilence) { return; }
}
2022-09-24 23:01:07 +00:00
_this->writer.write(data, count);
2021-07-26 01:11:51 +00:00
}
2023-01-02 00:24:30 +00:00
static void moduleInterfaceHandler(int code, void* in, void* out, void* ctx) {
RecorderModule* _this = (RecorderModule*)ctx;
std::lock_guard lck(_this->recMtx);
if (code == RECORDER_IFACE_CMD_GET_MODE) {
int* _out = (int*)out;
*_out = _this->recMode;
}
else if (code == RECORDER_IFACE_CMD_SET_MODE) {
if (_this->recording) { return; }
int* _in = (int*)in;
_this->recMode = std::clamp<int>(*_in, 0, 1);
}
else if (code == RECORDER_IFACE_CMD_START) {
if (!_this->recording) { _this->start(); }
}
else if (code == RECORDER_IFACE_CMD_STOP) {
if (_this->recording) { _this->stop(); }
}
}
2020-10-01 11:46:12 +00:00
std::string name;
2020-12-08 03:36:37 +00:00
bool enabled = true;
2022-09-24 23:01:07 +00:00
std::string root;
2023-01-02 00:24:30 +00:00
char nameTemplate[1024];
2021-02-06 20:28:27 +00:00
2023-01-01 02:20:49 +00:00
OptionList<std::string, wav::Format> containers;
2022-12-06 13:52:42 +00:00
OptionList<int, wav::SampleType> sampleTypes;
FolderSelect folderSelect;
2022-09-24 23:01:07 +00:00
int recMode = RECORDER_MODE_AUDIO;
2023-01-01 02:20:49 +00:00
int containerId;
2022-12-06 13:52:42 +00:00
int sampleTypeId;
2022-09-24 23:01:07 +00:00
bool stereo = true;
2021-02-06 20:28:27 +00:00
std::string selectedStreamName = "";
2022-09-24 23:01:07 +00:00
float audioVolume = 1.0f;
bool ignoreSilence = false;
dsp::stereo_t audioLvl = { -100.0f, -100.0f };
2020-10-01 11:46:12 +00:00
2022-09-24 23:01:07 +00:00
bool recording = false;
bool ignoringSilence = false;
2022-09-24 23:01:07 +00:00
wav::Writer writer;
std::recursive_mutex recMtx;
dsp::stream<dsp::complex_t>* basebandStream;
dsp::stream<dsp::stereo_t> stereoStream;
2022-09-24 23:01:07 +00:00
dsp::sink::Handler<dsp::complex_t> basebandSink;
dsp::sink::Handler<dsp::stereo_t> stereoSink;
dsp::sink::Handler<float> monoSink;
2022-06-26 01:42:22 +00:00
2022-11-23 09:22:29 +00:00
OptionList<std::string, std::string> audioStreams;
int streamId = 0;
2022-09-26 12:57:06 +00:00
dsp::stream<dsp::stereo_t>* audioStream = NULL;
2022-09-26 15:04:24 +00:00
dsp::audio::Volume volume;
2022-09-26 12:57:06 +00:00
dsp::routing::Splitter<dsp::stereo_t> splitter;
dsp::stream<dsp::stereo_t> meterStream;
dsp::bench::PeakLevelMeter<dsp::stereo_t> meter;
2023-01-01 02:20:49 +00:00
dsp::convert::StereoToMono s2m;
2022-09-26 12:57:06 +00:00
2022-09-24 23:01:07 +00:00
uint64_t samplerate = 48000;
2020-10-01 11:46:12 +00:00
2022-11-23 09:22:29 +00:00
EventHandler<std::string> onStreamRegisteredHandler;
EventHandler<std::string> onStreamUnregisterHandler;
2020-10-01 11:46:12 +00:00
};
MOD_EXPORT void _INIT_() {
// Create default recording directory
2022-02-24 20:01:51 +00:00
std::string root = (std::string)core::args["root"];
if (!std::filesystem::exists(root + "/recordings")) {
flog::warn("Recordings directory does not exist, creating it");
if (!std::filesystem::create_directory(root + "/recordings")) {
flog::error("Could not create recordings directory");
}
}
2021-02-28 15:32:57 +00:00
json def = json({});
config.setPath(root + "/recorder_config.json");
2021-02-28 15:32:57 +00:00
config.load(def);
config.enableAutoSave();
2020-08-17 22:56:51 +00:00
}
2020-12-08 03:36:37 +00:00
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new RecorderModule(name);
2020-10-01 11:46:12 +00:00
}
2020-12-08 03:36:37 +00:00
MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* inst) {
delete (RecorderModule*)inst;
2020-08-17 22:56:51 +00:00
}
2022-09-24 23:01:07 +00:00
MOD_EXPORT void _END_() {
2021-02-28 15:32:57 +00:00
config.disableAutoSave();
config.save();
2020-08-17 22:56:51 +00:00
}