Code covered by the BSD License  

Highlights from
AutoTune Toy

image thumbnail

AutoTune Toy

by

 

10 Jan 2010 (Updated )

Allows you to record and graphically manipulate and pitch correct your voice.

Editor's Notes:

This file was selected as MATLAB Central Pick of the Week

AutoTuneToy(varargin)
function varargout = AutoTuneToy(varargin)
% AUTOTUNETOY M-file for AutoTuneToy.fig
%      AUTOTUNETOY, by itself, creates a new AUTOTUNETOY or raises the existing
%      singleton*.
%
%      H = AUTOTUNETOY returns the handle to a new AUTOTUNETOY or the handle to
%      the existing singleton*.
%
%      AUTOTUNETOY('CALLBACK',hObject,eventData,handles,...) calls the local
%      function named CALLBACK in AUTOTUNETOY.M with the given input arguments.
%
%      AUTOTUNETOY('Property','Value',...) creates a new AUTOTUNETOY or raises the
%      existing singleton*.  Starting from the left, property value pairs are
%      applied to the GUI before AutoTuneToy_OpeningFcn gets called.  An
%      unrecognized property name or invalid value makes property application
%      stop.  All inputs are passed to AutoTuneToy_OpeningFcn via varargin.
%
%      *See GUI Options on GUIDE's Tools menu.  Choose "GUI allows only one
%      instance to run (singleton)".
%
% See also: GUIDE, GUIDATA, GUIHANDLES

% Edit the above text to modify the response to help AutoTuneToy

% Last Modified by GUIDE v2.5 09-Jan-2010 16:20:40

% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name',       mfilename, ...
                   'gui_Singleton',  gui_Singleton, ...
                   'gui_OpeningFcn', @AutoTuneToy_OpeningFcn, ...
                   'gui_OutputFcn',  @AutoTuneToy_OutputFcn, ...
                   'gui_LayoutFcn',  [] , ...
                   'gui_Callback',   []);
