function waveform = bleWaveformGenerator(message,varargin)
%bleWaveformGenerator Generate Bluetooth LE PHY waveform
%
%   WAVEFORM = bleWaveformGenerator(MESSAGE) generates waveform, a
%   time-domain Bluetooth(R) low energy (LE) physical layer (PHY) waveform
%   by using the input information bits, MESSAGE.
%
%   WAVEFORM is a complex valued column vector of size Ns-by-1, where Ns
%   represents the number of time-domain samples.
%
%   MESSAGE is a column vector of numeric or logical values. MESSAGE
%   contains the information bits and has a maximum length of 2088.
%
%   WAVEFORM = bleWaveformGenerator(...,Name,Value) specifies additional
%   name-value pair arguments described below. When a name-value pair is
%   not specified, its default value is used.
%
%   'Mode'                  Specify the PHY transmission mode as one of
%                           'LE1M', 'LE2M', 'LE500K' or 'LE125K'.
%                           The default value is 'LE1M'.
%
%   'ChannelIndex'          Specify the channel index as an integer in
%                           the range [0,39]. For data channels,
%                           specify this value in the range [0,36]. For
%                           advertising channels, specify this value in
%                           the range [37,39]. The default value is 37.
%
%   'SamplesPerSymbol'      Specify the samples per symbol as a
%                           positive integer. The default value is 8.
%
%   'AccessAddress'         Specify the access address as a 32-bit column
%                           vector of type numeric or logical. The default
%                           value is [1 0 0 1 0 1 0 0 1 0 0 0 0 0 1 0 0 ...
%                           1 1 0 1 1 1 0 1 0 0 0 1 1 1 0]'. The higher
%                           layers use this output to validate a packet.
%
%   'DFPacketType'          Specify the direction finding packet type as
%                           'ConnectionlessCTE', 'ConnectionCTE', or
%                           'Disabled'. The default value is 'Disabled'.
%
%   'WhitenStatus'          Specify the whiten status as 'On' or
%                           'Off'. The default value is 'On'.
%
%   'ModulationIndex'       Specify the modulation index as a scalar in the
%                           range [0.45, 0.55]. This input specifies the
%                           modulation index that the waveform generator
%                           uses while performing Gaussian Frequency Shift
%                           Keying (GFSK) modulation. The default value is
%                           0.5.
%
%   'PulseLength'           Specify the length of the frequency pulse shape
%                           in the range [1, 4]. Units are in symbol
%                           intervals. The default value is 1.
%   
%   % Examples:
%
%   % Example 1:
%   % Generate Bluetooth LE waveform for random input bits by using pulse
%   % length of 3 for GFSK modulation.
%
%   message = randi([0 1],2056,1);
%   symbolRate = 1e6; % Symbol rate as per standard
%   waveform = bleWaveformGenerator(message,'PulseLength',3);
%   scope = spectrumAnalyzer;
%   scope.SampleRate = 8*symbolRate; % Default sps value is 8
%   scope(waveform);
%
%   % Example 2:
%   % Generate Bluetooth LE waveform with mode of 'LE125K' using specified
%   % samples per symbol, channel index, access address, and modulation
%   % index.
%   message = randi([0 1],640,1);
%   phyMode = 'LE125K';
%   sps = 4;
%   channelIndex = 2;
%   accessAddress = [1 1 1 1 0 1 0 0 1 1 0 1 0 0 1 0 0 1 1 0 1 1 1 0 1 ...
%                 0 1 0 1 1 0 0].';
%   modIndex = 0.48;
%   symbolRate = 1e6; % Symbol rate as per standard
%   waveform = bleWaveformGenerator(message,'Mode',phyMode, ...
%    'SamplesPerSymbol',sps,'ChannelIndex',channelIndex,...
%    'AccessAddress',accessAddress,'ModulationIndex',modIndex);
%   scope = spectrumAnalyzer;
%   scope.SampleRate = sps*symbolRate;
%   scope(waveform);
%
%   % Example 3:
%   % Generate Bluetooth LE waveform by enabling constant tone extension
%   % (CTE) for connection-oriented scenario.
%
%   % Generate a connection data channel PDU for AoD CTE with 2 micro-
%   % seconds slots and CTE time of 16 microseconds.
%   pduHex = '1B820127'; % Valid PDU in hexadecimal
%   pdu = int2bit(hex2dec(pduHex),32,false);
%   crcGen = comm.CRCGenerator('z^24+z^10+z^9+z^6+z^4+z^3+z+1',...
%   'InitialConditions',int2bit(hex2dec('555551'),24)',...
%   'DirectMethod',true);
%   pduCRC = crcGen(pdu);
%   txWaveform = bleWaveformGenerator(pduCRC,'ChannelIndex',36,...
%                       'DFPacketType','ConnectionCTE');
%
%   See also bleIdealReceiver, bluetoothWaveformGenerator,
%   bluetoothIdealReceiver, bleAngleEstimate, bleCTEIQSample.

