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

▍ 640 килобайт хватит всем, или как впихнуть невпихуемое


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

Как вы помните, видеопамять ПЭВМ «Микроша» занимает, грубо 78х30 символов, либо 2340 байт или 2,2 килобайта. Из характеристик ПЭВМ, оперативной памяти всего 32 кБ. И если я хочу показывать какие-либо мультики на данном вычислительном устройстве, мне нужно где-то хранить кадры. Если просто взять отдельные кадры, сохранить их в ОЗУ, и далее по очереди их выводить, то получится:

$32kB\div2,2kB \approx14$


Всего 14 кадров, и это без учёта размещения кода, который их будет выводить!

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

  • Делать вычислительную отрисовку, то есть картинку выводить с помощью формул, рисуя это с помощью формул. Как уже говорил, это медленно и мне такой вариант не подходит.
  • Использовать сжатие. Например, алгоритмы RLE и LZ77, неплохая статья на хабре. Вариант вполне достойный, но узнал я о нём, уже после после того как сделал свой вариант.
  • Хранить разницу между кадрами. Этот вариант хорош тем, если каждый кадр меняется не сильно, по сравнению с предыдущим, то diff будет занимать мало места. Алгоритм отлично подходит для задачи вращения. Но нужно помнить, что при полной смене кадра, объём разницы может занимать тройной размер видеопамяти, что весьма расточительно и тут проще применить банальное копирование областей памяти.

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

Первый кадр или фрейм — это просто картинка, которая копируется функцией memcpy в область памяти.


Первый фрейм.

Функция memcpy достаточно простая и грех не показать её код:

	; bc: number of bytes to copy
	; de: source block
	; hl: target block
memcpy:
	mov     a,b         ;Copy register B to register A
	ora     c           ;Bitwise OR of A and C into register A
	rz                  ;Return if the zero-flag is set high.
loop:
	ldax    d          ;Load A from the address pointed by DE
	mov     m,a         ;Store A into the address pointed by HL
	inx     d          ;Increment DE
	inx     h          ;Increment HL
	dcx     b          ;Decrement BC   (does not affect Flags)
	mov     a,b         ;Copy B to A    (so as to compare BC with zero)
	ora     c           ;A = A | C      (set zero)
	jnz     loop        ;Jump to 'loop:' if the zero-flag is not set.
	ret                 ;Return

После первого кадра, который является просто слепком видеопамяти, идут другие фреймы, которые представляют собой структуру:

  1. Количество элементов фрейма (2 байта).
  2. Адрес изменения (2 байта).
  3. Символ изменения (1 байт)
  4. Адрес изменения
  5. ...
  6. Последний фрейм содержит в количестве элементов фрейма «невозможное» число 0xFFFF, что свидетельствует, что мы подошли к концу.

Проще посмотреть код:

initial_frame:
  db 020h, 020h, 020h ...
...
frame_001: dw 029eh
  dw 772fh
  db 020h
  dw 7730h
  db 020h
  dw 7731h
...
frame_002: dw 0275h
  dw 7732h
  db 020h
  dw 7733h
  db 020h
...

frame_016: dw 0ffffh

initial_frame: — это первая картинка, которая просто копируется.
frame_001: — первый diff-фрейм. Первые два байта dw 029eh — это количество изменений. Два следующих байта dw 772fh — это адрес куда внести изменения. Последний байт db 020h — символ изменения (в данном случае пробел). Последний фрейм frame_016: dw 0ffffh содержит «невозможное число».

В программе converter, которая и генерирует этот ассемблеровский файл (я разбирал её в предыдущей части), конвертация идёт следующим образом:

save_to_asmfile(new_canvas_m, old_canvas_m, frames++);
tmp_m = old_canvas_m;
old_canvas_m = new_canvas_m;
new_canvas_m = tmp_m;

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

