Мелодия из «Призрака оперы» через системный динамик — классическая учебная задача на MASM: программа напрямую программирует канал 2 таймера Intel 8253/8254 через порты 42h и 43h, а звук включается установкой битов порта 61h. Если ваша программа молчит, чаще всего причина в том, что забыт стробирующий бит в порте 61h либо делитель частоты записан в неверном порядке — сначала младший байт, затем старший.
В этой статье разберём принцип генерации тона, структуру таблицы нот для фразы из «Призрака оперы», типовой каркас программы на MASM и типичные ошибки, из-за которых спикер молчит или играет «мимо» нот. Материал ориентирован на DOS-окружение (реальное или в DOSBox), поскольку прямой доступ к портам в современных Windows приложениям запрещён.
Как PC-спикер превращает числа в звук
Системный динамик подключён к каналу 2 программируемого интервального таймера. Таймер получает базовую частоту примерно 1,193 МГц (точное значение — 1193180 Гц), и вы задаёте делитель: частота ноты равна 1193180, делённому на этот делитель. Например, для ноты Ля первой октавы (440 Гц) делитель будет примерно 2712.
Последовательность действий строго фиксирована. Сначала в порт 43h записывается управляющее слово, выбирающее канал 2 и режим генератора прямоугольных импульсов. Затем в порт 42h дважды выводится делитель — младший байт, потом старший. Наконец, в порт 61h записывается значение с установленными двумя младшими битами, которые подключают выход таймера к динамику.
Частота ноты = 1193180 / делитель. Делитель всегда пишется в порт 42h двумя байтами: сначала младший, затем старший.
Таблица нот для «Призрака оперы»
Узнаваемая фраза темы Эндрю Ллойда Уэббера строится на последовательности полутонов с характерным «криком» вверх. Для программы удобно хранить не частоты, а сразу делители таймера — так код проигрывания становится тривиальным. Ниже ориентировочная таблица основных нот, которые понадобятся для мелодии.
| Нота | Частота, Гц | Делитель (примерно) | Комментарий |
|---|---|---|---|
| Ре (D) | 294 | 4058 | Начальный тон фразы |
| До диез (C#) | 277 | 4308 | Полутон вниз |
| До (C) | 262 | 4554 | Продолжение спуска |
| Си (B) | 247 | 4831 | Низкая опора |
| Ля (A) | 440 | 2712 | Опорная нота для проверки |
Длительности хранятся отдельным массивом — в «тиках» вашей процедуры задержки. Проще всего завести базовую единицу (например, восьмую ноту) и задавать длительности кратно ей: 1, 2, 4. Точный темп подбирается на слух, потому что скорость программной задержки зависит от процессора и среды исполнения.
Почему делители указаны примерно
Делитель — целое число, поэтому большинство нот получаются с небольшой погрешностью в доли герца. Для PC-спикера и человеческого слуха это несущественно, но при желании можно округлять делитель в ближайшую сторону и проверять результат тюнером.
Каркас программы на MASM
Ниже — упрощённый фрагмент, показывающий суть: включение канала таймера и вывод делителя. Это учебный пример для DOS-окружения; полная программа дополнительно содержит цикл по массиву нот и процедуру задержки.
; Включение звука заданной частоты
; BX = делитель таймера
sound_on proc
mov al, 0B6h ; канал 2, младший+старший байт, режим 3
out 43h, al
mov ax, bx
out 42h, al ; младший байт делителя
mov al, ah
out 42h, al ; старший байт делителя
in al, 61h ; подключаем таймер к динамику
or al, 03h
out 61h, al
ret
sound_on endp
Выключение звука — зеркальная операция: читаем порт 61h, сбрасываем два младших бита командой and al, 0FCh и записываем обратно. Забыть это нельзя: иначе динамик будет гудеть даже после выхода из программы, пока машину не перезапустят.
Основной цикл проигрывания выглядит так: берём очередной делитель из массива нот, вызываем sound_on, ждём заданное число единиц задержки, при необходимости вставляем короткую паузу между нотами (выключаем звук на долю длительности), переходим к следующему элементу. Признак конца мелодии удобно кодировать нулевым делителем.
Задержки: почему мелодия «плывёт»
Классическая проблема учебных программ — задержка пустым циклом вида loop $. Её длительность зависит от тактовой частоты процессора: на быстрой машине мелодия превращается в трель, на медленной — тянется бесконечно. В DOSBox эмулируемая скорость задаётся настройкой cycles, поэтому одна и та же программа звучит по-разному.
Более надёжные варианты:
- ⏱️ Использовать системный тик таймера — обновление счётчика в области BIOS по адресу
0040:006Cпроисходит примерно 18,2 раза в секунду. - 🎵 Перепрограммировать канал 0 таймера на более частые прерывания — точнее, но требует аккуратного восстановления состояния при выходе.
- 🔁 Оставить программный цикл, но подобрать константу под конкретную среду и зафиксировать cycles в DOSBox.
⚠️ Внимание: если ваша программа меняет частоту канала 0 таймера или перехватывает прерывания, обязательно восстанавливайте исходные значения перед завершением. Иначе DOS-сессия после выхода будет вести себя непредсказуемо — от «замедления» клавиатуры до зависания.
Типичные ошибки и диагностика
Когда программа собирается без ошибок, но спикер молчит, проверяйте по цепочке от простого к сложному. Первым делом убедитесь, что среда вообще эмулирует спикер: в DOSBox звук динамика включён по умолчанию, но громкость зависит от микшера эмулятора и системы.
- 🔇 Нет звука вообще — проверьте, что в порт
61hзаписано значение с битами 0 и 1 установленными, и что управляющее слово0B6hушло в порт43hдо записи делителя. - 🎼 Нота есть, но «не та» — скорее всего, перепутан порядок байтов делителя или в таблице стоят частоты вместо делителей.
- ⏩ Мелодия играет слишком быстро или медленно — проблема в задержке, а не в нотах; см. раздел про таймер.
- 📢 Динамик гудит после выхода — забыто отключение битов порта
61hв конце программы.
☑️ Проверка перед запуском мелодии
Сборка и запуск
Для сборки понадобятся сам MASM (или совместимый ассемблер — TASM в режиме совместимости, JWASM/UASM) и компоновщик, умеющий создавать DOS-исполняемые файлы. Классическая последовательность для COM- или EXE-формата выглядит так:
ml /c phantom.asm
link phantom.obj;
Точные ключи зависят от версии ассемблера и выбранной модели памяти — сверяйтесь с документацией вашего инструментария. COM-формат проще для маленьких программ (код начинается с org 100h), EXE удобнее при нескольких сегментах данных. Запускать результат следует в DOSBox или на реальном DOS: в Windows программа с прямым доступом к портам просто не выполнится корректно.
Начните отладку с одной непрерывной ноты Ля (делитель около 2712), звучащей пару секунд. Как только она стабильно играет и гаснет — переходите к массиву нот и ритму. Так вы отделите ошибки генерации звука от ошибок логики мелодии.
Ограничения подхода и что дальше
PC-спикер воспроизводит только один голос прямоугольной формы — никакой полифонии и тембра. Для «Призрака оперы» это даже аутентично: драматичная монофоническая тема звучит узнаваемо. Но если хочется большего, следующие шаги очевидны: звук через AdLib/Sound Blaster (FM-синтез или PCM), где программирование идёт уже через другие порты и требует инициализации карты.
⚠️ Внимание: прямой доступ к портам — привилегированная операция. В DOS это нормально, но не пытайтесь переносить этот код в Windows-приложение: доступ к оборудованию там возможен только через драйверы, и попытка выполнить out из пользовательского режима завершится ошибкой защиты.
Из учебной задачи «Призрак оперы» на MASM вырастает вполне практический навык: работа с портами ввода-вывода, таймерами, табличными данными и аккуратным завершением программы. Это та же база, что нужна для драйверов, демосцены и низкоуровневой диагностики железа.
Формула успеха: правильное управляющее слово в 43h, делитель двумя байтами в 42h, биты 0–1 порта 61h включены перед звуком и выключены после. Всё остальное — таблица нот и ритм.
Частые вопросы
Почему программа работает в DOSBox, но молчит в Windows?
Современные Windows запрещают приложениям прямой доступ к портам ввода-вывода. Инструкции in/out из пользовательского режима вызывают исключение. Используйте DOSBox, DOS-виртуальную машину или реальное DOS-железо.
Как подобрать делитель для любой ноты?
Разделите 1193180 на частоту ноты в герцах и округлите до целого. Таблицы частот всех нот равномерно темперированного строя есть в любой справочной литературе по музыкальной акустике.
Можно ли играть аккорды на PC-спикере?
Аппаратно канал один, поэтому настоящей полифонии нет. Существуют программные приёмы (быстрое чередование нот, PWM-подобные техники), но это существенно сложнее и выходит за рамки базовой задачи.
Чем отличается COM от EXE для такой программы?
COM — плоский образ до 64 Кбайт, загружается со смещения 100h, все сегментные регистры равны. EXE имеет заголовок и отдельные сегменты кода, данных и стека. Для мелодии на пару сотен байт COM проще, но EXE привычнее при использовании сегментных директив MASM.
Динамик продолжает гудеть после выхода — что делать?
Ваша программа не сбросила биты порта 61h. Добавьте в конец: in al, 61h, затем and al, 0FCh, затем out 61h, al. До исправления поможет перезагрузка машины или сброс DOSBox.