Программирование звуковых эффектов

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

Тон🎶

Начнем с эффектов, основанных на генерации тона. Что они из себя представляют? Примерно то же, что и эффекты на бейсике: звук с плавно изменяющейся частотой. Для полного сходства можно даже использовать ту же подпрограмму из ПЗУ:
Assembler Z80:
10   LD B,30     ; B=количество нот
20   LD HL,100   ; HL=начальная частота
30   LOOP LD DE,2  ; DE=длительность
40   PUSH HL     ; сохранение HL
50   PUSH BC     ; сохранение BC
60   CALL 949    ; вызов подпрограммы ПЗУ
70   POP BC      ; восстановление BC
80   POP HL      ; восстановление HL
90   LD DE,25    ; DE=шаг изменения частоты
100  ADD HL,DE   ; увеличение HL
110  DJNZ LOOP   ; цикл
120  RET         ; возврат в бейсик

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

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

🔄 Несколько модифицировать данный эффект можно изменяя не всю частоту, а только её младший байт:
Assembler Z80:
10   LD B,60     ; B=количество нот
20   LD C,50     ; C=шаг изменения частоты
30   LD HL,300   ; HL=начальная частота
40   LOOP LD DE,10  ; DE=длительность
50   PUSH HL     ; сохранение HL
60   PUSH BC     ; сохранение BC
70   CALL 949    ; вызов подпрограммы ПЗУ
80   POP BC      ; восстановление BC
90   POP HL      ; восстановление HL
100  LD A,L      ; увели-
110  ADD C       ; чение
120  LD L,A      ; L
130  DJNZ LOOP   ; цикл
140  RET         ; возврат в бейсик

🔧 От использования подпрограмм ПЗУ перейдем к созданию собственных. Звучание предыдущих примеров можно сделать более плавным и протяжным:
Assembler Z80:
10   DI          ; запрет прерываний
20   XOR A       ; A=цвет бордюра (0)
30   LD B,255    ; B=начальная частота
40   LD C,255    ; C=длительность
50   BEEP XOR 16 ; инвертирование бита D4
60   OUT (254),A ; вывод A в порт 254
70   PUSH BC     ; сохранение BC
80   LOOP DJNZ LOOP ; задержка
90   POP BC      ; восстановление BC
100  DEC B       ; уменьшение B
110  DEC C       ; C=C-1
120  JR NZ,BEEP  ; если C<>0, то цикл
130  EI          ; разрешение прерываний
140  RET         ; возврат в бейсик

🚀 Возможности этого эффекта легко увеличиваются:
Assembler Z80:
10   DI          ; запрет прерываний
20   XOR A       ; A=цвет бордюра (0)
30   LD B,255    ; B=количество нот
40   LD C,1      ; C=начальная частота
50   LOOP1 PUSH BC ; сохранение BC
60   LD B,5      ; B=длительность
70   LOOP2 XOR 16 ; инвертирование бита D4
80   OUT (254),A ; вывод A в порт 254
90   PUSH BC     ; сохранение BC
100  LD B,C      ; B=C
110  LOOP3 DJNZ LOOP3 ; задержка
120  POP BC      ; восстановление BC
130  DJNZ LOOP2  ; цикл
140  POP BC      ; восстановление BC
150  INC C       ; увеличение C
160  DJNZ LOOP1  ; второй цикл
170  EI          ; разрешение прерываний
180  RET         ; возврат в бейсик

👉 Скорее всего, Вы заметили, что в первом варианте задержка уменьшается, а во втором увеличивается. Ничто не мешает Вам сменить направление её изменения. Для этого только стоит выбрать команду DEC (уменьшение) или INC (увеличение). Также можно настроить и длительность, начальную частоту и т.п.

🎛️ Можно изменить шаг смещения частоты:
Assembler Z80:
10   DI          ; запрет прерываний
20   XOR A       ; A=цвет бордюра (0)
30   LD B,255    ; B=начальная частота
40   LD C,255    ; C=длительность
50   BEEP XOR 16 ; инвертирование бита D4
60   OUT (254),A ; вывод A в порт 254
70   PUSH BC     ; сохранение BC
80   LOOP DJNZ LOOP ; задержка
90   POP BC      ; восстановление BC
100  EX AF,AF'   ; смена регистров A и F на альтернативные
110  LD A,B      ; A=B
120  SUB 3       ; A=A-3
130  LD B,A      ; B=A
140  EX AF,AF'   ; обратная смена регистров
150  DEC C       ; C=C-1
160  JR NZ,BEEP  ; если C<>0, то цикл
170  EI          ; разрешение прерываний
180  RET         ; возврат в бейсик

