Code covered by the BSD License  

Highlights from
AutoTune Toy

image thumbnail
from AutoTune Toy by Carl
Allows you to record and graphically manipulate and pitch correct your voice.

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