Commit 8d3002ef authored by Hark Braren's avatar Hark Braren
Browse files

update sofa interface functions to AES69-2020 (SOFA 2.0) - only HRTFs supported at the moment

parent a3be5a15
function varargout = ita_write_sofa_hrtf(varargin)
%ITA_WRITE_SOFA_HRTF - +++ Write HRTF as SOFA file +++
% This function writes a itaHRTF into a .sofa file following the SOFA
%
% Syntax:
% ita_write_sofa_hrtf(hrtfObj, fileName)
% See also:
% ita_read_sofa_hrtf, ita_write, itaHRTF
%
% Reference page in Help browser
% <a href="matlab:doc ita_write_sofa_hrtf">doc ita_write_sofa_hrtf</a>
% <ITA-Toolbox>
% This file is part of the ITA-Toolbox. Some rights reserved.
% You can find the license for this m-file in the license.txt file in the ITA-Toolbox folder.
% </ITA-Toolbox>
% Author: Hark Braren -- Email: hark.braren@akustik.rwth-aachen.de
% Created: 12-Apr-2021
%% Initialization and Input Parsing
if nargin == 0 % Return possible argument layout
result{1}.extension = '*.sofa';
result{1}.comment = 'SOFA Files (*.sofa)';
return;
end
sArgs = struct('pos1_data','itaHRTF','pos2_filename','char');
[data, fileName] = ita_parse_arguments(sArgs,varargin);
%% check if sofa is installed
if ~exist('SOFAstart.m','file')
error('SOFA not installed. Run ita_sofa_install');
end
SOFAstart
%% get sofa definitions and domain specific data
switch data.domain
case 'time'
Obj=SOFAgetConventions('SimpleFreeFieldHRIR');
%time data
Obj.Data.IR = zeros(data.nDirections, 2, data.nSamples);
Obj.Data.IR(:,1,:) = data.getEar('L').timeData.';
Obj.Data.IR(:,2,:) = data.getEar('R').timeData.';
%data info
Obj.Data.SamplingRate = data.samplingRate;
Obj.Data.SamplingRate_Units = 'hertz';
case 'freq'
Obj=SOFAgetConventions('SimpleFreeFieldHRTF');
%freq data
Obj.Data.Real = zeros(data.nDirections, 2, data.nBins);
Obj.Data.Imag = zeros(data.nDirections, 2, data.nBins);
Obj.Data.Real(:,1,:) = real(data.getEar('L').freqData).';
Obj.Data.Real(:,2,:) = real(data.getEar('R').freqData).';
Obj.Data.Imag(:,1,:) = imag(data.getEar('L').freqData).';
Obj.Data.Imag(:,2,:) = imag(data.getEar('R').freqData).';
%data info
Obj.N = data.freqVector;
Obj.N_LongName = 'frequency';
Obj.N_Units = 'hertz';
end
%% General Info
Obj.GLOBAL_ApplicationName = 'ITA-Toolbox';
Obj.GLOBAL_ApplicationVersion = num2str(ita_toolbox_version_number);
Obj.GLOBAL_DateCreated = date;
Obj.GLOBAL_AuthorContact = [ita_preferences('AuthorStr') ': ' ita_preferences('EmailStr')];
%% HRTF Info
%head position and orientation
Obj.ListenerPosition_Type = 'cartesian';
Obj.ListenerPosition_Units = 'metre';
Obj.ListenerPosition = [0 0 0];
Obj.ReceiverPosition_Type = 'cartesian';
Obj.ReceiverPosition_Units = 'metre';
Obj.ReceiverPosition = repmat(data.objectCoordinates.cart,1,1,data.nDirections);
Obj.ListenerView_Type = 'cartesian';
Obj.ListenerView_Units = 'metre';
Obj.ListenerView = data.objectViewVector.cart;
Obj.ListenerUp = data.objectUpVector.cart;
Obj.SourcePosition_Type = 'spherical';
Obj.SourcePosition_Units = 'degree,degree,metre';
Obj.SourcePosition = sofaSphericalCoordiantas(data.dirCoord);
Obj=SOFAupdateDimensions(Obj);
SOFAsave(fileName,Obj);
end
function sofaCoord = sofaSphericalCoordiantas(coords)
%transform from zenith to elevation angle
r = coords.r;
elevation = coords.theta_deg-90;
azimuth = coords.phi_deg;
sofaCoord = [azimuth,elevation,r];
end
This diff is collapsed.
......@@ -2,13 +2,13 @@ function result = ita_read_sofa(filename,varargin)
%ITA_READ_SOFA - +++ Short Description here +++
% This function ++++ FILL IN INFO HERE ++*
% <ITA-Toolbox>
% This file is part of the ITA-Toolbox. Some rights reserved.
% You can find the license for this m-file in the license.txt file in the ITA-Toolbox folder.
% This file is part of the ITA-Toolbox. Some rights reserved.
% You can find the license for this m-file in the license.txt file in the ITA-Toolbox folder.
% </ITA-Toolbox>
% Author: Jan Gerrit Richter -- Email: jan.richter@akustik.rwth-aachen.de
% Created: 30-Sep-2014
% Created: 30-Sep-2014
%% Return type of data this function can read
if nargin == 0
......@@ -36,18 +36,17 @@ end
if ~exist(filename,'file')
f=filesep;
filename=[SOFAdbPath f 'SOFA' f filename];
filename=[SOFAdbPath f 'SOFA' f filename];
end
handleSofa = SOFAload(filename);
numDirection = size(handleSofa.Data.IR,1);
audioObj = itaAudio(numDirection,1);
numDirection = handleSofa.API.M;
sourceCoordinates = itaCoordinates();
isHRTF = 0;
switch handleSofa.GLOBAL_SOFAConventions
case 'SimpleFreeFieldHRIR'
case {'SimpleFreeFieldHRIR', 'SimpleFreeFieldHRTF'}
% assuming HRTF is read in itaHRTF
isHRTF = 1;
if ~exist('itaHRTF','class')
......@@ -56,7 +55,7 @@ switch handleSofa.GLOBAL_SOFAConventions
sourceView = itaCoordinates(handleSofa.ListenerView,numDirection,1);
sourceUp = itaCoordinates(handleSofa.ListenerUp);
% source coordinates as channel coordinates
positionCoordinates = ita_sofa_getCoordinates(handleSofa,'channelCoordinateType','SourcePosition');
end
......@@ -73,74 +72,75 @@ end
%% if the object is an hrtf, load it directly with itaHRTF class
if isHRTF
% this would be faster, if the HRTF class would be used from the
% begining
if exist('itaHRTF','class')
audioObj = itaHRTF('sofa',filename);
end
else
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});
% this would be faster, if the HRTF class would be used from the
% begining
if exist('itaHRTF','class')
audioObj = itaHRTF('sofa',filename);
end
end
% set the data
for index = 1:numDirection
% first, set the sampling rate
if (strcmpi(handleSofa.Data.SamplingRate_Units,'hertz'))
audioObj(index).samplingRate = handleSofa.Data.SamplingRate;
else
ita_verbose_info('Error');
else
audioObj = itaAudio(numDirection,1);
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
audioObj(index).timeData = squeeze(handleSofa.Data.IR(index,:,:)).';
if isHRTF == 0
if (handleSofa.Data.Delay(index) ~= 0)
audioObj(index) = ita_time_shift(audioObj(index),handleSofa.Data.Delay(index),'samples');
% set the data
for index = 1:numDirection
% first, set the sampling rate
if (strcmpi(handleSofa.Data.SamplingRate_Units,'hertz'))
audioObj(index).samplingRate = handleSofa.Data.SamplingRate;
else
ita_verbose_info('Error');
end
% receiver coordinates
coordinates = ita_sofa_getCoordinates(handleSofa,'channelCoordinateType','ReceiverPosition');
audioObj(index).channelCoordinates = coordinates;
% source coordinates
audioObj(index).objectCoordinates = sourceCoordinates.n(index);
% source orientation
audioObj(index).objectUpVector = sourceUp;
audioObj(index).objectViewVector = sourceView.n(index);
else
audioObj(index).timeData = squeeze(handleSofa.Data.IR(index,:,:)).';
if (sum(handleSofa.Data.Delay) ~= 0)
audioObj(index) = ita_time_shift(audioObj(index),handleSofa.Data.Delay,'samples');
if isHRTF == 0
if (handleSofa.Data.Delay(index) ~= 0)
audioObj(index) = ita_time_shift(audioObj(index),handleSofa.Data.Delay(index),'samples');
end
% receiver coordinates
coordinates = ita_sofa_getCoordinates(handleSofa,'channelCoordinateType','ReceiverPosition');
audioObj(index).channelCoordinates = coordinates;
% source coordinates
audioObj(index).objectCoordinates = sourceCoordinates.n(index);
% source orientation
audioObj(index).objectUpVector = sourceUp;
audioObj(index).objectViewVector = sourceView.n(index);
else
if (sum(handleSofa.Data.Delay) ~= 0)
audioObj(index) = ita_time_shift(audioObj(index),handleSofa.Data.Delay,'samples');
end
audioObj(index).channelCoordinates = merge(positionCoordinates.n(index),positionCoordinates.n(index));
% source coordinates
audioObj(index).objectCoordinates = sourceCoordinates;
% source orientation
audioObj(index).objectUpVector = sourceUp;
audioObj(index).objectViewVector = sourceView;
end
% comment
audioObj(index).comment = sprintf('Imported from SOFA file: %s',filename);
% user data
audioObj(index).userData = userData;
audioObj(index).channelCoordinates = merge(positionCoordinates.n(index),positionCoordinates.n(index));
% source coordinates
audioObj(index).objectCoordinates = sourceCoordinates;
% source orientation
audioObj(index).objectUpVector = sourceUp;
audioObj(index).objectViewVector = sourceView;
end
% comment
audioObj(index).comment = sprintf('Imported from SOFA file: %s',filename);
% user data
audioObj(index).userData = userData;
end
end
result = audioObj;
result = audioObj;
end
\ No newline at end of file
function result = ita_write_sofa(varargin)
%ITA_WRITE_SOFA - +++ Writes HRTF to Sofa Format +++
% This function is used to save itaHRTF to SOFA
%
%ITA_WRITE_SOFA - +++ Writes itaObject to Sofa Format +++
% This function is currently used to save itaHRTF to SOFA format.
% Updated to SOFA Version 2.0 -- AES69-2020 (SOFA 2.0)
% -- www.sofaconventions.org
%
% Syntax:
% ita_write_sofa(hrtfObj,fileName,options)
%
% Options (default):
% 'dataType' (HRTF) : sets the data type. currently only HRTF
% supported
% userData : a struct with userDataFields
% options:
% 'dataType' ('') : for later usage to allow spcification of dataryoe
% when not clear from itaClass
%
% Example:
% audioObjOut = ita_write_sofa(hrtfObj,'testHRTF.sofa')
%
% See also:
% ita_toolbox_gui, ita_read, ita_write, ita_generate
% ita_toolbox_gui, ita_read, ita_write, ita_generate, ita_write_sofa_hrtf
%
% Reference page in Help browser
% <a href="matlab:doc ita_write_sofa">doc ita_write_sofa</a>
......@@ -28,6 +28,8 @@ function result = ita_write_sofa(varargin)
% Author: Jan Gerrit Richter -- Email: jan.richter@akustik.rwth-aachen.de
% Created: 30-Sep-2014
% Update as container for different write function:
% Hark Braren -- hark.braren@akustik.rwth-aachen.de
if nargin == 0 % Return possible argument layout
......@@ -36,137 +38,40 @@ if nargin == 0 % Return possible argument layout
return;
end
sArgs = struct('pos1_data','itaAudio','pos2_filename','char','dataType','HRTF','userdata',[]);
sArgs = struct('pos1_data','itaAudio','pos2_filename','char','dataType','');
[data, filename, sArgs] = ita_parse_arguments(sArgs,varargin);
AuthorStr = ita_preferences('AuthorStr');
EmailStr = ita_preferences('EmailStr');
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'};
%% check if sofa is installed
if ~exist('SOFAstart.m','file')
error('SOFA not installed. Run ita_sofa_install');
end
%%
if isempty(sArgs,dataType)
%when not stated explicitly -> derive from data type
switch class(data)
case 'itaHRTF'
sArgs.dataType = 'HRTF';
otherwise
error('Unable to determine Sofa type from datatyoe. Check if write function is implemented')
end
end
%%
switch(sArgs.dataType)
case 'HRTF'
sofaObj = SOFAgetConventions('SimpleFreeFieldHRIR');
sofaObj = createSofaHRTF(sofaObj,data,userDataFields); % userdatafields are generated from sofaObj.userdata
sofaObj = ita_sofa_setCoordinates(sofaObj,data,'channelCoordinateType','SourcePosition');
if ~isempty(sArgs.userdata) % user data are replaced by the struct
fNames = fieldnames(sArgs.userdata);
for idxFN = 1:numel(fNames)
sofaObj.(fNames{idxFN}) = sArgs.userdata.(fNames{idxFN});
end
end
ita_write_sofa_hrtf(data,filename);
% case 'Directivity'
% sofaObj = SOFAgetConventions('GeneralTF');
% sofaObj = createSofaDirectivity(sofaObj,data,userDataFields);
% sofaObj = ita_sofa_setCoordinates(sofaObj,data,'channelCoordinateType','ReceiverPosition');
% case 'SingleRoomDRIR'
% sofaObj = SOFAgetConventions('SingleRoomDRIR');
% sofaObj = createSofaDRIR(sofaObj,data,userDataFields);
% sofaObj = ita_sofa_setCoordinates(sofaObj,data,'channelCoordinateType','ReceiverPosition');
otherwise
error('ITA_WRITE_SOFA: Only HRTF Type is defined');
end
sofaObj.GLOBAL_ApplicationName = 'ITA-Toolbox';
sofaObj.GLOBAL_AuthorContact = sprintf('%s (%s)',AuthorStr,EmailStr);
sofaObj.GLOBAL_Comment = data.comment;
sofaObj.GLOBAL_DataType = 'Directivity';
SOFAupdateDimensions(sofaObj);
SOFAsave(filename,sofaObj);
result = 1;
end
function sofaObj = createSofaHRTF(sofaObj,data,userDataFields)
if ~isa(data,'itaHRTF')
error('ITA_WRITE_SOFA: Only itaHRTF Type supported for HRTF data type');
end
leftData = data.getEar('L').timeData.';
rightData = data.getEar('R').timeData.';
sofaObj.Data.IR = zeros(size(leftData,1),1,size(leftData,2));
sofaObj.Data.IR(:,1,:) = leftData; % irs.left is [N M], data.IR must be [M R N]
sofaObj.Data.IR(:,2,:) = rightData;
sofaObj.Data.SamplingRate = data.samplingRate;
userData = data.userData;
for index = 1:length(userDataFields)
if isfield(userData,userDataFields{index})
sofaObj.(userDataFields{index}) = userData.(userDataFields{index});
end
end
% two channels are needed
if ~isempty(data.objectCoordinates.cart)
sofaObj.ReceiverPosition = data.objectCoordinates.cart;
end
% if ~isempty(data.objectUpVector.cart) % is not working with one or
% two channels
% sofaObj.ReceiverUp = data.objectUpVector.cart;
% end
% if ~isempty(data.objectViewVector.cart)
% sofaObj.ReceiverView = data.objectViewVector.cart;
% end
end
function sofaObj = createSofaDirectivity(sofaObj,data,userDataFields)
% set the main data
freqData = data.freqData.';
sofaObj.Data.Real = zeros(1,size(freqData,1),size(freqData,2));
sofaObj.Data.Imag = zeros(1,size(freqData,1),size(freqData,2));
sofaObj.Data.Real(1,:,:) = real(freqData);
sofaObj.Data.Imag(1,:,:) = imag(freqData);
sofaObj.Data.SamplingRate = data.samplingRate;
if ~isempty(data.objectCoordinates.cart)
sofaObj.SourcePosition = data.objectCoordinates.cart;
else
sofaObj.SourcePosition = [0 0 0];
end
sofaObj.SourcePosition_Type = 'cartesian';
sofaObj.SourcePosition_units = 'meter';
if ~isempty(data.objectUpVector.cart)
sofaObj.SourceUp = data.objectUpVector.cart;
end
if ~isempty(data.objectViewVector.cart)
sofaObj.SourceView = data.objectViewVector.cart;
end
% all useless userdata
userData = data.userData;
for index = 1:length(userDataFields)
if isfield(userData,userDataFields{index})
sofaObj.(userDataFields{index}) = userData.(userDataFields{index});
end
end
end
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment