diff --git a/Resources/svg/play.svg b/Resources/svg/play.svg
new file mode 100644
index 00000000..6ef2006c
--- /dev/null
+++ b/Resources/svg/play.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Resources/svg/repeat.svg b/Resources/svg/repeat.svg
new file mode 100644
index 00000000..122a1578
--- /dev/null
+++ b/Resources/svg/repeat.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Resources/svg/stop.svg b/Resources/svg/stop.svg
new file mode 100644
index 00000000..365d3980
--- /dev/null
+++ b/Resources/svg/stop.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Source/CommonPluginEditor.cpp b/Source/CommonPluginEditor.cpp
index 2e1ec3dd..a41efcfb 100644
--- a/Source/CommonPluginEditor.cpp
+++ b/Source/CommonPluginEditor.cpp
@@ -44,7 +44,7 @@ CommonPluginEditor::CommonPluginEditor(CommonAudioProcessor& p, juce::String app
recordingSettings.setLookAndFeel(&getLookAndFeel());
recordingSettings.setSize(300, 200);
- recordingSettingsWindow.centreWithSize(300, 200);
+ recordingSettingsWindow.centreWithSize(300, 230);
#if JUCE_WINDOWS
// if not standalone, use native title bar for compatibility with DAWs
recordingSettingsWindow.setUsingNativeTitleBar(processor.wrapperType == juce::AudioProcessor::WrapperType::wrapperType_Standalone);
diff --git a/Source/CommonPluginEditor.h b/Source/CommonPluginEditor.h
index 4ac07725..5273fae3 100644
--- a/Source/CommonPluginEditor.h
+++ b/Source/CommonPluginEditor.h
@@ -52,15 +52,13 @@ public:
RecordingSettings recordingSettings = RecordingSettings(audioProcessor.recordingParameters);
SettingsWindow recordingSettingsWindow = SettingsWindow("Recording Settings", recordingSettings);
VisualiserComponent visualiser{
- audioProcessor.lastOpenedDirectory,
+ audioProcessor,
#if SOSCI_FEATURES
sharedTextureManager,
#endif
applicationFolder.getChildFile(ffmpegFileName),
- audioProcessor.haltRecording,
- audioProcessor.threadManager,
visualiserSettings,
- audioProcessor.recordingParameters,
+ recordingSettings,
nullptr,
appName == "sosci"
};
diff --git a/Source/CommonPluginProcessor.cpp b/Source/CommonPluginProcessor.cpp
index bceac628..006e887a 100644
--- a/Source/CommonPluginProcessor.cpp
+++ b/Source/CommonPluginProcessor.cpp
@@ -9,6 +9,7 @@
#include "CommonPluginProcessor.h"
#include "CommonPluginEditor.h"
#include "audio/EffectParameter.h"
+#include "components/AudioPlayerComponent.h"
//==============================================================================
CommonAudioProcessor::CommonAudioProcessor()
@@ -182,3 +183,36 @@ bool CommonAudioProcessor::hasEditor() const {
double CommonAudioProcessor::getSampleRate() {
return currentSampleRate;
}
+
+void CommonAudioProcessor::loadAudioFile(const juce::File& file) {
+ auto stream = std::make_unique(file);
+ if (stream->openedOk()) {
+ juce::SpinLock::ScopedLockType lock(wavParserLock);
+ wavParser = std::make_shared(*this, std::move(stream));
+
+ juce::SpinLock::ScopedLockType lock2(audioPlayerListenersLock);
+ for (auto listener : audioPlayerListeners) {
+ listener->parserChanged(wavParser);
+ }
+ }
+}
+
+void CommonAudioProcessor::stopAudioFile() {
+ juce::SpinLock::ScopedLockType lock(wavParserLock);
+ wavParser = nullptr;
+
+ juce::SpinLock::ScopedLockType lock2(audioPlayerListenersLock);
+ for (auto listener : audioPlayerListeners) {
+ listener->parserChanged(wavParser);
+ }
+}
+
+void CommonAudioProcessor::addAudioPlayerListener(AudioPlayerListener* listener) {
+ juce::SpinLock::ScopedLockType lock(audioPlayerListenersLock);
+ audioPlayerListeners.push_back(listener);
+}
+
+void CommonAudioProcessor::removeAudioPlayerListener(AudioPlayerListener* listener) {
+ juce::SpinLock::ScopedLockType lock(audioPlayerListenersLock);
+ audioPlayerListeners.erase(std::remove(audioPlayerListeners.begin(), audioPlayerListeners.end(), listener), audioPlayerListeners.end());
+}
diff --git a/Source/CommonPluginProcessor.h b/Source/CommonPluginProcessor.h
index 86685c2b..a5ba9bc3 100644
--- a/Source/CommonPluginProcessor.h
+++ b/Source/CommonPluginProcessor.h
@@ -15,10 +15,10 @@
#include "visualiser/VisualiserSettings.h"
#include "visualiser/RecordingSettings.h"
#include "audio/Effect.h"
+#include "wav/WavParser.h"
-//==============================================================================
-/**
-*/
+
+class AudioPlayerListener;
class CommonAudioProcessor : public juce::AudioProcessor, public SampleRateManager
#if JucePlugin_Enable_ARA
, public juce::AudioProcessorARAExtension
@@ -54,6 +54,13 @@ public:
const juce::String getProgramName(int index) override;
void changeProgramName(int index, const juce::String& newName) override;
double getSampleRate() override;
+ void loadAudioFile(const juce::File& file);
+ void stopAudioFile();
+ void addAudioPlayerListener(AudioPlayerListener* listener);
+ void removeAudioPlayerListener(AudioPlayerListener* listener);
+
+ juce::SpinLock audioPlayerListenersLock;
+ std::vector audioPlayerListeners;
std::atomic volume = 1.0;
std::atomic threshold = 1.0;
@@ -82,6 +89,9 @@ public:
)
);
+ juce::SpinLock wavParserLock;
+ std::shared_ptr wavParser;
+
std::atomic currentSampleRate = 0.0;
juce::SpinLock effectsLock;
VisualiserParameters visualiserParameters;
diff --git a/Source/SosciPluginEditor.cpp b/Source/SosciPluginEditor.cpp
index b6c5c623..f4e3e710 100644
--- a/Source/SosciPluginEditor.cpp
+++ b/Source/SosciPluginEditor.cpp
@@ -30,5 +30,8 @@ void SosciPluginEditor::resized() {
visualiserSettings.setSize(settingsArea.getWidth(), 550);
visualiserSettingsWrapper.setBounds(settingsArea);
+ if (area.getWidth() < 10 || area.getHeight() < 10) {
+ return;
+ }
visualiser.setBounds(area);
}
diff --git a/Source/SosciPluginProcessor.cpp b/Source/SosciPluginProcessor.cpp
index 2da4fb67..075e63fa 100644
--- a/Source/SosciPluginProcessor.cpp
+++ b/Source/SosciPluginProcessor.cpp
@@ -20,7 +20,7 @@ void SosciAudioProcessor::processBlock(juce::AudioBuffer& buffer, juce::M
auto inputArray = input.getArrayOfWritePointers();
auto outputArray = output.getArrayOfWritePointers();
- juce::CriticalSection::ScopedLockType lock2(wavParserLock);
+ juce::SpinLock::ScopedLockType lock2(wavParserLock);
bool readingFromWav = wavParser != nullptr;
for (int sample = 0; sample < input.getNumSamples(); ++sample) {
@@ -175,19 +175,6 @@ void SosciAudioProcessor::setStateInformation(const void* data, int sizeInBytes)
}
}
-void SosciAudioProcessor::loadAudioFile(const juce::File& file) {
- auto stream = std::make_unique(file);
- if (stream->openedOk()) {
- juce::CriticalSection::ScopedLockType lock(wavParserLock);
- wavParser = std::make_unique(*this, std::move(stream));
- }
-}
-
-void SosciAudioProcessor::stopAudioFile() {
- juce::CriticalSection::ScopedLockType lock(wavParserLock);
- wavParser = nullptr;
-}
-
juce::AudioProcessorEditor* SosciAudioProcessor::createEditor() {
auto editor = new SosciPluginEditor(*this);
return editor;
diff --git a/Source/SosciPluginProcessor.h b/Source/SosciPluginProcessor.h
index 9b8f8e0f..6e29b6ea 100644
--- a/Source/SosciPluginProcessor.h
+++ b/Source/SosciPluginProcessor.h
@@ -30,16 +30,9 @@ public:
void getStateInformation(juce::MemoryBlock& destData) override;
void setStateInformation(const void* data, int sizeInBytes) override;
-
- void loadAudioFile(const juce::File& file);
- void stopAudioFile();
juce::AudioProcessorEditor* createEditor() override;
-private:
- juce::CriticalSection wavParserLock;
- std::unique_ptr wavParser;
-
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SosciAudioProcessor)
};
diff --git a/Source/audio/EffectParameter.h b/Source/audio/EffectParameter.h
index 1ff9511f..9da1575a 100644
--- a/Source/audio/EffectParameter.h
+++ b/Source/audio/EffectParameter.h
@@ -129,8 +129,6 @@ private:
// value is not necessarily in the range [min, max] so effect applications may need to clip to a valid range
std::atomic value = 0.0;
juce::String label;
-
- JUCE_HEAVYWEIGHT_LEAK_DETECTOR(FloatParameter)
};
class IntParameter : public juce::AudioProcessorParameterWithID {
@@ -253,8 +251,6 @@ public:
private:
// value is not necessarily in the range [min, max] so effect applications may need to clip to a valid range
std::atomic value = 0;
-
- JUCE_HEAVYWEIGHT_LEAK_DETECTOR(IntParameter)
};
enum class LfoType : int {
@@ -404,6 +400,4 @@ public:
}
EffectParameter(juce::String name, juce::String description, juce::String id, int versionHint, float value, float min, float max, float step = 0.01, bool smoothValueChange = true) : FloatParameter(name, id, versionHint, value, min, max, step), smoothValueChange(smoothValueChange), description(description) {}
-
- JUCE_HEAVYWEIGHT_LEAK_DETECTOR(EffectParameter)
};
diff --git a/Source/components/AudioPlayerComponent.cpp b/Source/components/AudioPlayerComponent.cpp
new file mode 100644
index 00000000..e6f69f13
--- /dev/null
+++ b/Source/components/AudioPlayerComponent.cpp
@@ -0,0 +1,146 @@
+#include "AudioPlayerComponent.h"
+#include "../CommonPluginProcessor.h"
+
+AudioPlayerComponent::AudioPlayerComponent(CommonAudioProcessor& processor) : audioProcessor(processor) {
+ setOpaque(false);
+
+ audioProcessor.addAudioPlayerListener(this);
+
+ parser = audioProcessor.wavParser;
+
+ addAndMakeVisible(slider);
+ slider.setSliderStyle(juce::Slider::SliderStyle::LinearHorizontal);
+ slider.setTextBoxStyle(juce::Slider::NoTextBox, true, 0, 0);
+ slider.setOpaque(false);
+ slider.setRange(0, 1, 0.001);
+ slider.setLookAndFeel(&timelineLookAndFeel);
+ slider.setColour(juce::Slider::ColourIds::thumbColourId, juce::Colours::black);
+
+ slider.onValueChange = [this]() {
+ juce::SpinLock::ScopedLockType sl(audioProcessor.wavParserLock);
+
+ if (parser != nullptr) {
+ parser->setProgress(slider.getValue());
+ } else {
+ slider.setValue(0, juce::dontSendNotification);
+ }
+ };
+
+ addChildComponent(playButton);
+ addChildComponent(pauseButton);
+
+ playButton.setTooltip("Play audio file");
+ pauseButton.setTooltip("Pause audio file");
+
+ repeatButton.setToggleState(true, juce::dontSendNotification);
+
+ playButton.onClick = [this]() {
+ juce::SpinLock::ScopedLockType sl(audioProcessor.wavParserLock);
+
+ if (parser != nullptr) {
+ parser->setPaused(false);
+ playButton.setVisible(false);
+ pauseButton.setVisible(true);
+ }
+ };
+
+ pauseButton.onClick = [this]() {
+ juce::SpinLock::ScopedLockType sl(audioProcessor.wavParserLock);
+
+ if (parser != nullptr) {
+ parser->setPaused(true);
+ playButton.setVisible(true);
+ pauseButton.setVisible(false);
+ }
+ };
+
+ addAndMakeVisible(repeatButton);
+
+ repeatButton.setTooltip("Repeat audio file once it finishes playing");
+
+ repeatButton.onClick = [this]() {
+ juce::SpinLock::ScopedLockType sl(audioProcessor.wavParserLock);
+
+ if (parser != nullptr) {
+ parser->setLooping(repeatButton.getToggleState());
+ }
+ };
+
+ addAndMakeVisible(stopButton);
+
+ stopButton.setTooltip("Stop and close audio file");
+
+ stopButton.onClick = [this]() {
+ audioProcessor.stopAudioFile();
+ };
+
+ {
+ juce::SpinLock::ScopedLockType sl(audioProcessor.wavParserLock);
+ setup();
+ }
+}
+
+AudioPlayerComponent::~AudioPlayerComponent() {
+ audioProcessor.removeAudioPlayerListener(this);
+ juce::SpinLock::ScopedLockType sl(audioProcessor.wavParserLock);
+ if (parser != nullptr) {
+ parser->onProgress = nullptr;
+ }
+}
+
+// must hold lock
+void AudioPlayerComponent::setup() {
+ if (parser != nullptr) {
+ slider.setVisible(true);
+ repeatButton.setVisible(true);
+ stopButton.setVisible(true);
+ parser->onProgress = [this](double progress) {
+ juce::WeakReference weakRef(this);
+ juce::MessageManager::callAsync([this, progress, weakRef]() {
+ if (weakRef) {
+ slider.setValue(progress, juce::dontSendNotification);
+ repaint();
+ }
+ });
+ };
+ playButton.setVisible(parser->isPaused());
+ pauseButton.setVisible(!parser->isPaused());
+ parser->setLooping(repeatButton.getToggleState());
+ } else {
+ slider.setVisible(false);
+ repeatButton.setVisible(false);
+ stopButton.setVisible(false);
+ slider.setValue(0);
+ playButton.setVisible(false);
+ pauseButton.setVisible(false);
+ }
+
+ parserWasNull = parser == nullptr;
+}
+
+void AudioPlayerComponent::setPaused(bool paused) {
+ if (paused) {
+ pauseButton.triggerClick();
+ } else {
+ playButton.triggerClick();
+ }
+}
+
+void AudioPlayerComponent::parserChanged(std::shared_ptr parser) {
+ this->parser = parser;
+ setup();
+ repaint();
+}
+
+void AudioPlayerComponent::resized() {
+ auto r = getLocalBounds();
+
+ auto playPauseBounds = r.removeFromLeft(25);
+ playButton.setBounds(playPauseBounds);
+ pauseButton.setBounds(playPauseBounds);
+ stopButton.setBounds(r.removeFromLeft(25));
+
+ repeatButton.setBounds(r.removeFromRight(25));
+
+ slider.setBounds(r);
+}
diff --git a/Source/components/AudioPlayerComponent.h b/Source/components/AudioPlayerComponent.h
new file mode 100644
index 00000000..b56b4823
--- /dev/null
+++ b/Source/components/AudioPlayerComponent.h
@@ -0,0 +1,83 @@
+#pragma once
+
+#include
+#include "../CommonPluginProcessor.h"
+#include "../LookAndFeel.h"
+#include "../wav/WavParser.h"
+
+class TimelineLookAndFeel : public OscirenderLookAndFeel {
+public:
+ TimelineLookAndFeel() {}
+
+ void drawLinearSlider(juce::Graphics& g, int x, int y, int width, int height, float sliderPos, float minSliderPos, float maxSliderPos, const juce::Slider::SliderStyle style, juce::Slider& slider) override {
+ auto trackWidth = juce::jmin (6.0f, slider.isHorizontal() ? (float) height * 0.25f : (float) width * 0.25f);
+
+ juce::Point startPoint (slider.isHorizontal() ? (float) x : (float) x + (float) width * 0.5f,
+ slider.isHorizontal() ? (float) y + (float) height * 0.5f : (float) (height + y));
+
+ juce::Point endPoint (slider.isHorizontal() ? (float) (width + x) : startPoint.x,
+ slider.isHorizontal() ? startPoint.y : (float) y);
+
+ juce::Path backgroundTrack;
+ backgroundTrack.startNewSubPath (startPoint);
+ backgroundTrack.lineTo (endPoint);
+ g.setColour (slider.findColour (juce::Slider::backgroundColourId));
+ g.strokePath (backgroundTrack, { trackWidth, juce::PathStrokeType::curved, juce::PathStrokeType::rounded });
+
+ juce::Path valueTrack;
+ juce::Point minPoint, maxPoint, thumbPoint;
+
+ auto kx = slider.isHorizontal() ? sliderPos : ((float) x + (float) width * 0.5f);
+ auto ky = slider.isHorizontal() ? ((float) y + (float) height * 0.5f) : sliderPos;
+
+ minPoint = startPoint;
+ maxPoint = { kx, ky };
+
+ auto thumbWidth = getSliderThumbRadius (slider);
+
+ valueTrack.startNewSubPath (minPoint);
+ valueTrack.lineTo (maxPoint);
+ g.setColour (slider.findColour (juce::Slider::trackColourId));
+ g.strokePath (valueTrack, { trackWidth, juce::PathStrokeType::curved, juce::PathStrokeType::rounded });
+
+ g.setColour (slider.findColour (juce::Slider::thumbColourId));
+ g.fillRect (juce::Rectangle (thumbWidth / 2, 1.5 * thumbWidth).withCentre (maxPoint));
+
+ g.setColour(slider.findColour(sliderThumbOutlineColourId).withAlpha(slider.isEnabled() ? 1.0f : 0.5f));
+ g.drawRect(juce::Rectangle(thumbWidth / 2, 1.5 * thumbWidth).withCentre(maxPoint));
+ }
+};
+
+class AudioPlayerListener {
+public:
+ virtual void parserChanged(std::shared_ptr parser) = 0;
+};
+
+class CommonAudioProcessor;
+class AudioPlayerComponent : public juce::Component, public AudioPlayerListener {
+public:
+ AudioPlayerComponent(CommonAudioProcessor& processor);
+ ~AudioPlayerComponent() override;
+
+ void resized() override;
+ void setup();
+ void parserChanged(std::shared_ptr parser) override;
+ void setPaused(bool paused);
+
+private:
+ CommonAudioProcessor& audioProcessor;
+
+ bool parserWasNull = true;
+ std::shared_ptr parser;
+
+ TimelineLookAndFeel timelineLookAndFeel;
+
+ SvgButton playButton{ "Play", BinaryData::play_svg, juce::Colours::white, juce::Colours::white};
+ SvgButton pauseButton{ "Pause", BinaryData::pause_svg, juce::Colours::white, juce::Colours::white };
+ SvgButton repeatButton{ "Repeat", BinaryData::repeat_svg, juce::Colours::white, Colours::accentColor };
+ SvgButton stopButton{ "Stop", BinaryData::stop_svg, juce::Colours::white, juce::Colours::white };
+ juce::Slider slider;
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AudioPlayerComponent)
+ JUCE_DECLARE_WEAK_REFERENCEABLE(AudioPlayerComponent)
+};
diff --git a/Source/components/SosciMainMenuBarModel.cpp b/Source/components/SosciMainMenuBarModel.cpp
index 43ad7acf..6d6716fb 100644
--- a/Source/components/SosciMainMenuBarModel.cpp
+++ b/Source/components/SosciMainMenuBarModel.cpp
@@ -37,18 +37,16 @@ SosciMainMenuBarModel::SosciMainMenuBarModel(SosciPluginEditor& e, SosciAudioPro
};
addMenuItem(0, "Open Audio File", [&]() {
- fileChooser = std::make_unique("Open Audio File", processor.lastOpenedDirectory, "*.wav;*.aiff;*.flac;*.ogg");
+ fileChooser = std::make_unique("Open Audio File", processor.lastOpenedDirectory, "*.wav;*.aiff;*.flac;*.ogg;*.mp3");
auto flags = juce::FileBrowserComponent::openMode | juce::FileBrowserComponent::canSelectFiles;
fileChooser->launchAsync(flags, [&](const juce::FileChooser& chooser) {
auto file = chooser.getResult();
if (file != juce::File()) {
processor.loadAudioFile(file);
+ processor.lastOpenedDirectory = file.getParentDirectory();
}
});
});
- addMenuItem(0, "Stop Audio File", [&]() {
- processor.stopAudioFile();
- });
addMenuItem(0, "Open Project", [&]() { editor.openProject(); });
addMenuItem(0, "Save Project", [&]() { editor.saveProject(); });
addMenuItem(0, "Save Project As", [&]() { editor.saveProjectAs(); });
diff --git a/Source/visualiser/RecordingSettings.h b/Source/visualiser/RecordingSettings.h
index ba3773d4..ac347642 100644
--- a/Source/visualiser/RecordingSettings.h
+++ b/Source/visualiser/RecordingSettings.h
@@ -38,24 +38,6 @@ public:
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"));
@@ -94,6 +76,24 @@ public:
void resized() override;
+ int getCRF() {
+ double quality = juce::jlimit(0.0, 1.0, parameters.qualityEffect.getValue());
+ // mapping to 0-51 for ffmpeg's crf value
+ return 51 * (1.0 - quality) ;
+ }
+
+ bool recordingVideo() {
+ return parameters.recordVideo.getBoolValue();
+ }
+
+ bool recordingAudio() {
+ return parameters.recordAudio.getBoolValue();
+ }
+
+ juce::String getCompressionPreset() {
+ return parameters.compressionPreset;
+ }
+
RecordingParameters& parameters;
private:
diff --git a/Source/visualiser/VisualiserComponent.cpp b/Source/visualiser/VisualiserComponent.cpp
index 3efc6e12..156e00a5 100644
--- a/Source/visualiser/VisualiserComponent.cpp
+++ b/Source/visualiser/VisualiserComponent.cpp
@@ -1,5 +1,7 @@
#include "../LookAndFeel.h"
#include "VisualiserComponent.h"
+#include "../CommonPluginProcessor.h"
+
#include "BlurFragmentShader.glsl"
#include "BlurVertexShader.glsl"
#include "WideBlurFragmentShader.glsl"
@@ -16,28 +18,24 @@
#include "TexturedVertexShader.glsl"
VisualiserComponent::VisualiserComponent(
- juce::File& lastOpenedDirectory,
+ CommonAudioProcessor& processor,
#if SOSCI_FEATURES
SharedTextureManager& sharedTextureManager,
#endif
juce::File ffmpegFile,
- std::function& haltRecording,
- AudioBackgroundThreadManager& threadManager,
VisualiserSettings& settings,
- RecordingParameters& recordingParameters,
+ RecordingSettings& recordingSettings,
VisualiserComponent* parent,
bool visualiserOnly
-) : lastOpenedDirectory(lastOpenedDirectory),
+) : audioProcessor(processor),
ffmpegFile(ffmpegFile),
#if SOSCI_FEATURES
sharedTextureManager(sharedTextureManager),
#endif
- haltRecording(haltRecording),
settings(settings),
- recordingParameters(recordingParameters),
- threadManager(threadManager),
+ recordingSettings(recordingSettings),
visualiserOnly(visualiserOnly),
- AudioBackgroundThread("VisualiserComponent" + juce::String(parent != nullptr ? " Child" : ""), threadManager),
+ AudioBackgroundThread("VisualiserComponent" + juce::String(parent != nullptr ? " Child" : ""), processor.threadManager),
parent(parent) {
#if SOSCI_FEATURES
addAndMakeVisible(ffmpegDownloader);
@@ -45,11 +43,18 @@ VisualiserComponent::VisualiserComponent(
ffmpegDownloader.onSuccessfulDownload = [this] {
juce::MessageManager::callAsync([this] {
record.setEnabled(true);
+ juce::Timer::callAfterDelay(3000, [this] {
+ juce::MessageManager::callAsync([this] {
+ ffmpegDownloader.setVisible(false);
+ downloading = false;
+ resized();
+ });
+ });
});
};
#endif
- haltRecording = [this] {
+ audioProcessor.haltRecording = [this] {
setRecording(false);
};
@@ -110,6 +115,8 @@ VisualiserComponent::VisualiserComponent(
popoutWindow();
};
+ addAndMakeVisible(audioPlayer);
+
setFullScreen(false);
openGLContext.setRenderer(this);
@@ -121,7 +128,7 @@ VisualiserComponent::VisualiserComponent(
VisualiserComponent::~VisualiserComponent() {
setRecording(false);
if (parent == nullptr) {
- haltRecording = nullptr;
+ audioProcessor.haltRecording = nullptr;
}
openGLContext.detach();
setShouldBeRunning(false, [this] {
@@ -255,6 +262,7 @@ void VisualiserComponent::setPaused(bool paused) {
active = !paused;
renderingSemaphore.release();
setShouldBeRunning(active);
+ audioPlayer.setPaused(paused);
repaint();
}
@@ -289,8 +297,8 @@ void VisualiserComponent::setRecording(bool recording) {
if (recording) {
#if SOSCI_FEATURES
- recordingVideo = recordingParameters.recordingVideo();
- recordingAudio = recordingParameters.recordingAudio();
+ recordingVideo = recordingSettings.recordingVideo();
+ recordingAudio = recordingSettings.recordingAudio();
if (!recordingVideo && !recordingAudio) {
record.setToggleState(false, juce::NotificationType::dontSendNotification);
return;
@@ -311,6 +319,9 @@ void VisualiserComponent::setRecording(bool recording) {
if (result == 1) {
record.setEnabled(false);
ffmpegDownloader.download();
+ ffmpegDownloader.setVisible(true);
+ downloading = true;
+ resized();
}
});
record.setToggleState(false, juce::NotificationType::dontSendNotification);
@@ -325,10 +336,10 @@ void VisualiserComponent::setRecording(bool recording) {
" -s " + resolution +
" -i -" +
" -threads 4" +
- " -preset " + recordingParameters.getCompressionPreset() +
+ " -preset " + recordingSettings.getCompressionPreset() +
" -y" +
" -pix_fmt yuv420p" +
- " -crf " + juce::String(recordingParameters.getCRF()) +
+ " -crf " + juce::String(recordingSettings.getCRF()) +
" -vf vflip" +
" \"" + tempVideoFile->getFile().getFullPathName() + "\"";
@@ -366,7 +377,7 @@ void VisualiserComponent::setRecording(bool recording) {
audioRecorder.stop();
juce::String extension = "wav";
#endif
- chooser = std::make_unique("Save recording", lastOpenedDirectory, "*." + extension);
+ chooser = std::make_unique("Save recording", audioProcessor.lastOpenedDirectory, "*." + extension);
auto flags = juce::FileBrowserComponent::saveMode | juce::FileBrowserComponent::canSelectFiles | juce::FileBrowserComponent::warnAboutOverwriting;
#if SOSCI_FEATURES
@@ -381,7 +392,7 @@ void VisualiserComponent::setRecording(bool recording) {
} else if (wasRecordingVideo) {
tempVideoFile->getFile().copyFileTo(file);
}
- lastOpenedDirectory = file.getParentDirectory();
+ audioProcessor.lastOpenedDirectory = file.getParentDirectory();
}
});
#else
@@ -389,7 +400,7 @@ void VisualiserComponent::setRecording(bool recording) {
auto file = chooser.getResult();
if (file != juce::File()) {
tempAudioFile->getFile().copyFileTo(file);
- lastOpenedDirectory = file.getParentDirectory();
+ audioProcessor.lastOpenedDirectory = file.getParentDirectory();
}
});
#endif
@@ -430,11 +441,13 @@ void VisualiserComponent::resized() {
stopwatch.setVisible(false);
}
#if SOSCI_FEATURES
- if (child == nullptr) {
+ if (child == nullptr && downloading) {
auto bounds = buttons.removeFromRight(160);
ffmpegDownloader.setBounds(bounds.withSizeKeepingCentre(bounds.getWidth() - 10, bounds.getHeight() - 10));
}
#endif
+ buttons.removeFromRight(10); // padding
+ audioPlayer.setBounds(buttons);
viewportArea = area;
viewportChanged(viewportArea);
}
@@ -447,15 +460,13 @@ void VisualiserComponent::popoutWindow() {
#endif
setRecording(false);
auto visualiser = new VisualiserComponent(
- lastOpenedDirectory,
+ audioProcessor,
#if SOSCI_FEATURES
sharedTextureManager,
#endif
ffmpegFile,
- haltRecording,
- threadManager,
settings,
- recordingParameters,
+ recordingSettings,
this
);
visualiser->settings.setLookAndFeel(&getLookAndFeel());
@@ -481,12 +492,12 @@ void VisualiserComponent::childUpdated() {
#endif
record.setVisible(child == nullptr);
if (child != nullptr) {
- haltRecording = [this] {
+ audioProcessor.haltRecording = [this] {
setRecording(false);
child->setRecording(false);
};
} else {
- haltRecording = [this] {
+ audioProcessor.haltRecording = [this] {
setRecording(false);
};
}
@@ -660,7 +671,7 @@ void VisualiserComponent::renderOpenGL() {
void VisualiserComponent::viewportChanged(juce::Rectangle area) {
using namespace juce::gl;
-
+
if (openGLContext.isAttached()) {
float realWidth = area.getWidth() * renderScale;
float realHeight = area.getHeight() * renderScale;
@@ -1175,11 +1186,7 @@ void VisualiserComponent::checkGLErrors(const juce::String& location) {
void VisualiserComponent::paint(juce::Graphics& g) {
-#if SOSCI_FEATURES
- g.setColour(settings.getScreenType() == ScreenType::Real ? Colours::dark : Colours::veryDark);
-#else
g.setColour(Colours::veryDark);
-#endif
g.fillRect(buttonRow);
if (!active) {
// draw a translucent overlay
diff --git a/Source/visualiser/VisualiserComponent.h b/Source/visualiser/VisualiserComponent.h
index 527b0097..436e2f06 100644
--- a/Source/visualiser/VisualiserComponent.h
+++ b/Source/visualiser/VisualiserComponent.h
@@ -12,6 +12,8 @@
#include "../components/DownloaderComponent.h"
#include "../concurrency/WriteProcess.h"
#include "../audio/AudioRecorder.h"
+#include "../wav/WavParser.h"
+#include "../components/AudioPlayerComponent.h"
#define FILE_RENDER_DUMMY 0
#define FILE_RENDER_PNG 1
@@ -29,19 +31,18 @@ struct Texture {
int height;
};
+class CommonAudioProcessor;
class VisualiserWindow;
class VisualiserComponent : public juce::Component, public AudioBackgroundThread, public juce::MouseListener, public juce::OpenGLRenderer, public juce::AsyncUpdater {
public:
VisualiserComponent(
- juce::File& lastOpenedDirectory,
+ CommonAudioProcessor& processor,
#if SOSCI_FEATURES
SharedTextureManager& sharedTextureManager,
#endif
juce::File ffmpegFile,
- std::function& haltRecording,
- AudioBackgroundThreadManager& threadManager,
VisualiserSettings& settings,
- RecordingParameters& recordingParameters,
+ RecordingSettings& recordingSettings,
VisualiserComponent* parent = nullptr,
bool visualiserOnly = false
);
@@ -76,13 +77,13 @@ public:
std::atomic active = true;
private:
+ CommonAudioProcessor& audioProcessor;
+
float intensity;
const double FRAME_RATE = 60.0;
bool visualiserOnly;
- std::function& haltRecording;
-
- AudioBackgroundThreadManager& threadManager;
+ AudioPlayerComponent audioPlayer{audioProcessor};
SvgButton fullScreenButton{ "fullScreen", BinaryData::fullscreen_svg, juce::Colours::white, juce::Colours::white };
SvgButton popOutButton{ "popOut", BinaryData::open_in_new_svg, juce::Colours::white, juce::Colours::white };
@@ -97,12 +98,13 @@ private:
std::function fullScreenCallback;
VisualiserSettings& settings;
- RecordingParameters& recordingParameters;
+ RecordingSettings& recordingSettings;
juce::File ffmpegFile;
bool recordingAudio = true;
#if SOSCI_FEATURES
bool recordingVideo = true;
+ bool downloading = false;
long numFrames = 0;
std::vector framePixels;
@@ -144,7 +146,6 @@ private:
StopwatchComponent stopwatch;
SvgButton record{"Record", BinaryData::record_svg, juce::Colours::red, juce::Colours::red.withAlpha(0.01f)};
- juce::File& lastOpenedDirectory;
std::unique_ptr chooser;
std::unique_ptr tempAudioFile;
AudioRecorder audioRecorder;
@@ -260,7 +261,6 @@ private:
void viewportChanged(juce::Rectangle area);
void renderScope(const std::vector& xPoints, const std::vector& yPoints, const std::vector& zPoints);
- int renderAudioFile(juce::File& sourceAudio, int method = 1, int width = 1024, int height = 1024);
double getSweepIncrement();
diff --git a/Source/visualiser/VisualiserSettings.h b/Source/visualiser/VisualiserSettings.h
index 7bd802be..4129f826 100644
--- a/Source/visualiser/VisualiserSettings.h
+++ b/Source/visualiser/VisualiserSettings.h
@@ -307,11 +307,12 @@ private:
juce::Component& component;
};
-class SettingsWindow : public juce::DocumentWindow {
+class SettingsWindow : public juce::DialogWindow {
public:
- SettingsWindow(juce::String name, juce::Component& component) : juce::DocumentWindow(name, Colours::darker, juce::DocumentWindow::TitleBarButtons::closeButton), component(component) {
- juce::Component::addAndMakeVisible(viewport);
+ SettingsWindow(juce::String name, juce::Component& component) : juce::DialogWindow(name, Colours::darker, true, true), component(component) {
+ setContentComponent(&viewport);
setResizable(false, false);
+ viewport.setColour(juce::ScrollBar::trackColourId, juce::Colours::white);
viewport.setViewedComponent(&component, false);
viewport.setScrollBarsShown(true, false, true, false);
setAlwaysOnTop(true);
@@ -321,10 +322,6 @@ public:
setVisible(false);
}
- void resized() override {
- viewport.setBounds(getLocalBounds());
- }
-
private:
juce::Viewport viewport;
juce::Component& component;
diff --git a/Source/wav/WavParser.cpp b/Source/wav/WavParser.cpp
index 004018f0..f17f7140 100644
--- a/Source/wav/WavParser.cpp
+++ b/Source/wav/WavParser.cpp
@@ -9,7 +9,8 @@ WavParser::WavParser(CommonAudioProcessor& p, std::unique_ptr
if (reader == nullptr) {
return;
}
- auto* afSource = new juce::AudioFormatReaderSource(reader, true);
+ afSource = new juce::AudioFormatReaderSource(reader, true);
+ totalSamples = afSource->getTotalLength();
afSource->setLooping(true);
source = std::make_unique(afSource, true);
fileSampleRate = reader->sampleRate;
@@ -32,11 +33,23 @@ OsciPoint WavParser::getSample() {
if (currentSampleRate != audioProcessor.currentSampleRate) {
setSampleRate(audioProcessor.currentSampleRate);
}
- if (source == nullptr) {
+ if (source == nullptr || paused) {
return OsciPoint();
}
source->getNextAudioBlock(juce::AudioSourceChannelInfo(audioBuffer));
+ currentSample += source->getResamplingRatio();
+ counter++;
+ if (currentSample >= totalSamples && afSource->isLooping()) {
+ currentSample = 0;
+ counter = 0;
+ afSource->setNextReadPosition(0);
+ }
+ if (counter % currentSampleRate == 0) {
+ if (onProgress != nullptr) {
+ onProgress((double)currentSample / totalSamples);
+ }
+ }
if (audioBuffer.getNumChannels() == 1) {
return OsciPoint(audioBuffer.getSample(0, 0), audioBuffer.getSample(0, 0));
@@ -44,3 +57,29 @@ OsciPoint WavParser::getSample() {
return OsciPoint(audioBuffer.getSample(0, 0), audioBuffer.getSample(1, 0));
}
}
+
+void WavParser::setProgress(double progress) {
+ if (source == nullptr) {
+ return;
+ }
+ afSource->setNextReadPosition(progress * totalSamples);
+ currentSample = progress * totalSamples;
+}
+
+void WavParser::setLooping(bool looping) {
+ afSource->setLooping(looping);
+ this->looping = looping;
+}
+
+bool WavParser::isLooping() {
+ return looping;
+}
+
+void WavParser::setPaused(bool paused) {
+ this->paused = paused;
+ counter = 0;
+}
+
+bool WavParser::isPaused() {
+ return paused;
+}
diff --git a/Source/wav/WavParser.h b/Source/wav/WavParser.h
index 41848f36..9d20acf5 100644
--- a/Source/wav/WavParser.h
+++ b/Source/wav/WavParser.h
@@ -10,12 +10,25 @@ public:
OsciPoint getSample();
+ void setProgress(double progress);
+ void setPaused(bool paused);
+ bool isPaused();
+ void setLooping(bool looping);
+ bool isLooping();
+
+ std::function onProgress;
+
private:
void setSampleRate(double sampleRate);
+ juce::AudioFormatReaderSource* afSource;
+ bool looping = true;
std::unique_ptr source;
juce::AudioBuffer audioBuffer;
- int currentSample = 0;
+ long totalSamples;
+ long counter = 0;
+ std::atomic currentSample = 0;
+ std::atomic paused = false;
int fileSampleRate;
int currentSampleRate;
CommonAudioProcessor& audioProcessor;
diff --git a/osci-render.jucer b/osci-render.jucer
index 7bc67618..22f8384a 100644
--- a/osci-render.jucer
+++ b/osci-render.jucer
@@ -58,11 +58,14 @@
+
+
+
@@ -148,6 +151,10 @@
file="Source/components/AboutComponent.cpp"/>
+
+
diff --git a/sosci.jucer b/sosci.jucer
index ea37db38..a10644b7 100644
--- a/sosci.jucer
+++ b/sosci.jucer
@@ -53,11 +53,14 @@
+
+
+
@@ -102,6 +105,10 @@
file="Source/components/AboutComponent.cpp"/>
+
+
+ JUCE_WIN_PER_MONITOR_DPI_AWARE="0" JUCE_WEB_BROWSER="0" JUCE_USE_MP3AUDIOFORMAT="1"/>