Skip to main content

Utilities & Shared Types

Shared model types, error types, and time utilities used across Calibra APIs.

LessonMaterial

Reference material for singing evaluation. Contains the reference audio, segment boundaries, and musical key.

Creating LessonMaterial

From File

val material = LessonMaterial.fromFile(
audioPath = "/path/to/reference.mp3",
segments = segments,
keyHz = 261.63f // Middle C
)
let material = LessonMaterial.fromFile(
url: URL(fileURLWithPath: "/path/to/reference.mp3"),
segments: segments,
keyHz: 261.63
)

From Audio Samples

val material = LessonMaterial.fromAudio(
samples = audioSamples,
sampleRate = 16000,
segments = segments,
keyHz = 196.0f,
pitchContour = precomputedContour, // Optional: enables fast path
hpcpFrames = precomputedHpcp // Optional: for DTW alignment
)
let material = LessonMaterial.fromAudio(
samples: audioSamples,
sampleRate: 16000,
segments: segments,
keyHz: 196.0,
pitchContour: precomputedContour,
hpcpFrames: precomputedHpcp
)

Properties

PropertyTypeDescription
audioSourceAudioSourceSource of the reference audio
segmentsList<Segment>List of segments with timing and lyrics
keyHzFloatMusical key frequency in Hz (e.g., 261.63 for middle C)
pitchContourPitchContour?Pre-computed pitch contour (enables fast path, skipping YIN extraction)
hpcpFramesList<FloatArray>?Pre-computed HPCP frames for DTW alignment (each frame is 12 floats)
durationFloatTotal duration based on the last segment's end time
segmentCountIntNumber of segments

AudioSource

Represents the source of audio data for evaluation. A sealed class with three variants.

VariantPropertiesDescription
AudioSource.Filepath: StringAudio from a local file path
AudioSource.Urlurl: StringAudio from a URL (for future streaming support)
AudioSource.Samplessamples: FloatArray, sampleRate: IntRaw audio samples already in memory (default 16000 Hz)

Segment

A segment of a song or exercise with timing and optional lyrics. Supports both singalong (student sings with reference) and singafter (student sings after reference) modes.

Creating Segments

Kotlin

// Single segment
val segment = Segment(
index = 0,
startSeconds = 0.0f,
endSeconds = 5.0f,
lyrics = "Sa Re Ga Ma"
)

// Singafter segment (student sings after reference)
val segment = Segment(
index = 0,
startSeconds = 0.0f,
endSeconds = 5.0f,
lyrics = "Sa Re Ga Ma",
studentStartSeconds = 2.5f,
studentEndSeconds = 5.0f
)

// From parallel arrays
val segments = Segment.fromArrays(
starts = floatArrayOf(0f, 5f, 10f),
ends = floatArrayOf(5f, 10f, 15f),
lyrics = listOf("Line 1", "Line 2", "Line 3")
)

Swift

// Single segment
let segment = Segment.create(
index: 0,
startSeconds: 0.0,
endSeconds: 5.0,
lyrics: "Sa Re Ga Ma"
)

// Singafter segment
let segment = Segment.create(
index: 0,
startSeconds: 0.0,
endSeconds: 5.0,
lyrics: "Sa Re Ga Ma",
studentStartSeconds: 2.5,
studentEndSeconds: 5.0
)

Properties

PropertyTypeKotlinSwiftDescription
indexIntIntInt (via extension)Zero-based index of the segment
startSecondsFloatFloatDouble (via extension)Reference audio start time in seconds
endSecondsFloatFloatDouble (via extension)Reference audio end time in seconds
lyricsStringStringStringText/lyrics for this segment
studentStartSecondsFloat?Float?Float?When student recording starts (null = same as startSeconds)
studentEndSecondsFloat?Float?Float?When student recording ends (null = same as endSeconds)
durationFloatFloatDouble (via extension)Duration of the segment in seconds
isSingafterBooleanBooleanBoolTrue if student starts after reference
effectiveStudentStartFloatFloatDouble (via extension)Effective student start time (falls back to segment start)
effectiveStudentEndFloatFloatDouble (via extension)Effective student end time (falls back to segment end)
studentDurationFloatFloatDouble (via extension)Duration of the student recording portion

Factory Methods

MethodDescription
Segment.fromArrays(starts, ends, lyrics, studentStarts, studentEnds)Create segments from parallel arrays of start and end times

SegmentResult

Result of evaluating a single segment.

Properties

PropertyTypeDescription
segmentSegmentThe segment that was evaluated
scoreFloatOverall score for this segment (0.0 - 1.0)
pitchAccuracyFloatPitch accuracy component of the score (0.0 - 1.0)
levelPerformanceLevelPerformance level classification
attemptNumberIntWhich attempt this is (1-based, for retry tracking)
referencePitchPitchContourReference pitch contour for visualization
studentPitchPitchContourStudent pitch contour for visualization
isPassingBooleanTrue if score >= 0.5
isGoodBooleanTrue if score >= 0.7
isExcellentBooleanTrue if score >= 0.9
scorePercentIntScore as a percentage (0-100)
feedbackMessageStringHuman-readable feedback based on performance level

Swift Pitch Data Extensions

In Swift, pitch contour data is accessed via tuple extensions:

let result: SegmentResult = ...

// Reference pitch data
let ref = result.referencePitchData
// ref.times: [Float], ref.pitchesHz: [Float], ref.pitchesMidi: [Float]

// Student pitch data
let student = result.studentPitchData
// student.times: [Float], student.pitchesHz: [Float], student.pitchesMidi: [Float]

Factory Method

val result = SegmentResult.create(
segment = segment,
score = 0.85f,
pitchAccuracy = 0.82f,
attemptNumber = 2,
referencePitch = refContour,
studentPitch = studentContour
)

SingingResult

Complete result of a singing evaluation session, aggregating results across all segments.

Properties

PropertyTypeDescription
overallScoreFloatAggregate score across all segments (0.0 - 1.0)
segmentResultsMap<Int, List<SegmentResult>>Map of segment index to list of attempts
aggregationResultAggregationHow the overall score was calculated
overallScorePercentIntOverall score as a percentage (0-100)
segmentCountIntNumber of segments evaluated
totalAttemptsIntNumber of total attempts across all segments
allPassingBooleanTrue if all segments pass (score >= 0.5)

Methods

MethodReturn TypeDescription
latestScorePerSegment()Map<Int, Float>Get the latest score for each segment
bestScorePerSegment()Map<Int, Float>Get the best score for each segment
averageScorePerSegment()Map<Int, Float>Get the average score for each segment
latestResultPerSegment()Map<Int, SegmentResult>Get the latest result for each segment
latestScore(segmentIndex)Float?Latest score for a single segment, or null if not practiced
bestScore(segmentIndex)Float?Best score for a single segment, or null if not practiced
getAllFeedback()List<String>Get feedback messages for all segments

Single-segment accessors

latestScore / bestScore return one segment's score on both platforms. Swift uses the forSegment: label and native Int:

val score = result.latestScore(segmentIndex = 0)   // Float?
val best = result.bestScore(segmentIndex = 0) // Float?
if let score = result.latestScore(forSegment: 0) {
print("Score: \(Int(score * 100))%")
}
if let best = result.bestScore(forSegment: 0) {
print("Best: \(Int(best * 100))%")
}

Static Members

MemberDescription
SingingResult.EMPTYEmpty result constant (score 0, no segments)
SingingResult.calculateOverallScore(segmentResults, aggregation)Calculate overall score from segment results

ResultAggregation

How to aggregate multiple attempts per segment into a final score.

ValueDescription
LATESTUse the most recent attempt's score
BESTUse the highest score across all attempts
AVERAGEUse the average of all attempts

PerformanceLevel

Score-based classification for singing evaluation results.

Values

ValueScore RangeDisplay NameDescription
NEEDS_WORK< 0.3"Needs Work"Significant improvement needed
FAIR0.3 - 0.6"Fair"Room for improvement
GOOD0.6 - 0.8"Good"Solid performance
VERY_GOOD0.8 - 0.95"Very Good"Very strong performance
EXCELLENT>= 0.95"Excellent"Outstanding performance
NOT_EVALUATEDN/A"Not Evaluated"Could not evaluate (insufficient data)
NOT_DETECTED< 0"No Voice"No voice detected during segment

Properties

PropertyTypeDescription
displayNameStringHuman-readable display name for UI

Factory Methods

MethodDescription
PerformanceLevel.fromScore(score)Get level based on score (0.0-1.0, negative for NOT_DETECTED)
PerformanceLevel.fromCode(code)Convert from integer code (for JNI/C interop)

