% umtsDownlinkWaveformGenerator UMTS Downlink waveform generation
%   WAVEFORM = umtsDownlinkWaveformGenerator(DEF) returns the UMTS downlink
%   waveform WAVEFORM defined by the configuration structure DEF. This
%   generator supports W-CDMA, HSDPA and HSPA+. The output WAVEFORM will be
%   sampled at 3.84*OversamplingRatio MHz.
%
%   DEF must be a structure characterizing the waveform to generate. This
%   structure can be generated easily by using the
%   <a href="matlab:
%   help('umtsDownlinkReferenceChannels')">umtsDownlinkReferenceChannels</a> function. This structure includes fields
%   to describe the top level parameters and substructures to describe the
%   different channels to include in the waveform.
%   
%   The top level parameters of DEF are:
%   TotFrames             - Total number of frames to be generated
%   PrimaryScramblingCode - Cell primary scrambling code
%   FilterType            - Filter type ('RRC','Off')
%   OversamplingRatio     - The oversampling ratio
%   NormalizedPower       - Overall waveform power in dBW relative to 1
%                           ohm. Set to 'Off' to disable power
%                           normalization
%
%   Substructures can be added to enable the different channels: 
%   DPCH             - DPCH configuration structure
%   PCCPCH           - P-CCPCH configuration structure
%   SCCPCH           - S-CCPCH configuration structure
%   PCPICH           - P-CPICH configuration structure
%   SCPICH           - S-CPICH configuration structure
%   PSCH             - P-SCH configuration structure
%   SSCH             - S-SCH configuration structure
%   PICH             - PICH configuration structure
%   HSDPA            - HSDPA configuration structure
%   OCNS             - OCNS configuration structure
% 
%   The OCNS structure definition is used for the RMC/H-Set OCNS channels
%   and Test Model DPCHs and HS-PDSCH/HS-SCCH channels generation. The RMC
%   OCNS channels are defined in 25.101 Table C.6 and the H-Set OCNS
%   channels are defined in 25.101 Tables C.13 and C.13A. The Test Model
%   DPCHs and HS-PDSCH/HS-SCCH are defined in 25.141 Section 6.1.1.
%   OCNS must be a structure with the following fields:
%   OCNSType - The OCNS channels for RMC/H-Set or DPCH/HS-PDSCH/HS-SCCH set 
%              for Test Models. For the RMC/H-Set, the valid values are 
%              'RMC_16DPCH','H-Set_6DPCH' and 'H-Set_4DPCH'. 
%              For the Test Model DPCHs, the valid values are 'TM1_4DPCH',
%              'TM1_8DPCH', 'TM1_16DPCH', 'TM1_32DPCH', 'TM1_64DPCH',
%              'TM2_3DPCH', 'TM3_4DPCH', 'TM3_8DPCH', 'TM3_16DPCH',
%              'TM3_32DPCH', 'TM5_4DPCH_4HSPDSCH', 'TM5_6DPCH_2HSPDSCH',
%              'TM5_14DPCH_4HSPDSCH', 'TM5_30DPCH_8HSPDSCH',
%              'TM6_4DPCH_4HSPDSCH', 'TM6_30DPCH_8HSPDSCH'.
%   Power    - The overall power level of the OCNS/DPCH channels in dB.
%       
%   Example:
%   The example shows the generation of FRC H-Set1 configuration and how
%   this is used by umtsDownlinkWaveformGenerator.
%
%   config = umtsDownlinkReferenceChannels('H-Set1','16QAM');
%   waveform = umtsDownlinkWaveformGenerator(config);
%
%   See also umtsDownlinkReferenceChannels, umtsUplinkWaveformGenerator.

%    Copyright 2014-2017 The MathWorks, Inc.

function out = umtsDownlinkWaveformGenerator(config)
    
%%  Setup
    validateUMTSParameter('PrimaryScramblingCode',config);
    PrimaryCodeGroup = fix(config.PrimaryScramblingCode/8);
    PrimaryScramblingCodeIdx = mod(config.PrimaryScramblingCode,PrimaryCodeGroup*8); 
    validateUMTSParameter('TotFrames',config);
    out = zeros(38400*config.TotFrames,1);
    % Define function for checking if channel is enabled
    isChannelEnabled = @(x)isfield(config,x) && ~strcmpi(config.(x).Enable,'off');
    powerGainAdj = calcGainAdj(config);

