THE AUDITORY MODELING TOOLBOX

Applies to version: 1.6.0

View the help

Go to function

demo_baumgartner2014_blockprocessing
Demonstration of block processing with |baumgartner2014|

Program code:

%demo_baumgartner2014_blockprocessing Demonstration of block processing with BAUMGARTNER2014
%
%   DEMO_BAUMGARTNER2014_BLOCKPROCESSING demonstrates how to use the
%   block-processing framework to compute the localization errors over time
%   of a longer signal. The demo shows the baseline prediction (localization of
%   broadband sounds with own ears) for a listener from the AMT listener pool
%   for the median plane. The model used is the
%   sagittal-plane localization model (Baumgartner et al., 2014).
%
%   Figure 1: Time-domain signal and localization errors as a function of time.
%
%   See also: baumgartner2014 exp_baumgartner2014 baumgartner2014_virtualexp
%   localizationerror demo_baumgartner2014
%
%   References:
%     R. Baumgartner, P. Majdak, and B. Laback. Modeling sound-source
%     localization in sagittal planes for human listeners. The Journal of the
%     Acoustical Society of America, 136(2):791--802, 2014.
%     
%
%   Url: http://amtoolbox.org/amt-1.6.0/doc/demos/demo_baumgartner2014_blockprocessing.php


%   #Author: Fabian Brinkmann (2021)
%   #Author: Piotr Majdak (2024): Documenantation improvement for AMT 1.6.
%   #Author: Michael Mihocic (2024): Bug fixed when indexing scalar values in Octave. Fixes in figure legends to avoid warnings.

% This file is licensed unter the GNU General Public License (GPL) either
% version 3 of the license, or any later version as published by the Free Software
% Foundation. Details of the GPLv3 can be found in the AMT directory "licences" and
% at <https://www.gnu.org/licenses/gpl-3.0.html>.
% You can redistribute this file and/or modify it under the terms of the GPLv3.
% This file is distributed without any warranty; without even the implied warranty
% of merchantability or fitness for a particular purpose.


% Load HRIRs --------------------------------------------------------------
% HRIRs from the FABIAN head-related transfer function database
hrirname = 'FABIAN_HRIR_measured_HATO_0';
HRIRs = amt_load('engel2021',[hrirname,'.sofa']);


% use only median plane HRIRs for faster computation

id = HRIRs.SourcePosition(:,1) == 0 | HRIRs.SourcePosition(:,1) == 180;

HRIRs.Data.IR = HRIRs.Data.IR(id,:,:);

HRIRs.SourcePosition = HRIRs.SourcePosition(id,:);

HRIRs = SOFAupdateDimensions(HRIRs);



% Parameters for blockwise processing -------------------------------------

% audio content. We use noise  and low-pass filtered noise for this demo.

% In other cases this might be raw and processed audio files.

fsstim = 44100;

content_raw = randn(2^13, 1);

% 4th order Butterworth low-pass with 3 kHz cut-off frequency

sos = [1.26277170e-03  2.52554339e-03  1.26277170e-03 1.00000000e+00 -1.31605254e+00  4.46155785e-01
       1.00000000e+00  2.00000000e+00  1.00000000e+00 1.00000000e+00 -1.57087561e+00  7.26170328e-01];

if ~isoctave
    content_proc = sosfilt(sos, content_raw);
else
    [b, a] = butter(4, 3000 * 2/fsstim);
    content_proc = filter(b, a, content_raw);
end

N_block = 2048; % block length in samples

N_hop = 2048/2; % hop size in samples

window = true;  % apply Hann window to each block



% Parameters for baumgartner 2014 -----------------------------------------

errflag = 'QE_PE_EB';      % (currently only working with this errflag)

regular_flag = 'regular';

S = 0.76;

polsamp = -30:5:210;

fs = HRIRs.Data.SamplingRate;



%% Run baumgartner 2014 ---------------------------------------------------

% check equal length of audio content
if size(content_raw, 1) ~= size(content_proc, 1)
    error('audio contents must be of the same length')
end

