| @@ -1777,4 +1777,166 @@ void AudioProcessorGraph::AudioGraphIOProcessor::setParentGraph (AudioProcessorG | |||||
| } | } | ||||
| } | } | ||||
| //============================================================================== | |||||
| //============================================================================== | |||||
| #if JUCE_UNIT_TESTS | |||||
| class AudioProcessorGraphTests : public UnitTest | |||||
| { | |||||
| public: | |||||
| AudioProcessorGraphTests() | |||||
| : UnitTest ("AudioProcessorGraph", UnitTestCategories::audioProcessors) {} | |||||
| void runTest() override | |||||
| { | |||||
| const auto midiChannel = AudioProcessorGraph::midiChannelIndex; | |||||
| beginTest ("isConnected returns true when two nodes are connected"); | |||||
| { | |||||
| AudioProcessorGraph graph; | |||||
| const auto nodeA = graph.addNode (BasicProcessor::make ({}, MidiIn::no, MidiOut::yes))->nodeID; | |||||
| const auto nodeB = graph.addNode (BasicProcessor::make ({}, MidiIn::yes, MidiOut::no))->nodeID; | |||||
| expect (graph.canConnect ({ { nodeA, midiChannel }, { nodeB, midiChannel } })); | |||||
| expect (! graph.canConnect ({ { nodeB, midiChannel }, { nodeA, midiChannel } })); | |||||
| expect (! graph.canConnect ({ { nodeA, midiChannel }, { nodeA, midiChannel } })); | |||||
| expect (! graph.canConnect ({ { nodeB, midiChannel }, { nodeB, midiChannel } })); | |||||
| expect (graph.getConnections().empty()); | |||||
| expect (! graph.isConnected ({ { nodeA, midiChannel }, { nodeB, midiChannel } })); | |||||
| expect (! graph.isConnected (nodeA, nodeB)); | |||||
| expect (graph.addConnection ({ { nodeA, midiChannel }, { nodeB, midiChannel } })); | |||||
| expect (graph.getConnections().size() == 1); | |||||
| expect (graph.isConnected ({ { nodeA, midiChannel }, { nodeB, midiChannel } })); | |||||
| expect (graph.isConnected (nodeA, nodeB)); | |||||
| expect (graph.disconnectNode (nodeA)); | |||||
| expect (graph.getConnections().empty()); | |||||
| expect (! graph.isConnected ({ { nodeA, midiChannel }, { nodeB, midiChannel } })); | |||||
| expect (! graph.isConnected (nodeA, nodeB)); | |||||
| } | |||||
| beginTest ("graph lookups work with a large number of connections"); | |||||
| { | |||||
| AudioProcessorGraph graph; | |||||
| std::vector<AudioProcessorGraph::NodeID> nodeIDs; | |||||
| constexpr auto numNodes = 100; | |||||
| for (auto i = 0; i < numNodes; ++i) | |||||
| { | |||||
| nodeIDs.push_back (graph.addNode (BasicProcessor::make (BasicProcessor::getStereoProperties(), | |||||
| MidiIn::yes, | |||||
| MidiOut::yes))->nodeID); | |||||
| } | |||||
| for (auto it = nodeIDs.begin(); it != std::prev (nodeIDs.end()); ++it) | |||||
| { | |||||
| expect (graph.addConnection ({ { it[0], 0 }, { it[1], 0 } })); | |||||
| expect (graph.addConnection ({ { it[0], 1 }, { it[1], 1 } })); | |||||
| } | |||||
| // Check whether isConnected reports correct results when called | |||||
| // with both connections and nodes | |||||
| for (auto it = nodeIDs.begin(); it != std::prev (nodeIDs.end()); ++it) | |||||
| { | |||||
| expect (graph.isConnected ({ { it[0], 0 }, { it[1], 0 } })); | |||||
| expect (graph.isConnected ({ { it[0], 1 }, { it[1], 1 } })); | |||||
| expect (graph.isConnected (it[0], it[1])); | |||||
| } | |||||
| const auto& nodes = graph.getNodes(); | |||||
| expect (! graph.isAnInputTo (*nodes[0], *nodes[0])); | |||||
| // Check whether isAnInputTo behaves correctly for a non-cyclic graph | |||||
| for (auto it = std::next (nodes.begin()); it != std::prev (nodes.end()); ++it) | |||||
| { | |||||
| expect (! graph.isAnInputTo (**it, **it)); | |||||
| expect (graph.isAnInputTo (*nodes[0], **it)); | |||||
| expect (! graph.isAnInputTo (**it, *nodes[0])); | |||||
| expect (graph.isAnInputTo (**it, *nodes[nodes.size() - 1])); | |||||
| expect (! graph.isAnInputTo (*nodes[nodes.size() - 1], **it)); | |||||
| } | |||||
| // Make the graph cyclic | |||||
| graph.addConnection ({ { nodeIDs.back(), 0 }, { nodeIDs.front(), 0 } }); | |||||
| graph.addConnection ({ { nodeIDs.back(), 1 }, { nodeIDs.front(), 1 } }); | |||||
| // Check whether isAnInputTo behaves correctly for a cyclic graph | |||||
| for (const auto* node : graph.getNodes()) | |||||
| { | |||||
| expect (graph.isAnInputTo (*node, *node)); | |||||
| expect (graph.isAnInputTo (*nodes[0], *node)); | |||||
| expect (graph.isAnInputTo (*node, *nodes[0])); | |||||
| expect (graph.isAnInputTo (*node, *nodes[nodes.size() - 1])); | |||||
| expect (graph.isAnInputTo (*nodes[nodes.size() - 1], *node)); | |||||
| } | |||||
| } | |||||
| } | |||||
| private: | |||||
| enum class MidiIn { no, yes }; | |||||
| enum class MidiOut { no, yes }; | |||||
| class BasicProcessor : public AudioProcessor | |||||
| { | |||||
| public: | |||||
| explicit BasicProcessor (const AudioProcessor::BusesProperties& layout, MidiIn mIn, MidiOut mOut) | |||||
| : AudioProcessor (layout), midiIn (mIn), midiOut (mOut) {} | |||||
| const String getName() const override { return "Basic Processor"; } | |||||
| double getTailLengthSeconds() const override { return {}; } | |||||
| bool acceptsMidi() const override { return midiIn == MidiIn ::yes; } | |||||
| bool producesMidi() const override { return midiOut == MidiOut::yes; } | |||||
| AudioProcessorEditor* createEditor() override { return {}; } | |||||
| bool hasEditor() const override { return {}; } | |||||
| int getNumPrograms() override { return 1; } | |||||
| int getCurrentProgram() override { return {}; } | |||||
| void setCurrentProgram (int) override {} | |||||
| const String getProgramName (int) override { return {}; } | |||||
| void changeProgramName (int, const String&) override {} | |||||
| void getStateInformation (juce::MemoryBlock&) override {} | |||||
| void setStateInformation (const void*, int) override {} | |||||
| void prepareToPlay (double, int) override {} | |||||
| void releaseResources() override {} | |||||
| void processBlock (AudioBuffer<float>&, MidiBuffer&) override {} | |||||
| bool supportsDoublePrecisionProcessing() const override { return true; } | |||||
| bool isMidiEffect() const override { return {}; } | |||||
| void reset() override {} | |||||
| void setNonRealtime (bool) noexcept override {} | |||||
| using AudioProcessor::processBlock; | |||||
| static std::unique_ptr<AudioProcessor> make (const BusesProperties& layout, | |||||
| MidiIn midiIn, | |||||
| MidiOut midiOut) | |||||
| { | |||||
| return std::make_unique<BasicProcessor> (layout, midiIn, midiOut); | |||||
| } | |||||
| static BusesProperties getStereoProperties() | |||||
| { | |||||
| return BusesProperties().withInput ("in", AudioChannelSet::stereo()) | |||||
| .withOutput ("out", AudioChannelSet::stereo()); | |||||
| } | |||||
| private: | |||||
| MidiIn midiIn; | |||||
| MidiOut midiOut; | |||||
| }; | |||||
| }; | |||||
| static AudioProcessorGraphTests audioProcessorGraphTests; | |||||
| #endif | |||||
| } // namespace juce | } // namespace juce | ||||