Example Sketches¶
The library ships with eight examples, each built around one DSP idea and
grouped below by complexity. Start at the top and work down. Open them from the
Arduino IDE under File → Examples → SignalCore. They report over the Serial
Monitor or Serial Plotter, and every one but FFTFromAnalogInput runs on the
board alone, with no external signal source required.
Each example notes the matching VoidLoop ViewPoint™ sketch. SignalCore computes and
verifies a result over plain Serial. Where the natural next step is plotting a
whole spectrum or scope trace, VoidLoop ViewPoint™ takes over.
Beginner¶
SignalGeneratorBasic¶
Best for: First end-to-end smoke test
Drives the
SignalGeneratorat a 10 kSPS virtual sample rate and prints the synthesized waveform one sample per line, which the Arduino Serial Plotter renders directly. It configures a 200 Hz sine carrier with light AM and gaussian noise. This is the simplest way to confirm the library compiles and runs on a new board, with no analog input required.Things to try: sweep
CARRIER_HZbetween 50 and 4000; setNOISE_PERCENTto 0 for a clean tone; swapWAVEFORMthroughSine,Square,Triangle, andSawtoothto compare shapes.Pairs with VoidLoop ViewPoint™:
SpectrumAnalyzer,SignalAndNoise,SignalAnalyzer_A0. The synthesis pattern is written out in Generate a test signal in software.
EMASmoother¶
Best for: A gentle, scalar first touch of
ExponentialMovingAverageReads
A0once per loop, packs the value into a single-element vector, and pushes it throughExponentialMovingAverage. Raw and smoothed values print side by side so the Serial Plotter shows howsampleWeighttrades transient response against noise rejection. This is the easy introduction to the class. Its primary job, smoothing an entire spectrum vector frame by frame, is shown in SpectrumSmoothingPeakHold.Hardware: any board with an analog input on
A0. A floating pin works fine, since the noise is exactly what the filter removes.Things to try: drop
SMOOTHING_WEIGHTto 0.05 for heavy smoothing; raise it to 0.8 to mostly pass through; touchA0with a finger and watch the smoothed line trail the raw one.Pairs with VoidLoop ViewPoint™:
SignalAnalyzer_A0.
Intermediate¶
FFTSpectrumPeak¶
Best for: FFT and spectrum smoke test
Fills a 512-sample buffer with a synthesized tone, removes DC, applies a Blackman-Harris window, runs the FFT, and prints the bin and frequency where the spectrum peaks. The reported frequency tracks the carrier within one bin (
SAMPLE_RATE / FFT_LENGTH), which makes it a good check of the FFT path on a new board.Hardware: a 32-bit board with ~8 KB free RAM (the
FFT<512>object is ~5 KB plus a 2 KB sample buffer). Does not build on AVR as shipped —FFT_LEN 512exceeds the AVR cap of 256, and UNO/Nano-class boards lack the RAM at any size.Things to try: set
CARRIER_HZto an off-bin value like 1234.5 and watch the peak report the nearest bin center; swapWindowType::BlackmanHarrisforRectangleto see leakage broaden the peak; dropFFT_LENGTHto 256 to halve the RAM footprint.Pairs with VoidLoop ViewPoint™:
SpectrumAnalyzer.
This is the canonical FFT-peak pattern: removeDC before the transform, then
peakBin() and peakFrequency() for the dominant tone.
for (int i = 0; i < FFT_LENGTH; i++) samples[i] = (float)sig.nextSample(0);
removeDC(samples, FFT_LENGTH); // keep any DC offset out of bin 0
fft.calculate(samples); // copy + window + transform + magnitude
// peakBin() searches result[0 .. FFT_LENGTH/2] and skips DC by default, so a
// constant offset can't be mistaken for the tone. Read it before postScaleDB().
int peakBin = fft.peakBin();
float peakHz = fft.peakFrequency(SAMPLE_RATE);
fft.postScaleDB(-100.0f, 1.0f); // linear magnitudes -> dBFS, after the search
float* mags = (float*)fft;
float peakDB = mags[peakBin];
WindowComparison¶
Best for: The single most useful FFT lesson: windows and leakage
Window functions trade main-lobe width for sidelobe suppression. This sketch synthesizes one tone placed deliberately between bins so leakage is visible, then runs the same data through
Rectangle,Hann, andBlackmanHarris. For each it prints a row: window name, peak bin and Hz and dB, and the worst sidelobe level. Watch the worst-sidelobe column fall by tens of dB as the window gets gentler.Hardware: a 32-bit board with ~8 KB free RAM (5 KB
FFT<512>plus a 2 KB sample buffer). Does not build on AVR as shipped (512 exceeds the AVR cap of 256).Things to try: set
CARRIER_HZto an exact bin center (a multiple ofSAMPLE_RATE / FFT_LENGTH) and watch Rectangle's leakage collapse; raiseNOISE_PERCENTto lift the sidelobe floor; addFlatToporNuttallto the comparison.Pairs with VoidLoop ViewPoint™:
WindowFunctionExplorer. The sidelobe-search recipe is in Compare windows / measure leakage.
Advanced¶
EdgeTriggeredFrame¶
Best for: Phase-stable oscilloscope framing
Every oscilloscope rests on one trick. Instead of plotting whatever samples happen to be in the buffer, it finds a repeatable feature, a rising edge through a threshold, and aligns the display to it. The sketch fills an oversized capture buffer with a slightly noisy synthesized wave each frame, asks
Triggerfor the aligned window start, and streams that frame to the Serial Plotter. The trace stands still because triggering cancels the phase drift between captures. A# …line prints whenever no edge is found.Hardware: any board (pure synthesis, no analog input).
Things to try: switch
TRIGGER_MODEtoTriggerMode::FallingEdge; raiseTRIGGER_LEVELabove the amplitude to force "no trigger"; setTRIGGER_POSto 0.1 to push the edge left; plotbufferdirectly to watch an untriggered capture scroll for contrast.Pairs with VoidLoop ViewPoint™:
SignalAnalyzer_A0. See also Add an oscilloscope-style triggered trace.
SpectrumSmoothingPeakHold¶
Best for: EMA vs peak-hold on a live spectrum, the vector workflow
A raw FFT magnitude frame is noisy and twitchy. Two post-processors tame it in opposite ways. An exponential moving average smooths each bin toward its recent history (steady but laggy), while a peak-hold latches each bin's maximum and lets it bleed down slowly (catching transients you would otherwise miss). The sketch sweeps a tone across the band and watches a few fixed bins, so as the tone passes you see the instant value spike, the smoothed value lag, and the held value jump then linger.
Hardware: a 32-bit board with ~10 KB free RAM: a 512-point FFT plus the sample buffer and two heap-allocated 257-element vectors. Does not build on AVR as shipped (512 exceeds the AVR cap of 256).
Things to try: raise
HOLD_DECAYtoward 0.99 and the held values linger much longer; dropEMA_SAMPLE_WEIGHTto 0.05 for heavy smoothing that trails the sweep; increaseSWEEP_STEPso the tone races and the held peak trails behind it.Pairs with VoidLoop ViewPoint™:
SpectrumAnalyzer.
Both processors read the linear magnitude vector each frame. The contrast lives in this update pair:
float* mags = (float*)fft; // linear magnitudes, bins 0..N/2
ema.update(mags); // smooths the whole vector in place
hold.update(mags, BIN_COUNT); // per-bin running max with decay
float* smoothed = ema; // tracks the tone, lagging slightly
float* held = hold; // jumps up, then lingers after the tone moves on
See Smooth or max-hold a spectrum for the sizing and convention details.
SignalQualityMetrics¶
Best for: Quantifying signal and converter quality: SINAD and ENOB
Two numbers grade a signal, or the converter that captured it, from a single FFT. SINAD (signal-to-noise-and-distortion, in dB) compares the tone's power to everything else in the spectrum. ENOB (effective number of bits) restates SINAD as the resolution an ideal converter would need to be this clean. The sketch synthesizes a coherent sine (a whole number of cycles in the window, so it does not leak), removes DC, transforms, and computes both metrics on the linear magnitudes before any dB scaling, printing them with the detected peak once a second.
A single non-coherent FFT understates ENOB, because a tone that does not close on itself leaks across bins and reads as noise. That is why
CARRIER_HZis chosen to land exactly on a bin, with a rectangular window.Hardware: a 32-bit board with ~8 KB free RAM (5 KB
FFT<512>plus a 2 KB sample buffer). Does not build on AVR as shipped (512 exceeds the AVR cap of 256).Things to try: raise
NOISE_PERCENTand watch SINAD and ENOB fall together; nudge the tone off-bin to see the leakage penalty drag ENOB down; switch the window and compare the estimate.The full method is in Measure SINAD and ENOB.
FFTFromAnalogInput¶
Best for: Bridging a real ADC to a spectrum
The bridge from a physical signal to a spectrum. The sketch block-reads
FFT_LENGTHsamples fromanalogRead(A0)at a paced rate, feeds the rawuint16_tbuffer straight into the FFT's integer overload, and reports the dominant frequency in dBFS. Two relationships matter: the read pacing sets the effective sample rate, and the spectrum only reaches half that rate (Nyquist). Anything above Nyquist folds back and lies about its frequency.Hardware: a board with an ADC on
A0(Teensy 4.x, ESP32, and similar) and a signal source, such as a function generator or AC-coupled audio biased into the input range. Runs on any ADC board, with no DMA required.Things to try: feed different tones and confirm the reported Hz tracks the source; change
SAMPLE_RATEto move Nyquist, then feed a tone above it and watch the reading alias to a lower, wrong value; shortA0to GND to see only the noise floor.Pairs with VoidLoop ViewPoint™:
SignalAnalyzer_A0. The capture-and-scale recipe is in Run an FFT from a real ADC.
Created by VoidLoop · Founded by Gregory Kovacs · Written by Zachariah Magee