Страница Ивана Рощина > Статьи >

© Иван Рощин, Москва

ZXNet: 500:95/462.53
E-mail: bestview@mtu-net.ru
WWW: http://www.ivr.da.ru

Некоторые особенности музыкального сопроцессора

Радиолюбитель. Ваш компьютер» 11/2000, под псевдонимом BV_Creator)
Дата последнего редактирования: 26.04.2003.

Необходимые пояснения
Изменение громкости при сложении звуков одинаковой частоты
Посторонние призвуки при изменении частоты звука
Определение типа музыкального сопроцессора

Необходимые пояснения

Эта статья написана на основе материалов, ранее помещённых мной в конференцию ZX.SPECTRUM, и на основе последовавших за этим обсуждений, в которых приняли участие Vadik Akimoff, Roman Panteleyev и Ilya Vinogradov. Благодарю их за помощь! Также спасибо Алексею Летаеву, который помог мне приобрести музыкальный сопроцессор AY и обеспечил доступ к Fido и Internet.

Приведённые в статье сведения были проверены мной на двух экземплярах сопроцессоров YM2149F (Yamaha, Japan) 1990 и 1992 годов выпуска и на сопроцессоре AY38910A/P (Microchip, Taiwan).

Изменение громкости при сложении звуков одинаковой частоты

Изучая возможности музыкального редактора «Pro Tracker 3», я обнаружил такое явление. Допустим, в одном канале стоит какая-то нота, и я ставлю такую же ноту в другом канале, чтобы результирующая громкость звука возросла:

|00|....|..|A-4 1F.F|A-4 1F.F|--- ....|
|01|....|..|--- ....|--- ....|--- ....|
|02|....|..|--- ....|--- ....|--- ....|

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

Объяснить этот факт можно так. Частота звука в каждом канале определяется соответствующим генератором тона. В моём компьютере сопроцессор подключён так, что все три канала объединяются (по некоторым сведениям, именно так он подключён и в фирменном Sinclair ZX Spectrum +2A). Значит, звук, который я слышу, получается в результате сложения колебаний этих каналов. Если складываются два колебания одной частоты, то результат будет зависеть от разности фаз этих колебаний. То есть звук может как усиливаться, так и ослабляться, что, очевидно, и происходит:

а) два канала в одной фазе — звук усиливается.
 +                    +                    +
A|                   A|                   A|
 |                    |                    +---+   +---+
 |                 +  |                 =  |   |   |   |
 +---+   +---+        +---+   +---+        |   |   |   |
 |   |   |   |        |   |   |   |        |   |   |   |
 +---+---+---+---->   +---+---+---+---->   +---+---+---+---->
                 t                    t                    t
б) два канала в противофазе — звук исчезает.
 +                    +                    +
A|                   A|                   A|
 |                    |                    |
 |                 +  |                 =  |
 +---+   +---+        |   +---+   +---+    +---------------+
 |   |   |   |        |   |   |   |   |    |               |
 +---+---+---+---->   +---+---+---+---+>   +---------------+>
                 t                    t                    t
в) промежуточная разность фаз — изменяется как громкость, так и тембр звука.
 +                    +                    +
A|                   A|                   A|
 |                    |                    | +-+     +-+
 |                 +  |                 =  | | |     | |
 +---+   +---+        | +---+   +---+      +-+ +-+ +-+ +-+
 |   |   |   |        | |   |   |   |      |     | |     |
 +---+---+---+---->   +-+---+---+---+-->   +-----+-+-----+-->
                 t                    t                    t

Реально наблюдается «почти» случай «а» (звук усиливается) и «почти» случай «б» (звук сильно ослабляется, но всё же не совсем). Других случаев (с промежуточной громкостью) не наблюдалось. Следовательно, у генераторов тона, работающих на одной частоте, фаза колебаний не совпадает, и возможны две (и только две) различные разности фаз. У меня не было возможности изучить форму сигнала на выходе сопроцессора и узнать точные значения разности фаз в каждом случае — может быть, это сможет сделать кто-либо из читателей? Форму сигнала можно изучить либо с помощью осциллографа, либо с помощью АЦП, подключённого к выходу сопроцессора (при этом специальная программа, которую нетрудно написать самому, должна опрашивать АЦП и строить график на экране).