%%  DCH (DTCH & DCCH) Transport Channel Processing
    if isfield(config,'DPCH') 
        dchframes = cell(1,numel(config.DPCH));
        for ch = 1:numel(config.DPCH)
            if ~strcmpi(config.DPCH(ch).Enable,'Off')
                dims = FddDLDPCHDims(config.DPCH(ch).SlotFormat);
                phyframecapacity = dims.NDataPerFrame;
                validateUMTSParameter('NMulticodes',config.DPCH(ch));
                if strcmpi(config.DPCH(ch).DataSource,'CCTrCH')
                    % Generate the DCH channel coded data
                    transportblocks = FddCCTrCHData(config.DPCH(ch).CCTrCH,config.TotFrames,'DLDCH');
                    dframes = FddCCTrCH(config.DPCH(ch).CCTrCH,phyframecapacity*config.DPCH(ch).NMulticodes,transportblocks{:});           
                    dchframes{ch} = dframes(1:dims.NDataPerFrame*config.TotFrames*config.DPCH(ch).NMulticodes); 
                else
                    config.DPCH(ch).Enable2Interleaving = 0;
                    phySource = vectorDataSource(config.DPCH(ch).DataSource);
                    dchframes{ch} = phySource.getPacket(dims.NDataPerFrame*config.TotFrames*config.DPCH(ch).NMulticodes);
                end
                config.DPCH(ch).SpreadingCode = config.DPCH(ch).SpreadingCode:config.DPCH(ch).SpreadingCode+config.DPCH(ch).NMulticodes-1;
            end
        end
    end
    
%%  BCH 
    if isChannelEnabled('PCCPCH')
        dims = FddPCCPCHDims();
        if strcmpi(config.PCCPCH.DataSource,'CCTrCH')
            % Generate the BCH channel coded data
            transportblocks = FddCCTrCHData(config.PCCPCH.CCTrCH,config.TotFrames);
            bchframes = FddCCTrCH(config.PCCPCH.CCTrCH,dims.NDataPerFrame,transportblocks{:});  
            bchframes = bchframes(1:dims.NDataPerFrame*config.TotFrames);
        else
            config.PCCPCH.Enable2Interleaving = 0;
            phySource = vectorDataSource(config.PCCPCH.DataSource);
            bchframes = repmat(phySource.getPacket(dims.NDataPerFrame),config.TotFrames,1);
        end
    end

%%  PCH/FACH
    if isChannelEnabled('SCCPCH')
        dims = FddSCCPCHDims(config.SCCPCH.SlotFormat);
        if strcmpi(config.SCCPCH.DataSource,'CCTrCH')
            % Generate the PCH/FACH channel coded data
            transportblocks = FddCCTrCHData(config.SCCPCH.CCTrCH,config.TotFrames);
            pchfachframes = FddCCTrCH(config.SCCPCH.CCTrCH,dims.NDataPerFrame,transportblocks{:});  
            pchfachframes = pchfachframes(1:dims.NDataPerFrame*config.TotFrames);
        else
            config.SCCPCH.Enable2Interleaving = 0;
            phySource = vectorDataSource(config.SCCPCH.DataSource);
            pchfachframes = repmat(phySource.getPacket(dims.NDataPerFrame),config.TotFrames,1);
        end    
    end

%%  DPCH
    if isfield(config,'DPCH') 
        for ch = 1:numel(config.DPCH)
            if ~strcmpi(config.DPCH(ch).Enable,'Off')
                validateUMTSParameter('SecondaryScramblingCode',config.DPCH(ch));
                config.DPCH(ch).ScramblingCode = [PrimaryCodeGroup PrimaryScramblingCodeIdx config.DPCH(ch).SecondaryScramblingCode];
                tpcSource = vectorDataSource(config.DPCH(ch).TPCData);
                dpchchips = FddDLDPCH(config.DPCH(ch),dchframes{ch},tpcSource.getPacket(15*config.TotFrames),config.DPCH(ch).TFCI);  
                out = out + dpchchips*db2mag(config.DPCH(ch).Power);
            end
        end
    end
    
