Ordnerstruktur
This commit is contained in:
@@ -344,14 +344,16 @@ classdef Signal
|
||||
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 = [];
|
||||
options.color = [];
|
||||
options.linestyle = '-';
|
||||
options.normalizeToNyquist = 0;
|
||||
options.normalizeToSamplingRate = 0;
|
||||
options.addDCoffset = 0;
|
||||
options.normalizeToDC = 0;
|
||||
options.normalizeTo0dB = 0;
|
||||
options.show_onesided = false;
|
||||
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]
|
||||
@@ -362,18 +364,21 @@ classdef Signal
|
||||
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
|
||||
useSamplingRateAxis = options.normalizeToSamplingRate ~= 0;
|
||||
useRadPerSampleAxis = options.normalizeToNyquist ~= 0 && ~useSamplingRateAxis;
|
||||
|
||||
if ~useRadPerSampleAxis && ~useSamplingRateAxis
|
||||
[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 modes, pwelch returns rad/sample centered on 0.
|
||||
% Divide by 2*pi for the f/fs axis where Nyquist is 0.5.
|
||||
end
|
||||
|
||||
% p_lin = movmean(p_lin,4);
|
||||
|
||||
@@ -387,45 +392,60 @@ classdef Signal
|
||||
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]
|
||||
if options.useWavelengthAxis && ~useRadPerSampleAxis && ~useSamplingRateAxis
|
||||
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;
|
||||
% assign axis
|
||||
x_vec = lambda_nm(:);
|
||||
x_label = "Wavelength [nm]";
|
||||
dc_axis = f_Hz;
|
||||
|
||||
% Sort to ensure axis is ascending
|
||||
[x_vec, sortIdx] = sort(x_vec, 'ascend');
|
||||
p_dbm = p_dbm(sortIdx, :);
|
||||
dc_axis = dc_axis(sortIdx);
|
||||
else
|
||||
% Frequency or normalized axes
|
||||
if ~useRadPerSampleAxis && ~useSamplingRateAxis
|
||||
x_vec = f_GHz;
|
||||
x_label = "Frequency in GHz";
|
||||
dc_axis = f_GHz;
|
||||
elseif useSamplingRateAxis
|
||||
x_vec = f_rad ./ (2*pi);
|
||||
x_label = "Normalized Frequency f/fs";
|
||||
dc_axis = x_vec;
|
||||
else
|
||||
x_vec = f_rad; % normalized frequency in rad/sample
|
||||
x_label = "Normalized Frequency [rad/sample]";
|
||||
dc_axis = x_vec;
|
||||
end
|
||||
end
|
||||
|
||||
if options.show_onesided
|
||||
keep_idx = dc_axis >= 0;
|
||||
x_vec = x_vec(keep_idx);
|
||||
dc_axis = dc_axis(keep_idx);
|
||||
p_dbm = p_dbm(keep_idx, :);
|
||||
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 = p_dbm+options.addDCoffset;
|
||||
|
||||
if options.normalizeToDC
|
||||
[~,min_idx]=min(abs(dc_axis));
|
||||
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))
|
||||
@@ -448,17 +468,27 @@ classdef Signal
|
||||
% 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
|
||||
if options.useWavelengthAxis && ~useRadPerSampleAxis && ~useSamplingRateAxis
|
||||
xlim([min(x_vec) max(x_vec)]);
|
||||
else
|
||||
if ~useRadPerSampleAxis && ~useSamplingRateAxis
|
||||
% 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)]);
|
||||
elseif useSamplingRateAxis
|
||||
if options.show_onesided
|
||||
xlim([0, 0.5]);
|
||||
else
|
||||
xlim([-0.5, 0.5]);
|
||||
end
|
||||
else
|
||||
if options.show_onesided
|
||||
xlim([0, pi]);
|
||||
else
|
||||
xlim([-pi, pi]);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ylabel(ylab);
|
||||
|
||||
|
||||
@@ -21,21 +21,22 @@ classdef Signalgenerator
|
||||
% "dimension", 2, ...
|
||||
% "randkey", 3).process();
|
||||
%
|
||||
% % PRMS bit matrix for PAM-4 mapping. If length is omitted, the
|
||||
% % output length is derived from order, as in the old PAMsource.
|
||||
% % PRMS bit matrix for PAM-4 mapping. M selects the mapper-ready bit
|
||||
% % shape. If length is omitted, the output length is derived from
|
||||
% % order, as in the old PAMsource.
|
||||
% bits = Signalgenerator( ...
|
||||
% "form", signalform.prms, ...
|
||||
% "dimension", 2, ...
|
||||
% "M", 4, ...
|
||||
% "order", 7).process();
|
||||
% symbols = PAMmapper(4, 0).map(bits);
|
||||
%
|
||||
% % PRMS for PAM-6: 5 bits map to 2 PAM-6 symbols
|
||||
% % PRMS for PAM-6: 5 bits map to 2 PAM-6 symbols. M = 6 returns
|
||||
% % the 1-D bit vector expected by PAMmapper.
|
||||
% bits = Signalgenerator( ...
|
||||
% "form", signalform.prms, ...
|
||||
% "dimension", 5, ...
|
||||
% "M", 6, ...
|
||||
% "order", 7).process();
|
||||
% bitVector = reshape(bits.signal.', [], 1);
|
||||
% symbols = PAMmapper(6, 0).map(bitVector);
|
||||
% symbols = PAMmapper(6, 0).map(bits);
|
||||
%
|
||||
% % Map, demap, and check BER
|
||||
% mapper = PAMmapper(8, 0);
|
||||
@@ -54,6 +55,7 @@ classdef Signalgenerator
|
||||
fs
|
||||
fsig
|
||||
dimension
|
||||
M
|
||||
order
|
||||
randkey
|
||||
skip
|
||||
@@ -72,6 +74,7 @@ classdef Signalgenerator
|
||||
options.fs double = 1000 %Hz sampling
|
||||
options.fsig double = 50 % Hz fundamental frex e.g. of the sine or sawtooth
|
||||
options.dimension double = 1
|
||||
options.M double = []
|
||||
options.order double = 7
|
||||
options.randkey double = 0
|
||||
options.skip double = 0
|
||||
@@ -86,6 +89,11 @@ classdef Signalgenerator
|
||||
end
|
||||
end
|
||||
|
||||
if ~isempty(obj.M)
|
||||
obj.validate_pam_format();
|
||||
obj.dimension = obj.mapper_bit_dimension();
|
||||
end
|
||||
|
||||
if isempty(obj.length)
|
||||
obj.length = obj.default_length();
|
||||
end
|
||||
@@ -136,7 +144,8 @@ classdef Signalgenerator
|
||||
case signalform.prms
|
||||
signal = obj.build_prms_data();
|
||||
end
|
||||
|
||||
|
||||
signal = obj.format_for_mapper(signal);
|
||||
|
||||
|
||||
end
|
||||
@@ -169,12 +178,15 @@ classdef Signalgenerator
|
||||
% PAM-8: dimension = 3
|
||||
%
|
||||
% PAM-6 is special in this codebase: the mapper consumes 5 bits and
|
||||
% maps them to two PAM-6 symbols. Use dimension = 5, then flatten
|
||||
% the output before calling PAMmapper(6, ...).map(...).
|
||||
% maps them to two PAM-6 symbols. Prefer M = 6 to generate the 1-D
|
||||
% vector expected by PAMmapper(6, ...).map(...). The legacy
|
||||
% dimension = 5 path still returns the unflattened bit matrix.
|
||||
%
|
||||
% Relevant Signalgenerator options:
|
||||
% length Number of generated PRMS bit groups / rows.
|
||||
% dimension Number of parallel bit streams per row.
|
||||
% M Optional PAM format. When set, dimension is selected
|
||||
% automatically and PAM-6 output is mapper-ready.
|
||||
% order User-facing sequence order, matching the old
|
||||
% PAMsource behavior. If length is omitted, it sets the
|
||||
% output length. Internally it is converted to the PRMS
|
||||
@@ -252,10 +264,17 @@ classdef Signalgenerator
|
||||
end
|
||||
end
|
||||
|
||||
function validate_pam_format(obj)
|
||||
if ~ismember(obj.M, [2 4 6 8 16])
|
||||
error("Signalgenerator:InvalidPAMFormat", ...
|
||||
"M must be one of 2, 4, 6, 8, or 16.");
|
||||
end
|
||||
end
|
||||
|
||||
function length = default_length(obj)
|
||||
switch obj.form
|
||||
case {signalform.random, signalform.prms}
|
||||
if obj.dimension == 5
|
||||
if obj.is_pam6_shape()
|
||||
length = 2^max(0, obj.order - 2);
|
||||
else
|
||||
length = 2^max(0, obj.order - 1);
|
||||
@@ -266,7 +285,9 @@ classdef Signalgenerator
|
||||
end
|
||||
|
||||
function prms_order = internal_prms_order(obj)
|
||||
if obj.dimension == 5
|
||||
if ~isempty(obj.M)
|
||||
bits_per_symbol = log2(obj.M);
|
||||
elseif obj.dimension == 5
|
||||
bits_per_symbol = log2(6);
|
||||
else
|
||||
bits_per_symbol = obj.dimension;
|
||||
@@ -275,6 +296,25 @@ classdef Signalgenerator
|
||||
prms_order = max(1, floor(obj.order / bits_per_symbol));
|
||||
end
|
||||
|
||||
function dimension = mapper_bit_dimension(obj)
|
||||
if obj.M == 6
|
||||
dimension = 5;
|
||||
else
|
||||
dimension = log2(obj.M);
|
||||
end
|
||||
end
|
||||
|
||||
function tf = is_pam6_shape(obj)
|
||||
tf = (~isempty(obj.M) && obj.M == 6) || (isempty(obj.M) && obj.dimension == 5);
|
||||
end
|
||||
|
||||
function signal = format_for_mapper(obj, signal)
|
||||
if ~isempty(obj.M) && obj.M == 6
|
||||
signal = reshape(signal.', [], 1);
|
||||
signal = signal(1:end - mod(numel(signal), 5));
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user