Skip to content

Reference

Sensors Reference

All 37 built-in sensors, the signal phenomena each claims, the accepted source types, and the peer-dep footprint (three optional – sharp for image decode, face-api.js for faces, @xenova/transformers for scene). Plus createSensorStream for live audio.


How to read this page

The sense phase runs one or many sensors over a NavigateResult whose content is audio, video, or text bytes. Each sensor implements accepts(source) to tell the registry "I can read this," plus sense(source, options) to produce a SenseResult with typed findings.

  • One optional peer (sharp) for image sensors. The audio + text + video sensors are pure TypeScript. The 5 image sensors (v0.9) load sharp lazily on first call and throw MISSING_PEER_DEP if it isn't installed – users who don't touch images don't pay for the native bindings. The DSP primitives (FFT, autocorrelation, envelope detection, Goertzel, Hilbert, Welch, windowing, text-DSP helpers like chi-squared / Kasiski / index-of-coincidence, 2D convolution + FFT) live in packages/core/src/sense/ and every sensor composes from them. That still makes Pluck the only signal-analysis library in the JS ecosystem whose audio + text + video sensors run on Cloudflare Workers, Vercel edge, or any other runtime without native bindings.
  • Resolution knob. Every sensor respects SenseOptions.resolution: "fast" | "standard" | "deep" – trade throughput for confidence.
  • pluck.dowse() runs every sensor in fast mode and ranks findings by confidence. Use it when you don't yet know what's in a signal.

See Concepts: Sense for the phase mental model and SenseResult shape.


The 37 built-in sensors

All sensors are registered in a deterministic order; pluck.sense(uri, { detect: [...] }) picks the ones whose names match your list.

Spectral analysis

Foundational frequency-domain primitives. Everything else composes from these.

#SensorWhat it detectsOutput (in features)
1fftMagnitude spectrum of the signal windowFrequency bins + magnitudes via SenseResult.spectra
2spectrogramTime-frequency map (STFT). Frame count caps via hop widening on long signals2D magnitude array via SenseResult.spectra
3pitchFundamental frequency via autocorrelation + refinement{ pitchHz, confidence }
4tempoBPM via onset detection + autocorrelation{ bpm, beatMs[] }
5chromagram12-band pitch-class energy (key / chord detection). Floor 55 Hz{ bins: Float32Array[12], topClass: "C"|"C#"|..., confidence }
6mfccMel-Frequency Cepstral Coefficients via 26-filter mel bank + DCT-II{ coefficients: Float32Array[13], filterCount: 26 }

Decoded signals

Decoders that read sub-perceptual data out of audio.

#SensorWhat it detectsOutput (in decoded[] or features)
7dtmfTouch-tone dialing (8 Bell-standard frequencies) via Goertzel{ kind: "dtmf", data: "012...", startTime, endTime, confidence }
8morseMorse code via envelope + dit/dah timing{ kind: "morse", data: "SOS", ... }
9fskFrequency-shift keying. Default Bell 103 (mark=1270, space=1070, baud=300). Optional UART 8N1 → ASCII text. Throws FSK_INVALID_OPTIONS on NaN / negative / mark≡space / above-Nyquist inputs{ bits, bitCount, text?, markHz, spaceHz, baud, confidence }
10pskBPSK demodulation. carrierHz required; default BPSK31 baud. Tolerates ±5 Hz carrier drift. Throws PSK_INVALID_OPTIONS on bad inputs{ bits, bitCount, carrierHz, baud, phaseJitter, confidence }
11am-demodAM radio demodulation{ kind: "am-demod", data: ArrayBuffer, ... }
12fm-demodFM radio demodulation{ kind: "fm-demod", data: ArrayBuffer, ... }
13ssb-demodSingle-sideband radio demodulation{ kind: "ssb-demod", data: ArrayBuffer, ... }

Band presence + diagnostic

Boolean-ish "is there energy in this band" sensors and the "is the mic live?" sanity check.

#SensorWhat it detectsNotes
14ultrasonicEnergy above 18 kHz (cross-device tracking beacons)Powers pluck snitch.
15infrasonicEnergy below 20 Hz (earthquake / HVAC / wind rumble)Rare but load-bearing for forensic work.
16noise-floorRMS + spectral flatness (power-domain). Returns isNoise gateFail-fast check before expensive downstream sensors.

Identity

#SensorWhat it detectsNotes
17birdsongBird species via trained acoustic fingerprintsBird-only today. Broader animal coverage lives in the planned animalsong sensor. Community signature registry proposal in IDEAS.md.
18rppgRemote photoplethysmography – pulse rate from video framesPowers the pluck deepfake liveness check.

Physiological + periodicity

