commit e5f823df3f198e2aaee10bfff12166d84a349ce1
parent d5bacbaec3d93560108f502c4729e12a7add70aa
Author: dsp56300 <dsp56300@users.noreply.github.com>
Date: Sun, 20 Mar 2022 19:04:04 +0100
add ability to load skins from disk
Diffstat:
6 files changed, 168 insertions(+), 25 deletions(-)
diff --git a/source/jucePlugin/PluginEditor.cpp b/source/jucePlugin/PluginEditor.cpp
@@ -6,27 +6,39 @@
#include "ui2/VirusEditor.h"
#include "ui3/VirusEditor.h"
+#include "../synthLib/os.h"
+
//==============================================================================
AudioPluginAudioProcessorEditor::AudioPluginAudioProcessorEditor(AudioPluginAudioProcessor &p) :
AudioProcessorEditor(&p), processorRef(p), m_parameterBinding(p)
{
+ m_includedSkins.push_back({"Hoverland", "VirusC_Hoverland.json", ""});
+ m_includedSkins.push_back({"Trancy", "VirusC_Trancy.json", ""});
+
addMouseListener(this, true);
const auto config = processorRef.getController().getConfig();
const auto scale = config->getIntValue("scale", 100);
- const int skinId = config->getIntValue("skin", 0);
+
+ Skin skin = readSkinFromConfig();
+
+ if(skin.jsonFilename.empty())
+ {
+ skin = m_includedSkins[0];
+ }
- loadSkin(skinId);
+ loadSkin(skin);
setGuiScale(scale);
}
-void AudioPluginAudioProcessorEditor::loadSkin(int index)
+void AudioPluginAudioProcessorEditor::loadSkin(const Skin& _skin)
{
- if(m_currentSkinId == index)
+ if(m_currentSkin == _skin)
return;
- m_currentSkinId = index;
+ m_currentSkin = _skin;
+ writeSkinToConfig(_skin);
if (m_virusEditor)
{
@@ -41,26 +53,24 @@ void AudioPluginAudioProcessorEditor::loadSkin(int index)
try
{
- auto* editor = new genericVirusUI::VirusEditor(m_parameterBinding, processorRef.getController(), processorRef, "VirusC_Trancy.json", [this] { openMenu(); });
+ auto* editor = new genericVirusUI::VirusEditor(m_parameterBinding, processorRef, _skin.jsonFilename, _skin.folder, [this] { openMenu(); });
m_virusEditor.reset(editor);
setSize(m_virusEditor->getWidth(), m_virusEditor->getHeight());
m_rootScale = editor->getScale();
+
+ m_virusEditor->setTopLeftPosition(0, 0);
+ addAndMakeVisible(m_virusEditor.get());
}
catch(const std::runtime_error& _err)
{
LOG("ERROR: Failed to create editor: " << _err.what());
- auto* errorLabel = new juce::Label();
- errorLabel->setText(juce::String("Failed to load editor\n\n") + _err.what(), juce::dontSendNotification);
- errorLabel->setFont(juce::Font(juce::Font::getDefaultSansSerifFontName(), 36, 0));
- errorLabel->setJustificationType(juce::Justification::centred);
- errorLabel->setSize(400, 300);
+ juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::WarningIcon, "Skin load failed", _err.what(), "OK");
+ m_virusEditor.reset();
- m_virusEditor.reset(errorLabel);
+ if(!_skin.folder.empty())
+ loadSkin(m_includedSkins[0]);
}
-
- m_virusEditor->setTopLeftPosition(0, 0);
- addAndMakeVisible(m_virusEditor.get());
}
void AudioPluginAudioProcessorEditor::setGuiScale(int percent)
@@ -80,10 +90,50 @@ void AudioPluginAudioProcessorEditor::openMenu()
juce::PopupMenu menu;
juce::PopupMenu skinMenu;
- skinMenu.addItem("Modern", true, skinId == 0,[this] {loadSkin(0);});
- skinMenu.addItem("Classic", true, skinId == 1,[this] {loadSkin(1);});
- if(m_virusEditor)
+ auto addSkinEntry = [this, &skinMenu](const Skin& _skin)
+ {
+ skinMenu.addItem(_skin.displayName, true, _skin == m_currentSkin,[this, _skin] {loadSkin(_skin);});
+ };
+
+ for (const auto & skin : m_includedSkins)
+ addSkinEntry(skin);
+
+ bool haveSkinsOnDisk = false;
+
+ // find more skins on disk
+ const auto modulePath = synthLib::getModulePath();
+
+ std::vector<std::string> entries;
+ synthLib::getDirectoryEntries(entries, modulePath + "skins");
+
+ for (const auto& entry : entries)
+ {
+ std::vector<std::string> files;
+ synthLib::getDirectoryEntries(files, entry);
+
+ for (const auto& file : files)
+ {
+ if(synthLib::hasExtension(file, ".json"))
+ {
+ if(!haveSkinsOnDisk)
+ {
+ haveSkinsOnDisk = true;
+ skinMenu.addSeparator();
+ }
+
+ const auto relativePath = entry.substr(modulePath.size());
+ auto jsonName = file;
+ const auto pathEndPos = jsonName.find_last_of("/\\");
+ if(pathEndPos != std::string::npos)
+ jsonName = file.substr(pathEndPos+1);
+ const Skin skin{jsonName + " (" + relativePath + ")", jsonName, relativePath};
+ addSkinEntry(skin);
+ }
+ }
+ }
+
+ if(m_virusEditor && m_currentSkin.folder.empty())
{
auto* editor = dynamic_cast<genericVirusUI::VirusEditor*>(m_virusEditor.get());
if(editor)
@@ -130,6 +180,26 @@ void AudioPluginAudioProcessorEditor::exportCurrentSkin() const
}
}
+AudioPluginAudioProcessorEditor::Skin AudioPluginAudioProcessorEditor::readSkinFromConfig() const
+{
+ const auto* config = processorRef.getController().getConfig();
+
+ Skin skin;
+ skin.displayName = config->getValue("skinDisplayName", "").toStdString();
+ skin.jsonFilename = config->getValue("skinFile", "").toStdString();
+ skin.folder = config->getValue("skinFolder", "").toStdString();
+ return skin;
+}
+
+void AudioPluginAudioProcessorEditor::writeSkinToConfig(const Skin& _skin) const
+{
+ auto* config = processorRef.getController().getConfig();
+
+ config->setValue("skinDisplayName", _skin.displayName.c_str());
+ config->setValue("skinFile", _skin.jsonFilename.c_str());
+ config->setValue("skinFolder", _skin.folder.c_str());
+}
+
AudioPluginAudioProcessorEditor::~AudioPluginAudioProcessorEditor()
{
m_virusEditor.reset();
diff --git a/source/jucePlugin/PluginEditor.h b/source/jucePlugin/PluginEditor.h
@@ -16,11 +16,25 @@ public:
void mouseDown(const juce::MouseEvent& event) override;
private:
+ struct Skin
+ {
+ std::string displayName;
+ std::string jsonFilename;
+ std::string folder;
+
+ bool operator == (const Skin& _other) const
+ {
+ return displayName == _other.displayName && jsonFilename == _other.jsonFilename && folder == _other.folder;
+ }
+ };
+
void timerCallback() override;
- void loadSkin(int index);
+ void loadSkin(const Skin& _skin);
void setGuiScale(int percent);
void openMenu();
void exportCurrentSkin() const;
+ Skin readSkinFromConfig() const;
+ void writeSkinToConfig(const Skin& _skin) const;
// This reference is provided as a quick way for your editor to
// access the processor object that created it.
@@ -29,8 +43,10 @@ private:
VirusParameterBinding m_parameterBinding;
std::unique_ptr<juce::Component> m_virusEditor;
- int m_currentSkinId = -1;
+ Skin m_currentSkin;
float m_rootScale = 1.0f;
+ std::vector<Skin> m_includedSkins;
+
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AudioPluginAudioProcessorEditor)
};
diff --git a/source/jucePlugin/ui3/VirusEditor.cpp b/source/jucePlugin/ui3/VirusEditor.cpp
@@ -7,12 +7,15 @@
#include "../VirusParameterBinding.h"
#include "../version.h"
+#include "../synthLib/os.h"
+
namespace genericVirusUI
{
- VirusEditor::VirusEditor(VirusParameterBinding& _binding, Virus::Controller& _controller, AudioPluginAudioProcessor &_processorRef, const std::string& _jsonFilename, std::function<void()> _openMenuCallback) :
+ VirusEditor::VirusEditor(VirusParameterBinding& _binding, AudioPluginAudioProcessor &_processorRef, const std::string& _jsonFilename, const std::string& _skinFolder, std::function<void()> _openMenuCallback) :
Editor(static_cast<EditorInterface&>(*this)),
m_processor(_processorRef),
m_parameterBinding(_binding),
+ m_skinFolder(_skinFolder),
m_openMenuCallback(std::move(_openMenuCallback))
{
create(_jsonFilename);
@@ -144,6 +147,51 @@ namespace genericVirusUI
const char* VirusEditor::getResourceByFilename(const std::string& _name, uint32_t& _dataSize)
{
+ if(!m_skinFolder.empty())
+ {
+ auto readFromCache = [this, &_name, &_dataSize]()
+ {
+ const auto it = m_fileCache.find(_name);
+ if(it == m_fileCache.end())
+ {
+ _dataSize = 0;
+ return static_cast<char*>(nullptr);
+ }
+ _dataSize = static_cast<uint32_t>(it->second.size());
+ return &it->second.front();
+ };
+
+ auto* res = readFromCache();
+
+ if(res)
+ return res;
+
+ const auto modulePath = synthLib::getModulePath();
+ const auto folder = m_skinFolder.find(modulePath) == 0 ? m_skinFolder : modulePath + m_skinFolder;
+
+ // try to load from disk first
+ FILE* hFile = fopen((folder + _name).c_str(), "rb");
+ if(hFile)
+ {
+ fseek(hFile, 0, SEEK_END);
+ _dataSize = ftell(hFile);
+ fseek(hFile, 0, SEEK_SET);
+
+ std::vector<char> data;
+ data.resize(_dataSize);
+ const auto readCount = fread(&data.front(), 1, _dataSize, hFile);
+ fclose(hFile);
+
+ if(readCount == _dataSize)
+ m_fileCache.insert(std::make_pair(_name, std::move(data)));
+
+ res = readFromCache();
+
+ if(res)
+ return res;
+ }
+ }
+
for(size_t i=0; i<BinaryData::namedResourceListSize; ++i)
{
if (BinaryData::originalFilenames[i] != _name)
diff --git a/source/jucePlugin/ui3/VirusEditor.h b/source/jucePlugin/ui3/VirusEditor.h
@@ -16,7 +16,7 @@ namespace genericVirusUI
class VirusEditor : public genericUI::EditorInterface, public genericUI::Editor
{
public:
- VirusEditor(VirusParameterBinding& _binding, Virus::Controller& _controller, AudioPluginAudioProcessor &_processorRef, const std::string& _jsonFilename, std::function<void()> _openMenuCallback);
+ VirusEditor(VirusParameterBinding& _binding, AudioPluginAudioProcessor &_processorRef, const std::string& _jsonFilename, const std::string& _skinFolder, std::function<void()> _openMenuCallback);
~VirusEditor() override;
void setPart(size_t _part);
@@ -56,6 +56,8 @@ namespace genericVirusUI
AudioPluginAudioProcessor& m_processor;
VirusParameterBinding& m_parameterBinding;
+ const std::string m_skinFolder;
+
std::unique_ptr<Parts> m_parts;
std::unique_ptr<Tabs> m_tabs;
std::unique_ptr<MidiPorts> m_midiPorts;
@@ -77,5 +79,7 @@ namespace genericVirusUI
std::unique_ptr<juce::FileChooser> m_fileChooser;
juce::String m_previousPath;
std::function<void()> m_openMenuCallback;
+
+ std::map<std::string, std::vector<char>> m_fileCache;
};
}
diff --git a/source/synthLib/os.cpp b/source/synthLib/os.cpp
@@ -179,9 +179,7 @@ namespace synthLib
for (const auto& file : files)
{
- const std::string ext = lowercase(getExtension(file));
-
- if (ext != ".bin")
+ if(!hasExtension(file, ".bin"))
continue;
if (!_minSize && !_maxSize)
@@ -215,6 +213,11 @@ namespace synthLib
return findROM(_expectedSize, _expectedSize);
}
+ bool hasExtension(const std::string& _filename, const std::string& _extension)
+ {
+ return lowercase(getExtension(_filename)) == lowercase(_extension);
+ }
+
void setFlushDenormalsToZero()
{
#if defined(_MSC_VER)
diff --git a/source/synthLib/os.h b/source/synthLib/os.h
@@ -14,5 +14,7 @@ namespace synthLib
std::string findROM(size_t _minSize, size_t _maxSize);
std::string findROM(size_t _expectedSize = 524288);
+ bool hasExtension(const std::string& _filename, const std::string& _extension);
+
void setFlushDenormalsToZero();
} // namespace synthLib