classdef AWG < handle
    %AWG Summary of this class goes here
    %   Detailed explanation goes here

    properties(Access=public)

        kover %oversampling factor e.g. 16
        upsampling_method
        repetitions %repeat the signal to generate a longer sequence?
        fdac %needed
        normalize2dac %want to normalize at first? either 0 or 1
        bit_resolution %bit res. of quantizer (e.g. 5 bit)
        dac_min
        dac_max

        skew_active = 0;
        awg_skew ; %skew vector for each output channel

        lpf_active = 0;
        lpf_type ;
        f_cutoff ;

        signal_length;
        H_lpf;


    end

    methods (Access=public)
        function obj = AWG(options)
            %AWG Construct an instance of this class
            %   Detailed explanation goes here

            arguments
                options.kover           = 16;
                options.upsampling_method upsampling_mode
                options.repetitions     = 1;
                options.normalize2dac       = 1;
                options.fdac            = 92e9;
                options.bit_resolution  = 5.5
                options.dac_min         = -0.5;
                options.dac_max         = 0.5;
                options.skew_active     = 0;
                options.awg_skew        = 0;
                options.lpf_active      = 0;
                options.lpf_type        = 0;
                options.f_cutoff        = 32e9;
                options.H_lpf Filter
                
            end

            fn = fieldnames(options);
            for n = 1:numel(fn)
                try
                    obj.(fn{n}) = options.(fn{n});
                end
            end

        end

        function signalclass_out = process(obj,signalclass_in)

            len_in = length(signalclass_in.signal);

            if signalclass_in.fs ~= obj.fdac
                k = obj.kover*obj.fdac / signalclass_in.fs;
                signalclass_in = signalclass_in.resample("fs_in",signalclass_in.fs,"fs_out",obj.fdac);
            end
                       
            % 1-3. actual processing of the signal (normalize->quantize->sample hold)
            signalclass_in.signal = obj.process_(signalclass_in.signal);
            
            % cast the inform. signal to electrical signal
            if ~isa(signalclass_in,'Electricalsignal')
                signalclass_in = Electricalsignal(signalclass_in,"fs",obj.fdac*obj.kover,"logbook",signalclass_in.logbook);
            end

            % normalize to 0dBm before applying the lowpass
            %signalclass_in = signalclass_in.normalize("mode","milliwatt");
            signalclass_in = signalclass_in.setPower(13,"dBm");


            % 4. Apply LPF on the signal
            if obj.lpf_active
                if isa(obj.H_lpf,'Filter')
                    %4.A) user already specified a complete filter class when
                    % initializing the AWG module
                    signalclass_in = obj.H_lpf.process(signalclass_in);
                else
                    %4.B) just use a standard filter
                    lpf = Filter('filtdegree',5,"f_cutoff",obj.f_cutoff,"fsamp",obj.kover*obj.fdac,"filterType",obj.lpf_type);
                    signalclass_in = lpf.process(signalclass_in);
                end
            end
            
            disp(['AWG output power: ',num2str(signalclass_in.power),' dBm']);
            
            % append to logbook
            current_class = class(obj);
            lbdesc = ['AWG ', current_class , '// k_over:',num2str(obj.kover),'. f_dac:',num2str(obj.fdac*1e-9),'GHz. Resolution:',num2str(obj.bit_resolution),' bits.'];
            signalclass_in  = signalclass_in.logbookentry(lbdesc);

            % write to output
            signalclass_out = signalclass_in;

            len_out = length(signalclass_out.signal);

            if len_out ~= len_in * obj.kover
                warning("AWG: Output length maybe not correct.")
            end

        end

        function elec_out = process_(obj,data_in)
            %METHOD1 Summary of this method goes here
            %   Detailed explanation goes here
            arguments(Input)
                obj
                data_in
            end

            arguments(Output)
                elec_out
            end

            obj.signal_length = length(data_in);

            if obj.normalize2dac
                % 0a Normalize the signal to full scale DAC range
               
                data_in = data_in - min(data_in); %set "foot" to zero
                data_in = data_in / (max(data_in)-min(data_in)); %scale between 0 and 1
                data_in = data_in * (obj.dac_max-obj.dac_min); %scale to desired total range of DAC
                data_in = data_in + obj.dac_min; %set "foot" to desired value
            end

            data_in = data_in - mean(data_in);

            % 1. Quantize the signal - Full Scale is between obj.dac_min
            % and dac_max. If signal is smaller in between, you won't use
            % the full bit-resolution.
            if obj.bit_resolution>0
                elec_out = obj.quantization(data_in) ;
            else
                elec_out = data_in;
            end
             

            % 2. Sample and hold + repeat (data_out: 1xsignal length)
            if obj.upsampling_method == 1
                % just use matlab function
                elec_out = resample(elec_out,obj.kover,1);
            elseif obj.upsampling_method == 2
                % sample and hold
                elec_out = repmat(elec_out,obj.repetitions,obj.kover);
                elec_out = reshape(elec_out',[],1);
            else
                error('chosen upsampling method not implemented?');
            end
          

            % 3. Add skew (not implemented so far)
            if obj.skew_active
                elec_out = obj.skew(elec_out);
            end

        end

    end

    methods (Access=private)

        function quant_out = quantization(obj,x_in)

            steps = round(2^obj.bit_resolution) + rem(round(2^obj.bit_resolution),2) ;

            if isreal(x_in)
                % shift signal and clip to quantizer intervall
                x_in =   min( max(x_in-obj.dac_min,0), obj.dac_max-obj.dac_min);

                % quantize signal
                x_in = round((steps-1)/(obj.dac_max-obj.dac_min)*x_in);

                % scale and shift back
                quant_out = x_in*(obj.dac_max-obj.dac_min)/(steps-1) +obj.dac_min;

            else

                % shift signal and clip to quantizer intervall
                x_in = min(max(real(x_in)-obj.dac_min,0),obj.dac_max-obj.dac_min) + ...
                    1i*min(max(imag(x_in)-obj.dac_min,0),obj.dac_max-obj.dac_min);

                % quantize signal and shift back signal
                x_in = round((steps-1)/(obj.dac_max-obj.dac_min)*x_in);

                % scale and shift back
                quant_out = x_in*(obj.dac_max-obj.dac_min)/(steps-1) +(1+1i)*obj.dac_min;

            end

        end

        function skew_out = skew(obj,x_in)

            % Exact delay calculation

            % Calculate transfer function
            fsimu = obj.kover*obj.fdac;
            faxis = linspace(fsimu/2, fsimu/2, length(x_in)+1);
            faxis = fftshift(faxis(1:end-1));

            skew_out = ifft(fft(x_in) .* exp(-1i*2*pi*obj.awg_skew*faxis)' );

            skew_out = real(skew_out) ; % get rid of negligible imaginary part

        end

    end
end
