The oscilloscope is the time-domain view - and the one you should look at first. Before any spectral number is worth trusting, the waveform has to look sane: present at all, not clipping, free of gross DC offset or mains hum, at the level and frequency you expect. The scope answers those questions directly, and puts a live readout table beside the trace: peak-to-peak and RMS voltage, DC offset, frequency and period, duty cycle, rise and fall times.
The scope reads the newest window from the ring buffer on every repaint, conditions it, finds a stable trigger point, and draws it. A separate background pass measures the numbers at a steady 10 Hz so the readout is calm even when the trace updates fast.
Two optional filters run before display:
Only one of the two also reaches the measurement pass: the mains comb, because rejecting hum is a measurement decision rather than a display one - hum you have deliberately notched out should not be counted into Vpp or Vrms either. The high-frequency cleanup is a property of the trace alone. It is a setting you pick to make a picture readable, and the moment it moves Vpp, Vmean, rise and fall time or duty cycle, those stop being measurements of the signal and become measurements of our rendering of it. It would also err in one direction only: the filter is reset at the start of every pass, so it is measured ringing its way out of a step of up to the full peak-to-peak - which a peak detector reads as signal. On the bench that put a clean sine about 12 % high in Vpp and a quarter long in rise time, which is why the measurement pass no longer sees it.
A free-running scope would redraw the waveform at a random phase each frame, so a periodic signal would appear to slide across the screen. Triggering anchors every frame at the same feature - a chosen level crossed in a chosen direction (rising or falling edge). The display is then drawn relative to that point, so a stationary tone stands still.
Phonalyser.web triggers on absolute sample positions in the stream: each captured trigger records the absolute index of the crossing, including a sub-sample fraction from linear interpolation between the two straddling samples. That fraction shifts the rendered trace by less than one pixel so the waveform doesn't jitter by a sample even on a fast time base.
Some events worth catching are not level crossings at all. A capture hiccup that swallows samples leaves an amplitude step or a slope splice that can sit anywhere on the wave, in either direction - an edge trigger parked at a fixed level almost never lands on it. The glitch trigger fires on any waveform discontinuity instead, wherever it occurs; the trigger level, hysteresis and sub-sample refinement do not apply. (It is available in normal and single modes only: a free-running auto scope would overwrite the caught frame on the very next repaint.)
The detector exploits the fact that a sampled sinusoid - at any frequency - obeys an exact three-term recurrence[1]: each sample is fully predicted by the previous two,
so the prediction error
nulls exactly on a clean tone at any frequency, leaving only what the tone does not explain: the noise floor, residual harmonics, a second tone. When the measured channel is the trigger channel, ω is pinned from the measured fundamental (the millihertz-accurate figure from the measurement pass below); otherwise the coefficient 2 cos ω is fitted to the window itself by least squares, clamped to the range a real sinusoid allows.
A sample flags as discontinuous when |e| exceeds 8× the window's own mean |e| - a threshold that rides the noise floor. On a clean tone that is about 6.4 standard deviations, which broadband noise essentially never reaches, while a genuine splice breaks the prediction by a large fraction of the signal amplitude - decades above it. And because the baseline is measured from the same window, harmonics or a second tone raise it proportionally: the rule scales itself.
Pinning the frequency is what keeps the detector alive up high. The plain second difference - (2) with ω = 0 - does not null the tone: it leaves a residual of amplitude 2(1 − cos ω)·A, the tone's own curvature. For a 20 kHz tone sampled at 384 kHz that residual pushes the 8×-mean threshold to roughly half the signal amplitude (≈ 0.5 A), so every glitch short of a half-scale break would pass unseen; the tuned recurrence keeps the baseline at the noise floor at any frequency.
A real dropout has two boundaries - the entry into the gap and the recovery after it (observed USB dropouts run 120-160 µs) - and both break the prediction. Above-threshold samples closer together than 1 ms therefore merge into a single glitch event, and the edge-direction buttons choose the anchor: ↑ places the trigger on the glitch's start (the last sample still on the old trend), ↓ on its end (the first sample the local prediction accepts again).
The same detector guards the FFT analyser's coherent averaging: each captured window is screened with it in the time domain beside the spectral gates, so the scope's trigger and the analyser's block rejection agree on what counts as damaged.
The measurement pass works on the captured window in normalised units - mains-rejected if that is switched on, but never high-frequency filtered (see Conditioning) - converting to volts only with the ADC full-scale calibration (a sample of ±1 corresponds to Vfs·√2 peak). Level statistics are direct; frequency takes more care.
Over the window of N samples xi:
The RMS in (5) is the AC RMS - the DC bias is removed first (the variance form subtracts the mean), because a total RMS that collapsed to ≈ Vmean on a DC-biased input is far less useful on a scope.
On a periodic signal, (4) and (5) are not evaluated over the raw window. A window holding a fractional number of cycles leaves a dangling partial cycle whose contribution to the mean - up to A/(π n) for amplitude A and n cycles in the window - depends on where the window happens to start. The spread of Vmean then scales with the signal amplitude rather than the noise: millivolts of phase-random wander at full scale, swamping a microvolt noise floor. The scope therefore integrates Vmean and Vrms only between the first and last rising crossings of the half-amplitude midpoint - an integer number of periods. At a midpoint crossing the signal sits at its own mean by definition, so the boundary error turns second-order, and Vmean settles to the noise floor: its standard deviation on a full-scale signal drops from millivolts to under a microvolt - the same ≈ 0.6 µV it shows on a 1 mV signal. Vpp, min and max stay full-window (a peak must not be dropped for landing in the partial cycle), and a signal with no two same-direction crossings - DC, or noise alone - falls back to the full window.
Reading frequency off a short buffer accurately is the hard part. A three-stage strategy gets it to a few millihertz:
A quality figure (the peak magnitude relative to the AC RMS) gates the result: ≈ 0.707 for a clean sine, far lower for noise. Below the threshold the frequency, period and duty read as undefined rather than latching onto noise. For a genuine dual-tone signal these single-value fields have no meaningful answer and are blanked entirely; the level figures stay valid.
At fast time bases only a handful of samples cross the screen. Connecting the sample dots with straight lines draws corners that do not exist in the signal - the ADC's anti-alias filter guarantees the captured signal contains no energy above Nyquist, and the sampling theorem[4] states that such a band-limited signal is completely determined between its samples:
The scope evaluates (6) in its practical, windowed form - a
Lanczos[5] kernel
with 16 sinc lobes each side, precomputed into a phase table -
producing one true reconstructed value per pixel column when zoomed
in. The same reconstruction refines the trigger instant between two
samples (a bisection on the reconstructed curve, to about 1/1000 of a
sample), which is why the trace stands still instead of jittering by
a sample. Reconstruction is switchable per channel (the
sin x/x option); with it off, the scope falls back to
straight-line interpolation.
Zoomed out, the problem inverts: many samples land on one pixel. The kernel is then widened so it doubles as an anti-alias low-pass at the screen's own sample rate - without it, energy between the "pixel Nyquist" and the capture Nyquist folds into spurious beat patterns on the trace. Beyond ~5 samples per pixel the scope switches to per-column min/max envelope bars instead.
Mains interference sits at the line frequency and every integer harmonic of it. A single feedback comb filter[6] places a notch at DC and at every multiple of f₀ at once:
where α (just below 1) sets how sharp each notch is. The trick is that the comb is frequency-tracked: real mains drifts by a few tenths of a hertz, and the k-th harmonic drifts k× as far, so a fixed comb loses depth up high. By measuring the true line fundamental from a reference block - auto-detecting 50 vs 60 Hz - and re-tuning the single value N, every notch repositions to its exact k·f₀ together. N is generally fractional, so the delays interpolate linearly between the two straddling taps.
The trace answers "what does the waveform look like over time". The amplitude histogram answers a different question about the same samples: how much of its time does the signal spend at each voltage. Time is thrown away and only the occupancy is kept, which is what makes the picture worth having - the shape of the distribution identifies the signal's character at a glance, independently of its frequency.
A sine spends most of its time near its turning points, where it is moving slowly, and races through zero. Its probability density is the arcsine distribution, and the bathtub on screen is that curve:
which rises to infinity at ±A and is flattest at zero. Broadband noise draws a Gaussian bell instead; a clipped signal piles the time it spends against the rail into a spike at that voltage; and a waveform clipped on one side only shows as unequal bars about the centre line. Nothing else on the bench separates those three cases as directly.
Samples are binned before the high-frequency cleanup, before DC removal and before the mains comb - the same reasoning as for the measurement pass. A distribution taken after a display filter would be a distribution of our rendering rather than of the signal, and the low-pass in particular narrows exactly the tails the histogram exists to show.
The obvious construction - divide full scale into a fixed number of bins - ties the resolution to the converter's range instead of to what is being measured. On a card whose full scale is 1.79 VRMS, 2048 divisions is a 2.5 mV bin: six orders of magnitude coarser than the 1.18 nV a 32-bit converter actually resolves, so a millivolt residual would collapse into two or three bars and tell you nothing.
So the range is sized from the signal's own peak, symmetric about zero, and re-established when the signal outgrows it. The peak used is a stable one - aggregated over the scope's measurement-average window - because a single block's peak is a random draw from the signal's tail and ranging on that would restart the distribution on nearly every pass of anything noisy.
Two constants carry the rest of it:
Bin width is floored at one converter code - 1/2³¹ in normalised units, the deepest capture format the application supports. Below that a bin could only ever hold the gaps between codes, and a dead-flat signal would produce a range of zero width.
When the signal no longer fits, the range is re-established and the counts are cleared, deliberately. Counts gathered at a materially different level describe a different signal, and merging the two would present two populations as one distribution. A restart is the honest signal that the measurement changed. A signal that shrinks keeps its range - and so simply gets coarser - until you reset, which re-ranges onto whatever is present now. Between restarts collection is unbounded: it survives a V/div change, a range switch and a recalibration, because none of those alters a single count.
Two layers share the work, and the split is what keeps a DC offset honest. The accumulator is unit-agnostic and absolute: it is fed normalised samples, stores nothing but counts, and its range is centred on zero - so a signal carrying an offset sits off centre by exactly that offset. Nothing subtracts a running mean before binning; doing so would file one physical voltage into different bins while the estimate was still settling, smearing a single distribution into two.
The display then windows those bins symmetrically about the distribution's own mean and labels every tick as an offset from it, so 0 V is always the centre line and the visible span cannot slide off-picture when the signal is offset. Because the axis is only a labelling of bin edges - bin edges multiplied by the channel's peak volts - a recalibration relabels the axis without disturbing a single count. That is why a range switch mid-collection is not a reason to start again.
The oscilloscope shares its capture device and ring buffer with the FFT analyser through the common capture session, so both can record together off one open device. It also borrows the generator's commanded waveform as a hint: when a dual-tone signal is playing it knows to blank the single-tone fields rather than guess.
◀ Theory of operation · next: FFT analyser ▶