diff --git a/Sound Fields/MATLAB/Functions/ptSrcFieldTD.m b/Sound Fields/MATLAB/Functions/ptSrcFieldTD.m new file mode 100644 index 0000000000000000000000000000000000000000..909e7808f4a9562addb29fb337adf3682a49feb1 --- /dev/null +++ b/Sound Fields/MATLAB/Functions/ptSrcFieldTD.m @@ -0,0 +1,148 @@ +%% Signal generated by point sources in the time-domain +% -------------------------------------------------- +% Author: Achilles Kappis +% e-mail: axilleaz@protonmail.com +% +% Date: 13/09/2024 (DD/MM/YYYY) +% +% Copyright: MIT +% -------------------------------------------------- +% Functionality: Calculate the signals generated by ideal point sources in +% the time-domain. +% -------------------------------------------------- +% Input +% +% sPos [numeric]: The Cartesian coordinates of the source positions. This +% must be an Nx3 matrix where N is the number of sources. +% +% rPos [numeric]: The Cartesian coordinates of the receiver positions. This +% must be an Mx3 matrix where M is the number of receivers. +% +% fs [numeric]: The sampling frequency. This is required in order to +% calculate the time-of-flight in samples and must be a +% positive real scalar. +% +% Q [numeric] (Optional): The source signals. This can be either an IxNxJ +% array, where I is the length of the source +% signals in samples and J is the number of +% trials/sound field realisations, or a real +% positive integer denoting the length of the +% source signals. In case the length of the source +% signals is given, the generated signals are +% zero-mean, (approximately) uniformly distributed +% and normalised to unity. [Default: 128]. +% +% nTrials [numeric] (Optional): This is the number of trials/sound field +% realisations that will be generated. If "Q" +% is provided, this argument is ignored. It +% must be a positive real scalar. +% [Default: 1] +% +% c [numeric] (Optional): The speed of sound. [Default: 343]. +% +% -------------------------------------------------- +% Output +% +% rSig [numeric]: The signals at the receiver positions for all +% trials/sournd field realisations. This is an IxMxJ array. +% +% rSigMean [numeric]: The signals at the receiver positions averaged over +% all trials/sound field realisations. This is an IxM +% matrix. +% +% rSigMtx [numeric]: The signals at each receiver position due to each +% source for each trial/sound field realisation. This is +% an IxMxNxJ array. +% +% rSigMtxMean [numeric]: The signals at each receiver position due to each +% source averaged over the trials/sound field +% realisations. This is an IxMxN array. +% +% -------------------------------------------------- +% Notes +% +% - It is the responsibility of the user to pick (or provide) adequately +% long source signals +% +% -------------------------------------------------- +function [rSig, rSigMean, rSigMtx, rSigMtxMean] = ptSrcFieldTD(sPos, rPos, fs, Q, nTrials, c) + % ==================================================== + % Check for number of arguments + % ==================================================== + narginchk(3, 6); + nargoutchk(0, 4); + + % ==================================================== + % Validate input arguments + % ==================================================== + % Validate mandatory arguments + validateattributes(sPos, "numeric", {'2d', 'real', 'nonnan', 'nonempty', 'finite', 'ncols', 3}, mfilename, "Cartesian coordinates of the source positions", 1); + validateattributes(rPos, "numeric", {'2d', 'real', 'nonnan', 'nonempty', 'finite', 'ncols', 3}, mfilename, "Cartesian coordinates of the receiver positions", 2); + validateattributes(fs, "numeric", {'scalar', 'real', 'nonnan', 'nonempty', 'finite', 'positive'}, mfilename, "The sampling frequency", 3); + + % Validate optional arguments + if nargin > 3 && ~isempty(Q) + if isscalar(Q) + validateattributes(Q, "numeric", {'scalar', 'real', 'nonnan', 'nonempty', 'finite', 'positive', 'integer'}, mfilename, "Length of source signals in samples", 4); + else + validateattributes(Q, "numeric", {'3d', 'real', 'nonnan', 'nonempty', 'finite', 'ncols', size(sPos, 1)}, mfilename, "Source signals", 4); + nTrials = size(Q, 3); + end + else + Q = 128; + end + + if nargin > 4 && ~isempty(nTrials) && isscalar(Q) + validateattributes(nTrials, "numeric", {'scalar', 'positive', 'nonnan', 'nonempty', 'finite'}, mfilename, "Number of trials/sound field realisations", 5); + elseif nargin > 4 && isempty(nTrials) + nTrials = 1; + end + + if nargin > 5 && ~isempty(c) + validateattributes(c, "numeric", {'scalar', 'nonempty', 'nonnan', 'finite', 'real', 'positive'}, mfilename, "The speed of sound", 6); + else + c = 343; + end + + + % ==================================================== + % Pre-process data + % ==================================================== + % Generate source signals + if isscalar(Q) + Q = rand(Q, size(sPos, 1), nTrials); + Q = Q - mean(Q); + Q = Q./max(abs(Q)); + end + + % Calculate source-receiver distances + dist = twoPtDist(sPos, rPos); % Distances + del = dist/c; % Delays in seconds + + % ==================================================== + % Calculate signals + % ==================================================== + % Go through the trials/sound field realisations + for jIdx = size(Q, 3):-1:1 + % Go through the sources (calculate for all receivers in one go) + for sIdx = size(dist, 1):-1:1 + rSigMtx(:, :, sIdx, jIdx) = (1./dist(sIdx, :)) .* delayseq(Q(:, sIdx, jIdx), del(sIdx, :), fs); + end + end + + % Sum source signals at each receiver position + rSig = squeeze(sum(rSigMtx, 3)); + + % ==================================================== + % Calculate output arguments + % ==================================================== + % Mean receiver signal over all trials/sound field realisations + if nargout > 1 + rSigMean = squeeze(mean(rSig, 3)); + end + + % Mean receiver signal due to each source over all trials/sound field realisations + if nargout > 3 + rSigMtxMean = squeeze(mean(rSigMtx, 4)); + end +end \ No newline at end of file