Metadata-Version: 2.4
Name: mimora
Version: 1.1.0rc1
Summary: Local, offline pronunciation trainer: Kokoro TTS prompts, Wav2Vec2 scoring, local LLM phrase generation.
Author: Valeriy Kovalev
License-Expression: MIT
Project-URL: Homepage, https://github.com/vikonix/Mimora
Project-URL: Repository, https://github.com/vikonix/Mimora
Project-URL: Issues, https://github.com/vikonix/Mimora/issues
Keywords: pronunciation,language-learning,speech,text-to-speech,speech-recognition,offline,llm,tkinter
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: MacOS X
Classifier: Environment :: Win32 (MS Windows)
Classifier: Environment :: X11 Applications
Classifier: Intended Audience :: End Users/Desktop
Classifier: Natural Language :: English
Classifier: Natural Language :: Spanish
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Education
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
Requires-Python: <3.13,>=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy<2.5,>=1.24.0; platform_system != "Darwin" or platform_machine != "x86_64"
Requires-Dist: numpy<2,>=1.24.0; platform_system == "Darwin" and platform_machine == "x86_64"
Requires-Dist: torch>=2.0.0; platform_system != "Darwin" or platform_machine != "x86_64"
Requires-Dist: torch==2.2.2; platform_system == "Darwin" and platform_machine == "x86_64"
Requires-Dist: torchaudio>=2.0.0; platform_system != "Darwin" or platform_machine != "x86_64"
Requires-Dist: torchaudio==2.2.2; platform_system == "Darwin" and platform_machine == "x86_64"
Requires-Dist: transformers>=5.3; platform_system != "Darwin" or platform_machine != "x86_64"
Requires-Dist: transformers<5,>=4.44; platform_system == "Darwin" and platform_machine == "x86_64"
Requires-Dist: soundfile>=0.12.0
Requires-Dist: sounddevice>=0.4.6
Requires-Dist: librosa>=0.10.0
Requires-Dist: scipy>=1.10.0
Requires-Dist: scikit-learn>=1.2.0
Requires-Dist: kokoro>=0.3.0
Requires-Dist: supertonic>=0.1
Requires-Dist: phonemizer-fork>=3.3.0
Requires-Dist: panphon>=0.20.0
Requires-Dist: python-Levenshtein>=0.21.0
Requires-Dist: fastdtw>=0.3.4
Requires-Dist: sentencepiece>=0.1.99
Requires-Dist: protobuf>=3.20.0
Requires-Dist: openai>=1.0.0
Requires-Dist: wordfreq>=3.0
Requires-Dist: huggingface_hub>=1.24; platform_system != "Darwin" or platform_machine != "x86_64"
Requires-Dist: click>=8.4.2
Requires-Dist: ttkbootstrap<2,>=1.10
Requires-Dist: pillow>=9.1
Dynamic: license-file

# Mimora

**A local, offline pronunciation trainer.** Mimora says a phrase out loud, you repeat it, and it scores how close you were - highlighting the words to work on. Practice the same phrase until you pass, then move on to the next one. Everything runs on your machine: speech synthesis, speech recognition, phrase generation, and acoustic analysis.

<p align="center">
  <img src="https://raw.githubusercontent.com/vikonix/Mimora/main/docs/mimora1.gif" width="400"
       alt="Mimora demo: one full practice loop - new phrase, speak, score, highlighted words, replay">
</p>

