Files
hamori/docs/chord_format_spec.md
T
H1K0 f0352015cf docs: simplify §8 filename convention to snake_case_title-function.chord
Replace the YYYY_NNN_kebab-case scheme with title_in_snake_case-function.chord.
Snake_case makes the title double-click-selectable; dash unambiguously
separates the title from the optional function suffix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 00:51:22 +03:00

498 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Спецификация формата данных hamori
**Версия:** 2.2
**Дата:** 2026-05-20
---
## 1. Что описывает этот документ
Формат хранения и кодирования гармонических периодов для обучения генеративной модели и для практического использования модели как творческого помощника.
Один файл в этом формате описывает **один гармонический период** (4–16 тактов) — замкнутую гармоническую фразу.
---
## 2. Архитектура формата
Формат двухуровневый:
- **Исходный (`.chord`)** — то, что пишется руками. Близок к лид-шиту, человекочитаем, легко правится в любом текстовом редакторе.
- **Токенизированный** — то, что подаётся в модель. Факторизованное представление с фиксированным словарём (85 токенов).
Между уровнями стоит детерминированный парсер.
---
## 3. Исходный формат `.chord`
### 3.1 Пример
```
# title: Sea Glass - chorus
# key: D_major
# time: 4/4
# subdivision: 4
# style: H1K0
# function: chorus
| Gmaj7 . . . | A . . . | F#m7 . . . | Bm7 . . . |
| Gmaj7 . . . | A . . . | D . . . | D . . . |
```
### 3.2 Структура файла
Один файл — один период. Расширение `.chord`. Кодировка UTF-8.
Состоит из двух частей:
1. **Шапка** — строки, начинающиеся с `#`, в любом порядке.
2. **Тело** — последовательность тактов, каждый обрамлён `|`.
Пустые строки игнорируются. Комментарии (`// ...`) игнорируются парсером.
### 3.3 Поля шапки
| Поле | Обязательно | Допустимые значения | Назначение |
| ------------- | ----------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `title` | да | свободная строка | идентификация периода |
| `key` | да | `<note>_major` или `<note>_minor` | для нормализации в C/Am перед обучением |
| `time` | да | `4/4`, `3/4`, `6/8`, `2/4`, `12/8`, `5/4`, `7/4`, `7/8`, `9/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`, ...) принимаются и нормализуются к диезной форме.
**`subdivision`** определяет, сколько позиций в одном такте:
- При `subdivision: 4` в `4/4` — 4 позиции на такт (одна на четверть).
- При `subdivision: 8` в `4/4` — 8 позиций на такт (одна на восьмую).
- Для `3/4` — соответственно 3 или 6 позиций.
- Для `6/8` — 6 позиций при `subdivision: 8`, рекомендуемое значение.
### 3.4 Тело файла
Последовательность тактов. Такты записываются на одной строке через `|` или каждый на своей строке — парсер обрабатывает оба варианта одинаково.
Внутри такта — ровно `subdivision` позиций (или соответствующее число для нестандартных размеров), разделённых **одним пробелом**. На каждой позиции — один токен.
### 3.5 Что может стоять на позиции
| Запись | Значение |
| ----------------- | ------------------------------------------------------------ |
| Аккордовый символ | новый аккорд начинается здесь |
| `.` | удержать предыдущий аккорд |
| `NC` | no chord — пауза в гармонии |
| `?` | unknown — для случаев неуверенности; парсер выпустит `<UNK>` |
Первая позиция первого такта не может быть `.` (нечего удерживать).
### 3.6 Комментарии
Строки и хвосты строк, начинающиеся с `//`, игнорируются:
```
// здесь характерный приём с проходящим басом
| F . . . | F/E . . . | Dm . . . | Dm/C . . . |
```
---
## 4. Аккордовая нотация
### 4.1 Структура аккордового символа
```
<root><quality?><extension?>(/<bass>)?
```
Примеры:
- `C` → root=C, quality=maj (по умолчанию), без 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=m7, extension=9, bass=F
### 4.2 Корневой тон
Один из 12 классов: `C, C#, D, D#, E, F, F#, G, G#, A, A#, B`. Бемольная запись принимается.
### 4.3 Базовые качества (18)
| Символ | Альтернативы | Состав от корня |
| --------------- | ------------------------------ | --------------- |
| `maj` или пусто | — | 1, 3, 5 |
| `m`, `min` | `-` | 1, ♭3, 5 |
| `dim` | `°` | 1, ♭3, ♭5 |
| `aug` | `+` | 1, 3, ♯5 |
| `sus2` | — | 1, 2, 5 |
| `sus4` | `sus` | 1, 4, 5 |
| `maj7` | `M7`, `Δ7`, `Δ` | 1, 3, 5, 7 |
| `m7` | `min7`, `-7` | 1, ♭3, 5, ♭7 |
| `7` | — | 1, 3, 5, ♭7 |
| `m7b5` | `ø`, `ø7`, `m7♭5` | 1, ♭3, ♭5, ♭7 |
| `dim7` | `°7` | 1, ♭3, ♭5, ♭♭7 |
| `mM7` | `m(maj7)`, `minMaj7` | 1, ♭3, 5, 7 |
| `7sus4` | `7sus` | 1, 4, 5, ♭7 |
| `aug7` | `7#5`, `+7` | 1, 3, ♯5, ♭7 |
| `6` | `maj6` | 1, 3, 5, 6 |
| `m6` | `min6` | 1, ♭3, 5, 6 |
| `add9` | `2` (в некоторых поп-нотациях) | 1, 3, 5, 9 |
| `m(add9)` | `madd9`, `m(add2)` | 1, ♭3, 5, 9 |
Если качество не указано (например, просто `C`), оно автоматически = `maj`.
### 4.4 Расширения (опционально)
Один опциональный слот после качества:
| Символ | Значение |
| ------ | ---------------- |
| `9` | нона натуральная |
| `b9` | ♭9 |
| `#9` | ♯9 |
| `11` | ундецима |
| `#11` | ♯11 (лидийская) |
| `13` | терцдецима |
| `b13` | ♭13 |
**Условные сокращения, которые парсер расшифровывает автоматически:**
| Запись | Расшифровка |
| ---------- | --------------------------- |
| `C9` | quality=7, extension=9 |
| `Cmaj9` | quality=maj7, extension=9 |
| `Cm9` | quality=m7, extension=9 |
| `C13` | quality=7, extension=13 |
| `Cmaj13` | quality=maj7, extension=13 |
| `Cm11` | quality=m7, extension=11 |
| `C7b9` | quality=7, extension=b9 |
| `Cmaj7#11` | quality=maj7, extension=#11 |
**Ограничение:** ровно один слот расширения на аккорд. Если в исходной пьесе встречается аккорд с несколькими альтерациями (например, `C7♯9♭13`), нужно выбрать одну наиболее характерную и записать её. Это сознательное упрощение для уменьшения комбинаторики словаря.
### 4.5 Инверсии и слэш-аккорды
Стандартный формат `<chord>/<bass>`:
```
F/A ← F, бас A (первое обращение)
G/B ← G, бас B (первое обращение)
F/G ← F с басом G (характерный on-аккорд из J-Pop)
Em7/G ← Em7, бас G
D/F# ← D, бас F♯
```
Бас может быть любой из 12 нот. Парсер не проверяет, входит ли бас в состав аккорда — это ответственность транскрибера. Если слэш не указан, считается `bass = root`.
**Инверсии обязательны к точной записи.** Они несут значительную часть стилистической информации (особенно для J-Pop материала с характерной on-нотацией).
### 4.6 Полные примеры разбора
| Запись | root | quality | extension | bass |
| --------- | ---- | ------- | --------- | ---- |
| `C` | C | maj | none | root |
| `Am` | A | m | none | root |
| `F#m7` | F# | m7 | none | root |
| `Cmaj9` | C | maj7 | 9 | root |
| `G7sus4` | G | 7sus4 | none | root |
| `F/G` | F | maj | none | G |
| `Bb7b9/D` | A# | 7 | b9 | D |
| `Em7b5` | E | m7b5 | none | root |
| `D#dim7` | D# | dim7 | none | root |
---
## 5. Токенизированное представление
### 5.1 Принцип нормализации тональности
**Перед токенизацией каждый период транспонируется в каноническую тональность:**
- мажорные периоды → C major,
- минорные периоды → A minor.
После транспозиции аккорды записываются абсолютными именами, но фактически отражают функциональные отношения относительно тоники. Тональность исходного периода в токенах **не присутствует**.
На инференсе пользователь задаёт желаемую тональность, модель генерирует в C/Am, и результат транспонируется обратно постпроцессингом.
### 5.2 Полный словарь
**Служебные (4):**
`<BOS>`, `<EOS>`, `<PAD>`, `<UNK>`
**Метаданные периода (выпускаются один раз в начале последовательности, после `<BOS>`):**
- `MODE_major`, `MODE_minor` — 2 токена (наследие тональности; нужны, потому что мажор и минор остаются разделёнными)
- `TIME_4/4`, `TIME_3/4`, `TIME_6/8`, `TIME_2/4`, `TIME_12/8`, `TIME_5/4`, `TIME_7/4`, `TIME_7/8`, `TIME_9/8` — 9 токенов
- `SUB_4`, `SUB_8` — 2 токена
- `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):**
- `ROOT_C`, `ROOT_C#`, ..., `ROOT_B` — 12 токенов
- `QUAL_maj`, `QUAL_m`, `QUAL_dim`, `QUAL_aug`, `QUAL_sus2`, `QUAL_sus4`, `QUAL_maj7`, `QUAL_m7`, `QUAL_7`, `QUAL_m7b5`, `QUAL_dim7`, `QUAL_mM7`, `QUAL_7sus4`, `QUAL_aug7`, `QUAL_6`, `QUAL_m6`, `QUAL_add9`, `QUAL_m_add9` — 18 токенов
- `EXT_none`, `EXT_9`, `EXT_b9`, `EXT_#9`, `EXT_11`, `EXT_#11`, `EXT_13`, `EXT_b13` — 8 токенов
- `BASS_root`, `BASS_C`, `BASS_C#`, ..., `BASS_B` — 13 токенов
**Временные/структурные:**
- `HOLD` — позиция продолжает предыдущий аккорд
- `NC` — пауза в гармонии
- `BAR` — конец такта
**Итого:** 4 + 2 + 9 + 2 + 5 + 9 + 12 + 18 + 8 + 13 + 3 = **85 токенов**.
### 5.3 Структура обучающей последовательности
```
<BOS>
MODE_<x> TIME_<x> SUB_<x> STYLE_<x> FUNC_<x>
ROOT_<x> QUAL_<x> EXT_<x> BASS_<x> ← новый аккорд = 4 токена
HOLD ← удержание = 1 токен
HOLD
HOLD
BAR
ROOT_<x> QUAL_<x> EXT_<x> BASS_<x>
HOLD
ROOT_<x> QUAL_<x> EXT_<x> BASS_<x>
HOLD
BAR
...
<EOS>
```
### 5.4 Алгоритм токенизации (источник → токены)
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`; если `STYLE_<style>` отсутствует в словаре — выпустить `STYLE_other` с предупреждением в лог).
5. Для каждого такта:
- Для каждой позиции в такте:
- Если новый аккорд: разобрать на (root, quality, extension, bass), выпустить 4 токена в этом порядке.
- Если `.`: выпустить `HOLD`.
- Если `NC`: выпустить `NC`.
- Если `?`: выпустить `<UNK>`.
- После последней позиции — `BAR`.
6. Выпустить `<EOS>`.
### 5.5 Алгоритм детокенизации (токены → MIDI)
1. Считать метатокены, восстановить параметры периода.
2. Группировать аккордовые токены по 4 (root, quality, extension, bass).
3. Развернуть в последовательность аккордов с длительностями, считая HOLD-ы как продолжение предыдущего.
4. Транспонировать обратно из C/Am в целевую тональность (задаваемую пользователем на инференсе).
5. Сгенерировать MIDI через `pretty_midi`: для каждого аккорда выложить ноты в один трек, бас — отдельной линией в нижней октаве.
### 5.6 Оценка длины последовательности
Период 8 тактов в 4/4 с subdivision=4, в среднем 2 смены аккорда на такт:
- Метатокены: 1 (BOS) + 5 = 6 токенов
- На такт: 2 аккорда × 4 + 2 HOLD-а + 1 BAR = 11 токенов
- 8 тактов: ~88 токенов
- EOS: 1
- **Итого: ~95 токенов на типичный период.**
Длинный период (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. Полный пример
### 6.1 Исходный `.chord` файл
```
# title: Example period
# key: G_major
# time: 4/4
# subdivision: 4
# style: H1K0
# function: chorus
| Gmaj7 . . . | Bm7 . . . | Em7 . . . | C . D . |
| Cmaj7 . . . | G/B . . . | Am7 . D . | G . . . |
```
### 6.2 Токенизация
Шаг 1: транспонировать из G major в C major (вниз на 7 полутонов или вверх на 5):
```
| Cmaj7 . . . | Em7 . . . | Am7 . . . | F . G . |
| Fmaj7 . . . | C/E . . . | Dm7 . G . | C . . . |
```
Шаг 2: выпустить токены:
```
<BOS>
MODE_major TIME_4/4 SUB_4 STYLE_H1K0 FUNC_chorus
ROOT_C QUAL_maj7 EXT_none BASS_root
HOLD HOLD HOLD
BAR
ROOT_E QUAL_m7 EXT_none BASS_root
HOLD HOLD HOLD
BAR
ROOT_A QUAL_m7 EXT_none BASS_root
HOLD HOLD HOLD
BAR
ROOT_F QUAL_maj EXT_none BASS_root
HOLD
ROOT_G QUAL_maj EXT_none BASS_root
HOLD
BAR
ROOT_F QUAL_maj7 EXT_none BASS_root
HOLD HOLD HOLD
BAR
ROOT_C QUAL_maj EXT_none BASS_E
HOLD HOLD HOLD
BAR
ROOT_D QUAL_m7 EXT_none BASS_root
HOLD
ROOT_G QUAL_maj EXT_none BASS_root
HOLD
BAR
ROOT_C QUAL_maj EXT_none BASS_root
HOLD HOLD HOLD
BAR
<EOS>
```
(Переносы строк здесь для читаемости; в реальности — один поток.)
---
## 7. Краевые случаи
### 7.1 Анакруза (затакт)
Записывается как первый такт с `NC` на пустых позициях:
```
| NC . . D | G . . . | ...
```
### 7.2 Тонально нестабильные фрагменты
Если внутри периода невозможно однозначно определить тональность (например, секвенция через несколько тоник) — записать его в наиболее подходящей тональности по контексту исходной пьесы. Внутренние отклонения и тонизации записываются обычными функциональными аккордами; это не модуляция.
Истинная модуляция (смена тональности в середине периода) **в текущей версии формата не поддерживается**. Если в исходной пьесе период модулирует — разрезать его на два периода: до и после модуляции.
### 7.3 Смена тактового размера внутри периода
Не поддерживается. Если есть — разрезать на два периода.
### 7.4 Полиаккорды
Не поддерживаются. Записывать как слэш-аккорд или ближайший наиболее характерный single chord.
### 7.5 Микротональность
Не поддерживается. Округлять до ближайшего темперированного аккорда.
### 7.6 Аккорды короче subdivision
Если в пьесе систематически встречаются аккорды длительностью короче выбранного subdivision (например, восьмая нота при `subdivision: 4`) — переключить весь период на `subdivision: 8`. Не округлять и не выбрасывать аккорды.
---
## 8. Соглашения по именам файлов
Рекомендованный формат:
```
<title>-<function>.chord
```
- `<title>` — короткое имя пьесы в **snake_case** (слова через подчёркивание). Двойной клик в редакторе выделяет имя целиком.
- `<function>` — функциональная роль периода; отделяется **дефисом**.
Примеры:
```
electricity-verse.chord
electricity-chorus.chord
hikari_no_shizuku.chord
sea_glass-bridge.chord
```
Поле `function` в имени файла — необязательно. Если период не привязан к конкретной функции, достаточно просто `<title>.chord`.
---
## 9. Что НЕ кодируется (сознательные пропуски)
- **Voicing** (расположение голосов внутри аккорда выше баса). Бас передаётся, остальное — на ручную работу в DAW.
- **Ритмический паттерн** внутри удержания аккорда (восьмые, синкопы, паттерны типа альбертиевых басов).
- **Динамика, артикуляция, тембр, аранжировка.**
- **Мелодия.** Это отдельная задача за пределами текущего проекта.
- **Полная форма пьесы.** Юнит — период, не пьеса целиком.
- **Метроритмическая микроразметка** (свинг, рубато, ramp'ы темпа).
---
## 10. Чек-лист транскрипции
При перекладывании периода из DAW в `.chord` файл:
1. **Прослушать пьесу целиком**, определить границы периодов. Замкнутая фраза с возвратом к тонике или ясной полукаденцией — кандидат на период.
2. **Заполнить шапку:** title, key, time, subdivision, style, function.
3. **Проверить subdivision.** Если в выбранном периоде аккорды стабильно меняются не чаще четверти — `subdivision: 4`. Если есть систематические восьмые смены — `subdivision: 8`.
4. **Транскрибировать гармонию по позициям**, аккуратно фиксируя инверсии.
5. **Прогнать парсер**, проверить отсутствие `<UNK>` и предупреждений.
6. **Прогнать sanity-check утилиту** (`.chord` → MIDI), воспроизвести в DAW параллельно с оригиналом, убедиться в совпадении.
7. **Сохранить файл** под именем по схеме из §8.
---
## 11. История изменений
- **v2.2** (текущая)
- Добавлены тактовые размеры `5/4`, `7/4`, `7/8`, `9/8` — в допустимые значения поля `time` и в словарь (`TIME_*`).
- Размер словаря: 81 → 85 токенов.
- **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` (необязательный) как метаданные периода.
- Удалены секционные теги (больше не нужны при периодной разбивке).
- Удалён тег `tempo_bucket` (мало пользы при текущем объёме данных).
- Словарь сокращён с ~100 до 81 токена.
- **v1.0** — первоначальная спецификация, единицей была целая пьеса, тональность входила в словарь, использовались секционные теги внутри последовательности.