% check equal number of channels of audio content
if size(content_raw, 2) ~= size(content_proc, 2)
    error('audio contents must have the same number of channels')
end

% check number of channels of audio content
if size(content_raw, 2) > 2
    error('audio contents must have 1 or 2 channels')
end

if ~isstruct(HRIRs)
    error('HRIRs must be a SOFA file')
end

if ~isfield(HRIRs, 'GLOBAL_SOFAConventions')
    error('HRIRs must be a SOFA file')
end

if ~strcmp(HRIRs.GLOBAL_SOFAConventions, 'SimpleFreeFieldHRIR')
    error('HRIRs must be a SOFA file of the ''SimpleFreeFieldHRIR'' convention')
end

if N_block > size(content_raw, 1)
    error('Block lengths exceeds length of audio content')
end

if N_block < size(HRIRs.Data.IR, 3)
    error('N_block is smaller than the HRIR length')
end

if N_block < N_hop
    warning('Hop size exceeds block length')
end


%% loop across chunks of audio content ------------------------------------

% get the window function
if window
    w = hann(N_block);
end

% audio content length and number of channels
N_samples = size(content_raw, 1);
N_channels = size(content_raw, 2);

% number of blocks
if N_block == N_samples
    N_blocks = 1;
else
    N_blocks = ceil((N_samples-N_block) / N_hop) + 1;
end

% zero pad audio content
N_samples = N_block + (N_blocks - 1) * N_hop;
content_raw(end+1:N_samples, :) = 0;
content_proc(end+1:N_samples, :) = 0;

% force two channel audio content for convenience
if N_channels == 1
    content_raw = [content_raw content_raw];
    content_proc = [content_proc content_proc];
end

% copy SOFA file
H_raw = HRIRs;
H_proc = HRIRs;

% get HRIRs
h = shiftdim(HRIRs.Data.IR, 2);

% zero pad for easy overlap and add fft convolution
N_HRIR = size(h, 1);
h(end+1:N_block+N_HRIR, :, :) = 0;


% time axis for blocks
t = nan(N_blocks, 1);

% arrays for overlap and add
ola_target = zeros(N_HRIR, size(h,2), size(h,3));
ola_template = ola_target;



