# Альбомы и Singles: формальная FSM Проект следующего изменения Riff: как собрать альбомы, убрать короткие релизы в Singles и сохранить структуру при временной недоступности файлов. Это формальная модель предлагаемого поведения, а не описание уже выпущенной функции. Новые корзины и пороги ещё предстоит реализовать. ## Область модели Singles — категория представления локальной библиотеки. Она включает короткие релизы, имеющиеся фрагменты альбомов, наборы версий одной или нескольких композиций и подтверждённые DJ-сеты. Официальный тип релиза в Интернете не требуется для каждого решения. Модель состоит из двух расширенных конечных автоматов: классификатора `C` и автомата публикации `P`. Данные каталога хранятся в регистрах; поэтому это EFSM, а не автомат с отдельным состоянием на каждый возможный альбом. Автомат доступности ортогонален классификации. Cuetree определяет состав, Indexer публикует согласованный каталог, Player его читает. Модель запускается заново при изменении согласованных фактов, в том числе после IA. Существующие правила идентичности, конкурирующих изданий, копий и порядка позиций продолжают действовать. **Действующее объединение по весам сохраняется целиком.** Состояние `G` вызывает существующую глобальную группировку RIFF-ALBUM-V2: структурные и артистные правила, ограничения изданий, взвешенный остаточный score AG-9 и проверку всех пар при объединении. Порог 65 и веса AG-9 не заменяются предикатами Singles. Сначала эта группировка собирает полный допустимый состав, затем `C` решает, показывать готовую группу отдельным альбомом или внутри корзины. Короткие подгруппы нельзя отправлять в Singles до завершения глобального объединения. Для библиотеки iTunes сохраняются все прежние доказательства из тегов, каталогов и нумерации. Распознавание имени `0 01 …` — дополнительное наблюдение для `G`, когда номер в тегах отсутствует; оно не становится обязательным условием группировки. Неполная последовательность имён не отключает существующий score. Если новый предикат Singles не доказан, собранный прежним алгоритмом альбом остаётся альбомом. Изменение касается только классификации и представления результата. [AG-9: действующие веса и ограничения](https://wiki.271.pics/Riff/LATEST/album-grouping-residual-score). ## Входы и предикаты Для поколения `k` сначала применяются обычные правила удержания и TTL. Вход `R(k)` содержит все успешно полученные и ещё удерживаемые факты, включая недоступные. Число `N(g)` — количество логических позиций группы после разрешения эквивалентных копий. Это не количество доступных файлов. | Обозначение | Точное значение в предлагаемой модели | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `explicit(g)` | Признанная валидная многотрековая CUE/TOC либо самостоятельно озаглавленная встроенная таблица; также удерживаемое явное владение до обычного истечения. | | `chapters(g)` | У одного из базовых MEDIA есть признанная таблица глав без самостоятельного названия альбома. Конкурирующие таблицы проходят существующий explicit-путь. | | `grouped(g)` | Обычная группировка независимо допустила группу по действующим правилам. Отсутствие Album не исправляется именем папки. | | `protected(g)` | Подтверждённые речь/книга; такие группы сохраняются вне новых эвристик Singles. Явные таблицы и главы обрабатываются раньше. | | `short(g)` | `1 <= N(g) <= 3`, все длительности известны и `0 < duration < 600 s`. Неполная нумерация и больший заявленный Total Tracks не мешают. | | `versions(g)` | `4 <= N(g) <= 16`; один непротиворечивый диск; позиции `1..N`; каждый присутствующий Total Tracks равен `N`; согласованный исполнитель; содержательные названия дают не более трёх точных основ и хотя бы один распознанный маркер версии. | | `djset(g)` | Положительное, независимое от имени исполнителя подтверждение DJ-сета. Неизвестное значение считается false; слово Live и большая длительность сами по себе недостаточны. | | `candidate(g)` | `short(g) OR versions(g) OR djset(g)`, только после защитных ветвей. | | `owner(g)` | Однозначный содержательный кредит исполнителя, общий для всех позиций: сначала Album Artist, затем Track Artist. Неизвестные/служебные значения не создают владельца. | Все отсутствующие положительные доказательства дают false соответствующего предиката. Неизвестная длительность запрещает именно `short`, а не независимо доказанный `versions`. Противоречие не исправляется большинством голосов. Числа в названиях сохраняют смысл. `djset` — явная граница незавершённой части проекта: в исследовании использовалась проверенная аннотация; универсальный производственный распознаватель ещё не определён. До его реализации нельзя автоматически считать все записи конкретного DJ сетами. FSM детерминирована при заданных значениях этого входа; неподтверждённые случаи идут по остальным правилам. ## Классификатор C `C = (Q, Σ, D, δ, E, O)`, где `Q = {E, G, H, C, R, X, A, P, S, Z}`, `Σ = {evaluate, reset}`, `D` — факты и предикаты выше. `E` — начальное состояние. `O = {X, A, P, S, Z}` — результаты: явный альбом, обычный альбом, персональные Singles, общие Singles и отсутствие членов. Буква `P` внутри `C` обозначает персональную корзину, а не автомат публикации. ![FSM: явное владение, обычная группировка, главы, проверка Singles и выбор корзины](https://roadio.link/assets/singles-fsm-ru.453a322fc421.svg) [Открыть диаграмму отдельно](https://roadio.link/assets/singles-fsm-ru.453a322fc421.svg). Диаграмма обзорная; таблица ниже задаёт все переходы. В каждом состоянии проверяется первая подходящая строка. Обозначение «иначе» — дополнение всех предыдущих условий этого состояния. | Из | Условие события `evaluate` | В | Действие | | ----------- | ------------------------------------------------ | ----- | --------------------------------------------------------------------------------------- | | `E` | Нет удерживаемых позиций | `Z` | Не создавать непустую запись; старые пустые проекции живут по собственному TTL. | | `E` | `explicit(g)` | `X` | Сохранить объявленный порядок FILF; исключить соответствующие базовые MEDIA и их копии. | | `E` | Иначе | `G` | Выполнить существующую глобальную группировку базовых MEDIA, включая AG-9 и его веса. | | `G` | Есть главы | `H` | Разрешить принадлежность базового MEDIA, затем расширение глав. | | `G` | `grouped(g)`, без глав | `C` | Передать разрешённые логические позиции. | | `G` | Иначе | `R` | Передать каждую невостребованную позицию отдельно, не изобретая релиз из общих Singles. | | `H` | `grouped(g)` | `A` | Заменить контейнеры упорядоченными FILF; остальные позиции сохранить. | | `H` | Иначе | `X` | Создать явный fallback-альбом таблицы; не отправлять контейнер или главы в Singles. | | `C` | `protected(g)` | `A` | Сохранить обычный альбом. | | `C` | `candidate(g)` | `R` | Перенести группу целиком; сохранить исходные теги и порядок внутри группы. | | `C` | Иначе | `A` | Сохранить обычный альбом. | | `R` | Единственный `owner(g)` удовлетворяет `eligible` | `P` | Добавить в `: singles`. | | `R` | Иначе | `S` | Добавить в общую `Singles`. | | `X/A/P/S/Z` | Любое `evaluate` без новых входов | То же | Результат вычисления неизменен. | | Любое | `reset` с новым согласованным входом | `E` | Пересчитать принадлежность по новому снимку. | Последняя строка имеет приоритет для события `reset`. Остальные неоговорённые события дают переход в то же состояние без изменения регистров. Сборщик сначала устанавливает явное владение во всём снимке; один и тот же логический контейнер нельзя пропустить через разные ветви благодаря разным физическим копиям. Это автомат решения о членстве, а не идентификатор каталожной записи. ## Выделение основ и персональные корзины Парсер основ снимает только распознанные завершающие обозначения версии: например, `Song (Radio Edit)`, `Song (Artist Remix)`, `Song - Instrumental`. Результат сравнивается точно после уже существующего канонического сравнения. Новая нормализация и Левенштейн не добавляются. Короткая основа вроде `Go` допустима. Произвольные скобки, номера частей, томов и одинаковый концертный суффикс не доказывают общую композицию. Извлечение основы не меняет фактический Title и не объединяет разные альбомы. Словарь допустимых суффиксов должен быть закрытым и проверяемым. Перед маршрутизацией рассчитывается фиксированная таблица `eligible(artist)` для всего снимка: - Есть хотя бы один независимо собранный музыкальный альбом вне Singles с не менее чем семью логическими позициями, либо с не менее чем четырьмя позициями и известной суммарной длительностью не менее 30 минут. Противоречивая структура не служит таким подтверждением. - **Или** есть как минимум три независимо выделенные группы `candidate` этого исполнителя. Три трека одного релиза — одна группа, не три. Невостребованные одиночные позиции не создают фиктивные релизы для достижения порога. Условия альтернативны: студийный или полный альбом не обязателен. Учитываются удерживаемые недоступные группы; корзина сама себя не квалифицирует. Маршрутизация не меняет таблицу `eligible` в процессе обхода. Полные компоненты кредита сравниваются по действующим правилам; запятая или амперсанд не являются разрешением разрезать совместный кредит. Неоднозначный владелец ведёт в `S`. Персональная корзина и общая корзина не содержат копии одних и тех же назначенных позиций. ## Публикация и время жизни Автомат публикации: `Qp = {IDLE, BUILD, VALIDATE, COMMIT}`, начальное состояние `IDLE`. Регистр `accepted` указывает на последний принятый согласованный каталог; `pending` изолирован. События, не допустимые в текущем состоянии, не меняют `accepted`. | Из | Событие / условие | В | Действие | | ----------------------- | ------------------------------ | ---------- | -------------------------------------------------------------------------------- | | `IDLE` | Изменились согласованные входы | `BUILD` | Применить TTL; вычислить `C` для всех групп, затем корзины и связанные каталоги. | | `IDLE` | Входы неизменны | `IDLE` | Нет публикации и расхода TTL. | | `BUILD` | Расчёт завершён | `VALIDATE` | Проверить членство, ссылки, порядок, ID и согласованность полного набора. | | `VALIDATE` | Проверки пройдены | `COMMIT` | Подготовить активацию нового набора. | | `COMMIT` | Атомарная активация успешна | `IDLE` | Заменить `accepted`, зафиксировать новое поколение. | | `BUILD/VALIDATE/COMMIT` | Ошибка или отмена до активации | `IDLE` | Отбросить `pending`, сохранить `accepted`. | TTL измеряется поколениями успешной публикации, не минутами и не количеством запусков сканирования. Изменённые промежуточные публикации также расходуют TTL. Счётчик поколения и удаление фактов фиксируются вместе со снимком; проваленная попытка не старит принятый каталог. Автомат доступности `V = {AVAILABLE, OFFLINE}` на каждом обновлении переходит в `AVAILABLE`, если доступна хотя бы одна удерживаемая позиция, иначе в `OFFLINE`. Он не изменяет выход `C`. Доступность альбома — отдельный признак: есть хотя бы одна доступная позиция либо нет ни одной. Полностью недоступная удерживаемая запись не видна в доступном каталоге Player, но её состав продолжает участвовать в классификации и квалификации исполнителя до истечения удержания. ## Проверяемые сценарии и инварианты | Событие | Ожидаемый результат | | ------------------------------------------------------------- | ----------------------------------------------------------------------------------- | | Впервые найдены две короткие позиции из заявленных двенадцати | Singles, даже при номерах 4/12 и 9/12. | | К этим двум добавились остальные десять разных композиций | Вся группа становится отдельным альбомом, если другие признаки Singles отсутствуют. | | Из известных двенадцати временно доступны только две | Альбом остаётся альбомом до TTL отсутствующих позиций. | | Десять позиций действительно истекли по TTL | Остаток пересчитывается и может перейти в Singles. | | До TTL недоступны все двенадцать | Состав удерживается; доступной карточки нет. | | Один контейнер содержит десять валидных глав | В ALBM представлены FILF; это не однотрековый Singles по числу файлов. | | Явная таблица содержит два фрагмента | Сохраняется явный альбом, правило короткой группы не применяется. | | У исполнителя только три подтверждённых сета | Возможна персональная корзина без студийного альбома. | | Построение нового каталога отменено | Предыдущий согласованный каталог остаётся принятым. | Каждая обычная назначенная позиция получает один выход `A/P/S`; явные таблицы сохраняют объявленные повторы и независимые варианты. Глобальная дедупликация по Media.ID запрещена. Контейнер никогда не публикуется вместе со своими FILF в альбомах или Singles. Изменение доступности само по себе не сокращает удерживаемый состав. ID корзины не превращается в ID выделенного из неё альбома. Нумерация имени файла допускается как вспомогательное доказательство лишь для полной уникальной последовательности `1..N` среди непосредственных аудиофайлов папки, без конфликтов с тегами и без подмены структуры FILF. Формы `01 …` и `0 01 …` распознаются отдельно. Это наблюдаемая нумерация, а не объявленный Total Tracks; исторически удерживаемые отсутствующие члены не исчезают из подсчёта. Действующие владельцы правил: [Cuetree catalogs](https://wiki.271.pics/Cuetree/catalogs), [группировка](https://wiki.271.pics/Riff/LATEST/album-grouping) и [жизненный цикл](https://wiki.271.pics/Riff/LATEST/indexer-library-lifecycle). Персональные корзины, перечисленные пороги и новые предикаты этой статьи — проект изменения. Перед кодом необходимо согласованно изменить их нормативные контракты. Статья не меняет формат данных, приложение или существующие пользовательские каталоги.