projects

PyTuner

  • Python
  • Tkinter
  • NumPy
  • Sounddevice

The PyTuner Project

PyTuner was my first serious programming project, which I made for submission to a local technology competition. Through this project I learned alot about Graphical User Interaces (GUIs) and complex data structures within desktop applications. I used fast fourier transforms and linear algebra to transform audio from a raw guitar pluck to a message that tells the user whether or not their string is out of tune.

PyTuner

YIN Algorithm

The YIN algorithm converts waveform-based audio to a signular pitch, which is the entire goal of a guitar tuner.

I used sounddevice to record audio and numpy to perform computations.

The python code for the algorithm is as follows:

def YIN(sig, sr, wl=882, ws=441, f0_min=50,
        f0_max=500, ht=0.1):
    """

    Implement the YIN algorithm. Notice how 
    differenceFunction, cummalative_mean_norm_df, and pitch
    are all helper functions for this tool.

    Parameters:
    --------------
    sig iterable:
        numpy array of audio
    sr int:
        samplerate
    wl=882 int:
        length of calculation window for pitch
    ws=441 int:
        step for calculation window
    f0_min=50 int:
        minimum frequency threshold
    f0_max=500 int:
        maximum frequency threshold
    ht=0.1 float:
        harmonic threshold

    """

    t_min = int(sr / f0_max)
    t_max = int(sr / f0_min)

    timeScale = range(0, len(sig) - wl, ws)
    times = [t/float(sr) for t in timeScale]
    frames = [sig[t:t + wl] for t in timeScale]

    pitches = [0.0] * len(timeScale)
    harmonic_rates = [0.0] * len(timeScale)
    argmins = [0.0] * len(timeScale)

    for i, frame in enumerate(frames):

        # Compute YIN
        df = differenceFunction(frame, wl, t_max)
        CMNDF = cummalative_mean_norm_df(df, wl)
        p = pitch(CMNDF, t_min, t_max, ht)

        # Get results
        if np.argmin(CMNDF) > t_min:
            argmins[i] = float(sr / np.argmin(CMNDF))
        if p != 0:  # A pitch was found
            pitches[i] = float(sr / p)
            harmonic_rates[i] = CMNDF[p]
        else:
            harmonic_rates[i] = min(CMNDF)

    return pitches, harmonic_rates, argmins, times

Graphical User Interface

For the graphical user interface, I used a library called Tkinter, a simple python library for GUI development. Here is a sample of Tkinter code for a simple dropdown, implemented using object-oriented programming (OOP):

import tkinter as tk
from tkinter import ttk



class LabelDropdown(tk.Frame):
    """
    Dropdown - like object which projects a multiline string to GUI
    see LabelDropdown.__init__ for more details.
    """


    def __init__(self, master, label, buttonlabel, label_font, label_fg, label_bg,
                 buttonlabel_font, buttonlabel_fg, buttonlabel_bg, indent='  '):
        """__init__.

        Parameters
        ----------
        master : tk.Tk
            master
        label : str
            label
        buttonlabel : str
            label for button
        label_font : str
            font for button
        label_fg : str
            forground for button
        label_bg : str
            background for button
        buttonlabel_font : str
            font for button
        buttonlabel_fg : str
            foreground for button
        buttonlabel_bg : str
            background for button
        indent : str
            indentation for label
        """
        super(LabelDropdown, self).__init__()
        self['background'] = '#ffffff'
        self.label = label
        self.buttonlabel = buttonlabel
        self.label_font = label_font
        self.label_fg = label_fg
        self.label_bg = label_bg
        self.buttonlabel_font = buttonlabel_font
        self.buttonlabel_fg = buttonlabel_fg
        self.buttonlabel_bg = buttonlabel_bg
        self.indent = indent
        self.button = ttk.Button(self, text=self.buttonlabel+' ▶', style='DD.TButton', command=self.show)
        self.button.grid(row=0, column=0)
        self.style_init()
    def show(self):
        """
        Show Label
        """
        self.button['command'] = self.hide
        self.button['text'] = self.buttonlabel + ' ▼'
        self.label_list = self.label.split('\n')
        self.label_widgets = []
        if len(self.label_widgets) == 0:
            for i in range(len(self.label_list)):
                self.label_list[i] = self.indent+self.label_list[i] # notice that indent.join(label_list) would leave out the first line.
                self.label_widgets.append(ttk.Label(self, text=self.label_list[i], style='DD.TLabel'))
                self.label_widgets[i].grid(row=i+1, column=0)
        else:
            for i in range(len(self.label_widgets)):
                self.label_widgets[i].grid(row=i+1,column=0)
    def hide(self):
        """
        Hide Docstring
        """
        self.button['command'] = self.show
        self.button['text'] = self.buttonlabel + ' ▶'
        for i in self.label_widgets:
            i.grid_forget()
    def style_init(self):
        """
        initialize styles
        """
        self.style = ttk.Style(self)
        self.style.configure('DD.TLabel',
                             font=self.buttonlabel_font,
                             foreground=self.buttonlabel_fg,
                             background=self.buttonlabel_bg,
                             relief='flat', pady=10)
        self.style.configure('DD.TButton',
                             font=self.buttonlabel_font,
                             foreground=self.buttonlabel_fg,
                             background=self.buttonlabel_bg,
                             relief='flat')
        self.style.map('DD.TButton',
                       foreground=[('active', '#000000')],
                       background=[('active', '#ffffff')])
        self.style.layout('DD.TButton',
                          [('Button.button',
                            {'sticky': 'nswe', 'children': 
                                #[('Button.padding', {'sticky': 'nswe', 'children': 
                                    [('Button.label', {'sticky': 'nswe'})]
                                   # })]
                                })]
                          )

See the code!