Skip to content
Snippets Groups Projects
  • jsiegle's avatar
    a66d703f
    Major refactoring. · a66d703f
    jsiegle authored
    Three important changes:
    - The FilterList is now the ProcessorList
    - The FilterViewport is now the EditorViewport
    - Any classes that need to access important UI objects have become subclasses
      of the "AccessClass". Such objects automatically obtain pointers from
      the UIComponent and register the MessageCenter as an ActionListener. This
      will make it much easier to allocate pointers to objects.
    a66d703f
    History
    Major refactoring.
    jsiegle authored
    Three important changes:
    - The FilterList is now the ProcessorList
    - The FilterViewport is now the EditorViewport
    - Any classes that need to access important UI objects have become subclasses
      of the "AccessClass". Such objects automatically obtain pointers from
      the UIComponent and register the MessageCenter as an ActionListener. This
      will make it much easier to allocate pointers to objects.
ProcessorGraph.cpp 12.94 KiB
/*
    ------------------------------------------------------------------

    This file is part of the Open Ephys GUI
    Copyright (C) 2012 Open Ephys

    ------------------------------------------------------------------

    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 3 of the License, or
    (at your option) 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.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

#include <stdio.h>

#include "ProcessorGraph.h"

#include "AudioNode.h"
#include "LfpDisplayNode.h"
#include "EventNode.h"
#include "FileReader.h"
#include "FilterNode.h"
#include "GenericProcessor.h"
#include "RecordNode.h"
#include "ResamplingNode.h"
#include "SignalGenerator.h"
#include "SourceNode.h"
#include "SpikeDetector.h"
#include "WiFiOutput.h"
#include "Utilities/Splitter.h"
#include "../UI/UIComponent.h"
#include "../UI/Configuration.h"
#include "../UI/EditorViewport.h"

ProcessorGraph::ProcessorGraph() : 
	currentNodeId(100),
	RECORD_NODE_ID(199), 
	AUDIO_NODE_ID(200), 
	OUTPUT_NODE_ID(201), 
	RESAMPLING_NODE_ID(202),
	totalAudioConnections(0),
	totalRecordConnections(0)
	
	{

	// ProcessorGraph will always have 0 inputs (all content is generated within graph)
	// but it will have N outputs, where N is the number of channels for the audio monitor
	setPlayConfigDetails(0, // number of inputs
				         2, // number of outputs
				         44100.0, // sampleRate
				         128);    // blockSize

	createDefaultNodes();

}

ProcessorGraph::~ProcessorGraph() { }


void ProcessorGraph::createDefaultNodes()
{

	// add output node -- sends output to the audio card
	AudioProcessorGraph::AudioGraphIOProcessor* on = 
		new AudioProcessorGraph::AudioGraphIOProcessor(AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode);

	// add record node -- sends output to disk
	RecordNode* recn = new RecordNode();
	recn->setNodeId(RECORD_NODE_ID);
	//recn->setConfiguration(config);

	// add audio node -- takes all inputs and selects those to be used for audio monitoring
	AudioNode* an = new AudioNode();
	recn->setNodeId(AUDIO_NODE_ID);
	//an->setConfiguration(config);

	// add resampling node -- resamples continuous signals to 44.1kHz
	ResamplingNode* rn = new ResamplingNode(true);
	rn->setNodeId(RESAMPLING_NODE_ID);

	addNode(on,OUTPUT_NODE_ID);
	addNode(recn,RECORD_NODE_ID);
	addNode(an, AUDIO_NODE_ID);
	addNode(rn, RESAMPLING_NODE_ID);

	// connect audio network
	for (int n = 0; n < 2; n++) {
		
		addConnection(AUDIO_NODE_ID, n,
		              RESAMPLING_NODE_ID, n);
		
		addConnection(RESAMPLING_NODE_ID, n,
		              OUTPUT_NODE_ID, n);

	}

	std::cout << "Default nodes created." << std::endl;
	
}


void* ProcessorGraph::createNewProcessor(String& description)//,
										// GenericProcessor* source,
										// GenericProcessor* dest) 
{

	GenericProcessor* processor = createProcessorFromDescription(description);

	int id = currentNodeId++;

	if (processor != 0) {

		processor->setNodeId(id); // identifier within processor graph

		std::cout << "  Adding node to graph with ID number " << id << std::endl;
		
		//processor->setFilterViewport(filterViewport);
		//processor->setConfiguration(config);
		//processor->addActionListener(messageCenter);
		processor->setUIComponent(getUIComponent());

		addNode(processor,id); // have to add it so it can be deleted by the graph

		return processor->createEditor();

	} else {

		sendActionMessage("Not a valid processor type.");

		return 0;
	}

}

void ProcessorGraph::clearConnections()
{

	for (int i = 0; i < getNumConnections(); i++)
	{
		const Connection* connection = getConnection(i);

		if (connection->destNodeId == RESAMPLING_NODE_ID ||
		    connection->destNodeId == OUTPUT_NODE_ID)
		 {
		 	; // leave it   	
		 } else {
		 	removeConnection(i);
		 }
	}
}

void ProcessorGraph::updateConnections(Array<SignalChainTabButton*, CriticalSection> tabs)
{
	clearConnections(); // clear processor graph

	std::cout << "Updating connections:" << std::endl;

 	for (int n = 0; n < tabs.size(); n++)
	{
		std::cout << "Signal chain " << n << std::endl;

		GenericEditor* sourceEditor = (GenericEditor*) tabs[n]->getEditor();
		GenericProcessor* source = (GenericProcessor*) sourceEditor->getProcessor();

		while (source != 0)// && destEditor->isEnabled())
		{
			std::cout << "Source node: " << source->getName() << ", ";
			GenericProcessor* dest = (GenericProcessor*) source->getDestNode();
			if (dest != 0)
			{
				std::cout << "Dest node: " << dest->getName() << std::endl;
			} else {
				std::cout << "no dest node." << std::endl;
			}

			if (source->enabledState())
			{

				// add the connections to audio and record nodes if necessary
				if (!(source->isSink() || source->isSource() ||
				      source->isSplitter() || source->isMerger()))
				{
					std::cout << "   Connecting to audio and record nodes." << std::endl;

					for (int chan = 0; chan < source->getNumOutputs(); chan++) {

						addConnection(source->getNodeId(), // sourceNodeID
						  	chan, // sourceNodeChannelIndex
						   	AUDIO_NODE_ID, // destNodeID
						  	getNextFreeAudioChannel()); // destNodeChannelIndex

						addConnection(source->getNodeId(), // sourceNodeID
						  	chan, // sourceNodeChannelIndex
						   	RECORD_NODE_ID, // destNodeID
						  	getNextFreeRecordChannel()); // destNodeChannelIndex
					}
				}

				if (dest != 0) {
					if (dest->enabledState())
						std::cout << "OK." << std::endl;
					else
						std::cout << "Not OK." << std::endl;

					if (dest->enabledState())
					{

						std::cout << "     Connecting " << source->getName() << " channel ";

						for (int chan = 0; chan < source->getNumOutputs(); chan++) 
						{

							// eventually need to account for splitter and mergers
							
							std::cout << chan << " ";
							           

							addConnection(source->getNodeId(), // sourceNodeID
							  	chan, // sourceNodeChannelIndex
							   	dest->getNodeId(), // destNodeID
							  	chan); // destNodeChannelIndex
						}

						std::cout << " to " << dest->getName() << std::endl;
							
							std::cout << "     Connecting " << source->getName() <<
							           " event channel to " <<
							           dest->getName() << std::endl;
							// connect event channel
							addConnection(source->getNodeId(), // sourceNodeID
							  	midiChannelIndex, // sourceNodeChannelIndex
							   	dest->getNodeId(), // destNodeID
							  	midiChannelIndex); // destNodeChannelIndex

					}

				}
			}	
			
			source = dest; // switch source and dest
		} // end while source != 0
	} // end "tabs" for loop
} // end method

int ProcessorGraph::getNextFreeAudioChannel()
{
	return totalAudioConnections++;
}

int ProcessorGraph::getNextFreeRecordChannel()
{
	return totalRecordConnections++;
}

GenericProcessor* ProcessorGraph::createProcessorFromDescription(String& description)
{
	int splitPoint = description.indexOf("/");
	String processorType = description.substring(0,splitPoint);
	String subProcessorType = description.substring(splitPoint+1);

	std::cout << processorType << "::" << subProcessorType << std::endl;

	GenericProcessor* processor = 0;

	if (processorType.equalsIgnoreCase("Sources")) {

		if (subProcessorType.equalsIgnoreCase("Intan Demo Board")) {
			
			// only one Intan Demo Board at a time, please
			if (!processorWithSameNameExists(subProcessorType)) {
				processor = new SourceNode(subProcessorType);
			}
				
			std::cout << "Creating a new data source." << std::endl;
		} else if (subProcessorType.equalsIgnoreCase("Signal Generator"))
		{
			processor = new SignalGenerator();
			std::cout << "Creating a new signal generator." << std::endl;
		} else if (subProcessorType.equalsIgnoreCase("Event Generator"))
		{
			processor = new EventNode();
			std::cout << "Creating a new event node." << std::endl;
		}

		
		sendActionMessage("New source node created.");
		

	} else if (processorType.equalsIgnoreCase("Filters")) {

		if (subProcessorType.equalsIgnoreCase("Bandpass Filter")) {
			std::cout << "Creating a new filter." << std::endl;
			processor = new FilterNode();

		} else if (subProcessorType.equalsIgnoreCase("Resampler")) {
			std::cout << "Creating a new resampler." << std::endl;
			processor = new ResamplingNode(false);
		
		} else if (subProcessorType.equalsIgnoreCase("Spike Detector")) {
			std::cout << "Creating a new spike detector." << std::endl;
			processor = new SpikeDetector();
		}

		sendActionMessage("New filter node created.");

	} else if (processorType.equalsIgnoreCase("Utilities")) {

	 	if (subProcessorType.equalsIgnoreCase("Splitter")) {
			
			std::cout << "Creating a new splitter." << std::endl;
			processor = new Splitter();

			sendActionMessage("New splitter created.");

	 	}

	} else if (processorType.equalsIgnoreCase("Sinks")) {

		if (subProcessorType.equalsIgnoreCase("LFP Viewer")) {
			std::cout << "Creating an LfpDisplayNode." << std::endl;
			processor = new LfpDisplayNode();
		   
		   // std::cout << "Graph data viewport: " << UI->getDataViewport() << std::endl;
			// processor->setDataViewport(getDataViewport());
			//processor->setUIComponent(UI);
		} else if (subProcessorType.equalsIgnoreCase("WiFi Output")) {
			std::cout << "Creating a WiFi node." << std::endl;
			processor = new WiFiOutput();
		}
	
		sendActionMessage("New sink created.");
	}

	return processor;
}


bool ProcessorGraph::processorWithSameNameExists(const String& name)
{
	for (int i = 0; i < getNumNodes(); i++)
	{
	 	Node* node = getNode(i);

	 	if (name.equalsIgnoreCase(node->getProcessor()->getName()))
	 		return true;
	
	 }

	 return false;

}

void ProcessorGraph::removeProcessor(GenericProcessor* processor) {
	
	std::cout << "Removing processor with ID " << processor->getNodeId() << std::endl;

	removeNode(processor->getNodeId());

}

// void ProcessorGraph::setUIComponent(UIComponent* ui)
// {
// 	UI = ui;
// }

// void ProcessorGraph::setFilterViewport(FilterViewport* fv)
// {
// 	filterViewport = fv;
// }

// void ProcessorGraph::setMessageCenter(MessageCenter* mc)
// {
// 	messageCenter = mc;
// }

// void ProcessorGraph::setConfiguration(Configuration* c)
// {
// 	config = c;
// }


bool ProcessorGraph::enableProcessors() {

	updateConnections(getEditorViewport()->requestSignalChain());

	std::cout << "Enabling processors..." << std::endl;

	bool allClear;

	if (getNumNodes() < 5)
	{
		getUIComponent()->disableCallbacks();
		return false;
	}

	for (int i = 0; i < getNumNodes(); i++)
	{

		Node* node = getNode(i);

		if (node->nodeId != OUTPUT_NODE_ID)
		{
			GenericProcessor* p = (GenericProcessor*) node->getProcessor();
			allClear = p->isReady();

			if (!allClear) {
				std::cout << p->getName() << " said it's not OK." << std::endl;
				sendActionMessage("Could not initialize acquisition.");
				getUIComponent()->disableCallbacks();
				return false;

			}
		}
	}

	for (int i = 0; i < getNumNodes(); i++)
	{

		Node* node = getNode(i);

		if (node->nodeId != OUTPUT_NODE_ID)
		{
			GenericProcessor* p = (GenericProcessor*) node->getProcessor();
			p->enable();
		}
	}
	
	getEditorViewport()->signalChainCanBeEdited(false);

	sendActionMessage("Acquisition started.");

	return true;
}

bool ProcessorGraph::disableProcessors() {

	std::cout << "Disabling processors..." << std::endl;

	bool allClear;

	for (int i = 0; i < getNumNodes(); i++)
	{
		Node* node = getNode(i);
		if (node->nodeId != OUTPUT_NODE_ID)
		{
			GenericProcessor* p = (GenericProcessor*) node->getProcessor();
			std::cout << "Disabling " << p->getName() << std::endl;
			allClear = p->disable();

			if (!allClear) {
				sendActionMessage("Could not stop acquisition.");
				return false;
			}
		}
	}

	getEditorViewport()->signalChainCanBeEdited(true);

	sendActionMessage("Acquisition ended.");

	return true;
}


AudioNode* ProcessorGraph::getAudioNode() {
	
	Node* node = getNodeForId(AUDIO_NODE_ID);
	return (AudioNode*) node->getProcessor();

}

RecordNode* ProcessorGraph::getRecordNode() {
	
	Node* node = getNodeForId(RECORD_NODE_ID);
	return (RecordNode*) node->getProcessor();

}

void ProcessorGraph::saveState()
{
	File file = File("./savedState.xml");
	getEditorViewport()->saveState(file);
}

void ProcessorGraph::loadState()
{
	File file = File("./savedState.xml");
	getEditorViewport()->loadState(file);
}