Commit 4e1548d8 authored by rbo's avatar rbo Committed by Marco Berzborn

some fixes... coming soon

parent afeef577
classdef itaHRTF < itaAudio
%ITAHRTF - class to deal with HRTFs
%
% Examples:
% hrtf = itaHRTF('sofa','TU-Berlin_QU_KEMAR_anechoic_radius_1m.sofa')
%
% These objects can be used like itaAudios and helps to find HRTF angles
% quickly. In addition different methods are implemented to evaluate
% binaural parameters and interpolate the data set.
%
% itaHRTF Properties:
% dirCoord Measured directions
% EarSide Ear side ('L' left or 'R' right) of each channel
% TF_type [HRTF DTF Recording]
% sphereType [ring cap sphere undefined]
%
% resAzimuth resolution in azimuth (only equiangular)
% resElevation resolution in elevation (only equiangular)
%
% rangeAzimuth min. and max. angle in azimuth
% rangeElevation min. and max. angle in elevation
%
% nPointsAzimuth number of directions in azimuth
% nPointsElevation number of directions in elevation
%
% nPoints total number of directions
%
% itaHRTF Methods (find & select directions):
% HRTFfind = findnearestHRTF(varargin)
% HRTFdir = direction(idxCoord)
% thetaUni = theta_Unique
% phiUni = phi_Unique
% slice = sphericalSlice(dirID,dir_deg)
% HRTF_left = getEar(earSide)
%
% itaHRTF Methods (play):
% play_gui(stimulus)
%
% itaHRTF Methods (store):
% audioHRTF = itaHRTF2itaAudio
% writeDAFFFile(filePath)
%
% itaHRTF Methods (binaural parameter):
% ITD = ITD(varargin)
% t0 = meanTimeDelay(varargin)
% ILD = ILD(varargin)
%
% itaHRTF Methods (manipulation):
% DTF = calcDTF
% HRTF_int = interp(varargin)
%
% itaHRTF Methods (plot):
% plot_ITD(varargin)
% plot_freqSlice(varargin)
%
% See also:
% itaAudio, test_rbo_postprocessing_HRTF_arc_CropDiv
%
% Reference page in Help browser
% <a href="matlab:doc itaHRTF">doc itaHRTF</a>
% <ITA-Toolbox>
% This file is part of the application HRTF_class for the ITA-Toolbox. All rights reserved.
% You can find the license for this m-file in the application folder.
% </ITA-Toolbox>
% Author: Ramona Bomhardt -- Email: rbo@akustik.rwth-aachen.de
% Created: 10-Jul-2014
properties (Access = private)
mCoordSave = [];
mChNames = [];
mDirCoord = itaCoordinates;
mEarSide = [];
mTF_type = 'HRTF';
mSphereType = 'undefined';
end
properties (Dependent = true, Hidden = false)
dirCoord = itaCoordinates;
EarSide = [];
TF_type = 'HRTF';
sphereType = 'undefined';
resAzimuth = 5;
resElevation = 5;
rangeAzimuth = [0 359];
rangeElevation = [0 180];
nPointsAzimuth = 72;
nPointsElevation= 37;
nPoints = [];
phi_Offset = zeros(37,1);
end
properties (Dependent = true, Hidden = true)
end
properties (Dependent = true, SetAccess = private)
openDAFF2itaHRTF;
itaAudio2itaHRTF;
init;
hdf2itaHRTF;
sofa2itaHRTF;
nDirections = [];
end
methods % Special functions that implement operations that are usually performed only on instances of the class
%% Input
function this = itaHRTF(varargin)
this = this@itaAudio();
if nargin >1
% itaAudio input
TF_types = this.propertiesTF_type;
for iTF = 1:numel(TF_types)
if ~isempty(find(strcmpi(varargin, TF_types{iTF})==1, 1))
this.itaAudio2itaHRTF = varargin{find(strcmpi(varargin, TF_types{iTF})==1)-1};
this.TF_type = TF_types(iTF);
end
end
% init
if nargin == 4
this.init = varargin;
end
% openDaff input
if ~isempty(find(strcmpi(varargin,'Daff')==1, 1))
this.openDAFF2itaHRTF = varargin{find(strcmpi(varargin,'Daff')==1)+1};
end
% hdf5 input
if ~isempty(find(strcmpi(varargin,'hdf5')==1, 1))
this.hdf2itaHRTF = varargin{find(strcmpi(varargin,'hdf5')==1)+1};
end
% sofa input
if ~isempty(find(strcmpi(varargin,'SOFA')==1, 1))
this.sofa2itaHRTF = varargin{find(strcmpi(varargin,'SOFA')==1)+1};
end
elseif nargin == 1
if isa(varargin{1},'itaHRTF')
this = varargin{1};
elseif nargin ==1 && isstruct(varargin{1}) % only for loading
obj = varargin{1};
this.data = obj.data;
this.signalType = 'energy';
% additional itaHRTF data
if datenum(2014,7,5)<obj.dateCreated, objFNsaved = this.propertiesSaved;
else objFNsaved = this.oldPropertiesSaved;
end
objFNload = this.propertiesLoad;
for i1 = 1:numel(objFNload)
this.(objFNload{i1}) = obj.(objFNsaved{i1});
end
% saving itaCoordinates in itaHRTF does not work at the
% moment
this.dirCoord.sph = this.mCoordSave;
% saving channelNames in itaHRTF does not work at the
% moment
for iCh = 1:this.dimensions
this.channelNames{iCh} = this.mChNames(iCh,:);
end
elseif isa(varargin{1},'itaAudio')
this.itaAudio2itaHRTF = varargin{1};
end
end
end
%% ......................GET.......................................
function nDirections = get.nDirections(this)
[~,idxDim] = unique([this.channelCoordinates.phi_deg this.channelCoordinates.theta_deg] ,'rows');
nDirections = numel(idxDim);
end
function dirCoord = get.dirCoord(this)
dirCoord = this.channelCoordinates.n(1:2:this.dimensions);
end
function EarSide = get.EarSide(this)
EarSide = this.mEarSide;
if numel(this.mEarSide)~=this.dimensions
EarSide = repmat(['L'; 'R'],this.dirCoord.nPoints, 1);
end
end
function TF_type = get.TF_type(this)
TF_type = this.mTF_type; end
function sphereType = get.sphereType(this)
% aktuell wird noch nicht erkannt, wenn die theta Winkel
% kontinuierlich ansteigen. Dann gibt es keinen Bruch...
numPhi = numel(this.phi_Unique);
numTheta = numel(this.theta_Unique);
deltaPhi_deg = 360/numPhi;
deltaTheta_deg = 180/numTheta;
gradPhi_deg = gradient(rad2deg(this.phi_Unique)) ;
gradTheta_deg = gradient(rad2deg(this.theta_Unique));
tmpPhi = round(deltaPhi_deg-gradPhi_deg);
tmpTheta = round(deltaTheta_deg-gradTheta_deg);
if sum(tmpPhi)==0 && sum(tmpTheta)==0 && sum(gradTheta_deg)==180
sphereType = 'full';
elseif sum(tmpPhi)==0 && numel(tmpTheta)==1 && tmpTheta(1)==180
sphereType = 'ring';
elseif sum(tmpPhi)==0 && sum(gradTheta_deg)<180
sphereType = 'cap';
else
sphereType = 'undefined';
end
end
function resAzi = get.resAzimuth(this)
resAzi = round(median(diff(rad2deg(this.phi_Unique))));
end
function resElevation = get.resElevation(this)
resElevation = round(median(diff(rad2deg(this.theta_Unique))));
end
function nPointsAzi = get.nPointsAzimuth(this)
nPointsAzi = numel(this.phi_Unique);
end
function nPointsEle = get.nPointsElevation(this)
nPointsEle = numel(this.theta_Unique);
end
function rangeAzi = get.rangeAzimuth(this)
rangeAzi = uint16([min(rad2deg(this.phi_Unique)) max(rad2deg(this.phi_Unique))]);
end
function rangeEle = get.rangeElevation(this)
rangeEle = uint16([min(rad2deg(this.theta_Unique)) max(rad2deg(this.theta_Unique))]);
end
function phi_Offset = get.phi_Offset(this)
thetaU = this.theta_Unique;
phi_Offset = zeros(numel(thetaU),1);
for idxT = 1:numel(thetaU)
phi_Offset(idxT,1) = test_rbo_azimuthOffset0(this.sphericalSlice('theta_deg',rad2deg(thetaU(idxT))));
end
end
%% ..............SET PRIVAT........................................
function this = set.itaAudio2itaHRTF(this,HRTF)
if isa(HRTF,'itaAudio'),
% Multi instance?
if numel(HRTF)>1,
if numel(HRTF)>1000 % takes a while
ita_verbose_info(' A lot of data ...please wait... don''t use itaAudio multi instances for the next time!', 0);
end
coordinates = HRTF(1).channelCoordinates;
if (coordinates.nPoints == 2) & (sum(isnan(coordinates.sph)) < numel(coordinates.sph))
ita_verbose_info('Found NaNs in the coordinates. I will copy existing coordinates');
for index = 1:length(HRTF)
coordinates = HRTF(index).channelCoordinates;
coordinates.sph = repmat(coordinates.sph(1,:),2,1);
HRTF(index).channelCoordinates = coordinates;
end
end
HRTFc = HRTF.merge;
else HRTFc = HRTF;
end
% coordinates available?
if isnan(HRTFc.channelCoordinates.cart)
error('itaHRTF:Def', ' No channelCoordinates available')
end
coord = HRTFc.channelCoordinates;
% find the corresponding left and right channel
pairs = zeros(coord.nPoints/2,2);
if coord.nPoints>10000 % takes a while
ita_verbose_info([num2str(coord.nPoints) ' Points has to be sorted ...please wait...'], 0);
end
counter = 1;
thetaPhi = round([coord.theta_deg coord.phi_deg]*10)/10;
deletedChannel = 0;
for i1 = 1:coord.nPoints
coordCurrent = thetaPhi(i1,:);
if isempty(find(pairs(:) == i1, 1)) % only if the corresponding channel is not found
% find corresponding channel
coordComp = thetaPhi([1:i1-1 i1+1:coord.nPoints],:);
diffCoord = bsxfun(@minus,coordCurrent,coordComp)== zeros(size(coordComp));
idxCoord = find(diffCoord(:,1).*diffCoord(:,2) ==1);
if length(idxCoord) > 1
% deletedChannel = deletedChannel + length(idxCoord) -1;
idxCoord = idxCoord(1);
end
% store the corresponding channel
pairs(counter,1) = i1;
if idxCoord <i1
pairs(counter,2) = idxCoord;
else
pairs(counter,2) = idxCoord+1;
end
counter = counter+1;
end
% break if all corresponding channels are found
if sum(pairs(:))== sum(1:coord.nPoints),break
end
end
% ........................................................
% split data in right and left channel
idxLeft = pairs(:,1); % odd number
idxRight = pairs(:,2); % even number
numNewChannels = length(pairs)*2;
this.data = zeros(HRTFc.nSamples, numNewChannels);
this.data(:,1:2:numNewChannels) = HRTFc.timeData(:,idxLeft);
this.data(:,2:2:numNewChannels) = HRTFc.timeData(:,idxRight);
this.domain = 'time';
pairsT = pairs';
this.channelCoordinates = HRTFc.channelCoordinates.n(pairsT(:));
this.mEarSide = repmat(['L'; 'R'],numNewChannels/2, 1);
this.samplingRate = HRTFc.samplingRate;
% store coordinates
this.mDirCoord = this.channelCoordinates.n(1:2:numNewChannels);
this.signalType = 'energy';
% channelnames coordinates
this.channelNames = ita_sprintf('%s ( %2.0f, %2.0f)',...
this.mEarSide ,...
this.channelCoordinates.theta_deg, this.channelCoordinates.phi_deg );
end
end
function this = set.openDAFF2itaHRTF( this, daff_file_path )
try_daff_old_version = false;
% First try new version (v17)
try
handleDaff = DAFFv17( 'open', daff_file_path );
props = DAFFv17( 'getProperties', handleDaff);
counter = 1;
data = zeros(props.filterLength,props.numRecords*2,'double' ) ;
coordDaff = zeros(props.numRecords,2) ;
for iDir = 1:props.numRecords
data(:,[counter counter+1]) = DAFFv17( 'getRecordByIndex', handleDaff,iDir )';
coordDaff(iDir,:) = DAFFv17( 'getRecordCoords', handleDaff, 'data', iDir )';
counter= counter+2;
end
catch
disp( 'Could not read DAFF file right away, falling back to old version and retrying ...' );
try_daff_old_version = true;
end
if try_daff_old_version
% Old version (v15)
handleDaff = DAFFv15( 'open',daff_file_path);
props = DAFFv15( 'getProperties', handleDaff);
counter = 1;
data = zeros(props.filterLength,props.numRecords*2,'double' ) ;
coordDaff = zeros(props.numRecords,2) ;
for iDir = 1:props.numRecords
data(:,[counter counter+1]) = DAFFv15( 'getRecordByIndex', handleDaff,iDir )';
coordDaff(iDir,:) = DAFFv15( 'getRecordCoords', handleDaff, 'data', iDir )';
counter= counter+2;
end
end
% Proceed (version independent)
phiM = coordDaff(:,1)*pi/180;
%phiM = mod(coordDaff(:,1),360)*pi/180;
%if ~isempty(find(0<coordDaff(:,2),1,'first'))
thetaM = coordDaff(:,2)*pi/180;
%thetaM = mod(180-(coordDaff(:,2)+90),180)*pi/180;
%else
% thetaM = coordDaff(:,2)*pi/180;
%end
radius = ones(props.numRecords,1);
chCoord = itaCoordinates;
chCoord.sph = ones(size(data,2),3);
chCoord.phi(1:2:2*props.numRecords) = phiM;
chCoord.phi(2:2:2*props.numRecords) = phiM;
chCoord.theta(1:2:2*props.numRecords) = thetaM;
chCoord.theta(2:2:2*props.numRecords) = thetaM;
this.data = data;
this.mDirCoord = itaCoordinates([radius thetaM phiM],'sph');
this.channelCoordinates = chCoord;
this.mEarSide = repmat(['L'; 'R'],props.numRecords, 1);
this.signalType = 'energy';
% channelnames coordinates
this.channelNames = ita_sprintf('%s ( %2.0f, \\theta= %2.0f)',...
this.mEarSide , this.channelCoordinates.theta_deg, this.channelCoordinates.phi_deg);
end
function this = set.init(this,var)
% TO DO !!!!!!!!!!!!!!!!!!!!!!!!!!!
% Make it nicer and combine it with itaAudio2itaHRTF!!!
% TO DO !!!!!!!!!!!!!!!!!!!!!!
coord = var{find(strcmp(var,'dirCoord')==1)+1};
this.domain = 'time';
nSamples = var{find(strcmp(var,'nSamples')==1)+1};
this.data = zeros(nSamples ,coord.nPoints*2);
this.channelCoordinates.sph(1:2:coord.nPoints*2,:) = coord.sph;
this.channelCoordinates.sph(2:2:coord.nPoints*2,:) = coord.sph;
this.mEarSide = repmat(['L'; 'R'],coord.nPoints, 1);
this.signalType = 'energy';
% channelnames coordinates
this.channelNames = ita_sprintf('%s ( %2.0f, %2.0f)',...
this.mEarSide , ...
this.channelCoordinates.theta_deg,this.channelCoordinates.phi_deg );
end
function this = set.hdf2itaHRTF(this,pathHDF5)
handleHDF5 = itaHDF5(pathHDF5);
names = fieldnames(handleHDF5);
HRTF = handleHDF5.(names{4});
dataHDF5 = HRTF.get_time;
data = zeros(size(dataHDF5,1),HRTF.coordinates.nPoints*2);
data(:,1:2:HRTF.coordinates.nPoints*2) = dataHDF5(:,:,1);
data(:,2:2:HRTF.coordinates.nPoints*2) = dataHDF5(:,:,2);
chCoord = itaCoordinates;
chCoord.sph = ones(HRTF.coordinates.nPoints*2,3);
chCoord.phi(1:2:2*HRTF.size_time(2)) = HRTF.coordinates.phi;
chCoord.phi(2:2:2*HRTF.size_time(2)) = HRTF.coordinates.phi;
chCoord.theta(1:2:2*HRTF.size_time(2)) = HRTF.coordinates.theta;
chCoord.theta(2:2:2*HRTF.size_time(2)) = HRTF.coordinates.theta;
radius = ones(HRTF.coordinates.nPoints,1);
this.data = data;
this.mDirCoord = itaCoordinates([radius HRTF.coordinates.theta HRTF.coordinates.phi],'sph');
this.channelCoordinates = chCoord;
this.mEarSide = repmat(['L'; 'R'],HRTF.size_time(2), 1);
this.signalType = 'energy';
% channelnames coordinates
this.channelNames = ita_sprintf('%s ( %2.0f, %2.0f)',...
this.mEarSide , ...
this.channelCoordinates.theta_deg, this.channelCoordinates.phi_deg);
end
function this = set.sofa2itaHRTF(this,pathFile)
if ~exist(pathFile,'file')
f=filesep;
pathFile=[SOFAdbPath f 'SOFA' f pathFile];
end
handleSofa = SOFAload(pathFile);
% get the number of measurement positions
numPositions = length(handleSofa.SourcePosition);
% data
% the data is saved as positions x channel x filterdata
data = zeros(size(handleSofa.Data.IR,3),numPositions*2);
data(:,1:2:numPositions*2) = squeeze(handleSofa.Data.IR(:,1,:)).';
data(:,2:2:numPositions*2) = squeeze(handleSofa.Data.IR(:,2,:)).';
% coordinates
coordinates = ita_sofa_getCoordinates(handleSofa,'channelCoordinateType','SourcePosition');
% duplicate the coordinates for both channels
channelCoordinates = itaCoordinates(numPositions*2);
channelCoordinates.x(1:2:numPositions*2) = coordinates.x;
channelCoordinates.x(2:2:numPositions*2) = coordinates.x;
channelCoordinates.y(1:2:numPositions*2) = coordinates.y;
channelCoordinates.y(2:2:numPositions*2) = coordinates.y;
channelCoordinates.z(1:2:numPositions*2) = coordinates.z;
channelCoordinates.z(2:2:numPositions*2) = coordinates.z;
% added view and up vector
this.objectViewVector = itaCoordinates(handleSofa.ListenerView);
this.objectUpVector = itaCoordinates(handleSofa.ListenerUp);
this.objectCoordinates = itaCoordinates(handleSofa.ListenerPosition);
warning('ITA_HRTF: Sofa Up and View vectors are ignored');
this.data = data;
this.channelCoordinates = channelCoordinates;
this.mDirCoord = coordinates;
this.mEarSide = repmat(['L'; 'R'],numPositions, 1);
this.signalType = 'energy';
this.channelNames = ita_sprintf('%s ( %2.0f, %2.0f)',...
this.mEarSide ,...
this.channelCoordinates.theta_deg, this.channelCoordinates.phi_deg );
%% user data
userDataFields = {'GLOBAL_Conventions','GLOBAL_Version','GLOBAL_SOFAConventions','GLOBAL_SOFAConventionsVersion' ...
,'GLOBAL_APIName','GLOBAL_APIVersion','GLOBAL_ApplicationName','GLOBAL_ApplicationVersion','GLOBAL_AuthorContact' ...
,'GLOBAL_Comment','GLOBAL_DataType','GLOBAL_History','GLOBAL_License','GLOBAL_Organization','GLOBAL_References' ...
,'GLOBAL_RoomType','GLOBAL_Origin','GLOBAL_DateCreated','GLOBAL_DateModified','GLOBAL_Title','GLOBAL_DatabaseName' ...
,'GLOBAL_RoomDescription','GLOBAL_ListenerShortName','API','ListenerPosition','ListenerPosition_Type','ListenerPosition_Units'...
,'EmitterPosition','EmitterPosition_Type','EmitterPosition_Units','RoomCornerA','RoomCornerA_Type','RoomCornerA_Units' ...
,'RoomCornerB','RoomCornerB_Type','RoomCornerB_Units','','','','','','',''};
for index = 1:length(userDataFields)
if isfield(handleSofa,userDataFields{index})
userData.(userDataFields{index}) = handleSofa.(userDataFields{index});
end
end
this.userData = userData;
end
%% .......................SET......................................
function this = set.dirCoord(this,dirCoord)
if isa(dirCoord,'itaCoordinates')
this.mDirCoord = dirCoord;
this.channelCoordinates.sph(1:2:end,:) = dirCoord.sph;
this.channelCoordinates.sph(2:2:end,:) = dirCoord.sph;
end
end
function this = set.EarSide(this,Side)
if sum(uint16(Side) == uint16('L') | uint16(Side) == uint16('R')) ==numel(Side)
this.mEarSide = Side;
end
end
function this = set.TF_type(this,type)
TF_types = this.propertiesTF_type;
if sum(strcmpi(type, TF_types))==1
this.mTF_type = TF_types{strcmpi(type, TF_types)};
end
end
%% ......................FUNCTIONS.................................
%% Functions of this class
function HRTFout = findnearestHRTF(this,varargin)
if nargin ==2
coordC = varargin{1};
if isa(coordC, 'itaCoordinates') && this.dirCoord.nPoints~=0
coordC.r = ones(coordC.nPoints,1)*mean(this.dirCoord.r); % use the existing radius
else
error('itaHRTF:Def', ' Input must be itaCoordinates or HRTF has no coordinates.')
end
else % rbo mode (theta,phi)
thetaC = deg2rad(varargin{1});
phiC = deg2rad(varargin{2});
r = ones(numel(phiC)*numel(thetaC),1)*mean(this.mDirCoord.r);
if numel(thetaC)~=1 && numel(phiC)==1,
phiC = ones(numel(thetaC),1)*phiC;
if size(thetaC,2)>1,
thetaC = thetaC';
end
elseif numel(thetaC)==1 && numel(phiC)~=1,
thetaC = ones(numel(phiC),1)*thetaC;
if size(phiC,2)>1,
phiC = phiC';
end
end
coordC = itaCoordinates([r thetaC phiC],'sph');
end
idxCoord = this.dirCoord.findnearest(coordC);
[~, I] = unique(idxCoord);
idxCoordUnique = idxCoord(I);
% idxCoordUnique = unique(idxCoord,'stable');
if numel(idxCoord)~= numel(idxCoordUnique)
ita_verbose_info('Multiple coordinates are neglected!', 0);
end
if sum(this.EarSide == 'R') ~= sum(this.EarSide == 'L') % only one ear is available
ita_verbose_info('You use only one Ear! Conversion to itaAudio.', 0);
idxCoord = this.channelCoordinates.findnearest(coordC);
[~, I] = unique(idxCoord);
idxCoordUnique = idxCoord(I);
HRTFout = this.ch(idxCoordUnique).itaHRTF2itaAudio;
else
HRTFout = this.direction(idxCoordUnique);
end
%HRTFout = this.direction(idxCoord);
end
function obj = direction(this, idxCoord)
idxDir = zeros(numel(idxCoord)*2,1);
idxDir(1:2:numel(idxCoord)*2,:) = 2*idxCoord-1;
idxDir(idxDir==0)=1;
idxDir(2:2:numel(idxCoord)*2) = idxDir(1:2:numel(idxCoord)*2,:)+1;
hrtfTMP = this.ch(idxDir);
hrtfTMP.channelCoordinates = this.channelCoordinates.n(idxDir);
hrtfTMP.EarSide = this.EarSide(idxDir);
obj = itaHRTF(hrtfTMP);
end
function thetaUni = theta_Unique(this,varargin)
thetaUni = unique(this.dirCoord.theta);
if nargin == 2
thetaUni = unique(this.dirCoord.theta,'stable');
end
end
function phiUni = phi_Unique(this,varargin)
phiUni = unique(this.dirCoord.phi);
if nargin == 2
phiUni = unique(this.dirCoord.phi,'stable');
end
end
function thetaUni = theta_UniqueDeg(this,varargin)
thetaUni = rad2deg(theta_Unique(this,varargin));
end
function phiUni = phi_UniqueDeg(this,varargin)
phiUni = rad2deg(phi_Unique(this,varargin));
end
function slice = sphericalSlice(this,dirID,dir_deg)
% dir in degree
% dirID [phi, theta]
phiU = rad2deg(this.phi_Unique);
thetaU = rad2deg(this.theta_Unique);
switch dirID
case {'phi_deg', 'p'}
slice = this.findnearestHRTF(thetaU,dir_deg);
case {'theta_deg', 't'}
slice = this.findnearestHRTF(dir_deg,phiU);
end
end
function slice = ss(this,dirID,dir_deg)
slice = this.sphericalSlice(dirID,dir_deg);
end
function HRTFout = getEar(this,earSide)
switch earSide
case 'L',
HRTFout = this.ch(this.EarSide == 'L');
HRTFout.mEarSide = repmat('L',HRTFout.nChannels,1);
case 'R',
HRTFout = this.ch(this.EarSide == 'R');
HRTFout.mEarSide = repmat('R',HRTFout.nChannels,1);
end