Было предположение о том, что разность фаз возникает из-за того, что между установкой частоты в одном канале и установкой частоты в другом канале проходит некоторое время. Если это так, то при уменьшении частоты звука должна уменьшаться и разность фаз, ведь промежуток между установкой частоты в одном и другом каналах сопроцессора остаётся постоянным, а период колебаний растёт с уменьшением частоты. Но на опыте это не подтвердилось.

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

По-видимому, в «on interrupt»-мелодиях, т.е. когда музыкальный сопроцессор программируется 50 раз в секунду, с возникновением разности фаз ничего не поделаешь. Преодолеть проблему можно лишь с помощью «цифрового звука», когда программа непосредственно устанавливает уровень сигнала на выходе сопроцессора в каждый момент времени, без использования генераторов тона (т.е. сопроцессор работает в режиме ЦАП). Может быть, кто-то сможет написать основанный на этом принципе проигрыватель «on interrupt»-музыки?

Не знаю, как с этой проблемой обстоят дела в различных эмуляторах ZX Spectrum и эмуляторах AY. Вполне возможно, что там разность фаз не возникает (а значит, музыка звучит лучше, чем на реальном ZX Spectrum!). Как писал известный спектрумовский музыкант EA/Antares, «нельзя программно сэмулировать микросхему вместе с её глюками».

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

 +                    +                    +
A|                   A|                   A|
 |                    |                    |  +-+   +-+   +-
 |                 +  |                 =  |  | |   | |   |
 |  +-+   +-+   +-    |  +-+   +-+   +-    | ++ ++ ++ ++ ++
 | ++ ++ ++ ++ ++     | ++ ++ ++ ++ ++     | |   | |   | |
 +-+---+-+---+-+-->   +-+---+-+---+-+-->   +-+---+-+---+-+-->
                 t                    t                    t

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

Посторонние призвуки при изменении частоты звука

Этот неприятный эффект был обнаружен опять-таки при изучении возможностей редактора «Pro Tracker». Установим в этом редакторе частотную таблицу «SOUNDTRACKER» и создадим такой сэмпл:

+----------------------------------------+
|           EDIT SAMPLE 01               |
| _00 000 -0004 00 T-E ############### F |
| _01 000 -0003 00 T-E ############### F |

Теперь попробуем, как будет звучать нота B–4 (клавиша «M»). Сравните её звучание со звучанием других нот. Ну как, услышали?

Как мне удалось выяснить, при проигрывании этой ноты сопроцессор поочерёдно выводит звук с частотой, соответствующей коэффициенту деления #0100 (далее — звук #0100) и звук #00FF. Между двумя изменениями частоты звука проходит 1/50 секунды.

Для определённости примем, что звук выводится в канале B. Коэффициент деления частоты для этого канала задаётся в регистрах R2 (младший байт) и R3 (старший байт). Так как обращение к регистрам сопроцессора происходит по очереди, и Pro Tracker сначала изменяет регистр R2, а потом R3, при изменении значения #00FF на #0100 происходит вот что:

R3  R2

00  FF   ;Начальное значение - #00FF.
00  00   ;Изменили R2 - получили #0000.
01  00   ;Изменили R3 - получили #0100.

При обратном изменении:

R3  R2

01  00   ;Начальное значение - #0100.
01  FF   ;Изменили R2 - получили #01FF.
00  FF   ;Изменили R3 - получили #00FF.

Видно, что в определённые моменты времени значение делителя частоты оказывается равным #0000 и #01FF, и каким-то образом это приводит к возникновению посторонних призвуков.

Я попробовал изменять значения регистров в другом порядке, написав небольшую программу на ассемблере. Пусть, когда надо перейти от звука #00FF к #0100, первым изменяется регистр R3, а когда надо перейти от #0100 к #00FF, первым изменяется регистр R2:

           Способ 1

R3  R2

00  FF   ;Начальное значение - #00FF.
01  FF   ;Изменили R3 - получили #01FF.
01  00   ;Изменили R2 - получили #0100.