if nargin && ischar(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
    gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT


% --- Executes just before AutoTuneToy is made visible.
function AutoTuneToy_OpeningFcn(hObject, eventdata, handles, varargin)
% This function has no output args, see OutputFcn.
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
% varargin   command line arguments to AutoTuneToy (see VARARGIN)

% Choose default command line output for AutoTuneToy
handles.output = hObject;

handles.version = '0.1';

set(handles.axes1,'XTickLabel',{});
set(handles.axes1,'YTickLabel',{});


% Axes2 is just for mouse click input
set(handles.axes2,'XTick',[]);
set(handles.axes2,'YTick',[]);
set(handles.axes2,'XTickLabel',{});
set(handles.axes2,'YTickLabel',{});
set(handles.axes2,'ButtonDownFcn','AutoTuneToy(''mouseclick'',gcbo,[],guidata(gcbo))');


% Set defaults for the program
handles = set_defaults(handles);

% Update handles structure
guidata(hObject, handles);

% UIWAIT makes AutoTuneToy wait for user response (see UIRESUME)
% uiwait(handles.figure1);


% --- Outputs from this function are returned to the command line.
function varargout = AutoTuneToy_OutputFcn(hObject, eventdata, handles) 
% varargout  cell array for returning output args (see VARARGOUT);
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Get default command line output from handles structure
varargout{1} = handles.output;


function handles = set_defaults(handles)

% Recording options defaults
handles.record_options.Fs = 11025;              % Sampling frequency (Hz)
handles.record_options.nbits = 16;              % Bits of precision
handles.record_options.precision = 'single';    % Save as single precision vector
handles.record_options.nchans = 1;              % Single audio channel (mono)
handles.record_options.initial_trim = 0.1;      % Always trim off the initial 0.1 sec

% Scale defaults
handles.scale_options.scale = 'Cmajor';         % Scale for pitch correction
handles.scale_options.indices = [];
handles.scale_options.freqs = [];
handles.scale_options.notes = {};
handles.scale_options.fund_index = [];

% Pitch detection defaults
handles.pitch_options.Fmin = 50;             	% Min frequency to search
handles.pitch_options.Fmax = 600;            	% Max frequency to search
handles.pitch_options.block_length = 0.04;   	% Length of each chuck to analyze for frequency
handles.pitch_options.step_per_block = 2;       % What fraction of the block width to step for each pitch detect
handles.pitch_options.threshold_amp = 0.25;      % Threshold of autocorr to be called a peak
handles.pitch_options.harmonic_deviation = 0.2; % Max deviation from multiple fundamental to be considered a harmonic (0.2 = +/- 20%)
handles.pitch_options.threshold_ratio = 0.6;    % Max ratio in heights of autocorr allowed between fundamental and harmonics
handles.pitch_options.f0_target_sample_time = 0.08; % Over what time interval to sample previous freq's to determine next target f0
handles.pitch_options.target_tol = 0.3;         % Max tolerance from f0 target (0.2 = +/-20%)

handles.pitch_options.slider1 = [0.02:0.01:0.1];
handles.pitch_options.slider2 = [1:8];
handles.pitch_options.slider3 = [0:0.05:1];
handles.pitch_options.slider4 = [0:0.05:1];
handles.pitch_options.slider5 = [0:0.05:1];
handles.pitch_options.slider6 = [0:0.02:0.4];
handles.pitch_options.slider7 = [0:0.05:1];
handles.pitch_options.fields = {
    'block_length'
    'step_per_block'
    'threshold_amp'
    'harmonic_deviation'
    'threshold_ratio'
    'f0_target_sample_time'
    'target_tol'
    };

handles.pitch_compress_options.scale_factor = 0.5;
handles.pitch_compress_options.decay_time = 0;

handles.pitch_compress_options.slider1 = [0:0.10:2];
handles.pitch_compress_options.slider2 = [0:0.5:10];
handles.pitch_compress_options.fields = {
    'scale_factor'
    'decay_time'
    };

handles.pitch_snap_options.snap_delay = 0;

handles.pitch_snap_options.slider1 = [0:0.1:1];
handles.pitch_snap_options.fields = {
    'snap_delay'
    };

handles.vibrato_options.amplitude = 0.2;
handles.vibrato_options.frequency = 6;
handles.vibrato_options.ramp = 0.4;

handles.vibrato_options.slider1 = [0:0.1:1];
handles.vibrato_options.slider2 = [1:1:10];
handles.vibrato_options.slider3 = [0:0.2:2];
handles.vibrato_options.fields = {
    'amplitude'
    'frequency'
    'ramp'
    };
    
% Initialize objects for record and playback
handles.record_obj = [];
handles.play_obj = [];

% Initialize structure for saving/manipulating sound data
handles.sound.A = [];
handles.sound.A_corrected = [];
handles.sound.Fs = [];
handles.sound.t = [];
handles.sound.f0 = [];
handles.sound.f0_save = [];
handles.sound.f0_corrected = [];
handles.sound.f0_corrected_save = [];
handles.sound.scale_factor = [];
handles.sound.tcalc = [];
handles.sound.selected_points = logical([]);
handles.sound.selected_points_save = logical([]);

% Initialize status reporting
handles.status.isrecording = false;
handles.status.isplaying = false;
handles.status.isreset = false;
handles.status.ispitch = false;
handles.status.iscompress = false;
handles.status.issnap = false;
handles.status.isvibrato = false;
handles.status.isundo = false;
handles.status.isview = false;
handles.status.ismousezoom = false;
handles.status.issave = false;
handles.status.Xlim = [];
handles.status.Ylim = [];
handles.status.filename = '';


[handles.scale_options.indices,...
    handles.scale_options.freqs,...
    handles.scale_options.notes,...
    handles.scale_options.fund_index] = ...
    get_scale(handles.scale_options.scale);

handles.status.isreset = true;
handles = update_GUI(handles);

set(handles.original_button,'value',1);
set(handles.plot_selection,'value',2);



function handles = update_plot(handles)

val = get(handles.plot_selection,'value');
axes(handles.axes1);

if val == 1
    if ~isempty(handles.sound.t) && ~isempty(handles.sound.A)
        hplot = [];
        legends = {};
        if ~isempty(handles.sound.A_corrected)
            hplot(1) = plot(handles.axes1,handles.sound.t,handles.sound.A,'color',[0.7 0.7 0.7]);
        else
            hplot(1) = plot(handles.axes1,handles.sound.t,handles.sound.A,'color','b');
        end
        legends{1} = 'Original';
        if ~isempty(handles.sound.A_corrected)
            hold on
            hplot(2) = plot(handles.axes1,handles.sound.t,handles.sound.A_corrected,'color','b');
            legends{2} = 'Modified';
            hold off
        end
        if isempty(handles.status.Xlim)
            set(handles.axes1,'Xlim',[min(handles.sound.t) max(handles.sound.t)]);
        else
            set(handles.axes1,'Xlim',handles.status.Xlim);
        end
        dA = max(handles.sound.A)-min(handles.sound.A);
        set(handles.axes1,'Ylim',[min(handles.sound.A)-0.1*dA max(handles.sound.A)+0.1*dA]);
        %legend(hplot,legends);
    else
        cla
    end
elseif val == 2
    if ~isempty(handles.sound.tcalc) && ~isempty(handles.sound.f0)
        hplot = [];
        f0 = handles.sound.f0;
        f0(f0 < 1) = NaN;
        if all(isnan(f0))
            cla
            hwarn = warndlg('No pitch detected with current settings!','WARNING');
            waitfor(hwarn);
        else
            hplot(1) = semilogy(handles.axes1,handles.sound.tcalc,f0,'.-','color',[0.7 0.7 0.7]);
            %legends{1} = 'Original';
            if ~isempty(handles.sound.f0_corrected)
                hold on
                f02 = handles.sound.f0_corrected;
                f02(f02 < 1) = NaN;
                hplot(2) = semilogy(handles.axes1,handles.sound.tcalc,f02,'.-','color','b');
                %legends{2} = 'Modified';
                
                if any(handles.sound.selected_points)
                    f03 = f02;
                    f03(~handles.sound.selected_points) = NaN;
                    semilogy(handles.axes1,...
                        handles.sound.tcalc,f03,'.-','color','r');
                end
                hold off
            end
            grid on
%             factors = [1.2,10];
%             tmp = min(f0)./factors;
%             Ylim(1) = max(tmp(tmp <= min(f02)));
%             tmp = max(f0).*factors;
%             Ylim(2) = min(tmp(tmp >= max(f02)));
            Ylim = [min(f0)/1.2 max(f0)*1.2];
            set(handles.axes1,'Ylim',Ylim);
            set(handles.axes1,'Ytick',handles.scale_options.freqs);
            set(handles.axes1,'YtickLabel',handles.scale_options.notes);
            if isempty(handles.status.Xlim)
                set(handles.axes1,'Xlim',[min(handles.sound.t) max(handles.sound.t)]);
            else
                set(handles.axes1,'Xlim',handles.status.Xlim);
            end
            %legend(hplot,legends);
        end
    else
        cla
    end
end

% Invisible axes for getting mouse information
set(handles.axes2,'Xlim',get(handles.axes1,'Xlim'));
set(handles.axes2,'Ylim',get(handles.axes1,'Ylim'));
set(handles.axes2,'Yscale',get(handles.axes1,'Yscale'));
set(handles.axes2,'ButtonDownFcn','AutoTuneToy(''mouseclick'',gcbo,[],guidata(gcbo))');
set(handles.axes2,'Color','none');
axes(handles.axes2);

clear f0 f02 f03

function handles = update_GUI(handles)

if handles.status.isrecording
    set(handles.play_button,'enable','off');
    set(handles.record_button,'enable','off');
    set(handles.stop_button,'enable','on');
elseif handles.status.isplaying
    set(handles.play_button,'enable','off');
    set(handles.record_button,'enable','off');
    set(handles.stop_button,'enable','off');
else
    if ~isempty(handles.sound.A)
        set(handles.play_button,'enable','on');
    else
        set(handles.play_button,'enable','off');
    end
    set(handles.record_button,'enable','on');
    set(handles.stop_button,'enable','off');
end
    

% Set all apply buttons to disabled until there is a frequency vector
if isempty(handles.sound.f0_corrected) || handles.status.isplaying
    state = 'off';
else
    state = 'on';
end
% set(handles.apply_compress_button,'enable',state);
% set(handles.snap_up_button,'enable',state);
% set(handles.snap_down_button,'enable',state);
% set(handles.apply_vibrato_button,'enable',state);
% set(handles.delete_button,'enable',state);
% set(handles.join_button,'enable',state);    
% set(handles.zoom_mouse_button,'enable',state); 
% set(handles.zoom_in_button,'enable',state); 
% set(handles.zoom_out_button,'enable',state); 
% set(handles.zoom_full_button,'enable',state); 
% set(handles.pan_left_button,'enable',state); 
% set(handles.pan_right_button,'enable',state); 
% set(handles.undo_button,'enable',state); 
% set(handles.undo_all_button,'enable',state); 
    
% Update pitch detection panel
if handles.status.isreset || handles.status.ispitch
    fields = handles.pitch_options.fields;
    
    set(handles.pitch_text1,'String',...
        sprintf('%0.0f ms',1000*handles.pitch_options.(fields{1})));
    set(handles.pitch_text2,'String',...
        sprintf('%0.0f',handles.pitch_options.(fields{2})));
    set(handles.pitch_text3,'String',...
        sprintf('%0.0f',100*handles.pitch_options.(fields{3})));
    set(handles.pitch_text4,'String',...
        sprintf('%0.0f %%',100*handles.pitch_options.(fields{4})));
    set(handles.pitch_text5,'String',...
        sprintf('%0.0f',100*handles.pitch_options.(fields{5})));
    set(handles.pitch_text6,'String',...
        sprintf('%0.0f ms',1000*handles.pitch_options.(fields{6})));
    set(handles.pitch_text7,'String',...
        sprintf('%0.0f %%',100*handles.pitch_options.(fields{7})));
    handles.status.ispitch = false;
end
if handles.status.isreset
    for N = 1:7
        all_vals = handles.pitch_options.(sprintf('slider%i',N));
        val = (handles.pitch_options.(fields{N}) - all_vals(1)) / ...
            (all_vals(end) - all_vals(1));
        set(handles.(sprintf('pitch_slider%i',N)),'value',val);
        set(handles.(sprintf('pitch_slider%i',N)),'sliderstep',...
            [1 1].*(1/(length(all_vals)-1)));
    end
end

% Update pitch compression panel
if handles.status.isreset || handles.status.iscompress
    fields = handles.pitch_compress_options.fields;
    
    set(handles.compress_text1,'String',...
        sprintf('%0.0f %%',100*handles.pitch_compress_options.(fields{1})));
    set(handles.compress_text2,'String',...
        sprintf('%0.1f s',handles.pitch_compress_options.(fields{2})));
    handles.status.iscompress = false;
end
if handles.status.isreset
    for N = 1:2
        all_vals = handles.pitch_compress_options.(sprintf('slider%i',N));
        val = (handles.pitch_compress_options.(fields{N}) - all_vals(1)) / ...
            (all_vals(end) - all_vals(1));
        set(handles.(sprintf('compress_slider%i',N)),'value',val);
        set(handles.(sprintf('compress_slider%i',N)),'sliderstep',...
            [1 1].*(1/(length(all_vals)-1)));
    end
end
 
% Update pitch snap panel
if handles.status.isreset || handles.status.issnap
    fields = handles.pitch_snap_options.fields;
    
    set(handles.snap_text1,'String',...
        sprintf('%0.1f s',handles.pitch_snap_options.(fields{1})));
    handles.status.issnap = false;
end
if handles.status.isreset
    for N = 1:1
        all_vals = handles.pitch_snap_options.(sprintf('slider%i',N));
        val = (handles.pitch_snap_options.(fields{N}) - all_vals(1)) / ...
            (all_vals(end) - all_vals(1));
        set(handles.(sprintf('snap_slider%i',N)),'value',val);
        set(handles.(sprintf('snap_slider%i',N)),'sliderstep',...
            [1 1].*(1/(length(all_vals)-1)));
    end
end

% Update vibrato panel
if handles.status.isreset || handles.status.isvibrato
    fields = handles.vibrato_options.fields;
    
    set(handles.vibrato_text1,'String',...
        sprintf('%0.1f',handles.vibrato_options.(fields{1})));
    set(handles.vibrato_text2,'String',...
        sprintf('%0.0f Hz',handles.vibrato_options.(fields{2})));
    set(handles.vibrato_text3,'String',...
        sprintf('%0.1f s',handles.vibrato_options.(fields{3})));
    handles.status.isvibrato = false;
end
if handles.status.isreset
    for N = 1:3
        all_vals = handles.vibrato_options.(sprintf('slider%i',N));
        val = (handles.vibrato_options.(fields{N}) - all_vals(1)) / ...
            (all_vals(end) - all_vals(1));
        set(handles.(sprintf('vibrato_slider%i',N)),'value',val);
        set(handles.(sprintf('vibrato_slider%i',N)),'sliderstep',...
            [1 1].*(1/(length(all_vals)-1)));
    end
end

% Fill in Scale Selection menu
if handles.status.isreset
    [indices,freqs,notes,fund_index] = get_scale();
    set(handles.scale_selection_list,'String',[indices,'CUSTOM']);
    I = find(strcmp(indices,handles.scale_options.scale));
    if ~isempty(I)
        set(handles.scale_selection_list,'Value',I);
    end
end

% Handle undo stuff
if isempty(handles.sound.f0_corrected_save);
    set(handles.undo_button,'enable','off');
    set(handles.undo_all_button,'enable','off');
else
    set(handles.undo_button,'enable','on');
    set(handles.undo_all_button,'enable','on');
end
handles.status.isundo = false;

% Handle view/zoom stuff
if handles.status.isview || handles.status.isreset
    if isempty(handles.sound.A)
        onoff = 'off';
    else
        onoff = 'on';
    end
    set(handles.plot_selection,'enable',onoff);
    set(handles.zoom_mouse_button,'enable',onoff);
    set(handles.zoom_in_button,'enable',onoff);
    set(handles.zoom_full_button,'enable',onoff);
    set(handles.pan_right_button,'enable',onoff);
    set(handles.pan_left_button,'enable',onoff);
    
    if isempty(handles.status.Xlim) && isempty(handles.status.Ylim)
        set(handles.zoom_out_button,'enable','off');
        set(handles.zoom_full_button,'enable','off');
    else
        set(handles.zoom_out_button,'enable','on');
        set(handles.zoom_full_button,'enable','on');
    end
    
    handles.status.isview = false;
end

if handles.status.isreset || handles.status.issave
    if isempty(handles.status.filename)
        set(handles.figure1,'Name','AutoTune Toy [untitled]');
    else
        [pathstr, name, ext, versn] = fileparts(handles.status.filename);
        set(handles.figure1,'Name',sprintf('AutoTune Toy [%s]',name))
    end
    handles.status.issave = false;
end

handles.status.isreset = false;    
 
function handles = update_pitch_sliders(handles,N)

fn = handles.pitch_options.fields{N};
slider_val = get(handles.(sprintf('pitch_slider%i',N)),'value');
all_vals = handles.pitch_options.(sprintf('slider%i',N));
new_val = slider_val*(all_vals(end)-all_vals(1))+all_vals(1);
[what,where] = min(abs(all_vals - new_val));
handles.pitch_options.(fn) = all_vals(where);

handles.status.ispitch = true;
handles = update_GUI(handles);

function handles = update_compress_sliders(handles,N)

fn = handles.pitch_compress_options.fields{N};
slider_val = get(handles.(sprintf('compress_slider%i',N)),'value');
all_vals = handles.pitch_compress_options.(sprintf('slider%i',N));
new_val = slider_val*(all_vals(end)-all_vals(1))+all_vals(1);
[what,where] = min(abs(all_vals - new_val));
handles.pitch_compress_options.(fn) = all_vals(where);

handles.status.iscompress = true;
handles = update_GUI(handles);

function handles = update_snap_sliders(handles,N)

fn = handles.pitch_snap_options.fields{N};
slider_val = get(handles.(sprintf('snap_slider%i',N)),'value');
all_vals = handles.pitch_snap_options.(sprintf('slider%i',N));
new_val = slider_val*(all_vals(end)-all_vals(1))+all_vals(1);
[what,where] = min(abs(all_vals - new_val));
handles.pitch_snap_options.(fn) = all_vals(where);

handles.status.issnap = true;
handles = update_GUI(handles);

function handles = update_vibrato_sliders(handles,N)

fn = handles.vibrato_options.fields{N};
slider_val = get(handles.(sprintf('vibrato_slider%i',N)),'value');
all_vals = handles.vibrato_options.(sprintf('slider%i',N));
new_val = slider_val*(all_vals(end)-all_vals(1))+all_vals(1);
[what,where] = min(abs(all_vals - new_val));
handles.vibrato_options.(fn) = all_vals(where);

handles.status.isvibrato = true;
handles = update_GUI(handles);    


% --- Executes on button press in 
function Untitled_1_Callback(hObject, eventdata, handles)
% hObject    handle to record_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)


