Accura

object Accura

Public facade for intonation analysis and scoring.

What is Intonation Analysis?

Intonation measures how accurately a singer hits target pitches against a chosen tuning system. Accura builds a pitch histogram from a performance, detects peaks corresponding to sung notes, maps them to target intervals in the tuning system (Equal Temperament or Just Intonation), and reports per-note deviations in cents. An overall 0–100 score can be derived from the analysis via a perceptually-weighted grading scale.

When to Use

ScenarioUse This?Why
Grade pitch accuracy of a sung melody against EQ/JIYesCore use case
Per-note deviation breakdown (cents off target)YesanalyzePitching returns note-level detail
Compare a student against a reference melodyNoUse CalibraMelodyEval
Raw pitch histogram without a tuning systemNoUse PitchAnalysis.computeHistogram
Voice qualities (breath, agility, range)NoUse Tessera* facades

Quick Start

Kotlin

// 1. Extract a pitch contour from audio
val extractor = PitchDetection.createContourExtractor(ContourExtractorConfig.SCORING)
val contour = extractor.extract(audioSamples, sampleRate = 16000)
extractor.release()

// 2. Analyze against a tuning system
val result = Accura.analyzePitching(
contour = contour,
tonicHz = 196.0f, // G3
intonationSystem = IntonationSystem.EQ,
scaleIntervals = listOf( // optional: explicit raaga targets
TargetInterval(0f, "Sa"),
TargetInterval(200f, "Re"),
TargetInterval(400f, "Ga"),
TargetInterval(700f, "Pa"),
TargetInterval(900f, "Da"),
),
)

// 3. Inspect notes and compute an overall score
if (result.error == null) {
val score = Accura.calculateScore(result, weightingMethod = WeightingMethod.EQUAL)
println("Score: ${score.score}/100 over ${score.noteCount} notes")
for (note in result.notes) {
println("${note.label}: ${note.deviationCents} cents (${note.tier})")
}
} else {
println("Analysis inconclusive: ${result.error}")
}

Swift

let extractor = PitchDetection.createContourExtractor(config: .scoring)
let contour = extractor.extract(audio: audioSamples, sampleRate: 16000)
extractor.release()

let result = Accura.analyzePitching(
contour: contour,
tonicHz: 196.0, // G3
intonationSystem: .eq,
scaleIntervals: [
TargetInterval(cents: 0, label: "Sa"),
TargetInterval(cents: 200, label: "Re"),
TargetInterval(cents: 400, label: "Ga"),
TargetInterval(cents: 700, label: "Pa"),
TargetInterval(cents: 900, label: "Da"),
]
)

if result.error == nil {
let score = Accura.calculateScore(result: result, weightingMethod: .equal)
print("Score: \(score.score)/100 over \(score.noteCount) notes")
} else {
print("Analysis inconclusive: \(result.error ?? "")")
}

Failure Semantics (ADR-022)

Accura follows the SDK rule for public APIs:

Kind of failureHow it surfacesCaller action
Invalid input (caller bug)IllegalArgumentExceptionFix the call site
Domain-level inconclusive (valid input, but analysis couldn't produce a score — e.g. < 3 histogram peaks, no target intervals match the scale)Non-null result with IntonationAnalysisResult.error setCheck result.error and fall back gracefully

Preconditions enforced by analyzePitching: non-empty contour, tonicHz > 0, non-empty scaleIntervals if provided. See ADR-022 for the rationale.

Common Pitfalls

  1. Unvoiced-heavy or too-short audio: Analysis needs at least three histogram peaks. Short samples return a result with error set rather than throwing — this is a domain outcome, not a caller bug.

  2. scaleIntervals are exact (cents, label) targets: Pass the precise intervals of the scale you're grading against and the labels you want in the per-note result. Multi-octave is supported and recommended for sustained-note tools where the user sings across octaves. null falls back to the full 12-TET / JI multi-octave grid with chromatic labels derived from noteLabelTradition and tonicHz. Passing an empty list is rejected as a precondition violation.

  3. alignTuning shifts the histogram, not tonicHz: When true, a global tuning offset (≈ drift from concert pitch) is estimated and bin centers are shifted. The tonic frequency itself is unchanged and is reported back in the result unmodified.

  4. intonationSystem selects target intervals, not an input-tuning assumption. Use EQ to grade against 12-TET; use JI to grade against just-intonation ratios. Both are evaluated relative to tonicHz.

See also

The per-note result structure returned by analyzePitching

The 0–100 score returned by calculateScore

EQ (12-TET) vs JI (Just Intonation)

Carnatic / Hindustani / Western label conventions

For producing the input contour

Hz / cents / note-label conversions

Functions

Link copied to clipboard
fun analyzePitching(contour: PitchContour, tonicHz: Float, intonationSystem: IntonationSystem, scaleIntervals: List<TargetInterval>? = null, noteLabelTradition: NoteLabelTradition = NoteLabelTradition.CARNATIC, alignTuning: Boolean = true, minNotes: Int = 3): IntonationAnalysisResult

Analyze intonation of a vocal performance.

Link copied to clipboard
fun calculateScore(result: IntonationAnalysisResult, weightingMethod: WeightingMethod = WeightingMethod.EQUAL): PitchingScore

Calculate an overall intonation score (0–100) from analysis results.