Skip to main content

PitchProcessing

Batch cleanup of an existing pitch contour: octave correction, smoothing, blip removal, masking, segmentation, resampling, interpolation. Use when you already have a contour (from PitchDetection) and want to clean it up before scoring or visualization.

Quick Start

Kotlin

// Tier 1: Preset
val cleaned = PitchProcessing.process(contour, PitchProcessingConfig.SCORING)

// Tier 2: Builder
val config = PitchProcessingConfig.Builder()
.fixOctaveErrors(true)
.removeSpuriousJumps(false)
.smoothingWindowSize(9)
.build()
val cleaned = PitchProcessing.process(contour, config)

// Tier 3: .copy()
val cleaned = PitchProcessing.process(contour, PitchProcessingConfig.DISPLAY.copy(hopMs = 20))

Swift

let cleaned = PitchProcessing.process(contour: contour, config: .scoring)

let config = PitchProcessingConfig.Builder()
.fixOctaveErrors(true)
.removeSpuriousJumps(false)
.smoothingWindowSize(9)
.build()
let cleaned = PitchProcessing.process(contour: contour, config: config)

Pipeline

process(contour, config) runs three stages in order. Each stage is skipped when its toggle is false:

input ─► octave correction ─► blip removal ─► smoothing ─► output

For per-frame realtime processing (smoothing + octave correction), set enableProcessing() on PitchDetectorConfig.Builder instead — it runs inside the detector at hop rate.

PitchProcessingConfig

Presets

PresetOctave fixSpurious jumpsBoundary octavesSmoothingBlip removal
RAWoffoffoffoffoff
SCORINGonoffonoffon (≥80 ms)
DISPLAY (Builder default)onononon (window 7)on (≥50 ms)

Properties

PropertyTypeDefaultDescription
fixOctaveErrorsBooleantrueSnap-back octave correction
removeSpuriousJumpsBooleantruePair-based jump classification (tona)
octaveThresholdCentsFloat150Distance to 1200 c that counts as "octave-jumpy"
referencePitchHzFloat0Reference for snap-back (0 = auto)
fixBoundaryOctavesBooleantrueOnset/offset octave correction
boundaryWindowMsFloat50Edge window checked for boundary octaves
smoothPitchBooleantrueApply smoothing filter
smoothingWindowSizeInt7Smoothing window (must be odd)
removeBlipsBooleantrueDrop short voiced runs
minimumNoteDurationMsFloat80Min duration for valid run
hopMsInt10Hop between frames (ms)

Methods

Pipeline (config-driven)

MethodDescription
process(contour: PitchContour, config = DISPLAY): PitchContourRun pipeline; preserves contour metadata
process(pitchesHz: FloatArray, config = DISPLAY): FloatArrayRun pipeline on a raw array

Octave correction

MethodDescription
correctOctaveErrors(pitchesHz, config: OctaveCorrectionConfig = FULL): FloatArrayUp to three stages: spurious-jump removal, snap-back, boundary correction. Each toggleable in config.

Smoothing & filters

MethodDescription
smooth(pitchesHz, windowSize: Int = 7): FloatArrayWeighted average smoothing in cents space. windowSize must be odd and ≥ 1 (IllegalArgumentException otherwise).
smoothGaussian(pitchesHz, sigma: Float = 3f): FloatArrayGaussian smoothing with NaN-aware gap interpolation
medianFilter(pitchesHz, kernelSize: Int = 5): FloatArrayMedian filter. kernelSize must be odd and ≥ 1.
rangeFilter(pitchesHz, minHz, maxHz): FloatArrayOut-of-range values replaced with -1
iqrFilter(pitchesHz, multiplier: Float = 2.5f): FloatArrayIQR-based outlier filter; outliers set to -1
dbscanFilter(pitchesHz, epsCents: Float = 100f, minSamples: Int = 200): FloatArrayDBSCAN-based outlier filter

Blip removal & interpolation

MethodDescription
removeBlips(pitchesHz, hopMs: Int = 10, minDurationMs: Float = 80f): FloatArrayMark voiced runs shorter than minDurationMs as unvoiced
interpolateSilence(pitchesHz, hopMs: Int = 10, method = LINEAR, maxGapMs: Float = 250f, forceRemaining: Boolean = false): FloatArrayFill short gaps in cents space (or Hz / exponential, see InterpolationMethod)

Masks & segmentation

MethodDescription
getValidPitchMask(pitchesHz): BooleanArrayTrue where pitch is voiced
getDurationMask(pitchesHz, hopMs = 10, minDurationMs = 80f): BooleanArrayTrue where voiced run ≥ duration
getStableSlopeMask(pitchesHz, hopMs = 10, maxSlopeCentsPerSec = 500f): BooleanArrayTrue where pitch slope is below threshold
findSegments(pitchesHz, mask, minGapMs = 0f, hopMs = 10): List<IntRange>Contiguous segments where mask is true

Resampling

MethodDescription
resample(pitchesHz, confidences, sourceHopMs, targetHopMs): FloatArrayResample pitch sequence to a different hop rate

OctaveCorrectionConfig

Standalone config for correctOctaveErrors.

PresetSpurious jumpsSnap-backBoundary
FULL (default)ononon
BASICoffonoff
OFFoffoffoff
PropertyDefault
removeSpuriousJumpstrue
snapBackCorrectiontrue
snapBackThresholdCents150
referencePitchHz0 (auto)
correctBoundariestrue
boundaryWindowMs50
hopMs10

InterpolationMethod

enum class InterpolationMethod { LINEAR, EXPONENTIAL, LINEAR_HZ }

LINEAR interpolates in cents (log-frequency); LINEAR_HZ in linear Hz; EXPONENTIAL decays toward target.

Common Pitfalls

  1. smooth and medianFilter require odd window/kernel size. Even values throw IllegalArgumentException (per ADR-022).
  2. Unvoiced frames use -1, not 0. All operations preserve the -1 sentinel.
  3. hopMs matters for duration-based ops. removeBlips, interpolateSilence, and the duration mask convert ms thresholds to frame counts via hopMs. Wrong hop → wrong durations.
  4. This is a batch facade. For per-frame realtime processing, set enableProcessing() on PitchDetectorConfig.Builder.

See also