Skip to content
Snippets Groups Projects
ptSrcFieldTD.m 8.05 KiB
%% Signal generated by point sources in the time-domain
% --------------------------------------------------
% Author: Achilles Kappis
% e-mail: axilleaz@protonmail.com
%
% Date: 15/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, a vector of
%                         positive real values holding the source strengths
%                         (this is just an amplitude scaling factor), or a
%                         real positive integer denoting the common
%                         strength of all the source signals. [Default: 1].
% 
% sigLen [numeric] (Optional): The length of the source signals. This must
%                              be real positive integer. The generated
%                              signals are zero-mean, (approximately)
%                              uniformly distributed and normalised to
%                              unity. If "Q" is an array, "sigLen" is used
%                              to set the length of the signals. If
%                              "sigLen" is smaller than I, the signals are
%                              truncated and if it is larger the signals
%                              are padded with zeros. Leave empty to have
%                              the signals unchanged.
%                              [Default: 128 or if Q is matrix size(Q, 1)].
% 
% 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.
% 
% Q [numeric]: The source signals. If Q is provided, the same variable is
%              returned here. If Q is generated internally, the signals are
%              returned.
% 
% --------------------------------------------------
% Notes
% 
% Dependencies: - twoPtDist(): To calculate the distances between sources
%                              and receivers.
% 
% - It is the responsibility of the user to pick (or provide) adequately
%   long source signals
% 
% --------------------------------------------------
function [rSig, rSigMean, rSigMtx, rSigMtxMean, Q] = ptSrcFieldTD(sPos, rPos, fs, Q, sigLen, nTrials, c)
    % ====================================================
    % Check for number of arguments
    % ====================================================
    narginchk(3, 7);
    nargoutchk(0, 5);

    % ====================================================
    % 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);
        elseif isvector(Q) && size(Q, 2) == size(sPos, 1)
            validateattributes(Q, "numeric", {'vector', 'real', 'nonnan', 'nonempty', 'finite', 'ncols', size(sPos, 1)}, mfilename, "Source signals", 4);
        else
            validateattributes(Q, "numeric", {'3d', 'real', 'nonnan', 'nonempty', 'finite', 'ncols', size(sPos, 1)}, mfilename, "Source signals", 4);
        end
    else
        Q = 1;
    end

    if nargin > 4 && ~isempty(sigLen)
        validateattributes(sigLen, "numeric", {'scalar', 'nonempty', 'nonnan', 'finite', 'real', 'positive', 'integer'}, mfilename, "The length of the source signals", 5)
    else
        if ~isvector(Q)
            sigLen = size(Q, 1);
        else
            sigLen = 128;
        end
    end


    if ~ismatrix(Q)
        nTrials = size(Q, 3);
    elseif nargin > 5 && ~isempty(nTrials)
        validateattributes(nTrials, "numeric", {'scalar', 'positive', 'nonnan', 'nonempty', 'finite', 'integer'}, mfilename, "Number of trials/sound field realisations", 6); 
    else
        nTrials = 1;
    end

    if nargin > 6 && ~isempty(c)
        validateattributes(c, "numeric", {'scalar', 'nonempty', 'nonnan', 'finite', 'real', 'positive'}, mfilename, "The speed of sound", 7);
    else
        c = 343;
    end
    

    % ====================================================
    % Pre-process data
    % ====================================================
    % Generate source signals
    if isvector(Q)
        tmp = rand(sigLen, size(sPos, 1), nTrials);
        tmp = tmp - mean(tmp);
        tmp = tmp./max(abs(tmp));
        Q = Q(:).' .* tmp;
    elseif sigLen ~= size(Q, 1)
        if sigLen > size(Q, 1)
            Q = cat(1, Q, zeros(sigLen - size(Q, 1), size(Q, 2), size(Q, 3)));
        else
            Q = Q(1:sigLen, :, :);
        end
    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 = nTrials:-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 = reshape(sum(rSigMtx, 3), size(rSigMtx, 1), size(rSigMtx, 2), size(rSigMtx, 4));

    % ====================================================
    % 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