SoundEmitter2D.java
package org.hammer.audio.experimental.acoustic.simulation;
import org.hammer.audio.geometry.Vector2;
/** Synthetic moving tonal emitter for repeatable acoustic localization experiments. */
public record SoundEmitter2D(
Vector2 startMeters, Vector2 velocityMetersPerSecond, double frequencyHz, double amplitude) {
/** Create a synthetic emitter. */
public SoundEmitter2D {
if (startMeters == null || velocityMetersPerSecond == null) {
throw new IllegalArgumentException("positions must not be null");
}
if (!(frequencyHz > 0.0) || !Double.isFinite(frequencyHz)) {
throw new IllegalArgumentException("frequencyHz must be finite and > 0");
}
if (amplitude < 0.0 || amplitude > 1.0 || !Double.isFinite(amplitude)) {
throw new IllegalArgumentException("amplitude must be finite and in [0,1]");
}
}
/** Position at simulation time {@code seconds}. */
public Vector2 positionAt(double seconds) {
return startMeters.plus(velocityMetersPerSecond.scale(seconds));
}
/** Sample emitted at simulation time {@code seconds}. */
public double sampleAt(double seconds) {
return amplitude * Math.sin(2.0 * Math.PI * frequencyHz * seconds);
}
/** Sample emitted at simulation time {@code seconds} with a Doppler-shifted frequency. */
public double sampleAt(double seconds, double observedFrequencyHz) {
if (!(observedFrequencyHz > 0.0) || !Double.isFinite(observedFrequencyHz)) {
throw new IllegalArgumentException("observedFrequencyHz must be finite and > 0");
}
return amplitude * Math.sin(2.0 * Math.PI * observedFrequencyHz * seconds);
}
}