Unchained Nostalgia v1.5 [2023/11/05] (демка для NES/Famicom/Dendy)

Демка на основе популярного многоигрового картриджа для Dendy с романтическим сюжетом и Unchained Melody в меню. Для запуска нужен эмулятор NES (например, Nestopia). Архив включает также Unchained Melody в форматах NSF и NSFe. Прослушать её можно, например, в foobar2000 с плагином Game Emu Player. Вариант в формате NSFe более предпочтителен, так как он содержит больше мета-информации и длину трека, чего не может быть в устаревшем формате NSF. Это означает, что проигрывание NSFe корректно заканчивается вовремя, в отличие от NSF.

Скачать: unchained_nostalgia.zip (20KB).

Посмотреть в записи

Что сделано?

  • Создано на основе многоигровки 9999-in-1. Сравнение →
  • Появились осмысленные облака и звёзды, теперь все слайды уникальны и нет дублей только с изменённой палитрой.
  • Добавлено два новых слайда для точного соответствия музыке при автоматическом переключении.
  • Добавлен эффект плавной смены слайдов (медленный при старте, быстрый при просмотре).
  • Автоматическая подстройка скорости воспроизведения и высоты звука на системах NTSC и PAL для работы как на Dendy.
  • Автоматическое переключение слайдов в такт музыке (каждые 4 удара). Для отключения нажмите любую кнопку после запуска. Для повторного включения автоматического режима нажмите Start.
  • При удержании одной из кнопок направления, слайды меняются в такт музыке (каждый удар) в выбранном направлении.
  • Секретный режим управления Вселенной (только не говорите об этом санитарам).
  • Супер-секретный режим гордой птички со спецэффектами от легендарного Майкла Бэя.
  • И целый ряд других секретных фишек =)

