Fourier Synchrosqueezed Transform — Control Systems/Time Frequency
Control_Systems/Time_Frequency/Fourier_Synchrosqueezed_Transform · 1 input / 4 output port(s) at insert · exports to Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text
Description#
The block's own DESCRIPTION_HTML, rendered verbatim — the same text the config dialog's info panel and the library navigator show. Fix a wrong sentence in the block's .cpp (R-D9), never here.
Fourier Synchrosqueezed Transform
Control Systems / Time Frequency
The synchrosqueezed short-time transform of a stream – MATLAB's
fsst, one column per sample. The last L samples are transformed
twice, with the window and with the window's derivative; the ratio gives every bin
its own instantaneous frequency and the bin's contribution is moved there:
ν[m] = m − Im(D[m] ÷ X[m]), S[round(ν) mod L] += X[m]·(−1)m
so a ridge an ordinary spectrogram spreads over several bins comes back on one.
The same rows, summed over a band and scaled, reconstruct the signal – that is
MATLAB's ifsst, and it is the fourth output.
Ports
- u – the sampled signal. Scalar: one channel and its own window.
- Mag – |S|, the synchrosqueezed column, [L/2+1, 1], one-sided with DC first and Nyquist last. Row m is the normalized frequency m/L in cycles per sample, or m·fs/L in hertz for a signal sampled at fs. This is the map to plot, and the map Time-Frequency Ridges reads.
- Re – the real part of that same column, [L/2+1, 1]: the synchrosqueezed transform is complex, and a wire carries a real number, so its two halves ride two ports.
- Im – its imaginary part, same size, same rows.
- xr – [1, 1], the signal reconstructed from the rows inside Reconstruction Band – the sample at the published column's own time, which is L/2 − 1 samples behind the input (see Frames and timing). Over the whole band it is the input itself, delayed by that much.
Parameters
- Window Length – L, the window and the transform length: an even whole number from 4 to 20. Even because a one-sided spectrum needs a Nyquist bin, and because the transform's phase shift onto the window centre is then exactly (−1)m. Bounded above because the four transforms are unrolled at export: a column costs 4·L² inlined products.
- Window – the analysis window, the symmetric shapes MATLAB's
kaiser(),hann()andhamming()return:- Kaiser –
fsst's own default, shaped by Kaiser Beta. - Hann – two end taps of exactly zero.
- Hamming – end taps of 0.08.
- Kaiser –
- Kaiser Beta – β, the Kaiser shape parameter, a scalar of
zero or more (MATLAB's
fsstuses 10). Read only by the Kaiser window; a larger value means a wider main lobe and lower side lobes. - Reconstruction Band – the rows xr is summed over, as [low high] in normalized frequency (cycles per sample, 0 to 0.5), so the default [0 0.5] is the whole spectrum whatever L is. Each edge snaps to the nearest row – MATLAB's rule – and a tie keeps the lower one. The band is read by xr only; Mag, Re and Im always carry every row.
- Sampling Time (s) – zero or less inherits the solver's rate; a positive value runs the block at that period.
Frames and timing
fsst fixes its overlap at L − 1 samples, so it has one
column per input sample and pads the record with L/2 zeros in front. Column
k therefore reads samples k − L/2 to k + L/2 − 1,
and the newest column a stream can finish at sample n is
k = n − L/2 + 1: the four outputs run L/2 − 1 samples
behind the input, and they are all zero until the register first fills at sample
L − 1. The leading zeros of the register are exactly MATLAB's leading
zero padding, so the columns agree from the first one published.
Code export
All ten targets: Python, MATLAB, Java, Rust,
C, C++, VHDL, Verilog, SystemVerilog and
PLC Structured Text. The window, its spline derivative and the transform's
basis are fixed by the configuration and inlined as numbers, so no generated core
computes a sine, a cosine or a Bessel function; the reassignment's rounding is a
bounded count and its range reduction a bounded halving loop, so no core needs a
floor either (PLC Structured Text has none). All four settings are
structural – re-export after changing one.
The three HDL targets carry the transform in real arithmetic
and are therefore simulation-only: the instantaneous frequency is a division
by the transform's own squared magnitude, which leaves Q16.16 in both directions.
The window is held in the port's fixed-point format, so quantization happens only at
the port boundary.
⚠ The destination row is a ROUNDING, so it is a discrete answer. A bin whose instantaneous frequency sits within a quantum of a half-row boundary can be reassigned one row apart in fixed point, and that row then differs by the whole of that bin's contribution. It is arithmetic and not a code-generation defect – the same algorithm on different numbers – and it is why a fixed-point comparison of this block should be read as a spread rather than a single figure. Feeding it a narrowband signal, whose bins all agree about one frequency, keeps every decision far from its boundary.
Simulink bridge
None (Support::None). fsst is a Signal Processing
Toolbox function and that toolbox ships no Simulink library, so there is no
path a diagram could name. The bridge reports this block rather than dropping it
silently, and it therefore has no parity testbench; code export verification
still covers it across all ten languages. Sampling Time (s) →
SampleTime, as on every block, has nothing to cross to for the same
reason.
Notes
- Stateful, and discrete by nature: a register of the last L samples and the column published from it.
- The reassignment runs over the whole two-sided spectrum before the
one-sided half is taken, exactly as
fsstdoes, so a bin from the negative half whose frequency wraps into the positive one still contributes. That is also why the rows are not doubled. - Verified against R2026a: the whole complex matrix to 1.96e-14 on a scale of 10.68, the full-band reconstruction to 8.9e-16 and a band reconstruction to 4.4e-16 (the source banner carries what was checked).
- Not reproduced: a window vector given tap by tap, an odd window
length, a complex input (
fsstthen returns a centred two-sided spectrum), andifsst's reconstruction along a ridge withNumFrequencyBins– that one needs a ridge index per column, which is a second input this block does not have. - It is not Spectrogram. Spectrogram publishes |X| every hop samples and holds in between; this block publishes a column every sample and moves each bin's energy to where its instantaneous frequency says it belongs, which is what makes a ridge sharp enough to follow.
- No state space. The instantaneous frequency is a ratio of two transforms, so the block is not linear in its input and model reduction correctly declines to merge it.
Code facts#
| Fact | Value |
|---|---|
| registered type | Control_Systems/Time_Frequency/Fourier_Synchrosqueezed_Transform |
| family | Control_Systems/Time_Frequency |
| solver environment class | ICoreBlock_0_Control_Systems_1_Time_Frequency_2_Fourier_Synchrosqueezed_Transform |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Time_Frequency/Fourier_Synchrosqueezed_Transform/ICoreBlock_0_Control_Systems_1_Time_Frequency_2_Fourier_Synchrosqueezed_Transform.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Time_Frequency/Fourier_Synchrosqueezed_Transform/ICoreBlock_0_Control_Systems_1_Time_Frequency_2_Fourier_Synchrosqueezed_Transform.h |
| default size on canvas | 170 × 100 px |
| ports at insert | 1 in, 4 out |
| code generators implemented | Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text |
Ports#
| # | Direction | Signal type | Description label |
|---|---|---|---|
| 1 | in | ICoreDouble | u |
| 2 | out | ICoreDouble | Mag |
| 3 | out | ICoreDouble | Re |
| 4 | out | ICoreDouble | Im |
| 5 | out | ICoreDouble | xr |
Ports the constructor creates. A block whose port list changes with its configuration adds or removes ports at load time; the count above is the one a freshly inserted block has.
Configuration variables#
| Config variable | Default | Simulink parameter |
|---|---|---|
Window Length | 16 | — |
Window | FS::WIN_KAISER%~%FS::WIN_HANN%~%FS::WIN_HAMMING~~FS::WIN_… | — |
Kaiser Beta | 10 | — |
Reconstruction Band | [0 0.5] | — |
Every block also carries Sampling Time (s) from ICoreBlockSolverEnvironment: zero or less inherits the solver's rate, a positive value runs the block at that period.
Simulink bridge#
| support | Support::None |
| Simulink path | — |
| port-count rule | PortsParam::None |
SampleTime parameter | yes |
Caveat (shown to the user): fsst is a Signal Processing Toolbox function, not a Simulink library block -- that toolbox ships no Simulink library at all -- so there is no path a diagram could name; the block is reported rather than dropped when a model crosses
Catalog contract: src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h
Description vs code#
The lists agree. check_block_descriptions.py finds no disagreement between the description's Ports, Parameters, Code export and Simulink bridge lists and the code's.
The verdict above is
tools/docs/check_block_descriptions.py(P7.1), which compares LISTS. It cannot read a sentence: "stateless" on a block with a state, an initial-value semantic the recursion does not implement, a "not synthesizable" caveat the HDL banner contradicts. That is the agent audit (P7.3) on BLOCK_DESCRIPTION_AUDIT.md, and this tool's green is not a substitute for one.
File banner (developer view)#
The top comment of the block's .cpp — the maths, the realization and the export strategy, addressed to whoever changes it. It must not contradict the description above (P7.5).
Fourier Synchrosqueezed Transform -- MATLAB's fsst, one column per sample, and its inverse A windowed transform of the last L samples is taken twice, once with the window and once with the window's derivative. Their ratio gives every bin its own instantaneous frequency, and each bin's contribution is MOVED to the row nearest that frequency:
X[m] = SUM(n) w[n] * fr[n] * e^(-i2*pi*m*n/L) D[m] = SUM(n) w'[n] * fr[n] * e^(-i2*pi*m*n/L) w' in bins: the derivative * L/(2*pi) nu = m - Im(D[m]/X[m]) S[mod(round(nu), L)] += X[m] * (-1)^m
so a ridge an ordinary spectrogram smears over several bins comes back on one. The one-sided rows 0 .. L/2 are published, and the same rows, summed over a band and scaled, reconstruct the signal -- MATLAB's ifsst.
MEASURED AGAINST R2026a BEFORE A LINE WAS WRITTEN, on the 24-sample record x = [1.2 0.7 2.5 0.3 0.12 0.8 1.1 -0.22 0.45 1.7 0.35 0.62 0.09 -1.4 0.66 0.2 -0.9 1.05 0.4 -0.55 0.8 -0.3 1.15 0.05] at L = 16, and every convention was checked rather than assumed:
- fsst fixes noverlap at L-1, so there is ONE COLUMN PER SAMPLE, and it pads the record with
L/2 zeros in front and L/2-1 behind (even L). Column k therefore reads x[k-L/2 .. k+L/2-1] and the newest column a stream can complete at sample n is k = n - L/2 + 1: a latency of L/2 - 1. Verified by taking fsst of the first 16 samples alone -- its first nine columns equal the 24-sample record's to 0.0 -- so a register starting at zero reproduces the leading zero padding exactly.
- The whole transform is INDEPENDENT OF THE SAMPLING RATE: fsst(x, 100, w) minus
fsst(x, 1, w) is exactly 0.0 in both parts, and so is the reconstruction, because the rate cancels between the window derivative's Fs/(2*pi), the frequency vector's m*Fs/L and the reassignment's (nf-1)/(fmax-fmin). The block therefore takes no rate at all and works in bins: row m is the normalized frequency m/L, or m*Fs/L in hertz for a signal sampled at Fs.
- The window derivative is a NOT-A-KNOT CUBIC SPLINE derivative at the knots (MATLAB's dtwin
differentiates spline(1:L, w)), not a finite difference; reproduced to 2.0e-15 relative.
- The whole 9x24 complex matrix is reproduced to 1.96e-14 on a scale of 10.68 with
kaiser(16,10) and to 2.07e-14 on 12.30 with hann(16).
- ifsst of the FULL band returns the signal: ifsst(fsst(x,1,w), w) equals x to 8.9e-16. Its
scale is 1/(w[L/2-1]*L) and every row but DC and Nyquist is counted twice; a band reconstruction matches MATLAB to 4.4e-16, with the band edges snapped to the NEAREST row.
Support::None: fsst is a Signal Processing Toolbox FUNCTION and that toolbox ships no Simulink library, so there is no counterpart to bridge to or run a parity testbench against.
Sample results#
The same rig also ran:
| Stimulus | What it is | Output range |
|---|---|---|
impulse | Impulse: one sample of 1 at k = 5, 0 elsewhere (Repeating Sequence Stair) | 0 … 0.5818 |
ramp | Ramp: slope 1 from t = 0 | 0 … 81.46 |
sine | Sine Wave: amplitude 1, 2 rad/s, no phase, no bias | 0 … 15.35 |
table | Repeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample | 0 … 3.868 |
Plotted: step — Step: 0 -> 1 at t = 1 s
Category dynamic · sample time 0.1 · 60 steps · commit 875fdbf564cf31a145283edf6a75dc1d64d1090a · produced by docsSample --out <folder> --blocks Wigner_Ville_Distribution Cross_Wigner_Ville_Distribution Inverse_STFT Fourier_Synchrosqueezed_Transform Time_Frequency_Ridges Frequency_Domain_Filter_Identification Fill_Gaps EOM_6DOF_ECEF_Quaternion EOM_6DOF_Custom_Variable_Mass_ECEF_Quaternion EOM_6DOF_Simple_Variable_Mass_ECEF_Quaternion ECI_To_ECEF_Rotation_Matrix ECI_Position_To_LLA LLA_To_ECI_Position ECI_Position_To_AER --steps 60 · data docs/generated/samples/Control_Systems__Time_Frequency__Fourier_Synchrosqueezed_Transform.json · the SVG is generated from those numbers by tools/docs/plot_svg.py, so it is a run and not a drawing (R-D10).