6.2. Программирование Все звуковые эффекты и музыка програм- мируются путем постоянной смены значений регистров с необходимыми задержками. Осу- ществить это может, например, такая под- программа: 1415. 10 LD HL,60000 ; HL=адрес данных 20 LOOP LD A,(HL) ; A=байт данных 30 INC HL ; HL=HL+1 40 CP 255 ; A=255 ? 50 RET Z ; если да, то возврат 60 CP 16 ; A=16 ? 70 JR NZ,REG ; если нет, то перейти на REG 80 LD B,(HL) ; B=длительность паузы 90 PAUSE HALT ; ожидание прерывания 100 DJNZ PAUSE ; цикл 110 JR CONT ; перейти на CONT 120 REG LD BC,65533 ; BC=адрес порта регистра 130 OUT (C),A ; записать номер регистра 140 LD B,191 ; BC=адрес порта данных 150 LD A,(HL) ; A=значение регистра 160 OUT (C),A ; записать данные в регистр 170 CONT INC HL ; HL=HL+1 180 JR LOOP ; переход к началу 2 Эта программа довольно примитивна, и для воспроизведения музыки не очень подхо- дит, но для создания простых эффектов - в самый раз. Перед ее запуском не забудьте подготовить блок данных по адресу 60000, состоящий из пар данных и заканчивающийся числом 255. Первым значением в каждой па- ре должен быть номер регистра, а вторым - число, которое нужно записать в этот ре- гистр. Кроме того, если первое значение равно 16, то второе интерпретируется как задержка (в пятидесятых долях секунды). Для воспроизведения музыки используют- ся гораздо более сложные подпрограммы, как правило, работающие во втором режиме пре- рываний. Данные для этих подпрограмм обыч- но хранятся в значительно более удобной форме, раздельно для каждого из трех кана- лов. Привести полностью хотя бы простей- ший пример такой подпрограммы в этой кни- ге не представляется возможным из-за его сложности, но, если Вы умеете обращаться с ассемблером, Вам ничего не стоит сочинить такую подпрограмму, а я могу подсказать, как это сделать. Сначала разберемся с форматом данных. Я предлагаю следующую систему: мелодия бу- дет задаваться тремя (по числу голосов) основными блоками данных. Так как практи- чески любая мелодия состоит из одинаковых фрагментов, повторяющихся в разном поряд- ке, логично было бы в главных блоках зада- вать не саму мелодию, а адреса таких фраг- ментов. Эти фрагменты обычно называют пат- тернами (pattern - трафарет, шаблон). Итак, в главных блоках могут содер- жаться следующие двухбайтовые значения: 65535 (#FFFF) - конец мелодии 0 (#0000) - начало цикла addr (#XXXX) - адрес следующего паттерна Код "начало цикла" (0) отмечает место, с которого начнет воспроизводиться мело- дия при повторении. Теперь займемся паттернами. Не вда- ваясь в теорию, приведу разработанный мной формат: 128 (#80) - конец паттерна 129,n (#81,#XX) - установить длительность n 130,n (#82,#XX) - шум с частотой n (0...31) 131,n1,n2 (#83,#XX,#XX) - шум с частотой n (0...31) + нота (0...100) 132,n1,n2 (#84,#XX,#XX) - прямое задание частоты тона (0...4095) 133,addr (#85,#XXXX) - задание блока изменения частоты тона 134,addr (#86,#XXXX) - задание блока изменения частоты шума 135,addr (#87,#XXXX) - задание блока изменения громкости 136,n1,n2,n3 (#88,#XX,#XXXX) - управление генератором огибающей 0...100 (#00...#64) - ноты от ЛЯ субконтроктавы Поясню некоторые коды: 129 - Как Вы, наверное, заметили, при за- дании ноты ее длительность не указы- вается. Дело в том, что использует- ся длительность, установленная зара- нее с помощью этой команды. Дли- тельность измеряется в пятидесятых долях секунды. 131 - С помощью этого кода Вы можете од- новременно воспроизводить тон и шум. 133,134,135 - Эти коды задают дополни- тельные блоки данных, указывающие способ изменения частоты тона, час- тоты шума и громкости на протяжении звучания ноты. Если после кодов 133 или 134 вместо адреса блока находит- ся 0, то изменение частот отключает- ся. Число от 0 до 15 после кода 135 указывает на необходимость поддержа- ния постоянного уровня громкости, соответствующего этому числу. 136 - Этот код управляет генератором оги- бающей. После него должно нахо- диться однобайтовое число от 0 до 7, указывающее на форму огибающей в соответствии с таблицей 5 и двухбай- товое число, задающее период измене- ния огибающей. 0...100 - Эти коды задают ноты. Код 0 соответствует ноте ЛЯ субконтрокта- вы, 1 - ЛЯ#, 2 - СИ и т.д. В блоке, описывающем изменение частоты тона, используются следующие значения: 128 (#80) - конец блока -127...127 (#81...#79) - смещения частоты Блок изменения частоты шума будет зада- ваться в следующем формате: 128 (#80) - конец блока -31...31 (#E1...#1F) - смещения частоты А блок изменения громкости пусть зада- ется так: 128 (#80) - конец блока 0...15 (#00...#0F) - значения громкости Вполне логично было бы сделать эту про- цедуру работающей во втором режиме преры- ваний. Исходя из этого, а также из предло- женного формата данных, можно понять, что для каждого из каналов понадобится массив переменных. Я предлагаю следующий его фор- мат: смещение размер значение 0 2 начальный адрес основного блока данных 2 2 текущий адрес в основном блоке данных 4 2 начальный адрес блока изменения частоты тона 6 2 текущий адрес в блоке изменения частоты тона 8 2 начальный адрес блока изменения частоты шума 10 2 текущий адрес в блоке изменения частоты шума 12 2 начальный адрес блока изменения громкости 14 2 текущий адрес в блоке изменения громкости 16 2 текущий адрес в текущем паттерне 18 1 значение текущей длительности 19 1 счетчик длительности 20 1 число оставшихся повторений Кроме того, понадобится еще по три бай- та на каждый канал для хранения темпа, счетчика темпа и флага разрешения звука. В этот пакет будут входить следующие процедуры: SINIT - Инициализация таблиц, подключение второго режима прерываний. SSTOP - Выключение сопроцессора, восстано- вление стандартного режима преры- ваний. SNEW - Запуск всех трех каналов. При вы- зове этой подпрограммы в регис- трах HL, DE и BC должны быть адре- са главных блоков данных для кана- лов A, B и C соответственно. В ре- гистре A должно находиться число повторений мелодии от 1 до 254 или 255, если Вы хотите, чтобы мело- дия повторялась бесконечно. SNEWA - Запуск канала A. В регистре HL должен быть адрес блока данных, а в регистре A - число повторений (аналогично SNEW). Работу каналов B и C эта процедура не затрагива- ет. SNEWB - Запуск канала B. Все аналогично SNEWA. SNEWC - Запуск канала C. Все аналогично SNEWA. MUTE - Запрет/разрешение работы. В регис- тре A должен быть номер канала от 0 до 2 или 3, если Вы обращаетесь ко всем каналам. В регистре B дол- жен быть код режима работы: 0 - остановка, 1 - беззвучная работа, 2 - воспроизведение. STATUS - Получение состояния канала. В ре- гистре A должен быть номер канала от 0 до 2. По возвращению из про- цедуры STATUS регистр A содержит код режима работы выбранного кана- ла (аналогично B в MUTE). TEMPO - Установка темпа. В регистре A дол- жен быть номер канала от 0 до 2 или 3, если Вы обращаетесь ко всем каналам. В регистре B должно нахо- диться значение темпа. Итак, начало пакета может выглядеть так: 1415. 10 ORG 60000 20 JP SINIT 30 JP SSTOP 40 JP SNEW 50 JP SNEWA 60 JP SNEWB 70 JP SNEWC 80 JP MUTE 90 JP STATUS 2 Процедура TEMPO может выглядеть так: 1415. 100 TEMPO DI 110 PUSH HL 120 PUSH AF 130 LD HL,TEMPS ; HL=указатель на темпы 140 CP A,3 150 JR Z,TEMP3 160 ADD A,L 170 LD L,A 180 JR NC,TEMP1 190 INC H 200 TEMP1 LD (HL),B 210 INC HL 220 INC HL 230 INC HL 240 LD (HL),B 250 TEMP2 POP AF 260 POP HL 270 EI 280 RET 290 TEMP3 PUSH DE 300 LD D,6 310 TEMP4 LD (HL),B 320 INC HL 330 DEC D 340 JR NZ,TEMP4 350 POP DE 360 JR TEMP2 2 Раз уж пошли обращения к данным, надо привести строки с их описанием: 1415. 370 CHAN_A DEFS 21 ; массив переменных для канала A 380 CHAN_B DEFS 21 ; массив переменных для канала B 390 CHAN_C DEFS 21 ; массив переменных для канала C 400 MUTS DEFS 3 ; флаги разрешения звука 410 TEMPS DEFS 3 ; темпы 420 CURTS DEFS 3 ; счетчики темпов 430 AYREGS DEFS 14 ; копии регистров сопроцессора 440 ENVS DEFB 0,4,11,13,8,12,14,10 ; формы огибающей 450 SVOLS DEFW #8000,#8001,#8002 ; таблицы измене- 460 DEFW #8003,#8004,#8005 ; ния громкости 470 DEFW #8006,#8007,#8008 ; для стандартных 480 DEFW #8009,#800A,#800B ; значений 490 DEFW #800C,#800D,#800E 500 DEFW #800F 510 NOTES DEFW ... ; сюда необходимо записать все значе- ; ния из таблицы 9 от левого верхнего ; угла в порядке сверху - вниз, спра- ; ва - налево 2 Строка 430 содержит область данных, ко- торая используется для вывода звука. Сна- чала в ней формируются значения всех ре- гистров сопроцессора с помощью следующей подпрограммы: 1415. 520 SETAY PUSH HL 530 PUSH AF 540 LD AL,AYREGS 550 ADD A,L 560 LD L,A 570 JR NC,SETAY1 580 INC HL 590 SETAY1 LD (HL),B 600 POP AF 610 POP HL 620 RET 2 Ей надо передать номер регистра в A и его значение в B. Затем содержимое этой области копирует- ся в реальные регистры сопроцессора другой подпрограммой: 1415. 630 AYOUT PUSH HL 640 PUSH DE 650 PUSH BC 660 LD HL,AYREGS+13 670 LD D,13 680 AYOUT1 LD BC,65533 690 OUT (C),D 700 LD B,191 710 LD E,(HL) 720 OUT (C),E 730 DEC HL 740 DEC D 750 JP P,AYOUT1 ; если D>=0, то перейти на AYOUT1 760 POP BC 770 POP DE 780 POP HL 790 RET 2 Любой из регистров в этой области можно также прочитать: 1415. 800 GETAY PUSH HL 810 PUSH AF 820 LD HL,AYREGS 830 ADD A,L 840 LD L,A 850 JR NC,GETAY1 860 INC H 870 GETAY1 LD B,(HL) 880 POP AF 890 POP HL 900 RET 2 Этой подпрограмме нужно передать номер требуемого регистра в A и она вернет его значение в B. Строка 440 понадобится для дешифрации кода огибающей. Если любое число из табли- цы 5 прибавить к метке ENVS и по получен- ному адресу прочитать один байт, Вы полу- чите значение, которое надо записать в R13. Строка 450 будет нужна для дешифрации стандартных уровней громкости. Для этого надо число от 0 до 15 умножить на 2 (мож- но использовать команду SLA) и прибавить к метке SVOLS. Полученное значение нужно ис- пользовать в качестве адреса блока измене- ния громкости. Строка 510 будет полезна для дешифра- ции нот. Если код ноты (от 0 до 100) умно- жить на 2 (команда SLA) и прибавить к мет- ке NOTES, а по полученному адресу считать двухбайтовое число, Вы получите значения младшего и старшего регистров частоты. Теперь займемся основными процедурами: 1415. 910 STATUS PUSH HL 920 LD HL,MUTS 930 ADD A,L 940 LD L,A 950 JR NC,STAT1 960 INC H 970 STAT1 LD A,(HL) 980 POP HL 990 RET 2 Эта маленькая, но полезная процедура поможет программисту узнать, в каком сос- тоянии находится тот или иной канал. Нап- ример, чтобы найти, какой из них свободен. Следующая процедура - MUTE: 1415. 1000 MUTE DI 1010 PUSH HL 1020 PUSH AF 1030 LD HL,MUTS 1040 CP A,3 1050 JR Z,MUT2 1060 ADD A,L 1070 LD L,A 1080 JR NC,MUT1 1090 INC H 1100 MUT1 LD (HL),B 1110 POP AF 1120 POP HL 1130 EI 1140 RET 1150 MUT2 LD (HL),B 1160 INC HL 1170 LD (HL),B 1180 INC HL 1190 JR MUT1 2 Эта процедура понадобится, например, для временной остановки работы одного из каналов. Теперь - процедуры инициализации кана- лов: 1415. 1200 SNEWC PUSH IX 1210 LD IX,CHAN_C 1220 PUSH BC 1230 LD B,2 1240 JR SNEW1 1250 SNEWB PUSH IX 1260 LD IX,CHAN_B 1270 PUSH BC 1280 LD B,1 1290 JR SNEW1 1300 SNEWA PUSH IX 1310 LD IX,CHAN_A 1320 PUSH BC 1330 LD B,0 1340 SNEW1 DI 1350 PUSH BC 1360 PUSH DE 1370 PUSH HL 1380 PUSH IX 1390 POP HL 1400 PUSH HL 1410 POP DE 1420 INC DE 1430 LD BC,20 1440 LD (HL),B 1450 LDIR 1460 POP HL 1470 PUSH HL 1480 LD (IX+20),A 1490 LD (IX+0),L 1500 LD (IX+1),H 1510 LD (IX+19),0 1520 LD E,(HL) 1530 INC HL 1540 LD D,(HL) 1550 INC HL 1560 LD (IX+2),L 1570 LD (IX+3),H 1580 LD (IX+16),E 1590 LD (IX+17),D 1600 LD HL,SVOLS+30 1610 LD (IX+12),L 1620 LD (IX+13),H 1630 LD (IX+14),L 1640 LD (IX+15),H 1650 LD (IX+18),13 ; длительность по умолчанию = 1/4 1660 POP HL 1670 POP DE 1680 POP BC 1690 LD A,B 1700 LD B,2 1710 CALL MUTE 1720 LD B,1 1730 CALL TEMPO 1740 POP BC 1750 POP IX 1760 EI 1770 RET 2 И, наконец, инициализация всех трех ка- налов: 1415. 1780 SNEW PUSH HL 1790 CALL SNEWA 1800 PUSH DE 1810 POP HL 1820 CALL SNEWB 1830 PUSH BC 1840 POP HL 1850 CALL SNEWC 1860 POP HL 1870 RET 2 Подпрограммы инициализации подготавли- вают в массивах переменных все необходи- мые для работы данные. Они устанавливают адреса блоков изменения частоты тона и шу- ма в положение "не используется" (заносят в них 0). Выбирают постоянный уровень громкости (15). А также устанавливают дли- тельность нот по умолчанию равной 1/4 се- кунды. Вот подпрограмма подключения второго режима прерываний: 1415. 1880 SINIT LD A,24 1890 LD (65535),A 1900 LD A,195 1910 LD (65524),A 1920 LD HL,INTR ; HL=адрес обработчика 1930 LD (65525),HL 1940 LD HL,65024 1950 LD DE,65025 1960 LD BC,256 1970 LD (HL),255 1980 LD A,H 1990 LDIR 2000 DI 2010 LD I,A 2020 IM 2 2030 LD HL,MUTS ; запрет 2040 XOR A 2050 LD (HL),A ; работы 2060 INC HL 2070 LD (HL),A ; всех 2080 INC HL 2090 LD (HL),A ; каналов 2100 EI 2110 RET 2 А это подпрограмма, возвращающая стан- дартный режим прерываний и выключающая сопроцессор: 1415. 2120 SSTOP DI 2130 LD A,63 2140 LD I,A 2150 IM 1 2160 EI 2170 LD HL,AYREGS 2180 LD DE,AYREGS+1 2190 LD BC,13 2200 LD (HL),B ; B=0 2210 LDIR ; очистка области регистров 2220 LD A,7 2230 DEC B ; B=255 2240 CALL SETAY ; R7=255 (выключение микшера) 2250 JP AYOUT ; вывод регистров в сопроцессор 2 Теперь займемся обработчиком прерыва- ний. Так как он должен обслуживать три ка- нала, а массивы переменных легче всего ад- ресовать регистром IX, можно предложить такую подпрограмму: 1415. 2260 INTR PUSH AF ; сохранение регистров 2270 PUSH HL 2280 PUSH DE 2290 PUSH BC 2300 PUSH IX 2310 LD IX,CHAN_A ; подготовка регистров 2320 XOR A 2330 CALL DISPAT ; сопроцессора 2340 LD IX,CHAN_B 2350 LD A,1 ; для всех каналов 2360 CALL DISPAT 2370 LD IX,CHAN_C 2380 LD A,2 2390 CALL DISPAT 2400 CALL AYOUT ; вывод регистров в сопроцессор 2410 POP IX ; восстановление регистров 2420 POP BC 2430 POP DE 2440 POP HL 2450 POP AF 2460 RST 56 ; вызов стандартного обработчика 2470 RET 2 Этот обработчик для каждого из каналов вызывает процедуру-диспетчер (DISPAT), за- нося в регистр A номер канала, а в регистр IX - адрес массива его переменных. Роль процедуры DISPAT заключается в об- работке переменных TEMPS, CURTS и MUTS для указанного канала, а также в вызове основ- ной подпрограммы создания звука - GETSND. Вот текст процедуры DISPAT: 1415. 2480 DISPAT PUSH AF 2490 LD E,A 2500 LD D,0 2510 CALL STATUS 2520 OR A ; канал остановлен ? 2530 JR NZ,DISP1 2540 POP AF 2550 LD B,A 2560 JR DISP3 2570 DISP1 LD HL,CURTS 2580 ADD HL,DE 2590 LD A,(HL) 2600 OR A 2610 JR Z,DISP2 2620 DEC (HL) ; уменьшение счетчика темпа 2630 POP AF 2640 RET 2650 DISP2 DEC HL ; обновление 2660 DEC HL 2670 DEC HL ; счетчика 2680 LD A,(HL) 2690 INC HL ; темпа 2700 INC HL 2710 INC HL 2720 LD (HL),A 2730 POP AF 2740 CALL GETSND ; вызов основной процедуры 2750 LD B,A 2760 CALL STATUS 2770 CP 1 ; надо "заглушать" ? 2780 RET NZ 2790 DISP3 LD C,B ; "заглушение" 2800 LD HL,AYREGS+6 2810 LD A,9 ; канала 2820 DISP4 SLA A 2830 DJNZ DISP4 2840 OR (HL) 2850 LD (HL),A 2860 INC HL 2870 ADD HL,BC 2880 LD (HL),0 2890 RET 2 Итак, все сервисные процедуры приведе- ны. Осталась только одна - GETSND: 1415. 2900 GETSND ... 2 Вот ее-то написание я и предлагаю Вам. Но не пугайтесь - я все подробно объясню. Скорее всего, процедура GETSND будет достаточно велика. Может быть, даже больше всех приведенных процедур, вместе взятых. Но ничего трудного в ней нет, а ее объем обусловлен достаточно сложным форматом данных. Задача процедуры GETSND сводится к фор- мированию в определенных ячейках области AYREGS данных одного из каналов для после- дующего копирования их в регистры сопро- цессора. В качестве параметров этой процедуре передается номер канала в регистре A (что- бы она знала, в каких ячейках размещать данные для частоты, громкости и т.п.) и адрес массива переменных в регистре IX. Обратите внимание, что она обязана сохра- нить значение регистра A! Возможно, Вам придется даже завести дополнительную пере- менную для его хранения. Итак, порядок действий в процедуре GETSND следующий: 1. Проверить, не равен ли нулю счетчик длительности (IX+19). 2. Если нет, то продолжить воспроизведе- ние текущей ноты. 3. Если равен, то выбрать новую ноту и начать ее воспроизведение. В понятие "продолжить воспроизведение текущей ноты" входит следующее: 1. Уменьшить счетчик длительности. 2. Если адрес одного из дополнительных блоков равен 0, то пункты 3...5 для этого блока выполнять не надо. 3. Выбрать очередные значения из блоков изменения частот и громкости, исполь- зуя переменные IX+6/IX+7, IX+10/IX+11 и IX+14/IX+15. 4. В соответствии с выбранными значения- ми и номером канала обновить с по- мощью процедур GETAY и SETAY область AYREGS (обратите внимание, что смеще- ния частот могут быть и отрицательны- ми). 5. Обновить переменные по адресам IX+6/ IX+7, IX+10/IX+11 и IX+14/IX+15 в со- ответствии с пунктом 3. "Начать воспроизведение ноты" включает в себя только один пункт: 1. Переписать переменные по адресам IX+4/ IX+5, IX+8/IX+9, IX+12/IX+13 и IX+18 в их счетчики - дубли (IX+6/IX+7, IX+10/ IX+11, IX+14/IX+15 и IX+19). А вот пункт "выбрать новую ноту" - пос- ложнее: 1. Выбрать очередной байт из текущего паттерна (адрес - IX+16/IX+17). 2. Если он не равен 0...100, 130, 131 и 132 - обработать как соответствующий управляющий код и перейти к пункту 1. 3. Соответственно с байтом или его пара- метрами и номером канала обновить об- ласть AYREGS и переменную IX+16/IX+17. Теперь об обработке управляющих кодов: Код 128: 1. Выбрать адрес следующего паттерна из основного блока (адрес в основном бло- ке содержится в IX+2/IX+3). 2. Обновить переменную IX+2/IX+3. 3. Если адрес паттерна равен нулю, скопи- ровать переменную IX+2/IX+3 в IX+0/IX+ 1 и перейти к пункту 1. 4. Если адрес равен 65535, скопировать переменную IX+0/IX+1 в IX+2/IX+3 и уменьшить счетчик повторений (IX+20). Если счетчик повторений равен нулю, установить канал в состояние "останов- лен" с помощью процедуры MUTE. Перейти к пункту 1. 5. Записать выбранный адрес в IX+16/IX+17 и "выбрать новую ноту". Код 129: 1. Взять байт, следующий за этим кодом, и занести в IX+18. "Выбрать новую ноту". Код 133 (134): 1. Взять адрес, следующий за этим кодом, и поместить его в IX+4/IX+5 (IX+8/IX+ 9). "Выбрать новую ноту". Код 135: 1. Взять адрес, следующий за этим кодом. 2. Если он меньше 16, вычислить соответ- ствующий адрес в таблице SVOLS. 3. Поместить полученный адрес в IX+12/IX+ 13. 4. "Выбрать новую ноту". Код 136: 1. Установить в регистре громкости задан- ного канала значение 16. 2. Записать в IX+14/IX+15 ноль. 3. Взять байт, следующий за этим кодом. 4. Вычислить по таблице ENVS значение R13 и обновить область AYREGS. 5. Взять два байта, следующие за кодом формы, и занести их в ячейки 11 и 12 области AYREGS. 6. "Выбрать новую ноту". Теперь о том, как можно рассчитать но- мера регистров для заданного канала. Что- бы рассчитать номер регистра частоты, дос- таточно номер канала умножить на 2 (ADD A,A). Полученное число будет номером ре- гистра младшего байта частоты. Чтобы полу- чить номер регистра старшего байта часто- ты, полученное значение надо увеличить на 1 (INC A). Чтобы вычислить номер регистра громкос- ти, надо к номеру канала прибавить 8 (ADD A,8). Если Вы напишете процедуру GETSND, в Ваших руках окажется довольно мощная прог- рамма, пригодная для написания как музыки, так и эффектов. И в конце описания этой программы - со- веты по составлению для нее блоков данных. Чтобы ноту повысить или понизить на ок- таву, необходимо ее значение соответствен- но увеличить или уменьшить на 12. Ноте ДО первой октавы соответствует число 39. Значения длительностей Вы можете взять из таблицы 11. Теперь несколько советов по программи- рованию сопроцессора. Для формирования но- вых тембров можно использовать генератор огибающей, настроенный на периодически из- меняющуюся громкость и большую частоту. Особенно хороших результатов можно до- биться, настроив его на частоту, кратную частоте основного сигнала. Для заглушения музыкального сопроцессо- ра довольно часто используется запрещение всех функций микшера (вывод байта 255 в R7), но такой способ не очень надежен. Если генератор огибающей настроен на пе- риодически изменяющуюся громкость, то этот трюк не пройдет: останется слышным щел- канье. Для полного заглушения сопроцессо- ра могу посоветовать такую подпрограмму: 1415. 10 LD HL,DATA ; HL=адрес данных 20 LD E,10 ; E=номер первого регистра 30 LOOP LD BC,65533 ; BC=адрес порта регистров 40 OUT (C),E ; вывод E в порт BC 50 LD A,(HL) ; A=значение очередного регистра 60 LD B,191 ; BC=адрес порта данных 70 OUT (C),A ; вывод A в порт BC 80 INC HL ; HL=HL+1 90 DEC E ; E=E-1 100 LD A,E ; E= 110 CP 6 ; 6 ? 120 JR NZ,LOOP ; если нет, то цикл 130 RET ; возврат 140 DATA DEFB 0,0,0,255 ; данные для регистров AY 2 Во многих случаях требуется определить, присутствует ли сопроцессор в данном ком- пьютере. Некоторые делают это проверяя тип компьютера (48K/128K), но этот способ не совсем справедлив, ведь AY может стоять и на старом добром Спектруме. Вот подпрог- рамма, определяющая присутствие сопроцес- сора более достоверно: 1415. 10 LD BC,65533 ; BC=адрес порта регистров 20 XOR A ; A=0 30 OUT (C),A ; выбор регистра 0 40 LD B,191 ; BC=адрес порта данных 50 OUT (C),A ; вывод 0 в выбранный регистр 60 LD B,255 ; BC=адрес порта регистров 70 IN A,(C) ; ввод значения из выбранного регистра 80 OR A ; A=0 ? 90 RET ; возврат 2 Если после вызова этой подпрограммы флаг Z сброшен, то сопроцессор присут- ствует. В противном случае - нет. И напоследок хочу рассказать об одном довольно интересном приеме. Он очень час- то используется во многих программах. Чи- тая данные из регистров громкости и преоб- разовывая их соответствующим образом в графическую информацию одновременно с вос- произведением музыки, можно сделать цвето- музыку или пиковые индикаторы уровня сиг- нала.