mirror of
https://github.com/Boof2015/prism.git
synced 2026-08-18 11:34:19 +02:00
855 lines
40 KiB
C++
855 lines
40 KiB
C++
#include "tui_settings.h"
|
||
|
||
#include <algorithm>
|
||
#include <cmath>
|
||
#include <cstdlib>
|
||
#include <fstream>
|
||
#include <iomanip>
|
||
#include <iterator>
|
||
#include <sstream>
|
||
#include <system_error>
|
||
#include <utility>
|
||
|
||
namespace Prism::Tui {
|
||
namespace {
|
||
|
||
const std::vector<SettingDescriptor> kAppearanceSettings = {
|
||
{SettingId::Theme, "Theme", "Uses Prism .iro files from the shared Prism Themes folder."},
|
||
{SettingId::TerminalCompatibility, "Terminal mode", "Modern uses truecolor; Compatible uses 256 colors at up to 60 FPS; Safe uses ANSI colors at 30 FPS."},
|
||
};
|
||
|
||
const std::vector<SettingDescriptor> kGeneralSettings = {
|
||
{SettingId::InputTrim, "Input trim", "Applies gain before every analyzer."},
|
||
{SettingId::RefreshRate, "Refresh rate", "120 FPS is experimental and depends heavily on the terminal."},
|
||
};
|
||
|
||
const std::vector<SettingDescriptor> kSpectrumSettings = {
|
||
{SettingId::SpectrumPeakReadout, "Peak readout", "Shows the strongest stable spectral peak in the panel title."},
|
||
{SettingId::SpectrumTilt, "Display tilt", "Offsets the spectrum by decibels per octave around 1 kHz."},
|
||
};
|
||
|
||
const std::vector<SettingDescriptor> kOscilloscopeSettings = {
|
||
{SettingId::OscilloscopePitchLock, "Pitch lock", "Stabilizes the waveform around its detected fundamental."},
|
||
{SettingId::OscilloscopeFrequencyReadout, "Frequency readout", "Shows the live detected fundamental while pitch lock is enabled."},
|
||
{SettingId::OscilloscopeTraceWeight, "Trace weight", "Changes the thickness of the oscilloscope trace."},
|
||
};
|
||
|
||
const std::vector<SettingDescriptor> kVectorscopeSettings = {
|
||
{SettingId::VectorscopeMode, "Display mode", "Changes the stereo projection used by the vectorscope."},
|
||
{SettingId::VectorscopeGuides, "Guides", "Shows the mode-specific reference contours and axes."},
|
||
{SettingId::VectorscopeDetail, "Point detail", "Balances point density against terminal rendering cost."},
|
||
};
|
||
|
||
const std::vector<SettingDescriptor> kVUMeterSettings = {
|
||
{SettingId::VUMeterMode, "Display mode", "Switches between Prism's bar and classic needle faces."},
|
||
{SettingId::VUMeterOrientation, "Bar orientation", "Chooses horizontal or vertical bars when bar mode is active."},
|
||
{SettingId::VUNeedleChannels, "Needle channels", "Shows stereo needles or one power-averaged needle."},
|
||
{SettingId::VUReferenceLevel, "0 VU reference", "Calibrates 0 VU to a dBFS reference level."},
|
||
};
|
||
|
||
const std::vector<SettingDescriptor> kLUFSMeterSettings = {
|
||
{SettingId::LUFSReadout, "Loudness readout", "Chooses which LUFS window drives the main bar and badge."},
|
||
};
|
||
|
||
const std::vector<SettingDescriptor> kSpectrogramSettings = {
|
||
{SettingId::SpectrogramColor, "Color", "Switches between Prism's heat palette and a focused mono display."},
|
||
{SettingId::SpectrogramClarity, "Clarity", "Controls frequency reassignment and spectral sharpness."},
|
||
{SettingId::SpectrogramScale, "Frequency scale", "Chooses logarithmic, mel, or linear frequency spacing."},
|
||
{SettingId::SpectrogramOrientation, "Orientation", "Scrolls time horizontally or vertically."},
|
||
{SettingId::SpectrogramScrollSpeed, "Scroll speed", "Changes how quickly new time columns move through the pane."},
|
||
{SettingId::SpectrogramContrast, "Contrast", "Shapes the intensity range without changing the analyzer input."},
|
||
{SettingId::SpectrogramTilt, "Display tilt", "Offsets frequencies by decibels per octave around 1 kHz."},
|
||
};
|
||
|
||
const std::vector<SettingDescriptor> kWaveformSettings = {
|
||
{SettingId::WaveformMode, "Channels", "Shows one mono envelope or separate left and right lanes."},
|
||
{SettingId::WaveformScrollSpeed, "Scroll speed", "Changes the time resolution of the rolling waveform."},
|
||
{SettingId::WaveformMultiband, "Multiband color", "Colors each envelope column by its dominant frequency band."},
|
||
};
|
||
|
||
float snap(float value, float step) {
|
||
return std::round(value / step) * step;
|
||
}
|
||
|
||
std::string boolValue(bool enabled) {
|
||
return enabled ? "● On" : "○ Off";
|
||
}
|
||
|
||
std::string trimFloat(float value, int precision) {
|
||
std::ostringstream output;
|
||
output << std::fixed << std::setprecision(precision) << value;
|
||
return output.str();
|
||
}
|
||
|
||
std::string serializeVectorMode(VectorscopeMode mode) {
|
||
switch (mode) {
|
||
case VectorscopeMode::Lissajous: return "lissajous";
|
||
case VectorscopeMode::PolarUnipolar: return "polar_unipolar";
|
||
case VectorscopeMode::PolarBipolar: return "polar_bipolar";
|
||
case VectorscopeMode::LinearUnipolar: return "linear_unipolar";
|
||
case VectorscopeMode::LinearBipolar: return "linear_bipolar";
|
||
}
|
||
return "lissajous";
|
||
}
|
||
|
||
std::string serializeVectorDetail(VectorscopeDetail detail) {
|
||
switch (detail) {
|
||
case VectorscopeDetail::Balanced: return "balanced";
|
||
case VectorscopeDetail::Detailed: return "detailed";
|
||
case VectorscopeDetail::Maximum: return "maximum";
|
||
}
|
||
return "detailed";
|
||
}
|
||
|
||
std::string serializeVuMode(VUMeterMode mode) {
|
||
return mode == VUMeterMode::Needle ? "needle" : "bar";
|
||
}
|
||
|
||
std::string serializeVuOrientation(VUMeterOrientation orientation) {
|
||
return orientation == VUMeterOrientation::Vertical ? "vertical" : "horizontal";
|
||
}
|
||
|
||
std::string serializeVuNeedleChannels(VUNeedleChannels channels) {
|
||
return channels == VUNeedleChannels::Combined ? "combined" : "stereo";
|
||
}
|
||
|
||
std::string serializeLufsReadout(LUFSReadout readout) {
|
||
switch (readout) {
|
||
case LUFSReadout::Momentary: return "momentary";
|
||
case LUFSReadout::ShortTerm: return "short_term";
|
||
case LUFSReadout::Integrated: return "integrated";
|
||
}
|
||
return "short_term";
|
||
}
|
||
|
||
std::string serializeSpectrogramColor(SpectrogramColorMode mode) {
|
||
return mode == SpectrogramColorMode::Mono ? "mono" : "heat";
|
||
}
|
||
|
||
std::string serializeSpectrogramClarity(SpectrogramClarity clarity) {
|
||
switch (clarity) {
|
||
case SpectrogramClarity::Classic: return "classic";
|
||
case SpectrogramClarity::Sharp: return "sharp";
|
||
case SpectrogramClarity::Sharper: return "sharper";
|
||
}
|
||
return "sharper";
|
||
}
|
||
|
||
std::string serializeSpectrogramScale(SpectrogramScale scale) {
|
||
switch (scale) {
|
||
case SpectrogramScale::Mel: return "mel";
|
||
case SpectrogramScale::Logarithmic: return "log";
|
||
case SpectrogramScale::Linear: return "linear";
|
||
}
|
||
return "log";
|
||
}
|
||
|
||
std::string serializeSpectrogramOrientation(SpectrogramOrientation orientation) {
|
||
return orientation == SpectrogramOrientation::Vertical
|
||
? "vertical"
|
||
: "horizontal";
|
||
}
|
||
|
||
std::string serializeWaveformMode(WaveformMode mode) {
|
||
return mode == WaveformMode::Stereo ? "stereo" : "mono";
|
||
}
|
||
|
||
std::string serializeTerminalCompatibility(TerminalCompatibilityMode mode) {
|
||
switch (mode) {
|
||
case TerminalCompatibilityMode::Modern: return "modern";
|
||
case TerminalCompatibilityMode::Compatible: return "compatible";
|
||
case TerminalCompatibilityMode::Safe: return "safe";
|
||
}
|
||
return "modern";
|
||
}
|
||
|
||
bool parseBool(const std::string& value, bool fallback) {
|
||
if (value == "true" || value == "1" || value == "on") return true;
|
||
if (value == "false" || value == "0" || value == "off") return false;
|
||
return fallback;
|
||
}
|
||
|
||
float parseFloat(const std::string& value, float fallback) {
|
||
try {
|
||
size_t consumed = 0;
|
||
const float parsed = std::stof(value, &consumed);
|
||
return consumed == value.size() && std::isfinite(parsed) ? parsed : fallback;
|
||
} catch (...) {
|
||
return fallback;
|
||
}
|
||
}
|
||
|
||
int parseInt(const std::string& value, int fallback) {
|
||
try {
|
||
size_t consumed = 0;
|
||
const int parsed = std::stoi(value, &consumed);
|
||
return consumed == value.size() ? parsed : fallback;
|
||
} catch (...) {
|
||
return fallback;
|
||
}
|
||
}
|
||
|
||
VectorscopeMode parseVectorMode(const std::string& value, VectorscopeMode fallback) {
|
||
if (value == "lissajous") return VectorscopeMode::Lissajous;
|
||
if (value == "polar_unipolar") return VectorscopeMode::PolarUnipolar;
|
||
if (value == "polar_bipolar") return VectorscopeMode::PolarBipolar;
|
||
if (value == "linear_unipolar") return VectorscopeMode::LinearUnipolar;
|
||
if (value == "linear_bipolar") return VectorscopeMode::LinearBipolar;
|
||
return fallback;
|
||
}
|
||
|
||
VectorscopeDetail parseVectorDetail(const std::string& value,
|
||
VectorscopeDetail fallback) {
|
||
if (value == "balanced") return VectorscopeDetail::Balanced;
|
||
if (value == "detailed") return VectorscopeDetail::Detailed;
|
||
if (value == "maximum") return VectorscopeDetail::Maximum;
|
||
return fallback;
|
||
}
|
||
|
||
VUMeterMode parseVuMode(const std::string& value, VUMeterMode fallback) {
|
||
if (value == "bar") return VUMeterMode::Bar;
|
||
if (value == "needle") return VUMeterMode::Needle;
|
||
return fallback;
|
||
}
|
||
|
||
VUMeterOrientation parseVuOrientation(const std::string& value,
|
||
VUMeterOrientation fallback) {
|
||
if (value == "horizontal") return VUMeterOrientation::Horizontal;
|
||
if (value == "vertical") return VUMeterOrientation::Vertical;
|
||
return fallback;
|
||
}
|
||
|
||
VUNeedleChannels parseVuNeedleChannels(const std::string& value,
|
||
VUNeedleChannels fallback) {
|
||
if (value == "stereo") return VUNeedleChannels::Stereo;
|
||
if (value == "combined") return VUNeedleChannels::Combined;
|
||
return fallback;
|
||
}
|
||
|
||
LUFSReadout parseLufsReadout(const std::string& value, LUFSReadout fallback) {
|
||
if (value == "momentary") return LUFSReadout::Momentary;
|
||
if (value == "short_term") return LUFSReadout::ShortTerm;
|
||
if (value == "integrated") return LUFSReadout::Integrated;
|
||
return fallback;
|
||
}
|
||
|
||
SpectrogramColorMode parseSpectrogramColor(
|
||
const std::string& value, SpectrogramColorMode fallback) {
|
||
if (value == "heat") return SpectrogramColorMode::Heat;
|
||
if (value == "mono") return SpectrogramColorMode::Mono;
|
||
return fallback;
|
||
}
|
||
|
||
SpectrogramClarity parseSpectrogramClarity(
|
||
const std::string& value, SpectrogramClarity fallback) {
|
||
if (value == "classic") return SpectrogramClarity::Classic;
|
||
if (value == "sharp") return SpectrogramClarity::Sharp;
|
||
if (value == "sharper") return SpectrogramClarity::Sharper;
|
||
return fallback;
|
||
}
|
||
|
||
SpectrogramScale parseSpectrogramScale(
|
||
const std::string& value, SpectrogramScale fallback) {
|
||
if (value == "mel") return SpectrogramScale::Mel;
|
||
if (value == "log") return SpectrogramScale::Logarithmic;
|
||
if (value == "linear") return SpectrogramScale::Linear;
|
||
return fallback;
|
||
}
|
||
|
||
SpectrogramOrientation parseSpectrogramOrientation(
|
||
const std::string& value, SpectrogramOrientation fallback) {
|
||
if (value == "horizontal") return SpectrogramOrientation::Horizontal;
|
||
if (value == "vertical") return SpectrogramOrientation::Vertical;
|
||
return fallback;
|
||
}
|
||
|
||
WaveformMode parseWaveformMode(const std::string& value, WaveformMode fallback) {
|
||
if (value == "mono") return WaveformMode::Mono;
|
||
if (value == "stereo") return WaveformMode::Stereo;
|
||
return fallback;
|
||
}
|
||
|
||
TerminalCompatibilityMode parseTerminalCompatibility(
|
||
const std::string& value,
|
||
TerminalCompatibilityMode fallback) {
|
||
if (value == "modern") return TerminalCompatibilityMode::Modern;
|
||
if (value == "compatible") return TerminalCompatibilityMode::Compatible;
|
||
if (value == "safe") return TerminalCompatibilityMode::Safe;
|
||
return fallback;
|
||
}
|
||
|
||
const char* environmentValue(const char* name) {
|
||
const char* value = std::getenv(name);
|
||
return value != nullptr && value[0] != '\0' ? value : nullptr;
|
||
}
|
||
|
||
} // namespace
|
||
|
||
const char* spectrogramColorName(SpectrogramColorMode mode) {
|
||
return mode == SpectrogramColorMode::Mono ? "Mono" : "Heat";
|
||
}
|
||
|
||
const char* spectrogramClarityName(SpectrogramClarity clarity) {
|
||
switch (clarity) {
|
||
case SpectrogramClarity::Classic: return "Classic";
|
||
case SpectrogramClarity::Sharp: return "Sharp";
|
||
case SpectrogramClarity::Sharper: return "Sharper";
|
||
}
|
||
return "Sharper";
|
||
}
|
||
|
||
const char* spectrogramScaleName(SpectrogramScale scale) {
|
||
switch (scale) {
|
||
case SpectrogramScale::Mel: return "Mel";
|
||
case SpectrogramScale::Logarithmic: return "Log";
|
||
case SpectrogramScale::Linear: return "Linear";
|
||
}
|
||
return "Log";
|
||
}
|
||
|
||
const char* spectrogramOrientationName(SpectrogramOrientation orientation) {
|
||
return orientation == SpectrogramOrientation::Vertical
|
||
? "Vertical"
|
||
: "Horizontal";
|
||
}
|
||
|
||
const char* waveformModeName(WaveformMode mode) {
|
||
return mode == WaveformMode::Stereo ? "Stereo" : "Mono";
|
||
}
|
||
|
||
const char* terminalCompatibilityName(TerminalCompatibilityMode mode) {
|
||
switch (mode) {
|
||
case TerminalCompatibilityMode::Modern: return "Modern";
|
||
case TerminalCompatibilityMode::Compatible: return "Compatible";
|
||
case TerminalCompatibilityMode::Safe: return "Safe";
|
||
}
|
||
return "Modern";
|
||
}
|
||
|
||
int effectiveRefreshRate(int requestedRate, TerminalCompatibilityMode mode) {
|
||
const int normalized = requestedRate <= 30
|
||
? 30
|
||
: requestedRate <= 60 ? 60 : 120;
|
||
switch (mode) {
|
||
case TerminalCompatibilityMode::Modern:
|
||
return normalized;
|
||
case TerminalCompatibilityMode::Compatible:
|
||
return std::min(normalized, 60);
|
||
case TerminalCompatibilityMode::Safe:
|
||
return 30;
|
||
}
|
||
return normalized;
|
||
}
|
||
|
||
TuiSettings normalizeSettings(TuiSettings settings) {
|
||
settings.themeId.erase(std::remove_if(
|
||
settings.themeId.begin(), settings.themeId.end(), [](char character) {
|
||
return character == '\n' || character == '\r';
|
||
}), settings.themeId.end());
|
||
if (settings.themeId.empty()) settings.themeId = "Default";
|
||
settings.inputTrimDb = std::clamp(snap(settings.inputTrimDb, 0.5f), -12.0f, 12.0f);
|
||
settings.refreshRate = settings.refreshRate <= 30
|
||
? 30
|
||
: settings.refreshRate <= 60 ? 60 : 120;
|
||
settings.rackLayout = normalizeRackLayout(std::move(settings.rackLayout));
|
||
settings.spectrumTiltDbPerOctave = std::clamp(
|
||
snap(settings.spectrumTiltDbPerOctave, 0.1f), -2.0f, 8.0f);
|
||
settings.oscilloscopeTraceWeight = std::clamp(settings.oscilloscopeTraceWeight, 1, 3);
|
||
settings.vuReferenceDbfs = std::clamp(
|
||
snap(settings.vuReferenceDbfs, 1.0f), -30.0f, 0.0f);
|
||
settings.spectrogramScrollSpeed = std::clamp(
|
||
snap(settings.spectrogramScrollSpeed, 0.5f), 0.5f, 4.0f);
|
||
settings.spectrogramContrast = std::clamp(
|
||
snap(settings.spectrogramContrast, 0.1f), 0.5f, 2.0f);
|
||
settings.spectrogramTiltDbPerOctave = std::clamp(
|
||
snap(settings.spectrogramTiltDbPerOctave, 0.5f), -2.0f, 8.0f);
|
||
settings.waveformScrollSpeed = std::clamp(settings.waveformScrollSpeed, 1, 8);
|
||
if (!settings.oscilloscopePitchLock) {
|
||
settings.oscilloscopeFrequencyReadout = false;
|
||
}
|
||
return settings;
|
||
}
|
||
|
||
bool operator==(const TuiSettings& left, const TuiSettings& right) {
|
||
return left.themeId == right.themeId &&
|
||
left.terminalCompatibility == right.terminalCompatibility &&
|
||
left.inputTrimDb == right.inputTrimDb &&
|
||
left.refreshRate == right.refreshRate &&
|
||
left.rackLayout == right.rackLayout &&
|
||
left.spectrumPeakReadout == right.spectrumPeakReadout &&
|
||
left.spectrumTiltDbPerOctave == right.spectrumTiltDbPerOctave &&
|
||
left.oscilloscopePitchLock == right.oscilloscopePitchLock &&
|
||
left.oscilloscopeFrequencyReadout == right.oscilloscopeFrequencyReadout &&
|
||
left.oscilloscopeTraceWeight == right.oscilloscopeTraceWeight &&
|
||
left.vectorscopeMode == right.vectorscopeMode &&
|
||
left.vectorscopeGuides == right.vectorscopeGuides &&
|
||
left.vectorscopeDetail == right.vectorscopeDetail &&
|
||
left.vuMeterMode == right.vuMeterMode &&
|
||
left.vuMeterOrientation == right.vuMeterOrientation &&
|
||
left.vuNeedleChannels == right.vuNeedleChannels &&
|
||
left.vuReferenceDbfs == right.vuReferenceDbfs &&
|
||
left.lufsReadout == right.lufsReadout &&
|
||
left.spectrogramColor == right.spectrogramColor &&
|
||
left.spectrogramClarity == right.spectrogramClarity &&
|
||
left.spectrogramScale == right.spectrogramScale &&
|
||
left.spectrogramOrientation == right.spectrogramOrientation &&
|
||
left.spectrogramScrollSpeed == right.spectrogramScrollSpeed &&
|
||
left.spectrogramContrast == right.spectrogramContrast &&
|
||
left.spectrogramTiltDbPerOctave == right.spectrogramTiltDbPerOctave &&
|
||
left.waveformMode == right.waveformMode &&
|
||
left.waveformScrollSpeed == right.waveformScrollSpeed &&
|
||
left.waveformMultiband == right.waveformMultiband;
|
||
}
|
||
|
||
bool operator!=(const TuiSettings& left, const TuiSettings& right) {
|
||
return !(left == right);
|
||
}
|
||
|
||
std::vector<SettingsPage> settingsPages() {
|
||
return {
|
||
SettingsPage::Appearance,
|
||
SettingsPage::General,
|
||
SettingsPage::Spectrum,
|
||
SettingsPage::Oscilloscope,
|
||
SettingsPage::Vectorscope,
|
||
SettingsPage::VUMeter,
|
||
SettingsPage::LUFSMeter,
|
||
SettingsPage::Spectrogram,
|
||
SettingsPage::Waveform,
|
||
};
|
||
}
|
||
|
||
const char* settingsPageName(SettingsPage page) {
|
||
switch (page) {
|
||
case SettingsPage::Home: return "Settings";
|
||
case SettingsPage::Appearance: return "Appearance";
|
||
case SettingsPage::General: return "General";
|
||
case SettingsPage::Spectrum: return "Spectrum";
|
||
case SettingsPage::Oscilloscope: return "Oscilloscope";
|
||
case SettingsPage::Vectorscope: return "Vectorscope";
|
||
case SettingsPage::VUMeter: return "VU meter";
|
||
case SettingsPage::LUFSMeter: return "LUFS meter";
|
||
case SettingsPage::Spectrogram: return "Spectrogram";
|
||
case SettingsPage::Waveform: return "Waveform";
|
||
}
|
||
return "Settings";
|
||
}
|
||
|
||
const char* settingsPageDescription(SettingsPage page) {
|
||
switch (page) {
|
||
case SettingsPage::Home: return "Choose a section.";
|
||
case SettingsPage::Appearance: return "Shared Prism .iro colors for the terminal dashboard.";
|
||
case SettingsPage::General: return "Audio input and dashboard behavior.";
|
||
case SettingsPage::Spectrum: return "Frequency analysis and readouts.";
|
||
case SettingsPage::Oscilloscope: return "Waveform stabilization and presentation.";
|
||
case SettingsPage::Vectorscope: return "Stereo projection and point rendering.";
|
||
case SettingsPage::VUMeter: return "Classic level, peak, and phase metering.";
|
||
case SettingsPage::LUFSMeter: return "Loudness window and target presentation.";
|
||
case SettingsPage::Spectrogram: return "Frequency history, color, and time direction.";
|
||
case SettingsPage::Waveform: return "Rolling amplitude envelope and band color.";
|
||
}
|
||
return {};
|
||
}
|
||
|
||
const std::vector<SettingDescriptor>& settingsForPage(SettingsPage page) {
|
||
switch (page) {
|
||
case SettingsPage::Appearance: return kAppearanceSettings;
|
||
case SettingsPage::General: return kGeneralSettings;
|
||
case SettingsPage::Spectrum: return kSpectrumSettings;
|
||
case SettingsPage::Oscilloscope: return kOscilloscopeSettings;
|
||
case SettingsPage::Vectorscope: return kVectorscopeSettings;
|
||
case SettingsPage::VUMeter: return kVUMeterSettings;
|
||
case SettingsPage::LUFSMeter: return kLUFSMeterSettings;
|
||
case SettingsPage::Spectrogram: return kSpectrogramSettings;
|
||
case SettingsPage::Waveform: return kWaveformSettings;
|
||
case SettingsPage::Home: break;
|
||
}
|
||
static const std::vector<SettingDescriptor> empty;
|
||
return empty;
|
||
}
|
||
|
||
std::string settingValue(const TuiSettings& settings, SettingId setting) {
|
||
switch (setting) {
|
||
case SettingId::Theme:
|
||
return settings.themeId;
|
||
case SettingId::TerminalCompatibility:
|
||
return terminalCompatibilityName(settings.terminalCompatibility);
|
||
case SettingId::InputTrim:
|
||
return (settings.inputTrimDb > 0.0f ? "+" : "") +
|
||
trimFloat(settings.inputTrimDb, 1) + " dB";
|
||
case SettingId::RefreshRate:
|
||
return std::to_string(settings.refreshRate) + " FPS" +
|
||
(settings.refreshRate == 120 ? " (experimental)" : "");
|
||
case SettingId::SpectrumPeakReadout:
|
||
return boolValue(settings.spectrumPeakReadout);
|
||
case SettingId::SpectrumTilt:
|
||
return trimFloat(settings.spectrumTiltDbPerOctave, 1) + " dB/oct";
|
||
case SettingId::OscilloscopePitchLock:
|
||
return boolValue(settings.oscilloscopePitchLock);
|
||
case SettingId::OscilloscopeFrequencyReadout:
|
||
return boolValue(settings.oscilloscopeFrequencyReadout);
|
||
case SettingId::OscilloscopeTraceWeight:
|
||
return std::to_string(settings.oscilloscopeTraceWeight);
|
||
case SettingId::VectorscopeMode:
|
||
return vectorscopeModeName(settings.vectorscopeMode);
|
||
case SettingId::VectorscopeGuides:
|
||
return boolValue(settings.vectorscopeGuides);
|
||
case SettingId::VectorscopeDetail:
|
||
switch (settings.vectorscopeDetail) {
|
||
case VectorscopeDetail::Balanced: return "Balanced";
|
||
case VectorscopeDetail::Detailed: return "Detailed";
|
||
case VectorscopeDetail::Maximum: return "Maximum";
|
||
}
|
||
case SettingId::VUMeterMode:
|
||
return vuMeterModeName(settings.vuMeterMode);
|
||
case SettingId::VUMeterOrientation:
|
||
return vuMeterOrientationName(settings.vuMeterOrientation);
|
||
case SettingId::VUNeedleChannels:
|
||
return vuNeedleChannelsName(settings.vuNeedleChannels);
|
||
case SettingId::VUReferenceLevel:
|
||
return trimFloat(settings.vuReferenceDbfs, 0) + " dBFS";
|
||
case SettingId::LUFSReadout:
|
||
return lufsReadoutName(settings.lufsReadout);
|
||
case SettingId::SpectrogramColor:
|
||
return spectrogramColorName(settings.spectrogramColor);
|
||
case SettingId::SpectrogramClarity:
|
||
return spectrogramClarityName(settings.spectrogramClarity);
|
||
case SettingId::SpectrogramScale:
|
||
return spectrogramScaleName(settings.spectrogramScale);
|
||
case SettingId::SpectrogramOrientation:
|
||
return spectrogramOrientationName(settings.spectrogramOrientation);
|
||
case SettingId::SpectrogramScrollSpeed:
|
||
return trimFloat(settings.spectrogramScrollSpeed, 1) + "×";
|
||
case SettingId::SpectrogramContrast:
|
||
return trimFloat(settings.spectrogramContrast, 1) + "×";
|
||
case SettingId::SpectrogramTilt:
|
||
return trimFloat(settings.spectrogramTiltDbPerOctave, 1) + " dB/oct";
|
||
case SettingId::WaveformMode:
|
||
return waveformModeName(settings.waveformMode);
|
||
case SettingId::WaveformScrollSpeed:
|
||
return std::to_string(settings.waveformScrollSpeed) + "×";
|
||
case SettingId::WaveformMultiband:
|
||
return boolValue(settings.waveformMultiband);
|
||
}
|
||
return {};
|
||
}
|
||
|
||
bool settingIsBoolean(SettingId setting) {
|
||
return setting == SettingId::SpectrumPeakReadout ||
|
||
setting == SettingId::OscilloscopePitchLock ||
|
||
setting == SettingId::OscilloscopeFrequencyReadout ||
|
||
setting == SettingId::VectorscopeGuides ||
|
||
setting == SettingId::WaveformMultiband;
|
||
}
|
||
|
||
bool adjustSetting(TuiSettings& settings, SettingId setting, int direction) {
|
||
if (direction == 0) return false;
|
||
const TuiSettings before = settings;
|
||
switch (setting) {
|
||
case SettingId::Theme:
|
||
return false;
|
||
case SettingId::TerminalCompatibility: {
|
||
int value = static_cast<int>(settings.terminalCompatibility);
|
||
value = (value + (direction > 0 ? 1 : 2)) % 3;
|
||
settings.terminalCompatibility =
|
||
static_cast<TerminalCompatibilityMode>(value);
|
||
break;
|
||
}
|
||
case SettingId::InputTrim:
|
||
settings.inputTrimDb += direction > 0 ? 0.5f : -0.5f;
|
||
break;
|
||
case SettingId::RefreshRate:
|
||
if (direction > 0) {
|
||
settings.refreshRate = settings.refreshRate == 30
|
||
? 60
|
||
: settings.refreshRate == 60 ? 120 : 30;
|
||
} else {
|
||
settings.refreshRate = settings.refreshRate == 120
|
||
? 60
|
||
: settings.refreshRate == 60 ? 30 : 120;
|
||
}
|
||
break;
|
||
case SettingId::SpectrumPeakReadout:
|
||
settings.spectrumPeakReadout = !settings.spectrumPeakReadout;
|
||
break;
|
||
case SettingId::SpectrumTilt:
|
||
settings.spectrumTiltDbPerOctave += direction > 0 ? 0.1f : -0.1f;
|
||
break;
|
||
case SettingId::OscilloscopePitchLock:
|
||
settings.oscilloscopePitchLock = !settings.oscilloscopePitchLock;
|
||
if (!settings.oscilloscopePitchLock) {
|
||
settings.oscilloscopeFrequencyReadout = false;
|
||
}
|
||
break;
|
||
case SettingId::OscilloscopeFrequencyReadout:
|
||
if (!settings.oscilloscopePitchLock) {
|
||
return false;
|
||
}
|
||
settings.oscilloscopeFrequencyReadout = !settings.oscilloscopeFrequencyReadout;
|
||
break;
|
||
case SettingId::OscilloscopeTraceWeight:
|
||
settings.oscilloscopeTraceWeight += direction > 0 ? 1 : -1;
|
||
break;
|
||
case SettingId::VectorscopeMode:
|
||
if (direction > 0) {
|
||
settings.vectorscopeMode = nextVectorscopeMode(settings.vectorscopeMode);
|
||
} else {
|
||
for (int index = 0; index < 4; ++index) {
|
||
settings.vectorscopeMode = nextVectorscopeMode(settings.vectorscopeMode);
|
||
}
|
||
}
|
||
break;
|
||
case SettingId::VectorscopeGuides:
|
||
settings.vectorscopeGuides = !settings.vectorscopeGuides;
|
||
break;
|
||
case SettingId::VectorscopeDetail: {
|
||
int value = static_cast<int>(settings.vectorscopeDetail);
|
||
value = std::clamp(value + (direction > 0 ? 1 : -1), 0, 2);
|
||
settings.vectorscopeDetail = static_cast<VectorscopeDetail>(value);
|
||
break;
|
||
}
|
||
case SettingId::VUMeterMode:
|
||
settings.vuMeterMode = settings.vuMeterMode == VUMeterMode::Bar
|
||
? VUMeterMode::Needle
|
||
: VUMeterMode::Bar;
|
||
break;
|
||
case SettingId::VUMeterOrientation:
|
||
settings.vuMeterOrientation =
|
||
settings.vuMeterOrientation == VUMeterOrientation::Horizontal
|
||
? VUMeterOrientation::Vertical
|
||
: VUMeterOrientation::Horizontal;
|
||
break;
|
||
case SettingId::VUNeedleChannels:
|
||
settings.vuNeedleChannels =
|
||
settings.vuNeedleChannels == VUNeedleChannels::Stereo
|
||
? VUNeedleChannels::Combined
|
||
: VUNeedleChannels::Stereo;
|
||
break;
|
||
case SettingId::VUReferenceLevel:
|
||
settings.vuReferenceDbfs += direction > 0 ? 1.0f : -1.0f;
|
||
break;
|
||
case SettingId::LUFSReadout: {
|
||
int value = static_cast<int>(settings.lufsReadout);
|
||
value = (value + (direction > 0 ? 1 : 2)) % 3;
|
||
settings.lufsReadout = static_cast<LUFSReadout>(value);
|
||
break;
|
||
}
|
||
case SettingId::SpectrogramColor:
|
||
settings.spectrogramColor = settings.spectrogramColor == SpectrogramColorMode::Heat
|
||
? SpectrogramColorMode::Mono
|
||
: SpectrogramColorMode::Heat;
|
||
break;
|
||
case SettingId::SpectrogramClarity: {
|
||
int value = static_cast<int>(settings.spectrogramClarity);
|
||
value = (value + (direction > 0 ? 1 : 2)) % 3;
|
||
settings.spectrogramClarity = static_cast<SpectrogramClarity>(value);
|
||
break;
|
||
}
|
||
case SettingId::SpectrogramScale: {
|
||
int value = static_cast<int>(settings.spectrogramScale);
|
||
value = (value + (direction > 0 ? 1 : 2)) % 3;
|
||
settings.spectrogramScale = static_cast<SpectrogramScale>(value);
|
||
break;
|
||
}
|
||
case SettingId::SpectrogramOrientation:
|
||
settings.spectrogramOrientation =
|
||
settings.spectrogramOrientation == SpectrogramOrientation::Horizontal
|
||
? SpectrogramOrientation::Vertical
|
||
: SpectrogramOrientation::Horizontal;
|
||
break;
|
||
case SettingId::SpectrogramScrollSpeed:
|
||
settings.spectrogramScrollSpeed += direction > 0 ? 0.5f : -0.5f;
|
||
break;
|
||
case SettingId::SpectrogramContrast:
|
||
settings.spectrogramContrast += direction > 0 ? 0.1f : -0.1f;
|
||
break;
|
||
case SettingId::SpectrogramTilt:
|
||
settings.spectrogramTiltDbPerOctave += direction > 0 ? 0.5f : -0.5f;
|
||
break;
|
||
case SettingId::WaveformMode:
|
||
settings.waveformMode = settings.waveformMode == WaveformMode::Mono
|
||
? WaveformMode::Stereo
|
||
: WaveformMode::Mono;
|
||
break;
|
||
case SettingId::WaveformScrollSpeed:
|
||
settings.waveformScrollSpeed += direction > 0 ? 1 : -1;
|
||
break;
|
||
case SettingId::WaveformMultiband:
|
||
settings.waveformMultiband = !settings.waveformMultiband;
|
||
break;
|
||
}
|
||
settings = normalizeSettings(settings);
|
||
return settings != before;
|
||
}
|
||
|
||
bool resetSetting(TuiSettings& settings, SettingId setting) {
|
||
const TuiSettings defaults;
|
||
const TuiSettings before = settings;
|
||
switch (setting) {
|
||
case SettingId::Theme: settings.themeId = defaults.themeId; break;
|
||
case SettingId::TerminalCompatibility: settings.terminalCompatibility = defaults.terminalCompatibility; break;
|
||
case SettingId::InputTrim: settings.inputTrimDb = defaults.inputTrimDb; break;
|
||
case SettingId::RefreshRate: settings.refreshRate = defaults.refreshRate; break;
|
||
case SettingId::SpectrumPeakReadout: settings.spectrumPeakReadout = defaults.spectrumPeakReadout; break;
|
||
case SettingId::SpectrumTilt: settings.spectrumTiltDbPerOctave = defaults.spectrumTiltDbPerOctave; break;
|
||
case SettingId::OscilloscopePitchLock: settings.oscilloscopePitchLock = defaults.oscilloscopePitchLock; break;
|
||
case SettingId::OscilloscopeFrequencyReadout: settings.oscilloscopeFrequencyReadout = defaults.oscilloscopeFrequencyReadout; break;
|
||
case SettingId::OscilloscopeTraceWeight: settings.oscilloscopeTraceWeight = defaults.oscilloscopeTraceWeight; break;
|
||
case SettingId::VectorscopeMode: settings.vectorscopeMode = defaults.vectorscopeMode; break;
|
||
case SettingId::VectorscopeGuides: settings.vectorscopeGuides = defaults.vectorscopeGuides; break;
|
||
case SettingId::VectorscopeDetail: settings.vectorscopeDetail = defaults.vectorscopeDetail; break;
|
||
case SettingId::VUMeterMode: settings.vuMeterMode = defaults.vuMeterMode; break;
|
||
case SettingId::VUMeterOrientation: settings.vuMeterOrientation = defaults.vuMeterOrientation; break;
|
||
case SettingId::VUNeedleChannels: settings.vuNeedleChannels = defaults.vuNeedleChannels; break;
|
||
case SettingId::VUReferenceLevel: settings.vuReferenceDbfs = defaults.vuReferenceDbfs; break;
|
||
case SettingId::LUFSReadout: settings.lufsReadout = defaults.lufsReadout; break;
|
||
case SettingId::SpectrogramColor: settings.spectrogramColor = defaults.spectrogramColor; break;
|
||
case SettingId::SpectrogramClarity: settings.spectrogramClarity = defaults.spectrogramClarity; break;
|
||
case SettingId::SpectrogramScale: settings.spectrogramScale = defaults.spectrogramScale; break;
|
||
case SettingId::SpectrogramOrientation: settings.spectrogramOrientation = defaults.spectrogramOrientation; break;
|
||
case SettingId::SpectrogramScrollSpeed: settings.spectrogramScrollSpeed = defaults.spectrogramScrollSpeed; break;
|
||
case SettingId::SpectrogramContrast: settings.spectrogramContrast = defaults.spectrogramContrast; break;
|
||
case SettingId::SpectrogramTilt: settings.spectrogramTiltDbPerOctave = defaults.spectrogramTiltDbPerOctave; break;
|
||
case SettingId::WaveformMode: settings.waveformMode = defaults.waveformMode; break;
|
||
case SettingId::WaveformScrollSpeed: settings.waveformScrollSpeed = defaults.waveformScrollSpeed; break;
|
||
case SettingId::WaveformMultiband: settings.waveformMultiband = defaults.waveformMultiband; break;
|
||
}
|
||
return settings != before;
|
||
}
|
||
|
||
std::filesystem::path defaultSettingsPath() {
|
||
#if defined(_WIN32)
|
||
if (const char* appData = environmentValue("APPDATA")) {
|
||
return std::filesystem::path(appData) / "Prism" / "tui.conf";
|
||
}
|
||
#elif defined(__APPLE__)
|
||
if (const char* home = environmentValue("HOME")) {
|
||
return std::filesystem::path(home) /
|
||
"Library" / "Application Support" / "Prism" / "tui.conf";
|
||
}
|
||
#else
|
||
if (const char* xdgConfig = environmentValue("XDG_CONFIG_HOME")) {
|
||
return std::filesystem::path(xdgConfig) / "prism" / "tui.conf";
|
||
}
|
||
if (const char* home = environmentValue("HOME")) {
|
||
return std::filesystem::path(home) / ".config" / "prism" / "tui.conf";
|
||
}
|
||
#endif
|
||
return std::filesystem::path("prism-tui.conf");
|
||
}
|
||
|
||
TuiSettings loadSettings(const std::filesystem::path& path) {
|
||
std::ifstream input(path);
|
||
if (!input) return {};
|
||
return parseSettingsText(
|
||
std::string(
|
||
std::istreambuf_iterator<char>(input),
|
||
std::istreambuf_iterator<char>()));
|
||
}
|
||
|
||
TuiSettings parseSettingsText(const std::string& text,
|
||
const TuiSettings& fallback) {
|
||
TuiSettings settings = fallback;
|
||
std::istringstream input(text);
|
||
std::string line;
|
||
while (std::getline(input, line)) {
|
||
const size_t separator = line.find('=');
|
||
if (separator == std::string::npos) continue;
|
||
const std::string key = line.substr(0, separator);
|
||
const std::string value = line.substr(separator + 1);
|
||
if (key == "theme_id") settings.themeId = value;
|
||
else if (key == "terminal_mode") settings.terminalCompatibility = parseTerminalCompatibility(value, settings.terminalCompatibility);
|
||
else if (key == "input_trim_db") settings.inputTrimDb = parseFloat(value, settings.inputTrimDb);
|
||
else if (key == "refresh_rate") settings.refreshRate = parseInt(value, settings.refreshRate);
|
||
else if (key == "rack_layout") settings.rackLayout = parseRackLayout(value, settings.rackLayout);
|
||
else if (key == "spectrum_peak") settings.spectrumPeakReadout = parseBool(value, settings.spectrumPeakReadout);
|
||
else if (key == "spectrum_tilt") settings.spectrumTiltDbPerOctave = parseFloat(value, settings.spectrumTiltDbPerOctave);
|
||
else if (key == "osc_pitch_lock") settings.oscilloscopePitchLock = parseBool(value, settings.oscilloscopePitchLock);
|
||
else if (key == "osc_frequency") settings.oscilloscopeFrequencyReadout = parseBool(value, settings.oscilloscopeFrequencyReadout);
|
||
else if (key == "osc_trace_weight") settings.oscilloscopeTraceWeight = parseInt(value, settings.oscilloscopeTraceWeight);
|
||
else if (key == "vector_mode") settings.vectorscopeMode = parseVectorMode(value, settings.vectorscopeMode);
|
||
else if (key == "vector_guides") settings.vectorscopeGuides = parseBool(value, settings.vectorscopeGuides);
|
||
else if (key == "vector_detail") settings.vectorscopeDetail = parseVectorDetail(value, settings.vectorscopeDetail);
|
||
else if (key == "vu_mode") settings.vuMeterMode = parseVuMode(value, settings.vuMeterMode);
|
||
else if (key == "vu_orientation") settings.vuMeterOrientation = parseVuOrientation(value, settings.vuMeterOrientation);
|
||
else if (key == "vu_needle_channels") settings.vuNeedleChannels = parseVuNeedleChannels(value, settings.vuNeedleChannels);
|
||
else if (key == "vu_reference_dbfs") settings.vuReferenceDbfs = parseFloat(value, settings.vuReferenceDbfs);
|
||
else if (key == "lufs_readout") settings.lufsReadout = parseLufsReadout(value, settings.lufsReadout);
|
||
else if (key == "spectrogram_color") settings.spectrogramColor = parseSpectrogramColor(value, settings.spectrogramColor);
|
||
else if (key == "spectrogram_clarity") settings.spectrogramClarity = parseSpectrogramClarity(value, settings.spectrogramClarity);
|
||
else if (key == "spectrogram_scale") settings.spectrogramScale = parseSpectrogramScale(value, settings.spectrogramScale);
|
||
else if (key == "spectrogram_orientation") settings.spectrogramOrientation = parseSpectrogramOrientation(value, settings.spectrogramOrientation);
|
||
else if (key == "spectrogram_scroll") settings.spectrogramScrollSpeed = parseFloat(value, settings.spectrogramScrollSpeed);
|
||
else if (key == "spectrogram_contrast") settings.spectrogramContrast = parseFloat(value, settings.spectrogramContrast);
|
||
else if (key == "spectrogram_tilt") settings.spectrogramTiltDbPerOctave = parseFloat(value, settings.spectrogramTiltDbPerOctave);
|
||
else if (key == "waveform_mode") settings.waveformMode = parseWaveformMode(value, settings.waveformMode);
|
||
else if (key == "waveform_scroll") settings.waveformScrollSpeed = parseInt(value, settings.waveformScrollSpeed);
|
||
else if (key == "waveform_multiband") settings.waveformMultiband = parseBool(value, settings.waveformMultiband);
|
||
}
|
||
return normalizeSettings(settings);
|
||
}
|
||
|
||
std::string serializeSettingsText(const TuiSettings& rawSettings,
|
||
bool includeRefreshRate) {
|
||
const TuiSettings settings = normalizeSettings(rawSettings);
|
||
std::ostringstream output;
|
||
output << "theme_id=" << settings.themeId << '\n';
|
||
if (includeRefreshRate) {
|
||
output << "terminal_mode="
|
||
<< serializeTerminalCompatibility(settings.terminalCompatibility) << '\n'
|
||
<< "refresh_rate=" << settings.refreshRate << '\n';
|
||
}
|
||
output << "input_trim_db=" << settings.inputTrimDb << '\n'
|
||
<< "rack_layout=" << serializeRackLayout(settings.rackLayout) << '\n'
|
||
<< "spectrum_peak=" << (settings.spectrumPeakReadout ? "true" : "false") << '\n'
|
||
<< "spectrum_tilt=" << settings.spectrumTiltDbPerOctave << '\n'
|
||
<< "osc_pitch_lock=" << (settings.oscilloscopePitchLock ? "true" : "false") << '\n'
|
||
<< "osc_frequency=" << (settings.oscilloscopeFrequencyReadout ? "true" : "false") << '\n'
|
||
<< "osc_trace_weight=" << settings.oscilloscopeTraceWeight << '\n'
|
||
<< "vector_mode=" << serializeVectorMode(settings.vectorscopeMode) << '\n'
|
||
<< "vector_guides=" << (settings.vectorscopeGuides ? "true" : "false") << '\n'
|
||
<< "vector_detail=" << serializeVectorDetail(settings.vectorscopeDetail) << '\n'
|
||
<< "vu_mode=" << serializeVuMode(settings.vuMeterMode) << '\n'
|
||
<< "vu_orientation=" << serializeVuOrientation(settings.vuMeterOrientation) << '\n'
|
||
<< "vu_needle_channels=" << serializeVuNeedleChannels(settings.vuNeedleChannels) << '\n'
|
||
<< "vu_reference_dbfs=" << settings.vuReferenceDbfs << '\n'
|
||
<< "lufs_readout=" << serializeLufsReadout(settings.lufsReadout) << '\n'
|
||
<< "spectrogram_color=" << serializeSpectrogramColor(settings.spectrogramColor) << '\n'
|
||
<< "spectrogram_clarity=" << serializeSpectrogramClarity(settings.spectrogramClarity) << '\n'
|
||
<< "spectrogram_scale=" << serializeSpectrogramScale(settings.spectrogramScale) << '\n'
|
||
<< "spectrogram_orientation=" << serializeSpectrogramOrientation(settings.spectrogramOrientation) << '\n'
|
||
<< "spectrogram_scroll=" << settings.spectrogramScrollSpeed << '\n'
|
||
<< "spectrogram_contrast=" << settings.spectrogramContrast << '\n'
|
||
<< "spectrogram_tilt=" << settings.spectrogramTiltDbPerOctave << '\n'
|
||
<< "waveform_mode=" << serializeWaveformMode(settings.waveformMode) << '\n'
|
||
<< "waveform_scroll=" << settings.waveformScrollSpeed << '\n'
|
||
<< "waveform_multiband=" << (settings.waveformMultiband ? "true" : "false") << '\n';
|
||
return output.str();
|
||
}
|
||
|
||
bool saveSettings(const TuiSettings& rawSettings,
|
||
const std::filesystem::path& path,
|
||
std::string* error) {
|
||
std::error_code filesystemError;
|
||
if (!path.parent_path().empty()) {
|
||
std::filesystem::create_directories(path.parent_path(), filesystemError);
|
||
if (filesystemError) {
|
||
if (error) *error = filesystemError.message();
|
||
return false;
|
||
}
|
||
}
|
||
std::ofstream output(path, std::ios::trunc);
|
||
if (!output) {
|
||
if (error) *error = "could not open settings file";
|
||
return false;
|
||
}
|
||
output << serializeSettingsText(rawSettings);
|
||
if (!output) {
|
||
if (error) *error = "could not write settings file";
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
} // namespace Prism::Tui
|