%%  CPICH

% P-CPICH
    if isChannelEnabled('PCPICH')
        config.PCPICH.ScramblingCode = [PrimaryCodeGroup PrimaryScramblingCodeIdx];
        pcpichchips = FddPCPICH(config.PCPICH,config.TotFrames);
        out = out + pcpichchips*db2mag(config.PCPICH.Power);
    end
% S-CPICH
    if isChannelEnabled('SCPICH')
        validateUMTSParameter('SecondaryScramblingCode',config.SCPICH);
        config.SCPICH.ScramblingCode = [PrimaryCodeGroup PrimaryScramblingCodeIdx config.SCPICH.SecondaryScramblingCode];
        scpichchips = FddSCPICH(config.SCPICH,config.TotFrames);
        out = out + scpichchips*db2mag(config.SCPICH.Power);
    end
    
%%  CCPCH
% P-CCPCH
    if isChannelEnabled('PCCPCH')
        config.PCCPCH.ScramblingCode =  [PrimaryCodeGroup PrimaryScramblingCodeIdx];
        pccpchchips = FddPCCPCH(config.PCCPCH,bchframes);
        out = out + pccpchchips*db2mag(config.PCCPCH.Power);

    end
 % S-CCPCH
    if isChannelEnabled('SCCPCH')
        validateUMTSParameter('SecondaryScramblingCode',config.SCCPCH);
        config.SCCPCH.ScramblingCode = [PrimaryCodeGroup PrimaryScramblingCodeIdx config.SCCPCH.SecondaryScramblingCode];
        sccpchchips = FddSCCPCH(config.SCCPCH,pchfachframes,config.SCCPCH.TFCI);
        out = out + sccpchchips*db2mag(config.SCCPCH.Power);
    end

%%  SCH
    %P-SCH
    if isChannelEnabled('PSCH')
        config.PSCH.SCH = 'PSCH';
        pschchips = repmat(FddSCH(config.PSCH),config.TotFrames,1);
        out = out + pschchips*db2mag(config.PSCH.Power);
    end
    %S-SCH
    if isChannelEnabled('SSCH')
        config.SSCH.ScramblingCode = [PrimaryCodeGroup PrimaryScramblingCodeIdx];
        config.SSCH.SCH = 'SSCH';
        sschchips = repmat(FddSCH(config.SSCH),config.TotFrames,1);
        out = out + sschchips*db2mag(config.SSCH.Power);
    end
    
