diff --git a/examples/DemoRunner/Source/UI/MainComponent.cpp b/examples/DemoRunner/Source/UI/MainComponent.cpp index d47bc21e50..5bd57584b7 100644 --- a/examples/DemoRunner/Source/UI/MainComponent.cpp +++ b/examples/DemoRunner/Source/UI/MainComponent.cpp @@ -199,7 +199,7 @@ public: demos.deselectAllRows(); demos.setHeaderComponent (categoryName.isEmpty() ? nullptr - : new Header (*this)); + : std::make_unique
(*this)); demos.updateContent(); } diff --git a/examples/Plugins/MidiLoggerPluginDemo.h b/examples/Plugins/MidiLoggerPluginDemo.h new file mode 100644 index 0000000000..ce49c7ebe7 --- /dev/null +++ b/examples/Plugins/MidiLoggerPluginDemo.h @@ -0,0 +1,331 @@ +/* + ============================================================================== + + This file is part of the JUCE examples. + Copyright (c) 2017 - ROLI Ltd. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, + WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR + PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +/******************************************************************************* + The block below describes the properties of this PIP. A PIP is a short snippet + of code that can be read by the Projucer and used to generate a JUCE project. + + BEGIN_JUCE_PIP_METADATA + + name: MIDILogger + version: 1.0.0 + vendor: JUCE + website: http://juce.com + description: Logs incoming MIDI messages. + + dependencies: juce_audio_basics, juce_audio_devices, juce_audio_formats, + juce_audio_plugin_client, juce_audio_processors, + juce_audio_utils, juce_core, juce_data_structures, + juce_events, juce_graphics, juce_gui_basics, juce_gui_extra + exporters: xcode_mac, vs2019, linux_make + + moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1 + + type: AudioProcessor + mainClass: MidiLoggerPluginDemoProcessor + + useLocalCopy: 1 + + pluginCharacteristics: pluginWantsMidiIn, pluginProducesMidiOut + + END_JUCE_PIP_METADATA + +*******************************************************************************/ + +#pragma once + +#include + +class MidiQueue +{ +public: + void push (const MidiBuffer& buffer) + { + for (const auto metadata : buffer) + fifo.write (1).forEach ([&] (int dest) { messages[(size_t) dest] = metadata.getMessage(); }); + } + + template + void pop (OutputIt out) + { + fifo.read (fifo.getNumReady()).forEach ([&] (int source) { *out++ = messages[(size_t) source]; }); + } + +private: + static constexpr auto queueSize = 1 << 14; + AbstractFifo fifo { queueSize }; + std::vector messages = std::vector (queueSize); +}; + +//============================================================================== +class MidiTable : public Component, + private TableListBoxModel +{ +public: + MidiTable() + { + addAndMakeVisible (table); + + table.setModel (this); + table.setClickingTogglesRowSelection (false); + table.setHeader ([&] + { + auto header = std::make_unique(); + header->addColumn ("Message", messageColumn, 200, 30, -1, TableHeaderComponent::notSortable); + header->addColumn ("Channel", channelColumn, 100, 30, -1, TableHeaderComponent::notSortable); + header->addColumn ("Data", dataColumn, 200, 30, -1, TableHeaderComponent::notSortable); + return header; + }()); + } + + void resized() override { table.setBounds (getLocalBounds()); } + + template + void addMessages (It begin, It end) + { + const auto numNewMessages = (int) std::distance (begin, end); + const auto numToAdd = juce::jmin (numToStore, numNewMessages); + const auto numToRemove = jmax (0, (int) messages.size() + numToAdd - numToStore); + messages.erase (messages.begin(), std::next (messages.begin(), numToRemove)); + messages.insert (messages.end(), end - numToAdd, end); + table.updateContent(); + } + + void clear() + { + messages.clear(); + table.updateContent(); + } + +private: + enum + { + messageColumn = 1, + channelColumn, + dataColumn + }; + + int getNumRows() override { return (int) messages.size(); } + + void paintRowBackground (Graphics&, int, int, int, bool) override {} + void paintCell (Graphics&, int, int, int, int, bool) override {} + + Component* refreshComponentForCell (int rowNumber, + int columnId, + bool, + Component* existingComponentToUpdate) override + { + delete existingComponentToUpdate; + + const auto index = (int) messages.size() - 1 - rowNumber; + const auto message = messages[(size_t) index]; + + return new Label ({}, [&] + { + switch (columnId) + { + case messageColumn: return getEventString (message); + case channelColumn: return String (message.getChannel()); + case dataColumn: return getDataString (message); + default: break; + } + + jassertfalse; + return String(); + }()); + } + + static String getEventString (const MidiMessage& m) + { + if (m.isNoteOn()) return "Note on"; + if (m.isNoteOff()) return "Note off"; + if (m.isProgramChange()) return "Program change"; + if (m.isPitchWheel()) return "Pitch wheel"; + if (m.isAftertouch()) return "Aftertouch"; + if (m.isChannelPressure()) return "Channel pressure"; + if (m.isAllNotesOff()) return "All notes off"; + if (m.isAllSoundOff()) return "All sound off"; + if (m.isMetaEvent()) return "Meta event"; + + if (m.isController()) + { + const auto* name = MidiMessage::getControllerName (m.getControllerNumber()); + return "Controller " + (name == nullptr ? String (m.getControllerNumber()) : String (name)); + } + + return String::toHexString (m.getRawData(), m.getRawDataSize()); + } + + static String getDataString (const MidiMessage& m) + { + if (m.isNoteOn()) return MidiMessage::getMidiNoteName (m.getNoteNumber(), true, true, 3) + " Velocity " + String (m.getVelocity()); + if (m.isNoteOff()) return MidiMessage::getMidiNoteName (m.getNoteNumber(), true, true, 3) + " Velocity " + String (m.getVelocity()); + if (m.isProgramChange()) return String (m.getProgramChangeNumber()); + if (m.isPitchWheel()) return String (m.getPitchWheelValue()); + if (m.isAftertouch()) return MidiMessage::getMidiNoteName (m.getNoteNumber(), true, true, 3) + ": " + String (m.getAfterTouchValue()); + if (m.isChannelPressure()) return String (m.getChannelPressureValue()); + if (m.isController()) return String (m.getControllerValue()); + + return {}; + } + + static constexpr auto numToStore = 1000; + std::vector messages; + + TableListBox table; +}; + +//============================================================================== +class MidiLoggerPluginDemoProcessor : public AudioPluginInstance +{ +public: + MidiLoggerPluginDemoProcessor() + : AudioPluginInstance (BusesProperties()) + { + state.addChild ({ "uiState", { { "width", 500 }, { "height", 300 } }, {} }, -1, nullptr); + } + + void processBlock (AudioBuffer& audio, MidiBuffer& midi) override { process (audio, midi); } + void processBlock (AudioBuffer& audio, MidiBuffer& midi) override { process (audio, midi); } + + bool isBusesLayoutSupported (const BusesLayout&) const override { return true; } + bool isMidiEffect() const override { return true; } + bool hasEditor() const override { return true; } + AudioProcessorEditor* createEditor() override { return new Editor (*this); } + + const String getName() const override { return "MIDILogger"; } + bool acceptsMidi() const override { return true; } + bool producesMidi() const override { return false; } + double getTailLengthSeconds() const override { return 0.0; } + + int getNumPrograms() override { return 0; } + int getCurrentProgram() override { return 0; } + void setCurrentProgram (int) override {} + const String getProgramName (int) override { return {}; } + void changeProgramName (int, const String&) override {} + + void prepareToPlay (double, int) override {} + void releaseResources() override {} + + void getStateInformation (MemoryBlock& destData) override + { + if (auto xmlState = state.createXml()) + copyXmlToBinary (*xmlState, destData); + } + + void setStateInformation (const void* data, int size) override + { + if (auto xmlState = getXmlFromBinary (data, size)) + state = ValueTree::fromXml (*xmlState); + } + + void fillInPluginDescription (PluginDescription& d) const override + { + d.name = getName(); + d.uid = d.name.hashCode(); + d.category = "Utility"; + d.pluginFormatName = "Internal"; + d.manufacturerName = "JUCE"; + d.version = "1.0"; + d.isInstrument = false; + d.numInputChannels = getTotalNumInputChannels(); + d.numOutputChannels = getTotalNumOutputChannels(); + } + +private: + class Editor : public AudioProcessorEditor, + private Timer, + private Value::Listener + { + public: + explicit Editor (MidiLoggerPluginDemoProcessor& ownerIn) + : AudioProcessorEditor (ownerIn), + owner (ownerIn) + { + addAndMakeVisible (table); + addAndMakeVisible (clearButton); + + setResizable (true, true); + lastUIWidth .referTo (owner.state.getChildWithName ("uiState").getPropertyAsValue ("width", nullptr)); + lastUIHeight.referTo (owner.state.getChildWithName ("uiState").getPropertyAsValue ("height", nullptr)); + setSize (lastUIWidth.getValue(), lastUIHeight.getValue()); + + lastUIWidth. addListener (this); + lastUIHeight.addListener (this); + + clearButton.onClick = [&] { table.clear(); }; + + startTimerHz (60); + } + + ~Editor() override + { + stopTimer(); + owner.editorBeingDeleted (this); + } + + void paint (Graphics& g) override + { + g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId)); + } + + void resized() override + { + auto bounds = getLocalBounds(); + + clearButton.setBounds (bounds.removeFromBottom (30).withSizeKeepingCentre (50, 24)); + table.setBounds (bounds); + + lastUIWidth = getWidth(); + lastUIHeight = getHeight(); + } + + private: + void timerCallback() override + { + std::vector messages; + owner.queue.pop (std::back_inserter (messages)); + table.addMessages (messages.begin(), messages.end()); + } + + void valueChanged (Value&) override + { + setSize (lastUIWidth.getValue(), lastUIHeight.getValue()); + } + + MidiLoggerPluginDemoProcessor& owner; + + MidiTable table; + TextButton clearButton { "Clear" }; + + Value lastUIWidth, lastUIHeight; + }; + + template + void process (AudioBuffer& audio, MidiBuffer& midi) + { + audio.clear(); + queue.push (midi); + } + + ValueTree state { "state" }; + MidiQueue queue; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiLoggerPluginDemoProcessor) +}; diff --git a/extras/AudioPluginHost/Source/Plugins/InternalPlugins.cpp b/extras/AudioPluginHost/Source/Plugins/InternalPlugins.cpp index a525f45d2c..d8b7637f95 100644 --- a/extras/AudioPluginHost/Source/Plugins/InternalPlugins.cpp +++ b/extras/AudioPluginHost/Source/Plugins/InternalPlugins.cpp @@ -20,6 +20,8 @@ #include "InternalPlugins.h" #include "PluginGraph.h" +#include "../../../../examples/Plugins/MidiLoggerPluginDemo.h" + //============================================================================== class InternalPlugin : public AudioPluginInstance { @@ -354,17 +356,23 @@ InternalPluginFormat::InternalPluginFormat() AudioProcessorGraph::AudioGraphIOProcessor p (AudioProcessorGraph::AudioGraphIOProcessor::midiOutputNode); p.fillInPluginDescription (midiOutDesc); } + + { + MidiLoggerPluginDemoProcessor p; + p.fillInPluginDescription (midiMonitorDesc); + } } std::unique_ptr InternalPluginFormat::createInstance (const String& name) { - if (name == audioOutDesc.name) return std::make_unique (AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode); - if (name == audioInDesc.name) return std::make_unique (AudioProcessorGraph::AudioGraphIOProcessor::audioInputNode); - if (name == midiInDesc.name) return std::make_unique (AudioProcessorGraph::AudioGraphIOProcessor::midiInputNode); - if (name == midiOutDesc.name) return std::make_unique (AudioProcessorGraph::AudioGraphIOProcessor::midiOutputNode); + if (name == audioOutDesc.name) return std::make_unique (AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode); + if (name == audioInDesc.name) return std::make_unique (AudioProcessorGraph::AudioGraphIOProcessor::audioInputNode); + if (name == midiInDesc.name) return std::make_unique (AudioProcessorGraph::AudioGraphIOProcessor::midiInputNode); + if (name == midiOutDesc.name) return std::make_unique (AudioProcessorGraph::AudioGraphIOProcessor::midiOutputNode); + if (name == midiMonitorDesc.name) return std::make_unique(); - if (name == SineWaveSynth::getIdentifier()) return std::make_unique (SineWaveSynth::getPluginDescription()); - if (name == ReverbPlugin::getIdentifier()) return std::make_unique (ReverbPlugin::getPluginDescription()); + if (name == SineWaveSynth::getIdentifier()) return std::make_unique (SineWaveSynth::getPluginDescription()); + if (name == ReverbPlugin::getIdentifier()) return std::make_unique (ReverbPlugin::getPluginDescription()); return {}; } @@ -384,8 +392,13 @@ bool InternalPluginFormat::requiresUnblockedMessageThreadDuringCreation (const P return false; } -void InternalPluginFormat::getAllTypes (Array& results) +Array InternalPluginFormat::getAllTypes() const { - results.add (audioInDesc, audioOutDesc, midiInDesc, midiOutDesc, - SineWaveSynth::getPluginDescription(), ReverbPlugin::getPluginDescription()); + return { audioInDesc, + audioOutDesc, + midiInDesc, + midiOutDesc, + midiMonitorDesc, + SineWaveSynth::getPluginDescription(), + ReverbPlugin::getPluginDescription() }; } diff --git a/extras/AudioPluginHost/Source/Plugins/InternalPlugins.h b/extras/AudioPluginHost/Source/Plugins/InternalPlugins.h index 24a3f8e019..56cbebd10e 100644 --- a/extras/AudioPluginHost/Source/Plugins/InternalPlugins.h +++ b/extras/AudioPluginHost/Source/Plugins/InternalPlugins.h @@ -30,11 +30,10 @@ class InternalPluginFormat : public AudioPluginFormat public: //============================================================================== InternalPluginFormat(); - ~InternalPluginFormat() override {} //============================================================================== - PluginDescription audioInDesc, audioOutDesc, midiInDesc, midiOutDesc; - void getAllTypes (Array&); + PluginDescription audioInDesc, audioOutDesc, midiInDesc, midiOutDesc, midiMonitorDesc; + Array getAllTypes() const; //============================================================================== String getName() const override { return "Internal"; } diff --git a/extras/AudioPluginHost/Source/Plugins/PluginGraph.cpp b/extras/AudioPluginHost/Source/Plugins/PluginGraph.cpp index ce68311637..6407fce641 100644 --- a/extras/AudioPluginHost/Source/Plugins/PluginGraph.cpp +++ b/extras/AudioPluginHost/Source/Plugins/PluginGraph.cpp @@ -144,7 +144,7 @@ PluginWindow* PluginGraph::getOrCreateWindowFor (AudioProcessorGraph::Node* node { auto description = plugin->getPluginDescription(); - if (description.pluginFormatName == "Internal") + if (! plugin->hasEditor() && description.pluginFormatName == "Internal") { getCommandManager().invokeDirectly (CommandIDs::showAudioSettings, false); return nullptr; diff --git a/extras/AudioPluginHost/Source/UI/MainHostWindow.cpp b/extras/AudioPluginHost/Source/UI/MainHostWindow.cpp index f29782cb8a..0e4b30c4b9 100644 --- a/extras/AudioPluginHost/Source/UI/MainHostWindow.cpp +++ b/extras/AudioPluginHost/Source/UI/MainHostWindow.cpp @@ -98,7 +98,7 @@ MainHostWindow::MainHostWindow() setVisible (true); InternalPluginFormat internalFormat; - internalFormat.getAllTypes (internalTypes); + internalTypes = internalFormat.getAllTypes(); if (auto savedPluginList = getAppProperties().getUserSettings()->getXmlValue ("pluginList")) knownPluginList.recreateFromXml (*savedPluginList); diff --git a/extras/Projucer/Source/Project/UI/jucer_FileGroupInformationComponent.h b/extras/Projucer/Source/Project/UI/jucer_FileGroupInformationComponent.h index 511470307c..048ff93a6c 100644 --- a/extras/Projucer/Source/Project/UI/jucer_FileGroupInformationComponent.h +++ b/extras/Projucer/Source/Project/UI/jucer_FileGroupInformationComponent.h @@ -29,8 +29,8 @@ public: : item (group), header (item.getName(), { getIcons().openFolder, Colours::transparentBlack }) { - list.setHeaderComponent (new ListBoxHeader ( { "File", "Binary Resource", "Xcode Resource", "Compile", "Compiler Flag Scheme" }, - { 0.3f, 0.15f, 0.15f, 0.15f, 0.25f } )); + list.setHeaderComponent (std::make_unique (Array { "File", "Binary Resource", "Xcode Resource", "Compile", "Compiler Flag Scheme" }, + Array { 0.3f, 0.15f, 0.15f, 0.15f, 0.25f })); list.setModel (this); list.setColour (ListBox::backgroundColourId, Colours::transparentBlack); addAndMakeVisible (list); diff --git a/extras/Projucer/Source/Project/UI/jucer_ModulesInformationComponent.h b/extras/Projucer/Source/Project/UI/jucer_ModulesInformationComponent.h index 61e1a64f1e..1dd165dd45 100644 --- a/extras/Projucer/Source/Project/UI/jucer_ModulesInformationComponent.h +++ b/extras/Projucer/Source/Project/UI/jucer_ModulesInformationComponent.h @@ -29,9 +29,10 @@ public: : project (p), modulesValueTree (project.getEnabledModules().getState()) { - listHeader = new ListBoxHeader ( { "Module", "Version", "Make Local Copy", "Paths" }, - { 0.25f, 0.2f, 0.2f, 0.35f } ); - list.setHeaderComponent (listHeader); + auto tempHeader = std::make_unique (Array { "Module", "Version", "Make Local Copy", "Paths" }, + Array { 0.25f, 0.2f, 0.2f, 0.35f }); + listHeader = tempHeader.get(); + list.setHeaderComponent (std::move (tempHeader)); list.setModel (this); list.setColour (ListBox::backgroundColourId, Colours::transparentBlack); addAndMakeVisible (list); diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp index 22c53529f9..82d922262a 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp @@ -1377,8 +1377,8 @@ const String AudioProcessorGraph::AudioGraphIOProcessor::getName() const { case audioOutputNode: return "Audio Output"; case audioInputNode: return "Audio Input"; - case midiOutputNode: return "Midi Output"; - case midiInputNode: return "Midi Input"; + case midiOutputNode: return "MIDI Output"; + case midiInputNode: return "MIDI Input"; default: break; } diff --git a/modules/juce_gui_basics/widgets/juce_ListBox.cpp b/modules/juce_gui_basics/widgets/juce_ListBox.cpp index 46cc5854f9..fc61dbd22a 100644 --- a/modules/juce_gui_basics/widgets/juce_ListBox.cpp +++ b/modules/juce_gui_basics/widgets/juce_ListBox.cpp @@ -848,14 +848,11 @@ void ListBox::setOutlineThickness (int newThickness) resized(); } -void ListBox::setHeaderComponent (Component* newHeaderComponent) +void ListBox::setHeaderComponent (std::unique_ptr newHeaderComponent) { - if (headerComponent.get() != newHeaderComponent) - { - headerComponent.reset (newHeaderComponent); - addAndMakeVisible (newHeaderComponent); - ListBox::resized(); - } + headerComponent = std::move (newHeaderComponent); + addAndMakeVisible (headerComponent.get()); + ListBox::resized(); } void ListBox::repaintRow (const int rowNumber) noexcept diff --git a/modules/juce_gui_basics/widgets/juce_ListBox.h b/modules/juce_gui_basics/widgets/juce_ListBox.h index ff68c34b07..aed2ea4d09 100644 --- a/modules/juce_gui_basics/widgets/juce_ListBox.h +++ b/modules/juce_gui_basics/widgets/juce_ListBox.h @@ -484,7 +484,7 @@ public: The component will be deleted when setHeaderComponent() is called with a different component, or when the listbox is deleted. */ - void setHeaderComponent (Component* newHeaderComponent); + void setHeaderComponent (std::unique_ptr newHeaderComponent); /** Returns whatever header component was set with setHeaderComponent(). */ Component* getHeaderComponent() const noexcept { return headerComponent.get(); } diff --git a/modules/juce_gui_basics/widgets/juce_TableListBox.cpp b/modules/juce_gui_basics/widgets/juce_TableListBox.cpp index 1f0931fdb5..2c327dd839 100644 --- a/modules/juce_gui_basics/widgets/juce_TableListBox.cpp +++ b/modules/juce_gui_basics/widgets/juce_TableListBox.cpp @@ -264,7 +264,7 @@ TableListBox::TableListBox (const String& name, TableListBoxModel* const m) { ListBox::model = this; - setHeader (new Header (*this)); + setHeader (std::make_unique
(*this)); } TableListBox::~TableListBox() @@ -280,19 +280,23 @@ void TableListBox::setModel (TableListBoxModel* newModel) } } -void TableListBox::setHeader (TableHeaderComponent* newHeader) +void TableListBox::setHeader (std::unique_ptr newHeader) { - jassert (newHeader != nullptr); // you need to supply a real header for a table! + if (newHeader == nullptr) + { + jassertfalse; // you need to supply a real header for a table! + return; + } Rectangle newBounds (100, 28); if (header != nullptr) newBounds = header->getBounds(); - header = newHeader; + header = newHeader.get(); header->setBounds (newBounds); - setHeaderComponent (header); + setHeaderComponent (std::move (newHeader)); header->addListener (this); } diff --git a/modules/juce_gui_basics/widgets/juce_TableListBox.h b/modules/juce_gui_basics/widgets/juce_TableListBox.h index 0c0a11c104..4295ed9666 100644 --- a/modules/juce_gui_basics/widgets/juce_TableListBox.h +++ b/modules/juce_gui_basics/widgets/juce_TableListBox.h @@ -238,7 +238,7 @@ public: when it's no longer needed. The pointer passed in may not be null. */ - void setHeader (TableHeaderComponent* newHeader); + void setHeader (std::unique_ptr newHeader); /** Changes the height of the table header component. @see getHeaderHeight