#SensorWhat it detectsOutput
19heartbeatPCG / stethoscope-audio peak detection. Audio only; for heart-rate from video use rppg{ bpm, peakTimes: number[], rhythm: "regular"|"irregular"|"undetected", confidence }
20breathingRespiration rate from very-low-frequency envelope modulation{ bpm, intervals: number[], quality: "good"|"noisy"|"undetected", confidence }
21periodicityGeneral-purpose autocorrelation – fundamental period, harmonic ratio{ periodSamples, periodSeconds, fundamentalHz, harmonicRatio, secondaryRatio, isPeriodic }

Outlier detection

#SensorWhat it detectsOutput (in anomalies[])
22anomalyOutlier regions in the signal (audio dropouts, clicks, discontinuities){ kind, start, end, confidence }

Text domain (cipher + steganography)

Operates on text/* sources (plain text, JSON, XML, YAML, .txt / .md / .csv / .log URLs). All four enforce a 1 MB pre-DSP input cap (TEXT_TOO_LARGE) and honour AbortSignal.

#SensorWhat it detectsOutput (in features)
23cipher-classifyFingerprints unknown ciphertext: Caesar / Vigenère / base64 / base32 / hex / URL-encoded / JWT{ family, confidence, metrics: { indexOfCoincidence, bigramEntropy, length }, rationale }
24cipher-crack-caesarBrute-forces all 26 Caesar shifts; chi-squared against English letter frequencies{ shift, plaintext, score, confidence, candidates: CaesarCandidate[] }
25cipher-crack-vigenereTwo-stage Vigenère attack – Kasiski key-length + per-column Caesar crack{ key, keyLength, plaintext, score, confidence, candidates: VigenereKeyCandidate[] }
26steganography-textTrailing-whitespace payloads, 14 zero-width code points + the full Unicode tag block (U+E0000-U+E007F – "ASCII smuggling" prompt-injection), homoglyph substitution (Cyrillic / Greek / Armenian / Latin-extended){ detected, hits: StegoTextHit[], summary: { trailingWhitespace, zeroWidth, homoglyph }, whitespacePayload?, cleaned }

Every StegoTextHit.offset indexes the original text so operators can surgically strip the payload.

Image domain (forensics + screen-recording detection)

Operates on image/* sources (PNG / JPEG / WebP / TIFF / GIF / BMP via the optional sharp peer). Decodes go through a two-phase cap: sharp.metadata() is checked against MAX_IMAGE_PIXELS (8192²) BEFORE .raw().toBuffer(), defending against image-bomb inputs. All five sensors honour AbortSignal inside their Sobel / FFT inner loops (poll every 64 rows).

#SensorWhat it detectsOutput (in features)
27elaError-Level Analysis – re-compresses at JPEG q=90, diffs per-pixel luminance{ tamperingScore, meanError, p99Error, maxError, width, height, quality }
28heatmapGeneric Sobel-magnitude energy map, binned into an 8×8 grid{ grid, rows, cols, maxCell, meanEnergy, width, height }
29moirePeriodic-frequency detection via 2D FFT with low-frequency mask + aspect-preserving resize{ detected, peakToMeanRatio, peak, periodPixels, confidence }
30flickerHorizontal-banding detection from AC-light captured by rolling-shutter cameras{ detected, dominantCycles, bandPeriodPx, peakToMeanRatio, confidence }
31rolling-shutterConsistent slant on vertical edges – CMOS shutter signature for deepfake / composite detection{ detected, meanSlantDeg, slantStddevDeg, edgeCount, confidence }

CV domain (ML-backed + aerial / thermal)

The v0.10 CV sensors wrap optional face-api.js / @xenova/transformers peers, lazy-loaded so consumers who don't run them don't pay for the ~40 MB install. Every sensor that runs Sobel / CCL pre-resizes to a 1024-longest-edge analysis cap; detection boxes + reported dimensions are scaled back to the ORIGINAL image space so consumer overlays always align.

#SensorWhat it detectsOutput (in features)
32facesFace bounding boxes + 68-point landmarks via face-api.js. Single-frame liveness heuristic cross-references Sobel edge density against face-api's confidence{ count, faces: DetectedFace[], maxLiveness, width, height }
33sceneImage classification via @xenova/transformers (default Xenova/vit-base-patch16-224; overridable via SceneSensorOptions.model){ topLabel, topScore, predictions: ScenePrediction[], width, height }
34ocr-text-regionsPre-OCR text-region detection – Sobel horizontal-gradient + morphological closing + CCL → ranked bounding boxes (no peer){ regions: TextRegion[], imageWidth, imageHeight }
35animalsongBioacoustic ID beyond birds: frogs, crickets, cicadas, bats (ultrasonic – skipped below Nyquist), mammal calls (audio sensor){ matches: AnimalsongMatch[], topMatch }
36thermalIR / FLIR hotspot detection – p95-threshold segmentation + stddev-based dynamic-range gate{ hotspots: Hotspot[], meanIntensity, hotspotThreshold, width, height }
37ground-anomalySatellite / aerial two-mode: single-image NDVI surrogate OR two-frame change-detection via SenseOptions.reference{ mode, anomalyFraction, blobs: GroundBlob[], width, height }

The image foundation module (toLuminance, applyKernel, sobelMagnitude, fft2d, connectedComponents, sourceToImage, resizeImage, resizeToAnalysisMax, recompressJpeg, MAX_IMAGE_PIXELS, MAX_FFT_PADDED_PIXELS, CV_ANALYSIS_MAX_DIM) is exported for consumers building custom image sensors – defineSensor({ name, accepts, sense }) composes as naturally as it does for audio / text.


Live streaming – createSensorStream

For mic / SDR / SIP / live-tail sources, the streaming primitive runs the same sensors over an incoming ReadableStream<Float32Array> and emits per-window events.

TypeScript
import { createSensorStream } from "@sizls/pluck";

const stream = createSensorStream(micAudio, {
  sampleRate: 44_100,
  detect: ["fft", "heartbeat", "breathing"],
  windowSize: 4096,        // default
  hop: 2048,               // default = 50% overlap
  signal: abortSignal,     // optional; aborts both directions
});

for await (const event of stream) {
  // { time: number, feature: SenseFeature, result: Partial<SenseFeatures> }
}

Memory-bounded (rolling buffer; ~40 KB peak regardless of stream length), backpressured (highWaterMark: 1 queuing strategy), abort-aware. Non-audio sensors like rppg are silently skipped on audio streams.


Planned sensors (not yet shipped)

PlannedSenseFeature is a tag union in the type layer; calling pluck.sense(uri, { detect: ["watermark"] }) today throws NO_SENSOR. Check availability before calling:

TypeScript
pluck.sensors.whichHandles("dtmf");     // "dtmf"
pluck.sensors.whichHandles("flicker");  // "flicker" (v0.9 – now ships)
pluck.sensors.whichHandles("watermark"); // undefined – planned, not shipped

Tracked: watermark (content-ID style spectral watermarks), voiceprint (speaker identity), engine (RPM / cylinder fingerprint), seismic (earthquake precursors), animalsong (broader-than-birds bioacoustic coverage), steganography-image (LSB channel extraction – v0.9 ships ELA which covers the tampering half of the image-stego diagnostic surface; the pure LSB decoder is still planned).


Custom sensors

TypeScript
import { createPluck, defineSensor } from "@sizls/pluck";
import { goertzel } from "@sizls/pluck/dsp"; // re-exported DSP primitive

const lisnr = defineSensor({
  name: "lisnr",
  accepts: (source) => source.contentType.startsWith("audio/"),
  sense(source, options) {
    // Proprietary Lisnr ultrasonic beacon decoder
    const { sampleRate, samples } = readAudioSource(source);
    const decoded = decodeLisnrFrames(samples, sampleRate);

    return {
      features: { lisnr: decoded },
      confidence: decoded.length > 0 ? 0.9 : 0.0,
      decoded: decoded.map((d) => ({
        kind: "lisnr",
        data: d.payload,
        startTime: d.t0,
        endTime: d.t1,
        confidence: d.snr,
      })),
    };
  },
});

const pluck = createPluck({ sensors: [lisnr] });
await pluck.sense("./store-visit.wav", { detect: ["lisnr"] });

Custom sensors prepend to the registry. The community signature registry proposal (@sizls/signatures-*) in IDEAS makes the pattern a shipping primitive – each npm package declares patterns the main sense API picks up at registration.


pluck.dowse() – zero-config scan

When you don't know what's in a signal, dowse is the one-liner:

TypeScript
import { pluck } from "@sizls/pluck";

const scan = await pluck.dowse("./mystery.wav");

console.log(scan.topFinding?.summary);
// → 'Decoded dtmf: "0123456789"'

for (const finding of scan.findings) {
  console.log(`${finding.sensor} ${finding.confidence.toFixed(2)} ${finding.summary}`);
}

dowse runs every shipped sensor at resolution: "fast", returns findings sorted by confidence descending, and aliases findings[0] as topFinding. See Concepts: Sense → pluck.dowse().


Introspection

TypeScript
import { createPluck } from "@sizls/pluck";

const pluck = createPluck();

pluck.sensors.list();
// ["fft", "spectrogram", "dtmf", "pitch", "tempo", ...]

pluck.sensors.whichHandles("dtmf");
// "dtmf"

pluck.sensors.find("dtmf", audioSource);
// Sensor – narrows by name AND accepts() probe

What's next

  • Concepts: Sense – phase model, SenseResult, DSP primitive catalog.
  • Recipe: Snitch Privacy – composing ultrasonic + fft + spectrogram into a forensic privacy audit.
  • IDEAS backlog – Sense Receipts, pluck.senseDiff, the Community Signature Registry.
Edit this page on GitHub
Previous
Actors
Next
CLI

Ready to build?

Install Pluck and follow the Quick Start guide to wire MCP-first data pipelines into your agents and fleets in minutes.

Get started →