WorkbenchParameters.java

package org.hammer.audio.experimental.acoustic.workbench;

import java.util.Objects;

/**
 * Configurable parameters for an acoustic localization workbench run.
 *
 * <p>Use {@link #defaults()} to obtain a {@link Builder} pre-populated with research-grade
 * defaults, then customise individual fields and call {@link Builder#build()}.
 *
 * @param blockSize frames per processed audio block (also used as default FFT size)
 * @param fftSize FFT size in samples (power of two)
 * @param maxPeaks maximum peaks per channel returned by the multi-peak detector
 * @param minSnr minimum peak-to-band-median SNR ratio for accepting a peak
 * @param bandMinHz lower bound of the frequency search band in Hz
 * @param bandMaxHz upper bound of the frequency search band in Hz
 * @param clusteringToleranceHz maximum frequency distance for merging peaks into one cluster in Hz
 * @param candidateGridSteps number of grid steps along each room axis
 * @param tdoaEstimatorType TDOA estimator strategy
 * @param trackerFrequencyMatchHz maximum frequency difference for matching an observation in Hz
 * @param trackerMissingFramesToDrop consecutive missed frames after which a track is dropped
 * @param trackerConfidenceDecay multiplicative confidence decay when a track is not observed
 * @param trackerConfidenceGain additive confidence gain when a track is observed
 */
public record WorkbenchParameters(
    int blockSize,
    int fftSize,
    int maxPeaks,
    double minSnr,
    double bandMinHz,
    double bandMaxHz,
    double clusteringToleranceHz,
    int candidateGridSteps,
    TdoaEstimatorType tdoaEstimatorType,
    double trackerFrequencyMatchHz,
    int trackerMissingFramesToDrop,
    double trackerConfidenceDecay,
    double trackerConfidenceGain) {

  /** TDOA estimator strategy for the workbench pipeline. */
  public enum TdoaEstimatorType {
    /** Integer-sample frequency-domain GCC-PHAT baseline and default. */
    GCC_PHAT,
    /** Sixteen-times interpolated GCC-PHAT with peak diagnostics and ambiguity evidence. */
    SUB_SAMPLE_GCC_PHAT,
    /** Time-domain normalised cross-correlation (faster, weaker under reflections). */
    CROSS_CORRELATION
  }

  // Compact constructor: validates inputs
  public WorkbenchParameters {
    Objects.requireNonNull(tdoaEstimatorType, "tdoaEstimatorType");
    if (!Double.isFinite(minSnr)) {
      throw new IllegalArgumentException("minSnr must be finite, got " + minSnr);
    }
    if (!Double.isFinite(bandMinHz) || !Double.isFinite(bandMaxHz)) {
      throw new IllegalArgumentException("frequency band bounds must be finite");
    }
    if (bandMinHz >= bandMaxHz) {
      throw new IllegalArgumentException(
          "bandMinHz (" + bandMinHz + ") must be less than bandMaxHz (" + bandMaxHz + ")");
    }
  }

  /** Return a builder pre-populated with defaults. */
  public static Builder defaults() {
    return new Builder();
  }

  /** Builder for {@link WorkbenchParameters}. */
  public static final class Builder {

    private int valBlockSize = 1024;
    private int valFftSize = 1024;
    private int valMaxPeaks = 3;
    private double valMinSnr = 2.0;
    private double valBandMinHz = 150.0;
    private double valBandMaxHz = 2500.0;
    private double valClusteringToleranceHz = 25.0;
    private int valCandidateGridSteps = 8;
    private TdoaEstimatorType valTdoaEstimatorType = TdoaEstimatorType.GCC_PHAT;
    private double valTrackerFrequencyMatchHz = 35.0;
    private int valTrackerMissingFramesToDrop = 4;
    private double valTrackerConfidenceDecay = 0.85;
    private double valTrackerConfidenceGain = 0.4;

    private Builder() {
      // use defaults()
    }

    /** Set block size (and default FFT size). */
    public Builder blockSize(int v) {
      this.valBlockSize = v;
      // Only adopt v as the default FFT size when it is itself a valid power of two.
      if (v > 0 && (v & (v - 1)) == 0) {
        this.valFftSize = v;
      }
      return this;
    }

    /** Set FFT size explicitly (must be a positive power of two). */
    public Builder fftSize(int v) {
      if (v <= 0 || (v & (v - 1)) != 0) {
        throw new IllegalArgumentException("fftSize must be a positive power of two, got " + v);
      }
      this.valFftSize = v;
      return this;
    }

    /** Set maximum peaks per channel. */
    public Builder maxPeaks(int v) {
      this.valMaxPeaks = v;
      return this;
    }

    /** Set minimum SNR for peak acceptance. */
    public Builder minSnr(double v) {
      this.valMinSnr = v;
      return this;
    }

    /** Set lower frequency band boundary in Hz. */
    public Builder bandMinHz(double v) {
      this.valBandMinHz = v;
      return this;
    }

    /** Set upper frequency band boundary in Hz. */
    public Builder bandMaxHz(double v) {
      this.valBandMaxHz = v;
      return this;
    }

    /** Set frequency clustering tolerance in Hz. */
    public Builder clusteringToleranceHz(double v) {
      this.valClusteringToleranceHz = v;
      return this;
    }

    /** Set number of candidate grid steps. */
    public Builder candidateGridSteps(int v) {
      if (v <= 0) {
        throw new IllegalArgumentException("candidateGridSteps must be positive, got " + v);
      }
      this.valCandidateGridSteps = v;
      return this;
    }

    /** Set TDOA estimator type. */
    public Builder tdoaEstimatorType(TdoaEstimatorType v) {
      this.valTdoaEstimatorType = Objects.requireNonNull(v, "tdoaEstimatorType");
      return this;
    }

    /** Set tracker frequency match tolerance in Hz. */
    public Builder trackerFrequencyMatchHz(double v) {
      this.valTrackerFrequencyMatchHz = v;
      return this;
    }

    /** Set number of missing frames before a track is dropped. */
    public Builder trackerMissingFramesToDrop(int v) {
      this.valTrackerMissingFramesToDrop = v;
      return this;
    }

    /** Set tracker confidence decay factor. */
    public Builder trackerConfidenceDecay(double v) {
      this.valTrackerConfidenceDecay = v;
      return this;
    }

    /** Set tracker confidence gain per observation. */
    public Builder trackerConfidenceGain(double v) {
      this.valTrackerConfidenceGain = v;
      return this;
    }

    /** Build the parameter object. */
    public WorkbenchParameters build() {
      return new WorkbenchParameters(
          valBlockSize,
          valFftSize,
          valMaxPeaks,
          valMinSnr,
          valBandMinHz,
          valBandMaxHz,
          valClusteringToleranceHz,
          valCandidateGridSteps,
          valTdoaEstimatorType,
          valTrackerFrequencyMatchHz,
          valTrackerMissingFramesToDrop,
          valTrackerConfidenceDecay,
          valTrackerConfidenceGain);
    }
  }
}