Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Аппаратное обеспечение IBM PC

© Александр Фролов, Григорий Фролов
Том 2, книга 1, М.: Диалог-МИФИ, 1992.

[Назад] [Содеожание] [Дальше]

5.5. Таймер и музыка

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

Как мы уже говорили, канал 2 микросхемы 8254 связан с громкоговорителем компьютера. Однако громкоговоритель не просто соединен с выходом OUT канала 2. Порт вывода 61h также используется для управления громкоговорителем. Младший бит порта 61h подключен ко входу GATE канала 2 таймера. Этот бит при установке в 1 разрешает работу канала, т.е. генерацию импульсов для громкоговорителя.

Дополнительно для управления громкоговорителем используется бит 1 порта 61h. Если этот бит установлен в 1, импульсы от канала 2 таймера смогут проходить на громкоговоритель.

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

  • запрограммировать канал 2 таймера на нужную частоту (т.е. загрузить регистр счетчика канала нужным значением);
  • для включения звука установить в 1 два младших бита порта 61h.

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

Очевидно, что для выключения звука надо сбросить два младших бита порта 61h в 0 (при этом нельзя изменять значение остальных битов этого порта).

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

Программа может генерировать звуки и другим способом, не используя таймер. Для этого нужно сбросить младший бит порта 61h и, управляя битом 1 этого порта, формировать импульсы для громкоговорителя. Т.е. программа должна устанавливать этот бит то в 0, то в 1 с некоторым периодом. Высота генерируемого звука будет соответствовать этому периоду.

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

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

Основная идея заключается в использовании прерывания 1Ch, которое вырабатывается таймером с частотой примерно 18,2 Гц. Ваш обработчик этого прерывания осуществляет контроль за выборкой нот из массива, содержащего мелодию, и программирование микросхемы 8254. Например, один раз в полсекунды обработчик проверяет, не пора ли прекратить звучание одной ноты и начать проигрывание следующей ноты. Если пора, он выключает громкоговоритель и перепрограммирует канал 8254 на новую частоту, соответствующую следующей ноте.

Основное преимущество использования таймера для проигрывания мелодии - независимость констант, используемых для программирования канала таймера от производительности системы. Ваша мелодия будет звучать одинаково и на медленной IBM XT и на Super-AT с процессором 80486, но при условии, что вы будете использовать таймер и для организации задержек при исполнении мелодии.

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

Мы подготовили функцию, предназначенную для генерации на громкоговорителе тона заданной частоты и длительности:

/**
*.Name         tm_sound
*.Title        Формирование тона заданной длительности
*
*.Descr        Эта функция предназначена для генерации
*              на громкоговорителе тона заданной
*              длительности и частоты.
*
*.Proto        void tm_sound(int freq, int time);
*
*.Params       int freq - частота в герцах;
*              int time - длительность в тиках
*                         таймера (за одну секунду таймер
*                         тикает 18.2 раза).
*
*.Return       Ничего
*
*.Sample       play.c
**/

#include <stdio.h>
#include <conio.h>
#include "sysp.h"

void tm_sound(int freq, int time) {

        int cnt, i;

// Задаем режим канала 2 таймера

        outp(0x43, 0xb6);

// Вычисляем задержку для загрузки в
// регистр счетчика таймера

        cnt = 1193180L / freq;

// Загружаем регистр счетчика таймера - сначала
// младший, затем старший байты

        outp(0x42, cnt & 0x00ff);

        outp(0x42, (cnt &0xff00) >> 8);

// Включаем громкоговоритель. Сигнал от
// канала 2 таймера теперь будет проходить
// на вход громкоговорителя.

        outp(0x61, inp(0x61) | 3);

// Выполняем задержку.

        tm_delay(time);

// Выключаем громкоговоритель.

        outp(0x61, inp(0x61) & 0xfc);

}


Если подготовить для этой функции таблицы частот и длительностей, то можно с ее помощью проигрывать простейшие мелодии. В следующем примере мы так и поступили:

#include <stdio.h>
#include <conio.h>
#include "sysp.h"

void main(void);
void sound(int, int);

// Массив частот для мелодии

int mary[] = {
        330, 294, 262, 294, 330, 330, 330,
        294, 294, 294, 330, 392, 392,
        330, 294, 262, 294, 330, 330, 330, 330,
        294, 294, 330, 294, 262, 0
};

// Массив длительностей

int del[] = {
        5,   5,   5,   5,   5,   5,   10,
        5,   5,   10,  5,   5,   10,
        5,   5,   5,   5,   5,   5,   5,   5,
        5,   5,   5,   5,   20
};


void main(void) {

  int i;

  for(i=0 ;mary[i] != 0 ;i++)
                tm_sound(mary[i], del[i]);

}


Запускайте эту программу и слушайте, как она работает!

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

Нота      Частота, Гц

До        261,7
До-диез   277,2

Ре        293,7
Ре-диез   311,1

Ми        329,6

Фа        349,2
Фа-диез   370,0

Соль      392,0
Соль-диез 415,3

Ля        440,0
Ля-диез   466,2

Си        493,9


Приведем еще одну программу, генерирующую звук без использования таймера. Эта программа формирует импульсы при помощи манипуляций с разрядом 1 порта 61h:

#include <stdio.h>
#include <conio.h>

#define FREQUENCY 200
#define CYCLES 10000

void main(void);

void main(void) {

        int cnt;

// Во время генерации звука прерывания должны
// быть запрещены.

        _disable();

        _asm {

// Загружаем количество циклов - периодов
// генерируемых импульсов

                mov   dx, CYCLES

// Отключаем громкоговоритель от таймера

                in    al, 61h
                and   al, 0feh

// Цикл формирования периода

sound_cycle:

// Формируем первый полупериод, подаем
// на громкоговоритель уровень 1

                or    al, 2
                out   61h, al

// Формируем задержку

                mov   cx, FREQUENCY

first: loop  first

// Формируем второй полупериод, подаем
// на громкоговоритель уровень 0

                and   al,  0fdh
                out   61h, al

// Формируем задержку

                mov   cx, FREQUENCY

second: loop  second

// Если сформированы не все периоды, переходим
// к формированию следующего периода.

                dec   dx
                jnz   sound_cycle

        }

// Разрешаем прерывания.

        _enable();

// Выключаем громкоговоритель.

        outp(0x61, inp(0x61) & 0xfc);

}


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

Измерение производительности лучше всего выполнять с помощью таймера, определяя время выполнения команды LOOP.

[Назад] [Содеожание] [Дальше]