README.md

# open_jtalk_elixir

[![Hex version](https://img.shields.io/hexpm/v/open_jtalk_elixir.svg "Hex version")](https://hex.pm/packages/open_jtalk_elixir)
[![CI](https://github.com/mnishiguchi/open_jtalk_elixir/actions/workflows/ci.yml/badge.svg)](https://github.com/mnishiguchi/open_jtalk_elixir/actions/workflows/ci.yml)

<!-- MODULEDOC -->

Use Open JTalk from Elixir. This package builds a local `open_jtalk` CLI (and
optionally bundles a UTF-8 dictionary and an HTS voice) and exposes three
convenient APIs:

- `OpenJTalk.to_wav/2` — synthesize text to a WAV file
- `OpenJTalk.to_binary/2` — synthesize and return WAV bytes
- `OpenJTalk.say/2` — synthesize and play via a system audio player

## Install

Add the dependency to your `mix.exs`:

```elixir
def deps do
  [
    {:open_jtalk_elixir, "~> 0.1.0"}
  ]
end
```

Then:

```bash
mix deps.get
mix compile
```

On first compile the project may download and build MeCab, HTS Engine API,
and Open JTalk. You can optionally bundle a UTF-8 dictionary and a Mei voice
into `priv/` (see `BUNDLE_ASSETS` below).

### Build requirements

You’ll need common build tools: `gcc`/`g++`, `make`, `curl`, `tar`, `unzip`.
On macOS Xcode Command Line Tools are sufficient.

Optional environment flags (honored by the Makefile):

- `FULL_STATIC=1` — attempt a fully static `open_jtalk` (Linux only; requires static libstdc++)
- `BUNDLE_ASSETS=0|1` — whether to bundle dictionary/voice into `priv/`

## Quick start

```elixir
# play via system audio player (aplay/paplay/afplay/play)
OpenJTalk.say("元氣でっすか、元氣があればなんでもできる")
```

### Options

All synthesis calls accept the same options (values are clamped):

- `:timbre` — voice color offset `-0.8..0.8` (default `0.0`)
- `:pitch_shift` — semitones `-24..24` (default `0`)
- `:rate` — speaking speed `0.5..2.0` (default `1.0`)
- `:gain` — output gain in dB (default `0`)
- `:voice` — path to a `.htsvoice` file (optional)
- `:dictionary` — path to a directory containing `sys.dic` (optional)
- `:timeout` — max runtime in ms (default `20_000`)
- `:out` — output WAV path (only for `to_wav/2`)

<!-- MODULEDOC -->

## How asset resolution works

The package resolves required assets in this order:

1. Environment variable override
2. Bundled asset in `priv/`
3. System-installed location

### CLI binary (`open_jtalk`)

- **Env:** `OPENJTALK_CLI` — full path to `open_jtalk`.
- **Bundled:** `priv/bin/open_jtalk` (built during compile).
- **System:** `open_jtalk` found on `$PATH`.

### Dictionary (`sys.dic`)

- **Env:** `OPENJTALK_DIC_DIR` — directory containing `sys.dic`.
- **Bundled:** `priv/dic/sys.dic` or any `priv/dic/**/sys.dic` (e.g. `naist-jdic`).
- **System:** common locations such as `/var/lib/mecab/dic/open-jtalk/naist-jdic`,
  `/usr/lib/*/mecab/dic/open-jtalk/naist-jdic`, etc.

### Voice (`.htsvoice`)

- **Env:** `OPENJTALK_VOICE` — path to a `.htsvoice` file.
- **Bundled:** first file matching `priv/voices/**/*.htsvoice`.
- **System:** standard locations like `/usr/share/hts-voice/**` or `/usr/local/share/hts-voice/**`.

If you change environment variables at runtime (or move files), refresh the
cached paths:

```elixir
:ok = OpenJTalk.Assets.reset_cache()
```

## Using with Nerves

This library is Nerves-aware. When `MIX_TARGET` is set the build defaults to:

- `FULL_STATIC=1` — try to statically link the CLI on Linux targets when possible
- `BUNDLE_ASSETS=1` — bundle CLI, dictionary, and voice into `priv/`

So for many projects no extra configuration is needed.

### Quick Nerves flow

```bash
export MIX_TARGET=rpi4
mix deps.get
mix compile
mix firmware
```

On the device:

```elixir
{:ok, info} = OpenJTalk.info()
# bundled assets should show up as :bundled

OpenJTalk.say("こんにちは")
```

### Audio on Nerves

`OpenJTalk.say/2` requires a system audio player. Most Nerves images use ALSA
`aplay`. If your image does not include a player:

- add one to the system image, or
- use `OpenJTalk.to_wav/2` and play the WAV with your chosen mechanism.

### Firmware size notes

Bundling the full dictionary + voice + binary increases firmware size. Approximate
(uncompressed) sizes:

- Dictionary (NAIST-JDIC): ~100–110 MB
- Mei voice: ~2.2 MB
- CLI binary: ~0.7 MB

If that’s too large you can avoid bundling at compile time and provision assets
separately (rootfs overlay, `/data`, OTA, etc.):

```bash
MIX_TARGET=rpi4 BUNDLE_ASSETS=0 mix deps.compile open_jtalk_elixir
```

Then point the library to the provisioned assets (for example in
`config/runtime.exs`):

```elixir
System.put_env("OPENJTALK_CLI",     "/data/open_jtalk/bin/open_jtalk")
System.put_env("OPENJTALK_DIC_DIR", "/data/open_jtalk/dic")
System.put_env("OPENJTALK_VOICE",   "/data/open_jtalk/voices/mei_normal.htsvoice")

:ok = OpenJTalk.Assets.reset_cache()
```

How you provision those files into your image is outside the scope of this
library.

### Overriding the defaults

For Nerves builds this project uses the build defaults above, but you can
override them by exporting `BUNDLE_ASSETS` or `FULL_STATIC` before `mix compile`.

> Note: fully static linking is unsupported on macOS host triplets; this is
> only relevant for cross-compile targets that try to produce macOS artifacts.

## Testing

```bash
mix test

# run audio playback test if you have an audio player available
mix test --include audio
```

## Troubleshooting

- `{:error, {:binary_missing, _}}` — `open_jtalk` binary not found/built.
- `{:error, {:dictionary_missing, _}}` — `sys.dic` not found; set `OPENJTALK_DIC_DIR` or bundle assets.
- `{:error, {:voice_missing, _}}` — `.htsvoice` not found; set `OPENJTALK_VOICE` or bundle assets.
- `{:error, {:open_jtalk_exit, code, msg}}` — `open_jtalk` exited non-zero; see `msg`.
- Audio playback requires one of: `aplay` (ALSA), `paplay` (PulseAudio), `afplay` (macOS), or `play` (SoX).

## Third-party components & licenses

This package does not redistribute third-party assets by default. At compile
time it may download and build:

- **Open JTalk 1.11** — Modified BSD (BSD 3-Clause)  
  Source: http://open-jtalk.sourceforge.net/

- **HTS Engine API 1.10** — Modified BSD (BSD 3-Clause)  
  Source: http://hts-engine.sourceforge.net/

- **MeCab 0.996** — tri-licensed (GPL / LGPL / BSD); this project uses the BSD terms  
  Source: https://taku910.github.io/mecab/

- **Open JTalk Dictionary (NAIST-JDIC UTF-8) 1.11** — BSD-style by NAIST  
  Source: https://sourceforge.net/projects/open-jtalk/files/Dictionary/

- **HTS Voice “Mei” (MMDAgent_Example 1.8)** — CC BY 3.0  
  Source: https://sourceforge.net/projects/mmdagent/files/MMDAgent_Example/  
  Attribution: “HTS Voice ‘Mei’ © Nagoya Institute of Technology, licensed CC BY 3.0.”