% --- Executes on button press in record_button.
function record_button_Callback(hObject, eventdata, handles)
% hObject    handle to record_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

try
    handles.record_obj = audiorecorder(handles.record_options.Fs,...
        handles.record_options.nbits,handles.record_options.nchans);
    record(handles.record_obj);

    handles.status.isrecording = true;

    handles = update_GUI(handles);
    guidata(hObject, handles);
catch
    h = errordlg('Error starting audio input device!  Check sound card and microphone!','ERROR');
    waitfor(h);
    return
end


% --- Executes on button press in stop_button.
function stop_button_Callback(hObject, eventdata, handles)
% hObject    handle to stop_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

if handles.status.isrecording
    stop(handles.record_obj);
    handles.status.isrecording = false;
    
    % Get the waveform
    handles.sound.A = getaudiodata(handles.record_obj,...
        handles.record_options.precision);
    
    % Save sampling frequency
    handles.sound.Fs = handles.record_options.Fs;
    
    % Trim initial samples
    Nstart = round(handles.record_options.initial_trim*...
        handles.record_options.Fs);
    handles.sound.A = handles.sound.A(Nstart:end);
    handles.sound.A_corrected = handles.sound.A;
    
    % Calculate time vector
    handles.sound.t = (0:length(handles.sound.A)-1)./handles.record_options.Fs;
    
    % Clear old frequency data
    handles.sound.f0 = [];
    handles.sound.f0_save = [];
    handles.sound.f0_corrected = [];
    handles.sound.f0_corrected_save = [];
    handles.sound.selected_points = logical([]);
    handles.sound.selected_points_save = logical([]);
    
    % Clear plot limits
    handles.status.Xlim = [];
    handles.status.Ylim = [];
    
    handles = run_pitch_detection(handles);
    handles.status.isview = true;
    handles = update_plot(handles);
    handles = update_GUI(handles);
    guidata(hObject, handles);
    
end



% --- Executes on button press in play_button.
function play_button_Callback(hObject, eventdata, handles)
% hObject    handle to play_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Decide which to play
val = get(handles.original_button,'val');
if val == 1
    if isempty(handles.sound.A)
        return
    end
    A = handles.sound.A;
else
    if isempty(handles.sound.A_corrected)
        handles = run_pitch_correction(handles);
    end
    A = handles.sound.A_corrected;
end



handles.status.isplaying = true;
handles = update_GUI(handles);
handles = update_plot(handles);

% Get the current plot limits
Ylim = get(handles.axes1,'Ylim');
if isempty(handles.status.Xlim)
    tmin = min(handles.sound.t);
    tmax = max(handles.sound.t);
else
    [what,Imin] = min(abs(handles.status.Xlim(1)-handles.sound.t));
    [what,Imax] = min(abs(handles.status.Xlim(2)-handles.sound.t));
    tmin = handles.sound.t(Imin);
    tmax = handles.sound.t(Imax);
    A = A(Imin:Imax);
end

% Draw the vertical line to scan across
axes(handles.axes1);
hline = line(tmin.*[1 1],[Ylim],'color','r');

pause(0.1);

% Start playing the wave file
% Note: Updated wavplay to audioplayer on 12/10/12 for cross-platform functionality
%wavplay(A,handles.sound.Fs,'async');
p = audioplayer(A, handles.sound.Fs); 
play(p);
tic;
while 1
    tnow = toc + tmin;
    if tnow > tmax
        break
    end
    % Scan the line
    set(hline,'XData',[tnow tnow]);
    pause(0.02);
end
set(hline,'XData',[tmin tmin]);

% Done
handles.status.isplaying = false;
handles = update_GUI(handles);

guidata(hObject, handles);

clear A;
axes(handles.axes2);


% --------------------------------------------------------------------
function record_options_menu_Callback(hObject, eventdata, handles)
% hObject    handle to record_options_menu (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

Fs = handles.record_options.Fs;
nbits = handles.record_options.nbits;
initial_trim = handles.record_options.initial_trim;

prompt = {
    'Sampling rate (1000 - 44100 Hz):'
    'Bits per sample (8, 16, or 24):'
    'Record start delay (0 - 1000 ms):'
    };
dlg_title = 'Record options';
num_lines = [1 35];
def = {
    num2str(Fs)
    num2str(nbits)
    num2str(initial_trim*1000)
    };
answer = inputdlg(prompt,dlg_title,num_lines,def);
if ~isempty(answer)
    if ~isempty(answer{1})
        Fs = round(str2num(answer{1}));
    end
    if ~isempty(answer{2})
        nbits = round(str2num(answer{2}));
    end
    if ~isempty(answer{3})
        initial_trim = round(str2num(answer{3}))/1000;
    end

    input_error = false;
    if Fs < 1000 || Fs > 44100
        Fs = 11025;
        input_error = true;
    end
    if ~ismember(nbits,[8,16,24]);
        nbits = 16;
        input_error = true;
    end
    if initial_trim < 0 || initial_trim > 1
        initial_trim = 0.1;
        input_error = true;
    end
    if input_error
        hwarn = warndlg('One or more values out of range, default values used!',...
            'WARNING');
        waitfor(hwarn);
    end
    
    if handles.record_options.Fs ~= Fs || ...
            handles.record_options.nbits ~= nbits || ...
            handles.record_options.initial_trim ~= initial_trim

%         if ~isempty(handles.sound.A)
%             button = questdlg('This will erase previous sound data.  Continue?',...
%                 'WARNING','Yes','No','Yes');
%             if strcmp(button,'No')
%                 return
%             end
%         end
        
        handles.record_options.Fs = Fs;
        handles.record_options.nbits = nbits;
        handles.record_options.initial_trim = initial_trim;

%         handles.sound.A = [];
%         handles.sound.A_corrected = [];
%         handles.sound.t = [];
%         handles.sound.f0 = [];
%         handles.sound.f0_corrected = [];
%         handles.sound.tcalc = [];
%         handles.sound.selected_points = logical([]);
        
        handles = update_plot(handles);

        guidata(hObject, handles);
    end
    
end

% --- Executes on selection change in plot_selection.
function plot_selection_Callback(hObject, eventdata, handles)
% hObject    handle to plot_selection (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: contents = get(hObject,'String') returns plot_selection contents as cell array
%        contents{get(hObject,'Value')} returns selected item from plot_selection

if isempty(handles.sound.A_corrected)
    handles = run_pitch_correction(handles);
end
handles = update_plot(handles);
guidata(hObject, handles);

% --- Executes during object creation, after setting all properties.
function plot_selection_CreateFcn(hObject, eventdata, handles)
% hObject    handle to plot_selection (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: popupmenu controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end


% --- Executes on button press in original_button.
function original_button_Callback(hObject, eventdata, handles)
% hObject    handle to original_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hint: get(hObject,'Value') returns toggle state of original_button

set(handles.original_button,'val',1);
set(handles.modified_button,'val',0);


% --- Executes on button press in modified_button.
function modified_button_Callback(hObject, eventdata, handles)
% hObject    handle to modified_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hint: get(hObject,'Value') returns toggle state of modified_button

set(handles.original_button,'val',0);
set(handles.modified_button,'val',1);


% --- Executes on slider movement.
function pitch_slider1_Callback(hObject, eventdata, handles)
% hObject    handle to pitch_slider1 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'Value') returns position of slider
%        get(hObject,'Min') and get(hObject,'Max') to determine range of slider

N = 1;
handles = update_pitch_sliders(handles,N);
guidata(hObject, handles);

% --- Executes during object creation, after setting all properties.
function pitch_slider1_CreateFcn(hObject, eventdata, handles)
% hObject    handle to pitch_slider1 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: slider controls usually have a light gray background.
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end

% --- Executes on slider movement.
function pitch_slider2_Callback(hObject, eventdata, handles)
% hObject    handle to pitch_slider2 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'Value') returns position of slider
%        get(hObject,'Min') and get(hObject,'Max') to determine range of slider

N = 2;
handles = update_pitch_sliders(handles,N);
guidata(hObject, handles);

% --- Executes during object creation, after setting all properties.
function pitch_slider2_CreateFcn(hObject, eventdata, handles)
% hObject    handle to pitch_slider2 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: slider controls usually have a light gray background.
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end


% --- Executes on slider movement.
function pitch_slider3_Callback(hObject, eventdata, handles)
% hObject    handle to pitch_slider3 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'Value') returns position of slider
%        get(hObject,'Min') and get(hObject,'Max') to determine range of slider

