image thumbnail

Guitar Tuner Demo from MATLAB Expo 2011 in Tokyo

by

 

03 Nov 2011 (Updated )

This takes audio input and calculates the frequency for use in tuning a guitar

guitartuner
classdef guitartuner < handle
    %GUITARTUNER Generates a guitartuner object
    %   This program uses autocorrelation to find the frequency of a sound
    %   input. It requires the Statistics Toolbox, Signal Processing
    %   Toolbox, and Data Acquisition Toolbox
    %
    %   Example:
    %   x = guitartuner;

    
    properties (SetAccess = private)
        haxes
        haxes2
        hplot
        note
        visible
        figurenumber
        T
        ai
        hbuttons
        Fs
        Flist
        dialback
        dial
        mode
        auto
        hgreenlight
    end
    
    methods
        function obj = guitartuner()
            
            obj.figurenumber = colordef(figure,'black');
            fcol = get(gcf,'color');
            set(obj.figurenumber,'closerequestfcn',@(~,~) delete(obj));
            
            obj.T = timer;
            
            uipanel('units','no','pos',[.03 .03 .64 .94],'backgroundcolor',fcol+0.1);
            obj.Fs = 44100;
            obj.ai = analoginput('winsound');
            addchannel(obj.ai,1);
            set(obj.ai,'Samplerate',obj.Fs,'samplespertrigger',Inf);
            set(obj.ai,'bufferingconfig',[512 1024]);
            
            obj.haxes = subplot(211);
            obj.hplot = plot(0:2047,nan(1,2048),'g'); xlim([0 2047]);  grid on;
            obj.haxes2 = subplot(212);
            
            [obj.dialback obj.dial obj.hgreenlight] = guitartuner.startpatch;
            axis equal;
            axis([-1.2 1.2 0 1.2]);
            
            set(obj.haxes,'units','n','pos',[.1 .6 .5 .3],'ylim',[-1 1],'ylimmode','man','xlimmode','man');
            set(obj.haxes2,'units','n','pos',[.1 .1 .5 .4],'color',[0 0 0]);
            box on;
            set(obj.haxes2,'xgrid','off','ygrid','off','ytick',[],'xtick',[]);
             
            h(1) = uicontrol('style','push','units','no','pos',[.7 .2 .28 .1],'String','E 82.407 Hz');
            h(2) = uicontrol('style','push','units','no','pos',[.7 .3 .28 .1],'String','A 110 Hz   ');
            h(3) = uicontrol('style','push','units','no','pos',[.7 .4 .28 .1],'String','D 146.83 Hz');
            h(4) = uicontrol('style','push','units','no','pos',[.7 .5 .28 .1],'String','G 196 Hz   ');
            h(5) = uicontrol('style','push','units','no','pos',[.7 .6 .28 .1],'String','B 246.94 Hz');
            h(6) = uicontrol('style','push','units','no','pos',[.7 .7 .28 .1],'String','e 329.63 Hz');
            h(7) = uicontrol('style','push','units','no','pos',[.7 .8 .28 .1],'String','Automatic','Fore','r');
            
            set(h,'fontsize',16,'backgroundcolor',fcol+0.1,'foregroundcolor','w');
            obj.hbuttons = h;
            obj.auto = 1;
            
            obj.mode = 'tuning';
            obj.note = 'E';
            obj.Flist = nan(1,3);
            
            set(obj.T,'timerfcn',{@(x,y,obj)dosound(obj),obj},'period',1/1000,'ExecutionMode','fixedspacing');
            for i = 1:7
                set(h(i),'callback',{@(x,y,obj,i)setlevel(obj,i),obj,i})
            end
            start(obj);
        end
        
        function setlevel(obj,i)
            if isequal(i,'auto') || i == 7
                obj.auto = 1;
            elseif isnumeric(i)
                notes = 'EADGBe';
                obj.note = notes(i);
                obj.auto = 0;
            else
                obj.note = i;
                obj.auto = 0;
            end
            set(obj.hbuttons,'ForegroundColor','w')
            set(obj.hbuttons(i),'ForegroundColor','r')
            
            
        end
        
        function start(obj)
            obj.visible = 1;
            set(obj.figurenumber, 'visible','on');
            
            start(obj.T);
            
        end
        
        function stop(obj)
            obj.visible = 0;
            set(obj.dial,'facecolor',[.9 .9 .9]);
            %set(obj.figurenumber, 'visible','off');
            stop(obj.T);
            stop(obj.ai);
            flushdata(obj.ai);
        end
        
        function delete(obj)
            stop(obj.T);
            delete(obj.T);
            delete(obj.figurenumber);
            delete(obj.ai);
            daqreset;
        end
        
    end
    
    methods (Access = private)
        function adjustval(obj)
            switch obj.note
                case 'E'
                    f = 82.407;
                case 'A'
                    f = 110;
                case 'D'
                    f = 146.83;
                case 'G'
                    f = 196;
                case 'B'
                    f = 246.94;
                case 'e'
                    f = 329.63;
            end
            
            %F = nanmean(obj.Flist);
            F = mean(obj.Flist(~isnan(obj.Flist)));
            
            if obj.auto && ~isnan(F)
                notelist = [82.407 110 146.83 196 246.94 329.63];
                dist = abs(log(F) - log(notelist));
                [~, loc] = min(dist);
                f = notelist(loc);
                set(obj.hbuttons(1:6),'ForegroundColor','w')
                set(obj.hbuttons(loc),'ForegroundColor','r')
            end
            
            q = log(F/f)*10;
            q = min(max(q,-pi/2),pi/2);
            
            qp = round((q+pi/2) * 81/(pi));
            set( obj.hgreenlight(2,:) ,'visible','on');
            set( obj.hgreenlight(2,1:qp) ,'visible','off');
            
           
            q2 = linspace(0,pi,11);
            
            P = [cos(q) sin(q); -sin(q) cos(q)]*[[-.1*cos(q2) 0];[-.1*sin(q2) 1]];
            set(obj.dial,'xdata',P(1,:),'ydata',P(2,:));
            
            set(obj.dial,'facecolor',[1 1 1]);
            if abs(log(F/f)) < 0.005 %Within about 0.5%
                set(obj.haxes2,'color',[.7 1 .7])
            else
                set(obj.haxes2,'color',[0.0 0 0])
            end
            if isnan(F)
                F = '--';
                set(obj.dial,'visible','off')
            else
                set(obj.dial,'visible','on')
            end
            
            title(obj.haxes2,[num2str(F) ' Hz'],'fontsize',20);
            axis equal;
            axis([-1.2 1.2 0 1.2]);
            drawnow;
            
            
        end
        
        function dosound(obj)
            
            if ~isrunning(obj.ai)
                start(obj.ai)
            end
            N = get(obj.ai,'samplesavailable');
            if N < 2048
                return
            end
            D = getdata(obj.ai,2048);
            F = guitartuner.getfreq(D,obj.Fs);
            obj.Flist = [obj.Flist(2:end) F];
            flushdata(obj.ai);
            adjustval(obj);
            set(obj.hplot,'ydata',D);

            drawnow;
        end
        
        
    end
    
    methods (Access = private, Static = true)
        function F = getfreq(V,Fs)
            V = interpft(V,4*numel(V));
            Fs = 4*Fs;
            V = xcorr(V,4*1000,'unbiased');
            thresh = 0.8;
            if V(4001) < 0.001  % 0.001 = (amplitude)^2 / 2
                F = nan;
                return;
            end
            
            V = V(4002:end)/V(4001);
            
            dV = diff(V);
            peaks = [0; dV] > 0 & [dV; 0] < 0 & (V > thresh);
            peaks = find(peaks,1);
            if isempty(peaks)
                F = nan;
                return
            end
            warning('off','MATLAB:polyfit:RepeatedPointsOrRescale');
            C = polyfit(-1:1,V(peaks-1:peaks+1)',2);
            peak = peaks-C(2)/2/C(1);
            F = Fs/peak;
            if F > 4000 %It should never be this high, just as a check
                F = nan;
            end
            
        end
        function [dialback dial hgreenlight] = startpatch()
            hold on;
            
            q = linspace(0,pi,82);
            r1 = 0.5;
            r2 = 1.15;
            dq = 0.005;
            cmap = jet(numel(q)-1);
            cmap = flipud(cmap);
            hgreenlight = zeros(2,numel(q)-1);
            for n = 1:numel(q)-1
                xd = [r1*cos(q(n)+dq)  r2*cos(q(n)+dq) r2*cos(q(n+1)-dq) r1*cos(q(n+1)-dq)];
                yd = [r1*sin(q(n)+dq)  r2*sin(q(n)+dq) r2*sin(q(n+1)-dq) r1*sin(q(n+1)-dq)];
                hgreenlight(1,n) = patch(xd,yd,cmap(n,:));
                hgreenlight(2,n) = patch(xd,yd,cmap(n,:)*0.1+0.2);
            end
            hgreenlight = fliplr(hgreenlight);

            
            
            q = linspace(0,pi,101);
            x = cos(q); y = sin(q);
            dialback = patch(x([1:end 1]),y([1:end 1]),'k');
            set(dialback,'facecolor','none');
            plot(x([1:end 1]),y([1:end 1]),'w');
            q = linspace(0,pi,11);
            dial = patch([-.1*cos(q) 0],[-.1*sin(q) 1],[.9 .9 .9]);
            for n = round(linspace(1,101,9));
                plot([0 x(n)],[0 y(n)],'w:');
            end
            
           
        end
    end
end

Contact us