function [out, par] = amt_emuexp(command,par,varargin)
%AMT_EMUEXP Emulates psychoacoustic experiments
% Usage: par = amt_emuexp(command, par);
% [out, par] = amt_emuexp('run', par);
% [out, par] = amt_emuexp('run', par, 'plot');
%
% Input parameters:
% command : One of the following commands. 'expinit' intializes the
% general experiment parameters. 'signalinit' intializes
% the signal generator creating model inputs. 'modelinit'
% intializes model parameters. 'decisioninit' intializes
% the parameters of the decision stage in the experiment.
% Finally, 'run' runs the experiment and lets the model decide.
% par : Structure of the experimental parameters used by AMT_EMUEXP.
% Set to [] on the first call (when par is not set up yet).
%
% Output parameters:
% par : Structure containing all parameters
% out : Vector with the experiment output. out(:,1) is the average threshold of the
% experimental variable. out(:,2) is the standard deviation of the variable
% across all runs. out(:,3:end) provides the individual experimental variables
% used in each trial.
%
% par = AMT_EMUEXP(init_command, par) initializes the various parts of the
% psychoacoustic experiment to be emulated depending on init_command.
%
% out = AMT_EMUEXP('run', par) runs the experiment defined
% by the structure par and outputs the experimental result in out.
%
% [out, par] = AMT_EMUEXP('run', par) runs the experiment and outputs
% more details on the experiment parameters in par.
%
% [out, par] = AMT_EMUEXP('run', par, 'plot') runs the experiment and
% plots the experiment progress.
%
% Initialization
% --------------
%
% Experiment
% *********
%
% par=AMT_EMUEXP('expinit', [], exp) initilizes the experiment with key-value pairs provided
% in a cell array exp. The following pairs are required:
%
% 'intnum',intnum number of intervals in a trial,
% e.g. 3 sets up a 3-afc experiment.
%
% 'rule',down_up vector with down-up-rule
% e.g. [2 1] sets up a 2-down, 1-up experiment.
%
% 'expvarstart',expvarstart step size of the experimental variable at the
% beginning of the experiment.
%
% 'expvarsteprule',factor_turns vector with a factor and number of turn arounds.
% The factor affects the step size of the experimental
% variable after the number of turn arounds, e.g. [0.5 2]
% multiplies the stepsize by 0.5 after two turn arounds.
%
% 'stepmin',min_threshturn vector with minimal step size and number of turn arounds
% after reaching that minimal step size for the threshold
% calculation. E.g. [1 8] means that after reaching the
% step size 1, the experiment will continue for
% another 8 reversals before terminating.
%
% Signal generator
% ***************
%
% par=AMT_EMUEXP('signalinit', par, sig) intializes the signal generator creating
% signals for the model with key-value pairs provided in the cell array sig. The signal
% generator is called with those parameters in each trial of the experiment.
% Up to 15 input parameters are supported. One of inputs must be inttyp: In each
% experimental interval, this input will be replaced by 'target' or 'reference'
% depending on the interval type. One of the inputs must be expvar: In each trial,
% this input will be replaced by the value of the experimental variable. The
% following pairs are required:
%
% 'name',name string which defines the name of the signal
% generation
%
% 'inputX',inputX input parameter X needed for the signal generator
%
% Model called in each interval
% ****************************
%
% par=AMT_EMUEXP('modelinit', par, mod) initializes the model called in each interval with
% the key-value pairs provided in mod. Up to 10 input parameters are supported.
% One of the inputs must contain the keyword expsignal.
% This keyword is replaced in the run routine with the output of
% the signal generation function:
%
% 'name',name string which defines the name of the model
% function
%
% 'inputX',intputX input parameter X needed by the model
%
% 'outputs',outputs indicies of used model outputs for the decision
% e.g. [1 2 6]: output 1,2 and 6 used
%
% Decision stage called in each trial
% **********************************
%
% par=AMT_EMUEXP('decisioninit', par, dec) initializes the decision stage of the experiment
% with key-value pairs provided in dec. Up to 10 input parameters are supported.
% All inputs containing the keyword modelout are
% replaced with the outputs of the model function during an
% experimental run. Therefore the number of inputs with the keyword
% modelout must be equal to number of outputs defined in
% modelinit. An output of the modelfunction contains a cell with an
% entry for each interval. E.g. param1{1} contains the first output of
% the model function of the first interval and param3{2} contains the
% third output of the modelfunction of the second interval. Therefore the
% decision function must be implemented so that the inputs of the decision
% function are cells with entries for each interval.
% Following parameters are required:
%
% 'name',name name of the decision fuction
% 'inputX',intputX input parameter X needed by the decision function
%
% Running the experiment
% ----------------------
%
% After the initialization, the experiment can be started by
% out = AMT_EMUEXP('run', par);. The threshold will be in out.
%
% See also: exp_breebaart2001, demo_breebaart2001
%
% Url: http://amtoolbox.org/amt-1.2.0/doc/core/amt_emuexp.php
% Copyright (C) 2009-2022 Piotr Majdak, Clara Hollomey, and the AMT team.
% This file is part of Auditory Modeling Toolbox (AMT) version 1.2.0
%
% This program is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program. If not, see <http://www.gnu.org/licenses/>.
% #Author: Martina Kreuzbichler
% turn warning off
warning('off','MATLAB:nargchk:deprecated')
switch command
case 'expinit'
definput.keyvals.intnum = [];
definput.keyvals.rule = [];
definput.keyvals.expvarstepstart = [];
definput.keyvals.expvarsteprule = [];
definput.keyvals.stepmin = [];
definput.keyvals.expvarstart = [];
definput.keyvals.interface = 'AMT';
definput.keyvals.fs = [];
definput.keyvals.directory = [];
if iscell(varargin{1}) && nargin == 3
[~,kvexp]=ltfatarghelper({},definput,varargin{:});
else
[~,kvexp]=ltfatarghelper({},definput,varargin);
end
% out = setstructfields(kvexp, par);
out = par;
out.exp = kvexp;
case 'modelinit'
definput.keyvals.name=[];
definput.keyvals.input1 = [];
definput.keyvals.input2 = [];
definput.keyvals.input3 = [];
definput.keyvals.input4 = [];
definput.keyvals.input5 = [];
definput.keyvals.input6 = [];
definput.keyvals.input7 = [];
definput.keyvals.input8 = [];
definput.keyvals.input9 = [];
definput.keyvals.input10 = [];
definput.keyvals.outputs = [];
if iscell(varargin{1}) && nargin == 3
[~,kvmodel]=ltfatarghelper({},definput,varargin{:});
else
[~,kvmodel]=ltfatarghelper({},definput,varargin);
end
% check what outputs of model are needed
outputnumber = nargout(kvmodel.name);
callmodelstring = [];
for outputcounter = 1:outputnumber
if any(kvmodel.outputs==outputcounter)
modelstring = ['modelout.par',num2str(outputcounter)];
% amt_disp(modelstring);
if isempty(callmodelstring)
callmodelstring = ['[' modelstring '{interval_num}'];
else
callmodelstring = [callmodelstring ',' modelstring '{interval_num}'];
end
else
callmodelstring = [callmodelstring ',~'];
end
end
modelinputs = struct2cell(kvmodel);
% delete name of model function & outputs
modelinputs = modelinputs(2:end-1);
% delete empty cells
modelinputs = modelinputs(~cellfun('isempty',modelinputs));
callmodelstring = [callmodelstring ']=' kvmodel.name '('];
for modelinputscounter = 1:length(modelinputs)
callmodelstring = [callmodelstring num2str(modelinputs{modelinputscounter}) ','];
end
callmodelstring(end:end+1) = ');';
out = par;
out.model = kvmodel;
out.callstrings.model = callmodelstring;
%TODO CALLMODELSTRING
case 'signalinit'
definput.keyvals.name= [];
definput.keyvals.input1 = [];
definput.keyvals.input2 = [];
definput.keyvals.input3 = [];
definput.keyvals.input4 = [];
definput.keyvals.input5 = [];
definput.keyvals.input6 = [];
definput.keyvals.input7 = [];
definput.keyvals.input8 = [];
definput.keyvals.input9 = [];
definput.keyvals.input10 = [];
definput.keyvals.input11 = [];
definput.keyvals.input12 = [];
definput.keyvals.input13 = [];
definput.keyvals.input14 = [];
definput.keyvals.input15 = [];
if iscell(varargin{1}) && nargin == 3
[~,kvsignal]=ltfatarghelper({},definput,varargin{:});
else
[~,kvsignal]=ltfatarghelper({},definput,varargin);
end
out = par;
out.signal = kvsignal;
case 'decisioninit'
definput.keyvals.name= [];
definput.keyvals.input1 = [];
definput.keyvals.input2 = [];
definput.keyvals.input3 = [];
definput.keyvals.input4 = [];
definput.keyvals.input5 = [];
definput.keyvals.input6 = [];
definput.keyvals.input7 = [];
definput.keyvals.input8 = [];
definput.keyvals.input9 = [];
definput.keyvals.input10 = [];
definput.commands.plot = {'no_plot','plot'};
if iscell(varargin{1}) && nargin == 3
[~,kvdecision]=ltfatarghelper({},definput,varargin{:});
else
[~,kvdecision]=ltfatarghelper({},definput,varargin);
end
out = par;
out.decision = kvdecision;
case 'run'
definput.flags.plot = {'no_plot','plot'};
[commands,~]=ltfatarghelper({},definput,varargin);
if strcmp(par.exp.interface,'ModelInitiative'),
csvwrite(fullfile(par.exp.directory,'a_priori.csv'),...
[par.model.input2, par.model.input3, par.model.input4]);
decision=par.decision;
save(fullfile(par.exp.directory,'decision_parameters.mat'),'decision');
delete(fullfile(par.exp.directory,'detector_out.csv'));
end
% find experimental variable and inttyp variable
sigparnames = fieldnames(par.signal);
for count = 1: length(sigparnames)
name = sigparnames(count);
if strcmp(getfield(par.signal, name{:}),'expvar')
experimentvar = name{:};
elseif strcmp(getfield(par.signal, name{:}),'inttyp')
inttypvar = name{:};
end
end
stepsize = par.exp.expvarstepstart;
truecounter = 0;
par.signal.(experimentvar) = par.exp.expvarstart;
expparvalue = [];
downturn = 0;
upturn = 1;
turncounter = 0;
lastturn = [];
checkmodelout = 0;
condition = 1;
wrongcounter = -1;
trialcounter = 1;
while condition
for interval_num=1:par.exp.intnum
if interval_num == 1
par.signal.(inttypvar) = 'target';
else
par.signal.(inttypvar) = 'reference';
end
signalinputs = struct2cell(par.signal);
% delete name of signal function
signalinputs = signalinputs(2:end);
% delete empty cells
signalinputs = signalinputs(~cellfun('isempty',signalinputs));
% call signalfunction
testsignal = feval(par.signal.name,signalinputs{:});
% call model or transfer files to the model
switch par.exp.interface
case 'AMT'
% find experimental signal variable
modelparnames = fieldnames(par.model);
for count = 1: length(modelparnames)
name = modelparnames(count);
if strcmp(getfield(par.model, name{:}),'expsignal')
experimentsignal = name{:};
break
end
end
par.model.(experimentsignal) = testsignal;
% call model
par.callstrings.model = strrep(par.callstrings.model,'expsignal', 'testsignal');
eval(par.callstrings.model);
case 'ModelInitiative'
audiowrite(fullfile(par.exp.directory,['interval_' num2str(interval_num) '.wav']), testsignal, par.exp.fs);
end
end % for each interval
switch par.exp.interface
case 'AMT'
% find model output variable, only at the first time
if checkmodelout == 0
modeloutvarcount = 1;
decisionparnames = fieldnames(par.decision);
for count = 1:length(decisionparnames)
name = decisionparnames(count);
if strcmp(getfield(par.decision, name{:}),'modelout')
modeloutvar{modeloutvarcount} = name{:};
modeloutvarcount = modeloutvarcount+1;
end
end
checkmodelout = 1;
end
%set model outputs to decsion inputs
for count = 1:modeloutvarcount-1
modeloutname = ['par', num2str(par.model.outputs(count))];
% amt_disp(modeloutname);
par.decision.(modeloutvar{count}) = modelout.(modeloutname);
end
decisioninputs = struct2cell(par.decision);
% delete name of model function & outputs
decisioninputs = decisioninputs(2:end);
% delete empty cells
decisioninputs = decisioninputs(~cellfun('isempty',decisioninputs));
% call decision
decision = feval(par.decision.name,decisioninputs{:});
case 'ModelInitiative'
amt_disp(['Trial #' num2str(trialcounter) ', ModelInitiative on ' par.exp.directory]);
while ~exist(fullfile(par.exp.directory,'detector_out.csv'),'file');
pause(.1);
end
fid=-1;
while fid==-1
fid=fopen(fullfile(par.exp.directory,'detector_out.csv'),'r');
end
fclose(fid);
decision=csvread(fullfile(par.exp.directory,'detector_out.csv'));
delete(fullfile(par.exp.directory,'detector_out.csv'));
end
% store expparvalue
expparvalue = [expparvalue par.signal.(experimentvar)];
% count reversals
% wrong answers are par.exp.rule(2)and no low point reversal
if decision ~= 1 && wrongcounter == par.exp.rule(2)-1 && downturn == 0
turncounter = turncounter + 1;
downturn = 1;
upturn = 0;
if stepsize == par.exp.stepmin(1)
lastturn = [lastturn par.signal.(experimentvar)];
end
% right answers are par.exp.rule(1)
% and no high point reversal
elseif decision == 1 && truecounter == par.exp.rule(1)-1 && upturn == 0
turncounter = turncounter + 1;
downturn = 0;
upturn = 1;
if stepsize == par.exp.stepmin(1)
lastturn = [lastturn par.signal.(experimentvar)];
end
end
% change stepsize after par.exp.expvarsteprule(2) reversals, if stepsize
% is not already par.exp.stepmin(1) dB
if turncounter == par.exp.expvarsteprule(2) && truecounter == 1 && ...
stepsize ~= par.exp.stepmin(1)
stepsize = stepsize * par.exp.expvarsteprule(1);
turncounter = 0;
end
if decision == 1
wrongcounter = 0;
truecounter = truecounter + 1;
else
truecounter = 0;
wrongcounter = wrongcounter +1;
end
% amount of right answers is par.exp.rule(1)
if truecounter == par.exp.rule(1)
par.signal.(experimentvar) = par.signal.(experimentvar) - stepsize;
truecounter = 0;
end
% amount of wrong answers is par.exp.rule(2)
if wrongcounter == par.exp.rule(2)
par.signal.(experimentvar) = par.signal.(experimentvar) + stepsize;
wrongcounter = 0;
end
% amount of reversals is par.stepmin(2)
if size(lastturn,2) == par.exp.stepmin(2)
threshold = median(lastturn);
threshstd = std(lastturn,1);
condition = 0;
out = [threshold threshstd expparvalue];
end
trialcounter = trialcounter+1;
end
%clear persistent variables
clear (par.decision.name);
if commands.do_plot
figure
for plotcounter = 1:(size(expparvalue,2)-1)
if expparvalue(plotcounter) < expparvalue(plotcounter+1)
style = 'or';
stem(plotcounter,(expparvalue(plotcounter)),'or',...
'LineStyle','none')
hold on
else
style = '+g';
stem(plotcounter,(expparvalue(plotcounter)),'+g',...
'LineStyle','none')
hold on
end
end
% special case last entry
if decision == 1
stem(plotcounter+1,(expparvalue(plotcounter+1)),'+g',...
'LineStyle','none')
else
stem(plotcounter+1,(expparvalue(plotcounter+1)),'or',...
'LineStyle','none')
end
title(['Threshold (Median): ' num2str(threshold) ...
'dB, Std: ' num2str(threshstd) 'dB'])
end
end