Алгоритм генерации сжатых фреймов вроде понятен, осталось понять как уместить мультфильм вращения в памяти. Совершенно очевидно, что 360 кадров, даже сжатых, никак не влезет. И я начал эмпирическим путём подбирать шаг, с которым проводить вращение, так чтобы это влезло в ОЗУ, но было понятно, что на экране происходит трёхмерное вращение картинки, а не просто набор какие-то случайных кадров.

Самым естественным было генерация кадра, каждые 20 градусов поворота, но при таком шаге количество фреймов получалось очень большим, и они никак не хотели умещаться в памяти. В результате, самым оптимальным по размещению в памяти, но не самым красивым, стало генерация кадра через каждые 30 градусов. Однако всё же оно оказалось резковатым. Поэтому пришлось добавить дополнительный фрейм на 80, 110, 260 и 280 градусах. Это не очень элегантно, но зато при этом вращение выглядит более естественно. Результат меня вполне устроил.


Получившийся «мультфильм» вращения, эмуляция в консоли.

Можно скомпилировать получившийся файл frames.asm и взвесить, сколько же он будет занимать в памяти.



24 килобайта, вполне сносный объём. Ещё остаётся 8 кБ на код программы и музыку, есть где разбежаться.

▍ Процедура показа мультиков


Как ни странно, но процедура смены фреймов оказалась очень простой. Не буду лукавить, я подсмотрел её у begoon в его демке, только адаптировал под свой формат diff-фреймов.

nit_frame_start:
	lxi b, (78*30);размер
	lxi d, initial_frame
	lxi h, video_area
	call memcpy
	lxi h, frame_001 ;7C52
next_frame:
	push h
	call long_frame_delay
	pop h
	mov a, m
	inx h
	mov c, a
	mov b, m
	inx h
	ora b ; если всё по нулям, значит следующий фрейм
	jz next_frame
	cpi 0ffh
	jz init_frame_start; подошли к концу
frame_loop:
	mov e, m
	inx h
	mov d, m
	inx h
	mov a, m
	inx h
	stax d
	dcx b
	mov a, c
	ora b
	jnz frame_loop
	jmp next_frame

Вначале идёт копирование инициализационного фрейма в область видеопамяти, после чего вызывается процедура задержки (между каждым фреймом), затем чего считывается количество изменений в регистровую пару BC, если она равна «несуществующему» числу 0xFFFF, то начинаем сначала. Иначе, в регистровую пару DC считывается адрес, где изменить, и считывается в аккумулятор A символ изменения, и записывается по адресу DC. Декрементируется регистровая пара BC, и если она не равна нулю, процедура повторяется.

Если вам кажется это сложным, то это самый простой кусок кода во всей демке. Дальше будет хуже.

▍ Функция задержки


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

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

long_frame_delay:
	call frame_delay
	call frame_delay
	call frame_delay
	call frame_delay
	call frame_delay
	call frame_delay
	ret

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

frame_delay:
	lxi d, 2000
frame_delay_loop:
	dcx d
	mov a, d
	ora e
	jnz frame_delay_loop
	ret

Как это работает и как посчитать задержку? Ничего сложного, всё основано на времени выполнения отдельных инструкций. Регистровая пара DE выполняет роль счётчика, далее она декрементируется, загружается содержимое регистра D в аккумулятор и с помощью логического «или» аккумулятора с регистром E идёт проверка на нуль, если не нуль, цикл повторяется. Все ассемблеровские инструкции dcx, mov, ora, jnz — имеют время исполнения в тактах процессора. Процессор в ПЭВМ «Микроша» работает на частоте 1,77 МГц, соответственно каждый такт процессора занимает:

$1\;Такт = \frac{1}{1770000}\;Секунды$


Количество тактов для выполнения каждой инструкции мне удалось найти в великолепной книге "Intel 8080 Assembly Language Programming Manual". Если записать функцию выше вместе с количеством тактов, то получится.

frame_delay:
	lxi d, 2000		;10
