Adventurer #13
31 марта 2002

Обмен опытом - прямое программирование General Sound.

     (C) PSB/Halloween

         Прямое программирование
              General Sound.

     Всем привет! Решил я написать немно-
го о кульной спектрумовской звуковой кар-
точке,  а также о ее программировании. Но
только не думайте, что это очередная ста-
тья о том, как играть MOD'ы и эффекты или
озвучивать  игры... Подобной литературы в
Спектрумовской прессе не было (или я про-
сто не видел :-! ).
     Начну с самых истоков. Году в 1997 я
приобрел GS (кстати, спасибо за это Hell-
Raiser/Halloween),  и как, наверное, мно-
гие,  стал учиться его программить, играл
те  же MOD и FX, озвучивал геймы, написал
свой player MOD (который понимал и TR-DOS
и MS-DOS ).  Но все это потихоньку подна-
доело,  ведь разнообразие-то небольшое. А
ведь  когда  GS еще только рекламировали,
писали, что это - еще один комп, со своим
процом, памятью и частотой раза в 3 выше.
Так оно и есть. А главное, проц-то - ува-
жаемый всеми нами Z80!!!
     Многие, наверное, до сих пор думают,
как  можно  программировать  GS напрямую,
если в описании нет таких команд? Где лю-
ди  берут информацию по недокументирован-
ным  командам  и  вообще, как устроен GS?
Думаете есть какие-нить доки по этому по-
воду?  Сейчас - ДА! Сейчас уже даже схему
GS  по кусочкам разобрали, а раньше всего
этого  не  было, но узнать все равно было
можно и даже просто. Опишу как это сделал
я  (по-любому, кому-то это будет интерес-
но).
     В  старые добрые времена была тради-
ция  (можно и так сказать) драть отовсюду
AY'шные музоны. Ну меня и прибило дернуть
MOD  из Target Renegade и Xecutor. Дерну-
ть-то дернул, а вот в Xecutor в загрузчи-
ке  нашел  тест GS. Он помимо стандартной
информации  еще  выдавал  и  копирайты из
прошивки.  Рассмотрев повнимательней про-
гу,  которая  все это достает из карточки
(там  несколько  разных кусочков достава-
лось), прикинул, где может задаваться ад-
рес,  из  которого  это все берется... ну
взял и набрал похожую прогу в MASM, толь-
ко  она у меня тянула с адреса #0000 пер-
вые  32кб.  А  потом, посмотрев через STS
(Z80 ведь!), что получилось, увидел прог-
рамму!  Все правильно, это было ПЗУ. Ну а
дальше  понятно:  стал  потихоньку разби-
рать,  что и как, нашел главный цикл, ко-
торый  обрабатывает  поступающие команды,
посмотрел, как работают документированные
команды, из них узнал о некоторых портах,
как выводится звук и так далее. Несколько
экспериментов,  и  я знал уже практически
все, задолго до появления подобной инфор-
мации. И это все благодаря 3-м(!!!) кома-
ндам из загрузчика Xecutor !
     Хорошо,  предположим, знаем мы Gene-
ral  Sound "изнутри", а что нам это дает?
Да  очень  многое, точнее - использование
платы  нестандартным  образом.  Это может
быть  все, что угодно, начиная от дописы-
вания процедур, которых не хватает в ста-
ндартной прошивке, до своих программ, ко-
торые, может быть, и к музыке-то не отно-
сятся.
     Вот  так.  А  сейчас,  собственно, о
программировании.

       Интерфейс со стороны Speccy