01  00   ;Начальное значение - #0100.
01  FF   ;Изменили R2 - получили #01FF.
00  FF   ;Изменили R3 - получили #00FF.

При этом постороннее значение делителя частоты — только #01FF, но ситуация от этого не улучшилась. Пусть теперь наоборот, когда надо перейти от звука #00FF к #0100, первым изменяется регистр R2, а когда надо перейти от #0100 к #00FF, первым изменяется регистр R3:

           Способ 2

R3  R2

00  FF   ;Начальное значение - #00FF.
00  00   ;Изменили R2 - получили #0000.
01  00   ;Изменили R3 - получили #0100.

01  00   ;Начальное значение - #0100.
00  00   ;Изменили R3 - получили #0000.
00  FF   ;Изменили R2 - получили #00FF.

Постороннее значение делителя частоты в этом случае равно #0000, но звучание не стало лучше. Тогда я попробовал запрещать вывод частоты тона на время изменения R2 и R3, а потом — разрешать. Для этого используется регистр R7 (микшер). Три младших бита этого регистра управляют выводом частоты тона в каналах A, B и C (0 — вывод разрешён, 1 — запрещён), а три старших бита аналогично управляют выводом шума.

                  Способ 3

   R7      R3  R2

%00111000  00  FF   ;Начальное значение - #00FF.
%00111010  00  FF   ;Запретили вывод частоты тона.
%00111010  00  00   ;Изменили R2 - получили #0000.
%00111010  01  00   ;Изменили R3 - получили #0100.
%00111000  01  00   ;Разрешили вывод частоты тона.

Но при этом появились какие-то потрескивания, что тоже неприятно, хотя и лучше, чем в предыдущем случае. Тогда я попробовал сделать так: установить в канале A значение делителя частоты #0100, в канале B — #00FF, а затем переключать эти каналы, управляя микшером. Так как при этом одновременно разрешается вывод частоты тона в одном канале и запрещается в другом, звук должен был стать лучше…

                     Способ 4

   R7      R3  R2  R1  R0

%00111001  00  FF  01  00   ;A - запрещён, B - разрешён.
%00111010  00  FF  01  00   ;A - разрешён, B - запрещён.

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

Попробуем разобраться более подробно. Что мы имеем? При чередовании звуков #00FF и #0100 становятся слышны посторонние призвуки (вместо плавного перехода). Очевидно, существенную роль в этом играет тот факт, что #00FF и #0100 различаются и младшим, и старшим байтом, а значит, делитель частоты при переходе от одного звука к другому приобретает посторонние значения (#0000 и #01FF). Ведь если чередовать звуки, которые различаются лишь в одном байте (к примеру, #00FE и #00FF), никаких призвуков не возникает.

Делаем обобщение: при чередовании любых звуков, у которых делители частоты различаются и младшим, и старшим байтом, должны возникать посторонние призвуки. В результате эксперимента это предположение подтверждается. Вы и сами можете проверить его на парах звуков #01FF и #0200, #02FF и #0300, …, #0EFF и #0F00 (значение делителя частоты ограничено числом #0FFF). С увеличением значений призвуки становятся менее заметны, но совсем не пропадают. Разница между двумя чередуемыми значениями не обязательно должна быть единичной — призвуки появятся и при чередовании звуков #00FE и #0101.

Совершенно ясно, что это сокращает возможности использования музыкального сопроцессора. Музыканты знают об этом неприятном явлении — ведь не используют же они подобные чередования звуков в своих произведениях!

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

Разумеется, можно было бы вообще отказаться от использования генератора тона, при этом все проблемы, связанные с установкой значения делителя частоты для этого генератора, отпали бы сами собой. Используем цифровой звук — и всё! Качество — идеальное! Да вот только это потребует абсолютно всех ресурсов Z80, что нас совершенно не устраивает.

Ну что же, будем разбираться дальше. Во-первых, может ли вообще появление на очень короткое время посторонних делителей частоты оказать какое-либо влияние на звук? Мы-то с вами знаем, что может, да ещё как! Но посмотрим, что по этому поводу говорит теория…