frame_delay_loop:
	dcx d			;5
	mov a, d		;5
	ora e			;4
	jnz frame_delay_loop; 10
	ret				;10

Инструкциями lxi и ret можно пренебречь, но всё же внесу их в общую формулу.

$(10 + 10) + (5 + 5 + 4 + 10) \cdot 2000) = 48020\;Тактов$


Итого 48020 тактов (20 тактов занимают ret и lxi). Количество итераций определяется константой 2000. Лично я выбрал такую, мне она подошла наиболее полно. Эта задержка будет длиться:

$T=48020\div1770000=0,027\;с$


Долго игрался с разными константами, остановился на этой. Четыре вызова этой процедуры, получится, грубо, около 0,1 с.
Функция задержки между фреймами long_frame_delay содержит шесть вызовов этой функции, и как раз занимает 0,163 с.

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

▍ Да будет звук!


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

Изначально я пошёл вообще самым тернистым путём: скачал все журналы «Радио» с 1985 по 1995 года и просмотрел все статьи по ЭВМ за этот период. Были и по программированию звука, но на деле они меня больше запутали, чем помогли.

Как оказалось, наиболее полезная и важная информация, которая мне была нужна уже была под рукой в той чудесной книжечке, которая шла с ПЭВМ «Микроша». Для начала следует взглянуть на схему организации аудио на этом вычислительном устройстве. Часто ли вы смотрите схему вашего компьютера, для его программирования? Вот, а тут приходилось часто.


Схема организации звука на ПЭВМ «Микроша».

Схема генерации звука выполнена на таймере КР580ВИ53, при этом для аудиовыхода используется канал 2. Обратите внимание, на цифру 92 — этот сигнал идёт к параллельному порту КР580ВВ55, порт C, бит №1. Устанавливая или снимая этот бит в порту, можно включать или отключать воспроизведение звука. Это нужно, если мы играем музыку по нотам, то там есть кроме воспроизведения ещё и паузы, вот это позволяет включать или отключать звук.

Не хочу подробно останавливаться на всех режимах работы БИС таймера КР580ВИ53, они подробно изложены в мануале, и будем честны, именно сейчас нас мало интересуют. В данном проекте нас нужен режим 3.



Проще говоря, в этом режиме можно генерировать прямоугольный сигнал заданной частоты, и это то что мне нужно. В документации на таймер-счётчик есть также пример кода, однако он не будет работать без настроек порта КР580ВВ55. А вот в документации на порт есть уже полный пример кода, как настроить таймер-счётчик и вывести на нём звук. Читайте документацию полностью и внимательно!



Вкратце поясню, что тут происходит. В регистровую пару HL записывается адрес регистра БИС таймера КР580ВИ53 — 0xD803. По данному адресу записывается конфигурационное число 0xB6, что говорит что мы будем передавать двоичные данные, таймер-счётчик работает в режиме 3, используется младший и старший байт, работает второй канал (10110110: 0 — двоичный, *11 — режим 3, 11 мл, ст байт, 10 — канал 2).

Далее декрементируется регистровая пара HL, и она начинает содержать значение адреса регистра таймера 0xD802, по данному адресу уже записывается значение таймера, которое и будет звучать. В данном случае 0x2010 (сначала младший байт, потом старший).

Для включения динамической головки, в регистровую пару HL записывается адрес регистра управляющего слова параллельно порта КР580ВВ55. Запись 0x80, говорит о том, что весь порт C идёт на выход. Идёт декремент адреса, и мы пишем в порт C значение 0x06 (можно было бы только 0x02, так как управление идёт только первым битом).

Если данный код скомпилировать и выполнить на «Микроше», он будет весьма неприятно верещать.

Всё это я оформил в весьма удобных функциях. Как показала практика, инициализировать звук не обязательно, потому что программа «Монитор» и так его инициализирует, можно сразу его использовать уже. Но приведу пример выше в более оформленном виде.