Ссылки

  1. #1
    пустой бамбук

    А-а-а! Что Вы наделали? Меня разрывают эмоции. Я хочу обратно в детство...

  2. #2
    Kensei

    Ну неужели я это нашел!!!!!....я все детство хотел полностью увидеть, на моих катриджах был только закат. Рад что кто-то создал такую статью

  3. #3
    VEG Автор

    Я рад, что вам понравилось :)

  4. #4
    AJ_Maker

    Евгений, молодец )) не ожида, что ты до сих пор занимаешься этим хаком ))
    Только недавно репостнул видос первой версии, с сылкой на 1.2, а ты уже 1,3 выпустил ))
    Просто молодец )) так держать )

  5. #5
    VEG Автор

    Перезалил архив. Исправил мелкий косяк с пасхалкой «управления» птичками: во время нажатия кнопок не срабатывала автоматическая смена слайда, хотя она на самом деле оставалась включенной.

  6. #6
    VEG Автор

    Релиз Unchained Nostalgia v1.3.1. Исправлена регрессия из v1.3.0, которая приводила к «плавающему» темпу музыки на точных эмуляторах и реальном железе.

  7. #7
    xapuk

    Идея пришла внезапно.
    Ещё лучше то, что идею есть кому воплотить в жизнь.
    Идея в том , чтобы вместо влюблённой пары сделать Россию и Украину - ну и далее на тему примирения наций. Аж прослезился, когда представил, как здорово это будет.
    Думал просто подрисовать имеющиеся изображения и запаковать обратно в ROM. Никогда не задумывался в каком виде в ROM хранятся изображения - но, как оказалось, всё намного сложнее.Поэтому не справлюсь - однозначно.
    Желаю успеха и миллиарда просмотров на ТыТрубе

  8. #8
    VEG Автор

    Спасибо, конечно, за предложение. Но этот проект уже завершён и его цели уже достигнуты. Впрочем, у вас всегда остаётся возможность самостоятельно сделать желаемое.

  9. #9
    «G~Lí†ch»

    Помнится мне, что при переключении страницы (кнопкой Select или B, или просто перемещая "птичку") затормаживалась музыка. А перед повтором цикла действительно есть задержка! Даже нашёл отдельную NSF'шку, весом в 13 402 байт (то ли zophar.net, то ли http://ftp.modland.com, если не modarchive.org), и при помощи NSFImport'a я закинул в FamiTracker эту NSF'шку с параметрами 144 строк за "такт" (12 нот на Pulse 2 канале в каждом Frame).
    Так вот, если приглядеться, то можно заметить, что на канале Noise попадается разный контур громкости — сначала идут D-B-9-7-5-3-1-0, потом внезапно E-C-A-8-6-4-2-0, или иногда задевает концовку предыдущей ( D-B-9-7-5-3-2-0 или D-B-9-7-5-4-2-0 и т.д.), получается, что в FamiTracker не помещаются все изменения громкости, вписанные в исходном NSF.
    А если промотать ко второму циклу, то повторяемое место уже будет выглядеть немного иначе: все ноты (и их снятие, эффекты) смещены на одну строку (что и создало запаздывание при повторении), в т.ч. по DPCM каналу (точно так же разбросаны "=" (Note Off), непонятно зачем — ни однго звука не прописано же). А эта самая "громкость" по-идее должна быть "с такими же ошибками" и интервалом, но оказывается, E-C-A-8-6-4-2-0 появляется в других местах как будто продолжает глючить дальше! Это может значить, что в NSF на Noise канале записан всего один Pattern, в котором прописан зацикленный звук с изменением громкости (от E до 0), но скорость изменения громкости гораздо быстрее, чем позволяет это сделать FamiTracker (да и DefleMask тоже). Можно конечно в FamiTracker'е просто удалить последний Frame, и экспортировать в NSF, чтобы не было задержки перед повторением, но модуль уже не совпадает с исходным вариантом... А то этот "горе-аранжировщик" походу умудрился воткнуть Pattern Break (или может быть Position Jump) в первую строку после концовки.

  10. #10
    VEG Автор

    «G~Lí†ch»,

    Вы себе неправильно представляете формат NSF. Там внутри находится программа. Она отсылает команды эмулируемому чипу NES APU RP2A03. Тут нет никакого стандартного способа хранения нот. В каждую NSF зашивается код звукового движка из какой-то игры и музыкальные данные из этой же игры, которые понимает именно этот движок. Достоверно импортировать любой NSF в формат FamiTracker нельзя. Можно написать эмулятор, который будет выполнять программу из NSF и запоминать команды, которые она отправляла на NES APU, пытаясь преобразовать их в то что понимает FamiTracker. Но это не одно и то же что банальное «импортирование». В исходных музыкальных данных все ноты и настройки инструментов представлены в очень компактном виде. Чтобы более-менее достоверно это «импортировать» таким образом, нужно запоминать отправляемые в NES APU команды 60 раз в секунду, и наверняка множество неявных трудностей тут тоже есть. Например, в исходных музыкальных данных может быть какой-то один инструмент, который отсылает сразу несколько разных команд в звуковой чип, и движок из соответствующего NSF это понимает. Но при подобном «импортировании» вы получите совсем иную картину, снаружи это будет выглядеть как несколько разных инструментов, играющих одновременно.

    Ещё один момент. Музыка этой менюшки писалась фамиклонов типа Dendy. В Dendy частота обновления была 50Гц, как в NES PAL, но при этом железо было заточено для совместимости с NES NTSC, поскольку для этой версии системы игр было больше, но у NTSC частота обновления 60Гц. Таким образом, Dendy был некоторым гибридом NES NTSC и NES PAL. От NES PAL была частота обновления экрана (и в NES все процессы на это завязаны), от NES NTSC было почти всё остальное, включая высоту тона одних и тех же команд звуковому процессору. Таким образом, если NSF запускается в режиме NTSC на 60Гц, чтобы музыка звучала с нормальной скоростью, программа пропускает каждый шестой кадр, таким образом получается высота звука и скорость воспроизведения как у Dendy. Если же NSF запускается в режиме PAL, то используются другие таблицы преобразования нот в команды звуковому процессору, которые дают похожий по высоте звук на NTSC. Эти адаптации я добавлял в музыкальный движок демки. Оно сразу определяет на чём оно работает, и потом применяет соответствующую адаптацию (или ничего не адаптирует, если определяет что оно работает на настоящей Dendy или в эмуляторе с поддержкой Dendy). NSF-плееры на PC, как правило, запускают движок в режиме NTSC, так что движок пропускает каждый шестой кадр.

    Помнится мне, что при переключении страницы (кнопкой Select или B, или просто перемещая "птичку") затормаживалась музыка.

    Это я исправил в этой демке. Точнее, затормаживание при переключении слайдов всё равно есть, но его нереально услышать, теперь музыка «задерживается» лишь на 1/60 секунды при смене кадра. Это тоже можно было обойти, но пришлось бы сильно усложнять код загрузки новой картинки в видеопамять без реальной на то необходимости.

    Даже нашёл отдельную NSF'шку, весом в 13 402 байт

    В архиве с этой демкой есть и отдельный NSF, гораздо более компактный (всего 2240 байт), но с той же (идентичной) музыкой. Я там оставил только код, который относится к музыкальному движку и сами музыкальные данные, плюс к самим музыкальным данным я применил поддерживаемую движком возможность отсылки на дублирующиеся фрагменты мелодии без их повтора. Можно было сделать ещё меньше немного, если лишить движок возможности проигрывания звуковых эффектов по событиям (в NSF оно не используется). Но я решил оставить полную версию движка, как и в демке, тем более что в демке звуковые эффекты используются в пасхалках :)

  11. #11
    «G~Lí†ch»

    Сравнил эти две NSF'шки, и нашёл в них два сходства, только по разным адресам: Первое сходство: [5C6]h—[6F7]h; второе: [816]h—[8B0]h, кроме байта [8A5]h (03h вместо 06h). Ещё есть немного похожее место с [194]h по [1E6]h, только в 13402-байтной версии (сразу же после 11 тысяч нулей) некоторые байты забиты нулями в таком же промежутке... Дальше ещё больше различий... Видимо, из-за "прописанной адаптации".
    Получается, что ноты там те же (так же, как и лишние «Note Off» на DMC). Но чего только в вашей "версии" темп такой медленный (да-да, знаю, что сама песня ещё медленнее)? Темп почти такой же, как в переключенной в PAL 9999999-in-1.nes! Вот в FCE Ultra 13-килобайтная NSF-шка вообще не реагирует на переключение PAL/NTSC, в отличие от Nestopia. А если вашу редакцию NSF'шки переключить из PAL в NTSC, то темп станет более привычным, но тоном чуть выше. А если открыть как NTSC (в обоих эмуляторах), то можно ЕЩЁ замедлить и понизить, переключив в PAL :D
    Даже NSFImport'ёр из вашей NSF'шки гораздо кривее "извлёк" мелодию, чем из 13-килобайтной с 11 тысячью нулями.

    может быть какой-то один инструмент, который отсылает сразу несколько разных команд в звуковой чип

    Значит, те Note Off, которые я видел на DPCM канале в FamiTracker'е, на самом деле занимают всего один бит в тех байтах, в которых прописаны «звучащие» "ноты" (т.е. размер файла не изменится, если убрать эти комманды)?
    В NES Audio Processor Unit'е должно же быть что-то вроде "указателя зацикливания" (ведь не все же мелодии воспроизводятся сначала), и в этой мелодии надо его как-то сместить (тогда уже придётся либо на слух, либо записывать в аудио, и проверять совпадение атаки прямо по фазе)... Если все остальные NSF-мелодии зацикливаются ровно, почему Unchained Melody в NSF варианте такая ошибка? Вряд ли тут из-за длины трека, поэтому и "обвинил" аранжировщика.
    Вообще, если существует дамп той версии, у которой абсолютно другая мелодия, то надо будет и её всунуть в демку, прописав затухание по вызову (такое в NES APU тоже должно быть, т.к. в Маугли (Jungle Book) такой эффект есть) "секретной комбинации смены трека" =)... Хотя, лучше после трёх циклов просто модуляцию сделать, чтоб не надоедала.

  12. #12
    VEG Автор

    «G~Lí†ch»,

    Получается, что ноты там те же (так же, как и лишние «Note Off» на DMC). Но чего только в вашей "версии" темп такой медленный (да-да, знаю, что сама песня ещё медленнее)?

    С чего вы взяли что он медленный? Я же объяснил, что моя версия играет именно так, как эта мелодия играла на Dendy, на которую и было это рассчитано. То что у вас другой файл играет слишком быстро — это только потому, что он запущен в режиме NTSC, на который он совершенно не рассчитан. Вместо 50 тиков в секунду (как было в Dendy и NES PAL) делается 60 (как было в NES NTSC), и поэтому всё играет сильно быстрее чем должно быть. В моей версии когда мелодия запущена в режиме NTSC, каждый 6-й тик пропускается, поэтому мелодия играет с правильной скоростью, как было задумано авторами.

    Вот в FCE Ultra 13-килобайтная NSF-шка вообще не реагирует на переключение PAL/NTSC, в отличие от Nestopia. А если вашу редакцию NSF'шки переключить из PAL в NTSC, то темп станет более привычным, но тоном чуть выше. А если открыть как NTSC (в обоих эмуляторах), то можно ЕЩЁ замедлить и понизить, переключив в PAL :D

    Не стоит такой ерундой заниматься. Определение что за железо используется идёт при старте, и дальше всё работает так, будто всё время работает именно на этом железе. В случае с NSF тип железа (NTSC или PAL) передаётся отдельным параметром самим эмулятором при старте. То есть возможность дополнительной адаптации при переключении типа на лету в принципе не предусмотрено (и по-хорошему эмулятор обязан делать reset сразу после изменения типа железа, чтобы избежать непонятных эффектов).

    Даже NSFImport'ёр из вашей NSF'шки гораздо кривее "извлёк" мелодию, чем из 13-килобайтной с 11 тысячью нулями.

    Вероятно, пропуск каждого 6 кадра сбивает эту утилиту с толку.

    Значит, те Note Off, которые я видел на DPCM канале в FamiTracker'е, на самом деле занимают всего один бит в тех байтах, в которых прописаны «звучащие» "ноты" (т.е. размер файла не изменится, если убрать эти комманды)?

    Вполне возможно, что это движок синтезатора отсылает эту команду, и в нотах этого в принципе нет.

    В NES Audio Processor Unit'е должно же быть что-то вроде "указателя зацикливания" (ведь не все же мелодии воспроизводятся сначала)

    Нет там ничего такого. Есть программа, которая отсылает команды чипу, и эти команды исполняются немедленно. APU ничего другого сам делать не умеет. Нельзя ему скормить команды и длительности пауз между ними, чтобы он их воспроизводил самостоятельно. В любом случае CPU должен сам отмерять время между командами, и в нужный момент их отсылать в APU. То есть за зацикливание и тому подобное в любом случае отвечает звуковой движок, исполняемый на CPU.

    Если все остальные NSF-мелодии зацикливаются ровно, почему Unchained Melody в NSF варианте такая ошибка? Вряд ли тут из-за длины трека, поэтому и "обвинил" аранжировщика.

    Не понимаю о какой именно ошибке речь.

  13. #13
    «G~Lí†ch»

    Ну, посмотрел "исходный код" этой мелодии, удобно расписано для каждого канала:
    sound_notes_0: Pulse 1
    sound_notes_1: Pulse 2
    sound_notes_2: Triangle
    sound_notes_3: Noise.
    С лишней «Note Off» я разборался: во всём виноваты "паузы" в sound_notes_0 и в sound_notes_2, с байтами $81 и $83 — как я понял, после $81 здесь указывается байт с длительностью этой паузы, затем байт $83 — конец паузы (?) ...Да, в данном случае действительно размер файла не изменится, но размер можно уменьшить в других местах, не изменив звучание мелодии.
    Для "треугольника" можно сократить дублирующие ноты (NSFImport'ёр вместо двух нот всё равно одну распознаёт, да и эмулятор играет их "непрерывно"):

    sound_notes_2:
    	.BYTE $80, 2
    	.BYTE $18, $90, $15, $90, $11, $90, $13, $90, $18, $90, $15, $90, $10, $90, $13, $24
    	.BYTE $1F, $24, $23, $24, $26, $24, $1C, $48, $1F, $24, $24, $48, $1C, $24, $21, $24
    	.BYTE $24, $48, $1D, $24, $26, $24, $24, $24, $23, $24, $1F, $24, $28, $24, $26, $24
    	.BYTE $81, $24, $83, $1C, $24, $1F, $24, $24, $48, $1C, $24, $21, $24, $24, $24, $1F
    	.BYTE $90, $26, $90, $24, $90, $23, $24, $1F, $24, $23, $24, $26, $24, $18, $90, $1C
    	.BYTE $90, $1D, $90, $1F, $90, $18, $90, $81, $90, $83
    	.BYTE $84, 1, @endbass
    	.BYTE $18, $48, $16, $48
    	.BYTE $84, 1, @endbass
    	.BYTE $18, $90
    	.BYTE $86
    
    @endbass:
    	.BYTE $11, $48, $13, $48, $15, $48, $16, $48, $11, $48, $13, $48, $85
    

    Правильно ли я понял, что ещё $85 должно стоять в данном случае?
    И мелодия в sound_notes_0 в этом месте так же повторяется. Правильно ли я "укомпановал" ?

    	.BYTE $81, $06
    	.BYTE $84, 1, @ending_volta
    	.BYTE $26, $0C, $24, $0C, $28, $0C, $2B, $24, $2D, $0C
    	.BYTE $28, $0C, $24, $06, $26, $06, $24, $30, $81, $0C
    	.BYTE $84, 1, @ending_volta
    	.BYTE $28, $0C, $26, $0C, $24, $90
    	.BYTE $86
    
    @ending_volta:
    	.BYTE $83, $1D, $0C, $1F, $24, $21, $0C, $24, $0C, $23, $30, $24, $0C, $23, $0C
    	.BYTE $21, $30, $1D, $0C, $21, $0C, $1F, $48
    	.BYTE $1D, $0C, $1F, $24, $21, $0C, $24, $0C, $26, $30, $85
  14. #14
    VEG Автор

    «G~Lí†ch», из моих заметок:

    $80 - начало канала, выбор инструмента след байтом?
    $81 - начало блока задержек (по одному байту)
    $82 - следующим байтом задержка
    $83 - конец блока задержек
    $84 - проиграть заданный кусок заданное количество раз
    $85 - конец отдельного куска, вернуться назад где было $84
    $86 - мотай в начало!
    ноты идут по два байта, нота в первом байте, задержка во втором

    Скорее всего разбивать блоки $81 .. $83 нельзя. Вероятно, блоки $81 .. $83 с одним значением между можно заменять одним $82 с параметром. Надо смотреть код движка чтобы понять есть ли там различия. Там ещё иногда блоки $81 .. $83 идут подряд. Непонятно почему их не пообъединяли. Возможно, просто редактор, в котором писалась музыка, сохранял звуковые данные не в самом оптимальном виде.

    Объединять одинаковые команды, которые издают звуки, я не стал, так как моей задачей было добиться идентичного звучания во всех возможных случаях. То есть на оригинальном железе Dendy оно должно звучать так же, как и прежде. Возможно, идущие подряд одинаковые команды со своими задержками давали немного другой звук. Мои адаптации для NES NTSC и NES PAL касаются только того, чтобы сделать звучание как можно ближе к Dendy.

    Примеченные вами повторы фрагментов нот не заметил. Если когда-нибудь вернусь к этому проекту, то приму ваше замечание во внимание и сокращу запись и в этих местах. Вообще я для этого проекта хотел сделать новый алгоритм сжатия данных фоновых картинок. Чисто из спортивного интереса :) Было бы весьма любопытно сделать что-то типа LZ (словарного) сжатия для такого слабого процессора с такими ограничениями по памяти, чтобы оно ещё моментально работало, не медленнее того что есть. Интересный challenge.

    А вы это как-то логично разбили sound_notes_0 и sound_notes_2, типа блоками как оно звучит? Если да, не могли бы вы предоставить полностью разбитые по строкам варианты этих данных — включу в исходник в таком виде, чтобы понятнее выглядело.

    UPD: Проверил в коде. $81 и $83 ещё что-то дополнительное отсылают в APU, в сравнении с просто с $82. Так что заменять на $82 не стоит. $82 вообще не используется конкретно в этой мелодии — в мелодиях из других игр от NTDEC можно глянуть как оно используется :)

    UPD2: В sound_notes_2 ещё два повтора заметил небольших :) Думаю, обновлю всё же файл, раз уж залез.

  15. #15
    «G~Lí†ch»

    типа блоками как оно звучит?

    Я пока не въёхал в вопрос...
    Я сначала думал, что $80 это № паттерна, но потом подумал, что само "название sound_notes_№" и есть паттерн, а на каких каналах этот "блок" играет, должно указываться заранее. Но, скорее всего, я ошибаюсь.
    Если больше пяти нот повторяется, то имеет смысл делать, т.к. сам "повтор" 4 байта занимает, а ещё байт $85 в конце повторяющегося фрагмента вставлять. Ведь недаром в sound_notes_1 есть только два фрагмента, которые ни разу не повторяются, поэтому он не выносил их в отдельные "@part'ы"
    Можно уточнить гармонию, которые воспроизводят эти "парты":
    @part1 — @c (до мажорное трезвучие)
    @part2 — @am (ля минорное трезвучие)
    @part3 — @f (фа мажорное трезвучие)
    @part4 — @g (соль мажорное трезвучие)
    @part5 — @em (ми минорное трезвучие)
    @part6 — @c7 (до мажорный спетаккорд)
    @part7 — @g7 (соль мажорный септаккорд)
    @part8 — @eb (ми-бемоль мажорное трезвучие)
    Кстати, если уж и перерелизить, то с исправленным окончанием в NSF: Перед $86 сократить значение на единицу:
    в sound_notes_0 последняя нота будет: $24, $8F
    в sound_notes_1 предпоследняя строка:

    	.BYTE $18, $C, $1C, $C, $1F, $C, $24, $C, $1F, $C, $1C, $B

    в sound_notes_2: .BYTE $18, $8F (или $18 $48 $18 $47)
    после

    @beat:
    	.BYTE $34, $C, $2F, $C, $2F, $C, $2F, $C, $2F, $C, $2F, $C
    	.BYTE $84, 62, @beat
    	.BYTE $34, $C, $2F, $C, $2F, $C, $2F, $C, $2F, $C, $2F, $B
    	.BYTE $86

    Поправил эти байты HEX-editor'ом в 13-килобайтной NSF'шки (там повторы не указаны, просто подряд, как играется, так и написано), потом завёл на эмуляторе — задержки больше нет!
    Однако в этом случае нужно проверить на железном Dendy. Если же там наоборот "ускорится", то придётся возвращать всё обратно, и разбираться как раз именно из-за $86 (а может и $80, ведь он тоже повторяется?). Возможно в нём какой-то код создаёт эту задержку на эмуляторах.

  16. #16
    VEG Автор

    «G~Lí†ch»,

    Я пока не въёхал в вопрос...

    Ну условно то как играет музыка можно разбить на сегменты, отмеренные четырьмя повторами «ударников» (по которым синхронизировано автоматическое переключение слайдов). Данные других каналов можно разбить по строкам соответственно, типа вот это первый сегмент, это второй, это третий и т.д. Я имел в виду это.

    Ведь недаром в sound_notes_1 есть только два фрагмента, которые ни разу не повторяются, поэтому он не выносил их в отдельные "@part'ы"

    Этот некто «он» — это я :) Увидел что движок умеет выносить наружу повторяющиеся части — задействовал эту фишку.

    Поправил эти байты HEX-editor'ом в 13-килобайтной NSF'шки (там повторы не указаны, просто подряд, как играется, так и написано), потом завёл на эмуляторе — задержки больше нет!

    О какой задержке речь? Я ради интереса прокрутил демку десяток раз — не слышу никаких задержек. Не могли бы вы конкретнее описать что именно вы хотите исправить? Или вы хотели просто уменьшить задержку между повторами на один такт? Судя по коду — задержку в один такт создаёт $86. Он очищает все регистры APU и чтение следующей ноты начинается только на следующем такте. $80 же принимается за воспроизведение следующей ноты сразу же. По идее можно обучить $86 начинать проигрывать повтор на этом же такте, но не уверен, действительно ли это нужно :)

    Этот же код умеет воспроизводить и звуковые эффекты, которые по событиям запускаются. Типа звука по Select+Start. Так вот в этом случае $86 значит «конец воспроизведения», то есть зацикливания нет. Переделать так чтобы оно не делало задержку в один такт для музыки здесь кажется просто (сразу вижу 3 места где нужно внести небольшие правки), но это не будет учитывать, что для эффектов мы не должны такого делать, и надо немного глубже вникнуть в код, чтобы добавить проверку, не эффект ли это, и если эффект, то как и прежде завершать работу на этом.

    К слову, из NSF можно ещё вырезать куски кода, которые отвечают за воспроизведение этих эффектов, поскольку в NSF этот код никак не задействован. Но я решил что лучше пускай там будет полная версия движка :) Тем более, что там может каких 100-200 байт на этом можно сэкономить разве что.

    Глянул код. У $86 логика несколько сложнее. Он останавливает звук на канале, но движок ждёт когда на всех каналах будет $86, и только тогда перезапускает воспроизведение. И на этом теряется один такт. В принципе, тоже можно было бы переделать, чтобы воспроизведение начиналось с того же такта, когда везде уже $86, но это уже будет несколько сложнее, чем казалось сразу. Похоже, что игра не стоит свеч, тем более что речь о задержке всего в 1 такт (тут и выше под «тактом» я понимаю «кадр», то есть 1/50 или 1/60 в зависимости от приставки).

  17. #17
    «G~Lí†ch»

    когда на всех каналах будет $86, и только тогда перезапускает воспроизведение.

    Хм. Надо посмотреть, что будет, если вместо шестидесяти трёх повторений @beat'а (сразу после первых 6 нот в sound_notes_3) вставить $86. Я думал, что эти ноты должны воспроизводиться сначала, не дожидаясь остальных каналов. Но раз вы говорите, что в движке прописано, что они должны ожидать, то значит дело в другом.

    не слышу никаких задержек

    У меня просто уже натренирован слух, что и без программы могу услышать эту задержку. Но вот привожу метод, при помощи которого я доказываю, что там задержка есть:
    1) открываете аудишн или любой другой многодорожечный аудиоредактор, закидываете аудио "дамп" вашей nsf'шки (не просто полностью, А С ПОВТОРЕНИЕМ, т.е. не NSFe версию, записать нужно примерно две минуты),
    2) затем закидываете этот же звуковой дамп на вторую дорожку (чтобы можно было их двигать независимо).
    3) Затем сместите один из двух клипов РОВНО на 8 тактов мелодии (т.е. пропустить 16 повторяющихся "@beat'ов" белого шума).
    4) Затем приблизьте (Zoom in) и сверьте по ФАЗЕ (чтобы совпадали удары в клипе верхней дорожки и нижней).
    В моём случае клип поместился на 0:23,002 секунде. (файл был сконвертирован аимповским конвертером в mp3 (mono/160/CBR)),
    5) Потом промотайте до места, где в одном из двух клипов мелодия уже играет ПОВТОР, затем, приблизьте те "первые 4 такта". (в моём случае расхождения появляются в промежутке от 1:32 до 1:55).
    Позже, второй клип "Догонит" (после 1:55, т.е. через столько же тактов, сколько пропустили), и фаза опять сойдётся.
    Однако, можно разглядеть расхождения по фазе и в других местах (с той же разницой в 16 @beat-циклов), но нужно ещё больше "zoom'ировать". Так в тех местах фаза "смещённого на 8 тактов" клипа может не только отставать, но и временами опережать. Но в промежутке от 1:32 до 1:55 фаза второго клипа будет всегда отставать. Да и вообще при ещё большим зумом можно увидеть расхождения даже в "моём исправленном варианте" (на единицу).

    Ну условно то как играет музыка можно разбить на сегменты, отмеренные четырьмя повторами «ударников»

    Я думаю, что неудобно разбивать мелодию на "сегменты", т.к. в одних двух тактах может быть больше нот, чем в других четырёх, поэтому сегменты в любом случае получатся разбиты на не равные части. Если нужна конкретика, то можно разбить КАЖДУЮ ноту с определённой длительностью, но получится очень длинный "вертикальный" список.
    Вот вы сами сделали парты для sound_notes_1. Там "арпеджио" из 6 нот, составляющие определённую гармонию, которую я расписал.
    Как я понял, здесь нет таких музыкальных понятий как размер (метр), такт. Здесь тупо указана нота, сразу после неё — длительность. Вот как раз о длительностях: если принять размер (метр) мелодии за 12/8, то $C — восьмая длительность, $24 — четверть с точкой, $30 — половинная, $48 — половинная с точкой; $60 — целая;
    если за "привычный 4/4", то $C — восьмая триоль, $24 — четвертная, $48 — половинная, $90 — целая.
    Вообщем, "такой расклад":

    .BYTE $24 $60 ; До второй октавы, целая (при размере 12/8) или До второй октавы, половинная триоль (при размере 4/4)
    .BYTE $81 $0C $83 ; восьмая пауза (при размере 12/8) или восьмая триольная пауза (при размере 4/4)
    .BYTE $28 $30 ; Ми второй октавы, четверть с точкой, залигованная с восьмой на следующем такте (при размере 12/8) или Ми второй октавы, четверть, залигованная с восьмой на следующем такте
    .BYTE $26 $0С ; Ре второй октавы, восьмая (при размере 12/8) или Ре второй октавы, триольная восьмая (при размере 4/4)
    ...

    ну и так далее
    если так продолжать, то я скажу, что даже считывать по шрифту Брайля и то быстрее, чем читать эти комментарии)
    А можно ли помещать в одну .BYTE строку больше 16 байт? Если да, то ради бога:

    sound_notes_0:
    	.BYTE $80, 0
    	.BYTE $24, $60, $81, $C, $83, $28, $30, $26, $C, $24, $C, $26, $C, $24, $48, $81, $C, $83, $26, $C
    	.BYTE $28, $C, $24, $C, $24, $60, $81, $C, $83, $24, $C, $1F, $30, $23, $18, $26, $30, $28, $18
    	.BYTE $24, $18, $24, $60, $81, $C, $83, $26, $C, $24, $60, $81, $C, $83, $1C, $18, $1D, $C
    	.BYTE $1F, $90, $81, $90, $83
    	.BYTE $24, $60, $81, $18, $83, $28, $24, $81, $6, $83, $26, $6, $24, $6, $26, $6, $24, $60, $26
    	.BYTE $18, $28, $6, $24, $6, $24, $6, $26, $6, $21, $60, $24, $C, $1F, $30, $23, $18, $26, $48
    	.BYTE $28, $C, $24, $C, $26, $C, $24, $48, $28, $12, $26, $6, $24, $6, $26, $6, $24, $60, $81, $18, $83, $24, $18
    	.BYTE $2B, $90, $2F, $48, $2D, $6, $2F, $6, $2D, $6, $2B, $6, $2D, $6, $2B, $6, $29, $24
    	.BYTE $28, $30, $24, $18, $1F, $90, $81, $48, $83
    	.BYTE $24, $30, $81, $6, $83, $26, $6, $24, $6, $26, $6, $24, $48, $23, $12, $1F, $12, $1C, $60
    	.BYTE $23, $48, $21, $C, $1D, $C, $1A, $30, $81, $C, $83, $26, $48, $28, $0C, $26, $C, $28, $C, $2B, $C, $2D, $12, $28, $6
    	.BYTE $26, $6, $24, $6, $26, $6, $24, $90, $81, $60, $83, $81, $18, $83, $81, $6
    	.BYTE $84, 1, @ending_volta
    	.BYTE $26, $C, $24, $C, $28, $C, $2B, $24, $2D, $C, $28, $C, $24, $6, $26, $6, $24, $30, $81, $C
    	.BYTE $84, 1, @ending_volta
    	.BYTE $28, $C, $26, $C, $24, $8F
    	.BYTE $86
    
    @ending_volta:
    	.BYTE $83, $1D, $C, $1F, $24, $21, $C, $24, $C, $23, $30, $24, $C, $23, $C
    	.BYTE $21, $30, $1D, $C, $21, $C, $1F, $48
    	.BYTE $1D, $C, $1F, $24, $21, $C, $24, $C, $26, $30, $85
    

    7-я строка (начинающаяся с $18) — нельзя разбить байт, указывающий длительность на два байта, т.к. нота звучит во время смены картинки, можно либо сместить на эту строку ноту ($26), либо перенести $18 наверх. Тут я не знаю, как вам удобно.
    Теперь бас:

    sound_notes_2:
    	.BYTE $80, 2
    	.BYTE $18, $90, $15, $90
    	.BYTE $11, $90, $13, $90
    	.BYTE $18, $90, $15, $90
    	.BYTE $10, $90, $13, $24, $1F, $24, $23, $24, $26, $24
    	.BYTE $1C, $48, $1F, $24, $24, $48, $1C, $24, $21, $24, $24, $48
    	.BYTE $1D, $24, $26, $24, $24, $24, $23, $24, $1F, $24, $28, $24, $26, $24
    	.BYTE $81, $24, $83, $1C, $24, $1F, $24, $24, $48, $1C, $24, $21, $24, $24, $24
    	.BYTE $1F, $90, $26, $90, 
    	.BYTE $24, $90, $23, $24, $1F, $24, $23, $24, $26, $24
    	.BYTE $18, $90, $1C, $90
    	.BYTE $1D, $90, $1F, $90, 
    	.BYTE $18, $90, $81, $90, $83
    	.BYTE $84, 1, @endbass
    	.BYTE $18, $48, $16, $48, 
    	.BYTE $84, 1, @endbass
    	.BYTE $18, $8F
    	.BYTE $86
    
    @endbass:
    	.BYTE $11, $48, $13, $48, $15, $48, $16, $48, $11, $48, $13, $48, $85
    

    Вот он более красивым получился =)
    Правда, заметьте, здесь я уже "урезал" единицу. (это чтоб вы услышали, что такое "непрерывность")
    Осталось вам урезать единицу в sound_notes_1 и в sound_notes_3 (т.е. ещё одну строку, с шестью нотами, каждые с длительностью $C, кроме ПОСЛЕДНЕЙ — $B)

  18. #18
    VEG Автор

    «G~Lí†ch»,

    Хм. Надо посмотреть, что будет, если вместо шестидесяти трёх повторений @beat'а (сразу после первых 6 нот в sound_notes_3) вставить $86. Я думал, что эти ноты должны воспроизводиться сначала, не дожидаясь остальных каналов. Но раз вы говорите, что в движке прописано, что они должны ожидать, то значит дело в другом.

    Движок точно ожидает когда каждый канал достигнет $86. Я это проверял. Пробовал как урезать количество повторов бита (и тогда после указанного числа битов на этом канале была тишина до того как остальные каналы доиграют), так и добавлять (и тогда в самом конце играет просто этот бит, в одиночку, пока не доиграет добавленное число раз).

    Но в промежутке от 1:32 до 1:55 фаза второго клипа будет всегда отставать. Да и вообще при ещё большим зумом можно увидеть расхождения даже в "моём исправленном варианте" (на единицу).

    Не думаю, что разница будет действительно заметна на слух, ведь речь идёт о всего одном кадре (1/60 секунды для NTSC). В режиме NTSC после каждого пятого кадра один пропускается для поддержания правильной скорости. То есть каждую секунду оно автоматом вставляет 10 таких же задержек. Другого способа отмерять по 50 тактов в секунду, которые нужны этой мелодии, на NES NTSC попросту нет. На Dendy и NES PAL частота обновления экрана 50 герц, соответственно и эта логика автоматической вставки задержек не используется. Вообще эту методику с пропуском каждого шестого кадра не я придумал. Я её подглядел у разработчиков игр, когда те делают чтобы игра работала с одной скоростью на NTSC и PAL. Альтернативный вариант — пересчитывать все задержки с учётом на то что кадров в секунде у нас не 50, а 60. Но это точно сделать тоже не получится, будет погрешность, и автоматически задержки не пересчитаешь, нужно вручную заниматься адаптацией.

    У меня просто уже натренирован слух, что и без программы могу услышать эту задержку.

    Может быть, вы слышите эту задержку потому что программа в этот момент обнуляет все регистры и APU на один момент останавливает воспроизведение любых звуков? Просто иначе вы бы и пропуск каждого шестого кадра слышали бы. Надо провести слепой тест =)

    Плохо только что в зависимости от системы (NES NTSC/NES PAL/Dendy) немного меняется и звучание APU, поскольку оно привязано к частоте процессора, и она отличается у разных вариантов системы. То есть даже если вы не будете в реальности слышать эту задержку в один кадр 10 раз в секунду, вы будете слышать разницу именно в высоте звука самого APU. Я попытался это сгладить немного (для PAL вычислил другую таблицу периодов нот, учитывающую другую частоту, на котором работает процессор NES PAL), но некоторая разница всё равно есть, особенно на канале с ударником, для которой такая адаптация оказалась недоступной.

    Подробнее про APU, если интересно:
    https://wiki.nesdev.com/w/index.php/APU
    https://wiki.nesdev.com/w/index.php/APU_basics

    Также стоит отметить, что разные эмуляторы будут по-разному эмулировать APU. Какой из них самый точный в этом плане по отношению к реальному железу — не изучал. Могу точно сказать, что FCEUX — не особо точный эмулятор. У меня в демке из-за него в предыдущей версии проскочил один отвратительный баг, который проявлялся на реальном железе и в более точных эмуляторах (Nestopia и Nintendulator), но не на FCEUX.

  19. #19
    «G~Lí†ch»

    Блин! Простое "удвоение" 13-килобайтной мелодии HEX-редактором не прокатывает... (копировал с третьего байта после $80 и до последнего перед $86 для каждого "канала"... Поэтому не могу прямо так сказать, что будет, если вычесть одну запись из другой.
    Запишите в WAV "оригинал" (обязательно с повтором мелодии), затем удвойте на каждом канале содержимое мелодии (со второй .BYTE строки (не $80 .. ) sound_notes_ и до предпоследней (т.е. не включая $86). Запишите теперь "этот вариант" в WAV.
    Сравните оба варианта (вычесть один из другого "противофазой"). Если всё сделано правильно, то первый цикл мелодии (т.е. сначала) будет тишина. Что будет на втором цикле?
    Может тут дело в GEP'е, или basszxtune.dll, которые неправильно воспроизводят NSF'шку?

  20. #20
    VEG Автор

    «G~Lí†ch»,

    Что будет на втором цикле?

    Должен будет быть сдвиг размером в один кадр (1/60 секунды на NTSC), плюс между повторами в случае когда он вызван кодом $86, в APU будет отправлен сигнал сброса, то есть звук полностью на всех каналах остановится на мгновение. Вообще как мелодия звучит с повтором лучше слушать в самой демке. NSFe-версия почему-то не очень хорошо циклится, если делать это средствами foobar2000 + GEP. Возможно, там в самом начале ещё движок добавляет какую-то задержку при первой инициализации. Нужно код изучать.

  21. #21
    VEG Автор

    «G~Lí†ch», посмотрите, я там переделал звуковой движок, теперь при повторе мелодии не должен вставляться лишний такт. Также применил ваши предложения по оформлению звуковых данных. Ещё сделал код $83 необязательным — звук автоматом включается обратно при следующей после $81 ноте. Просьба послушать, не сломал ли я случайно чего. Проверять зацикленность нужно в демке, а не в NSFe-файле, так как в последнем плеером будет вставлен как минимум один лишний такт в самом начале. По идее это можно обойти, начиная проигрывать первую ноту прямо после вызова функции инициализации, но не уверен что это будет хорошо работать во всех плеерах.

    Файл демки можно взять в репозитории с исходным кодом (в подкаталоге release).

  22. #22
    «G~Lí†ch»

    О... что-то в декабре заходил, этого коммента почему-то не заметил (или может увидел, но забыл даже скачать перед ответом), не ожидал, что Вы решились переделать сам движок... Уж прошу прощения, что затянул с ответом...
    Залез шестнадцатеричным редактором в обновлённую nsf-шку, нашёл сокращения... Даже как-то ровнее стало выглядеть (не было такой ассиметрии в шестнадцатеричном редакторе как с $83). Забыл написать (или даже не подумал), а если объединить несколько пауз (т.е. вместо $81, $60, $81, $18, $81, $6 поставить $81, $7E)?
    Сначала думал сделать запись с приставки, и сравнить с записью с эмулятора... Возможно нужно калибровать "часы", но, пока не нашёл эмулятора, где можно было бы задать частоту в пределах мГц (или микро), затем наложить одну запись на другую в противофазе. Но, после того, о чём речь пойдёт ниже, планы разрушились так и не начавшись воплощаться (да ещё приставка так и валяется не восстановленной, выписал только то, что было написано на взорвавшемся кондёре). Давно хотелось вставить тот картридж да записать наконец звук прямо с неё, чтобы наконец убедиться, какой был на самом деле темп, да и посчитать эту задержку там (если там она вообще была), но вот оказалось, что даже если записать несколько раз одну и ту же мелодию с одного и того же эмулятора (не только через Стерео микшер/виртуальный аудио кабель, а даже самим эмулятором!), то сделанные записи даже между собой толком не сходятся! `:Щ В одном и том же месте на одной записи доля начинается раньше положенного времени, на другой – позже, прям как humanizer, только не одного инструмента а сразу всех (не хватало мне ещё тут расхождения "ансамбля", наелся уже на работе, ровнять каждый голос)... Но зато хоть дальше есть совпадения. А вот попробовал повторить то же, что и раньше (сдвинув на 8 тактов), то уже при возврате на начало появляется что-то типа "застрявшего Flanger'а".. Опять же, это может зависеть от того, как работал генератор (а может сколько FPS?) во время записи, и от того, под какой долей такта сравнивать фазы обоих клипов, т.е. если мы переместим клип так, что первая доля второго такта будет начинаться точно так, как первая доля десятого такта, то соседние доли могут не совпадать по фазе – где-то раньше, где-то позже, а дальше опять всё ровно, но на других долях... Если второй клип чаще отстаёт/опережает, значит надо выбрать другую долю для "калибровки фаз". Уже в этом случае "застрявший Flagner" не будет слышен... Такую неровность можно наблюдать на записях с абсолютно любых игр (nes) или nsf! Я уже подумал, нужен Word Clock... только "виртуальный"! Но что-то подсказывает, он тут мало бы чем помог...
    Короче, из-за такого "эффекта очеловечивания не совсем ровной игры" трудно определить точнее, но в целом-то, вроде звучит как надо, нет таких застреваний на повторе.
    Всё что касается тембра (изменение частоты, амплитуды (атака/спад/протягивание/затухание), точность импульса) – уже вопрос посложнее, а с шумовым и подавно... Ведь если скорость игры другая, то и амплитуда меняется с другой скоростью, т.е. атака уже "более размазаная", чем была при NTSC... Так что, пока не представляю, что можно ещё было сломать. NSF-шка спокойно воспроизводится в AIMP'е (basszxtune). NSFe не проверял (уже не надо).
    NSFImport всё равно лажает, ну это уже не имеет значения.
    Настроил nestoпию (версия 1.35) на 50 гц, и попробовал поиграть в многоигровки с этой заставкой... брр.. жуть.. непривычно медленно, особенно музыка (хотя, для Galaxian (ага, та самая гудёжь) и Battle City ещё норм, но что касается Lunar Ball, Super Mario и прочих стрелялок по уткам/тарелкам/бандюкам – точно что-то не то), трудно поверить, что на железной приставке лишь только эта мелодия могла играть медленно. Единственное, что я точно помню, в Super Mario мелодия "потери жизни" была без резких/быстрых скольжений тона (или портаменто (portamento/glide) было не такое в приставке, или кто-то поколдовал над мелодией?).

  23. #23
    VEG Автор

    Забыл написать (или даже не подумал), а если объединить несколько пауз (т.е. вместо $81, $60, $81, $18, $81, $6 поставить $81, $7E)?

    По идее, технически это то же самое. Объединил.

    Обновлённый движок можно использовать для звуковых данных от других мелодий от NTDEC и компании. Может как-нибудь в будущем займусь. По крайней мере Summer Wine наверное стоит так оформить =)

  24. #24
    Anatoliy

    Ну почему нельзя в MP3 тоже выложить? Мы должны с форматом nsf(e) заморачиваться?

  25. #25
    VEG Автор

    Проект был в активной доработке в октябре-ноябре 2018. Поскольку она была приостановлена, публикую текущее состояние задним числом.

    v1.4.0 [2018/11/15]

    • Переписан звуковой движок. Теперь он потребляет меньше RAM и циклов CPU.
    • Бесшовный повтор мелодии (раньше вставлялся один кадр тишины).
    • Более надёжная обработка ввода Konami Code.
    • Перезарядка снаряда занимает какое-то время.
    • На экране может быть до 6 снарядов одновременно!
    • Респаун других чаек и возврат в обычный режим после вашей смерти.
    • Обошлось без визуальных изменений в графике.

    NSF v1.2.0 [2018/11/15]

    • Основан на Unchained Nostalgia v1.4.0.
    • Размер файла: 1720 байт.
    • Ещё немного более компактное представление звуковых данных.
    • Исправлено количество треков в NSFe (некоторые NSFe-плееры ошибочно видели больше одной мелодии).

    Скачать: unchained_nostalgia.zip (20KB).

  26. #26
    VEG Автор

    Anatoliy, NSF — это стандартный формат для музыки с Денди. Практически все эмуляторы его поддерживают. При необходимости получить именно MP3 — можете взять его из видео на YouTube, например. Я, конечно, могу выложить эту мелодию в MP3/Ogg Vorbis/Opus, но кажется что пользы от этого будет немного. Разве что если вы её на звонок в телефоне хотите поставить, что я сам тоже когда-то делал =)

  27. #27
    SS Judge Eagle

    Всем доброго времени суток!
    Решил переделать с нуля для РС в векторе... При том что в корел и иллюстраторе я ноль :)
    Планы конечно наполеоновские:
    Обоина на рабочий стол 1920х1080
    обоина на андройде
    обоина на ios (скорее всего из-за закрытости системы придётся забить)
    Скринсейвер
    Причём синхронизация по времени и возможно погоде. Добавить зимний вариант :)
    Если есть какие либо картинки в макс. качестве, буду рад.
    Проект будет полностью некоммерчестким, с названием Unchained Nostalgia v2.0
    Статус: Идея

  28. #28
    VEG Автор

    Всё круто, я приветствую любое творчество =)

    Но всё же у вас же другой проект, а не продолжение этого, поэтому не совсем корректно называть его новой версией Unchained Nostalgia. Тем более, что Unchained Nostalgia не заброшен, в этом году планируется v1.5 (не смог удержаться от очередных финальных штришков), и возможно когда-нибудь дойдёт до v2.0 =)

  29. #29
    SS Judge Eagle

    Ну окей, тогда придумаю иное название :)
    Для меня это всё едино... Пираты сделали для людей... и мы вроде как тоже, от души и для всех :Р

  30. #30
    Александр

    VEG, перезалейте исходники, хотел взглянуть, но похоже все репо на битбакете роскомнадзорнулись.

  31. #31
    VEG Автор

    Александр, действительно, BitBucket удалил все Mercurial-репозитории. Позднее сконвертирую в Git и залью на GitHub.

  32. #32
    VINT64

    Здравствуй. Я тут гуглил, почему игры на Dendy порой имели замедленный звук по сравнению с "оригиналами", и гугл дал мне ссылку на https://veg.by/ru/projects/unchained/#comment-51725
    Мне бы хотелось написать короткий и ёмкий параграф насчёт этой разницы с частотами, и добавить в описание к своему видео https://youtu.be/DNmrt7hcwlM в качестве объяснения. Можешь рассказать об этом замедлении звука подробно? Возможно в отдельной статье

  33. #33
    VEG Автор

    Ну там как бы всё сказано. В Японии и США была NTSC-версия NES, которая работала с частотой обновления экрана 60Гц. Скорость воспроизведения музыки завязана на эту частоту. Наша Dendy подключалась к телевизорам PAL и работала с частотой обновления экрана 50Гц. Без дополнительной адаптации со стороны кода игры, весь геймплей и музыка становились соответственно медленнее, так как за одну секунду приставка показывала только 50 кадров вместо 60.

    В те времена в играх время жёстко привязывалось к кадрам, а не к тому сколько на самом деле прошло настоящего времени. То есть, если игра хотела подождать ровно 5 секунд, она ждала бы 60*5 кадров в расчёте на NES NTSC, и если эту же игру запустить на Dendy или NES PAL без адаптации кода игры, то ожидание в реале получилось бы уже 6 секунд (на 20% больше), так как эти приставки показывали только 50 кадров в секунду.

  34. #34
    VEG Автор

    В 2023 году я готовил юбилейное обновление Unchained Nostalgia: 5 лет предыдущей версии демки, 10 лет первой версии демки, 30 лет первым многоигровкам с Unchained Melody и самой Dendy, а также 40 лет оригинальному Famicom. Но на что-то под конец года отвлёкся, поэтому оно так и осталось без релиза. Выпускаю сейчас задним числом в том состоянии, в котором проект был в ноябре 2023.

    Unchained Nostalgia v1.5 [2023/11/05]

    • Эффект падающих звёзд на ночных слайдах. Можно загадывать желания =)
    • При включении режима автоматической демонстрации, сразу же отображается самый подходящий слайд.
    • Режим автоматической демонстрации включается автоматически после долгого простоя на подходящем слайде.
    • Более точная синхронизация автопролистывания слайдов с ударниками в мелодии (из-за ошибки, был сдвиг на 6 кадров).
    • Более отзывчивая реакция на быстрое переключение слайдов.
    • Используется стандартный вертикальный мирроринг NROM (вместо нестандартного 4-screen) для лучшей совместимости.
    • Облегчённая версия звукового движка в NSF.
    • Размер NSF-файла: 1632 байт.

Комментарии временно закрыты.