Add recording settings

pull/261/head
James H Ball 2024-12-15 19:23:31 +00:00 zatwierdzone przez James H Ball
rodzic cc3dae4267
commit 77a3271cde
19 zmienionych plików z 288 dodań i 278 usunięć

Wyświetl plik

@ -46,17 +46,27 @@ CommonPluginEditor::CommonPluginEditor(CommonAudioProcessor& p, juce::String app
visualiserSettingsWindow.setVisible(false);
};
visualiserSettingsWindow.setResizable(false, false);
visualiserSettings.setLookAndFeel(&getLookAndFeel());
visualiserSettings.setSize(550, 400);
visualiserSettingsWindow.setContentNonOwned(&visualiserSettings, true);
visualiserSettingsWindow.centreWithSize(550, 400);
#if JUCE_WINDOWS
// if not standalone, use native title bar for compatibility with DAWs
visualiserSettingsWindow.setUsingNativeTitleBar(processor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone);
#elif JUCE_MAC
visualiserSettingsWindow.setUsingNativeTitleBar(true);
#endif
visualiserSettings.setLookAndFeel(&getLookAndFeel());
visualiserSettings.setSize(550, 400);
visualiserSettingsWindow.setContentNonOwned(&visualiserSettings, true);
visualiserSettingsWindow.centreWithSize(550, 400);
recordingSettings.setLookAndFeel(&getLookAndFeel());
recordingSettings.setSize(300, 200);
recordingSettingsWindow.setContentNonOwned(&recordingSettings, true);
recordingSettingsWindow.centreWithSize(300, 200);
#if JUCE_WINDOWS
// if not standalone, use native title bar for compatibility with DAWs
recordingSettingsWindow.setUsingNativeTitleBar(processor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone);
#elif JUCE_MAC
recordingSettingsWindow.setUsingNativeTitleBar(true);
#endif
menuBar.toFront(true);
@ -149,6 +159,11 @@ void CommonPluginEditor::openAudioSettings() {
standalone->showAudioSettingsDialog();
}
void CommonPluginEditor::openRecordingSettings() {
recordingSettingsWindow.setVisible(true);
recordingSettingsWindow.toFront(true);
}
void CommonPluginEditor::resetToDefault() {
juce::StandaloneFilterWindow* window = findParentComponentOfClass<juce::StandaloneFilterWindow>();
if (window != nullptr) {

Wyświetl plik

@ -19,6 +19,7 @@ public:
void saveProjectAs();
void updateTitle();
void openAudioSettings();
void openRecordingSettings();
void resetToDefault();
void openVisualiserSettings();
@ -45,7 +46,9 @@ public:
VisualiserSettings visualiserSettings = VisualiserSettings(audioProcessor.visualiserParameters, 3);
SettingsWindow visualiserSettingsWindow = SettingsWindow("Visualiser Settings");
VisualiserComponent visualiser{audioProcessor.lastOpenedDirectory, applicationFolder.getChildFile(ffmpegFileName), audioProcessor.haltRecording, audioProcessor.threadManager, visualiserSettings, nullptr, appName == "sosci"};
RecordingSettings recordingSettings = RecordingSettings(audioProcessor.recordingParameters);
SettingsWindow recordingSettingsWindow = SettingsWindow("Recording Settings");
VisualiserComponent visualiser{audioProcessor.lastOpenedDirectory, applicationFolder.getChildFile(ffmpegFileName), audioProcessor.haltRecording, audioProcessor.threadManager, visualiserSettings, audioProcessor.recordingParameters, nullptr, appName == "sosci"};
std::unique_ptr<juce::FileChooser> chooser;
juce::MenuBarComponent menuBar;

Wyświetl plik

@ -13,6 +13,7 @@
#include "concurrency/AudioBackgroundThreadManager.h"
#include "audio/SampleRateManager.h"
#include "visualiser/VisualiserSettings.h"
#include "visualiser/RecordingSettings.h"
#include "audio/Effect.h"
//==============================================================================
@ -57,6 +58,7 @@ public:
std::atomic<double> currentSampleRate = 0.0;
juce::SpinLock effectsLock;
VisualiserParameters visualiserParameters;
RecordingParameters recordingParameters;
AudioBackgroundThreadManager threadManager;
std::function<void()> haltRecording;

Wyświetl plik

@ -297,14 +297,39 @@ void OscirenderLookAndFeel::drawMenuBarBackground(juce::Graphics& g, int width,
g.fillRect(r);
}
juce::TextLayout OscirenderLookAndFeel::layoutTooltipText(const juce::String& text, juce::Colour colour) {
const float tooltipFontSize = 17.0f;
const int maxToolTipWidth = 600;
juce::AttributedString s;
s.setJustification (juce::Justification::centred);
s.append (text, juce::Font (tooltipFontSize, juce::Font::bold), colour);
juce::TextLayout tl;
tl.createLayoutWithBalancedLineLengths (s, (float) maxToolTipWidth);
return tl;
}
juce::Rectangle<int> OscirenderLookAndFeel::getTooltipBounds (const juce::String& tipText, juce::Point<int> screenPos, juce::Rectangle<int> parentArea) {
const juce::TextLayout tl (layoutTooltipText(tipText, juce::Colours::black));
auto w = (int) (tl.getWidth() + 14.0f);
auto h = (int) (tl.getHeight() + 6.0f);
return juce::Rectangle<int> (screenPos.x > parentArea.getCentreX() ? screenPos.x - (w + 12) : screenPos.x + 24,
screenPos.y > parentArea.getCentreY() ? screenPos.y - (h + 6) : screenPos.y + 6,
w, h)
.constrainedWithin (parentArea);
}
void OscirenderLookAndFeel::drawTooltip(juce::Graphics& g, const juce::String& text, int width, int height) {
juce::Rectangle<int> bounds (width, height);
g.setColour(findColour(juce::TooltipWindow::backgroundColourId));
g.fillRect(bounds);
LookAndFeelHelpers::layoutTooltipText (text, findColour (juce::TooltipWindow::textColourId))
.draw (g, { static_cast<float> (width), static_cast<float> (height) });
layoutTooltipText(text, findColour(juce::TooltipWindow::textColourId))
.draw(g, {static_cast<float> (width), static_cast<float> (height)});
}
void OscirenderLookAndFeel::drawCornerResizer(juce::Graphics&, int w, int h, bool isMouseOver, bool isMouseDragging) {

Wyświetl plik

@ -94,6 +94,8 @@ public:
bool shouldDrawButtonAsHighlighted,
bool shouldDrawButtonAsDown) override;
void drawMenuBarBackground(juce::Graphics& g, int width, int height, bool, juce::MenuBarComponent& menuBar) override;
juce::TextLayout layoutTooltipText(const juce::String& text, juce::Colour colour);
juce::Rectangle<int> getTooltipBounds(const juce::String& tipText, juce::Point<int> screenPos, juce::Rectangle<int> parentArea) override;
juce::CodeEditorComponent::ColourScheme getDefaultColourScheme();
void drawTooltip(juce::Graphics& g, const juce::String& text, int width, int height) override;
void drawCornerResizer(juce::Graphics&, int w, int h, bool isMouseOver, bool isMouseDragging) override;

Wyświetl plik

@ -414,8 +414,3 @@ void OscirenderAudioProcessorEditor::mouseMove(const juce::MouseEvent& event) {
setMouseCursor(juce::MouseCursor::NormalCursor);
}
}
void OscirenderAudioProcessorEditor::openAudioSettings() {
juce::StandalonePluginHolder* standalone = juce::StandalonePluginHolder::getInstance();
standalone->showAudioSettingsDialog();
}

Wyświetl plik

@ -31,8 +31,6 @@ public:
void editCustomFunction(bool enabled);
void openAudioSettings();
private:
OscirenderAudioProcessor& audioProcessor;
public:

Wyświetl plik

@ -659,6 +659,9 @@ void OscirenderAudioProcessor::getStateInformation(juce::MemoryBlock& destData)
fileXml->addTextElement(base64);
}
xml->setAttribute("currentFile", currentFile);
recordingParameters.save(xml.get());
copyXmlToBinary(*xml, destData);
}
@ -773,6 +776,8 @@ void OscirenderAudioProcessor::setStateInformation(const void* data, int sizeInB
}
changeCurrentFile(xml->getIntAttribute("currentFile", -1));
recordingParameters.load(xml.get());
broadcaster.sendChangeMessage();
prevMidiEnabled = !midiEnabled->getBoolValue();
}

