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"/>