AcousticGroundTruth.java
package org.hammer.audio.experimental.acoustic.scenario;
import java.util.List;
/**
* Acoustic ground truth for a single source in a scenario.
*
* <p>Only {@link #fundamentalFrequencyHz()} is required. All other fields may be {@code null} to
* represent partial ground truth (e.g. harmonics or modulation parameters that are unknown for a
* real-world recording).
*
* <p>Use {@link #ofFrequency(double)} when only the fundamental frequency is known.
*
* @param fundamentalFrequencyHz emitted fundamental frequency in Hz
* @param harmonicCount optional number of harmonic partials
* @param harmonics optional harmonic amplitude profile
* @param modulationFrequencyHz optional modulation frequency in Hz
* @param modulationDepth optional modulation depth in [0, 1]
* @param jitter optional maximum signed per-sample frequency deviation in Hz
* @param drift optional frequency drift parameter
* @param noiseAmplitude optional additive white-noise amplitude in [0, 1]
* @param signalPower optional signal power parameter
*/
public record AcousticGroundTruth(
double fundamentalFrequencyHz,
Integer harmonicCount,
List<Double> harmonics,
Double modulationFrequencyHz,
Double modulationDepth,
Double jitter,
Double drift,
Double noiseAmplitude,
Double signalPower) {
/* Validate required fields and defensively copy {@link #harmonics()} when provided. */
public AcousticGroundTruth {
if (fundamentalFrequencyHz <= 0.0 || !Double.isFinite(fundamentalFrequencyHz)) {
throw new IllegalArgumentException("fundamentalFrequencyHz must be finite and > 0");
}
if (harmonicCount != null && harmonicCount < 1) {
throw new IllegalArgumentException("harmonicCount must be >= 1 when provided");
}
harmonics = harmonics != null ? List.copyOf(harmonics) : null;
if (harmonics != null) {
for (int i = 0; i < harmonics.size(); i++) {
Double harmonic = harmonics.get(i);
if (harmonic == null || !Double.isFinite(harmonic)) {
throw new IllegalArgumentException(
"harmonics[%d] must be non-null and finite".formatted(i));
}
}
}
if (harmonicCount != null && harmonics != null && harmonicCount != harmonics.size()) {
throw new IllegalArgumentException(
"harmonicCount must match harmonics.size() when both are provided");
}
if (modulationFrequencyHz != null
&& (!Double.isFinite(modulationFrequencyHz) || modulationFrequencyHz < 0.0)) {
throw new IllegalArgumentException(
"modulationFrequencyHz must be finite and >= 0 when provided");
}
if (modulationDepth != null
&& (!Double.isFinite(modulationDepth) || modulationDepth < 0.0 || modulationDepth > 1.0)) {
throw new IllegalArgumentException("modulationDepth must be in [0, 1] when provided");
}
if (jitter != null && (!Double.isFinite(jitter) || jitter < 0.0)) {
throw new IllegalArgumentException("jitter must be finite and >= 0 when provided");
}
if (drift != null && !Double.isFinite(drift)) {
throw new IllegalArgumentException("drift must be finite when provided");
}
if (noiseAmplitude != null
&& (!Double.isFinite(noiseAmplitude) || noiseAmplitude < 0.0 || noiseAmplitude > 1.0)) {
throw new IllegalArgumentException("noiseAmplitude must be in [0, 1] when provided");
}
if (signalPower != null && !Double.isFinite(signalPower)) {
throw new IllegalArgumentException("signalPower must be finite when provided");
}
}
/**
* Create acoustic truth with only the fundamental frequency known.
*
* @param fundamentalFrequencyHz emitted fundamental frequency in Hz; must be finite and positive
*/
public static AcousticGroundTruth ofFrequency(double fundamentalFrequencyHz) {
return new AcousticGroundTruth(
fundamentalFrequencyHz, null, null, null, null, null, null, null, null);
}
}