% RECEIVER 
%
% This is the receiver structure that you will have to complete.
% The function: receiver(fc) is a setup function for the receiver. Here,
% the audiorecorder object is initialized (see help audiorecorder or
% MATLAB's homepage for more information about the object).
% 
% The callback function audioTimerFcn() is a callback function that is
% triggered on a specified time interval (here it is determined in the
% setup function, by the variable time_value)
% 
% Your task is to extend this code to work in the project!
%%

function [audio_recorder] = receiver(fc)

rx_fs = 48000/2; %sampling frequency
audio_recorder = audiorecorder(rx_fs,16,1);% create the recorder. Full 24 bits not needed.


%attach callback function
time_value = 0.2; % how often the function should be called in seconds
set(audio_recorder,'TimerPeriod',time_value,'TimerFcn',@audioTimerFcn); % attach a function that should be called every second, the function that is called is specified below.


%ADD USER DATA FOR CALLBACK FUNCTION (DO NOT CHANGE THE NAMES OF THESE VARIABLES!)
 audio_recorder.UserData.receive_complete = 0; % this is a flag that the while loop in the GUI will check
 audio_recorder.UserData.pack  = []; %allocate for data package
 audio_recorder.UserData.pwr_spect = []; %allocate for PSD
 audio_recorder.UserData.const = []; %allocate for constellation
 audio_recorder.UserData.eyed  = []; %allocate for eye diagram


% %%%%%%%%

audio_recorder.UserData.previous = [];  %Initialise a previous recorder variable
audio_recorder.UserData.timecounter = 0;         %Initialise a counter that keeps track of how many times some loops have been run. 
% audio_recorder.UserData.currentrecording = [];   %Initialise the currentrecording variable, which should keep the current recording inside it!
% %%%%%%%%%%%%%%%

audio_recorder.UserData.fc = fc;
tx_fs = 12e3;
Rb 			= 480;          % bit rate [bit/sec]    
Ts 			= 1/tx_fs;  % Sample time [s/Sample]
span    	= 6;   % Decides truncation for used pulse
rolloff 	= 0.4; % Roll-off factor (ie. alpha) for Raised Cosine pulse.
constellation = generateConstellation('8-PSK');
M 				= length(constellation); % Number of symbols in the constellation
bpsymb 		= log2(M);  % Number of bits per symbol                    
Rsymb  		= Rb/bpsymb;% Symbol Rate
Tsymb 		= 1/Rsymb;   % symbol time
sps    		= tx_fs/Rsymb; % Samples per symbol. [Sample/s / Symbol/s = Sample/Symbol]
OS          = rx_fs/tx_fs;

audio_recorder.UserData.Rb 			   = Rb; 			
audio_recorder.UserData.Ts 			   = Ts; 			
audio_recorder.UserData.span    	   = span;    	
audio_recorder.UserData.rolloff 	   = rolloff; 	
audio_recorder.UserData.constellation  = constellation;
audio_recorder.UserData.M 			   = M; 			
audio_recorder.UserData.bpsymb 		   = bpsymb; 		
audio_recorder.UserData.Rsymb  		   = Rsymb;  		
audio_recorder.UserData.Tsymb 		   = Tsymb; 		
audio_recorder.UserData.sps    		   = sps;    
audio_recorder.UserData.OS    		   = OS; 
audio_recorder.UserData.rx_fs          = rx_fs;
audio_recorder.UserData.tx_fs          = tx_fs;
audio_recorder.UserData.prefound       = 0; %Initialises a preamble found variable, which flags whether or not a preamble has been found
audio_recorder.UserData.initialcontact = [];%Used to store the recording where the preamble was found!
audio_recorder.UserData.peak_val = 0;       %Used to store the peak value of the correlation so that it can be used later on
audio_recorder.UserData.peak_idx = 0;       %Used to store the index of the peak value of the correlation so that it can be used later on
%% Preambles
B2 =  [1 -1];       % Barker-2
B3 =  [1 1 -1];     % Barker-3
B4 =  [1 1 -1 1];   % Barker-4
B5 =  [1 1 1 -1 1]; % Barker-5%
B7 =  [1 1 1 -1 -1 1 -1];               % Barker-7
B11 = [1 1 1 -1 -1 -1 1 -1 -1 1 -1];   % Barker 11
B13 = [1 1 1 1 1 -1 -1 1 1 -1 1 -1 1];  % Barker-13
preamble = kron(B7,B7); % Choose preamble type.
preamble_symbs = preamble; % OPTIONAL. Map preamble to symbols.



%% Preamble-pulse modulation
[pulse_OS ~]  = rtrcpuls(rolloff, Tsymb, rx_fs, span); % Generate basis pulse.
preamble_upsample = upsample(preamble, OS*sps);
preamble_pulse_mod = conv(pulse_OS, preamble_upsample);
audio_recorder.UserData.preamble_pulse_mod = preamble_pulse_mod;
% Length of transient before and after pulse modulated preamble, due to convolution.
transient_length = (length(preamble_pulse_mod)-length(preamble_upsample))/2;
preamble_pulse_mod = preamble_pulse_mod(transient_length+1:end-(transient_length+sps*OS)); 
audio_recorder.UserData.preamble_pulse_mod = preamble_pulse_mod;
% Save upsampled, pulse modulated, truncated preamble to struct.
audio_recorder.UserData.preamble_pulse_mod = preamble_pulse_mod;
% Save preamble bits to struct.
audio_recorder.UserData.preamble = preamble;


%ADD USER DATA FOR CALLBACK FUNCTION
audio_recorder.UserData.counter = 1; %initialize a counter in the structure UserData
audio_recorder.UserData.tx_fs = tx_fs;
audio_recorder.UserData.rx_fs = rx_fs;
audio_recorder.UserData.fc = fc;



record(audio_recorder); %start recording
end


% CALLBACK FUNCTION
% This function will be called every [time_value] seconds, where time_value
% is specified above. Note that, as stated in the project MEMO, all the
% fields: pwr_spect, eyed, const and pack need to be assigned if you want
% to get outputs in the GUI.


function audioTimerFcn(recObj, event, handles)
disp('Callback triggered')

tx_fs = recObj.UserData.tx_fs;  %The frequency with which we are transmitting, 12kHz
rx_fs = recObj.UserData.rx_fs;  %The frequency with which we are receiving, 48kHz
fc = recObj.UserData.fc;        %Carrier frequency
% Read from sctruct into local vars.
Rb 			   = recObj.UserData.Rb 			 ;
Ts 			   = recObj.UserData.Ts 			 ;
span    	   = recObj.UserData.span    	     ;
rolloff 	   = recObj.UserData.rolloff 	     ;
constellation  = recObj.UserData.constellation   ;
M 			   = recObj.UserData.M 			     ;
bpsymb 		   = recObj.UserData.bpsymb 		 ;
Rsymb  		   = recObj.UserData.Rsymb  		 ;
Tsymb 		   = recObj.UserData.Tsymb 		     ;
sps    		   = recObj.UserData.sps    		 ;
OS             = recObj.UserData.OS;
preamble       = recObj.UserData.preamble;





 if recObj.UserData.prefound == 0   %If no event (preamble) has been detected yet

    rx_sig = getaudiodata(recObj);         %Save the current recording so that it can be used in the next call
     
    rx_sig_waveform = rx_sig';

    t_s_OS = (0:length(rx_sig_waveform)-1).*(1/rx_fs); % Redefine time axis for our received signal % Signal time-axis ie. x[n]*n*Ts.
    rx_I =  rx_sig_waveform.*cos(2*pi*fc*t_s_OS);
    rx_Q = -rx_sig_waveform.*sin(2*pi*fc*t_s_OS);
     %% Recombine
    rx_sig = rx_I + 1i*rx_Q;


     %% Match-filter
    [pulse_OS ~]  = rtrcpuls(rolloff, Tsymb, rx_fs, span); % Generate basis pulse.
    
    MF = fliplr(conj(pulse_OS));        %create matched filter impulse response
    MF_out = filter(MF, 1, rx_sig);
    MF_out = MF_out(length(MF):end);   %remove transient
    MF_out_normalized = MF_out./( max(abs(MF_out))); % Normalize, so that peak correlation detection is more robust.
     


    %  Correlate
    preamble_pulse_mod = recObj.UserData.preamble_pulse_mod;    %Fetch the modulated preamble pulse.
    corr = conv(MF_out_normalized, fliplr(preamble_pulse_mod));    %Correlation of output from matched filter and the expected preamble
    [recObj.UserData.peak_val ,recObj.UserData.peak_idx] = max(corr); % Find index of correlation peak.
    disp(abs(recObj.UserData.peak_val))
   % figure(3)
  %  plot(abs(corr)) %debugging reasons
    if abs(recObj.UserData.peak_val) > 150
        disp('Preamble detected, starting to receive...')   %love when this message pops up...
        recObj.UserData.prefound       = 1; %indicates that a preamble has been found.
        recObj.UserData.initialcontact = rx_sig;
    end
    
elseif  recObj.UserData.prefound == 1 %In the case that an event has been detected  
    
    disp(num2str(recObj.UserData.timecounter))
    if  recObj.UserData.timecounter == 0 %if it is the first time entering this loop 
%         disp('first')
         %stop(recObj)
         %record(recObj); %restarts the recorder again, so that it is reset
         recObj.UserData.timecounter = recObj.UserData.timecounter + 1;
%         
   
    
    elseif recObj.UserData.timecounter <= 8 %If the recording has been going on for less than 1 second
%         disp('second')
         recObj.UserData.timecounter = recObj.UserData.timecounter + 1;
           
    else
        
        recObj.UserData.timecounter = 0;   %Set the timecounter to 0, preparing it for the next time there is a transmission

        disp('Decoding received message...')
        recObj.UserData.prefound = 0;

        recObj.UserData.peak_val = 0;         
        stop(recObj)  

        recObj.UserData.prefound = 0;  %Make sure that we reset the event, so that we start scanning again  
        rx_sig = getaudiodata(recObj)';


        t_s_upsample = (0:length(rx_sig)-1).*(1/rx_fs); % Redefine time axis for our received signal % Signal time-axis ie. x[n]*n*Ts.
        t_s = t_s_upsample;
        rx_I =  rx_sig.*cos(2*pi*fc*t_s);
        rx_Q = -rx_sig.*sin(2*pi*fc*t_s);

        rx_I_downsampled= downsample(rx_I, OS);
        rx_Q_downsampled = downsample(rx_Q, OS);


        % Matched filter
        [pulse ~] = rtrcpuls(rolloff, Tsymb, tx_fs, span); % Generate basis pulse.
        MF = fliplr(conj(pulse));        %create matched filter impulse response
        MF_I_output = filter(MF, 1, rx_I_downsampled); 
        MF_I_output = MF_I_output(length(MF):end); %remove transient
        MF_Q_output = filter(MF, 1, rx_Q_downsampled); 
        MF_Q_output = MF_Q_output(length(MF):end); %remove transient

        avg = zeros(sps, 1);

        MF_output_complex = MF_I_output + i.*MF_Q_output;

        for delta = 1:sps
            shifted_MF_output = circshift(MF_output_complex, delta-1);
            shifted_samples = shifted_MF_output(1:sps:end);
            avg(delta) = sum(abs(shifted_samples))./length(shifted_samples);
        end

        [~, max_idx] = max(avg);
        sync_offset = max_idx-1;

        MF_I_output_shifted = circshift(MF_I_output, sync_offset);
        MF_Q_output_shifted = circshift(MF_Q_output, sync_offset);
        rx_symbs = [MF_I_output_shifted(1:sps:end)', MF_Q_output_shifted(1:sps:end)'];

        % Unmapping to symbols
        samples = rx_symbs(:,1) + 1i.*rx_symbs(:,2); 

        % Preamble correlation
        sample_corr = conv(samples, fliplr(preamble));
        [~,peak_idx] = max(sample_corr);

        phzError = angle(sample_corr(peak_idx));%-pi/4 % Offset by nominal phase of preamble.
        samples_phzCorrected = samples.*exp(1i*-phzError); 

        %% Normalize received sample energy
        samples_phzCorrected = samples_phzCorrected.* (1./abs(samples(peak_idx)));

        % Plot phase-corrected scatterplot.
        %     figure
        %     hold on
        %     plot(samples_phzCorrected, 'ob'); title('Phase-corrected rx. Decision regions overlaid.')
        %     if length(constellation)>2
        %         voronoi(real(constellation), imag(constellation))
        %     end
        %     plot(real(constellation), imag(constellation), 'or')
        %     grid on; axis square;
        %     xlim([-2 2]);
        %     ylim([-2 2]);
        %     hold off
        % Minimum euclidean distance. Unmap symbols to messages
        d = abs(repmat(samples_phzCorrected, 1, length(constellation)) - repmat(constellation, length(samples), 1)).^2; % Compute distance of every sample symbol to every constellation point.
        [~, nearest_idx] = min(d, [], 2);  % Get index of value with minimum distance to a constellation point. Traverse along column.

        rx_mapped = constellation(nearest_idx); % Choose corresponding symbol.

        % Symbols to messages
        m_hat = rx_mapped(peak_idx+1:end); % Get symbols after preamble -> actual message.

        % Messages to bits
        b_hat_buffer = de2bi(nearest_idx(peak_idx+1:end)-1, bpsymb, 'left-msb')';
        b_hat = b_hat_buffer(1:432); %write as a vector
        
        %% Debug received string
        b_hat_debug = b_hat(1:432)';
        b_hat_trunc = b_hat_debug(1:end-rem(length(b_hat_debug), 8)); % Truncate received bitstream so that it's divisible by 7 (ASCII length).
        disp(['Received message (quote marks added): "' bitStream2string(b_hat_trunc) '"'])

         
         
         
         
         
         
         
    %------------------------------------------------------------------------------
    % HOW TO SAVE DATA FOR THE GUI
    %   NOTE THAT THE EXAMPLE HERE IS ONLY USED TO SHOW HOW TO OUTPUT DATA
    %------------------------------------------------------------------------------

    % Step 1: save the estimated bits
    recObj.UserData.pack = b_hat;
    % 
    % 
    % % Step 2: save the sampled symbols
    recObj.UserData.const = samples_phzCorrected(peak_idx+1:end);
    % 
    % % Step 3: provide the matched filter output for the eye diagram
    
    
    sync_MF_out = MF_I_output_shifted + 1i*MF_Q_output_shifted;
    sync_MF_out = sync_MF_out(peak_idx*sps+sps:end);    % Remove preamble.
    sync_MF_out = sync_MF_out.*exp(1i*-phzError); % Compensate for phase-offset.
    sync_MF_out = sync_MF_out.*(1./abs(samples(peak_idx))); % Normalize as before.
    
    recObj.UserData.eyed.r = sync_MF_out;
    recObj.UserData.eyed.fsfd = sps;
    % 
    % % Step 4: Compute the PSD and save it. 
    % % !!!! NOTE !!!! the PSD should be computed on the BASE BAND signal BEFORE matched filtering
    [pxx, f] = pwelch(rx_sig' ,1024,768,1024, rx_fs); % note that pwr_spect.f will be normalized frequencies
    f = fftshift(f); %shift to be centered around fs
    f(1:length(f)/2) = f(1:length(f)/2) - rx_fs; % center to be around zero
    p = fftshift(10*log10(pxx/max(pxx))); % shift, normalize and convert PSD to dB
    recObj.UserData.pwr_spect.f = f;
    recObj.UserData.pwr_spect.p = p;

    % In order to make the GUI look at the data, we need to set the
    % receive_complete flag equal to 1:
    recObj.UserData.receive_complete = 1; 

    disp('Hello world')



    end      
 end


end
