| @@ -25,6 +25,8 @@ | |||
| #include "juce_audio_processors.h" | |||
| // TODO - use setPlayConfigDetails | |||
| using namespace juce; | |||
| CARLA_BACKEND_START_NAMESPACE | |||
| @@ -33,6 +33,7 @@ STATIC_LIBS += ../../modules/juce_data_structures.a | |||
| STATIC_LIBS += ../../modules/juce_events.a | |||
| STATIC_LIBS += ../../modules/juce_graphics.a | |||
| STATIC_LIBS += ../../modules/juce_gui_basics.a | |||
| STATIC_LIBS += ../../modules/juce_gui_extra.a | |||
| LINK_FLAGS += $(JUCE_AUDIO_BASICS_LIBS) | |||
| LINK_FLAGS += $(JUCE_AUDIO_DEVICES_LIBS) | |||
| LINK_FLAGS += $(JUCE_CORE_LIBS) | |||
| @@ -40,6 +41,7 @@ LINK_FLAGS += $(JUCE_DATA_STRUCTURES_LIBS) | |||
| LINK_FLAGS += $(JUCE_EVENTS_LIBS) | |||
| LINK_FLAGS += $(JUCE_GRAPHICS_LIBS) | |||
| LINK_FLAGS += $(JUCE_GUI_BASICS_LIBS) | |||
| LINK_FLAGS += $(JUCE_GUI_EXTRA_LIBS) | |||
| endif | |||
| ifeq ($(HAVE_DGL),true) | |||
| @@ -93,6 +93,14 @@ juce_gui_basics_%: | |||
| # -------------------------------------------------------------- | |||
| juce_gui_extra: | |||
| $(MAKE) -C juce_gui_extra | |||
| juce_gui_extra_%: | |||
| $(MAKE) -C juce_gui_extra $* | |||
| # -------------------------------------------------------------- | |||
| lilv: | |||
| $(MAKE) -C lilv | |||
| @@ -211,6 +219,6 @@ clean: | |||
| .PHONY: \ | |||
| daz-plugins dgl jackbridge lilv rtaudio rtmidi rtmempool stk theme \ | |||
| juce_audio_basics juce_audio_devices juce_audio_formats juce_audio_processors juce_core juce_data_structures juce_events juce_graphics juce_gui_basics | |||
| juce_audio_basics juce_audio_devices juce_audio_formats juce_audio_processors juce_core juce_data_structures juce_events juce_graphics juce_gui_basics juce_gui_extra | |||
| # -------------------------------------------------------------- | |||
| @@ -100,6 +100,7 @@ OBJS += \ | |||
| ifeq ($(HAVE_JUCE),true) | |||
| OBJS += \ | |||
| juce-patchbay.cpp.o \ | |||
| vex-fx.cpp.o \ | |||
| vex-synth.cpp.o \ | |||
| vex-src.cpp.o | |||
| @@ -257,6 +258,9 @@ distrho-bigmeterM.cpp.o: distrho-bigmeterM.cpp bigmeterM/*.cpp bigmeterM/*.h big | |||
| distrho-notes.cpp.o: distrho-notes.cpp notes/*.cpp notes/*.h notes/*.hpp distrho/DistrhoPluginCarla.cpp $(CXXDEPS) | |||
| $(CXX) $< $(BUILD_CXX_FLAGS) -I../dgl -Inotes -DDISTRHO_NAMESPACE=DISTRHO_Notes -c -o $@ | |||
| juce-patchbay.cpp.o: juce-patchbay.cpp | |||
| $(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ | |||
| midi-file.cpp.o: midi-file.cpp midi-base.hpp $(CXXDEPS) | |||
| $(CXX) $< $(MF_CXX_FLAGS) -c -o $@ | |||
| @@ -36,6 +36,7 @@ extern void carla_register_native_plugin_carla(); | |||
| #endif | |||
| #ifdef HAVE_JUCE | |||
| extern void carla_register_native_plugin_jucePatchbay(); | |||
| extern void carla_register_native_plugin_vex_fx(); | |||
| extern void carla_register_native_plugin_vex_synth(); | |||
| #endif | |||
| @@ -93,6 +94,7 @@ void carla_register_all_plugins() | |||
| #endif | |||
| #ifdef HAVE_JUCE | |||
| carla_register_native_plugin_jucePatchbay(); | |||
| carla_register_native_plugin_vex_fx(); | |||
| carla_register_native_plugin_vex_synth(); | |||
| #endif | |||
| @@ -0,0 +1,362 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2013 - Raw Material Software Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| //#include "MainHostWindow.h" | |||
| #include "FilterGraph.h" | |||
| #include "InternalFilters.h" | |||
| //#include "GraphEditorPanel.h" | |||
| //============================================================================== | |||
| const int FilterGraph::midiChannelNumber = 0x1000; | |||
| FilterGraph::FilterGraph (AudioPluginFormatManager& formatManager_) | |||
| : FileBasedDocument (filenameSuffix, | |||
| filenameWildcard, | |||
| "Load a filter graph", | |||
| "Save a filter graph"), | |||
| formatManager (formatManager_), lastUID (0) | |||
| { | |||
| setChangedFlag (false); | |||
| } | |||
| FilterGraph::~FilterGraph() | |||
| { | |||
| graph.clear(); | |||
| } | |||
| void FilterGraph::ready() | |||
| { | |||
| InternalPluginFormat internalFormat; | |||
| addFilter (internalFormat.getDescriptionFor (InternalPluginFormat::audioInputFilter), 0.5f, 0.1f); | |||
| addFilter (internalFormat.getDescriptionFor (InternalPluginFormat::midiInputFilter), 0.25f, 0.1f); | |||
| addFilter (internalFormat.getDescriptionFor (InternalPluginFormat::audioOutputFilter), 0.5f, 0.9f); | |||
| } | |||
| uint32 FilterGraph::getNextUID() noexcept | |||
| { | |||
| return ++lastUID; | |||
| } | |||
| //============================================================================== | |||
| int FilterGraph::getNumFilters() const noexcept | |||
| { | |||
| return graph.getNumNodes(); | |||
| } | |||
| const AudioProcessorGraph::Node::Ptr FilterGraph::getNode (const int index) const noexcept | |||
| { | |||
| return graph.getNode (index); | |||
| } | |||
| const AudioProcessorGraph::Node::Ptr FilterGraph::getNodeForId (const uint32 uid) const noexcept | |||
| { | |||
| return graph.getNodeForId (uid); | |||
| } | |||
| void FilterGraph::addFilter (const PluginDescription* desc, double x, double y) | |||
| { | |||
| if (desc != nullptr) | |||
| { | |||
| AudioProcessorGraph::Node* node = nullptr; | |||
| String errorMessage; | |||
| if (AudioPluginInstance* instance = formatManager.createPluginInstance (*desc, graph.getSampleRate(), graph.getBlockSize(), errorMessage)) | |||
| node = graph.addNode (instance); | |||
| if (node != nullptr) | |||
| { | |||
| node->properties.set ("x", x); | |||
| node->properties.set ("y", y); | |||
| changed(); | |||
| } | |||
| else | |||
| { | |||
| AlertWindow::showMessageBox (AlertWindow::WarningIcon, | |||
| TRANS("Couldn't create filter"), | |||
| errorMessage); | |||
| } | |||
| } | |||
| } | |||
| void FilterGraph::removeFilter (const uint32 id) | |||
| { | |||
| //PluginWindow::closeCurrentlyOpenWindowsFor (id); | |||
| if (graph.removeNode (id)) | |||
| changed(); | |||
| } | |||
| void FilterGraph::disconnectFilter (const uint32 id) | |||
| { | |||
| if (graph.disconnectNode (id)) | |||
| changed(); | |||
| } | |||
| void FilterGraph::removeIllegalConnections() | |||
| { | |||
| if (graph.removeIllegalConnections()) | |||
| changed(); | |||
| } | |||
| void FilterGraph::setNodePosition (const int nodeId, double x, double y) | |||
| { | |||
| const AudioProcessorGraph::Node::Ptr n (graph.getNodeForId (nodeId)); | |||
| if (n != nullptr) | |||
| { | |||
| n->properties.set ("x", jlimit (0.0, 1.0, x)); | |||
| n->properties.set ("y", jlimit (0.0, 1.0, y)); | |||
| } | |||
| } | |||
| void FilterGraph::getNodePosition (const int nodeId, double& x, double& y) const | |||
| { | |||
| x = y = 0; | |||
| const AudioProcessorGraph::Node::Ptr n (graph.getNodeForId (nodeId)); | |||
| if (n != nullptr) | |||
| { | |||
| x = (double) n->properties ["x"]; | |||
| y = (double) n->properties ["y"]; | |||
| } | |||
| } | |||
| //============================================================================== | |||
| int FilterGraph::getNumConnections() const noexcept | |||
| { | |||
| return graph.getNumConnections(); | |||
| } | |||
| const AudioProcessorGraph::Connection* FilterGraph::getConnection (const int index) const noexcept | |||
| { | |||
| return graph.getConnection (index); | |||
| } | |||
| const AudioProcessorGraph::Connection* FilterGraph::getConnectionBetween (uint32 sourceFilterUID, int sourceFilterChannel, | |||
| uint32 destFilterUID, int destFilterChannel) const noexcept | |||
| { | |||
| return graph.getConnectionBetween (sourceFilterUID, sourceFilterChannel, | |||
| destFilterUID, destFilterChannel); | |||
| } | |||
| bool FilterGraph::canConnect (uint32 sourceFilterUID, int sourceFilterChannel, | |||
| uint32 destFilterUID, int destFilterChannel) const noexcept | |||
| { | |||
| return graph.canConnect (sourceFilterUID, sourceFilterChannel, | |||
| destFilterUID, destFilterChannel); | |||
| } | |||
| bool FilterGraph::addConnection (uint32 sourceFilterUID, int sourceFilterChannel, | |||
| uint32 destFilterUID, int destFilterChannel) | |||
| { | |||
| const bool result = graph.addConnection (sourceFilterUID, sourceFilterChannel, | |||
| destFilterUID, destFilterChannel); | |||
| if (result) | |||
| changed(); | |||
| return result; | |||
| } | |||
| void FilterGraph::removeConnection (const int index) | |||
| { | |||
| graph.removeConnection (index); | |||
| changed(); | |||
| } | |||
| void FilterGraph::removeConnection (uint32 sourceFilterUID, int sourceFilterChannel, | |||
| uint32 destFilterUID, int destFilterChannel) | |||
| { | |||
| if (graph.removeConnection (sourceFilterUID, sourceFilterChannel, | |||
| destFilterUID, destFilterChannel)) | |||
| changed(); | |||
| } | |||
| void FilterGraph::clear() | |||
| { | |||
| //PluginWindow::closeAllCurrentlyOpenWindows(); | |||
| graph.clear(); | |||
| changed(); | |||
| } | |||
| //============================================================================== | |||
| String FilterGraph::getDocumentTitle() | |||
| { | |||
| if (! getFile().exists()) | |||
| return "Unnamed"; | |||
| return getFile().getFileNameWithoutExtension(); | |||
| } | |||
| Result FilterGraph::loadDocument (const File& file) | |||
| { | |||
| XmlDocument doc (file); | |||
| ScopedPointer<XmlElement> xml (doc.getDocumentElement()); | |||
| if (xml == nullptr || ! xml->hasTagName ("FILTERGRAPH")) | |||
| return Result::fail ("Not a valid filter graph file"); | |||
| restoreFromXml (*xml); | |||
| return Result::ok(); | |||
| } | |||
| Result FilterGraph::saveDocument (const File& file) | |||
| { | |||
| ScopedPointer<XmlElement> xml (createXml()); | |||
| if (! xml->writeToFile (file, String::empty)) | |||
| return Result::fail ("Couldn't write to the file"); | |||
| return Result::ok(); | |||
| } | |||
| File FilterGraph::getLastDocumentOpened() | |||
| { | |||
| return File(); | |||
| } | |||
| void FilterGraph::setLastDocumentOpened (const File&) | |||
| { | |||
| } | |||
| //============================================================================== | |||
| static XmlElement* createNodeXml (AudioProcessorGraph::Node* const node) noexcept | |||
| { | |||
| AudioPluginInstance* plugin = dynamic_cast <AudioPluginInstance*> (node->getProcessor()); | |||
| if (plugin == nullptr) | |||
| { | |||
| jassertfalse | |||
| return nullptr; | |||
| } | |||
| XmlElement* e = new XmlElement ("FILTER"); | |||
| e->setAttribute ("uid", (int) node->nodeId); | |||
| e->setAttribute ("x", node->properties ["x"].toString()); | |||
| e->setAttribute ("y", node->properties ["y"].toString()); | |||
| e->setAttribute ("uiLastX", node->properties ["uiLastX"].toString()); | |||
| e->setAttribute ("uiLastY", node->properties ["uiLastY"].toString()); | |||
| PluginDescription pd; | |||
| plugin->fillInPluginDescription (pd); | |||
| e->addChildElement (pd.createXml()); | |||
| XmlElement* state = new XmlElement ("STATE"); | |||
| MemoryBlock m; | |||
| node->getProcessor()->getStateInformation (m); | |||
| state->addTextElement (m.toBase64Encoding()); | |||
| e->addChildElement (state); | |||
| return e; | |||
| } | |||
| void FilterGraph::createNodeFromXml (const XmlElement& xml) | |||
| { | |||
| PluginDescription pd; | |||
| forEachXmlChildElement (xml, e) | |||
| { | |||
| if (pd.loadFromXml (*e)) | |||
| break; | |||
| } | |||
| String errorMessage; | |||
| AudioPluginInstance* instance = formatManager.createPluginInstance (pd, graph.getSampleRate(), graph.getBlockSize(), errorMessage); | |||
| if (instance == nullptr) | |||
| { | |||
| // xxx handle ins + outs | |||
| } | |||
| if (instance == nullptr) | |||
| return; | |||
| AudioProcessorGraph::Node::Ptr node (graph.addNode (instance, xml.getIntAttribute ("uid"))); | |||
| if (const XmlElement* const state = xml.getChildByName ("STATE")) | |||
| { | |||
| MemoryBlock m; | |||
| m.fromBase64Encoding (state->getAllSubText()); | |||
| node->getProcessor()->setStateInformation (m.getData(), (int) m.getSize()); | |||
| } | |||
| node->properties.set ("x", xml.getDoubleAttribute ("x")); | |||
| node->properties.set ("y", xml.getDoubleAttribute ("y")); | |||
| node->properties.set ("uiLastX", xml.getIntAttribute ("uiLastX")); | |||
| node->properties.set ("uiLastY", xml.getIntAttribute ("uiLastY")); | |||
| } | |||
| XmlElement* FilterGraph::createXml() const | |||
| { | |||
| XmlElement* xml = new XmlElement ("FILTERGRAPH"); | |||
| for (int i = 0; i < graph.getNumNodes(); ++i) | |||
| xml->addChildElement (createNodeXml (graph.getNode (i))); | |||
| for (int i = 0; i < graph.getNumConnections(); ++i) | |||
| { | |||
| const AudioProcessorGraph::Connection* const fc = graph.getConnection(i); | |||
| XmlElement* e = new XmlElement ("CONNECTION"); | |||
| e->setAttribute ("srcFilter", (int) fc->sourceNodeId); | |||
| e->setAttribute ("srcChannel", fc->sourceChannelIndex); | |||
| e->setAttribute ("dstFilter", (int) fc->destNodeId); | |||
| e->setAttribute ("dstChannel", fc->destChannelIndex); | |||
| xml->addChildElement (e); | |||
| } | |||
| return xml; | |||
| } | |||
| void FilterGraph::restoreFromXml (const XmlElement& xml) | |||
| { | |||
| clear(); | |||
| forEachXmlChildElementWithTagName (xml, e, "FILTER") | |||
| { | |||
| createNodeFromXml (*e); | |||
| changed(); | |||
| } | |||
| forEachXmlChildElementWithTagName (xml, e, "CONNECTION") | |||
| { | |||
| addConnection ((uint32) e->getIntAttribute ("srcFilter"), | |||
| e->getIntAttribute ("srcChannel"), | |||
| (uint32) e->getIntAttribute ("dstFilter"), | |||
| e->getIntAttribute ("dstChannel")); | |||
| } | |||
| graph.removeIllegalConnections(); | |||
| } | |||
| @@ -0,0 +1,114 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2013 - Raw Material Software Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef __FILTERGRAPH_JUCEHEADER__ | |||
| #define __FILTERGRAPH_JUCEHEADER__ | |||
| class FilterInGraph; | |||
| class FilterGraph; | |||
| const char* const filenameSuffix = ".filtergraph"; | |||
| const char* const filenameWildcard = "*.filtergraph"; | |||
| //============================================================================== | |||
| /** | |||
| A collection of filters and some connections between them. | |||
| */ | |||
| class FilterGraph : public FileBasedDocument | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| FilterGraph (AudioPluginFormatManager& formatManager); | |||
| ~FilterGraph(); | |||
| void ready(); | |||
| //============================================================================== | |||
| AudioProcessorGraph& getGraph() noexcept { return graph; } | |||
| int getNumFilters() const noexcept; | |||
| const AudioProcessorGraph::Node::Ptr getNode (const int index) const noexcept; | |||
| const AudioProcessorGraph::Node::Ptr getNodeForId (const uint32 uid) const noexcept; | |||
| void addFilter (const PluginDescription* desc, double x, double y); | |||
| void removeFilter (const uint32 filterUID); | |||
| void disconnectFilter (const uint32 filterUID); | |||
| void removeIllegalConnections(); | |||
| void setNodePosition (const int nodeId, double x, double y); | |||
| void getNodePosition (const int nodeId, double& x, double& y) const; | |||
| //============================================================================== | |||
| int getNumConnections() const noexcept; | |||
| const AudioProcessorGraph::Connection* getConnection (const int index) const noexcept; | |||
| const AudioProcessorGraph::Connection* getConnectionBetween (uint32 sourceFilterUID, int sourceFilterChannel, | |||
| uint32 destFilterUID, int destFilterChannel) const noexcept; | |||
| bool canConnect (uint32 sourceFilterUID, int sourceFilterChannel, | |||
| uint32 destFilterUID, int destFilterChannel) const noexcept; | |||
| bool addConnection (uint32 sourceFilterUID, int sourceFilterChannel, | |||
| uint32 destFilterUID, int destFilterChannel); | |||
| void removeConnection (const int index); | |||
| void removeConnection (uint32 sourceFilterUID, int sourceFilterChannel, | |||
| uint32 destFilterUID, int destFilterChannel); | |||
| void clear(); | |||
| //============================================================================== | |||
| XmlElement* createXml() const; | |||
| void restoreFromXml (const XmlElement& xml); | |||
| //============================================================================== | |||
| String getDocumentTitle(); | |||
| Result loadDocument (const File& file); | |||
| Result saveDocument (const File& file); | |||
| File getLastDocumentOpened(); | |||
| void setLastDocumentOpened (const File& file); | |||
| /** The special channel index used to refer to a filter's midi channel. | |||
| */ | |||
| static const int midiChannelNumber; | |||
| private: | |||
| //============================================================================== | |||
| AudioPluginFormatManager& formatManager; | |||
| AudioProcessorGraph graph; | |||
| uint32 lastUID; | |||
| uint32 getNextUID() noexcept; | |||
| void createNodeFromXml (const XmlElement& xml); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilterGraph) | |||
| }; | |||
| #endif // __FILTERGRAPH_JUCEHEADER__ | |||
| @@ -0,0 +1,141 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2013 - Raw Material Software Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef __GRAPHEDITORPANEL_JUCEHEADER__ | |||
| #define __GRAPHEDITORPANEL_JUCEHEADER__ | |||
| #include "FilterGraph.h" | |||
| class FilterComponent; | |||
| class ConnectorComponent; | |||
| class PinComponent; | |||
| //============================================================================== | |||
| /** | |||
| A panel that displays and edits a FilterGraph. | |||
| */ | |||
| class GraphEditorPanel : public Component, | |||
| public ChangeListener | |||
| { | |||
| public: | |||
| GraphEditorPanel (FilterGraph& graph); | |||
| ~GraphEditorPanel(); | |||
| void paint (Graphics& g); | |||
| void mouseDown (const MouseEvent& e); | |||
| void createNewPlugin (const PluginDescription* desc, int x, int y); | |||
| FilterComponent* getComponentForFilter (uint32 filterID) const; | |||
| ConnectorComponent* getComponentForConnection (const AudioProcessorGraph::Connection& conn) const; | |||
| PinComponent* findPinAt (int x, int y) const; | |||
| void resized(); | |||
| void changeListenerCallback (ChangeBroadcaster*); | |||
| void updateComponents(); | |||
| //============================================================================== | |||
| void beginConnectorDrag (uint32 sourceFilterID, int sourceFilterChannel, | |||
| uint32 destFilterID, int destFilterChannel, | |||
| const MouseEvent& e); | |||
| void dragConnector (const MouseEvent& e); | |||
| void endDraggingConnector (const MouseEvent& e); | |||
| //============================================================================== | |||
| private: | |||
| FilterGraph& graph; | |||
| ScopedPointer<ConnectorComponent> draggingConnector; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GraphEditorPanel) | |||
| }; | |||
| //============================================================================== | |||
| /** | |||
| A panel that embeds a GraphEditorPanel with a midi keyboard at the bottom. | |||
| It also manages the graph itself, and plays it. | |||
| */ | |||
| class GraphDocumentComponent : public Component | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| GraphDocumentComponent (FilterGraph& graph); | |||
| ~GraphDocumentComponent(); | |||
| //============================================================================== | |||
| void createNewPlugin (const PluginDescription* desc, int x, int y); | |||
| //============================================================================== | |||
| FilterGraph& graph; | |||
| //============================================================================== | |||
| void resized(); | |||
| private: | |||
| //============================================================================== | |||
| MidiKeyboardState keyState; | |||
| GraphEditorPanel* graphPanel; | |||
| Component* keyboardComp; | |||
| Component* statusBar; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GraphDocumentComponent) | |||
| }; | |||
| //============================================================================== | |||
| /** A desktop window containing a plugin's UI. */ | |||
| class PluginWindow : public DocumentWindow | |||
| { | |||
| public: | |||
| enum WindowFormatType | |||
| { | |||
| Normal = 0, | |||
| Generic, | |||
| Programs, | |||
| Parameters | |||
| }; | |||
| PluginWindow (Component* pluginEditor, AudioProcessorGraph::Node*, WindowFormatType); | |||
| ~PluginWindow(); | |||
| static PluginWindow* getWindowFor (AudioProcessorGraph::Node*, WindowFormatType); | |||
| static void closeCurrentlyOpenWindowsFor (const uint32 nodeId); | |||
| static void closeAllCurrentlyOpenWindows(); | |||
| 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) | |||
| }; | |||
| #endif // __GRAPHEDITORPANEL_JUCEHEADER__ | |||
| @@ -0,0 +1,80 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2013 - Raw Material Software Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #include "InternalFilters.h" | |||
| #include "FilterGraph.h" | |||
| //============================================================================== | |||
| InternalPluginFormat::InternalPluginFormat() | |||
| { | |||
| { | |||
| AudioProcessorGraph::AudioGraphIOProcessor p (AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode); | |||
| p.fillInPluginDescription (audioOutDesc); | |||
| } | |||
| { | |||
| AudioProcessorGraph::AudioGraphIOProcessor p (AudioProcessorGraph::AudioGraphIOProcessor::audioInputNode); | |||
| p.fillInPluginDescription (audioInDesc); | |||
| } | |||
| { | |||
| AudioProcessorGraph::AudioGraphIOProcessor p (AudioProcessorGraph::AudioGraphIOProcessor::midiInputNode); | |||
| p.fillInPluginDescription (midiInDesc); | |||
| } | |||
| } | |||
| AudioPluginInstance* InternalPluginFormat::createInstanceFromDescription (const PluginDescription& desc, | |||
| double /*sampleRate*/, int /*blockSize*/) | |||
| { | |||
| if (desc.name == audioOutDesc.name) | |||
| return new AudioProcessorGraph::AudioGraphIOProcessor (AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode); | |||
| if (desc.name == audioInDesc.name) | |||
| return new AudioProcessorGraph::AudioGraphIOProcessor (AudioProcessorGraph::AudioGraphIOProcessor::audioInputNode); | |||
| if (desc.name == midiInDesc.name) | |||
| return new AudioProcessorGraph::AudioGraphIOProcessor (AudioProcessorGraph::AudioGraphIOProcessor::midiInputNode); | |||
| return 0; | |||
| } | |||
| const PluginDescription* InternalPluginFormat::getDescriptionFor (const InternalFilterType type) | |||
| { | |||
| switch (type) | |||
| { | |||
| case audioInputFilter: return &audioInDesc; | |||
| case audioOutputFilter: return &audioOutDesc; | |||
| case midiInputFilter: return &midiInDesc; | |||
| default: break; | |||
| } | |||
| return 0; | |||
| } | |||
| void InternalPluginFormat::getAllTypes (OwnedArray <PluginDescription>& results) | |||
| { | |||
| for (int i = 0; i < (int) endOfFilterTypes; ++i) | |||
| results.add (new PluginDescription (*getDescriptionFor ((InternalFilterType) i))); | |||
| } | |||
| @@ -0,0 +1,76 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2013 - Raw Material Software Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef __INTERNALFILTERS_JUCEHEADER__ | |||
| #define __INTERNALFILTERS_JUCEHEADER__ | |||
| #include "FilterGraph.h" | |||
| //============================================================================== | |||
| /** | |||
| Manages the internal plugin types. | |||
| */ | |||
| class InternalPluginFormat : public AudioPluginFormat | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| InternalPluginFormat(); | |||
| ~InternalPluginFormat() {} | |||
| //============================================================================== | |||
| enum InternalFilterType | |||
| { | |||
| audioInputFilter = 0, | |||
| audioOutputFilter, | |||
| midiInputFilter, | |||
| endOfFilterTypes | |||
| }; | |||
| const PluginDescription* getDescriptionFor (const InternalFilterType type); | |||
| void getAllTypes (OwnedArray <PluginDescription>& results); | |||
| //============================================================================== | |||
| String getName() const override { return "Internal"; } | |||
| bool fileMightContainThisPluginType (const String&) override { return false; } | |||
| FileSearchPath getDefaultLocationsToSearch() override { return FileSearchPath(); } | |||
| bool canScanForPlugins() const override { return false; } | |||
| void findAllTypesForFile (OwnedArray <PluginDescription>&, const String&) override {} | |||
| bool doesPluginStillExist (const PluginDescription&) override { return true; } | |||
| String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override { return fileOrIdentifier; } | |||
| bool pluginNeedsRescanning (const PluginDescription&) override { return false; } | |||
| StringArray searchPathsForPlugins (const FileSearchPath&, bool) override { return StringArray(); } | |||
| AudioPluginInstance* createInstanceFromDescription (const PluginDescription&, double, int) override; | |||
| private: | |||
| //============================================================================== | |||
| PluginDescription audioInDesc; | |||
| PluginDescription audioOutDesc; | |||
| PluginDescription midiInDesc; | |||
| }; | |||
| #endif // __INTERNALFILTERS_JUCEHEADER__ | |||
| @@ -0,0 +1,439 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2013 - Raw Material Software Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #include "juce_gui_basics.h" | |||
| #include "MainHostWindow.h" | |||
| #include "InternalFilters.h" | |||
| //============================================================================== | |||
| class MainHostWindow::PluginListWindow : public DocumentWindow | |||
| { | |||
| public: | |||
| PluginListWindow (MainHostWindow& owner_, AudioPluginFormatManager& formatManager) | |||
| : DocumentWindow ("Available Plugins", Colours::white, | |||
| DocumentWindow::minimiseButton | DocumentWindow::closeButton), | |||
| owner (owner_) | |||
| { | |||
| const File deadMansPedalFile (getAppProperties().getUserSettings() | |||
| ->getFile().getSiblingFile ("RecentlyCrashedPluginsList")); | |||
| setContentOwned (new PluginListComponent (formatManager, | |||
| owner.knownPluginList, | |||
| deadMansPedalFile, | |||
| getAppProperties().getUserSettings()), true); | |||
| setResizable (true, false); | |||
| setResizeLimits (300, 400, 800, 1500); | |||
| setTopLeftPosition (60, 60); | |||
| restoreWindowStateFromString (getAppProperties().getUserSettings()->getValue ("listWindowPos")); | |||
| setVisible (true); | |||
| } | |||
| ~PluginListWindow() | |||
| { | |||
| getAppProperties().getUserSettings()->setValue ("listWindowPos", getWindowStateAsString()); | |||
| clearContentComponent(); | |||
| } | |||
| void closeButtonPressed() | |||
| { | |||
| owner.pluginListWindow = nullptr; | |||
| } | |||
| private: | |||
| MainHostWindow& owner; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PluginListWindow) | |||
| }; | |||
| //============================================================================== | |||
| MainHostWindow::MainHostWindow() | |||
| : DocumentWindow (JUCEApplication::getInstance()->getApplicationName(), Colours::lightgrey, | |||
| DocumentWindow::allButtons) | |||
| { | |||
| formatManager.addDefaultFormats(); | |||
| formatManager.addFormat (new InternalPluginFormat()); | |||
| ScopedPointer<XmlElement> savedAudioState (getAppProperties().getUserSettings() | |||
| ->getXmlValue ("audioDeviceState")); | |||
| //deviceManager.initialise (256, 256, savedAudioState, true); | |||
| setResizable (true, false); | |||
| setResizeLimits (500, 400, 10000, 10000); | |||
| centreWithSize (800, 600); | |||
| //setContentOwned (new GraphDocumentComponent (formatManager, &deviceManager), false); | |||
| restoreWindowStateFromString (getAppProperties().getUserSettings()->getValue ("mainWindowPos")); | |||
| setVisible (true); | |||
| InternalPluginFormat internalFormat; | |||
| internalFormat.getAllTypes (internalTypes); | |||
| ScopedPointer<XmlElement> savedPluginList (getAppProperties().getUserSettings()->getXmlValue ("pluginList")); | |||
| if (savedPluginList != nullptr) | |||
| knownPluginList.recreateFromXml (*savedPluginList); | |||
| pluginSortMethod = (KnownPluginList::SortMethod) getAppProperties().getUserSettings() | |||
| ->getIntValue ("pluginSortMethod", KnownPluginList::sortByManufacturer); | |||
| knownPluginList.addChangeListener (this); | |||
| addKeyListener (getCommandManager().getKeyMappings()); | |||
| Process::setPriority (Process::HighPriority); | |||
| #if JUCE_MAC | |||
| setMacMainMenu (this); | |||
| #else | |||
| setMenuBar (this); | |||
| #endif | |||
| getCommandManager().setFirstCommandTarget (this); | |||
| } | |||
| MainHostWindow::~MainHostWindow() | |||
| { | |||
| pluginListWindow = nullptr; | |||
| #if JUCE_MAC | |||
| setMacMainMenu (nullptr); | |||
| #else | |||
| setMenuBar (nullptr); | |||
| #endif | |||
| knownPluginList.removeChangeListener (this); | |||
| getAppProperties().getUserSettings()->setValue ("mainWindowPos", getWindowStateAsString()); | |||
| clearContentComponent(); | |||
| } | |||
| void MainHostWindow::closeButtonPressed() | |||
| { | |||
| tryToQuitApplication(); | |||
| } | |||
| bool MainHostWindow::tryToQuitApplication() | |||
| { | |||
| PluginWindow::closeAllCurrentlyOpenWindows(); | |||
| if (getGraphEditor() == nullptr | |||
| || getGraphEditor()->graph.saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk) | |||
| { | |||
| JUCEApplication::quit(); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| void MainHostWindow::changeListenerCallback (ChangeBroadcaster*) | |||
| { | |||
| menuItemsChanged(); | |||
| // save the plugin list every time it gets chnaged, so that if we're scanning | |||
| // and it crashes, we've still saved the previous ones | |||
| ScopedPointer<XmlElement> savedPluginList (knownPluginList.createXml()); | |||
| if (savedPluginList != nullptr) | |||
| { | |||
| getAppProperties().getUserSettings()->setValue ("pluginList", savedPluginList); | |||
| getAppProperties().saveIfNeeded(); | |||
| } | |||
| } | |||
| StringArray MainHostWindow::getMenuBarNames() | |||
| { | |||
| const char* const names[] = { "File", "Plugins", "Options", nullptr }; | |||
| return StringArray (names); | |||
| } | |||
| PopupMenu MainHostWindow::getMenuForIndex (int topLevelMenuIndex, const String& /*menuName*/) | |||
| { | |||
| PopupMenu menu; | |||
| if (topLevelMenuIndex == 0) | |||
| { | |||
| // "File" menu | |||
| menu.addCommandItem (&getCommandManager(), CommandIDs::open); | |||
| RecentlyOpenedFilesList recentFiles; | |||
| recentFiles.restoreFromString (getAppProperties().getUserSettings() | |||
| ->getValue ("recentFilterGraphFiles")); | |||
| PopupMenu recentFilesMenu; | |||
| recentFiles.createPopupMenuItems (recentFilesMenu, 100, true, true); | |||
| menu.addSubMenu ("Open recent file", recentFilesMenu); | |||
| menu.addCommandItem (&getCommandManager(), CommandIDs::save); | |||
| menu.addCommandItem (&getCommandManager(), CommandIDs::saveAs); | |||
| menu.addSeparator(); | |||
| menu.addCommandItem (&getCommandManager(), StandardApplicationCommandIDs::quit); | |||
| } | |||
| else if (topLevelMenuIndex == 1) | |||
| { | |||
| // "Plugins" menu | |||
| PopupMenu pluginsMenu; | |||
| addPluginsToMenu (pluginsMenu); | |||
| menu.addSubMenu ("Create plugin", pluginsMenu); | |||
| menu.addSeparator(); | |||
| menu.addItem (250, "Delete all plugins"); | |||
| } | |||
| else if (topLevelMenuIndex == 2) | |||
| { | |||
| // "Options" menu | |||
| menu.addCommandItem (&getCommandManager(), CommandIDs::showPluginListEditor); | |||
| PopupMenu sortTypeMenu; | |||
| sortTypeMenu.addItem (200, "List plugins in default order", true, pluginSortMethod == KnownPluginList::defaultOrder); | |||
| sortTypeMenu.addItem (201, "List plugins in alphabetical order", true, pluginSortMethod == KnownPluginList::sortAlphabetically); | |||
| sortTypeMenu.addItem (202, "List plugins by category", true, pluginSortMethod == KnownPluginList::sortByCategory); | |||
| sortTypeMenu.addItem (203, "List plugins by manufacturer", true, pluginSortMethod == KnownPluginList::sortByManufacturer); | |||
| sortTypeMenu.addItem (204, "List plugins based on the directory structure", true, pluginSortMethod == KnownPluginList::sortByFileSystemLocation); | |||
| menu.addSubMenu ("Plugin menu type", sortTypeMenu); | |||
| menu.addSeparator(); | |||
| menu.addCommandItem (&getCommandManager(), CommandIDs::aboutBox); | |||
| } | |||
| return menu; | |||
| } | |||
| void MainHostWindow::menuItemSelected (int menuItemID, int /*topLevelMenuIndex*/) | |||
| { | |||
| GraphDocumentComponent* const graphEditor = getGraphEditor(); | |||
| if (menuItemID == 250) | |||
| { | |||
| if (graphEditor != nullptr) | |||
| graphEditor->graph.clear(); | |||
| } | |||
| else if (menuItemID >= 100 && menuItemID < 200) | |||
| { | |||
| RecentlyOpenedFilesList recentFiles; | |||
| recentFiles.restoreFromString (getAppProperties().getUserSettings() | |||
| ->getValue ("recentFilterGraphFiles")); | |||
| if (graphEditor != nullptr && graphEditor->graph.saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk) | |||
| graphEditor->graph.loadFrom (recentFiles.getFile (menuItemID - 100), true); | |||
| } | |||
| else if (menuItemID >= 200 && menuItemID < 210) | |||
| { | |||
| if (menuItemID == 200) pluginSortMethod = KnownPluginList::defaultOrder; | |||
| else if (menuItemID == 201) pluginSortMethod = KnownPluginList::sortAlphabetically; | |||
| else if (menuItemID == 202) pluginSortMethod = KnownPluginList::sortByCategory; | |||
| else if (menuItemID == 203) pluginSortMethod = KnownPluginList::sortByManufacturer; | |||
| else if (menuItemID == 204) pluginSortMethod = KnownPluginList::sortByFileSystemLocation; | |||
| getAppProperties().getUserSettings()->setValue ("pluginSortMethod", (int) pluginSortMethod); | |||
| menuItemsChanged(); | |||
| } | |||
| else | |||
| { | |||
| createPlugin (getChosenType (menuItemID), | |||
| proportionOfWidth (0.3f + Random::getSystemRandom().nextFloat() * 0.6f), | |||
| proportionOfHeight (0.3f + Random::getSystemRandom().nextFloat() * 0.6f)); | |||
| } | |||
| } | |||
| void MainHostWindow::createPlugin (const PluginDescription* desc, int x, int y) | |||
| { | |||
| GraphDocumentComponent* const graphEditor = getGraphEditor(); | |||
| if (graphEditor != nullptr) | |||
| graphEditor->createNewPlugin (desc, x, y); | |||
| } | |||
| void MainHostWindow::addPluginsToMenu (PopupMenu& m) const | |||
| { | |||
| for (int i = 0; i < internalTypes.size(); ++i) | |||
| m.addItem (i + 1, internalTypes.getUnchecked(i)->name); | |||
| m.addSeparator(); | |||
| knownPluginList.addToMenu (m, pluginSortMethod); | |||
| } | |||
| const PluginDescription* MainHostWindow::getChosenType (const int menuID) const | |||
| { | |||
| if (menuID >= 1 && menuID < 1 + internalTypes.size()) | |||
| return internalTypes [menuID - 1]; | |||
| return knownPluginList.getType (knownPluginList.getIndexChosenByMenu (menuID)); | |||
| } | |||
| //============================================================================== | |||
| ApplicationCommandTarget* MainHostWindow::getNextCommandTarget() | |||
| { | |||
| return findFirstTargetParentComponent(); | |||
| } | |||
| void MainHostWindow::getAllCommands (Array <CommandID>& commands) | |||
| { | |||
| // this returns the set of all commands that this target can perform.. | |||
| const CommandID ids[] = { CommandIDs::open, | |||
| CommandIDs::save, | |||
| CommandIDs::saveAs, | |||
| CommandIDs::showPluginListEditor, | |||
| CommandIDs::aboutBox | |||
| }; | |||
| commands.addArray (ids, numElementsInArray (ids)); | |||
| } | |||
| void MainHostWindow::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result) | |||
| { | |||
| const String category ("General"); | |||
| switch (commandID) | |||
| { | |||
| case CommandIDs::open: | |||
| result.setInfo ("Open...", | |||
| "Opens a filter graph file", | |||
| category, 0); | |||
| result.defaultKeypresses.add (KeyPress ('o', ModifierKeys::commandModifier, 0)); | |||
| break; | |||
| case CommandIDs::save: | |||
| result.setInfo ("Save", | |||
| "Saves the current graph to a file", | |||
| category, 0); | |||
| result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier, 0)); | |||
| break; | |||
| case CommandIDs::saveAs: | |||
| result.setInfo ("Save As...", | |||
| "Saves a copy of the current graph to a file", | |||
| category, 0); | |||
| result.defaultKeypresses.add (KeyPress ('s', ModifierKeys::shiftModifier | ModifierKeys::commandModifier, 0)); | |||
| break; | |||
| case CommandIDs::showPluginListEditor: | |||
| result.setInfo ("Edit the list of available plug-Ins...", String::empty, category, 0); | |||
| result.addDefaultKeypress ('p', ModifierKeys::commandModifier); | |||
| break; | |||
| case CommandIDs::aboutBox: | |||
| result.setInfo ("About...", String::empty, category, 0); | |||
| break; | |||
| default: | |||
| break; | |||
| } | |||
| } | |||
| bool MainHostWindow::perform (const InvocationInfo& info) | |||
| { | |||
| GraphDocumentComponent* const graphEditor = getGraphEditor(); | |||
| switch (info.commandID) | |||
| { | |||
| case CommandIDs::open: | |||
| if (graphEditor != nullptr && graphEditor->graph.saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk) | |||
| graphEditor->graph.loadFromUserSpecifiedFile (true); | |||
| break; | |||
| case CommandIDs::save: | |||
| if (graphEditor != nullptr) | |||
| graphEditor->graph.save (true, true); | |||
| break; | |||
| case CommandIDs::saveAs: | |||
| if (graphEditor != nullptr) | |||
| graphEditor->graph.saveAs (File::nonexistent, true, true, true); | |||
| break; | |||
| case CommandIDs::showPluginListEditor: | |||
| if (pluginListWindow == nullptr) | |||
| pluginListWindow = new PluginListWindow (*this, formatManager); | |||
| pluginListWindow->toFront (true); | |||
| break; | |||
| case CommandIDs::aboutBox: | |||
| // TODO | |||
| break; | |||
| default: | |||
| return false; | |||
| } | |||
| return true; | |||
| } | |||
| bool MainHostWindow::isInterestedInFileDrag (const StringArray&) | |||
| { | |||
| return true; | |||
| } | |||
| void MainHostWindow::fileDragEnter (const StringArray&, int, int) | |||
| { | |||
| } | |||
| void MainHostWindow::fileDragMove (const StringArray&, int, int) | |||
| { | |||
| } | |||
| void MainHostWindow::fileDragExit (const StringArray&) | |||
| { | |||
| } | |||
| void MainHostWindow::filesDropped (const StringArray& files, int x, int y) | |||
| { | |||
| GraphDocumentComponent* const graphEditor = getGraphEditor(); | |||
| if (graphEditor != nullptr) | |||
| { | |||
| if (files.size() == 1 && File (files[0]).hasFileExtension (filenameSuffix)) | |||
| { | |||
| if (graphEditor->graph.saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk) | |||
| graphEditor->graph.loadFrom (File (files[0]), true); | |||
| } | |||
| else | |||
| { | |||
| OwnedArray <PluginDescription> typesFound; | |||
| knownPluginList.scanAndAddDragAndDroppedFiles (formatManager, files, typesFound); | |||
| Point<int> pos (graphEditor->getLocalPoint (this, Point<int> (x, y))); | |||
| for (int i = 0; i < jmin (5, typesFound.size()); ++i) | |||
| createPlugin (typesFound.getUnchecked(i), pos.getX(), pos.getY()); | |||
| } | |||
| } | |||
| } | |||
| GraphDocumentComponent* MainHostWindow::getGraphEditor() const | |||
| { | |||
| return dynamic_cast <GraphDocumentComponent*> (getContentComponent()); | |||
| } | |||
| @@ -0,0 +1,99 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2013 - Raw Material Software Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef __MAINHOSTWINDOW_JUCEHEADER__ | |||
| #define __MAINHOSTWINDOW_JUCEHEADER__ | |||
| #include "FilterGraph.h" | |||
| #include "GraphEditorPanel.h" | |||
| //============================================================================== | |||
| namespace CommandIDs | |||
| { | |||
| static const int open = 0x30000; | |||
| static const int save = 0x30001; | |||
| static const int saveAs = 0x30002; | |||
| static const int showPluginListEditor = 0x30100; | |||
| static const int aboutBox = 0x30300; | |||
| } | |||
| //============================================================================== | |||
| /** | |||
| */ | |||
| class MainHostWindow : public DocumentWindow, | |||
| public MenuBarModel, | |||
| public ApplicationCommandTarget, | |||
| public ChangeListener, | |||
| public FileDragAndDropTarget | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| MainHostWindow(); | |||
| ~MainHostWindow(); | |||
| //============================================================================== | |||
| void closeButtonPressed(); | |||
| void changeListenerCallback (ChangeBroadcaster*); | |||
| bool isInterestedInFileDrag (const StringArray& files); | |||
| void fileDragEnter (const StringArray& files, int, int); | |||
| void fileDragMove (const StringArray& files, int, int); | |||
| void fileDragExit (const StringArray& files); | |||
| void filesDropped (const StringArray& files, int, int); | |||
| StringArray getMenuBarNames(); | |||
| PopupMenu getMenuForIndex (int topLevelMenuIndex, const String& menuName); | |||
| void menuItemSelected (int menuItemID, int topLevelMenuIndex); | |||
| ApplicationCommandTarget* getNextCommandTarget(); | |||
| void getAllCommands (Array <CommandID>& commands); | |||
| void getCommandInfo (CommandID commandID, ApplicationCommandInfo& result); | |||
| bool perform (const InvocationInfo& info); | |||
| bool tryToQuitApplication(); | |||
| void createPlugin (const PluginDescription* desc, int x, int y); | |||
| void addPluginsToMenu (PopupMenu& m) const; | |||
| const PluginDescription* getChosenType (const int menuID) const; | |||
| GraphDocumentComponent* getGraphEditor() const; | |||
| private: | |||
| //============================================================================== | |||
| //AudioDeviceManager deviceManager; | |||
| AudioPluginFormatManager formatManager; | |||
| OwnedArray <PluginDescription> internalTypes; | |||
| KnownPluginList knownPluginList; | |||
| KnownPluginList::SortMethod pluginSortMethod; | |||
| class PluginListWindow; | |||
| ScopedPointer <PluginListWindow> pluginListWindow; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainHostWindow) | |||
| }; | |||
| #endif // __MAINHOSTWINDOW_JUCEHEADER__ | |||
| @@ -0,0 +1,901 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2013 - Raw Material Software Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| class MidiKeyboardUpDownButton : public Button | |||
| { | |||
| public: | |||
| MidiKeyboardUpDownButton (MidiKeyboardComponent& comp, const int d) | |||
| : Button (String::empty), | |||
| owner (comp), | |||
| delta (d) | |||
| { | |||
| setOpaque (true); | |||
| } | |||
| void clicked() override | |||
| { | |||
| int note = owner.getLowestVisibleKey(); | |||
| if (delta < 0) | |||
| note = (note - 1) / 12; | |||
| else | |||
| note = note / 12 + 1; | |||
| owner.setLowestVisibleKey (note * 12); | |||
| } | |||
| void paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) override | |||
| { | |||
| owner.drawUpDownButton (g, getWidth(), getHeight(), | |||
| isMouseOverButton, isButtonDown, | |||
| delta > 0); | |||
| } | |||
| private: | |||
| MidiKeyboardComponent& owner; | |||
| const int delta; | |||
| JUCE_DECLARE_NON_COPYABLE (MidiKeyboardUpDownButton) | |||
| }; | |||
| //============================================================================== | |||
| MidiKeyboardComponent::MidiKeyboardComponent (MidiKeyboardState& s, | |||
| const Orientation o) | |||
| : state (s), | |||
| xOffset (0), | |||
| blackNoteLength (1), | |||
| keyWidth (16.0f), | |||
| orientation (o), | |||
| midiChannel (1), | |||
| midiInChannelMask (0xffff), | |||
| velocity (1.0f), | |||
| shouldCheckState (false), | |||
| rangeStart (0), | |||
| rangeEnd (127), | |||
| firstKey (12 * 4.0f), | |||
| canScroll (true), | |||
| useMousePositionForVelocity (true), | |||
| shouldCheckMousePos (false), | |||
| keyMappingOctave (6), | |||
| octaveNumForMiddleC (3) | |||
| { | |||
| addChildComponent (scrollDown = new MidiKeyboardUpDownButton (*this, -1)); | |||
| addChildComponent (scrollUp = new MidiKeyboardUpDownButton (*this, 1)); | |||
| // initialise with a default set of querty key-mappings.. | |||
| const char* const keymap = "awsedftgyhujkolp;"; | |||
| for (int i = 0; keymap[i] != 0; ++i) | |||
| setKeyPressForNote (KeyPress (keymap[i], 0, 0), i); | |||
| mouseOverNotes.insertMultiple (0, -1, 32); | |||
| mouseDownNotes.insertMultiple (0, -1, 32); | |||
| setOpaque (true); | |||
| setWantsKeyboardFocus (true); | |||
| state.addListener (this); | |||
| startTimer (1000 / 20); | |||
| } | |||
| MidiKeyboardComponent::~MidiKeyboardComponent() | |||
| { | |||
| state.removeListener (this); | |||
| } | |||
| //============================================================================== | |||
| void MidiKeyboardComponent::setKeyWidth (const float widthInPixels) | |||
| { | |||
| keyWidth = widthInPixels; | |||
| resized(); | |||
| } | |||
| void MidiKeyboardComponent::setOrientation (const Orientation newOrientation) | |||
| { | |||
| if (orientation != newOrientation) | |||
| { | |||
| orientation = newOrientation; | |||
| resized(); | |||
| } | |||
| } | |||
| void MidiKeyboardComponent::setAvailableRange (const int lowestNote, | |||
| const int highestNote) | |||
| { | |||
| jassert (lowestNote >= 0 && lowestNote <= 127); | |||
| jassert (highestNote >= 0 && highestNote <= 127); | |||
| jassert (lowestNote <= highestNote); | |||
| if (rangeStart != lowestNote || rangeEnd != highestNote) | |||
| { | |||
| rangeStart = jlimit (0, 127, lowestNote); | |||
| rangeEnd = jlimit (0, 127, highestNote); | |||
| firstKey = jlimit ((float) rangeStart, (float) rangeEnd, firstKey); | |||
| resized(); | |||
| } | |||
| } | |||
| void MidiKeyboardComponent::setLowestVisibleKey (int noteNumber) | |||
| { | |||
| setLowestVisibleKeyFloat ((float) noteNumber); | |||
| } | |||
| void MidiKeyboardComponent::setLowestVisibleKeyFloat (float noteNumber) | |||
| { | |||
| noteNumber = jlimit ((float) rangeStart, (float) rangeEnd, noteNumber); | |||
| if (noteNumber != firstKey) | |||
| { | |||
| const bool hasMoved = (((int) firstKey) != (int) noteNumber); | |||
| firstKey = noteNumber; | |||
| if (hasMoved) | |||
| sendChangeMessage(); | |||
| resized(); | |||
| } | |||
| } | |||
| void MidiKeyboardComponent::setScrollButtonsVisible (const bool newCanScroll) | |||
| { | |||
| if (canScroll != newCanScroll) | |||
| { | |||
| canScroll = newCanScroll; | |||
| resized(); | |||
| } | |||
| } | |||
| void MidiKeyboardComponent::colourChanged() | |||
| { | |||
| repaint(); | |||
| } | |||
| //============================================================================== | |||
| void MidiKeyboardComponent::setMidiChannel (const int midiChannelNumber) | |||
| { | |||
| jassert (midiChannelNumber > 0 && midiChannelNumber <= 16); | |||
| if (midiChannel != midiChannelNumber) | |||
| { | |||
| resetAnyKeysInUse(); | |||
| midiChannel = jlimit (1, 16, midiChannelNumber); | |||
| } | |||
| } | |||
| void MidiKeyboardComponent::setMidiChannelsToDisplay (const int midiChannelMask) | |||
| { | |||
| midiInChannelMask = midiChannelMask; | |||
| shouldCheckState = true; | |||
| } | |||
| void MidiKeyboardComponent::setVelocity (const float v, const bool useMousePosition) | |||
| { | |||
| velocity = jlimit (0.0f, 1.0f, v); | |||
| useMousePositionForVelocity = useMousePosition; | |||
| } | |||
| //============================================================================== | |||
| void MidiKeyboardComponent::getKeyPosition (int midiNoteNumber, const float keyWidth_, int& x, int& w) const | |||
| { | |||
| jassert (midiNoteNumber >= 0 && midiNoteNumber < 128); | |||
| static const float blackNoteWidth = 0.7f; | |||
| static const float notePos[] = { 0.0f, 1 - blackNoteWidth * 0.6f, | |||
| 1.0f, 2 - blackNoteWidth * 0.4f, | |||
| 2.0f, | |||
| 3.0f, 4 - blackNoteWidth * 0.7f, | |||
| 4.0f, 5 - blackNoteWidth * 0.5f, | |||
| 5.0f, 6 - blackNoteWidth * 0.3f, | |||
| 6.0f }; | |||
| static const float widths[] = { 1.0f, blackNoteWidth, | |||
| 1.0f, blackNoteWidth, | |||
| 1.0f, | |||
| 1.0f, blackNoteWidth, | |||
| 1.0f, blackNoteWidth, | |||
| 1.0f, blackNoteWidth, | |||
| 1.0f }; | |||
| const int octave = midiNoteNumber / 12; | |||
| const int note = midiNoteNumber % 12; | |||
| x = roundToInt (octave * 7.0f * keyWidth_ + notePos [note] * keyWidth_); | |||
| w = roundToInt (widths [note] * keyWidth_); | |||
| } | |||
| void MidiKeyboardComponent::getKeyPos (int midiNoteNumber, int& x, int& w) const | |||
| { | |||
| getKeyPosition (midiNoteNumber, keyWidth, x, w); | |||
| int rx, rw; | |||
| getKeyPosition (rangeStart, keyWidth, rx, rw); | |||
| x -= xOffset + rx; | |||
| } | |||
| Rectangle<int> MidiKeyboardComponent::getWhiteNotePos (int noteNum) const | |||
| { | |||
| int x, w; | |||
| getKeyPos (noteNum, x, w); | |||
| Rectangle<int> pos; | |||
| switch (orientation) | |||
| { | |||
| case horizontalKeyboard: pos.setBounds (x, 0, w, getHeight()); break; | |||
| case verticalKeyboardFacingLeft: pos.setBounds (0, x, getWidth(), w); break; | |||
| case verticalKeyboardFacingRight: pos.setBounds (0, getHeight() - x - w, getWidth(), w); break; | |||
| default: break; | |||
| } | |||
| return pos; | |||
| } | |||
| int MidiKeyboardComponent::getKeyStartPosition (const int midiNoteNumber) const | |||
| { | |||
| int x, w; | |||
| getKeyPos (midiNoteNumber, x, w); | |||
| return x; | |||
| } | |||
| int MidiKeyboardComponent::getNoteAtPosition (Point<int> p) | |||
| { | |||
| float v; | |||
| return xyToNote (p, v); | |||
| } | |||
| const uint8 MidiKeyboardComponent::whiteNotes[] = { 0, 2, 4, 5, 7, 9, 11 }; | |||
| const uint8 MidiKeyboardComponent::blackNotes[] = { 1, 3, 6, 8, 10 }; | |||
| int MidiKeyboardComponent::xyToNote (Point<int> pos, float& mousePositionVelocity) | |||
| { | |||
| if (! reallyContains (pos, false)) | |||
| return -1; | |||
| Point<int> p (pos); | |||
| if (orientation != horizontalKeyboard) | |||
| { | |||
| p = Point<int> (p.y, p.x); | |||
| if (orientation == verticalKeyboardFacingLeft) | |||
| p = Point<int> (p.x, getWidth() - p.y); | |||
| else | |||
| p = Point<int> (getHeight() - p.x, p.y); | |||
| } | |||
| return remappedXYToNote (p + Point<int> (xOffset, 0), mousePositionVelocity); | |||
| } | |||
| int MidiKeyboardComponent::remappedXYToNote (Point<int> pos, float& mousePositionVelocity) const | |||
| { | |||
| if (pos.getY() < blackNoteLength) | |||
| { | |||
| for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12) | |||
| { | |||
| for (int i = 0; i < 5; ++i) | |||
| { | |||
| const int note = octaveStart + blackNotes [i]; | |||
| if (note >= rangeStart && note <= rangeEnd) | |||
| { | |||
| int kx, kw; | |||
| getKeyPos (note, kx, kw); | |||
| kx += xOffset; | |||
| if (pos.x >= kx && pos.x < kx + kw) | |||
| { | |||
| mousePositionVelocity = pos.y / (float) blackNoteLength; | |||
| return note; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12) | |||
| { | |||
| for (int i = 0; i < 7; ++i) | |||
| { | |||
| const int note = octaveStart + whiteNotes [i]; | |||
| if (note >= rangeStart && note <= rangeEnd) | |||
| { | |||
| int kx, kw; | |||
| getKeyPos (note, kx, kw); | |||
| kx += xOffset; | |||
| if (pos.x >= kx && pos.x < kx + kw) | |||
| { | |||
| const int whiteNoteLength = (orientation == horizontalKeyboard) ? getHeight() : getWidth(); | |||
| mousePositionVelocity = pos.y / (float) whiteNoteLength; | |||
| return note; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| mousePositionVelocity = 0; | |||
| return -1; | |||
| } | |||
| //============================================================================== | |||
| void MidiKeyboardComponent::repaintNote (const int noteNum) | |||
| { | |||
| if (noteNum >= rangeStart && noteNum <= rangeEnd) | |||
| repaint (getWhiteNotePos (noteNum)); | |||
| } | |||
| void MidiKeyboardComponent::paint (Graphics& g) | |||
| { | |||
| g.fillAll (Colours::white.overlaidWith (findColour (whiteNoteColourId))); | |||
| const Colour lineColour (findColour (keySeparatorLineColourId)); | |||
| const Colour textColour (findColour (textLabelColourId)); | |||
| int octave; | |||
| for (octave = 0; octave < 128; octave += 12) | |||
| { | |||
| for (int white = 0; white < 7; ++white) | |||
| { | |||
| const int noteNum = octave + whiteNotes [white]; | |||
| if (noteNum >= rangeStart && noteNum <= rangeEnd) | |||
| { | |||
| const Rectangle<int> pos (getWhiteNotePos (noteNum)); | |||
| drawWhiteNote (noteNum, g, pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight(), | |||
| state.isNoteOnForChannels (midiInChannelMask, noteNum), | |||
| mouseOverNotes.contains (noteNum), lineColour, textColour); | |||
| } | |||
| } | |||
| } | |||
| float x1 = 0.0f, y1 = 0.0f, x2 = 0.0f, y2 = 0.0f; | |||
| const int width = getWidth(); | |||
| const int height = getHeight(); | |||
| if (orientation == verticalKeyboardFacingLeft) | |||
| { | |||
| x1 = width - 1.0f; | |||
| x2 = width - 5.0f; | |||
| } | |||
| else if (orientation == verticalKeyboardFacingRight) | |||
| x2 = 5.0f; | |||
| else | |||
| y2 = 5.0f; | |||
| int x, w; | |||
| getKeyPos (rangeEnd, x, w); | |||
| x += w; | |||
| const Colour shadowCol (findColour (shadowColourId)); | |||
| g.setGradientFill (ColourGradient (shadowCol, x1, y1, shadowCol.withAlpha (0.0f), x2, y2, false)); | |||
| switch (orientation) | |||
| { | |||
| case horizontalKeyboard: g.fillRect (0, 0, x, 5); break; | |||
| case verticalKeyboardFacingLeft: g.fillRect (width - 5, 0, 5, x); break; | |||
| case verticalKeyboardFacingRight: g.fillRect (0, 0, 5, x); break; | |||
| default: break; | |||
| } | |||
| g.setColour (lineColour); | |||
| switch (orientation) | |||
| { | |||
| case horizontalKeyboard: g.fillRect (0, height - 1, x, 1); break; | |||
| case verticalKeyboardFacingLeft: g.fillRect (0, 0, 1, x); break; | |||
| case verticalKeyboardFacingRight: g.fillRect (width - 1, 0, 1, x); break; | |||
| default: break; | |||
| } | |||
| const Colour blackNoteColour (findColour (blackNoteColourId)); | |||
| for (octave = 0; octave < 128; octave += 12) | |||
| { | |||
| for (int black = 0; black < 5; ++black) | |||
| { | |||
| const int noteNum = octave + blackNotes [black]; | |||
| if (noteNum >= rangeStart && noteNum <= rangeEnd) | |||
| { | |||
| getKeyPos (noteNum, x, w); | |||
| Rectangle<int> pos; | |||
| switch (orientation) | |||
| { | |||
| case horizontalKeyboard: pos.setBounds (x, 0, w, blackNoteLength); break; | |||
| case verticalKeyboardFacingLeft: pos.setBounds (width - blackNoteLength, x, blackNoteLength, w); break; | |||
| case verticalKeyboardFacingRight: pos.setBounds (0, height - x - w, blackNoteLength, w); break; | |||
| default: break; | |||
| } | |||
| drawBlackNote (noteNum, g, pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight(), | |||
| state.isNoteOnForChannels (midiInChannelMask, noteNum), | |||
| mouseOverNotes.contains (noteNum), blackNoteColour); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| void MidiKeyboardComponent::drawWhiteNote (int midiNoteNumber, | |||
| Graphics& g, int x, int y, int w, int h, | |||
| bool isDown, bool isOver, | |||
| const Colour& lineColour, | |||
| const Colour& textColour) | |||
| { | |||
| Colour c (Colours::transparentWhite); | |||
| if (isDown) c = findColour (keyDownOverlayColourId); | |||
| if (isOver) c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId)); | |||
| g.setColour (c); | |||
| g.fillRect (x, y, w, h); | |||
| const String text (getWhiteNoteText (midiNoteNumber)); | |||
| if (text.isNotEmpty()) | |||
| { | |||
| g.setColour (textColour); | |||
| g.setFont (Font (jmin (12.0f, keyWidth * 0.9f)).withHorizontalScale (0.8f)); | |||
| switch (orientation) | |||
| { | |||
| case horizontalKeyboard: g.drawFittedText (text, x + 1, y, w - 1, h - 2, Justification::centredBottom, 1); break; | |||
| case verticalKeyboardFacingLeft: g.drawFittedText (text, x + 2, y + 2, w - 4, h - 4, Justification::centredLeft, 1); break; | |||
| case verticalKeyboardFacingRight: g.drawFittedText (text, x + 2, y + 2, w - 4, h - 4, Justification::centredRight, 1); break; | |||
| default: break; | |||
| } | |||
| } | |||
| g.setColour (lineColour); | |||
| switch (orientation) | |||
| { | |||
| case horizontalKeyboard: g.fillRect (x, y, 1, h); break; | |||
| case verticalKeyboardFacingLeft: g.fillRect (x, y, w, 1); break; | |||
| case verticalKeyboardFacingRight: g.fillRect (x, y + h - 1, w, 1); break; | |||
| default: break; | |||
| } | |||
| if (midiNoteNumber == rangeEnd) | |||
| { | |||
| switch (orientation) | |||
| { | |||
| case horizontalKeyboard: g.fillRect (x + w, y, 1, h); break; | |||
| case verticalKeyboardFacingLeft: g.fillRect (x, y + h, w, 1); break; | |||
| case verticalKeyboardFacingRight: g.fillRect (x, y - 1, w, 1); break; | |||
| default: break; | |||
| } | |||
| } | |||
| } | |||
| void MidiKeyboardComponent::drawBlackNote (int /*midiNoteNumber*/, | |||
| Graphics& g, int x, int y, int w, int h, | |||
| bool isDown, bool isOver, | |||
| const Colour& noteFillColour) | |||
| { | |||
| Colour c (noteFillColour); | |||
| if (isDown) c = c.overlaidWith (findColour (keyDownOverlayColourId)); | |||
| if (isOver) c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId)); | |||
| g.setColour (c); | |||
| g.fillRect (x, y, w, h); | |||
| if (isDown) | |||
| { | |||
| g.setColour (noteFillColour); | |||
| g.drawRect (x, y, w, h); | |||
| } | |||
| else | |||
| { | |||
| g.setColour (c.brighter()); | |||
| const int xIndent = jmax (1, jmin (w, h) / 8); | |||
| switch (orientation) | |||
| { | |||
| case horizontalKeyboard: g.fillRect (x + xIndent, y, w - xIndent * 2, 7 * h / 8); break; | |||
| case verticalKeyboardFacingLeft: g.fillRect (x + w / 8, y + xIndent, w - w / 8, h - xIndent * 2); break; | |||
| case verticalKeyboardFacingRight: g.fillRect (x, y + xIndent, 7 * w / 8, h - xIndent * 2); break; | |||
| default: break; | |||
| } | |||
| } | |||
| } | |||
| void MidiKeyboardComponent::setOctaveForMiddleC (const int octaveNum) | |||
| { | |||
| octaveNumForMiddleC = octaveNum; | |||
| repaint(); | |||
| } | |||
| String MidiKeyboardComponent::getWhiteNoteText (const int midiNoteNumber) | |||
| { | |||
| if (keyWidth > 11.0f && midiNoteNumber % 12 == 0) | |||
| return MidiMessage::getMidiNoteName (midiNoteNumber, true, true, octaveNumForMiddleC); | |||
| return String::empty; | |||
| } | |||
| void MidiKeyboardComponent::drawUpDownButton (Graphics& g, int w, int h, | |||
| const bool mouseOver, | |||
| const bool buttonDown, | |||
| const bool movesOctavesUp) | |||
| { | |||
| g.fillAll (findColour (upDownButtonBackgroundColourId)); | |||
| float angle; | |||
| switch (orientation) | |||
| { | |||
| case horizontalKeyboard: angle = movesOctavesUp ? 0.0f : 0.5f; break; | |||
| case verticalKeyboardFacingLeft: angle = movesOctavesUp ? 0.25f : 0.75f; break; | |||
| case verticalKeyboardFacingRight: angle = movesOctavesUp ? 0.75f : 0.25f; break; | |||
| default: jassertfalse; angle = 0; break; | |||
| } | |||
| Path path; | |||
| path.addTriangle (0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f); | |||
| path.applyTransform (AffineTransform::rotation (float_Pi * 2.0f * angle, 0.5f, 0.5f)); | |||
| g.setColour (findColour (upDownButtonArrowColourId) | |||
| .withAlpha (buttonDown ? 1.0f : (mouseOver ? 0.6f : 0.4f))); | |||
| g.fillPath (path, path.getTransformToScaleToFit (1.0f, 1.0f, w - 2.0f, h - 2.0f, true)); | |||
| } | |||
| void MidiKeyboardComponent::resized() | |||
| { | |||
| int w = getWidth(); | |||
| int h = getHeight(); | |||
| if (w > 0 && h > 0) | |||
| { | |||
| if (orientation != horizontalKeyboard) | |||
| std::swap (w, h); | |||
| blackNoteLength = roundToInt (h * 0.7f); | |||
| int kx2, kw2; | |||
| getKeyPos (rangeEnd, kx2, kw2); | |||
| kx2 += kw2; | |||
| if ((int) firstKey != rangeStart) | |||
| { | |||
| int kx1, kw1; | |||
| getKeyPos (rangeStart, kx1, kw1); | |||
| if (kx2 - kx1 <= w) | |||
| { | |||
| firstKey = (float) rangeStart; | |||
| sendChangeMessage(); | |||
| repaint(); | |||
| } | |||
| } | |||
| scrollDown->setVisible (canScroll && firstKey > (float) rangeStart); | |||
| xOffset = 0; | |||
| if (canScroll) | |||
| { | |||
| const int scrollButtonW = jmin (12, w / 2); | |||
| Rectangle<int> r (getLocalBounds()); | |||
| if (orientation == horizontalKeyboard) | |||
| { | |||
| scrollDown->setBounds (r.removeFromLeft (scrollButtonW)); | |||
| scrollUp ->setBounds (r.removeFromRight (scrollButtonW)); | |||
| } | |||
| else if (orientation == verticalKeyboardFacingLeft) | |||
| { | |||
| scrollDown->setBounds (r.removeFromTop (scrollButtonW)); | |||
| scrollUp ->setBounds (r.removeFromBottom (scrollButtonW)); | |||
| } | |||
| else | |||
| { | |||
| scrollDown->setBounds (r.removeFromBottom (scrollButtonW)); | |||
| scrollUp ->setBounds (r.removeFromTop (scrollButtonW)); | |||
| } | |||
| int endOfLastKey, kw; | |||
| getKeyPos (rangeEnd, endOfLastKey, kw); | |||
| endOfLastKey += kw; | |||
| float mousePositionVelocity; | |||
| const int spaceAvailable = w; | |||
| const int lastStartKey = remappedXYToNote (Point<int> (endOfLastKey - spaceAvailable, 0), mousePositionVelocity) + 1; | |||
| if (lastStartKey >= 0 && ((int) firstKey) > lastStartKey) | |||
| { | |||
| firstKey = (float) jlimit (rangeStart, rangeEnd, lastStartKey); | |||
| sendChangeMessage(); | |||
| } | |||
| int newOffset = 0; | |||
| getKeyPos ((int) firstKey, newOffset, kw); | |||
| xOffset = newOffset; | |||
| } | |||
| else | |||
| { | |||
| firstKey = (float) rangeStart; | |||
| } | |||
| getKeyPos (rangeEnd, kx2, kw2); | |||
| scrollUp->setVisible (canScroll && kx2 > w); | |||
| repaint(); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void MidiKeyboardComponent::handleNoteOn (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/, float /*velocity*/) | |||
| { | |||
| shouldCheckState = true; // (probably being called from the audio thread, so avoid blocking in here) | |||
| } | |||
| void MidiKeyboardComponent::handleNoteOff (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/) | |||
| { | |||
| shouldCheckState = true; // (probably being called from the audio thread, so avoid blocking in here) | |||
| } | |||
| //============================================================================== | |||
| void MidiKeyboardComponent::resetAnyKeysInUse() | |||
| { | |||
| if (! keysPressed.isZero()) | |||
| { | |||
| for (int i = 128; --i >= 0;) | |||
| if (keysPressed[i]) | |||
| state.noteOff (midiChannel, i); | |||
| keysPressed.clear(); | |||
| } | |||
| for (int i = mouseDownNotes.size(); --i >= 0;) | |||
| { | |||
| const int noteDown = mouseDownNotes.getUnchecked(i); | |||
| if (noteDown >= 0) | |||
| { | |||
| state.noteOff (midiChannel, noteDown); | |||
| mouseDownNotes.set (i, -1); | |||
| } | |||
| mouseOverNotes.set (i, -1); | |||
| } | |||
| } | |||
| void MidiKeyboardComponent::updateNoteUnderMouse (const MouseEvent& e, bool isDown) | |||
| { | |||
| updateNoteUnderMouse (e.getPosition(), isDown, e.source.getIndex()); | |||
| } | |||
| void MidiKeyboardComponent::updateNoteUnderMouse (Point<int> pos, bool isDown, int fingerNum) | |||
| { | |||
| float mousePositionVelocity = 0.0f; | |||
| const int newNote = xyToNote (pos, mousePositionVelocity); | |||
| const int oldNote = mouseOverNotes.getUnchecked (fingerNum); | |||
| if (oldNote != newNote) | |||
| { | |||
| repaintNote (oldNote); | |||
| repaintNote (newNote); | |||
| mouseOverNotes.set (fingerNum, newNote); | |||
| } | |||
| int oldNoteDown = mouseDownNotes.getUnchecked (fingerNum); | |||
| if (isDown) | |||
| { | |||
| if (newNote != oldNoteDown) | |||
| { | |||
| if (oldNoteDown >= 0) | |||
| { | |||
| mouseDownNotes.set (fingerNum, -1); | |||
| if (! mouseDownNotes.contains (oldNoteDown)) | |||
| state.noteOff (midiChannel, oldNoteDown); | |||
| } | |||
| if (newNote >= 0) | |||
| { | |||
| if (! useMousePositionForVelocity) | |||
| mousePositionVelocity = 1.0f; | |||
| state.noteOn (midiChannel, newNote, mousePositionVelocity * velocity); | |||
| mouseDownNotes.set (fingerNum, newNote); | |||
| } | |||
| } | |||
| } | |||
| else if (oldNoteDown >= 0) | |||
| { | |||
| mouseDownNotes.set (fingerNum, -1); | |||
| if (! mouseDownNotes.contains (oldNoteDown)) | |||
| state.noteOff (midiChannel, oldNoteDown); | |||
| } | |||
| } | |||
| void MidiKeyboardComponent::mouseMove (const MouseEvent& e) | |||
| { | |||
| updateNoteUnderMouse (e, false); | |||
| shouldCheckMousePos = false; | |||
| } | |||
| void MidiKeyboardComponent::mouseDrag (const MouseEvent& e) | |||
| { | |||
| float mousePositionVelocity; | |||
| const int newNote = xyToNote (e.getPosition(), mousePositionVelocity); | |||
| if (newNote >= 0) | |||
| mouseDraggedToKey (newNote, e); | |||
| updateNoteUnderMouse (e, true); | |||
| } | |||
| bool MidiKeyboardComponent::mouseDownOnKey (int, const MouseEvent&) { return true; } | |||
| void MidiKeyboardComponent::mouseDraggedToKey (int, const MouseEvent&) {} | |||
| void MidiKeyboardComponent::mouseUpOnKey (int, const MouseEvent&) {} | |||
| void MidiKeyboardComponent::mouseDown (const MouseEvent& e) | |||
| { | |||
| float mousePositionVelocity; | |||
| const int newNote = xyToNote (e.getPosition(), mousePositionVelocity); | |||
| if (newNote >= 0 && mouseDownOnKey (newNote, e)) | |||
| { | |||
| updateNoteUnderMouse (e, true); | |||
| shouldCheckMousePos = true; | |||
| } | |||
| } | |||
| void MidiKeyboardComponent::mouseUp (const MouseEvent& e) | |||
| { | |||
| updateNoteUnderMouse (e, false); | |||
| shouldCheckMousePos = false; | |||
| float mousePositionVelocity; | |||
| const int note = xyToNote (e.getPosition(), mousePositionVelocity); | |||
| if (note >= 0) | |||
| mouseUpOnKey (note, e); | |||
| } | |||
| void MidiKeyboardComponent::mouseEnter (const MouseEvent& e) | |||
| { | |||
| updateNoteUnderMouse (e, false); | |||
| } | |||
| void MidiKeyboardComponent::mouseExit (const MouseEvent& e) | |||
| { | |||
| updateNoteUnderMouse (e, false); | |||
| } | |||
| void MidiKeyboardComponent::mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel) | |||
| { | |||
| const float amount = (orientation == horizontalKeyboard && wheel.deltaX != 0) | |||
| ? wheel.deltaX : (orientation == verticalKeyboardFacingLeft ? wheel.deltaY | |||
| : -wheel.deltaY); | |||
| setLowestVisibleKeyFloat (firstKey - amount * keyWidth); | |||
| } | |||
| void MidiKeyboardComponent::timerCallback() | |||
| { | |||
| if (shouldCheckState) | |||
| { | |||
| shouldCheckState = false; | |||
| for (int i = rangeStart; i <= rangeEnd; ++i) | |||
| { | |||
| if (keysCurrentlyDrawnDown[i] != state.isNoteOnForChannels (midiInChannelMask, i)) | |||
| { | |||
| keysCurrentlyDrawnDown.setBit (i, state.isNoteOnForChannels (midiInChannelMask, i)); | |||
| repaintNote (i); | |||
| } | |||
| } | |||
| } | |||
| if (shouldCheckMousePos) | |||
| { | |||
| const Array<MouseInputSource>& mouseSources = Desktop::getInstance().getMouseSources(); | |||
| for (MouseInputSource* mi = mouseSources.begin(), * const e = mouseSources.end(); mi != e; ++mi) | |||
| updateNoteUnderMouse (getLocalPoint (nullptr, mi->getScreenPosition()), mi->isDragging(), mi->getIndex()); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void MidiKeyboardComponent::clearKeyMappings() | |||
| { | |||
| resetAnyKeysInUse(); | |||
| keyPressNotes.clear(); | |||
| keyPresses.clear(); | |||
| } | |||
| void MidiKeyboardComponent::setKeyPressForNote (const KeyPress& key, | |||
| const int midiNoteOffsetFromC) | |||
| { | |||
| removeKeyPressForNote (midiNoteOffsetFromC); | |||
| keyPressNotes.add (midiNoteOffsetFromC); | |||
| keyPresses.add (key); | |||
| } | |||
| void MidiKeyboardComponent::removeKeyPressForNote (const int midiNoteOffsetFromC) | |||
| { | |||
| for (int i = keyPressNotes.size(); --i >= 0;) | |||
| { | |||
| if (keyPressNotes.getUnchecked (i) == midiNoteOffsetFromC) | |||
| { | |||
| keyPressNotes.remove (i); | |||
| keyPresses.remove (i); | |||
| } | |||
| } | |||
| } | |||
| void MidiKeyboardComponent::setKeyPressBaseOctave (const int newOctaveNumber) | |||
| { | |||
| jassert (newOctaveNumber >= 0 && newOctaveNumber <= 10); | |||
| keyMappingOctave = newOctaveNumber; | |||
| } | |||
| bool MidiKeyboardComponent::keyStateChanged (const bool /*isKeyDown*/) | |||
| { | |||
| bool keyPressUsed = false; | |||
| for (int i = keyPresses.size(); --i >= 0;) | |||
| { | |||
| const int note = 12 * keyMappingOctave + keyPressNotes.getUnchecked (i); | |||
| if (keyPresses.getReference(i).isCurrentlyDown()) | |||
| { | |||
| if (! keysPressed [note]) | |||
| { | |||
| keysPressed.setBit (note); | |||
| state.noteOn (midiChannel, note, velocity); | |||
| keyPressUsed = true; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| if (keysPressed [note]) | |||
| { | |||
| keysPressed.clearBit (note); | |||
| state.noteOff (midiChannel, note); | |||
| keyPressUsed = true; | |||
| } | |||
| } | |||
| } | |||
| return keyPressUsed; | |||
| } | |||
| void MidiKeyboardComponent::focusLost (FocusChangeType) | |||
| { | |||
| resetAnyKeysInUse(); | |||
| } | |||
| @@ -0,0 +1,409 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2013 - Raw Material Software Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_MIDIKEYBOARDCOMPONENT_H_INCLUDED | |||
| #define JUCE_MIDIKEYBOARDCOMPONENT_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| A component that displays a piano keyboard, whose notes can be clicked on. | |||
| This component will mimic a physical midi keyboard, showing the current state of | |||
| a MidiKeyboardState object. When the on-screen keys are clicked on, it will play these | |||
| notes by calling the noteOn() and noteOff() methods of its MidiKeyboardState object. | |||
| Another feature is that the computer keyboard can also be used to play notes. By | |||
| default it maps the top two rows of a standard querty keyboard to the notes, but | |||
| these can be remapped if needed. It will only respond to keypresses when it has | |||
| the keyboard focus, so to disable this feature you can call setWantsKeyboardFocus (false). | |||
| The component is also a ChangeBroadcaster, so if you want to be informed when the | |||
| keyboard is scrolled, you can register a ChangeListener for callbacks. | |||
| @see MidiKeyboardState | |||
| */ | |||
| class JUCE_API MidiKeyboardComponent : public Component, | |||
| public MidiKeyboardStateListener, | |||
| public ChangeBroadcaster, | |||
| private Timer | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** The direction of the keyboard. | |||
| @see setOrientation | |||
| */ | |||
| enum Orientation | |||
| { | |||
| horizontalKeyboard, | |||
| verticalKeyboardFacingLeft, | |||
| verticalKeyboardFacingRight, | |||
| }; | |||
| /** Creates a MidiKeyboardComponent. | |||
| @param state the midi keyboard model that this component will represent | |||
| @param orientation whether the keyboard is horizonal or vertical | |||
| */ | |||
| MidiKeyboardComponent (MidiKeyboardState& state, | |||
| Orientation orientation); | |||
| /** Destructor. */ | |||
| ~MidiKeyboardComponent(); | |||
| //============================================================================== | |||
| /** Changes the velocity used in midi note-on messages that are triggered by clicking | |||
| on the component. | |||
| Values are 0 to 1.0, where 1.0 is the heaviest. | |||
| @see setMidiChannel | |||
| */ | |||
| void setVelocity (float velocity, bool useMousePositionForVelocity); | |||
| /** Changes the midi channel number that will be used for events triggered by clicking | |||
| on the component. | |||
| The channel must be between 1 and 16 (inclusive). This is the channel that will be | |||
| passed on to the MidiKeyboardState::noteOn() method when the user clicks the component. | |||
| Although this is the channel used for outgoing events, the component can display | |||
| incoming events from more than one channel - see setMidiChannelsToDisplay() | |||
| @see setVelocity | |||
| */ | |||
| void setMidiChannel (int midiChannelNumber); | |||
| /** Returns the midi channel that the keyboard is using for midi messages. | |||
| @see setMidiChannel | |||
| */ | |||
| int getMidiChannel() const noexcept { return midiChannel; } | |||
| /** Sets a mask to indicate which incoming midi channels should be represented by | |||
| key movements. | |||
| The mask is a set of bits, where bit 0 = midi channel 1, bit 1 = midi channel 2, etc. | |||
| If the MidiKeyboardState has a key down for any of the channels whose bits are set | |||
| in this mask, the on-screen keys will also go down. | |||
| By default, this mask is set to 0xffff (all channels displayed). | |||
| @see setMidiChannel | |||
| */ | |||
| void setMidiChannelsToDisplay (int midiChannelMask); | |||
| /** Returns the current set of midi channels represented by the component. | |||
| This is the value that was set with setMidiChannelsToDisplay(). | |||
| */ | |||
| int getMidiChannelsToDisplay() const noexcept { return midiInChannelMask; } | |||
| //============================================================================== | |||
| /** Changes the width used to draw the white keys. */ | |||
| void setKeyWidth (float widthInPixels); | |||
| /** Returns the width that was set by setKeyWidth(). */ | |||
| float getKeyWidth() const noexcept { return keyWidth; } | |||
| /** Changes the keyboard's current direction. */ | |||
| void setOrientation (Orientation newOrientation); | |||
| /** Returns the keyboard's current direction. */ | |||
| Orientation getOrientation() const noexcept { return orientation; } | |||
| /** Sets the range of midi notes that the keyboard will be limited to. | |||
| By default the range is 0 to 127 (inclusive), but you can limit this if you | |||
| only want a restricted set of the keys to be shown. | |||
| Note that the values here are inclusive and must be between 0 and 127. | |||
| */ | |||
| void setAvailableRange (int lowestNote, | |||
| int highestNote); | |||
| /** Returns the first note in the available range. | |||
| @see setAvailableRange | |||
| */ | |||
| int getRangeStart() const noexcept { return rangeStart; } | |||
| /** Returns the last note in the available range. | |||
| @see setAvailableRange | |||
| */ | |||
| int getRangeEnd() const noexcept { return rangeEnd; } | |||
| /** If the keyboard extends beyond the size of the component, this will scroll | |||
| it to show the given key at the start. | |||
| Whenever the keyboard's position is changed, this will use the ChangeBroadcaster | |||
| base class to send a callback to any ChangeListeners that have been registered. | |||
| */ | |||
| void setLowestVisibleKey (int noteNumber); | |||
| /** Returns the number of the first key shown in the component. | |||
| @see setLowestVisibleKey | |||
| */ | |||
| int getLowestVisibleKey() const noexcept { return (int) firstKey; } | |||
| /** Returns the length of the black notes. | |||
| This will be their vertical or horizontal length, depending on the keyboard's orientation. | |||
| */ | |||
| int getBlackNoteLength() const noexcept { return blackNoteLength; } | |||
| /** If set to true, then scroll buttons will appear at either end of the keyboard | |||
| if there are too many notes to fit them all in the component at once. | |||
| */ | |||
| void setScrollButtonsVisible (bool canScroll); | |||
| //============================================================================== | |||
| /** A set of colour IDs to use to change the colour of various aspects of the keyboard. | |||
| These constants can be used either via the Component::setColour(), or LookAndFeel::setColour() | |||
| methods. | |||
| @see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour | |||
| */ | |||
| enum ColourIds | |||
| { | |||
| whiteNoteColourId = 0x1005000, | |||
| blackNoteColourId = 0x1005001, | |||
| keySeparatorLineColourId = 0x1005002, | |||
| mouseOverKeyOverlayColourId = 0x1005003, /**< This colour will be overlaid on the normal note colour. */ | |||
| keyDownOverlayColourId = 0x1005004, /**< This colour will be overlaid on the normal note colour. */ | |||
| textLabelColourId = 0x1005005, | |||
| upDownButtonBackgroundColourId = 0x1005006, | |||
| upDownButtonArrowColourId = 0x1005007, | |||
| shadowColourId = 0x1005008 | |||
| }; | |||
| /** Returns the position within the component of the left-hand edge of a key. | |||
| Depending on the keyboard's orientation, this may be a horizontal or vertical | |||
| distance, in either direction. | |||
| */ | |||
| int getKeyStartPosition (int midiNoteNumber) const; | |||
| /** Returns the key at a given coordinate. */ | |||
| int getNoteAtPosition (Point<int> position); | |||
| //============================================================================== | |||
| /** Deletes all key-mappings. | |||
| @see setKeyPressForNote | |||
| */ | |||
| void clearKeyMappings(); | |||
| /** Maps a key-press to a given note. | |||
| @param key the key that should trigger the note | |||
| @param midiNoteOffsetFromC how many semitones above C the triggered note should | |||
| be. The actual midi note that gets played will be | |||
| this value + (12 * the current base octave). To change | |||
| the base octave, see setKeyPressBaseOctave() | |||
| */ | |||
| void setKeyPressForNote (const KeyPress& key, | |||
| int midiNoteOffsetFromC); | |||
| /** Removes any key-mappings for a given note. | |||
| For a description of what the note number means, see setKeyPressForNote(). | |||
| */ | |||
| void removeKeyPressForNote (int midiNoteOffsetFromC); | |||
| /** Changes the base note above which key-press-triggered notes are played. | |||
| The set of key-mappings that trigger notes can be moved up and down to cover | |||
| the entire scale using this method. | |||
| The value passed in is an octave number between 0 and 10 (inclusive), and | |||
| indicates which C is the base note to which the key-mapped notes are | |||
| relative. | |||
| */ | |||
| void setKeyPressBaseOctave (int newOctaveNumber); | |||
| /** This sets the octave number which is shown as the octave number for middle C. | |||
| This affects only the default implementation of getWhiteNoteText(), which | |||
| passes this octave number to MidiMessage::getMidiNoteName() in order to | |||
| get the note text. See MidiMessage::getMidiNoteName() for more info about | |||
| the parameter. | |||
| By default this value is set to 3. | |||
| @see getOctaveForMiddleC | |||
| */ | |||
| void setOctaveForMiddleC (int octaveNumForMiddleC); | |||
| /** This returns the value set by setOctaveForMiddleC(). | |||
| @see setOctaveForMiddleC | |||
| */ | |||
| int getOctaveForMiddleC() const noexcept { return octaveNumForMiddleC; } | |||
| //============================================================================== | |||
| /** @internal */ | |||
| void paint (Graphics&) override; | |||
| /** @internal */ | |||
| void resized() override; | |||
| /** @internal */ | |||
| void mouseMove (const MouseEvent&) override; | |||
| /** @internal */ | |||
| void mouseDrag (const MouseEvent&) override; | |||
| /** @internal */ | |||
| void mouseDown (const MouseEvent&) override; | |||
| /** @internal */ | |||
| void mouseUp (const MouseEvent&) override; | |||
| /** @internal */ | |||
| void mouseEnter (const MouseEvent&) override; | |||
| /** @internal */ | |||
| void mouseExit (const MouseEvent&) override; | |||
| /** @internal */ | |||
| void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override; | |||
| /** @internal */ | |||
| void timerCallback() override; | |||
| /** @internal */ | |||
| bool keyStateChanged (bool isKeyDown) override; | |||
| /** @internal */ | |||
| void focusLost (FocusChangeType) override; | |||
| /** @internal */ | |||
| void handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override; | |||
| /** @internal */ | |||
| void handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber) override; | |||
| /** @internal */ | |||
| void colourChanged() override; | |||
| protected: | |||
| //============================================================================== | |||
| /** Draws a white note in the given rectangle. | |||
| isOver indicates whether the mouse is over the key, isDown indicates whether the key is | |||
| currently pressed down. | |||
| When doing this, be sure to note the keyboard's orientation. | |||
| */ | |||
| virtual void drawWhiteNote (int midiNoteNumber, | |||
| Graphics& g, | |||
| int x, int y, int w, int h, | |||
| bool isDown, bool isOver, | |||
| const Colour& lineColour, | |||
| const Colour& textColour); | |||
| /** Draws a black note in the given rectangle. | |||
| isOver indicates whether the mouse is over the key, isDown indicates whether the key is | |||
| currently pressed down. | |||
| When doing this, be sure to note the keyboard's orientation. | |||
| */ | |||
| virtual void drawBlackNote (int midiNoteNumber, | |||
| Graphics& g, | |||
| int x, int y, int w, int h, | |||
| bool isDown, bool isOver, | |||
| const Colour& noteFillColour); | |||
| /** Allows text to be drawn on the white notes. | |||
| By default this is used to label the C in each octave, but could be used for other things. | |||
| @see setOctaveForMiddleC | |||
| */ | |||
| virtual String getWhiteNoteText (const int midiNoteNumber); | |||
| /** Draws the up and down buttons that change the base note. */ | |||
| virtual void drawUpDownButton (Graphics& g, int w, int h, | |||
| const bool isMouseOver, | |||
| const bool isButtonPressed, | |||
| const bool movesOctavesUp); | |||
| /** Callback when the mouse is clicked on a key. | |||
| You could use this to do things like handle right-clicks on keys, etc. | |||
| Return true if you want the click to trigger the note, or false if you | |||
| want to handle it yourself and not have the note played. | |||
| @see mouseDraggedToKey | |||
| */ | |||
| virtual bool mouseDownOnKey (int midiNoteNumber, const MouseEvent& e); | |||
| /** Callback when the mouse is dragged from one key onto another. | |||
| @see mouseDownOnKey | |||
| */ | |||
| virtual void mouseDraggedToKey (int midiNoteNumber, const MouseEvent& e); | |||
| /** Callback when the mouse is released from a key. | |||
| @see mouseDownOnKey | |||
| */ | |||
| virtual void mouseUpOnKey (int midiNoteNumber, const MouseEvent& e); | |||
| /** Calculates the positon of a given midi-note. | |||
| This can be overridden to create layouts with custom key-widths. | |||
| @param midiNoteNumber the note to find | |||
| @param keyWidth the desired width in pixels of one key - see setKeyWidth() | |||
| @param x the x position of the left-hand edge of the key (this method | |||
| always works in terms of a horizontal keyboard) | |||
| @param w the width of the key | |||
| */ | |||
| virtual void getKeyPosition (int midiNoteNumber, float keyWidth, | |||
| int& x, int& w) const; | |||
| private: | |||
| //============================================================================== | |||
| friend class MidiKeyboardUpDownButton; | |||
| MidiKeyboardState& state; | |||
| int xOffset, blackNoteLength; | |||
| float keyWidth; | |||
| Orientation orientation; | |||
| int midiChannel, midiInChannelMask; | |||
| float velocity; | |||
| Array<int> mouseOverNotes, mouseDownNotes; | |||
| BigInteger keysPressed, keysCurrentlyDrawnDown; | |||
| bool shouldCheckState; | |||
| int rangeStart, rangeEnd; | |||
| float firstKey; | |||
| bool canScroll, useMousePositionForVelocity, shouldCheckMousePos; | |||
| ScopedPointer<Button> scrollDown, scrollUp; | |||
| Array<KeyPress> keyPresses; | |||
| Array<int> keyPressNotes; | |||
| int keyMappingOctave, octaveNumForMiddleC; | |||
| static const uint8 whiteNotes[]; | |||
| static const uint8 blackNotes[]; | |||
| void getKeyPos (int midiNoteNumber, int& x, int& w) const; | |||
| int xyToNote (Point<int>, float& mousePositionVelocity); | |||
| int remappedXYToNote (Point<int>, float& mousePositionVelocity) const; | |||
| void resetAnyKeysInUse(); | |||
| void updateNoteUnderMouse (Point<int>, bool isDown, int fingerNum); | |||
| void updateNoteUnderMouse (const MouseEvent&, bool isDown); | |||
| void repaintNote (const int midiNoteNumber); | |||
| void setLowestVisibleKeyFloat (float noteNumber); | |||
| Rectangle<int> getWhiteNotePos (int noteNumber) const; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiKeyboardComponent) | |||
| }; | |||
| #endif // JUCE_MIDIKEYBOARDCOMPONENT_H_INCLUDED | |||
| @@ -0,0 +1,181 @@ | |||
| /* | |||
| * Carla Native Plugins | |||
| * Copyright (C) 2013-2014 Filipe Coelho <falktx@falktx.com> | |||
| * | |||
| * This program is free software; you can redistribute it and/or | |||
| * modify it under the terms of the GNU General Public License as | |||
| * published by the Free Software Foundation; either version 2 of | |||
| * the License, or any later version. | |||
| * | |||
| * This program is distributed in the hope that it will be useful, | |||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
| * GNU General Public License for more details. | |||
| * | |||
| * For a full copy of the GNU General Public License see the doc/GPL.txt file. | |||
| */ | |||
| #include "CarlaNative.hpp" | |||
| #include "juce_audio_processors.h" | |||
| #include "juce_gui_extra.h" | |||
| #include "JucePluginWindow.hpp" | |||
| using namespace juce; | |||
| // ----------------------------------------------------------------------- | |||
| #include "juce-host/FilterGraph.h" | |||
| #include "juce-host/InternalFilters.h" | |||
| #include "juce-host/GraphEditorPanel.h" | |||
| // ----------------------------------------------------------------------- | |||
| class JucePatchbayPlugin : public NativePluginClass | |||
| { | |||
| public: | |||
| JucePatchbayPlugin(const NativeHostDescriptor* const host) | |||
| : NativePluginClass(host), | |||
| graph(formatManager), | |||
| fAudioBuffer(1, 0) | |||
| { | |||
| formatManager.addDefaultFormats(); | |||
| formatManager.addFormat(new InternalPluginFormat()); | |||
| graph.ready(); | |||
| graph.getGraph().setPlayConfigDetails(2, 2, getSampleRate(), static_cast<int>(getBufferSize())); | |||
| fMidiBuffer.ensureSize(512*2); | |||
| fMidiBuffer.clear(); | |||
| } | |||
| ~JucePatchbayPlugin() override | |||
| { | |||
| } | |||
| protected: | |||
| // ------------------------------------------------------------------- | |||
| // Plugin process calls | |||
| void activate() override | |||
| { | |||
| graph.getGraph().prepareToPlay(getSampleRate(), static_cast<int>(getBufferSize())); | |||
| fAudioBuffer.setSize(2, static_cast<int>(getBufferSize())); | |||
| } | |||
| void deactivate() override | |||
| { | |||
| graph.getGraph().releaseResources(); | |||
| } | |||
| void process(float** inBuffer, float** const outBuffer, const uint32_t frames, const NativeMidiEvent* const, const uint32_t) override | |||
| { | |||
| fAudioBuffer.copyFrom(0, 0, inBuffer[0], static_cast<int>(frames)); | |||
| fAudioBuffer.copyFrom(1, 0, inBuffer[1], static_cast<int>(frames)); | |||
| graph.getGraph().processBlock(fAudioBuffer, fMidiBuffer); | |||
| FloatVectorOperations::copy(outBuffer[0], fAudioBuffer.getSampleData(0), static_cast<int>(frames)); | |||
| FloatVectorOperations::copy(outBuffer[1], fAudioBuffer.getSampleData(1), static_cast<int>(frames)); | |||
| } | |||
| // ------------------------------------------------------------------- | |||
| // Plugin UI calls | |||
| void uiShow(const bool show) override | |||
| { | |||
| if (show) | |||
| { | |||
| if (fWindow == nullptr) | |||
| { | |||
| fWindow = new JucePluginWindow(); | |||
| fWindow->setName(getUiName()); | |||
| fWindow->setResizable(true, false); | |||
| } | |||
| if (fComponent == nullptr) | |||
| { | |||
| fComponent = new GraphDocumentComponent(graph); | |||
| fComponent->setSize(300, 300); | |||
| } | |||
| fWindow->show(fComponent); | |||
| } | |||
| else if (fWindow != nullptr) | |||
| { | |||
| fWindow->hide(); | |||
| fComponent = nullptr; | |||
| fWindow = nullptr; | |||
| } | |||
| } | |||
| void uiIdle() override | |||
| { | |||
| if (fWindow == nullptr) | |||
| return; | |||
| if (fWindow->wasClosedByUser()) | |||
| { | |||
| uiShow(false); | |||
| uiClosed(); | |||
| } | |||
| } | |||
| private: | |||
| AudioPluginFormatManager formatManager; | |||
| FilterGraph graph; | |||
| AudioSampleBuffer fAudioBuffer; | |||
| MidiBuffer fMidiBuffer; | |||
| ScopedPointer<GraphDocumentComponent> fComponent; | |||
| ScopedPointer<JucePluginWindow> fWindow; | |||
| //OwnedArray <PluginDescription> internalTypes; | |||
| //KnownPluginList knownPluginList; | |||
| //KnownPluginList::SortMethod pluginSortMethod; | |||
| PluginClassEND(JucePatchbayPlugin) | |||
| CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(JucePatchbayPlugin) | |||
| }; | |||
| // ----------------------------------------------------------------------- | |||
| static const NativePluginDescriptor jucePatchbayDesc = { | |||
| /* category */ PLUGIN_CATEGORY_UTILITY, | |||
| /* hints */ static_cast<NativePluginHints>(PLUGIN_IS_SYNTH|PLUGIN_HAS_UI/*|PLUGIN_USES_STATE*/), | |||
| /* supports */ static_cast<NativePluginSupports>(0x0), | |||
| /* audioIns */ 2, | |||
| /* audioOuts */ 2, | |||
| /* midiIns */ 1, | |||
| /* midiOuts */ 1, | |||
| /* paramIns */ 0, | |||
| /* paramOuts */ 0, | |||
| /* name */ "Juce Patchbay", | |||
| /* label */ "jucePatchbay", | |||
| /* maker */ "falkTX", | |||
| /* copyright */ "GNU GPL v2+", | |||
| PluginDescriptorFILL(JucePatchbayPlugin) | |||
| }; | |||
| // ----------------------------------------------------------------------- | |||
| CARLA_EXPORT | |||
| void carla_register_native_plugin_jucePatchbay() | |||
| { | |||
| carla_register_native_plugin(&jucePatchbayDesc); | |||
| } | |||
| // ----------------------------------------------------------------------- | |||
| #include "juce-host/juce_MidiKeyboardComponent.h" | |||
| #include "juce-host/juce_MidiKeyboardComponent.cpp" | |||
| #include "juce-host/FilterGraph.cpp" | |||
| #include "juce-host/InternalFilters.cpp" | |||
| #include "juce-host/GraphEditorPanel.cpp" | |||
| // ----------------------------------------------------------------------- | |||
| @@ -1019,8 +1019,6 @@ protected: | |||
| void uiShow(const bool show) override | |||
| { | |||
| MessageManagerLock mmLock; | |||
| if (show) | |||
| { | |||
| if (fWindow == nullptr) | |||
| @@ -24,11 +24,11 @@ | |||
| namespace juce { | |||
| class PluginWindow : public DocumentWindow | |||
| class JucePluginWindow : public DocumentWindow | |||
| { | |||
| public: | |||
| PluginWindow() | |||
| : DocumentWindow("PluginWindow", Colour(50, 50, 200), DocumentWindow::closeButton, false), | |||
| JucePluginWindow() | |||
| : DocumentWindow("JucePluginWindow", Colour(50, 50, 200), DocumentWindow::closeButton, false), | |||
| fClosed(false) | |||
| { | |||
| setVisible(false); | |||
| @@ -38,12 +38,16 @@ public: | |||
| setUsingNativeTitleBar(true); | |||
| } | |||
| void show(Component* const comp) | |||
| void show(Component* const comp, const bool useContentOwned = false) | |||
| { | |||
| fClosed = false; | |||
| centreWithSize(comp->getWidth(), comp->getHeight()); | |||
| setContentNonOwned(comp, true); | |||
| if (useContentOwned) | |||
| setContentOwned(comp, false); | |||
| else | |||
| setContentNonOwned(comp, true); | |||
| if (! isOnDesktop()) | |||
| addToDesktop(); | |||
| @@ -75,12 +79,13 @@ protected: | |||
| private: | |||
| volatile bool fClosed; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginWindow) | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(JucePluginWindow) | |||
| }; | |||
| } // namespace juce | |||
| typedef juce::PluginWindow JucePluginWindow; | |||
| using juce::JucePluginWindow; | |||
| //typedef juce::PluginWindow JucePluginWindow; | |||
| // ----------------------------------------------------------------------- | |||