diff --git a/Resources/fonts/FiraSans-Bold.ttf b/Resources/fonts/FiraSans-Bold.ttf new file mode 100644 index 0000000..e3593fb Binary files /dev/null and b/Resources/fonts/FiraSans-Bold.ttf differ diff --git a/Resources/fonts/FiraSans-Italic.ttf b/Resources/fonts/FiraSans-Italic.ttf new file mode 100644 index 0000000..27d32ed Binary files /dev/null and b/Resources/fonts/FiraSans-Italic.ttf differ diff --git a/Resources/fonts/font.ttf b/Resources/fonts/FiraSans-Regular.ttf similarity index 100% rename from Resources/fonts/font.ttf rename to Resources/fonts/FiraSans-Regular.ttf diff --git a/Resources/fonts/OFL.txt b/Resources/fonts/OFL.txt new file mode 100644 index 0000000..fc506b8 --- /dev/null +++ b/Resources/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/Source/LookAndFeel.cpp b/Source/LookAndFeel.cpp index a1ed38e..f1e25d1 100644 --- a/Source/LookAndFeel.cpp +++ b/Source/LookAndFeel.cpp @@ -26,6 +26,8 @@ OscirenderLookAndFeel::OscirenderLookAndFeel() { setColour(juce::TooltipWindow::backgroundColourId, Colours::darker); setColour(juce::TooltipWindow::outlineColourId, juce::Colours::white); setColour(juce::TextButton::buttonOnColourId, Colours::darker); + setColour(juce::AlertWindow::outlineColourId, Colours::darker); + setColour(juce::AlertWindow::backgroundColourId, Colours::darker); // combo box setColour(juce::ComboBox::backgroundColourId, Colours::veryDark); @@ -79,13 +81,15 @@ OscirenderLookAndFeel::OscirenderLookAndFeel() { setColour(juce::MidiKeyboardComponent::shadowColourId, juce::Colours::transparentBlack); setColour(juce::MidiKeyboardComponent::upDownButtonBackgroundColourId, Colours::veryDark); setColour(juce::MidiKeyboardComponent::upDownButtonArrowColourId, juce::Colours::white); + + // progress bar + setColour(juce::ProgressBar::backgroundColourId, juce::Colours::transparentBlack); + setColour(juce::ProgressBar::foregroundColourId, Colours::accentColor); // UI colours getCurrentColourScheme().setUIColour(ColourScheme::widgetBackground, Colours::veryDark); getCurrentColourScheme().setUIColour(ColourScheme::UIColour::defaultFill, Colours::accentColor); - setDefaultSansSerifTypeface(juce::Typeface::createSystemTypefaceFor(BinaryData::font_ttf, BinaryData::font_ttfSize)); - // I have to do this, otherwise components are initialised before the look and feel is set juce::LookAndFeel::setDefaultLookAndFeel(this); } @@ -372,3 +376,88 @@ void OscirenderLookAndFeel::drawCallOutBoxBackground(juce::CallOutBox& box, juce g.setColour(juce::Colours::black); g.strokePath(path, juce::PathStrokeType(1.0f)); } + +void OscirenderLookAndFeel::drawProgressBar(juce::Graphics& g, juce::ProgressBar& progressBar, int width, int height, double progress, const juce::String& textToShow) { + switch (progressBar.getResolvedStyle()) { + case juce::ProgressBar::Style::linear: + customDrawLinearProgressBar(g, progressBar, width, height, progress, textToShow); + break; + case juce::ProgressBar::Style::circular: + juce::LookAndFeel_V4::drawProgressBar(g, progressBar, width, height, progress, textToShow); + break; + } +} + +void OscirenderLookAndFeel::customDrawLinearProgressBar(juce::Graphics& g, const juce::ProgressBar& progressBar, int width, int height, double progress, const juce::String& textToShow) { + auto background = progressBar.findColour(juce::ProgressBar::backgroundColourId); + auto foreground = progressBar.findColour(juce::ProgressBar::foregroundColourId).withAlpha(0.5f); + int rectRadius = 2; + + auto barBounds = progressBar.getLocalBounds().toFloat(); + + g.setColour(background); + g.fillRoundedRectangle(barBounds, rectRadius); + + juce::String text = textToShow.isEmpty() ? "waiting..." : textToShow; + + if (progress >= 0.0f && progress <= 1.0f) { + juce::Path p; + p.addRoundedRectangle(barBounds, rectRadius); + g.reduceClipRegion(p); + + barBounds.setWidth(barBounds.getWidth() * (float) progress); + g.setColour(foreground); + g.fillRoundedRectangle(barBounds, rectRadius); + } else { + if (progress == -2) { + background = juce::Colours::red; + text = "Error"; + } + + // spinning bar.. + g.setColour(background); + + auto stripeWidth = height * 2; + auto position = static_cast(juce::Time::getMillisecondCounter() / 15) % stripeWidth; + + juce::Path p; + + for (auto x = static_cast (-position); x < (float) (width + stripeWidth); x += (float) stripeWidth) { + p.addQuadrilateral (x, 0.0f, + x + (float) stripeWidth * 0.5f, 0.0f, + x, static_cast (height), + x - (float) stripeWidth * 0.5f, static_cast (height)); + } + + juce::Image im(juce::Image::ARGB, width, height, true); + + { + juce::Graphics g2(im); + g2.setColour(foreground); + g2.fillRoundedRectangle(barBounds, rectRadius); + } + + g.setTiledImageFill(im, 0, 0, 0.85f); + g.fillPath(p); + } + + g.setColour(juce::Colours::white); + juce::Font font = juce::Font(juce::FontOptions((float) height * 0.9f, juce::Font::bold)); + g.setFont(font); + + g.drawText(text, 0, 0, width, height, juce::Justification::centred, false); +} + +juce::Typeface::Ptr OscirenderLookAndFeel::getTypefaceForFont(const juce::Font& font) { + if (font.getTypefaceName() == juce::Font::getDefaultSansSerifFontName()) { + if (font.getTypefaceStyle() == "Regular") { + return regularTypeface; + } else if (font.getTypefaceStyle() == "Bold") { + return boldTypeface; + } else if (font.getTypefaceStyle() == "Italic") { + return italicTypeface; + } + } + + return juce::Font::getDefaultTypefaceForFont(font); +} diff --git a/Source/LookAndFeel.h b/Source/LookAndFeel.h index 4841060..1ad6754 100644 --- a/Source/LookAndFeel.h +++ b/Source/LookAndFeel.h @@ -66,6 +66,9 @@ public: OscirenderLookAndFeel(); static const int RECT_RADIUS = 5; + juce::Typeface::Ptr regularTypeface = juce::Typeface::createSystemTypefaceFor(BinaryData::FiraSansRegular_ttf, BinaryData::FiraSansRegular_ttfSize); + juce::Typeface::Ptr boldTypeface = juce::Typeface::createSystemTypefaceFor(BinaryData::FiraSansBold_ttf, BinaryData::FiraSansBold_ttfSize); + juce::Typeface::Ptr italicTypeface = juce::Typeface::createSystemTypefaceFor(BinaryData::FiraSansItalic_ttf, BinaryData::FiraSansItalic_ttfSize); void drawLabel(juce::Graphics& g, juce::Label& label) override; void fillTextEditorBackground(juce::Graphics& g, int width, int height, juce::TextEditor& textEditor) override; @@ -97,4 +100,7 @@ public: void drawToggleButton(juce::Graphics&, juce::ToggleButton&, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override; juce::MouseCursor getMouseCursorFor(juce::Component& component) override; void drawCallOutBoxBackground(juce::CallOutBox& box, juce::Graphics& g, const juce::Path& path, juce::Image& cachedImage) override; + void drawProgressBar(juce::Graphics& g, juce::ProgressBar& progressBar, int width, int height, double progress, const juce::String& textToShow) override; + void customDrawLinearProgressBar(juce::Graphics& g, const juce::ProgressBar& progressBar, int width, int height, double progress, const juce::String& textToShow); + juce::Typeface::Ptr getTypefaceForFont(const juce::Font& font) override; }; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 7d71f29..7685c33 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -131,6 +131,9 @@ OscirenderAudioProcessorEditor::OscirenderAudioProcessorEditor(OscirenderAudioPr } OscirenderAudioProcessorEditor::~OscirenderAudioProcessorEditor() { + if (audioProcessor.haltRecording != nullptr) { + audioProcessor.haltRecording(); + } setLookAndFeel(nullptr); juce::Desktop::getInstance().setDefaultLookAndFeel(nullptr); juce::MessageManagerLock lock; diff --git a/Source/SosciPluginEditor.cpp b/Source/SosciPluginEditor.cpp index 4d5f617..7525b19 100644 --- a/Source/SosciPluginEditor.cpp +++ b/Source/SosciPluginEditor.cpp @@ -67,6 +67,9 @@ SosciPluginEditor::SosciPluginEditor(SosciAudioProcessor& p) } SosciPluginEditor::~SosciPluginEditor() { + if (audioProcessor.haltRecording != nullptr) { + audioProcessor.haltRecording(); + } setLookAndFeel(nullptr); juce::Desktop::getInstance().setDefaultLookAndFeel(nullptr); } diff --git a/Source/components/DownloaderComponent.cpp b/Source/components/DownloaderComponent.cpp index 3d6f132..c0db25c 100644 --- a/Source/components/DownloaderComponent.cpp +++ b/Source/components/DownloaderComponent.cpp @@ -1,6 +1,11 @@ #include "DownloaderComponent.h" -DownloaderComponent::DownloaderComponent(juce::URL url, juce::File file, juce::String title, juce::Component* parent) : juce::ThreadWithProgressWindow(title, true, true, 1000, juce::String(), parent), url(url), file(file) { +DownloaderComponent::DownloaderComponent(juce::URL url, juce::File file) : juce::Thread("DownloaderComponent"), url(url), file(file) { + addChildComponent(progressBar); + addChildComponent(successLabel); + + successLabel.setText(file.getFileName() + " downloaded!", juce::dontSendNotification); + if (url.toString(false).endsWithIgnoreCase(".gz")) { uncompressOnFinish = true; this->file = file.getSiblingFile(file.getFileName() + ".gz"); @@ -15,26 +20,52 @@ void DownloaderComponent::run() { juce::CriticalSection::ScopedTryLockType lock(taskLock); if (lock.isLocked() && task != nullptr) { if (task->isFinished()) { - return; + break; } } } wait(100); } + threadComplete(); } -void DownloaderComponent::threadComplete(bool userPressedCancel) { - if (!userPressedCancel && uncompressOnFinish && task->isFinished() && !task->hadError()) { +void DownloaderComponent::threadComplete() { + bool error = task->hadError(); + if (uncompressOnFinish && task->isFinished() && !error) { juce::FileInputStream input(file); juce::GZIPDecompressorInputStream decompressedInput(&input, false, juce::GZIPDecompressorInputStream::gzipFormat); juce::File uncompressedFile = file.getSiblingFile(file.getFileNameWithoutExtension()); juce::FileOutputStream output(uncompressedFile); if (output.writeFromInputStream(decompressedInput, -1) < 1) { uncompressedFile.deleteFile(); + error = true; } else { uncompressedFile.setExecutePermission(true); } } + + if (error) { + file.deleteFile(); + progressValue = -2; + } else { + progressValue = 1; + if (onSuccessfulDownload != nullptr) { + onSuccessfulDownload(); + } + juce::MessageManager::callAsync([this]() { + progressBar.setVisible(false); + successLabel.setVisible(true); + }); + + juce::Timer::callAfterDelay(3000, [this]() { + successLabel.setVisible(false); + }); + } +} + +void DownloaderComponent::resized() { + progressBar.setBounds(getLocalBounds()); + successLabel.setBounds(getLocalBounds()); } void DownloaderComponent::finished(juce::URL::DownloadTask* task, bool success) { @@ -42,9 +73,16 @@ void DownloaderComponent::finished(juce::URL::DownloadTask* task, bool success) } void DownloaderComponent::progress(juce::URL::DownloadTask* task, juce::int64 bytesDownloaded, juce::int64 totalLength) { - setProgress((float)bytesDownloaded / (float)totalLength); + if (uncompressOnFinish) { + progressValue = ((double) bytesDownloaded / (double) totalLength) * 0.9; + } else { + progressValue = (double) bytesDownloaded / (double) totalLength; + } } void DownloaderComponent::download() { - launchThread(); + progressValue = -1; + successLabel.setVisible(false); + progressBar.setVisible(true); + startThread(); } diff --git a/Source/components/DownloaderComponent.h b/Source/components/DownloaderComponent.h index 8ecfc95..59ac57c 100644 --- a/Source/components/DownloaderComponent.h +++ b/Source/components/DownloaderComponent.h @@ -19,20 +19,26 @@ private: std::unique_ptr& task; }; -class DownloaderComponent : public juce::ThreadWithProgressWindow, public juce::URL::DownloadTaskListener { +class DownloaderComponent : public juce::Component, public juce::Thread, public juce::URL::DownloadTaskListener { public: - DownloaderComponent(juce::URL url, juce::File file, juce::String title, juce::Component* parent); + DownloaderComponent(juce::URL url, juce::File file); void download(); void run() override; - void threadComplete(bool userPressedCancel) override; + void threadComplete(); + void resized() override; void finished(juce::URL::DownloadTask* task, bool success) override; void progress(juce::URL::DownloadTask* task, juce::int64 bytesDownloaded, juce::int64 totalLength) override; + + std::function onSuccessfulDownload; private: juce::URL url; juce::File file; + double progressValue = -1; + juce::ProgressBar progressBar = juce::ProgressBar(progressValue); + juce::Label successLabel; juce::CriticalSection taskLock; std::unique_ptr task; std::unique_ptr downloader; diff --git a/Source/concurrency/AudioBackgroundThread.cpp b/Source/concurrency/AudioBackgroundThread.cpp index 70ab2ba..b8897d2 100644 --- a/Source/concurrency/AudioBackgroundThread.cpp +++ b/Source/concurrency/AudioBackgroundThread.cpp @@ -9,6 +9,7 @@ AudioBackgroundThread::AudioBackgroundThread(const juce::String& name, AudioBack } AudioBackgroundThread::~AudioBackgroundThread() { + deleting = true; setShouldBeRunning(false); manager.unregisterThread(this); } @@ -69,7 +70,9 @@ void AudioBackgroundThread::start() { } void AudioBackgroundThread::stop() { - stopTask(); + if (!deleting) { + stopTask(); + } consumer->forceNotify(); stopThread(1000); } diff --git a/Source/concurrency/AudioBackgroundThread.h b/Source/concurrency/AudioBackgroundThread.h index afca324..5c8e66a 100644 --- a/Source/concurrency/AudioBackgroundThread.h +++ b/Source/concurrency/AudioBackgroundThread.h @@ -25,6 +25,7 @@ private: std::unique_ptr consumer = nullptr; bool shouldBeRunning = false; std::atomic isPrepared = false; + std::atomic deleting = false; protected: diff --git a/Source/visualiser/VisualiserComponent.cpp b/Source/visualiser/VisualiserComponent.cpp index 6ad00c1..0e8ee60 100644 --- a/Source/visualiser/VisualiserComponent.cpp +++ b/Source/visualiser/VisualiserComponent.cpp @@ -12,7 +12,14 @@ #include "TexturedVertexShader.glsl" VisualiserComponent::VisualiserComponent(juce::File ffmpegFile, std::function& haltRecording, AudioBackgroundThreadManager& threadManager, VisualiserSettings& settings, VisualiserComponent* parent, bool visualiserOnly) : ffmpegFile(ffmpegFile), haltRecording(haltRecording), settings(settings), threadManager(threadManager), visualiserOnly(visualiserOnly), AudioBackgroundThread("VisualiserComponent" + juce::String(parent != nullptr ? " Child" : ""), threadManager), parent(parent) { - + addAndMakeVisible(ffmpegDownloader); + + ffmpegDownloader.onSuccessfulDownload = [this] { + juce::MessageManager::callAsync([this] { + record.setEnabled(true); + }); + }; + haltRecording = [this] { setRecording(false); }; @@ -21,14 +28,6 @@ VisualiserComponent::VisualiserComponent(juce::File ffmpegFile, std::functionsettings.setLookAndFeel(&getLookAndFeel()); visualiser->openSettings = openSettings; @@ -247,7 +268,8 @@ void VisualiserComponent::popoutWindow() { void VisualiserComponent::childUpdated() { popOutButton.setVisible(child == nullptr); - record.setEnabled(child == nullptr); + ffmpegDownloader.setVisible(child == nullptr); + record.setVisible(child == nullptr); if (child != nullptr) { haltRecording = [this] { setRecording(false); @@ -826,6 +848,10 @@ void VisualiserComponent::paint(juce::Graphics& g) { g.setColour(juce::Colours::black); g.fillRect(buttonRow); if (!active) { + // draw a translucent overlay + g.setColour(juce::Colours::black.withAlpha(0.5f)); + g.fillRect(viewportArea); + g.setColour(juce::Colours::white); g.setFont(30.0f); juce::String text = child == nullptr ? "Paused" : "Open in another window"; diff --git a/Source/visualiser/VisualiserComponent.h b/Source/visualiser/VisualiserComponent.h index c98d5e0..d66ae8c 100644 --- a/Source/visualiser/VisualiserComponent.h +++ b/Source/visualiser/VisualiserComponent.h @@ -112,7 +112,7 @@ private: #endif #endif + ".gz"; - DownloaderComponent ffmpegDownloader{ffmpegURL, ffmpegFile, "Downloading ffmpeg...", this}; + DownloaderComponent ffmpegDownloader{ffmpegURL, ffmpegFile}; Semaphore renderingSemaphore{0}; diff --git a/osci-render.jucer b/osci-render.jucer index 4d84ee0..d13a044 100644 --- a/osci-render.jucer +++ b/osci-render.jucer @@ -10,7 +10,12 @@ - + + + @@ -137,6 +142,10 @@ file="Source/components/ComponentList.cpp"/> + + - + + +