<?xml version='1.0' encoding="utf-8"?>
      <rss version='2.0'>
      <channel>
      <title>Форум на Исходниках.RU</title>
      <link>https://forum.sources.ru</link>
      <description>Форум на Исходниках.RU</description>
      <generator>Форум на Исходниках.RU</generator>
  	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=89297&amp;view=findpost&amp;p=665947</guid>
        <pubDate>Thu, 31 Mar 2005 16:13:29 +0000</pubDate>
        <title>Воспроизведение звука из PC Speaker&amp;#39;а</title>
        <link>https://forum.sources.ru/index.php?showtopic=89297&amp;view=findpost&amp;p=665947</link>
        <description><![CDATA[Jin X:  <br>
<div class='tag-align-center'><span class='tag-size' data-value='13' style='font-size:13pt;'><span class="tag-color tag-color-named" data-value="blue" style="color: blue"><strong class='tag-b'>Воспроизведение звука из системного динамика (PC Speaker&#39;а)</strong></span></span></div><br>
Для того чтобы заставить встроенный системный динамик издать какой-либо сигнал, необходимо задействовать две микросхемы:<ul class="tag-list"><li> Программируемый таймер i8253/i8254.</li><li> Программируемый периферийный интерфейс (ППИ) i8255.</li></ul>Системный таймер представляет собой генератор импульсов, работающий с частотой 1193181 Гц (~ 1.19 МГц). Он подаёт импульсы на три канала: канал 0 (генерирующий прерывание 8, т.е. IRQ0), канал 1 (предназначенный для регенерации памяти) и канал 2 (связанный с системным динамиком). Принцип работы всех каналов одинаковый. Каждый из них имеет несколько регистров: регистр управляющего слова (CWR, 8 бит), регистр состояния счётчика, регистр-счётчик (Counting Element (CE), 16 бит), регистр, хранящий значение коэффициента пересчёта (CR, 16 бит) и регистр-защёлку (OL, 16 бит), в который периодически передаётся значение регистра-счётчика. При каждом поступлении импульса от таймера происходит декремент (уменьшение на единицу) значения регистра-счётчика. В режиме работы 2 и 3 (см. ниже) при достижении нулевого значения в регистр-счётчик загружается значение коэффициента пересчёта, которое может быть установлено при помощи записи в соответствующий порт ввода-вывода (40h для канала 0, 41h для канала 1 и 42h для канала 2) и весь процесс повторяется заново. Т.о, чем меньше коэффициент пересчёта, тем короче цикл счёта (и длится он CR/1193181 секунды, т.е. повторяется с частотой 1193181/CR Гц). Каждый канал имеет шесть режимов работы, но нам больше всего подходит режим 3 (генератор меандра), т.к. именно в этом режиме положительный и отрицательный выходной сигнал занимают по половине периода счёта<sup class='tag-sup'>1</sup>.<br>
На вход звукогенератора поступает логическое &quot;И&quot; двух сигналов: выхода второго канала таймера и содержимого бита 1 порта PB ППИ (порт 61h). Счёт же второго канала разрешается только в том случае, если установлен бит 0 порта PB ППИ. Т.о, всё, что нам необходимо сделать - это инициализировать таймер и установить биты 0 и 1 порта PB ППИ. Для инициализации таймера нужно записать некоторое значение в регистр управляющего слова (доступ к которому осуществляется посредством записи в порт 43h), а затем поочерёдно младший и старший байты коэффициента пересчёта, т.е. значения (1193181/частота) в порт ввода-вывода канала 2 (порт 42h). Регистр управляющего слова имеет следующую структуру:<ul class="tag-list"><li> бит 0 определяет тип константы, загружаемой в CR: 0 - двоичное число, 1 - двоично-десятичное (BCD), обычно = 0;</li><li> биты 1-3 определяют режим работы канала: для режима 3 = 011b;</li><li> биты 4-5 определяют тип операции: 00b - &quot;защёлкивание&quot; - зафиксировать значение регистра-защёлки для (и до) последующего чтения через порт 40h, 41h или 42h (сначала читается младший байт, затем старший), 01b - запись младшего байта регистра пересчёта, 10b - запись старшего байта регистра пересчёта, 11b - запись сначала младшего, затем старшего байта регистра пересчёта;</li><li>биты 6-7 определяют номер канала: для канала 2 = 10h.</li></ul>Т.е. в нашем случае необходимо записать значение 10110110b (0B6h).<br>
<br>
Для отключения звука достаточно сбросить биты 0 и 1 порта PB ППИ.<br>
<br>
А вот, собственно, и сам код (SOUND.INC):<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">; Подключка для генерации звука</div><div class="code_line">&nbsp;</div><div class="code_line">; Процедура Sound: включить звук</div><div class="code_line">; Вход: AX = частота звука (Гц)</div><div class="code_line">Sound &nbsp; &nbsp; &nbsp; proc</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;mov dx,12h</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;cmp ax,dx &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;; Частота &#60;= 18 Гц ?</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;jbe @@Exit &nbsp; &nbsp; &nbsp; &nbsp; ; Да, на выход, чтобы избежать переполнения</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;xchg &nbsp; &nbsp;cx,ax &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;; Сохраняем частоту в СX</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;mov al,10110110b &nbsp; ; Упр.сл.таймера: канал 2, режим 3, дв.слово</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;out 43h,al &nbsp; &nbsp; &nbsp; &nbsp; ; Выводим в регистр режима</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;mov ax,34DDh &nbsp; &nbsp; &nbsp; ; DX:AX = 1193181</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;div cx &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ; AX = (DX:AX) / СX</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;out 42h,al &nbsp; &nbsp; &nbsp; &nbsp; ; Записываем младший байт счетчика</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;mov al,ah</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;out 42h,al &nbsp; &nbsp; &nbsp; &nbsp; ; Записываем старший байт счетчика</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;in &nbsp;al,61h &nbsp; &nbsp; &nbsp; &nbsp; ; Порт PB</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;or &nbsp;al,11b &nbsp; &nbsp; &nbsp; &nbsp; ; Устанавливаем биты 0-1</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;out 61h,al &nbsp; &nbsp; &nbsp; &nbsp; ; Записываем обратно в PB</div><div class="code_line">@@Exit: &nbsp; &nbsp; ret &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;; Выходим из процедуры</div><div class="code_line">Sound &nbsp; &nbsp; &nbsp; endp</div><div class="code_line">&nbsp;</div><div class="code_line">; Процедура NoSound: отключить звук</div><div class="code_line">NoSound &nbsp; &nbsp; proc</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;in &nbsp;al,61h &nbsp; &nbsp; &nbsp; &nbsp; ; Порт PB</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;and al,not 11b &nbsp; &nbsp; ; Сбрасываем биты 0-1</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;out 61h,al &nbsp; &nbsp; &nbsp; &nbsp; ; Записываем обратно в PB</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;ret &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;; Выходим из процедуры</div><div class="code_line">NoSound &nbsp; &nbsp; endp</div></ol></div></div></div></div><script>preloadCodeButtons('1');</script><sup class='tag-sup'>1</sup> Кстати, при начальной загрузке BIOS инициализирует канал 0 для работы в режиме 3, а в регистр коэффициента пересчёта записывается 0, т.е. 65536 декрементов на цикл счёта. При этом IRQ0 (int 08h) генерируется каждый раз при достижении регистром-счётчиком нуля, т.е. ~ 18.2 раза в секунду.<br>
<br>
Теперь я хочу рассказать немного о нотах. Как известно, в природе существует 12 нот (7 основных и 5 с производными названиями), также известно, что изменение высоты на октаву - это изменение частоты в два раза, а частота ноты &quot;ля&quot; первой октавы = 440 Гц. Отсюда получаем, что изменение высоты на полутон - это изменение в корень 12-й степени из 2 раз (~ 1.059463). Вычисляем частоту ноты &quot;до&quot; самой низкой октавы фортепиано (субконтроктавы) и получаем ~ 16.351598 Гц. Таким образом, вычисление частоты ноты сводится к простейшей процедуре:<ul class="tag-list"><li>Умножаем значение 16.351598 на 2 столько раз, на сколько нужная нам октава выше субконтроктавы.</li><li>Умножаем получившееся значение на 1.059463 столько раз, на сколько полутонов нужная нам нота выше ноты &quot;до&quot;.</li></ul><strong class='tag-b'>P.S.</strong> Кстати, Вы не забыли, что от перестановки мест множителей произведение не меняется? :)<br>
Для вычислений можно, конечно, привлечь математический сопроцессор, но я нашёл более, как мне кажется, короткий и быстрый путь (NOTEFREQ.INC):<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">; Подключка для получения частоты ноты</div><div class="code_line">&nbsp;</div><div class="code_line">; Процедура GetNoteFreq: получение частоты ноты</div><div class="code_line">; Вход: &nbsp; AH = номер октавы, AL = номер ноты</div><div class="code_line">; Выход: &nbsp;AX = частота (Гц)</div><div class="code_line">; Октавы: 0-субконтроктава, 1-контроктава, 2-большая, 3-малая, 4-первая, 5-вторая... 9-шестая</div><div class="code_line">; Ноты: &nbsp; 0-до, 1-до#, 2-ре, 3-ре#, 4-ми, 5-фа, 6-фа#, 7-соль, 8-соль#, 9-ля, 10-ля#, 11-си</div><div class="code_line">GetNoteFreq &nbsp; &nbsp; proc</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;xchg &nbsp; &nbsp;cx,ax</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;cmp cl,11</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;jbe @@OkNote</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;mov cl,11</div><div class="code_line">@@OkNote: &nbsp; mov ax,7</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;mov dx,0B78Ah+23</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;mov bx,61858</div><div class="code_line">@@1: &nbsp; &nbsp; &nbsp; &nbsp;xchg &nbsp; &nbsp;dx,ax</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;div bx</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;push &nbsp; &nbsp;ax</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;div bx</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;xchg &nbsp; &nbsp;ax,dx</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;pop ax</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;dec cl</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;jns @@1</div><div class="code_line">&nbsp;</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;cmp ch,9</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;jbe @@2</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;mov ch,9</div><div class="code_line">@@2: &nbsp; &nbsp; &nbsp; &nbsp;shl dx,1</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;rcl ax,1</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;dec ch</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;jns @@2</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;shl dx,1</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;adc ax,0</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;ret</div><div class="code_line">GetNoteFreq &nbsp; &nbsp; endp</div></ol></div></div></div></div>]]></description>
        <author>Jin X</author>
        <category>Assembler FAQ</category>
      </item>
	
      </channel>
      </rss>
	