Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b2ab8a2
Add emet.wake plugin category
alexander-wang03 Sep 1, 2026
cdc4887
Make the wake phrase free text and split capability plugins from life…
alexander-wang03 Sep 1, 2026
feb36be
Add the pocketsphinx wake engine
alexander-wang03 Sep 2, 2026
78d3c2b
Add microphone capture and speaker playback
alexander-wang03 Sep 3, 2026
be26b9f
Make an uninstalled wake engine a hard error
alexander-wang03 Sep 3, 2026
9032eaf
Move the audio contract into the SDK
alexander-wang03 Sep 3, 2026
13ac292
Add the emet.audio plugin group
alexander-wang03 Sep 3, 2026
7022c53
Add emet-engine and the listen loop
alexander-wang03 Sep 3, 2026
210f06f
Document the patience default against measured human timing
alexander-wang03 Sep 4, 2026
177fcb4
Add CITATIONS.md and require attribution for outside work
alexander-wang03 Sep 4, 2026
541cf1b
Measure whether the listen loop keeps up
alexander-wang03 Sep 4, 2026
318dc4f
Add the emet.audio_out sink seam and wire playback
alexander-wang03 Sep 4, 2026
e96b461
Single-source the version and bump to 0.3.0
alexander-wang03 Sep 5, 2026
839cf79
Add a release checklist and bring the docs to 0.3
alexander-wang03 Sep 5, 2026
efe1cf5
Bring the docs and comments in line with 0.3 and the style rules
alexander-wang03 Sep 6, 2026
d880dfd
Add the Pi speakerphone body and open ALSA cards through plughw
alexander-wang03 Sep 10, 2026
88a74c2
Discard microphone frames that arrive after stop
alexander-wang03 Sep 11, 2026
00093dd
Add the soak recording prompts
alexander-wang03 Sep 11, 2026
66661ca
Set the wake threshold from a real-room recording
alexander-wang03 Sep 11, 2026
8654247
Play through a raw output stream without numpy
alexander-wang03 Sep 11, 2026
c327970
Set the wake threshold from both recordings
alexander-wang03 Sep 11, 2026
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
53 changes: 47 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
#
# Two things are checked, and the second matters more than it looks.
#
# tests the 0.1 acceptance criteria, executable.
# layering emet_sdk imports nothing internal; emet_hal and emet_engine
# tests: the 0.1 acceptance criteria, executable.
# layering: emet_sdk imports nothing internal; emet_hal and emet_engine
# import emet_sdk only.
#
# The layering check exists because the closed-engine plan used to enforce that
Expand All @@ -13,7 +13,7 @@ name: ci

# Branch work is covered by `pull_request`; `push` only guards what lands.
# Triggering on both for every branch ran the whole suite twice per push,
# doubling Actions minuteswhich are metered on private repositories and
# doubling Actions minutes, which are metered on private repositories, and
# filling the checks list with confusing (push)/(pull_request) pairs.
on:
push:
Expand All @@ -40,6 +40,12 @@ jobs:
- name: Check package layering
run: python tools/check_layering.py .

# Cross-package invariants no single suite can see: the three packages
# agreeing about the version, every discovery group having something
# behind it, and no document advertising a version the code is not.
- name: Check release invariants
run: python tools/release_check.py .

dco:
name: dco sign-off
runs-on: ubuntu-latest
Expand Down Expand Up @@ -82,7 +88,7 @@ jobs:
# In a source checkout `schemas/` sits BESIDE the package; in a built
# wheel `force-include` moves it INSIDE. An editable install keeps the
# source layout, so every other job in this file exercises only one of
# those two paths and it is the path no user of the project ever takes.
# those two paths, and it is the path no user of the project ever takes.
#
# A typo in the force-include would leave the whole suite green and break
# the first person to run `pip install emet-sdk`. So: build a real wheel,
Expand All @@ -93,6 +99,7 @@ jobs:
python -m pip install --upgrade build
python -m build --wheel emet-sdk
python -m build --wheel emet-hal
python -m build --wheel emet-engine

- name: Data files are actually inside the wheel
run: |
Expand All @@ -118,7 +125,7 @@ jobs:

