Creating own algorithms
The Entropy-Piano-Tuner allows to add new custom algorithms that can be used to tune you piano. To get started you need to compile the EPT from source (here). Afterwards you can create a new algorithm.
Create the required files
Every algorithm must be stored in a subdirectory the algorithms directory and contain at least these four files:
- myawesomealgorithm.cpp and myawesomealgorithm.h
The contents of the *.pro file lists all the files that belong to the algorithm:
ALGORITHM_SOURCES = myawesomealgorithm.cpp
ALGORITHM_HEADERS = myawesomealgorithm.h
DISTFILES += myawesomealgorithm.xml
The *.xml file describes the algorithm (name, author, description, ...). A minimal file would look like this:
<algorithm year="2016" author="Your name"/>
<string>This algorithm will produce an awesome tuning for your piano!</string>
In the *.h and *.cpp file you define a new class for your algorithm. This class will extend the class Algorithm and implement the pure virtual function algorithmWorkerFuntion that is called in a separate thread to start the calculation. Each algorithm should be declared in namespace to prevent naming conflicts. The minimal class would look like this:
class MyAwesomeAlgorithm : public Algorithm
MyAwesomeAlgorithm(const Piano &piano, const AlgorithmFactoryDescription &description);
virtual void algorithmWorkerFunction() override;
} // namespace myawsomealgorithm
#endif // MYAWESOMEALGORITHM_H
const AlgorithmFactory<myawesomealgorithm::MyAwesomeAlgorithm> AlgorithmFactory<myawesomealgorithm::MyAwesomeAlgorithm>::mSingleton(
MyAwesomeAlgorithm::MyAwesomeAlgorithm(const Piano &piano, const AlgorithmFactoryDescription &description) :
// Here comes the algorithm
} // namespace myawesomealgorithm
As you can see the constructor delivers a reference to a Piano and to the AlgorithmFactoryDescription. Internal the Piano will be copied in the class member mPiano that copies the content. Therefore you can do any changes to mPiano at your will.
In the source file you have to declare a Factory for your algorithm. In the simplest case you can just declare a new AlgorithmFactory with your new class as template argument. The constructor of the AlgorithmFactory expects a unique id of your algorithm as string parameter (here "myawesomealgorithm"). Note that your algorithm will be created when the user presses start calculation and will be deleted when the algorithm ends. You can store any amount of data in your algorithm, because it will not create an overhead if it is not running.
The constructor of your algorithm just calls its parent. To extend an algorithm with parameters it is possible to initialize constant member variables at this point.
Add file entry to algorithms/algorithms.pro
As last step you have to add the myawesomealgorthm.xml file to algorithms/algorithms.qrc. Add a new 'file'-tag in a text editor.
This is sole point where your have to change code of the EPT itself. Hopefully this can be omitted in the future.
Writing the actual algorithm
Your algorithm code comes into the algorithmWorkerFunction() that will be called in a separate thread when the user wants to start the calculation.
A simple algorithm that sets the tuning to a equal temperament on 440Hz would look like this:
for (int i = 0; i < mNumberOfKeys; ++i)
updateTuningCurve(i, mPiano.getEqualTempFrequency(i, 0, 440));
The first line loops over all the keys, whereby mNumberOfKeys is a member variable of the parent class Algorithm, that stores the number of keys. The function updateTuningCurve should always be called if there is a change in the computed frequency, because it will change the computed frequency of the specified key and also notify the system to redraw the current tuning curve. The getEqualTempFrequency will compute the frequency of the given key index with respect to an equal temperament. In the second parameter you can specify additional cents (in cents) and the third parameter is the frequency of A4.
Some algorithms might endure a long time. If a user wants to exit or cancel during this calculation you have to specify exit points. E.g. after each loop or iteration you can check with the macro CHECK_CANCEL_THREAD if the user wants to exits. If so it will call return and exit the function.
If you want to display the algorithm progress you can call showCalculationProgress(double) that expects a number between 0 and 1. This value will be displayed in the status bar.
Some algorithm can be adjusted using parameters that can be changed by the users, e.g. a seed for the random generator or an accuracy for the algorithm. Therefore you can define parameters in your *.xml file that will be displayed as changeable to the user in the algorithm info dialog. In the calculation you can access the current value of these parameters.
Define the parameter
If we want to add a 'cents' parameter to our upper algorithm we have to add a double parameter to our algorithm.xml file. This node is added into the 'algorithm'-node:
<param id="cents" type="double" default="0" min="-10" max="10" precision="1">
<string>Deviation in cents</string>
<string>This parameter defines the deviation in cents of the equal temperament tuning.</string>
Each parameter required the following attributes
- an id (this is the id how you can access this parameter later on)
- a type (can be double, int, list)
- a default value
and the following child nodes
- label (Text that is visible to the user)
- description (Description of the parameter, displayed as tool tip)
A double parameter has the following extra parameters
- a minimum value [optional]
- a maximum value [optional]
- a precision (this defines the decimal digits)
The defined parameter will display a slider along a spin box to the user.
Access the parameter
To access the parameter in your code use must use the mParameters member of Algorithm. Call
to access the parameter that we just created.
Translate your algorithm
To translate the description and labels of your algorithm you have to simply add another string entry with the language code as attribute to your *.xml file. To translate the name of the algorithm into German use: