Function bodies 463 total
compute_fatigue function · rust · L13-L49 (37 LOC)core/src/analysis/fatigue.rs
pub fn compute_fatigue(
mpt_per_trial: Vec<f32>,
cpps_per_trial: Vec<Option<f32>>,
effort_per_trial: Vec<u8>,
) -> Option<FatigueAnalysis> {
if mpt_per_trial.len() < 2 {
return None;
}
// Compute MPT slope: trial index (0, 1, 2, ...) vs MPT
let mpt_points: Vec<(f32, f32)> = mpt_per_trial
.iter()
.enumerate()
.map(|(i, &mpt)| (i as f32, mpt))
.collect();
let (mpt_slope, _) = util::linear_regression(&mpt_points);
// Compute CPPS slope from trials that have CPPS values
let cpps_points: Vec<(f32, f32)> = cpps_per_trial
.iter()
.enumerate()
.filter_map(|(i, cpps)| cpps.map(|c| (i as f32, c)))
.collect();
let cpps_slope = if cpps_points.len() >= 2 {
util::linear_regression(&cpps_points).0
} else {
0.0
};
Some(FatigueAnalysis {
mpt_per_trial,
cpps_per_trial,
effort_per_trial,
mpt_slope,
cpps_slope,
})
}compute_from_raw function · rust · L53-L60 (8 LOC)core/src/analysis/fatigue.rs
pub fn compute_from_raw(raw: &FatigueRawData) -> Option<FatigueAnalysis> {
let cpps_per_trial = vec![None; raw.mpt_per_trial.len()];
compute_fatigue(
raw.mpt_per_trial.clone(),
cpps_per_trial,
raw.effort_per_trial.clone(),
)
}declining_mpt_negative_slope function · rust · L67-L79 (13 LOC)core/src/analysis/fatigue.rs
fn declining_mpt_negative_slope() {
let result = compute_fatigue(
vec![10.0, 9.0, 8.0, 7.0, 6.0],
vec![None; 5],
vec![3, 4, 5, 6, 7],
)
.unwrap();
assert!(
result.mpt_slope < -0.5,
"Declining MPT should have negative slope, got {:.3}",
result.mpt_slope
);
}stable_mpt_flat_slope function · rust · L82-L94 (13 LOC)core/src/analysis/fatigue.rs
fn stable_mpt_flat_slope() {
let result = compute_fatigue(
vec![10.0, 10.0, 10.0, 10.0],
vec![None; 4],
vec![3, 3, 3, 3],
)
.unwrap();
assert!(
result.mpt_slope.abs() < 0.1,
"Stable MPT should have ~0 slope, got {:.3}",
result.mpt_slope
);
}cpps_slope_computed function · rust · L97-L109 (13 LOC)core/src/analysis/fatigue.rs
fn cpps_slope_computed() {
let result = compute_fatigue(
vec![10.0, 9.0, 8.0],
vec![Some(8.0), Some(7.0), Some(6.0)],
vec![3, 4, 5],
)
.unwrap();
assert!(
result.cpps_slope < -0.5,
"Declining CPPS should have negative slope, got {:.3}",
result.cpps_slope
);
}cpps_slope_zero_when_no_cpps function · rust · L112-L120 (9 LOC)core/src/analysis/fatigue.rs
fn cpps_slope_zero_when_no_cpps() {
let result = compute_fatigue(
vec![10.0, 9.0, 8.0],
vec![None, None, None],
vec![3, 4, 5],
)
.unwrap();
assert_eq!(result.cpps_slope, 0.0);
}too_few_trials function · rust · L123-L125 (3 LOC)core/src/analysis/fatigue.rs
fn too_few_trials() {
assert!(compute_fatigue(vec![10.0], vec![None], vec![3]).is_none());
}Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
compute_from_raw_no_cpps function · rust · L128-L137 (10 LOC)core/src/analysis/fatigue.rs
fn compute_from_raw_no_cpps() {
let raw = FatigueRawData {
mpt_per_trial: vec![10.0, 9.0, 8.0, 7.0, 6.0],
effort_per_trial: vec![3, 4, 5, 6, 7],
};
let result = compute_from_raw(&raw).unwrap();
assert!(result.mpt_slope < -0.5);
assert_eq!(result.cpps_slope, 0.0); // no audio = no CPPS
assert!(result.cpps_per_trial.iter().all(|c| c.is_none()));
}analyze function · rust · L10-L78 (69 LOC)core/src/analysis/reading.rs
pub fn analyze(
samples: &[f32],
sample_rate: u32,
pitch_config: &pitch::PitchConfig,
) -> Result<ReadingAnalysis> {
// Activity detection — ground truth for sound production
let activity_result = activity::detect_activity(samples, sample_rate, &activity::ActivityConfig::default());
let result = pitch::extract_contour_with_fallback(samples, sample_rate, pitch_config);
let pitch_contour = &result.contour;
let mut frequencies = pitch::voiced_frequencies(pitch_contour);
let vf = pitch::voiced_fraction(pitch_contour);
if frequencies.is_empty() {
anyhow::bail!(
"No voiced frames detected in reading passage. \
Recording may be silent or too quiet."
);
}
// F0 statistics
let mean_f0: f32 = frequencies.iter().sum::<f32>() / frequencies.len() as f32;
let variance: f32 = frequencies.iter().map(|&f| (f - mean_f0).powi(2)).sum::<f32>()
/ frequencies.len() as f32;
let f0_std = variance.sanalyze function · rust · L16-L53 (38 LOC)core/src/analysis/scale.rs
pub fn analyze(
samples: &[f32],
sample_rate: u32,
pitch_config: &pitch::PitchConfig,
) -> Result<ScaleAnalysis> {
let pitch_contour = pitch::extract_pitch_contour(samples, sample_rate, pitch_config);
let mut frequencies = pitch::voiced_frequencies(&pitch_contour);
if frequencies.is_empty() {
anyhow::bail!(
"No voiced frames detected in scale recording. \
Recording may be silent or too quiet."
);
}
// Sort for percentile computation
frequencies.sort_by(|a, b| a.partial_cmp(b).unwrap());
let floor = contour::percentile(&frequencies, 0.05);
let ceiling = contour::percentile(&frequencies, 0.95);
let range_hz = ceiling - floor;
// Semitones: the musical unit of pitch interval.
// 12 semitones = 1 octave = doubling of frequency.
// semitones = 12 * log2(f2 / f1)
let range_semitones = if floor > 0.0 {
12.0 * (ceiling / floor).log2()
} else {
0.0
};
Ok(Scalanalyze function · rust · L11-L98 (88 LOC)core/src/analysis/sustained.rs
pub fn analyze(
samples: &[f32],
sample_rate: u32,
pitch_config: &pitch::PitchConfig,
) -> Result<SustainedAnalysis> {
// Activity detection — ground truth for sound production
let activity_result = activity::detect_activity(samples, sample_rate, &activity::ActivityConfig::default());
let result = pitch::extract_contour_with_fallback(samples, sample_rate, pitch_config);
let contour = &result.contour;
let frequencies = pitch::voiced_frequencies(contour);
if frequencies.is_empty() {
anyhow::bail!(
"No voiced frames detected in sustained vowel. \
Recording may be silent or too quiet."
);
}
// F0 statistics
let mean_f0: f32 = frequencies.iter().sum::<f32>() / frequencies.len() as f32;
let variance: f32 = frequencies.iter().map(|&f| (f - mean_f0).powi(2)).sum::<f32>()
/ frequencies.len() as f32;
let f0_std = variance.sqrt();
// Prefer gated jitter/shimmer (tier 1/2 frames only) compute_sz function · rust · L13-L34 (22 LOC)core/src/analysis/sz.rs
pub fn compute_sz(s_durations: Vec<f32>, z_durations: Vec<f32>) -> Option<SzAnalysis> {
if s_durations.is_empty() || z_durations.is_empty() {
return None;
}
let mean_s = s_durations.iter().sum::<f32>() / s_durations.len() as f32;
let mean_z = z_durations.iter().sum::<f32>() / z_durations.len() as f32;
if mean_z == 0.0 {
return None;
}
let sz_ratio = mean_s / mean_z;
Some(SzAnalysis {
s_durations,
z_durations,
mean_s,
mean_z,
sz_ratio,
})
}compute_from_raw function · rust · L37-L39 (3 LOC)core/src/analysis/sz.rs
pub fn compute_from_raw(raw: &SzRawData) -> Option<SzAnalysis> {
compute_sz(raw.s_durations.clone(), raw.z_durations.clone())
}normal_ratio function · rust · L46-L50 (5 LOC)core/src/analysis/sz.rs
fn normal_ratio() {
let result = compute_sz(vec![10.0, 11.0], vec![10.5, 10.0]).unwrap();
assert!((result.sz_ratio - 1.0).abs() < 0.15);
assert!((result.mean_s - 10.5).abs() < 0.01);
}concerning_ratio function · rust · L53-L57 (5 LOC)core/src/analysis/sz.rs
fn concerning_ratio() {
// /s/ much longer than /z/ — cord dysfunction
let result = compute_sz(vec![15.0, 14.0], vec![8.0, 7.0]).unwrap();
assert!(result.sz_ratio > 1.4, "Expected ratio > 1.4, got {:.2}", result.sz_ratio);
}All rows scored by the Repobility analyzer (https://repobility.com)
single_trial_each function · rust · L60-L63 (4 LOC)core/src/analysis/sz.rs
fn single_trial_each() {
let result = compute_sz(vec![12.0], vec![10.0]).unwrap();
assert!((result.sz_ratio - 1.2).abs() < 0.01);
}empty_s_returns_none function · rust · L66-L68 (3 LOC)core/src/analysis/sz.rs
fn empty_s_returns_none() {
assert!(compute_sz(vec![], vec![10.0]).is_none());
}empty_z_returns_none function · rust · L71-L73 (3 LOC)core/src/analysis/sz.rs
fn empty_z_returns_none() {
assert!(compute_sz(vec![10.0], vec![]).is_none());
}compute_from_raw_roundtrip function · rust · L76-L84 (9 LOC)core/src/analysis/sz.rs
fn compute_from_raw_roundtrip() {
let raw = SzRawData {
s_durations: vec![15.0, 14.0],
z_durations: vec![8.0, 7.0],
};
let result = compute_from_raw(&raw).unwrap();
assert!(result.sz_ratio > 1.4);
assert_eq!(result.s_durations, vec![15.0, 14.0]);
}default function · rust · L53-L63 (11 LOC)core/src/config.rs
fn default() -> Self {
Self {
pitch_floor_hz: 30.0,
pitch_ceiling_hz: 1000.0,
frame_size_ms: 30.0,
hop_size_ms: 10.0,
sustained_ceiling_hz: 500.0,
reading_ceiling_hz: 600.0,
thresholds: ThresholdConfig::default(),
}
}pitch_config_for function · rust · L68-L82 (15 LOC)core/src/config.rs
pub fn pitch_config_for(&self, exercise: &str) -> PitchConfig {
let ceiling = match exercise {
"sustained" => self.sustained_ceiling_hz,
"reading" => self.reading_ceiling_hz,
_ => self.pitch_ceiling_hz, // scale uses global ceiling
};
PitchConfig {
pitch_floor_hz: self.pitch_floor_hz,
pitch_ceiling_hz: ceiling,
frame_size_ms: self.frame_size_ms,
hop_size_ms: self.hop_size_ms,
..PitchConfig::default()
}
}default function · rust · L86-L93 (8 LOC)core/src/config.rs
fn default() -> Self {
Self {
jitter_pathological: 1.04,
shimmer_pathological: 3.81,
hnr_low: 7.0,
hnr_normal: 20.0,
}
}default function · rust · L97-L106 (10 LOC)core/src/config.rs
fn default() -> Self {
Self {
reading_passage: "\
When the sunlight strikes raindrops in the air, they act as a prism \
and form a rainbow. The rainbow is a division of white light into \
many beautiful colors. These take the shape of a long round arch, \
with its path high above, and its two ends apparently beyond the horizon."
.into(),
}
}Repobility · code-quality intelligence · https://repobility.com
default function · rust · L110-L116 (7 LOC)core/src/config.rs
fn default() -> Self {
Self {
sample_rate: 44100,
channels: 1,
device: "default".into(),
}
}from function · rust · L121-L129 (9 LOC)core/src/config.rs
fn from(cfg: &AnalysisConfig) -> Self {
PitchConfig {
pitch_floor_hz: cfg.pitch_floor_hz,
pitch_ceiling_hz: cfg.pitch_ceiling_hz,
frame_size_ms: cfg.frame_size_ms,
hop_size_ms: cfg.hop_size_ms,
..PitchConfig::default()
}
}default_config_values function · rust · L137-L141 (5 LOC)core/src/config.rs
fn default_config_values() {
let cfg = AnalysisConfig::default();
assert_eq!(cfg.pitch_floor_hz, 30.0);
assert_eq!(cfg.thresholds.jitter_pathological, 1.04);
}pitch_config_conversion function · rust · L144-L149 (6 LOC)core/src/config.rs
fn pitch_config_conversion() {
let cfg = AnalysisConfig::default();
let pitch_cfg: PitchConfig = (&cfg).into();
assert_eq!(pitch_cfg.pitch_floor_hz, 30.0);
assert_eq!(pitch_cfg.hop_size_ms, 10.0);
}pitch_config_for_sustained function · rust · L152-L156 (5 LOC)core/src/config.rs
fn pitch_config_for_sustained() {
let cfg = AnalysisConfig::default();
let pitch = cfg.pitch_config_for("sustained");
assert_eq!(pitch.pitch_ceiling_hz, 500.0);
}pitch_config_for_reading function · rust · L159-L163 (5 LOC)core/src/config.rs
fn pitch_config_for_reading() {
let cfg = AnalysisConfig::default();
let pitch = cfg.pitch_config_for("reading");
assert_eq!(pitch.pitch_ceiling_hz, 600.0);
}pitch_config_for_scale function · rust · L166-L170 (5 LOC)core/src/config.rs
fn pitch_config_for_scale() {
let cfg = AnalysisConfig::default();
let pitch = cfg.pitch_config_for("scale");
assert_eq!(pitch.pitch_ceiling_hz, 1000.0);
}default function · rust · L19-L27 (9 LOC)core/src/dsp/activity.rs
fn default() -> Self {
Self {
threshold_on_db: -45.0,
threshold_off_db: -50.0,
min_active_ms: 80.0,
min_silent_ms: 120.0,
frame_size_ms: 10.0,
}
}Want fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
detect_activity function · rust · L44-L111 (68 LOC)core/src/dsp/activity.rs
pub fn detect_activity(
samples: &[f32],
sample_rate: u32,
config: &ActivityConfig,
) -> ActivityResult {
let sr = sample_rate as f32;
let frame_size = (config.frame_size_ms / 1000.0 * sr) as usize;
if frame_size == 0 || samples.len() < frame_size {
return ActivityResult {
active_frames: Vec::new(),
active_fraction: 0.0,
};
}
// Step 1: Compute RMS dB per frame
let mut rms_db_values = Vec::new();
let mut pos = 0;
while pos + frame_size <= samples.len() {
let frame = &samples[pos..pos + frame_size];
let rms = frame_rms(frame);
let db = if rms > 0.0 {
20.0 * rms.log10()
} else {
f32::NEG_INFINITY
};
rms_db_values.push(db);
pos += frame_size;
}
// Step 2: Apply hysteresis
let mut active_frames: Vec<bool> = Vec::with_capacity(rms_db_values.len());
let mut is_active = false;
for &db in &rms_db_values {
remove_short_segments function · rust · L114-L136 (23 LOC)core/src/dsp/activity.rs
fn remove_short_segments(frames: &mut [bool], target_value: bool, min_length: usize) {
if frames.is_empty() || min_length == 0 {
return;
}
let mut i = 0;
while i < frames.len() {
if frames[i] == target_value {
let start = i;
while i < frames.len() && frames[i] == target_value {
i += 1;
}
let run_len = i - start;
if run_len < min_length {
for frame in &mut frames[start..i] {
*frame = !target_value;
}
}
} else {
i += 1;
}
}
}voiced_quality function · rust · L140-L163 (24 LOC)core/src/dsp/activity.rs
pub fn voiced_quality(contour: &[PitchFrame], active_frames: &[bool]) -> f32 {
let len = contour.len().min(active_frames.len());
if len == 0 {
return 0.0;
}
let mut active_count = 0;
let mut pitched_in_active = 0;
for i in 0..len {
if active_frames[i] {
active_count += 1;
if contour[i].frequency.is_some() {
pitched_in_active += 1;
}
}
}
if active_count == 0 {
return 0.0;
}
pitched_in_active as f32 / active_count as f32
}frame_rms function · rust · L164-L171 (8 LOC)core/src/dsp/activity.rs
fn frame_rms(samples: &[f32]) -> f32 {
if samples.is_empty() {
return 0.0;
}
let sum_sq: f32 = samples.iter().map(|&s| s * s).sum();
(sum_sq / samples.len() as f32).sqrt()
}sine_wave function · rust · L177-L186 (10 LOC)core/src/dsp/activity.rs
fn sine_wave(freq_hz: f32, sample_rate: u32, duration_secs: f32) -> Vec<f32> {
let num_samples = (sample_rate as f32 * duration_secs) as usize;
(0..num_samples)
.map(|i| {
let t = i as f32 / sample_rate as f32;
0.5 * (2.0 * PI * freq_hz * t).sin()
})
.collect()
}default_config function · rust · L187-L190 (4 LOC)core/src/dsp/activity.rs
fn default_config() -> ActivityConfig {
ActivityConfig::default()
}sine_wave_fully_active function · rust · L193-L201 (9 LOC)core/src/dsp/activity.rs
fn sine_wave_fully_active() {
let samples = sine_wave(100.0, 44100, 1.0);
let result = detect_activity(&samples, 44100, &default_config());
assert!(
result.active_fraction > 0.9,
"Sine wave should be mostly active, got {:.2}",
result.active_fraction
);
}silence_fully_inactive function · rust · L204-L212 (9 LOC)core/src/dsp/activity.rs
fn silence_fully_inactive() {
let samples = vec![0.0; 44100];
let result = detect_activity(&samples, 44100, &default_config());
assert!(
result.active_fraction < 0.01,
"Silence should be inactive, got {:.2}",
result.active_fraction
);
}Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
signal_with_gap function · rust · L215-L238 (24 LOC)core/src/dsp/activity.rs
fn signal_with_gap() {
let sr = 44100u32;
// 500ms tone + 200ms silence + 500ms tone
let mut samples = sine_wave(100.0, sr, 0.5);
samples.extend(vec![0.0; (sr as f32 * 0.2) as usize]);
samples.extend(sine_wave(100.0, sr, 0.5));
let result = detect_activity(&samples, sr, &default_config());
// Should have two active segments with a gap
let total_ms = result.active_frames.len() as f32 * 10.0;
let active_ms = result.active_fraction * total_ms;
// ~1000ms of signal, expect ~800-1000ms active (some ramp-up at edges)
assert!(
active_ms > 700.0,
"Expected significant active time, got {active_ms:.0}ms"
);
assert!(
result.active_fraction < 0.95,
"Gap should cause some inactive frames, got {:.2}",
result.active_fraction
);
}hysteresis_prevents_toggling function · rust · L241-L272 (32 LOC)core/src/dsp/activity.rs
fn hysteresis_prevents_toggling() {
let sr = 44100u32;
// Signal hovering near threshold: alternate between -44 dB and -46 dB
// This tests that hysteresis prevents rapid on/off toggling
let config = ActivityConfig {
threshold_on_db: -45.0,
threshold_off_db: -50.0,
..default_config()
};
// Create a signal at ~-44 dB (just above on threshold)
// amplitude for -44 dB: 10^(-44/20) ≈ 0.0063
let amp = 0.0063;
let samples: Vec<f32> = (0..sr)
.map(|i| amp * (2.0 * PI * 100.0 * i as f32 / sr as f32).sin())
.collect();
let result = detect_activity(&samples, sr, &config);
// Count transitions
let transitions: usize = result
.active_frames
.windows(2)
.filter(|w| w[0] != w[1])
.count();
// With hysteresis, there should be very few transitions (0 or 2 at most)
assert!(
short_transient_filtered function · rust · L275-L289 (15 LOC)core/src/dsp/activity.rs
fn short_transient_filtered() {
let sr = 44100u32;
// Silence with a 50ms burst in the middle (should be filtered at min_active=80ms)
let mut samples = vec![0.0; (sr as f32 * 0.5) as usize];
let burst = sine_wave(100.0, sr, 0.05); // 50ms
samples.extend(burst);
samples.extend(vec![0.0; (sr as f32 * 0.5) as usize]);
let result = detect_activity(&samples, sr, &default_config());
assert!(
result.active_fraction < 0.05,
"50ms transient should be filtered by min_active_ms=80ms, got {:.2}",
result.active_fraction
);
}voiced_quality_all_pitched function · rust · L292-L301 (10 LOC)core/src/dsp/activity.rs
fn voiced_quality_all_pitched() {
let contour: Vec<PitchFrame> = (0..10)
.map(|i| PitchFrame {
time: i as f32 * 0.01,
frequency: Some(100.0),
})
.collect();
let active = vec![true; 10];
assert!((voiced_quality(&contour, &active) - 1.0).abs() < 0.01);
}voiced_quality_no_active function · rust · L304-L313 (10 LOC)core/src/dsp/activity.rs
fn voiced_quality_no_active() {
let contour: Vec<PitchFrame> = (0..10)
.map(|i| PitchFrame {
time: i as f32 * 0.01,
frequency: Some(100.0),
})
.collect();
let active = vec![false; 10];
assert!((voiced_quality(&contour, &active)).abs() < 0.01);
}voiced_quality_half_pitched function · rust · L316-L325 (10 LOC)core/src/dsp/activity.rs
fn voiced_quality_half_pitched() {
let contour: Vec<PitchFrame> = (0..10)
.map(|i| PitchFrame {
time: i as f32 * 0.01,
frequency: if i < 5 { Some(100.0) } else { None },
})
.collect();
let active = vec![true; 10];
assert!((voiced_quality(&contour, &active) - 0.5).abs() < 0.01);
}voiced_runs function · rust · L10-L36 (27 LOC)core/src/dsp/contour.rs
pub fn voiced_runs(contour: &[PitchFrame]) -> Vec<(usize, usize)> {
let mut runs = Vec::new();
let mut start = None;
for (i, frame) in contour.iter().enumerate() {
match (frame.frequency.is_some(), start) {
// Voiced frame, not currently in a run → start one
(true, None) => start = Some(i),
// Voiced frame, already in a run → continue
(true, Some(_)) => {}
// Unvoiced frame, was in a run → end it
(false, Some(s)) => {
runs.push((s, i - 1));
start = None;
}
// Unvoiced frame, not in a run → nothing to do
(false, None) => {}
}
}
// Don't forget a run that extends to the end of the contour
if let Some(s) = start {
runs.push((s, contour.len() - 1));
}
runs
}run_duration_secs function · rust · L68-L70 (3 LOC)core/src/dsp/contour.rs
pub fn run_duration_secs(start: usize, end: usize, hop_size_ms: f32) -> f32 {
(end - start + 1) as f32 * hop_size_ms / 1000.0
}All rows scored by the Repobility analyzer (https://repobility.com)
percentile function · rust · L74-L78 (5 LOC)core/src/dsp/contour.rs
pub fn percentile(sorted: &[f32], p: f32) -> f32 {
assert!(!sorted.is_empty(), "Cannot compute percentile of empty slice");
let idx = (p * (sorted.len() - 1) as f32).round() as usize;
sorted[idx.min(sorted.len() - 1)]
}frame function · rust · L83-L89 (7 LOC)core/src/dsp/contour.rs
fn frame(time: f32, freq: Option<f32>) -> PitchFrame {
PitchFrame {
time,
frequency: freq,
}
}single_voiced_run function · rust · L92-L100 (9 LOC)core/src/dsp/contour.rs
fn single_voiced_run() {
let contour = vec![
frame(0.0, Some(100.0)),
frame(0.01, Some(101.0)),
frame(0.02, Some(99.0)),
];
let runs = voiced_runs(&contour);
assert_eq!(runs, vec![(0, 2)]);
}page 1 / 10next ›