- name: Install the wheels, not the source
run: |
python -m pip install emet-sdk/dist/*.whl emet-hal/dist/*.whl
python -m pip install emet-sdk/dist/*.whl emet-hal/dist/*.whl emet-engine/dist/*.whl

# `cd /tmp` is the point of this step: from here the source checkout is
# not on the path, so anything that resolves must be coming out of the
Expand All @@ -127,6 +134,7 @@ jobs:
run: |
cd /tmp
emet --version
emet-listen --help > /dev/null
emet validate "$GITHUB_WORKSPACE/emet-sdk/examples/bodiless.yaml"
emet validate "$GITHUB_WORKSPACE/emet-sdk/examples/mock-scout.yaml" --verify-drivers
emet explain "$GITHUB_WORKSPACE/emet-sdk/examples/mock-scout.yaml" --why
Expand All @@ -135,12 +143,27 @@ jobs:
run: |
cd /tmp
python - <<'PY'
from importlib.metadata import version
from emet_sdk.discovery import PluginRegistry

r = PluginRegistry.discover()
assert r.has_locomotion("differential"), "entry points did not survive packaging"
assert r.has_locomotion("tracked")
assert r.has_driver("emet_hal.mock")
assert r.has_wake("pocketsphinx"), "wake group did not survive packaging"
assert r.has_audio("microphone"), "audio group did not survive packaging"
assert r.has_audio_out("speaker"), "audio_out group did not survive packaging"
print("discovered:", [f"{g}:{n}" for g, n in r])

# The version is declared once, in pyproject.toml, and read back
# through importlib.metadata. From a wheel that resolution path is
# different from an editable install, so it is worth checking here
# rather than only in the test suite.
import emet_sdk, emet_hal, emet_engine
for dist, mod in (("emet-sdk", emet_sdk), ("emet-hal", emet_hal), ("emet-engine", emet_engine)):
assert mod.__version__ == version(dist), f"{dist} version drifted"
assert mod.__version__ != "0+unknown", f"{dist} reports no version"
print("versions:", emet_sdk.__version__)
PY

test:
Expand All @@ -164,6 +187,17 @@ jobs:
run: |
python -m pip install -e "emet-sdk[dev]"
python -m pip install -e "emet-hal[dev]"
python -m pip install -e "emet-engine[dev]"

# The shipped wake engine, which is an optional extra rather than a hard
# dependency. pocketsphinx publishes cp311 and cp313 wheels and no cp312
# one, so installing it everywhere would make the 3.12 job compile a
# speech decoder from source. The wake tests `importorskip`, so 3.12 runs
# the rest of the suite and skips them, and the engine is still exercised
# on two of the three versions.
- name: Install the wake engine where a wheel exists
if: matrix.python-version != '3.12'
run: python -m pip install -e "emet-hal[wake]"

- name: Test emet-sdk
working-directory: emet-sdk
Expand All @@ -173,8 +207,15 @@ jobs:
working-directory: emet-hal
run: python -m pytest -q

# Runs without a microphone or an acoustic model: the loop is exercised
# through a wav file and the mock detector, both reached by the same
# entry-point discovery the real ones use.
- name: Test emet-engine
working-directory: emet-engine
run: python -m pytest -q

# Exercises the CLI against the editable install. This does NOT prove the
# packaged layout works see the `wheel` job for that.
# packaged layout works; see the `wheel` job for that.
- name: CLI accepts the valid examples
working-directory: emet-sdk
run: emet validate examples/bodiless.yaml examples/scout-01.yaml examples/emet-soul.yaml
Expand Down
11 changes: 11 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
// Emet uses a DCO, so every commit needs a Signed-off-by line and CI
// rejects pull requests without one. This makes the built-in Git UI add it
// for you, so contributing never involves learning what a DCO is.
//
// There is no git config that does this. `commit.signoff` is not a real
// option and git ignores it silently; `format.signoff` applies only to
// `git format-patch`. The flag has to come from the client, which is why
// this setting is checked in rather than left to each person to discover.
"git.alwaysSignOff": true
}
104 changes: 104 additions & 0 deletions CITATIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Citations

Outside work whose **ideas, findings, or data** shaped Emet, and what was taken
from each.

This is not a dependency list. Installed packages are declared in each
`pyproject.toml`, and copyright notices live in [NOTICE](NOTICE). This file
exists for the harder-to-track case: a measured result that justifies a
default, a taxonomy that shaped a schema, a phoneme set a data file is written
in. Those leave no trace in a lockfile, and by the time somebody asks where a
constant came from, the reasoning is usually gone.

Each entry records the source, what Emet took, and **the licence of the thing
taken**, because a non-commercially licensed corpus or a differently licensed
repository is a constraint the project has to carry forward.

---

## TurnBench (2026)

**Freeman Jiang, Ramon Sanabria, Soham Deshmukh, Bandhav Veluri, Simon Michael
Vuch Williams, Elliott K. Suen, Garreth Lee, Kevin Yoonho Choi, Takuya Umeki,
Riku Kubo, Sathvik Udupa, Chien-yu Huang, Shih-Yun Shan Kuan, Zhuoyan Tao,
Satyapriya Krishna, Sefik Emre Eskimez, Yu Tsao, Hung-yi Lee, Shinji
Watanabe.** *TurnBench: A Multi-Domain Benchmark for Turn-Taking Dynamics in
Spoken Dialogue.* arXiv:2608.25218 [eess.AS], 25 August 2026.

Sesame AI · Mundo AI · Carnegie Mellon University · National Taiwan University
· Academia Sinica · Oto · Brno University of Technology

- Project: <https://turnbench.sesame.com>
- Scorer: <https://github.com/SesameAILabs/turnbench>, **MIT**
- Corpus: **non-commercial licence, prohibits voice cloning**

**What Emet took.** Three measured medians from the corpus analysis (§IV-B),
used in [`emet_engine/turn.py`](emet-engine/emet_engine/turn.py) to justify the
default `patience_ms`:

| | |
|---|---|
| Floor transfer offset, excluding interruptions | −151 ms (listeners begin before the turn ends) |
| Inter-speaker gap | 380 ms |
| Pause within one speaker's turn | 510 ms |

The third against the second is the argument that a silence threshold cannot
separate "still thinking" from "finished", because the two distributions
overlap. Emet's endpointer is deliberately conservative for that reason, and
that reasoning is theirs, not ours.

The paper also supplies the honest grade for what Emet currently ships: an
RMS-energy detector is the benchmark's explicit floor. Recording that is part
of the attribution: the finding was inconvenient, and taking the numbers while
omitting the verdict would be quoting selectively.

**No corpus data, model weights, or code from this work is redistributed
here.** Only findings are cited. If Emet ever scores itself with their scorer,
the MIT terms apply to the scorer and the non-commercial terms apply to the
corpus, and the distinction has to be respected.

**Their taxonomy is grounded in conversation analysis**, principally Sacks,
Schegloff and Jefferson (1974) on transition-relevance places, and Yngve
(1970) on backchannels. Emet has those concepts second-hand through this paper
rather than from the primary sources, and says so rather than citing work it
has not read.

---

## CMU PocketSphinx and CMUdict

**Carnegie Mellon University Speech Group.** PocketSphinx.
<https://github.com/cmusphinx/pocketsphinx>, **BSD-2-Clause (CMU)**. The
published wheel also carries BSD-3-Clause WebRTC VAD code (Google) and MIT
pieces (a JSON parser, the Python VAD bindings); all permissive.

The shipped wake word engine
([`emet_hal/pocketsphinx_wake.py`](emet-hal/emet_hal/pocketsphinx_wake.py)),
used as a dependency rather than copied.

**What is worth naming beyond the dependency**: `SHIPPED_LEXICON`, the
pronunciations that let `emet`, `hugr` and `neuma` be heard, is written in
**ARPAbet**, and is meaningful only against CMU's pronouncing dictionary, which
supplies every other word in a wake phrase. "hey barnaby" needs no lexicon
entry at all because CMUdict already knows the name. That property is the
practical argument for Emet's phonetic-wake design, and it is CMU's work
underneath.

The pronunciations themselves were derived for this project by decoding
reference audio with PocketSphinx's own allphone search, then checked for false
firing.

---

## Adding to this file

If a change takes an idea, a finding, a number, or a data format from outside
work, add it here and cite it at the point of use. See
[CONTRIBUTING.md](CONTRIBUTING.md#citing-outside-work). Two rules that are easy
to get wrong:

- **Ideas count, not only code.** Using a paper's measurement to pick a
constant is taking something, even though nothing was copied.
- **Record the licence of what was taken**, not just of the repository it came
from. A project can ship an MIT tool alongside a corpus you may not use
commercially, and only one of those is safe to build on.
Loading