Initial commit

This commit is contained in:
Rolando Islas
2015-12-30 04:35:33 -07:00
commit de54174241
14 changed files with 1106 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
#include "../JuceLibraryCode/JuceHeader.h"
#include "IconMenu.hpp"
#if ! (JUCE_PLUGINHOST_VST || JUCE_PLUGINHOST_VST3 || JUCE_PLUGINHOST_AU)
#error "If you're building the audio plugin host, you probably want to enable VST and/or AU support"
#endif
class PluginHostApp : public JUCEApplication
{
public:
PluginHostApp() {}
void initialise (const String&) override
{
PropertiesFile::Options options;
options.applicationName = getApplicationName();
options.filenameSuffix = "settings";
options.osxLibrarySubFolder = "Preferences";
appProperties = new ApplicationProperties();
appProperties->setStorageParameters (options);
LookAndFeel::setDefaultLookAndFeel (&lookAndFeel);
mainWindow = new IconMenu();
Process::setDockIconVisible(false);
File fileToOpen;
for (int i = 0; i < getCommandLineParameterArray().size(); ++i)
{
fileToOpen = File::getCurrentWorkingDirectory().getChildFile (getCommandLineParameterArray()[i]);
if (fileToOpen.existsAsFile())
break;
}
}
void shutdown() override
{
mainWindow = nullptr;
appProperties = nullptr;
LookAndFeel::setDefaultLookAndFeel (nullptr);
}
void systemRequestedQuit() override
{
JUCEApplicationBase::quit();
}
const String getApplicationName() override { return "Light Host"; }
const String getApplicationVersion() override { return ProjectInfo::versionString; }
bool moreThanOneInstanceAllowed() override { return false; }
ApplicationCommandManager commandManager;
ScopedPointer<ApplicationProperties> appProperties;
LookAndFeel_V3 lookAndFeel;
private:
ScopedPointer<IconMenu> mainWindow;
};
static PluginHostApp& getApp() { return *dynamic_cast<PluginHostApp*>(JUCEApplication::getInstance()); }
ApplicationCommandManager& getCommandManager() { return getApp().commandManager; }
ApplicationProperties& getAppProperties() { return *getApp().appProperties; }
START_JUCE_APPLICATION (PluginHostApp)
+284
View File
@@ -0,0 +1,284 @@
//
// IconMenu.cpp
// Light Host
//
// Created by Rolando Islas on 12/26/15.
//
//
#include "../JuceLibraryCode/JuceHeader.h"
#include "IconMenu.hpp"
#include "PluginWindow.h"
IconMenu::IconMenu()
{
// Initiialization
formatManager.addDefaultFormats();
// Audio device
ScopedPointer<XmlElement> savedAudioState (getAppProperties().getUserSettings()->getXmlValue("audioDeviceState"));
deviceManager.initialise(256, 256, savedAudioState, true);
player.setProcessor(&graph);
deviceManager.addAudioCallback(&player);
// Plugins - all
ScopedPointer<XmlElement> savedPluginList(getAppProperties().getUserSettings()->getXmlValue("pluginList"));
if (savedPluginList != nullptr)
knownPluginList.recreateFromXml(*savedPluginList);
pluginSortMethod = KnownPluginList::sortByManufacturer;
knownPluginList.addChangeListener(this);
// Plugins - active
ScopedPointer<XmlElement> savedPluginListActive(getAppProperties().getUserSettings()->getXmlValue("pluginListActive"));
if (savedPluginListActive != nullptr)
activePluginList.recreateFromXml(*savedPluginListActive);
loadActivePlugins();
activePluginList.addChangeListener(this);
// Set menu icon
if (exec("defaults read -g AppleInterfaceStyle").compare("Dark") == 1)
setIconImage(ImageFileFormat::loadFrom(BinaryData::menu_icon_white_png, BinaryData::menu_icon_white_pngSize));
else
setIconImage(ImageFileFormat::loadFrom(BinaryData::menu_icon_png, BinaryData::menu_icon_pngSize));
};
IconMenu::~IconMenu()
{
}
void IconMenu::loadActivePlugins()
{
graph.clear();
inputNode = graph.addNode(new AudioProcessorGraph::AudioGraphIOProcessor(AudioProcessorGraph::AudioGraphIOProcessor::audioInputNode), 1);
outputNode = graph.addNode(new AudioProcessorGraph::AudioGraphIOProcessor(AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode), 2);
if (activePluginList.getNumTypes() == 0)
{
graph.addConnection(1, 0, 2, 0);
graph.addConnection(1, 1, 2, 1);
}
for (int i = 0; i < activePluginList.getNumTypes(); i++)
{
PluginDescription plugin = *activePluginList.getType(i);
String errorMessage;
AudioPluginInstance* instance = formatManager.createPluginInstance(plugin, graph.getSampleRate(), graph.getBlockSize(), errorMessage);
String pluginUid = "pluginState-" + std::to_string(i);
String savedPluginState = getAppProperties().getUserSettings()->getValue(pluginUid);
MemoryBlock savedPluginBinary;
savedPluginBinary.fromBase64Encoding(savedPluginState);
instance->setStateInformation(savedPluginBinary.getData(), savedPluginBinary.getSize());
graph.addNode(instance, i+3);
// Input to plugin
if (i == 0)
{
graph.addConnection(1, 0, i+3, 0);
graph.addConnection(1, 1, i+3, 1);
}
// Plugin to output
if (i == activePluginList.getNumTypes() - 1)
{
graph.addConnection(i+3, 0, 2, 0);
graph.addConnection(i+3, 1, 2, 1);
}
// Connect previous plugin to current
if (i > 0)
{
graph.addConnection(i+2, 0, i+3, 0);
graph.addConnection(i+2, 1, i+3, 1);
}
}
}
void IconMenu::changeListenerCallback(ChangeBroadcaster* changed)
{
if (changed == &knownPluginList)
{
ScopedPointer<XmlElement> savedPluginList (knownPluginList.createXml());
if (savedPluginList != nullptr)
{
getAppProperties().getUserSettings()->setValue ("pluginList", savedPluginList);
getAppProperties().saveIfNeeded();
}
}
else if (changed == &activePluginList)
{
ScopedPointer<XmlElement> savedPluginList (activePluginList.createXml());
if (savedPluginList != nullptr)
{
getAppProperties().getUserSettings()->setValue ("pluginListActive", savedPluginList);
getAppProperties().saveIfNeeded();
loadActivePlugins();
}
}
}
std::string IconMenu::exec(const char* cmd)
{
std::shared_ptr<FILE> pipe(popen(cmd, "r"), pclose);
if (!pipe) return "ERROR";
char buffer[128];
std::string result = "";
while (!feof(pipe.get()))
{
if (fgets(buffer, 128, pipe.get()) != NULL)
result += buffer;
}
return result;
}
void IconMenu::timerCallback()
{
stopTimer();
menu.clear();
menu.addSectionHeader(JUCEApplication::getInstance()->getApplicationName());
if (menuIconLeftClicked) {
menu.addItem(1, "Preferences");
menu.addItem(2, "Reload Plugins");
menu.addSeparator();
// Active plugins
for (int i = 0; i < activePluginList.getNumTypes(); i++)
{
PopupMenu options;
options.addItem(i+3, "Edit");
options.addItem(activePluginList.getNumTypes()+i+3, "Delete");
// TODO bypass
menu.addSubMenu(activePluginList.getType(i)->name, options);
}
menu.addSeparator();
// All plugins
knownPluginList.addToMenu(menu, pluginSortMethod);
}
else
{
menu.addItem(1, "Quit");
}
menu.showMenuAsync(PopupMenu::Options().withTargetComponent(this), ModalCallbackFunction::forComponent(menuInvocationCallback, this));
}
void IconMenu::mouseDown(const MouseEvent& e)
{
Process::setDockIconVisible(true);
Process::makeForegroundProcess();
menuIconLeftClicked = e.mods.isLeftButtonDown();
startTimer(50);
}
void IconMenu::menuInvocationCallback(int id, IconMenu* im)
{
// Right click
if ((!im->menuIconLeftClicked) && id == 1)
{
im->savePluginStates();
return JUCEApplication::getInstance()->quit();
}
// Click elsewhere
if (id == 0 && !PluginWindow::containsActiveWindows())
Process::setDockIconVisible(false);
// Audio settings
if (id == 1)
im->showAudioSettings();
// Reload
if (id == 2)
im->reloadPlugins();
// Plugins
if (id > 2)
{
// Delete plugin
if (id > im->activePluginList.getNumTypes() + 2 && id <= im->activePluginList.getNumTypes() * 2 + 2)
{
im->deletePluginStates();
im->activePluginList.removeType(id - im->activePluginList.getNumTypes() - 3);
}
// Add plugin
else if (id > im->activePluginList.getNumTypes() + 2)
{
im->deletePluginStates();
im->activePluginList.addType(*im->knownPluginList.getType(im->knownPluginList.getIndexChosenByMenu(id)));
}
// Show active plugin GUI
else
{
if (const AudioProcessorGraph::Node::Ptr f = im->graph.getNodeForId(id))
if (PluginWindow* const w = PluginWindow::getWindowFor(f, PluginWindow::Normal))
w->toFront(true);
}
// Update menu
im->startTimer(50);
}
}
void IconMenu::deletePluginStates()
{
for (int i = 0; i < activePluginList.getNumTypes(); i++)
{
String pluginUid = "pluginState-" + std::to_string(i);
getAppProperties().getUserSettings()->removeValue(pluginUid);
getAppProperties().saveIfNeeded();
}
}
void IconMenu::savePluginStates()
{
for (int i = 0; i < activePluginList.getNumTypes(); i++)
{
AudioProcessor& processor = *graph.getNodeForId(i+3)->getProcessor();
String pluginUid = "pluginState-" + std::to_string(i);
MemoryBlock savedStateBinary;
processor.getStateInformation(savedStateBinary);
ScopedPointer<XmlElement> savedStateXml(XmlElement::createTextElement(savedStateBinary.toBase64Encoding()));
getAppProperties().getUserSettings()->setValue(pluginUid, savedStateBinary.toBase64Encoding());
getAppProperties().saveIfNeeded();
}
}
void IconMenu::showAudioSettings()
{
AudioDeviceSelectorComponent audioSettingsComp (deviceManager, 0, 256, 0, 256, false, false, true, true);
audioSettingsComp.setSize(500, 450);
DialogWindow::LaunchOptions o;
o.content.setNonOwned(&audioSettingsComp);
o.dialogTitle = "Audio Settings";
o.componentToCentreAround = this;
o.dialogBackgroundColour = Colour::fromRGB(236, 236, 236);
o.escapeKeyTriggersCloseButton = true;
o.useNativeTitleBar = true;
o.resizable = false;
o.runModal();
ScopedPointer<XmlElement> audioState(deviceManager.createStateXml());
getAppProperties().getUserSettings()->setValue("audioDeviceState", audioState);
getAppProperties().getUserSettings()->saveIfNeeded();
}
void IconMenu::reloadPlugins()
{
NativeMessageBox::showOkCancelBox(AlertWindow::AlertIconType::InfoIcon, "Reload Plugins?", "Confirm scan and load of any new or updated plugins.", this, ModalCallbackFunction::forComponent(doReload, this));
}
void IconMenu::doReload(int id, IconMenu* im)
{
// Canceled
if (id == 0)
return Process::setDockIconVisible(false);
// Scan
const File deadMansPedalFile (getAppProperties().getUserSettings()->getFile().getSiblingFile("RecentlyCrashedPluginsList"));
String pluginName;
for (int i = 0; i < im->formatManager.getNumFormats(); i++)
{
im->scanner = new PluginDirectoryScanner(im->knownPluginList, *im->formatManager.getFormat(i), im->formatManager.getFormat(i)->getDefaultLocationsToSearch(), true, deadMansPedalFile);
while (im->scanner->scanNextFile(true, pluginName)) { }
}
// Remove plugins without inputs and/or outputs
std::vector<int> removeIndex;
for (int i = 0; i < im->knownPluginList.getNumTypes(); i++)
{
PluginDescription* plugin = im->knownPluginList.getType(i);
if (plugin->numInputChannels < 2 || plugin->numOutputChannels < 2)
removeIndex.push_back(i);
}
for (int i = 0; i < removeIndex.size(); i++)
im->knownPluginList.removeType(removeIndex[i] - i);
// Finish
NativeMessageBox::showMessageBox(AlertWindow::AlertIconType::InfoIcon, "Completed", "Plugins have been refreshed.");
Process::setDockIconVisible(false);
}
+48
View File
@@ -0,0 +1,48 @@
//
// IconMenu.hpp
// Light Host
//
// Created by Rolando Islas on 12/26/15.
//
//
#ifndef IconMenu_hpp
#define IconMenu_hpp
ApplicationProperties& getAppProperties();
class IconMenu : public SystemTrayIconComponent, private Timer, public ChangeListener
{
public:
IconMenu();
~IconMenu();
void mouseDown(const MouseEvent&);
static void menuInvocationCallback(int id, IconMenu*);
static void doReload(int id, IconMenu*);
void changeListenerCallback(ChangeBroadcaster* changed);
private:
std::string exec(const char* cmd);
void timerCallback();
void reloadPlugins();
void showAudioSettings();
void loadActivePlugins();
void savePluginStates();
void deletePluginStates();
AudioDeviceManager deviceManager;
AudioPluginFormatManager formatManager;
KnownPluginList knownPluginList;
KnownPluginList activePluginList;
KnownPluginList::SortMethod pluginSortMethod;
PopupMenu menu;
ScopedPointer<PluginDirectoryScanner> scanner;
bool menuIconLeftClicked;
AudioProcessorGraph graph;
AudioProcessorPlayer player;
AudioProcessorGraph::Node *inputNode;
AudioProcessorGraph::Node *outputNode;
class ScanThread;
};
#endif /* IconMenu_hpp */
+190
View File
@@ -0,0 +1,190 @@
#include "../JuceLibraryCode/JuceHeader.h"
#include "PluginWindow.h"
class PluginWindow;
static Array <PluginWindow*> activePluginWindows;
PluginWindow::PluginWindow (Component* const pluginEditor,
AudioProcessorGraph::Node* const o,
WindowFormatType t)
: DocumentWindow (pluginEditor->getName(), Colours::lightgrey,
DocumentWindow::minimiseButton | DocumentWindow::closeButton),
owner (o),
type (t)
{
setSize (400, 300);
setUsingNativeTitleBar(true);
setContentOwned (pluginEditor, true);
setTopLeftPosition (owner->properties.getWithDefault (getLastXProp (type), Random::getSystemRandom().nextInt (500)),
owner->properties.getWithDefault (getLastYProp (type), Random::getSystemRandom().nextInt (500)));
owner->properties.set (getOpenProp (type), true);
setVisible (true);
activePluginWindows.add (this);
}
void PluginWindow::closeCurrentlyOpenWindowsFor (const uint32 nodeId)
{
for (int i = activePluginWindows.size(); --i >= 0;)
if (activePluginWindows.getUnchecked(i)->owner->nodeId == nodeId)
delete activePluginWindows.getUnchecked (i);
}
void PluginWindow::closeAllCurrentlyOpenWindows()
{
if (activePluginWindows.size() > 0)
{
for (int i = activePluginWindows.size(); --i >= 0;)
delete activePluginWindows.getUnchecked (i);
Component dummyModalComp;
dummyModalComp.enterModalState();
MessageManager::getInstance()->runDispatchLoopUntil (50);
}
}
bool PluginWindow::containsActiveWindows()
{
return activePluginWindows.size() > 0;
}
//==============================================================================
class ProcessorProgramPropertyComp : public PropertyComponent,
private AudioProcessorListener
{
public:
ProcessorProgramPropertyComp (const String& name, AudioProcessor& p, int index_)
: PropertyComponent (name),
owner (p),
index (index_)
{
owner.addListener (this);
}
~ProcessorProgramPropertyComp()
{
owner.removeListener (this);
}
void refresh() { }
virtual void audioProcessorChanged (AudioProcessor*) { }
virtual void audioProcessorParameterChanged(AudioProcessor* processor, int, float) { }
private:
AudioProcessor& owner;
const int index;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProcessorProgramPropertyComp)
};
class ProgramAudioProcessorEditor : public AudioProcessorEditor
{
public:
ProgramAudioProcessorEditor (AudioProcessor* const p)
: AudioProcessorEditor (p)
{
jassert (p != nullptr);
setOpaque (true);
addAndMakeVisible (panel);
Array<PropertyComponent*> programs;
const int numPrograms = p->getNumPrograms();
int totalHeight = 0;
for (int i = 0; i < numPrograms; ++i)
{
String name (p->getProgramName (i).trim());
if (name.isEmpty())
name = "Unnamed";
ProcessorProgramPropertyComp* const pc = new ProcessorProgramPropertyComp (name, *p, i);
programs.add (pc);
totalHeight += pc->getPreferredHeight();
}
panel.addProperties (programs);
setSize (400, jlimit (25, 400, totalHeight));
}
void paint (Graphics& g)
{
g.fillAll (Colours::grey);
}
void resized()
{
panel.setBounds (getLocalBounds());
}
private:
PropertyPanel panel;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProgramAudioProcessorEditor)
};
//==============================================================================
PluginWindow* PluginWindow::getWindowFor (AudioProcessorGraph::Node* const node,
WindowFormatType type)
{
jassert (node != nullptr);
for (int i = activePluginWindows.size(); --i >= 0;)
if (activePluginWindows.getUnchecked(i)->owner == node
&& activePluginWindows.getUnchecked(i)->type == type)
return activePluginWindows.getUnchecked(i);
AudioProcessor* processor = node->getProcessor();
AudioProcessorEditor* ui = nullptr;
if (type == Normal)
{
ui = processor->createEditorIfNeeded();
if (ui == nullptr)
type = Generic;
}
if (ui == nullptr)
{
if (type == Generic || type == Parameters)
ui = new GenericAudioProcessorEditor (processor);
else if (type == Programs)
ui = new ProgramAudioProcessorEditor (processor);
}
if (ui != nullptr)
{
if (AudioPluginInstance* const plugin = dynamic_cast<AudioPluginInstance*> (processor))
ui->setName (plugin->getName());
return new PluginWindow (ui, node, type);
}
return nullptr;
}
PluginWindow::~PluginWindow()
{
activePluginWindows.removeFirstMatchingValue (this);
clearContentComponent();
}
void PluginWindow::moved()
{
owner->properties.set (getLastXProp (type), getX());
owner->properties.set (getLastYProp (type), getY());
}
void PluginWindow::closeButtonPressed()
{
owner->properties.set (getOpenProp (type), false);
delete this;
}
+56
View File
@@ -0,0 +1,56 @@
#ifndef PluginWindow_h
#define PluginWindow_h
ApplicationProperties& getAppProperties();
class PluginWindow : public DocumentWindow
{
public:
enum WindowFormatType
{
Normal = 0,
Generic,
Programs,
Parameters,
NumTypes
};
PluginWindow (Component* pluginEditor, AudioProcessorGraph::Node*, WindowFormatType);
~PluginWindow();
static PluginWindow* getWindowFor (AudioProcessorGraph::Node*, WindowFormatType);
static void closeCurrentlyOpenWindowsFor (const uint32 nodeId);
static void closeAllCurrentlyOpenWindows();
static bool containsActiveWindows();
void moved() override;
void closeButtonPressed() override;
private:
AudioProcessorGraph::Node* owner;
WindowFormatType type;
float getDesktopScaleFactor() const override { return 1.0f; }
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginWindow)
};
inline String toString (PluginWindow::WindowFormatType type)
{
switch (type)
{
case PluginWindow::Normal: return "Normal";
case PluginWindow::Generic: return "Generic";
case PluginWindow::Programs: return "Programs";
case PluginWindow::Parameters: return "Parameters";
default: return String();
}
}
inline String getLastXProp (PluginWindow::WindowFormatType type) { return "uiLastX_" + toString (type); }
inline String getLastYProp (PluginWindow::WindowFormatType type) { return "uiLastY_" + toString (type); }
inline String getOpenProp (PluginWindow::WindowFormatType type) { return "uiopen_" + toString (type); }
#endif /* PluginWindow_hpp */