N = 3;
handles = update_pitch_sliders(handles,N);
guidata(hObject, handles);

% --- Executes during object creation, after setting all properties.
function pitch_slider3_CreateFcn(hObject, eventdata, handles)
% hObject    handle to pitch_slider3 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: slider controls usually have a light gray background.
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end


% --- Executes on slider movement.
function pitch_slider4_Callback(hObject, eventdata, handles)
% hObject    handle to pitch_slider4 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'Value') returns position of slider
%        get(hObject,'Min') and get(hObject,'Max') to determine range of slider

N = 4;
handles = update_pitch_sliders(handles,N);
guidata(hObject, handles);

% --- Executes during object creation, after setting all properties.
function pitch_slider4_CreateFcn(hObject, eventdata, handles)
% hObject    handle to pitch_slider4 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: slider controls usually have a light gray background.
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end


% --- Executes on slider movement.
function pitch_slider5_Callback(hObject, eventdata, handles)
% hObject    handle to pitch_slider5 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'Value') returns position of slider
%        get(hObject,'Min') and get(hObject,'Max') to determine range of slider

N = 5;
handles = update_pitch_sliders(handles,N);
guidata(hObject, handles);

% --- Executes during object creation, after setting all properties.
function pitch_slider5_CreateFcn(hObject, eventdata, handles)
% hObject    handle to pitch_slider5 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: slider controls usually have a light gray background.
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end


% --- Executes on slider movement.
function pitch_slider6_Callback(hObject, eventdata, handles)
% hObject    handle to pitch_slider6 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'Value') returns position of slider
%        get(hObject,'Min') and get(hObject,'Max') to determine range of slider

N = 6;
handles = update_pitch_sliders(handles,N);
guidata(hObject, handles);

% --- Executes during object creation, after setting all properties.
function pitch_slider6_CreateFcn(hObject, eventdata, handles)
% hObject    handle to pitch_slider6 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: slider controls usually have a light gray background.
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end


% --- Executes on slider movement.
function pitch_slider7_Callback(hObject, eventdata, handles)
% hObject    handle to pitch_slider7 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'Value') returns position of slider
%        get(hObject,'Min') and get(hObject,'Max') to determine range of slider

N = 7;
handles = update_pitch_sliders(handles,N);
guidata(hObject, handles);

% --- Executes during object creation, after setting all properties.
function pitch_slider7_CreateFcn(hObject, eventdata, handles)
% hObject    handle to pitch_slider7 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: slider controls usually have a light gray background.
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end


% --- Executes on button press in apply_pitch_button.
function apply_pitch_button_Callback(hObject, eventdata, handles)
% hObject    handle to apply_pitch_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

if ~isempty(handles.sound.A)
    handles = run_pitch_detection(handles);
    handles = update_plot(handles);
    handles = update_GUI(handles);
    guidata(hObject, handles);
end


% --- Executes on slider movement.
function snap_slider1_Callback(hObject, eventdata, handles)
% hObject    handle to snap_slider1 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'Value') returns position of slider
%        get(hObject,'Min') and get(hObject,'Max') to determine range of slider

N = 1;
handles = update_snap_sliders(handles,N);
guidata(hObject, handles);

% --- Executes during object creation, after setting all properties.
function snap_slider1_CreateFcn(hObject, eventdata, handles)
% hObject    handle to snap_slider1 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: slider controls usually have a light gray background.
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end



% --- Executes on button press in snap_down_button.
function snap_button_Callback(hObject, eventdata, handles)
% hObject    handle to snap_down_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

if isempty(handles.sound.f0_corrected) || ~any(handles.sound.selected_points) ||...
        isempty(handles.scale_options.freqs);
    return
end

snap_direction = get(gcbo,'String');

% Pull out the frequency and time of the selected points
I = find(handles.sound.selected_points);
sp = handles.sound.selected_points(I(1):I(end));
f0 = handles.sound.f0_corrected(I(1):I(end));
t = handles.sound.tcalc(I(1):I(end));
t = t-t(1);

% Come up with a envelope for compressing that goes from 1 down to the
% desired compression factor over the desired time interval
if length(t) < 2
    where = 1;
elseif handles.pitch_snap_options.snap_delay > t(2)
    [what,where] = min(abs(t - handles.pitch_snap_options.snap_delay));
else
    where = 1;
end

tmp = f0(where:end);
mean_f0 = 10^mean(log10(tmp(sp(where:end))));
if strcmp(snap_direction,'Down')
    Iu = find(handles.scale_options.freqs < 0.99*mean_f0);
    mean_new = max(handles.scale_options.freqs(Iu));
elseif strcmp(snap_direction,'Up')
    Iu = find(handles.scale_options.freqs > 1.01*mean_f0);
    mean_new = min(handles.scale_options.freqs(Iu));
end
factor = mean_new/mean_f0.*ones(size(t));
if where > 1
    factor(1:where) = linspace(1,factor(end),where);
end

% Scale the deviations around the mean in a log sense
f0 = f0.*factor;
handles.sound.f0_corrected_save(end+1,:) = handles.sound.f0_corrected;
handles.sound.f0_save(end+1,:) = handles.sound.f0;
handles.sound.selected_points_save(end+1,:) = handles.sound.selected_points;
handles.sound.f0_corrected(handles.sound.selected_points) = f0(sp);

handles.sound.A_corrected = [];

handles = update_plot(handles);
handles = update_GUI(handles);
guidata(hObject, handles);

clear t f0 what where factor

% --- Executes on slider movement.
function compress_slider1_Callback(hObject, eventdata, handles)
% hObject    handle to compress_slider1 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'Value') returns position of slider
%        get(hObject,'Min') and get(hObject,'Max') to determine range of slider

N = 1;
handles = update_compress_sliders(handles,N);
guidata(hObject, handles);

% --- Executes during object creation, after setting all properties.
function compress_slider1_CreateFcn(hObject, eventdata, handles)
% hObject    handle to compress_slider1 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: slider controls usually have a light gray background.
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end


% --- Executes on slider movement.
function compress_slider2_Callback(hObject, eventdata, handles)
% hObject    handle to compress_slider2 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'Value') returns position of slider
%        get(hObject,'Min') and get(hObject,'Max') to determine range of slider

N = 2;
handles = update_compress_sliders(handles,N);
guidata(hObject, handles);

% --- Executes during object creation, after setting all properties.
function compress_slider2_CreateFcn(hObject, eventdata, handles)
% hObject    handle to compress_slider2 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: slider controls usually have a light gray background.
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end

% --- Executes on button press in apply_compress_button.
function apply_compress_button_Callback(hObject, eventdata, handles)
% hObject    handle to apply_compress_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
                  
if isempty(handles.sound.f0_corrected) || ~any(handles.sound.selected_points)
    return
end

% Pull out the frequency and time of the selected points
f0 = handles.sound.f0_corrected(handles.sound.selected_points);
I = find(handles.sound.selected_points);
t = handles.sound.tcalc(I(1):I(end));
t = t-t(1);

% Come up with a envelope for compressing that goes from 1 down to the
% desired compression factor over the desired time interval
factor = ones(size(t)).*handles.pitch_compress_options.scale_factor;
if handles.pitch_compress_options.decay_time > t(2)
    [what,where] = min(abs(t - handles.pitch_compress_options.decay_time));
    factor(1:where) = linspace(1,handles.pitch_compress_options.scale_factor,where);
end
factor = factor(handles.sound.selected_points(I(1):I(end)));

% Scale the deviations around the mean in a log sense
f0 = 10.^(mean(log10(f0)) + factor.*(log10(f0)-mean(log10(f0))));
handles.sound.f0_corrected_save(end+1,:) = handles.sound.f0_corrected;
handles.sound.f0_save(end+1,:) = handles.sound.f0;
handles.sound.selected_points_save(end+1,:) = handles.sound.selected_points;
handles.sound.f0_corrected(handles.sound.selected_points) = f0;

handles.sound.A_corrected = [];

handles = update_plot(handles);
handles = update_GUI(handles);
guidata(hObject, handles);

clear t f0 what where factor



% --- Executes on slider movement.
function vibrato_slider1_Callback(hObject, eventdata, handles)
% hObject    handle to vibrato_slider1 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'Value') returns position of slider
%        get(hObject,'Min') and get(hObject,'Max') to determine range of slider

N = 1;
handles = update_vibrato_sliders(handles,N);
guidata(hObject, handles);

% --- Executes during object creation, after setting all properties.
function vibrato_slider1_CreateFcn(hObject, eventdata, handles)
% hObject    handle to vibrato_slider1 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: slider controls usually have a light gray background.
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end


% --- Executes on slider movement.
function vibrato_slider2_Callback(hObject, eventdata, handles)
% hObject    handle to vibrato_slider2 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'Value') returns position of slider
%        get(hObject,'Min') and get(hObject,'Max') to determine range of slider

N = 2;
handles = update_vibrato_sliders(handles,N);
guidata(hObject, handles);

% --- Executes during object creation, after setting all properties.
function vibrato_slider2_CreateFcn(hObject, eventdata, handles)
% hObject    handle to vibrato_slider2 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: slider controls usually have a light gray background.
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end


% --- Executes on slider movement.
function vibrato_slider3_Callback(hObject, eventdata, handles)
% hObject    handle to vibrato_slider3 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'Value') returns position of slider
%        get(hObject,'Min') and get(hObject,'Max') to determine range of slider

N = 3;
handles = update_vibrato_sliders(handles,N);
guidata(hObject, handles);

