--- title: "Signal processing: AT2TS / VT2TS / DT2TS" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Signal processing: AT2TS / VT2TS / DT2TS} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>", eval = FALSE) ``` ```{r minimal-example, eval = TRUE} # Minimal executable example library(gmsp) library(data.table) dt_acc <- data.table(t = seq(0, 5, by = 0.01), H1 = sin(2 * pi * 2 * seq(0, 5, by = 0.01))) tsl <- AT2TS(dt_acc, units.source = "mm", Fmax = 10, audit = FALSE, output = "TSL") head(tsl) ``` `gmsp` implements three workflows to build a consistent set of **Acceleration (AT)**, **Velocity (VT)** and **Displacement (DT)** time series from a single input quantity: * `AT2TS()` starts from acceleration; produces VT and DT by integration. * `VT2TS()` starts from velocity; produces AT by differentiation and DT by integration. * `DT2TS()` starts from displacement; produces VT and AT by differentiation. All three share the same internal pipeline: 1. Time regularisation if needed. 2. STFT parameter selection and resampling decision (internal strategy + optional `auditSTFT()`). 3. Optional anti-alias resampling. 4. Edge tapering to "turn off" low-amplitude pre/post segments. 5. Integration or differentiation (STFT-domain or finite differences). References for STFT/windows and OLA: Allen & Rabiner (1977), Harris (1978). ## Data formats ### Input (wide) The workflows expect a `data.table` with: * a time column (default `t`), and * one or more signal columns (e.g. `H1`, `H2`, `UP`). Use `time = "..."` when the input time column is not named `t`. The pipeline canonicalizes time internally and `TSL` output is always `t` and `s`; `COL.t` and `COL.s` are not part of this interface. ### Outputs | `output` | Shape | Columns | |---|---|---| | `"ATo"`, `"VTo"`, `"DTo"` | wide | `ts` (time from 0), `Units`, channels | | `"AT"`, `"VT"`, `"DT"` | wide | channels only | | `"TSW"` | wide | `ts`, `AT.`, `VT.`, `DT.` | | `"TSL"` (default) | long | `t`, `s`, `ID` ∈ {AT,VT,DT}, `OCID` | Canonical table projections are also available after construction. Use `TSL2TSW()` to cast a long table to wide columns and `TSW2TSL()` to return to the canonical long contract. `TSL2TSW()` writes canonical time as `t`; `TSW2TSL()` accepts either `t` or constructor-style `ts`. ```{r tsl-tsw-converters, eval = TRUE} tsw <- TSL2TSW(tsl) roundtrip <- TSW2TSL(tsw) head(roundtrip) ``` ## Time regularisation Given a time series with potentially irregular sampling, it builds a uniform grid: * Let $t_0 = \min(t)$ and $t_1 = \max(t)$. * Let $\Delta t = \min(t_i - t_{i-1})$. * "Rationalize" $\Delta t$ to force an integer sampling rate $F_s$: $$\Delta t \leftarrow \mathrm{rationalize}(\Delta t), \qquad F_s = 1/\Delta t \in \mathbb{Z}.$$ Then it interpolates each channel using a monotone cubic Hermite spline (`splinefun(..., method = "monoH.FC")`). **Practical motivation:** many later steps (STFT bin quantization, resampling to `TargetFs`) assume integer $F_s$. Reference for monotone cubic interpolation: Fritsch & Carlson (1980). Regularisation is an internal step of `AT2TS()`, `VT2TS()`, `DT2TS()`, `TS2IMF()`, and `TSL2PS()`. It always works on canonical time `t`; callers select a different input time column through the constructor argument `time`. ## Internal STFT Strategy Selection When `Fmax` is provided, the internal STFT strategy tries to choose `Fs_out` and `NW` (from a candidate grid) to: * ensure enough STFT windows (`MW >= MW.min`), and * **minimise leakage** around `Fmax` by aligning `Fmax` to an STFT bin. The leakage criterion used is $$J = \left|\frac{F_{\max}}{\Delta f} - \mathrm{round}\!\left(\frac{F_{\max}}{\Delta f}\right)\right| + \text{penalty}(\Delta F_s), \qquad \Delta f = F_s / NW.$$ If the user passes `kNyq` explicitly, it forces $F_{s,\mathrm{target}} \approx \mathrm{round}(kNyq\,F_{\max})$. The function returns a list with `NW` (window length), `OVLP` (overlap), `MW` (window count), `Fs` (target sampling rate after resampling), the flags `Resample` / `Upsample` / `Downsample`, and a `strategy` label. ## STFT audit: `auditSTFT()` `auditSTFT()` is invoked from `AT2TS/VT2TS/DT2TS` when `audit = TRUE`. It performs two checks. **Spectrum and content above Fmax.** Estimates the fraction of spectral amplitude above `Fmax` (via `buildFFT()`), and warns if the spectral peak `Fpeak` is above `Fmax`. **Strategy consistency.** Re-evaluates the anti-alias heuristics (transition bins `bins.tr`, margin to Nyquist `nyq.margin`, window count `MW`). Returns `list(warnings = ..., info = ..., stft = ...)`. ## STFT / OLA filtering: `.ffilter()` `.ffilter()` applies a frequency-domain filter defined by a `custom` vector in the STFT domain: 1. Compute STFT with `seewave::stdft()` using a Hann window (`wn = "hanning"`). 2. Multiply each frequency-bin row by `custom`. 3. Reconstruct with `seewave::istft()`. Notation: let $Z[k, m]$ be the STFT (bin $k$, frame $m$) and $H[k]$ be the filter (`custom`). Then $$\tilde{Z}[k,m] = H[k]\,Z[k,m]$$ and ISTFT/OLA yields $x[n]$. ## Integration: `.integrate()` Integrates a signal $x[n]$ using a frequency-domain (STFT-frame-wise) filter. The complex integration filter is $$H_I(\omega_k) = \begin{cases} 0, & k = 0 \\ \dfrac{1}{i\,\omega_k}, & k > 0 \end{cases}$$ applied via `.ffilter(..., custom = H_I)`. The DC component is set to zero (the integration constant is "fixed"), which helps reduce drift. Implementation details: * RMS normalisation $x \leftarrow x / \max(\mathrm{RMS}(x), \epsilon)$, then rescale. * Padding to an STFT-compatible length via `.padZeros()`. ## Differentiation: `.derivate()` Two methods are available via `method`. **`method = "time"` (finite differences).** Four-point central difference in the interior, $$\dot{x}_i \approx \frac{-x_{i+2} + 8x_{i+1} - 8x_{i-1} + x_{i-2}}{12\,\Delta t}$$ with three-point formulas at the edges. **`method = "freq"` (STFT-domain).** The derivative filter $$H_D(\omega_k) = \begin{cases} 0, & k = 0 \\ i\,\omega_k, & k > 0 \end{cases}$$ optionally multiplied by an additional low-pass if `lowPass = TRUE` and `Fmax` is provided. ## Anti-alias resampling: `.resample()` Per channel: 1. Design a Butterworth-like low-pass with `Fpass`, `Fstop` (quantised to bins). 2. Filter via `.ffilter()` (anti-alias). 3. Resample to `TargetFs` using `signal::resample`. 4. Remove DC. 5. Rescale amplitudes to preserve peak (per-channel peak matching). The Butterworth-like low-pass magnitude implemented is $$\left|H_{LP}(f)\right| = \frac{1} {\sqrt{1 + \left(\dfrac{1}{A_{stop}^2}-1\right) \left(\dfrac{f}{F_{stop}}\right)^{\!2N}}}$$ where the order $N$ is chosen to approximately satisfy $|H(F_{pass})| = A_{pass}$. ## Edge tapering: `.taperA()` `.taperA()` builds a window $w[n] \in [0,1]$ based on thresholds normalised by the max amplitude: * `Astop` and `Apass` are compared against $|x|/\max|x|$. * It finds start/end indices where amplitude exceeds those thresholds. * It creates smooth transitions using Butterworth-like filters in index-space (not frequency). `AT2TS/VT2TS/DT2TS` apply this window at the beginning and the end of each channel, and can optionally trim with `trimZeros`. A companion `.taperI()` exists (energy-cumulative taper) but is not used by the current workflows. ## Notes (parameter audit) * `resample` is a parameter on `AT2TS/VT2TS/DT2TS`, but the effective decision is made by the internal STFT strategy (`STFTParams$Resample`). * `time` selects the input time column for `AT2TS/VT2TS/DT2TS`; `TSL` output uses canonical `t` and `s`. ## References * Allen, J. B., & Rabiner, L. R. (1977). A unified approach to short-time Fourier analysis and synthesis. *Proceedings of the IEEE*, 65(11), 1558–1564. * Harris, F. J. (1978). On the use of windows for harmonic analysis with the discrete Fourier transform. *Proceedings of the IEEE*, 66(1), 51–83. * Fritsch, F. N., & Carlson, R. E. (1980). Monotone Piecewise Cubic Interpolation. *SIAM Journal on Numerical Analysis*, 17(2), 238–246. * Oppenheim, A. V., & Schafer, R. W. (2010). *Discrete-Time Signal Processing* (3rd ed.). Pearson.