From 83a4f74b1facfb9786ff098efbb97758223d3c79 Mon Sep 17 00:00:00 2001 From: hogliux Date: Tue, 10 Jan 2017 12:50:45 +0000 Subject: [PATCH] Added support saving/restoring plugin bus layouts in the audio host demo --- .../audio plugin host/Source/FilterGraph.cpp | 79 +++++++++++++++++++ .../buffers/juce_AudioChannelSet.cpp | 53 +++++++++++++ .../buffers/juce_AudioChannelSet.h | 8 ++ 3 files changed, 140 insertions(+) diff --git a/examples/audio plugin host/Source/FilterGraph.cpp b/examples/audio plugin host/Source/FilterGraph.cpp index f95ef85509..02089a7779 100644 --- a/examples/audio plugin host/Source/FilterGraph.cpp +++ b/examples/audio plugin host/Source/FilterGraph.cpp @@ -288,6 +288,67 @@ void FilterGraph::setLastDocumentOpened (const File& file) } //============================================================================== +static void readBusLayoutFromXml (AudioProcessor::BusesLayout& busesLayout, AudioProcessor* plugin, const XmlElement& xml, const bool isInput) +{ + Array& targetBuses = (isInput ? busesLayout.inputBuses : busesLayout.outputBuses); + int maxNumBuses = 0; + + if (auto* buses = xml.getChildByName (isInput ? "INPUTS" : "OUTPUTS")) + { + forEachXmlChildElementWithTagName (*buses, e, "BUS") + { + const int busIdx = e->getIntAttribute ("index"); + maxNumBuses = jmax (maxNumBuses, busIdx + 1); + + // the number of buses on busesLayout may not be in sync with the plugin after adding buses + // because adding an input bus could also add an output bus + for (int actualIdx = plugin->getBusCount (isInput) - 1; actualIdx < busIdx; ++actualIdx) + if (! plugin->addBus (isInput)) return; + + for (int actualIdx = targetBuses.size() - 1; actualIdx < busIdx; ++actualIdx) + targetBuses.add (plugin->getChannelLayoutOfBus (isInput, busIdx)); + + const String& layout = e->getStringAttribute("layout"); + + if (layout.isNotEmpty()) + targetBuses.getReference (busIdx) = AudioChannelSet::fromAbbreviatedString (layout); + } + } + + // if the plugin has more buses than specified in the xml, then try to remove them! + while (maxNumBuses < targetBuses.size()) + { + if (! plugin->removeBus (isInput)) + return; + + targetBuses.removeLast(); + } +} + +//============================================================================== +static XmlElement* createBusLayoutXml (const AudioProcessor::BusesLayout& layout, const bool isInput) +{ + const Array& buses = (isInput ? layout.inputBuses : layout.outputBuses); + + XmlElement* xml = new XmlElement (isInput ? "INPUTS" : "OUTPUTS"); + + const int n = buses.size(); + for (int busIdx = 0; busIdx < n; ++busIdx) + { + XmlElement* bus = new XmlElement ("BUS"); + bus->setAttribute ("index", busIdx); + + const AudioChannelSet& set = buses.getReference (busIdx); + const String layoutName = set.isDisabled() ? "disabled" : set.getSpeakerArrangementAsString(); + + bus->setAttribute ("layout", layoutName); + + xml->addChildElement (bus); + } + + return xml; +} + static XmlElement* createNodeXml (AudioProcessorGraph::Node* const node) noexcept { AudioPluginInstance* plugin = dynamic_cast (node->getProcessor()); @@ -327,6 +388,14 @@ static XmlElement* createNodeXml (AudioProcessorGraph::Node* const node) noexcep state->addTextElement (m.toBase64Encoding()); e->addChildElement (state); + XmlElement* layouts = new XmlElement ("LAYOUT"); + const AudioProcessor::BusesLayout layout = plugin->getBusesLayout(); + + for (bool isInput : { true, false }) + layouts->addChildElement (createBusLayoutXml (layout, isInput)); + + e->addChildElement (layouts); + return e; } @@ -347,6 +416,16 @@ void FilterGraph::createNodeFromXml (const XmlElement& xml) if (instance == nullptr) return; + if (const XmlElement* const layoutEntity = xml.getChildByName ("LAYOUT")) + { + AudioProcessor::BusesLayout layout = instance->getBusesLayout(); + + for (bool isInput : { true, false }) + readBusLayoutFromXml (layout, instance, *layoutEntity, isInput); + + instance->setBusesLayout (layout); + } + AudioProcessorGraph::Node::Ptr node (graph.addNode (instance, (uint32) xml.getIntAttribute ("uid"))); if (const XmlElement* const state = xml.getChildByName ("STATE")) diff --git a/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp b/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp index 9dde9438cc..932c43cd29 100644 --- a/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp +++ b/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp @@ -114,6 +114,43 @@ String AudioChannelSet::getAbbreviatedChannelTypeName (AudioChannelSet::ChannelT return ""; } +AudioChannelSet::ChannelType AudioChannelSet::getChannelTypeFromAbbreviation (const String& abbr) +{ + if (abbr.length() > 0 && (abbr[0] >= '0' && abbr[0] <= '9')) + return static_cast (static_cast (discreteChannel0) + + abbr.getIntValue() + 1); + + if (abbr == "L") return left; + else if (abbr == "R") return right; + else if (abbr == "C") return centre; + else if (abbr == "Lfe") return LFE; + else if (abbr == "Ls") return leftSurround; + else if (abbr == "Rs") return rightSurround; + else if (abbr == "Lc") return leftCentre; + else if (abbr == "Rc") return rightCentre; + else if (abbr == "Cs") return centreSurround; + else if (abbr == "Lrs") return leftSurroundRear; + else if (abbr == "Rrs") return rightSurroundRear; + else if (abbr == "Tm") return topMiddle; + else if (abbr == "Tfl") return topFrontLeft; + else if (abbr == "Tfc") return topFrontCentre; + else if (abbr == "Tfr") return topFrontRight; + else if (abbr == "Trl") return topRearLeft; + else if (abbr == "Trc") return topRearCentre; + else if (abbr == "Trr") return topRearRight; + else if (abbr == "Wl") return wideLeft; + else if (abbr == "Wr") return wideRight; + else if (abbr == "Lfe2") return LFE2; + else if (abbr == "Lss") return leftSurroundSide; + else if (abbr == "Rss") return rightSurroundSide; + else if (abbr == "W") return ambisonicW; + else if (abbr == "X") return ambisonicX; + else if (abbr == "Y") return ambisonicY; + else if (abbr == "Z") return ambisonicZ; + + return unknown; +} + String AudioChannelSet::getSpeakerArrangementAsString() const { StringArray speakerTypes; @@ -130,6 +167,22 @@ String AudioChannelSet::getSpeakerArrangementAsString() const return speakerTypes.joinIntoString (" "); } +AudioChannelSet AudioChannelSet::fromAbbreviatedString (const String& str) +{ + StringArray abbr = StringArray::fromTokens(str, true); + AudioChannelSet set; + + for (int i = 0; i < abbr.size(); ++i) + { + AudioChannelSet::ChannelType type = getChannelTypeFromAbbreviation (abbr[i]); + + if (type != unknown) + set.addChannel (type); + } + + return set; +} + String AudioChannelSet::getDescription() const { if (isDiscreteLayout()) return String ("Discrete #") + String (size()); diff --git a/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h b/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h index d3c1303109..40f5209a2d 100644 --- a/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h +++ b/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h @@ -308,6 +308,9 @@ public: /** Returns the abbreviated name of a channel type. For example, this method may return "Ls". */ static String getAbbreviatedChannelTypeName (ChannelType); + /** Returns the channel type from an abbreviated name. */ + static ChannelType getChannelTypeFromAbbreviation (const String& abbreviation); + //============================================================================== enum { @@ -342,6 +345,11 @@ public: the returned string will be empty.*/ String getSpeakerArrangementAsString() const; + /** Returns an AudioChannelSet from a string returned by getSpeakerArrangementAsString + + @see getSpeakerArrangementAsString */ + static AudioChannelSet fromAbbreviatedString (const String& set); + /** Returns the description of the current layout. For example, this method may return "Quadraphonic". Note that the returned string may not be unique. */ String getDescription() const;