Попытка написать видеопроигрыватель для Sprinter computer
Есть еще над чем поработать.
This commit is contained in:
Константин Косарев 2026-07-31 22:31:28 +03:00
commit 026dede6bc
5 changed files with 937 additions and 0 deletions

854
source/main3.asm Normal file
View File

@ -0,0 +1,854 @@
device zxspectrum128
; ============================================================
; КОНСТАНТЫ
; ============================================================
VMODE:
.T40: EQU #02
.T80: EQU #03
.V320: EQU #81
.V640: EQU #82
.background: EQU #50
.sprite: EQU #50
BANKSIZE EQU #0A
; ---- Константы портов ----
CTC_CH2: EQU #12
CTC_CH3: EQU #13
; ---- Порты управления страницами фастрам ----
FastRAM:
.ON EQU #FB
.OFF EQU #7B
.SLOT0 EQU #5C
SYS_PORT:
.ON EQU #7C
.OFF EQU #3C
; ---- Коды частоты звука ----
SOUND_8KHZ EQU 0
SOUND_11KHZ EQU 1
SOUND_16KHZ EQU 2
SOUND_22KHZ EQU 3
; ============================================================
; ПОДКЛЮЧАЕМ INCLUDE-ФАЙЛЫ
; ============================================================
include "include\head.asm"
include "include\keyboard.asm"
include "include\dss_equ.asm"
include "include\bios_equ.asm"
include "include\sp_equ.asm"
; ============================================================
; ТОЧКА ВХОДА
; ============================================================
begin: jp main
; ============================================================
; ДАННЫЕ
; ============================================================
; ============================================================
; ЗАГОЛОВОК .SPR
; ============================================================
HEAD_SIGN EQU 0 ; 3 bytes "SPR"
HEAD_WIDTH EQU 3 ; 2 bytes ширина
HEAD_HEIGHT EQU 5 ; 1 byte высота
HEAD_OFFSET_Y EQU 6 ; 1 byte отступ Y
HEAD_OFFSET_X EQU 7 ; 1 byte отступ X
HEAD_FPS EQU 8 ; 1 byte FPS
HEAD_SOUNDRATE EQU 9 ; 1 byte код частоты звука
HEAD_PALETTE EQU 10 ; 1024 байта палитра
HEAD_AUDIO_OFF EQU 1034 ; 4 bytes смещение до аудио
HEAD_PART_SIZE EQU 1038 ; 2 bytes размер 1/5 части кадра
HEAD_MFP_SIZE EQU 1040 ; 2 bytes размер таблицы смещений
HEAD_MFP_PAGES EQU 1042 ; 1 byte страниц под таблицу
HEAD_MFP_LAST EQU 1043 ; 2 bytes размер последней страницы
HEADER_SIZE EQU 1045 ; размер заголовка
MFP_START = HEADER_SIZE ; = 1045 - здесь начинается таблица смещений
; ============================================================
; БУФЕР ЗАГОЛОВКА
; ============================================================
header:
.signature: db "SPR"
.width: dw 0
.height: db 0
.offset_y: db 0
.offset_x: db 0
.fps: db 0
.soundrate: db 0
.palette: ds 1024
.sound_off: dw 0,0
.part_size: dw 0 ; размер 1/5 части
.mfp_size: dw 0 ; размер таблицы
.mfp_pages: db 0 ; страниц под таблицу
.mfp_last: dw 0 ; размер последней страницы
; ============================================================
; Переменные файлового ввода-вывода
; ============================================================
idfile: db 0 ; временный ID файла
scr_id: db 0 ; ID файла видео
snd_id: db 0 ; ID файла звука
buffer: db 0 ; базовая страница выделенной памяти
SavePage: db 0 ; сохранённая страница окна 0
win1: db 0 ; сохранённая страница окна 1
sound_load: db 0 ; флаг: нужно ли загружать звук
counter_notes: dw #1000 ; счётчик записей в таблице смещений. После каждой загрузки #1000
addres_wav: dw #4000 ; текущий адрес звуковых данных в окне #4000
; ---- Строки для вывода ----
About: db #0d, #0a, "(c) Sprinter Video Player v0.15.035 Alpha by kostya261. 09.07.2026", #0d, #0a, #00
new_line: db #0a, #0d, #00
cmd: ds #100
commandLine: dw #8082
; ---- Основной цикл ----
main:
; ---- Выводим информацию о плеере ----
ld hl,About
ld c,Dss.PChars
rst DssRst
; ---- Разбираем командную строку ----
ld hl,(commandLine)
ld de,cmd
ld bc,#0745
rst DssRst
; ---- Выводим информацию командной строки ----
ld hl,cmd
ld c,Dss.PChars
rst DssRst
; ============================================
; Нужно продумать какие страницы куда пойдут.
; ============================================
; Немного переборщил. Нужно раза в два меньше.
; но пока пускай будут. Мало ли, пригодяться...
; Запрашиваем память (11 страниц)
ld b,BANKSIZE + 1
ld c,Dss.GetMem
rst DssRst
ld (buffer),a
ld b,0
metka_p: djnz metka_p
; ---- Устанавливаем графический режим ----
ld a,#5c: ld (Graphics.video_page),a
ld a,#81: call Graphics
call LoadHeader
ld hl,header.palette
call SetPalette
call Copy_Palette
call ExtractMemoryPage
call ClearSound
call SetUp_CovoxBL
; call SetUp_Interrupt
; ---- Ищем самый тёмный цвет ----
ld hl, header.palette
call FindDarkestColor
ld (clrscr.color), a ; сохраняем номер цвета
; ---- Очищаем экран ----
call clrscr
call LoadHeader
; ============================================
; Открываем видеопоток.
; ============================================
ld hl,cmd
call OpenFile
ld (LoadMoveFP.move_fp),a
call LoadMoveFP
ld hl,cmd
call OpenFile
ld (scr_id),a
ld (DrawFrame.idfile1),a
ld (DrawFrame.idfile1_2),a
ld (DrawFrame.idfile1_3),a
ld (DrawFrame.idfile1_4),a
ld (DrawFrame.idfile1_5),a
;==============================================
ld hl, cmd
call OpenFile
ld (snd_id),a
ld (LoadBlockWav.snd_id),a
ld (LoadBlockWav_.snd_id),a
call Move_FP_Sound
call LoadBlockWav ; загружаем первую половину
call LoadBlockWav_ ; загружаем вторую половину
call SetUp_Interrupt
metka_cinema:
di: IM 2: ei; Свои прерывания
call Flip
call DrawFrame
; ---- решаем, нужно ли загружать звук ----
ld a,(sound_load): cp 0: jr nz,.no_skip2:
ld a,(next_data.trigger): cp 0: jr nz,.nn1
call LoadBlockWav: jr .nn2
.nn1: call LoadBlockWav_
.nn2: ld a,1: ld (sound_load),a; сбрасываем флаг загрузки звука
.no_skip2:
di: IM 1: ei; возвращаем дос прерывания
halt
ld c,Dss.ScanKey: rst DssRst
jp nz,_exit
ld a,(DrawFrame.exit_video)
cp #ff
jr nz,metka_cinema
; ============================================
; ВЫХОД ИЗ ПРОГРАММЫ
; ============================================
_exit:
call clear_covox
ld a,(LoadMoveFP.move_fp)
ld (idfile),a
call CloseFile
ld a,(scr_id)
ld (idfile),a
call CloseFile
ld a,(snd_id)
ld (idfile),a
call CloseFile
ld c,Dss.Exit
rst DssRst
ret
; ============================================================
; ПОИСК САМОГО ТЁМНОГО ЦВЕТА В ПАЛИТРЕ
; ============================================================
; Вход: HL = указатель на палитру (header.palette)
; Выход: A = номер самого тёмного цвета (0-255)
; BC = яркость самого тёмного цвета (сумма R+G+B)
; ============================================================
FindDarkestColor:
ld b, 0 ; номер текущего цвета (0-255)
ld de, 0xFFFF ; минимальная яркость (пока максимальная)
ld c, 0 ; номер самого тёмного цвета (пока 0)
.loop:
; ---- Читаем 4 байта цвета (B, G, R, 0) ----
ld a, (hl) ; B
inc hl
ld d, a ; сохраняем B в D
ld a, (hl) ; G
inc hl
ld e, a ; сохраняем G в E
ld a, (hl) ; R
inc hl
inc hl ; пропускаем 0
; ---- Считаем яркость = R + G + B (16 бит!) ----
ld h, 0
ld l, a ; HL = R
add hl, de ; HL = R + G
ld d, 0
ld e, d ; DE = 0
add hl, de ; HL = R + G + B (теперь яркость в HL)
; ---- Сравниваем с текущей минимальной яркостью ----
ld a, h ; сравниваем старшие байты
cp d
jr c, .darker ; если HL < DE, то это темнее
jr nz, .not_darker ; если HL > DE, пропускаем
ld a, l ; сравниваем младшие байты
cp e
jr nc, .not_darker ; если HL >= DE, пропускаем
.darker:
; ---- Это самый тёмный цвет на данный момент ----
ld de, hl ; сохраняем новую минимальную яркость в DE
ld c, b ; сохраняем номер цвета
.not_darker:
; ---- Следующий цвет ----
inc b
ld a, b
cp 255
jr nz, .loop
; ---- Возвращаем результат ----
ld a, c ; A = номер самого тёмного цвета
ld b, d ; B = старший байт яркости
ld c, e ; C = младший байт яркости
ret
; ============================================
; УСТАНОВКА ГРАФИЧЕСКОГО РЕЖИМА
; ============================================
; в А - номер режима (81 = 320*256б 82 = 640*256)
; ld a,#50 - номер подключаемой видеостраницы, по умолчанию #50
; Пример вызова: ld a,#5c
; ld (video_page),a
; ld a,#81
; call Graphics
Graphics:
ld bc,#0050: rst DssRst
.video_page: equ $+1: ld a,#50
out (EmmWin.P3),a
ret
; ============================================
; Загружаем и отрисовываем кадр частями
; ============================================
DrawFrame:
.pageA: equ $+1: ld a,#00: out(#a2),a
push de
.idfile1: equ $+1: ld a,#00
ld hl,#4000
.size_file_1: equ $+1: ld de,#4000
ld c,Dss.Read
rst DssRst
pop de
ld de,0
.buffer_screen: equ $+1: ld hl,#c000
add hl,de: ld (.x),hl
ld hl,#4000
ld b,64; Ширина сегмента
.y1: equ $+1: ld a,55: out (Y_PORT),a
.x: equ $+1: ld de,#0000
di
.m1: ld l,l: ld a,(hl): ld a,a: ld (de),a: ld b,b: inc de
push de
.hght1: equ $+1: ld de,50: add hl,de
pop de
.y_1: equ $+1: ld a,55: out (Y_PORT),a
djnz .m1
ei
push de
.idfile1_2: equ $+1: ld a,#00
ld hl,#4000
.size_file_2: equ $+1: ld de,#4000
ld c,Dss.Read
rst DssRst
pop de
ld hl,#4000
ld b,64; Ширина сегмента
.y2: equ $+1: ld a,55: out (Y_PORT),a
.m2: di
ld l,l: ld a,(hl): ld a,a: ld (de),a: ld b,b: inc de
push de
.hght2: equ $+1: ld de,50: add hl,de
pop de
.y_2: equ $+1: ld a,55: out (Y_PORT),a
djnz .m2
ei
push de
.idfile1_3: equ $+1: ld a,#00
ld hl,#4000
.size_file_3: equ $+1: ld de,#4000
ld c,Dss.Read
rst DssRst
pop de
ld hl,#4000
ld b,64; Ширина сегмента
.y3: equ $+1: ld a,55: out (Y_PORT),a
.m3: di
ld l,l: ld a,(hl): ld a,a: ld (de),a: ld b,b: inc de
push de
.hght3: equ $+1: ld de,50: add hl,de
pop de
.y_3: equ $+1: ld a,55: out (Y_PORT),a
djnz .m3
ei
push de
.idfile1_4: equ $+1: ld a,#00
ld hl,#4000
.size_file_4: equ $+1: ld de,#4000
ld c,Dss.Read
rst DssRst
pop de
ld hl,#4000
ld b,64; Ширина сегмента
.y4: equ $+1: ld a,55: out (Y_PORT),a
.m4: di
ld l,l: ld a,(hl): ld a,a: ld (de),a: ld b,b: inc de
push de
.hght4: equ $+1: ld de,50: add hl,de
pop de
.y_4: equ $+1: ld a,55: out (Y_PORT),a
djnz .m4
ei
push de
.idfile1_5: equ $+1: ld a,#00
ld hl,#4000
.size_file_5: equ $+1: ld de,#4000
ld c,Dss.Read
rst DssRst
cp #ff
jr nz,.skip
ld (.exit_video),a
.skip:
pop de
ld hl,#4000
ld b,64; Ширина сегмента
.y5: equ $+1: ld a,55: out (Y_PORT),a
.m5: di
ld l,l: ld a,(hl): ld a,a: ld (de),a: ld b,b: inc de
push de
.hght5: equ $+1: ld de,50: add hl,de
pop de
.y_5: equ $+1: ld a,55: out (Y_PORT),a
djnz .m5
ei
ret
.exit_video:
db #00
; HL = старшие 16 бит
; IX = младшие 16 бит
Move_FP:
ld a,(scr_id)
.hl_reg: equ $+2:ld hl,#0000
.ix_reg: equ $+2: ld ix,0000
ld bc,#0015
rst DssRst
ret
; ld a,file_id
; HL = старшие 16 бит
; IX = младшие 16 бит
Move_FP_Small:
ld bc,#0015
rst DssRst
ret
; HL = старшие 16 бит
; IX = младшие 16 бит
Move_FP_Sound:
ld a,(snd_id)
ld hl,(header.sound_off + 2)
ld ix,(header.sound_off)
ld bc,#0015
rst DssRst
ret
; ============================================================
; ПЕРЕКЛЮЧЕНИЕ ЭКРАННЫХ СТРАНИЦ
; ============================================================
Flip:
ld hl,.flip_s
.flip_s: equ $+1
ld a,#01
inc (hl)
and %00000001
out (RGMOD),a
cp 1
jr z,.skip
ld hl,#c140
ld (DrawFrame.buffer_screen), hl
ret
.skip: ld hl,#c000
ld (DrawFrame.buffer_screen), hl
ret
; ============================================================
; НАСТРОЙКА COVOX-BLASTER
; ============================================================
SetUp_CovoxBL:
ld bc,#04e
ld a,%10011001
out (c),a
ret
; ============================================================
; НАСТРОЙКА ПРЕРЫВАНИЙ
; ============================================================
SetUp_Interrupt:
DI
LD HL,interrupt
ld (#90ff),hl
LD A,#90
LD I,A
IM 2
EI
ret
; ============================================================
; ОБРАБОТЧИК ПРЕРЫВАНИЯ
; ============================================================
interrupt:
di
push af
push bc
push de
push hl
xor a
in a,(#fe): and #80
call nz,next_data
.next:
pop hl
pop de
pop bc
pop af
ei
ret
.inter: db #00
next_data:
in a,(#a2): ld (win1),a
.trigger: equ $+1: ld a,#00
cp 0: jr nz,.n2
.page7: equ $+1: ld a,#00: out (#a2),a: jr .n3
.n2:
.page6: equ $+1: ld a,#00: out (#a2),a
.n3:
ld hl,(addres_wav)
ld c,#4f
ld b,128
otir
ld a,#80: cp h: jr nz,.next
ld hl,#4000: ld (addres_wav),hl
ld a,(.trigger)
cpl: ld (.trigger),a
.nn2: xor a: ld (sound_load),a
.next: ld (addres_wav),hl
ld a,(win1): out (#a2),a
ret
; ============================================================
; ОЧИСТКА ЗВУКА
; ============================================================
clear_covox:
ld a,#80
ld bc,#4f
clear_cbl: out (c),a
djnz clear_cbl
ret
; ============================================================
; ОЧИСТКА ЭКРАНА
; ============================================================
clrscr:
push bc
push hl
ld hl,(DrawFrame.buffer_screen)
ld bc,#280
.m1: push bc
di
ld d,d: ld a,0: ld e,e
.color: equ $+1: ld a,0: ld (hl),a
ld b,b
ei
pop bc
inc hl
dec bc
ld a,b: or c
jr nz,.m1
pop hl
pop bc
ret
; ============================================================
; УСТАНОВКА ПАЛИТРЫ
; ============================================================
;hl addres palette
SetPalette:
ld de,#0000
ld bc,#ffa4
xor a
rst BiosRst
ret
Copy_Palette:
ld b,0
.sp_loop:
push bc
ld a,b
out (Y_PORT),a
ld hl,#c3e0
ld de,#c3e4
ld bc,#04
ldir
pop bc
djnz .sp_loop
ret
; ============================================================
; ОТКРЫТИЕ/ЗАКРЫТИЕ ФАЙЛА
; ============================================================
OpenFile:
ld a,#C0
out (RGADR),a
ld a,1
ld c,Dss.Open
rst DssRst
ld (idfile),a
ret
CloseFile:
ld a,(idfile)
ld c,Dss.Close
rst DssRst
ret
; ---- Загрузка заголовка ----
LoadHeader:
ld hl,cmd
call OpenFile
ld a,(idfile)
ld hl,header
ld de,HEADER_SIZE
ld c,Dss.Read
rst DssRst
call CloseFile
; ---- Установка параметров проигрывателя ----
ld a,(header.offset_y)
ld (DrawFrame.y1),a: ld (DrawFrame.y_1),a
ld (DrawFrame.y2),a: ld (DrawFrame.y_2),a
ld (DrawFrame.y3),a: ld (DrawFrame.y_3),a
ld (DrawFrame.y4),a: ld (DrawFrame.y_4),a
ld (DrawFrame.y5),a: ld (DrawFrame.y_5),a
; ---- Устанавливаем высоту кадра ----
di
ld d,d
ld a,(header.height)
ld b,b
ei
ld (DrawFrame.hght1),a
ld (DrawFrame.hght2),a
ld (DrawFrame.hght3),a
ld (DrawFrame.hght4),a
ld (DrawFrame.hght5),a
; ---- размер загружаемых получастей кадра ----
ld hl,(header.part_size)
ld (DrawFrame.size_file_1),hl
ld (DrawFrame.size_file_2),hl
ld (DrawFrame.size_file_3),hl
ld (DrawFrame.size_file_4),hl
ld (DrawFrame.size_file_5),hl
ld a,(header.mfp_pages)
inc a
ld (header.mfp_pages),a
ret
LoadMoveFP:
.move_fp: equ $+1: ld a,#00
ld hl,#0000
ld ix,#0415
call Move_FP_Small
.load:
.page1: equ $+1: ld a,#00: out(#a2),a
ld a,(header.mfp_pages)
dec a
ret z
ld (header.mfp_pages), a
ld a,(.move_fp)
ld hl,#4000: ld d,h: ld e,l
ld c,Dss.Read
rst DssRst
ld hl,#1000
ld (counter_notes),hl
ret
LoadBlockWav:
.page6: equ $+1: ld a,#00: out (#a2),a
.snd_id: equ $+1: ld a,#00
ld hl,#4000: ld d,h: ld e,l
ld c,Dss.Read
rst DssRst
ld hl, (counter_notes)
dec hl
jr z,.skip
ld (counter_notes),hl
ret
.skip:
call LoadMoveFP.load
jr Move_FP_Small_
LoadBlockWav_:
.page7: equ $+1: ld a,#00: out (#a2),a
.snd_id: equ $+1: ld a,#00
ld hl,#4000: ld d,h: ld e,l
ld c,Dss.Read
rst DssRst
ld hl, (counter_notes)
dec hl
jr z,.skip
ld (counter_notes),hl
ret
.skip:
call LoadMoveFP.load
jp Move_FP_Small_
Move_FP_Small_:
.current_point: equ $+2: ld ix,(#4000)
.current_point_2: equ $+1: ld hl,(#4002)
ld a,(scr_id)
ld bc,#0015
rst DssRst
ld hl,(.current_point)
inc hl: inc hl: inc hl: inc hl
ld (.current_point),hl
inc hl: inc hl
ld (.current_point_2),hl
ret
ClearSound:
ld a,(LoadBlockWav.page6): out (#a2),a
xor a
ld hl, #4000
ld de, #4001
ld bc, #3fff
ld (hl), a
ldir
ld a,(LoadBlockWav_.page7): out (#a2),a
xor a
ld hl, #4000
ld de, #4001
ld bc, #3fff
ld (hl), a
ldir
ret
; ============================================================
; Облегчаем доступ к страницам памяти
; ============================================================
ExtractMemoryPage:
ld b,10
ld a,(buffer)
ld c,Dss.SetWin1
rst DssRst
in a,(#a2): ld (DrawFrame.pageA),a
ld b,5
ld a,(buffer)
ld c,Dss.SetWin1
rst DssRst
in a,(#a2): ld (next_data.page7),a: ld (LoadBlockWav_.page7),a
ld b,4
ld a,(buffer)
ld c,Dss.SetWin1
rst DssRst
in a,(#a2): ld (next_data.page6),a: ld (LoadBlockWav.page6),a
ld b,3
ld a,(buffer)
ld c,Dss.SetWin1
rst DssRst
in a,(#a2): ld (LoadMoveFP.page1),a
ret
; ============================================================
; КОНЕЦ
; ============================================================
code_end:
savebin "player.exe",start_addr,code_end-start_addr

18
source/make3.bat Normal file
View File

@ -0,0 +1,18 @@
@echo off
if EXIST player.exe (
del player.exe
)
tools\sjasmplus\sjasmplus.exe main3.asm --lst=main.lst
if errorlevel 1 goto ERR
echo Ok!
goto END
:ERR
del player.exe
pause
echo Something was happened...
pause
goto END
:END

47
source/make_image.bat Normal file
View File

@ -0,0 +1,47 @@
rem echo OFF
set PROG=FBIRD
mkdir build
echo Unmounting old image ...
osfmount.com -D -m X:
echo Assembling ...
tools\sjasmplus\sjasmplus.exe %PROG%.asm --lst=%PROG%.lst
if errorlevel 1 goto ERR
echo Preparing floppy disk image ...
copy /Y image\dss_image.img build\%PROG%.img
rem Delay before copy image
timeout 2 > nul
osfmount.com -a -t file -o rw -f build/%PROG%.img -m X:
if errorlevel 1 goto ERR
mkdir X:\%PROG%
mkdir X:\%PROG%\ASSETS
copy /Y %PROG%.EXE /B X:\%PROG%\ /B
copy /Y assets\*.b* /B X:\%PROG%\ASSETS\ /B
mkdir build\%PROG%
mkdir build\%PROG%\ASSETS
copy /Y %PROG%.EXE /B X:\%PROG%\ /B
copy /Y assets\*.b* /B X:\%PROG%\ASSETS /B
copy /Y %PROG%.EXE /B build\%PROG%\ /B
copy /Y assets\*.b* /B build\%PROG%\ASSETS /B
if errorlevel 1 goto ERR
rem Delay before unmounting image
timeout 2 > nul
echo Unmounting image ...
osfmount.com -d -m X:
goto SUCCESS
:ERR
rem pause
echo Some Building ERRORs!!!
pause 0
rem exit
goto END
:SUCCESS
echo Copying image to ZXMAK2 Emulator
copy /Y build\%PROG%.img /B %SPRINTER_EMULATOR% /B
echo Done!
:END
pause 0

18
source/makefast.bat Normal file
View File

@ -0,0 +1,18 @@
@echo off
if EXIST fastram.bin (
del fastram.bin
)
tools\sjasmplus\sjasmplus.exe FastRam.asm --lst=FastRam.lst
if errorlevel 1 goto ERR
echo Ok!
goto END
:ERR
del fastram.bin
pause
echo Something was happened...
pause
goto END
:END

BIN
source/player.exe Normal file

Binary file not shown.