classdef Scope
    %SCOPE Summary of this class goes here
    %   Detailed explanation goes here

    properties
        fsimu
        fadc
        adcresolution
        quantbuffer

        rand_samplingdelay %on or off
        samplingdelay %specifiy a sampling delay

        freq_offset %offset of the sampler
        samp_jitter %include jitter

        fixed_delay %fix the delay of the filter or use minimal delay for kausal system
        delay  %specify a fixed delay of the filter

        lpf_active
        filtertype
        lpf_bw
        H_lpf

        block_dc

        %during construction

        %during process
        Nout
    end

    methods
        function obj = Scope(options)
            %SCOPE Construct an instance of this class
            %   Detailed explanation goes here
            arguments
                options.fsimu
                options.fadc
                options.adcresolution
                options.quantbuffer

                options.rand_samplingdelay = 0;
                options.samplingdelay = 0;

                options.freq_offset = 0;
                options.samp_jitter = 0;

                options.fixed_delay = 0;
                options.delay = 0;

                options.lpf_active = 0;
                options.filtertype = filtertypes.bessel_bilin;
                options.lpf_bw = 120e9;
                options.H_lpf;

                options.block_dc = 1;
            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)

            % actual processing of the signal
            signalclass_in.signal = obj.process_(signalclass_in.signal);
            signalclass_in.fs = obj.fadc;

            % apply LPF
            % 4. Apply LPF on the signal
            assert (signalclass_in.fs == obj.H_lpf.fs)
            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
                    obj.H_lpf = Filter('filtdegree',3,"f_cutoff",obj.lpf_bw,"fsamp",obj.fsimu,"filterType",obj.filtertype);
                    signalclass_in = obj.H_lpf.process(signalclass_in);
                end
            end

            % cast the electrical signal to information signal
            signalclass_in = Informationsignal(signalclass_in,"fs",obj.fadc,"logbook",signalclass_in.logbook);

            % append to logbook
            lbdesc = ['Scope '];
            signalclass_in  = signalclass_in.logbookentry(lbdesc);

            % write to output
            signalclass_out = signalclass_in;

        end

        function yout = process_(obj,xin)
            %METHOD1 Summary of this method goes here
            %   Detailed explanation goes here

            % sample signal
            % TODO: implement and test the delays. 

            % resample
            yout = resample(xin,obj.fadc,obj.fsimu);

            % quantize signal
            yout = obj.quantize(yout);

            if obj.block_dc
                yout = yout-mean(yout,1);
            end

        end

        function jitvec = buildSamplingJitterVector(obj)
            % Generation of sampling jitter vector for real part of signal
            jitvec = obj.samp_jitter*randn(obj.Nout,1) ;

            % Avoid exceding the simulation block edges relentlessly!
            jitvec(1)   = abs(jitvec(1)) ;
            jitvec(end) = -abs(jitvec(end)) ;

        end

        function sampout = sampleSignal(obj,xin)

            % Initialization sampler and output signal in case of 4real

            Tout = 1 / obj.fadc; % output signal sample period [sec]

            Tsig = length(xin) / obj.fsimu ;      % simulation block period [sec]

            if obj.rand_samplingdelay == 1
                obj.samplingdelay = Tout*rand ;
            end

            %get number of delayed samples
            Ndelay        = floor(obj.samplingdelay*obj.fsimu) ;

            %get fractional sample delay
            fracdelay     = obj.samplingdelay - Ndelay/obj.fsimu ;

            %calc number of samples after down-sampling no offset
            obj.Nout  = ceil((Tsig - fracdelay)*obj.fadc) ;

            %build a jitter vector
            jitvec = obj.buildSamplingJitterVector();

            % Create the index vector of the sampling instances for the real and imaginary part
            inx_re = ( (0: Tout : (obj.Nout-1) / obj.fadc)' + jitvec ) * obj.fsimu + 1 ;

            % Create the vector of the relative location of each sample between
            % to neighboring "Analog" samples for the real and imaginary part
            val_re = mod(inx_re,1) ;

            % Shift the signal
            xin = circshift(xin, [0 - Ndelay]) ;

            % Sample the signal using linear interpolation
            sampout = xin(floor(inx_re)) .* (1 - val_re) + xin(ceil(inx_re)) .* val_re ;

            sampout2 = resample(xin,obj.fadc,obj.fsimu);

        end

        function quantout = quantize(obj,xin)

            quant_steps = round(2^obj.adcresolution) + rem(round(2^obj.adcresolution),2);

            if quant_steps > 0

                index = round(0.1*length(xin)); %sioe 2022: short blocksize resulted in error -> now 10%-90% of length are evaluated

                max_re = max(real(xin(index:end-index)))*(1+obj.quantbuffer/2); % adaptations in the indices
                min_re = min(real(xin(index:end-index)))*(1-obj.quantbuffer/2); %(1+para.max/2);

                range_re = max_re-min_re;

                max_im = max(imag(xin))*(1+obj.quantbuffer/2);
                min_im = min(imag(xin))*(1+obj.quantbuffer/2);

                range_im = max_im-min_im;

                if isreal(xin)
                    % shift signal and clip to quantizer intervall
                    xin =   min(max(xin-min_re,0),range_re);

                    % quantize signal
                    xin = round((quant_steps-1)/range_re*xin);

                    % scale and shift back
                    quantout = xin*range_re/(quant_steps-1) +min_re;

                else

                    % shift signal and clip to quantizer intervall
                    xin_re =      min(max(real(xin)-min_re,0),range_re);
                    xin_im =      min(max(imag(xin)-min_im,0),range_im);

                    % quantize signal

                    % quantize signal and shift back signal
                    xin_re = round((quant_steps-1)/range_re*xin_re);
                    xin_im = round((quant_steps-1)/range_im*xin_im);

                    % scale and shift back
                    quant_out_re = xin_re*range_re/(quant_steps-1) + min_re;
                    quant_out_im = xin_im*range_im/(quant_steps-1) + min_im;
                    quantout = quant_out_re + 1i * quant_out_im;

                end
            else
                quantout = xin;
            end

        end

    end
end

