diff --git a/include/element/audioengine.hpp b/include/element/audioengine.hpp index 679f5ae6a..3ce8c20a1 100644 --- a/include/element/audioengine.hpp +++ b/include/element/audioengine.hpp @@ -50,9 +50,8 @@ class AudioEngine final : public juce::ReferenceCountedObject { bool addGraph (RootGraph* graph); bool removeGraph (RootGraph* graph); - void setCurrentGraph (const int index) { setActiveGraph (index); } - void setActiveGraph (const int index); - int getActiveGraph() const; + void setActiveGraphIndex (const int index); + int getActiveGraphIndex() const; RootGraph* getGraph (const int index); diff --git a/include/element/session.hpp b/include/element/session.hpp index 2662a78fe..2b8448047 100644 --- a/include/element/session.hpp +++ b/include/element/session.hpp @@ -44,7 +44,6 @@ class Session : public Model, inline int getNumGraphs() const { return objectData.getChildWithName (tags::graphs).getNumChildren(); } inline Node getGraph (const int index) const { return Node (getGraphValueTree (index), false); } - Node getCurrentGraph() const { return getActiveGraph(); } Node getActiveGraph() const; int getActiveGraphIndex() const; diff --git a/src/engine/audioengine.cpp b/src/engine/audioengine.cpp index 5d6a67318..6a4602a0e 100644 --- a/src/engine/audioengine.cpp +++ b/src/engine/audioengine.cpp @@ -36,20 +36,20 @@ struct RootGraphRender : public AsyncUpdater } } - const int setCurrentGraph (const int index) + const int setActiveGraph (const int index) { - if (index == currentGraph) - return currentGraph; - currentGraph = index; + if (index == activeGraphIndex) + return activeGraphIndex; + activeGraphIndex = index; triggerAsyncUpdate(); - return currentGraph; + return activeGraphIndex; } - constexpr const int getCurrentGraphIndex() const noexcept { return currentGraph; } + constexpr const int getActiveGraphIndex() const noexcept { return activeGraphIndex; } - RootGraph* getCurrentGraph() const + RootGraph* getActiveGraph() const { - return isPositiveAndBelow (currentGraph, graphs.size()) ? graphs.getUnchecked (currentGraph) + return isPositiveAndBelow (activeGraphIndex, graphs.size()) ? graphs.getUnchecked (activeGraphIndex) : nullptr; } @@ -79,15 +79,15 @@ struct RootGraphRender : public AsyncUpdater if (program.wasRequested()) { const int nextGraph = findGraphForProgram (program); - if (nextGraph != currentGraph) - setCurrentGraph (nextGraph); + if (nextGraph != activeGraphIndex) + setActiveGraph (nextGraph); program.reset(); } - auto* const current = getCurrentGraph(); - auto* const last = (lastGraph >= 0 && lastGraph < graphs.size()) ? getGraph (lastGraph) : nullptr; + auto* const activeGraph = getActiveGraph(); + auto* const activeGraphLastRenderPass = (priorActiveGraphIndex >= 0 && priorActiveGraphIndex < graphs.size()) ? getGraph (priorActiveGraphIndex) : nullptr; - if (current == nullptr || last == nullptr) + if (activeGraph == nullptr || activeGraphLastRenderPass == nullptr) { buffer.clear(); midi.clear(); @@ -96,125 +96,114 @@ struct RootGraphRender : public AsyncUpdater const int numSamples = buffer.getNumSamples(); const int numChans = buffer.getNumChannels(); - const bool graphChanged = lastGraph != currentGraph; - const bool shouldProcess = true; - const RootGraph::RenderMode mode = current->getRenderMode(); - const bool modeChanged = graphChanged && mode != last->getRenderMode(); + const bool activeGraphChanged = priorActiveGraphIndex != activeGraphIndex; + const RootGraph::RenderMode mode = activeGraph->getRenderMode(); + const bool modeChanged = activeGraphChanged && mode != activeGraphLastRenderPass->getRenderMode(); - if (shouldProcess) - { - audioOut.setSize (numChans, numSamples, false, false, true); - audioTemp.setSize (numChans, numSamples, false, false, true); + audioOut.setSize (numChans, numSamples, false, false, true); + audioTemp.setSize (numChans, numSamples, false, false, true); - // clear the mixing area - for (int i = numChans; --i >= 0;) - audioOut.clear (i, 0, numSamples); - midiOut.clear(); + // clear the mixing area + for (int i = numChans; --i >= 0;) + audioOut.clear (i, 0, numSamples); + midiOut.clear(); - for (auto* const graph : graphs) + for (auto* const graph : graphs) + { + // copy inputs, clear outs if more than input count + for (int i = 0; i < numInputChans; ++i) + audioTemp.copyFrom (i, 0, buffer, i, 0, numSamples); + for (int i = numInputChans; i < numChans; ++i) + audioTemp.clear (i, 0, numSamples); + + // avoids feedback loop when IO node ins are + // connected to IO node outs + midiTemp.clear (0, numSamples); + + if ((activeGraphLastRenderPass == graph && activeGraphChanged && activeGraphLastRenderPass->isSingle()) + || (activeGraphChanged && activeGraph != nullptr && activeGraph->isSingle() && graph != activeGraph)) { - // copy inputs, clear outs if more than input count - for (int i = 0; i < numInputChans; ++i) - audioTemp.copyFrom (i, 0, buffer, i, 0, numSamples); - for (int i = numInputChans; i < numChans; ++i) - audioTemp.clear (i, 0, numSamples); - - // avoids feedback loop when IO node ins are - // connected to IO node outs - midiTemp.clear (0, numSamples); - - if ((last == graph && graphChanged && last->isSingle()) - || (graphChanged && current != nullptr && current->isSingle() && graph != current)) + // send kill messages to the last graph(s) when the graph changes + // see http://nickfever.com/music/midi-cc-list + for (int i = 0; i < 16; ++i) { - // send kill messages to the last graph(s) when the graph changes - // see http://nickfever.com/music/midi-cc-list - for (int i = 0; i < 16; ++i) - { - // sustain pedal off - midiTemp.addEvent (MidiMessage::controllerEvent (i + 1, 64, 0), 0); - // Sostenuto off - midiTemp.addEvent (MidiMessage::controllerEvent (i + 1, 66, 0), 0); - // Hold off - midiTemp.addEvent (MidiMessage::controllerEvent (i + 1, 69, 0), 0); - - midiTemp.addEvent (MidiMessage::allNotesOff (i + 1), 0); - } + // sustain pedal off + midiTemp.addEvent (MidiMessage::controllerEvent (i + 1, 64, 0), 0); + // Sostenuto off + midiTemp.addEvent (MidiMessage::controllerEvent (i + 1, 66, 0), 0); + // Hold off + midiTemp.addEvent (MidiMessage::controllerEvent (i + 1, 69, 0), 0); + + midiTemp.addEvent (MidiMessage::allNotesOff (i + 1), 0); } - else if ((current == graph && graph->isSingle()) - || (current != nullptr && ! current->isSingle() && ! graph->isSingle())) + } + else if ((activeGraph == graph && graph->isSingle()) + || (activeGraph != nullptr && ! activeGraph->isSingle() && ! graph->isSingle())) + { + // active single graph or parallel graphs get MIDI always + midiTemp.addEvents (midi, 0, numSamples, 0); + } + + { + RenderContext rc (audioTemp, cvTemp, midiTemp, numSamples); + const ScopedLock sl (graph->getPropertyLock()); + if (graph->isSuspended()) { - // current single graph or parallel graphs get MIDI always - midiTemp.addEvents (midi, 0, numSamples, 0); + graph->renderBypassed (rc); } - + else { - RenderContext rc (audioTemp, cvTemp, midiTemp, numSamples); - const ScopedLock sl (graph->getPropertyLock()); - if (graph->isSuspended()) - { - graph->renderBypassed (rc); - } - else - { - graph->render (rc); - } + graph->render (rc); } + } - // clang-format off - if (graphChanged && ((current->isSingle() && graph == last) || - (modeChanged && ! current->isSingle() && graph->isSingle() && graph == last))) + // clang-format off + if (activeGraphChanged && ((activeGraph->isSingle() && graph == activeGraphLastRenderPass) || + (modeChanged && ! activeGraph->isSingle() && graph->isSingle() && graph == activeGraphLastRenderPass))) + { + for (int i = 0; i < numOutputChans; ++i) + audioOut.addFromWithRamp (i, 0, audioTemp.getReadPointer (i), numSamples, 1.f, 0.f); + } + else if ((graph == activeGraph && graph->isSingle()) || (! graph->isSingle() && (activeGraph != nullptr) && ! activeGraph->isSingle())) + { + // if it's the active single graph or both are parallel... + if (activeGraphChanged && (graph->isSingle() || (modeChanged && ! graph->isSingle() && ! activeGraph->isSingle()))) { - // DBG(" FADE OUT LAST GRAPH: " << graph->engineIndex); + // DBG(" FADE IN NEW GRAPH: " << graph->engineIndex); for (int i = 0; i < numOutputChans; ++i) - audioOut.addFromWithRamp (i, 0, audioTemp.getReadPointer (i), numSamples, 1.f, 0.f); + audioOut.addFromWithRamp (i, 0, audioTemp.getReadPointer (i), numSamples, 0.f, 1.f); } - else if ((graph == current && graph->isSingle()) || (! graph->isSingle() && (current != nullptr) && ! current->isSingle())) + else { - // if it's the current single graph or both are parallel... - if (graphChanged && (graph->isSingle() || (modeChanged && ! graph->isSingle() && ! current->isSingle()))) - { - // DBG(" FADE IN NEW GRAPH: " << graph->engineIndex); - for (int i = 0; i < numOutputChans; ++i) - audioOut.addFromWithRamp (i, 0, audioTemp.getReadPointer (i), numSamples, 0.f, 1.f); - } - else - { - for (int i = 0; i < numOutputChans; ++i) - audioOut.addFrom (i, 0, audioTemp, i, 0, numSamples); - } - - midiOut.addEvents (midiTemp, 0, numSamples, 0); + for (int i = 0; i < numOutputChans; ++i) + audioOut.addFrom (i, 0, audioTemp, i, 0, numSamples); } - // clang-format on - } - - for (int i = 0; i < numChans; ++i) - buffer.copyFrom (i, 0, audioOut, i, 0, numSamples); - // setup a program change if present - for (auto m : midi) - { - auto msg = m.getMessage(); - if (m.samplePosition >= numSamples) - break; - if (! msg.isProgramChange()) - continue; - program.program = msg.getProgramChangeNumber(); - program.channel = msg.getChannel(); + midiOut.addEvents (midiTemp, 0, numSamples, 0); } - - // done with input, swap it with the rendered output - midi.swapWith (midiOut); + // clang-format on } - else + + for (int i = 0; i < numChans; ++i) + buffer.copyFrom (i, 0, audioOut, i, 0, numSamples); + + // setup a program change if present + for (auto m : midi) { - midi.clear(); - for (int i = 0; i < buffer.getNumChannels(); ++i) - zeromem (buffer.getWritePointer (i), sizeof (float) * (size_t) numSamples); + auto msg = m.getMessage(); + if (m.samplePosition >= numSamples) + break; + if (! msg.isProgramChange()) + continue; + program.program = msg.getProgramChangeNumber(); + program.channel = msg.getChannel(); } - lastGraph = currentGraph; + // done with input, swap it with the rendered output + midi.swapWith (midiOut); + + priorActiveGraphIndex = activeGraphIndex; } /** not realtime safe! */ @@ -225,8 +214,8 @@ struct RootGraphRender : public AsyncUpdater if (graph->engineIndex == 0) { - setCurrentGraph (0); - lastGraph = 0; + setActiveGraph (0); + priorActiveGraphIndex = 0; } return true; @@ -239,10 +228,10 @@ struct RootGraphRender : public AsyncUpdater graphs.removeFirstMatchingValue (graph); graph->engineIndex = -1; updateIndexes(); - if (currentGraph >= graphs.size()) - currentGraph = graphs.size() - 1; - if (lastGraph >= graphs.size()) - lastGraph = graphs.size() - 1; + if (activeGraphIndex >= graphs.size()) + activeGraphIndex = graphs.size() - 1; + if (priorActiveGraphIndex >= graphs.size()) + priorActiveGraphIndex = graphs.size() - 1; } int size() const { return graphs.size(); } @@ -252,8 +241,8 @@ struct RootGraphRender : public AsyncUpdater private: Array graphs; - int currentGraph = -1; - int lastGraph = -1; + int activeGraphIndex = -1; + int priorActiveGraphIndex = -1; struct ProgramRequest { @@ -291,7 +280,7 @@ struct RootGraphRender : public AsyncUpdater } } - return currentGraph; + return activeGraphIndex; } }; @@ -313,11 +302,11 @@ class AudioEngine::Private : public AudioIODeviceCallback, { tempoValue.addListener (this); externalClockValue.addListener (this); - currentGraph.set (-1); + activeGraphIndex.set (-1); processMidiClock.set (0); sessionWantsExternalClock.set (0); midiClock.addListener (this); - graphs.onActiveGraphChanged = std::bind (&AudioEngine::Private::onCurrentGraphChanged, this); + graphs.onActiveGraphChanged = std::bind (&AudioEngine::Private::onActiveGraphChanged, this); midiIOMonitor = new MidiIOMonitor(); messageCollector.reset (sampleRate); @@ -346,30 +335,30 @@ class AudioEngine::Private : public AudioIODeviceCallback, midiIOMonitor->notify(); } - RootGraph* getCurrentGraph() const { return graphs.getCurrentGraph(); } + RootGraph* getActiveGraph() const { return graphs.getActiveGraph(); } - void onCurrentGraphChanged() + void onActiveGraphChanged() { int renderingIndex = -1; { ScopedLock sl (lock); - renderingIndex = graphs.getCurrentGraphIndex(); + renderingIndex = graphs.getActiveGraphIndex(); } - if (renderingIndex != currentGraph.get()) + if (renderingIndex != activeGraphIndex.get()) { // a change is about to happen next audio cycle. return; } auto session = engine.context().session(); - if (currentGraph.get() >= 0 && currentGraph.get() != session->getActiveGraphIndex()) + if (activeGraphIndex.get() >= 0 && activeGraphIndex.get() != session->getActiveGraphIndex()) { // NOTE: this is a cheap way to refresh the GUI, in the future this // will need to be smarter by determining whether or not EC needs to // handle the change at the model layer. auto graphs = session->data().getChildWithName (tags::graphs); - graphs.setProperty (tags::active, currentGraph.get(), nullptr); + graphs.setProperty (tags::active, activeGraphIndex.get(), nullptr); } } @@ -428,7 +417,7 @@ class AudioEngine::Private : public AudioIODeviceCallback, AudioSampleBuffer buffer (channels, totalNumChans, numSamples); tempMidi.clear(); - processCurrentGraph (buffer, tempMidi); + processGraphs (buffer, tempMidi); { ScopedLock lockMidiOut (engine.world.midi().getMidiOutputLock()); @@ -451,7 +440,7 @@ class AudioEngine::Private : public AudioIODeviceCallback, outMeters.getObjectPointerUnchecked (c)->updateLevel (outputChannelData, c, numSamples); } - void processCurrentGraph (AudioBuffer& buffer, MidiBuffer& midi) + void processGraphs (AudioBuffer& buffer, MidiBuffer& midi) { const int numSamples = buffer.getNumSamples(); messageCollector.removeNextBlockOfMessages (midi, numSamples); @@ -493,15 +482,15 @@ class AudioEngine::Private : public AudioIODeviceCallback, midiClockMaster.render (midi, numSamples); } - const auto nextGraph = currentGraph.get(); - if (nextGraph != graphs.getCurrentGraphIndex()) + const auto nextGraph = activeGraphIndex.get(); + if (nextGraph != graphs.getActiveGraphIndex()) { - graphs.setCurrentGraph (nextGraph); + graphs.setActiveGraph (nextGraph); } graphs.renderGraphs (buffer, midi); // user requested index can be cancelled by program changed - if (nextGraph != graphs.getCurrentGraphIndex()) + if (nextGraph != graphs.getActiveGraphIndex()) { - currentGraph.set (graphs.getCurrentGraphIndex()); + activeGraphIndex.set (graphs.getActiveGraphIndex()); } // MIDI Clock out. @@ -656,9 +645,9 @@ class AudioEngine::Private : public AudioIODeviceCallback, ScopedLock sl (lock); graphs.removeGraph (graph); // keep the requested index valid, otherwise a stale index gets - // written back into the session model by onCurrentGraphChanged() - if (currentGraph.get() >= graphs.size()) - currentGraph.set (graphs.size() - 1); + // written back into the session model by onActiveGraphChanged() + if (activeGraphIndex.get() >= graphs.size()) + activeGraphIndex.set (graphs.size() - 1); } graph->renderingSequenceChanged.disconnect_all_slots(); @@ -751,7 +740,7 @@ class AudioEngine::Private : public AudioIODeviceCallback, double sampleRate = 44100.0; int blockSize = 1024; bool isPrepared = false; - Atomic currentGraph; + Atomic activeGraphIndex; int numInputChans, numOutputChans; HeapBlock channels; @@ -927,13 +916,13 @@ void AudioEngine::addMidiMessage (const MidiMessage msg, bool handleOnDeviceQueu priv->messageCollector.addMessageToQueue (msg); } -void AudioEngine::setActiveGraph (const int index) +void AudioEngine::setActiveGraphIndex (const int index) { - while (priv != nullptr && index != priv->currentGraph.get()) - priv->currentGraph.set (index); + while (priv != nullptr && index != priv->activeGraphIndex.get()) + priv->activeGraphIndex.set (index); } -int AudioEngine::getActiveGraph() const { return (priv != nullptr) ? priv->currentGraph.get() : -1; } +int AudioEngine::getActiveGraphIndex() const { return (priv != nullptr) ? priv->activeGraphIndex.get() : -1; } void AudioEngine::setSession (SessionPtr session) { @@ -1000,7 +989,7 @@ void AudioEngine::processExternalBuffers (AudioBuffer& buffer, MidiBuffer { if (getRunMode() == RunMode::Plugin) world.midi().processMidiBuffer (midi, buffer.getNumSamples(), priv->sampleRate); - priv->processCurrentGraph (buffer, midi); + priv->processGraphs (buffer, midi); } } @@ -1045,13 +1034,13 @@ void AudioEngine::updateExternalLatencySamples() { ScopedLock sl (priv->lock); - auto* current = priv->getCurrentGraph(); - if (nullptr == current) + auto* activeGraph = priv->getActiveGraph(); + if (nullptr == activeGraph) return; - if (current->getRenderMode() == RootGraph::SingleGraph) + if (activeGraph->getRenderMode() == RootGraph::SingleGraph) { - latencySamples = current->getLatencySamples(); + latencySamples = activeGraph->getLatencySamples(); } else { diff --git a/src/services.cpp b/src/services.cpp index 538eb5354..5909758a1 100644 --- a/src/services.cpp +++ b/src/services.cpp @@ -182,7 +182,7 @@ void Services::run() if (auto* gui = find()) { gui->stabilizeContent(); - const Node graph (session->getCurrentGraph()); + const Node graph (session->getActiveGraph()); auto* const window = gui->getMainWindow(); // don't show plugin windows on load if the UI is hidden diff --git a/src/services/engineservice.cpp b/src/services/engineservice.cpp index cedbdecb8..d5857707f 100644 --- a/src/services/engineservice.cpp +++ b/src/services/engineservice.cpp @@ -228,7 +228,7 @@ class EngineService::RootGraphs auto engine = owner.context().audio(); if (! engine) return 0; - const int currentIndex = engine->getActiveGraph(); + const int currentIndex = engine->getActiveGraphIndex(); if (currentIndex >= 0) for (auto* h : graphs) if (auto* root = h->getRootGraph()) @@ -313,7 +313,7 @@ EngineService::~EngineService() void EngineService::addConnection (const uint32 s, const uint32 sp, const uint32 d, const uint32 dp) { if (auto session = context().session()) - if (auto* h = graphs->findFor (session->getCurrentGraph())) + if (auto* h = graphs->findFor (session->getActiveGraph())) if (auto* c = h->getController()) c->addConnection (s, sp, d, dp); } @@ -403,7 +403,7 @@ void EngineService::duplicateGraph() auto& world = context(); auto engine = world.audio(); auto session = world.session(); - const Node current (session->getCurrentGraph()); + const Node current (session->getActiveGraph()); duplicateGraph (current); } @@ -451,7 +451,7 @@ void EngineService::removeGraph (int index) index = session->getNumGraphs() - 1; sgraphs.setProperty (tags::active, index, 0); - const Node nextGraph = session->getCurrentGraph(); + const Node nextGraph = session->getActiveGraph(); if (nextGraph.isRootGraph()) { @@ -846,7 +846,7 @@ void EngineService::setRootNode (const Node& newRootNode) r->setNodeModel (newRootNode); } - engine->setCurrentGraph (index); + engine->setActiveGraphIndex (index); } else { diff --git a/src/services/guiservice.cpp b/src/services/guiservice.cpp index 9669a12b0..7a12b89a9 100644 --- a/src/services/guiservice.cpp +++ b/src/services/guiservice.cpp @@ -247,7 +247,7 @@ void GuiService::ForegroundCheck::timerCallback() if (foreground) { if (session) - ui.showPluginWindowsFor (session->getCurrentGraph(), true, false); + ui.showPluginWindowsFor (session->getActiveGraph(), true, false); ui.getMainWindow()->toFront (true); } else if (! foreground) @@ -1084,7 +1084,7 @@ bool GuiService::perform (const InvocationInfo& info) } case Commands::exportGraph: { auto session = context().session(); - auto node = session->getCurrentGraph(); + auto node = session->getActiveGraph(); node.savePluginState(); if (! impl->lastExportedGraph.isDirectory()) diff --git a/src/ui/connectiongrid.cpp b/src/ui/connectiongrid.cpp index 010d2aef0..e3287ac8e 100644 --- a/src/ui/connectiongrid.cpp +++ b/src/ui/connectiongrid.cpp @@ -1031,6 +1031,6 @@ void ConnectionGrid::itemDropped (const SourceDetails& sd) void ConnectionGrid::didBecomeActive() { auto session = ViewHelpers::findContentComponent (this)->session(); - setNode (session->getCurrentGraph()); + setNode (session->getActiveGraph()); } } // namespace element diff --git a/src/ui/content.cpp b/src/ui/content.cpp index dae461ed0..61dafd430 100644 --- a/src/ui/content.cpp +++ b/src/ui/content.cpp @@ -444,7 +444,7 @@ Content::Content (Context& ctx) toolBarVisible = true; toolBarSize = 32; - const Node node (context().session()->getCurrentGraph()); + const Node node (context().session()->getActiveGraph()); setCurrentNode (node); resized(); diff --git a/src/ui/grapheditorview.cpp b/src/ui/grapheditorview.cpp index 9a720a161..5565ea64d 100644 --- a/src/ui/grapheditorview.cpp +++ b/src/ui/grapheditorview.cpp @@ -88,7 +88,7 @@ void GraphEditorView::stabilizeContent() if (! getGraph().isValid() || ! getGraph().isGraph()) { if (auto session = ViewHelpers::getSession (this)) - setNode (session->getCurrentGraph()); + setNode (session->getActiveGraph()); } const auto g = getGraph(); diff --git a/src/ui/graphsettingsview.cpp b/src/ui/graphsettingsview.cpp index 9448ab433..26d7bb685 100644 --- a/src/ui/graphsettingsview.cpp +++ b/src/ui/graphsettingsview.cpp @@ -545,7 +545,7 @@ void GraphSettingsView::stabilizeContent() auto& ui = *srvc.find(); if (! props->node().isValid()) - props->setNode (world->session()->getCurrentGraph()); + props->setNode (world->session()->getActiveGraph()); if (! props->node().isValid()) props->setNode (detail::findGraph (ui.getSelectedNode())); diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp index d7dd0e08f..0e76a2822 100644 --- a/src/ui/mainwindow.cpp +++ b/src/ui/mainwindow.cpp @@ -100,7 +100,7 @@ void MainWindow::nameChangedSession() } auto sessionName = session->getName().trim(); - auto graphName = session->getCurrentGraph().getName().trim(); + auto graphName = session->getActiveGraph().getName().trim(); if (sessionName.isEmpty()) { const auto file = controller->getSessionFile(); diff --git a/src/ui/sessiontreepanel.cpp b/src/ui/sessiontreepanel.cpp index 300e702f8..60c092c2f 100644 --- a/src/ui/sessiontreepanel.cpp +++ b/src/ui/sessiontreepanel.cpp @@ -577,7 +577,7 @@ class SessionRootGraphTreeItem : public SessionGraphTreeItem void activateGraph() { - const bool nodeIsCurrent = node == session()->getCurrentGraph(); + const bool nodeIsCurrent = node == session()->getActiveGraph(); if (nodeIsCurrent) return;