Синтезирование речи 🗣️

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

💬 Программа, позволяющая Spectrum разговаривать

Бейсик ZX:
10  CLEAR 25343: LET L=3
20  PRINT "Wait please…"
30  FOR A=25344 TO 25433: READ D: POKE A,D: NEXT A
40  FOR A=0 TO 25: POKE 25437,A*L+100
50  CLS: PRINT "Press any key, then say '";CHR$ (A+65);"'": PAUSE 0
60  RANDOMIZE USR 25344: NEXT A
70  CLS: INPUT "Enter phrase:"; LINE A$: PRINT A$
80  FOR A=1 TO LEN A$
90  IF A$(A)=" " THEN POKE 25433+A,32:NEXT A
100 IF A$(A)>="a" THEN LET A$(A)=CHR$(CODE A$(A)-32)
110 POKE 25433+A,(CODE A$(A)-65)*L+100: NEXT A: POKE 25433+A, 0
120 RANDOMIZE USR 25406: GOTO 70
130 DATA 243, 33, 0, 0, 17, 0, L, 6, 8, 203, 38, 219, 254, 203, 119, 40, 2, 203, 198, 16, 244, 35, 27, 122, 179, 32, 236, 251, 201
140 DATA 243, 33, 0, 0, 17, 0, L, 62, 7, 6, 8, 230, 239, 203, 6, 48, 2, 246, 16, 211, 254, 16, 244, 35, 27, 79, 122, 179, 121, 32, 234, 251, 201
150 DATA 33, 90, 99, 126, 183, 200, 254, 32, 32, 7, 6, 5, 116, 16, 253, 24, 8, 50, 32, 99, 229, 205, 29, 99, 225, 35, 24, 231

📋 Поясню некоторые строки:
  • 30 — считываются в память программы в кодах
  • 40…60 — записываются фонемы с микрофона
  • 70 — ввод фразы
  • 80…110 — фраза заносится в память в специальном формате
  • 120 — вызывается подпрограмма воспроизведения
  • 130…150 — подпрограммы в кодах
🚀 После запуска программа:
  1. Попросит немного подождать, считывая кодовую часть
  2. Затем Вы должны будете последовательно произнести в микрофон 26 фонем
  3. После этого Вы можете вводить любые фразы и наслаждаться их звучанием
🔤 При вводе этих фраз допустимы прописные и строчные латинские буквы и пробелы. Как будут звучать все остальные символы никому неизвестно. Ни в коем случае не стоит вводить знак «@», иначе программа просто «зависнет».

🛠️ Эта программа написана как пример, поэтому не содержит дополнительных сервисных возможностей (например, изменение отдельной фонемы), но Вы свободно можете их добавить.

🎚️ Для изменения длительности отдельной фонемы смените в десятой строке значение переменной L (в данном случае оно может быть от 1 до 6).

🔄 Если Вам не удалось с первого раза произнести все фонемы с удовлетворительным качеством, то можете попытаться еще раз, остановив программу с помощью клавиши BREAK и выполнив команду GO TO 40.

💾 Когда фонемы будут записаны, Вы можете сохранить их на ленте, набрав команду:
Бейсик ZX:
SAVE "name" CODE 25600,6656*L

📝 (число 6656 получилось путём умножения количества фонем (26) на 256)

💡 Лучше всего, если перед этим блоком будет записана вышеприведенная программа со следующей строкой:
Бейсик ZX:
35 LOAD "name" CODE 25600: GOTO 70

📀 Записать эту программу можно с помощью команды:
Бейсик ZX:
SAVE "SPEAKER" LINE 10

⚠️ К сожалению, 26 фонем — это маловато даже для английского языка, не говоря уже про русский. Их количество можно конечно увеличить, но при этом увеличится и объём занимаемой ими памяти. Тут уж ничего не сделаешь.

🇷🇺 Если Вы хотите чтобы Ваш ZX-Spectrum говорил по-русски, то Вы должны:
  1. Подключить русский шрифт (подробнее см. в [1])
  2. Увеличить число фонем
  3. Сменить все сообщения, выводимые на экран
🗣️ Количество русских фонем можно значительно сократить, если:
  • Не использовать мягкие согласные (в компьютерном исполнении их мягкость практически не слышна)
  • Гласные «Е», «Ё», «Ю» и «Я» заменять на «Й» и, соответственно, «Э», «О», «У» и «А»
  • Твердый знак также заменять буквой «Й»
💡 И в заключение один совет. Гораздо приятнее слышать нормальную человеческую речь, чем слова, выговариваемые в соответствии с правилами грамматики. Поэтому вводите слова как слышите:
  • Sinclair → sinkler
  • компьютер → кампйутер

🔊 Звук на прерываниях⚡

🎯 Проблема и её решение