%%  PICH
    if isChannelEnabled('PICH')
        config.PICH.ScramblingCode = [PrimaryCodeGroup PrimaryScramblingCodeIdx];
        if strcmpi(config.PICH.DataSource,'PagingData')
            validateUMTSParameter('Np',config.PICH);
            pichdata = repmat([1 0 1 1 0 0 0 1 0 1 1 0 0 0 1 0 1 0]',config.TotFrames*config.PICH.Np/18,1);
        else
            NDataPerFrame = 288;
            phySource = vectorDataSource(config.PICH.DataSource);
            pichdata = phySource.getPacket(NDataPerFrame*config.TotFrames);
        end
        pichchips = FddPICH(config.PICH,pichdata);
        out = out + pichchips*db2mag(config.PICH.Power);
    end

%%  HSDPA
    if isfield(config,'HSDPA')
        for h=1:numel(config.HSDPA)
            if ~strcmpi(config.HSDPA(h).Enable,'Off') 
                config.HSDPA(h).TotFrames = config.TotFrames;
                validateUMTSParameter('SecondaryScramblingCode',config.HSDPA(h));
                config.HSDPA(h).ScramblingCode = [PrimaryCodeGroup PrimaryScramblingCodeIdx config.HSDPA(h).SecondaryScramblingCode];
                out = out + FddHSDPA(config.HSDPA(h)); 
            end
        end
    end
    
%%  OCNS
    if isChannelEnabled('OCNS')
        config.OCNS.ScramblingCode = [PrimaryCodeGroup PrimaryScramblingCodeIdx];
        config.OCNS.TotFrames = config.TotFrames;
        ocnschips = FddOCNS(config.OCNS);  
        out = out + ocnschips*db2mag(config.OCNS.Power);
    end
        
%%  Gain Adjustment and Filtering
    out = out*powerGainAdj;
    validateUMTSParameter('FilterType',config);
    validateUMTSParameter('OversamplingRatio',config);
    out = FddRRCFiltering(out,32,config.OversamplingRatio,config.FilterType);
    
end


%% Local Functions

% This is the function to calculate the gain adjust value if
% 'NormalizedPower' is not 'Off'
function powerGainAdj = calcGainAdj(config)
    % get the total power from all active physical channels
    p = 0;
    p = p+getChannelPower(config,'DPCH');
    p = p+getChannelPower(config,'PCCPCH');
    p = p+getChannelPower(config,'SCCPCH');
    p = p+getChannelPower(config,'PCPICH');
    p = p+getChannelPower(config,'SCPICH');
    p = p+getChannelPower(config,'PSCH');
    p = p+getChannelPower(config,'SSCH');
    p = p+getChannelPower(config,'PICH');
    p = p+getChannelPower(config,'OCNS');
    p = p+getChannelPower(config,'HSDPA');
    
    if ischar(config.NormalizedPower) || isstring(config.NormalizedPower)
        if strcmpi(config.NormalizedPower,'Off')
            powerGainAdj = 1;
            return;
        else
            error('umts:error','Invalid value (%s) specified for NormalizedPower',config.NormalizedPower);
        end
    end
    % Get the overall gain in dB
    validateUMTSParameter('NormalizedPower',config);
    overallgain = config.NormalizedPower;

    % If total channel power is not overall power specified, we need
    % gain adjustment
    powerScaledB = overallgain-pow2db(p);
    powerGainAdj = db2mag(powerScaledB);
end

% Function to calculate the power of each phy channel
function pow = getChannelPower(config,channel)
    pow = 0;
    if isfield(config,channel) 
        for c = 1:numel(config.(channel))
            currentch = config.(channel)(c);
            validateUMTSParameter('Enable',currentch); 
            if ~strcmpi(currentch.Enable,'off')
                % Calculate the power scaling depending on whether there
                % are multi-codes and the channel duration
                powerscaling = 1;
                if strcmpi(channel,'DPCH')
                    % Add the powers of all the physical channels. the
                    % Layer 1 control information is transmitted only on
                    % the first downlink DPCH. DTX bits are transmitted
                    % during the corresponding time period for the
                    % additional downlink DPCHs
                    validateUMTSParameter('NMulticodes',currentch);
                    dims = FddDLDPCHDims(config.DPCH(c).SlotFormat);
                    powerscaling =  1 + (currentch.NMulticodes-1)* (dims.NData1+dims.NData2)/dims.BitsPerSlot;
                elseif any(strcmpi(channel,{'PSCH','SSCH'}))
                    % The SCH only exists for 256 chips in a slot
                    powerscaling = 1/10;
                elseif strcmpi(channel,'PCCPCH')
                    % The P-CCPCH is off for the first 256 chips in a slot
                    powerscaling = 9/10;
                elseif strcmpi(channel,'PICH')
                    % PICH is transmitted only on the first 288 bits 
                    powerscaling = 288/300;
                elseif strcmpi(channel,'OCNS')
                    % Calculate the power of the OCNS channel set
                    chsetpower = FddOCNS(currentch,'poweronly');  
                    powerscaling = db2pow(chsetpower);
                end
                
                if strcmpi(channel,'HSDPA')
                    % The HSDPA channel set consists of HS-PDSCH and
                    % HS-SCCH channels and so the power calculation is
                    % different
                    validateUMTSParameter('HSPDSCHPower',currentch); 
                    validateUMTSParameter('HSSCCHPower',currentch); 
                    validateUMTSParameter('CodeGroup',currentch);
                    % Add the powers of all HS-PDSCH channels and HS-SCCH
                    pow = pow + db2pow(currentch.HSPDSCHPower)*currentch.CodeGroup + db2pow(currentch.HSSCCHPower);
                else
                    validateUMTSParameter('Power',currentch); 
                    pow = pow + powerscaling*db2pow(currentch.Power);
                end
            end

        end
        
    end
end