%   Copyright 2018-2022 The MathWorks, Inc.

%#codegen

% Check the number of input arguments
narginchk(1,17);

% Validation of input arguments
[phyMode,channelIndex,sps,accessAddress,dfPacketType,whitenStatus, ...
    modIndex,pulseLen] = validateInputArgs(message,varargin{:});

% Preamble generation
preamble = ble.internal.preambleGenerator(phyMode,accessAddress);
persistent whiten
% Perform whitening on message bits, if the whiten status is 'On'
if strcmp(whitenStatus,'On')
    chanIdxBin = int2bit(double(channelIndex),6);
    initState = [1;chanIdxBin]; % Initial conditions of shift register
    if isempty(coder.target)
        if isempty(whiten)
            whiten = bluetoothWhiten("InitialConditionsSource","Input port");
        end
        whitenedMessage = whiten(message,initState);
        reset(whiten);
    else
        cgWhiten = bluetoothWhiten("InitialConditionsSource","Input port");
        whitenedMessage = cgWhiten(message,initState);
        reset(cgWhiten);
    end
else
    whitenedMessage = message;
end

% Append CTE to the whitened bits, if direction finding packet type is
% either ConnectionCTE or ConnectionlessCTE.
if ~strcmp(dfPacketType,'Disabled')
    % CTE time information in 8 microseconds units
    cteTime = ble.internal.cteInfoExtract(message,dfPacketType);
    % Number of CTE bits based on phyMode
    cteNumBits = cteTime*8*(1+strcmp(phyMode,'LE2M'));
    cte = ones(cteNumBits,1);
    pduCTE = [whitenedMessage;cte]; % Append CTE
else % Do not append CTE if dfPacketType is set to 'Disabled'
    pduCTE = whitenedMessage;
end

% Physical layer frame formation
if any(strcmp(phyMode,{'LE500K','LE125K'}))
    if strcmp(phyMode,'LE500K')
        codingIndicator = [0 1]';
    else % LE125K
        codingIndicator = [0 0]';
    end
    encodedMessage = ble.internal.encode(whitenedMessage,accessAddress,...
                            codingIndicator); % FEC and pattern mapping
    phyFrame = [preamble; double(encodedMessage)];
else % LE1M or LE2M
    phyFrame = [preamble; double(accessAddress); double(pduCTE)];
end

% Perform GFSK modulation on physical layer frame
 waveform = ble.internal.gmskmod(phyFrame,double(sps),double(modIndex),double(pulseLen));
end

% Validate the input arguments
function [phyMode,channelIndex,sps,accessAddress,dfPacketType, ...
    whitenStatus,modIndex,pulseLen] = validateInputArgs(message,varargin)

% Validate message
validateattributes(message,{'numeric','logical'},{'nonempty','nonnan',...
                        'finite','column','binary'},mfilename,'MESSAGE');

if(nargin==1)
    % For 'bleWaveformGenerator(message)'
    phyMode = 'LE1M';
    channelIndex = 37;
    sps = 8;
    accessAddress = [1 0 0 1 0 1 0 0 1 0 0 0 0 0 1 0 0 1 ...
                            1 0 1 1 1 0 1 0 0 0 1 1 1 0].';
    dfPacketType = 'Disabled';
    whitenStatus = 'On';
    modIndex = 0.5;
    pulseLen = 1;
else
    % For 'bleWaveformGenerator(message,N1,V1,...)'
    [phyMode,channelIndex,sps,accessAddress,dfPacketType,~,whitenStatus, ...
        modIndex,pulseLen] = ...
                    ble.internal.validatePhysicalLayerInputs(varargin{:});
end

% Check for length of message
coder.internal.errorIf((numel(message)>2088),...
    'bluetooth:bleWaveformGenerator:InputLength',length(message));
% The message must include bits up to cteInfo field when dfPacketType is
% either ConnectionCTE or ConnectionlessCTE.
if ~strcmp(dfPacketType,'Disabled')
    cteInfoInd = 24;
    if strcmp(dfPacketType,'ConnectionlessCTE')
        cteInfoInd = 40;
    end
    coder.internal.errorIf((numel(message)<cteInfoInd),...
        'bluetooth:bleWaveformGenerator:DFInputLength',cteInfoInd,...
        dfPacketType,length(message));
end
end