В предыдущей части мы только начали входить во вкус создания демки, как статья неожиданно закончилась на самом интересном месте. Не буду сильно томить и продолжу описывать свой квест по созданию этой интересной программы. Борьба за размеры памяти, задержки, звук, всё в этой серии.
▍ 640 килобайт хватит всем, или как впихнуть невпихуемое
Остановился я на том, что в процессе компиляции раскадровка не влезла в размер памяти, который может быть непосредственно адресован на архитектуре i8080. И тут следует внимательно разобраться, кто виноват и что с этим делать.
Как вы помните, видеопамять ПЭВМ «Микроша» занимает, грубо 78х30 символов, либо 2340 байт или 2,2 килобайта. Из характеристик ПЭВМ, оперативной памяти всего 32 кБ. И если я хочу показывать какие-либо мультики на данном вычислительном устройстве, мне нужно где-то хранить кадры. Если просто взять отдельные кадры, сохранить их в ОЗУ, и далее по очереди их выводить, то получится:
Всего 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
После первого кадра, который является просто слепком видеопамяти, идут другие фреймы, которые представляют собой структуру:
- Количество элементов фрейма (2 байта).
- Адрес изменения (2 байта).
- Символ изменения (1 байт)
- Адрес изменения
- ...
- Последний фрейм содержит в количестве элементов фрейма «невозможное» число
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 МГц, соответственно каждый такт процессора занимает:Количество тактов для выполнения каждой инструкции мне удалось найти в великолепной книге "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
можно пренебречь, но всё же внесу их в общую формулу.Итого 48020 тактов (20 тактов занимают
ret
и lxi
). Количество итераций определяется константой 2000. Лично я выбрал такую, мне она подошла наиболее полно. Эта задержка будет длиться:Долго игрался с разными константами, остановился на этой. Четыре вызова этой процедуры, получится, грубо, около 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 МГц. Таким образом двухбайтовое магическое число для записи в регистр рассчитывается следующим образом:
Где
f
— нужная частота звучания, и когда в примере из книжки я записал магическое число magic=0x2010
, то частота звучания была равна примерно 216 Гц.Всё, теперь всё готово, чтобы делать музыку!
▍ Поиск музыки и способов конвертации
Во всей демке эта часть оказалась самой сложной и затратной по времени.
Поскольку познаний музыки и умения у меня немного, было принято решение использовать готовую мелодию.
Надо понимать, что в силу аппаратных особенностей, как я уже говорил, мелодия может быть только монофонической (один инструмент), без аккордов и второй руки. То есть, этакая «ученическая» игра одним пальцем. Это невероятно сужало круг поисков подходящей мелодии. Также, поскольку я не хотел вручную перебивать коэффициенты, то хотелось сразу взять подходящий формат и мой выбор пал на формат midi. Не буду вдаваться в подробности этого формата, всё хорошо изложено как на википедии, так и в статье на хабре. Грубо говоря, midi хранит номер ноты, её длительность, длительность паузы (в качестве паузы может выступать нулевая нота). А из номера ноты можно легко получить частоту, а уже из неё магическое число для записи в регистр таймера КР580ВИ53.
На вот этом сайте даётся хороший разбор, как это всё пересчитать. И там же приводится весьма удобная и достойная картинка соответствие номера ноты в MIDI и частоты.
И приводится также удобная формула по переводу номера ноты в частоту:
Далее просто подставить полученный результат в формулу расчёта магического числа, и можно получить результат.
После того как я понял общую механику работы 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 было несколько примеров файлов, из которых я подобрал идеальный для моих целей.
▍ Заключение второй части
Итак, мне удалось подобраться к тому, как выводить музыку, но впереди ещё много работы: выбор мелодии, адаптация её для микроши и ещё большая борьба с видеоподсистемой…
Все статьи серии с «Микрошей»:
- Знакомство с «Микрошей»
- Создание демки специально для HABR — Часть 1
- Создание демки специально для HABR — Часть 2< — Вы тут
- Создание демки специально для HABR — Часть 3
Комментарии (31)
amphasis
06.06.2022 15:08+4А не пробовали зайти со стороны того, что при использовании только символов псевдографики достаточно всего одного байта на два знакоместа, а половина кадров у вас дублируется (при вращении от 180 до 360 градусов)? При тех же вводных (78х30 символов, вращение, использование только псевдографики, 24 кб на хранение кадров) можно было бы закодировать кадры с шагом 9 градусов, что дало бы анимацию гораздо плавнее.
dlinyj Автор
06.06.2022 15:16+1Не, не пробовал. Там ещё есть куда и что оптимизировать, но можно было в этой части закопаться ещё на пол года. А мне хотелось увидеть конечный результат, может и не идеальный, но работающий. Поэтому, как уже сказал, добился того, что меня устроило.
Так-то можно взять ваш способ, сжатие, diff и можно было упихать сильно больше кадров.
webhamster
06.06.2022 16:28+1А есть видео, где вращение сделано вообще без задержек? Ну то есть максимум скорости анимации увидеть, без музыки, только анимация напрямую.
dlinyj Автор
06.06.2022 16:29Не совсем понял вопрос. Перечитал, понял.
Музыка никак не влияет на задержки, так как задержки между кадрами всё равно нужно ставить. А если убрать задержку, будет просто мазанное изображение.
eurol
06.06.2022 16:35В функции memcpy можно выкинуть первые три строчки, а метку саму переместить ниже. Экономия — 3 байта кода!!!
dlinyj Автор
06.06.2022 16:55Можно, но не нужно. Лишние проверки никому не вредили.
eurol
07.06.2022 08:32А кто предлагает убирать проверки? Проверка как раз стоит в конце цикла.
Не трушный подход к разработке под 8080. Отказаться от ТРЕХ байтов экономии… И ради чего? Да у нас этой памяти завались просто???dlinyj Автор
07.06.2022 09:47Понимаю, что когда видишь проект и не понимаешь масштабов, хочется зацепиться хоть за что-нибудь, и находишь область: «вот оно». Там можно килобайты оптимизировать, если поработать с кодом. А это место трогать нельзя.
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 — тоже.
К сожалению, не знаю, как сделать здесь код более красиво.dlinyj Автор
07.06.2022 11:44Там просто главная засада это во фреймах, в способах сжатия и оптимизации. Вот где главная экономия. На данный момент у меня свободно осталось несколько килобайт (может даже 4-5), так что эти экономии в байтах мне были не интересны.
А вот сделать лишний кадр, для того чтобы вращение было плавнее — интересно. Но уже пороху не хватило.eurol
07.06.2022 12:52Сколько тактов тратится на рисование самого большого (сложного) кадра?
dlinyj Автор
07.06.2022 12:57Ну я там приводил пример задержки, она где-то 48020*6, плюс копирование (там плавающая величина), ну где-то 2 тысчячи сравнений. Можете посчитать сами. Другое дело, зачем…
eurol
07.06.2022 14:29Не задержка, а время отрисовки.
Если добавить в информацию о кадре время на его отрисовку, то можно учесть его. Тогда и кадры будут рисоваться строго через равные промежутки, и музыка будет идти в соответствии с ритмом.
Насколько я понял, возможности использовать таймер для измерения временных промежутков в Микроше нет. Или есть?dlinyj Автор
07.06.2022 14:35Насколько я понял, возможности использовать таймер для измерения временных промежутков в Микроше нет. Или есть?
Нету.
Да, музыку можно оптимизировать. Но там отрисовка каждого кадра плавающая. Но лично я на слух не слышу плавания мелодии (кроме перехода с портьеры). Так что смысла не вижу особо. Но как я сказал вам в личном сообщении, вы всегда можете взять и улучшить код, он открыт.
Albert2009ru
06.06.2022 18:13Добрый день! Объясните старому неучу, воспроизведение звука в данном случае - это частотно-импульсная модуляция, оно же широтно-импульсная модуляция с постоянным коэффициентом заполнения 50% ??? Или?
Т.е. тональность буззера будет меняться за счёт изменения частоты прямоугольных импульсов от м/с счетчика, а не от изменения коэффициента заполнения?
dlinyj Автор
06.06.2022 18:43+1ШИМ тут не при чём. Это обычный генератор прямоугольных импульсов заданной частоты, вроде же подробно всё расписал.
checkpoint
07.06.2022 13:20А почему не сделать именно ШИМ ? Это позволит регулировать громкость звучания отдельных нот. А если вы подвесите конденсатор в несколько нФ параллельно динамику, то звучание будет качественней (ФНЧ).
И еще, бит управления тоже можно модулировать (программно), у вас получится "второй голос".
Вспомните старый добрый Scream Tracker который манипулируя всего одним битом умудрялся выводить на PC спикер четыре голосовых канала. В середине 90-х это было умопомрачительное зрелище.
dlinyj Автор
07.06.2022 13:30У вас реальная частота процессора около 300 кГц. С учётом того, что надо выводить видео, это будет невозможно.
checkpoint
07.06.2022 13:58Не уверен. Во-первых нужно задействовать контроллер прерываний. Во-вторых, есть ли на Микроше контроллер ПДП ? Если есть, то почти всю работу по проигрыванию музыки можно переложить на аппаратуру. Или наоборот - работу по отрисовке.
dlinyj Автор
07.06.2022 14:01Если вы прочитаете первую статью, то там было сказано: не вносить аппаратных изменений.
ПДП только для видео. И больше его не для чего использовать нельзя. Таймер не может генерировать прерывания, вообще, никак.
Если бы было всё так просто, то всё по другому бы реализовал.checkpoint
07.06.2022 14:08Никаких аппаратных изменений (кроме конденсатора) я не предлагаю.
Я не верю в то, что на данном ПЭВМ нет системного таймера способного генерировать прерывания. Где можно ознакомиться со схемой ?
ПДП для видео это уже большое дело, Вы разгрузите ЦП и сможете на нем генерировать музыку.
dlinyj Автор
07.06.2022 14:29Я не верю в то, что на данном ПЭВМ нет системного таймера способного генерировать прерывания. Где можно ознакомиться со схемой ?
Зачем мне верить или не верить, я не проповедник. Гугл творит чудеса. Схемы Микроши и различная литератураПДП для видео это уже большое дело, Вы разгрузите ЦП и сможете на нем генерировать музыку.
Ощущение будто бы вы статью не читали.
dlinyj Автор
07.06.2022 14:32В самой первой статье саги, говорил следующее:
В «Микроше» установлен микропроцессор КР580ВМ80А, работающий на тактовой частоте 1,77 МГц, быстродействие которого составляет всего 300 тыс. оп/с (грубо 300 кГц!). Это достаточно мало даже для тех лет. Например, первый микроконтроллер AVR AT90S1200 просто мейнфрейм в сравнении с этим процессором (хотя рассматривать их в одной линейке не совсем корректно). У него хотя бы каждая операция выполнялась за один такт, была команда умножения и таймер генерировал прерывания.
В целом, вы можете купить себе «Микрошу», и заткнуть меня за пояс многоголосым воспроизведением звука. Лично для меня даже такое решение оказалось весьма сложным. Сейчас у вас есть серия моих статей с подробным разбором как всё работает.
Albert2009ru
06.06.2022 18:45А для создания ШИМ разве используется генератор каких-то других импульсов? Можете не отвечать. Спасибо за статью ????????????
dlinyj Автор
06.06.2022 19:02+1Как я помню, этим таймером можно и ШИМ делать. Но это лежит вне моих задач.
Manwe_SandS
07.06.2022 12:21+1Круто, только это не PPK, а Эдуард Артемьев в оригинале, музыка из фильма "Сибириада". PPK сделали только ремейк.
dlinyj Автор
07.06.2022 12:38+2Это именно ППК, но сделана на основе это мелодии. Отличие там есть, она сделана по мотивам, но таки самостоятельное произведение.
Вы можете сравнить сами звучание:
Более подробный разбор был в "Откуда ноты растут". Ну и если бы не ППК, вы бы даже не знали об этой мелодии.
gears
Еще немного и прям нормальную графику реализуешь. Респект!
dlinyj Автор
Спасибо! Думаю это максимум, что можно выжать из этого железа. Другое дело, что можно много классных игрушек сделать, было бы желание.