function [Bits, Symbols, Scpe_cell, found_sync] = loadAndSyncRunSignals(dataTable, options) %LOADANDSYNCRUNSIGNALS Load and synchronize signal files for one run. % % Inputs: % dataTable - one-row table with run metadata and signal file paths % options - struct with storage_path, start_occurence and max_occurences % % Outputs: % Bits - transmitted bit reference % Symbols - transmitted symbol reference % Scpe_cell - synchronized received signal occurrences % found_sync - true when a valid synchronization was found found_sync = 0; tempLocalStorage = 1; Scpe_cell = {}; loaded_from_cache = false; storage_dir = fullfile(prefdir, 'temp_sync_data'); if tempLocalStorage == 1 local_filename = fullfile(storage_dir, sprintf('sync_data_run_%s.mat', num2str(dataTable.run_id))); if exist(local_filename, 'file') try cacheData = load(local_filename, 'Bits', 'Symbols', 'Scpe_cell'); if isValidSyncCache(cacheData) Bits = cacheData.Bits; Symbols = cacheData.Symbols; Scpe_cell = cacheData.Scpe_cell; found_sync = 1; loaded_from_cache = true; else warning('loadAndSyncRunSignals:InvalidSyncCache', ... 'Ignoring incomplete sync cache file "%s".', local_filename); safeDelete(local_filename); end catch safeDelete(local_filename); found_sync = 0; end end end if ~found_sync Bits = load(composeStoragePath(options.storage_path, dataTable.tx_bits_path)); Bits = Bits.Bits; M = double(dataTable.pam_level); fsym = dataTable.symbolrate; Symbols_mapped = PAMmapper(M,0).map(Bits); Symbols_mapped.fs = fsym; Symbols = load(composeStoragePath(options.storage_path, dataTable.tx_symbols_path)); Symbols = Symbols.Symbols; found_sync = 0; try Scpe_load = load(composeStoragePath(options.storage_path, dataTable.rx_sync_path)); Scpe_cell = Scpe_load.S; [~,~,~,found_sync] = Scpe_cell{1}.tsynch("reference", Symbols, ... "fs_ref", fsym, ... "debug_plots", 0); catch % Continue with raw data if pre-synchronized data is unavailable. end end if ~found_sync try Scpe_sig_raw = load(composeStoragePath(options.storage_path, dataTable.rx_raw_path)); Scpe_sig_raw = Scpe_sig_raw.Scpe_sig_raw; Scpe_sig_resampled = Scpe_sig_raw.resample("fs_in", Scpe_sig_raw.fs, "fs_out", 2*fsym); [~, Scpe_cell, ~, found_sync] = Scpe_sig_resampled.tsynch("reference", Symbols, ... "fs_ref", fsym, ... "debug_plots", 0); catch % Continue to mapped-symbol fallback if raw data sync fails. end end if ~found_sync && exist('Scpe_sig_raw', 'var') if length(Symbols_mapped.signal) ~= sum(Symbols_mapped.signal == Symbols.signal) [~, Scpe_cell, ~, found_sync] = Scpe_sig_raw.tsynch("reference", Symbols_mapped, ... "fs_ref", fsym, ... "debug_plots", 0); end end if tempLocalStorage == 1 && found_sync && ~loaded_from_cache if ~exist(storage_dir, 'dir') mkdir(storage_dir); end max_local_files = 10; files = dir(fullfile(storage_dir, 'sync_data_run_*.mat')); if length(files) >= max_local_files [~, fileOrder] = sort([files.datenum]); delete(fullfile(storage_dir, files(fileOrder(1)).name)); end writeSyncCache(local_filename, Bits, Symbols, Scpe_cell); end if found_sync Scpe_cell = selectSyncedOccurrences(Scpe_cell, options); else warning('Could not synchronize the received signal with the stored symbols!'); end end function path = composeStoragePath(storagePath, relativePath) relativePath = firstValue(relativePath); path = char(string(storagePath) + string(relativePath)); if ismac path = strrep(path,'\','/'); else path = strrep(path,'/','\'); end end function value = firstValue(value) if iscell(value) value = value{1}; elseif ~ischar(value) && ~isscalar(value) value = value(1); end end function Scpe_cell = selectSyncedOccurrences(Scpe_cell, options) available_occurences = length(Scpe_cell); start_occurence = floor(getOption(options, 'start_occurence', 1)); max_occurences = floor(getOption(options, 'max_occurences', available_occurences)); if available_occurences < 1 warning('loadAndSyncRunSignals:NoSyncedOccurrences', ... 'Synchronization reported success, but no synced occurrences are available.'); return end if start_occurence < 1 error('loadAndSyncRunSignals:InvalidStartOccurrence', ... 'start_occurence must be >= 1.'); end if max_occurences < 1 Scpe_cell = Scpe_cell(1:0); return end if start_occurence > available_occurences warning('loadAndSyncRunSignals:StartOccurrenceTooHigh', ... ['Requested start_occurence %d, but only %d synced occurrences are available. ', ... 'Processing the last occurrence only.'], ... start_occurence, available_occurences); Scpe_cell = Scpe_cell(available_occurences); return end stop_occurence = min(available_occurences, start_occurence + max_occurences - 1); Scpe_cell = Scpe_cell(start_occurence:stop_occurence); end function value = getOption(options, name, defaultValue) if isfield(options, name) value = options.(name); else value = defaultValue; end end function valid = isValidSyncCache(cacheData) valid = isfield(cacheData, 'Bits') && ... isfield(cacheData, 'Symbols') && ... isfield(cacheData, 'Scpe_cell') && ... iscell(cacheData.Scpe_cell) && ... ~isempty(cacheData.Scpe_cell); end function writeSyncCache(local_filename, Bits, Symbols, Scpe_cell) cache_dir = fileparts(local_filename); temp_filename = [tempname(cache_dir), '.mat']; try save(temp_filename, 'Bits', 'Symbols', 'Scpe_cell'); movefile(temp_filename, local_filename, 'f'); catch ME safeDelete(temp_filename); rethrow(ME); end end function safeDelete(filename) if exist(filename, 'file') try delete(filename); catch % Another parallel worker may already have removed or replaced it. end end end