Kotlin

val level = PerformanceLevel.fromScore(0.85f)
// level == PerformanceLevel.VERY_GOOD
println(level.displayName) // "Very Good"

Swift

let level = PerformanceLevel.fromScore(0.85)
// level == .veryGood
print(level.displayName) // "Very Good"

PracticePhase

Practice phase during a CalibraLiveEval session.

Phase Progressions

Singalong: IDLE -> SINGING -> EVALUATED

  • Student sings with the reference audio simultaneously

Singafter: IDLE -> LISTENING -> SINGING -> EVALUATED

  • Student listens to reference first, then sings during their turn
ValueDescription
IDLENot practicing - waiting to start
LISTENINGReference playing, student not recording yet (singafter only)
SINGINGStudent is being recorded and evaluated
EVALUATEDSegment complete, score available

SessionPhase

Current phase of a CalibraLiveEval session.

ValueDescription
IDLESession created but not started
READYReference loaded, ready to begin practicing
PRACTICINGActively capturing and evaluating audio for a segment
BETWEEN_SEGMENTSFinished one segment, waiting before next
COMPLETEDAll segments completed or session manually finished
CANCELLEDSession was cancelled
ERRORAn error occurred

SessionState

Current state of a CalibraLiveEval session. Exposed as a StateFlow from CalibraLiveEval.

Properties

PropertyTypeDescription
phaseSessionPhaseCurrent phase of the session
activeSegmentIndexInt?Index of segment being practiced, or null if none
activeSegmentSegment?The segment being practiced, or null if none
currentPitchFloatCurrent detected pitch in Hz (-1 for unvoiced)
currentAmplitudeFloatCurrent audio amplitude (0.0 - 1.0)
segmentProgressFloatProgress through current segment (0.0 - 1.0)
completedSegmentsSet<Int>Set of segment indices that have been completed
errorString?Error message if phase is ERROR, null otherwise
isPracticingBooleanTrue if session is actively practicing
canBeginSegmentBooleanTrue if session is ready to start or between segments
isFinishedBooleanTrue if session is finished (completed, cancelled, or error)
completedCountIntNumber of completed segments

Static Members

MemberKotlinSwiftDescription
IdleSessionState.IDLE.idleInitial idle state

ActiveSegmentState

State of the currently active segment during practice.

Properties

PropertyTypeDescription
segmentIndexIntIndex of the segment
segmentSegmentThe segment being practiced
currentPitchFloatCurrent detected pitch in Hz (-1 for unvoiced)
currentAmplitudeFloatCurrent audio amplitude (0.0 - 1.0)
elapsedSecondsFloatTime elapsed since segment started
isCapturingBooleanWhether audio is currently being captured
progressFloatProgress through the segment (0.0 - 1.0)
remainingSecondsFloatTime remaining in seconds
hasVoiceBooleanTrue if detected pitch is valid

SessionConfig

Configuration for a CalibraLiveEval session.

Presets

PresetKotlinSwiftDescription
DefaultSessionConfig.DEFAULT.defaultBalanced, auto-advancing
PracticeSessionConfig.PRACTICE.practiceRepeats until 70% or 3 attempts, best score
KaraokeSessionConfig.KARAOKE.karaokeAlways advances, one attempt
PerformanceSessionConfig.PERFORMANCE.performanceStrict, one attempt, no repetition

Builder

Kotlin

val config = SessionConfig.Builder()
.preset(SessionConfig.PRACTICE)
.scoreThreshold(0.6f)
.maxAttempts(5)
.resultAggregation(ResultAggregation.BEST)
.build()

Swift

let config = SessionConfig.Builder()
.preset(.practice)
.scoreThreshold(0.6)
.maxAttempts(5)
.resultAggregation(.best)
.build()

Config Properties

PropertyTypeDefaultDescription
autoAdvanceBooleantrueAutomatically advance to next segment
scoreThresholdFloat0Min score to auto-advance (0 = disabled)
maxAttemptsInt0Max attempts before forced advance (0 = unlimited)
resultAggregationResultAggregationLATESTHow to aggregate multiple attempts
hopSizeInt320Hop size between frames in samples (320 = 20 ms at 16 kHz, 2 frames per buffer per ADR-020)
autoPhaseTransitionBooleantrueAuto transition LISTENING to SINGING in singafter mode
autoSegmentDetectionBooleantrueAuto detect segment end from player time

