Make volume button mutable, and add ability to save global settings

pull/298/head
James H Ball 2025-04-04 23:00:38 +01:00
rodzic 3580ef5fa4
commit cdf3e88e17
11 zmienionych plików z 134 dodań i 23 usunięć

Wyświetl plik

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3,9H7L12,4V20L7,15H3V9M16.59,12L14,9.41L15.41,8L18,10.59L20.59,8L22,9.41L19.41,12L22,14.59L20.59,16L18,13.41L15.41,16L14,14.59L16.59,12Z" /></svg>

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 216 B

Wyświetl plik

@ -17,6 +17,20 @@ CommonAudioProcessor::CommonAudioProcessor(const BusesProperties& busesPropertie
: AudioProcessor(busesProperties)
#endif
{
// Initialize the global settings with the plugin name
juce::PropertiesFile::Options options;
options.applicationName = JucePlugin_Name + juce::String("_globals");
options.filenameSuffix = ".settings";
options.osxLibrarySubFolder = "Application Support";
#if JUCE_LINUX || JUCE_BSD
options.folderName = "~/.config";
#else
options.folderName = "";
#endif
globalSettings = std::make_unique<juce::PropertiesFile>(options);
// locking isn't necessary here because we are in the constructor
for (auto effect : visualiserParameters.effects) {
@ -36,6 +50,9 @@ CommonAudioProcessor::CommonAudioProcessor(const BusesProperties& busesPropertie
intParameters.push_back(parameter);
}
muteParameter = new BooleanParameter("Mute", "mute", VERSION_HINT, false, "Mute audio output");
booleanParameters.push_back(muteParameter);
permanentEffects.push_back(volumeEffect);
permanentEffects.push_back(thresholdEffect);
effects.push_back(volumeEffect);
@ -67,7 +84,10 @@ void CommonAudioProcessor::addAllParameters() {
}
}
CommonAudioProcessor::~CommonAudioProcessor() {}
CommonAudioProcessor::~CommonAudioProcessor()
{
saveGlobalSettings();
}
const juce::String CommonAudioProcessor::getName() const {
return JucePlugin_Name;
@ -301,3 +321,41 @@ void CommonAudioProcessor::loadProperties(juce::XmlElement& xml) {
}
}
}
bool CommonAudioProcessor::getGlobalBoolValue(const juce::String& keyName, bool defaultValue) const
{
return globalSettings != nullptr ? globalSettings->getBoolValue(keyName, defaultValue) : defaultValue;
}
int CommonAudioProcessor::getGlobalIntValue(const juce::String& keyName, int defaultValue) const
{
return globalSettings != nullptr ? globalSettings->getIntValue(keyName, defaultValue) : defaultValue;
}
double CommonAudioProcessor::getGlobalDoubleValue(const juce::String& keyName, double defaultValue) const
{
return globalSettings != nullptr ? globalSettings->getDoubleValue(keyName, defaultValue) : defaultValue;
}
juce::String CommonAudioProcessor::getGlobalStringValue(const juce::String& keyName, const juce::String& defaultValue) const
{
return globalSettings != nullptr ? globalSettings->getValue(keyName, defaultValue) : defaultValue;
}
void CommonAudioProcessor::setGlobalValue(const juce::String& keyName, const juce::var& value)
{
if (globalSettings != nullptr)
globalSettings->setValue(keyName, value);
}
void CommonAudioProcessor::removeGlobalValue(const juce::String& keyName)
{
if (globalSettings != nullptr)
globalSettings->removeValue(keyName);
}
void CommonAudioProcessor::saveGlobalSettings()
{
if (globalSettings != nullptr)
globalSettings->saveIfNeeded();
}

Wyświetl plik

@ -4,6 +4,7 @@
This file contains the basic framework code for a JUCE plugin processor.
==============================================================================
*/
#pragma once
@ -63,12 +64,22 @@ public:
std::any getProperty(const std::string& key);
std::any getProperty(const std::string& key, std::any defaultValue);
void setProperty(const std::string& key, std::any value);
// Global settings methods
bool getGlobalBoolValue(const juce::String& keyName, bool defaultValue = false) const;
int getGlobalIntValue(const juce::String& keyName, int defaultValue = 0) const;
double getGlobalDoubleValue(const juce::String& keyName, double defaultValue = 0.0) const;
juce::String getGlobalStringValue(const juce::String& keyName, const juce::String& defaultValue = "") const;
void setGlobalValue(const juce::String& keyName, const juce::var& value);
void removeGlobalValue(const juce::String& keyName);
void saveGlobalSettings();
juce::SpinLock audioPlayerListenersLock;
std::vector<AudioPlayerListener*> audioPlayerListeners;
std::atomic<double> volume = 1.0;
std::atomic<double> threshold = 1.0;
BooleanParameter* muteParameter = nullptr;
std::shared_ptr<Effect> volumeEffect = std::make_shared<Effect>(
[this](int index, OsciPoint input, const std::vector<std::atomic<double>>& values, double sampleRate) {
@ -132,6 +143,9 @@ protected:
juce::SpinLock propertiesLock;
std::unordered_map<std::string, std::any> properties;
// Global settings that persist across plugin instances
std::unique_ptr<juce::PropertiesFile> globalSettings;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CommonAudioProcessor)

Wyświetl plik

@ -612,14 +612,20 @@ void OscirenderAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, ju
x = juce::jmax(-threshold, juce::jmin(threshold.load(), x));
y = juce::jmax(-threshold, juce::jmin(threshold.load(), y));
threadManager.write(OsciPoint(x, y, 1));
// Apply mute if active
if (muteParameter->getBoolValue()) {
x = 0.0;
y = 0.0;
}
if (totalNumOutputChannels >= 2) {
channelData[0][sample] = x;
channelData[1][sample] = y;
} else if (totalNumOutputChannels == 1) {
channelData[0][sample] = x;
}
threadManager.write(OsciPoint(x, y, 1));
if (isPlaying) {
playTimeSeconds += sTimeSec;

Wyświetl plik

@ -69,6 +69,12 @@ void SosciAudioProcessor::processBlock(juce::AudioBuffer<float>& buffer, juce::M
point.x = juce::jmax(-threshold, juce::jmin(threshold.load(), point.x));
point.y = juce::jmax(-threshold, juce::jmin(threshold.load(), point.y));
// Apply mute if active
if (muteParameter->getBoolValue()) {
point.x = 0.0;
point.y = 0.0;
}
// this is the point that the volume component will draw (i.e. post scale/clipping)
threadManager.write(point, "VolumeComponent");
}

Wyświetl plik

@ -4,7 +4,7 @@
class SvgButton : public juce::DrawableButton, public juce::AudioProcessorParameter::Listener, public juce::AsyncUpdater {
public:
SvgButton(juce::String name, juce::String svg, juce::Colour colour, juce::Colour colourOn, BooleanParameter* toggle = nullptr) : juce::DrawableButton(name, juce::DrawableButton::ButtonStyle::ImageFitted), toggle(toggle) {
SvgButton(juce::String name, juce::String svg, juce::Colour colour, juce::Colour colourOn, BooleanParameter* toggle = nullptr, juce::String toggledSvg = "") : juce::DrawableButton(name, juce::DrawableButton::ButtonStyle::ImageFitted), toggle(toggle) {
auto doc = juce::XmlDocument::parse(svg);
changeSvgColour(doc.get(), colour);
@ -16,14 +16,27 @@ class SvgButton : public juce::DrawableButton, public juce::AudioProcessorParame
changeSvgColour(doc.get(), colour.withBrightness(0.3f));
disabledImage = juce::Drawable::createFromSVG(*doc);
changeSvgColour(doc.get(), colourOn);
normalImageOn = juce::Drawable::createFromSVG(*doc);
changeSvgColour(doc.get(), colourOn.withBrightness(0.7f));
overImageOn = juce::Drawable::createFromSVG(*doc);
changeSvgColour(doc.get(), colourOn.withBrightness(0.5f));
downImageOn = juce::Drawable::createFromSVG(*doc);
changeSvgColour(doc.get(), colourOn.withBrightness(0.3f));
disabledImageOn = juce::Drawable::createFromSVG(*doc);
// If a toggled SVG is provided, use it for the "on" state images
if (toggledSvg.isNotEmpty()) {
auto toggledDoc = juce::XmlDocument::parse(toggledSvg);
changeSvgColour(toggledDoc.get(), colourOn);
normalImageOn = juce::Drawable::createFromSVG(*toggledDoc);
changeSvgColour(toggledDoc.get(), colourOn.withBrightness(0.7f));
overImageOn = juce::Drawable::createFromSVG(*toggledDoc);
changeSvgColour(toggledDoc.get(), colourOn.withBrightness(0.5f));
downImageOn = juce::Drawable::createFromSVG(*toggledDoc);
changeSvgColour(toggledDoc.get(), colourOn.withBrightness(0.3f));
disabledImageOn = juce::Drawable::createFromSVG(*toggledDoc);
} else {
changeSvgColour(doc.get(), colourOn);
normalImageOn = juce::Drawable::createFromSVG(*doc);
changeSvgColour(doc.get(), colourOn.withBrightness(0.7f));
overImageOn = juce::Drawable::createFromSVG(*doc);
changeSvgColour(doc.get(), colourOn.withBrightness(0.5f));
downImageOn = juce::Drawable::createFromSVG(*doc);
changeSvgColour(doc.get(), colourOn.withBrightness(0.3f));
disabledImageOn = juce::Drawable::createFromSVG(*doc);
}
basePath = normalImage->getOutlineAsPath();

Wyświetl plik

@ -1,6 +1,9 @@
#include "VolumeComponent.h"
VolumeComponent::VolumeComponent(CommonAudioProcessor& p) : AudioBackgroundThread("VolumeComponent", p.threadManager), audioProcessor(p) {
VolumeComponent::VolumeComponent(CommonAudioProcessor& p)
: AudioBackgroundThread("VolumeComponent", p.threadManager),
audioProcessor(p)
{
setOpaque(false);
setShouldBeRunning(true);
@ -38,12 +41,14 @@ VolumeComponent::VolumeComponent(CommonAudioProcessor& p) : AudioBackgroundThrea
audioProcessor.thresholdEffect->setValue(thresholdSlider.getValue());
};
auto doc = juce::XmlDocument::parse(BinaryData::volume_svg);
volumeIcon = juce::Drawable::createFromSVG(*doc);
doc = juce::XmlDocument::parse(BinaryData::threshold_svg);
addAndMakeVisible(volumeButton);
volumeButton.onClick = [this] {
audioProcessor.muteParameter->setBoolValueNotifyingHost(!audioProcessor.muteParameter->getBoolValue());
};
auto doc = juce::XmlDocument::parse(BinaryData::threshold_svg);
thresholdIcon = juce::Drawable::createFromSVG(*doc);
addAndMakeVisible(*volumeIcon);
addAndMakeVisible(*thresholdIcon);
}
@ -122,9 +127,11 @@ void VolumeComponent::stopTask() {}
void VolumeComponent::resized() {
auto r = getLocalBounds();
auto iconRow = r.removeFromTop(20).toFloat();
volumeIcon->setTransformToFit(iconRow.removeFromLeft(iconRow.getWidth() / 2).reduced(1), juce::RectanglePlacement::centred);
thresholdIcon->setTransformToFit(iconRow.reduced(2), juce::RectanglePlacement::centred);
auto iconRow = r.removeFromTop(20);
auto volumeRect = iconRow.removeFromLeft(iconRow.getWidth() / 2);
volumeButton.setBounds(volumeRect.expanded(3));
thresholdIcon->setTransformToFit(iconRow.reduced(2).toFloat(), juce::RectanglePlacement::centred);
volumeSlider.setBounds(r.removeFromLeft(r.getWidth() / 2));
auto radius = volumeSlider.getLookAndFeel().getSliderThumbRadius(volumeSlider);
thresholdSlider.setBounds(r.reduced(0, radius / 2));

Wyświetl plik

@ -4,6 +4,7 @@
#include "../CommonPluginProcessor.h"
#include "../LookAndFeel.h"
#include "../concurrency/AudioBackgroundThread.h"
#include "SvgButton.h"
class ThumbRadiusLookAndFeel : public OscirenderLookAndFeel {
public:
@ -88,7 +89,7 @@ private:
ThresholdLookAndFeel thresholdLookAndFeel{7};
juce::Slider thresholdSlider;
std::unique_ptr<juce::Drawable> volumeIcon;
SvgButton volumeButton = SvgButton("VolumeButton", BinaryData::volume_svg, juce::Colours::white, juce::Colours::red, audioProcessor.muteParameter, BinaryData::mute_svg);
std::unique_ptr<juce::Drawable> thresholdIcon;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(VolumeComponent)

Wyświetl plik

@ -36,7 +36,7 @@ void TextParser::parse(juce::String text, juce::Font font) {
juce::String displayText = attributedString.getText();
// remove all whitespace
displayText = displayText.replaceCharacters(" \t\n\r", "");
displayText = displayText.removeCharacters(" \t\n\r");
int index = 0;
// Iterate through all lines and all runs in each line
@ -50,6 +50,9 @@ void TextParser::parse(juce::String text, juce::Font font) {
juce::GlyphArrangement glyphs;
for (int k = 0; k < run->glyphs.size(); ++k) {
if (index >= displayText.length()) {
break;
}
juce::juce_wchar character = displayText[index];
juce::TextLayout::Glyph glyph = run->glyphs.getUnchecked(k);
juce::PositionedGlyph positionedGlyph = juce::PositionedGlyph(

Wyświetl plik

@ -54,6 +54,7 @@
<FILE id="WIkl6l" name="fullscreen.svg" compile="0" resource="1" file="Resources/svg/fullscreen.svg"/>
<FILE id="n1esUp" name="left_arrow.svg" compile="0" resource="1" file="Resources/svg/left_arrow.svg"/>
<FILE id="PxYKbt" name="microphone.svg" compile="0" resource="1" file="Resources/svg/microphone.svg"/>
<FILE id="eGMxwy" name="mute.svg" compile="0" resource="1" file="Resources/svg/mute.svg"/>
<FILE id="hJHxFY" name="open_in_new.svg" compile="0" resource="1" file="Resources/svg/open_in_new.svg"/>
<FILE id="pSc1mq" name="osci.svg" compile="0" resource="1" file="Resources/svg/osci.svg"/>
<FILE id="f2D5tv" name="pause.svg" compile="0" resource="1" file="Resources/svg/pause.svg"/>

Wyświetl plik

@ -58,6 +58,7 @@
<FILE id="WIkl6l" name="fullscreen.svg" compile="0" resource="1" file="Resources/svg/fullscreen.svg"/>
<FILE id="n1esUp" name="left_arrow.svg" compile="0" resource="1" file="Resources/svg/left_arrow.svg"/>
<FILE id="PxYKbt" name="microphone.svg" compile="0" resource="1" file="Resources/svg/microphone.svg"/>
<FILE id="WoY9r2" name="mute.svg" compile="0" resource="1" file="Resources/svg/mute.svg"/>
<FILE id="hJHxFY" name="open_in_new.svg" compile="0" resource="1" file="Resources/svg/open_in_new.svg"/>
<FILE id="pSc1mq" name="osci.svg" compile="0" resource="1" file="Resources/svg/osci.svg"/>
<FILE id="f2D5tv" name="pause.svg" compile="0" resource="1" file="Resources/svg/pause.svg"/>