| Dark theme | Light theme |
|:---:|:---:|
| ![Mimora pronunciation trainer, dark theme](https://raw.githubusercontent.com/vikonix/Mimora/main/docs/mimora-dark.png) | ![Mimora pronunciation trainer, light theme](https://raw.githubusercontent.com/vikonix/Mimora/main/docs/mimora-light.png) |

*Both themes ship with the app; drop your own `<name>_schema.json` into `config/themes/` to add or replace one.*

---

## Why Mimora?

- 🔒 **100% offline after install** - your voice never leaves your computer. No cloud, no accounts, no API keys.
- ♾️ **Unlimited, varied practice** - a local LLM writes fresh phrases from your own text, so you are not stuck repeating the same canned sentences.
- 💸 **No subscription** - free and open source; the only cost is disk space for the models.
- 💻 **Runs on a normal PC** - works on **16 GB RAM with no graphics card**. A GPU is optional and just makes it faster.
- 🎯 **Instant, word-level feedback** - a 0-100 score plus the exact words to work on, with reference-vs-you replay.

## Who is it for?

Anyone working on clearer speech in a new language - language learners, accent reduction, and shadowing practice. You bring your own text (a paragraph, a song, a script) and Mimora turns it into spoken drills and scores how close you get.

## Supported languages

The practice language is chosen in the settings window (**Language**, applies after a restart); each language may offer regional variants (**Accent**).

| Language | Variants | Scoring engines |
|---|---|---|
| **English** | American, British | `phoneme` (default, calibrated), `acoustic`, `none` |
| **Spanish** (Peninsular / Castilian) | Castilian | `phoneme` (**experimental** - no Spanish calibration yet, scoring falls back to the English calibration until it lands), `none` |

Mimora is built for practicing **English** (American and British, fully calibrated). Under the hood the scoring engine is language-independent, and multilingual support is in development. **Spanish** is included as an experiment: until a Spanish calibration lands, scoring falls back to the English calibration (usable, not tuned); a proper calibration requires labeled non-native speech data for that language.

The English-only `acoustic` engine is not offered for Spanish. (The translation panel already renders the practice phrase in 200+ languages - that is the translation shown beside the phrase, not the practice language itself.)

---

## How it works

For each practice phrase Mimora runs a simple loop:

1. **Prompt** - a phrase is generated by the local LLM from your *practice text*, then spoken aloud by the active language's TTS voice (Kokoro for English, Supertonic for Spanish; this synthesized audio is also the reference for scoring).
2. **Record** - you press `SPACE` (or click the mic) and repeat the phrase; the take stops on its own once you fall silent (or press `SPACE` / click the mic again).
3. **Analyze** - your recording and the reference are compared in a background thread by the active pronunciation engine - by default the **phoneme** engine (espeak reference phonemes vs a wav2vec2 phoneme recognizer) - plus prosody (pitch / energy). An alternative **acoustic** engine (Wav2Vec2 embeddings + DTW) is selectable in the settings window (**Engine**; settings.json `"engine"`).
4. **Feedback** - you get a score out of 100, what was recognized, and the words to improve.
5. **Loop** - repeat the same phrase until you are happy with the score, then generate the next phrase.

You can replay the **reference** and **your own recording** back-to-back to hear the difference.

> **No-LLM mode for slow machines.** Setting the **LLM backend** to `off` in the settings skips the language model entirely - nothing is loaded or started, and each practice phrase is a sentence of your practice text, taken verbatim and in order. This mode is aimed primarily at low-end machines (no GGUF model, no LLM server subprocess, less RAM/VRAM and a faster start); it also suits drilling a text exactly as written. The phrase-length choice is disabled in this mode - sentences are never shortened. Combined with the `none` scoring engine, Mimora runs with no LLM and no recognizer model at all.

### Example

```text
Reference:  The weather is getting colder.
You said:   The weather is getting color.
Score:      82 / 100
Work on:    colder
```

---

## Features

- 🎙️ **One-press recording** - press once, speak, and it stops by itself when you go quiet (peak normalization, silence-based auto-stop).
- 🗣️ **One consistent reference voice** - prompts and the scored reference are spoken by the same TTS voice, so you always compare against the same target (no second TTS). Prefer variety? Enable **Random voice per phrase** in the settings.
- 🧠 **Practice your own material** - paste a paragraph, song, or sentences into the *practice text* panel and the local LLM turns it into an endless stream of phrases to drill.
- ⚙️ **Settings window** (the gear button) - pick the practice **language** and its **accent** (both apply after a restart), the TTS **voice** and playback **speed** (or let **Random voice per phrase** speak every new phrase with a different voice of the current language - needs at least two voices), choose the **phrase length** (full phrase or a few words), and set the **translation language** shown under the phrase. A **user name** selects the per-user scoring calibration.
- 📊 **Objective scoring with two interchangeable engines**, selected in the settings window (**Engine**; settings.json `"engine"`). The default **phoneme** engine scores espeak reference phonemes against a wav2vec2 phoneme recognizer (feature-weighted edit distance, mapped to a calibrated 0-5 grade). The **acoustic** engine combines per-step cosine DTW over Wav2Vec2 embeddings (40%) with phoneme (30%) and word (30%) error rates. Both are length-invariant and calibratable to your voice (`python pronunciation/<engine>/calibrate.py`).
- 🔁 **Replay reference vs. your recording** to hear the difference.
- 😀 **Articulation face** - a schematic mouth opens and closes with the speech as a reference or your recording plays, and shows a smiley reflecting your score while idle.
- 🧵 **Responsive UI** - analysis and model loading run in daemon threads; the GUI is updated only via `root.after()`.
- 💻 **Fully local & offline** after the models are downloaded.

---

## Requirements

- **Hardware** - runs on a typical laptop or desktop: **16 GB RAM and no GPU required** (CPU-only works; the first few phrases are slower). An NVIDIA GPU is optional and speeds up pronunciation analysis and phrase generation.
- **Python 3.11 or 3.12** (developed and tested on 3.11 and 3.12). Python 3.13 and newer are not yet supported (as of June 2026).
- **Windows** is the primary target (TTS playback uses `winsound`); a `sounddevice` fallback exists for other platforms.
- A microphone and speakers.
- For GPU acceleration: an NVIDIA GPU with a CUDA-enabled PyTorch build.
- **espeak-ng** (native binary, required by the phonemizer) - installed separately, see below.
- **PortAudio** (Linux only) - the native library `sounddevice` binds to for recording and playback (`libportaudio2` on Debian/Ubuntu). The Windows and macOS `sounddevice` wheels bundle it; the Linux ones do not. See below.

### macOS notes

**Apple Silicon** Macs run the same pinned stack as Windows and Linux. **Intel
Macs (x86_64)** are supported too, but with an automatic fallback: PyTorch
publishes no macOS x86_64 wheel newer than **torch 2.2.2**, and that torch cannot
run `transformers >= 5`. The requirements files therefore carry environment
markers that, on Intel macOS only, install a relaxed stack automatically -
**torch 2.2.2, transformers 4.x, NumPy < 2** - with no manual steps. The
trade-off is that this fallback forgoes the `transformers >= 5.3` fix for
CVE-2026-4372 and re-enables `torch.load` for the pinned models (the
CVE-2025-32434 gate, handled in `pronunciation/common/compat.py`), which is
acceptable for a local app that loads only these fixed, trusted model repos.
Every other platform keeps the hardened pins.

`tkinter` is bundled by the python.org installer but **not** by Homebrew Python.
`install.py` installs the matching `python-tk@<version>` formula for the
interpreter it runs in; if you set things up by hand on Homebrew Python, match
your version (e.g. `brew install python-tk@3.12` for Homebrew Python 3.12).

### Models

`install.py` pre-downloads all of these (see [Quick install](#quick-install-script-recommended)).
You do not have to run it: on the first start Mimora checks what is missing and
offers to fetch it, naming the exact volume before anything is downloaded. Each
model also has its own command, listed per row below.

The first-run window asks about two levels separately. The models a session
cannot run without (the active engine's recognizer plus the active language's
TTS) are a notice with **Download** and **Quit**; the local chat model
(llama-server plus the GGUF) is a real choice, and declining it writes
`"llm_backend": "off"` into `config/settings.json`, where you can turn it back
on later. The translator is fetched lazily and is never part of that question.

Download sizes below are measured, not estimated, and are kept as data in
[`mimora/models_info.py`](https://github.com/vikonix/Mimora/blob/main/mimora/models_info.py); re-snap them with
`python tools/measure_model_sizes.py`.

| Model | Used by | Download | Notes |
|---|---|---|---|
| `facebook/wav2vec2-xlsr-53-espeak-cv-ft` | pronunciation analysis (**phoneme** engine, default) | 1264 MB | espeak IPA phoneme recognizer; `python -m mimora.model_fetch --hf` |
| `facebook/wav2vec2-large-960h` | pronunciation analysis (**acoustic** engine) | 1262 MB | `python -m mimora.model_fetch --hf` |
| Kokoro-82M (`hexgrad/Kokoro-82M`) | text-to-speech (English) | 363 MB | `python -m mimora.model_fetch --hf` |
| Supertonic 3 (`Supertone/supertonic-3`) | text-to-speech (Spanish) | 404 MB | into `model_cache/supertonic3/`; `python -m mimora.model_fetch --supertonic`. Weights are **OpenRAIL-M** licensed (code MIT), so they are downloaded, never bundled |
| `facebook/nllb-200-distilled-600M` | offline translation (translation panel) | 2483 MB | NLLB-200 200-language translator; fetched on demand when translation is switched on |
| A GGUF chat model (e.g. `Llama-3.2-3B-Instruct-Q4_K_M`) | phrase generation | 2019 MB | `python -m mimora.gguf_fetch`. Not needed with `"llm_backend": "off"` (phrases come verbatim from the practice text) |
| llama-server binary (pinned llama.cpp release) | phrase generation | 641 MB CUDA, 18 MB CPU | most of the CUDA figure is NVIDIA's runtime (391 MB), not llama.cpp; `python -m mimora.llama_server_fetch` |

---

## Installation

**`install.py` is the recommended way to install** - it handles dependencies, GPU
builds, and model downloads in one guided run. The manual steps further below are
an alternative if you prefer to run each command yourself.

Installation is set up to use **prebuilt packages (wheels), so no compilation
toolchain is needed** - everything installs from ready-made binaries except a few
small pure-Python packages that build trivially. You do not need a C/C++ compiler
or CUDA toolkit for the standard setup.

### Quick install (script, recommended)

`install.py` automates the whole setup: it checks the native pieces pip cannot
supply (`espeak-ng`, plus `tkinter` and PortAudio on Linux and the MSVC runtime
on Windows), installs the Python dependencies, auto-detects an NVIDIA GPU and
installs the matching CUDA build of `torch`, pre-downloads the Hugging Face
models into `model_cache/`, installs the pinned llama-server binary into
`bin/llama/` and downloads the GGUF chat model into `models/`.

```bash
git clone https://github.com/vikonix/Mimora.git Mimora
cd Mimora

# Create and activate a virtual environment, then run the installer INSIDE it
# (the script installs into whatever interpreter runs it):
python -m venv .venv
.venv\Scripts\activate            # Windows
# source .venv/bin/activate       # macOS / Linux

python install.py
```

The installer prints each step and the exact command, then asks before running
it (answer `Y` to run, `n` to abort, `s` to skip). Anything already installed is
detected and offered as reinstall-or-skip rather than blindly redone. The full
run is logged to `logs/install.log`.

Expect the full run to take **several minutes** (mostly downloads, so it depends
on your internet speed), and to use roughly **10 GB** of disk once all packages
and models are in place.

Useful flags:

- `--yes` - run non-interactively (skips already-installed steps; add `--reinstall` to force them)
- `--dry-run` - print the steps and commands without executing anything
- `--cpu` - skip the GPU (CUDA) installs
- `--skip-models` / `--skip-gguf` - skip the model / GGUF downloads

`espeak-ng` (a native binary, see below) is checked but not installed on Windows -
follow the printed instructions if it is missing. On Windows, enabling
**Developer Mode** lets the model cache use symlinks; without it the installer
falls back to copying files (more disk use, but it always works).

### Manual installation (alternative)

```bash
# 1. Clone
git clone https://github.com/vikonix/Mimora.git Mimora
cd Mimora

# 2. All dependencies in one step
#    The list lives in [project.dependencies] in pyproject.toml and covers both
#    pronunciation engines (panphon included, for the default phoneme engine).
#    No separate per-engine install is needed.
#
#    Editable (-e), because this is a clone: a plain `pip install .` would copy
#    the code into site-packages, leaving a second copy that your edits do not
#    reach. Editable also keeps Mimora in "source tree" mode, so config/, models/
#    and logs/ stay in the project directory rather than moving to the OS
#    user-data directory (see mimora/paths.py).
pip install -e .
```

The offline translator (NLLB-200) needs no extra step - its dependencies
(`transformers`, `sentencepiece`) are in that same list.

On Windows with an NVIDIA card, add the CUDA build of `torch` afterwards (see
[GPU support](#gpu-support-recommended)): PyPI's `torch` is CPU-only there. On
Linux PyPI already serves a CUDA build, and macOS has no CUDA at all.

### Install espeak-ng (required for phoneme analysis)

`phonemizer` needs the native **espeak-ng** binary on your `PATH`:

- **Windows** - download and run the installer from the [espeak-ng releases](https://github.com/espeak-ng/espeak-ng/releases).
- **macOS** - `brew install espeak-ng`
- **Linux** - `sudo apt-get install espeak-ng`

### Audio on Linux (PortAudio)

`sounddevice` is a wrapper around the native **PortAudio** library. Its Windows
and macOS wheels ship that library inside; its Linux wheels do not, so without
the system package every import fails with
`OSError: PortAudio library not found` - including the one in
`mimora/detect_hardware.py`, which is why `install.py` checks for it up front:

```bash
sudo apt-get install libportaudio2     # Debian / Ubuntu
sudo dnf install portaudio             # Fedora
```

If the library is installed but the app finds **no audio devices** (the
installer prints `0 input / 0 output`), the usual cause is the backend rather
than the library: Debian and Ubuntu build PortAudio with the ALSA backend only,
while WSL and most desktop setups route audio through PulseAudio. Check it with

```bash
ldd "$(ldconfig -p | grep -m1 portaudio | awk '{print $NF}')" | grep pulse
```

An empty answer means no PulseAudio backend. Two known ways out: install
`libasound2-plugins` and point ALSA's default device at pulse
(`pcm.!default pulse` in `~/.asoundrc`), or rebuild PortAudio from source with
`./configure --with-pulseaudio`.

### Emoji icons on Linux (mic button shows a blank box)

The mic/record button (`mimora/ui.py` `draw_mic_button`) draws its state icons
(`🎤` `🔴` `🔊` `⌛` `⚡`) as text on the Tk canvas, using the platform font
(`mimora/ui_theme.py`, `"DejaVu Sans"` on Linux). DejaVu Sans covers `⚡`/`⌛`
(older BMP symbols) but not `🎤`/`🔴`/`🔊` (astral-plane emoji), so on a fresh
Linux install those three render as a blank/tofu box instead of the icon -
it can look like the mic and speaker icons are simply missing.

Fix: install a **monochrome** emoji font so Tk can render the glyphs as normal
vector outlines (Tk canvas text cannot render color/bitmap emoji fonts like
`fonts-noto-color-emoji`, which is the one `apt` installs by default):

```bash
sudo apt install fonts-symbola   # in Ubuntu's universe repo; enable it first if missing:
                                  # sudo add-apt-repository universe && sudo apt update
fc-cache -f -v
```

Then restart Mimora.

### GPU support (recommended)

Whether you need this step depends on your platform. PyPI serves **CPU-only**
`torch` on Windows and macOS and a **CUDA-enabled** build on Linux, and macOS has
no CUDA at all - so this section is about **Windows with an NVIDIA card**, which
is the one combination that gets a CPU wheel it did not want. Mimora says so at
startup if it happens: the app still works, Wav2Vec2 and speech synthesis are
just several times slower.

- **PyTorch** - install a CUDA build (other CUDA versions: see [pytorch.org](https://pytorch.org/get-started/locally/)):
  ```powershell
  python -m pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu124 --force-reinstall
  ```
  Reinstall `torch` and `torchaudio` **together**: force-reinstalling `torch` alone
  leaves a `torchaudio` built against the previous torch, which then fails to
  import (`OSError: [WinError 127]`) and breaks pronunciation analysis.

  With **uv** this is one flag instead - it reads the installed driver and picks
  the matching PyTorch index itself, falling back to CPU when there is no GPU:
  ```powershell
  uv tool install mimora --torch-backend auto
  # or, equivalently:
  # UV_TORCH_BACKEND=auto uv tool install mimora
  ```
  Needs uv 0.9.20 or newer. There is no equivalent for `pipx`: an index cannot be
  named in a published package's metadata, so a pipx install gets the CPU wheel
  and the manual step above.
- **The LLM** needs no pip package at all: it runs in the official llama.cpp
  binary, and the fetcher below picks the GPU build for your platform
  automatically - CUDA on Windows, Vulkan on Linux, where llama.cpp publishes
  no CUDA binaries at all (see the next section).

### Get the llama-server binary

The default LLM backend runs the official **llama.cpp** server as a subprocess.
`install.py` installs it, and the app offers to fetch it on the first start; to
do it separately, or to change the build:

```bash
python -m mimora.llama_server_fetch
```

This downloads a pinned llama.cpp release into `bin/llama/`, verifies the
checksum of every asset, and then confirms that the binary really runs on the
GPU backend it advertises - a CUDA build with missing runtime DLLs otherwise
falls back to the CPU **silently** and just runs about three times slower.
`--list` shows the available builds, `--variant` picks one explicitly, and
`--dry-run` prints the plan without downloading. If you already manage your own
`llama-server`, put it on `PATH` or name it in `settings.json`
(`"llama_server_path"`) instead.

Builds are pinned for **Windows x64** (CUDA, falling back to CPU when the
driver is too old), **Linux x64** (Vulkan, falling back to CPU) and **macOS**
(Metal on Apple Silicon, CPU-only on Intel - llama.cpp builds its Intel asset
with Metal switched off).

The macOS builds come with the release's own limits, and neither can be worked
around from here. Both carry a minimum macOS version stamped into the binary at
build time, read out of the current pin and checked **before** anything is
downloaded: **13.3** for the Intel build (llama.cpp sets that deployment target
explicitly) and **26.0** for the Apple Silicon one (llama.cpp sets none, so the
build inherits the macOS of the runner it was compiled on). A Mac below its
build's minimum is told so and left alone - the installer records it as a manual
step and moves on, rather than failing.

The Intel build has a second limit that no header states and nothing can check
in advance: it is compiled for the CPU of llama.cpp's CI runner, so an older
Intel Mac stops with an illegal instruction the first time the binary runs.

In either case the way out is the same: build llama.cpp on the machine and name
the result in `"llama_server_path"`, or switch `"llm_backend"` to `lm-studio`.

The Linux fallback is worth a word. llama.cpp ships no CUDA binary for Linux,
so the GPU build there is the Vulkan one, and whether it can see the GPU cannot
be known before downloading it - it needs a Vulkan loader (`libvulkan1`), an
ICD manifest published by the driver, and a device reachable through both.
Under WSL2 the NVIDIA driver publishes no Vulkan ICD at all, so the check comes
back empty. The fetcher therefore tries Vulkan, verifies it with
`--list-devices`, and installs the CPU build instead when no device appears,
saying so in the log. If you fix the Vulkan side later, `--force` re-runs the
whole selection.

### Get a GGUF model

`install.py` already downloads `llama-3.2-3b-instruct-q4_k_m.gguf` into `models/`,
the first-run window offers the same download, and `python -m mimora.gguf_fetch`
does it on its own (`--list` shows the target path and whether the file is there).
To use a different model instead, download a small instruct model (e.g. `Llama-3.2-3B-Instruct-Q4_K_M.gguf`) and place it at the path set by `EXTERNAL_MODEL_PATH` in `mimora/config.py` (default: `models/llama-3.2-3b-instruct-q4_k_m.gguf`).

---

## Usage

Run from the **same virtual environment** you installed into (so the app uses the interpreter that has all the dependencies):

```bash
.venv\Scripts\activate            # Windows
# source .venv/bin/activate       # macOS / Linux

python main.py
```

On first launch the app loads the TTS and pronunciation (Wav2Vec2) models and starts the LLM server. If you ran `install.py` (or already launched once), the models are cached and this is just a load that takes a moment; if any model is still missing, it is downloaded first (several GB), which takes a while. Once it shows **Ready**:

1. Edit the **Practice text** panel (or keep the default).
2. Click **Next phrase ▶** - Mimora generates a phrase and speaks it.
3. **Press `SPACE`** (or click the mic button) and repeat the phrase; the take auto-stops on silence (press `SPACE` / click the mic again to stop manually).
4. Read your **score** and verdict on the phrase card: mispronounced words are underlined (click any word to hear it slowly) and the **WORK ON** badges name the sounds to fix (click one for an example word). Earlier takes stay in the attempt history below.
5. Use **Reference ▶** (or **Slow ▶**) / **My recording ▶** to compare, then repeat or generate the next phrase.

The **first few phrases run noticeably slowly** - the models are still warming up
and loading their data into memory on their first call. This is normal; speed
settles to its steady state after the initial requests.

Press `ESC` or close the window to quit (the LLM server subprocess is terminated cleanly).

### Where Mimora keeps your files

Running from a clone - which is every installation today - everything stays
inside the project directory: `config/`, `models/`, `model_cache/`, `bin/llama/`
and `logs/`. Nothing about that changes, and there is nothing to migrate.

Installed as a package (a future option), the same layout moves under the
operating system's user-data directory, because a package's own directory
belongs to whatever installed it and is rebuilt on the next upgrade:

| OS | Location |
|---|---|
| Windows | `%APPDATA%\Mimora\` |
| macOS | `~/Library/Application Support/Mimora/` |
| Linux | `$XDG_DATA_HOME/mimora/` (default `~/.local/share/mimora/`) |

Set the `MIMORA_HOME` environment variable to put that directory anywhere else.
It overrides both cases and is the answer to the three situations that need one:
moving several gigabytes of downloads to another drive, escaping a roaming
Windows profile that would carry them across the network at every login, and
running two configurations side by side.

**Paths inside `config/settings.json` resolve against that file's own
directory.** Absolute paths work everywhere and are what the settings window
writes; a relative one is read as relative to the settings file you are editing.
Keys you leave out are not affected by the rule at all - their defaults are
resolved for you, and the downloads and the files shipped with the app do not
live in the same place.

---

## GPU / CPU notes

Several torch models (the active engine's Wav2Vec2 - the `phoneme` recognizer by default, Kokoro, and the NLLB translator) plus llama.cpp can compete for VRAM on a single GPU. Mimora mitigates this two ways:

- The LLM runs in a **separate process** (llama-server), and the practice loop runs its phases (LLM → Kokoro → Wav2Vec2) **sequentially**, so they don't synthesize/infer at the same time. The NLLB translator defaults to CPU (`TRANSLATOR_DEVICE`).
- If VRAM is still tight, set `WAV2VEC2_DEVICE = "cpu"` in `mimora/config.py` - short phrases analyze acceptably on CPU.

---

## Known limitations

- **Spanish `phoneme` scoring is experimental for now.** The default `phoneme` engine uses a multilingual IPA recognizer, but its scoring calibration is per-language; a proper Spanish calibration requires labeled non-native speech data and is not available yet, so Spanish scoring falls back to the English calibration (usable, not tuned - the app logs a startup warning and the settings window shows a notice). The `acoustic` engine is English-only (English ASR model) and is not offered for other languages. (The translation panel already targets many languages - that is the practice phrase's translation, not the practice language itself.)
- The transcription-based word errors only surface mistakes the ASR actually "hears"; subtle distortions where the word is still recognized may not appear in the word list (the default phoneme engine's IPA edit distance, or the acoustic engine's DTW, plus prosody partially compensate).
- Scoring is **heuristic** and depends on your voice and microphone. After a practice session, re-anchor the active engine to your data: `python pronunciation/phoneme/calibrate.py` (default engine) or `python pronunciation/acoustic/calibrate.py` (acoustic engine); `--dry-run` previews the change. Every attempt's raw components are logged to `logs/phoneme_samples.jsonl` (or `logs/acoustic_samples.jsonl`) and `logs/main.log` for inspection.

---

## Credits

- **[OpenPronounce](https://github.com/Halleck45/OpenPronounce)** (MIT) - the pronunciation-scoring core reused in `pronunciation/acoustic/`.
- **[Kokoro-82M](https://huggingface.co/hexgrad/Kokoro-82M)** - text-to-speech (English variants).
- **[Supertonic 3](https://huggingface.co/Supertone/supertonic-3)** ([supertonic-py](https://github.com/supertone-inc/supertonic-py), code MIT, weights OpenRAIL-M) - text-to-speech (Spanish variant), ONNX runtime.
- **[wav2vec2-xlsr-53-espeak-cv-ft](https://huggingface.co/facebook/wav2vec2-xlsr-53-espeak-cv-ft)** (Hugging Face Transformers) - espeak-style IPA phoneme recognizer for the default `phoneme` engine.
- **[Wav2Vec2](https://huggingface.co/facebook/wav2vec2-large-960h)** (Hugging Face Transformers) - acoustic embeddings and transcription (`acoustic` engine).
- **[NLLB-200](https://huggingface.co/facebook/nllb-200-distilled-600M)** (Hugging Face Transformers) - offline translation for the translation panel.
- **[espeak-ng](https://github.com/espeak-ng/espeak-ng)** / **[phonemizer-fork](https://github.com/bootphon/phonemizer)** - reference phonemization (espeak IPA).
- **[panphon](https://github.com/dmort27/panphon)** - articulatory feature distance used by the phoneme edit-distance scoring.
- **[llama.cpp](https://github.com/ggerganov/llama.cpp)** - local LLM inference; the official `llama-server` binary is run as a subprocess.

## License

See [`LICENSE`](https://github.com/vikonix/Mimora/blob/main/LICENSE). The reused OpenPronounce components are MIT-licensed; their attribution is retained in `pronunciation/acoustic/speech.py`.

Model weights have their own licenses. In particular, the Supertonic 3 TTS
weights are licensed under [OpenRAIL-M](https://huggingface.co/Supertone/supertonic-3/blob/main/LICENSE)
(the `supertonic` package code is MIT). Mimora therefore never bundles these
weights: they are downloaded from Hugging Face by `install.py` or on the first
online run, into `model_cache/supertonic3/`.
