function slt (action)
%SLT Speech/Spectrogram labelling tool
%  
%  To start, use load option to load a speech file. Any existing
%  transcriptions from the default transcriptions directory will also 
%  be loaded, but not displayed until selected.
%
%  Any 'mystery' files will disable the play option.
%
%  Martin Cooke & Stuart Wrigley   
%    updated July 1999 for v2.1

if nargin<1 
  action='init';
end

switch action   
case 'init'
  f=findobj('Tag','slt');
  if ~isempty(f)
    figure(f);
  else     
    slt_gui;
    ud = get(gcf,'UserData'); 
    ud.signal=[];
    ud.savename=[];
    ud.currentLabel=[];
    set([ud.saveopt ud.saveAsopt ud.loadopt],'Enable','off');
    set(gcf,'UserData',ud,'Name','Speech Labelling Tool - [no data loaded]',...
         'KeyPressFcn','slt keypressed');
  end

case 'loadSpeech'
  ud=get(gcf,'UserData');
  if checkSave                  % check if any transcription needs saving & save it
    [name,sig,fs]=loadsig(ud.signaldir);
    if length(sig)              % loaded successfully  
      ud.signal=normalise(sig); % scale to range [-1,1]
      ud.filename=name;
      ud.fs=fs;
      ud.samples=length(ud.signal);
      set(ud.loadopt,'Enable','on'); 
      ud.left = round(0.1*ud.samples);    % cursors
      ud.right = round(0.9*ud.samples);
      ud.winleft = 1;                     % current limits of display
      ud.winright= ud.samples;            
      set(ud.signalLine,'XData',1:ud.samples,'YData',ud.signal);
      set(gcf,'Name',strcat('Speech Labelling Tool - [',name,']'),...
      'WindowButtonMotionFcn','slt motion');
      ud.name=name;
      if length(name) >= 4
         ud.noplay=strcmp(name(1:4),'myst');
      else
         ud.noplay=0;
      end
      
      
      % spectrogram stuff
      axes(ud.spectAxes);
      colormap(sqrt(1-gray));
      xscale=[1 ud.samples];
      yscale=[1 ud.fs/2];  
      set(ud.spectAxes,'YLim',[0 4000],'XLim',xscale);    
      set(gcf,'UserData',ud);
      slt 'setFFTwindow'
      slt 'setQuality'
      ud=get(gcf,'UserData');
      spectArray=spectrogram(ud.signal,ud.fs,ud.win,ud.shift)';
      ud.im=imagesc(xscale,yscale,spectArray);
      set(ud.im,'ButtonDownFcn','slt play');
      
      % cursors on spectrogram
      ud.spectLC = line('Parent',ud.spectAxes,'Color','red',...
      'EraseMode','xor','XData',[ud.left ud.left],'YData',[1 4000],...
      'ButtonDownFcn','slt left');
      ud.spectRC = line('Parent',ud.spectAxes,'Color','red',...
      'EraseMode','xor','XData',[ud.right ud.right],'YData',[1 4000],...
      'ButtonDownFcn','slt right');
      
      % load any transcriptions here
      % and clear old ones, of course
      delete(findobj(gcf,'Tag','Annot'));
      delete(findobj(ud.signalAxes,'Tag','annot'));
      delete(findobj(gcf,'Tag','annotradio'));
      ud.annots=emptyAnnot;
      % find all annotations for given file
      d=dir(fullfile(ud.annotdir,[name '.*']));
      annotcount=0;
      annotstrings=['hide transcription'];
      for f=1:length(d)
        a=readAnnot(fullfile(ud.annotdir,d(f).name));
        if ~isempty(a.times)
          annotcount = annotcount+1;
          annots(annotcount)=a;
          [apath,aname,aext,aver] = fileparts(d(f).name);
          annotstrings=strvcat(annotstrings,aext(2:length(aext)));       
        end
      end
      if annotcount
        uicontrol('Parent',gcf,'Style','popupmenu',...
        'Position',[ud.wavleft ud.wavbase-0.05 0.2 0.05],...
        'String',annotstrings,...
        'Value',1,...
        'Tag','annotradio',...
        'Callback','slt annotselected',...
        'UserData',annots); 
      end
      set([findobj(gcf,'Style','pushbutton') ; findobj(gcf,'Style','popupmenu')],'Enable','on');
      set([ud.saveAsopt ud.saveopt],'Enable','off');
      set(gcf,'UserData',ud);
      slt unzoomall
      slt replot
    end
 end
 
  case 'annotselected'
    annots=get(gcbo,'UserData');  
    ud=get(gcf,'UserData');
    v=get(gcbo,'Value');
    if v > 1  % 1 is the 'hide' option
      a=annots(v-1);
      axes(ud.signalAxes);
      delete(findobj(gca,'Tag','annot'));
      ylims=get(gca,'YLim');
      for l=1:length(a.times)
        text(a.times(l)+100,-0.9,a.labels(l,:),'Tag','annot');
        line(a.times(l)*[1 1],ylims,'Tag','annot');   
      end
    else
      % clear annotation from display
      delete(findobj(ud.signalAxes,'Tag','annot'));
    end   
  
  case 'makespect'
    ud=get(gcf,'UserData'); 
    set(ud.im,'CData',spectrogram(ud.signal,ud.fs,ud.win,ud.shift)');
    set(gcf,'UserData',ud);
    
  case 'spectResChanged'
    slt 'setFFTwindow'
    slt 'makespect'
    slt 'replot'
    
  case 'qualityChanged'
    slt 'setQuality'
    slt 'makespect'
    slt 'replot'
    
  case 'setFFTwindow'
    ud=get(gcf,'UserData');
    p=getuicontrolvalue(ud.spectResMenu);
    switch p
    case 'wide'
      ud.win=5;
    case 'medium'
      ud.win = 10;
    case 'narrow'
      ud.win = 25;     
    end
    set(gcf,'UserData',ud);
    
  case 'setQuality'
    ud=get(gcf,'UserData');
    p=getuicontrolvalue(ud.qualityMenu);
    switch p
    case 'rough'
      ud.shift=10;
    case 'good'
      ud.shift = 2;
    case 'best'
      ud.shift = 1;     
    end
    set(gcf,'UserData',ud);
    
  case 'replot'
    ud=get(gcf,'UserData');
    set([ud.signalAxes ud.annotAxes ud.spectAxes],...
      'XLim',[ud.winleft ud.winright]);
    set(gcf,'WindowButtonMotionFcn','slt motion');
    % check which buttons to enable ..
    if ud.winleft > 1
      set([ud.leftbutton],'Enable','on');
    else
      set([ud.leftbutton],'Enable','off');
    end 
    if ud.winright < ud.samples
      set([ud.rightbutton],'Enable','on');
    else
      set([ud.rightbutton],'Enable','off');
    end
    if and(ud.winleft <= 1,ud.winright >= ud.samples)
      set([ud.unzoombutton ud.showallbutton],'Enable','off');
    else
      set([ud.unzoombutton ud.showallbutton],'Enable','on');
    end
    slt plotCursors
    
  case 'zoom'
    % zooms to cursors
    ud=get(gcf,'UserData');
    mid=mean([ud.winleft ud.winright]);
    siz=round((ud.winright-ud.winleft)/2);
    ud.winleft=round(max(1,mid-0.5*siz));
    ud.winright=round(min(ud.samples,mid+0.5*siz));
    mid=mean([ud.winleft ud.winright]);
    siz=round((ud.winright-ud.winleft)/2);
    ud.left=round(mid-0.9*siz);
    ud.right=round(mid+0.9*siz);
    set(gcf,'UserData',ud);
    slt replot
    
  case 'zoomcursors'
    % zooms to cursors
    ud=get(gcf,'UserData');
    ud.winleft=ud.left;
    ud.winright=ud.right;
    mid=mean([ud.winleft ud.winright]);
    siz=round((ud.winright-ud.winleft)/2);
    ud.left=round(mid-0.9*siz);
    ud.right=round(mid+0.9*siz);
    set(gcf,'UserData',ud);
    slt replot
    
  case 'unzoomall'
    % show whole signal
    ud=get(gcf,'UserData');
    ud.winleft=1;
    ud.winright=ud.samples;
    set(gcf,'UserData',ud);
    slt replot  
    
  case 'unzoom'
    % unzoom by factor of 2
    ud=get(gcf,'UserData');
    mid=mean([ud.winleft ud.winright]);
    siz=round((ud.winright-ud.winleft)/2);
    ud.winleft=round(max(1,mid-2*siz));
    ud.winright=round(min(ud.samples,mid+2*siz));
    set(gcf,'UserData',ud);
    slt replot  
    
  case 'goleft'
    % scroll leftwards
    ud=get(gcf,'UserData');
    shift=min(round(0.4*(ud.winright-ud.winleft)),ud.winleft);
    ud.winleft=round(ud.winleft-shift);
    ud.left=round(ud.left-shift);  
    ud.winright=round(ud.winright-shift);
    ud.right=round(ud.right-shift);
    if ud.winleft == 1
      set(ud.leftbutton,'Enable','off');
    end  
    set(gcf,'UserData',ud);
    slt replot  
    
  case 'goright'
    % scroll rightwards
    ud=get(gcf,'UserData');
    shift=round(0.4*(ud.winright-ud.winleft));
    if ud.winright+shift > ud.samples
      shift = ud.samples-ud.winright;
    end  
    ud.winright=round(ud.winright+shift);
    ud.right=round(ud.right+shift);
    ud.winleft=round(ud.winleft+shift);
    ud.left=round(ud.left+shift);
    set(gcf,'UserData',ud);
    slt replot  
    
  case 'plotCursors'
    ud=get(gcf,'UserData');
    set([ud.signalRC ud.annotRC ud.spectRC], 'XData',[ud.right ud.right]);
    set([ud.signalLC ud.annotLC ud.spectLC], 'XData',[ud.left ud.left]);
    
  case 'annotate'
    ud=get(gcf,'UserData');
    axes(ud.annotAxes);
    currentPoint=get(gca,'CurrentPoint');
    addAnnot(currentPoint(1));
    if ~isempty(ud.savename)
      set(ud.saveopt,'Enable','on');
    else
      set(ud.saveAsopt,'Enable','on');
    end
    set(gcf,'UserData',ud);
    
  case 'keypressed'
    % if delete key is pressed, delete any selected object
    if double(get(gcf,'CurrentCharacter')) == 8
      f=findobj(gcf,'Selected','on');
      fud=get(f,'UserData');
      delete([fud.label f]);
      ud=get(gcf,'UserData');
    if ~isempty(ud.savename)
      set(ud.saveopt,'Enable','on');
    end  
    set(gcf,'UserData',ud);
    end  
  
  case 'select'
    ud=get(gcf,'UserData');  
    set(findobj(gcf,'Selected','on'),'Selected','off');   
    lud=get(gcbo,'UserData');
    %set([lud.line lud.label],'Selected','on');
    set([lud.line],'Selected','on');
    ud.currentLine=gcbo;
    ud.currentLabel=lud.label;
    set(gcf,'WindowButtonMotionFcn','slt moveannot');
    set(gcf,'WindowButtonUpFcn','slt releaseannot');
    set(gcf,'UserData',ud);
    
  case 'selectfine'
    ud=get(gcf,'UserData');
    tok=strtok(getuicontrolvalue(gcbo));
    switch (get(gcf,'SelectionType'))
    case 'normal' 
    case 'open'
      if ~isempty(ud.currentLabel)
        set(ud.currentLabel,'String',tok);
        slt unselect
      end
    end  

case 'selectSUV'
  ud=get(gcf,'UserData');
  tok=strtok(getuicontrolvalue(gcbo));
  switch (get(gcf,'SelectionType'))
  case 'normal'  
    switch tok
    case 'sil'
      set(ud.broadlistbox,'String','');
      set(ud.narrowlistbox,'String','');
    case 'V'
      set(ud.broadlistbox,'String',ud.voiced,'Value',1);
      set(ud.narrowlistbox,'String','');
    case 'U'
      set(ud.broadlistbox,'String',ud.unvoiced,'Value',1);  
      set(ud.narrowlistbox,'String','');
    case '??'
      set(ud.broadlistbox,'String','');  
      set(ud.narrowlistbox,'String','');
    case 'D'
      set(ud.broadlistbox,'String',ud.digits);  
      set(ud.narrowlistbox,'String','');
    end
  case 'open'
    if ~isempty(ud.currentLabel)
      set(ud.currentLabel,'String',tok);
      slt unselect
    end
  end

case 'selectBroad'
  ud=get(gcf,'UserData');
  tok=strtok(getuicontrolvalue(gcbo));
  switch (get(gcf,'SelectionType'))
  case 'normal'  
    switch tok
    case 'VS'
      set(ud.narrowlistbox,'String',ud.voicedstops,'Value',1);
    case 'US'
      set(ud.narrowlistbox,'String',ud.unvoicedstops,'Value',1);
    case 'UF'
      set(ud.narrowlistbox,'String',ud.unvoicedfrics,'Value',1);   
    case 'VF'
      set(ud.narrowlistbox,'String',ud.voicedfrics,'Value',1); 
    case 'N'
      set(ud.narrowlistbox,'String',ud.nasals,'Value',1); 
    case 'V'
      set(ud.narrowlistbox,'String',ud.vowels,'Value',1); 
    case 'SV'
      set(ud.narrowlistbox,'String',ud.semivowels,'Value',1);      
    case 'hh'
      set(ud.narrowlistbox,'String','');     
    case 'DP'
      set(ud.narrowlistbox,'String',ud.diphthongs,'Value',1); 
    otherwise
  end  
case 'open'
  if ~isempty(ud.currentLabel)
    set(ud.currentLabel,'String',tok);
    slt unselect
  end
end

case 'chooseLabel'
  ud=get(gcf,'UserData');
  p=getuicontrolvalue(gcbo);
  % strip off from first space
  set(ud.currentLabel,'String',strtok(p));  
  set(ud.labels,'Visible','off');
    if ~isempty(ud.savename)
      set(ud.saveopt,'Enable','on');
    end  
    set(gcf,'UserData',ud);
  
case 'unselect'
  ud=get(gcf,'UserData');  
  set(findobj(gcf,'Selected','on'),'Selected','off');
  ud.selected=[];
    if ~isempty(ud.savename)
      set(ud.saveopt,'Enable','on');
    end  
    set(gcf,'UserData',ud);
  
case 'moveannot'
  ud=get(gcf,'UserData');  
  currentPoint=get(gca,'CurrentPoint');
  cp=currentPoint(1);
  lud=get(ud.currentLine,'UserData');
  set(lud.line,'XData',[cp cp]);
  set(lud.label,'Position',[cp+100 0.1]);
  set(gcf,'UserData',ud);
  
case 'releaseannot'
  set(gcf,'WindowButtonMotionFcn','slt motion');
  ud=get(gcf,'UserData');
    if ~isempty(ud.savename)
      set(ud.saveopt,'Enable','on');
    end  
    set(gcf,'UserData',ud);
  
case 'play'
  ud=get(gcf,'UserData');
  if and(~ud.noplay,~isempty(ud.signal))
    cp=get(ud.signalAxes,'CurrentPoint');
    cp=cp(1);
    if and(cp>=ud.left,cp<=ud.right)
      sound(ud.signal(ud.left:ud.right),ud.fs);
    else   
      sound(ud.signal,ud.fs);
    end
  end   

case 'playBetweenLabels'
  ud=get(gcf,'UserData');
  if ~ud.noplay
    cp=get(ud.annotAxes,'CurrentPoint');
    cp=cp(1);  
    lins=findobj(ud.annotAxes,'Tag','Annot','LineWidth',0.5);
    x=[];
    for i=1:length(lins)
      lud=get(lins(i),'UserData');
      xval=get(lud.line,'XData');   
      x=[x xval(1)];
    end
    x=sort(round(x));
    leftlab=x(x<cp);
    if isempty(leftlab)
      leftlab=1;
    else
      leftlab=fliplr(leftlab); 
      leftlab=leftlab(1);
    end
    rightlab=x(x>cp);
    if isempty(rightlab)
      rightlab=ud.samples;
    else
      rightlab=rightlab(1);
    end
    sound(ud.signal(leftlab:rightlab),ud.fs);  
  end

case 'inccontrast'
  % increase contrast on display
  ud=get(gcf,'UserData');
  clims=get(ud.spectAxes,'CLim');
  gap=clims(2)-clims(1);
  set(ud.spectAxes,'CLim',[clims(1)+0.2*gap clims(2)]);
  set(gcf,'UserData',ud);

case 'deccontrast'
  % decrease contrast on display
  ud=get(gcf,'UserData');
  clims=get(ud.spectAxes,'CLim');
  gap=clims(2)-clims(1);
  set(ud.spectAxes,'CLim',[clims(1)-0.2*gap clims(2)]);
  set(gcf,'UserData',ud);
  
case 'saveAsannot'
  ud=get(gcf,'UserData');
  savedir=pwd;
  cd(ud.annotdir)
  [f,p]=uiputfile(fullfile(ud.annotdir,ud.name,'.*'),'SAVE transcription as');
  cd(savedir)
  if f
    fid=fopen(fullfile(p,f),'w');
    if fid
      fclose(fid);
      ud.savename=fullfile(p,f);
      set(gcf,'UserData',ud);
      slt 'saveannot'
    else
      warndlg(['Cannot open file ' ud.savename ' for writing']);
      slt 'saveAsannot';
    end  
  end
  
case 'saveannot'
  ud=get(gcf,'UserData');
  lins=findobj(ud.annotAxes,'Tag','Annot','LineWidth',0.5);
  x=[]; y=[];
  for i=1:length(lins)
    lud=get(lins(i),'UserData');
    xval=get(lud.line,'XData');   
    lab=get(lud.label,'String');   
    x=[x xval(1)];
    y=strvcat(y,lab);
  end
  x=round(x);
  [a,b]=sort(x);
  fid=fopen(ud.savename,'w');
  if fid
    for i=1:length(lins)
      fprintf(fid,'%d %s\n',a(i),y(b(i),:));
    end
    fclose(fid);
  else
    warndlg(['slt: Problem writing to annotation file ' ud.savename]);
  end  
  set(ud.saveopt,'Enable','off');
  set(gcf,'UserData',ud);
  
case 'loadannot'
  ud=get(gcf,'UserData');
  if checkSave                  % check if any transcription needs saving & save it    
    savedir=pwd;
    cd(ud.annotdir)
    [f,p]=uigetfile(fullfile(ud.annotdir,[ud.name '.*']),'LOAD transcription file');
    cd(savedir)
    if f
      annot=readAnnot(fullfile(p,f));
      axes(ud.annotAxes);
      delete(findobj(gca,'Tag','Annot'));
      for L=1:length(annot.times)
        addAnnot(annot.times(L),annot.labels(L,:));
      end
      ud.savename=fullfile(p,f);
      set(ud.saveopt,'Enable','off');
      set(ud.saveAsopt,'Enable','on');
      set(gcf,'UserData',ud);
    end
  end


case 'left'
  switch (get(gcf,'SelectionType'))
  case 'normal'  
    set(gcf,'WindowButtonMotionFcn','slt moveleft');
    set(gcf,'WindowButtonUpFcn','slt release');  
  case 'open'
    slt 'annotate'
  end   

case 'right'
  switch (get(gcf,'SelectionType'))
  case 'normal'  
    set(gcf,'WindowButtonMotionFcn','slt moveright');
    set(gcf,'WindowButtonUpFcn','slt release');  
  case 'open'
    slt 'annotate'
  end 

case 'release'
  set(gcf,'WindowButtonMotionFcn','slt motion');
  
case 'moveleft'
  ud=get(gcf,'UserData');
  cp=get(gca,'CurrentPoint');
  ud.left=round(min(cp(1),ud.right)); 
  set(ud.timebox,'String',num2str(round((1000*ud.left)/ud.fs)));
  set(gcf,'UserData',ud);
  slt plotCursors;
  
case 'moveright'
  ud=get(gcf,'UserData');
  cp=get(gca,'CurrentPoint');
  ud.right=round(max(cp(1),ud.left)); 
  set(ud.timebox,'String',num2str(round((1000*ud.right)/ud.fs)));
  set(gcf,'UserData',ud);
  slt plotCursors;
  
case 'motion'
  ud=get(gcf,'UserData');  
  cp=get(ud.spectAxes,'CurrentPoint')';
  t=cp(1); f=cp(2);
  xlims=get(ud.spectAxes,'XLim');
  if and(t>xlims(1),t<xlims(2))
    set(ud.timebox,'String',num2str(round(t*1000/ud.fs)));
  end
  if and(f>0,f<6000)
    set(ud.freqbox,'String',num2str(round(f)));
  end

case 'close'
  ud=get(gcf,'UserData');
  if checkSave
    if strcmp(questdlg('Are you sure you want to quit?'),'Yes')
      slt reallyquit;
    end  
  end  

otherwise
  maduievent('slt',action);
 
end

function a=readAnnot(f)
% reads annotation from file f
% form assumed is list of st end label  or just st label
fid=fopen(f,'r');
a=emptyAnnot;
if fid
  [lims,read]=fscanf(fid,'%d');
  if read
    [sym,read]=fscanf(fid,'%s',1);
  end  
  while read
    a.times = [a.times lims(1)];
    a.labels= strvcat(a.labels,sym);
    [lims,read]=fscanf(fid,'%d');
    if read
      [sym,read]=fscanf(fid,'%s',1);
    end
  end
  %a.times=20*a.times;
end
fclose(fid);

function a=emptyAnnot
a.times=[];
a.labels=[];

function addAnnot(tim,label)

if nargin < 2
  label='?';
end  
ud=get(gcf,'UserData');
thelabel=text(tim+100,0.1,label,'ButtonDownFcn','slt select',...
'Erasemode','xor','Tag','Annot','Color',[0 0 1]);
theline=line([tim tim],ud.YLim,'EraseMode','xor',...
'Color',[0.1 0.1 0.1],'LineWidth',0.5,...
'ButtonDownFcn','slt select',...
'Tag','Annot');
lud.label=thelabel;
lud.line=theline;
set(thelabel,'UserData',lud);
set(theline,'UserData',lud);


function ok=checkSave
% determine if user wants to save transcription first, or cancel, or continue
% return ok if anything but cancel
ok=1;
ud=get(gcf,'UserData');
if strcmp(get(ud.saveopt,'Enable'),'on')
  ans=questdlg('Save transcription?');
  if strcmp(ans,'Yes')
    if ud.savename
      slt saveannot
    else  
      slt saveAsannot;
    end  
  elseif strcmp(ans,'Cancel')
    ok=0;
  end
end  
