Играем музыку из Mario на системном динамике

Марио. Ноты

Предисловие


Привет, Мир!

Уже года 3 хочу написать что-нибудь на Хабр, но никак не находилось темы, на которую можно было бы накатать пост. Так было до тех пор пока мне не понадобилось узнать немного про работу системного таймера и системного динамика для лабораторной работы. Порыскав немного в интернете, я не нашел ничего дельного: что-то было написано слишком сложным языком, что-то было не особо содержательно. Я обзавелся неплохой книгой, целой ночью и попытался сыграть всеми известную тему из игры Марио. Продолжение прямо под катом, вроде бы у вас тут так заведено.

Дисклеймер


Код написан так как он написан. Автор не гений программирования, а всего лишь студент, но тем не менее, попытался написать максимально читаемый и понятный код. Всё было написано на Borland C и было протестировано в DOSBox только потому, что нет установленного доса и не очень хочется напортачить с часами реального времени.

В далеком 2012 году Loiqig уже написал более крутую версию, но, как мне показалось, мало внимания уделил теории.

Так же автор (т.е. я) имеет 4 года музыкального образования и был плох в сольфеджио (музыкальная грамота).

Немного теории


Давным давно, когда был популярен процессор Intel 8086, а IBM PC не вызывало вопросов, в эти самые IBM PC и совместимых с ним компьютерах использовался Intel 8253 — таймер и счетчик интервалов. В современных компьютерах этим занимается южный мост (Источник: Wikipedia).

Примерная логическая схема Intel 8253 PIT:

Логическая схема Intel 8253. Рис. 1

Логическая схема Intel 8253. Рис. 2

Как можно видеть на изображении выше, таймер подключен к линии IRQ0. Он вырабатывает прерывание 8h 18.2 раз в секунду.

Таймер состоит из 3 счетчиков (COUNTER0-2), которые работают независимо друг от друга.

Как бы это было не странно, но каждый счетчик выполняет свою работу. В современных компьютерах первый канал считает время суток. С помощью второго канала происходит регенерация DRAM. С помощью третьего канала можно сделать псевдослучайный генератор чисел и пиликать системным динамиком.

Каждый канал имеет 6 режимов работы:

  • Режим 0 — прерывание терминального счета
  • Режим 1 — программируемый ждущий мультивибратор
  • Режим 2 — импульсный генератор частоты
  • Режим 3 — генератор меандра
  • Режим 4 — программно формируемый строб
  • Режим 5 — аппаратно формируемый строб

Переходим к делу


Итак, мы узнали немного теории о системном таймере, узнали, что третий канал подключен к системному динамику. Вроде бы всё классно. Только как с помощью этого сыграть тему из Марио? Пока не понятно.

Каждый счетчик (канал) программируется отдельно. Мы уже решили, что нужно использовать третий. Как видно на изображении выше, динамик соединён с выходом OUT. В то же время он соединен с портом 61h, с помощью которого мы можем управлять динамиком. Первый (младший) бит подключен к входу Gate2 и определяет работает ли счетчик или нет. Второй бит запускает динамик.

Исходя из этой теории становится понятен фронт работы для проигрывания звука:

  • Программируем CLOCK2 на нужную нам частоту (об этом позже)
  • С помощью первых двух бит 61h включить динамик

Так как дефолтный системный динамик может воспроизводить только одноголосые звуки (ну это как было на старых кнопочных телефонах, где бумер наигрывали), то нам нужно получить частоту каждой ноты.

Таблица соотношений нот и частот
Таблица соотношений нот и частот

Как можно увидеть из таблицы, чтоб повысить/понизить октаву, нужно просто умножить/поделить значение частоты на 2.

Для того, чтоб установить нужную частоту для нашего счётчика нужно воспользоваться определенной формулой: 1193182 / N Гц, где 1193182 — частота таймера (1.193182 МГц если быть правильным), N — частота ноты, которую вы решили вывести в динамик.

// Функция проигрывания звука
// _freq — частота ноты
// _dur — длина ноты
// _del — задержка между двумя нотами
void play_sound(int _freq, int _dur, int _del)
{
	outp(0x43, 0xb6); // Устанавливаем счетчик в режим 2 (меандра)

	int timer_soundFreq = TIMER_FREQUENCY/_freq; // Получаем нужную нам 
                                                    //частоту.
                                                   // TIMER_FREQUENCY = 1193182

	// Загрузка частоты в регистр счетчика
	outp(0x42, timer_delay & 0x00ff); // Сначала младший байт
	outp(0x42, (timer_delay & 0xff00) >> 8); // Потом старший


	outp(0x61, inp(0x61) | 3); // Включаем динамик


	delay(_dur); // Делаем задержку, чтоб нота проигравалась именно столько,
		    // сколько нам нужно

	outp(0x61, inp(0x61) & 0xfc); // Выключаем динамик

	delay(_del); // Делаем задержку между двумя нотами
}

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

int main(int argc, char const *argv[])
{
	for (size_t i = 0; i < N; ++i) // N — количесто нот, которые проигрываются
	{
		play_sound(FREQUENCY[i], DURATION[i], DELAY[i]);
	}
	return 0;
}

