Class AnalysisPipeline

java.lang.Object
com.tino1b2be.dtmf.internal.AnalysisPipeline

public final class AnalysisPipeline extends Object
Block-level DTMF detection engine shared by DtmfDecoder (batch) and DtmfDetector (push).

One AnalysisPipeline owns:

Samples enter via accept(double) or acceptAll(double[], int, int) one at a time. Every N = analysisBlockSize samples, the block buffer is windowed (unless the configured WindowFunction is WindowFunction.RECTANGULAR) and fed through the Goertzel bank in a single GoertzelBank.computeMagnitudesSquaredInto(double[], double[]) call. The peaks of the low (indices 0–3 in FrequencyBins.ALL_EIGHT) and high (indices 4–7) groups are picked by argmax; the candidate is validated by ConfidenceScorer.compute(double, double, double) against DtmfConfig.detectionThreshold() and by TwistEvaluator.withinTolerance(double, DtmfConfig). The valid candidate (or "no candidate") drives the state machine.

Emissions are handed to a Consumer<DtmfTone> sink supplied at construction time. A tone is emitted on the first non-confirming block after the state machine has entered Active, provided its duration (in samples) is at least round(config.minimumToneDuration() * config.sampleRate()). Tones shorter than the minimum are discarded.

Sample indices reported on each emitted DtmfTone are cumulative from the first sample ever passed to this pipeline instance; this gives the push detector chunk invariance (Requirement 6.7) by construction.

Instances are mutable and not thread-safe. Each pipeline is tagged with a channel value at construction so the stereo-independent detector can run two pipelines in parallel with the correct channel tag on each emission.

Package-private by convention: com.tino1b2be.dtmf.internal.* is not part of the published API. The type is public so tests in the same package and the DtmfDetector in com.tino1b2be.dtmf can reach it via the existing internal-friend pattern other helpers follow.

Since:
2.0.0
  • Constructor Details

    • AnalysisPipeline

      public AnalysisPipeline(DtmfConfig config, int channel, Consumer<DtmfTone> sink)
      Construct a pipeline bound to the given config, channel tag, and tone sink.
      Parameters:
      config - configuration whose sample rate, analysis block size, detection threshold, confirmation frames, minimum tone duration, window function, and twist tolerances all feed the state machine; non-null
      channel - channel tag written onto every emitted DtmfTone; must be >= 0. Detectors use 0 for mono or left, 1 for right
      sink - consumer invoked with each confirmed tone, at the first non-confirming block (Tone_End_Event); non-null
      Throws:
      NullPointerException - if config or sink is null
      IllegalArgumentException - if channel < 0
  • Method Details

    • accept

      public void accept(double sample)
      Feed a single sample through the pipeline. Samples accumulate into an internal block buffer; every analysisBlockSize samples a block is evaluated and the state machine advanced, possibly emitting a DtmfTone to the sink.
      Parameters:
      sample - next sample in the signal (any finite double)
    • acceptAll

      public void acceptAll(double[] samples, int offset, int length)
      Feed a range of samples through the pipeline. Equivalent to calling accept(double) in order for each element in samples[offset .. offset + length).
      Parameters:
      samples - source array; non-null
      offset - starting index; must satisfy 0 <= offset && offset + length <= samples.length
      length - number of samples to consume; must be >= 0
      Throws:
      NullPointerException - if samples is null
      IndexOutOfBoundsException - if offset/length describe a range outside samples
    • flush

      public void flush()
      Finalise any in-progress tone. If the state machine is in AnalysisPipeline.State.ACTIVE or AnalysisPipeline.State.ENDING and the tone duration so far meets the configured minimum, the tone is emitted with endSample = currentSample. The internal block buffer contents (a possibly-partial block) are not processed; flush is a state-machine termination, not a block boundary.

      After flush() the pipeline is returned to AnalysisPipeline.State.IDLE and can be fed further samples.

    • samplesProcessed

      public long samplesProcessed()
      Returns the cumulative number of samples that have been fed to accept(double) since this pipeline was constructed..
      Returns:
      the cumulative number of samples that have been fed to accept(double) since this pipeline was constructed