Генератор тона в музыкальном сопроцессоре работает довольно просто: имеется счётчик, значение которого последовательно уменьшается, и как только оно уменьшится до нуля, уровень сигнала на выходе меняется, а в счётчик загружается значение из регистров делителя частоты. Таким образом, формируются прямоугольные импульсы:

  +
A |
  |
  +----+    +----+    +----+    +----+    +---
  |    |    |    |    |    |    |    |    |
  |    |    |    |    |    |    |    |    |
  |    |    |    |    |    |    |    |    |
  +----+----+----+----+----+----+----+----+---->
                                              t

Из этого следует, что пока в счётчике не 0, изменение регистров делителя частоты не должно оказывать влияния на звук. А вот если счётчик обнулится в тот момент, когда в регистрах делителя частоты находится постороннее значение, это несомненно окажет влияние на звук. Попробуем подсчитать вероятность этого события.

Пусть значение делителя частоты меняется с #0100 на #00FF, промежуток между изменением регистров делителя частоты равен 50 тактов Z80 (именно в течение этого промежутка делитель частоты будет принимать постороннее значение #01FF), тактовая частота Z80 равна 3,5 МГц, а тактовая частота сопроцессора равна 1,75 МГц.

Когда делитель частоты равен #0100, генератор тона формирует звук со следующей частотой:

(1750000/16)/256 = 427,2 Гц.

Счётчик генератора тона, соответственно, перезагружается вдвое чаще, с частотой 854,4 Гц. Посчитаем, сколько тактов Z80 проходит между двумя перезагрузками счётчика:

3500000/854,4 = 4096.

Тогда вероятность того, что перезагрузка счётчика произойдёт в тот момент, когда делитель частоты будет равен #01FF, равна:

50/4096 = 0,012.

Как видим, вероятность того, что генератор тона сделает хотя бы один полупериод с посторонней частотой, очень мала — чуть более 1%. Но посторонние призвуки мы слышим с вероятностью 100%! Что-то здесь не так. Может быть, счётчик частоты перезагружается сразу же при записи нового значения в регистры делителя частоты?

Если это так, появление посторонних призвуков становится понятным. Рассмотрим форму сигнала во время изменения делителя частоты с #0100 на #00FF:

  +
A |
  |
  +----+    +--++----+    +----+    +----+
  |    |    |  ||    |    |    |    |    |
  |    |    |  ||    |    |    |    |    |
  |    |    |  ||    |    |    |    |    |
  +----+----+--++----+----+----+----+----+--->
               xy                           t

Сначала делитель частоты был равен #0100 — то есть R3=#01, R2=#00. Пусть в момент изменения регистра R2 на выходе был высокий уровень сигнала, как это показано на графике. В точке x в R2 записывается #FF — делитель частоты принимает постороннее значение #01FF, счётчик частоты перезагружается, уровень сигнала меняется с высокого на низкий. В точке y в R3 записывается #00 — делитель частоты стал равен #00FF, счётчик частоты перезагружается, уровень сигнала меняется с низкого на высокий. Из-за этих двух перепадов уровня сигнала с очень малым промежутком между ними, по-видимому, и появляются призвуки.

У меня не было возможности проверить, действительно ли на выходе сопроцессора получается сигнал именно такой формы, как на графике. Тогда я сделал по-другому: используя «цифровой звук», попробовал воспроизвести изображённый на графике сигнал и оценить его звучание. Как и следовало ожидать, звучание оказалось таким же, как и при использовании генератора тона, с такими же посторонними призвуками. Из этого можно сделать вывод, что для значений, рассмотренных в примере, счётчик частоты перезагружается сразу же при записи нового значения в регистры делителя частоты.

Кстати, нетрудно проверить на слух, что счётчик частоты огибающей тоже перезагружается сразу же при установке нового делителя частоты огибающей (он задаётся в регистрах R11 и R12). Вот способ проверки: если занести в регистр формы огибающей (R13) значение %1000 (постепенное уменьшение уровня сигнала от 15 до 0 в каждом периоде огибающей), а в регистры R11, R12 занести #FFFF (чтобы период огибающей был максимальным), на выходе сопроцессора будет вот что:

                  +
                A |
                  |\    |\    |\
                  | \   | \   | \
                  |  \  |  \  |  \
                  |   \ |   \ |   \
                  |    \|    \|    \
                  +------------------>
                  <--T-->           t

