1190 lines
41 KiB
Matlab
1190 lines
41 KiB
Matlab
classdef Signal
|
|
%SIGNAL Summary of this class goes here
|
|
% Detailed explanation goes here
|
|
|
|
properties
|
|
signal
|
|
logbook
|
|
fs
|
|
|
|
gitSHA
|
|
gitStatus
|
|
gitPatch
|
|
end
|
|
|
|
methods
|
|
function obj = Signal(signal,options)
|
|
%SIGNAL Construct an instance of this class
|
|
% Detailed explanation goes here
|
|
arguments
|
|
signal
|
|
options.fs = [];
|
|
end
|
|
|
|
obj.signal = signal;
|
|
obj.signal = obj.signal;
|
|
obj.fs = options.fs;
|
|
|
|
[~,obj.gitSHA] = system('git rev-parse HEAD');
|
|
[~,obj.gitStatus] = system('git status --porcelain');
|
|
% [~,obj.gitPatch] = system('git diff');
|
|
|
|
%%% Stuff for Logbook %%%
|
|
SignalType = [];
|
|
TimeStamp = [];
|
|
Length = [];
|
|
SignalPower = [];
|
|
Nase = [];
|
|
SignalCopy = [];
|
|
ModifierName = [];
|
|
ModifierCopy= {};
|
|
Description = [];
|
|
|
|
obj.logbook = table(SignalType,TimeStamp,Length,SignalPower,Nase,SignalCopy,ModifierName, ModifierCopy, Description);
|
|
|
|
|
|
end
|
|
|
|
%% CONVERT TO INFORMATIONSIGNAL
|
|
function [i_sig, varargout] = Informationsignal(obj,options)
|
|
arguments
|
|
obj
|
|
options.fs
|
|
options.logbook
|
|
end
|
|
|
|
if isa(obj,'Electricalsignal')
|
|
|
|
%convert to information
|
|
varargout{1} = obj.fs;
|
|
i_sig = Informationsignal(obj.signal,"fs",options.fs,"logbook",options.logbook);
|
|
|
|
elseif isa(obj,'Opticalsignal')
|
|
|
|
error("Cannot convert from optical- to informationsignal. Use O/E conversion first.");
|
|
|
|
end
|
|
|
|
end
|
|
|
|
%% CONVERT TO Electricalsignal
|
|
function [e_sig, varargout] = Electricalsignal(obj,options)
|
|
|
|
arguments
|
|
obj
|
|
options.fs
|
|
options.logbook
|
|
end
|
|
|
|
obj.logbook = options.logbook;
|
|
|
|
if isa(obj,'Opticalsignal')
|
|
|
|
%convert to electrical
|
|
varargout{1} = obj.nase;
|
|
varargout{2} = obj.lambda;
|
|
e_sig = Electricalsignal(obj.signal,"fs",obj.fs,"logbook",obj.logbook);
|
|
|
|
elseif isa(obj,'Informationsignal')
|
|
|
|
try
|
|
% specify fs at varargin{1}
|
|
e_sig = Electricalsignal(obj.signal,"fs",options.fs,"logbook",options.logbook);
|
|
catch
|
|
error("Signal Conversion failed [I -> E] ");
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
%% CONVERT TO Opticalsignal
|
|
function o_sig = Opticalsignal(obj, options)
|
|
|
|
arguments
|
|
obj
|
|
options.fs
|
|
options.logbook
|
|
options.nase
|
|
options.lambda
|
|
options.polrot
|
|
end
|
|
|
|
fn = fieldnames(options);
|
|
|
|
for l = 1:numel(fn)
|
|
try
|
|
obj.(fn{l}) = options.(fn{l});
|
|
end
|
|
end
|
|
|
|
if isa(obj,'Electricalsignal')
|
|
|
|
%convert to optical
|
|
|
|
o_sig = Opticalsignal(obj.signal,"fs",obj.fs,"lambda",options.lambda,"logbook",obj.logbook,"nase",options.nase,"polrot",options.polrot);
|
|
|
|
|
|
elseif isa(obj,'Informationsignal')
|
|
|
|
error("Cannot convert from information- to opticalsignal. Use E/O conversion first.");
|
|
|
|
end
|
|
|
|
end
|
|
|
|
%%
|
|
function plot(obj, options)
|
|
% signal to plot: obj.signal
|
|
% fsamp : obj.fs (e.g. 92e9 => 92 GHz)
|
|
% length: length(obj.signal)
|
|
|
|
arguments
|
|
obj
|
|
options.fignum = randi(1000)
|
|
options.displayname = '';
|
|
options.timeframe = 0;
|
|
options.clear = 0;
|
|
options.color = [];
|
|
end
|
|
|
|
figure(options.fignum); % If figure does not exist, create new figure
|
|
if options.clear
|
|
clf
|
|
end
|
|
|
|
% 2) Plot into the figure handle found or created in one
|
|
t = (0:length(obj.signal)-1) / obj.fs; % time vector
|
|
|
|
if options.timeframe ~= 0
|
|
%only show a certain timeframe of signal
|
|
t = t(t<options.timeframe);
|
|
end
|
|
% 2 a) Actual plot (hold on, displayname??)
|
|
|
|
dn = options.displayname;
|
|
|
|
if isa(obj,'Opticalsignal')
|
|
sig = abs(obj.signal).^2;
|
|
else
|
|
sig = obj.signal;
|
|
end
|
|
|
|
hold on;
|
|
if isempty(options.color)
|
|
plot(t* 1e6, sig(1:length(t)), 'DisplayName', dn, 'LineWidth', 0.1, 'Marker', '.', 'LineStyle','none', 'MarkerSize', 0.1);
|
|
else
|
|
plot(t* 1e6, sig(1:length(t)), 'DisplayName', dn, 'LineWidth', 0.1, 'Marker', '.', 'LineStyle','none', 'MarkerSize', 0.1,'Color',options.color);
|
|
end
|
|
% 2 c)
|
|
% - xlabel if not already here: time in readable format (1 ms and not 1e-3 s)
|
|
% - ylabel amplitude
|
|
if isempty(get(gca, 'XLabel').String)
|
|
xlabel('Time (mu s)');
|
|
end
|
|
|
|
if isempty(get(gca, 'YLabel').String)
|
|
ylabel('Amplitude');
|
|
end
|
|
|
|
|
|
% Add legend if not already present
|
|
if isempty(get(gca, 'Legend'))
|
|
legend;
|
|
end
|
|
|
|
hold off;
|
|
|
|
end
|
|
|
|
%% Add signals from one signal to another, the first object will sustain
|
|
function Sum = plus(X,y)
|
|
|
|
if isa(y,'Signal')
|
|
Sum = X;
|
|
Sum.signal = X.signal + y.signal;
|
|
elseif isnumeric(y)
|
|
Sum = X;
|
|
Sum.signal = X.signal + y;
|
|
end
|
|
|
|
end
|
|
|
|
%% Add signals from one signal to another, the first object will sustain
|
|
function Diff = minus(X,y)
|
|
|
|
if isa(y,'Signal')
|
|
Diff = X;
|
|
Diff.signal = X.signal - y.signal;
|
|
elseif isnumeric(y)
|
|
Diff = X;
|
|
Diff.signal = X.signal - y;
|
|
end
|
|
|
|
end
|
|
|
|
function Product = times(X,y)
|
|
|
|
if isa(y,'Signal')
|
|
Product = X;
|
|
Product.signal = X.signal .* y.signal;
|
|
elseif isnumeric(y)
|
|
Product = X;
|
|
Product.signal = X.signal .* y;
|
|
end
|
|
|
|
end
|
|
|
|
%% Display length
|
|
function return_length = length(obj)
|
|
%METHOD1 Summary of this method goes here
|
|
% Detailed explanation goes here
|
|
return_length = length(obj.signal);
|
|
end
|
|
|
|
%% Write Logbook Entry
|
|
function obj = logbookentry(obj,varargin)
|
|
|
|
if nargin == 2
|
|
Description = varargin{1};
|
|
CallingModifier = evalin('caller','obj');
|
|
elseif nargin == 3
|
|
Description = varargin{1};
|
|
CallingModifier = varargin{2};
|
|
else
|
|
Description = "";
|
|
CallingModifier = evalin('caller','obj');
|
|
end
|
|
|
|
if isa(CallingModifier,"Signal")
|
|
CallingModifierStruct = obj.objToStructFilteredRecursive(CallingModifier);
|
|
ModifierCopy = {CallingModifierStruct};
|
|
end
|
|
|
|
ModifierCopy = {CallingModifier};
|
|
|
|
SignalType = [string(class(obj))];
|
|
TimeStamp = [(datetime('now','TimeZone','local','Format','HH:mm:ss'))];
|
|
Length = num2str(obj.length, ['%' sprintf('.%df', 0)]);%[obj.length];
|
|
SignalPower = [obj.power];
|
|
Nase = [0];
|
|
SignalCopy = obj.signal;
|
|
SignalCopy = [];
|
|
ModifierName = class(CallingModifier);
|
|
|
|
cell = {SignalType , TimeStamp , Length , SignalPower(1) , Nase, SignalCopy, ModifierName, ModifierCopy, Description};
|
|
|
|
obj.logbook = [obj.logbook; cell];
|
|
|
|
end
|
|
|
|
function s = objToStructFilteredRecursive(~,obj)
|
|
% Convert the object to a structure using 'struct' and catch warnings
|
|
warnState = warning('off', 'MATLAB:structOnObject');
|
|
s = struct(obj); % Convert to struct
|
|
warning(warnState); % Restore previous warning state
|
|
|
|
% Get all field names of the struct
|
|
fields = fieldnames(s);
|
|
|
|
% Loop over each field and handle filtering
|
|
for i = 1:numel(fields)
|
|
fieldData = s.(fields{i});
|
|
|
|
if isstruct(fieldData) % If the field is a struct, call recursively
|
|
s.(fields{i}) = obj.objToStructFilteredRecursive(fieldData);
|
|
elseif numel(fieldData) > 1000 % Remove field if it has more than 1000 elements
|
|
%s = rmfield(s, fields{i});
|
|
s.(fields{i}) = [];
|
|
elseif isa(fieldData,'table')
|
|
s = rmfield(s, fields{i});
|
|
end
|
|
end
|
|
end
|
|
|
|
%% Resample Signal
|
|
function obj = resample(obj,options)
|
|
|
|
arguments
|
|
obj Signal
|
|
options.fs_in double = obj.fs
|
|
options.fs_out double
|
|
options.n double = 10;
|
|
options.beta double = 5;
|
|
end
|
|
|
|
if options.fs_in ~= obj.fs
|
|
warning('The signals fs is different from the given fs_in while it should be the same.');
|
|
end
|
|
|
|
if options.fs_in == options.fs_out
|
|
|
|
desc = ['No need to resample signal from ', num2str(options.fs_in*1e-9), ' GHz to ', num2str(options.fs_out*1e-9), ' GHz' ];
|
|
|
|
obj = obj.logbookentry(desc,obj);
|
|
|
|
else
|
|
|
|
obj.signal = resample(obj.signal,options.fs_out,options.fs_in,options.n,options.beta);
|
|
|
|
desc = ['resample signal from ', num2str(options.fs_in*1e-9), ' GHz to ', num2str(options.fs_out*1e-9), ' GHz' ];
|
|
|
|
obj = obj.logbookentry(desc,obj);
|
|
|
|
obj.fs = options.fs_out;
|
|
|
|
end
|
|
|
|
end
|
|
|
|
%%
|
|
function spectrum(obj,options)
|
|
|
|
arguments
|
|
obj
|
|
options.fignum = 2025
|
|
options.displayname = "";
|
|
options.color = [];
|
|
options.linestyle = '-';
|
|
options.normalizeToNyquist = 0;
|
|
options.addDCoffset = 0;
|
|
options.normalizeToDC = 0;
|
|
options.normalizeTo0dB = 0;
|
|
options.max_num_lines = []; % Leave empty or omit to disable line rotation
|
|
options.fft_length = [];
|
|
% --- NEW options ---
|
|
options.useWavelengthAxis (1,1) logical = false % plot x-axis in wavelength
|
|
options.lambda0_nm (1,1) double = 1310 % center wavelength [nm]
|
|
end
|
|
|
|
if isempty(options.fft_length)
|
|
options.fft_length = 2^(nextpow2(length(obj.signal))-9);
|
|
end
|
|
|
|
|
|
if options.normalizeToNyquist == 0
|
|
[p_lin,f_Hz] = pwelch(obj.signal, hanning(options.fft_length), ...
|
|
options.fft_length/2, options.fft_length, ...
|
|
obj.fs, "centered", "power", "mean");
|
|
f_GHz = f_Hz*1e-9; % keep frequency vector for frequency axis
|
|
else
|
|
[p_lin,f_rad] = pwelch(obj.signal, hanning(options.fft_length), ...
|
|
options.fft_length/2, options.fft_length, ...
|
|
"centered", "power", "mean");
|
|
% In normalized mode, pwelch returns rad/sample centered on 0.
|
|
% We'll keep f_rad for the x-axis in that mode.
|
|
end
|
|
|
|
% p_lin = movmean(p_lin,4);
|
|
|
|
if options.normalizeTo0dB
|
|
p_lin = p_lin ./ max(p_lin);
|
|
p_dbm = 10*log10(p_lin); % normalized to 0 dB
|
|
ylab = "Normalized PSD";
|
|
else
|
|
p_dbm = 10*log10(p_lin);
|
|
ylab = "Power (dB/Hz)";
|
|
end
|
|
|
|
% --- If requested, build wavelength axis from frequency offset ---
|
|
if options.useWavelengthAxis && options.normalizeToNyquist == 0
|
|
c = physconst('LightSpeed'); % [m/s]
|
|
lambda0_m = options.lambda0_nm*1e-9; % center wavelength [m]
|
|
f_c = c / lambda0_m; % carrier frequency [Hz]
|
|
|
|
% exact mapping
|
|
f_abs = f_c + f_Hz; % absolute frequency [Hz]
|
|
lambda_m = c ./ f_abs; % wavelength [m]
|
|
lambda_nm = lambda_m * 1e9; % wavelength [nm]
|
|
|
|
% assign axis
|
|
x_vec = lambda_nm(:);
|
|
x_label = "Wavelength [nm]";
|
|
|
|
% Sort to ensure axis is ascending
|
|
[x_vec, sortIdx] = sort(x_vec, 'ascend');
|
|
p_dbm = p_dbm(sortIdx, :);
|
|
else
|
|
% Frequency or normalized axes
|
|
if options.normalizeToNyquist == 0
|
|
x_vec = f_GHz;
|
|
x_label = "Frequency in GHz";
|
|
else
|
|
x_vec = f_rad; % normalized frequency in rad/sample
|
|
x_label = "Normalized Frequency";
|
|
end
|
|
end
|
|
|
|
figure(options.fignum);
|
|
ax = gca;
|
|
hold on
|
|
|
|
p_dbm = p_dbm+options.addDCoffset;
|
|
|
|
if options.normalizeToDC
|
|
[~,min_idx]=min(abs(f_GHz));
|
|
pow_at_dc = p_dbm(min_idx);
|
|
p_dbm = p_dbm-pow_at_dc;
|
|
end
|
|
% p_dbm = movmean(p_dbm,10);
|
|
|
|
for s = 1:min(size(p_dbm))
|
|
if isempty(options.color)
|
|
plot(x_vec, p_dbm(:,s), 'DisplayName', options.displayname, 'LineWidth', 1);
|
|
else
|
|
plot(x_vec, p_dbm(:,s), 'DisplayName', options.displayname, 'LineWidth', 1, 'Color', options.color,'LineStyle',options.linestyle);
|
|
end
|
|
end
|
|
|
|
% Limit number of lines if requested
|
|
if ~isempty(options.max_num_lines) && options.max_num_lines > 0
|
|
allLines = findall(ax, 'Type', 'Line');
|
|
if length(allLines) > options.max_num_lines
|
|
numToRemove = length(allLines) - options.max_num_lines;
|
|
delete(allLines(1:numToRemove));
|
|
end
|
|
end
|
|
|
|
% Axis labels and limits
|
|
xlabel(x_label);
|
|
|
|
if options.useWavelengthAxis && options.normalizeToNyquist == 0
|
|
xlim([min(x_vec) max(x_vec)]);
|
|
else
|
|
if options.normalizeToNyquist == 0
|
|
% Keep your existing freq handling (you can fine-tune as needed)
|
|
% xlim([-128 128]); % example for 256 GSa/s if desired
|
|
xlim([min(x_vec) max(x_vec)]);
|
|
else
|
|
xlim([-pi, pi]);
|
|
end
|
|
end
|
|
|
|
ylabel(ylab);
|
|
|
|
% --- Y-Axis scaling (auto with margin) ---
|
|
y_min = min(p_dbm(:));
|
|
y_max = max(p_dbm(:));
|
|
|
|
% Add 5% dynamic range margin on both sides
|
|
y_range = y_max - y_min;
|
|
if y_range == 0
|
|
y_range = 10; % fallback if flat
|
|
end
|
|
|
|
y_margin = 0.05 * y_range;
|
|
ylim([y_min - y_margin, y_max + y_margin]);
|
|
|
|
% Set ticks automatically, avoid overpopulation
|
|
try
|
|
yticks(round(linspace(y_min, y_max, min(10, max(4, ceil(y_range/10))))));
|
|
end
|
|
grid on;
|
|
|
|
% Add legend if not already present
|
|
if isempty(get(gca, 'Legend'))
|
|
legend;
|
|
end
|
|
|
|
|
|
end
|
|
|
|
|
|
function move_it_spectrum(obj,options)
|
|
arguments
|
|
obj
|
|
options.fignum
|
|
options.displayname = "";
|
|
options.color = [];
|
|
options.normalizeToNyquist = 0;
|
|
options.normalizeTo0dB = 0;
|
|
end
|
|
|
|
data_in = obj.signal;
|
|
|
|
if size(data_in,1) > size(data_in,2)
|
|
data_in = data_in';
|
|
end
|
|
|
|
for pol = 1:size(data_in,1)
|
|
|
|
%compute FFT of input
|
|
Data_in = fft( data_in(pol,:) );
|
|
|
|
%psd = Data_in.*conj(Data_in);
|
|
psd = Data_in;
|
|
|
|
%Use only magnitude of FFT (which was complex)
|
|
psd = abs(psd);
|
|
|
|
%Shift the spectrum to yield
|
|
psd = fftshift(psd);
|
|
|
|
%divide by N
|
|
psd = psd/length(data_in(pol,:));
|
|
|
|
psd_plot = 20*log10(psd);
|
|
|
|
% psd_plot = psd_plot - max(psd_plot);
|
|
|
|
|
|
%smoothing
|
|
% psd_smoothed = smooth(psd,1000);
|
|
%
|
|
% psd_smoothed = 10*log10(psd_smoothed);
|
|
|
|
% psd_smoothed = psd_smoothed - max(psd_smoothed);
|
|
|
|
carrier_power_time_dbm = 20*log10( mean(abs(data_in)) .^2 )+30; % dB -> +30 -> dBm
|
|
carrier_power_freq_dbm = max(psd_plot);
|
|
|
|
|
|
% psd_plot = psd_plot - max(psd_plot);
|
|
|
|
%% cspr
|
|
c = mean(data_in).^2;
|
|
s = mean(data_in.^2);
|
|
cspr = 10*log10(c / s);
|
|
|
|
testParseval = 1;
|
|
|
|
if testParseval == 1
|
|
|
|
E_FreqDomain =1/length(psd) * sum((psd.*length(psd)).^2);
|
|
|
|
%test parseval
|
|
E_TimeDomain = sum( (data_in(pol,:).^2) );
|
|
|
|
if isequal(round(E_FreqDomain,1),round(E_TimeDomain,1))
|
|
disp('Parseval is right!');
|
|
else
|
|
% disp('Something is wrong here?!');
|
|
end
|
|
end
|
|
|
|
figure(options.fignum); % If figure does not exist, create new figure
|
|
|
|
if 1
|
|
%Frequency Axis
|
|
freq_vec = linspace(-obj.fs/2,obj.fs/2,length(psd));
|
|
freq_vec = reshape(freq_vec,size(psd_plot));
|
|
|
|
|
|
if nargin == 4
|
|
p = plot(freq_vec*1e-9,psd_plot,'Linewidth',0.5,'DisplayName',options.displayname);
|
|
% plot(freq_vec*1e-9,psd_smoothed','Linewidth',1,'Color',[0 0 0],'DisplayName',[char(varargin{2}),' smoothed']);
|
|
|
|
else
|
|
p = plot(freq_vec*1e-9,psd_plot,'Linewidth',0.5);
|
|
% plot(freq_vec*1e-9,psd_smoothed','Linewidth',1,'Color',[1 1 1],'LineStyle',':');
|
|
|
|
end
|
|
|
|
%xlim([freq_vec(1)/1e9-2 freq_vec(end)/1e9+2])
|
|
xlabel('frequency [GHz]')
|
|
else
|
|
%Wavelength Axis
|
|
freq_vec = physconst('LightSpeed')*linspace(-obj.fs/2,obj.fs/2,length(psd))./((physconst('LightSpeed')/1310e-9)^2)*1e9;
|
|
freq_vec = freq_vec+1310;
|
|
if nargin == 4
|
|
plot(freq_vec',psd_plot,'Linewidth',0.5,'DisplayName',options.displayname)
|
|
else
|
|
plot(freq_vec,psd_plot','Linewidth',0.5);
|
|
end
|
|
|
|
%xlim([freq_vec(1)/1e9-2 freq_vec(end)/1e9+2])
|
|
xlabel('wavelength [nm]')
|
|
end
|
|
hold on
|
|
|
|
|
|
end
|
|
|
|
% xlim([-150 150])
|
|
% ylim([-100,0]);
|
|
ylabel('magnitude [dBm]')
|
|
legend
|
|
grid minor;
|
|
|
|
|
|
end
|
|
|
|
%% Power of signal
|
|
function pow = power(obj,options)
|
|
|
|
arguments
|
|
obj
|
|
options.unit power_notation = power_notation.dBm
|
|
end
|
|
|
|
pow = sum(mean(abs(obj.signal).^2));
|
|
|
|
switch options.unit
|
|
case power_notation.dBm
|
|
if isa(obj,'Electricalsignal')
|
|
pow = pow / 50;
|
|
end
|
|
pow = 10*log10(pow)+30; %dbm
|
|
|
|
case power_notation.mW
|
|
pow = pow .* 1e3; %mW
|
|
|
|
case power_notation.W
|
|
%pow = pow % Watt
|
|
end
|
|
|
|
end
|
|
|
|
%% Peak Power of Signal
|
|
function pow_pk = power_peak(obj,options)
|
|
|
|
arguments
|
|
obj
|
|
options.unit power_notation = power_notation.dBm
|
|
end
|
|
|
|
pow_pk = max(abs(obj.signal).^2); % dBm
|
|
|
|
switch options.unit
|
|
case power_notation.dBm
|
|
pow_pk = pow2db(pow_pk)+30; %dbm
|
|
case power_notation.mW
|
|
pow_pk = pow_pk .* 1e3; %mW
|
|
case power_notation.W
|
|
pow_pk = pow_pk; % Watt
|
|
end
|
|
|
|
end
|
|
|
|
%% PAPR of signal
|
|
function papr = papr_lin(obj)
|
|
%PAPR The peak-to-average power ratio (PAPR) is the peak amplitude squared (giving the peak power)
|
|
% divided by the RMS value squared (giving the average power).[1] It is the square of the crest factor.
|
|
% papr = max(abs(timesignal))^2 / rms(timesignal)^2; ODER papr = peak2rms(sig)^2;
|
|
|
|
papr = obj.power_peak("unit",power_notation.W) / obj.power("unit",power_notation.W); %linear
|
|
|
|
end
|
|
|
|
%% PAPR of signal
|
|
function papr_db = papr_db(obj)
|
|
% PAPR The peak-to-average power ratio (PAPR) is the peak amplitude squared (giving the peak power)
|
|
% divided by the RMS value squared (giving the average power).[1] It is the square of the crest factor.
|
|
% papr = max(abs(timesignal))^2 / rms(timesignal)^2; ODER papr = peak2rms(sig)^2;
|
|
|
|
papr = obj.power_peak("unit",power_notation.W) / obj.power("unit",power_notation.W);
|
|
papr_db = 10*log10(papr);
|
|
|
|
average_power = obj.power;
|
|
peak_power = obj.power_peak;
|
|
papr_db = peak_power - average_power; %db
|
|
|
|
end
|
|
|
|
%% Normalize
|
|
function obj = normalize(obj,options)
|
|
|
|
arguments
|
|
obj Signal
|
|
options.mode normalization_mode = normalization_mode.rms
|
|
end
|
|
|
|
switch options.mode
|
|
case normalization_mode.rms
|
|
obj.signal = obj.signal/sqrt(mean(abs(obj.signal).^2,"all"));
|
|
case normalization_mode.oneone
|
|
obj.signal = obj.signal - min(obj.signal);
|
|
obj.signal = obj.signal/max(abs(obj.signal));
|
|
obj.signal = (2*obj.signal) - 1;
|
|
end
|
|
|
|
end
|
|
|
|
%% Delay
|
|
function [obj] = delay(obj,delay,options)
|
|
|
|
arguments
|
|
obj Signal
|
|
delay double = 0
|
|
options.mode delay_mode = delay_mode.samples
|
|
end
|
|
|
|
if options.mode == delay_mode.samples
|
|
|
|
obj.signal=delayseq(obj.signal,delay);
|
|
% obj.signal=circshift(obj.signal,delay);
|
|
|
|
elseif options.mode == delay_mode.time
|
|
|
|
obj.signal=delayseq(obj.signal,delay,obj.fs);
|
|
|
|
end
|
|
|
|
end
|
|
|
|
%%
|
|
function [obj,S,inverted,sequenceFound,sequenceStarts] = tsynch(obj,options)
|
|
% time sync and cut
|
|
arguments
|
|
obj Signal
|
|
options.reference Signal
|
|
options.fs_ref = 0;
|
|
options.debug_plots = 0;
|
|
end
|
|
|
|
S = {};
|
|
inverted = -1;
|
|
sequenceFound = 0;
|
|
sequenceStarts = [];
|
|
|
|
%normalize the signal
|
|
a = obj.normalize("mode","oneone").signal;
|
|
|
|
%resample the reference
|
|
q = obj.fs/options.fs_ref;
|
|
b = options.reference.resample("fs_in",options.fs_ref,"fs_out",obj.fs).normalize("mode","oneone").signal;
|
|
|
|
max_occurences = floor(length(a)/length(b));
|
|
|
|
%estimate delay between signals
|
|
[co,lags] = xcorr(a,b);
|
|
[~,pos] = max(abs(co));
|
|
D = lags(pos);
|
|
|
|
%estimate start pos of signal
|
|
maxpeaknum = floor(length(a)/length(b));
|
|
try
|
|
[pks,pkpos,w,p] = findpeaks(abs(co./max(co)),'MinPeakDistance',length(b)/2,'MinPeakHeight',0.2,'NPeaks',maxpeaknum,'SortStr','descend');
|
|
catch
|
|
warning(['Error in findpeaks, ususally the seuqnece is too short. Max peak num: ', num2str(maxpeaknum)]);
|
|
return
|
|
end
|
|
|
|
pkpos = sort(pkpos);
|
|
|
|
% if mean(w) > 15 || mean(p) > 15
|
|
% return
|
|
% else
|
|
% sequenceFound = 1;
|
|
% end
|
|
|
|
if options.debug_plots
|
|
figure(121212);clf
|
|
subplot(1,2,1);
|
|
findpeaks(abs(co./max(co)),'MinPeakDistance',length(b)/2,'MinPeakHeight',0.2,'NPeaks',maxpeaknum,'SortStr','descend')
|
|
end
|
|
|
|
shifts = lags(pkpos);
|
|
sequenceStarts = shifts;
|
|
shifts = shifts(shifts>=0);
|
|
|
|
if numel(shifts) > 0
|
|
|
|
%Cut occurences of ref signal from signal (only positive shifts)
|
|
if all(sign(co(pkpos))==-1)
|
|
inverted = 1;
|
|
end
|
|
|
|
for c = shifts
|
|
sig = obj.delay(-c,'mode','samples');
|
|
sig.signal = sig.signal(1:length(b));% .* -inverted;
|
|
S{end+1,1} = sig;
|
|
end
|
|
|
|
% %return/keep the sinal with the highest correlation (only within positive shifts)
|
|
% [~,idx]=max(pks);
|
|
% obj.signal = S{idx}.signal;
|
|
% %put signal with highest corr. to first index in S array
|
|
% swap = S{1};
|
|
% S{1} = S{idx};
|
|
% S{idx} = swap;
|
|
|
|
for c = 1:numel(shifts)
|
|
S{c}.logbook = [];
|
|
end
|
|
|
|
else
|
|
|
|
%do nothing when shifts are negative or there are none...
|
|
|
|
end
|
|
|
|
%plot all synced signals and the ref signal
|
|
if options.debug_plots
|
|
figure(121212);hold on;
|
|
subplot(1,2,2);
|
|
for i = 1:size(S,1)
|
|
hold on
|
|
plot(S{i}.normalize('mode','oneone').signal(1000:1100),'LineWidth',0.1,'Color',[0.2157 0.4941 0.7216]);
|
|
plot(b(1000:1100),'LineWidth',1);
|
|
end
|
|
drawnow;
|
|
end
|
|
|
|
|
|
|
|
|
|
end
|
|
|
|
%%
|
|
function obj = filter(obj,a,b)
|
|
|
|
lbdesc = ['Filtering signal with H = a: ',num2str(a),' / b: ',num2str(b)];
|
|
obj = obj.logbookentry(lbdesc,obj);
|
|
|
|
obj.signal = filter(a,b,obj.signal);
|
|
end
|
|
|
|
%%
|
|
function er = extinctionratio(obj,fsym,M)
|
|
histpoints = 1024; %% verticale resolution
|
|
histpoints = floor(histpoints/2)*2+1; %% to have the eye digram centered around one point make the vertical resolution uneven
|
|
histpoints_horizontal = 512; %% horizontal resolution
|
|
hist_data=zeros(histpoints,histpoints_horizontal ); %% initilize eye diagram
|
|
|
|
if isa(obj,'Opticalsignal')
|
|
sig = abs(obj.signal).^2;
|
|
elseif isa(obj,'Electricalsignal')
|
|
sig = obj.signal;
|
|
else
|
|
sig = obj.signal;
|
|
end
|
|
|
|
x = (sig); %% make input signal rea)l
|
|
|
|
x = resample(x,fsym*histpoints_horizontal/2,obj.fs); %% up sample to original fsym rate
|
|
|
|
if mod(length(x),2)==1 %% if the signal lenght is not divisible by 2 (symbols displayed in the eye diagram are 2) remove last symbol
|
|
x = x(1:end-1);
|
|
end
|
|
|
|
eye_mat = reshape(x(1:end-mod(length(x),histpoints_horizontal)),histpoints_horizontal,floor(length(x)/histpoints_horizontal)); %% reshape signal into 256 rows each row has the histogram(eye data of all symbols)
|
|
|
|
maxA = max(sig(100:end-100));
|
|
minA = min(sig(100:end-100));
|
|
|
|
difference= maxA-minA;
|
|
|
|
data_ind_y=round((eye_mat-minA)/difference*(histpoints-1)) +1;
|
|
|
|
for n=1:size(data_ind_y,1)
|
|
nn=histcounts(data_ind_y(n,:),1:histpoints+1);
|
|
hist_data(:,n)=flip(nn.'); %without flip, the eye is upside down :-(
|
|
end
|
|
|
|
plot_data = 20*log10(hist_data);
|
|
plot_data(plot_data==-Inf) = 0;
|
|
|
|
maxall = 0;
|
|
|
|
for l = 1:size(plot_data,2)
|
|
[maxpk_,pos_] = max(plot_data(:,l));
|
|
if maxpk_ > maxall
|
|
maxall = maxpk_;
|
|
posxall = l;
|
|
posyall = pos_;
|
|
end
|
|
end
|
|
|
|
hist_interest = plot_data(:,posxall);
|
|
hist_interest_smoth = smooth(hist_interest,20);
|
|
[pk,loc] = findpeaks(hist_interest_smoth,"MinPeakDistance",40,"NPeaks",M,"MinPeakHeight",30);
|
|
|
|
for i = 1:numel(loc)
|
|
ppeak(i) = maxA - (difference/histpoints*loc(i));
|
|
end
|
|
|
|
|
|
|
|
if isa(obj,'Opticalsignal')
|
|
er=10*log10(ppeak(1)/ppeak(end));
|
|
elseif isa(obj,'Electricalsignal')
|
|
if mean([ppeak(1),ppeak(end)]) < 1e-2
|
|
disp("No Extiction Ration for Bipolar Electrical Signal. Calculating Outer OMA instead...")
|
|
er=max(ppeak)-min(ppeak);
|
|
else
|
|
er=10*log10(ppeak(1)/ppeak(end));
|
|
end
|
|
else
|
|
er=10*log10(ppeak(1)/ppeak(end));
|
|
end
|
|
|
|
if 0
|
|
findpeaks(hist_interest_smoth,"MinPeakDistance",40,"NPeaks",M,"MinPeakHeight",30);
|
|
end
|
|
|
|
|
|
end
|
|
|
|
%%
|
|
function eye(obj,fsym,M,options)
|
|
|
|
arguments
|
|
obj
|
|
fsym
|
|
M
|
|
options.fignum = 100;
|
|
options.displayname = "";
|
|
end
|
|
|
|
mode = 2;
|
|
|
|
histpoints = 2048; %% verticale resolution
|
|
histpoints = floor(histpoints/2)*2+1; %% to have the eye digram centered around one point make the vertical resolution uneven
|
|
histpoints_horizontal = 2048; %% horizontal resolution
|
|
hist_data=zeros(histpoints,histpoints_horizontal ); %% initilize eye diagram
|
|
|
|
if isa(obj,'Opticalsignal')
|
|
sig = abs(obj.signal).^2;
|
|
elseif isa(obj,'Electricalsignal')
|
|
sig = obj.signal;
|
|
else
|
|
sig = obj.signal;
|
|
end
|
|
|
|
startpos = floor(0.1*length(sig));
|
|
endpos = floor(0.9*length(sig));
|
|
endpos = min(endpos,startpos+200000);
|
|
x = sig(startpos:endpos); %% make input signal rea)l
|
|
|
|
x = resample(x,fsym*histpoints_horizontal/2,obj.fs); %% up sample to original fsym rate
|
|
|
|
if mod(length(x),2)==1 %% if the signal lenght is not divisible by 2 (symbols displayed in the eye diagram are 2) remove last symbol
|
|
x = x(1:end-1);
|
|
end
|
|
|
|
eye_mat = reshape(x(1:end-mod(length(x),histpoints_horizontal)),histpoints_horizontal,floor(length(x)/histpoints_horizontal)); %% reshape signal into 256 rows each row has the histogram(eye data of all symbols)
|
|
|
|
figure(options.fignum)
|
|
clf
|
|
if mode == 2
|
|
% generate "intuitive eye diagram" by drawing lines on top over
|
|
% each other; only draw 1000 lines, otherwise the plot is too
|
|
% crowded
|
|
|
|
|
|
col = cbrewer2('Set1',2);
|
|
for n=1:1000
|
|
hold on
|
|
plot(eye_mat(:,n),'LineStyle',':','LineWidth',0.1,'Color',col(2,:));
|
|
end
|
|
xlabel('Samples','Interpreter','latex')
|
|
ylabel('Amplitude of Signal','Interpreter','latex');
|
|
xlim([0 histpoints_horizontal])
|
|
|
|
elseif mode == 1
|
|
% generate eye diagram using histogram
|
|
|
|
maxA = max(sig(100:end-100))*1.3;
|
|
minA = min(sig(100:end-100))*1.3;
|
|
|
|
maxA = 0.12;
|
|
minA = -0.08;
|
|
|
|
difference= maxA-minA;
|
|
|
|
data_ind_y=round((eye_mat-minA)/difference*(histpoints-1)) +1;
|
|
|
|
for n=1:size(data_ind_y,1)
|
|
nn=histcounts(data_ind_y(n,:),1:histpoints+1);
|
|
hist_data(:,n)=flip(nn.'); %without flip, the eye is upside down :-(
|
|
end
|
|
|
|
|
|
|
|
plot_data = 20*log10(hist_data);
|
|
plot_data(plot_data==-Inf) = 0;
|
|
|
|
imagesc(plot_data);
|
|
|
|
% beautify
|
|
cm=flip(cbrewer2("RdYlBu",4096));
|
|
% cm=flip(cbrewer2("RdBu",4096));
|
|
% cm=flip(cbrewer2("Blues",4096));
|
|
cm(1,:) = [1,1,1]; % set zeros to white => clean background
|
|
|
|
colormap(cm);
|
|
% colormap('turbo');
|
|
% ax.CLim = [0 50];
|
|
if isa(obj,'Opticalsignal')
|
|
title(['Optical Eye ',options.displayname])
|
|
ylabel("Power in mW");
|
|
y_tickstring = string(linspace(maxA.*1e3,minA.*1e3,6));
|
|
min_ = min(abs(obj.signal(100:end-100)).^2);
|
|
max_ = abs(max(obj.signal(100:end-100)).^2);
|
|
elseif isa(obj,'Electricalsignal')
|
|
title(['Electrical Eye ',options.displayname])
|
|
ylabel("Voltage in V");
|
|
y_tickstring = string(linspace(maxA,minA,6));
|
|
min_ = min(obj.signal(100:end-100));
|
|
max_ = abs(max(obj.signal(100:end-100)));
|
|
else
|
|
title(['Digital Eye ',options.displayname])
|
|
ylabel("Digital Signal Amplitude");
|
|
y_tickstring = string(linspace(maxA,minA,6));
|
|
min_ = min(obj.signal(100:end-100));
|
|
max_ = abs(max(obj.signal(100:end-100)));
|
|
end
|
|
xlabel('Time in ps')
|
|
|
|
|
|
|
|
% add information
|
|
|
|
if 0
|
|
|
|
pwr_dbm = round(obj.power,3);
|
|
pwr_lin = obj.power("unit",power_notation.W);
|
|
|
|
papr_ = obj.papr_lin;%round(papr(obj.signal.^2),3);
|
|
|
|
yline( histpoints-(pwr_lin - minA)/difference*histpoints );
|
|
yline( histpoints-(min_ - minA)/difference*histpoints );
|
|
yline( histpoints-(max_ - minA)/difference*histpoints );
|
|
|
|
maxall = 0;
|
|
for l = 1:size(plot_data,2)
|
|
[maxpk_,pos_] = max(plot_data(:,l));
|
|
if maxpk_ > maxall
|
|
maxall = maxpk_;
|
|
posxall = l;
|
|
posyall = pos_;
|
|
end
|
|
end
|
|
|
|
hold on
|
|
xline(posxall)
|
|
|
|
|
|
|
|
try
|
|
hist_interest = plot_data(:,posxall);
|
|
hist_interest_smoth = smooth(hist_interest,20);
|
|
a = scatter(hist_interest_smoth+posxall,1:length(hist_interest_smoth),4,'.','MarkerEdgeColor','red');
|
|
|
|
[pk,loc] = findpeaks(hist_interest_smoth,"MinPeakDistance",10,"NPeaks",M,"MinPeakHeight",30,"MinPeakProminence",10);
|
|
|
|
scatter(posxall,loc,'red','Marker','x','LineWidth',2);
|
|
|
|
yline(loc,'Color','red','LineWidth',1,'LineStyle',':');
|
|
|
|
for i = 1:numel(loc)
|
|
ppeak(i) = maxA - (difference/histpoints*loc(i));
|
|
end
|
|
|
|
oma = false;
|
|
if isa(obj,'Opticalsignal')
|
|
er=10*log10(ppeak(1)/ppeak(end));
|
|
elseif isa(obj,'Electricalsignal')
|
|
if mean([ppeak(1),ppeak(end)]) < 1e-2
|
|
oma = true;
|
|
er=max(ppeak)-min(ppeak);
|
|
else
|
|
er=10*log10(ppeak(1)/ppeak(end));
|
|
end
|
|
else
|
|
er=10*log10(ppeak(1)/ppeak(end));
|
|
end
|
|
|
|
% Adjust position for the third box (slightly to the right)
|
|
boxPosition = [0.59 0.86 0.2 0.05]; % Adjusted position
|
|
|
|
% Create third annotation box for Vmax
|
|
if ~oma
|
|
thirdboxstring = ['ER (db):',num2str(er),' dB'];
|
|
else
|
|
thirdboxstring = ['OMA outer:',num2str(er),' V'];
|
|
end
|
|
|
|
plot_infos = 0;
|
|
if plot_infos
|
|
|
|
% Define properties
|
|
boxPosition = [0.15 0.86 0.2 0.05]; % Position for the first box [x y width height]
|
|
boxColor = [0.9 0.9 0.9]; % Light grey background color
|
|
boxEdgeColor = 'k'; % Black edge color
|
|
boxLineStyle = '--'; % Dashed line style
|
|
boxFontWeight = 'bold'; % Bold font
|
|
|
|
% Create first annotation box for Power
|
|
annotation('textbox', boxPosition, ...
|
|
'String', ['Power: ',num2str(pwr_dbm),' dBm'], ...
|
|
'BackgroundColor', boxColor, ...
|
|
'EdgeColor', boxEdgeColor, ...
|
|
'LineStyle', boxLineStyle, ...
|
|
'FontWeight', boxFontWeight, ...
|
|
'HorizontalAlignment', 'center');
|
|
|
|
% Adjust position for the second box (slightly to the right)
|
|
boxPosition = [0.37 0.86 0.2 0.05]; % Adjusted position
|
|
|
|
% Create second annotation box for PAPR
|
|
annotation('textbox', boxPosition, ...
|
|
'String', ['PAPR(lin):',num2str(papr_),''], ...
|
|
'BackgroundColor', boxColor, ...
|
|
'EdgeColor', boxEdgeColor, ...
|
|
'LineStyle', boxLineStyle, ...
|
|
'FontWeight', boxFontWeight, ...
|
|
'HorizontalAlignment', 'center');
|
|
|
|
|
|
|
|
annotation('textbox', boxPosition, ...
|
|
'String',thirdboxstring , ...
|
|
'BackgroundColor', boxColor, ...
|
|
'EdgeColor', boxEdgeColor, ...
|
|
'LineStyle', boxLineStyle, ...
|
|
'FontWeight', boxFontWeight, ...
|
|
'HorizontalAlignment', 'center');
|
|
end
|
|
|
|
end
|
|
|
|
grid off
|
|
|
|
end
|
|
|
|
|
|
yticks(linspace(0,histpoints,6));
|
|
y_tickstring = sprintfc('%.2f', y_tickstring);
|
|
yticklabels(y_tickstring);
|
|
|
|
xticks(linspace(0,histpoints_horizontal,6))
|
|
x_tickstring = sprintfc('%.2f', linspace(0, 2/fsym, 8) .* 1e12);
|
|
xticklabels(x_tickstring);
|
|
|
|
%
|
|
end
|
|
|
|
% disp('h');
|
|
%
|
|
% fsig = obj.fs;
|
|
% q = fsig/fsym;
|
|
%
|
|
% if q > 10 && isinteger(q)
|
|
% sig = (obj.signal);
|
|
% else
|
|
% sig = (obj.resample("fs_in",fsig,"fs_out",fsym*30).signal);
|
|
% q = 10;
|
|
% end
|
|
%
|
|
% figure()
|
|
% clf
|
|
% cursor = 200*q;
|
|
%
|
|
% for s = 1:700
|
|
% plot(sig(cursor-q:cursor+q),'Color','black','LineWidth',0.1,'LineStyle','-');
|
|
% hold on
|
|
% cursor=cursor+q;
|
|
% s=s+1;
|
|
% end
|
|
%
|
|
% ylim([-3 3]);
|
|
|
|
|
|
end
|
|
|
|
end
|
|
end
|
|
|