Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@

A Java 17 library for detecting and generating [DTMF](https://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling) (Dual-Tone Multi-Frequency) signalling tones per ITU-T Q.23 and Q.24. Ships a Goertzel-based detection backend with both batch and streaming APIs.

> **Status:** v2 foundation. File I/O (WAV/MP3/OGG), CLI, GUI, microphone capture, Android support, and Maven Central publishing are explicitly out of scope for this spec and planned for follow-on releases. See [Out of scope](#out-of-scope) below.
> **Status:** v2.1.0. File I/O ships as a separate layer in the `dtmf-io`, `dtmf-io-wav`, and `dtmf-io-mp3` modules (added in this release). CLI, GUI, microphone capture, Android support, FLAC/OGG/Opus/AAC codecs, and Maven Central publishing remain out of scope. See [Out of scope](#out-of-scope) below.

## Modules

The project is a Gradle multi-module build. Four modules ship as artifacts; a fifth root aggregator coordinates the build.
The project is a Gradle multi-module build. Five modules ship as artifacts; a sixth root aggregator coordinates the build, and a seventh (`dtmf-benchmarks`) hosts JMH benchmarks and is not published.

| Module | Coordinates | Depends on | Purpose |
|---|---|---|---|
| `goertzel` | `com.tino1b2be:goertzel:2.0.0` | JDK 17 only | General-purpose Goertzel filter + filter bank |
| `dtmf-core` | `com.tino1b2be:dtmf-core:2.0.0` | `goertzel` | DTMF detection, generation, streaming |
| `goertzel` | `com.tino1b2be:goertzel:2.1.0` | JDK 17 only | General-purpose Goertzel filter + filter bank |
| `dtmf-core` | `com.tino1b2be:dtmf-core:2.1.0` | `goertzel` | DTMF detection, generation, streaming |
| `dtmf-io` | `com.tino1b2be:dtmf-io:2.1.0` | `dtmf-core` | Pull-based `AudioSource` SPI + `DtmfFileDecoder` |
| `dtmf-io-wav` | `com.tino1b2be:dtmf-io-wav:2.1.0` | `dtmf-io` | WAV `AudioSourceProvider` (clean-room RIFF parser) |
| `dtmf-io-mp3` | `com.tino1b2be:dtmf-io-mp3:2.1.0` | `dtmf-io`, JLayer, mp3spi | MP3 `AudioSourceProvider` |
| `dtmf-benchmarks` | *(not published)* | `dtmf-core`, `goertzel` | JMH benchmarks |
| `dtmf-bom` | `com.tino1b2be:dtmf-bom:2.0.0` | *(BOM only)* | Pins `goertzel` and `dtmf-core` at a coordinated version |
| `dtmf-bom` | `com.tino1b2be:dtmf-bom:2.1.0` | *(BOM only)* | Pins the five shipping libraries at a coordinated version |

## Prerequisites

Expand Down Expand Up @@ -138,9 +141,10 @@ The standard factories accept exactly `{8000, 16000, 44100, 48000}` Hz. The adva

## Out of scope

The following are explicitly **not** part of v2 foundation:
The following are explicitly **not** part of the current release:

- **File I/O** — no WAV, MP3, or OGG readers. Callers supply PCM samples as `double[]`, `short[]`, `float[]`, or `int[]`.
- **Additional audio codecs** — beyond WAV and MP3. FLAC, OGG Vorbis, Opus, and AAC are future spec candidates.
- **Production WAV/MP3 encoding** — `dtmf-io-wav` includes only a test-only encoder for round-trip tests; no public encoding API ships.
- **CLI** — no command-line interface module.
- **GUI** — no Swing, AWT, JavaFX, or applet code.
- **Microphone capture** — no real-time audio input.
Expand Down
15 changes: 12 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// Per Task 1.6 of the dtmf-v2-foundation spec: the root applies no plugins
// itself. Its sole responsibility is to stamp consistent Maven coordinates
// — group `com.tino1b2be` and version `2.0.0` — onto every subproject
// — group `com.tino1b2be` and version `2.1.0` — onto every subproject
// (Requirements 2.1, 2.2, 2.3).
//
// `allprojects` (rather than `subprojects`) is used deliberately. The root
Expand All @@ -14,7 +14,7 @@

allprojects {
group = "com.tino1b2be"
version = "2.0.0"
version = "2.1.0"
}

// -------------------------------------------------------------------------
Expand All @@ -31,10 +31,19 @@ allprojects {
// index.html ← landing page listing the modules
// goertzel/ ← :goertzel javadoc
// dtmf-core/ ← :dtmf-core javadoc
// dtmf-io/ ← :dtmf-io javadoc
// dtmf-io-wav/ ← :dtmf-io-wav javadoc
// dtmf-io-mp3/ ← :dtmf-io-mp3 javadoc
//
// Consumers can browse the full API at tino1b2be.github.io/DTMF-Decoder.

val publishedModuleNames = setOf("goertzel", "dtmf-core")
val publishedModuleNames = setOf(
"goertzel",
"dtmf-core",
"dtmf-io",
"dtmf-io-wav",
"dtmf-io-mp3",
)

tasks.register("aggregateJavadoc") {
group = "documentation"
Expand Down
15 changes: 8 additions & 7 deletions dtmf-bom/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
//
// Group and version are stamped by the root `build.gradle.kts` via
// `allprojects` — this BOM therefore publishes as
// `com.tino1b2be:dtmf-bom:2.0.0` and pins the two shipping libraries at the
// same coordinate. Maven Central publishing is intentionally out of scope
// `com.tino1b2be:dtmf-bom:2.1.0` and pins the five shipping libraries
// (goertzel, dtmf-core, dtmf-io, dtmf-io-wav, dtmf-io-mp3) at the same
// coordinate. Maven Central publishing is intentionally out of scope
// for the foundation spec (Requirement 16.6); this module only wires the
// publication so `publishToMavenLocal` works for local smoke-testing.

Expand All @@ -27,11 +28,11 @@ javaPlatform {

dependencies {
constraints {
api("com.tino1b2be:goertzel:2.0.0")
api("com.tino1b2be:dtmf-core:2.0.0")
api("com.tino1b2be:dtmf-io:2.0.0")
api("com.tino1b2be:dtmf-io-wav:2.0.0")
api("com.tino1b2be:dtmf-io-mp3:2.0.0")
api("com.tino1b2be:goertzel:2.1.0")
api("com.tino1b2be:dtmf-core:2.1.0")
api("com.tino1b2be:dtmf-io:2.1.0")
api("com.tino1b2be:dtmf-io-wav:2.1.0")
api("com.tino1b2be:dtmf-io-mp3:2.1.0")
}
}

Expand Down
2 changes: 1 addition & 1 deletion dtmf-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// and the bare `maven-publish` publication all come from
// `dtmf.published-library-conventions` (layered on top of
// `dtmf.java-library-conventions`). Maven coordinates
// (`com.tino1b2be:dtmf-core:2.0.0`) are inherited from the root
// (`com.tino1b2be:dtmf-core:2.1.0`) are inherited from the root
// `build.gradle.kts` via `allprojects`.

plugins {
Expand Down
2 changes: 1 addition & 1 deletion dtmf-io-mp3/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
// `maven-publish` publication all come from
// `dtmf.published-library-conventions` (layered on top of
// `dtmf.java-library-conventions`). Maven coordinates
// (`com.tino1b2be:dtmf-io-mp3:2.0.0`) are inherited from the root
// (`com.tino1b2be:dtmf-io-mp3:2.1.0`) are inherited from the root
// `build.gradle.kts` via `allprojects`.

plugins {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
* identifying the source as closed (Requirement 3.14).
* {@link #close()} itself is idempotent.
*
* @since 2.0.0
* @since 2.1.0
* @see AudioSource
* @see Mp3AudioSourceProvider
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@
* their own lifecycle and are <em>not</em> thread-safe &mdash; see
* {@link Mp3AudioSource}.
*
* @since 2.0.0
* @since 2.1.0
* @see Mp3AudioSource
* @see Mp3HeaderScanner
* @see AudioSourceProvider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
* entirely to whether the caller-supplied stream is safe to read from
* concurrently &mdash; a question outside this scanner's scope.
*
* @since 2.0.0
* @since 2.1.0
*/
public final class Mp3HeaderScanner {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@
* {@code Mp3AudioSourceProvider} and {@code Mp3AudioSource} in the parent
* package.
*
* @since 2.0.0
* @since 2.1.0
*/
package com.tino1b2be.dtmf.io.mp3.internal;
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@
* Stage 1 so the source tree exists for the build-shape smoke tests in
* Task 1.8.
*
* @since 2.0.0
* @since 2.1.0
*/
package com.tino1b2be.dtmf.io.mp3;
2 changes: 1 addition & 1 deletion dtmf-io-wav/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
// `maven-publish` publication all come from
// `dtmf.published-library-conventions` (layered on top of
// `dtmf.java-library-conventions`). Maven coordinates
// (`com.tino1b2be:dtmf-io-wav:2.0.0`) are inherited from the root
// (`com.tino1b2be:dtmf-io-wav:2.1.0`) are inherited from the root
// `build.gradle.kts` via `allprojects`.

plugins {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
* identifying the source as closed (Requirement 3.14).
* {@link #close()} itself is idempotent.
*
* @since 2.0.0
* @since 2.1.0
* @see AudioSource
* @see WavAudioSourceProvider
* @see WavSampleReader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
* whose {@link AudioSource#close()} transitions the source into the
* closed state but leaves the caller's {@link InputStream} untouched.
*
* @since 2.0.0
* @since 2.1.0
* @see WavAudioSource
* @see AudioSourceProvider
* @see AudioSource
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
* @param dataStartOffset absolute byte position of the first payload
* byte within the enclosing source; must be
* non-negative
* @since 2.0.0
* @since 2.1.0
*/
public record RiffChunk(String id, long size, long dataStartOffset) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
* mediates mutable byte-source state and must be used by one thread at a
* time. Concurrent calls are undefined behaviour.
*
* @since 2.0.0
* @since 2.1.0
*/
public final class RiffReader {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
* classes in the parent {@code com.tino1b2be.dtmf.io.wav} package can
* reach it; external callers MUST NOT depend on it.
*
* @since 2.0.0
* @since 2.1.0
*/
public final class WavSampleReader {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
* @param totalFrames number of complete frames in the payload
* ({@code dataSizeBytes / bytesPerFrame});
* must be non-negative
* @since 2.0.0
* @since 2.1.0
*/
public record WaveFormat(
int sampleRate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@
* lives on {@code WavAudioSourceProvider} and {@code WavAudioSource} in the
* parent package.
*
* @since 2.0.0
* @since 2.1.0
*/
package com.tino1b2be.dtmf.io.wav.internal;
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@
* Stage 1 so the source tree exists for the build-shape smoke tests in
* Task 1.8.
*
* @since 2.0.0
* @since 2.1.0
*/
package com.tino1b2be.dtmf.io.wav;
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@
* {@code dtmf-io-wav} jar. External consumers MUST NOT depend on it, at
* any version, via any mechanism.
*
* @since 2.0.0
* @since 2.1.0
*/
final class WavEncoder {

Expand Down
2 changes: 1 addition & 1 deletion dtmf-io/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// `maven-publish` publication all come from
// `dtmf.published-library-conventions` (layered on top of
// `dtmf.java-library-conventions`). Maven coordinates
// (`com.tino1b2be:dtmf-io:2.0.0`) are inherited from the root
// (`com.tino1b2be:dtmf-io:2.1.0`) are inherited from the root
// `build.gradle.kts` via `allprojects`.

plugins {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
* <p>Not part of the published API. Used only by
* {@code OpenWavIT} / {@code UnrecognizedInputIT} helpers.
*
* @since 2.0.0
* @since 2.1.0
*/
final class IntegrationWavEncoder {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
* pathological case (e.g. LAME producing a degenerate frame) does not
* fail the suite as long as the corpus average stays above 99%.
*
* @since 2.0.0
* @since 2.1.0
*/
@Tag("slow")
@EnabledIfSystemProperty(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
* directory happens to be the repo root rather than the module
* directory.
*
* @since 2.0.0
* @since 2.1.0
*/
final class OpenMp3IT {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
* package, and feed the same file through {@code DtmfFileDecoder} to
* recover the original keys.
*
* @since 2.0.0
* @since 2.1.0
*/
final class OpenWavIT {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* {@code dtmf-io-mp3} — what matters for callers is that both names are
* present.
*
* @since 2.0.0
* @since 2.1.0
*/
final class RegisteredFormatsIT {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
* is negligible but non-zero for truly random bytes, so pinning the seed
* is strictly more robust than calling {@code new Random()} here.
*
* @since 2.0.0
* @since 2.1.0
*/
final class UnrecognizedInputIT {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
* as closed (Requirement 3.14). {@link #close()} itself remains callable
* and is a no-op on subsequent invocations.
*
* @since 2.0.0
* @since 2.1.0
* @see AudioSourceProvider
* @see AudioSources
* @see RawPcmAudioSource
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
* explicitly nullable and implementations must tolerate {@code null}
* without throwing.
*
* @since 2.0.0
* @since 2.1.0
*/
public interface AudioSourceProvider {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
* call. Provider instances themselves may be called concurrently from
* multiple threads; see {@link AudioSourceProvider} for that contract.
*
* @since 2.0.0
* @since 2.1.0
* @see AudioSource
* @see AudioSourceProvider
* @see UnsupportedAudioFormatException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
* are safe to invoke concurrently from multiple threads on different
* inputs.
*
* @since 2.0.0
* @since 2.1.0
* @see AudioSources
* @see DtmfDecoder
* @see DtmfConfig
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
* decode to signed 16-bit PCM and bypass the raw-PCM constructor path
* entirely.
*
* @since 2.0.0
* @since 2.1.0
* @see RawPcmAudioSource
*/
public enum PcmEncoding {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
* underlying {@code byte[]} (which is safe provided no one mutates the
* bytes).
*
* @since 2.0.0
* @since 2.1.0
* @see AudioSource
* @see PcmEncoding
* @see com.tino1b2be.dtmf.io.internal.SampleConversion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
* caught exception to multiple consumers is safe; attempting to mutate
* the returned collections throws {@link UnsupportedOperationException}.
*
* @since 2.0.0
* @since 2.1.0
* @see AudioSources
* @see AudioSourceProvider
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
* @param priority the value of {@link AudioSourceProvider#priority()} at
* the time of scoring, used as the tie-breaker when two
* providers return the same {@code score}
* @since 2.0.0
* @since 2.1.0
*/
public record ProviderScore(AudioSourceProvider provider, int score, int priority) {

Expand Down
Loading
Loading