Частота огибающей: F = (1750000/256)/#FFFF = 0,1043 Гц.
Период огибающей:  T = 1/F = 9,59 с.

Т.е. счётчик частоты огибающей перезагружается каждые 9,59/16 = 0,6 секунды (это для сопроцессора AY, а на YM счётчик будет перезагружаться вдвое чаще: там формируются 32 ступеньки огибающей вместо 16). Если считать, что счётчик перезагружается, только когда он уменьшится до нуля, то придётся признать, что как бы ни изменялось значение регистров R11, R12, звучание изменится только через 0,6 секунды — а при проведении эксперимента звучание изменялось мгновенно!

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

В различных эмуляторах ZX Spectrum и эмуляторах AY, насколько мне известно, перезагрузка счётчика происходит лишь при его обнулении, поэтому воспроизводимый звук отличается от звука реального сопроцессора (например, при смене делителя частоты с #0100 на #00FF посторонних призвуков слышно не будет). Можно было бы сказать, что под эмулятором сопроцессор звучит лучше настоящего — но только пользы от этого никакой, никто же не будет писать музыку исключительно для прослушивания под эмулятором. А вот плохая сторона неточной эмуляции очевидна: тот, кто будет писать музыку под эмулятором, может допустить такие изменения частоты звуков, которые на реальном сопроцессоре будут воспроизводиться с посторонними призвуками.

Определение типа музыкального сопроцессора

В восьмом номере электронной газеты «KrNews» я прочитал, что можно программным путём различить тип используемого сопроцессора — AY или YM, и что такая проверка была в demo «Action». Правда, способ определения в этой газете не описывался. В процессе изучения указанной demo я обнаружил следующий фрагмент кода:

#8021: LD   BC,#FFFD
       LD   A,0
       OUT  (C),A     ;Установили R0.
       LD   B,#BF
       LD   A,#40     ;Записали #40 в R0.
       OUT  (C),A
       LD   B,#FF
       IN   A,(C)     ;Читаем из R0.
       CP   #40       ;Прочитали то, что записали?
       JR   Z,#803B   ;Да -> сопроцессор есть.

       LD   HL,#84FA  ;Адрес сообщения "NO".
       JR   #804E     ;Переход к процедуре печати
                      ;сообщения.
#803B: LD   BC,#FFFD
       LD   A,#10
       OUT  (C),A     ;Установили "недокументированный"
                      ;регистр R16.
       IN   A,(C)     ;Читаем из него.
       CP   #FF       ;Сравниваем с #FF.
       LD   HL,#84E6  ;Адрес сообщения "AY-8910/12".
       JR   NZ,#804E  ;Не #FF -> выводим это сообщение.

       LD   HL,#84F0  ;Адрес сообщения "YM2149F".

#804E: ............   ;Процедура печати сообщения.

Таким образом, если попытаться прочитать содержимое «недокументированного регистра», то на YM всегда будет читаться #FF. А на AY, как я выяснил при экспериментах, прочитается номер регистра (в данном примере — число #10).

Если сопроцессор подключён так, что его регистры недоступны для чтения, тест покажет отсутствие сопроцессора.

Другие мои статьи об AY-музыке:

1. 

«Правильное изменение частоты огибающей в Pro Tracker 3». «Радиолюбитель. Ваш компьютер» 10,11/2000.

2. 

«Частотная таблица с нулевой погрешностью». «Радиолюбитель. Ваш компьютер» 6/2001, «Радиомир. Ваш компьютер» 7,8/2001 (под псевдонимом BV_Creator).

3. 

«Оптимизация на примере intro „Start“». «Радиомир. Ваш компьютер» 7—10/2001.

(В этой статье можно найти сведения о программировании проигрывания музыки.)

4. 

«Повышение степени сжатия музыкальных модулей». «Радиомир. Ваш компьютер» 7/2002.

5. 

«Построение таблицы громкости в плеере Pro Tracker 3». «Радиомир. Ваш компьютер» 4/2004.

Страница Ивана Рощина > Статьи >