-
Achilles Kappis authoredAchilles Kappis authored
virtMicGeo.m 8.60 KiB
%% Calculate virtual microphone positions for specific geometries
% --------------------------------------------------
% Author: Achilles Kappis
% e-mail: axilleaz@protonmail.com
%
% Date: 18/09/2024 (DD/MM/YYYY)
%
% Copyright: MIT
% --------------------------------------------------
% Functionality: Calculate virtual microphone positions for specific
% geometries.
% --------------------------------------------------
% Input
%
% gType [char/string]: The type of the source geometry. At the moment the
% available options are "Single", "Array", "Cube" and
% "Dual". The last is an arrangement a distance apart
% that can be translated and rotated.
%
% geoDim [numeric] (Optional): The dimensions of the geometry. This is a
% vector with three elements holding the
% dimensions of the setup in each Cartesian
% direction. Based on the "gType" some or all
% of them are used. For "gType" set either as
% "Array" or "Dual", the first value is used
% as the length of the "Array" or distance
% between the two position in "Dual". When
% "gType" is "Single" this argument is
% ignored. [Default: ones(3, 1)].
%
% nSens [numeric] (Optional): This can be either a real integer value
% denoting the number of sensors in the array
% or a vector with three elements denoting
% the number of sensor along each Cartesian
% dimension. If a dimension is equal to zero
% the corresponding value is ignored. If this
% is a scalar value the used must ensure that
% it is correctly divisible over the non-zero
% dimensions of the geometry uniformly (see
% Notes for info). For the "Array" geometry,
% only the first value (if "nSens" is a
% vector) is used and for the "Single"
% geometry the argument is ignored.
% [Default: 10 * ones(3, 1)].
%
% xOff [numeric] (Optional): X-axis offset. [Default: 0].
%
% yOff [numeric] (Optional): Y-axis offset. [Default: 0].
%
% zOff [numeric] (Optional): Z-axis offset. [Default: 0].
%
% orient [numeric] (Optional): This is a real vector holding the rotation
% for the geometry in degrees, along each
% Cartesian axis. The rotations are performed
% clockwise. [Default: zeros(3, 1)].
%
% --------------------------------------------------
% Output
%
% vPos [numeric]: The matrix with the source coordinates in the Cartesian
% system. The matrix has dimensions Nx3, where N is the
% number of sources and the other dimension correspond to
% the x, y and z coodrinates.
%
% vPosMesh [numeric]: The matrix has dimensions nSens(1)xnSens(2)x3. It
% holds the x, y and z Cartesian coordinates for each
% position of the "Grid" geometry. If the geometry is
% otherwise not "Grid", this is an empty array.
%
% --------------------------------------------------
% Notes
%
% - Dependencies: rotMat3d(): To rotate the geometry.
%
% - If the number of sources are provided as a single integer, the user
% must make sure that they can be uniformly distributed in the non-zero
% dimensions of the geometry. For example, for the "Cube" geometry with
% two non-zero dimensions, the square root of the number of sensors must
% be an integer and for three non-zero dimensions its third root must be
% an integer. For the "Array" configuration, there is not an issue as the
% setup is one dimensional.
%
% --------------------------------------------------
function [vPos, vPosMesh] = virtMicGeo(gType, geoDim, nSens, xOff, yOff, zOff, orient)
% ====================================================
% Check for number of arguments
% ====================================================
narginchk(1, 7);
nargoutchk(0, 2);
% ====================================================
% Validate input arguments
% ====================================================
% Validate mandatory arguments
validateattributes(gType, {'char', 'string'}, {'scalartext', 'nonempty'}, mfilename, "Geometry type", 1);
validatestring(gType, ["Single", "Dual", "Array", "Cube"], mfilename, "Geometry type", 1);
% Validate optional arguments
if nargin > 1 && ~isempty(geoDim) && ~strcmpi(gType, "Single")
validateattributes(geoDim, "numeric", {'vector', 'real', 'nonnan', 'finite', 'nonempty', 'positive'}, mfilename, "Geometry dimensions", 2);
if numel(geoDim) > 3 || numel(geoDim) == 2
error("The dimensions argument must be either a scalar or a vector with three elements");
end
if isscalar(geoDim)
geoDim = geoDim * ones(3, 1);
end
else
geoDim = ones(3, 1);
end
if nargin > 2 && ~isempty(nSens) && sum(strcmpi(gType, ["Single", "Dual"])) == 0
validateattributes(nSens, "numeric", {'vector', 'real', 'nonempty', 'nonnan', 'nonnegative', 'integer', 'finite'}, mfilename, "Number of sensors in the geometry", 3);
if numel(nSens) > 3 || numel(nSens) == 2
error("The number of sensors must be either a scalar or a vector with three elements");
end
if isscalar(nSens) && strcmpi(gType, "Cube")
if mod(nthroot(nSens, sum(geoDim ~= 0)), 1) ~= 0
error("The number of sources is cannot be divided uniformly over the non-zero dimensions");
else
nSens = nthroot(nSens, sum(geoDim ~= 0)) * double(geoDim ~= 0);
end
end
elseif strcmpi(gType, "Dual")
nSens = [2, 0, 0];
else
nSens = 10 * ones(3, 1);
end
if nargin > 3 && ~isempty(xOff)
validateattributes(xOff, "numeric", {'scalar', 'real', 'nonnan', 'finite'}, mfilename, "X-offset", 4);
else
xOff = 0;
end
if nargin > 4 && ~isempty(yOff)
validateattributes(yOff, "numeric", {'scalar', 'real', 'nonnan', 'finite'}, mfilename, "Y-offset", 5);
else
yOff = 0;
end
if nargin > 5 && ~isempty(zOff)
validateattributes(zOff, "numeric", {'scalar', 'real', 'nonnan', 'finite'}, mfilename, "Z-offset", 6);
else
zOff = 0;
end
if nargin > 6 && ~isempty(orient) && ~strcmpi(gType, "Single")
validateattributes(orient, {'numeric'}, {'vector', 'nonempty', 'nonnan', 'finite', 'real', 'numel', 3}, mfilename, "Geometry rotation around the Cartesian axes", 7);
else
orient = zeros(3, 1);
end
% ====================================================
% Calculate virtual microphone positions
% ====================================================
switch lower(gType)
case "single"
% Create a single point at offset coordinates and return
vPos = zeros(1, 3);
case "dual"
vPos = [-geoDim(1)/2, 0, 0; ...
geoDim(1)/2, 0, 0];
case "array"
vPos = linspace(-geoDim(1)/2, geoDim(1)/2, nSens(1));
vPos = [vPos(:), zeros(numel(vPos), 2)];
case "cube"
% Make we don't get empty arrays when dimensions are 0
if nSens(1) == 0
x = 0;
else
x = linspace(-geoDim(1)/2, geoDim(1)/2, nSens(1));
end
if nSens(2) == 0
y = 0;
else
y = linspace(-geoDim(2)/2, geoDim(2)/2, nSens(2));
end
if nSens(3) == 0
z = 0;
else
z = linspace(-geoDim(3)/2, geoDim(3)/2, nSens(3));
end
[x, y, z] = meshgrid(x, y, z);
vPos = [x(:), y(:), z(:)];
otherwise
error("Oops... something went wrong here... not known geometry...!!!");
end
% Rotate
vPos = vPos * rotMat3d(-orient(1), -orient(2), -orient(3), "Degs");
% Translate
vPos = vPos + [xOff, yOff, zOff];
% For "Cube" geometry provide the coordinates in a "mesh format"
if nargout > 1 && strcmpi(gType, "Cube")
vPosMesh = cat(3, x, y, z);
elseif nargout > 1
vPosMesh = [];
end
end