Autotel

Universal tuner

Go to the tuner: autotel.co/tuner

I bought a harp because I wanted to delve into non western tunings. While many synthesizers are capable of being tuned in this way, I have observed that I tend to find harmonies more attractive in real strings. In other words, I would have a greater tendency to find ugly a harmony on an electronic synthesizer than a real one. Finding out that the so-called universal tuners in android's app store, in reality only works in terms of the western scale. I could not find tuners that would let me utilize any arbitrary list of frequencies. That is why I created my own actually universal tuner.

Things I discovered in the development

In my wave-sculpting experiment, I had been developing a method of working inspired in frameworks. This method consists simply on having a class named Model, which represents a set of attributes; and adding a function that lets any object subscribe to changes on those attributes. This simple idea has led me to make much easier to understand code, without having to use an actual framework. With the model abstraction, all the data points can be tied together, especially GUI elements that have to display the current value of some variable. One example of model, is how the FFT display keeps up with the ability to "pan" (grab and drag left) in it.

class Model {
    constructor(settings) {
        const beforeChangeListeners = [];
        const changeListeners = [];
        
        this.settings = settings;

        /** 
         * this is used to tie parameters together, for example if one setting is 
         * always 2 times other setting.
         * @param {BeforeChangesListener} newCallback
         **/
        this.beforeUpdate = (newCallback) => {
            if (typeof newCallback !== "function")
                throw new Error(`Callback has to be function but it is ${typeof newCallback}`);
            beforeChangeListeners.push(newCallback);
            newCallback(this.settings,this.settings);
        };
        /**
         * interface uses this method to connect changes in model to redraws
         * @param {ChangedParameterList} newCallback
         * */
        this.onUpdate = (newCallback) => {
            if (typeof newCallback !== "function")
                throw new Error(`Callback has to be function but it is ${typeof newCallback}`);
            changeListeners.push(newCallback);
            newCallback(this.settings);
        };

        /**
         * model uses this method to notify changes to the interface
         * it will not trigger "beforeUpdate" listeners.
         * @param {ChangedParameterList} [changes]
         **/
        this.changed = (changes = {}) => {
            changeListeners.forEach((cb) => { cb(changes); });
        };

        /**
         * change parameter values, triggering onchange listeners
         * @param {ChangedParameterList} [changes]
         **/
        this.set=(changes = {})=>{
            beforeChangeListeners.forEach((cb) => { cb(changes,this.settings); });
            Object.assign(this.settings,changes);
            this.changed(changes);
            return this;
        }

        //get the initial state of the model
        this.triggerInitialState = () => {
            this.set(settings);
        };
    }
}
export default Model;

The pitch detection method took more than one try. At first, it consisted on calculating the FFT (with web audio api), and picking the FFT bin which had the highest level. This led to correct results, but the frequencies are quantized to the amount of bins in the FFT. Increasing the FFT size high enough to procure a precise enough tone estimation with this method would require too big of an FFT size; which takes a lot of computer power and adds too much latency. I managed to get a better estimate of the frequency by making a weighted average of the FFT bin of the highest level, with its neighbouring bins. The weight of each frequency, is of course, the sound level of each. This approach gave more precise results. The result tends to measure about 0.2 Hz below the actual frequency (probably the algorithm needs a bit of fine-tuning).

I was interested in being able to try many intonations before having to actually tune an instrument, which is why I implemented a simple synthesizer that gets tuned automatically to the list of frequencies of the tuner. I also added the capability to upload scala files, which are parsed thanks to Joseph Post's scl-to-frequencies repository. There is a zip file with ~ 5000 scala files available at hhuygens-fokker scala downloads page.

My personal projects usually start from an idea, and then evolve wildly either because I found out that the initial concept didn't work or the idea mutated. In this case, the project ended up being quite accurately the same as I decided at the beginning.

Future ideas

Some aspects of the interface are a bit ugly, it could be a nice point of improvement in the future.

As a future project, I can see how it would be possible to create an algorithm that generates music based on harmony between raw frequency values: by measuring . By measuring harmonicity between two frequencies and then building "harmonic distances" between pairs of frequencies.

The repository of this tuner is available at https://gitlab.com/autotel/universal-tuner