💡 Теперь воспользуемся свободой действий, которую нам предоставляет программирование в кодах. Напишем нестандартную процедуру воспроизведения:
Assembler Z80:
10   DI          ; запрет прерываний
20   LD D,10     ; D=задержка 1
30   LD E,100    ; E=задержка 2
40   LD C,255    ; C=длительность
50   XOR A       ; A=цвет бордюра (0)
60   LOOP1 XOR 16 ; инвертирование бита D4
70   OUT (254),A ; вывод A в порт 254
80   LD B,D      ; B=D
90   LOOP2 DJNZ LOOP2 ; задержка
100  XOR 16      ; инвертирование бита D4
110  OUT (254),A ; вывод A в порт 254
120  LD B,E      ; B=E
130  LOOP3 DJNZ LOOP3 ; задержка
140  INC D       ; увеличение D
150  INC E       ; увеличение E
160  DEC C       ; C=C-1
170  JR NZ,LOOP1 ; если C<>0, то цикл
180  EI          ; разрешение прерываний
190  RET         ; возврат в бейсик

👉 Здесь тоже можно долго и старательно варьировать параметры
🌀 Довольно интересный эффект получается при использовании регистра R, значение младших семи битов которого увеличивается после выполнения очередного машинного цикла. Этот эффект можно условно назвать «полушумом».

Вот пример такого эффекта:
Assembler Z80:
10   DI          ; запрет прерываний
20   LD C,53     ; C=задержка 1
30   LD B,207    ; B=задержка 2
40   LD E,203    ; E=длительность
50   LD D,0      ; D=цвет бордюра
60   LD A,128    ; A=темп (0/128)
70   LD R,A      ; R=A
80   BEGIN LD A,R ; A=R
90   PAUS1 DEC A ; A=A-1
100  JR NZ,PAUS1 ; если A<>0, то цикл
110  LD A,D      ; A=D
120  OR 16       ; установка бита D4
130  OUT (254),A ; вывод A в порт 254
140  LD A,C      ; A=C
150  PAUS2 DEC A ; A=A-1
160  JR NZ,PAUS2 ; если A<>0, то цикл
170  LD A,D      ; A=D
180  OUT (254),A ; вывод A в порт 254
190  LD A,B      ; A=B
200  PAUS3 DEC A ; A=A-1
210  JR NZ,PAUS3 ; если A<>0, то цикл
220  INC C       ; C=C+1
230  INC B       ; B=B+1
240  DEC E       ; E=E-1
250  JR NZ,BEGIN ; если E<>0, то цикл
260  EI          ; разрешение прерываний
270  RET         ; возврат

📊 Значения в строках 20 и 30 определяют задержки между перепадами уровня, в строке 40 — длительность эффекта, в строке 50 — цвет бордюра, а в строке 60 — темп.
В строке 60 имеет смысл употреблять только значения 0 и 128, так как все остальные будут аналогичны этим.
В строках 220 и 230 Вы можете установить закон изменения обеих задержек (команды INC, DEC и NOP с регистрами B и C в любой комбинации).

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

Шум