1. Command register (#BB=187, запись).
2. Status register (#BB=187, чтение).
     Биты: 7 - Data bit*
           0 - Command bit*
3. Data register (#B3=179, запись).
4. Output register (#B3=179, чтение).

*)  Вообще,  не помню точно, как описыва-
лись значения этих битов после определен-
ных  операций  в  стандартной  инструкции
(типа, когда в какой-то порт что-то пишем
или  из  него читаем, биты как-то меняют-
ся), но могу сказать проще - БИТ УСТАНОВ-
ЛЕН  В 1, ЕСЛИ ОН _НЕ_ОБСЛУЖЕН_. То есть,
если  мы  что-нить кинем в Data register,
то Data bit будет в 1, пока GS из него не
прочитает.  То же самое будет и внутри GS
- пока ZX не прочтет порт, в Data bit бу-
дет 1.

       Особенности описания команд

SC @$%&%$ - Послать код команды @$%&%$ в регистр команд
WC        - Ожидание принятия/выполнения команды
            (сброса Command bit)
SD @$%&%$ - Послать данные @$%&%$ в регистр данных
WD        - Ожидание принятия данных (сброса Data bit)
GD @$%&%$ - Принять данные @$%&%$ из регистра данных
WN        - Ожидание новых данных от GS (установки Data bit)

                 Команды

#18 - LD DE,nnnn

 SD nnnn_LOW
 SC #18:WC
 SD nnnn_HIGH

     Заносит в рег. пару DE значение nnnn
(внутри GS!). nnnn_LOW и nnnn_HIGH - ста-
рший и младший байты значения nnnn.

#1A - Get data from (DE)

 SC #1A:WC
 GD Value

     Читает  байт  из  ячейки, адресуемой
DE.

#1B - INC DE

 SC #1B:WC

     Увеличивает значение DE на 1.

     Таким  образом, уже используя только
эти 3 команды, вы можете прочитать память
GS и узнать все, что надо:

        LD HL,#0000; адрес в GS, откуда хотим что-то прочитать
        LD A,L:CALL SD;  делаем LD DE,nnnn в GS
        LD A,#18:CALL SC
        LD A,H:CALL SD

        LD HL,#8000; адрес в ZX, куда все будем складывать
        LD DE,#8000; длина блока

LOOP    LD A,#1A:CALL SC; берем значение из GS
        IN A,(#B3)

        LD (HL),A

        LD A,#1B:CALL SC; увеличиваем адрес в GS

        INC HL:DEC DE
        LD A,D:OR E:JR NZ,LOOP
        RET

SC      OUT (#BB),A
WC      IN A,(#BB):RRCA:JR C,WC
        RET
SD      OUT (#B3),A:RET
WD      IN A,(#BB):RLCA:JR C,WD
        RET
WN      IN A,(#BB):RLCA:JR NZ,WN
        RET

     Но есть и ряд других полезных (прос-
то необходимых) команд.

#14 - Put datablock to GS

 SD LENG_LOW
 SC #14:WC
 SD LENG_HIGH:WD
 SD GS_ADR_LOW:WD
 SD GS_ADR_HIGH:WD

 SD DataByte:WD ; столько раз подряд, сколько указано в LENG

     Засылает  блок данных в GS по адресу
GS_ADR и длиной LENG. Этой командой очень
удобно загружать свои программы в GS.

#13 - Jump to ADR

 SD ADR_LOW
 SC #13:WC
 SD ADR_HIGH

     Переходит по адресу ADR в GS. Подхо-
дит для запуска загруженной программы.

#10 - OUT (PORT),A

 SD PORT
 SC #10:WC
 SD Value

#11 - IN A,(PORT)

 SD PORT
 SC #11:WC
 GD Value

     Эти  две команды позволяют прочитать
или записать значение в порт/из порта GS.

     Есть еще куча всяких полезных и бес-
полезных  команд  (аля  всякие  ковоксы и
т.п.),  но  для  прямого программинга они
нам не потребуются.

    Интерфейс со стороны General Sound

Внутренние порты:

#00, запись - открыть нужную страничку памяти
#01, чтение - прочитать содержимое регистра команд
#02, чтение - прочитать содержимое регистра данных
#03, запись - заслать данные для Speccy
#04, чтение - прочитать содержимое регистра состояния
#05, запись - сброс бита Command bit в регистре состояния
#06, запись - громкость канала A
#07, запись - громкость канала B
#08, запись - громкость канала C
#09, запись - громкость канала D

     Есть  еще  порты  #0A  и #0B, но они
вообще  нафиг  не нужны и не используются
(хотя,  кто хочет, по схеме может посмот-
реть, что они делают ;).

     А теперь подробнее...

1. OUT (#00),Page (открыть нужную страни-
чку  памяти) В General Sound расположение
памяти такое:

#0000-#3FFF - первые 16к ПЗУ
#4000-#7FFF - 16к ОЗУ
#8000-#FFFF - страницы ОЗУ

     Т.е.  странички в GS по 32 килобайта
и располагаются с адреса #8000. В нулевой
странице лежит ПЗУ целиком (т.е. по адре-
сам    #8000-#BFFF    тоже,   что   и   с
#0000-#3FFF,  а с #C000 - продолжение). В
первой странице вторые 16к - копия памяти
с  #4000-#7FFF,  а  первые 16к - обычные.
Вторая,  третья и т.д. страницы - обычные
страницы,  которые можно свободно исполь-
зовать.  В  базовом  варианте GS (128к) 3
странички: 2, 3 и 4, а у GS512 - 14 стра-
ниц  (не учитывая 1-ю и 0-ю). Короче, чем
больше  памяти,  тем  больше  страниц.  В
GS_ROM есть программа тестирования памяти
(после  reset),  так вот там вроде задано
аж 64 страницы...
     И  еще  по  поводу памяти. На 512-ти
килобайтной версии есть такой  аппаратный
"глюк":  при  включении одной из страниц,
включаются сразу две, т.е. 32к пропадают.
Поэтому   всего  у  GS  доступной  памяти
51232=480к!

2. IN A,(#01) (прочитать содержимое реги-
стра команд) Т.е. прочитать, что было по-
слано Спеком в регистр команд (#BB).

3. IN A,(#02) (прочитать содержимое реги-
стра данных) Т.е. прочитать, что было по-
слано Спеком в регистр данных (#B3).

4.  OUT  (#03),Data  (заслать  данные для
Speccy) По сути, передать данные для Спе-
ктрума,  т.е. их можно будет прочитать на
ZX из регистра данных (#B3).

5. IN A,(#04) (прочитать содержимое реги-
стра состояния)
Биты: 0 - Command bit
      7 - Data bit

     Назначение их такое же, как и у Sta-
tus register со стороны Speccy. Например,
если Command bit равен 1, значит поступи-
ла команда и надо бы ее выполнить.

Примечание: при работе с регистром данных
(чтение/запись), Data bit _автоматически_
изменяется, в отличие от Command bit! Ес-
ли  со  Спектрума послали число в регистр
команд,  то  Command bit, как и положено,
установится  в  1, но он не сбросится при
чтении его GS'ом.

6.  OUT (#05),A (сброс бита Command bit в
регистре  состояния)  Обычно  этим дается
знать  компу,  что команда в GS выполнена
(или принята к обслуживанию). Число засы-
лаемое в порт может быть любым.

Примерчик (алгоритмик) работы со всем этим:

>  Ждем пока Command bit не установится в
1. Этим мы дожидаемся когда со Спека пос-
тупит команда.

>  Берем значение из регистра команд (но-
мер команды). В зависимости от этого пры-
гаем куда надо.

>...прыгнули, допустим, сюда... Необходи-
мо  дать понять Спеку, что команда приня-
та,  чтобы он не повисал в ожидании. Т.е.
делаем  OUT (#05), а затем выполняем нуж-
ную прогу. Либо можно сделать иначе, если
что-то  нужно выполнить, а затем передать
результаты  компу: сначала все выполняем,
запихиваем  данные с помощью OUT (#03), a
затем  делаем  OUT  (#05). На Speccy надо
будет  дать  номер  команды, дождаться ее
выполнения и смело брать данные из Output
register. Вообще, здесь необходимо дейст-
вовать  логично,  и  если вы сделаете OUT
(#05)  раньше, чем OUT (#03), то рискуете
взять  на  Спектруме  не те данные. Ну да
мне кажется, что такие моменты и так дол-
жны быть ясны...

7. OUT (#06),Volume (громкость канала A)
   OUT (#07),Volume (громкость канала B)
   OUT (#08),Volume (громкость канала C)
   OUT (#09),Volume (громкость канала D)

     Громкости  задаются  числом  от 0 до
63(#3F).

               Вывод в ЦАП:

     Он  сделан  весьма  оригинально:  по
чтению из памяти. Так, сначала надо запи-
сать  нужное число в ячейку, а затем про-
читать  его оттуда. Канал (точнее сказать
ЦАП), зависит от адреса:

#6000-#60FF - для канала A
#6100-#61FF - для канала B
#6200-#62FF - для канала C
#6300-#63FF - для канала D

#6400-#64FF - для канала A
#6500-#65FF - для канала B
#6600-#66FF - для канала C
#6700-#67FF - для канала D

И т.д. до #7FFF.

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

               Прерывания:

     Частота  сигнала INT - 37500Гц, т.е.
прерывания приходят 37,5 тыс. раз в секу-
нду.  На них обычно и вешают проигрывалку
музы,  точнее, прогу, которая кидает дан-
ные в ЦАП'ы.
     Прикинем,  12000000/37500=320 тактов
на  прерывание. Это не так много, поэтому
надо рассчитывать, чтобы то, что висит на
прерываниях, не было слишком долгим, ина-
че основная прога будет сильно тормозить.

                 Практика

     Здесь  будут  рассмотрены конкретные
примеры по программированию GS на ASM'е.

1. Определение наличия GS.

;CY=1, если GS отсутствует

GS_DET  LD A,#7F:IN A,(#FE):CPL; самый удачный вариант -
        RRCA:RET C; принудительное отключение user'ом.

        LD A,#F3:OUT (#BB),A; restart GS

        LD B,200; время (ZX_INTs), в течение которого ждем
                ; ответа от GS - 4 сек.

GS_DET1 EI:HALT:DI
        IN A,(#BB):RRCA; смотрим, взял ли GS команду
        IN A,(#7B):JR NC,GS_DET2

;in  a,(#7b) нужен для того, чтобы прога не вылетала на Penta-
;gon'ах со включенным ZX-LPRINT3, т.к. если GS нет,то включит-
;ся ПЗУ ZX-LPRINT3!

        DJNZ GS_DET1
        SCF:RET; GS так и не ответил

GS_DET2 XOR A:OUT (#BB),A; след. этап - команда Reset Flags.
GS_DET3 EI:HALT:DI
        IN A,(#BB):CP #7E; проверяем, сбросились ли флаги...
        IN A,(#7B):RET Z
        DJNZ GS_DET3
        SCF:RET; время вышло, а флаги не сбросились

;Хотя... если вдруг из (#BB) всегда читается 0 (платы нет), то
;тест  ошибется... Тогда  надо бы (а надо ли вообще?) добавить
;тестов, например, заставить GS вернуть заданное вами значение
;(команда #10, порт 3).

2. Загрузчик программ в GS.

INITgs  CALL DEINIT; restart GS, если еще не делали
        LD HL,SUBPRG; адрес нашего вспомогательного загрузчика
        LD BC,#0080; длина блока

        LD A,C:CALL SD
        LD A,#14:CALL SC; загрузить блок в GS
        LD A,B:CALL SD:CALL WD
        XOR A:CALL SD:CALL WD; адрес загрузки #4000
        LD A,#40:CALL SD:CALL WD

LOOPiGS LD A,(HL):CALL SD:CALL WD; засылаем блок
        INC HL:DEC BC
        LD A,B:OR C:JR NZ,LOOPiGS

        XOR A:CALL SD; запускаем программу с адреса #4000
        LD A,#13:CALL SC
        LD A,#40:CALL SD

;А дальше работает наш загрузчик, который подготовит все  для
;загрузки нашей программы. Зачем такой изврат? Сначала, когда
;работало внутреннее ПО карты, стек был в  районе #4400, а  с
;#4000 шли всякие системные  переменные этого  ПО. А наш  ма-
;ленький загрузчик переставит стек и, если надо, включит нуж-
;ную нам страницу и т.п.

        LD HL,PROG; адрес и длина самой программы
        LD BC,EPRG-PROG_

LOOpiGS LD A,(HL):CALL SD:CALL WD; засылаем блок
        INC HL:DEC BC
        LD A,B:OR C:JR NZ,LOOpiGS
        RET


;А вот и сам загрузчик. Учитываем, что он должен работать  по
;адресу #4000, поэтому либо ассемблируем его  соответственно,
;либо не используем прямых адресаций (JP, CALL)

SUBPRG  DI
        LD SP,#407F
        LD HL,#4080:PUSH HL; адрес загрузки в GS
        LD C,2; порт
        LD DE,EPRG-PROG_

SUBPRG1 IN A,(4):BIT 7,A:JR Z,SUBPRG1; ждем прихода байта
        INI:DEC DE; кидаем его в память
        LD A,D:OR E:JR NZ,SUBPRG1
        RET

;Далее запускается наша прога. Шаблон такой:

PROG
        ORG #4080,$
PROG_   .....
        .....
EPRG

;org #4080,$ - это синтаксис STORM'а, а  как  это  делается  в
;других  ассемблерах, вам лучше знать. Смысл такой, что  прог-
;рамма физически располагается следом за основной, а ассембли-
;рована под адрес #4080.

3. Поиск свободной памяти в GS.

        LD HL,#FFFF
        LD A,#0F; максимум 15 страниц...

LPP0    OUT (0),A:LD (HL),A; в страницу записываем ее номер
        DEC A:CP 1:JR NZ,LPP0; все страницы кроме 1-й и 0-й

;Можно, конечно, и 1-ю посчитать, но работать с  ней не  очень
;удобно (аля 5-й банк в Speccy).

        INC A
        LD DE,PAGETAB; таблица номеров страниц
        LD IX,0; будем считать количество страниц

LPP1    OUT (0),A
        CP (HL):JR NZ,LPP2
        LD (DE),A; если номер страницы совпадает с числом,
        INC DE,HX; записанным в ней, то заносим ее в таблицу

LPP2    INC A:BIT 6,A:JR Z,LPP1; 15-я - последняя

;Дальше,  если  надо,  можем  очистить страницы. Но желательно
;учесть, что страниц может быть много, и лучше бы (если память
;позволяет) очищать через PUSH.

.....

;Можно передать информацию о количестве страниц Спеку.

LPP3_   LD A,HX:OUT (3),A
        OR A:JP Z,0; если страниц нет, то RESET
        IN A,(4):RLCA:JR C,$-3; ждем пока он их возьмет

.....

PAGETAB DS 14

4. Основной цикл и процедуры.

;Вариантов построения программы несколько. Если предполагается
;наличие  небольшого количества команд, то можно просто  поль-
;зоваться условиями CP #XX:JR Z,NNNN. Если же команд много, то
;лучше составить табличку с адресами.

GSMAIN1 IN A,(4):RRCA:JR NC,GSMAIN1; ждем команду
        IN A,(1); берем ее номер
        LD HL,GSMAIN1:PUSH HL; по RET вернемся в цикл

        OR A:JR Z,PROG0; команда 0
        CP 1:JR Z,PROG1; команда 1
        CP 2:JP Z,PROG2; команда 2
        CP #F3:JP Z,0; стандартные RESET'ы
        CP #F4:JP Z,0

;если команда отсутствует, то ничего не делаем, а просто дадим
;Спектруму  знать,  что  команда  принята,  а то он повиснет в
;ожидании

        OUT (5),A
        RET

;Вариант  программы, когда сначала сигналим Спектруму, что все
;ОК, а затем исполняем прогу.

PROG0   OUT (5),A
        .....
        RET

;Вариант  программы, когда сначала что-то исполняем, получаем,
;затем посылаем результат Спектруму, а потом уже сигналим, что
;прога выполнилась.

PROG1   .....
        OUT (5),A
        RET

;А можно и так выпендриться.

PROG2   OUT (5),A
        .....
        IN A,(4):RRCA:JR NC,$-3; ждем прихода любой команды
        .....
        OUT (5),A
        RET

;Т.е.  здесь  будет так: вы посылаете со Спектрума команду #2,
;GS говорит, что команда принята, что-то исполняет и ждет при-
;хода  любой команды. Потом опять что-то исполняет, и говорит,
;что все готово. Получается некий триггер.

5. Загрузка/выгрузка блоков данных.


а) Вариант 1 - коротко и ясно.

;Загрузка данных (GS side):

        LD HL,#8000; адрес в GS
        LD BC,#4000; длина блока

WAITD   IN A,(4):RLCA:JR NC,WAITD; ждем байт
        IN A,(2):LD (HL),A:INC HL; принимаем
        DEC C:JR NZ,WAITD; повторяем
        DEC B:JR NZ,WAITD

;Выгрузка (GS side):

        LD HL,#8000; адрес в GS
        LD BC,#4000; длина блока

WAITD_1 LD A,(HL):OUT (3),A; высылаем байт
WAITD_  IN A,(4):RLCA:JR C,WAITD_; ждем принятия
        INC HL
        DEC C:JR NZ,WAITD_1; повторяем
        DEC B:JR NZ,WAITD_1

;Загрузка данных в GS (ZX side):

        LD HL,#8000
        LD BC,0-#4000; (0 минус длина блока)

GS1     LD A,(HL):OUT (#B3),A; кидаем
        IN A,(#BB):RLCA:JR C,$-3; ждем принятия
        INC C:JR NZ,GS1
        INC B:JR NZ,GS1

;Выгрузка данных из GS (ZX side):

        LD HL,#8000
        LD BC,0-#4000

GS2     IN A,(#BB):RLCA:JR NC,$-3; ждем поступления
        IN A,(#B3):LD (HL),A; принимаем
        INC C:JR NZ,GS2
        INC B:JR NZ,GS2

б) Вариант 2 - ускоренный за счет раскры-
тия  циклов, но при этом должна быть фик-
сированная длина блока.

;Загрузка данных (GS side):

        LD HL,#8000; адрес в GS
        LD E,8; количество циклов
        LD C,2; порт

LOAD_2
.0      IN A,(4):RLCA:JP NC,$-3:INI; повторяем 256 (0) раз
        DEC E:JP NZ,LOAD_2; 256*8=2048 байт примем

;Загрузка данных в GS (ZX side):

        LD HL,#8000
        LD C,#B3; порт данных
        LD E,8; количество циклов

LOAD_2_
.0      OUTI:IN A,(#BB):RLCA:JP C,$-3; повторяем 256 (0) раз
        DEC E:JP NZ,LOAD_2_; 2048 байт передадим

;Смысл  понятен  - раскрываем циклы, тем самым жрем память, но
;немного  повышаем  быстродействие.  Остальные процедуры, надо
;будет, сами напишете... Кстати, цикл ожидания тоже можно  не-
;много развернуть:

;было

        IN A,(#BB):RLCA:JP C,$-3; 11+4+10=25 тактов

;стало

LOOOP
        IN A,(#BB):RLCA:JR NC,LOOOPE; 11+4+7=22 такта
        IN A,(#BB):RLCA:JR NC,LOOOPE
        IN A,(#BB):RLCA:JR C,LOOOP
LOOOPE

;Т.к. jr при невыполнении условия длится 7 тактов, то  мы эко-
;номим по 3 такта на цикл. Т.е. опрос происходит чаще.

в) И  еще  один не менее интересный  спо-
соб:

;Загрузка данных (GS side):

        LD HL,#8000; адрес
        LD C,1; порт

        IN A,(4):RLCA:JP NC,$-3;   
        INI;                        +повторяем нужное кол. раз
        IN A,(2):LD (HL),A:INC HL; /

        OUT (5),A;чтобы потом GS не подумал,что пришла команда

;Таким  образом,  прием  идет через 2 порта. Т.е. через порт 1
;(#BB на ZX) идет первый байт, а через порт 2 (#B3) - второй.

;Загрузка данных в GS (ZX side):

        LD HL,#8000; адрес
        LD C,#BB; порт

        OUTI;                         
        LD A,(HL):OUT (#B3),A:INC HL;  +повт. нужное кол. раз
        IN A,(#BB):RLCA:JP C,$-3;     /

;Воспользоваться данным методом для передачи данных из GS  не-
;возможно, т.к. в этом направлении порт только один.

6. Вывод звука.

а) Проигрывание сэмпла из памяти без пре-
рываний.

        LD HL,#8000; адрес сэмпла в GS
        LD BC,#8000; длина сэмпла
        LD DE,#6000; адрес ячейки ЦАП (#61XX, #62XX, #63XX)

LOOP    LD A,(HL); берем байт
        LD (DE),A; заносим в память
        LD A,(DE); кидаем в ЦАП
        .....; некоторая задержка (EI:HALT или NOP'ы ?!?)
        INC HL:DEC BC
        LD A,B:OR C:JR NZ,LOOP

б) Проигрывание сэмпла из памяти, с испо-
льзованием прерываний.

;программа, висящая на прерываниях, может выглядеть так:

;DE=SMP_ADR

INT     PUSH HL,AF
        LD HL,#6000; адрес ЦАП'а
        LD A,(DE):INC DE; берем очередной байт сэмпла
        LD (HL),A:LD A,(HL):INC H; ЦАП A
        LD (HL),A:LD A,(HL):INC H; ЦАП B
        LD (HL),A:LD A,(HL):INC H; ЦАП C
        LD (HL),A:LD A,(HL); ЦАП D
        POP AF,HL
        EI:RET

;Естественно,  что проигрывание не будет остановлено - нет та-
;кого условия, т.к. это только пример. Причем плохой. Прерыва-
;ние не должно быть слишком длинным, поэтому желательно убрать
;оттуда лишние команды, да и вообще, организовать работу по-д-
;ругому.  Сделать область памяти #6000-#60FF и т.д. буфером, в
;который мы быстро напихаем данные, а потом они будут играться
;со своей скоростью. А когда закончатся, снова напихаем!

;HL'=#6000

INT     EXX:EXA; EXA=EX AF,AF' 
        LD A,(HL); кидаем в ЦАП
        INC L:JR Z,FILLBUF; если буфер кончился...
        EXA:EXX
        EI:RET

FILLBUF EXX:EXA
        EI
FB1     LD HL,#8000; адрес сэмпла
        LD BC,256; длина буфера
        LD DE,#6000; адрес буфера
        LDIR
        LD (FB1+1),HL; сохраняем след. адрес
        RET

;Несмотря на то, что FILLBUF вызывается из прерываний и  раз;-
;решает их,  ничего  страшного  не произойдет, если успеть все
;сделать,  пока  буфер  не закончился опять. Также надо успеть
;закинуть  первый  байт  до того, как придет прерывание, иначе
;проиграется  не то и будет щелчок. Выходом из этого положения
;является  создание 2-го буфера, таким образом, пока один  иг-
;рается, заполняем второй.

;Кстати,  частота  дискретизации  в этом случае будет 37500Гц,
;поэтому,  если вам надо меньше, то придется изменить алгоритм
;поставить делители частоты (в FILLBUF), которые заставят один
;и тот же байт повторяться несколько раз.

7. Отладка.

     В  связи с отсутствием отладчика под
GS,  написанные программы нелегко отлажи-
вать.  Но  можно воспользоваться тем, что
GS  умеет  воспроизводить  звук, отследив
тем  самым этап, на котором прога глючит.
Напишем пищалку:

GSBEEP  LD BC,#0010,DE,#6000
GSBEEP1 LD A,120:LD (DE),A,A,(DE)
        DJNZ $
        LD A,128:LD (DE),A,A,(DE)
        DJNZ $
        DEC C:JR NZ,GSBEEP1

;сюда можно вставить паузу, на случай, если 2 бипа пойдут  че-
;рез небольшое время

        RET

     Теперь можно навставлять в программу
обращений к GSBEEP и слушать, сколько би-
пов будет до сбоя...
     Найти ошибку в алгоритме можно с по-
мощью  STS. Просто нужно программу для GS
поместить в память ZX-Spectrum (по тем же
адресам) и пошагово исполнять, только ко-
манды in a,(xx) пропускать, а в аккумуля-
тор заносить нужные значения вручную (out
(xx),a можно просто пропускать).
     Кстати,  на данный момент уже появи-
лась   низкоуровневая   эмуляция   GS   в
Z80Stealth,  там  же есть и отладчик, так
что  можно  пользоваться им, правда, вот,
только не очень удобно...
     Ну  вот  пока и все. Надеюсь, я ясно
все  об'яснил и показал. Теперь любой че-
ловек, знающий ассемблер Z80, может легко
разобраться  с  прямым  программированием
GS.  Кто знает, может скоро появится куча
хороших  программ под GS ? Никто не хочет
сделать  дебагер для Speccy с ядром в GS?
;-)))




Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
ENDец - послесловие и размышления Pixel'a о том как зарождалаксь ZX сцена.
Дискуссия - Письма. Вопросы и ответы: Кемерево,Баянов Даниил, МAXWELL/JURRASIC, ГАЛУЗЕ И. из города Минска, Гена из города Шахты, Виктор из г.Симферополя.
Приколы - техника безопасности на досуге.

В этот день...   18 августа