Так что же с нашим Марио?


Мы научились воспроизводить звуки через системный динами. Отлично! Но как же нам сыграть мелодию из Марио?

Воспользовавшись магией гугления мы находим ноты:

Ноты мелодии из Марио
Ноты. Марио

Далее придется вспомнить курс нотной грамоты и выписать каждую ноту:

Выписанные ноты и их размерность:
ми2 — 1/4
ми2 — 1/4
ми2 — 1/8
до2 — 1/8
ми2 — 1/4

соль2 — 1/4
соль — 1/4

до2 — 1/4
соль — 1/4
ми — 1/4

ля — 1/4
си — 1/4
си (бемоль) — 1/8
ля — 1/4

соль — 1/4
ми2 — 1/4
соль2 — 1/4
ля2 — 1/4
фа2 — 1/8
соль2 --1/8

ми2 — 1/4
до2 — 1/8
ре2 — 1/8
си — 1/8

соль2 — 1/4
ми (диез)2 — 1/4
фа (бекар)2 — 1/8
ре (диез)2 — 1/4
ми (диез)2 — 1/8

соль (диез) — 1/4
ля (диез) — 1/4
до2 — 1/4
ля — 1/4
до2 — 1/4
ре2 — 1/4

соль2 — 1/4
фа (диез)2 — 1/4
фе (бекар)2 — 1/8
ре2 — 1/4
ми2 — 1/4

до3 — 1/4
до3 — 1/8
до3 — 1/4

Стоит отметить, что я ввел несколько допущений. В реальной мелодии играется по две ноты одновременно. Синхронизировать два досбокса мне не хотелось, поэтому я играл по одной ноте.

Вооружившись еще большим терпением переводим каждую ноту в частоты и собираем массивы частот и длительностей:

// Mario -- 43
int FREQUENCY[] = {659.255, 659.255, 659.255, 523.251, 659.255, 783.991, 391.995, 523.251, 391.995, 329.628, 440, 493.883, 466.164, 440, 391.995, 659.255, 783.991, 880, 698.456, 783.991, 659.255, 523.251, 587.33, 987.767, 783.991, 680.255, 698.456, 622.254, 680.255, 415.305, 466.164, 523.251, 440, 523.251, 587.33, 783.991, 739.989, 729.989, 587.33, 659.255, 1046.502, 1046.502, 1046.502};

int DURATION[] = {300, 300, 160, 160, 300, 300, 300, 300, 300, 300, 300, 300, 160, 300, 300, 300, 300, 300, 160, 160, 300, 160, 160, 160, 300, 300, 160, 300, 160, 300, 300, 300, 300, 300, 300, 300, 300, 160, 300, 300, 300, 160, 300};

int DELAY[] = {35, 35, 50, 35, 35, 350, 200, 35, 35, 200, 35, 35, 35, 200, 35, 35, 35, 35, 35, 200, 35, 35, 35, 35, 35, 35, 35, 35, 200, 35, 35, 35, 35, 35, 200, 35, 35, 35, 35, 200, 35, 35, 0};

В данной мелодии я насчитал 43 ноты. Тут тоже стоит заметить, что задержку между двумя соседними нотами я выбирал на слух. Получилось немного медленнее, чем в оригинале.

Заключение


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

Если вдруг у кого-нибудь возникнет желание улучшить мою мелодию или написать что-то свое, то милости прошу в комментарии.

P.S.


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

Хинты по нотной грамоте
Ноты. Названия.
Размерность нот.

