Skip to content
Snippets Groups Projects
Commit 38955c07 authored by Achilles Kappis's avatar Achilles Kappis
Browse files

Moved geometry creation functions into a functions folder for the MATLAB implementations

parent 27bf034d
No related branches found
No related tags found
No related merge requests found
%% Calculate positions for specific receiver geometries
% --------------------------------------------------
% Author: Achilles Kappis
% e-mail: axilleaz@protonmail.com
%
% Date: 10/03/2024 (DD/MM/YYYY)
%
% Copyright: MIT
% --------------------------------------------------
% Functionality: Calculate receiver positions for specific receiver
% geometries.
% --------------------------------------------------
% Input
%
% gType [char/string]: The type of the receiver geometry. At the moment the
% available options are "Single", "ULA", "UCA" and
% "Log".
%
% nSens [numeric] (Optional): The number of sensors. This value is
% ignored for the "Single" geometry.
% [Default: 4].
%
% elemDist [numeric] (Optional): The distance between the elements of
% the array configuration. For "Single"
% it is ignored, for "ULA" corresponds
% to the distance between successive
% elements, for "UCA" it corresponds to
% the radius of the circle on which the
% sensors are placed. [Default: 0.1].
%
% dmaElemDist [numeric] (Optional): The distance between the elements
% of the directional configurations.
% The available configurations are
% "Figure-of-Eight", "Triangle" and
% "Box", where the first comprises of
% two elements with their axis
% parallel to the y-axis, the
% "Triangle" has an addition element
% with positive z-coordinate forming
% an equilateral triangle and the
% "Box" configuration consists of
% three "Figure-of-Eights", one on
% each axis. If the given value is
% not positive or left empty the
% respective output variables are
% empty. [Default: 0.05].
%
% xOff [numeric] (Optional): An offset along the x-axis of the microphone
% configuration. [Default: 0].
%
% yOff [numeric] (Optional): An offset along the y-axis of the microphone
% configuration. [Default: 0].
%
% zOff [numeric] (Optional): An offset along the z-axis of the microphone
% configuration. [Default: 0].
%
% --------------------------------------------------
% Output
%
% omniPos [numeric]: This matrix contains the coordinates of the
% omnidirectional receivers and has dimensions Nx3,
% where N is the number of receivers and the other
% dimension corresponds to the x, y and z Cartesian
% coordinates.
%
% fig8Pos [numeric] (Optional): This matrix contains the coordinates of the
% Figure-of-Eight elements (two sensors that
% can be used to create a figure-of-eight
% pattern when combined as a first order
% DMA). The dimensions of the matrix are
% Nx3x2 where N is the number of sensors, the
% second dimension corresponds to the x, y
% and z Cartesian coordinates and the last
% dimension corresponds to the
% "Figure-of-Eight" elements. For the
% "Single" and "ULA" geometries, the elements
% are positioned so that their axis is
% parallel to the y-axis and for the "UCA"
% geometry, the elements axis passes through
% the origin of the geometry.
%
% triPos [numeric] (Optional): This matrix contains the coordinates of the
% "Triangle" geometry where in addition to the
% "figure-of-eight" elements, one more is
% placed in the middle of ther axis and higher
% in order to create an equilateral triangle.
% The matrix has dimensions Nx3x3 where N is
% the number of microphone positions, the
% second dimension corresponds to the x, y and
% z Cartesian coordinates and the third
% dimension to individual elements. For the
% "Single" and "ULA geometries, the axis of
% the "Figure-of-Eight" elements on the x-y
% plane is parallel to the y-axis. For the
% "UCA" geometry the axis passes through the
% origin of the geometry. For "Single" and
% "ULA", the first element is the one with
% positive y-coordinate and for "UCA" the
% furthest from the origin. For all
% configurations the element with postitive
% z-coordinate is the last.
%
% boxPos [numeric] (Optional): This matrix contains the coordinates of
% three "Figure-of-Eight" sensors (two sensors
% which can be combined as a first order DMA
% to create a figure-of-eight sensor). The
% dimensions of the matrix are Nx3x2x3 where N
% is the number of sensor positions, the
% second dimension corresponds to the x, y and
% z Cartesian coordinates. The third dimension
% corresponds to the elements of each
% figure-of-eight sensor with the first
% element being the one having a positive
% coordinate value. The last dimension
% corresponds to the figure-of-eight sensors
% with the first being parallel to the x-axis,
% the second to the y-axis and the third to
% the z-axis.
%
% box2DPos [numeric] (Optional): This matrix contains the coordinates of
% the elements of "boxPos" lying on the x-y
% plane (the z-axis elements are discarded).
% The dimensions of the matrix are Nx3x2x2.
%
% tetPos [numeric]: This matrix contains the positions of a normal
% tetrahedron, with four vertices (points) non-parallel
% to the Cartesian axes. The dimensions of the matrix are
% Nx3x4 where N is the number of measurement positions,
% the second dimension is the x, y, and z Cartesian
% coordinates and the last dimension corresponds to the
% elements of the configuration. The element indices are:
% 1) Positive x, y and z, 2) negative x, positive y,
% zero z, 3) negative x and y, zero z and 4) negative x,
% y and z.
%
% fig8Vec [numeric]: This is an Nx3 matrix containing the positions of
% the figure-of-eight elements like:
% Pos_1_Elem_1_x, Pos_1_Elem_2_y, Pos_1_Elem_1_z
% Pos_1_Elem_2_x, Pos_1_Elem_2_y, Pos_1_Elem_2_z
% Pos_2_Elem_1_x, Pos_2_Elem_2_y, Pos_2_Elem_1_z
% Pos_2_Elem_2_x, Pos_2_Elem_2_y, Pos_2_Elem_2_z
% . . .
% . . .
% . . .
% Pos_M_Elem_1_x, Pos_M_Elem_2_y, Pos_M_Elem_1_z
% Pos_M_Elem_2_x, Pos_M_Elem_2_y, Pos_M_Elem_2_z
%
% triVec [numeric]: This is an Nx3 matrix containing the positions of
% the triangle elements like:
% Pos_1_Elem_1_x, Pos_1_Elem_2_y, Pos_1_Elem_1_z
% Pos_1_Elem_2_x, Pos_1_Elem_2_y, Pos_1_Elem_2_z
% Pos_1_Elem_3_x, Pos_1_Elem_3_y, Pos_1_Elem_3_z
% Pos_1_Elem_1_x, Pos_1_Elem_2_y, Pos_1_Elem_1_z
% Pos_1_Elem_2_x, Pos_1_Elem_2_y, Pos_1_Elem_2_z
% Pos_1_Elem_3_x, Pos_1_Elem_3_y, Pos_1_Elem_3_z
% . . .
% . . .
% . . .
% Pos_M_Elem_1_x, Pos_M_Elem_2_y, Pos_M_Elem_1_z
% Pos_M_Elem_2_x, Pos_M_Elem_2_y, Pos_M_Elem_2_z
% Pos_M_Elem_3_x, Pos_M_Elem_3_y, Pos_M_Elem_3_z
%
% boxVec [numeric]: This is an Nx3 matrix containing the positions of
% the box configuration elements like:
% Pos_1_X_Elem_1_x, Pos_1_X_Elem_2_y, Pos_1_X_Elem_1_z
% Pos_1_X_Elem_2_x, Pos_1_X_Elem_2_y, Pos_1_X_Elem_2_z
% Pos_1_Y_Elem_1_x, Pos_1_Y_Elem_2_y, Pos_1_Y_Elem_1_z
% Pos_1_Y_Elem_2_x, Pos_1_Y_Elem_2_y, Pos_1_Y_Elem_2_z
% Pos_1_Z_Elem_1_x, Pos_1_Z_Elem_2_y, Pos_1_Z_Elem_1_z
% Pos_1_Z_Elem_2_x, Pos_1_Z_Elem_2_y, Pos_1_Z_Elem_2_z
% Pos_2_X_Elem_1_x, Pos_2_X_Elem_2_y, Pos_2_X_Elem_1_z
% Pos_2_X_Elem_2_x, Pos_2_X_Elem_2_y, Pos_2_X_Elem_2_z
% Pos_2_Y_Elem_1_x, Pos_2_Y_Elem_2_y, Pos_2_Y_Elem_1_z
% Pos_2_Y_Elem_2_x, Pos_2_Y_Elem_2_y, Pos_2_Y_Elem_2_z
% Pos_2_Z_Elem_1_x, Pos_2_Z_Elem_2_y, Pos_2_Z_Elem_1_z
% Pos_2_Z_Elem_2_x, Pos_2_Z_Elem_2_y, Pos_2_Z_Elem_2_z
% . . .
% . . .
% . . .
% Pos_N_X_Elem_1_x, Pos_N_X_Elem_2_y, Pos_N_X_Elem_1_z
% Pos_N_X_Elem_2_x, Pos_N_X_Elem_2_y, Pos_N_X_Elem_2_z
% Pos_N_Y_Elem_1_x, Pos_N_Y_Elem_2_y, Pos_N_Y_Elem_1_z
% Pos_N_Y_Elem_2_x, Pos_N_Y_Elem_2_y, Pos_N_Y_Elem_2_z
% Pos_N_Z_Elem_1_x, Pos_N_Z_Elem_2_y, Pos_N_Z_Elem_1_z
% Pos_N_Z_Elem_2_x, Pos_N_Z_Elem_2_y, Pos_N_Z_Elem_2_z
%
% box2DVec [numeric]: This is an Nx3 matrix containing the positions of
% the 2D box configuration elements like:
% Pos_1_X_Elem_1_x, Pos_1_X_Elem_2_y, Pos_1_X_Elem_1_z
% Pos_1_X_Elem_2_x, Pos_1_X_Elem_2_y, Pos_1_X_Elem_2_z
% Pos_1_Y_Elem_1_x, Pos_1_Y_Elem_2_y, Pos_1_Y_Elem_1_z
% Pos_1_Y_Elem_2_x, Pos_1_Y_Elem_2_y, Pos_1_Y_Elem_2_z
% Pos_2_X_Elem_1_x, Pos_2_X_Elem_2_y, Pos_2_X_Elem_1_z
% Pos_2_X_Elem_2_x, Pos_2_X_Elem_2_y, Pos_2_X_Elem_2_z
% Pos_2_Y_Elem_1_x, Pos_2_Y_Elem_2_y, Pos_2_Y_Elem_1_z
% Pos_2_Y_Elem_2_x, Pos_2_Y_Elem_2_y, Pos_2_Y_Elem_2_z
% . . .
% . . .
% . . .
% Pos_N_X_Elem_1_x, Pos_N_X_Elem_2_y, Pos_N_X_Elem_1_z
% Pos_N_X_Elem_2_x, Pos_N_X_Elem_2_y, Pos_N_X_Elem_2_z
% Pos_N_Y_Elem_1_x, Pos_N_Y_Elem_2_y, Pos_N_Y_Elem_1_z
% Pos_N_Y_Elem_2_x, Pos_N_Y_Elem_2_y, Pos_N_Y_Elem_2_z
%
% tetVec [numeric]: This is an Nx3 matrix containg the positions of the
% normal tetrahedron configuration like:
% Pos_1_Elem_1_x, Pos_1_Elem_1_y, Pos_1_Elem_1_z
% Pos_1_Elem_2_x, Pos_1_Elem_2_y, Pos_1_Elem_2_z
% Pos_1_Elem_3_x, Pos_1_Elem_3_y, Pos_1_Elem_3_z
% Pos_1_Elem_4_x, Pos_1_Elem_4_y, Pos_1_Elem_4_z
% . . .
% . . .
% . . .
% Pos_N_Elem_1_x, Pos_1_Elem_1_y, Pos_1_Elem_1_z
% Pos_N_Elem_2_x, Pos_1_Elem_2_y, Pos_1_Elem_2_z
% Pos_N_Elem_3_x, Pos_1_Elem_3_y, Pos_1_Elem_3_z
% Pos_N_Elem_4_x, Pos_1_Elem_4_y, Pos_1_Elem_4_z
%
% --------------------------------------------------
% Notes
%
% --------------------------------------------------
function [omniPos, fig8Pos, triPos, boxPos, box2DPos, tetPos, fig8Vec, triangleVec, boxVec, box2DVec, tetVec] = rcvGeo(gType, nSens, elemDist, dmaElemDist, xOff, yOff, zOff)
% ====================================================
% Check for number of arguments
% ====================================================
narginchk(1, 7);
nargoutchk(0, 11);
% ====================================================
% Validate input arguments
% ====================================================
% Validate mandatory arguments
validateattributes(gType, {'char', 'string'}, {'scalartext', 'nonempty'}, mfilename, "Geometry type", 1);
validatestring(gType, ["Single", "ULA", "UCA", "Log"], mfilename, "Geometry type", 1);
% Validate optional arguments
if nargin > 6 && ~isempty(zOff)
validateattributes(zOff, "numeric", {'scalar', 'real', 'nonnan', 'finite'}, mfilename, "Z offset of the microphone setup", 7);
else
zOff = 0;
end
if nargin > 5 && ~isempty(yOff)
validateattributes(yOff, "numeric", {'scalar', 'real', 'nonnan', 'finite'}, mfilename, "Y offset of the microphone setup", 6);
else
yOff = 0;
end
if nargin > 4 && ~isempty(xOff)
validateattributes(xOff, "numeric", {'scalar', 'real', 'nonnan', 'finite'}, mfilename, "X offset of the microphone setup", 5);
else
xOff = 0;
end
if nargin > 3 && ~isempty(dmaElemDist)
validateattributes(dmaElemDist, "numeric", {'scalar', 'real', 'nonnan', 'finite'}, mfilename, "Directional configurations inter-element distance", 4);
else
dmaElemDist = 0.05;
end
if nargin > 2 && ~isempty(elemDist)
validateattributes(elemDist, "numeric", {'scalar', 'real', 'positive', 'nonnan', 'finite'}, mfilename, "Array inter-element distance", 3);
else
elemDist = 0.1;
end
if nargin > 1 && ~isempty(nSens)
validateattributes(nSens, "numeric", {'scalar', 'positive', 'real', 'nonnan', 'finite'}, mfilename, "Number of sensors", 2);
else
nSens = 4;
end
% ====================================================
% Calculate omnidirectional sensor positions
% ====================================================
% Omni sensor coordinates
switch lower(gType)
case "single"
omniPos = [0, 0, 0];
case "ula"
omniPos = elemDist * ((nSens - 1)/2) * linspace(-1, 1, nSens);
omniPos = [omniPos; zeros(2, nSens)]';
case "uca"
angsOfRot = (0:1/nSens:(1 - 1/nSens)) * 2 * pi;
[x, y] = pol2cart(angsOfRot, elemDist);
omniPos = [x; y; zeros(size(x))].';
end
% ====================================================
% Calculate directional configurations sensor positions
% ====================================================
% Check whether we have to calculate the directional configuration sensor position
if dmaElemDist <= 0 || isempty(dmaElemDist)
fig8Pos = []; fig8Vec = [];
triPos = []; triangleVec = [];
boxPos = []; boxVec = [];
box2DPos = []; box2DVec = [];
tetPos = []; tetVec = [];
% Translate
if nargout > 0
omniPos = omniPos + [xOff, yOff, zOff];
end
return;
end
% Figure-of-Eight positions
if nargout > 1
switch lower(gType)
case {"single", "ula"}
fig8Pos = omniPos + [0, dmaElemDist/2, 0];
fig8Pos = cat(3, fig8Pos, omniPos - [0, dmaElemDist/2, 0]);
case "uca"
[x, y] = pol2cart(angsOfRot, elemDist + dmaElemDist/2);
fig8Pos = [x; y; zeros(size(x))].';
[x, y] = pol2cart(angsOfRot, elemDist - dmaElemDist/2);
fig8Pos = cat(3, fig8Pos, [x; y; zeros(size(x))].');
end
end
% Triangle positions
if nargout > 2
triPos = cat(3, fig8Pos, omniPos + [0, 0, dmaElemDist * sqrt(3)/2]); % Add an element to create an equilateral triangle
triPos = triPos - [0, 0, dmaElemDist * tand(30)/2]; % Move it down so that the array centre will be at the coordinates of the omni microphone
end
% Box positions
if nargout > 3
switch lower(gType)
case {"single", "ula"}
% X-axis figure-of-eight elements
tempBox = omniPos + [dmaElemDist/2, 0, 0];
tempBox = cat(3, tempBox, omniPos - [dmaElemDist/2, 0, 0]);
boxPos = cat(4, tempBox, fig8Pos);
case "uca"
% X-Y plane perpendicular to the radial direction
% figure-of-eight elements (the "X-axis figure-of-eight")
[x, y] = pol2cart(angsOfRot + deg2rad(90), dmaElemDist/2);
tempBox = [x; y; zeros(size(x))].';
[x, y] = pol2cart(angsOfRot - deg2rad(90), dmaElemDist/2);
tempBox = cat(3, tempBox, [x; y; zeros(size(x))].');
tempBox = omniPos + tempBox;
boxPos = cat(4, tempBox, fig8Pos);
end
% Z-axis figure-of-eight elements (common to all geometries)
tempBox = omniPos + [0, 0, dmaElemDist/2];
tempBox = cat(3, tempBox, omniPos - [0, 0, dmaElemDist/2]);
boxPos = cat(4, boxPos, tempBox);
end
% 2D box positions
if nargout > 4
box2DPos = boxPos; % Get all elements from box
box2DPos(:, :, :, 3:3:end) = []; % Get rid of the z-axis elements
end
% Normal tetrahedron positions
if nargout > 5
% Generate normal tetrahedron with lower face parallel to x-y plane
% and edge length sqrt(8/3) [from: https://en.wikipedia.org/wiki/Tetrahedron]
tempPos = [sqrt(8/9), 0, -1/3;...
-sqrt(2/9), sqrt(2/3), -1/3;...
-sqrt(2/9), -sqrt(2/3), -1/3;...
0, 0, 1];
% Set edge length equal to the given DMA inter-element distance
tempPos = dmaElemDist * tempPos/(vecnorm(tempPos(1, :) - tempPos(2, :)));
switch lower(gType)
case "ula"
% Rotate the tetrahedrals on the positive side of they-axis
rotIdx = sum(omniPos(:, 1) > 0); % Calculate how many tetrahedrals we have to rotate
% Go through the tetrahedrals
for measPosIdx = size(omniPos, 1):-1:1
% Calculate rotation angle
if measPosIdx > rotIdx
rot = 60;
else
rot = 0;
end
% Rotate and position
tetPos(measPosIdx, :, :) = omniPos(measPosIdx, :) + tempPos * rotMat3dDeg(0, 0, rot);
end
case "uca"
% Get the angle of the positions on the circle
az = cart2sph(omniPos(:, 1), omniPos(:, 2), omniPos(:, 3));
% Rotate and position tetrahedrals
for measPosIdx = numel(az):-1:1
tetPos(measPosIdx, :, :) = omniPos(measPosIdx, :) + tempPos * rotMat3dDeg(0, 0, -az(measPosIdx));
end
case "single"
tetPos = omniPos + tempPos * rotMat3dDeg(30, 30, 0);
end
if ~strcmpi(gType, "Single")
tetPos = permute(tetPos, [1, 3, 2]);
end
end
% ====================================================
% Translate geometries
% ====================================================
if nargout > 0
omniPos = omniPos + [xOff, yOff, zOff];
end
if nargout > 1
fig8Pos = fig8Pos + [xOff, yOff, zOff];
end
if nargout > 2
triPos = triPos + [xOff, yOff, zOff];
end
if nargout > 3
boxPos = boxPos + [xOff, yOff, zOff];
end
if nargout > 4
box2DPos = box2DPos + [xOff, yOff, zOff];
end
if nargout > 5
tetPos = tetPos + [xOff, yOff, zOff];
end
% Provide a Nx3 "Figure-of-Eight" matrix
if nargout > 6
fig8Vec = reshape(permute(fig8Pos, [3, 1, 2]), [], 3);
end
% Provide a Nx3 "Triangle" matrix
if nargout > 7
triangleVec = reshape(permute(triPos, [3, 1, 2]), [], 3);
end
% Provide a Nx3 "Box" matrix
if nargout > 8
boxVec = reshape(permute(boxPos, [3, 4, 1, 2]), [], 3);
end
% Provide a Nx3 "2DBox" matrix
if nargout > 9
box2DVec = reshape(permute(box2DPos, [3, 4, 1, 2]), [], 3);
end
% Provide a Nx3 Tetrahedron matrix
if nargout > 10
tetVec = reshape(permute(tetPos, [3, 1, 2]), [], 3);
end
end
\ No newline at end of file
%% Calculate positions for specific source geometries
% --------------------------------------------------
% Author: Achilles Kappis
% e-mail: axilleaz@protonmail.com
%
% Date: 24/02/2024 (DD/MM/YYYY)
%
% Copyright: MIT
% --------------------------------------------------
% Functionality: Calculate source positions for specific source geometries.
% --------------------------------------------------
% Input
%
% gType [char/string]: The type of the source geometry. At the moment the
% available options are "Single", "Causal",
% "Anti-Causal", "Side", "Diagonal", "Diffuse" and
% "Directional", "Circle".
%
% srcLen [numeric]: It is used for all but the "Diffuse" and "Circle"
% geometries. For the "Single" geometry it specifies
% the x coordinate of the source and for all other
% geometries the length of the source array.
%
% originDist [numeric]: For the "Diffuse" and "Circle" geometries, this is
% the radius of the sphere on whose surface the
% sources are placed. For the rest of the geometries,
% it specifies the distance between the origin and
% the centre of the source array.
%
% ang [numeric] (Optional): It is used for the creation of "Diagonal" and
% "Diffuse" geometries (for the rest is ignored
% and can even be set to []). For "Diagonal" is
% the angle for which the source array is rotated
% (clockwise on the x-y plane) and for the
% "Diffuse" field it is the angle between sources
% on the sphere. Currently, this is the ONLY way
% to create a "Diffuse" field and so it is
% necessary for this geometry. The value is in
% degrees.
% [Diagonal default: 45, Diffuse default: 10]
%
% nSrc [numeric] (Optional): Used for all but the "Diffuse" and
% "Single" geometries and dictates the
% number of sources that will comprise the
% source array. It is ignored if "Diffuse"
% or "Single" is chosen as the gType. For
% the "directional" configuration, it
% defines the number of neighbouring sources
% picked for the dominant sources.
% [Default: 5]
%
% azim [numeric] (Optional): Used for the directional source configuration.
% It is a vector with the azimuthal angles, in
% degrees, of the "central" dominant sources.
% [Default: 90]
%
% elev [numeric] (Optional): Used for the directional source configuration.
% It is a vector with the elevation angles of
% the "central" dominant sources. It must have
% the same number of elements as the "azim"
% parameter. [Default: 0]
%
% SNR [numeric] (Optional): Used for the directional source configuration.
% It scales the dominant and background source
% strengths so that the resulting SNR at the
% origin is equal to the given value.
% [Default: Inf]
%
% --------------------------------------------------
% Output
%
% sPos [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.
%
% Q [numeric] (Optional): The source strengths for the directional source
% configuration. For all other configurations this
% parameter is empty.
%
% domIdx [numeric] (Optional): The dominant source indices in the
% directional source configuration. This may
% be used to extract the Cartesian coordinates
% of the dominant sources as well as their
% strenghts.
%
%
% --------------------------------------------------
% Notes
%
% - The "Directional" source configuration creates a diffuse field
% configuration and from those sources, the ones that fall closer to the
% given azimuthal and elevation angles are picked to be the "dominant"
% ones.
% The "nSrc" parameter defines how many neighbouring sources will be
% used for the generation of the "directional" field. For example if one
% azimuthal and elevation angle is chosen and sourceLen is equal to 3,
% the source with azimuthal and elevation angles closer to the given ones
% will be chosen and two neighbouring (on the horizontal plane) will be
% chosen to act as "dominant" sources too. This allows for "distributed"
% sources to be used and decrease the coherency of a "directional"
% source.
%
% Dependencies: - ptsOnSphere(): For the calculation of the "Diffuse" and
% "Directional" geometry source positions.
% - rotMat3dDeg(): To rotate the source configurations.
%
% --------------------------------------------------
function [sPos, Q, domIdx] = srcGeo(gType, srcLen, originDist, ang, nSrc, azim, elev, SNR)
% ====================================================
% Check for number of arguments
% ====================================================
narginchk(3, 8);
nargoutchk(0, 3);
% ====================================================
% Validate input arguments
% ====================================================
% Validate mandatory arguments
validateattributes(gType, {'char', 'string'}, {'scalartext', 'nonempty'}, mfilename, "Geometry type", 1);
validatestring(gType, ["Single", "Causal", "Anti-Causal", "AntiCausal", "Side", "Diagonal", "Diffuse", "Directional", "Circle"], mfilename, "Geometry type", 1);
validateattributes(srcLen, "numeric", {'scalar', 'real', 'nonnan', 'nonnegative', 'finite', 'nonempty'}, mfilename, "Source length", 2);
validateattributes(originDist, "numeric", {'scalar', 'nonnegative', 'real', 'nonnan', 'finite', 'nonempty'}, mfilename, "Distance from the origin", 3);
% Validate optional arguments
if nargin > 3 && ~isempty(ang)
validateattributes(ang, "numeric", {'scalar', 'real', 'nonnan', 'finite', 'nonempty'}, mfilename, "Angle value", 4);
if strcmpi(gType, "Diffuse")
if ang == 0
error("For the Diffuse geometry a non-zero value must be given for angle.");
elseif ang < 0
warning("For the Diffuse geometry the angle must be positive. A negative value is given so its absolute value will be used.");
end
end
else
if strcmpi(gType, "Diffuse") || strcmpi(gType, "Directional")
ang = 10;
else
ang = 45;
end
end
if nargin > 4 && ~isempty(nSrc)
validateattributes(nSrc, "numeric", {'scalar', 'real', 'nonnegative', 'nonnan', 'finite'}, mfilename, "Number of sources", 5);
else
nSrc = 5;
end
if nargin > 5 && ~isempty(azim)
validateattributes(azim, "numeric", {'vector', 'real', 'nonnan', 'finite'}, mfilename, "Directional field azimuthal angles", 6);
azim(azim > 180) = 360 - azim(azim > 180);
azim(azim < -180) = 360 + azim(azim < -180);
azim = deg2rad(azim);
else
azim = 0;
end
if nargin > 6 && ~isempty(elev)
validateattributes(elev, "numeric", {'vector', 'real', 'nonnan', 'finite'}, mfilename, "Directional field elevation angles", 7);
if numel(elev) ~= numel(azim)
error("Elevation angles vector must have the same number of elements as the azimuth angles vector parameter.");
end
elev(elev > 90) = 90 - elev(elev > 90);
elev(elev < -90) = 90 + elev(elev < -90);
elev = deg2rad(elev);
else
elev = zeros(length(azim), 1);
end
if nargin > 7 && ~isempty(SNR)
validateattributes(SNR, "numeric", {'scalar', 'real', 'nonnan'}, mfilename, "Directional field SNR", 8);
else
SNR = Inf;
end
% ====================================================
% Calculate source positions
% ====================================================
if strcmpi(gType, "Single")
sPos = [srcLen, originDist, 0];
domIdx = [];
elseif sum(strcmpi(gType, ["Causal", "Anti-Causal", "AntiCausal", "Side", "Diagonal"])) > 0
if srcLen ~= 0
sPos = [linspace(-srcLen/2, srcLen/2, nSrc); originDist * ones(1, nSrc); zeros(1, nSrc)].';
else
sPos = [srcLen, originDist, 0];
end
if nSrc ~= 0
switch lower(gType)
case {"anti-causal", "anticausal"}
sPos = sPos * rotMat3dDeg(0, 0, 180); % Rotate 180 degrees (clockwise)
case "side"
sPos = sPos * rotMat3dDeg(0, 0, 90); % Rotate 90 degrees (clockwise)
case "diagonal"
sPos = rotMat3dDeg(0, 0, -90 + ang) * sPos.'; % Rotate specified degrees (clockwise)
sPos = sPos.';
end
end
domIdx = [];
elseif sum(strcmpi(gType, ["Diffuse", "Directional"]) > 0)
sPos = ptsOnSphere(originDist, ang); % Calculate the source positions on the surface of the sphere
if strcmpi(gType, "Directional") && nSrc ~= 0
% Get angles of sources
[az, el] = cart2sph(sPos(:, 1), sPos(:, 2), sPos(:, 3));
domIdx = [];
% Go through the angles
for angIdx = length(azim):-1:1
closestVal = abs(elev(angIdx) - el);
elIdx = find((closestVal - 1e-5) <= min(closestVal));
closestVal = abs(azim(angIdx) - az(elIdx));
closestIdx = elIdx((closestVal - 1e-5) <= min(closestVal));
closestIdx = closestIdx(1); % Make sure to get only one index if there are two closest value candidates
% Take care of "distributed" sources
if nSrc == 1
domIdx = cat(1, domIdx, closestIdx);
else
for distSrcIdx = -ceil(nSrc/2)+1:floor(nSrc/2)
tempIdx = closestIdx + distSrcIdx;
if tempIdx > max(elIdx)
tempIdx = min(elIdx) + mod(tempIdx, max(elIdx)) - 1;
elseif tempIdx < min(elIdx)
tempIdx = max(elIdx) + mod(tempIdx, -min(elIdx)) + 1;
end
domIdx = cat(1, domIdx, tempIdx);
end
end
end
domIdx = unique(domIdx); % Make sure there's no duplicates
else
domIdx = [];
end
elseif strcmpi(gType, "circle")
sPos = (0:2 * pi/nSrc:2 * pi - 1/nSrc).';
[sPosX, sPosY] = sph2cart(sPos, 0, originDist);
sPos = [sPosX, sPosY, zeros(size(sPosX))];
domIdx = [];
end
% Calculate source strengths
if nargout > 1
% Set the amplitude of the "weak" sources
Q = (10^(-SNR/10))/sum(~ismember(1:size(sPos, 1), domIdx)) * ones(size(sPos, 1), 1);
% Set the amplitude of the dominant sources
if ~isinf(SNR)
Q(domIdx) = 1./length(domIdx);
else
Q(domIdx) = 1;
end
else
Q = [];
end
end
\ No newline at end of file
%% Calculate virtual microphone positions for specific geometries
% --------------------------------------------------
% Author: Achilles Kappis
% e-mail: axilleaz@protonmail.com
%
% Date: 12/07/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", "Grid",
% "Cube" and "Dual". The last is an arrangement with 2
% sensors "xLen" apart that can be translated and
% rotated.
%
%
% xLen [numeric] (Optional): The width of the "Array" geometry, the length
% of the side of the "Grid" and "Cube geometries
% and the distance between positions in "Dual"
% geometry.
% [Default: Array/Grid/Cube - 1, Dual - 0.2].
%
% xOff [numeric] (Optional): X-axis offset. [Default: 0].
%
% yOff [numeric] (Optional): Y-axis offset. [Default: 0].
%
% zOff [numeric] (Optional): Z-axis offset. [Default: 0].
%
% nSens [numeric] (Optional): The number of sensors in the array.
% For the "Grid" geometry the square root of
% "nSens" must be an integer value. For the
% "Cube" geometry the cubic root of "nSens"
% must be an integer value. This parameter is
% ignored for the "Single" geometry.
% [Default: Array/Grid - 100, Cube - 1000].
%
% orient [char/string/numeric] (Optional): The orientation of the
% geometry. For the "Single" and
% "Cube" geometries this value is
% ignored. For the "Dual"
% configuration only numerical
% values are used to declare the
% rotation of the configuration
% with respect to the local z-axis
% in degrees. For the rest of the
% configurations, the string
% values allowed are "XY", "XZ"
% and "YZ" and declare the plane
% on which the configuration will
% be placed. [Default: "XY" | 0].
%
% --------------------------------------------------
% 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 sqrt(N)xsqrt(N)x3 where N
% is the number of sources. It contains the x, y and z
% Cartesian coordinates for each position in a
% rectangular grid with dimensions sqrt(N)xsqrt(N).
% This is used only with the "Grid" geometry, otherwise
% it is empty.
%
% --------------------------------------------------
% Notes
%
% - Dependencies: rotMat3dDeg(): To rotate sources.
%
% - The position of the geometry is calculated like this: First the
% geometry is placed on the x-y plane, then rotated to match the
% orientation defined by the "orient" argument and finally, the offsets
% on each axis are applied.
% --------------------------------------------------
function [vPos, vPosMesh] = virtMicGeo(gType, xLen, xOff, yOff, zOff, nSens, 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", "Grid", "Cube"], mfilename, "Geometry type", 1);
% Validate optional arguments
if nargin > 1 && ~isempty(xLen) && ~strcmpi(gType, "Single")
validateattributes(xLen, "numeric", {'scalar', 'real', 'nonnan', 'finite', 'positive'}, mfilename, "Width of the geometry, or distance between positions in Dual geometry", 2);
else
if strcmpi(gType, "Dual")
xLen = 0.2;
else
xLen = 1;
end
end
if nargin > 2 && ~isempty(xOff)
validateattributes(xOff, "numeric", {'scalar', 'real', 'nonnan', 'finite'}, mfilename, "X-offset", 3);
else
xOff = 0;
end
if nargin > 3 && ~isempty(yOff)
validateattributes(yOff, "numeric", {'scalar', 'real', 'nonnan', 'finite'}, mfilename, "Y-offset", 4);
else
yOff = 0;
end
if nargin > 4 && ~isempty(zOff)
validateattributes(zOff, "numeric", {'scalar', 'real', 'nonnan', 'finite'}, mfilename, "Z-offset", 5);
else
zOff = 0;
end
if nargin > 5 && ~isempty(nSens)
if ~strcmpi(gType, "Single")
validateattributes(nSens, "numeric", {'scalar', 'real', 'nonnegative', 'finite', 'nonnan'}, mfilename, "Number of sensors", 6);
end
else
if sum(strcmpi(gType, ["Array", "Grid"])) > 0
nSens = 1e2;
elseif strcmpi(gType, "Cube")
nSens = 1e3;
else
nSens = 0;
end
end
if nargin > 6 && ~isempty(orient)
if ischar(orient) || isstring(orient)
validateattributes(orient, {'char', 'string'}, {'scalartext', 'nonempty'}, mfilename, "Geometry orientation", 7);
validatestring(orient, ["XY", "XZ", "YZ"], mfilename, "Geometry orientation", 7);
else
if ~strcmpi(gType, "Dual")
error("Planes can be provided for the orientation of only the Array and Grid geometries.");
end
validateattributes(orient, {'numeric'}, {'scalar', 'nonempty', 'nonnan', 'finite', 'real'}, mfilename, "Geometry orientation", 7);
end
else
if strcmpi(gType, "Dual")
orient = 0;
else
orient = "XY";
end
end
% ====================================================
% Check we have all needed parameters and they have
% acceptable values
% ====================================================
% For "Grid" geometry the square root of the number of sensors must be an integral value
if strcmpi(gType, "Grid") && mod(sqrt(nSens), 1) ~= 0
error("For the Grid geometry, the square root of the number of sensors must be an integer.");
elseif strcmpi(gType, "Cube") && mod(nthroot(nSens, 3), 1) ~= 0
error("For the Cube geometry, the cubic root of the number of sensors must be an integer (a tolerance of 1e-6 has been used for the calculation).");
end
% ====================================================
% Calculate virtual microphone positions
% ====================================================
switch lower(gType)
case "single"
% Create a single point at offset coordinates and return
vPos = [xOff, yOff, zOff];
vPosMesh = [];
return;
case "dual"
vPos = [-xLen/2, 0, 0; ...
xLen/2, 0, 0];
case "array"
vPos = linspace(-xLen/2, xLen/2, nSens);
vPos = [vPos(:), zeros(numel(vPos), 2)];
case "grid"
vPos = linspace(-xLen/2, xLen/2, sqrt(nSens));
[x, y] = meshgrid(vPos, vPos);
vPos = [x(:), y(:), zeros(numel(x), 1)];
case "cube"
vPos = linspace(-xLen/2, xLen/2, round(nSens^(1/3)));
[x, y, z] = meshgrid(vPos, vPos, vPos);
vPos = [x(:), y(:), z(:)];
otherwise
error("Oops... something went wrong here... not known geometry...!!!");
end
% Rotate
if ~strcmpi(gType, "Cube")
if isnumeric(orient)
vPos = vPos * rotMat3dDeg(0, 0, -orient);
elseif strcmpi(orient, "XZ")
if strcmpi(gType, "array")
vPos = vPos * rotMat3dDeg(0, 0, 90);
else
vPos = vPos * rotMat3dDeg(90, 0, 0);
end
elseif strcmpi(orient, "YZ")
vPos = vPos * rotMat3dDeg(0, 90, 0);
end
end
% Translate
if ~strcmpi(gType, "Cube")
vPos = vPos + [xOff, yOff, zOff];
end
% For "Grid" and "Cube" geometry provide the coordinates in a "mesh format"
if strcmpi(gType, "Grid")
vPosMesh = reshape(vPos, sqrt(nSens), [], 3);
elseif strcmpi(gType, "Cube")
vPosMesh = reshape(vPos, round(nSens^(1/3)), round(nSens^(1/3)), [], 3);
else
vPosMesh = [];
end
end
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment