function sLib = libaudprof()
% LIBAUDPROF - function handle library
%
% Usage:
% libaudprof()
%
% Help to function "fun" can be accessed by calling
% audprof.help.fun()
%

% This file was generated by "bake_mlib audprof".
% Do not edit! Edit sources audprof_*.m instead.
%
% Date: 15-Jun-2017 11:03:40
sLib = struct;
sLib.acalos_entry_add = @audprof_acalos_entry_add;
sLib.help.acalos_entry_add = @help_acalos_entry_add;
sLib.acalos_generate = @audprof_acalos_generate;
sLib.help.acalos_generate = @help_acalos_generate;
sLib.acalos_hfdfile_load = @audprof_acalos_hfdfile_load;
sLib.help.acalos_hfdfile_load = @help_acalos_hfdfile_load;
sLib.audprof_average = @audprof_audprof_average;
sLib.help.audprof_average = @help_audprof_average;
sLib.audprof_cleanup = @audprof_audprof_cleanup;
sLib.help.audprof_cleanup = @help_audprof_cleanup;
sLib.audprof_example = @audprof_audprof_example;
sLib.help.audprof_example = @help_audprof_example;
sLib.audprof_exists = @audprof_audprof_exists;
sLib.help.audprof_exists = @help_audprof_exists;
sLib.audprof_fillnan = @audprof_audprof_fillnan;
sLib.help.audprof_fillnan = @help_audprof_fillnan;
sLib.audprof_flat = @audprof_audprof_flat;
sLib.help.audprof_flat = @help_audprof_flat;
sLib.audprof_get = @audprof_audprof_get;
sLib.help.audprof_get = @help_audprof_get;
sLib.audprof_getall = @audprof_audprof_getall;
sLib.help.audprof_getall = @help_audprof_getall;
sLib.audprof_new = @audprof_audprof_new;
sLib.help.audprof_new = @help_audprof_new;
sLib.audprof_nh = @audprof_audprof_nh;
sLib.help.audprof_nh = @help_audprof_nh;
sLib.audprof_print = @audprof_audprof_print;
sLib.help.audprof_print = @help_audprof_print;
sLib.audprof_remove_from_db = @audprof_audprof_remove_from_db;
sLib.help.audprof_remove_from_db = @help_audprof_remove_from_db;
sLib.client_audprof_add = @audprof_client_audprof_add;
sLib.help.client_audprof_add = @help_client_audprof_add;
sLib.client_audprof_rm = @audprof_client_audprof_rm;
sLib.help.client_audprof_rm = @help_client_audprof_rm;
sLib.client_example = @audprof_client_example;
sLib.help.client_example = @help_client_example;
sLib.client_exists = @audprof_client_exists;
sLib.help.client_exists = @help_client_exists;
sLib.client_new = @audprof_client_new;
sLib.help.client_new = @help_client_new;
sLib.db_audprof_add = @audprof_db_audprof_add;
sLib.help.db_audprof_add = @help_db_audprof_add;
sLib.db_audprof_rm = @audprof_db_audprof_rm;
sLib.help.db_audprof_rm = @help_db_audprof_rm;
sLib.db_client_add = @audprof_db_client_add;
sLib.help.db_client_add = @help_db_client_add;
sLib.db_client_exists = @audprof_db_client_exists;
sLib.help.db_client_exists = @help_db_client_exists;
sLib.db_client_get = @audprof_db_client_get;
sLib.help.db_client_get = @help_db_client_get;
sLib.db_client_rm = @audprof_db_client_rm;
sLib.help.db_client_rm = @help_db_client_rm;
sLib.db_import_hfd_dir = @audprof_db_import_hfd_dir;
sLib.help.db_import_hfd_dir = @help_db_import_hfd_dir;
sLib.db_load = @audprof_db_load;
sLib.help.db_load = @help_db_load;
sLib.db_prettyclientlist = @audprof_db_prettyclientlist;
sLib.help.db_prettyclientlist = @help_db_prettyclientlist;
sLib.db_save = @audprof_db_save;
sLib.help.db_save = @help_db_save;
sLib.db_xlsimport = @audprof_db_xlsimport;
sLib.help.db_xlsimport = @help_db_xlsimport;
sLib.threshold_cleanup = @audprof_threshold_cleanup;
sLib.help.threshold_cleanup = @help_threshold_cleanup;
sLib.threshold_entry_add = @audprof_threshold_entry_add;
sLib.help.threshold_entry_add = @help_threshold_entry_add;
sLib.threshold_entry_addmissing = @audprof_threshold_entry_addmissing;
sLib.help.threshold_entry_addmissing = @help_threshold_entry_addmissing;
sLib.threshold_fill_intersect = @audprof_threshold_fill_intersect;
sLib.help.threshold_fill_intersect = @help_threshold_fill_intersect;
sLib.threshold_get = @audprof_threshold_get;
sLib.help.threshold_get = @help_threshold_get;
sLib.threshold_isempty = @audprof_threshold_isempty;
sLib.help.threshold_isempty = @help_threshold_isempty;
sLib.threshold_new = @audprof_threshold_new;
sLib.help.threshold_new = @help_threshold_new;
assignin('caller','audprof',sLib);


function vs = audprof_acalos_entry_add( vs, f, mlow, mhigh, lcut, data )
% add an ACALOS entry to a acalos structure
% vs    : ACALOS structure
% f     : frequency
% mlow  : slope at low levels
% mhigh : slope at high levels
% lcut  : level of 25 cu
% data  : measured (raw) data
  ;
  if isempty(vs)
    vs = struct('f',[],'mlow',[],'mhigh',[],'lcut',[],'data',[]);
    vs = vs([]);
  end
  vs(end+1) = struct('f',f,'mlow',mlow,'mhigh',mhigh,...
		     'lcut',lcut,'data',data);
  