Similar posts

Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 19

    +8

    Очень не хватает записи звучания мелодии.

      0
      Я чего-то не понимаю? По нотной записи много где звучит 3 ноты одновременно. А по коду одна. А где объяснение того почему и как так получилось? Как выбиралась звучащая нота? Почему остальные можно отбросить? Можно ли было обойти одноголосность?
        0
        Как выбиралась звучащая нота?

        В таких случаях обычно играют самую верхнюю ноту.
          0
          Я описал почему так, но, возможно, не очень понятно. Так как системный динамик может проигрывать только одну ноту за раз, я не могу сыграть две+ ноты одновременно. Для этого, в теории, можно использовать два синхронизированных досбокса. Так же можно попробовать высчитать среднюю частоту между двумя нотами.
          Как уже написал infund, в таких случаях и правда выбирают самую верхнюю ноту.
          • UFO just landed and posted this here
              0
              Спасибо. Я видел в тексте замечание о том, что две ноты сразу нельзя. Но не увидел пояснения как и почему выбирается звучащая нота. Потому осталось некоторое ощущение недосказанности.

              По решению, конечно, есть некоторые вопросы. Конечно, сказать что системный динамик это просто одноголосая пищалка, без возможности регулировки громкости — это самый простой вариант. В реальной жизни все сложнее. Чем более басовая нота извлекается из инструмента, тем она громче звучит при одинаковой силе и скорости нажатия на клавишу или дергании струны. Вопрос как этого добиться на системной пищалке — он очень интересный. Многие демомейкеры в свое время игрались с выводом звука на спикер. И результаты были очень даже впечатляющими. Один из вариантов описан ниже — ШИМ. Что позволяло использовать его как ЦАП, хранить таблицу нот (или генерировать ее при старте) с учетом физиологического ощущения из звучания, и для воспроизведения нескольких нот одновременно вычислять значение на входе ЦАП путем сумирования данных из таблиц.

              У Вас реализован метод наименьшего сопротивления. Это не плохо. Это экономит размер кода и данных, тем не менее позволяя решить поставленную задачу. Но только об этом лучше явно писать. Да и упрощенную партитуру неплохо бы привести. А то нотами записано одно, а играется другое.

              И да, не смотря на архаичность, подобные решения вполне себе востребованы. Подобного класса «пищалки» сейчас ставят ко многим микроконтроллерам (выход ШИМ, через RC-цепочку). Дешево и сердито. А тут возможность вместо надоедливого и выбешивающего БИП БИИИИП сделать хоть что-то приятное уху. Правда, там чаще всего ставяться не магнито-электрические динамики, а пьезоэлектрические звукоизлучатели. А у них очень не ровная АЧХ. Но, в принципе, даже с этим можно бороться номировав данные в таблицах под конкретную пищалку.

              Но это так… На случай, если захотите развивать проект дальше.
                +1
                Зачем нужно два синхронизированных досбокса? Можно обойтись и одним. Пресловутый Scream Tracker в далёкие 90е годы прекрасно справлялся и в простом DOS, играя немного побольше, чем две ноты одновременно. А досбоксов никаких в те времена ещё не было, даже Windows была только 3.1, «оболочка дешёвая» для того же DOS.
                  0
                  Scream tracker это совсем круто. Хотя бы как в Monotone.
                    0
                    А чего там в этом Monotone урезано по сравнению со Scream Tracker?
              0
              В играх управление динамиком было из прерывания по таймеру, который настраивался на срабатывание 8 тыс раз в секунду.
                0
                Да, был такой метод — программный ШИМ на динамике.
                Позволял даже проигрывать .WAV через встроенный спикер.

                см. www.codenet.ru/progr/audio/wavspeak.php
                +1

                Как известно, с помощью хитрой модуляции спикером можно играть живой звук хреновенького качества. Это использовалось в некоторых играх, и даже есть плеер под dos (называется вроде dss), который может играть спикером mp3. Правда, у меня на Pentium 2 не завелось, а на ноуте с Pentium 1 работало, но адски тормозило.
                Драйвер для этого вроде был в WinNT до 2000й (только wave-файлы), и в старых линуксах это было. Но в целом с начала 2000х, когда пошли массовые дешёвые звуковухи, в т.ч. встроенные, все эти приколы со спикером, ковоксом и пр. подзабросили. А жаль.
                Я всё мечтаю, что кто-то напишет драйвер под современные винды, превращающий спикер в полноценное системное устройство воспроизведения, да чтоб с настройками для тюнинга (ведь пьезо-пищалка и полноценный динамик на пол-ватта будут звучать по-разному).
                Вроде такая простая идея. И хриплый ретро-звук многие оценили бы. Но вот никто не сделал.


                Кстати, есть рабочий плеер, хорошо играющий midi через спикер. Правда, требует танцев с бубном. Называется BaWaMi или BaMaWi.

                  +2
                  Я всё мечтаю, что кто-то напишет драйвер под современные винды, превращающий спикер в полноценное системное устройство воспроизведения
                  Не нужно ждать, когда кто-то другой займётся таким творчеством. Творите сами =)
                    0
                    в виндовс х64 отсутствует возможность управлять спикером как факт. системные звуки при попытке вывода на спикер улетают в звуковую карту.
                    ссылки по теме:
                    раз
                    два

                    можно попробовать писать напрямую в порты, но мне кажется этого винда не даст сделать в целях безопасности… я сам не пробовал.
                      0
                      Речь же шла о написании драйвера. Драйверам можно всё.
                        0
                        я не уверен, но там бяда в том что х64 както использует этот таймер, вот честно,
                        я подробно не смотремл это вопрос, но там даже в мсдне сказано потря производительности…
                        там както связанно с адресами… я ещё внимательно почитаю… но там не всё так просто…
                        так что там драйвером не обойдешься, там что-то на нём завязанно… на этом прерывании…
                  0
                  Я делал полифонию на 8-и битном ПК и без таймера
                  Алгоритм следующий — определяем максимальное число нот, играющее одновременно.
                  Мелодия кодируется в виде нативных нот, которые задаются в виде спецсимволов.

                  За один такт можно передать сигнал на системный динамик (вкл/выкл), следовательно мы разбиваем звучание на блоки тактов, в которые эмулируем звук нот.
                  Эмуляция заключается в том, что если нота не играет, то сигналы на динамик не поступают. Условно говоря — мы имеем набор колебаний динамика в памяти и указатель, который с каждым тактом считывает блок и передает колебания.

                  Таким образом, у нас выходит полифония.
                    +1
                    Вроде как раньше были программы, позволявшие проигрывать wav-файлы на динамике в системном блоке, а есть ли сейчас такие готовые решения?
                      0

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

                    Only users with full accounts can post comments. Log in, please.