From 89d08bec6d3ff3199b6096cbe8e85abba3578044 Mon Sep 17 00:00:00 2001 From: Laura Berkowitz Date: Mon, 6 Apr 2026 13:24:14 -0400 Subject: [PATCH] Add downsample method to analogSignalArray Introduce analogSignalArray.downsample(new_fs) to decimate signals using MATLAB's resample() with a rational ratio (rat). The method handles empty data, prevents upsampling by requiring new_fs < old sampling rate, resamples columns, recomputes timestamps, and updates sampling_rate. Add unit test testDownsample that verifies sampling rate update, timestamp spacing, channel preservation, approximate sample count, and timestamp start. --- core/analogSignalArray.m | 33 +++++++++++++++++++++++++++++++++ test/test_analogSignalArray.m | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/core/analogSignalArray.m b/core/analogSignalArray.m index 333e497..2af2383 100644 --- a/core/analogSignalArray.m +++ b/core/analogSignalArray.m @@ -218,5 +218,38 @@ function disp(self) varargin{:}); end + function self = downsample(self, new_fs) + % Downsample analogSignalArray using MATLAB's resample() + % + % Usage: + % asa.downsample(10) + + if isempty(self.data) + warning('analogSignalArray is empty'); + return; + end + + old_fs = self.sampling_rate; + + if new_fs >= old_fs + warning('new_fs must be less than old_fs, downsampling not performed') + return; + end + + % Compute rational resampling ratio + [p, q] = rat(new_fs / old_fs); + + % --- Resample data --- + % resample operates column-wise (perfect for [samples x signals]) + self.data = resample(self.data, p, q); + + % --- Recompute timestamps --- + n_samples_new = size(self.data, 1); + self.timestamps = self.timestamps(1) + (0:n_samples_new-1)' / new_fs; + + % Update sampling rate + self.sampling_rate = new_fs; + end + end end diff --git a/test/test_analogSignalArray.m b/test/test_analogSignalArray.m index 8678daa..69b1d01 100644 --- a/test/test_analogSignalArray.m +++ b/test/test_analogSignalArray.m @@ -68,3 +68,37 @@ function testIssorted(testCase) verifyEqual(testCase, asa.issorted(), true); end + +function testDownsample(testCase) + % Create synthetic signal + fs = 100; % original sampling rate + t = (0:1/fs:5)'; % 5 seconds + data = [sin(2*pi*5*t), cos(2*pi*2*t)]; % 2-channel signal + + asa = analogSignalArray( ... + 'data', data, ... + 'timestamps', t, ... + 'sampling_rate', fs); + + % Downsample + new_fs = 10; + asa.downsample(new_fs); + + % Check sampling rate updated + verifyEqual(testCase, asa.sampling_rate, new_fs); + + % Check timestamps spacing + dt = diff(asa.timestamps); + verifyEqual(testCase, mean(dt), 1/new_fs, 'AbsTol', 1e-10); + + % Check number of channels preserved + verifyEqual(testCase, size(asa.data, 2), 2); + + % Check number of samples roughly correct + expected_n = floor(length(t) * (new_fs / fs)); + verifyLessThanOrEqual(testCase, abs(size(asa.data,1) - expected_n), 1); + + % Check timestamps start correctly + verifyEqual(testCase, asa.timestamps(1), t(1)); +end +