Class DtmfDetector

java.lang.Object
com.tino1b2be.dtmf.DtmfDetector

public final class DtmfDetector extends Object
Push-based DTMF detector: the caller feeds chunks of audio samples and receives DtmfTone emissions via a registered Consumer callback.

DtmfDetector is the streaming half of the public API (pair with the batch DtmfDecoder and the pull-style DtmfStream). Per Requirements 6.1–6.8 and 4.9, it exposes:

Channel handling. The detector honours DtmfConfig.channelMode():

  • MONO — one internal AnalysisPipeline, every emission tagged channel = 0.
  • STEREO_INDEPENDENT — two pipelines; even-index samples feed the left channel (0), odd-index samples feed the right (1). Odd-length chunks are rejected with IllegalArgumentException (Requirement 13.5).
  • STEREO_DOWNMIX — adjacent pairs averaged into a single mono stream; emissions tagged channel = 0. Odd-length chunks are rejected (Requirement 13.5).

Chunk invariance (Requirement 6.7). Calling process repeatedly with chunks whose concatenation equals a single buffer B emits the same tones, in the same order, with the same cumulative sample indices, as a single process(B) call on a fresh detector. This holds by construction because the pipeline does not buffer chunks — it streams samples one at a time through a block-synchronous state machine whose only non-trivial state is analysis-block-local.

Cumulative sample indices (Requirement 6.8). samplesProcessed() counts every sample ever passed to any process overload, including both channels of a stereo input. For stereo modes it is the number of interleaved samples consumed, not the per-channel count. Emitted tones' startSample/endSample come from the per-channel analysis pipeline, so for STEREO_INDEPENDENT those indices advance half as fast as samplesProcessed().

Format overloads (Requirement 4.9). The short[], float[], and int[] overloads convert into a reusable double[] scratch buffer owned by this detector and then feed the normalised samples through the same path as process(double[]). No per-chunk allocation occurs on the hot path — the scratch buffer is resized only when a chunk exceeds its current capacity.

Thread safety (Requirement 6.6). Instances are not thread-safe. Concurrent process calls on the same detector have undefined behaviour.

Since:
2.0.0
  • Constructor Summary

    Constructors
    Constructor
    Description
    Construct a new detector for the given configuration.
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    Finalise any tone still in flight.
    void
    Register the callback that receives every emitted DtmfTone.
    void
    process(double[] chunk)
    Feed a full chunk of double[] samples through the detector.
    void
    process(double[] chunk, int offset, int length)
    Feed a sub-range of a double[] through the detector.
    void
    process(float[] chunk)
    Feed a chunk of normalised float samples through the detector.
    void
    process(int[] chunk)
    Feed a chunk of signed PCM32 samples through the detector.
    void
    process(short[] chunk)
    Feed a chunk of signed PCM16 samples through the detector.
    long
    Returns the cumulative number of samples passed to any process overload since construction.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Constructor Details

    • DtmfDetector

      public DtmfDetector(DtmfConfig config)
      Construct a new detector for the given configuration.
      Parameters:
      config - detection configuration; non-null
      Throws:
      NullPointerException - if config is null
  • Method Details

    • onTone

      public void onTone(Consumer<DtmfTone> callback)
      Register the callback that receives every emitted DtmfTone. Calling onTone replaces any previously-registered callback (Requirement 6.1). Passing null clears the callback.
      Parameters:
      callback - consumer to receive emissions, or null to clear
    • process

      public void process(double[] chunk)
      Feed a full chunk of double[] samples through the detector.

      Equivalent to process(chunk, 0, chunk.length).

      Parameters:
      chunk - sample chunk; non-null. Samples are expected in the range [-1.0, 1.0]
      Throws:
      NullPointerException - if chunk is null
      IllegalArgumentException - if the channel mode is stereo and chunk.length is odd
    • process

      public void process(double[] chunk, int offset, int length)
      Feed a sub-range of a double[] through the detector. The callback registered via onTone(Consumer) is invoked for every tone confirmed within this chunk, synchronously before this method returns.
      Parameters:
      chunk - sample buffer; non-null
      offset - starting index; must satisfy 0 <= offset && offset + length <= chunk.length
      length - number of samples to consume; must be >= 0
      Throws:
      NullPointerException - if chunk is null
      IndexOutOfBoundsException - if offset/length are out of range
      IllegalArgumentException - if the channel mode is stereo and length is odd
    • process

      public void process(short[] chunk)
      Feed a chunk of signed PCM16 samples through the detector. Samples are normalised to double via division by 32768.0 (Requirement 4.5).
      Parameters:
      chunk - PCM16 sample chunk; non-null
      Throws:
      NullPointerException - if chunk is null
      IllegalArgumentException - if the channel mode is stereo and chunk.length is odd
    • process

      public void process(float[] chunk)
      Feed a chunk of normalised float samples through the detector. Samples are widened to double (Requirement 4.6).
      Parameters:
      chunk - float sample chunk; non-null. Samples are expected in the range [-1.0, 1.0]
      Throws:
      NullPointerException - if chunk is null
      IllegalArgumentException - if the channel mode is stereo and chunk.length is odd
    • process

      public void process(int[] chunk)
      Feed a chunk of signed PCM32 samples through the detector. Samples are normalised to double via division by 2^31 (Requirement 4.7).
      Parameters:
      chunk - PCM32 sample chunk; non-null
      Throws:
      NullPointerException - if chunk is null
      IllegalArgumentException - if the channel mode is stereo and chunk.length is odd
    • flush

      public void flush()
      Finalise any tone still in flight. After flush() returns, each internal pipeline is back in its idle state and can be fed further samples.

      If a tone was still Active or had just entered Ending when flush was called, it is emitted synchronously (via the registered callback) provided its duration so far meets the configured minimum (Requirement 6.3).

    • samplesProcessed

      public long samplesProcessed()
      Returns the cumulative number of samples passed to any process overload since construction.

      For stereo modes this counts interleaved input samples (i.e. left + right), not per-channel samples.

      Returns:
      the cumulative number of samples passed to any process overload since construction