The GUI and the JUCE library it's designed with are written almost entirely in C++. This language was the only choice for a fast, cross-platform user interface.
All of the current developers are self-taught programmers. We consider ourselves neuroscientists first, and have only chosen to become software engineers out of necessity. After years of being frustrated by the lack of flexibility and poor design inherent in all commercially available data acquisition software, we decided to build our own.
Since we have no formal training in C++, there may be some aspects of the code that are unorthodox or just plain inefficient. As much as possible, we've tried to follow the model set by the Juce examples. But if you spot any stylistic decisions that seem foolish, don't hesitate to point them out.
Here are some things we know might need to change:
- Pointers. As much as possible, we've tried to use Juce's `ScopedPointer` or `OwnedArray` classes so objects don't have to be manually deleted. There are many other alternatives, such as Juce's `SafePointer` and the boost library's `shared_ptr`, but our choice seems to work well so far.
- const declarations have been ignored for the most part, except where external libraries necessitate them. It will be probably be wise to add them in where appropriate, but we haven't gotten around to that yet.
- Exceptions are essentially nonexistent in the source code. Different people have different opinions on whether exceptions are necessary at all. We've chosen to leave them out entirely, for the sake of simplicity.
Where to start
If you've never programmed in C++ before, don't worry! We all had to teach ourselves at some point, and there's no better way to learn than by having a specific project you want to implement. If you already know Matlab or Python, C++ really isn't much more difficult. Your code-writing needs to be more precise, but the compiler will catch any obvious errors before they break your program. While some of the syntax may seem obscure, most of the functions perform very low-level tasks, so you don't need to parse a single line that's doing 10 complex matrix operations. Plus, your programs will be FAST, which is a blissful experience for anyone who's waited for Matlab to refresh a plot with more than 10,000 data points.
If you're eager to learn C++, we can recommend the following books:
Google will point you to countless tutorials that pick up at almost any level of prior knowledge.
The juce tutorials are a great introduction to the library, and will get you up to speed with almost all of the specific methods we use for data handling and all UI code.
For any specific issues, it should go without saying that online tutorials, documentation, and forums are indispensible.
We'll leave a more complete C++ introduction to the pros, but there's one aspect of the language that we found confusing at first, but turned out to be incredibly useful: virtual methods.
When you create a new class that's a child of an existing class, it inherits all of the public methods from its parent. This leads to two common situations:
- The child must override one of its parent's methods in order to gain new functionality. C++ makes this easy by allowing you to automatically override the original method by giving the new method the same name, inputs, and outputs.
- Your program wants to remain agnostic about which class it's dealing with at some point in time, whether it's the parent or one of several children. C++ makes this easy by allowing you to return pointers to any type of class (parent or child) and cast them as pointers to the parent class.
If both of these things happen at the same time, it leads to an ambiguity: if a particular method is called, should it be the method of the parent class (based on the pointer type) or the method of the child class (based on the actual object)? C++ defaults to the parent's method unless you declare the parent's method as virtual, e.g.: `virtual void parentMethod();`
With virtual functions, there's no more ambiguity. The program will definitely use the child's method in the case of virtual methods, and will definitely use the parent's method in the case of non-virtual methods. The GUI uses virtual methods extensively, for example in the case of the GenericProcessor class, which contains numerous methods that must be overridden by its child classes.
Just to confuse you a bit more, you have to keep in mind that virtual methods differ from pure virtual methods, which are defined by setting a virtual method equal to zero, e.g.: `virtual void parentMethod() = 0;`. If a method is pure virtual, it means that the class it belongs to _can never exist_. Classes with pure virtual methods can only act as parent classes, and all of their children must define some implementation of the pure virtual methods, otherwise they will themselves be virtual.