function help_acalos_entry_add
disp([' ACALOS_ENTRY_ADD - add an ACALOS entry to a acalos structure',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  vs = audprof.acalos_entry_add( vs, f, mlow, mhigh, lcut, data );',char(10),'',char(10),' vs    : ACALOS structure',char(10),' f     : frequency',char(10),' mlow  : slope at low levels',char(10),' mhigh : slope at high levels',char(10),' lcut  : level of 25 cu',char(10),' data  : measured (raw) data',char(10),'']);


function sAudProf = audprof_acalos_generate( sAudProf )
% generates loudness data from audiogram
% sAudProf : Auditory profile
  audiogram.frequencies = [sAudProf.l.htl_ac.data.f];
  audiogram.l.htl = [sAudProf.l.htl_ac.data.hl];
  audiogram.l.ucl = PascoeUCL(audiogram.l.htl);
  audiogram.r.htl = [sAudProf.r.htl_ac.data.hl];
  audiogram.r.ucl = PascoeUCL(audiogram.r.htl);
  audiogram.name = sAudProf.client_id;
  hfd = gt_Subject_new_from_audiogram(audiogram, audiogram.frequencies);
  for side='lr'
    acalos = [];
    for k=1:numel(hfd.(side))
      acalos = ...
	  audprof_acalos_entry_add( acalos, ...
				    hfd.(side)(k).frequency, ...
				    hfd.(side)(k).mlow, ...
				    hfd.(side)(k).mhigh, ...
				    hfd.(side)(k).lcut, ...
				    hfd.(side)(k).measured_data );
    end
    sAudProf.(side).acalos = acalos;
  end
  


function help_acalos_generate
disp([' ACALOS_GENERATE - generates loudness data from audiogram',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  sAudProf = audprof.acalos_generate( sAudProf );',char(10),'',char(10),' sAudProf : Auditory profile',char(10),'']);


function sAudProf = audprof_acalos_hfdfile_load( sAudProf, fname )
% load a HFD file (OMA exported loudness data)
% sAudProf : Auditory profile
% fname    : File name to load
  hfd = gt_Subject_new_from_hfd( fname, false );
  for side='lr'
    acalos = [];
    for k=1:numel(hfd.(side))
      acalos = ...
	  audprof_acalos_entry_add( acalos, ...
				    hfd.(side)(k).frequency, ...
				    hfd.(side)(k).mlow, ...
				    hfd.(side)(k).mhigh, ...
				    hfd.(side)(k).lcut, ...
				    hfd.(side)(k).measured_data );
    end
    sAudProf.(side).acalos = acalos;
  end
  
function subject = gt_Subject_add_fit(subject, fit, side)
% subject = gt_Subject_add_fit(subject, fit, side)
%
% Add a Fit to this subject on the given side

  fits = gt_Subject_get_fits(subject, side);
  fits = gt_Fits_add_fit(fits, fit);
  subject = gt_Subject_set_fits(subject, fits, side);  



function str = gt_Subject_as_kls_string(subject)
% str = gt_Subject_as_kls_string(subject)
%
% convert Subject data into kls formatted string (losing measured_data).

sides = {'Links', 'Rechts'};
str = '';
for sideindex = 1:2
    str = sprintf('%s"%s" "%s" "%s" %s LinBez2x3 4', ...
        str, subject.client_id, datestr(now), subject.client_id, ...
        sides{sideindex});
    fits = gt_Subject_get_fits(subject, sides{sideindex});
    for fit = fits
        str = sprintf('%s\t%d %.2f %.4g %.4g 0', ...
            str, fit.frequency, fit.lcut, fit.mlow, fit.mhigh);
    end
    str = sprintf('%s\n', str);
end


function frequencies = gt_Subject_frequencies(subject, side);
% frequencies = gt_Subject_frequencies(subject, side);
%
% returns the frequencies of the fits for the given side
  fits = gt_Subject_get_fits(subject, side);
  frequencies = [fits.frequency];


function fits = gt_Subject_get_fits(subject, side)
% fits = gt_Subject_get_fits(subject, side)
%
% Returns all fits of the subject for the given side.
%
% PARAMETERS:
%     subject:
%         A Subject struct created by one of the various gt_Subject_new*
%         functions.
%     side:
%         A string describing the side of the ear. Only the first character
%         in this string matters. It should be 'l' or 'r'.
% OUTPUT:
%     fits:
%         An array of Fit structures describing the loudness function. (See
%         gt_Fit_new for a description of the Fit structures).
s = lower(side(1));
if isfield(subject, s)
    fits = subject.(s);
else
    fits = gt_Fit_new;
end





function target_subject = gt_Subject_interpolate(source_subject, target_frequencies)
% target_subject = gt_Subject_interpolate(source_subject, target_frequencies)
% 
% Returns a gt_Subject structure with Fits for the given target_frequencies.
% The fit parameters are interpolated from the ones given in source_kls.
%
% PARAMETERS:
%     target_frequencies:
%         A row vector containing the target frequencies in Hz
%     source_subject:
%         A gt_Subject structure representing a categorial loudness
%         measurement
%
% OUTPUT:
%     target_subject:
%         A gt_Subjetct structure with Fits for the desired target_frequencies.
%
% (C) 2003 Universität Oldenburg

target_subject = source_subject;
for cellname = fieldnames(target_subject)'
    name = cellname{1};
    if isequal(1, length(name))
        target_subject.(name) = ...
            gt_Fit_interpolate(target_subject.(name), target_frequencies);
    end
end
target_subject.original = source_subject;



function subject = gt_Subject_new_from_audiogram(audiogram, ...
                                                 target_frequencies, nh)
% subject = gt_Subject_new_from_audiogram(audiogram)
%
% Create subject object from audiogram data
%
% OPTIONS:
%   audiogram
%     A struct with fields:
%       audiogram.frequencies -- Audiogram frequencies / Hz
%       audiogram.l.htl       -- Hearing threshold levels left / dB HL
%       audiogram.l.ucl       -- Uncomfortable levels left / dB HL
%       audiogram.r.htl       -- Hearing threshold levels right / dB HL
%       audiogram.r.ucl       -- Uncomfortable levels right / dB HL
%       audiogram.name        -- client_id
%     All (sub-)fields have to contain voectors of the same length
%   target_frequencies [optional]
%     Set to audiogram.frequencies or [] or regret it!
%   nh [optional]
%     mean normal hearing listener loudness function parameters

if (nargin < 2)
    target_frequencies = [];
end
if isempty(target_frequencies)
  target_frequencies = audiogram.frequencies;
end
if nargin < 3
  nh = gt_Subject_nh;
end

subject.client_id = audiogram.name;
num_frequencies = length(target_frequencies);

for side = 'lr'
    htls = interp1(...
        log(audiogram.frequencies), ...
        audiogram.(side).htl, ...
        log(target_frequencies));
    ucls = interp1(...
        log(audiogram.frequencies), ...
        audiogram.(side).ucl, ...
        log(target_frequencies));
    for freq_index = 1:num_frequencies
        fit = gt_Fit_new_from_aud(...
            htls(freq_index), ...
            ucls(freq_index), ...
            target_frequencies(freq_index), ...
            gt_Subject_get_fits(nh,side));
        subject = gt_Subject_add_fit(subject, fit, side);
    end
end


function subject = gt_Subject_new_from_db
subject = gt_Subject_new_from_string(kls_data_from_db);

function data = kls_data_from_db
  if (~exist('mwsh'))
    data = '';
    error('gt:Subject:new_from_db', ...
        'Mex file ("mwsh") for database access not found');
  end
  %winshell laden
  status = mwsh('load', 'wsh');
  %pruefen ob status==0, sonst Fehler
  if (~isequal(status, 0))
    error('Cannot load winshell for database access');
  end

  %Messung wählen
  status = mwsh(['call kls.e select']);
  %pruefen ob status==0, sonst Fehler
  if (~isequal(status, 0))
    error('Error while selecting measurement from database');
  end

  %String aus winshell holen
  data = mwsh('/tmp/Result', 'get');

  %winshell entladen
  status = mwsh('unload', 'wsh');





function subject = gt_Subject_new_from_db_with_data(refit)
% subject = gt_Subject_new_from_db_with_data(refit)
% 
% extract loudness data from mess-ol db
% If refit is true, then recompute the fit parameters using Toms new
% fitting prescription + passcoe option.

  if (~exist('mwsh'))
    error('gt:Subject:new_from_db', ...
        'Mex file ("mwsh") for database access not found');
  end
  
  %winshell laden
  status = mwsh('load', 'wsh');
  %pruefen ob status==0, sonst Fehler
  if (~isequal(status, 0))
    error('gt:Subject:new_from_db_with_data', ...
        'Cannot load winshell for database access');
  end

  %Messung wählen
  status = mwsh(['call kls.e select']);
  %pruefen ob status==0, sonst Fehler
  if (~isequal(status, 0))
    error('Error while selecting measurement from database');
  end

  % Daten aus winshell holen
  
  subject.client_id = strtok(mwsh('/tmp/Result', 'get'));
  
  %lese Anzahl subheader in KLS-Result
  numhd = sscanf(mwsh('/tmp/NumHd', 'get'), '%d', 1);

  for header_index = [0:(numhd-1)] 
      subheader = sprintf('/tmp/ResultHd/%d/', header_index);
      side = mwsh([subheader, 'Seite'], 'get');
      lcut = get_mwsh(subheader, 'Lcut');
      mlow = get_mwsh(subheader, 'Mlow');
      mhigh = get_mwsh(subheader, 'Mhigh');
      data = get_mwsh(subheader, 'DatenAbfolge');
      if isempty(data)
        data = get_mwsh(subheader, 'Daten');
      end
      frequency = get_mwsh(subheader, 'Frequenz');
      if (~isempty(lcut) & ~isempty(mlow) & ~isempty(mhigh) & ...
              ~isempty(data) & ~isempty(frequency))
        if refit
          fit = gt_Fit_new(data, frequency, 'tom2005');
        else
          fit = gt_Fit_new([lcut, mlow, mhigh], frequency, data);
        end
        subject = gt_Subject_add_fit(subject, fit, side);  
      end
  end
  %winshell entladen
  status = mwsh('unload', 'wsh');

function float_result = get_mwsh(subheader, fieldname)
  string_result = mwsh([subheader, fieldname], 'get');
  float_result = sscanf(string_result, '%f', [1,inf]);



function subject = gt_Subject_new_from_hfd(filename, refit)
% subject = gt_Subject_new_from_hfd(hfd_filename, refit)
%
% generate subject data structure containing loudness functions from the
% result file (containing the shs header generated by mess-ol from
% categorical loudness scaling.
% If refit is true, then recompute the fit parameters using Toms new
% fitting prescription + passcoe option.

if nargin < 2
  refit = 0;
end

fd = fopen(filename);
if (fd < 0)
    error(['Unable to open file ', filename, ' for reading']);
end

shs_data = fscanf(fd, '%c', [1,inf]);
fclose(fd);
subject = gt_Subject_new_from_shs(shs_data, refit);


function subject = gt_Subject_new_from_kls_file(filename)
fd = fopen(filename);
if (fd < 0)
    error(['Unable to open file ', filename, ' for reading']);
end

kls_data = fscanf(fd, '%c', [1,inf]);
fclose(fd);
subject = gt_Subject_new_from_string(kls_data);



function kls = gt_Subject_new_from_shs(shs_string, refit)
% subject = gt_Subject_new_from_shs(shs_string, refit)
%
% generate subject data structure containing loudness functions from the
% shs header string as generated by mess-ol from categorical loudness
% scaling.
% If refit is true, then recompute the fit parameters using Toms new
% fitting prescription + passcoe option.

  if nargin < 2
    refit = 0
  end
  
  %------------------------------------------------------------
  % split SHS data into lines 
  %------------------------------------------------------------
  lines_ = split_data_into_lines(shs_string);
  
  kls.client_id = '';

  %------------------------------------------------------------
  % scan each line
  %------------------------------------------------------------
  data = [];
  mlow = [];
  mhigh = [];
  lcut = [];
  frequency = [];
  seite = '';
  
  line_no = 0;
  while ( line_no < length(lines_) )
    line_no = line_no + 1;
    line_ = lines_{line_no};
    tokens = parse_shs_line(line_);
    if isequal(tokens{1}, 'ClientId:')
      kls.client_id = tokens{2};
    elseif isequal(tokens{1}, 'Daten:')
      if isempty(data) % Bevorzuge DatenAbfolge
        data = [tokens{2:end}];
      end
    elseif isequal(tokens{1}, 'DatenAbfolge:')
      data = [tokens{2:end}];
    elseif isequal(tokens{1}, 'Lcut:')
      lcut = (tokens{2});
    elseif isequal(tokens{1}, 'Mlow:')
      mlow = (tokens{2});
    elseif isequal(tokens{1}, 'Mhigh:')
      mhigh = (tokens{2});
    elseif isequal(tokens{1}, 'Frequenz:')
      frequency = (tokens{2});
    elseif isequal(tokens{1}, 'Seite:')
      seite = tokens{2};
    elseif isequal(tokens{1}, '}')
      if ~isempty(seite) & ~isempty(frequency) & ~isempty(lcut) & ...
           ~isempty(mlow) & ~isempty(mhigh)
        if refit
          fit = gt_Fit_new_from_data(data, frequency, 'tom2005')
        else
          params = [lcut, mlow, mhigh];
          fit = gt_Fit_new(params, frequency, data);
        end
        kls = gt_Subject_add_fit(kls, fit, seite);  
      end
      data = [];
      mlow = [];
      mhigh = [];
      lcut = [];
      frequency = [];
      seite = '';
    end
  end
  if (isempty(kls.client_id))
      error('insufficient shs data (client id).');
  end

% ------------------------------------------------------------
% Tools for reading KLS data from string
% ------------------------------------------------------------
  
function tokens = parse_shs_line(string)
  tokens = {};
  count = 0;
  rest = string;
  while (~isempty(rest))
    count = count + 1;
    [token, rest] = parse_1_shs_token(rest);
    if (isempty(token))
      break;
    end
    tokens{count} = token;
  end
  
function [token, rest] = parse_1_shs_token(string)
  rest = skip_space(string);
  token = [];
  if (isempty(rest))
    return
  end
  if isequal(rest(1), '"')
    delimiter = '"';
  else
    %            space, carriage return, newline , tab
    delimiter = [' '  , char(13)       , char(10), char(9)];
  end
  [token, rest] = strtok(rest, delimiter);
  rest = rest(2:length(rest));
  if (~isempty(token))
    if (~isnan(str2double(token)))
      token = str2double(token);
    end
  end
  
function rest = skip_space(string)
  rest = string;
  if (~isempty(rest))
    if (isspace(rest(1)))
      rest = skip_space(rest(2:length(rest)));
    end
  end

function kls = gt_Subject_new_from_string(kls_string)
  %------------------------------------------------------------
  % split KLS data into lines 
  %------------------------------------------------------------
  lines_ = split_data_into_lines(kls_string);
  
  kls.client_id = '';

  %------------------------------------------------------------
  % scan each line
  %------------------------------------------------------------
  line_no = 0;
  while ( line_no < length(lines_) )
    line_no = line_no + 1;
    line_ = lines_{line_no};
    tokens = parse_kls_line(line_);
    if (isempty(tokens)), break; end
    if (length(tokens) < 6)
        error(sprintf('invalid kls data (too few tokens in line %d)', ...
                      line_no));
    end

    ThisClientId = tokens{1};
    sDateTime = tokens{2};
    sRemark = tokens{3};
    ThisSide = tokens{4};
    ThisFitName = tokens{5};
    ThisNumber = tokens{6}; % number of fit parameters
    if ~isequal(ThisNumber,4)
        error(sprintf('%s (number of fit params on line %d not 4).', ...  
                      'invalid kls data', line_no));
    end

    % check client id on this line:
    if line_no == 1 & isempty(kls.client_id),
      kls.client_id = ThisClientId;
    else
      if kls.client_id ~= ThisClientId
        error('inconsistent kls data (client id).');
      end
    end

    % read all frequencies/params:
    ThisValues = cell2mat(tokens(7:length(tokens)));
    SetSize = ThisNumber+1;
    NumSets = floor(length(ThisValues)/SetSize);
    %
    for I = 1:NumSets,
      frequency = ThisValues((I-1)*SetSize+1);
      params = ThisValues((I-1)*SetSize+2:(I-1)*SetSize+1+ThisNumber);
      fit = gt_Fit_new(params, frequency);
      kls = gt_Subject_add_fit(kls, fit, ThisSide);  
    end
  end
  if (isempty(kls.client_id))
      error('insufficient kls data (client id).');
  end
% ------------------------------------------------------------
% Tools for reading KLS data from string
% ------------------------------------------------------------
  
function tokens = parse_kls_line(string)
  tokens = {};
  count = 0;
  rest = string;
  while (~isempty(rest))
    count = count + 1;
    [token, rest] = parse_1_kls_token(rest);
    if (isempty(token))
      break;
    end
    tokens{count} = token;
  end
  
function [token, rest] = parse_1_kls_token(string)
  rest = skip_white_space(string);
  token = [];
  if (isempty(rest))
    return
  end
  if isequal(rest(1), '"')
    delimiter = '"';
  else
    %            space, carriage return, newline , tab
    delimiter = [' '  , char(13)       , char(10), char(9)];
  end
  [token, rest] = strtok(rest, delimiter);
  rest = rest(2:length(rest));
  if (~isempty(token))
    if (~isnan(str2double(token)))
      token = str2double(token);
    end
  end
  
function rest = skip_white_space(string)
  rest = string;
  if (~isempty(rest))
    if (isspace(rest(1)))
      rest = skip_white_space(rest(2:length(rest)));
    end
  end



function subject = gt_Subject_nh
% loads the loudness functions of the average normal hearing listener into
% a gt_Subject structure
subject = gt_Subject_new_from_kls_file('nh.kls');




function gt_Subject_plot(subject, side, nh, hl_corr)
% gt_Subject_plot(subject, side, nh, hl_corr)

if nargin < 3
    nh = gt_Subject_nh;
end
if nargin < 4
    hl_corr.name = 'HL';
    hl_corr.f = gt_Subject_frequencies(subject,side);
    hl_corr.d = zeros(size(hl_corr.f));
else
    if isequal('char',class(hl_corr))
        mode = hl_corr;
        hl_corr.name = ['SPL ',mode,mode];
        hl_corr.f = gt_Subject_frequencies(subject,side);
        hl_corr.d = -hl_correction(hl_corr.f, mode);
    else
        hl_corr.d = interp1(hl_corr.f, hl_corr.d, gt_Subject_frequencies(subject,side));
        hl_corr.f = gt_Subject_frequencies(subject,side);
    end
end

fits = gt_Subject_get_fits(subject, side);
nh_fits = ...
    gt_Subject_get_fits(gt_Subject_interpolate(...
                                nh, ...
                                gt_Subject_frequencies(subject, side)), ...
                        side);
side = lower(side(1));
colors = 'br';
side_index = 1+isequal(side,'r');
color = colors(side_index);
rows = ceil(length(fits) / 2);                    
for fit_index = 1:length(fits)
    fit = fits(fit_index);
    fit.lcut = fit.lcut - hl_corr.d(fit_index);
    nh_fit = nh_fits(fit_index);
    nh_fit.lcut = nh_fit.lcut - hl_corr.d(fit_index);
    subplot(rows,2,fit_index);
    gt_Fit_plot(fit,color);
    hold on;
    gt_Fit_plot(nh_fit,['--' color]);
    axis([0 120 0 50]);
    set(gca,'ytick',[0:5:50],'xtick',[0:10:120])
    grid on
    title(sprintf('%s: %dHz', subject.client_id, fit.frequency))
    xlabel(['dB ' hl_corr.name])
    ylabel('cu')
end

set(gcf, 'PaperPosition', [0.6345    0.6345   19.7150   28.4084]);

function kls = gt_Subject_set_fits(kls, fits, side)
kls.(lower(side(1))) = fits;



function lines_ = split_data_into_lines(data)
% function lines = split_data_into_lines(data)
%
% split the string <data> into <lines> 
  lines_ = {};
  line_delimiter = [char(13), char(10)]; % recognized end-of-line characters
  
  % data given as string
  while length(data)
    [line_, data] = strtok(data, line_delimiter);
    if (~isempty(line_) & ~all(isspace(line_)))
      lines_{length(lines_)+1} = line_;
    end
  end

function fit = gt_Fit_new(fitparams, frequency, measured_data, fitname)
% fit = gt_Fit_new(data, frequency, measured_data, fitname)
%
% function creates a new Fit structure from the given arguments. A fit
% structure can be used to transform sound levels to categorial units
% (gt_Fit_cu) and vice versa (gt_Fit_level).
%
% The structure fields of a Fit structure are:
%     frequency (in Hz), lcut, mlow, mhigh, measured_data
%     
% PARAMETERS: (All are optional)
%     fitparams:
%         An array containing the fit parameters Lcut, Mlow, Mhigh, in
%         this order.
%         If this parameter is missing, an empty (1x0) structure array is
%         returned from this function.
%     frequency:
%         The frequency of the loudness function represented by this Fit.
%     measured_data:
%         The data points from the categorial loudness scaling.
%     fitname:
%         If given, must be 'LinBez2x3'.
%         Other fit models are not implemented for this Fit structure.
% OUTPUT:
%     fit:
%         The new fit structure describing the loudness function.

  if (nargin < 4)
    fitname = 'LinBez2x3';
  end
  if (nargin < 3)
      measured_data = [];
  end
  if (nargin < 2)
      frequency = [];
  end
  end_index = 1;
  if (nargin < 1)
      fitparams = [nan,nan,nan];
      end_index = 0;
  end
  fit.frequency = frequency;
  if ~isequal('LinBez2x3', fitname)
    error('will only work with ''LinBez2x3'' fit')
  end
  
  fit.lcut = fitparams(1);
  fit.mlow = fitparams(2);
  fit.mhigh = fitparams(3);
  fit.measured_data = measured_data;
  fit = fit(1:end_index);



function fit = gt_Fit_new_from_aud(hsl, ucl, frequency, nh_fits)
% fit = gt_Fit_new_from_aud(hsl, ucl, frequency, nh_fits)
%
% function creates a new Fit structure from audiogram data. A fit
% structure can be used to transform sound levels to categorial units
% (gt_Fit_cu) and vice versa (gt_Fit_level).
%
% The structure fields of a Fit structure are:
%     frequency (in Hz), lcut, mlow, mhigh, measured_data
%     
% PARAMETERS:
%     hsl:
%         The hearing threshold in dB HL.
%     ucl:
%         The uncomfortable level in dB HL
%     frequency:
%         The frequency of the audiogram data.
%     nh_fits:
%         Optional. The Loudness function fits for a normal hearing
%         listener.
% OUTPUT:
%     fit:
%         The new fit structure describing the loudness function.

if (nargin < 4)
    nh = gt_Subject_nh;
    nh_fits = nh.l;
end

if (frequency <= 0)
    error('frequency must be > 0');
end

nh_freq = [nh_fits.frequency];
nh_mhigh = [nh_fits.mhigh];

% for interpolation restrict frequency to covered range
interp_frequency = frequency;
if frequency < min(nh_freq)
    interp_frequency = min(nh_freq);
end
if frequency > max(nh_freq)
    interp_frequency = max(nh_freq);
end

mhigh = interp1(log(nh_freq), nh_mhigh, log(interp_frequency), 'linear');

fit = gt_Fit_new(audiogram2tomfit(hsl, ucl, mhigh), frequency);



function fit = gt_Fit_new_from_data(measured_data, frequency, fit_mode)
% fit = gt_Fit_new_from_data(measured_data, frequency, fit_mode)
%
% function creates a new Fit structure from loudness scaling data points.
% A fit structure can be used to transform sound levels to categorical
% units (gt_Fit_cu) and vice versa (gt_Fit_level).
%
% The structure fields of a Fit structure are:
%     frequency (in Hz), lcut, mlow, mhigh, measured_data
%     
% PARAMETERS:
%     measured_data:
%         The data points from the categorial loudness scaling.
%     frequency:
%         The frequency of the audiogram data.
%     fit_mode:
%         Optional. The fitting mode, either 'tom2000' or
%         'tom2005' (with passcoe ucl). Default is traditional 'tom2000'
%     
% OUTPUT:
%     fit:
%         The new fit structure describing the loudness function.

if (nargin < 3)
  fit_mode = 'tom2000';
end
if (frequency <= 0)
    error('frequency must be > 0');
end
if isequal('tom2005', fit_mode)
  [lcut, mlow, mhigh] = tomfit_passcoe(measured_data);
else
  [lcut, mlow, mhigh] = tomfit_orig(measured_data);
end
fit = gt_Fit_new([lcut, mlow, mhigh], frequency, measured_data);



function [Lcut,Mlow,Mhigh] = tomfit_orig(daten)

% Startwerte für Optimierung:
%fitparams =[Lcut,Mlow,Mhigh];
fitparams_start =[75,0.35,0.65];

[fitparams, exval, exitflag, output] = ...
    fminsearch( @(fitparams)costfc2(fitparams,daten),fitparams_start);

Lcut = fitparams(1);
Mlow = fitparams(2);
Mhigh = fitparams(3);

function [x] = costfc2(fitparams,daten)
% (zu minimierende Kostenfunktion)
% gibt Summe der quadratischen Fehler des Fit-Modells zu den Daten aus
% verwendet linear fortgeführte klsku.dll
% fitparams =[Lcut,Mlow,Mhigh]
% daten = [level1, cu1, level2, cu2, ... ]

level = daten(1:2:end);
cu = daten(2:2:end);
cu_fit = klsku(level,'LinBez2x3',[fitparams, 0]);

%% lineare Fortführung von klsku:
x2=klsku(50,'LinBez2x3',[fitparams, 0],1); %get level at CU = 50
x0=klsku(0,'LinBez2x3',[fitparams, 0],1); %get level at CU = 0

cu_fit(level<x0) = fitparams(2)*(level(level<x0) - x0) + 0;
cu_fit(level>x2) = fitparams(3)*(level(level>x2) - x2) + 50;

cu_fit(cu==50 & (cu_fit > cu))   = 50;
cu_fit(cu==0 & (cu_fit < cu))    = 0;

x = sum((cu_fit - cu).^2);





function [Lcut,Mlow,Mhigh] = tomfit_passcoe(daten)
%
% Berechnet [Lcut,Mlow,Mhigh] nach verbessertem Verfahren
%
% Thomas Bisitz, Mai/Juni 2005
%
% 3b: ohne 'if Lcut < UCL'

[Lcut, Mlow, Mhigh] = tomfit_orig(daten);

MaxUCL = 140;
vHFSData = [daten(1:2:end);daten(2:2:end)];

HTL = klsku(0,'LinBez2x3',[Lcut,Mlow,Mhigh],1);
UCL = klsku(50,'LinBez2x3',[Lcut,Mlow,Mhigh],1);

bReFit = 0;
% Falls die UCL höher als MaxUCL geschätzt wird, wird sie auf MaxUCL
% festgelegt, und der Fit wird mit dieser Nebenbedingung nochmal durchgeführt.
if UCL>MaxUCL,
  UCL = MaxUCL;
  bReFit = 1;
end 
% Falls die obere Steigung eindeutig zu niedrig ist (< 0.25 cu/dB) oder
% wenn nicht mindestens 4 Lautheitsurteile vorliegen, die mindestens als
% 'laut'(35cu) beurteilt wurden, wird die UCL nach Pascoe abgeschätzt
% und dann der Fit mit dieser Randbedingung wiederholt
	
if Mhigh<0.25 | sum(vHFSData(2,:)>=35)<4,
  UCL = PascoeUCL(HTL);
  bReFit = 1;
end
    
if bReFit
  fitparams_start =[UCL-20,0.35];
  [fitparams, exval, exitflag, output] = ...
      fminsearch(@(fitparams)costfc2([fitparams, 25/(UCL-fitparams(1))], ...
                                     daten), ...
                 fitparams_start);
  Lcut  = fitparams(1);
  Mlow  = fitparams(2);
  Mhigh = (50-25)/(UCL-Lcut);
end	


function UCL=PascoeUCL(HTL,mode)
%
%	UCL=PascoeUCL(HTL,mode)
%
%	schätzt die Unbehaglichkeitsschwelle UCL aus der Hörschwelle HTL ab
%	nach:
%	Pascoe, D. P., (1988), Clinical measurements of the auditory dynamic range
%       and their relation to formulas for hearing aid gain, In: J.H. Jensen (Editor),
%       Hearing Aid fitting, 13th Danavox Symposium, 129-152.
%
%	mode = 'smoothed': einfache Abschätzung nach Tabelle 4 (pooled data)

if nargin<2
	mode = 'smoothed';
end

if strcmp(mode,'smoothed')
	UCLverHTL=[...
		-100	100;
		40	100;
		120	140];
end
UCL=interp1(UCLverHTL(:,1),UCLverHTL(:,2),HTL);


function fits = gt_Fits_add_fit(fits, fit)
  if any([fits.frequency] == fit.frequency)
      error('gt:Fits:add_fit:BAD_FREQ', ...
            'More than 1 set of fit params for %dHz', ...
            fit.frequency);
  end
  fits = [fits,fit];



  


function help_acalos_hfdfile_load
disp([' ACALOS_HFDFILE_LOAD - load a HFD file (OMA exported loudness data)',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  sAudProf = audprof.acalos_hfdfile_load( sAudProf, fname );',char(10),'',char(10),' sAudProf : Auditory profile',char(10),' fname    : File name to load',char(10),'']);


function sAud = audprof_audprof_average( csAud, avgfun )
  sAud = audprof_audprof_new();
  if isempty(csAud)
    return;
  end
  if nargin < 2
    avgfun = @(x) mean(x,1);
  end
  for side='lr'
    for cType={'htl_ac','htl_bc','ucl'}
      stype = cType{:};
      sTh = audprof_threshold_get( csAud{1}, side, stype );
      vf = [sTh.data.f];
      for k=2:numel(csAud)
	sTh = audprof_threshold_get( csAud{k}, side, stype );
	vf = intersect(vf,[sTh.data.f]);
      end
      if ~isempty(vf)
	mH = zeros([numel(csAud),numel(vf)]);
	for k=1:numel(csAud)
	  sTh = audprof_threshold_get( csAud{k}, side, stype );
	  sTh = audprof_threshold_fill_intersect( sTh, vf, zeros(size(vf)));
	  mH(k,:) = [sTh.data.hl];
	end
	sAud.(side).(stype) = ...
	    audprof_threshold_entry_add([],vf, avgfun(mH));
      end
    end
  end


function help_audprof_average
disp([' AUDPROF_AVERAGE - ',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  sAud = audprof.audprof.average( csAud, avgfun );',char(10),'']);


function sAud = audprof_audprof_cleanup( sAud )
% Remove NaN and Inf entries from thresholds and remove empty
% auditory profile data.
  for side='lr'
    if isfield(sAud,side)
      for type={'htl_ac','htl_bc','ucl'}
	stype = type{:};
	if isfield( sAud.(side),stype )
	  sAud.(side).(stype) = ...
	      audprof_threshold_cleanup( sAud.(side).(stype) );
	  if audprof_threshold_isempty( sAud.(side).(stype) )
	    sAud.(side) = rmfield(sAud.(side),stype);
	  end
	end
      end
      if isempty(fieldnames(sAud.(side)))
	sAud = rmfield(sAud,side);
      end
    end
  end
  if ~isfield(sAud,'id')
    sAud.id = '';
  end
  if ~isfield(sAud,'client_id')
    sAud.client_id = '';
  end

function help_audprof_cleanup
disp([' AUDPROF_CLEANUP - Remove NaN and Inf entries from thresholds and remove empty',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  sAud = audprof.audprof.cleanup( sAud );',char(10),'',char(10),' auditory profile data.',char(10),'']);


function sAudProf = audprof_audprof_example( type )
% return an example auditory profile
% sAudProf : auditory profile
% type : profile type: slope, flat40 or flat50 (optional)
  if nargin < 1
    type = 'slope';
  end
  if ~ischar(type)
    error('The type argument must be a string');
  end
  fAud = [125 250 500 750 1000 1500 2000 3000 4000 6000 8000];
  switch type
   case 'slope'
    sAudProf = audprof_audprof_new();
    sAudProf.l.htl_ac = ...
	audprof_threshold_entry_add([],fAud,[10 15 30 40 45 50 65 65 75 65 65]);
    sAudProf.r.htl_ac = ...
	audprof_threshold_entry_add([],fAud,[20 15 20 30 35 35 40 55 70 65 85]);
%    sAudProf.l.acalos = dummy_acalos_l;
%    sAudProf.r.acalos = dummy_acalos_r;
   case 'flat40'
    sAudProf = audprof_audprof_flat(40);
   case 'flat50'
    sAudProf = audprof_audprof_flat(50);
   otherwise
    error('Invalid audiogram type');
  end
  sAudProf.client_id = 'TT123456';
  sAudProf.id = type;
  
function s = dummy_acalos_l
  s = [];
  s = audprof_acalos_entry_add(s,500,0.457643,0.829785,74.9837,[75 30 85 ...
		    40 60 20 90 40 45 15 95 45 30 0 100 50 35 15 55 ...
		    20 68 20 81 25 94 35 61 20 26 0 44 5 79 25 96 ...
		    35 45 15 28 0 62 20 96 45 79 25]);
  s = audprof_acalos_entry_add(s,1000,0.561393,0.890432,75.8846,[75 20 ...
		    85 35 60 20 90 45 45 5 95 40 30 0 100 45 35 0 ...
		    105 50 40 0 45 5 63 10 75 25 87 40 99 45 72 20 ...
		    45 15 58 20 85 30 98 45 45 10 58 20 72 25 85 30 98 45]);
  s = audprof_acalos_entry_add(s,2000,1.12774,1.45832,79.1485,[75 20 85 ...
		    40 60 0 90 45 65 15 95 45 100 50 76 15 83 30 90 ...
		    45 97 50 62 0 70 10 78 15 86 35 94 50 72 20 65 ...
		    20 78 25 84 30 91 45]);
  s = audprof_acalos_entry_add(s,4000,1.01754,1.2982,89.0316,[75 15 85 ...
		    25 60 0 90 25 65 0 95 30 70 0 100 30 75 15 105 ...
		    45 84 15 90 30 96 35 102 45 85 15 69 0 77 20 93 ...
		    35 101 40 85 15 69 0 77 15 93 35 101 45]);
  s = audprof_acalos_entry_add(s,6000,0.9258,3.06498,92.9758,[75 10 85 ...
		    15 60 0 90 15 65 0 95 35 70 0 100 45 75 15 101 ...
		    45 83 20 88 15 93 30 98 45 72 0 78 5 85 20 91 ...
		    25 98 40 81 20 75 5 86 15 92 25 98 45]);

function s = dummy_acalos_r
  s = [];
  s = audprof_acalos_entry_add(s,500,0.505853,0.437727,61.8088,[75 35 85 ...
		    35 60 25 90 35 45 15 95 35 30 15 100 45 15 0 105 ...
		    50 20 0 25 5 49 25 65 25 81 35 97 45 41 15 23 5 ...
		    59 20 96 40 78 25 25 5 60 25 96 35 78 25 43 20]);
  s = audprof_acalos_entry_add(s,1000,0.472,0.831115,75.8846,[75 20 85 ...
		    35 60 20 90 45 45 5 95 40 30 0 100 45 35 0 105 ...
		    50 40 0 45 5 63 10 75 25 87 40 99 45 72 20 45 ...
		    15 58 20 85 30 98 45 45 10 58 20 72 25 85 30 98 45]);
  s = audprof_acalos_entry_add(s,2000,0.608552,1.48877,72.6579,[75 35 85 ...
		    45 60 15 90 50 45 15 30 0 35 0 40 5 55 15 65 25 ...
		    75 25 85 45 42 5 53 15 64 15 75 35 86 40 44 5 ...
		    66 15 88 50 77 30 55 15]);
  s = audprof_acalos_entry_add(s,4000,0.94617,1.10556,89.7445,[75 15 85 ...
		    15 60 0 90 15 65 0 95 25 70 15 100 35 105 35 81 ...
		    25 88 25 95 35 102 45 74 0 65 0 83 15 101 35 92 ...
		    35 84 30 66 0 75 5 92 25 101 45]);
  s = audprof_acalos_entry_add(s, 6000,0.711054,2.99707,94.6282,[75 15 ...
		    85 25 60 0 90 15 65 0 95 25 70 15 100 35 101 50 ...
		    79 15 86 25 92 25 98 35 64 0 72 5 89 20 97 35 ...
		    81 20 68 0 76 10 83 25 90 25 97 30]);

  


function help_audprof_example
disp([' AUDPROF_EXAMPLE - return an example auditory profile',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  sAudProf = audprof.audprof.example( type );',char(10),'',char(10),' sAudProf : auditory profile',char(10),' type : profile type: slope, flat40 or flat50 (optional)',char(10),'']);


function b = audprof_audprof_exists( sClientID, sAudID, cDB )
% get auditory profile for a given client ID
  libconfigdb();
  if nargin < 3
    cDB = audprof_db_load();
  end
  cClient = audprof_db_client_get( cDB, sClientID );
  if isempty(cClient)
    error(['Client ID ''',sClientID,''' not found!']);
  end
  sAudProf = configdb.smap_get( cClient.audprofs,...
					      sAudID );
  b = ~isempty(sAudProf);


function help_audprof_exists
disp([' AUDPROF_EXISTS - get auditory profile for a given client ID',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  b = audprof.audprof.exists( sClientID, sAudID, cDB );',char(10),'',char(10),'']);


function sAud = audprof_audprof_fillnan( sAud )
  for side='rl'
    if ~isfield(sAud,side)
      sAud.(side) = struct;
    end
    for type={'htl_ac','htl_bc','ucl'}
      stype = type{:};
      vf = unique([1000*2.^[-3:3],1500*2.^[-1:2]])';
      if ~isfield(sAud.(side),stype)
	sAud.(side).(stype) = audprof_threshold_new();
      end
      sAud.(side).(stype) = ...
	  audprof_threshold_entry_addmissing(sAud.(side).(stype),...
					     vf,nan(size(vf)));
    end
  end

function help_audprof_fillnan
disp([' AUDPROF_FILLNAN - ',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  sAud = audprof.audprof.fillnan( sAud );',char(10),'']);


function sAud = audprof_audprof_flat( HTL )
% return a flat auditory profile
  sAud = audprof_audprof_new();
  sAud.id = sprintf('flat audiogram %g dB',HTL);
  vf = unique([1000*2.^[-3:3],1500*2.^[-1:2]]);
  for side='lr'
    sAud.(side).htl_ac = ...
	audprof_threshold_entry_add([],vf,HTL*ones(size(vf)));
  end


function help_audprof_flat
disp([' AUDPROF_FLAT - return a flat auditory profile',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  sAud = audprof.audprof.flat( HTL );',char(10),'',char(10),'']);


function sAudProf = audprof_audprof_get( sClientID, sAudID, cDB )
% get auditory profile for a given client ID
  if nargin < 3
    cDB = audprof_db_load();
  end
  cfdb = libconfigdb();
  cClient = cfdb.smap_get( cDB, sClientID );
  if isempty(cClient)
    error(['Client ID ''',sClientID,''' not found!']);
  end
  sAudProf = cfdb.smap_get( cClient.audprofs,...
					      sAudID );
  if isempty(sAudProf)
    error(['Auditory profile ''',sAudID,''' not found!']);
  end
  sAudProf.client_id = sClientID;

function help_audprof_get
disp([' AUDPROF_GET - get auditory profile for a given client ID',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  sAudProf = audprof.audprof.get( sClientID, sAudID, cDB );',char(10),'',char(10),'']);


function sAudProfs = audprof_audprof_getall( sClientID )
% get auditory profile for a given client ID
  cDB = audprof_db_load();
  cfdb = libconfigdb();
  cClient = cfdb.smap_get( cDB, sClientID );
  if isempty(cClient)
    error(['Client ID ''',sClientID,''' not found!']);
  end
  if isfield(cClient,'audprofs')
    sAudProfs = cClient.audprofs;
  else
    sAudProfs = cell([2,0]);
  end

function help_audprof_getall
disp([' AUDPROF_GETALL - get auditory profile for a given client ID',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  sAudProfs = audprof.audprof.getall( sClientID );',char(10),'',char(10),'']);


function sAud = audprof_audprof_new( client_id )
  if nargin < 1
    client_id = '';
  end
  sAud = struct;
  sAud.id = '';
  sAud.client_id = client_id;


function help_audprof_new
disp([' AUDPROF_NEW - ',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  sAud = audprof.audprof.new( client_id );',char(10),'']);


function sAud = audprof_audprof_nh( )
% return normal hearing auditory profile
  sAud = audprof_audprof_flat( 0 );
  sAud.id = 'normal hearing';
 

function help_audprof_nh
disp([' AUDPROF_NH - return normal hearing auditory profile',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  sAud = audprof.audprof.nh( );',char(10),'',char(10),'']);


function audprof_audprof_print( sAud )
  libmhagui();
  fh = figure('MenuBar','none','PaperUnits','centimeters',...
	      'PaperPosition',[2,10,17,15]);
  side = 'lr';
  csSide = {'left','right'};
  for k=1:2
    ax = subplot(2,2,3-k);
    oside = side(3-k);
    sAudTmp = sAud;
    if isfield(sAud,oside)
      sAudTmp = rmfield(sAudTmp,oside);
    end
    set(findobj(fh,'tag','audiogram_axes'),'tag','');
    set(ax,'tag','audiogram_axes');
    mhagui.audprof_plot( sAudTmp );
    title(sprintf('%s - %s',[sAud.client_id,' ',sAud.id],...
		  csSide{k}),...
	  'Interpreter','none');
    
    sAudTmp = audprof_audprof_fillnan( sAudTmp );
    ax = subplot(2,2,5-k);
    ldatestr = '';
    if k==2
      if isfield(sAud,'date')
	ldatestr = sAud.date;
      end
    elseif k==1
      ldatestr = sprintf('Printed: %s',datestr(now));
    end
    ktype = 0;
    for type={'htl_ac','htl_bc','ucl'}
      slab = type{:};
      slab = strrep(slab,'htl','HTL');
      slab = strrep(slab,'ucl','UCL');
      ktype = ktype+1;
      sA = sAudTmp.(side(k)).(type{:});
      vf = [sA.data.f];
      vhl = [sA.data.hl];
      text(ktype,0,slab,'fontweight','bold','horizontalAlignment','center',...
	   'Interpreter','none');
      csYLab = {''};
      for kf=1:numel(vf)
	if isfinite(vhl(kf))
	  text(ktype,kf,sprintf('%4.1f dB',vhl(kf)),...
				'horizontalAlignment','center');
	else
	  text(ktype,kf,'-',...
	       'horizontalAlignment','center');
	end
	csYLab{end+1} = num2str(vf(kf));
      end
    end
    set(ax,'YLim',[-0.5 numel(vf)+0.5],...
	   'YTick',0:numel(vf),'YTickLabel',csYLab,...
	   'XLim',[0.2 3.8],'XTick',[2],...
	   'XTickLabel',{ldatestr},...
	   'YDir','reverse','Box','on','NextPlot','add');
    plot([0 4],[0.5 0.5],'k-');
    title(sprintf('%s - %s',[sAud.client_id,' ',sAud.id],csSide{k}),...
	  'Interpreter','none');
  end
  if(exist('printdlg')==2)
    printdlg(fh);
  else
    [FNAME, FPATH] = uiputfile('.pdf','Save audiogramm as...');
    print(fh,fullfile(FPATH,FNAME),'-dpdf');
  end
  close(fh);


function help_audprof_print
disp([' AUDPROF_PRINT - ',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  audprof.audprof.print( sAud );',char(10),'']);


function sAudProf = audprof_audprof_remove_from_db( sClientID, sAudProf )
  libconfigdb();
  cDB = audprof_db_load();
  sClient = audprof_db_client_get( cDB, sClientID );
  if ~isempty(sClient)
    sClient.audprofs = ...
	configdb.smap_rm( sClient.audprofs, ...
					sAudProf.id );
    cDB = audprof_db_client_add( cDB, sClient );
    audprof_db_save( cDB );
  end

function help_audprof_remove_from_db
disp([' AUDPROF_REMOVE_FROM_DB - ',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  sAudProf = audprof.audprof.remove_from_db( sClientID, sAudProf );',char(10),'']);


function cClient = audprof_client_audprof_add( cClient, sAudProf )
  libconfigdb();
  cClient.audprofs = ...
      configdb.smap_set( cClient.audprofs,...
			 sAudProf.id,...
			 sAudProf );

function help_client_audprof_add
disp([' CLIENT_AUDPROF_ADD - ',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  cClient = audprof.client_audprof.add( cClient, sAudProf );',char(10),'']);


function cClient = audprof_client_audprof_rm( cClient, sAudProf )
  libconfigdb();
  cClient.audprofs = ...
      configdb.smap_rm( cClient.audprofs,...
				      sAudProf.id );

function help_client_audprof_rm
disp([' CLIENT_AUDPROF_RM - ',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  cClient = audprof.client_audprof.rm( cClient, sAudProf );',char(10),'']);


function sCl = audprof_client_example()
% return example client
  cfdb = libconfigdb();
  sCl = audprof_client_new();
  sCl.lastname = 'test subject';
  sCl.firstname = '';
  sCl.birthday = '';
  sCl.id = 'TT123456';
  for csProfs={'flat40','flat50','slope'}
    prof=csProfs{:};
    sProf = audprof_audprof_example( prof );
    sCl.audprofs = ...
	cfdb.smap_set( sCl.audprofs, prof, sProf );
  end


function help_client_example
disp([' CLIENT_EXAMPLE - return example client',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  sCl = audprof.client_example();',char(10),'',char(10),'']);


function bExists = audprof_client_exists( sClient )
  cDB = audprof_db_load();
  bExists = ~isempty(strmatch( sClient, cDB(1,:), 'exact'));
  

function help_client_exists
disp([' CLIENT_EXISTS - ',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  bExists = audprof.client_exists( sClient );',char(10),'']);


function sClient = audprof_client_new( client_id )
% return a new (empty) client
  if nargin < 1
    client_id = '';
  end
  sClient = struct;
  sClient.audprofs = cell([2,0]);
  sClient.firstname = '';
  sClient.lastname = '';
  sClient.birthday = '1900-01-01';
  sClient.id = client_id;


function help_client_new
disp([' CLIENT_NEW - return a new (empty) client',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  sClient = audprof.client_new( client_id );',char(10),'',char(10),'']);


function audprof_db_audprof_add( audprof )
  cDB = audprof_db_load();
  if ~audprof_db_client_exists( audprof.client_id, cDB )
    error(['Client ID ''',audprof.client_id,''' not found in database.']);
  end
  cClient = audprof_db_client_get( cDB, audprof.client_id );
  cClient = audprof_client_audprof_add( cClient, audprof );
  cDB = audprof_db_client_add( cDB, cClient );
  audprof_db_save( cDB );

function help_db_audprof_add
disp([' DB_AUDPROF_ADD - ',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  audprof.db_audprof.add( audprof );',char(10),'']);


function audprof_db_audprof_rm( audprof )
  cDB = audprof_db_load();
  if ~audprof_db_client_exists( audprof.client_id, cDB )
    return;
  end
  cClient = audprof_db_client_get( cDB, audprof.client_id );
  cClient = audprof_client_audprof_rm( cClient, audprof );
  cDB = audprof_db_client_add( cDB, cClient );
  audprof_db_save( cDB );

function help_db_audprof_rm
disp([' DB_AUDPROF_RM - ',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  audprof.db_audprof.rm( audprof );',char(10),'']);


function cDB = audprof_db_client_add( cDB, cClient )
  libconfigdb();
  cDB = configdb.smap_set( cDB, cClient.id, cClient );

function help_db_client_add
disp([' DB_CLIENT_ADD - ',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  cDB = audprof.db_client_add( cDB, cClient );',char(10),'']);


function b = audprof_db_client_exists( s, cDB )
  cfdb = libconfigdb();
  if nargin < 2
    cDB = audprof_db_load();
  end
  [sC,idx] = cfdb.smap_get(cDB,s);
  b = ~isempty(idx);

function help_db_client_exists
disp([' DB_CLIENT_EXISTS - ',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  b = audprof.db_client_exists( s, cDB );',char(10),'']);


function sClient = audprof_db_client_get( cDB, sClientID )
  libconfigdb();
  sClient = configdb.smap_get(cDB,sClientID);


function help_db_client_get
disp([' DB_CLIENT_GET - ',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  sClient = audprof.db_client_get( cDB, sClientID );',char(10),'']);


function cDB = audprof_db_client_rm( cDB, cClient )
  libconfigdb();
  cDB = configdb.smap_rm( cDB, cClient.id );

function help_db_client_rm
disp([' DB_CLIENT_RM - ',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  cDB = audprof.db_client_rm( cDB, cClient );',char(10),'']);


function [csID,csIDmissing] = audprof_db_import_hfd_dir( sPath )
  d = [dir([sPath,'*.hfd']);
       dir([sPath,'*.HFD']);];
  libconfigdb();
  cDB = audprof_db_load();
  csID = {};
  csIDmissing = {};
  for k=1:numel(d)
    sClientID = upper(d(k).name(1:end-4));
    if audprof_db_client_exists(sClientID,cDB)
      csID{end+1} = sClientID;
      cClient = audprof_db_client_get( cDB, sClientID );
      sAud = audprof_audprof_new();
      if isfield(cClient,'audprofs') && ~isempty(cClient.audprofs)
	sAud = cClient.audprofs{2,end};
      end
      sAud = audprof_acalos_hfdfile_load( sAud, [sPath,d(k).name] );
      sAud.id = ['ACALOS from ',[sPath,d(k).name],' ',sAud.id];
      cClient = audprof_client_audprof_add( cClient, sAud );
      cDB = audprof_db_client_add( cDB, cClient );
    else
      csIDmissing{end+1} = sClientID;
    end
  end
  audprof_db_save( cDB );
  %csID = d;

function help_db_import_hfd_dir
disp([' DB_IMPORT_HFD_DIR - csID = d;',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  [csID,csIDmissing] = audprof.db_import_hfd_dir( sPath );',char(10),'',char(10),'']);


function cDB = audprof_db_load
% load the auditory profile database
  ;
  global audprof_warn_;
  if isempty(audprof_warn_)
    audprof_warn_ = true;
    if exist('audprofdb.mat')
      p = fileparts(which('audprofdb.mat'));
      if ~strcmp(p,pwd)
	warning(sprintf(['\nUsing auditory profile database from directory ' ...
			 '"%s"!\nCurrent directory is "%s".'],p,pwd));
      end
    end
  end
  cfdb = libconfigdb();
  cClients = cfdb.readfile('audprofdb.mat',...
			   'clients',cell([2,0]));
  sCl = audprof_client_example();
  cDB = cfdb.smap_set( cClients, sCl.id, sCl );


function help_db_load
disp([' DB_LOAD - load the auditory profile database',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  cDB = audprof.db_load();',char(10),'',char(10),'']);


function csList = audprof_db_prettyclientlist( cDB )
  csList = {};
  for k=1:size(cDB,2)
    scl = cDB{2,k};
    scl = merge_structs(scl,audprof_client_new());
    sName = cDB{1,k};
    if ~isempty(scl.lastname)
      sName = sprintf('%s  %s', sName, scl.lastname);
    end
    if ~isempty(scl.firstname)
      sName = sprintf('%s, %s', sName, scl.firstname);
    end
    if ~isempty(scl.birthday)
      sName = sprintf('%s (%s)', sName, scl.birthday);
    end
    csList{end+1} = sName;
  end


function help_db_prettyclientlist
disp([' DB_PRETTYCLIENTLIST - ',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  csList = audprof.db_prettyclientlist( cDB );',char(10),'']);


function audprof_db_save( cDB )
% save auditory profile database
  cfdb = libconfigdb();
  cDB = cfdb.smap_rm( cDB, 'TT123456' );
  cfdb.writefile('audprofdb.mat','clients',cDB);


function help_db_save
disp([' DB_SAVE - save auditory profile database',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  audprof.db_save( cDB );',char(10),'',char(10),'']);


function sAud = audprof_db_xlsimport( xlsName )
% load air conduction threshold from XLS file
% xlsName : name of XLS file
% cDB : database to insert auditory profiles (optional)
% if no database is provided, then a new database will be created
  warning('off','MATLAB:xlsread:Mode');
  [mNum,mTxt,mRaw] = xlsread( xlsName, 1, '', 'basic' );
  csHead = mRaw(1,2:end);
  csData = mRaw(2:end,2:end);
  csClient = mRaw(2:end,1);
  vValidF = unique(round([1000*2.^[-4:4],750*2.^[0:4]]));
  vValidS = 'lr';
  csValidHead = {};
  vValidF_ = [];
  vValidS_ = '';
  for sSide=vValidS
    for fFreq=vValidF
      csValidHead{end+1} = sprintf('%s%d',sSide,fFreq);
      vValidF_(end+1) = fFreq;
      vValidS_(end+1) = sSide;
    end
  end
  csHead = strrep(lower(csHead),' ','');
  vInd = [];
  for k=1:numel(csHead)
    idx = strmatch(csHead{k},csValidHead,'exact');
    if isempty(idx)
      error(sprintf('the field "%s" is not a valid field', ...
		    csHead{k}));
    end
    vInd(end+1) = idx;
  end
  vF = vValidF_(vInd);
  idx = struct();
  idx.l = find(vValidS_(vInd) == 'l');
  idx.r = find(vValidS_(vInd) == 'r');
  vFreq = struct;
  vFreq.l = vF(idx.l);
  vFreq.r = vF(idx.r);
  for kClient = 1:size(csData,1)
    sClient = upper(csClient{kClient});
    if ~audprof_client_exists( sClient )
      cClient = audprof_client_new(sClient);
      cDB = audprof_db_load();
      cDB = audprof_db_client_add( cDB, cClient );
      audprof_db_save( cDB );
    end
    sAud = audprof_audprof_new( sClient );
    sAud.id = xlsName;
    for sSide=vValidS
      vThr = cell2mat(csData(kClient,idx.(sSide)));
      vs = audprof_threshold_entry_add(audprof_threshold_new(),...
				       vFreq.(sSide),...
				       vThr);
      sAud.(sSide).htl_ac = vs;
    end
    %% Tobias N. suggests to remove the next line:
    sAud = audprof_audprof_cleanup( sAud );
    audprof_db_audprof_add( sAud );
  end
  %csValidHead
  %vValidF_
  %vValidS_

function help_db_xlsimport
disp([' DB_XLSIMPORT - load air conduction threshold from XLS file',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  sAud = audprof.db_xlsimport( xlsName );',char(10),'',char(10),' xlsName : name of XLS file',char(10),' cDB : database to insert auditory profiles (optional)',char(10),' if no database is provided, then a new database will be created',char(10),'']);


function s = audprof_threshold_cleanup( s )
  if ~isfield(s,'data')
    if isfield(s,'f') && isfield(s,'hl')
      tmp = s;
      s = audprof_threshold_new();
      s.data = tmp;
    else
      s = audprof_threshold_new();
    end
  end
  s.data = s.data(find(~isnan([s.data.hl])));


function help_threshold_cleanup
disp([' THRESHOLD_CLEANUP - ',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  s = audprof.threshold_cleanup( s );',char(10),'']);


function vs = audprof_threshold_entry_add( vs, f, hl )
% add one or more entries to a threshold structure (htl_ac, htl_bc, ucl)
% vs  : Threshold structure
% f   : frequency (scalar or vector)
% hl  : threshold (same dimension as f)
  ;
  if isempty(vs)
    vs = audprof_threshold_new();
  end
  for k=1:numel(f)
    idx = find([vs.data.f]==f(k));
    if ~isempty(idx)
      vs.data(idx(1)) = struct('f',f(k),'hl',hl(k));
    else
      vs.data(end+1) = struct('f',f(k),'hl',hl(k));
    end
  end
  [tmp,idx] = sort([vs.data.f]);
  vs.data = vs.data(idx);


function help_threshold_entry_add
disp([' THRESHOLD_ENTRY_ADD - add one or more entries to a threshold structure (htl_ac, htl_bc, ucl)',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  vs = audprof.threshold_entry_add( vs, f, hl );',char(10),'',char(10),' vs  : Threshold structure',char(10),' f   : frequency (scalar or vector)',char(10),' hl  : threshold (same dimension as f)',char(10),'']);


function vs = audprof_threshold_entry_addmissing( vs, f, hl )
% add one or more entries to a threshold structure (htl_ac, htl_bc, ucl)
% vs  : Threshold structure
% f   : frequency (scalar or vector)
% hl  : threshold (same dimension as f)
  ;
  if isempty(vs)
    vs = audprof_threshold_new();
  end
  for k=1:numel(f)
    idx = find([vs.data.f]==f(k));
    if isempty(idx)
      vs.data(end+1) = struct('f',f(k),'hl',hl(k));
    end
  end
  [tmp,idx] = sort([vs.data.f]);
  vs.data = vs.data(idx);


function help_threshold_entry_addmissing
disp([' THRESHOLD_ENTRY_ADDMISSING - add one or more entries to a threshold structure (htl_ac, htl_bc, ucl)',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  vs = audprof.threshold_entry_addmissing( vs, f, hl );',char(10),'',char(10),' vs  : Threshold structure',char(10),' f   : frequency (scalar or vector)',char(10),' hl  : threshold (same dimension as f)',char(10),'']);


function vs = audprof_threshold_fill_intersect( vs, f, hl )
% fill a threshold structure, remove entries not present in f
% vs  : Threshold structure
% f   : frequency (scalar or vector)
% hl  : threshold (same dimension as f)
  ;
  if isempty(vs)
    vs = audprof_threshold_new();
  end
  [tmp,idx] = intersect([vs.data.f],f);
  vs.data = vs.data(idx);
  for k=1:numel(f)
    idx = find([vs.data.f]==f(k));
    if isempty(idx)
      vs.data(end+1) = struct('f',f(k),'hl',hl(k));
    end
  end
  [tmp,idx] = sort([vs.data.f]);
  vs.data = vs.data(idx);


function help_threshold_fill_intersect
disp([' THRESHOLD_FILL_INTERSECT - fill a threshold structure, remove entries not present in f',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  vs = audprof.threshold_fill_intersect( vs, f, hl );',char(10),'',char(10),' vs  : Threshold structure',char(10),' f   : frequency (scalar or vector)',char(10),' hl  : threshold (same dimension as f)',char(10),'']);


function sTh = audprof_threshold_get( sAud, side, stype )
  sTh = audprof_threshold_new();
  if isfield(sAud,side) && isfield(sAud.(side),stype)
    sTh = sAud.(side).(stype);
  end

function help_threshold_get
disp([' THRESHOLD_GET - ',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  sTh = audprof.threshold_get( sAud, side, stype );',char(10),'']);


function b = audprof_threshold_isempty( s )
  b = isempty(s.data);

function help_threshold_isempty
disp([' THRESHOLD_ISEMPTY - ',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  b = audprof.threshold_isempty( s );',char(10),'']);


function s = audprof_threshold_new
  s = struct;
  s.id = '';
  s.client_id = '';
  s.datestr = datestr(now);
  s.data = struct('f',[],'hl',[]);
  s.data = s.data([]);

function help_threshold_new
disp([' THRESHOLD_NEW - ',char(10),'',char(10),' Usage:',char(10),'  audprof = libaudprof();',char(10),'  s = audprof.threshold_new();',char(10),'']);