% --- Executes during object creation, after setting all properties.
function vibrato_slider3_CreateFcn(hObject, eventdata, handles)
% hObject    handle to vibrato_slider3 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: slider controls usually have a light gray background.
if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor',[.9 .9 .9]);
end


% --- Executes on button press in apply_vibrato_button.
function apply_vibrato_button_Callback(hObject, eventdata, handles)
% hObject    handle to apply_vibrato_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

if isempty(handles.sound.f0_corrected) || ~any(handles.sound.selected_points) ||...
        isempty(handles.scale_options.freqs);
    return
end

% Pull out the frequency and time of the selected points
I = find(handles.sound.selected_points);
sp = handles.sound.selected_points(I(1):I(end));
f0 = handles.sound.f0_corrected(I(1):I(end));
t = handles.sound.tcalc(I(1):I(end));
t = t-t(1);

% Come up with a envelope for compressing that goes from 1 down to the
% desired compression factor over the desired time interval
if length(t) < 2
    where = 1;
elseif handles.vibrato_options.ramp > t(2)
    [what,where] = min(abs(t - handles.vibrato_options.ramp));
else
    where = 1;
end

A = handles.vibrato_options.amplitude.*...
    sin(2*pi*handles.vibrato_options.frequency*t)/12;
if where > 1
    A(1:where) = A(1:where).*linspace(0,1,where);
end
factor = 2.^(A);

% Scale the deviations around the mean in a log sense
f0 = f0.*factor;
handles.sound.f0_corrected_save(end+1,:) = handles.sound.f0_corrected;
handles.sound.f0_save(end+1,:) = handles.sound.f0;
handles.sound.selected_points_save(end+1,:) = handles.sound.selected_points;
handles.sound.f0_corrected(handles.sound.selected_points) = f0(sp);

handles.sound.A_corrected = [];

handles = update_plot(handles);
handles = update_GUI(handles);
guidata(hObject, handles);

clear t f0 what where factor


% --- Executes on button press in delete_button.
function delete_button_Callback(hObject, eventdata, handles)
% hObject    handle to delete_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

if isempty(handles.sound.f0_corrected) || ~any(handles.sound.selected_points)
    return
end

% Save for undoing
handles.sound.f0_save(end+1,:) = handles.sound.f0;
handles.sound.f0_corrected_save(end+1,:) = handles.sound.f0_corrected;
handles.sound.selected_points_save(end+1,:) = handles.sound.selected_points;

% Pull out the frequency and time of the selected points
handles.sound.f0_corrected(handles.sound.selected_points) = 0;
handles.sound.f0(handles.sound.selected_points) = 0;
handles.sound.selected_points = false(size(handles.sound.f0));

handles.sound.A_corrected = [];

handles = update_plot(handles);
handles = update_GUI(handles);
guidata(hObject, handles);

clear t f0 what where factor

% --- Executes on button press in join_button.
function join_button_Callback(hObject, eventdata, handles)
% hObject    handle to join_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

if isempty(handles.sound.f0_corrected) || ~any(handles.sound.selected_points)
    return
end

% Save for undoing
handles.sound.f0_save(end+1,:) = handles.sound.f0;
handles.sound.f0_corrected_save(end+1,:) = handles.sound.f0_corrected;
handles.sound.selected_points_save(end+1,:) = handles.sound.selected_points;

% Pull out the frequency and time of the selected points
any_changes = false;
I = find(handles.sound.selected_points);
f0 = handles.sound.f0;
for n = 1:length(I)-1
    if I(n+1)-I(n) > 1
        if f0(I(n)) > 1 && f0(I(n+1)) > 1 && all(f0(I(n)+1:I(n+1)-1) < 1)
            f0(I(n):I(n+1)) = logspace(log10(f0(I(n))),log10(f0(I(n+1))),length(I(n):I(n+1)));
            handles.sound.selected_points(I(n):I(n+1)) = true;
            any_changes = true;
        end
    end
end
handles.sound.f0 = f0;

f0 = handles.sound.f0_corrected;
for n = 1:length(I)-1
    if I(n+1)-I(n) > 1
        if f0(I(n)) > 1 && f0(I(n+1)) > 1 && all(f0(I(n)+1:I(n+1)-1) < 1)
            f0(I(n):I(n+1)) = logspace(log10(f0(I(n))),log10(f0(I(n+1))),length(I(n):I(n+1)));
            any_changes = true;
        end
    end
end

if ~any_changes
    return
end

handles.sound.f0_corrected = f0;

handles.sound.A_corrected = [];

handles = update_plot(handles);
handles = update_GUI(handles);
guidata(hObject, handles);

clear f0

% --- Executes on button press in undo_button.
function undo_button_Callback(hObject, eventdata, handles)
% hObject    handle to undo_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

if ~isempty(handles.sound.f0_corrected_save)
    handles.sound.f0_corrected = handles.sound.f0_corrected_save(end,:);
    handles.sound.f0_corrected_save(end,:) = [];
    handles.sound.f0 = handles.sound.f0_save(end,:);
    handles.sound.f0_save(end,:) = [];
    handles.sound.selected_points = handles.sound.selected_points_save(end,:);
    handles.sound.selected_points_save(end,:) = [];
    
    handles.sound.A_corrected = [];
    
    handles = update_plot(handles);
    handles = update_GUI(handles);
    guidata(hObject, handles);
end


% --- Executes on button press in undo_all_button.
function undo_all_button_Callback(hObject, eventdata, handles)
% hObject    handle to undo_all_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

if ~isempty(handles.sound.f0_corrected_save)
    
    button = questdlg('Undo ALL changes?','Confirm undo','Yes','No','Yes');
    if strcmp(button,'No')
        return
    end
    
    handles.sound.f0_corrected = handles.sound.f0_corrected_save(1,:);
    handles.sound.f0_corrected_save = [];
    handles.sound.f0 = handles.sound.f0_save(1,:);
    handles.sound.f0_save = [];
    handles.sound.selected_points = handles.sound.selected_points_save(1,:);
    handles.sound.selected_points_save = logical([]);
    
    handles.sound.A_corrected = [];
    
    handles = update_plot(handles);
    handles = update_GUI(handles);
    guidata(hObject, handles);
end

% --- Executes on button press in redo_button.
function redo_button_Callback(hObject, eventdata, handles)
% hObject    handle to redo_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)


% --- Executes on button press in zoom_in_button.
function zoom_in_button_Callback(hObject, eventdata, handles)
% hObject    handle to zoom_in_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

if isempty(handles.sound.A)
    return
end

if isempty(handles.status.Xlim)
    handles.status.Xlim = [min(handles.sound.t) max(handles.sound.t)];
end

dX = diff(handles.status.Xlim);
handles.status.Xlim(1) = handles.status.Xlim(1)+dX/10;
handles.status.Xlim(2) = handles.status.Xlim(2)-dX/10;
if diff(handles.status.Xlim) < diff(handles.sound.t(1:2))
    dt = diff(handles.sound.t(1:2));
    handles.status.Xlim = mean(handles.status.Xlim) + [-dt/2 dt/2];
end

handles.status.isview = true;

handles = update_plot(handles);
handles = update_GUI(handles);
guidata(hObject, handles);

% --- Executes on button press in zoom_mouse_button.
function zoom_mouse_button_Callback(hObject, eventdata, handles)
% hObject    handle to zoom_mouse_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

if isempty(handles.sound.A)
    return
end

handles.status.ismousezoom = true;
guidata(hObject, handles);

set(gcf,'Pointer','crosshair');


% --- Executes on button press in zoom_out_button.
function zoom_out_button_Callback(hObject, eventdata, handles)
% hObject    handle to zoom_out_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

if isempty(handles.sound.A)
    return
end

if isempty(handles.status.Xlim)
    handles.status.Xlim = [min(handles.sound.t) max(handles.sound.t)];
end

dX = diff(handles.status.Xlim);
handles.status.Xlim(1) = handles.status.Xlim(1)-dX/10;
handles.status.Xlim(2) = handles.status.Xlim(2)+dX/10;

% if (handles.status.Xlim(1) <= min(handles.sound.t)) && ...
%         (handles.status.Xlim(2) >= max(handles.sound.t))
%     handles.status.Xlim = [];
% else
%     if handles.status.Xlim(1) <= min(handles.sound.t)
%         handles.status.Xlim(1) = min(handles.sound.t);
%     end
%     if handles.status.Xlim(2) >= max(handles.sound.t)
%         handles.status.Xlim(2) = max(handles.sound.t);
%     end
% end

handles.status.isview = true;

handles = update_plot(handles);
handles = update_GUI(handles);
guidata(hObject, handles);

% --- Executes on button press in zoom_full_button.
function zoom_full_button_Callback(hObject, eventdata, handles)
% hObject    handle to zoom_full_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

if isempty(handles.sound.A)
    return
end

handles.status.Xlim = [];
handles.status.Ylim = [];
handles.status.isview = true;

handles = update_plot(handles);
handles = update_GUI(handles);
guidata(hObject, handles);

% --- Executes on button press in pan_left_button.
function pan_left_button_Callback(hObject, eventdata, handles)
% hObject    handle to pan_left_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

if isempty(handles.sound.A)
    return
end

if isempty(handles.status.Xlim)
    handles.status.Xlim = [min(handles.sound.t) max(handles.sound.t)];
end

dX = diff(handles.status.Xlim);
handles.status.Xlim(1) = handles.status.Xlim(1)-dX/10;
handles.status.Xlim(2) = handles.status.Xlim(2)-dX/10;

handles.status.isview = true;

handles = update_plot(handles);
handles = update_GUI(handles);
guidata(hObject, handles);

% --- Executes on button press in pan_right_button.
function pan_right_button_Callback(hObject, eventdata, handles)
% hObject    handle to pan_right_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

if isempty(handles.sound.A)
    return
end

if isempty(handles.status.Xlim)
    handles.status.Xlim = [min(handles.sound.t) max(handles.sound.t)];
