Skip to content

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 SignalGenerator at 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_HZ between 50 and 4000; set NOISE_PERCENT to 0 for a clean tone; swap WAVEFORM through Sine, Square, Triangle, and Sawtooth to 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 ExponentialMovingAverage

Reads A0 once per loop, packs the value into a single-element vector, and pushes it through ExponentialMovingAverage. Raw and smoothed values print side by side so the Serial Plotter shows how sampleWeight trades 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_WEIGHT to 0.05 for heavy smoothing; raise it to 0.8 to mostly pass through; touch A0 with 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 512 exceeds the AVR cap of 256, and UNO/Nano-class boards lack the RAM at any size.

Things to try: set CARRIER_HZ to an off-bin value like 1234.5 and watch the peak report the nearest bin center; swap WindowType::BlackmanHarris for Rectangle to see leakage broaden the peak; drop FFT_LENGTH to 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, and BlackmanHarris. 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_HZ to an exact bin center (a multiple of SAMPLE_RATE / FFT_LENGTH) and watch Rectangle's leakage collapse; raise NOISE_PERCENT to lift the sidelobe floor; add FlatTop or Nuttall to 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 Trigger for 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_MODE to TriggerMode::FallingEdge; raise TRIGGER_LEVEL above the amplitude to force "no trigger"; set TRIGGER_POS to 0.1 to push the edge left; plot buffer directly 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_DECAY toward 0.99 and the held values linger much longer; drop EMA_SAMPLE_WEIGHT to 0.05 for heavy smoothing that trails the sweep; increase SWEEP_STEP so 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_HZ is 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_PERCENT and 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_LENGTH samples from analogRead(A0) at a paced rate, feeds the raw uint16_t buffer 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_RATE to move Nyquist, then feed a tone above it and watch the reading alias to a lower, wrong value; short A0 to 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