[Thomson] SDDRIVE Vidéo

Cette catégorie traite de développements récents pour nos vieilles machines, applications, jeux ou démos... Amis programmeurs, c'est ici que vous pourrez enfin devenir célèbres!

Modérateurs : Papy.G, fneck, Carl

Daniel
Messages : 17316
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

Re: [Thomson] SDDRIVE Vidéo

Message par Daniel »

En testant on trouve toujours des choses intéressantes !

J'ai fait ROR <$BF
(J'aurais pu faire LSR, le résultat serait identique)
Résultats :
- Ne marche plus du tout dans dcmoto
- Ne marche pas sur TO8D
- Marche sur MO5

Par rapport à INC, c'est exactement l'inverse. Donc on pourrait programmer INC ou ROR en fonction de la machine, et le programme fonctionnerait dans tous les cas. Reste à faire des tests sur T9000, TO7, TO7/70, MO5NR, MO6, TO9+, etc... Pour l'émulateur un petit patch devrait permettre de le faire fonctionner avec l'une ou l'autre instruction.

Cette solution est un peu trop hasardeuse pour la mettre en application dans l'EPROM officielle de SDDRIVE, mais c'est bon à savoir.

Il faut maintenant que je découvre s'il est possible d'arrêter la commande CMD17 après la lecture de 256 octets. Ce serait beaucoup plus propre et le gain serait énorme : gain de 4 cycles par bit. Pour 256 octets ça fait 8192 cycles !

[Edit 16:00]
D'après ce que je viens de lire la commande CMD17 (read single block) ne peut pas être interrompue. Par contre la commande CMD18 (read multiple block) peut être interrompue par la commande CMD12 (stop transmission). L'idée serait de remplacer CMD17 par CMD18, lire 256 octets, envoyer la commande CMD12 et ignorer les 256 octets suivants du bloc. A tester...
Daniel
L'obstacle augmente mon ardeur.
__sam__
Messages : 7923
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

Re: [Thomson] SDDRIVE Vidéo

Message par __sam__ »

CMD18 peut être interrompu.
Mais interrompu n'importe quand ou uniquement à la frontière de blocs ?
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
Daniel
Messages : 17316
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

Re: [Thomson] SDDRIVE Vidéo

Message par Daniel »

Le moment précis de l'interruption est l'une des questions qui se posent, mais pas la seule. La documentation est très pauvre sur le sujet.

J'ai modifié la lecture des blocs Thomson de 256 octets :
- Remplacement de la commande CMD17 (lecture d'un bloc) par CMD18 (lecture multibloc)
- Lecture de 256 octets
- Envoi de la commande CMD12 (stop transmission)

Résultat :
- Le programme de sélection SDDRIVE.SEL se charge bien (normal, il lit uniquement des blocs de 512 octets)
- Le programme SDDRIVE Vidéo (premier algorithme de Daniel) se lance et fonctionne, mais l'image est décalée vers le bas d'environ 1/4 d'écran, et la partie basse s'affiche en haut.
- Le programme SDDRIVE Vidéo (dernier algorithme de __sam__) plante dès le début.
Les deux derniers points s'expliquent facilement par un décalage d'un bloc (ou deux) dans la lecture des données.

Il faut savoir que la transmission SPI avec la carte SD est bidirectionnelle. A chaque accès à <$BF un bit est lu et un bit est écrit. Par contre le 6809 n'est pas bidirectionnel : s'il écrit, il perd le bit envoyé par la carte, s'il lit il ne maîtrise pas le bit écrit vers la carte qui est forcé à 1 par l'électronique de SDDRIVE.

Donc, pendant l'envoi de la commande CMD12, la carte continue à envoyer des bits, qui sont ignorés par le 6809. A quel moment elle s'arrête, c'est un grand mystère. Peut-être dès la fin de réception de CMD0, soit vers le 262ème octet du bloc, soit en fin de bloc. La commande CMD17 incrémente automatiquement le LBA (adresse physique dans la carte) après chaque lecture de bloc. La commande CMD18 le fait-elle aussi ? (en particulier si elle est interrompue au milieu d'un bloc ?).

Théoriquement la commande CMD12 envoie des octets à zéro tant que le processus d'interruption n'est pas terminé, puis des octets à $FF (carte prête). J'ai testé les octets à zéro pour ne pas continuer le programme tant que la carte n'est pas prête, mais ça ne change rien. Il est possible que CMD18 continue à envoyer les derniers octets du bloc et ils sont à $FF, donc on croit la carte prête mais en fait non, elle continue à lire le bloc.

Ce ne sont que des suppositions, il faudrait des heures et des heures de test pour arriver à tout comprendre. En plus rien ne prouve que tous les modèles de cartes ont un comportement identique, puisque les spécifications officielles sont muettes sur ce sujet.
Daniel
L'obstacle augmente mon ardeur.
__sam__
Messages : 7923
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

Re: [Thomson] SDDRIVE Vidéo

Message par __sam__ »

Oui je crois que la solution standard avec CMD17 est la plus fiable.
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
jasz
Messages : 1313
Inscription : 05 oct. 2016 20:05
Localisation : Quelque part dans le 31

Re: [Thomson] SDDRIVE Vidéo

Message par jasz »

:? :arrow: []
Daniel
Messages : 17316
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

Re: [Thomson] SDDRIVE Vidéo

Message par Daniel »

@sam : Oui, je pense comme toi et je préfère garder la version actuelle (20180702).

Le processus de mise au point a été long et laborieux mais le résultat est stable. Depuis début juillet je n'ai pas fait une seule modification.

L'utilisation de la commande CMD18 pour lire seulement 256 octets par bloc risque d'apporter des bugs, et si l'interruption par CMD12 intervient seulement à la fin d'un bloc on ne gagnera rien. Le contrôleur SDDRIVE charge déjà n'importe quel jeu trois ou quatre fois plus vite que le contrôleur de disquette, c'est bien suffisant.

Quand aux démonstrations en streaming (musique et vidéo), elles lisent des blocs de 512 octets avec la commande CMD18. L'amélioration de la lecture des secteurs Thomson de 256 octets n'a aucun impact sur leur débit.
Daniel
L'obstacle augmente mon ardeur.
Avatar de l’utilisateur
Silou78
Messages : 382
Inscription : 11 févr. 2017 14:54
Localisation : Yvelines (78)

Re: [Thomson] SDDRIVE Vidéo

Message par Silou78 »

Daniel a écrit : 15 août 2018 10:32
__sam__ a écrit : 12 août 2018 22:07 Pour aller plus vite il faut pouvoir lire les octets en une fois.
Eh eh ! C'est mon nouveau projet pour remplacer SDDRIVE. Mais chut, c'est encore ultra secret, ne le répétez pas : il s'appelle ARDDRIVE.
Daniel a écrit : 01 sept. 2018 08:39 Premières réactions après visualisation sur MO5 :
- Le son est bon. Je dirais presque excellent.
- La fluidité de l'image est remarquable pour un débit de 20 Ko/s
- Pour faire mieux il faudrait un débit de 160 Ko/s. J'y travaille :|

160 = 8 x 20. Vous comprenez ?
Bonjour Daniel,

Tes citations m'intriguent...

ARDDRIVE pour Arduino Drive ? Lecture de la carte SD accélérée par un Arduino ?

J'ai déjà pensé à un truc qu'avec des boitiers logiques (et donc pas d'Arduino) pour effectivement faire x 8 sur le débit, mais je n'ai pas trouvé le temps de faire le schéma et encore moi de prototyper :cry:
L'idée est d'implanter un multiplicateur de fréquence x8 (principe ici) à partir de l'horloge E et d'utiliser des registres à décalage sur MISO et MOSI cadencés par l'horloge à 8 MHz. Ainsi en une seule écriture sur le bus de données (et en lecture dans l'autre sens) avec les 8 bits utilisés permettrait d'accélérer encore SDDRIVE. La difficulté risque peut-être d'être liée à la montée en fréquence, au niveau du design du schéma et du PCB, déjà que la mise au point de la version actuelle n'a pas été de tout repos ;-)
Sylvain
Daniel
Messages : 17316
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

Re: [Thomson] SDDRIVE Vidéo

Message par Daniel »

Mon projet est effectivement d'utiliser un Arduino comme interface avec la carte SD. Le contrôleur serait sur le même principe que SDDRIVE, mais chaque lecture ou écriture transférerait 8 bits au lieu de 1. Reste à vérifier si l'Arduino est assez rapide. Par manque de temps le projet n'avance pas vite...

Les registres à décalage sont aussi une piste intéressante à explorer. La mise au point de l'électronique n'est pas forcément facile à ces fréquences élevées, mais sur le papier ça devrait marcher.
Daniel
L'obstacle augmente mon ardeur.
__sam__
Messages : 7923
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

Re: [Thomson] SDDRIVE Vidéo

Message par __sam__ »

J'ai introduit un filtre temporel dans l'outil de conversion. J'ai l'impression qu'il réduit visuellement l'effet de "vague" communément appelé "tearing-effect" en vidéo.

Code : Tout sélectionner

-- conversion fichier video en fichier sd-drive
--
-- version alpha 0.05
---
-- Samuel DEVULDER Aout-Sept 2018

-- code experimental. essaye de determiner
-- les meilleurs parametres (fps, taille ecran
-- pour respecter le fps ci-dessous. 

-- gray peut être true ou false suivant qu'on
-- veut une sortie couleur ou pas. Le gris est
-- generalement plus rapide et/ou avec un ecran
-- plus large. Si on le laisse à nil, l'outil 
-- détermine automatiquement le mode couleur de
-- la video.

-- Work in progress!
-- =================
-- le code doit être nettoye et rendu plus
-- amical pour l'utilisateur

local function round(x)
	return math.floor(x+.5)
end

local BUFFER_SIZE = 4096
local FPS_MAX = 30
local VIDEO_FILTER = {1,4,1} -- {1,4,10,4,1} -- {1,6,42,6,1} -- {1,4,10,30,10,4,1}
local tmp = 'tmp'
local img_pattern =  tmp..'/img%05d.bmp'
local cycles = 199 -- cycles par échantillons audio
local hz = round(8000000/cycles)/8
local fps = 13
local gray = nil
local interlace = false --gray
local dither = 1
local ffmpeg = 'tools\\ffmpeg'
local mode = 'p'

local file = arg[1]:gsub('^/cygdrive/(%w)/','%1:/')

local function exists(file)
   local ok, err, code = os.rename(file, file)
   if not ok then
      if code == 13 then
         -- Permission denied, but it exists
         return true
      end
   end
   return ok, err
end

local function isdir(file)
	return exists(file..'/')
end

if not exists(file) then os.exit(0) end

local function percent(x)
	return round(math.min(1,x)*100)
end

local function hms(secs, fmt)
	secs = round(secs)
	return string.format(fmt or "%d:%02d:%02d", 
			math.floor(secs/3600), math.floor(secs/60)%60, math.floor(secs)%60)
end

-- if file:match('miga') then
	-- gray = false
	-- interlace = false
-- end

-- nom fichier
io.stdout:write('\n'..file..'\n')
io.stdout:flush()

-- recherche la bonne taille d'image
local x,y = 80,45
local IN,line = assert(io.popen(ffmpeg..' -i "'..file ..'" 2>&1', 'r'))
for line in IN:lines() do 
	local h,m,s = line:match('Duration: (%d+):(%d+):(%d+%.%d+),')
	if h and m and s then duration = h*3600 + m*60 +s end
	local a,b = line:match(', (%d+)x(%d+)')
	if a and b then x,y=a,b end
end
IN:close()
if not duration then error("Can't get duration!") end
local max_ar
for i=2,10 do
	local t = x*i/y
	t = math.abs(t-round(t))
	if max_ar==nil or t<max_ar then
		max_ar = t
		aspect_ratio = round(x*i/y)..':'..i
	end
end
local w = 80
local h = round(w*y/x)
if h>50 then
   h = 50
   w = round(h*x/y)
end

if mode==nil then
	mode = (h*w>32*48 and 'i') or 'p'
end

-- initialise la progression dans les octets de l'image
local lines,indices = {},{}
for i=math.floor((50-h)/2)*4,(math.floor((50-h)/2)+h)*4-1 do
	if i%4<3 then
		table.insert(lines, i)
	end
end
if mode=='p' or mode=='a' then
	-- rien
elseif mode=='i' then
	local t = {}
	for i=1,#lines,2 do
		table.insert(t, lines[i])
	end
	for i=2,#lines,2 do
		table.insert(t, lines[i])
	end
	lines = t
elseif mode=='2' then
	local t = {}
	for i=1,#lines,3 do
		table.insert(t, lines[i])
	end
	for i=2,#lines,3 do
		table.insert(t, lines[i])
	end
	table.sort(t)
	for i=3,#lines,3 do
		table.insert(t, lines[i])
	end
	lines = t
elseif mode=='3' then
	local t = {}
	for i=2,#lines,3 do
		table.insert(t, lines[i])
	end
	for i=1,#lines,3 do
		table.insert(t, lines[i])
	end
	for i=2,#lines,3 do
		table.insert(t, lines[i])
	end
	lines = t
elseif mode=='ipi' then
	local t = {}
	for i=1+math.floor(#lines/3),math.floor(2*#lines/3) do
		table.insert(t, lines[i])
	end
	for i=1,math.floor(#lines/3),2 do
		table.insert(t, lines[i])
	end
	for i=1+math.floor(2*#lines/3),#lines,2 do
		table.insert(t, lines[i])
	end
	for i=2,math.floor(#lines/3),2 do
		table.insert(t, lines[i])
	end
	for i=2+math.floor(2*#lines/3),#lines,2 do
		table.insert(t, lines[i])
	end
	lines = t
elseif mode=='r' then
	local size = #lines
	for i = size, 1, -1 do
		local rand = math.random(size)
		lines[i], lines[rand] = lines[rand], lines[i]
	end
elseif mode=='3i' then
	-- 14 + 14+12
	local w = 2+4+4+4+4+4
	local x = math.floor((39-w)/2)
	for _,j in ipairs(lines) do
		for i=j*40+x,j*40+x+w-1 do
			table.insert(indices,i)
		end
	end
	for j=1,#lines,2 do
		for i=lines[j]*40,lines[j]*40+x-1 do
			table.insert(indices,i)
		end
		for i=lines[j]*40+x+w,lines[j]*40+39 do
			table.insert(indices,i)
		end
	end
	for j=2,#lines,2 do
		for i=lines[j]*40,lines[j]*40+x-1 do
			table.insert(indices,i)
		end
		for i=lines[j]*40+x+w,lines[j]*40+39 do
			table.insert(indices,i)
		end
	end
	lines = {}
elseif mode=='d' then
	lines = {}
	for x=0,39 do
		for y=0,math.min(24,x) do
			local p = y*320+(x-y)
			for j=p,p+319,40 do
				table.insert(indices,j)
			end
		end
	end
	for y=1,24 do
		for x=0,24-y do
			local p = (y+x)*320+39-x
			for j=p,p+319,40 do
				table.insert(indices,j)
			end
		end
	end
else 
	error('Unknown mode: ' .. mode)
end

for _,i in ipairs(lines) do
	-- print(i)
	for j=i*40,i*40+39 do
		table.insert(indices, j)
	end
end
-- os.exit(0)
-- flux audio
local AUDIO = {}
function AUDIO:new(file, hz)
	local o = {
		stream = assert(io.popen(ffmpeg..' -i "'..file ..'" -v 0 -f u8 -ac 1 -ar '..round(8*hz)..' -acodec pcm_u8 pipe:', 'rb')),
		cor = {8,255}, -- volume auto
		buf = '', -- buffer
		running = true
	}
	setmetatable(o, self)
	self.__index = self
	return o
end
function AUDIO:close()
	self.stream:close()
end
function AUDIO:next_sample()
	local buf = self.buf
	if buf:len()<=8 then
		local t = self.stream:read(BUFFER_SIZE)
		if not t then 
			self.running = false
			t = string.char(0,0,0,0,0,0,0,0)
		end
		buf = buf .. t
	end
	local v = (buf:byte(1) + buf:byte(2) + buf:byte(3) + buf:byte(4) +
	           buf:byte(5) + buf:byte(6) + buf:byte(7) + buf:byte(8))*.125
	self.buf = buf:sub(9)
	-- auto volume
	if v<self.cor[2]     then self.cor[2]=v end
	v = v-self.cor[2]
	if v*self.cor[1]>255 then self.cor[1]=255/v end
	v = v*self.cor[1]
	-- dither
	v = math.max(math.min(v/4 + math.random() + math.random() - 1, 63),0)
	return math.floor(v)
end

-- filtre video
local FILTER = {}
function FILTER:new(weights)
	local o = {w={}, t=nil, size=#weights}
	setmetatable(o, self)
	self.__index = self
	
	local total = 0
	for _,w in ipairs(weights) do total = total + w end
	for i=1,o.size do o.w[i] = weights[i]/total end
	
	if o.size==1 then
		function o:byte(offset)
			return self.t[1][offset]
		end
	elseif o.size==2 then
		function o:byte(offset)
			return round(
					self.t[2][offset]*self.w[2] + 
					self.t[1][offset]*self.w[1])
		end
	elseif o.size==3 then
		function o:byte(offset)
			return round(
					self.t[3][offset]*self.w[3] + 
					self.t[2][offset]*self.w[2] + 
					self.t[1][offset]*self.w[1])
		end	
	elseif o.size==4 then
		function o:byte(offset)
			return round(
					self.t[4][offset]*self.w[4] + 
					self.t[3][offset]*self.w[3] + 
					self.t[2][offset]*self.w[2] + 
					self.t[1][offset]*self.w[1])
		end
	elseif o.size==5 then
		function o:byte(offset)
			return round(
					self.t[5][offset]*self.w[5] + 
					self.t[4][offset]*self.w[4] + 
					self.t[3][offset]*self.w[3] + 
					self.t[2][offset]*self.w[2] + 
					self.t[1][offset]*self.w[1])
		end
	elseif o.size==6 then
		function o:byte(offset)
			return round(
					self.t[6][offset]*self.w[6] + 
					self.t[5][offset]*self.w[5] + 
					self.t[4][offset]*self.w[4] + 
					self.t[3][offset]*self.w[3] + 
					self.t[2][offset]*self.w[2] + 
					self.t[1][offset]*self.w[1])
		end
	elseif o.size==7 then
		function o:byte(offset)
			return round(
					self.t[7][offset]*self.w[7] + 
					self.t[6][offset]*self.w[6] + 
					self.t[5][offset]*self.w[5] + 
					self.t[4][offset]*self.w[4] + 
					self.t[3][offset]*self.w[3] + 
					self.t[2][offset]*self.w[2] + 
					self.t[1][offset]*self.w[1])
		end
	elseif o.size==8 then
		function o:byte(offset)
			return round(
					self.t[8][offset]*self.w[8] + 
					self.t[7][offset]*self.w[7] + 
					self.t[6][offset]*self.w[6] + 
					self.t[5][offset]*self.w[5] + 
					self.t[4][offset]*self.w[4] + 
					self.t[3][offset]*self.w[3] + 
					self.t[2][offset]*self.w[2] + 
					self.t[1][offset]*self.w[1])
		end
	end
	
	return o
end

function FILTER:push(bytecode)
	local t,insert,byte={},table.insert,string.byte
	for i=1,bytecode:len() do insert(t, byte(bytecode,i)) end

	if not self.t then
		self.t = {}
		for i=1,self.size do insert(self.t, t) end
	end
	
	table.remove(self.t, 1)
	table.insert(self.t, t)
	return self
end

function FILTER:byte(offset)
	local val = 0.5
	for i=1,#self.t do val = val + self.w[i]*self.t[i][offset] end
	return math.floor(val)
end
--	o.filter:new{1,4,10,30,10,4,1} -- {1,2,4,2,1} -- {1,4,10,4,1} -- {1,2,6,2,1} -- {1,1,2,4,2,1,1} -- {1,2,3,6,3,2,1} -- ,2,4,8,16,32}		

-- flux video
local VIDEO = {}
function VIDEO:new(file, w, h, fps, gray)
	if isdir(tmp) then
		os.execute('del >nul /s /q '..tmp)
	else
		os.execute('md '..tmp)
	end

	local o = {
		cpt = 1, -- compteur image
		width = w,
		height = h,
		fps = fps or 10,
		gray = gray or false,
		image = {},
		dither = nil,
		expected_size = 54 + h*(math.floor((w*3+3)/4)*4),
		running=true,
		streams = {
			inp = assert(io.open(file, 'rb')),
			out = assert(io.popen(ffmpeg..' -i pipe: -v 0 -r '..fps..' -s '..w..'x'..h..' -an '..img_pattern, 'wb')),
		}
	}
	setmetatable(o, self)
	self.__index = self
	for i=0,7999+3 do o.image[i]=0 end
	
	o.filter = FILTER:new(VIDEO_FILTER)
	
	return o
end
function VIDEO:close()
	if io.type(self.streams.inp)=='file' then self.streams.inp:close() end
	if io.type(self.streams.out)=='file' then self.streams.out:close() end
end
function VIDEO:init_dither()
	local function bayer(t)
		local m=#t
		local n=#t[1]
		local d={}
		for i=1,2*m do
			d[i] = {}
			for j=1,2*n do
				d[i][j] = 0
			end
		end
		for i=1,m do
			for j=1,n do
				local z = 4*t[i][j]
				d[m*0+i][n*0+j] = z-3
				d[m*1+i][n*1+j] = z-2
				d[m*1+i][n*0+j] = z-1
				d[m*0+i][n*1+j] = z-0
			end
		end
		return d
	end
	local m = {{1}}
	-- m={{1,3},{3,1}}
	for i=1,dither do m = bayer(m) end
	local x = 0
	for i=1,#m do
		for j=1,#m[1] do
			x = math.max(x, m[i][j])
		end
	end
	x = 1/(x + 1)
	for i = 1,#m do
		for j = 1,#m[1] do
			m[i][j] = m[i][j]*x
		end
	end
	m.w = #m
	m.h = #m[1]
	function m:get(i,j)
		return self[1+(i % self.w)][1+(j % self.h)]
	end
	self.dither = m
end
function VIDEO:linear(u)
	return u<0.04045 and u/12.92 or (((u+0.055)/1.055)^2.4)
end
function VIDEO:pset(x,y, r,g,b)
	if not self._linear then
		self._linear = {}
		for i=0,255 do self._linear[i] = self:linear(i/255) end
	end
	r,g,b = self._linear[r],self._linear[g],self._linear[b]
	if not self.dither then VIDEO:init_dither()	end
	local d = self.dither:get(x,y)
	
	if not self._pset then
		self._pset = {}
		self._pset[0] = {}
		self._pset[1] = {}
		for i=0,15 do
			self._pset[0][i] = {}
			self._pset[1][i] = {}
			for j=0,3 do
				self._pset[0][i][j] = (i%4)     + 4*j
				self._pset[1][i][j] = (i-(i%4)) +   j
			end
		end
	end
	local o,p = x%2,math.floor(x/2) + y*160
	local function v(v) 
		-- assert(0<=v and v<=3, 'v=' .. v)
		self.image[p] = self._pset[o][self.image[p]][v]
		p = p+40
	end	
	if interlace then
		local q = self.cpt%2 == 0
		function v(v) 
			if q then
				self.image[p] = self._pset[o][self.image[p]][v]
				q = false
			else
				q = true
			end
			p = p+40
		end	
	end
	
	if self.gray then
		r = (.2126*r + .7152*g + .0722*b)*9 + d
		if     r>=4 then	v(3)
		elseif r>=2 then	v(2)
		elseif r>=1 then	v(1)
		else 				v(0)	
		end
		if     r>=7 then	v(3)
		elseif r>=5 then	v(2)
		elseif r>=3 then	v(1)
		else 				v(0)	
		end
		if     r>=9 then	v(3)
		elseif r>=8 then	v(2)
		elseif r>=6 then	v(1)
		else 				v(0)	
		end
	else
		v(math.floor(r*3 + d))
		v(math.floor(g*3 + d))
		v(math.floor(b*3 + d))
	end
end
function VIDEO:read_bmp(bytecode) -- (https://www.gamedev.net/forums/topic/572784-lua-read-bitmap/)
	-- Helper function: Parse a 16-bit WORD from the binary string
	local function ReadWORD(str, offset)
		local loByte = str:byte(offset);
		local hiByte = str:byte(offset+1);
		return hiByte*256 + loByte;
	end

	-- Helper function: Parse a 32-bit DWORD from the binary string
	local function ReadDWORD(str, offset)
		local loWord = ReadWORD(str, offset);
		local hiWord = ReadWORD(str, offset+2);
		return hiWord*65536 + loWord;
	end
	
	-------------------------
	-- Parse BITMAPFILEHEADER
	-------------------------
	local offset = 1;
	local bfType = ReadWORD(bytecode, offset);
	if(bfType ~= 0x4D42) then
		error("Not a bitmap file (Invalid BMP magic value)");
		return;
	end
	local bfOffBits = ReadWORD(bytecode, offset+10);

	-------------------------
	-- Parse BITMAPINFOHEADER
	-------------------------
	offset = 15; -- BITMAPFILEHEADER is 14 bytes long
	local biWidth = ReadDWORD(bytecode, offset+4);
	local biHeight = ReadDWORD(bytecode, offset+8);
	local biBitCount = ReadWORD(bytecode, offset+14);
	local biCompression = ReadDWORD(bytecode, offset+16);
	if(biBitCount ~= 24) then
		error("Only 24-bit bitmaps supported (Is " .. biBitCount .. "bpp)");
		return;
	end
	if(biCompression ~= 0) then
		error("Only uncompressed bitmaps supported (Compression type is " .. biCompression .. ")");
		return;
	end

	---------------------
	-- Parse bitmap image
	---------------------
	local ox = math.floor((80 - biWidth)/4)*2
	local oy = math.floor((50 - biHeight)/2)
	local oo = 4*math.floor((biWidth*biBitCount/8 + 3)/4)
	local pr = self.filter:push(bytecode)
	for y = biHeight-1, 0, -1 do
		offset = bfOffBits + oo*y + 1;
		for x = ox, ox+biWidth-1 do
			self:pset(x, oy,
						pr:byte(offset+2), -- r
						pr:byte(offset+1), -- g
						pr:byte(offset  )  -- b
			);
			offset = offset + 3;
		end
		oy = oy+1
	end
end
function VIDEO:next_image()
	if not self.running then return end
	
	-- nom nouvelle image
	local name = img_pattern:format(self.cpt); self.cpt = self.cpt + 1
	local buf = ''
	local f = io.open(name,'rb')
	if f then 
		buf = f:read(self.expected_size) or buf
		f:close()
	end
		
	-- si pas la bonne taille, on nourrit ffmpeg
	-- jusqu'a obtenir un fichier BMP complet
	local timeout = 5
	while buf:len() ~= self.expected_size and timeout>0 do
		buf = self.streams.inp:read(BUFFER_SIZE)
		if buf then
			self.streams.out:write(buf)
			self.streams.out:flush()
		else 
			if io.type(self.streams.out)=='file' then
				self.streams.out:close()
			end
			-- io.stdout:write('wait ' .. name ..'\n')
			-- io.stdout:flush()
			local t=os.time()+1
			repeat until os.time()>t
			timeout = timeout - 1
		end
		f = io.open(name,'rb')
		if f then 
			buf = f:read(self.expected_size) or ''
			f:close()
			timeout = 5
		else
			buf = ''
		end
	end
	
	-- effacement temporaire
	os.remove(name)

	if buf and buf:len()>0 then 
		-- nettoyage de l'image précédente
		-- for i=0,7999+3 do self.image[i]=0 end
		-- lecture image
		self:read_bmp(buf)
	else
		if self.cpt==2 and not self._avi_hack then
			self:close()
			self.streams.inp = assert(io.popen(ffmpeg..' -i "'..file..'" -v 0 -f avi pipe:','rb'))
			self.streams.out = assert(io.popen(ffmpeg..' -i pipe: -v 0 -r '..self.fps..' -s '..self.width..'x'..self.height..' -an '..img_pattern, 'wb'))
			self._avi_hack   = true
			self.cpt = 1
			self:next_image()
		else
			self.running = false
		end
	end
end
function VIDEO:skip_image()
	local bak = self.pset
	self.pset = function() end
	self:next_image()
	self.pset = bak
end

-- auto determination des parametres
local stat = VIDEO:new(file,w,h,round(fps/2),gray)
stat.super_pset = stat.pset
stat.histo = {n=0}; for i=0,255 do stat.histo[i]=0 end
function stat:pset(x,y, r,g,b)
	self.histo[r] = self.histo[r]+1
	self.histo[g] = self.histo[g]+1
	self.histo[b] = self.histo[b]+1
	
	self:super_pset(x,y,r,g,b)
	
	if gray==nil then
		if self.mnt==nil then
			self.mnt = {n=0,r1=0,g1=0,b1=0,r2=0,g2=0,b2=0}
		end
		local m = math.max(r,g,b)
		if m>10 then 
			m=1/m
			r,g,b = r*m,g*m,b*m
		
			m = self.mnt
			m.n  = m.n + 1
			
			m.r1 = m.r1 + r
			m.g1 = m.g1 + g
			m.b1 = m.b1 + b
			
			m.r2 = m.r2 + r*r
			m.g2 = m.g2 + g*g
			m.b2 = m.b2 + b*b
		end
	end
end
stat.super_next_image = stat.next_image
stat.mill = {'|', '/', '-', '\\'}
stat.mill[0] = stat.mill[4]
function stat:next_image()
	self:super_next_image()
	io.stderr:write(string.format('> analyzing...%s %d%%\r', self.mill[self.cpt % 4], percent(self.cpt/self.fps/duration)))
	io.stderr:flush()
end
stat.trames = 0
stat.prev_img = {}
for i=0,7999 do stat.prev_img[i]=-1 end
function stat:count_trames()
	local pos,prev,curr = 0,stat.prev_img,stat.image
	local chg = 0
	for _,i in ipairs(indices) do
		if prev[i] ~= curr[i] then chg = chg+1 end
	end
	
	for _,i in ipairs(indices) do
	-- for i=0,7999 do
		if prev[i] ~= curr[i] then 
			stat.trames = stat.trames + 1
			local k = i - pos
			if k<0 then k=8000 end
			if k<=2 then
				prev[pos] = curr[pos]; pos = pos+1
				prev[pos] = curr[pos]; pos = pos+1
				prev[pos] = curr[pos]; pos = pos+1
				prev[pos] = curr[pos]; pos = pos+1
			elseif k<=256 then
				pos = i
				prev[pos] = curr[pos]; pos = pos+1
				prev[pos] = curr[pos]; pos = pos+1
			else
				pos = i
				prev[pos] = curr[pos]; pos = pos+1
			end
		end
	end
end
while stat.running do
	stat:next_image()
	stat:count_trames()
end
io.stderr:write(string.rep(' ',79)..'\r')
io.stderr:flush()

-- determine if monochrome
if gray==nil then
	local m = stat.mnt
	m.r1,m.g1,m.b1 = m.r1/m.n,m.g1/m.n,m.b1/m.n
	m.r2,m.g2,m.b2 = m.r2/m.n,m.g2/m.n,m.b2/m.n

	local e = 0
	e = e + math.sqrt(m.r2 - m.r1*m.r1)
	e = e + math.sqrt(m.g2 - m.g1*m.g1)
	e = e + math.sqrt(m.b2 - m.b1*m.b1)
	e = e/3
	
	gray = e<.07
	-- print(gray,e)
	-- if not gray then os.exit() end
end

local max_trames = 1000000/fps/cycles
local avg_trames = (stat.trames/stat.cpt) * 1.08 -- 001 -- 0.11% safety margin
local ratio = max_trames / avg_trames
-- print(ratio)
if ratio>1 then
	fps = math.min(math.floor(fps*ratio),interlace and 2*FPS_MAX or FPS_MAX)
elseif ratio<1 then
	local zoom = ratio^.5
	w=math.floor(w*zoom)
	h=math.floor(h*zoom)
end
stat.total = 0
for i=1,255 do
	stat.total = stat.total + stat.histo[i]
end
stat.threshold_min = (gray and .03 or .05)*stat.total
stat.min = 0
for i=1,255 do
	stat.min = stat.min + stat.histo[i]
	if stat.min>stat.threshold_min then
		stat.min = i-1
		break
	end
end
stat.max = 0
stat.threshold_max = (gray and .03 or .05)*stat.total
for i=254,1,-1 do
	stat.max = stat.max + stat.histo[i]
	if stat.max>stat.threshold_max then
		stat.max = i+1
		break
	end
end
-- print(stat.min, stat.max)
-- io.stdout:flush()
local video_cor = {stat.min, 255/(stat.max - stat.min)}

-- fichier de sortie
function file_content(file, size)
	local INP = assert(io.open(file, 'rb'))
	local buf = ''
	while true do
		local t = INP:read(256)
		if not t then break end
		buf = buf .. t .. string.rep(string.char(0),512-t:len())
	end
	size = size - buf:len()
	if size<0 then
		print('size',size)
		error('File ' .. file .. ' is too big')
	end
	return buf .. string.rep(string.char(0),size)
end

local OUT = assert(io.open(file:gsub('.*[/\\]',''):gsub('%.[%a%d]+','')..'.sd', 'wb'))
OUT:write(file_content('bin/bootblk.raw', 512))
OUT:write(file_content(gray and 'bin/player1.raw' or 'bin/player0.raw', 7*512))

-- flux audio/video
local audio  = AUDIO:new(file, hz)
local video  = VIDEO:new(file,w,h,fps,gray)

-- adaptation luminosité
video.super_pset = video.pset
function video:pset(x,y, r,g,b)
	local function f(x)
		x = round((x-video_cor[1])*video_cor[2]);
		return x<0 and 0 or x>255 and 255 or x
	end
	self:super_pset(x,y, f(r),f(g),f(b))
end

-- vars pour la cvonvesion
local start          = os.time()
local tstamp         = 0
local cycles_per_img = 1000000 / fps
local current_cycle  = 0
local completed_imgs = 0
local pos            = 8000
local blk            = ''

-- init previous image
local curr = video.image
local prev = {}
for i=0,7999+3 do prev[i] = -1 end

function test_fin_bloc()
	if blk:len()==3*170 then
		local s1 = audio:next_sample()
		local s2 = audio:next_sample()
		local s3 = audio:next_sample()
		local t = s1*1024 + math.floor(s2/2)*32 + math.floor(s3/2)
	
		blk = blk .. string.char(math.floor(t/256), t%256)
		OUT:write(blk)
		blk = ''

		current_cycle = current_cycle + cycles*3
	end
end

local _recalc_indices_d = {}
for i=0,255 do 
	local a = {i % 4, math.floor(i/4)%4}
	local b = {math.floor(i/16)%4, math.floor(i/64)%4}
	_recalc_indices_d[i] = math.abs(a[1]-b[1]) + math.abs(a[2]-b[2])
end
function recalc_indices_d()
	local d = {} 
	for i=0,7999,160 do
		local e,f=0,0
		for j=i,i+119 do
			if curr[j]~=prev[j] then
				f = f + 1
				e = e + (_recalc_indices_d[prev[j]*16+curr[j]] or 6)
			end
		end
		table.insert(d, {e=e/(f>0 and f or 1), i=i})
	end
	
	local MAX_DELAY = 1000000/fps -- 100*1000 -- µs
	local e,h = 0,math.floor(MAX_DELAY *8 / (w*3*cycles))
	local PRE = math.floor(h/3)
	-- print(h,PRE)
	for i=PRE,math.min(PRE+h,50) do e=e+d[i].e end
	local m,p = e,1
	for i=PRE+1,50-h do
		e = e - d[i-1].e + d[i+h].e
		if e>m then m,p = e,i-PRE end
	end
	-- print(p,m)
	indices = {}
	for i=p,50 do
		for j=d[i].i,d[i].i+119 do table.insert(indices, j) end
	end
	for i=1,p-1 do
		for j=d[i].i,d[i].i+119 do table.insert(indices, j) end
	end
end

-- conversion
io.stdout:write(string.format('> %dx%d %s (%s) %s at %d fps (%d%% zoom, %s)\n',
	w, h, mode, aspect_ratio,
	hms(duration, "%dh %dm %ds"), fps, percent(math.max(w/80,h/50)), gray and "gray" or "color"))
io.stdout:flush()
video:next_image()
current_cycle = current_cycle + cycles_per_img
video:next_image()
while audio.running do
	-- infos
	if video.cpt % video.fps == 0 then
		tstamp = tstamp + 1
		local d = os.time() - start
		local t = "> %d%% %s (%3.1fx) e=%5.3f a=(x%+d)*%.1g"
		t = t:format(
			percent(tstamp/duration), hms(tstamp),
			round(100*tstamp/(d==0 and 100000 or d))/100, completed_imgs/video.cpt,
			-audio.cor[2], audio.cor[1]
			)
		local etc = round(d*(duration-tstamp)/tstamp/5)*5
		etc = etc>0 and d>15 and "ETC="..hms(etc) or ""
		t = t .. string.rep(' ', math.max(0,79-t:len()-etc:len())) .. etc .. "\r"
		io.stdout:write(t)
		io.stdout:flush()
	end
	
	if mode=='a' then recalc_indices_d() end
	
	for _,i in ipairs(indices) do
	-- for i=0,7999 do
		if prev[i] ~= curr[i] then 
			local k = i - pos
			if k<0 then k=8000 end
			pos = i
			local buf = {audio:next_sample()*4,0,0}
			-- if mode=='i' and w==80 then
				-- if k<=2 and ((pos-k)%40)+4>=40
			-- end
			
			if k<=2 then
				-- deplacement trop faible: mise a jour des 4 octets
				-- videos suivants d'un coup
				pos = pos - k
				buf[1] = buf[1] + 1
				buf[2] = curr[pos+0]*16 + curr[pos+1]
				buf[3] = curr[pos+2]*16 + curr[pos+3]
				prev[pos] = curr[pos]; pos = pos+1
				prev[pos] = curr[pos]; pos = pos+1
				prev[pos] = curr[pos]; pos = pos+1
				prev[pos] = curr[pos]; pos = pos+1
			elseif k<=256 then
				-- deplacement 8 bit
				buf[1] = buf[1] + 0
				buf[2] = k%256
				buf[3] = curr[pos+0]*16 + curr[pos+1]
				prev[pos] = curr[pos]; pos = pos+1
				prev[pos] = curr[pos]; pos = pos+1
			else
				-- deplacement arbitraire
				buf[1] = buf[1] + 2 + math.floor(pos/4096)
				buf[2] = math.floor(pos/16) % 256
				buf[3] = (pos%16)*16 + curr[pos]
				prev[pos] = curr[pos]; pos = pos + 1
			end
			blk = blk .. string.char(buf[1], buf[2], buf[3])
			current_cycle = current_cycle + cycles
			
			test_fin_bloc()
		end
	end
	completed_imgs = completed_imgs + 1
	
	-- skip image if drift is too big
	while current_cycle>2*cycles_per_img do
		video:skip_image()
		if video.cpt % video.fps == 0 then
			tstamp = tstamp + 1
		end
		current_cycle = current_cycle - cycles_per_img
	end		
	
	-- add padding if image is too simple
	while current_cycle<cycles_per_img do
		blk = blk .. string.char(audio:next_sample()*4+2,0,curr[0])
		pos = 1
		current_cycle = current_cycle + cycles
		test_fin_bloc()
	end
	
	-- next image
	video:next_image()
	current_cycle = current_cycle - cycles_per_img
end
test_fin_bloc()
blk = blk .. string.char(3,255,255)
OUT:write(blk .. string.rep(string.char(255),512-blk:len()))
OUT:close()
audio:close()
video:close()
io.stdout:write('\n')
io.stdout:flush()
J'ai aussi découvert le projet LUA-JIT , qui offrent un interpréteur LUA bien plus rapide que celui de base. Il est certes un peu plus gros (400ko l'exe au lieu de 200ko pour l'interprète de base), mais grâce à lui j'encode en x3 au lieu de x1.6. A la louche le programmes LUA tournent deux à trois fois plus rapidement avec lui. Il faut compter un peu au dessus d'une minute pour un vidéo-clip standard. C'est cool! 8) (executables windows et annexes pour lancer le script)

J'ai testé ce filtre vidéo avec des clips bien pêchus principalement à base de bon gros rock qui tâche (mais pas que ca) et je trouve que ca ressort très bien au final :D. En outre question audio ca me permet aussi d’entendre que les aigus passent bien en dépit des pauvre 5khz du player.

Notes:
(1) les liens sont temporaires (durée 1 mois environ)
(2) vidéos avec un filtre temporal différent ainsi qu'un niveau de "dither" supplémentaire permettant de voir un peu plus de détails sombres (particulièrement dans le clip "Thriller"): >>ici<<
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
Daniel
Messages : 17316
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

Re: [Thomson] SDDRIVE Vidéo

Message par Daniel »

Oui, c'est de mieux en mieux. Mais pour ce type de vidéo, il faudrait beaucoup plus de pixels pour convaincre le spectateur moyen.
Malheureusement les pixels sont plus faciles à voir que la performance technique :wink:

Je note une fois de plus que ça passe mieux sur un moniteur Thomson que sur un écran moderne, à cause du gamma, de la luminosité et de la couleur de fond moins noire.
Daniel
L'obstacle augmente mon ardeur.
__sam__
Messages : 7923
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

Re: [Thomson] SDDRIVE Vidéo

Message par __sam__ »

Daniel a écrit : 17 sept. 2018 11:15 il faudrait beaucoup plus de pixels pour convaincre le spectateur moyen.
Malheureusement les pixels sont plus faciles à voir que la performance technique :wink:
C'est sur que techniquement peu de personnes ne réalisent que le rafraichissement complet de l'écran sur thomson est d'une lenteur absolue. Un simple CLS à l'écran s'écrit

Code : Tout sélectionner

F070 3636       PSHU   Y,X,B,A            11
F072 3630       PSHU   Y,X                 9
F074 119360     CMPU   <$60                7
F077 2EF7       BGT    $F070               3
c'est à dire qu'il mange 30 cycles pour 10 octets, soit environ 50ms (20fps) pour remplir l'écran forme et fond. Alors pour faire de l'animation à la même vitesse c'est pas donné techniquement car le CLS ne fait rien de plus qu'écrire à fond en ram vidéo sans aucun truc à coté.

Si on avait un mode texte qui permette de remplir 8 octets vidéos en écrivant un seul octet en mémoire, on pourrait peut-être faire mieux (cf les streams sur C64). Mais étant donné qu'on a juste un mode graphique, écrire ne serait-ce qu'un caractère de 8 octets coute un beau:

Code : Tout sélectionner

  LDX #TABCHAR (3)
  CLRA         (2)
  LSLB         (2)
  ROLA         (2)
  LSLB         (2)
  ROLA         (2)
  LSLB         (2)
  ROLA         (2)
  LEAX D,X     (8)
  
  LDB  ,X      (4)
  STB  ,Y      (4)
  LDB  1,X     (5)
  STB 40,Y     (5)
  LDB  2,X     (5)
  STB 80,Y     (5)
  LDB  3,X     (5)
  STB 120,Y    (5)
  LDB  4,X     (5)
  STB 160,Y    (6)
  LDB  5,X     (5)
  STB 200,Y    (6)
  LDB  6,X     (5)
  STB 240,Y    (6)
  LDB  7,X     (5)
  STB 280,Y    (6)
112 cycles donc environ auquel il faut ajouter 48 cycles pour lire le caractère et on tombe à 160ms par octet vidéo. La dessus si on ajoute la partie audio et contrôle (>60 cycles), on est à plus de 220ms par trame de 2 octets. Le player ferait alors le remplissage de l'écran en 220ms (au lieu de 300) et jouerait le son à 4khz. Ca ne serait pas tellement mieux, surtout que cela ne prends pas en compte la table des 256 caractères redefinissables (2ko à elle seule) à mettre régulièrement dans le stream, c'est à dire que pendant ce temps on ne modifie pas l'image.

Bref, j'ai pas trop d'idée pour tirer mieux du débit de la carte SD ni même du débit du pauvre 6809 qui utilise quand même 11 cycles pour recopier un octet en mémoire ce qui le limite intrinsèquement à faire du 11fps maxi en haute résolution quand il faut changer tout l'écran. Je pense que le player ASM actuel et le mode 80x50 est sans doute ce qu'on peut faire de mieux.
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
__sam__
Messages : 7923
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

Re: [Thomson] SDDRIVE Vidéo

Message par __sam__ »

Quand on regarde le code du player, on voit qu'en fin de bloc on a plusieurs attentes de 147 et 190 cycles pendant lesquels on ne fait rien. Or en desassemblant les différentes ROM thomson de DCMOTO je me rends compte que la routine KTST (détection d'appui sur une touche) prends respectivement:
  • 25 cycles sur TO9, TO9DE, TO8 proto #14
  • 32 cycles sur TO7
  • 41 cycles sur TO8, TO9+, T9000
  • 181 cycles sur TO7/70
(sur MO je ne sais pas où trouver ces routines, si quelqu'un a des adresses clefs, je pourrais poursuivre la liste)

Ou constate donc que même avec l'infâme routine du TO7/70 on tient largement dans les temps d'attente de fin de bloc. Il doit donc être possible de detecter l'appui sur une touche "gratuitement" (mais au prix d'une certaine complexité dans le code car il faut caler ces routines sur une durée homogène). Si une touche est appuyée alors le player peut appeller GETC (récupération de la touche appuyée) qui est plus long, mais c'est pas grave car c'est là qu'on peut faire des trucs sympa en plus du player:
  • Touche "STOP" --> pause du player
  • Touche "ACC" --> sortie du player
  • Touche "->" --> avancée de 10s dans le stream (1)
  • Touche "2" --> joue le stream ~2x plus vite (vidéo éteinte) (2)
  • Touche "1" --> retour au stream 1x (vidéo allumée)
Ca serait sympa, non ? Mais bon comme ca présage être un player plus lourd je me tâte de me lancer dedans. Ca interresse du monde ou pas ces fonctionnalités ?
________
(1) comme le player joue 173 échantillons audio par bloc, un bloc est lu en 173*199=34427µs et 10s représentent un saut de 290 blocs consécutifs. Il est donc relativement facile de sauter 10s de stream: on appelle la routine SAUT 290 fois, puis on lit le(s) bloc(s) suivant jusqu'à trouver le code "saut à une adresse en ram vidéo"

Code : Tout sélectionner

   REPT  6,GET_BIT     saut échantillon audio
   GET_BIT             carry=1 si trame position
   BCS   TRAME_POS
   REP   17,GET_BIT
   RTS
(code approximatif)

(2) si on n'affiche plus la partie vidéo le player peut lire une trame de 3 octets comme suit:

Code : Tout sélectionner

  REPT  6,READ_BIT    lecture donnee son          (36)
  STB   <$CD          joue echantillon            (4)
  REPT  18,GET_BIT    saute 18bits                (72)
 
soit 112cycles. Cela permet de streamer le son au max à 8.9khz soit 77% plus vite au mieux. Quand on a réalisé ca, on comprends que la partie vidéo est pas mal optimisée tout compte fait car elle ne tue pas trop la vitesse maxi qu'on peut tirer du streaming bit par bit de la carte SD.
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
Daniel
Messages : 17316
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

Re: [Thomson] SDDRIVE Vidéo

Message par Daniel »

J'avais déjà pensé à cette amélioration possible. S'il n'y avait que le MO5 ce serait facile, on peut tester l'appui sur une touche en 10 cycles (sans appeler KTST). Avec les autres machines c'est beaucoup plus compliqué, il est difficile de faire mieux que la routine KTST, et le nombre de cycles est variable d'une machine à l'autre. Dans dcmoto c'est encore différent, car le clavier n'est pas émulé au niveau matériel.

Sur MO5, l'adresse de KTST est en $F0CF. Normalement cette adresse devrait être $F1D2.
Le nombre de cycles est variable (et grand), car les touches sont testées une par une jusqu'à la première enfoncée.
Par contre si on teste une touche particulière il est inutile de faire une boucle, il ne faut pas passer par KTST.
Daniel
L'obstacle augmente mon ardeur.
__sam__
Messages : 7923
Inscription : 18 sept. 2010 12:08
Localisation : Brest et parfois les Flandres

Re: [Thomson] SDDRIVE Vidéo

Message par __sam__ »

Ah! A que (Johnny?) c'était bien les années 80 (surtout à 20fps)... :arrow: ici aussi.
Samuel.
A500 Vampire V2+ ^8^, A1200 (030@50mhz/fpu/64mb/cf 8go),
A500 GVP530(MMU/FPU) h.s., R-Pi, TO9, TO8D, TO8.Démos
Daniel
Messages : 17316
Inscription : 01 mai 2007 18:30
Localisation : Vaucluse
Contact :

Re: [Thomson] SDDRIVE Vidéo

Message par Daniel »

Oui, la musique est toujours excellente. Pour la vidéo il faut un peu d'imagination dans les passages sombres.
Finalement je crois que c'est mieux en couleurs, même s'il n'y a pas 20 fps, comme Bad Apple par exemple.
Daniel
L'obstacle augmente mon ardeur.
Répondre