kopia lustrzana https://github.com/jameshball/osci-render
Add recording settings
rodzic
cc3dae4267
commit
77a3271cde
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -31,8 +31,6 @@ public:
|
|||
|
||||
void editCustomFunction(bool enabled);
|
||||
|
||||
void openAudioSettings();
|
||||
|
||||
private:
|
||||
OscirenderAudioProcessor& audioProcessor;
|
||||
public:
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ OsciMainMenuBarModel::OsciMainMenuBarModel(OscirenderAudioProcessor& p, Oscirend
|
|||
});
|
||||
|
||||
addMenuItem(2, "Settings...", [this] {
|
||||
//editor.openRecordingSettings();
|
||||
editor.openRecordingSettings();
|
||||
});
|
||||
|
||||
addMenuItem(3, "Settings...", [this] {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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{¶meters.recordAudio};
|
||||
jux::SwitchButton recordVideo{¶meters.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)
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
Ładowanie…
Reference in New Issue