m55regcfg  equ 0c003h
portc_reg  equ 0c002h
tim_regcfg equ 0d803h

init_sound:; Никогда не вызывается. Работает и так
    lxi h, m55regcfg; регистр управляющего слова для клавиатуры
    mvi m, 80h ;все на вывод
    lxi h, tim_regcfg; запись команды для таймера
    mvi m, 0b6h ;10110110 (0 - двоичный, *11 - режим 3, 11 мл, ст байт, 10 - канал 2)
    ret

disable_sound:
    mvi a, 0
    sta portc_reg
    ret
enable_sound:
    mvi a, 06h
    sta portc_reg
    ret

Как видно, всё пока относительно просто.

Теперь важный момент, а как определить частоту с которой будет генерироваться сигнал? И как пересчитать частоту в те магические цифры, которые будут уже записаны в регистр таймера 0xD802?

Всё просто, таймер работает на частоте системной шины, как и процессор, с частотой 1,77 МГц. Таким образом двухбайтовое магическое число для записи в регистр рассчитывается следующим образом:

$magic=\frac{1770000}{f}$


Где f — нужная частота звучания, и когда в примере из книжки я записал магическое число magic=0x2010, то частота звучания была равна примерно 216 Гц.

Всё, теперь всё готово, чтобы делать музыку!

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


Во всей демке эта часть оказалась самой сложной и затратной по времени.

Поскольку познаний музыки и умения у меня немного, было принято решение использовать готовую мелодию.

Надо понимать, что в силу аппаратных особенностей, как я уже говорил, мелодия может быть только монофонической (один инструмент), без аккордов и второй руки. То есть, этакая «ученическая» игра одним пальцем. Это невероятно сужало круг поисков подходящей мелодии. Также, поскольку я не хотел вручную перебивать коэффициенты, то хотелось сразу взять подходящий формат и мой выбор пал на формат midi. Не буду вдаваться в подробности этого формата, всё хорошо изложено как на википедии, так и в статье на хабре. Грубо говоря, midi хранит номер ноты, её длительность, длительность паузы (в качестве паузы может выступать нулевая нота). А из номера ноты можно легко получить частоту, а уже из неё магическое число для записи в регистр таймера КР580ВИ53.

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



И приводится также удобная формула по переводу номера ноты в частоту:

$f_{n}=2^{\frac{n}{12}}\cdot440\;Гц$


Далее просто подставить полученный результат в формулу расчёта магического числа, и можно получить результат.

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

Я посетил тысячи сайтов с midi-файлами, были скачаны всевозможные торренты, хранящие гигабайты этих файлов. Самое большое удивление вызвало то, что до сих пор живы сайты типа «Отправь смс на короткий номер» и даже wap-сайты, с этими предложениями. Их же кто-то оплачивает!

Основная сложность заключалась в том, что я искал монофонический midi-файл, он должен был быть достаточно длинным (более минуты), при этом должен хорошо зацикливаться (логично звучать в цикле), не иметь никаких аккордов и партий левой руки. Вообще, больше всего мне хотелось использовать «Турецкий марш» В.А. Моцарта, потому что это была первая электронная музыка, которую я услышал ещё в часах, но подходящую длинную midi найти мне не удалось.

После гигабайтов скачанных и переслушанных midi-файлов ко мне начало уже приходить отчаяние и начал думать, что быть может проще будет написать мелодию самому. И именно в момент моего практически полного отчаяния мне пишет man_of_letters:
Помню ты возился с midi. Если хочешь посмотреть питоновский код для проигрывания midi на писиспикере, то он у меня есть.
И скидывает мне следующий код:

from mido import MidiFile
from pathlib import Path
import winsound
import time

def noteToFreq(note):
    a = 440 #frequency of A (coomon value is 440Hz)
    return (a / 32) * (2 ** ((note - 9) / 12))
  
f1_in_object = Path(r'c:\Users\User\Downloads\1.mid')

mid = MidiFile(f1_in_object, clip=True)
print('number of tracks', len(mid.tracks))

