refactor: replace fixed STYLE_user with open-ended style tag system

- STYLE_user renamed to STYLE_H1K0 in VOCAB (author's personal tag)
- Style field now accepts any [A-Za-z][A-Za-z0-9_]* identifier in .chord files
- Unknown styles fall back to STYLE_other at tokenization time with a log warning
- Test fixtures updated to style: other; drop closed _VALID_STYLES frozenset
- Spec bumped to v2.1: documents open style field, fallback behaviour, and §5.7
  guide on registering a new style token

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 00:29:52 +03:00
parent 84ba7b4743
commit 4fd8ece170
12 changed files with 60 additions and 38 deletions
+38 -21
View File
@@ -1,7 +1,7 @@
# Спецификация формата данных hamori
**Версия:** 2.0
**Дата:** 2026-05-16
**Версия:** 2.1
**Дата:** 2026-05-20
---
@@ -18,7 +18,7 @@
Формат двухуровневый:
- **Исходный (`.chord`)** — то, что пишется руками. Близок к лид-шиту, человекочитаем, легко правится в любом текстовом редакторе.
- **Токенизированный** — то, что подаётся в модель. Факторизованное представление с маленьким словарём (~75 токенов).
- **Токенизированный** — то, что подаётся в модель. Факторизованное представление с фиксированным словарём (81 токен).
Между уровнями стоит детерминированный парсер.
@@ -33,7 +33,7 @@
# key: D_major
# time: 4/4
# subdivision: 4
# style: user
# style: H1K0
# function: chorus
| Gmaj7 . . . | A . . . | F#m7 . . . | Bm7 . . . |
@@ -53,14 +53,14 @@
### 3.3 Поля шапки
| Поле | Обязательно | Допустимые значения | Назначение |
| ------------- | ----------- | -------------------------------------------------------------------------------- | -------------------------------------------- |
| `title` | да | свободная строка | идентификация периода |
| `key` | да | `<note>_major` или `<note>_minor` | для нормализации в C/Am перед обучением |
| `time` | да | `4/4`, `3/4`, `6/8`, `2/4`, `12/8` | тактовый размер |
| `subdivision` | да | `4` или `8` | сколько позиций в одном такте |
| `style` | да | `user`, `jpop`, `classical`, `jazz`, `other` | стилевой тег периода |
| `function` | нет | `verse`, `prechorus`, `chorus`, `bridge`, `intro`, `outro`, `interlude`, `other` | функциональная роль периода в исходной пьесе |
| Поле | Обязательно | Допустимые значения | Назначение |
| ------------- | ----------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `title` | да | свободная строка | идентификация периода |
| `key` | да | `<note>_major` или `<note>_minor` | для нормализации в C/Am перед обучением |
| `time` | да | `4/4`, `3/4`, `6/8`, `2/4`, `12/8` | тактовый размер |
| `subdivision` | да | `4` или `8` | сколько позиций в одном такте |
| `style` | да | любой идентификатор `[A-Za-z][A-Za-z0-9_]*` (например: `H1K0`, `jpop`, `other`) | стилевой тег периода; при токенизации тег должен совпасть с токеном `STYLE_<tag>` в словаре, иначе используется `STYLE_other` |
| `function` | нет | `verse`, `prechorus`, `chorus`, `bridge`, `intro`, `outro`, `interlude`, `other` | функциональная роль периода в исходной пьесе |
**Допустимые ноты для `key`:** `C, C#, D, D#, E, F, F#, G, G#, A, A#, B`. Бемольные написания (`Db`, `Eb`, ...) принимаются и нормализуются к диезной форме.
@@ -110,10 +110,10 @@
Примеры:
- `C` → root=C, quality=maj (по умолчанию), без extension, bass=root
- `Am7` → root=A, quality=min7, без extension, bass=root
- `Am7` → root=A, quality=m7, без extension, bass=root
- `Fmaj9` → root=F, quality=maj7, extension=9, bass=root
- `G7/B` → root=G, quality=7, без extension, bass=B
- `Dm9/F` → root=D, quality=min7, extension=9, bass=F
- `Dm9/F` → root=D, quality=m7, extension=9, bass=F
### 4.2 Корневой тон
@@ -228,7 +228,7 @@ D/F# ← D, бас F♯
- `MODE_major`, `MODE_minor` — 2 токена (наследие тональности; нужны, потому что мажор и минор остаются разделёнными)
- `TIME_4/4`, `TIME_3/4`, `TIME_6/8`, `TIME_2/4`, `TIME_12/8` — 5 токенов
- `SUB_4`, `SUB_8` — 2 токена
- `STYLE_user`, `STYLE_jpop`, `STYLE_classical`, `STYLE_jazz`, `STYLE_other` — 5 токенов
- `STYLE_H1K0`, `STYLE_jpop`, `STYLE_classical`, `STYLE_jazz`, `STYLE_other` — 5 токенов стиля (текущий словарь); неизвестный тег → `STYLE_other`
- `FUNC_verse`, `FUNC_prechorus`, `FUNC_chorus`, `FUNC_bridge`, `FUNC_intro`, `FUNC_outro`, `FUNC_interlude`, `FUNC_other`, `FUNC_unspecified` — 9 токенов
**Аккордовые слоты (новый аккорд = ровно 4 токена в порядке root, quality, extension, bass):**
@@ -274,7 +274,7 @@ BAR
1. Прочитать шапку.
2. Транспонировать все аккорды: если `key = X_major`, транспонировать так, чтобы X стало C; если `key = X_minor` — так, чтобы X стало A.
3. Выпустить `<BOS>`.
4. Выпустить метатокены: `MODE_<major|minor>`, `TIME_<x>`, `SUB_<x>`, `STYLE_<x>`, `FUNC_<x>` (если `function` не задан — выпустить `FUNC_unspecified`).
4. Выпустить метатокены: `MODE_<major|minor>`, `TIME_<x>`, `SUB_<x>`, `STYLE_<x>`, `FUNC_<x>` (если `function` не задан — выпустить `FUNC_unspecified`; если `STYLE_<style>` отсутствует в словаре — выпустить `STYLE_other` с предупреждением в лог).
5. Для каждого такта:
- Для каждой позиции в такте:
- Если новый аккорд: разобрать на (root, quality, extension, bass), выпустить 4 токена в этом порядке.
@@ -304,6 +304,18 @@ BAR
Длинный период (16 тактов с частой сменой): редко превышает 250 токенов. Контекстное окно 512 токенов более чем достаточно.
### 5.7 Добавление нового стилевого тега
Поле `style` в `.chord` файле принимает любой идентификатор — дополнительный код менять не нужно. Однако, чтобы модель кондиционировалась на новый стиль отдельным токеном (а не сваливала его в `STYLE_other`), нужно зарегистрировать токен в словаре:
1. Открыть `src/tokenizer.py`, найти блок `# Style (5)` в списке `VOCAB`.
2. Добавить `"STYLE_<tag>"` в этот блок. Пример: `"STYLE_ALICE"`.
3. Обновить счётчик в §5.2 этой спецификации и в итоговой строке («**N токенов**»).
4. **Переобучить модель с нуля** — изменение размера словаря меняет размер матрицы эмбеддингов и выходного слоя; дообучить существующий чекпоинт не получится.
5. Обновить все `.chord` файлы нужного корпуса, выставив новый тег в поле `style`.
Если токен не зарегистрирован, парсер принимает файл без ошибок, но токенизатор выдаёт `STYLE_other` и пишет предупреждение в лог. Это удобно для экспериментов до переобучения.
---
## 6. Полный пример
@@ -315,10 +327,10 @@ BAR
# key: G_major
# time: 4/4
# subdivision: 4
# style: user
# style: H1K0
# function: chorus
| Gmaj7 . . . | Bm7 . . . | Em7 . . . | C . . D . |
| Gmaj7 . . . | Bm7 . . . | Em7 . . . | C . D . |
| Cmaj7 . . . | G/B . . . | Am7 . D . | G . . . |
```
@@ -327,7 +339,7 @@ BAR
Шаг 1: транспонировать из G major в C major (вниз на 7 полутонов или вверх на 5):
```
| Cmaj7 . . . | Em7 . . . | Am7 . . . | F . . G . |
| Cmaj7 . . . | Em7 . . . | Am7 . . . | F . G . |
| Fmaj7 . . . | C/E . . . | Dm7 . G . | C . . . |
```
@@ -335,7 +347,7 @@ BAR
```
<BOS>
MODE_major TIME_4/4 SUB_4 STYLE_user FUNC_chorus
MODE_major TIME_4/4 SUB_4 STYLE_H1K0 FUNC_chorus
ROOT_C QUAL_maj7 EXT_none BASS_root
HOLD HOLD HOLD
@@ -468,7 +480,12 @@ YYYY_NNN_<short-title>_<function>.chord
## 11. История изменений
- **v2.0** (текущая)
- **v2.1** (текущая)
- Поле `style` теперь принимает любой идентификатор `[A-Za-z][A-Za-z0-9_]*`, а не фиксированный список.
- `STYLE_user` заменён на `STYLE_H1K0` (авторский тег) в словаре модели.
- Неизвестный стиль при токенизации маппируется на `STYLE_other` с предупреждением в лог.
- **v2.0**
- Единицей датасета стал **гармонический период**, не пьеса целиком.
- Введена **нормализация тональности** в C major / A minor. Поле `key` сохраняется в шапке для парсера, но в словаре модели нет — есть только `MODE_major` / `MODE_minor`.
- Введён тег `function` (необязательный) как метаданные периода.