⚠️ У всех подпрограмм, приведенных в предыдущих главах, есть один общий недостаток: на время их звучания выполнение основной программы приостанавливается. Это можно исправить, хотя и с трудом, используя второй режим прерываний. Те, кто знает, что это такое, могут смело пропустить следующие несколько абзацев.

📚 Напоминание о прерываниях

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

🎛️ Существует три различных режима прерываний: 0, 1 и 2, которые выбираются командами IM 0, IM 1 и IM 2. Стандартный режим имеет номер 1.

Режим 1 (стандартный):

📍 В качестве обработчика прерываний используется подпрограмма ПЗУ по адресу 56 (#38), которая следит за клавиатурой и счётчиком времени.

Режим 0:

🚫 Нам не интересен, так как в ZX-Spectrum он аналогичен режиму 1.

Режим 2:

🎉 А вот режим 2 и есть самое интересное!

🧠 Как работает режим 2 прерываний

⚡ Во втором режиме прерываний пятьдесят раз в секунду происходит следующее:
  1. Микропроцессор считывает с шины данных байт, называемый вектором прерывания
  2. Он передается в младший байт шины адреса
  3. В старший байт записывается содержимое регистра I
  4. По полученному таким образом адресу процессор считывает из памяти два байта
  5. Эти два байта интерпретируются как адрес подпрограммы обработки прерывания
⚠️ ZX-Spectrum устроен так, что вектор прерывания обычно равен 255 (#FF), но некоторые внешние устройства (например, AMX-mouse) могут генерировать другие вектора. Кроме того, в некоторых некачественных спектрумах вектор прерывания может изменяться абсолютно случайным образом.

🛠️ Установка собственного обработчика прерываний

📋 Последовательность действий:

  1. Запретить прерывания
  2. Записать в память адрес обработчика прерываний
  3. Задать в регистре I старший байт адреса указателя на обработчик
  4. Установить второй режим прерываний
  5. Разрешить прерывания

🔄 Возврат к стандартному режиму:

  1. Запретить прерывания
  2. Записать в регистр I число 63
  3. Установить первый режим прерываний
  4. Разрешить прерывания

📊 Таблица векторов прерываний​

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

⚠️ Правила составления обработчиков прерываний​

1. Скорость выполнения

🏎️ Обработчик прерывания должен выполняться за достаточно короткий промежуток времени.

2. Сохранение регистров

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

3. Осторожность с ПЗУ

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

4. Запрет вложенных прерываний

🔒 Вызов подпрограмм ПЗУ не желателен ещё и потому, что некоторые из них разрешают прерывания, что совершенно не допустимо во избежание вызова обработчика из самого себя. Любой обработчик должен работать при запрещенных прерываниях.

💡 Однако, использовать команду DI в самом начале процедуры не обязательно, так как это делается автоматически и Вам надо позаботиться только о разрешении прерываний перед выходом.

🔄 Совместимость со стандартным обработчиком

🤝 Если Вы не хотите лишаться возможностей, предоставляемых стандартным обработчиком прерываний, можете использовать в своей подпрограмме команду RST 56. А при использовании прерываний в бейсик-программах это просто необходимо, иначе будет заблокирована клавиатура.

🎵 Применение прерываний для звука

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


📝 Формат данных для звуковых эффектов

🎼 Условимся, что последовательность звуков будем задавать блоком данных в следующем формате:

Для каждой ноты в блоке данных должно быть по два байта:

  1. Первый байт - частота (1…253)
  2. Второй байт - длительность (0…255)

Контрольные коды:

  • 0 - переключение тон/шум
  • 254 - начало цикла
  • 255 - конец блока
🎛️ Изначально программа настроена на вывод чистого тона, но, если Вам необходимо получить шум, Вы можете переключить её на воспроизведение шума, вставив в данные байт, равный 0. Для повторного переключения на тон байт 0 должен встретиться еще раз.

⏹️ Когда программа встретит байт 255, вывод звука либо прекратится, либо вся последовательность повторится снова — в зависимости от заданного числа повторений.

🔄 Если в блоке данных встретится код 254, то при очередном повторении эффект начнется не с начала, а с места, где этот код встретился.

💻 Полный пакет программ для работы со звуком на прерываниях

Assembler Z80:
10   ORG 60000
20   JP SINIT      ; подключение прерываний
30   JP SSTOP      ; отключение прерываний
40   JP NEWFX      ; инициализация эффекта
50   MUTE LD (COUNT),A ; "заглушка"
60        RET
70   SINIT XOR A
80        LD (COUNT),A
90        LD A,24     ; код команды JR
100       LD (65535),A
110       LD A,195    ; код команды JP
120       LD (65524),A
130       LD HL,INTR  ; HL=адрес обработчика
140       LD (65525),HL
150       LD HL,65024
160       LD DE,65025
170       LD BC,256
180       LD (HL),255 ; адрес прерывания - 65535
190       LD A,H
200       LDIR        ; заполнение таблицы
210       DI
220       LD I,A
230       IM 2
240       EI
250       RET
260   SSTOP DI        ; отключение прерываний
270       LD A,63
280       LD I,A
290       IM 1
300       EI
310       RET
320   NEWFX DI        ; инициализация эффекта
330       LD (COUNT),A
340       XOR A
350       LD (FLAG),A
360       LD (ADDR),HL
370       LD (CURADD),HL
380       EI
390       RET
400   ADDR DEFW 0     ; начальный адрес блока данных
410   CURADD DEFW 0   ; текущий адрес в блоке данных
420   COUNT DEFB 0    ; количество повторений
430   FLAG DEFB 0     ; флаг тон/шум
440   INTR PUSH AF    ; обработчик прерывания
450        PUSH BC    ; сохранение регистров
460        PUSH DE
470        PUSH HL
480   TEST LD A,(COUNT) ; A=счетчик повторений
490        OR A        ; есть что играть?
500        JR Z,EXIT
510        LD HL,(CURADD) ; HL=текущий адрес
520   NEXT LD A,(HL)
530        INC HL
540        CP 254      ; A=254? (начало цикла)
550        JR NZ,CONT1
560        LD (ADDR),HL ; изменение начального адреса
570        JR NEXT
580   CONT1 CP 255     ; A=255? (конец)
590        JR NZ,CONT2
600        LD HL,(ADDR) ; восстановление начального
610        LD (CURADD),HL ; адреса блока данных
620        LD HL,COUNT
630        DEC (HL)    ; уменьшение счётчика повторений
640        JR TEST
650   CONT2 OR A       ; A=0? (переключатель)
660        JR NZ,CONT3
670        LD A,(FLAG)
680        CPL         ; инвертирование A
690        LD (FLAG),A
700        JR NEXT
710   CONT3 LD B,A     ; B=частота
720        LD C,(HL)   ; C=длительность
730        INC HL
740        LD (CURADD),HL ; сохранение текущего адреса
750        LD A,(FLAG) ; A=флаг
760        OR A        ; A=0?
770        LD A,7      ; A=цвет бордюра
780        JR NZ,NOISE
790   TONE XOR 16      ; воспроизведение тона
800        OUT (254),A
810        PUSH BC
820   PAUSE DJNZ PAUSE
830        POP BC
840        DEC C
850        JR NZ,TONE
860        JR EXIT
870   NOISE LD HL,1000 ; воспроизведение шума
880        LD D,A
890   NOIS2 LD A,(HL)
900        AND 248
910        OR D
920        OUT (254),A
930        PUSH BC
940   PAUS2 DJNZ PAUS2
950        POP BC
960        INC HL
970        DEC C
980        JR NZ,NOIS2
990   EXIT POP HL      ; восстановление регистров
1000       POP DE
1010       POP BC
1020       POP AF
1030       RST 56      ; вызов стандартного обработчика
1040       RET         ; возврат

🚀 Порядок использования этого пакета

1. Инициализация

🏁 В начале работы нужно вызвать подпрограмму SINIT, которая включит 2-ой режим прерываний.

2. Воспроизведение звука

🎵 В тот момент, когда Вам нужно получить звук надо:
  • Занести в регистр HL адрес блока данных
  • В регистр А - число повторений
  • Вызвать подпрограмму NEWFX

3. Управление звуком

🎛️ Если Вам по какой-либо причине надо:
  • Выключить звук
  • Продлить его звучание
Занесите в регистр А новое число повторений и вызовите подпрограмму MUTE. Кроме того, эту подпрограмму можно использовать для включения последнего звучавшего эффекта.

4. Завершение работы

🛑 По окончании работы (или если в программе предусмотрены обращения к дисководу) следует вызвать подпрограмму SSTOP, которая восстановит стандартный режим прерываний.

📍 Адреса подпрограмм​

🔢 Серия команд JP в начале пакета сделана для удобства. Благодаря ей все подпрограммы этого пакета могут вызываться по соседним адресам:
ПодпрограммаАдресHex
SINIT60000#EA60
SSTOP60003#EA63
NEWFX60006#EA66
MUTE60009#EA69

⚠️ Ограничения​

🎶 К сожалению, с помощью прерываний невозможно получить достаточно чистый тон. Поэтому, а также из-за совсем не музыкального формата данных, эта программа вряд-ли может использоваться для создания музыки. Но для звуковых эффектов она в самый раз.

🎮 Пример использования​

Assembler Z80:
10   ORG 50000
20   CALL 60000
30   LD HL,SNDFX
40   LD A,3
50   CALL 60006
60   RET
70   SNDFX DEFB 200,5,250,4,200,5,100,10,75,13,50,20,255

🔄 Строку 70 можно заменить на следующую:
Assembler Z80:
70   SNDFX DEFB 50,20,75,13,100,10,200,5,250,4,50,20,255

💡 Практический совет

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

🎉 Удачи в создании параллельных звуковых эффектов!
⚡
  • Like
Реакции: Xander