Что из себя представляет шум? Это последовательность импульсов случайной длительности. Поэтому для его создания нам понадобятся случайные данные. Где их взять? Единственный подходящий источник — ПЗУ, в котором записан интерпретатор бейсика. ПЗУ расположено с адреса 0 до 16383 (#3FFF). Правда, значения эти будут не совсем случайные, точнее совсем не случайные, но нас они устроят.

🎛️ Создавать шум можно двумя разными способами. Они немного отличаются по звучанию. Первый из них состоит в выводе в порт значений, прочитанных из ПЗУ. Второй же — в использовании этих значений в качестве задержки.

👉 При генерации шума любым из этих способов прерывания запрещать не обязательно, так как в шуме потрескивание слышно не будет.
🔸 Первый способ (вывод значений из ПЗУ):
Assembler Z80:
10   LD HL,0     ; HL=адрес ПЗУ
20   LD BC,1000  ; BC=длительность
30   BEGIN PUSH BC ; сохранение BC
40   LD A,(HL)   ; A=содержимое ячейки ПЗУ
50   AND 240     ; сброс битов бордюра
60   OUT (254),A ; вывод A в порт 254
70   LD B,50     ; B=частота
80   LOOP DJNZ LOOP ; задержка
90   INC HL      ; HL=HL+1
100  POP BC      ; восстановление BC
110  DEC BC      ; BC=BC-1
120  LD A,B      ; BC=
130  OR C        ; 0 ?
140  JR NZ,BEGIN ; цикл
150  RET         ; возврат в бейсик

🔹 Второй способ (использование значений как задержки):
Assembler Z80:
10   LD HL,0     ; HL=адрес ПЗУ
20   LD BC,1000  ; BC=длительность
30   XOR A       ; A=цвет бордюра (0)
40   BEGIN PUSH BC ; сохранение BC
50   XOR 16      ; инвертирование бита D4
60   OUT (254),A ; вывод A в порт 254
70   LD B,(HL)   ; B=содержимое ячейки ПЗУ
80   LOOP1 DJNZ LOOP1 ; задержка
90   LD B,40     ; B=частота
100  LOOP2 DJNZ LOOP2 ; задержка
110  INC HL      ; HL=HL+1
120  POP BC      ; восстановление BC
130  DEC BC      ; BC=BC-1
140  LD D,A      ; сохранение A
150  LD A,B      ; BC=
160  OR C        ; 0 ?
170  LD A,D      ; восстановление A
180  JR NZ,BEGIN ; цикл
190  RET         ; возврат в бейсик

👉 Если в этих эффектах команду INC HL заменить на INC L, то их звучание станет как бы скачущим. Это происходит из-за того, что данные берутся не из всего заданного объема ПЗУ, а из его части размером 256 байт. Причем, когда эта часть кончается, чтение продолжается с её начала.

🔧 Попробуйте поизменять другие параметры.
📈 Следующий шаг — шум с изменяющейся частотой. Выглядеть это будет примерно так:

Assembler Z80:
10   LD HL,0     ; HL=адрес ПЗУ
20   LD B,100    ; B=длина эффекта
30   LD C,10     ; C=начальная частота
40   LOOP1 PUSH BC ; сохранение BC
50   LD B,20     ; B=длительность
60   LOOP2 LD A,(HL) ; A=содержимое ячейки ПЗУ
70   AND 240     ; сброс битов бордюра
80   OUT (254),A ; вывод A в порт 254
90   PUSH BC     ; сохранение BC
100  LD B,C      ; B=C
110  LOOP3 DJNZ LOOP3 ; задержка
120  INC HL      ; HL=HL+1
130  POP BC      ; восстановление BC
140  DJNZ LOOP2  ; цикл
150  POP BC      ; восстановление BC
160  INC C       ; увеличение задержки
170  DJNZ LOOP1  ; цикл
180  RET         ; возврат в бейсик

🔄 Этот эффект тоже можно изменить до неузнаваемости: сменить длину, частоту, длительность, шаг смещения частоты, направление смещения частоты (команда INC С или DEC С), вид шума (команда INC HL или INC L).
🎛️ Еще один вариант шумового эффекта:
Assembler Z80:
10   LD HL,0     ; HL=адрес ПЗУ
20   LD D,100    ; D=задержка 1
30   LD E,10     ; E=задержка 2
40   LD C,255    ; C=длительность
50   XOR A       ; A=цвет бордюра (0)
60   BEGIN XOR 16 ; инвертирование бита D4
70   OUT (254),A ; вывод A в порт 254
80   LD B,(HL)   ; B=содержимое ячейки ПЗУ
90   LOOP1 DJNZ LOOP1 ; задержка 1
100  LD B,D      ; B=D
110  LOOP2 DJNZ LOOP2 ; задержка 2
120  XOR 16      ; инвертирование бита D4
130  OUT (254),A ; вывод A в порт 254
140  LD B,(HL)   ; B=содержимое ячейки ПЗУ
150  LOOP3 DJNZ LOOP3 ; задержка 3
160  LD B,E      ; B=E
170  LOOP4 DJNZ LOOP4 ; задержка 4
180  INC HL      ; HL=HL+1
190  INC D       ; увеличение D
200  INC E       ; увеличение E
210  DEC C       ; C=C-1
220  JR NZ,BEGIN ; если C<>0, то цикл
230  RET         ; возврат в бейсик

👉 Над этим примером можно издеваться так же долго, как и над всеми предыдущими.
🔧 Все эффекты, приведенные в данной главе, можно рассматривать как заготовки. Чтобы получить конечные варианты Вам, возможно, придется потрудиться. Как уже было сказано, все они имеют огромное число вариантов. Кроме того, Вы можете объединить несколько эффектов вместе, или вызывать их на выполнение в цикле и т.п.

✅ Все эффекты написаны так, чтобы можно было изменять максимальное число параметров. В случаях конкретного применения их можно значительно упрощать. Вот пример скомбинированного и упрощенного эффекта:
Assembler Z80:
10   DI          ; запрет прерываний
20   LD E,100    ; E=длительность цикла
30   LD C,0      ; C=цвет бордюра
40   LD B,4      ; B=число циклов
50   LD L,1      ; L=смещение частоты
60   LD H,30     ; H=начальная частота
70   LOOP1 LD D,E ; D=E
80   LOOP2 LD A,C ; A=C
90   XOR 16      ; инвертирование бита D4
100  OUT (254),A ; вывод A в порт 254
110  LD C,A      ; C=A
120  LD A,H      ; A=H
130  ADD A,L     ; прибавить L к A
140  LD H,A      ; H=A
150  LOOP3 DEC A ; A=A-1
160  JR NZ,LOOP3 ; если A<>0, то цикл
170  DEC D       ; D=D-1
180  JR NZ,LOOP2 ; если D<>0, то цикл
190  LD A,L      ; A=L
200  NEG         ; изменение знака регистра A
210  LD L,A      ; L=A
220  DJNZ LOOP1  ; цикл
230  EI          ; разрешение прерываний
240  RET         ; возврат

👉 Все вышеприведенные эффекты Вы можете настроить под собственные нужды. Например, изменить цвет бордюра или сделать так, чтобы сигнал кроме динамика выводился на магнитофон (для этого все команды XOR 16 надо заменить на XOR 24, a AND 240 на AND 248).

Комплексы эффектов

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

🔗 Если эффекты разнообразны и воспроизводятся различными подпрограммами, то в таблице эффектов лучше всего хранить адреса этих подпрограмм. В таком случае для воспроизведения эффектов можно воспользоваться такой подпрограммой:
Assembler Z80:
10   ADD A,A     ; A=A*2
20   LD E,A      ; DE
30   LD D,0      ; =A
40   LD HL,TABLE ; HL=адрес таблицы
50   ADD HL,DE   ; HL=HL+DE
60   LD E,(HL)   ; DE=
70   INC HL      ; адрес
80   LD D,(HL)   ; подпрограммы
90   EX DE,HL    ; обменять значениями HL и DE
100  JP (HL)     ; вызов подпрограммы эффекта
110  TABLE DEFW … ; таблица адресов

👉 Перед вызовом этой подпрограммы в регистр A надо занести номер эффекта. Не забудьте также заполнить таблицу адресами эффектов. Естественно, сами эффекты должны располагаться по указанным адресам. Ни в коем случае не стоит указывать номер эффекта больше числа описанных в таблице подпрограмм, иначе компьютер «зависнет» или «сбросится». Учтите также, что эта подпрограмма может работать максимум со 127 эффектами.

🔄 Если эффекты однотипные и воспроизводятся одной и той же подпрограммой, то в таблице эффектов можно хранить параметры этой подпрограммы. Вот пример, использующий данный способ:
Assembler Z80:
10   LD E,A      ; A
20   ADD A,A     ; =
30   ADD A,E     ; A*3
40   LD E,A      ; DE
50   LD D,0      ; =A
60   LD HL,TABLE ; HL=адрес таблицы
70   ADD HL,DE   ; HL=HL+DE
80   LD C,(HL)   ; C=длительность
90   INC HL      ; HL=HL+1
100  LD E,(HL)   ; E=частота
110  INC HL      ; HL=HL+1
120  LD A,(HL)   ; A=изменение частоты
130  LD (CHNG),A ; установка изменения частоты
140  DI          ; запрет прерываний
150  XOR A       ; A=цвет бордюра (0)
160  BEGIN XOR 16 ; инвертирование бита D4
170  OUT (254),A ; вывод A в порт 254
180  LD B,E      ; B=E
190  PAUSE DJNZ PAUSE ; задержка
200  CHNG NOP    ; резерв для изменения частоты
210  DEC C       ; C=C-1
220  JR NZ,BEGIN ; если C<>0, то цикл
230  EI          ; разрешение прерываний
240  RET         ; возврат
250  TABLE DEFB 0,0,28 ; таблица
260  DEFB 0,0,29 ;
270  DEFB 0,128,28 ; эффектов
280  DEFB 0,128,29 ;

👉 Перед вызовом этой подпрограммы в регистр A надо занести номер эффекта. В приведенной программе уже созданы четыре эффекта, но Вы можете изменить их или увеличить их число. При вызове эффекта, номер которого больше числа описанных подпрограмм, также может произойти что‑нибудь ужасное.

📝 В этом примере на описание эффекта отводится три байта. Первый байт — длительность эффекта, второй — начальная частота, а третий — способ изменения частоты. Третий байт может принимать значения 0, 28 и 29. Что означает, соответственно, сохранение, увеличение и уменьшение частоты. Заносить какие‑либо другие значения в этот байт не стоит, так как это может привести к непредсказуемым последствиям.

✅ Приведенная программа является только примером. Вы можете настроить её для работы с абсолютно любым эффектом. Эта подпрограмма может работать максимум с 85 эффектами.

👉 В обеих приведенных подпрограммах нумерация эффектов начинается с нуля.

Управление громкостью

⚠️ Вообще‑то, ZX‑Spectrum абсолютно не приспособлен для управления громкостью, но её все‑таки можно изменять в небольших пределах благодаря относительно высокой тактовой частоте микропроцессора Z‑80 (около 3.5 МГц).

📚 Как уже говорилось ранее, когда Вы посылаете сигнал на динамик, его мембрана изменяет свое положение. Если во время её движения послать обратный сигнал, то она вернется в исходное положение, не пройдя нужного пути. Таким образом, амплитуда (соответственно и громкость) будет меньше обычной. Для примера, сравните звучание таких двух практически одинаковых программ:

PROG1:
Assembler Z80:
10   DI          ; запрет прерываний
20   LD B,255    ; B=частота
30   LD C,255    ; C=длительность
40   XOR A       ; A=цвет бордюра (0)
50   LOOP1 XOR 16 ; инвертирование бита D4
60   OUT (254),A ; вывод A в порт 254
70   PUSH BC     ; сохранение BC
80   LOOP2 DJNZ LOOP2 ; задержка
90   POP BC      ; восстановление BC
100  DEC C       ; C=C-1
110  JR NZ,LOOP1 ; если C<>0, то цикл
120  EI          ; разрешение прерываний
130  RET         ; возврат в бейсик

PROG2:
Assembler Z80:
10   DI          ;
20   LD B,255    ;
30   LD C,255    ;
40   XOR A       ;
50   LOOP1 XOR 16 ; все
60   OUT (254),A ;
70   XOR 16      ; (кроме этих двух строк,
80   OUT (254),A ; повторяющих предыдущие)
90   PUSH BC     ;
100  LOOP2 DJNZ LOOP2 ; аналогично
110  POP BC      ; предыдущему
120  DEC C       ; примеру !
130  JR NZ,LOOP1 ;
140  EI          ;
150  RET         ;

👉 Когда Вы их наберете на ассемблере и запустите, Вы сильно удивитесь. Однако против фактов не попрёшь — вторая подпрограмма будет звучать в несколько раз тише первой!

🎚️ Приведу текст более универсальной подпрограммы:
Assembler Z80:
10   DI          ; запрет прерываний
20   LD B,50     ; B=громкость
30   LD C,50     ; C=частота
40   LD D,15     ; D=длительность
50   LD E,50     ; E=количество нот
60   XOR A       ; A=цвет бордюра (0)
70   LOOP1 PUSH DE ; сохранение DE
80   LOOP2 PUSH BC ; сохранение BC
90   XOR 16      ; инвертирование бита D4
100  OUT (254),A ; вывод A в порт 254
110  LOOP3 DJNZ LOOP3 ; задержка (громкость)
120  XOR 16      ; инвертирование бита D4
130  OUT (254),A ; вывод A в порт 254
140  LD B,C      ; B=C
150  LOOP4 DJNZ LOOP4 ; задержка (частота)
160  POP BC      ; восстановление BC
170  DEC D       ; D=D-1
180  JR NZ,LOOP2 ; если D<>0, то цикл
190  POP DE      ; восстановление DE
200  NOP         ; резерв
210  NOP         ; резерв
220  NOP         ; резерв
230  DEC E       ; E=E-1
240  JR NZ,LOOP1 ; если E<>0, то цикл
250  EI          ; разрешение прерываний
260  RET         ; возврат в бейсик

👉 В этом эффекте Вы можете подобрать нужную громкость. Но не очень увлекайтесь: при значениях больших, чем 40‑50 громкость уже не увеличивается. Необходимо заметить, что при изменении громкости во время воспроизведения меняется и частота. Поэтому надо предусмотреть её компенсацию.

💡 Теперь о трех байтах резерва (команды NOP). Они предназначены для изменения громкости (регистр В), частоты (регистр С) и длительности (регистр D). Если Вы замените эти три NOPa на команды DEC В (уменьшение громкости), INC С (компенсация частоты) и INC D (увеличение длительности), то у Вас получится интересный эффект с затухающей громкостью.

Управление тембром

Для управления тембром ZX‑Spectrum так же не приспособлен, как и для управления громкостью. Но, как и громкостью, им можно управлять в небольших пределах.

📊 Как Вы уже знаете, тембр зависит от формы звуковой волны. Так как приспособить для формирования тембра подпрограмму управления громкостью не так‑то просто, при создании различных форм волны придется обходиться только двумя уровнями громкости. Сделать это можно, например, с помощью такой подпрограммы:
Assembler Z80:
10   DI          ; запрет прерываний
20   LOOP1 LD B,8 ; B=темп
30   LOOP2 XOR A ; A=цвет бордюра (0)
40   RLC E       ; скроллинг тембра через флаг CY
50   JR NC,NOSIGN ; если флаг CY=0, то перейти на NOSIGN
60   OR 16       ; установка бита D4 регистра A
70   NOSIGN OUT (254),A ; вывод A в порт 254
80   LD D,H      ; D=задержка
90   PAUSE DEC D ; D=D-1
100  JR NZ,PAUSE ; если D<>0, то цикл
110  DJNZ LOOP2  ; цикл темпа
120  LD A,H      ; A=H
130  ADD A,L     ; прибавить L к A
140  LD H,A      ; H=A
150  DEC C       ; C=C-1
160  JR NZ,LOOP1 ; если C<>0, то цикл
170  EI          ; разрешение прерываний
180  RET         ; возврат

👉 Перед вызовом этой подпрограммы необходимо в регистр С занести длительность ноты, в регистр Н — частоту, в регистр L — смещение частоты (возможны отрицательные значения и ноль), а в регистр Е — тембр. Учтите, что не все значения регистра Е имеют смысл. Так, при значении Е равном 255 или 0 Вы вообще ничего не услышите, а тембры 1 и 128 будут звучать абсолютно одинаково и т.д.

📌 По существу, имеет смысл использовать только следующие 19 тембров:
1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 37, 43, 45, 51 и 85.
Все остальные значения на слух воспринимаются аналогично этим.

✅ Приведенная программа является только примером. Вы вполне можете написать свою, воспроизводящую звук с 16‑ти битовым тембром, или с тембром, заранее записанным в память с магнитофонного входа.


🎶 Удачи в экспериментах со звуком на вашем ZX Spectrum! 👾