What's the elvish word for "friend" ?  

# Mellon

Offline, fully in-browser **hotword / wake-word detection** powered by [EfficientWord-Net](https://github.com/Ant-Brain/EfficientWord-Net) (ResNet-50 ArcFace).

- **100% offline** — ONNX inference runs in the browser via WebAssembly; no server, no cloud.
- **Speaker-independent** — the model generalises across voices out of the box.
- **Custom words** — enroll any phrase with ≥ 3 audio samples.
- **TypeScript-ready** — ships with full `.d.ts` declarations.

---

## Table of contents

1. [Installation](#installation)
2. [Quick start](#quick-start)
3. [Enrolling words](#enrolling-custom-words)
4. [API reference](#api-reference)
   - [Detector](#detector)
   - [EnrollmentSession](#enrollmentsession)
5. [Science behind the lib](#science-behind-the-lib)
---

## Installation

```bash
npm install mellon
```

## Quick start

```js
import { Detector } from 'mellon'

const hotWordDetection = new Detector([
  {
    name: 'openDoors',
    triggers: [{ name: 'mellon', defaultRefPath: '/mellon-assets/mellon_ref.json' }],
    onMatch: () => console.log('opening the doors...')
  },
  {
    name: 'startEngine',
    triggers: [
      { name: 'start', defaultRefPath: '/mellon-assets/start_ref.json' },
      { name: 'go', defaultRefPath: '/mellon-assets/go_ref.json' }
    ],
    onMatch: (triggerNameMatched, confidence) => {
      console.log({ triggerNameMatched, confidence })
      console.log('starting engine...')
    }
  },
  {
    name: 'stopEngine',
    triggers: [
      { name: 'stop', defaultRefPath: '/mellon-assets/stop_ref.json' },
      { name: 'wait', defaultRefPath: '/mellon-assets/wait_ref.json' }
    ],
    onMatch: (triggerNameMatched, confidence) => {
      console.log({ triggerNameMatched, confidence })
      console.log('stopping engine...')
    }
  }
])

await hotWordDetection.start() // opens the mic and listens for all registered triggers
```

---

## Enrolling custom words

```js
import { Detector, EnrollmentSession, Storage } from 'mellon'

const hotwordDetection = new Detector([{
  name: 'startEngine',
  triggers: [{ name: 'start' }],
  onMatch: (triggerNameMatched, confidence) => { console.log('starting engine...') }
}])


// 1. Create an enrollment session
const session = new EnrollmentSession('start')

// 2. Record at least 3 samples (1.5 s each)
await session.recordSample()
await session.recordSample()
await session.recordSample()

// Optionally remove a bad take (0-based index)
// session.deleteSample(1)

// 3. Generate reference embeddings
const ref = await session.generateRef()

// 4a. Use immediately in the running detector
hotwordDetection.addCustomWord(ref)
await hotwordDetection.start()

// 4b. Persist for future sessions
Storage.saveWord(ref)
```

---

## API reference

### `Detector` 

The easiest way to use the library. Wraps mic access, AudioWorklet wiring, and detector management into a single class.

```ts
class Detector {
  constructor(commands: Command[], config?: MellonConfig)
  readonly threshold:  number   // read/write; persisted in localStorage
  readonly listening:  boolean

  init(): Promise<void>
  start(): Promise<void>
  stop(): Promise<void>
  addCustomWord(ref: WordRef): void

  // Storage helpers — static, work without a Detector instance
  static loadWords(storageKey?: string): WordRef[]
  static saveWord(ref: WordRef, storageKey?: string): void
  static deleteWord(wordName: string, storageKey?: string): void
}
```

### `Storage`

Static helpers for persisting enrolled word references in `localStorage`.

```ts
class Storage {
  static loadWords(storageKey?: string): WordRef[]
  static saveWord(ref: WordRef, storageKey?: string): void
  static deleteWord(wordName: string, storageKey?: string): void
}
```

### `EnrollmentSession`

Records audio samples from the mic (or uploaded files) and generates reference embeddings for a new custom word.

```ts
class EnrollmentSession {
  constructor(wordName: string, config?: EnrollmentSessionConfig)

  recordSample():                 Promise<number>    // records 1.5 s; returns new sample count
  deleteSample(index: number):    number             // removes sample at index; returns new count
  generateRef():                  Promise<WordRef>   // requires ≥ 3 samples
}
```

#### `WordRef` shape

```ts
interface WordRef {
  word_name:   string           // e.g. 'hello'
  model_type?: string
  embeddings:  number[][]       // N × 256 vectors
}
```

Compatible with the [EfficientWord-Net](https://github.com/Ant-Brain/EfficientWord-Net) `_ref.json` format — you can import reference files generated by the Python toolkit directly.

---
## Science behind the lib

Check out [this paper](https://arxiv.org/pdf/2111.00379).

## License

MIT