end

dX = diff(handles.status.Xlim);
handles.status.Xlim(1) = handles.status.Xlim(1)+dX/10;
handles.status.Xlim(2) = handles.status.Xlim(2)+dX/10;

handles.status.isview = true;

handles = update_plot(handles);
handles = update_GUI(handles);
guidata(hObject, handles);

% --- Executes on selection change in scale_selection_list.
function scale_selection_list_Callback(hObject, eventdata, handles)
% hObject    handle to scale_selection_list (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: contents = get(hObject,'String') returns scale_selection_list contents as cell array
%        contents{get(hObject,'Value')} returns selected item from scale_selection_list


scale = get(handles.scale_selection_list,'String');
scale = scale{get(handles.scale_selection_list,'Value')};

if strcmp(scale,'CUSTOM')
    
else
    handles.scale_options.scale = scale;
    [handles.scale_options.indices,...
        handles.scale_options.freqs,...
        handles.scale_options.notes,...
        handles.scale_options.fund_index] = ...
        get_scale(handles.scale_options.scale);
end

handles = update_plot(handles);
guidata(hObject, handles);



% --- Executes during object creation, after setting all properties.
function scale_selection_list_CreateFcn(hObject, eventdata, handles)
% hObject    handle to scale_selection_list (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: popupmenu controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

% --------------------------------------------------------------------
function save_button_Callback(hObject, eventdata, handles)
% hObject    handle to save_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

data.sound = handles.sound;
data.status = handles.status;
data.record_options = handles.record_options;
data.scale_options = handles.scale_options;
data.pitch_options = handles.pitch_options;
data.pitch_compress_options = handles.pitch_compress_options;
data.pitch_snap_options = handles.pitch_snap_options;
data.vibrato_options = handles.vibrato_options;

if ~isempty(handles.status.filename)
    FilterSpec = handles.status.filename;
else
    FilterSpec = 'untitled.prj';
end
[FileName,PathName,FilterIndex] = uiputfile(FilterSpec,...
    'Select file to save');
if ischar(FileName)
    handles.status.filename = fullfile(PathName,FileName);
    data.status.filename = fullfile(PathName,FileName);
    save(fullfile(PathName,FileName), 'data','-mat');
end

handles.status.issave = true;
handles = update_GUI(handles);

guidata(hObject, handles);



% --------------------------------------------------------------------
function load_button_Callback(hObject, eventdata, handles)
% hObject    handle to load_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

button = questdlg('Any changes since last save will be lost!  Continue?',...
    'Confirm load','Yes','No','Yes');
if strcmp(button,'No')
    return
end

if ~isempty(handles.status.filename)
    FilterSpec = handles.status.filename;
else
    FilterSpec = '*.prj';
end
[FileName,PathName,FilterIndex] = uigetfile(FilterSpec,...
    'Select file to load');
if ischar(FileName)
    load('-mat',fullfile(PathName,FileName));
    handles.status.filename = fullfile(PathName,FileName);


    handles.sound = data.sound;
    handles.status = data.status;
    handles.record_options = data.record_options;
    handles.scale_options = data.scale_options;
    handles.pitch_options = data.pitch_options;
    handles.pitch_compress_options = data.pitch_compress_options;
    handles.pitch_snap_options = data.pitch_snap_options;
    handles.vibrato_options = data.vibrato_options;



    handles.status.isreset = true;
    handles = update_GUI(handles);
    handles = update_plot(handles);

    guidata(hObject, handles);
end

% --------------------------------------------------------------------
function Untitled_2_Callback(hObject, eventdata, handles)
% hObject    handle to Untitled_2 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)


% --------------------------------------------------------------------
function Untitled_3_Callback(hObject, eventdata, handles)
% hObject    handle to Untitled_3 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)


% --------------------------------------------------------------------
function about_button_Callback(hObject, eventdata, handles)
% hObject    handle to about_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

h = msgbox({
    'AutoTune Toy'
    sprintf('Version %s',handles.version)
    'Carl Arft'
    'tfralrac@yahoo.com'
    },'About');
waitfor(h);


% --------------------------------------------------------------------
function help_button_Callback(hObject, eventdata, handles)
% hObject    handle to help_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)


try
    % Changed "winopen" to "open" command on 12/10/12 (some install's could
    % not find README location)
    %winopen('README.pdf');
    open(fullfile(fileparts(which(mfilename)), 'README.pdf'));
catch
    h = errordlg({
        'Cannot open README.pdf.  Please locate and open'
        'this file manually for instructions on using the'
        'AutoTune Toy!!!'
        },'ERROR');
    waitfor(h)
end



function handles = run_pitch_detection(handles)
% hObject    handle to pitch_detect_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

if isempty(handles.sound.A)
    return
end

plot_on = false;

% Get variables out of the handles structure
block_length = handles.pitch_options.block_length;                  
step_per_block = handles.pitch_options.step_per_block;
Fmin = handles.pitch_options.Fmin;                            
Fmax = handles.pitch_options.Fmax;   
f0_target_sample_time = handles.pitch_options.f0_target_sample_time;
A = handles.sound.A;
t = handles.sound.t;
Fs = handles.sound.Fs;
target_tol = handles.pitch_options.target_tol;        
harmonic_deviation = handles.pitch_options.harmonic_deviation; 
threshold_ratio = handles.pitch_options.threshold_ratio;    
threshold_amp = handles.pitch_options.threshold_amp;     
 

