PitchAnalysis

Public facade for pitch analysis — histograms, tuning estimation, and melodic transcription (ADR-001).

What is Pitch Analysis?

Once a pitch contour is extracted (PitchDetection) and optionally cleaned (PitchProcessing), analysis turns it into musical information:

  • Histogram — distribution of pitch values in cents relative to tonic. Basis for peak detection, intonation scoring (Accura), and shruti alignment.

  • Tuning estimation — how far the performance is from 12-TET grid.

  • Quantization — snap pitch values to the nearest target intervals (useful for displaying a "piano roll" view of sung notes).

  • Labelling — segment a contour into tonal regions and label each by its closest target interval (e.g., "Sa", "Ri", "Ga").

  • Linear fit — fit piecewise linear segments for gamaka / ornament analysis.

When to Use

ScenarioMethodWhy
Build a pitch histogram for visualizationcomputeHistogramCore building block
Estimate global tuning offset (concert pitch drift)estimateTuningOffsetAligns histogram to 12-TET grid
Snap pitches to discrete notes (piano roll)quantizeStability-filtered nearest-interval mapping
Segment into labelled tonal regionslabelByMeanPitchSliding window with majority-vote labelling
Analyze gamakas / ornaments with slopefitLinearSegmentsPiecewise linear regression
Full intonation analysis + scoringUse Accura insteadAccura wraps computeHistogram + its own scoring

Quick Start

Kotlin

val extractor = PitchDetection.createContourExtractor(ContourExtractorConfig.SCORING)
val contour = extractor.extract(audioSamples, 16000)
extractor.release()

val histogram = PitchAnalysis.computeHistogram(contour, tonicHz = 196f)
val tuningOffset = PitchAnalysis.estimateTuningOffset(contour, refFreqHz = 196f)

val targetIntervals = MusicTheory.EQ_TEMPERED_INTERVALS_CENTS_BASE.map { it.toFloat() }.toFloatArray()
val segments = PitchAnalysis.labelByMeanPitch(contour, tonicHz = 196f, targetIntervals)
for (seg in segments) {
println("${seg.label}: ${seg.startSeconds}s – ${seg.endSeconds}s")
}

Swift

let histogram = PitchAnalysis.computeHistogram(contour: contour, tonicHz: 196)
let offset = PitchAnalysis.estimateTuningOffset(contour: contour, refFreqHz: 196)

let targets = MusicTheory.eqTemperedIntervalsCentsBase.map { Float($0) }
let segments = PitchAnalysis.labelByMeanPitch(
contour: contour, tonicHz: 196, targetIntervalsCents: targets
)
for seg in segments {
print("\(seg.label ?? "?"): \(seg.startSeconds)s – \(seg.endSeconds)s")
}

Common Pitfalls

  1. tonicHz must be > 0: All methods convert Hz → cents relative to tonic. A zero or negative tonic produces NaN cents.

  2. Contour should be processed first: Raw contours with octave errors produce misleading histograms. Run through PitchProcessing.process with at least PitchProcessingConfig.SCORING before analyzing.

  3. targetIntervalsCents is in cents, not Hz: Pass 12-TET or JI interval values (e.g., [0, 100, 200, ..., 1100] for 12-TET). Use MusicTheory.EQ_TEMPERED_INTERVALS_CENTS_BASE for convenience.

  4. Histogram smoothing is off by default in HistogramConfig.RAW: If peaks look noisy, use HistogramConfig.DEFAULT (sigma = 5).

See also

For creating the contour that feeds into analysis

For cleaning the contour before analysis

For full intonation scoring (wraps this facade)

The histogram type returned by computeHistogram

The segment type returned by labelByMeanPitch and fitLinearSegments

For interval/note-label conversions

Functions

Link copied to clipboard
fun computeHistogram(contour: PitchContour, tonicHz: Float, config: HistogramConfig = HistogramConfig.DEFAULT): PitchHistogram

Compute a pitch histogram from a PitchContour.

Link copied to clipboard
fun estimateTuningOffset(contour: PitchContour, refFreqHz: Float, centTolerance: Float = 50.0f): Float

Estimate global tuning offset by aligning a pitch histogram with the 12-TET grid.

Link copied to clipboard
fun fitLinearSegments(contour: PitchContour, tonicHz: Float, config: LinearFitConfig = LinearFitConfig.DEFAULT): List<TonalSegment>

Fit linear segments to a pitch contour using a sliding window.

Link copied to clipboard
fun labelByMeanPitch(contour: PitchContour, tonicHz: Float, targetIntervalsCents: FloatArray, config: LabellingConfig = LabellingConfig.DEFAULT): List<TonalSegment>

Label segments of a pitch contour by mean pitch within a sliding window.

Link copied to clipboard
fun quantize(contour: PitchContour, tonicHz: Float, targetIntervalsCents: FloatArray, config: QuantizationConfig = QuantizationConfig.DEFAULT): PitchContour

Quantize a pitch contour to the nearest target intervals where the pitch is stable.