N_wait = ceil(N_blocks/100);
amt_disp('Processing audio blocks');
for nn = 1:N_blocks


    amt_disp(['block nr: ', num2str(nn)] ,'volatile');
    % get current block of audio
    nn_start = (nn-1) * N_hop + 1;
    nn_end = nn_start + N_block - 1;
    raw = content_raw(nn_start:nn_end, :, :);
    proc = content_proc(nn_start:nn_end, :, :);

    % time of current block
    t(nn) = mean([nn_start nn_end]) / HRIRs.Data.SamplingRate;

    % zero pad for easy overlap and add fft convolution
    raw(end+1:N_block+N_HRIR, :) = 0;
    proc(end+1:N_block+N_HRIR, :) = 0;

    % convolve HRIRs with audio content
    raw_l = zeros(size(h(:,:,1)));
    raw_r = zeros(size(h(:,:,1)));
    proc_l = zeros(size(h(:,:,1)));
    proc_r = zeros(size(h(:,:,1)));
    for ii = 1:size(h, 2)
        raw_l(:,ii) = fft(raw(:,1)) .* fft(h(:,ii,1));
        raw_r(:,ii) = fft(raw(:,2)) .* fft(h(:,ii,2));
        proc_l(:,ii) = fft(proc(:,1)) .* fft(h(:,ii,1));
        proc_r(:,ii) = fft(proc(:,2)) .* fft(h(:,ii,2));
    end



    % join left and right channels
    if ~isoctave
        raw = cat(3, ifft(raw_l, 'symmetric'), ifft(raw_r, 'symmetric'));
        proc = cat(3, ifft(proc_l, 'symmetric'), ifft(proc_r, 'symmetric'));
    else
        raw = cat(3, ifft(raw_l), ifft(raw_r));
        proc = cat(3, ifft(proc_l), ifft(proc_r));
    end

    % overlap and add (checked and working :)
    raw(1:N_HRIR, :, :) = raw(1:N_HRIR, :, :) + ola_target;
    proc(1:N_HRIR, :, :) = proc(1:N_HRIR, :, :) + ola_template;

    % save overlap for next step
    ola_target = raw(N_block+1:end, :, :);
    ola_template = proc(N_block+1:end, :, :);

    % remove overlap from current block
    raw = raw(1:N_block, :, :);
    proc = proc(1:N_block, :, :);

    % window
    if window
        for jj=1:size(raw, 2)
            raw(:,jj, 1) = raw(:,jj,2) .* w;
            proc(:,jj, 1) = proc(:,jj,2) .* w;
            raw(:,jj, 2) = raw(:,jj,2) .* w;
            proc(:,jj, 2) = proc(:,jj,2) .* w;
        end
    end

    % put in SOFA objects
    H_raw.Data.IR = shiftdim(raw, 1);
    H_proc.Data.IR = shiftdim(proc, 1);

    % update dimensions of SOFA files
    if nn == 1
        H_raw = SOFAupdateDimensions(H_raw);
        H_proc = SOFAupdateDimensions(H_proc);
    end

    % run the loaclization model with raw and processed audio content
    [err_current, pred_current] = baumgartner2014(H_proc, HRIRs, errflag, 'S', S, 'polsamp', polsamp, 'fs', fs, 'fsstim', fsstim);
    [err_base, pred_base] = baumgartner2014(H_raw, HRIRs, errflag, 'S', S, 'polsamp', polsamp, 'fs', fs, 'fsstim', fsstim );

    % collect the output

        % allocate space
        if nn == 1
            clear err % important in Octave
            err.qe = nan(N_blocks, 1);
            err.pe = err.qe;
            err.pb = err.qe;

            pred.p = nan(size(pred_current.p,1), size(pred_current.p,2), N_blocks);
            pred.rang = pred_current.rang;
            pred.tang = pred_current.tang;

            err.qe_baseline = err.qe;
            err.pe_baseline = err.qe;
            err.pb_baseline = err.qe;
            pred.p_baseline = pred.p;
        end

        % save current results
        err.qe(nn) = err_current.qe;
        err.pe(nn) = err_current.pe;
        err.pb(nn) = err_current.pb;

        pred.p(:,:,nn) = pred_current.p;

        err.qe_baseline(nn) = err_base.qe;
        err.pe_baseline(nn) = err_base.pe;
        err.pb_baseline(nn) = err_base.pb;
        pred.p_baseline(:,:,nn) = pred_base.p;

end
amt_disp();
    % add time vector to the output
    err.t = t;
    pred.t = t;

    varargout{1} = err;
    varargout{2} = pred;





%% Plot results -----------------------------------------------------------



% time axis for audio content

t = (0:size(content_raw,1) - 1) / fs;



figure()



subplot(3,1,1)

plot(t, content_raw)

title 'Template audio content'

xlabel 'Time in seconds'

ylabel 'Amplitude'

xlim([0 t(end)])

if size(content_raw, 2) > 1

  if isoctave
    legend({'left', 'right'}) % avoid warning
  else
    legend('left', 'right', 'Location', 'SouthEast')
  end
end


subplot(3,1,2)

plot(err.t, err.pe, 'r.-')

hold on

plot(err.t, err.pe_baseline, 'k.-')

title 'Polar error'

xlabel 'Time in seconds'

ylabel({'Polar error' 'in degrees'})

xlim([0 t(end)])

ylim([0 1.1 * max([err.pe; err.pe_baseline])])



subplot(3,1,3)

plot(err.t, err.qe, 'r.-')

hold on

plot(err.t, err.qe_baseline, 'k.-')

if isoctave
  legend('Processing', 'Baseline') % avoid warning
else
  legend('Processing', 'Baseline', 'location', 'best')
end


title 'Quadrant error'

xlabel 'Time in seconds'

ylabel({'Quadrant error' 'in percent'})

xlim([0 t(end)])

ylim([0 1.1 * max([err.qe; err.qe_baseline])])