block = step_per_block*round(block_length*Fs/step_per_block);     % Size of each block to find pitch
step = block/step_per_block;                         % Step (blocks are 4 times larger than "steps"

N = floor((length(A)-block)/step);      % Number of frequency computations

% Initialize variables for storing results
f0 = zeros(1,N);                        % Initialize vector for storing frequencies
tcalc = zeros(1,N);                     % The time at which that frequency calculation is valid
f0_target = [];                         % For keeping track of the target for the next calculation

f0_samples = floor...                   % Figure out how many f0 samples there will be
    (f0_target_sample_time/(step/Fs));

% Waitbar stuff
hwait = waitbar(0,'Determining pitch...');
set(hwait,'Name','Please Wait');
waitbar_count = 0;
waitbar_update = round(N/10);
pause(0.001);

%tic
I = 1;
for n = 1:N
    
    % Update waitbar
    if waitbar_count > waitbar_update
        waitbar(n/N,hwait);
        waitbar_count = 0;
    end
    waitbar_count = waitbar_count + 1;
    
    % Extract a block of the wav file
    Atemp = A(I:I+block-1);
    ttemp = t(I:I+block-1);
    I = I+step;
    
    % Do the autocorrelation to find the frequency
    [f0(n),acorr,Nshifts,Tshifts] = ...
        find_f0_timedomain2(Atemp,Fs,Fmin,Fmax,...
        threshold_amp,threshold_ratio,harmonic_deviation,...
        f0_target,target_tol);
    tcalc(n) = median(ttemp);
    
    if plot_on
        figure(2)
        subplot(2,1,1),
        plot(ttemp,Atemp)
        title(sprintf('Frequency: %0.1f Hz',f0(n)))
        subplot(2,1,2),
        plot(Tshifts,acorr)
        pause
    end
    
    % If the previous and current are invalid, then make the last one invalid
    % as well
    if n > 2
        if f0(n-2) < 1 && f0(n) < 1
            f0(n-1) = 0;
        end
    end
            
    % Look at the last few samples to determine the target frequency for
    % the next iteration
    if n >= f0_samples
        f0_chunk = f0(n-f0_samples+1:n);
        f0_target = f0_chunk(f0_chunk>0);
        if ~isempty(f0_target)
            f0_target = mean(f0_target);
        end
    end
    
    
end
%toc

if ishandle(hwait)
    close(hwait);
end

% Save calculation
handles.sound.A_corrected = [];
handles.sound.f0 = f0;
handles.sound.f0_save = [];
handles.sound.f0_corrected = f0;
handles.sound.f0_corrected_save= [];
handles.sound.tcalc = tcalc;
handles.sound.selected_points = false(size(f0));
handles.sound.selected_points_save= false(size(f0));
handles.sound.block = block;                  
handles.sound.step = step;
    
function handles = run_pitch_correction(handles)
% hObject    handle to pitch_detect_button (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

if isempty(handles.sound.A)
    return
end

plot_on = false;

A = handles.sound.A;
t = handles.sound.t;
f0 = handles.sound.f0;
f02 = handles.sound.f0_corrected;
Fs = handles.sound.Fs;
scale_factor = zeros(size(f0));
A_corrected = zeros(size(A));
block = handles.sound.block;                  
step = handles.sound.step;
N = length(f0);

max_acorr_shift = 0;
max_acorr_amp = 0;  

% Waitbar stuff
hwait = waitbar(0,'Correcting pitch...');
set(hwait,'Name','Please Wait');
waitbar_count = 0;
waitbar_update = round(N/10);
pause(0.001);

I = 1;
for n = 1:N
    
    % Update waitbar
    if waitbar_count > waitbar_update
        waitbar(n/N,hwait);
        waitbar_count = 0;
    end
    waitbar_count = waitbar_count + 1;

    % Pull out a chunk of the signal
    Atemp = A(I:I+block-1);
    ttemp = t(I:I+block-1);
       
    if f0(n) > 0 && ~isnan(f0(n))
        
        % Calculate the scale factor by which to shift the frequency
        scale_factor(n) = f02(n)/f0(n);
    
        % Interpolate
        tp = mean(ttemp) + (ttemp-mean(ttemp)).*scale_factor(n);
        Ainterp = interp1(ttemp,Atemp ,tp)';
        Ivalid = find(~isnan(Ainterp));
        Ainterp(isnan(Ainterp)) = 0;
        
        Nperiod = ceil(1/(f02(n)/Fs));
    else
        % No frequency shift
        scale_factor(n) = 1;
        Ainterp = Atemp;  
        Ivalid = find(~isnan(Ainterp));
        Nperiod = ceil(1/(handles.pitch_options.Fmin/Fs));
    end
    
    if n == 1
        A_corrected(I:I+block-1) = Ainterp;
        
    else
        
        % Pull out one period of the new waveform
        Achunk = Ainterp(Ivalid(1):Ivalid(1)+Nperiod-1);
        factor = sum(abs(Achunk))*2;
        
        if all(scale_factor(n-1:n) == 1)

        else

            % Start doing correlation
            max_acorr_amp = 0;
            max_acorr_shift = 0;

            for Nshift = -round(Nperiod/2)+ (1:Nperiod)

                % Calculate makeshift autocorrelation
                acorr = 1-sum(abs(Achunk - A_corrected((I:I+Nperiod-1) + Nshift + Ivalid(1))))./factor;

                if acorr > max_acorr_amp
                    max_acorr_amp = acorr;
                    max_acorr_shift = Nshift;
                end
            end
        end
        
        [what,where] = min(abs(Achunk - ...
            A_corrected((I:I+Nperiod-1) + max_acorr_shift + Ivalid(1))));
        
        if plot_on
            Asave = A_corrected((I:I+Nperiod-1) + max_acorr_shift + Ivalid(1));
        end
        
        A_corrected((I+where-1:I+length(Ivalid)-1) + max_acorr_shift + Ivalid(1)) = ...
            Ainterp(Ivalid(where:end));
        
        if plot_on
            tt = 1:length(Achunk);
            figure(1);
            plot(tt,Achunk,tt,Asave,tt,A_corrected((I:I+Nperiod-1) + max_acorr_shift + Ivalid(1)))
            title(sprintf('Acorr = %0.3f (Nshift = %i)',max_acorr_amp,max_acorr_shift))
            hold on
            plot(tt(where),Achunk(where),'*')
            hold off
            pause
        end

        

    end
    
    I = I+step;
    
end

handles.sound.A_corrected = A_corrected;
handles.sound.scale_factor = scale_factor;

clear f0 f02 scale factor A A_corrected

if ishandle(hwait)
    close(hwait);
end


% The following function makes nothing happen when you press a key
function nokeyresponse(hObject, eventdata, handles)

% The following function executes when the mouse is clicked on the plot


function mouseclick(hObject, eventdata, handles)


if isempty(handles.sound.f0)
    return
end

% keytype = get(gcf,'CurrentCharacter');
%
% if keytype == 's'

if handles.status.ismousezoom
    
    % Zoom with mouse 
    
    point1 = get(gca,'CurrentPoint');    % button down detected
    finalRect = rbbox;                  % return figure units
    point2 = get(gca,'CurrentPoint');    % button up detected
    point1 = point1(1,1:2);              % extract x and y
    point2 = point2(1,1:2);
    p1 = min(point1,point2);             % calculate locations
    offset = abs(point1-point2);         % and dimensions
    xminmax = [p1(1) p1(1)+offset(1)];
    yminmax = [p1(2) p1(2)+offset(2)];
    
    if diff(xminmax) < diff(handles.sound.t(1:2))
        dt = diff(handles.sound.t(1:2));
        xminmax = mean(xminmax) + [-dt/2 dt/2];
    end

    handles.status.Xlim = xminmax;
    handles.status.ismousezoom = false;
    handles.status.isview = true;
    
    handles = update_plot(handles);
    handles = update_GUI(handles);
    
    guidata(hObject, handles);
    set(gcf,'Pointer','arrow');
    
    return;

elseif get(handles.plot_selection,'value') ~= 1
    clicktype = get(gcf,'SelectionType');
    if strcmp(clicktype,'normal') || strcmp(clicktype,'alt')

        point1 = get(gca,'CurrentPoint');    % button down detected
        finalRect = rbbox;                  % return figure units
        point2 = get(gca,'CurrentPoint');    % button up detected
        point1 = point1(1,1:2);              % extract x and y
        point2 = point2(1,1:2);
        p1 = min(point1,point2);             % calculate locations
        offset = abs(point1-point2);         % and dimensions
        xminmax = [p1(1) p1(1)+offset(1)];
        yminmax = [p1(2) p1(2)+offset(2)];

        if strcmp(clicktype,'normal')
            handles.sound.selected_points(:) = false;
        end

        handles.sound.selected_points(...
            handles.sound.f0_corrected > yminmax(1) & ...
            handles.sound.f0_corrected < yminmax(2) & ...
            handles.sound.tcalc > xminmax(1) & ...
            handles.sound.tcalc < xminmax(2) & ...
            handles.sound.f0_corrected > 0) = true;

    elseif strcmp(clicktype,'extend')

        % Save for undoing
        handles.sound.f0_save(end+1,:) = handles.sound.f0;
        handles.sound.f0_corrected_save(end+1,:) = handles.sound.f0_corrected;
        handles.sound.selected_points_save(end+1,:) = handles.sound.selected_points;

        % Get initial click point
        point1 = get(gca,'CurrentPoint');    % button down detected
        point1 = point1(1,1:2);
        handles.point1 = point1;
        handles.single_point = false;

        % If no points have previously been selected, select the closest point
        if all(~handles.sound.selected_points)
            a = axis;
            tnorm = (handles.sound.tcalc - a(1))/(a(2)-a(1));
            fnorm = (handles.sound.f0_corrected - a(3))/(a(4)-a(3));
            xnorm = (point1(1) - a(1))/(a(2)-a(1));
            ynorm = (point1(2) - a(3))/(a(4)-a(3));
            [what,where] = min((tnorm-xnorm).^2+(fnorm-ynorm).^2);
            handles.sound.selected_points(where) = true;
            handles.single_point = true;
        end

        guidata(hObject, handles);
        pause(0.05);


        % Set function to track mouse position
        handles.tstart = now;
        guidata(hObject, handles);
        set(gcf,'WindowButtonMotionFcn','AutoTuneToy(''wbmcb'',gcbo,[],guidata(gcbo))');
        set(gcf,'WindowButtonUpFcn','AutoTuneToy(''wbucb'',gcbo,[],guidata(gcbo))');

        handles = update_GUI(handles);

    end
end

guidata(hObject, handles);
handles = update_plot(handles);


function wbmcb(hObject, eventdata, handles)
% Note: added in a timer so that the figure won't try to refresh too
% quickly and crash
if (now-handles.tstart)*24*60*60 > 0.08
    handles.tstart = now;
    
    cp = get(gca,'CurrentPoint');
    ydiff = cp(1,2)/handles.point1(2);
    handles.sound.f0_corrected(handles.sound.selected_points) = ...
        handles.sound.f0_corrected_save(end,handles.sound.selected_points).*ydiff;
    handles.sound.A_corrected = [];
    guidata(hObject, handles);
    handles = update_plot(handles);
else
    
end



function wbucb(hObject, eventdata, handles)
if handles.single_point
    handles.sound.selected_points(:) = false;

end
guidata(hObject, handles);
handles = update_plot(handles);
set(gcf,'WindowButtonMotionFcn','');
set(gcf,'WindowButtonUpFcn','');



function [f0,acorr,Nshifts,Tshifts] = find_f0_timedomain2(A,Fs,Fmin,Fmax,...
    threshold_amp,threshold_ratio,harmonic_deviation,target_f0,target_tol)

% This function calculates the fundamental frequency of a signal in the
% time domain

% OUTPUTS:
%
%   f0:         The interpolated fundamental freqency (Hz)
%   
%   acorr:      The vector of "autocorrelation" values
%
%   Nshifts:    The vector of shift indices associated with the values in
%               acorr
%
%   Tshifts:    The vector of time shifts associated with the values in
%               acorr
%
% INPUTS:
%   
%   A:          The input signal A(t), sampled at an interval Ts
%
%   Fs:         The sample frequency (Hz)
%
%   Fmin:       (Optional) The minimum expected frequency (Hz)
%
%   Fmax:       (Optional) The maximum expected freqency (Hz)
% 
%   threshold_amp: (Optional) The min autocorr amplitude considered peak
%   
%   threshold_ratio: (Optional) The min ratio between max autocorr peak and
%                       a given peak to be considered as a possible peak
%
%   harmonic deviation: (Optional) Tolerance for looking for another peak
%                       at half the frequency of the highest
%                       autocorrelation peak
%
%   target_f0:  (Optional) The probable target frequency
%
%   target_tol: (Optional) The max error between target_f0 and current freq
%                       (0.1 = 10%)
%

if nargin < 9 || isempty(target_tol)
    target_tol = 0.1;
end
if nargin < 8 || isempty(target_f0)
    target_f0 = [];
end
if nargin < 7 || isempty(harmonic_deviation)
    harmonic_deviation = 0.03;
end
if nargin < 6 || isempty(threshold_ratio)
    threshold_ratio = 0.7;
end
if nargin < 5 || isempty(threshold_amp)
    threshold_amp = 0.3;
end
if nargin < 4 || isempty(Fmax)
    Fmax = 800;
end
if nargin < 3 || isempty(Fmin)
    Fmin = 40;
end

f0 = 0;
acorr = [];
Nshifts = [];
Tshifts = [];

% Calculate index of min and max shift
Nshift_min = floor(1/(Fmax/Fs));
Nshift_max = ceil(1/(Fmin/Fs));
if Nshift_max+Nshift_min > length(A) || Nshift_min >= Nshift_max
    disp('Error in find_f0_timedomain2.m: Chunk size must be larger!')
    return
end

% Pull out the chunk of the signal and calculate a scale factor
% The scale factor is the probable maximum value
Achunk = A(1:Nshift_max);
scale_factor = sum(abs(Achunk))*2;

% Calculate a vector of all shifts
Nshifts = Nshift_min:Nshift_max;
Tshifts = Nshifts / Fs;

% Shift and calculate a makeshift autocorrelation
acorr = zeros(1,length(Nshifts));
index = 1;
for Nshift = Nshifts
    
    % Break out if we are shifting the chunk beyond the end of the vector A
    if Nshift_max + Nshift > length(A)
        Nshifts = Nshifts(1:index-1);
        Tshifts = Tshifts(1:index-1);
        acorr = acorr(1:index-1);
        break
    end
    
    % Calculate makeshift autocorrelation
    acorr(index) = 1-sum(abs(Achunk - A([1:Nshift_max] + Nshift)))./scale_factor; 
    index = index + 1;
    
end

% Try to make the autocorrelation "level" and with the minimum at zero
acorr = acorr - polyval(polyfit(Nshifts,acorr,1),Nshifts);
acorr = acorr - min(acorr);

% Find a list of all of the peaks above the threshold
[maxX,maxY,Imax,maxX_fit,peakX,peakY] = peakfind(Tshifts,acorr,5,1/Fmax,[],'',false);
% Keep only those harmonics that are within a certain height of the largest
% peak and whose height is above the threshold amplitude

% Make a counter to keep track of which peaks remain after each criteria is
% applied below...
Ipeak = 1:length(maxX_fit);

% Keep those whose heights are above the threshold
Ipeak = Ipeak(maxY > threshold_ratio*max(maxY) & maxY > threshold_amp);
maxX_fit = maxX_fit(Ipeak);

% If only 1 or zero peaks remain, return
N = length(Ipeak);
% if N == 1
%     f0 = 1./maxX_fit;
%     return
% elseif N == 0
%     return
% end
if N < 2
    return
end
    
% Keep only those harmonics whose frequency is less than a certain
% deviation around a multiple of the fundamental
[maxX_fit,Ix] = sort(maxX_fit);
Ipeak = Ipeak(Ix);
possible_f0 = zeros(1,N);
for n = 1:N-1
    if any(abs(1 - maxX_fit(n+1:N)./(2*maxX_fit(n))) < harmonic_deviation);
        if isempty(target_f0)
            f0 = 1./maxX_fit(n);
            Ipeak = Ipeak(n);
            break
        else
            possible_f0(n) = 1./maxX_fit(n);
        end
    end
end

% Now keep only the harmonic that is closest to the desired harmonic
if isempty(target_f0)
%    f0 = 1./maxX_fit(end);
%    return
else
    f0_error = abs(possible_f0./target_f0 - 1);
    [what,where] = min(f0_error);
    if what < target_tol
        f0 = possible_f0(where);
        Ipeak = Ipeak(where);
    end
end

% Now "fine tune" the frequency around the peak using a parabolic fit
if f0 > 0
    p = polyfit(peakX(Ipeak,:),peakY(Ipeak,:),2);
    Xfit = -p(2)/2/p(1);
    f0 = 1/Xfit;
end



function [maxX,maxY,Imax,maxX_fit,peakX,peakY] = peakfind(X,Y,Nmax,Xsep,Ymin,sortmethod,peakfit)


% This function finds the maxima of the given function, and
% returns the first Nvalues, sorted in decending order
%
% INPUTS:
%
% X = a vector of X-values for the signal
%
% Y = a fector of Y-values for the signal
%
% Nmax = maximum number of values to return
%
% Xsep = the minimum acceptable separation between "peaks"
% 
% Ymin = the minimum Y value to return
%
% sortmethod = how to sort the peaks...
%               'maxY' = Sort in decending order starting with maximum Y-valued peak
%               'minX' = Sort in ascending order starting with minimum X-valued peak
%
% peakfit = set to 'true' to do a parabolic fit and refine the maxX values
%
% OUTPUTS:
%
% maxX = the X-coordinates of all of the N peaks
%
% maxY = the Y-heights of all of the N peaks
%
% Imax = indices of all of the N peaks
%
% maxX_fit = a more refined list of the X-coordinates based on a parabolic
%           fit to each peak
%
% peakX = a Nx5 matrix with each row containing the 5 X-values around
%           each peak
%
% peakY = a Nx5 matrix with each row containing the 5 Y-values around each
%           peak

if nargin < 7 || isempty(peakfit)
    peakfit = true;
end
if nargin < 6 || isempty(sortmethod)
    sortmethod = 'maxY';
end
if nargin < 5
    Ymin = [];
end
if nargin < 4 || isempty(Xsep)
    Xsep = 0;
end
if nargin < 3 || isempty(Nmax)
    Nmax = 10;
end


% Differentiate, and find change in sign
Ydiff = diff(Y);
Imax1 = find(Ydiff(1:end-1) > 0 & Ydiff(2:end) < 0);
Imax2 = find(Ydiff(1:end-2) > 0 & Ydiff(2:end-1) == 0 & Ydiff(3:end) < 0);
Imax3 = find(Ydiff(1:end-3) > 0 & Ydiff(2:end-2) == 0 & Ydiff(3:end-1) == 0 & Ydiff(4:end) < 0);

% Concatenate all of the possible peaks
maxX = [X(Imax1+1) X(Imax2+1) X(Imax3+2)];
maxY = [Y(Imax1+1) Y(Imax2+1) Y(Imax3+2)];
Imax = [(Imax1+1) (Imax2+1) (Imax3+2)];

% Re-sort if desired
if strcmp(sortmethod,'minX')
    [maxX,IX] = sort(maxX,'ascend');
    maxY = maxY(IX);
    Imax = Imax(IX);
elseif strcmp(sortmethod,'maxY')
    [maxY,IY] = sort(maxY,'descend');
    maxX = maxX(IY);
    Imax = Imax(IY);
end

% Remove any peaks that are below the min peak value allowed
if ~isempty(Ymin)
    Irem = find(maxY < Ymin);
    maxY(Irem) = [];
    maxX(Irem) = [];
    Imax(Irem) = [];
end

% Remove any peaks that are closer than the minimum allowed peak seperation
% in X
if Xsep > 0
    Iremove = zeros(1,length(maxX));
    I = 0;
    for n = 2:length(maxX)
        if abs(maxX(n) - maxX(n-1)) < Xsep
            maxX(n) = maxX(n-1);
            maxY(n) = maxY(n-1);
            I = I+1;
            Iremove(I) = n;
        end
    end
    Iremove = Iremove(1:I);
    maxX(Iremove) = [];
    maxY(Iremove) = [];
    Imax(Iremove) = [];
end

% Remove any peaks beyond the max number to return
if length(maxX) > Nmax
    maxX = maxX(1:Nmax);
    maxY = maxY(1:Nmax);
    Imax = Imax(1:Nmax);
end

maxX_fit = maxX;
peakX = zeros(length(Imax),5);
peakY = zeros(length(Imax),5);
% Do a 2nd-order poly fit around the peak to pinpoint the peak location
for n = 1:length(Imax);
    if Imax(n) > 3 && Imax(n) < length(X)-2
        I = Imax(n)+[-2:2];
        if peakfit
            p = polyfit(X(I),Y(I),2);
            maxX_fit(n) = -p(2)/2/p(1);
        end
        peakX(n,1:5) = X(I);
        peakY(n,1:5) = Y(I);
    end
end

function [indices,freqs,notes,fund_index] = get_scale(scale)

indices = [];
freqs = [];
notes = {};
fund_index = [];

major_indices = [0 2 4 5 7 9 11];
minor_indices = [0 2 3 5 7 8 10];

fund_indices = [0 2 3 5 7 8 10];
fund_notes = {'A' 'B' 'C' 'D' 'E' 'F' 'G'};
scale_types = {'major','minor'};

if nargin < 1 || isempty(scale)
    % Return all possiblities of scales
    indices = {};
    for n = 1:length(fund_notes)
        for m = 1:2
            indices{end+1} = [fund_notes{n} scale_types{m}];
        end
    end
    return
else
    fund_index = fund_indices(strcmp(scale(1),fund_notes));
    if strcmp(scale(2:end),'major')
        indices = major_indices+fund_index;
    elseif strcmp(scale(2:end),'minor')
        indices = minor_indices+fund_index;
    else
        disp('ERROR in get_scale.m: unknown scale!')
        return
    end
end


indices = [indices-12 indices indices+12 indices+24 indices+36];
indices = indices(indices >=0 & indices <=48);

[indices,freqs,notes] = get_note_matrix(indices);

function [indices,freqs,notes] = get_note_matrix(note)

if nargin < 1
    note = '';
end

indices = (0:48)';
freqs = 55.*2.^(indices./12);
notes = {
    'A1'
    'A#/Bb1'
    'B1'
    'C1'
    'C#/Db1'
    'D1'
    'D#/Eb1'
    'E1'
    'F1'
    'F#/Gb1'
    'G1'
    'G#/Ab1'
    'A2'
    'A#/Bb2'
    'B2'
    'C2'
    'C#/Db2'
    'D2'
    'D#/Eb2'
    'E2'
    'F2'
    'F#/Gb2'
    'G2'
    'G#/Ab2'
    'A3'
    'A#/Bb3'
    'B3'
    'C3'
    'C#/Db3'
    'D3'
    'D#/Eb3'
    'E3'
    'F3'
    'F#/Gb3'
    'G3'
    'G#/Ab3'
    'A4'
    'A#/Bb4'
    'B4'
    'C4'
    'C#/Db4'
    'D4'
    'D#/Eb4'
    'E4'
    'F4'
    'F#/Gb4'
    'G4'
    'G#/Ab4'
    'A5'
    };

if ~isempty(note)
    if ischar(note)
        I = find(strcmp(notes,note));
        if ~isempty(I);
            indices = indices(I);
            freqs = freqs(I);
            notes = notes(I);
        else
            indices = [];
            freqs = [];
            notes = '';
        end
    else
        note = note(note>=0 & note<=48);
        indices = note;
        freqs = freqs(note+1);
        notes = notes(note+1);
    end
end





Contact us