From cad5bad8539377878ff1d707e24f6502681a59b6 Mon Sep 17 00:00:00 2001 From: James Ball Date: Sat, 27 Jan 2024 13:57:18 +0000 Subject: [PATCH] It works --- Resources/svg/record.svg | 1 + Resources/svg/timer.svg | 1 + Source/MainComponent.h | 2 +- Source/ObjComponent.h | 6 +- Source/components/AudioRecordingComponent.h | 72 +++++++++++++++++---- Source/components/DoubleTextBox.h | 59 +++++++++++++++++ Source/components/EffectComponent.cpp | 2 +- Source/components/EffectsListComponent.cpp | 4 +- Source/components/SvgButton.h | 32 +++++++-- osci-render.jucer | 3 + 10 files changed, 157 insertions(+), 25 deletions(-) create mode 100644 Resources/svg/record.svg create mode 100644 Resources/svg/timer.svg create mode 100644 Source/components/DoubleTextBox.h diff --git a/Resources/svg/record.svg b/Resources/svg/record.svg new file mode 100644 index 0000000..a217467 --- /dev/null +++ b/Resources/svg/record.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Resources/svg/timer.svg b/Resources/svg/timer.svg new file mode 100644 index 0000000..35a8847 --- /dev/null +++ b/Resources/svg/timer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Source/MainComponent.h b/Source/MainComponent.h index a9944be..efb28e4 100644 --- a/Source/MainComponent.h +++ b/Source/MainComponent.h @@ -26,7 +26,7 @@ private: std::unique_ptr chooser; juce::TextButton fileButton; juce::TextButton closeFileButton; - SvgButton inputEnabled{"inputEnabled", juce::String(BinaryData::microphone_svg), "white", "red", audioProcessor.inputEnabled}; + SvgButton inputEnabled{"inputEnabled", juce::String(BinaryData::microphone_svg), juce::Colours::white, juce::Colours::red, audioProcessor.inputEnabled}; juce::Label fileLabel; juce::TextEditor fileName; diff --git a/Source/ObjComponent.h b/Source/ObjComponent.h index 3fedcb2..3820fc0 100644 --- a/Source/ObjComponent.h +++ b/Source/ObjComponent.h @@ -27,9 +27,9 @@ private: juce::TextButton resetRotation{"Reset Rotation"}; juce::ToggleButton mouseRotate{"Rotate with Mouse (Esc to disable)"}; - std::shared_ptr fixedRotateX = std::make_shared("fixedRotateX", juce::String(BinaryData::fixed_rotate_svg), "white", "red", audioProcessor.fixedRotateX); - std::shared_ptr fixedRotateY = std::make_shared("fixedRotateY", juce::String(BinaryData::fixed_rotate_svg), "white", "red", audioProcessor.fixedRotateY); - std::shared_ptr fixedRotateZ = std::make_shared("fixedRotateZ", juce::String(BinaryData::fixed_rotate_svg), "white", "red", audioProcessor.fixedRotateZ); + std::shared_ptr fixedRotateX = std::make_shared("fixedRotateX", juce::String(BinaryData::fixed_rotate_svg), juce::Colours::white, juce::Colours::red, audioProcessor.fixedRotateX); + std::shared_ptr fixedRotateY = std::make_shared("fixedRotateY", juce::String(BinaryData::fixed_rotate_svg), juce::Colours::white, juce::Colours::red, audioProcessor.fixedRotateY); + std::shared_ptr fixedRotateZ = std::make_shared("fixedRotateZ", juce::String(BinaryData::fixed_rotate_svg), juce::Colours::white, juce::Colours::red, audioProcessor.fixedRotateZ); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ObjComponent) }; diff --git a/Source/components/AudioRecordingComponent.h b/Source/components/AudioRecordingComponent.h index 0ae983e..bc1d329 100644 --- a/Source/components/AudioRecordingComponent.h +++ b/Source/components/AudioRecordingComponent.h @@ -47,6 +47,7 @@ *******************************************************************************/ #pragma once +#include "DoubleTextBox.h" //============================================================================== class AudioRecorder final : public juce::Thread { @@ -115,6 +116,12 @@ public: while (!threadShouldExit()) { consumer = audioProcessor.consumerRegister(buffer); audioProcessor.consumerRead(consumer); + + if (nextSampleNum >= recordingLength * audioProcessor.currentSampleRate) { + stop(); + stopCallback(); + continue; + } const juce::ScopedLock sl(writerLock); int numSamples = buffer.size() / 2; @@ -135,6 +142,12 @@ public: } } + void setRecordLength(double recordLength) { + recordingLength = recordLength; + } + + std::function stopCallback; + private: OscirenderAudioProcessor& audioProcessor; @@ -145,6 +158,8 @@ private: std::vector buffer = std::vector(2 << 12); std::shared_ptr consumer; + double recordingLength = 99999999999.0; + juce::CriticalSection writerLock; std::atomic activeWriter { nullptr }; }; @@ -177,10 +192,7 @@ public: : juce::jmax(30.0, thumbnail.getTotalLength()); auto thumbArea = getLocalBounds(); - thumbnail.drawChannels(g, thumbArea.reduced(2), 0.0, endTime, 2.0f); - } else { - g.setFont(14.0f); - g.drawFittedText("(No file recorded)", getLocalBounds(), juce::Justification::centred, 2); + thumbnail.drawChannels(g, thumbArea.reduced(2), 0.0, endTime, 1.0f); } } @@ -204,12 +216,35 @@ class AudioRecordingComponent final : public juce::Component { public: AudioRecordingComponent(OscirenderAudioProcessor& p) : audioProcessor(p) { addAndMakeVisible(recordButton); + addAndMakeVisible(timedRecord); + addAndMakeVisible(recordLength); + + recordButton.setTooltip("Start recording audio to a WAV file. Press again to stop and save the recording."); + timedRecord.setTooltip("Record for a set amount of time. When enabled, the recording will automatically stop once the time is reached."); + + recordLength.setValue(1); recordButton.onClick = [this] { - if (recorder.isRecording()) - stopRecording(); - else - startRecording(); + if (recordButton.getToggleState()) { + startRecording(); + } else { + stopRecording(); + } + }; + + timedRecord.onClick = [this] { + if (timedRecord.getToggleState()) { + addAndMakeVisible(recordLength); + } else { + removeChildComponent(&recordLength); + } + resized(); + }; + + recorder.stopCallback = [this] { + juce::MessageManager::callAsync([this] { + recordButton.setToggleState(false, juce::sendNotification); + }); }; addAndMakeVisible(recordingThumbnail); @@ -217,9 +252,16 @@ public: } void resized() override { + double iconSize = 25; + auto area = getLocalBounds(); - recordButton.setBounds(area.removeFromLeft(80)); + recordButton.setBounds(area.removeFromLeft(iconSize).withSizeKeepingCentre(iconSize, iconSize)); area.removeFromLeft(5); + timedRecord.setBounds(area.removeFromLeft(iconSize).withSizeKeepingCentre(iconSize, iconSize)); + if (timedRecord.getToggleState()) { + recordLength.setBounds(area.removeFromLeft(80).withSizeKeepingCentre(60, 25)); + } + area.removeFromLeft(5); recordingThumbnail.setBounds(area); } @@ -229,16 +271,22 @@ private: RecordingThumbnail recordingThumbnail; AudioRecorder recorder{ audioProcessor, recordingThumbnail.getAudioThumbnail() }; - juce::TextButton recordButton{ "Record" }; + SvgButton recordButton{ "record", BinaryData::record_svg, juce::Colours::white, juce::Colours::red }; juce::File lastRecording; juce::FileChooser chooser { "Output file...", juce::File::getCurrentWorkingDirectory().getChildFile("recording.wav"), "*.wav" }; + SvgButton timedRecord{ "timedRecord", BinaryData::timer_svg, juce::Colours::white, juce::Colours::red }; + DoubleTextBox recordLength{ 0, 60 * 60 * 24 }; void startRecording() { auto parentDir = juce::File::getSpecialLocation(juce::File::tempDirectory); lastRecording = parentDir.getNonexistentChildFile("osci-render-recording", ".wav"); + if (timedRecord.getToggleState()) { + recorder.setRecordLength(recordLength.getValue()); + } else { + recorder.setRecordLength(99999999999.0); + } recorder.startRecording(lastRecording); - recordButton.setButtonText("Stop"); recordButton.setColour(juce::TextButton::buttonColourId, juce::Colours::red); recordButton.setColour(juce::TextButton::textColourOnId, juce::Colours::black); @@ -247,8 +295,6 @@ private: void stopRecording() { recorder.stop(); - recordButton.setButtonText("Record"); - recordButton.setColour(juce::TextButton::buttonColourId, findColour(juce::TextButton::buttonColourId)); recordButton.setColour(juce::TextButton::textColourOnId, findColour(juce::TextButton::textColourOnId)); diff --git a/Source/components/DoubleTextBox.h b/Source/components/DoubleTextBox.h new file mode 100644 index 0000000..6fd9a24 --- /dev/null +++ b/Source/components/DoubleTextBox.h @@ -0,0 +1,59 @@ +#pragma once +#include + +class DoubleTextBox : public juce::TextEditor { +public: + DoubleTextBox(double minValue, double maxValue) : minValue(minValue), maxValue(maxValue) { + setText(juce::String(minValue, 2), false); + setMultiLine(false); + setJustification(juce::Justification::centred); + setFont(juce::Font(15.0f, juce::Font::plain)); + onTextChange = [this]() { + setText(getText(), false); + }; + } + + double getValue() { + return getText().getDoubleValue(); + } + + void setValue(double value, bool sendChangeMessage = true) { + setText(juce::String(value, 2), sendChangeMessage); + } + + void setText(const juce::String& newText, bool sendChangeMessage = true) { + // remove all non-digits + juce::String text = newText.retainCharacters("0123456789.-"); + + // only keep first decimal point + int firstDecimal = text.indexOfChar('.'); + if (firstDecimal != -1) { + juce::String remainder = text.substring(firstDecimal + 1); + remainder = remainder.retainCharacters("0123456789"); + text = text.substring(0, firstDecimal + 1) + remainder; + } + + // only keep negative sign at beginning + if (text.contains("-")) { + juce::String remainder = text.substring(1); + remainder = remainder.retainCharacters("0123456789"); + text = text.substring(0, 1) + remainder; + } + + double value = text.getDoubleValue(); + if (value < minValue || value > maxValue) { + text = juce::String(prevValue); + } else { + prevValue = value; + } + + juce::TextEditor::setText(text, sendChangeMessage); + } + + ~DoubleTextBox() override {} + +private: + double minValue; + double maxValue; + double prevValue = minValue; +}; diff --git a/Source/components/EffectComponent.cpp b/Source/components/EffectComponent.cpp index c6c2c34..4a1fe15 100644 --- a/Source/components/EffectComponent.cpp +++ b/Source/components/EffectComponent.cpp @@ -9,7 +9,7 @@ EffectComponent::EffectComponent(OscirenderAudioProcessor& p, Effect& effect, in sidechainEnabled = effect.parameters[0]->sidechain != nullptr; if (sidechainEnabled) { - sidechainButton = std::make_unique(effect.parameters[0]->name, BinaryData::microphone_svg, "white", "red", effect.parameters[0]->sidechain); + sidechainButton = std::make_unique(effect.parameters[0]->name, BinaryData::microphone_svg, juce::Colours::white, juce::Colours::red, effect.parameters[0]->sidechain); sidechainButton->setTooltip("When enabled, the volume of the input audio controls the value of the slider, acting like a sidechain effect."); addAndMakeVisible(*sidechainButton); } diff --git a/Source/components/EffectsListComponent.cpp b/Source/components/EffectsListComponent.cpp index f3611c0..a2c6764 100644 --- a/Source/components/EffectsListComponent.cpp +++ b/Source/components/EffectsListComponent.cpp @@ -97,14 +97,14 @@ std::shared_ptr EffectsListComponent::createComponent(EffectPar toggle = audioProcessor.perspectiveEffect->fixedRotateZ; axis = "Z"; } - std::shared_ptr button = std::make_shared(parameter->name, BinaryData::fixed_rotate_svg, "white", "red", toggle); + std::shared_ptr button = std::make_shared(parameter->name, BinaryData::fixed_rotate_svg, juce::Colours::white, juce::Colours::red, toggle); button->setTooltip("Toggles whether the rotation around the " + axis + " axis is fixed, or changes according to the rotation speed."); button->onClick = [this, toggle] { toggle->setBoolValueNotifyingHost(!toggle->getBoolValue()); }; return button; } else if (parameter->paramID == "perspectiveStrength") { - std::shared_ptr button = std::make_shared(parameter->name, BinaryData::pencil_svg, "white", "red"); + std::shared_ptr button = std::make_shared(parameter->name, BinaryData::pencil_svg, juce::Colours::white, juce::Colours::red); std::weak_ptr weakButton = button; button->setEdgeIndent(5); button->setToggleState(editor.editingPerspective, juce::dontSendNotification); diff --git a/Source/components/SvgButton.h b/Source/components/SvgButton.h index 9a654c1..4dbece9 100644 --- a/Source/components/SvgButton.h +++ b/Source/components/SvgButton.h @@ -3,19 +3,33 @@ class SvgButton : public juce::DrawableButton, public juce::AudioProcessorParameter::Listener, public juce::AsyncUpdater { public: - SvgButton(juce::String name, juce::String svg, juce::String colour, juce::String 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::DrawableButton(name, juce::DrawableButton::ButtonStyle::ImageFitted), toggle(toggle) { auto doc = juce::XmlDocument::parse(svg); + changeSvgColour(doc.get(), colour); normalImage = juce::Drawable::createFromSVG(*doc); + changeSvgColour(doc.get(), colour.withBrightness(0.7f)); + overImage = juce::Drawable::createFromSVG(*doc); + changeSvgColour(doc.get(), colour.withBrightness(0.5f)); + downImage = juce::Drawable::createFromSVG(*doc); + 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); getLookAndFeel().setColour(juce::DrawableButton::backgroundOnColourId, juce::Colours::transparentWhite); if (colour != colourOn) { setClickingTogglesState(true); } - setImages(normalImage.get(), nullptr, nullptr, nullptr, normalImageOn.get()); + setImages(normalImage.get(), overImage.get(), downImage.get(), disabledImage.get(), normalImageOn.get(), overImageOn.get(), downImageOn.get(), disabledImageOn.get()); if (toggle != nullptr) { toggle->addListener(this); @@ -23,7 +37,7 @@ class SvgButton : public juce::DrawableButton, public juce::AudioProcessorParame } } - SvgButton(juce::String name, juce::String svg, juce::String colour) : SvgButton(name, svg, colour, colour) {} + SvgButton(juce::String name, juce::String svg, juce::Colour colour) : SvgButton(name, svg, colour, colour) {} ~SvgButton() override { if (toggle != nullptr) { @@ -42,12 +56,20 @@ class SvgButton : public juce::DrawableButton, public juce::AudioProcessorParame } private: std::unique_ptr normalImage; + std::unique_ptr overImage; + std::unique_ptr downImage; + std::unique_ptr disabledImage; + std::unique_ptr normalImageOn; + std::unique_ptr overImageOn; + std::unique_ptr downImageOn; + std::unique_ptr disabledImageOn; + BooleanParameter* toggle; - void changeSvgColour(juce::XmlElement* xml, juce::String colour) { + void changeSvgColour(juce::XmlElement* xml, juce::Colour colour) { forEachXmlChildElement(*xml, xmlnode) { - xmlnode->setAttribute("fill", colour); + xmlnode->setAttribute("fill", '#' + colour.toDisplayString(false)); } } }; diff --git a/osci-render.jucer b/osci-render.jucer index dbe2d7d..4942425 100644 --- a/osci-render.jucer +++ b/osci-render.jucer @@ -22,7 +22,9 @@ + + @@ -106,6 +108,7 @@ +