Ordnerstruktur

This commit is contained in:
Silas Oettinghaus
2026-04-23 16:16:58 +02:00
parent 8fd68c0a54
commit 456fd02186
13 changed files with 1117 additions and 194 deletions

View File

@@ -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);

View File

@@ -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