Builder Methods

MethodDescription
preset(config)Start from a preset configuration
autoAdvance(enabled)Enable or disable auto-advance
scoreThreshold(threshold)Set minimum score threshold (0 = disabled)
maxAttempts(max)Set maximum attempts (0 = unlimited)
resultAggregation(agg)Set how to aggregate multiple attempts
hopSize(samples)Set hop size between frames
autoPhaseTransition(enabled)Enable or disable auto phase transition
autoSegmentDetection(enabled)Enable or disable auto segment end detection

ScoringAlgorithm

Algorithm for computing note accuracy scores.

ValueDescription
SIMPLESimple threshold counting. Counts percentage of pitch samples within 35 cents of target. Good for beginners.
WEIGHTEDWeighted duration-aware scoring. Tighter thresholds, considers note duration. Good for advanced evaluation.

NoteEvalConfig

Configuration for note evaluation scoring.

Properties

PropertyTypeDefaultDescription
algorithmScoringAlgorithmSIMPLEAlgorithm for computing scores
boundaryToleranceMsInt0Milliseconds to skip at note start/end

Presets

PresetKotlinSwiftAlgorithmBoundary ToleranceDescription
LenientNoteEvalPreset.LENIENT.lenientSIMPLE200msBeginner-friendly
BalancedNoteEvalPreset.BALANCED.balancedSIMPLE100msStandard practice
StrictNoteEvalPreset.STRICT.strictWEIGHTED0msAdvanced/performance

For student key transposition, set studentKeyHz on the evaluation call (CalibraNoteEval.evaluate, CalibraMelodyEval via student.keyHz) or CalibraLiveEval.setStudentKeyHz(...) at runtime.

Breath types (moved to tessera)

The legacy calibra BreathMetrics { capacity, control, isValid } and the short-lived 2.0.0 BreathScore were both replaced by the unified tessera.model.BreathMetrics:

Old typeNew type
BreathMetrics { capacity: Float, control: Float, isValid: Boolean } (1.x calibra)BreathMetrics { controlScore, phrases, alignmentScore }
BreathScore { capacity: Float?, controlScore: Float } (2.0.0-only intermediate)BreathMetrics — capacity moved to phrases?.longestDuration
(none)BreathFunction — composable intermediate
(none)PhraseSummary — phrase-level structure
(none)Alignment match score is now BreathMetrics.alignmentScore: Float?

Error Types

Calibra follows the SDK-wide failure-semantics contract (ADR-022) and the exception hierarchy in ADR-011. There is no Calibra-specific exception type.

Failure semantics (ADR-022)

Failure kindHow it surfaces
SDK not initializedThrows VoxaTraceNotInitializedException (every facade calls VT.ensureInitialized() first)
License invalid / revokedThrows VoxaTraceKilledException
Caller bug / invalid input (empty samples, non-16kHz audio, malformed config)Throws IllegalArgumentException (via require())
Domain inconclusive (valid input, no usable result)Encoded in the return value — e.g. CalibraLiveEval.finishPracticingSegment() returns null, CalibraMelodyEval.evaluate(...) returns SingingResult.EMPTY — never thrown

All thrown types extend VoxaTraceException. See Authentication for VoxaTraceNotInitializedException / VoxaTraceKilledException details.

Kotlin

try {
val result = CalibraMelodyEval.evaluate(reference, student, extractor)
// Domain outcome: empty result when nothing could be scored.
if (result == SingingResult.EMPTY) {
println("Nothing to score (silent or unalignable recording)")
}
} catch (e: VoxaTraceNotInitializedException) {
println("Call VT.initialize(...) first")
} catch (e: IllegalArgumentException) {
// Caller bug, e.g. audio was not 16kHz.
println("Invalid input: ${e.message}")
}

Swift

IllegalArgumentException bridges through SKIE as a caller-catchable error (ADR-010 / ADR-022); domain outcomes come back as typed results, not optionals of optionals.

do {
let result = CalibraMelodyEval.evaluate(
reference: reference,
student: student,
contourExtractor: extractor
)
if result == SingingResult.companion.EMPTY {
print("Nothing to score")
}
} catch {
// IllegalArgumentException (invalid input) or an uninitialized-SDK error.
print("Evaluation failed: \(error.localizedDescription)")
}

Next Steps