note_time_scale = 4
pause_time_scale = 4

note = {'wait':0, 'freq':0, 'dur': 0 }

last_note = None
for x in mid.tracks[1]:
    if x.type == 'note_on':
        note['wait'] = x.time
        note['freq'] = int(noteToFreq(x.note))
    if x.type == 'note_off':
        note['dur'] = x.time
        if note['wait']>4:
            time.sleep(note['wait'] * pause_time_scale / 1000)
        else:
            time.sleep(0.01)
        note_length = int(note['dur'] * note_time_scale)
        winsound.Beep(note['freq'], note_length)
        last_note = note

И вместе с этим скидывает пример midi-файлов, который он гонял с этим примером — ППК «Воскрешение».

Для ностальгирующих оригинал мелодии:
И это всё вместе оказалось практически исчерпывающим ответом на вопросы, которые у меня возникали! У man_of_letters было несколько примеров файлов, из которых я подобрал идеальный для моих целей.

▍ Заключение второй части




Итак, мне удалось подобраться к тому, как выводить музыку, но впереди ещё много работы: выбор мелодии, адаптация её для микроши и ещё большая борьба с видеоподсистемой…

Все статьи серии с «Микрошей»:



Комментарии (31)


  1. gears
    06.06.2022 12:36
    +1

    Еще немного и прям нормальную графику реализуешь. Респект!


    1. dlinyj Автор
      06.06.2022 12:45

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


  1. amphasis
    06.06.2022 15:08
    +4

    А не пробовали зайти со стороны того, что при использовании только символов псевдографики достаточно всего одного байта на два знакоместа, а половина кадров у вас дублируется (при вращении от 180 до 360 градусов)? При тех же вводных (78х30 символов, вращение, использование только псевдографики, 24 кб на хранение кадров) можно было бы закодировать кадры с шагом 9 градусов, что дало бы анимацию гораздо плавнее.


    1. dlinyj Автор
      06.06.2022 15:16
      +1

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

      Так-то можно взять ваш способ, сжатие, diff и можно было упихать сильно больше кадров.


  1. webhamster
    06.06.2022 16:28
    +1

    А есть видео, где вращение сделано вообще без задержек? Ну то есть максимум скорости анимации увидеть, без музыки, только анимация напрямую.


    1. dlinyj Автор
      06.06.2022 16:29

      Не совсем понял вопрос. Перечитал, понял.
      Музыка никак не влияет на задержки, так как задержки между кадрами всё равно нужно ставить. А если убрать задержку, будет просто мазанное изображение.


      1. webhamster
        06.06.2022 17:21

        Ну вот хотелось на него посмотреть, в динамике.


        1. dlinyj Автор
          06.06.2022 17:31

          Не всё сразу, терпение. А так, можете посмотреть гифку вначале статьи.


  1. eurol
    06.06.2022 16:35

    В функции memcpy можно выкинуть первые три строчки, а метку саму переместить ниже. Экономия — 3 байта кода!!!


    1. dlinyj Автор
      06.06.2022 16:55

      Можно, но не нужно. Лишние проверки никому не вредили.


      1. eurol
        07.06.2022 08:32

        А кто предлагает убирать проверки? Проверка как раз стоит в конце цикла.
        Не трушный подход к разработке под 8080. Отказаться от ТРЕХ байтов экономии… И ради чего? Да у нас этой памяти завались просто???


        1. dlinyj Автор
          07.06.2022 09:47

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


          1. eurol
            07.06.2022 10:27

            Да, есть у меня особенность такая — когда вижу бессмысленные действия, всегда хочется обратить на них внимание, чтобы что-то упростить.
            Насчет килобайтов — сомневаюсь. Разве что алгоритм использовать другой.
            А насчет «место трогать нельзя» — это кто сказал?
            ;memcpy:
            ; mov a,b ;Copy register B to register A
            ; ora c ;Bitwise OR of A and C into register A
            ; rz ;Return if the zero-flag is set high.
            loop:
            ldax d ;Load A from the address pointed by DE
            mov m,a ;Store A into the address pointed by HL
            inx d ;Increment DE
            inx h ;Increment HL
            dcx b ;Decrement BC (does not affect Flags)
            memcpy:
            mov a,b ;Copy B to A (so as to compare BC with zero)
            ora c ;A = A | C (set zero)
            jnz loop ;Jump to 'loop:' if the zero-flag is not set.
            ret ;Return

            Да, на несколько тактов дольше выполняется первая проверка. Принципиально ли это? Если нужно увеличить скорость, можно попробовать развернуть цикл, использовать другой способ копирования. Да и просто проверять не содержимое пары, а отдельно содержимое младшего регистра, и лишь затем делать декремент старшего и его проверку — это даст выигрыш (в среднем) на каждом шаге. Тогда код будет выглядеть чуть иначе:

            memcpy:
            mov a,b ;Copy register B to register A
            ora c ;Bitwise OR of A and C into register A
            rz ;Return if the zero-flag is set high.
            inr b
            loop:
            ldax d ;Load A from the address pointed by DE
            mov m,a ;Store A into the address pointed by HL
            inx d ;Increment DE
            inx h ;Increment HL
            ; dcx b ;Decrement BC (does not affect Flags)
            dcr c
            jnz loop
            drc b
            jnz loop
            ; mov a,b ;Copy B to A (so as to compare BC with zero)
            ; ora c ;A = A | C (set zero)
            ; jnz loop ;Jump to 'loop:' if the zero-flag is not set.
            ret ;Return

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


            1. dlinyj Автор
              07.06.2022 11:44

              Там просто главная засада это во фреймах, в способах сжатия и оптимизации. Вот где главная экономия. На данный момент у меня свободно осталось несколько килобайт (может даже 4-5), так что эти экономии в байтах мне были не интересны.

              А вот сделать лишний кадр, для того чтобы вращение было плавнее — интересно. Но уже пороху не хватило.


              1. eurol
                07.06.2022 12:52

                Сколько тактов тратится на рисование самого большого (сложного) кадра?


                1. dlinyj Автор
                  07.06.2022 12:57

                  Ну я там приводил пример задержки, она где-то 48020*6, плюс копирование (там плавающая величина), ну где-то 2 тысчячи сравнений. Можете посчитать сами. Другое дело, зачем…


                  1. eurol
                    07.06.2022 14:29

                    Не задержка, а время отрисовки.
                    Если добавить в информацию о кадре время на его отрисовку, то можно учесть его. Тогда и кадры будут рисоваться строго через равные промежутки, и музыка будет идти в соответствии с ритмом.
                    Насколько я понял, возможности использовать таймер для измерения временных промежутков в Микроше нет. Или есть?


                    1. dlinyj Автор
                      07.06.2022 14:35

                      Насколько я понял, возможности использовать таймер для измерения временных промежутков в Микроше нет. Или есть?


                      Нету.

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


  1. Albert2009ru
    06.06.2022 18:13

    Добрый день! Объясните старому неучу, воспроизведение звука в данном случае - это частотно-импульсная модуляция, оно же широтно-импульсная модуляция с постоянным коэффициентом заполнения 50% ??? Или?

    Т.е. тональность буззера будет меняться за счёт изменения частоты прямоугольных импульсов от м/с счетчика, а не от изменения коэффициента заполнения?


    1. dlinyj Автор
      06.06.2022 18:43
      +1

      ШИМ тут не при чём. Это обычный генератор прямоугольных импульсов заданной частоты, вроде же подробно всё расписал.


      1. checkpoint
        07.06.2022 13:20

        А почему не сделать именно ШИМ ? Это позволит регулировать громкость звучания отдельных нот. А если вы подвесите конденсатор в несколько нФ параллельно динамику, то звучание будет качественней (ФНЧ).

        И еще, бит управления тоже можно модулировать (программно), у вас получится "второй голос".

        Вспомните старый добрый Scream Tracker который манипулируя всего одним битом умудрялся выводить на PC спикер четыре голосовых канала. В середине 90-х это было умопомрачительное зрелище.

        https://www.youtube.com/watch?v=_bj6n41Cr1w&t=858s


        1. dlinyj Автор
          07.06.2022 13:30

          У вас реальная частота процессора около 300 кГц. С учётом того, что надо выводить видео, это будет невозможно.


          1. checkpoint
            07.06.2022 13:58

            Не уверен. Во-первых нужно задействовать контроллер прерываний. Во-вторых, есть ли на Микроше контроллер ПДП ? Если есть, то почти всю работу по проигрыванию музыки можно переложить на аппаратуру. Или наоборот - работу по отрисовке.


            1. dlinyj Автор
              07.06.2022 14:01

              Если вы прочитаете первую статью, то там было сказано: не вносить аппаратных изменений.

              ПДП только для видео. И больше его не для чего использовать нельзя. Таймер не может генерировать прерывания, вообще, никак.

              Если бы было всё так просто, то всё по другому бы реализовал.


              1. checkpoint
                07.06.2022 14:08

                Никаких аппаратных изменений (кроме конденсатора) я не предлагаю.

                Я не верю в то, что на данном ПЭВМ нет системного таймера способного генерировать прерывания. Где можно ознакомиться со схемой ?

                ПДП для видео это уже большое дело, Вы разгрузите ЦП и сможете на нем генерировать музыку.


                1. dlinyj Автор
                  07.06.2022 14:29

                  Я не верю в то, что на данном ПЭВМ нет системного таймера способного генерировать прерывания. Где можно ознакомиться со схемой ?


                  Зачем мне верить или не верить, я не проповедник. Гугл творит чудеса. Схемы Микроши и различная литература

                  ПДП для видео это уже большое дело, Вы разгрузите ЦП и сможете на нем генерировать музыку.

                  Ощущение будто бы вы статью не читали.


                1. dlinyj Автор
                  07.06.2022 14:32

                  В самой первой статье саги, говорил следующее:

                  В «Микроше» установлен микропроцессор КР580ВМ80А, работающий на тактовой частоте 1,77 МГц, быстродействие которого составляет всего 300 тыс. оп/с (грубо 300 кГц!). Это достаточно мало даже для тех лет. Например, первый микроконтроллер AVR AT90S1200 просто мейнфрейм в сравнении с этим процессором (хотя рассматривать их в одной линейке не совсем корректно). У него хотя бы каждая операция выполнялась за один такт, была команда умножения и таймер генерировал прерывания.


                  В целом, вы можете купить себе «Микрошу», и заткнуть меня за пояс многоголосым воспроизведением звука. Лично для меня даже такое решение оказалось весьма сложным. Сейчас у вас есть серия моих статей с подробным разбором как всё работает.


  1. Albert2009ru
    06.06.2022 18:45

    А для создания ШИМ разве используется генератор каких-то других импульсов? Можете не отвечать. Спасибо за статью ????????????


    1. dlinyj Автор
      06.06.2022 19:02
      +1

      Как я помню, этим таймером можно и ШИМ делать. Но это лежит вне моих задач.


  1. Manwe_SandS
    07.06.2022 12:21
    +1

    Круто, только это не PPK, а Эдуард Артемьев в оригинале, музыка из фильма "Сибириада". PPK сделали только ремейк.


    1. dlinyj Автор
      07.06.2022 12:38
      +2

      Это именно ППК, но сделана на основе это мелодии. Отличие там есть, она сделана по мотивам, но таки самостоятельное произведение.

      Вы можете сравнить сами звучание:



      Более подробный разбор был в "Откуда ноты растут". Ну и если бы не ППК, вы бы даже не знали об этой мелодии.