Wyświetl plik

@ -2,7 +2,9 @@
#include "SosciPluginEditor.h"
#include "audio/EffectParameter.h"
SosciAudioProcessor::SosciAudioProcessor() {}
SosciAudioProcessor::SosciAudioProcessor() {
addAllParameters();
}
SosciAudioProcessor::~SosciAudioProcessor() {}
@ -77,6 +79,8 @@ void SosciAudioProcessor::getStateInformation(juce::MemoryBlock& destData) {
parameter->save(parameterXml);
}
recordingParameters.save(xml.get());
copyXmlToBinary(*xml, destData);
}
@ -134,6 +138,8 @@ void SosciAudioProcessor::setStateInformation(const void* data, int sizeInBytes)
}
}
}
recordingParameters.load(xml.get());
}
}

Wyświetl plik

@ -122,8 +122,8 @@ void EffectComponent::resized() {
auto bounds = getLocalBounds();
auto componentBounds = bounds.removeFromRight(25);
if (component != nullptr) {
component->setBounds(componentBounds);
}
component->setBounds(componentBounds);
}
if (sidechainEnabled) {
sidechainButton->setBounds(bounds.removeFromRight(20));
@ -135,7 +135,9 @@ void EffectComponent::resized() {
lfo.setBounds(bounds.removeFromRight(drawingSmall ? 70 : 100).reduced(0, 5));
}
rangeButton.setBounds(bounds.removeFromRight(20));
if (rangeButton.isVisible()) {
rangeButton.setBounds(bounds.removeFromRight(20));
}
bounds.removeFromLeft(5);
@ -169,6 +171,10 @@ void EffectComponent::handleAsyncUpdate() {
getParentComponent()->repaint();
}
void EffectComponent::setRangeEnabled(bool enabled) {
rangeButton.setVisible(enabled);
}
void EffectComponent::setComponent(std::shared_ptr<juce::Component> component) {
this->component = component;
addAndMakeVisible(component.get());

Wyświetl plik

@ -16,6 +16,8 @@ public:
void parameterGestureChanged(int parameterIndex, bool gestureIsStarting) override;
void handleAsyncUpdate() override;
void setRangeEnabled(bool enabled);
void setComponent(std::shared_ptr<juce::Component> component);
void setSliderOnValueChange();

Wyświetl plik

@ -45,7 +45,7 @@ OsciMainMenuBarModel::OsciMainMenuBarModel(OscirenderAudioProcessor& p, Oscirend
});
addMenuItem(2, "Settings...", [this] {
//editor.openRecordingSettings();
editor.openRecordingSettings();
});
addMenuItem(3, "Settings...", [this] {

Wyświetl plik

@ -1,61 +1,44 @@
#include "VisualiserSettings.h"
#include "RecordingSettings.h"
#include "VisualiserComponent.h"
#include "../PluginEditor.h"
VisualiserSettings::VisualiserSettings(VisualiserParameters& parameters, int numChannels) : parameters(parameters), numChannels(numChannels) {
addAndMakeVisible(brightness);
addAndMakeVisible(intensity);
addAndMakeVisible(persistence);
addAndMakeVisible(hue);
addAndMakeVisible(saturation);
addAndMakeVisible(focus);
addAndMakeVisible(noise);
addAndMakeVisible(glow);
addAndMakeVisible(smooth);
addChildComponent(sweepMs);
addAndMakeVisible(graticuleToggle);
addAndMakeVisible(smudgeToggle);
addAndMakeVisible(upsamplingToggle);
addAndMakeVisible(sweepToggle);
brightness.setSliderOnValueChange();
intensity.setSliderOnValueChange();
persistence.setSliderOnValueChange();
hue.setSliderOnValueChange();
saturation.setSliderOnValueChange();
focus.setSliderOnValueChange();
noise.setSliderOnValueChange();
glow.setSliderOnValueChange();
smooth.setSliderOnValueChange();
sweepMs.setSliderOnValueChange();
sweepToggle.onClick = [this] {
sweepMs.setVisible(sweepToggle.getToggleState());
resized();
RecordingSettings::RecordingSettings(RecordingParameters& ps) : parameters(ps) {
addAndMakeVisible(quality);
addAndMakeVisible(recordAudio);
addAndMakeVisible(recordVideo);
addAndMakeVisible(compressionPreset);
addAndMakeVisible(compressionPresetLabel);
quality.setSliderOnValueChange();
quality.setRangeEnabled(false);
recordAudio.onClick = [this] {
if (!recordAudio.getToggleState() && !recordVideo.getToggleState()) {
recordVideo.setToggleState(true, juce::NotificationType::sendNotification);
}
};
recordVideo.onClick = [this] {
if (!recordAudio.getToggleState() && !recordVideo.getToggleState()) {
recordAudio.setToggleState(true, juce::NotificationType::sendNotification);
}
};
compressionPreset.onChange = [this] {
parameters.compressionPreset = parameters.compressionPresets[compressionPreset.getSelectedId() - 1];
};
compressionPreset.addItemList(parameters.compressionPresets, 1);
compressionPreset.setSelectedId(parameters.compressionPresets.indexOf(parameters.compressionPreset) + 1);
compressionPresetLabel.setTooltip("The compression preset to use when recording video. Slower presets will produce smaller files at the expense of encoding time.");
}
VisualiserSettings::~VisualiserSettings() {}
RecordingSettings::~RecordingSettings() {}
void VisualiserSettings::resized() {
void RecordingSettings::resized() {
auto area = getLocalBounds().reduced(20);
double rowHeight = 30;
brightness.setBounds(area.removeFromTop(rowHeight));
intensity.setBounds(area.removeFromTop(rowHeight));
persistence.setBounds(area.removeFromTop(rowHeight));
hue.setBounds(area.removeFromTop(rowHeight));
saturation.setBounds(area.removeFromTop(rowHeight));
focus.setBounds(area.removeFromTop(rowHeight));
noise.setBounds(area.removeFromTop(rowHeight));
glow.setBounds(area.removeFromTop(rowHeight));
smooth.setBounds(area.removeFromTop(rowHeight));
graticuleToggle.setBounds(area.removeFromTop(rowHeight));
smudgeToggle.setBounds(area.removeFromTop(rowHeight));
upsamplingToggle.setBounds(area.removeFromTop(rowHeight));
sweepToggle.setBounds(area.removeFromTop(rowHeight));
if (sweepToggle.getToggleState()) {
sweepMs.setBounds(area.removeFromTop(rowHeight));
}
double rowHeight = 30;
quality.setBounds(area.removeFromTop(rowHeight).expanded(6, 0));
recordAudio.setBounds(area.removeFromTop(rowHeight));
recordVideo.setBounds(area.removeFromTop(rowHeight));
auto row = area.removeFromTop(rowHeight);
compressionPresetLabel.setBounds(row.removeFromLeft(140));
compressionPreset.setBounds(row.removeFromRight(80));
}

Wyświetl plik

@ -1,187 +1,99 @@
#pragma once
#define VERSION_HINT 2
#include <JuceHeader.h>
#include "../components/EffectComponent.h"
#include "../components/SvgButton.h"
#include "../LookAndFeel.h"
#include "../components/SwitchButton.h"
#include "../audio/SmoothEffect.h"
class VisualiserParameters {
#define VERSION_HINT 2
class RecordingParameters {
public:
BooleanParameter* graticuleEnabled = new BooleanParameter("Show Graticule", "graticuleEnabled", VERSION_HINT, false, "Show the graticule or grid lines over the oscilloscope display.");
BooleanParameter* smudgesEnabled = new BooleanParameter("Show Smudges", "smudgesEnabled", VERSION_HINT, false, "Adds a subtle layer of dirt/smudges to the oscilloscope display to make it look more realistic.");
BooleanParameter* upsamplingEnabled = new BooleanParameter("Upsample Audio", "upsamplingEnabled", VERSION_HINT, true, "Upsamples the audio before visualising it to make it appear more realistic, at the expense of performance.");
BooleanParameter* sweepEnabled = new BooleanParameter("Sweep", "sweepEnabled", VERSION_HINT, false, "Plots the audio signal over time, sweeping from left to right");
BooleanParameter* visualiserFullScreen = new BooleanParameter("Visualiser Fullscreen", "visualiserFullScreen", VERSION_HINT, false, "Makes the software visualiser fullscreen.");
RecordingParameters() {
qualityParameter.disableLfo();
qualityParameter.disableSidechain();
}
std::shared_ptr<Effect> persistenceEffect = std::make_shared<Effect>(
new EffectParameter(
"Persistence",
"Controls how long the light glows for on the oscilloscope display.",
"persistence",
VERSION_HINT, 0.5, 0, 6.0
)
EffectParameter qualityParameter = EffectParameter(
"Quality",
"Controls the quality of the recording video. 0 is the worst possible quality, and 1 is lossless.",
"brightness",
VERSION_HINT, 0.7, 0.0, 1.0
);
std::shared_ptr<Effect> hueEffect = std::make_shared<Effect>(
new EffectParameter(
"Hue",
"Controls the hue/colour of the oscilloscope display.",
"hue",
VERSION_HINT, 125, 0, 359, 1
)
);
std::shared_ptr<Effect> brightnessEffect = std::make_shared<Effect>(
new EffectParameter(
"Brightness",
"Controls how bright the light glows for on the oscilloscope display.",
"brightness",
VERSION_HINT, 2.0, 0.0, 10.0
)
);
std::shared_ptr<Effect> intensityEffect = std::make_shared<Effect>(
new EffectParameter(
"Intensity",
"Controls how bright the electron beam of the oscilloscope is.",
"intensity",
VERSION_HINT, 5.0, 0.0, 10.0
)
);
std::shared_ptr<Effect> saturationEffect = std::make_shared<Effect>(
new EffectParameter(
"Saturation",
"Controls how saturated the colours are on the oscilloscope.",
"saturation",
VERSION_HINT, 1.0, 0.0, 5.0
)
);
std::shared_ptr<Effect> focusEffect = std::make_shared<Effect>(
new EffectParameter(
"Focus",
"Controls how focused the electron beam of the oscilloscope is.",
"focus",
VERSION_HINT, 1.0, 0.01, 10.0
)
);
std::shared_ptr<Effect> noiseEffect = std::make_shared<Effect>(
new EffectParameter(
"Noise",
"Controls how much noise/grain is added to the oscilloscope display.",
"noise",
VERSION_HINT, 0.0, 0.0, 1.0
)
);
std::shared_ptr<Effect> glowEffect = std::make_shared<Effect>(
new EffectParameter(
"Glow",
"Controls how much the light glows on the oscilloscope display.",
"glow",
VERSION_HINT, 0.3, 0.0, 1.0
)
);
std::shared_ptr<Effect> smoothEffect = std::make_shared<Effect>(
std::make_shared<SmoothEffect>(),
new EffectParameter(
"Smoothing",
"This works as a low-pass frequency filter, effectively reducing the sample rate of the audio being visualised.",
"visualiserSmoothing",
VERSION_HINT, 0, 0.0, 1.0
)
);
std::shared_ptr<Effect> sweepMsEffect = std::make_shared<Effect>(
new EffectParameter(
"Sweep (ms)",
"The number of milliseconds it takes for the oscilloscope to sweep from left to right.",
"sweepMs",
VERSION_HINT, 0.3, 0.0, 1.0
)
);
std::vector<std::shared_ptr<Effect>> effects = {persistenceEffect, hueEffect, brightnessEffect, intensityEffect, saturationEffect, focusEffect, noiseEffect, glowEffect, sweepMsEffect};
std::vector<BooleanParameter*> booleans = {graticuleEnabled, smudgesEnabled, upsamplingEnabled, visualiserFullScreen, sweepEnabled};
Effect qualityEffect = Effect(&qualityParameter);
BooleanParameter recordAudio = BooleanParameter("Record Audio", "recordAudio", VERSION_HINT, true, "Record audio along with the video.");
BooleanParameter recordVideo = BooleanParameter("Record Video", "recordVideo", VERSION_HINT, true, "Record video output of the visualiser.");
juce::String compressionPreset = "fast";
int getCRF() {
double quality = juce::jlimit(0.0, 1.0, qualityEffect.getValue());
// mapping to 0-51 for ffmpeg's crf value
return 51 * (1.0 - quality) ;
}
bool recordingVideo() {
return recordVideo.getBoolValue();
}
bool recordingAudio() {
return recordAudio.getBoolValue();
}
juce::String getCompressionPreset() {
return compressionPreset;
}
void save(juce::XmlElement* xml) {
auto settingsXml = xml->createNewChildElement("recordingSettings");
recordAudio.save(settingsXml->createNewChildElement("recordAudio"));
recordVideo.save(settingsXml->createNewChildElement("recordVideo"));
settingsXml->setAttribute("compressionPreset", compressionPreset);
auto qualityXml = settingsXml->createNewChildElement("quality");
qualityEffect.save(qualityXml);
}
// opt to not change any values if not found
void load(juce::XmlElement* xml) {
if (auto* settingsXml = xml->getChildByName("recordingSettings")) {
if (auto* recordAudioXml = settingsXml->getChildByName("recordAudio")) {
recordAudio.load(recordAudioXml);
}
if (auto* recordVideoXml = settingsXml->getChildByName("recordVideo")) {
recordVideo.load(recordVideoXml);
}
if (settingsXml->hasAttribute("compressionPreset")) {
compressionPreset = settingsXml->getStringAttribute("compressionPreset");
}
if (auto* qualityXml = settingsXml->getChildByName("quality")) {
qualityEffect.load(qualityXml);
}
}
}
juce::StringArray compressionPresets = { "ultrafast", "superfast", "veryfast", "faster", "fast", "medium", "slow", "slower", "veryslow" };
};
class VisualiserSettings : public juce::Component {
class RecordingSettings : public juce::Component {
public:
VisualiserSettings(VisualiserParameters&, int numChannels = 2);
~VisualiserSettings();
RecordingSettings(RecordingParameters&);
~RecordingSettings();
void resized() override;
double getBrightness() {
return parameters.brightnessEffect->getActualValue() - 2;
}
double getIntensity() {
return parameters.intensityEffect->getActualValue() / 100;
}
double getPersistence() {
return parameters.persistenceEffect->getActualValue() - 1.33;
}
double getHue() {
return parameters.hueEffect->getActualValue();
}
double getSaturation() {
return parameters.saturationEffect->getActualValue();
}
double getFocus() {
return parameters.focusEffect->getActualValue() / 100;
}
double getNoise() {
return parameters.noiseEffect->getActualValue();
}
double getGlow() {
return parameters.glowEffect->getActualValue() * 3;
}
bool getGraticuleEnabled() {
return parameters.graticuleEnabled->getBoolValue();
}
bool getSmudgesEnabled() {
return parameters.smudgesEnabled->getBoolValue();
}
bool getUpsamplingEnabled() {
return parameters.upsamplingEnabled->getBoolValue();
}
VisualiserParameters& parameters;
int numChannels;
RecordingParameters& parameters;
private:
EffectComponent brightness{*parameters.brightnessEffect};
EffectComponent intensity{*parameters.intensityEffect};
EffectComponent persistence{*parameters.persistenceEffect};
EffectComponent hue{*parameters.hueEffect};
EffectComponent saturation{*parameters.saturationEffect};
EffectComponent focus{*parameters.focusEffect};
EffectComponent noise{*parameters.noiseEffect};
EffectComponent glow{*parameters.glowEffect};
EffectComponent smooth{*parameters.smoothEffect};
EffectComponent sweepMs{*parameters.sweepMsEffect};
jux::SwitchButton graticuleToggle{parameters.graticuleEnabled};
jux::SwitchButton smudgeToggle{parameters.smudgesEnabled};
jux::SwitchButton upsamplingToggle{parameters.upsamplingEnabled};
jux::SwitchButton sweepToggle{parameters.sweepEnabled};
EffectComponent quality{parameters.qualityEffect};
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(VisualiserSettings)
};
jux::SwitchButton recordAudio{&parameters.recordAudio};
jux::SwitchButton recordVideo{&parameters.recordVideo};
class SettingsWindow : public juce::DocumentWindow {
public:
SettingsWindow(juce::String name) : juce::DocumentWindow(name, Colours::darker, juce::DocumentWindow::TitleBarButtons::closeButton) {}
void closeButtonPressed() override {
setVisible(false);
}
juce::Label compressionPresetLabel{"Compression Speed", "Compression Speed"};
juce::ComboBox compressionPreset;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RecordingSettings)
};

Wyświetl plik

@ -13,7 +13,7 @@
#include "TexturedFragmentShader.glsl"
#include "TexturedVertexShader.glsl"
VisualiserComponent::VisualiserComponent(juce::File& lastOpenedDirectory, juce::File ffmpegFile, std::function<void()>& haltRecording, AudioBackgroundThreadManager& threadManager, VisualiserSettings& settings, VisualiserComponent* parent, bool visualiserOnly) : lastOpenedDirectory(lastOpenedDirectory), ffmpegFile(ffmpegFile), haltRecording(haltRecording), settings(settings), threadManager(threadManager), visualiserOnly(visualiserOnly), AudioBackgroundThread("VisualiserComponent" + juce::String(parent != nullptr ? " Child" : ""), threadManager), parent(parent) {
VisualiserComponent::VisualiserComponent(juce::File& lastOpenedDirectory, juce::File ffmpegFile, std::function<void()>& haltRecording, AudioBackgroundThreadManager& threadManager, VisualiserSettings& settings, RecordingParameters& recordingParameters, VisualiserComponent* parent, bool visualiserOnly) : lastOpenedDirectory(lastOpenedDirectory), ffmpegFile(ffmpegFile), haltRecording(haltRecording), settings(settings), recordingParameters(recordingParameters), threadManager(threadManager), visualiserOnly(visualiserOnly), AudioBackgroundThread("VisualiserComponent" + juce::String(parent != nullptr ? " Child" : ""), threadManager), parent(parent) {
addAndMakeVisible(ffmpegDownloader);
ffmpegDownloader.onSuccessfulDownload = [this] {
@ -176,49 +176,87 @@ void VisualiserComponent::setRecording(bool recording) {
stopwatch.reset();
if (recording) {
if (!ffmpegFile.exists()) {
// ask the user if they want to download ffmpeg
juce::MessageBoxOptions options = juce::MessageBoxOptions()
.withTitle("Recording requires FFmpeg")
.withMessage("FFmpeg is required to record video to .mp4.\n\nWould you like to download it now?")
.withButton("Yes")
.withButton("No")
.withIconType(juce::AlertWindow::QuestionIcon)
.withAssociatedComponent(this);
juce::AlertWindow::showAsync(options, [this](int result) {
if (result == 1) {
record.setEnabled(false);
ffmpegDownloader.download();
}
});
recordingVideo = recordingParameters.recordingVideo();
recordingAudio = recordingParameters.recordingAudio();
if (!recordingVideo && !recordingAudio) {
record.setToggleState(false, juce::NotificationType::dontSendNotification);
return;
}
tempVideoFile = std::make_unique<juce::TemporaryFile>(".mp4");
juce::String resolution = std::to_string(renderTexture.width) + "x" + std::to_string(renderTexture.height);
juce::String cmd = "\"" + ffmpegFile.getFullPathName() +
"\" -r " + juce::String(FRAME_RATE) + " -f rawvideo -pix_fmt rgba -s " + resolution + " -i - -threads 0 -preset fast -y -pix_fmt yuv420p -crf " + juce::String(21) + " -vf vflip \"" + tempVideoFile->getFile().getFullPathName() + "\"";
ffmpegProcess.start(cmd);
framePixels.resize(renderTexture.width * renderTexture.height * 4);
if (recordingVideo) {
if (!ffmpegFile.exists()) {
// ask the user if they want to download ffmpeg
juce::MessageBoxOptions options = juce::MessageBoxOptions()
.withTitle("Recording requires FFmpeg")
.withMessage("FFmpeg is required to record video to .mp4.\n\nWould you like to download it now?")
.withButton("Yes")
.withButton("No")
.withIconType(juce::AlertWindow::QuestionIcon)
.withAssociatedComponent(this);
tempAudioFile = std::make_unique<juce::TemporaryFile>(".wav");
audioRecorder.startRecording(tempAudioFile->getFile());
juce::AlertWindow::showAsync(options, [this](int result) {
if (result == 1) {
record.setEnabled(false);
ffmpegDownloader.download();
}
});
record.setToggleState(false, juce::NotificationType::dontSendNotification);
return;
}
tempVideoFile = std::make_unique<juce::TemporaryFile>(".mp4");
juce::String resolution = std::to_string(renderTexture.width) + "x" + std::to_string(renderTexture.height);
juce::String cmd = "\"" + ffmpegFile.getFullPathName() + "\"" +
" -r " + juce::String(FRAME_RATE) +
" -f rawvideo" +
" -pix_fmt rgba" +
" -s " + resolution +
" -i -" +
" -threads 4" +
" -preset " + recordingParameters.getCompressionPreset() +
" -y" +
" -pix_fmt yuv420p" +
" -crf " + juce::String(recordingParameters.getCRF()) +
" -vf vflip" +
" \"" + tempVideoFile->getFile().getFullPathName() + "\"";
ffmpegProcess.start(cmd);
framePixels.resize(renderTexture.width * renderTexture.height * 4);
}
if (recordingAudio) {
tempAudioFile = std::make_unique<juce::TemporaryFile>(".wav");
audioRecorder.startRecording(tempAudioFile->getFile());
}
setPaused(false);
stopwatch.start();
} else if (ffmpegProcess.isRunning()) {
audioRecorder.stop();
ffmpegProcess.close();
chooser = std::make_unique<juce::FileChooser>("Save recording", lastOpenedDirectory, "*.mp4");
} else if (ffmpegProcess.isRunning() || audioRecorder.isRecording()) {
bool wasRecordingAudio = recordingAudio;
bool wasRecordingVideo = recordingVideo;
recordingAudio = false;
recordingVideo = false;
juce::String extension = wasRecordingVideo ? "mp4" : "wav";
if (wasRecordingAudio) {
audioRecorder.stop();
}
if (wasRecordingVideo) {
ffmpegProcess.close();
}
chooser = std::make_unique<juce::FileChooser>("Save recording", lastOpenedDirectory, "*." + extension);
auto flags = juce::FileBrowserComponent::saveMode | juce::FileBrowserComponent::canSelectFiles | juce::FileBrowserComponent::warnAboutOverwriting;
chooser->launchAsync(flags, [this](const juce::FileChooser& chooser) {
chooser->launchAsync(flags, [this, wasRecordingAudio, wasRecordingVideo](const juce::FileChooser& chooser) {
auto file = chooser.getResult();
if (file != juce::File()) {
ffmpegProcess.start("\"" + ffmpegFile.getFullPathName() + "\" -i \"" + tempVideoFile->getFile().getFullPathName() + "\" -i \"" + tempAudioFile->getFile().getFullPathName() + "\" -c:v copy -c:a aac -y \"" + file.getFullPathName() + "\"");
ffmpegProcess.close();
if (wasRecordingAudio && wasRecordingVideo) {
ffmpegProcess.start("\"" + ffmpegFile.getFullPathName() + "\" -i \"" + tempVideoFile->getFile().getFullPathName() + "\" -i \"" + tempAudioFile->getFile().getFullPathName() + "\" -c:v copy -c:a aac -y \"" + file.getFullPathName() + "\"");
ffmpegProcess.close();
} else if (wasRecordingAudio) {
tempAudioFile->getFile().copyFileTo(file);
} else if (wasRecordingVideo) {
tempVideoFile->getFile().copyFileTo(file);
}
lastOpenedDirectory = file.getParentDirectory();
}
});
@ -256,7 +294,7 @@ void VisualiserComponent::resized() {
void VisualiserComponent::popoutWindow() {
setRecording(false);
auto visualiser = new VisualiserComponent(lastOpenedDirectory, ffmpegFile, haltRecording, threadManager, settings, this);
auto visualiser = new VisualiserComponent(lastOpenedDirectory, ffmpegFile, haltRecording, threadManager, settings, recordingParameters, this);
visualiser->settings.setLookAndFeel(&getLookAndFeel());
visualiser->openSettings = openSettings;
visualiser->closeSettings = closeSettings;
@ -384,12 +422,16 @@ void VisualiserComponent::renderOpenGL() {
}
if (record.getToggleState()) {
// draw frame to ffmpeg
glBindTexture(GL_TEXTURE_2D, renderTexture.id);
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, framePixels.data());
ffmpegProcess.write(framePixels.data(), 4 * renderTexture.width * renderTexture.height);
if (recordingVideo) {
// draw frame to ffmpeg
glBindTexture(GL_TEXTURE_2D, renderTexture.id);
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, framePixels.data());
ffmpegProcess.write(framePixels.data(), 4 * renderTexture.width * renderTexture.height);
}
audioRecorder.audioThreadCallback(xSamples, ySamples);
if (recordingAudio) {
audioRecorder.audioThreadCallback(xSamples, ySamples);
}
}
renderingSemaphore.release();

Wyświetl plik

@ -6,6 +6,7 @@
#include "../concurrency/AudioBackgroundThread.h"
#include "../components/SvgButton.h"
#include "VisualiserSettings.h"
#include "RecordingSettings.h"
#include "../components/StopwatchComponent.h"
#include "../img/qoixx.hpp"
#include "../components/DownloaderComponent.h"
@ -31,7 +32,7 @@ struct Texture {
class VisualiserWindow;
class VisualiserComponent : public juce::Component, public AudioBackgroundThread, public juce::MouseListener, public juce::OpenGLRenderer, public juce::AsyncUpdater {
public:
VisualiserComponent(juce::File& lastOpenedDirectory, juce::File ffmpegFile, std::function<void()>& haltRecording, AudioBackgroundThreadManager& threadManager, VisualiserSettings& settings, VisualiserComponent* parent = nullptr, bool visualiserOnly = false);
VisualiserComponent(juce::File& lastOpenedDirectory, juce::File ffmpegFile, std::function<void()>& haltRecording, AudioBackgroundThreadManager& threadManager, VisualiserSettings& settings, RecordingParameters& recordingParameters, VisualiserComponent* parent = nullptr, bool visualiserOnly = false);
~VisualiserComponent() override;
std::function<void()> openSettings;
@ -78,6 +79,9 @@ private:
std::function<void(FullScreenMode)> fullScreenCallback;
VisualiserSettings& settings;
RecordingParameters& recordingParameters;
bool recordingAudio = false;
bool recordingVideo = false;
StopwatchComponent stopwatch;
SvgButton record{"Record", BinaryData::record_svg, juce::Colours::red, juce::Colours::red.withAlpha(0.01f)};

Wyświetl plik

@ -11,8 +11,8 @@
class VisualiserParameters {
public:
BooleanParameter* graticuleEnabled = new BooleanParameter("Show Graticule", "graticuleEnabled", VERSION_HINT, false, "Show the graticule or grid lines over the oscilloscope display.");
BooleanParameter* smudgesEnabled = new BooleanParameter("Show Smudges", "smudgesEnabled", VERSION_HINT, false, "Adds a subtle layer of dirt/smudges to the oscilloscope display to make it look more realistic.");
BooleanParameter* graticuleEnabled = new BooleanParameter("Show Graticule", "graticuleEnabled", VERSION_HINT, true, "Show the graticule or grid lines over the oscilloscope display.");
BooleanParameter* smudgesEnabled = new BooleanParameter("Show Smudges", "smudgesEnabled", VERSION_HINT, true, "Adds a subtle layer of dirt/smudges to the oscilloscope display to make it look more realistic.");
BooleanParameter* upsamplingEnabled = new BooleanParameter("Upsample Audio", "upsamplingEnabled", VERSION_HINT, true, "Upsamples the audio before visualising it to make it appear more realistic, at the expense of performance.");
BooleanParameter* sweepEnabled = new BooleanParameter("Sweep", "sweepEnabled", VERSION_HINT, false, "Plots the audio signal over time, sweeping from left to right");
BooleanParameter* visualiserFullScreen = new BooleanParameter("Visualiser Fullscreen", "visualiserFullScreen", VERSION_HINT, false, "Makes the software visualiser fullscreen.");
@ -179,7 +179,9 @@ private:
class SettingsWindow : public juce::DocumentWindow {
public:
SettingsWindow(juce::String name) : juce::DocumentWindow(name, Colours::darker, juce::DocumentWindow::TitleBarButtons::closeButton) {}
SettingsWindow(juce::String name) : juce::DocumentWindow(name, Colours::darker, juce::DocumentWindow::TitleBarButtons::closeButton) {
setResizable(false, false);
}
void closeButtonPressed() override {
setVisible(false);

Wyświetl plik

@ -610,6 +610,10 @@
file="Source/visualiser/OutputFragmentShader.glsl" xcodeResource="0"/>
<FILE id="A0CrLF" name="OutputVertexShader.glsl" compile="0" resource="0"
file="Source/visualiser/OutputVertexShader.glsl" xcodeResource="0"/>
<FILE id="XpwLUN" name="RecordingSettings.cpp" compile="1" resource="0"
file="Source/visualiser/RecordingSettings.cpp"/>
<FILE id="EOcvBc" name="RecordingSettings.h" compile="0" resource="0"
file="Source/visualiser/RecordingSettings.h"/>
<FILE id="Gwupwc" name="SimpleFragmentShader.glsl" compile="0" resource="0"
file="Source/visualiser/SimpleFragmentShader.glsl"/>
<FILE id="RX1oPv" name="SimpleVertexShader.glsl" compile="0" resource="0"

Wyświetl plik

@ -135,6 +135,10 @@
file="Source/visualiser/OutputFragmentShader.glsl"/>
<FILE id="df9jjf" name="OutputVertexShader.glsl" compile="0" resource="0"
file="Source/visualiser/OutputVertexShader.glsl"/>
<FILE id="Hw3RUp" name="RecordingSettings.cpp" compile="1" resource="0"
file="Source/visualiser/RecordingSettings.cpp"/>
<FILE id="dAhaYq" name="RecordingSettings.h" compile="0" resource="0"
file="Source/visualiser/RecordingSettings.h"/>
<FILE id="ePJ1g0" name="SimpleFragmentShader.glsl" compile="0" resource="0"
file="Source/visualiser/SimpleFragmentShader.glsl"/>
<FILE id="GFGIEu" name="SimpleVertexShader.glsl" compile="0" resource="0"