Trackermodul-Engine (sehr einfach)

Diskussion zum Thema Programmierung unter DOS (Intel x86)
Benutzeravatar
zatzen
DOS-Guru
Beiträge: 518
Registriert: Di 17. Apr 2012, 05:10
Wohnort: bei Köln
Kontaktdaten:

Re: Trackermodul-Engine (sehr einfach)

Beitrag von zatzen »

Ich habe auch mal in der Pascal-Hilfe nachgesehen, da steht durch die Anweisung interrupt bzw. bei Interrupt-Routinen werden Register als Pseudo-Parameter übergeben, also möglicherweise bei Assembler-Prozeduren auch in Form von Locals bzw. Parameter, so dass BP und SP verändert werden und BP auf dem Stack liegt. Die "interrupt" Anweisung einfach weglassen geht wohl nicht, da viele Register durch die Player-Routine verändert werden. Wenn man nur ein Warteflag ändert in der Interrupt-Routine müsste man es ohne Register-Sichern machen können, jedenfalls habe ich das so früher schonmal gemacht in einer kleinen Demo in 100% Assembler. Naja gut, oder ich mache das eben alles "händisch", lasse interrupt weg und schreibe stattdessen pushf und pusha rein und unten die entsprechenden pops, oder vielleicht statt pusha explizit nur die Register die tatsächlich im Code verändert werden. pusha sichert nicht ES oder DS, daher wäre evtl. ein einzelnes Sichern besser. Ich denke ich werde mir auch mal das Kompilat dieser Interrupt-Routine ansehen...
mov ax, 13h
int 10h

while vorne_frei do vor;
Benutzeravatar
zatzen
DOS-Guru
Beiträge: 518
Registriert: Di 17. Apr 2012, 05:10
Wohnort: bei Köln
Kontaktdaten:

Re: Trackermodul-Engine (sehr einfach)

Beitrag von zatzen »

Okay, disassembliert, also, AX-DX, SI, DI, DS und ES werden gepusht, außerdem BP und mit SP zusammen ein Stackframe angelegt der später mit LEAVE aufgelöst wird, und danach werden auch die Register wieder vom Stack geholt. Danach folgt ein IRET. Also ich denke mal, das muss nicht unbedingt sein, dann lieber per Hand benutzte Register sichern. Ich habe nur in Pascal bisher noch nie Interrupt-Routinen ohne die Deklaration "Interrupt" gemacht. Kennt eigentlich jemand einen guten Disassembler? Ich habe jetzt für diese Codesegment https://defuse.ca/online-x86-assembler.htm benutzt, aber der interpretiert einiges nicht richtig, und ist generell auf 32 Bit minimum ausgelegt.
Naja, also zu obigem Vorhaben - ich verändere im Player alle oben aufgeführten Register, auch BP, nur SP nicht. Ich kann mir also nur den Stackframe sparen. SP müsste ich mir sparen können, keine der Unterroutinen haben einen Stackframe. BP allerdings auch, das wird innerhalb der Playerroutine gesichert. Probleme könnte es geben, wenn DS außerhalb verändert wird und dann die Interrupt-Routine plötzlich dazwischen kommt, dann sind die Datenzugriffe fehlerhaft. Demnach müsste man darauf achten dass DS niemals verändert wird... Oder man macht, was generell auch eigentlich vernünftiger wäre: In der Interrupt Routine nur den nächsten Tick flaggen, und alles andere in der Haupschleife erledigen. Alternativ könnte man zur Sicherheit den Ausgangswert von DS in einer Variable speichern und in der Interrupt-Routine dann zurück in DS schreiben. Aber nein - drüber nachgedacht, das geht ja gar nicht. Wie will ich auf eine Variable zugreifen wenn DS nicht mehr stimmt?! Dann bleibt wohl nur, dass man DS nicht mehr verändern darf sobald die Interrupt-Routine aktiviert ist.
mov ax, 13h
int 10h

while vorne_frei do vor;
DOSferatu
DOS-Übermensch
Beiträge: 1220
Registriert: Di 25. Sep 2007, 12:05
Kontaktdaten:

Re: Trackermodul-Engine (sehr einfach)

Beitrag von DOSferatu »

Ich antworte später ausführlicher - aber ich bringe mal Licht in's Dunkel.
Es gibt einige Arten (5!) von RET - mit verschiedenen Opcodes.

1.)
einfacher (near) RET: (Opcode $C3)
Holt NUR IP (ein Word) vom Stack zurück. CS bleibt erhalten.

2.)
spezieller near RET: (Opcode $C2)
Holt IP (ein Word) vom Stack zurück. CS bleibt erhalten.
Danac holt es ein Word vom Stack. Dieses Word gibt an, daß soviele Bytes im Stack "übersprungen" werden sollen (bzw zurückgesprungen - wobei das beim Stack ja "nach oben" geht).
Das dient dazu, die auf dem Stack angelegten Parameter/Variablen etc. für die Subroutinen (von Hochsprachen) wieder freizugeben.

3.)
far RET, also RETF: (Opcode $CB)
Holt (in diesere Reihenfolge) IP und CS vom Stack (2 Words). (ist also das Gegenstück zu einem FAR CALL)

4.)
spezieller far RET (also RETF) (Opcode $CA)
(vor allem für Hochsprachen, die Stack-Parameter-Übergaben haben) : (Opcode
Holt (in diesere Reihenfolge) IP und CS vom Stack (2 Words), danach wieder so ein "Anzahl-Word", das wieder angibt, wieviele Bytes übersprungen werden sollen.

5.)
Interrupt-Return, also IRET (Opcode $CF)
Macht fast das Gleiche wie der far RET bei 3.), nur holt er danach noch FLAGS vom Stack.
Also holt er (in dieser Reihenfolge) vom Stack: IP, CS, FLAGS.

Um also eine Interrupt-Routine (wie z.B. die Original ISR vom Ticker oder Keyboard) so aufzurufen wie eine Subroutine, braucht man nur das tun:
PUSHF
CALL FAR (Adresse der ISR)

Weil die sich ja mit einem IRET beendet, erwartet die, daß da vor der Rücksprungadresse noch was liegt. Und wenn sie schon FLAGS erwartet, sollte man das auch tun.

Erklärung Interrupt: Wie man sieht, sichert eine ISR nichts weiter als FLAGS und die (FAR-)Rücksprungadresse.
(Man muß also FLAGS nicht pushen, weil das der Interruptaufruf selbst tut. Er sperrt dabei auch gleich das I-Flag.)
Alle anderen Register, die man benutzen will, muß man selbst sichern. Weil aber eine ISR kaum anders funktioniert wie eine normale FAR-Routine, braucht man auch nur die Register sichern, die man verändern will.

Noch etwas: Wenn man eine Subroutine benutzt und entweder Parameter übergibt oder lokale Variablen benutzt (oder beides), sollte man nicht mit BP herumspielen, wenn man sich nicht sicher ist, was man tut. Jeder Zugriff auf eine lokale Variable (auch von ASM aus) wird umgewandelt in einen Zugriff auf SS:BP+Offset (wobei Offset eben dem Offset entspricht vom Aufruf.

Beim Aufruf einer Rotuine weiß man ja nicht, wo der Stackzeiger gerade ist, daher wird dieser nach BP gesichert, damit der Zugriff auf lokale Variablen oder übergebene Parameter relativ zum Stackzeiger sind. Wenn man also BP ändert und NICHT sichert/zurückändert und danach auf eine lokale Variable (übergebene Parameter werden eigentlich auch nur wie lokale Variablen behandelt) zugreift, geht das in die Hose.

Die "interrupt"-Anweisung hinter dem Prozedurkopf macht das was Du geschrieben hast: die Handvoll (16bit!) Register sichern, inkl. DS und ES und am Ende zurückholen -sowie ein Stackframe einrichten. Auf 8086 "manuell", ab 286er mit ENTER und LEAVE, wobei je nach Aufrufart manchmal trotzdem die manuelle Variante benutzt wird - hatte das schonmal erklärt. Das Ganze ist also nur etwas, um es Hochsprachen-Codern leichter zu machen - wenn man ASM kann, kann man das auch alles selbst.

Wichtig (daher das 16bit! da oben): Weil Turbo-Pascal sich der Existenz von 32bit-Systemen nicht bewußt ist, sichert diese obengenannte Automatik nur den 16bit-Teil von 32-Bit-Registern. Wenn man also z.B. in der ISR die oberen 16bit von EAX verändert, werden diese nicht zurückholt - außer man sichert es selbst.

So, genug geschwafelt. Hoffe, es hilft.
Sollte es dazu noch Fragen geben - bin ich wie immer bereit zu helfen.
(Freue mich ja so dermaßen, daß es "da draußen" noch andere Leute gibt, die programmieren - und auch noch hardwarenah und unter DOS. Das muß unterstützt werden.)
Benutzeravatar
zatzen
DOS-Guru
Beiträge: 518
Registriert: Di 17. Apr 2012, 05:10
Wohnort: bei Köln
Kontaktdaten:

Re: Trackermodul-Engine (sehr einfach)

Beitrag von zatzen »

Vielen Dank für diese Erläuterungen!

Es scheint mir, dass Pascal, auch wenn es dann ASM Routinen sind, automatisch je nach Situation die RETs in passende Opcodes umwandelt, denn ich habe überall nur RET geschrieben und nirgends RETF. Oder habe ich da nur Glück gehabt?

Alle Prozeduren die vom Player genutzt werden (bis auf die Initialisierungen) sind rein in Assembler gehalten und ohne Locals und Parameter. Daher habe ich BP für eigene Zwecke verwendet. Aber gut, dass Du nochmal darauf hinweist, wann genau man da aufpassen muss.

Und ja, eine Frage habe ich noch, nämlich mit dem Register DS. Ist das nicht eigentlich ein Dilemma im Zusammenhang mit Interrupt-Routinen? Man biegt DS ja gerne mal um z.B. für den Heap, aber wenn in der Zeit wo DS dann nicht mehr aufs Datensegment zeigt eine Interrupt Routine dazwischenfunkt die auf Variablen zugreifen will, dann haben wir doch ein Problem. Mir fällt dazu spontan nur ein, dass man Interrupts sperren muss (CLI) für die Zeit in der DS nicht aufs Datensegment zeigt.
mov ax, 13h
int 10h

while vorne_frei do vor;
DOSferatu
DOS-Übermensch
Beiträge: 1220
Registriert: Di 25. Sep 2007, 12:05
Kontaktdaten:

Re: Trackermodul-Engine (sehr einfach)

Beitrag von DOSferatu »

zatzen hat geschrieben:Vielen Dank für diese Erläuterungen!

Es scheint mir, dass Pascal, auch wenn es dann ASM Routinen sind, automatisch je nach Situation die RETs in passende Opcodes umwandelt, denn ich habe überall nur RET geschrieben und nirgends RETF. Oder habe ich da nur Glück gehabt?
Ja, Glück gehabt. Ich verlasse mich nicht auf sowas. Ich benutze immer RETN, wenn ich einen NEAR-Return haben will oder RETF, wenn ich einen FAR-Return will. In ASM kann man teilweise etwas "kreativ" zu Werke gehen und nicht immer ist dem Compiler klar, was man da angestellt hat, wenn man mit dem Stack herumspielt.

INNERHALB des gleichen Assemblerblocks, sind RET natürlich normalerweise NEAR. Da sollten die CALLs dann natürlich auch NEAR sein.
zatzen hat geschrieben:Alle Prozeduren die vom Player genutzt werden (bis auf die Initialisierungen) sind rein in Assembler gehalten und ohne Locals und Parameter. Daher habe ich BP für eigene Zwecke verwendet. Aber gut, dass Du nochmal darauf hinweist, wann genau man da aufpassen muss.
Kleiner Hinweis dazu: BP ist, genau wie die anderen 7 Register, ab 386er ja 32bit. Falls man von Registern nur die unteren 16bit benutzen will und den Rest sichern, kann man z.B. das tun:

Code: Alles auswählen

db $66;rol BP,16;
Das tauscht quasi die oberen und die unteren 16 Bit und somit kann man quasi beides haben - sowohl den ursprünglichen Wert als auch einen anderen. Außerdem ist es schneller als PUSH/POP.
Wenn man so ein Register übrigens sichern will und das neue soll auf 0 initialisiert werden, bietet sich stattdessen SHL an:

Code: Alles auswählen

db $66;shl BP,16;
So ist BP "nach oben" gesichert und gleichzeitig auf 0 gesetzt - mit EINEM Befehl.
zatzen hat geschrieben:Und ja, eine Frage habe ich noch, nämlich mit dem Register DS. Ist das nicht eigentlich ein Dilemma im Zusammenhang mit Interrupt-Routinen?
Ich kenne dieses Dilemma - habe dazu aber eine Lösung gefunden.
zatzen hat geschrieben:Man biegt DS ja gerne mal um z.B. für den Heap, aber wenn in der Zeit wo DS dann nicht mehr aufs Datensegment zeigt eine Interrupt Routine dazwischenfunkt die auf Variablen zugreifen will, dann haben wir doch ein Problem. Mir fällt dazu spontan nur ein, dass man Interrupts sperren muss (CLI) für die Zeit in der DS nicht aufs Datensegment zeigt.
Nicht nötig.
Ja, in manchen meiner Subroutinen verändere ich DS absichtlich nicht, weil ich andauernd auch auf globale Variablen zugreifen muß und dann müßte ich das jedes Mal wiederherstellen. In Interrupts allerdings ist es schon störend, nichts "außerhalb des Interrupts" nutzen zu können - daher sichere ich DS auf etwas elegante Weise.
Wir sind uns ja einig: Alles, worüber man sich sicher sein kann, wenn ein INT aufgerufen wird, ist, daß CS definiert ist, denn CS:IP werden ja schließlich automatisch geholt, um den INT auszuführen. Achja, und man weiß, daß FLAGS gesichert ist.

Also - dann benutzen wir doch CS. Nämlich, indem wir einfach IM CODE das DS sichern! Ich habe da mal so ein kleines Testprogramm geschrieben. Der Unfug, den es anstellt, dient nur dazu, zu zeigen, daß Interrupt und Hauptprogramm gleichzeitig laufen ohne sich gegenseitig zu behindern - im Hauptprogramm wird zwischendurch DS geändert...

Ich habe es getestet - es funktioniert.
(Weil ich es als "allgmeines Anschauungsmaterial" gemacht habe, habe ich da auch in Englisch so Kommentare reingetan.)

Code: Alles auswählen

var INT8PTR:Pointer absolute 0:32;
var OLDINT8:Pointer;
var TOPLAY:word;
var TIME:longint;
var TEXT:string;
var INDEX:byte;
{----------------------------------------------------------------------------}
procedure INTER; assembler;
asm
push DS
call near ptr @saveDS
dw CS:offset @saveDS
{here starts your code for interrupt - some silly example..........}
push ES
push AX
push SI
push DI
push BX
mov AX,$B800
mov ES,AX
lea SI,DS:TEXT
xor DI,DI
mov BX,DI
mov BL,INDEX
@loop1:
cmp BL,DS:[SI]
jbe @NoChange
mov BL,1
@NoChange:
mov AL,DS:[SI+BX]
mov ES:[DI],AL
inc DI
mov AX,DI
inc AX
shr AX,1
add ES:[DI],AL
and ES:byte ptr[DI],$F
inc BL
inc DI
cmp DI,160
jb @loop1
inc INDEX
mov AL,INDEX
cmp AL,DS:[SI]
jbe @NoWrap
mov INDEX,1
@NoWrap:
pop BX
pop DI
pop SI
pop AX
pop ES
{here ends your code for interrupt.................................}
  {here an example to call the old int 8}
   pushF {because the INT8 ISR ends with an IRET. Note that NOW I-Flag is clear!}
   call OLDINT8
  {end of the interrupt routine}
pop DS
push AX
mov AL,$20
out $20,AL
pop AX
IRET{here your interrupt ends}
{--the following is only executed once - and saves DS--}
@saveDS:
pop BX  {catch the address saved by the call above into BX}
mov CS:byte ptr[BX-3],$2E {write a "CS:" prefix where first CALL was}
mov CS:word ptr[BX-2],$1E8E {write a "MOV DS,@saveDS," after the CALL}
pop CS:word ptr @saveDS {pop DS into the place where "pop BX" stands}
retF {leave normally, because its called from main program}
end;
{----------------------------------------------------------------------------}
{MAIN PROGRAM}
begin
INTER;{1x, to save DS}

asm mov AX,3;int $10;end;{set 80x25, clear screen}
writeln;
writeln('upper line: done in int8, lower line: copied in main program, changing DS!');

INDEX:=1;
TEXT:='This is a Test * (C) Imperial Games 2020 * See how to save DS for interrupt with modified code * ';

{change INT 8 to your INTER routine}
asm CLI;end;
OLDINT8:=INT8PTR;
INT8PTR:=@INTER;
asm STI;end;

{a test loop to "do something while the interrupt runs"}
repeat
write(#13'Change DS to copy lines,');
asm
push DS
mov AX,$B800
mov DS,AX
add AX,30
mov ES,AX
xor SI,SI
xor DI,DI
mov CX,80
cld
rep movsw
pop DS
end;
write('...but INT uses original DS!');

TIME:=memL[64:108]*1080 div 19663;{get ticker and calculate into seconds}
write(' Time still works: ',TIME div 3600:2,':',TIME mod 3600 div 60:2,':',TIME mod 60:2);
until memW[64:26]<>memW[64:28];{wait until key pressed}

{get back your int 8}
asm CLI;end;
INT8PTR:=OLDINT8;
asm STI;end;

memW[64:26]:=memW[64:28];{clear keaboard buffer}
end.
{explanation:
after INTER; is called once from the main program (with DS set "normally")
the "nop;nop;call near ptr @saveDS" above is changed into:
"mov DS,CS:word ptr @saveDS"
and thus it gets the real DS everytime when an interrupt is called!}
Erklärung auf Deutsch, was das macht:
Damit einem im Interrupt nicht Zyklen durch sinnlose Sprünge verlorengehen habe ich die Routine "hybrid" gemacht. Bevor man den Interrupt aktiviert, muß man EINMALIG(!) die Routine wie eine normale Procedure aufrufen. In der Routine ist nach dem PUSH DS ein Sprung nach unten (HINTER das IRET). Dieser führt nur die Befehle aus, um den Code zu modifizieren, damit es danach im Interrupt etwas anderes macht.

Die Sequenz

Code: Alles auswählen

call near ptr @saveDS
dw CS:offset @saveDS
ist 5 Bytes lang. Der CALL ist 3 Bytes und der einzelnstehende WORD-Wert, der genau die Lage des Labels @saveDS angibt, ist 2 Bytes. Die Routine unten überschreibt den CALL mit $2F,$8E,$1E, das ist der Code für mov DS,CS:,,, - und das xxx sind die restlichen 2 Bytes. Cooler wäre zwar, DS direkt mit dem Wert zu laden - aber wie wir wissen, können Segmentregister nur entweder über ein normales Register oder über eine Speicherreferenz geladen werden. Unten (hinter dem IRET) das Ganze wird nur 1x benötigt. Deshalb überschreibt es praktischerweise gleich den POP BX Befehl mit dem Wert, den DS jetzt gerade hat. Das heißt, dieser Einmal-Aufruf der Routine sollte erfolgen, wenn DS gerade den RICHTiGEN Wert hat (also am besten gleich am Anfang des Programms, wenn noch nichts geändert wurde....).

Die INT-Routine hat durch die Modifikation dann keinen CALL (oder JUMP oder wasauchimmer) mehr drin, sondern hat als erste zwei Befehle quasi diese:

Code: Alles auswählen

push DS
mov DS,CS:word ptr @saveDS
Und das ist es ja, was man sowieso braucht: DS muß ja sowieso gesichert werden (weil man nicht weiß, ob es im Hauptprogramm gerade NICHT "original" ist (und das muß ja vom INT so wiederhergestellt werden!) und danach holt es das "originale" DS, was ganz zu Anfang durch diesen Einmal-Aufruf gesichert wurde quasi "aus seinem eigenen Code". Praktischerweise habe ich es HINTER das IRET getan - so muß nichts übersprungen werden oder so.

Den ganzen Schlunz in Pascal ringsherum habe ich nur gemacht, um zu zeigen, wie man das Ganze innerhalb von Pascal einbauen könnte.

Damit die Subroutine "irgendwas macht" habe ich diesen lustigen Scrolltext da hingemurkst - das ist nicht gerade schick gecodet, soll quasi nur zeigen, daß alles, was man da innerhalb des INTs macht, geht. Und: Man muß nur die Register sichern, die man wirklich benutzt.

Man KANN danach am Ende die Original-Routine (im Falle von INT8 also den Ticker) aufrufen, einfach mit einem CALL (und einem PUSHF davor! nicht vergessen!). Das IRET des normalen INT8 würde zwar INTs wieder am Ende freigeben - aber das PUSHF macht gleich 2 nützliche Dinge: Zum Einen sorgt es natürlich dafür, daß nicht abgestürzt wird - weil ja ein IRET IP,CS und FLAGS holt. Zum Anderen ist bei FLAGS ja (weil es INNERHALB des INT ist) zu der Zeit das INT-Flag gesperrt, d.h. das "zurückholen" von FLAGS von der Originalroutine wird trotzdem keine INTs freigeben (die vielleicht am Ende in "unseren neuen" reingrätschen können. Die Freigabe macht erst unser eigener INT mit seinem eigenen IRET.

Das POP DS am Ende ist wichtig - schließlich haben wir es zu Anfang gePUSHt.
Außerdem muß man noch (vor allem, falls man NICHT die Original-Routine aufruft!), dieses OUT $20,$20 machen (also über AL), damit setzt man den Interrupt-Controller zurück. (Wenn man das NICHT macht, kommen keine weiteren INTs!)
Die Original-Routinen haben natürlich selbst am Ende diese OUT-Sachen drin (sonst würden sie ja nicht funktionieren).

Achja, wieso da am Anfang ein CALL und kein JMP? Naja, JMP wäre auch gegangen, aber 1. weiß man nicht (weil es von der Länge des Codes im INT abhängt) ob der Compiler/Assembler einen SHORT oder NEAR JUMP macht. (Ein Short JUMP hat nur 2 Bytes, nicht drei. Dann muß man noch ein NOP davor machen, damit genug Platz für den neuen (MOV DS...) Befehl ist. Außerdem habe ich durch den CALL (den ich NICHT "returniere!") nämlich auf dem Stack jetzt die Offset-Adresse, die genau hinter dem Call ist (also die "Rücksprung-Adresse"). Die lade ich in BX und greife dann mit BX auf diese Stelle da oben zu, um den Code zu ändern. Natürlich kann man das auch einfach mit Labels machen und dann LABEL und LABEL+1 ... und damit den Befehl ändern. Aber man kann es ja auch mal auf die coole Art machen. Dann wird der Befehl geändert UND natürlich ist ein zweites POP da unten und das POPt den Inhalt von DS gleich in die Stelle hinter @saveDS - damit es von dort später geholt werden kann. Man kann ja nicht nur in Register POPen, sondern auch direkt in Speicher.

Daß ein CALL ein paar Zyklen mehr braucht als ein JMP, macht hier das Kraut nicht fett. Erstens wird der ja insgesamt nur EINMAL ausgeführt (danach wird er ja überschrieben) und zweitens wird das wohl unten wieder eingespart durch die Zugriffe per BX.

Ich hoffe, ich konnte das einigermaßen erklären. Es ist zwar modifizierter Code, aber ich finde, das ist eine einfache und sehr elegante Lösung, um das Ursprungs-DS zu retten, um es im INT benutzen zu können.

Wie immer: Falls noch Fragen auftreten, gerne fragen.
Benutzeravatar
zatzen
DOS-Guru
Beiträge: 518
Registriert: Di 17. Apr 2012, 05:10
Wohnort: bei Köln
Kontaktdaten:

Re: Trackermodul-Engine (sehr einfach)

Beitrag von zatzen »

Danke nochmals für die schnelle Antwort!

Was mich bzgl. RET's etwas verwirrt ist, dass im Assembler86FAQ nur RET aufgeführt ist, und dass es dort so beschrieben ist als sei durch die Art des CALLs bedingt ob nur IP oder auch CS zurückgeholt werden. D.h. demnach müsste die CPU durch den CALL "wissen", wie sie den Return zu interpretieren hat.

Es freut mich, dass es eine Lösung zu dem DS Problem gibt. Grob kann ich es schon nachvollziehen, aber ich möchte es erst selber verwenden wenn ich es so verstanden habe dass ich es ohne nachzusehen selber coden kann. Bis dahin vermeide ich DS-Änderungen, womit man ja meistens sowieso am besten fährt, zumal man stattdessen auch FS und GS benutzen kann, wenn auch etwas kryptisch innerhalb Pascal. Noch eine Frage wäre noch, ob Pascal selbst den Hochsprachencode so kompiliert, dass eine zeitweise Änderung von DS vorkommt.

Übrigens habe ich Bedenken, ob ich bei den ZV2 Routinen mit den Registern hinkomme, d.h. mit SI, DI, BX und BP. Kombinieren kann man ja nur immer SI/DI mit BX/BP, wenn ich das richtig erfahren habe.
mov ax, 13h
int 10h

while vorne_frei do vor;
DOSferatu
DOS-Übermensch
Beiträge: 1220
Registriert: Di 25. Sep 2007, 12:05
Kontaktdaten:

Re: Trackermodul-Engine (sehr einfach)

Beitrag von DOSferatu »

zatzen hat geschrieben:Danke nochmals für die schnelle Antwort!

Was mich bzgl. RET's etwas verwirrt ist, dass im Assembler86FAQ nur RET aufgeführt ist, und dass es dort so beschrieben ist als sei durch die Art des CALLs bedingt ob nur IP oder auch CS zurückgeholt werden. D.h. demnach müsste die CPU durch den CALL "wissen", wie sie den Return zu interpretieren hat.
Um Himmels Willen! - NEIN!
Auf GAR KEINEN FALL "wissen" die CPU-Befehle irgend etwas voneinander. Jeder Befehl steht für sich allein. Ein RET macht einfach nur das, was der Opcode ihm vorgibt. Man kann ein RET auch ausführen, ohne einen CALL gemacht zu haben! Man kann einen Offset (oder eine Adresse) auch einfach manuell auf den Stack schmeißen und dann RET ausführen. (Den Trick benutze ich gern, wenn ich mal irgendwo zu einer FAR-Adresse springen will und die gerade irgendwo in einem Register rumliegen habe.)

Was möglich ist, ist, einen NEAR-RET zu einem FAR-CALL zu machen, aber nur, wenn es einer ist, der keine zusätzlichen Bytes vom Stack holen soll.
D.h. man könnte so eine Routine dann NEAR oder FAR aufrufen. NEAR-RET holt nur IP, FAR-RET holt IP und CS. Allerdings wirft ein FAR-CALL ja auch CS und IP (in dieser Reihenfolge) auf den Stack - d.h. wenn man so eine Routine dann mit NEAR-RET beenden würde, müßte man danach noch das zusätzliche Word vom Stack holen (add SP,2). Aber dieses ganze Gefrickel macht überhaupt keinen Sinn - man spart dadurch quasi nix, wenn man dafür dann außenrum so Herummurksen muß - das lohnt nur, wenn man irgendeinen kreativen Trick/Hack einsetzen will.

Also, nochmal: Die Befehle machen nur für sich das, was sie machen sollen. ein FAR CALL wirft CS und IP auf den Stack und wechselt dann zum neuen Segment und dort in den neuen Offset. Ein NEAR CALL wirft nur IP auf den Stack und wechselt dann innerhalb des gleichen Segments (das in CS steht) zum neuen Offset. Ein FAR RET holt IP und CS vom Stack und wechselt dann zum neuen Segment (CS) und dort zum Offset (IP). Ein NEAR RET holt IP vom Stack und wechselt innerhalb des aktuellen Segments zum Offset (IP). Die beiden zusätzlichen "normalen" RETs sind für Hochsprachen gedacht, weil ja am Anfang einer Subroutine Parameter über den Header übergeben werden oder auch im Kopf (zwischen Header und BEGIN) noch lokale Variablen angelegt werden... Diese ganzen Dinge landen zusätzlich auf dem Stack - und beim Beenden muß der Stackpointer ja wieder da stehen, wo er vor dem CALL war, deshalb diese RETs, die zusätzlich einen Parameter haben, der entsprechend viele Bytes noch "überspringt".

Und der IRET - naja, bei dem macht es ja Sinn, u.a. daß FLAGS zu speichern (vor allem, weil ein INT ja selbst ein Bit darin ändert - das INT-Erlauben-Bit). Und weil man im Interrupt ja nicht NICHTS machen will, würde sich ja sowieso mindestens irgend etwas in FLAGS ändern - und das Hauptprogramm darf ja davon nichts mitbekommen...
zatzen hat geschrieben:Es freut mich, dass es eine Lösung zu dem DS Problem gibt. Grob kann ich es schon nachvollziehen, aber ich möchte es erst selber verwenden wenn ich es so verstanden habe dass ich es ohne nachzusehen selber coden kann.
Naja, es ist etwas mehr Code als nötig gewesen wäre - das ganze "Drumherum" habe ich nur gemacht um zu demonstrieren, wie man es anwendet und daß es funktioniert. In Wirklichkeit ist der einzige Witz daran, daß man einfach nur direkt irgendwo im Codebereich des INT (also irgendwo, wo CS:xx ist) das DS vorher hinterlegt, damit es der INT finden kann. Man MUß das auch nicht so elegant wie ich machen. Aber weil ja CS das einzige Segmentregister ist, was zum Zeitpunkt des Aufrufs des INT einen sichergestellten Wert enthält, ist das meiner Meinung nach die einzige Möglichkeit, innerhalb eines INT an das DS zu kommen.

Eigentlich ist das nicht ganz richtig. Der Assembler von Pascal bietet zwei Pseudo-Konstanten an: @DATA und @CODE.
mit SEG @DATA kann man das Haupt-Datensegment angeben, mit SEG @CODE das Haupt-Codesegment. Allerdings kann man ja leider einem Segment keine Konstante zuweisen (dafür gibts keinen Opcode). Also geht das dann wieder nur über ein Register: mov AX,SEG @DATA; mov DS,AX;

Bin aber nicht sicher, ob das immer funktioniert - normalerweise sollte es aber.
zatzen hat geschrieben:Bis dahin vermeide ich DS-Änderungen, womit man ja meistens sowieso am besten fährt, zumal man stattdessen auch FS und GS benutzen kann, wenn auch etwas kryptisch innerhalb Pascal. Noch eine Frage wäre noch, ob Pascal selbst den Hochsprachencode so kompiliert, dass eine zeitweise Änderung von DS vorkommt.
Naja, normalerweise würde ich nein sagen - andererseits KENNT Pascal ja noch kein FS/GS. Diese in ASM gern als "String-Operationen" bezeichneten Dinge (REP MOVSW und ähnliches) brauchen ja DS und ES. (Man kann dabei DS auch mit etwas anderem "überschreiben - wenn man hat! - aber da bleiben ja nur CS und SS, weil, wie gesagt, FS und GS benutzt Pascal von sich aus nicht.) Also... ja, es könnte entweder sein, daß Pascal DS auch mal ändert ODER es ändert, aber nur in CLI/STI eingegrenzt. Kann ich leider nicht sagen - habe noch nicht alle Befehle von Pascal quasi manuell überprüft.
zatzen hat geschrieben:Übrigens habe ich Bedenken, ob ich bei den ZV2 Routinen mit den Registern hinkomme, d.h. mit SI, DI, BX und BP. Kombinieren kann man ja nur immer SI/DI mit BX/BP, wenn ich das richtig erfahren habe.
So ist es: Es gibt diese Kombinationen:
[BX], [SI], [DI], [BX+SI], [BX+DI], [BP+SI], [BP+DI]
dann noch alle +Bytewert, alle +Wordwert (hier auch [BP+Bytewert] und [BP+Wordwert]) - nur [BP] einzeln gibt es nicht, das wird immer intern umgewandelt in [BP+0]. (Grund dafür ist, daß an der "Stelle", wo das von der Abfolge her stehen würde, der einfache Wert (ohne Indexregister) steht, also [Wert] - sonst hätte man das nicht umsetzen können.)

ABER: Das Ganze gilt nur für 16bit. Wir haben ja auch die Möglichkeit der 32-Bit-Adressierung! Wenn man damit im 16bit-Mode auf Offsets >65535 zugreift, stürzt zwar der Rechner ab - aber da muß man eben drauf achten, daß das nicht passiert! Indem man die oberen 16bit der Register =0 setzt. Denn in der 32-Bit-Adressierung kann man ALLE Register (außer natürlich die Segmentregister) zur Adressierung benutzen, diese werden dann aber in ihrer 32-Bit-Entsprechung benutzt (also EAX statt AX).

Dazu folgt dem normalen Mod-R/M-Byte, das den normalen Befehlen folgt, wenn sie das $67-Präfix haben, zusätzlich noch ein S-I-B Byte (Scale-Index-Base).

Das heißt so, weil man bei einem der beiden Register sogar angeben kann, daß es mit 1, 2, 4 oder 8 multipliziert wird (wenn man beide auf das gleiche Register setzt, sind so also auch Indezes wie 3, 5 und 9 möglich).

Das S-I-B Byte hat also in seinen 8 Bit: 2Bit, die angeben, ob *1, *2, *4 oder *8 (bei einem der Register) und dann 3Bit für das eine (Base) Register (das ist das, was multipliziert werden kann) und 3Bit für das andere (Index) Register (das immer *1 ist). Da gibt's auch so "Sonderfall" der auch an der Stelle steht, wo normal EBP wäre... also so Fall wo ohne Register sondern nur mit Wert bzw in Kombination mit Wert...

Die Register sind bei x86 ja bekanntlich "numeriert" in dieser Reihenfolge:
AX, CX, DX, BX, SP, BP, SI, DI - so sind dann also auch die Bitmuster:
(000, 001, 010, 011, 100, 101, 110, 111)
Das gilt dann auch für die Exx-Register: gleiche Reihenfolge.

Nun kennt ja TurboPascal keine 32bit-Sachen, deshalb muß man, WENN man diese Geschichte nutzen will, das natürlich alles schick manuell basteln - aber ich habe Tabellen da, falls man's braucht.

ja, diese super-indizierte Methode ist natürlich recht schick - allerdings, wie gesagt: Da immer dran denken, daß die oberen Bits =0 sind, bzw. daß das ERGEBNIS, also der "Offset" von der Konstruktion dann ein 32-Bit-Wert wird, der auch im 16-Bit-Mode NICHT "wraparoundet"! Und wenn dieses Ergebnis einen Wert >$FFFF erreicht, dann wird so ein Fehler-INT ausgelöst (weiß grad nicht auswendig, welcher - steht sicher irgendwo, z.B. in der ASM86FAQ) - was im Klartext bedeutet: Wenn man den nicht abgefangen hat (was man sowieso kaum tut): Absturz. Und WENN man ihn abfängt ... naja, dann hat man kaum Performance gewonnen durch die Konstruktion, weil wenn da jedesmal eine Fehler-INT-Bereinigungs-ISR laufen soll...

Wollte nur der Vollständigkeit halber drauf hinweisen, daß Dir im 32bit-Mode (bzw mit der 32-Bit-Address-Option) auch die anderen Register zur Indizierung zur Verfügung stehen.
Benutzeravatar
zatzen
DOS-Guru
Beiträge: 518
Registriert: Di 17. Apr 2012, 05:10
Wohnort: bei Köln
Kontaktdaten:

Re: Trackermodul-Engine (sehr einfach)

Beitrag von zatzen »

DOSferatu hat geschrieben:Eigentlich ist das nicht ganz richtig. Der Assembler von Pascal bietet zwei Pseudo-Konstanten an: @DATA und @CODE.
mit SEG @DATA kann man das Haupt-Datensegment angeben, mit SEG @CODE das Haupt-Codesegment. Allerdings kann man ja leider einem Segment keine Konstante zuweisen (dafür gibts keinen Opcode). Also geht das dann wieder nur über ein Register: mov AX,SEG @DATA; mov DS,AX;

Bin aber nicht sicher, ob das immer funktioniert - normalerweise sollte es aber.
Selbstdefinierte Konstanten liegen ja auch im Datensegment, mit denen kann es also nicht funktionieren. Auch im Hauptprogramm DS im Codesegment zu hinterlegen und dann mit "mov ax, cs:[offset @dsbackup]" darauf zuzugreifen wäre wahrscheinlich unsicher, da sich CS ändern kann. Aber wenn man diese Konstanten @DATA und @CODE als Compilerkonstanten betrachten könnte auf die referenziert werden kann unabhängig vom Registerinhalt von DS oder CS, dann könnte man daraus vielleicht wirklich eine simple Lösung zu dem Problem basteln, ohne modifizierten Code. Aber das lässt sich ja auch relativ einfach überprüfen.
DOSferatu hat geschrieben:ABER: Das Ganze gilt nur für 16bit. Wir haben ja auch die Möglichkeit der 32-Bit-Adressierung! Wenn man damit im 16bit-Mode auf Offsets >65535 zugreift, stürzt zwar der Rechner ab - aber da muß man eben drauf achten, daß das nicht passiert! Indem man die oberen 16bit der Register =0 setzt. Denn in der 32-Bit-Adressierung kann man ALLE Register (außer natürlich die Segmentregister) zur Adressierung benutzen, diese werden dann aber in ihrer 32-Bit-Entsprechung benutzt (also EAX statt AX).
Dann wohl am einfachsten "db 66h; xor ax, ax"? Klar, wenn ich den Wert behalten will wohl lieber "db 66h; ror ax, 16; xor ax, ax; ror ax, 16" - sind aber auf nem 486 direkt 5 Takte. Ach ich dumme Nuss - "db 66h; and ax, 0ffffh" geht ja auch.
Das sind natürlich tolle Möglichkeiten, gerade auch mit der Multiplikation, ich müsste mir nur wenn ich das "per Hand" bastle mir den Assembler-Code als Kommentar dazuschreiben. Ich brauche diese Übersichtlichkeit, deshalb rücke ich auch die Zeilen ein.

Danke für diesen Hinweis, vielleicht wird er bei ZVID2 sehr wichtig werden. Aber dazu muss dann auch erstmal die Sache mit dem Unchained Mode geklärt werden. Und irgendwie meine ich, dass die Idee mit dem nur teilweise wiederherstellen und nur veränderte Bereiche in den Grafikspeicher kopieren auch im Unchained Mode sinnvoll sein könnte, sogar mit Scrolling, wenn man das mittels vier Seiten macht. Aber alles noch Neuland...
mov ax, 13h
int 10h

while vorne_frei do vor;
Benutzeravatar
zatzen
DOS-Guru
Beiträge: 518
Registriert: Di 17. Apr 2012, 05:10
Wohnort: bei Köln
Kontaktdaten:

Re: Trackermodul-Engine (sehr einfach)

Beitrag von zatzen »

Hallo DOSferatu!
Ich habe das mit dem @data einmal ausprobiert mit folgendem Code:

Code: Alles auswählen

  var ds1, ds2: word;
begin
  asm
    mov ds1, ds
    push ds
    xor ax, ax
    mov ds, ax
    mov ax, seg @data
    pop ds
    mov ds2, ax
  end;
  writeln(ds1, ' ', ds2);
end.
Ergebnis: Beide Variablen, ds1 und ds2, haben den gleichen Wert.
Das müsste eigentlich beweisen, dass "seg @data" zum einen auf das Datensegment zeigt bzw. DS entspricht, und zum anderen unabhängig von dem Register DS seinen Wert behält. Deine Code-modifizierende Interrupt Routine ist ein tolles Kunststück, aber ich würde das ganze dann einfach über "seg @data" lösen. Oder was meinst Du?
mov ax, 13h
int 10h

while vorne_frei do vor;
Benutzeravatar
zatzen
DOS-Guru
Beiträge: 518
Registriert: Di 17. Apr 2012, 05:10
Wohnort: bei Köln
Kontaktdaten:

Re: Trackermodul-Engine (sehr einfach)

Beitrag von zatzen »

Okay, das Problem scheint gelöst. Hier nur die kleinere der beiden Interrupt-Routinen:

Code: Alles auswählen

procedure interrupt_flagtick; assembler;
asm
  push ax
  push ds
  mov ax, seg @data
  mov ds, ax
  mov btplay_next_tick, 1
  mov ax, tickervalue
  add tickerwrap, ax
  pop ds
  jc @call_oldint
    mov al, 020h
    out 020h, al
    pop ax
    iret
  @call_oldint:
    pop ax
    pushf
    call old_intvec
    iret
end;
So wie ich das nachgelesen habe beeinflusst ein POP die Flags nicht, daher müsste hier JC noch funktionieren.
mov ax, 13h
int 10h

while vorne_frei do vor;
DOSferatu
DOS-Übermensch
Beiträge: 1220
Registriert: Di 25. Sep 2007, 12:05
Kontaktdaten:

Re: Trackermodul-Engine (sehr einfach)

Beitrag von DOSferatu »

zatzen hat geschrieben:Ich antworte hier schonmal...
DOSferatu hat geschrieben:[Portierung des Beni Tracker Players]Ja, habe das so nebenher mitbekommen. Wie weit bist Du?
Ich bin soweit fertig mit der Unit. Die eigentliche Abspielroutine habe ich erstmal nur 1:1 in Assembler umgeschrieben, aber dabei die Möglichkeiten von Assembler genutzt, Register öfters auf ihrem Wert belassen zu können, und auch Berechnungen beim Adressieren von Speicher stark zu vereinfachen oder zu vermeiden.
Ja, wie ich schon (sicher mehrmals) erwähnt habe: Wenn man erstmal mit Assembler angefangen hat, merkt man, daß es einen nicht einschränkt, sondern oft sogar das Gegenteil.
zatzen hat geschrieben:Datenfeldadressierungen in Pascal arten ja je nachdem im Kompilat in Code mit MUL-Anweisungen aus.
Wollte immer mal prüfen, ob das so ist - vermute es aber auch. Vielleicht gibt es wenigstens Optimierungen für eindimensionale Felder mit Datenfeldgrößen in 2er-Potenzen.
zatzen hat geschrieben:Ich dachte noch dran, die Abspielroutine ganz neu nach meinem eigenen Gutdünken zu schreiben, wenn ich die Vorgänge verstanden habe. Aber die Mühe schien mir vorerst zu groß. Aber so eine Unit kann man ja jederzeit updaten und trotzdem nach außen hin abwärtskompatibel halten. Immerhin ist das Kompilat durch die Umsetzung in Assembler schon um ein paar KB geschrumpft.
Naja, vor allem ist es nun portiert und dadurch in Pascal einsetzbar. Wie Dir ja schon selbst ausgefallen ist, hat das GW-BASIC, sowohl was Speicherverfügbarkeit als auch Ausführungsgeschwindigkeit anbetrifft, gewisse Grenzen - die Borlands TurboPascal zwar auch hat, dort aber wohl etwas höher liegen.
zatzen hat geschrieben:Was ich aber gemacht habe, und da kennst Du mich mittlerweile, ist dass ich die Patterndaten komprimiert im Speicher ablege. Diese werden vom Tracker schon relativ intelligent angelegt, nämlich Track-weise, und man muss selbst im Tracker ein mehrspuriges "Order" aus diesen Einzelspalten zusammenstellen. Diese Spalten-Patterns haben je 64 Einträge, die wiederum aus Note, Oktave, Instrument, Effektnummer und Effektdaten bestehen. Der Tracker speichert diese Informationen jeweils zusammengepackt in 3 Bytes, der Player expandiert das aber im Original auf 5 Bytes und legt es im EMS ab, zusätzlich richtet er einen Puffer im Datensegment ein für alle 9 Kanäle, der für jeden Order aus dem EMS gefüttert wird, also 5 Bytes * 64 * 9.
Tja, und so in der Art wird es auch von AtavISM gehandhabt - trackweise und er stellt selbst das Pattern daraus zusammen, erkennt identische Tracks (speichert diese dann nur einmal) und "packt" die Tracks intern (weil sie ja intern im "programm-artigen" ISM-Format vorliegen, mit Option für Schleifen und Subroutinen). Ich hatte das eben so gestaltet, daß es für den AtavISM-User nicht mehr auffällt.
zatzen hat geschrieben:In BASIC kam dann noch Faktor zwei dazu, weil es nur Integer aber nicht Byte kennt.
Ja, Hochsprachen, die "allzusehr" auf "ich bin eine Hochsprache und niemand muß wissen wie ein Computer WIRKLICH funktioniert" ausgelegt sind, sind mir inzwischen ein Greuel. Nichtmal byteweise arbeiten zu dürfen oder nicht die Wahl zu haben, ob mit oder ohne Vorzeichen - da würd' ich ausrasten...
zatzen hat geschrieben:Ich mache ziemlich das Gegenteil, lege die Patterndaten auf den Heap, aber komprimiert: Jedes Spaltenpattern bekommt eine Tabelle, die alle Ereignisse (Byte-Triplets) enthält, aber mit jeglichen Redundanzen rausgekürzt. Dazu kommt 64 mal eine Info, welcher Tabelleneintrag zu welcher Zeile gehört, diese Info nur so viele Bit breit, wie zur Adressierung aller Tabelleneinträge nötig ist. Angenommen man hat typischerweise Tabelleneinträge < 17, dann reichen 4 Bit-Werte für die Adressierung und die Tabelle ist maximal 16*3 Bytes groß. Man hat dann also statt 192 Bytes für das unkomprimierte Pattern nur 16*3 + 32 Bytes = 80 Bytes, plus ein paar Bytes für die Deklaration der Bitbreite der Adressierung und der Offsets der Tabellen und Adressreferenzen.
Ja, SO komplex hab' ich's bei AtavISM nicht gemacht - aber das liegt auch daran, daß AtavISM ja nicht einer "eigenständigen Grundidee" entspricht, sondern nur eine zusätzliche Möglichkeit darstellen soll, Musikdaten einzugeben, die ohne Änderung von der ISM-Player-Routine abgespielt werden können. Daher hat das ganze Ding ja auch so ein "Template" aus ISM-Daten um sich herum. Die Speicherersparnis ist bei AtavISM also allgemein geringer als bei prISM.
zatzen hat geschrieben:Insgesamt ergeben sich nicht selten Kompressionsraten auf geringer als 25%, da z.B. bei Schlagzeug oder simplen Melodien die Redundanzen noch stärker ausgeprägt sind. Nehmen wir ein Bassdrum-Pattern als Extrem, zwei Tabelleneinträge (Bassdrumereignis und Leerzeile), dazu eine 1 Bit Adressierung, also 6 + 8 Byte.
Allerdings hatte ich ISM ja nie primär zum Speichersparen ausgelegt (hatte ich ja mal erwähnt) - das ist nur ein Nebenfeffekt der beabsichtigten Arbeitsweise, die mir angenehmer ist als in "Trackern":
Ich bin ein eher bescheidener Musiker/Komponist - meine Stücke bestehen meist aus 2-3 Stimmen - mein allererstes vierstimmiges Stück war allen Ernstes dieses abartige "Entenlied" was ich da gezimmert hatte - und das auch eher als Experiment/Machbarkeitsstudie. Meine Stücke bestehen aus einer Hauptmelodie, einer Begleitmelodie und einer Art Percussion. Ich mache meist zuerst die Begleitmelodie. Diese (und die Percussion) sind aber SEHR repetativ - und wenn ich irgend etwas an Begleitmelodie oder Percussion ändern will (weil es mir nicht mehr so gefällt oder es nicht mehr so paßt), brauche ich das nur an einer einzigen Stelle (bzw. sehr wenigen Stellen) ändern und schon ist es wieder wie gewünscht. Außerdem kann ich so z.B. den gleichen Ablauf transponieren/schneller/langsamer spielen, um mehr Dynamik in das Stück zu kriegen und selbst diese Teile werden dann gleich mit geändert. Das spart mir eine Menge Zeit - und unkreativ/unmusikalisch, wie ich bin, bin ich froh, wenn ich überhaupt mal ein einigermaßen gescheites Stück zusammenhabe.

Das ist die ganze (oder ein Hauptteil) der Erklärung für ISM. Der Rest ergab sich daraus, daß ich ein so ähnlich funktionierendes Ding (von jemand anderem programmiert) auf dem C64 hatte und gern benutzt hatte. (Übrigens ist sowohl prISM als auch meine Malprogramme alle basierend auf Ideen/Bedienkonzepten von entsprechenden C64-Programmen entstanden. Sowohl mein Textmode- als auch mein Grafikmode-Malprogramm basieren in der Bedienung grob auf dem C64-Programm "Amica-Paint".)
zatzen hat geschrieben:Und ich brauche keinen Puffer, die Daten werden für jede Zeile/Track sozusagen mit "random access" gelesen.
Naja. AtavISM puffert nur den aktuellen Track während der Bearbeitung für den User. Beim Abspielen ist aber nichts mehr davon da, das ganze Stück liegt abspielbar im Speicher und für ISM macht es keinen Unterschied, ob es in prISM oder AtavISM erstellt wurde - das war auch das Hauptanliegen.
zatzen hat geschrieben:Hier noch die bisherige Interrupt-Routine:

Code: Alles auswählen

procedure introut_playtick; interrupt; assembler;
asm
  mov btplay_next_tick, 1
  call btplay_playtick
  mov ax, tickervalue
  add tickerwrap, ax
  jc @call_oldint
    mov al, 020h
    out 020h, al
    jmp @end
  @call_oldint:
    call old_intvec
  @end:
end;
RET oder IRET hat hier nicht funktioniert, daher JMP @end. Ist da vielleicht noch etwas auf dem Stack?
NATÜRLICH ist etwas auf dem Stack! Hatte ich ja in dem anderen Thread bereits geschrieben - vielleicht überflüssig, das jetzt nochmal zu erwähnen:
Die Pascal-Anweisung "Interrupt" legt einen Stack-Frame an UND sichert auch die ganzen Register auf den Stack! Die müssen da natürlich wieder runter! Wenn Du sowieso in Assembler programmierst, spar' Dir die "Interrupt"-Anweisung! Die ist nur für Hochsprache nützlich. In Assembler machst Du das sowieso alleine. Da würd ich einfach am Anfang db $66;pushA; machen und am Ende db $66;popA;IRET.
zatzen hat geschrieben:btplay_playtick ist die "dicke" Abspielroutine, der Kern der ganzen Sache.
Schon klar. Ich selbst spiele ja nicht im Interrupt ab. (Der spielt sowieso nicht ab, sondern berechnet nur die Daten. Das Abspielen passiert ja im SoundBlaster...)
Kann man natürlich so machen, dann braucht man in der Hauptschleife nicht ständig darauf achten, ob der Puffer schon abgelaufen ist, sondern das im Interrupt machen UND gleich darauf entsprechend reagieren. So braucht man das nicht in jeder Schleife machen.

Mein Konzept ist da leicht anders - was aber nur daran liegt, daß es bei mir immer nur EINE Hauptschleife gibt.
zatzen hat geschrieben:In DOS jedenfalls lohnt sich Assembler, wie ich schon gesehen habe, dass eine Zeile Pascal-Code auf mehr als doppelt so viel Takte und deutlich mehr Instruktionen gebläht wurde als dasselbe intelligent in Assembler formuliert.
Ja, das liegt daran, daß die Hochsprachen Befehle immer die gleichen Dinge tun - mitunter braucht man manche Dinge, die ein Befehl tut, an der Stelle nicht wirklich. Und in Assembler läßt man eben alles weg, was man nicht braucht. Bestimmt ist jede Subroutine in Hochsprachen IMMER mit Prüfroutinen ausgestattet, die jedes Mal testen, ob übergebene Werte im korrekten Wertebereich liegen und das entsprechend berücksichtigen. In Assembler WEIß man ja, was man tut (naja, sollte man zumindest) und braucht einen EINMAL geprüften Wert nicht immer wieder prüfen. Außerdem müssen nur Werte geprüft werden, die von unbekannten Quellen stammen - Usereingabe, geladenes File u.ä. - auf Werte, die man selbst im Programm erzeugt hat, sollte man sich verlassen können, die müssen nicht durch 5 Prüfroutinen gejagt werden.
zatzen hat geschrieben:Die Beni Tracker Player Portierung besteht zu ca. 95% aus Assembler, da bekommt man langsam Lust oder den Mut, sich komplett von Pascal zu verabschieden und alles in Assembler zu schreiben. Man müsste nur mehr kommentieren, sonst kann man bei den ganzen Anweisungen doch schonmal die Übersicht verlieren. Aber naja, selbst wenn alle Procedures in Assembler geschrieben sind ist ein bisschen Pascal-Code als Gerüst doch ganz nett.
Ja, ich nutze gern Pascal als Gerüst, weil ich z.B. keine Lust hätte, selbst einen EXE-Header zu bauen. Aber viele Dinge, vor allem "Dinge, die andere Dinge ausführen", da setze ich zunehmend auf 99% Assembler, weil ich einfach den Speicher- UND Geschwindigkeitsvorteil sehe. Kleiner Nachteil ist, daß Entwicklung in Assembler etwas länger dauert - aber zum Glück hetzt mich ja keiner. (Oder gibt's hier etwa wirklich jemanden, der auf mein neues Spiel wartet?)
zatzen hat geschrieben:CALLs: Wenn ich nicht irre (und ich meine so hab ich es auch im ASM FAQ gelesen) gilt RET sowohl für NEAR als auch für FAR CALLs. Mir schwirrte noch "RETF" im Kopf, aber das gibt es wohl gar nicht.
Doch, RETF gibt es. Aber Ich hatte das ganze CALL/RET Thema ja schon ausführlich behandelt. Will ja nur noch auf diesen ursprünglichen Beitrag antworten, bevor quasi durch die nachfolgenden Sachen der Zusammenhang in diesem Topic etwas verlorenging.
zatzen hat geschrieben:Der große Unterschied zu DOS ist das Multitasking (auch wenn es nur emuliert ist), das ich bei DOS vermissen würde. Bei DOS hatte man ja immerhin schon eine Faszination dafür, nämlich wenn man TSRs benutzt hat. Sei es um gleichzeitig Musik zu hören und etwas anderes tun zu können, oder einen Screengrabber zu nutzen. Ich würde mich mittlerweile schon etwas eingeschränkt fühlen wenn ich immer jedes Programm erst beenden müsste um ein anderes zu benutzen, vor allem wenn ich eigentlich beide eher parallel brauche.
Naja, beim Programmieren ist das für mich eher kein Problem, da man Turbo-Pascal ja auch zwischendurch verlassen kann und wieder zurückgehen ("DOS aufrufen"). Pascal speichert dann alles und wenn man mit Exit zurückgeht, ist alles wieder so wie bevor man rausging.

Und: Computer (bzw. "moderne OS") können wohl Multitasking - ich aber nicht: Ich kann immer nur EIN Programm gleichzeitig bedienen - deshalb würde mir so ein "Taskwechsel" Zeug auch ausreichen: Immer wenn man ein anderes Programm benutzt, halten alle anderen an.

zatzen hat geschrieben:
DOSferatu hat geschrieben:Ich finds 'ne steinkalte Schande, daß VGA keinen Rasterzeileninterrupt hat
Ich habe vor Jahren mal in irgendeinem Code kommentiert gelesen "wait for vertical retrace". Ich bin mir nicht sicher ob das auch mit CRTs zu tun hatte und bei TFTs & Co. gar nicht funktioniert hätte.
Ja, deshalb ja "wait for vertical retrace". Gäbe es einen Interrupt dafür, bräuchte man nicht warten und "pollen". Es GIBT ein Bit (Flag) in einem VGA-Register, das anzeigt, ob gerade ein VR stattfindet - nur muß man das eben dauernd abfragen, um das zu erfahren. Beim Grafikchip des C64 kann man sogar die Bildschirmzeile festlegen, wann ein Interrupt erfolgen soll (oder mehrere) - also nicht nur beim Bildende.
zatzen hat geschrieben:
DOSferatu hat geschrieben:[Soundpuffer]Das "live" Ausführen, sobald angefordert funktioniert auf alten Konsolen, weil die einen dedizierten Soundchip haben, der neben der CPU her arbeitet und sofort etwas macht, sobald man es will. Aber so Dinge wie die Sound Blaster wollen ja erst einen fertig berechneten Puffer.
Viele Spiele nutzen ja die Kombination Adlib + Digisound, und letzterer geht dann nicht über einen Mischpuffer sondern spielt nur per DMA direkt Sounddaten ab, das geht dann ohne Verzögerung, aber eben nur "einstimmig".
Naja, daß ich drauf kam, Daten digital zu berechnen/abzuspielen, liegt wohl u.a. daran, daß ich damals nur mit PC-Speaker (sowohl "analog" als auch im "digitalen Modus") gearbeitet habe und der hat ja nicht so Dinge wie AdLib drin. Das komplette ISM (und prISM) hatte ich 100% mit dieser "Digi-Speaker"-Variante entwickelt und die SoundBlaster-Fähigkeit hatte ich dann quasi "nachgerüstet". Seit ich mir natürlich eine Blaster-Unit gebaut habe, ist SoundBlaster abspielen für mich genauso einfach wie PC-Speaker.

Schade, daß kein "Covox Speech Thing" oder wie das Ding hieß habe und auch keinen so Nachbau. Wäre mal cool, diese Methode per LPT zu hören... Ja, ich weiß: Kann man sich auch selber löten - wenn man kann.
zatzen hat geschrieben:Was Puffer-berechneten Sound angeht muss ich mir mal ein paar Spiele ansehen, wie z.B. Pinball Dreams, die Konversion vom Amiga, bei dem ich mich an keine besonderen Verzögerungen erinnere.
Naja, es werden kurze Puffer sein. Aber dank Puffer-Vorberechnung und Puffer-DMA wird hat man eben trotzdem eine Verzögerung. (Das Ding mit Ursache und und Wirkung.) Bei der ganzen Dynamik eines Spiels fallen einem so Dinge wahrscheinlich nur nicht so auf.
zatzen hat geschrieben:Dosbox: Sehr erfreulich dass der ROR/ROL Bug wahrscheinlich demnächst raus ist.
Naja, wie gesagt: Wenn man Core auf =dynamic stellt, hat man den Bug auch jetzt schon nicht. Eine offizielle neue Version (die dann den Bug entfernt hat) gibt es wohl noch nicht. Könnte man sich höchstens selbst compilieren. (Aber das mach ich sowieso nicht.)
Aber wenn ich mein Zeug weitergebe und das soll unter DOSBox laufen, muß ich ja davon ausgehen, daß die User nur eine "reguläre" Version haben. Aber, wie bereits gesagt: Ich verzichte weder auf Code-Tricks, noch optimiere ich "in Richtung DOSBox". Mein Zielsystem ist die reale Maschine. Wenn ein Emulator die fehlerhaft emuliert, ist der Emulator schuld, nicht das Programm, das auf der echten Maschine fehlerfrei läuft.
zatzen hat geschrieben:Für mich stellt Dosbox ein wenig auch soetwas dar, wie meinetwegen Ende der 80er Leute z.B. für den C64 oder Konsolen in einer technisch fortschrittlicheren Umgebung programmierten. Es ist nicht ganz damit zu vergleichen aber ich habe vor allem zwei Vorteile: Ich kann einen Hex-Editor oder andere Daten-lesende Programme parallel laufen lassen ohne dass ich für einen Refresh der Daten Pascal beenden muss. Und was ich schonmal sagte, bei dummen Programmierfehlern, sei es in Assembler oder Pascal, die zu einem Absturz führen, muss ich keinen echten Rechner neu booten. Kurzgesagt, das Debugging innerhalb einer Multitasking-Umgebung ist für mich wenigstens einfacher.
Ja, hast schonmal erwähnt. Wenn mein 486er einen Komplettabsturz hat und ich den neu booten muß, braucht der, selbst wenn die Treiber geladen werden, 20 Sekunden zum Booten. Genau die richtige Zeit zum "runterkommen" (vom Ärgern über Absturz) und zum "in sich gehen". Der Computer ist ja nie schuld (ja, außer bei echten Hardwarebugs), der Computer macht ja nicht das, was man will - sondern das, was man eingibt. Die meisten Fehler sind also "Layer 8 Error".

Und, wie erwähnt, kann man auch aus der Pascal-IDE "aus- und wieder einsteigen".
zatzen hat geschrieben:Es wäre natürlich schön, wenn mein aktueller Rechner noch 16 Bit DOS könnte, selbst wenn man mal Soundblaster außen vor lässt. Mein DMF-Renderer wäre blitzschnell.
Wieso kann Dein aktueller Rechner kein 16 Bit DOS? Etwa so UEFI-Dreck statt BIOS?
zatzen hat geschrieben:
DOSferatu hat geschrieben:[Trenz... 2D-Katakis]
Nur in eine Richtung scrollend scheint technisch einen deutlichen Unterschied zu machen, ich denke da an Super Mario, da konnte man ja auch nur immer weiter nach rechts laufen. Das hatte wohl auch mit der Berechnung der Feindbewegungen zu tun, die somit minimiert werden konnten (Feinde bewegten sich auch erst sobald sie sichtbar waren).
Klar, in der 8-Bit-Zeit haben die Leute eine Menge Dinge gemacht, um aus schwacher Technik mit wenig Speicher trotzdem eine Menge rauszuholen. Dem Spieler ist sowas auch damals nicht aufgefallen.
zatzen hat geschrieben:Ich bin aktuell eher noch am überlegen, wie ich den Grafikdatenumsatz minimal halte, und bei Scrolling sehe ich keinen anderen Weg als den ganzen Bildschirm für jeden Frame komplett neu zu zeichnen. Wenn ich dafür eine Lösung habe komme ich gerne nochmal auf die Ideen von Manfred Trenz zurück.
Naja, für das, was ich so mache (Mehrwege-Scrolling) ist auch nichts anderes möglich als Komplettframe neuzeichnen. Obwohl es ja, wie gesagt, auch noch die Möglichkeit gibt, die Softscroll-Register der VGA zu benutzen, die Scanline mehrere Bildschirme breit zu machen usw...
zatzen hat geschrieben:
DOSferatu hat geschrieben:Und meine neue Idee dazu ist eben, daß ein "Level" nicht die Daten enthält, sondern nur die Namen der Files (und ein paar Parameter), aus denen es besteht, bzw. die es braucht. Und die eigentlichen Files sind unabhängig davon im Datenfile verteilt. Eigentlich ist die Idee nicht so die totale Revolution oder so - aber vorher hatte ich daran eben nicht gedacht.
Ja, ich glaube ich hätte das auch so gelöst wie Du es jetzt machst, auch wenn dadurch die Ladezeit vielleicht minimal länger wird. Oder, ein anderer Ansatz, wenn ich genug Speicher habe bzw. die Gesamtdaten klein genug - Alles am Anfang laden und dann beliebig in den Levels benutzen.
Naja. Genug Speicher werd ich nie haben. Ich gehe ja davon aus, daß man mehrere Levels mit verschiedenen Hintergrund-Sets hat und eben auch dazu passende Figurentypen (und eben auch "allgemeine" Figurentypen, die es "überall" oder eben oft gibt. Und dann z.B. so Figuren wie "Endgegner", die man ja nur für das jeweilige Level braucht, weil jedes Hauptlevel natürlich einen eigenen Endgegner hätte - wäre ja sonst langweilig.

Und gerade, wenn man, wie Du, auch noch viel Platz für Samples übrig lassen will, ist das "von Grafik pro Level nur das laden, was gebraucht wird", ja sicher eine gute Idee. Wie schon erwähnt: Grafiken sind ein unheimlicher Speicherfresser. Da kann man packen wie man will. Und, wie (auch schonmal) erwähnt: (Zu) komplizierte Entpack-Routinen brauchen Speicher und kosten Performance. Da muß man dann den richtigen Mittelweg finden.
zatzen hat geschrieben:[.... Sprites....]
Es geht um das Codieren. Das (eher kleine) Problem ist, dass sich mehrere Sprites in einem Datensatz eine Meta-Palette teilen. Bisher habe ich den Encoder nur auf ein Sprite ausgelegt, bei mehreren muss die Meta-Palette je nachdem erweitert werden. Es funktioniert bisher gut für ein Sprite und für das Feature "mehrere" muss ich eben nochmal am Code rumfummeln mit der Gefahr dass Bugs reinkommen, das ist die kleine Hürde.
Etwas merkwürdig finde ich das schon. Wenn ICH irgendwas für Sprites machen würde, hätte ich erst gar nicht mit irgend etwas angefangen, das "erst mal nur für EIN Sprite" funktioniert - weil ich NIE vorhatte, irgend ein Spiel zu machen, das nur EIN Sprite braucht.
zatzen hat geschrieben:Aber ein wichtiger Vorteil Deiner Methode, die Grafik in Planes zu halten, ist die dass es ein festgelegter Bereich ist den man ohne Heap-Fragmentierung be- und entladen kann?
Nö. Diese "Sprite-Bahnen" können verschieden groß sein - da gibt es keine festgelegten Größen. Und bei meiner dynamischen Speicherverwaltung muß es das ja bekanntermaßen auch nicht. Heap-Fragmentierung tritt in meinen Programmen nicht auf.

Es geht eher darum, daß man verschiedene Grafiken "ein-/ausblenden" kann, wenn man sie nicht braucht. Die Image-Tabelle der Sprites bleibt erhalten! D.h. gleiche Images haben auch immer den gleichen Image-Index. Nur zeigen bei Figuren, die man gerade nicht braucht, die Image-Zeiger auf nichts Sinnvolles. Ich weiß nicht, ob ich das verständlich genug erklären kann.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Es ist manchmal schon erstaunlich, was eine einfache andere Farbanordnung mit dem gleichen Image anzustellen vermag.
Ich könnte das bei ZVID2 durch ändern/austauschen der Metapalette machen, allerdings ist das doch eher umständlich und mein Grafikstil eher derjenige, der für alle Charaktere völlig eigene Bitmaps vorsieht. Hatte ich ja schonmal gesagt, und ich kenne die Umfärbung von Sprites eher nur von 8 Bit Konsolen wie NES, wo die Sprites aus 8x8 Blöcken bestehen die je nur 2 Bit haben und zwingend eine "Meta"-Palette brauchen.
Naja, bei mir haben sie ja schon deswegen eine Meta-Palette, weil sie 15 aus 256 Farben haben können, und zwar BELIEBIGE 15 Farben der 256. Die Farben müssen NICHT in der 256-Palette direkt hintereinander liegen! Und wenn sie schon so eine Meta-Palette haben, können sie auch mehrere haben. So hat man noch mehr Figuren bei minimalem zusätzlichem Speicherbedarf. Kann ja jeder anders sehen - aber ich finde die Idee großartig.
zatzen hat geschrieben:Aber natürlich gibt es auch andere sinnvolle Anwendungen, wenn Du "Battle Chess" kennst, die werden wahrscheinlich auch für die roten bzw. blauen Figuren auch nur anderen Paletten genommen haben.
Ist anzunehmen. Und (wie sicher bereits erwähnt) es betrifft z.B. auch die Spinnen in Xpyderz.
zatzen hat geschrieben:Skript:
Ich weiss zwar jetzt aus dem Zusammenhang gerissen nicht genau, welchem Zweck so ein Script dient, aber Du sagst ja direkt einleitend dass es für einen Editor ist. Also wenn man so will für ein UI und was "dahinter" steckt. Klingt gut.
Naja, unter anderem. Es ist in dem Fall sozusagen eine Art primitive Interpreter-Sprache, die durch eine 100% Assembler-Routine ausgeführt wird. Vorteil für mich ist, daß ich diese "Skripts" dann einzeln nachladen kann, wenn ich sie brauche und nur das, was ich gerade brauche, im Speicher halten muß. Ursprüngliche Idee war, daß das z.B. für so Menüs im Spiel benutzt werden kann - weil das Ding auf den ganzen Speicher und auch auf Variablen zugreifen und damit arbeiten kann. So kann man damit irgendwelche Einstellungen machen, kann aber das Menüsystem trotzdem vom Rest trennen.

Das Ganze hat etwas damit zu tun, daß ich in den letzten Jahren immer mehr dazu übergegangen bin, mein ganzes Zeug "modular" zu machen. Um dem aber gerecht zu werden, und es deswegen nicht in totale Speicher- und Performancefresser ausarten zu lassen, wird das modulare Zeug mehr und mehr in Assembler gebaut...

Ich sehe schon: In 20 Jahren hab ich so viel coole Routinen hier, daß ich damit das Spiel des Jahrhunderts bauen könnte - und niemand wird mehr eine Maschine oder Emulator besitzen, auf dem das überhaupt lauffähig wäre...

So, damit habe ich endlich auch mal auf diesen Beitrag geantwortet.
Benutzeravatar
zatzen
DOS-Guru
Beiträge: 518
Registriert: Di 17. Apr 2012, 05:10
Wohnort: bei Köln
Kontaktdaten:

Re: Trackermodul-Engine (sehr einfach)

Beitrag von zatzen »

Hallo!

Ich bin in letzter Zeit mal wieder auf den Geschmack gekommen, Musik abzumischen. Passt ja mehr oder weniger auch etwas in den Thread hier. Naja und da habe ich mir gedacht, guck ich mal nach was ich noch so an älteren Musikstücken habe die ich bisher noch nicht "veröffentlicht" habe und mache ein neues Album daraus. Vielleicht schaffe ich das noch bis Ende dieses Jahres. Je nachdem sind das teilweise noch 4-Kanal MODs mit vielen 8 kHz Samples - ich suche die Quellen raus von denen ich damals gesamplet habe (habe einiges auf Kassetten gefunden und suche aber noch) und restauriere das alles. Ich erfülle mir da einen kleinen Traum, produziere nochmal die Sachen ab 1994 mit den technischen Möglichkeiten die ich heute habe. Das ist nicht nur nach dem Motto "Kristallsound" sondern erlaubt jetzt auch die uneingeschränkten gestalterischen Ideen, die damals technisch begrenzt waren.
Programmiert habe ich mir dafür auch noch schnell etwas, ich brauchte ein Tool das mir die Samples aus X-Tracker Modulen als vernünftig benannte und mit korrekter Samplerate versehene WAV Dateien rausholt. Kein großes Ding, aber musste gemacht werden. Und dabei durfte ich nochmal erleben wie wunderbar klar sowas geht mit gut strukturierten Binärdateien. Ich hatte auch schon angefangen für Renoise ein Tool zu bauen (damit ich damit musikalische Videos machen kann) aber so ein XML erscheint mir doch etwas haarig. Irgendwie so seriell, eben wie HTML, Strukturen werden nicht angekündigt durch nen Zähler sondern sie kommen einfach. Irgendwie müsste ich da auch etwas System reinkriegen, bisher ist alles nur provisorisch. Man muss das wohl regelrecht "parsen" bzw. gewissermaßen kompilieren.

Sicherlich habe ich demnächst dann wohl viel mit Musik zu tun, aber das muss nicht heissen dass deswegen das Programmieren total versiegen wird. Ich denke eher, durch mehr kreative Aktivität kommt der Stein allgemein mehr ins Rollen. Trotzdem ist mein jetziger Standpunkt (der sich aber ändern kann), dass ich lieber ein kleines Spiel machen möchte das nicht so viel Arbeit bedeutet. Ob Mode-X notwendig ist, ist fraglich. Sauberes 4:3 mit quadratischen Pixeln wäre schön, aber mit meinen derzeitigen Kenntnissen über Mode-X würde die Erschliessung dessen möglicherweise halb so lange dauern wie die Programmierung des ganzen Spiels. Motiviert wäre ich nach wie vor auch durch meine Spielerei mit ZVID2, die sich in Mode-X so wie ich das sehe ziemlich kompliziert gestalten würde.
Wie gesagt bin ich Mode-X absolut nicht abgeneigt, d.h. wenn ichs erstmal gerafft habe. Ansonsten freuen sich alle sicherlich am meisten wenn ich einfach ein Spiel raushaue das Spaß macht, selbst wenn es steinzeitliches MCGA ist oder gar per Soundpuffer getaktet - immerhin Sound abschaltbar und dann über Timer. Mode-X und nicht-per-Soundpuffer timen sind meine Ziele, aber ich kann das aktuell noch nicht. Ich werde da keinem Ideal gerecht wenn ich es so mache wie ich es momentan nur kann, aber wenn das Spiel Spaß macht, was soll's. Es wird dann eher so sein dass es einen 486 braucht obwohl es anders programmiert auch auf einem langsamen 386 funktionieren würde. Oder - ich mache AdLib + One-Shot DMA Digisound. Naja, ich laber mich hier schon wieder aus allem raus. Einfach mal sehen was wird. Ich möchte ZVID2, das Ding mit den 4x4 Blöcken für Sprites verwenden - auch wenn das Performance kostet, aber das ist für mich interessanter, auch wenns keiner versteht. Gleiche Sache mit ZSM, das kostet vor allem Performance wegen der Delta-Codierung der Samples. Aber auch da fasziniert mich dass es klein ist und trotzdem vernünftig klingt.
Mode Q fand ich noch interessant - nutzt komplette 64K und hat 256 Zeilen. Die direkte X/Y Adressierbarkeit spielt für mich wohl keine Rolle. Wirklich interessant fände ich den Modus wenn er kein quadratisches sondern 4:3 Bild erzeugen würde und die Pixel dann auch jeweils 4:3 wären. Ich weiss nicht ob man das entsprechend "tweaken" kann, aber wenn, dann fände ich das interessant, es wäre dann ein ähnlicher Bildmodus wie das NES mit seinen 256x240, wobei da die Pixel glaube ich auch 4:3 sind.
DOSferatu hat geschrieben:Wie Dir ja schon selbst ausgefallen ist, hat das GW-BASIC, sowohl was Speicherverfügbarkeit als auch Ausführungsgeschwindigkeit anbetrifft, gewisse Grenzen - die Borlands TurboPascal zwar auch hat, dort aber wohl etwas höher liegen.
GW-BASIC wäre ja dieses C64-ähnliche Ding. Es handelt sich hier um QuickBASIC, da hat man schon eine komfortable IDE welche die Subroutinen gesondert anzeigt, zudem braucht man keine Zeilennummern. Es ist allerlei möglich, ich habe aber zu den Zeiten als ich damit arbeitete keine Analogie zu Pascals GETMEM gefunden, daher war ich auf das kleine Datensegment angewiesen, und mangels Units auch auf 64 KB Codesegment, die oft viel zu schnell voll waren, zudem im Interpreter-Modus größere Programme zuliessen als man kompilieren konnte, das war oft ärgerlich. Bei Pascal würde mich am ehesten stören, dass die IDE viel Speicher belegt und man daher vielleicht nur 300 KB Speicher frei hat, aber sobald man kompiliert hat und das Programm ausserhalb aufruft hat man entsprechend mehr. Das spricht im Prinzip für die Entwicklung von Engines, so dass man die Daten designt und testet ohne die Pascal IDE parallel laufen zu haben.
DOSferatu hat geschrieben:[...]Nichtmal byteweise arbeiten zu dürfen[...]
In 64 Bit Zeiten und wenn man es nicht mit Speicherkritischen Daten zu tun hat vielleicht irgendwie verständlich. Vieles wird als DWORD gespeichert wo ein Byte reichen würde, und ich selbst nutze in Freepascal oft WORD oder DWORD wo vielleicht auch BYTE reichen würde, weil ich Überläufe bei Kalkulationen befürchte, die ich zumindest in DOS Pascal kenne. Also wenn man Byte * Byte rechnet kann es passieren dass das Ergebnis ein übergelaufenes Byte wird statt einem Word, oder so ähnlich. Kennst Du bestimmt. Dafür gibts ja aber das "Typecasting" word() oder was auch immer.
DOSferatu hat geschrieben:Ja, SO komplex hab' ich's bei AtavISM nicht gemacht
Klar. Das ging in meinem Fall auch vor allem deshalb, weil die Daten nicht mehr verändert werden mussten, deshalb das Bit-packing.
DOSferatu hat geschrieben:Meine Stücke bestehen aus einer Hauptmelodie, einer Begleitmelodie und einer Art Percussion. Ich mache meist zuerst die Begleitmelodie. Diese (und die Percussion) sind aber SEHR repetativ - und wenn ich irgend etwas an Begleitmelodie oder Percussion ändern will (weil es mir nicht mehr so gefällt oder es nicht mehr so paßt), brauche ich das nur an einer einzigen Stelle (bzw. sehr wenigen Stellen) ändern und schon ist es wieder wie gewünscht. Außerdem kann ich so z.B. den gleichen Ablauf transponieren/schneller/langsamer spielen, um mehr Dynamik in das Stück zu kriegen und selbst diese Teile werden dann gleich mit geändert. Das spart mir eine Menge Zeit - und unkreativ/unmusikalisch, wie ich bin, bin ich froh, wenn ich überhaupt mal ein einigermaßen gescheites Stück zusammenhabe.
Ja, im Grunde ist so ein Vorgehen sehr effizient und nutzt den Speicher optimal. Ich habe auch viele repetetive Elemente wenn ich etwas trackere. Aber es gibt auch ständig Veränderungen, und da wäre es für mich etwas fummelig wenn ich z.B. wie im Beni Tracker jedes mal diese Veränderung definieren müsste und dazu noch wo sie genau reinkommt. AtavISM gibt sich ja nach aussen hin wie ein normaler Tracker, und so bin ich das Arbeiten gewöhnt. Meistens habe ich in X-Tracker um weiterzukommen einfach das komplette letzte Pattern kopiert und dann entsprechend gewünschte Änderungen vorgenommen. Das ergibt Speichertechnisch viele Redundanzen, und so existieren diese auch in ZSM, obwohl dort alles erstaunlich klein gehalten wird. Allerdings sind auch komplexe Musikstücke wie sie in der Klassik zu finden sind denkbar und diese würden dann nicht mehr beanspruchen als redundante Popularmusik.
DOSferatu hat geschrieben:Ich kann immer nur EIN Programm gleichzeitig bedienen - deshalb würde mir so ein "Taskwechsel" Zeug auch ausreichen: Immer wenn man ein anderes Programm benutzt, halten alle anderen an.
Ja, im Prinzip könnten Taskwechsel genügen, daran hatte ich noch gar nicht gedacht, es gibt aber Ausnahmen: Wenn ich hier Musik abmische sehe ich mir gern das Spektrum in einem anderen Programm an das parallel läuft (im Aufnahmemodus), oder man hat langwierige Berechnungen, codierungen, sonstwas laufen und möchte nebenbei nochwas anderes machen. Letztlich kann man solche Situationen aber auch durch mehrere PCs lösen, das merke ich gerade dass das auch praktisch sein kann, weil ich seit ner Weile nur noch mit einem Laptop im Internet unterwegs bin und den anderen Rechner offline parallel nutze, da ist man noch ein Eckchen flexibler manchmal. So könnte ich mir besagte Spektrum auch auf dem Laptop anzeigen lassen und spare mir den Taskwechel.
DOSferatu hat geschrieben:Schade, daß kein "Covox Speech Thing" oder wie das Ding hieß habe und auch keinen so Nachbau. Wäre mal cool, diese Methode per LPT zu hören... Ja, ich weiß: Kann man sich auch selber löten - wenn man kann.
Ich habe mir so einen gelötet als ich 14 oder 15 war. Ziemlich einfach, man braucht nur einen LPT Stecker, Audiobuchse, Folienkondensator, und 20 oder so 1% Widerstände 10k oder was das war. Der Klang ist ziemlich gut, vor allem bei hohen Rates.
Das ist auch mit der Grund warum ich es schade fand dass heutige PCs keine parallele Schnittstelle mehr haben. Man konnte so einfach elektronische Basteleien damit machen. Heute braucht man für alles einen Arduino oder sowas.
DOSferatu hat geschrieben:[Zatzens Beobachtung bei Pinball Dreams]Naja, es werden kurze Puffer sein. Aber dank Puffer-Vorberechnung und Puffer-DMA wird hat man eben trotzdem eine Verzögerung. (Das Ding mit Ursache und und Wirkung.) Bei der ganzen Dynamik eines Spiels fallen einem so Dinge wahrscheinlich nur nicht so auf.
Ich habe mit Dosbox ein Video mitgeschnitten. Ich kann das bei Gelegenheit mal ganz akkurat überprüfen, d.h. bei genau welcher Stelle sich z.B. ein Hebel bewegt und wann dazu der Sound einsetzt.
DOSferatu hat geschrieben:Wieso kann Dein aktueller Rechner kein 16 Bit DOS? Etwa so UEFI-Dreck statt BIOS?
Ja, tatsächlich UEFI. Aber allein schon Windows 7 verbietet 16 Bit Programme.
Über sowas wie einen modernen Rechner in Dos zu booten habe ich noch gar nicht nachgedacht. Bisher genügt mir da aber auch Dosbox.
DOSferatu hat geschrieben:Naja, für das, was ich so mache (Mehrwege-Scrolling) ist auch nichts anderes möglich als Komplettframe neuzeichnen. Obwohl es ja, wie gesagt, auch noch die Möglichkeit gibt, die Softscroll-Register der VGA zu benutzen, die Scanline mehrere Bildschirme breit zu machen usw...
Softscroll/Scanline mehrere Bildschirme - das geht dann aber auch nur in dem Sinne dass man den Bildbereich nur vergrößert, oder? Sonst hat man ja mittendrin einen kleinen Hänger wenn ein kompletter neuer Screen gezeichnet werden muss.
Oder Stückeln? Müsste gehen... Aber das ist dann definitiv erst für mich möglich wenn ich Mode-X kapiert habe.
DOSferatu hat geschrieben:["META"Paletten bei Sprites]Naja, bei mir haben sie ja schon deswegen eine Meta-Palette, weil sie 15 aus 256 Farben haben können, und zwar BELIEBIGE 15 Farben der 256. Die Farben müssen NICHT in der 256-Palette direkt hintereinander liegen! Und wenn sie schon so eine Meta-Palette haben, können sie auch mehrere haben. So hat man noch mehr Figuren bei minimalem zusätzlichem Speicherbedarf. Kann ja jeder anders sehen - aber ich finde die Idee großartig.
Bei ZVID2 würden im Grunde auch 16 Byte reichen um weitere Farbgebungen zu realisieren, aber das wäre dann eine META-META Palette und man müsste das im Programm doppelt tabellarisch nachschlagen. Ich finde Deine prinzipiell simple Reduktion der Spritefarben auf 15 + Transparenz vernünftig, auch wegen der Performance. Bei mir ist es einfach so, dass ich zu verspielt bin mit diesen gepackten Formaten von mir. Ich glaube diese ganzen komischen Sachen die ich mir ausdenke motivieren mich mehr ein Spiel zu machen als das Spiel selbst. Vielleicht bin ich als eigentlicher "Musiker" einfach nicht so ideal für die Grafikprogrammierung.

Ich mache das ja irgendwie nur nebenbei. Ich spiele viel rum, experimentiere, manche haben schon gesagt ich soll nicht immer das Rad neu erfinden. Beim Programmieren bin ich auf jeden Fall nicht professionell, mir macht da das Tüfteln Spaß, und gerade auch in Assembler, da schreibe ich gerne mehr oder weniger komplizierte Routinen wie z.B. Entpackroutinen, die dann trotz allem verhältnismäßig schnell sind, jedenfalls schneller und überhaupt erst tolerabel gegenüber dem als hätte man sie in Hochsprache geschrieben. Das macht so einen Teil der Faszination für mich aus. Es ist ein bisschen wie Rästelhefte lösen manchmal, nur dass die Ergebnisse nicht in der Tonne landen, sondern evtl. in einem Spiel, das dann vielleicht nicht performt wie ein professionelles, aber doch besser ist als nichts.
ZSM hat sich jedenfalls schon gelohnt, ich höre mir damit schonmal gerne ein bisschen Musik an. Ich könnte auch einfach die originalen MODs in Windows im Winamp abspielen, aber irgendwie mag ich das Interface meiner Software, die ganzen Anzeigen usw., die hat Winamp nicht, und letztlich hält mich wohl auch bei der Stange, dass ich genau dieses Format auch in ein Spiel bringen könnte, mit genau dem eigentümlichen Klang, und so verschaffe ich mir ein Gefühl dafür, was wie klingt und wieviel Speicher es braucht etc.

Über Emulatoren würde ich mir mal keine Sorgen machen. Vielleicht wenn irgendwann 256 Bit Windows kommt.
mov ax, 13h
int 10h

while vorne_frei do vor;
Benutzeravatar
zatzen
DOS-Guru
Beiträge: 518
Registriert: Di 17. Apr 2012, 05:10
Wohnort: bei Köln
Kontaktdaten:

Re: Trackermodul-Engine (sehr einfach)

Beitrag von zatzen »

Ich habe mal den Ton vom Pinball Dreams Mitschnitt vermessen.

Ich bin da auf verschiedene Ergebnisse gekommen. Von 13 ms Versatz bis hin zu ca. 50 ms.
Ich vermute, dass die Länge des Puffers so gewählt ist dass die Frequenz der halben Bildwiederholrate entspricht, hier
also ca. 35 Hz. Die Musik klingt für meine Ohren gerade bei eigentlich sauber zu erwartenden, geraden Tönen etwas unsauber, vielleicht wird hier kein Auto-Init-DMA angewendet sondern jedesmal die DMA Übertragung neu angeschubst, was kleinste Aussetzer verursacht, die dann den Klang ein wenig "zerstückeln" bzw. modulieren.
Ich sehe da durchaus auch einen Grund: Man kann mit manuellem DMA-Auftrag den Sound per Timer takten, da so ein One-Shot-DMA nicht durchlaufen muss sondern abgebrochen wird sobald der nächste kommt. Das klingt zwar dann nur bei perfektem Timing optimal, orientiert sich aber eben immer fest am Timer und gallopiert nicht von selbst davon. Ob das für unabhängige Programmierung von Sound und Spieltiming ein Vorteil sein kann weiss ich noch nicht.
Und wie gesagt ist das hier nur eine Vermutung, aber ich bin mir fast sicher, so einen "Stückelsound" früher einmal bei einem Spiel erlebt zu haben.
Ein Vorteil davon, den Soundpuffer per One-Shot einzubinden wäre, dass man sich beim Hängen von Frames keine nervigen Soundloops einhandelt. Wenn das Timing per Interrupt gelöst wird und man eine verlässliche Samplerate bei der Soundkarte einstellt (was durchaus beachtet werden muss, da der Soundblaster mit seiner 256-Stufen Formel da eher ungenau ist) spricht eigentlich nichts dagegen, One Shot DMA zu nutzen - es sei denn so ein DMA-Befehl hat eine hörbare Verzögerung im einstelligen Millisekundenbereich. Wenn das möglich wäre, käme mir jedenfalls erstmal, so wie ich das sehe, die ganze Geschichte etwas "zahmer" vor. Aber wenn ich's mir recht überlege, wenn man wirklich Bild und Ton unabhängig timen will ist Auto-Init-DMA klar im Vorteil.

Überhaupt, auch zum Thema Grafik, mache ich lieber alles Schritt für Schritt. Für mich steht da erstmal ein Spiel an bei dem ich die Möglichkeiten von Assemblerroutinen mit Transparenz auskoste, das ist für mich noch relativ neu, auch wenn ich das ein oder andere Testspiel vor 20 Jahren schon gemacht habe. Deine bisherigen Erklärungen zu Mode-X, DOSferatu, sind nicht in den Ofen geschossen, auch nicht die zur unabhängigen Programmierung von Bild und Ton. Wenn man die Soundpuffer klein halten kann und sie dann wegen Unabhängigkeit trotzdem schnell berechnet werden sehe ich überhaupt kein Problem darin, dass ich das so mache.
mov ax, 13h
int 10h

while vorne_frei do vor;
DOSferatu
DOS-Übermensch
Beiträge: 1220
Registriert: Di 25. Sep 2007, 12:05
Kontaktdaten:

Re: Trackermodul-Engine (sehr einfach)

Beitrag von DOSferatu »

zatzen hat geschrieben:Hallo!
Ja, hallo erstmal.
zatzen hat geschrieben: [...] mache ein neues Album daraus. Vielleicht schaffe ich das noch bis Ende dieses Jahres. Je nachdem sind das teilweise noch 4-Kanal MODs mit vielen 8 kHz Samples - ich suche die Quellen raus von denen ich damals gesamplet habe (habe einiges auf Kassetten gefunden und suche aber noch) und restauriere das alles. Ich erfülle mir da einen kleinen Traum, produziere nochmal die Sachen ab 1994 mit den technischen Möglichkeiten die ich heute habe. Das ist nicht nur nach dem Motto "Kristallsound" sondern erlaubt jetzt auch die uneingeschränkten gestalterischen Ideen, die damals technisch begrenzt waren.
Was meinst Du eigentlich immer mit "Album"? Gibt's CDs von Dir zu kaufen?
zatzen hat geschrieben:Programmiert habe ich mir dafür auch noch schnell etwas, ich brauchte ein Tool das mir die Samples aus X-Tracker Modulen als vernünftig benannte und mit korrekter Samplerate versehene WAV Dateien rausholt. Kein großes Ding, aber musste gemacht werden. Und dabei durfte ich nochmal erleben wie wunderbar klar sowas geht mit gut strukturierten Binärdateien. Ich hatte auch schon angefangen für Renoise ein Tool zu bauen (damit ich damit musikalische Videos machen kann) aber so ein XML erscheint mir doch etwas haarig. Irgendwie so seriell, eben wie HTML, Strukturen werden nicht angekündigt durch nen Zähler sondern sie kommen einfach. Irgendwie müsste ich da auch etwas System reinkriegen, bisher ist alles nur provisorisch. Man muss das wohl regelrecht "parsen" bzw. gewissermaßen kompilieren.
Nicht nur "gewissermaßen". Es muß geparst werden. XML ist ein total aufgeblasener Mist. Alles "menschenlesbare" Parameter - also braucht man ein großes "Wörterbuch"-File dafür, desweiteren akzeptiert/benutzt es Verschachtelung - aber ohne Definition, wie tief verschachtelt werden kann... usw. Dafür 'n Tool zu bauen, ist die reine Pestilenz.

Wieso wird dann dieser Scheiß benutzt? - Aus Faulheit: Die heutigen OS bieten den Kram gleich als API oder Ähnliches an und es braucht nur "eingebaut" zu werden. Daß es aufgeblasene Files erzeugt und daß sowohl zum Schreiben als auch zum Lesen intern ein extremer Aufwand betrieben werden muß, scheint ja keinen zu interessieren, solange irgendwer anders schon den Code dafür geschrieben hat...

Es gäbe andere, einfachere Möglichkeiten, z.B. in der Art der "INI"-Files. Auch "menschenlesbar" - aber auch wirklich Menschen-editierbar. (Ehrlich: wer will so einen XML-Mist von Hand editieren?) - Wenn man's denn überhaupt menschenlesbar braucht.

Wenn's rein binär ist, ist der Vorteil: Kleinere Files, kein Parser nötig, keine "fehlerhaften" Dinge möglich. Da muß kein Parser "herumraten", ob man das vielleicht noch als valide Daten gelten lassen sollte oder nicht. (XML/HTML und eigentlich jeder Skript-Kram gehört ja zu den "fehlertoleranten" Markup-Codes.)

Wenn bei einem XML durch irgendwelche unsauberen Sachen das "Sublevel" nicht korrekt geschlossen wird (fehlende Klammer, fehlender Abschlußbefehl) - ist ein unübersehbares Chaos produziert... - das dann manche Parser noch versuchen, aufzuräumen. Ich frage mich wirklich, wieso immer noch Leute an diesem XML-Mist festhalten... - Das war mal so'ne Modeerscheinung, weil man dachte, verschachtelbare Einheiten und endlose Erweiterbarkeit wäre eine tolle Idee. Aber es hat eben zu dem geführt, was immer passiert, wenn man irgendwas mit "zu vielen Freiheiten" rausbringt: Dumme Leute reizen das bis zum Anschlag aus und erzeugen damit sinnlos abartige Datenmengen, die abartige Codeklumpen brauchen, um es zu verarbeiten.

Der Grund dafür ist auch, daß sich kaum noch einer mancher heutigen "Programmierer" (Quotes beabsichtigt!) die Mühe macht, zu verstehen, was sein komischer Skriptcode im Inneren der Maschine eigentlich anrichtet...
zatzen hat geschrieben:Sicherlich habe ich demnächst dann wohl viel mit Musik zu tun, aber das muss nicht heissen dass deswegen das Programmieren total versiegen wird. Ich denke eher, durch mehr kreative Aktivität kommt der Stein allgemein mehr ins Rollen. Trotzdem ist mein jetziger Standpunkt (der sich aber ändern kann), dass ich lieber ein kleines Spiel machen möchte das nicht so viel Arbeit bedeutet.
Ja, wenn man etwas findet, was einen begeistert (wie in Deinem Fall z.B. die Musik), verbessert das die allgemeine Laune/das allgemeine Befinden und gibt dann auch wieder Kraft für andere Projekte.
zatzen hat geschrieben:Ob Mode-X notwendig ist, ist fraglich.
Notwendig ist schonmal garnix. Hatte ich auch in anderen Threads/Beiträgen schon erwähnt: Es nur zu benutzten, "weil es geht", "weil jemand anders damit etwas hingekriegt hat" oder "weil's irgendwer cool findet", wäre der falsche Ansatz. Wenn man es nicht wirklich gebrauchen kann - d.h. die Besonderheiten einer bestimmten Technik/Modus/wasauchimmer nicht für seine Zwecke benutzen kann, bzw. keine Vorteile daraus ziehen kann, besteht keine Notwendigkeit, es zu benutzen.
zatzen hat geschrieben:Sauberes 4:3 mit quadratischen Pixeln wäre schön, aber mit meinen derzeitigen Kenntnissen über Mode-X würde die Erschliessung dessen möglicherweise halb so lange dauern wie die Programmierung des ganzen Spiels.
Du immer mit Deinen blöden quadratischen Pixeln! Auf C64 hat mich auch nie gestört, daß die Pixel nicht quadratisch sind und auch sonst ist das sowas von unwichtig und egal. (Ich weiß nicht, ob sich irgend ein Spieler bei DOOM oder Monkey Island oder den unzähligen anderen 320x200-DOS-Spielen jemals drüber aufgeregt hat "daß die Pixel nicht 100% quadratisch sind"...) komme später auch noch darauf zurück.
zatzen hat geschrieben:Motiviert wäre ich nach wie vor auch durch meine Spielerei mit ZVID2, die sich in Mode-X so wie ich das sehe ziemlich kompliziert gestalten würde.
Ja, wie schon erwähnt: Mode-X (bzw. alle "Mode-X"-artigen Modi, also alle "unchained") sind nur dann von Vorteil, wenn man seine Routinen direkt dafür schreibt oder direkt daran anpaßt. Generelles, vor allem auf "Zeilen"- oder "Block"-weise Abarbeitung ausgerichtetes Zeug (anstatt, wie bei Mode-X, eher spaltenweise und "split-block"-mäßig) wird, wenn man es einfach nur "auf Mode-X umbiegt" performancemäßig total in den Keller gehen.

Der Vorteil der Mode-X Dinge ist zweierlei:
1.) Durch das Unchaining kann man mit einem einzigen Befehl gleich 4-16 Pixel setzen.
2.) Man hat statt 64kB volle 256kB Grafik-RAM, dadurch Platz für größere Auflösungen als 320x200 und mehr als 1 Bildseite.

Nachteil ist: Weil der Mode-X eigentlich ein zurückge-"tweak"ter MCGA ist (der MCGA ist nämlich eigentlich ein Tweak), ist die Adressierung etwas gewöhnungsbedürftig.
zatzen hat geschrieben:Wie gesagt bin ich Mode-X absolut nicht abgeneigt, d.h. wenn ichs erstmal gerafft habe. Ansonsten freuen sich alle sicherlich am meisten wenn ich einfach ein Spiel raushaue das Spaß macht, selbst wenn es steinzeitliches MCGA ist oder gar per Soundpuffer getaktet - immerhin Sound abschaltbar und dann über Timer.
Wie schon gesagt: Außer Dir kenne ich keinen, der sich jemals über die nicht ganz quadratischen Pixel in 320x200 ereifert hätte. Man kann's auch echt übertreiben...
zatzen hat geschrieben:Mode-X und nicht-per-Soundpuffer timen sind meine Ziele, aber ich kann das aktuell noch nicht. Ich werde da keinem Ideal gerecht wenn ich es so mache wie ich es momentan nur kann, aber wenn das Spiel Spaß macht, was soll's. Es wird dann eher so sein dass es einen 486 braucht obwohl es anders programmiert auch auf einem langsamen 386 funktionieren würde.
Ach naja - mein Zeug braucht auch alles wohl schnellen 486er. Durch Vorhandensein einer solchen Kiste wird man irgendwie versaut. Wenn ich auf 386er entwickeln würde und sehen würde, wo es bremst, wäre ich wohl ganz anders beim Optimieren. Aber mein 486/X5 (133 MHz) inkl. des Motherboards hier ist quasi so das beste unterhalb Pentium, was überhaupt zu haben ist...

Manchmal schäme ich mich für mein lames Zeug. Aber zum Glück interessiert sich kaum jemand für den Müll.
zatzen hat geschrieben:Oder - ich mache AdLib + One-Shot DMA Digisound. Naja, ich laber mich hier schon wieder aus allem raus. Einfach mal sehen was wird. Ich möchte ZVID2, das Ding mit den 4x4 Blöcken für Sprites verwenden - auch wenn das Performance kostet, aber das ist für mich interessanter, auch wenns keiner versteht.
Ja, um es mal ganz blasphemisch auszudrücken:
Ich kann mir nicht vorstellen, daß ein Zocker 2 Spiele sieht/spielt und das eine performt mehr (FPS) als das andere und der findet das andere besser - und zwar, weil, es die komplexeren internen Routinen hat. Wenn die komplexeren internen Routinen das Endergebnis nicht rechtfertigen, hab ich meine komplexeren Routinen auch schon öfter mal gegen etwas einfacheres (zurück-)getauscht.

Hatte es ja schonmal erwähnt: Habe schon Zeug gebaut, was Performance und Speicher sparen sollte - am Ende hat das Zeug (der Code) selber mehr Speicher verbraucht als er an Daten gespart hat UND war zusätzlich noch langsamer als vorher, weil aufwendigerer Code. Also am Ende: Gewinn Minus 200%. Da ist es dann zwar schade um die ganze Mühe. Aber wenn die Mühe nicht das gewünschte Ergebnis bringt, find ich's noch mehr schade, ein an sich gutgemeintes Projekt damit zu versauen, nur weil's mir um die Mühe leidtäte.

Aber das muß natürlich trotzdem jeder selbst wissen. Ich weiß auch, daß man ungern Zeug "wegschmeißt", in das mal so viel Mühe reingesteckt hat. Aber ich sag's, wie erwähnt, mal ganz blasphemisch: Niemand außer einem selbst, wird das zu würdigen wissen. Die anderen sehen nur das Endergebnis - nicht den Code und auch nicht die Zeit+Mühe, die es gekostet hat.
zatzen hat geschrieben:Gleiche Sache mit ZSM, das kostet vor allem Performance wegen der Delta-Codierung der Samples. Aber auch da fasziniert mich dass es klein ist und trotzdem vernünftig klingt.
Naja, hier hat der einseitige Performanceverlust ja eindeutig einen anderseitigen Gewinn: Files auf weniger als die halbe Größe (Samples) zu kriegen und Daten noch kleiner - und das, ohne daß das Endergebnis wirklich dermaßen reduziert klingt, wie es bei dieser Ersparnis eigentlich müßte - das ermöglicht mehr Platz für mehr Sound, Levels, Grafiken... Das ist also nicht "umsonst".
zatzen hat geschrieben:Mode Q fand ich noch interessant - nutzt komplette 64K und hat 256 Zeilen. Die direkte X/Y Adressierbarkeit spielt für mich wohl keine Rolle.
Und dabei ist das ziemlich cool, diese direkte Adressierbarkeit.
zatzen hat geschrieben: Wirklich interessant fände ich den Modus wenn er kein quadratisches sondern 4:3 Bild erzeugen würde und die Pixel dann auch jeweils 4:3 wären.
Der Modus erzeugt ja auch kein quadratisches, sondern ein 4:3-Bild! Und sieht dann genau so geil "8-Bit-konsolen-mäßig" aus, wie man denken würde!
Daß er bei Dir quadratisch aussieht, liegt daran, daß Du einen Emulator benutzt UND (aus Performancegründen?) die Ratio ausgeschaltet hast.

Mit anderen Worten: Auf der echten Maschine IST der 4:3 und die Pixel auch!
zatzen hat geschrieben:Ich weiss nicht ob man das entsprechend "tweaken" kann, aber wenn, dann fände ich das interessant, es wäre dann ein ähnlicher Bildmodus wie das NES mit seinen 256x240, wobei da die Pixel glaube ich auch 4:3 sind.
Und das ist die Stelle, die ich oben erwähnt habe. Einerseits immer dieses (inzwischen zugegebenermaßen schon leicht nervige) "die Pixel sind nicht quadratisch, ogottogott, was solln die Leute denken?" - aber andererseits an dieser Stelle kein Problem mit den nichtquadratischen Konsolenmode-Pixeln haben. Daß die "Klötze" in Super Mario und Alex Kidd so cool rechteckig sind, liegt ja genau an diesem 256 breiten und 256/240 o.ä. hohen Bild, denn es sind intern natürlich 16x16-Pixel Klötze. Und ja, meine Unit, die die ganzen MCGA und Mode-X Dinge macht (verschiedenste Auflösungen) enthält auch den 256x256 (und auch 256x240 und 256x"n Haufen andere") und wenn Du willst, kannst Du das ja bei Dir testen, um zu sehen, wie das bei Dir aussieht.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Wie Dir ja schon selbst ausgefallen ist, hat das GW-BASIC,[...]
GW-BASIC wäre ja dieses C64-ähnliche Ding. Es handelt sich hier um QuickBASIC, da hat man schon eine komfortable IDE welche die Subroutinen gesondert anzeigt, zudem braucht man keine Zeilennummern.
Ja, schon klar. Aber auch dieses QuickBASIC macht gegenüber Borland/Turbo-Pascal nicht viel her. Das wollt' ich damit eigentlich bloß sagen. Auf PC hatte ich - wie bereits erwähnt - nie in BASIC programmiert.
zatzen hat geschrieben:Es ist allerlei möglich, ich habe aber zu den Zeiten als ich damit arbeitete keine Analogie zu Pascals GETMEM gefunden, daher war ich auf das kleine Datensegment angewiesen, und mangels Units auch auf 64 KB Codesegment, die oft viel zu schnell voll waren, zudem im Interpreter-Modus größere Programme zuliessen als man kompilieren konnte, das war oft ärgerlich.
Ja, wie Dir ja aufgefallen ist, ging's mir weniger darum, wie das BASIC jetzt genau hieß, sondern darum, daß ich weiß, daß viele mit diesem "PC-BASIC" angefangen haben (ich nicht, icb bin gleich bei Pascal gelandet. Hatte diverse Gründe) und dann gemerkt haben, daß man damit "nicht viel anfangen" kann, weil man irgendwie an Grenzen stößt. Wenn man sich da nicht total reinhängt und für sich selber irgendwelche Tricks findet, macht man damit nur so "kindergarten-mäßgen" Kram, den man am liebsten keinem zeigen würde.
zatzen hat geschrieben:Bei Pascal würde mich am ehesten stören, dass die IDE viel Speicher belegt und man daher vielleicht nur 300 KB Speicher frei hat, aber sobald man kompiliert hat und das Programm ausserhalb aufruft hat man entsprechend mehr.
Erstens: Wenn das Programm gestartet wird, nimmt sich die IDE zurück. Zweitens kann man von der IDE aus "in DOS wechseln", dann belegt die nur noch so knapp 10kB resident und man kann das kompilierte Ding in DOS starten (ich kompilier immer gleich auf Platte, nie "in Speicher"). Drittens kann man sich ne BAT reintun, die z.B. sowas
C:\BP\BIN\tpc.exe programm.pas /L
aufruft und damit quasi "extern" kompilieren, falls das Programm mal in der IDE "zu groß" wird.
Viertens: Das Wichtigste: Wenn das Programm so groß ist, daß es da nicht mehr reinpaßt - obwohl das fertige Programm ja noch Daten nachladen würde (Sound, Grafik, Levels...) sollte man sich überlegen, ob man da nicht zu viel Code gebaut hat, der keinen Platz mehr für Daten übrigläßt.
zatzen hat geschrieben:Das spricht im Prinzip für die Entwicklung von Engines, so dass man die Daten designt und testet ohne die Pascal IDE parallel laufen zu haben.
Naja, meiner Meinung nach spricht noch viel mehr für die Entwicklung von Engines, als bloß das Vorhandensein einer IDE.
zatzen hat geschrieben:
DOSferatu hat geschrieben:[...]Nichtmal byteweise arbeiten zu dürfen[...]
In 64 Bit Zeiten und wenn man es nicht mit Speicherkritischen Daten zu tun hat vielleicht irgendwie verständlich. Vieles wird als DWORD gespeichert wo ein Byte reichen würde, und ich selbst nutze in Freepascal oft WORD oder DWORD wo vielleicht auch BYTE reichen würde, weil ich Überläufe bei Kalkulationen befürchte, die ich zumindest in DOS Pascal kenne.
Für mich nicht mal "irgendwie" verständlich - sondern total unverständlich: Mehr Möglichkeiten zu haben - nur um damit nicht etwa für den User/Speicher coole Dinge zu machen, sondern nur aus Faulheit beschisseneren Code schreiben zu dürfen - da denke ich immer: Wenn das der technische Fortschritt bedeutet, dann kann ich darauf verzichten... (nur meine persönliche Meinung)
zatzen hat geschrieben:Also wenn man Byte * Byte rechnet kann es passieren dass das Ergebnis ein übergelaufenes Byte wird statt einem Word, oder so ähnlich. Kennst Du bestimmt. Dafür gibts ja aber das "Typecasting" word() oder was auch immer.
Immer diese Hochsprachen-Typen mit ihrer Panik vor dem bösen Überlauf! Überlauf-Prüfung hab ich generell AUS. Oft benutze ich sogar absichtlich Überläufe für irgendwelche Dinge. Und ja, man kann natürlich Überläufe kriegen, die man NICHT will. Aber selbst da ist es kein Hexenwerk, sich auszurechen, wie weit es mit den gegebenen Werten überlaufen kann. Ich bin ja auch kein Freund davon, bei selbstprogrammiertem Zeug immer so viel zu prüfen. Lieber einmal abstürzen lassen und dann in Ordnung bringen, als dauernd 'n Sicherheitszaun um alles zu ziehen, der den Kram groß und langsam macht.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Ja, SO komplex hab' ich's bei AtavISM nicht gemacht
Klar. Das ging in meinem Fall auch vor allem deshalb, weil die Daten nicht mehr verändert werden mussten, deshalb das Bit-packing.
Ja, schon klar.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Meine Stücke bestehen aus ... [Hauptmelodie, Begleitmelodie,Percussion... repetative Stücke, daher praktisch, wenn nicht überall zu ändern müssen]
Ja, im Grunde ist so ein Vorgehen sehr effizient und nutzt den Speicher optimal.
Naja, wie ich versuchte, zu erklären: Um Speicher geht's mir dabei nur sekundär.
zatzen hat geschrieben: Ich habe auch viele repetetive Elemente wenn ich etwas trackere. Aber es gibt auch ständig Veränderungen, und da wäre es für mich etwas fummelig wenn ich z.B. wie im Beni Tracker jedes mal diese Veränderung definieren müsste und dazu noch wo sie genau reinkommt.
Ach, da muß ich nichts "definieren". Ich füge das einfach ein.
zatzen hat geschrieben:AtavISM gibt sich ja nach aussen hin wie ein normaler Tracker, und so bin ich das Arbeiten gewöhnt. Meistens habe ich in X-Tracker um weiterzukommen einfach das komplette letzte Pattern kopiert und dann entsprechend gewünschte Änderungen vorgenommen.
Ja eben, und immer das Pattern kopieren müssen wär mir zu blöd. Und wenn irgendwas mir nicht mehr gefällt, müßte ich's an ALLEN Pattern ändern... da hätt ich keinen Bock drauf. Wozu hab ich 'n Computer, wenn ich repetative Dinge dann trotzdem manuell machen muß...?
zatzen hat geschrieben:Das ergibt Speichertechnisch viele Redundanzen, und so existieren diese auch in ZSM, obwohl dort alles erstaunlich klein gehalten wird. Allerdings sind auch komplexe Musikstücke wie sie in der Klassik zu finden sind denkbar und diese würden dann nicht mehr beanspruchen als redundante Popularmusik.
Ja, ich finds einerseits erstaunlich, wie klein man Sachen mit ZSM kriegt - was mir aber andererseits auch das zeigt, was ich immer schon gesagt habe: Nämlich, wie abartig redundant MOD-artige Files sind.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Schade, daß kein "Covox Speech Thing" oder wie das Ding hieß habe und auch keinen so Nachbau. Wäre mal cool, diese Methode per LPT zu hören... Ja, ich weiß: Kann man sich auch selber löten - wenn man kann.
Ich habe mir so einen gelötet als ich 14 oder 15 war. Ziemlich einfach, man braucht nur einen LPT Stecker, Audiobuchse, Folienkondensator, und 20 oder so 1% Widerstände 10k oder was das war. Der Klang ist ziemlich gut, vor allem bei hohen Rates.
Ja, den Schaltplan für so Ding hab ich schon gesehen und eigentlich wär's kein Hexenwerk. Aber Ich hab eben noch nie gelötet.
zatzen hat geschrieben:Das ist auch mit der Grund warum ich es schade fand dass heutige PCs keine parallele Schnittstelle mehr haben. Man konnte so einfach elektronische Basteleien damit machen. Heute braucht man für alles einen Arduino oder sowas.
Naja, ich finde so einiges schade. Auch, daß die seriellen Schnittstelle weggefallen ist (oder, wie wir es früher nannten: Der COM-Port).
Stattdessen USB - was meiner Meinung nach viel zu kompliziert zum "Selbermachen" ist - immer gleich 'n ganzes Arsenal Elektronik auffahren, nur um 'ne blöde Daten-Schnittstelle zu benutzen. Nerv!
zatzen hat geschrieben:
DOSferatu hat geschrieben:[Zatzens Beobachtung bei Pinball Dreams] Naja, es werden kurze Puffer sein. Aber dank Puffer-Vorberechnung und Puffer-DMA wird hat man eben trotzdem eine Verzögerung. (Das Ding mit Ursache und und Wirkung.) Bei der ganzen Dynamik eines Spiels fallen einem so Dinge wahrscheinlich nur nicht so auf.
Ich habe mit Dosbox ein Video mitgeschnitten. Ich kann das bei Gelegenheit mal ganz akkurat überprüfen, d.h. bei genau welcher Stelle sich z.B. ein Hebel bewegt und wann dazu der Sound einsetzt.
Und wie ich nicht müde werde zu betonen: DOSbox ist keine Referenz. Es emuliert keinen realen Rechner, sondern dient dazu, alte Spiele spielen zu können. Für so "Tests"/"Benchmarks" u.ä. ist es völlig ungeeignet. Der Soundversatz in Emulatoren ist ein generell bekanntes "Problem" und betrifft alle Emulatoren - inklusive denen, die in diesen neuen "The C64" Dingern eingebaut sind. Also wieder: Keine Referenz für reale Dinge.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Wieso kann Dein aktueller Rechner kein 16 Bit DOS? Etwa so UEFI-Dreck statt BIOS?
Ja, tatsächlich UEFI. Aber allein schon Windows 7 verbietet 16 Bit Programme.
Über sowas wie einen modernen Rechner in Dos zu booten habe ich noch gar nicht nachgedacht. Bisher genügt mir da aber auch Dosbox.
Ja, man kann so ein Mehrfach-Boot Ding davorklemmen. Meine "Windows"-Rechner haben das alle.
zatzen hat geschrieben:
DOSferatu hat geschrieben:Naja, für das, was ich so mache (Mehrwege-Scrolling) ist auch nichts anderes möglich als Komplettframe neuzeichnen. Obwohl es ja, wie gesagt, auch noch die Möglichkeit gibt, die Softscroll-Register der VGA zu benutzen, die Scanline mehrere Bildschirme breit zu machen usw...
Softscroll/Scanline mehrere Bildschirme - das geht dann aber auch nur in dem Sinne dass man den Bildbereich nur vergrößert, oder? Sonst hat man ja mittendrin einen kleinen Hänger wenn ein kompletter neuer Screen gezeichnet werden muss.Oder Stückeln? Müsste gehen... Aber das ist dann definitiv erst für mich möglich wenn ich Mode-X kapiert habe.
Naja. Das Verbreitern der Scanline kann z.B. das Problem des Clippings lösen. Und auch z.B. dafür sorgen, daß Zeilen an 2er-Potenz-Größen anfangen... Nur so'ne Idee...
zatzen hat geschrieben:[Sprites, Meta-Paletten...]Vielleicht bin ich als eigentlicher "Musiker" einfach nicht so ideal für die Grafikprogrammierung.
Naja, ich bin für gar nichts ideal. Ich kann ein bißchen coden, primitive Grafik und noch primitiveren Sound machen. Hält mich ja leider trotzdem nicht davon ab, Leute mit meiner Software zu belästigen...
zatzen hat geschrieben:Ich mache das ja irgendwie nur nebenbei. Ich spiele viel rum, experimentiere, manche haben schon gesagt ich soll nicht immer das Rad neu erfinden.
Wurde mir auch schon gesagt. ABER: Ich lerne viel mehr, wenn ich etwas selber mache, als wenn ich nur fertiges Zeug benutze. Damit kommt man zwar langsamer voran - aber mich interessiert eben auch, wie etwas funktioniert.
zatzen hat geschrieben:Beim Programmieren bin ich auf jeden Fall nicht professionell, mir macht da das Tüfteln Spaß, und gerade auch in Assembler, da schreibe ich gerne mehr oder weniger komplizierte Routinen wie z.B. Entpackroutinen, die dann trotz allem verhältnismäßig schnell sind, jedenfalls schneller und überhaupt erst tolerabel gegenüber dem als hätte man sie in Hochsprache geschrieben. Das macht so einen Teil der Faszination für mich aus. Es ist ein bisschen wie Rästelhefte lösen manchmal, nur dass die Ergebnisse nicht in der Tonne landen, sondern evtl. in einem Spiel, das dann vielleicht nicht performt wie ein professionelles, aber doch besser ist als nichts.
Performance ist ja nicht alles.
zatzen hat geschrieben:ZSM hat sich jedenfalls schon gelohnt, ich höre mir damit schonmal gerne ein bisschen Musik an. Ich könnte auch einfach die originalen MODs in Windows im Winamp abspielen, aber irgendwie mag ich das Interface meiner Software, die ganzen Anzeigen usw., die hat Winamp nicht, und letztlich hält mich wohl auch bei der Stange, dass ich genau dieses Format auch in ein Spiel bringen könnte, mit genau dem eigentümlichen Klang, und so verschaffe ich mir ein Gefühl dafür, was wie klingt und wieviel Speicher es braucht etc.
Ja, das ist doch gut. Und je mehr/öfter man etwas (selbstgebautes) benutzt/testet, umso eher fallen einem spontan Dinge ein, die man noch verbessern könnte - oder Bugs, die noch rausmüssen.
Ich habe hier echt selbstgeschriebene Tools, bei denen mir manche Bugs erst Jahre später aufgefallen sind. (Ich schreibe meine Tools ja quasi nie "nur aus Spaß", sondern immer, weil ich sie brauche. (D.h. ich benutze sie auch mehr oder weniger regelmäßig.)
zatzen hat geschrieben:Über Emulatoren würde ich mir mal keine Sorgen machen. Vielleicht wenn irgendwann 256 Bit Windows kommt.
Achnaja, auch dann wird man noch Dinge emulieren können. Es läuft eben nur nicht mehr auf der realen Maschine - nicht, weil es nicht technisch möglich wäre, sondern wegen absichtlicher künstlicher Veralterung.
zatzen hat geschrieben:Ich habe mal den Ton vom Pinball Dreams Mitschnitt vermessen.
Ich bin da auf verschiedene Ergebnisse gekommen. Von 13 ms Versatz bis hin zu ca. 50 ms.

Ich vermute, dass die Länge des Puffers so gewählt ist dass die Frequenz der halben Bildwiederholrate entspricht, hier also ca. 35 Hz. Die Musik klingt für meine Ohren gerade bei eigentlich sauber zu erwartenden, geraden Tönen etwas unsauber, vielleicht wird hier kein Auto-Init-DMA angewendet sondern jedesmal die DMA Übertragung neu angeschubst, was kleinste Aussetzer verursacht, die dann den Klang ein wenig "zerstückeln" bzw. modulieren.
Ist vieles möglich. Sähe man nur, wenn man den Code analysiert. Aber daran solltest Du vielleicht schon erkennen, daß es auf PC ein Unterschied ist, mit einem Standalone-Tool Musik abzuspielen oder währenddessen "nebenher" auch noch ein interaktives Spiel abzuspielen. Die Generierung der Grafik und die Steuerung des Spiels braucht ebenfalls Platz und Performance - das ist nicht "Musikroutinen mit ein bißchen Spiel drumherum".
zatzen hat geschrieben:Ich sehe da durchaus auch einen Grund: Man kann mit manuellem DMA-Auftrag den Sound per Timer takten, da so ein One-Shot-DMA nicht durchlaufen muss sondern abgebrochen wird sobald der nächste kommt. Das klingt zwar dann nur bei perfektem Timing optimal, orientiert sich aber eben immer fest am Timer und gallopiert nicht von selbst davon. Ob das für unabhängige Programmierung von Sound und Spieltiming ein Vorteil sein kann weiss ich noch nicht.
Ja, das hängt wahrscheinlich auch vom jeweiligen Anwendungsfall ab. So Pinball-Spiel stelle ich mir von der grafischen Programmierung her einfacher vor als andere Sachen: Den vertikalen Screen-Ausschnitt kann man selbst in VGA schon linienweise setzen - benutzt man Unchained "Mode-X" Varianten, so braucht man nur das feststehende Stück Anzeige immer rein/rauskopieren und den Rest könnte man mit nahezu Null Performanceverlust hoch-/runter-scrollen und hat einen 320x800-Pixel Pinball-Automaten.
zatzen hat geschrieben:Und wie gesagt ist das hier nur eine Vermutung, aber ich bin mir fast sicher, so einen "Stückelsound" früher einmal bei einem Spiel erlebt zu haben.
Wenn man sieht, wie erfolgreich viele DOS-Spiele waren, kann man davon ausgehen, daß 99% der Leute da nicht so empfindlich sind/waren wie Du ("oh weia, der Sound ist nicht framegenau"). Wenn man einerseits Sound-zu-Framegenaue Perfektion anstrebt, es einem aber andererseits egal ist, ob komplizierte Routinen die FPS runterziehen - dann ist das irgendwie ein Gegensatz - zu dem mir keine gescheiten Programmiertips mehr einfallen.
zatzen hat geschrieben:Ein Vorteil davon, den Soundpuffer per One-Shot einzubinden wäre, dass man sich beim Hängen von Frames keine nervigen Soundloops einhandelt.
Naja, IMHO: Wer so codet, daß er hängende Frames hat, hat es auch nicht verdient, vernünftigen nichtloopenden Sound zu haben.
zatzen hat geschrieben:Wenn das Timing per Interrupt gelöst wird und man eine verlässliche Samplerate bei der Soundkarte einstellt (was durchaus beachtet werden muss, da der Soundblaster mit seiner 256-Stufen Formel da eher ungenau ist)
Ich weiß. Keine Soundblaster hat jemals mit 44100 Hz gespielt. Die Frequenz kann man da nämlich nicht mal annähernd einstellen. Bei den meisten wirds dann 45454,545... Hz.
zatzen hat geschrieben:spricht eigentlich nichts dagegen, One Shot DMA zu nutzen - es sei denn so ein DMA-Befehl hat eine hörbare Verzögerung im einstelligen Millisekundenbereich.
Wenn Du a) Verzögerungen im einstelligen Millisekundenbereich hörst und b) das für Dich unerträglich ist, dann ist Spieleprogrammierung auf PC wohl nichts für Dich: Sowohl der SB als auch der Ticker arbeiten mit so einer Formel (nur der Ticker mit 65536 Stufen) und man kann ja nichts wirklich "gleichzeitig" machen. Diese Genauigkeit kann man selbst mit Standalone-Programmen auf einem PC quasi nicht erreichen - geschweige denn bei einem Spiel, wo außer Sound noch andere Dinge passieren.
zatzen hat geschrieben:Wenn das möglich wäre, käme mir jedenfalls erstmal, so wie ich das sehe, die ganze Geschichte etwas "zahmer" vor. Aber wenn ich's mir recht überlege, wenn man wirklich Bild und Ton unabhängig timen will ist Auto-Init-DMA klar im Vorteil.
Naja, solange die Musik nicht direkt vom Spiel abhängig ist oder umgekehrt, sollte es für den Spieler keinen Riesenunterschied machen, ob er beim Laufen grade an Steinchen 1 oder Steinchen 2 vorbeikommt, wenn Ton X oder Ton Y spielt. Bei Soundeffekten sieht's schon anders aus - aber, wie wir ja wissen: Je kürzer der Soundpuffer, desto schneller muß die Gesamtschleife sein, damit man rechtzeitig auf leeren Soundpuffer reagieren kann.

Meine Methode ist ja, alles, was an Klängen benötigt wird, als "Nummer" in einen Ringpuffer (in meinem Anwendungsfall ist es eher ein LIFO-Puffer ohne "Ring") zu schmeißen und wenn das nächste Soundpuffer-Stück zu generieren ist, wird das mit berücksichtigt. Wie gut oder schlecht das am Ende klingt, habe ich noch nicht testen können, weil ich ja noch kein genügend fertiges Spiel vorliegen habe, in dem ich das hören könnte.
zatzen hat geschrieben:Überhaupt, auch zum Thema Grafik, mache ich lieber alles Schritt für Schritt. Für mich steht da erstmal ein Spiel an bei dem ich die Möglichkeiten von Assemblerroutinen mit Transparenz auskoste, das ist für mich noch relativ neu, auch wenn ich das ein oder andere Testspiel vor 20 Jahren schon gemacht habe.
So gehe ich ja auch vor. Ich profitiere gern von Dingen, die ich schonmal gesehen habe oder Methoden, die ich irgendwo gelesen habe. Aber ich benutze so Zeug erst dann, wenn ich auch selbst verstanden habe, wie es funktioniert. Irgend etwas "einfach so einzubauen" und dann zu hoffen, daß schon nichts schiefgehen wird, ist auch für mich keine Option. Ich will nicht, daß etwas, das icb baue, dahingehend ausartet, daß ich irgend so eine "Master-Klotz" Routine habe, um die herum ich alles anzupassen habe, weil ich den Master-Klotz nicht verstehe, sondern nur benutze.
zatzen hat geschrieben:Deine bisherigen Erklärungen zu Mode-X, DOSferatu, sind nicht in den Ofen geschossen, auch nicht die zur unabhängigen Programmierung von Bild und Ton. Wenn man die Soundpuffer klein halten kann und sie dann wegen Unabhängigkeit trotzdem schnell berechnet werden sehe ich überhaupt kein Problem darin, dass ich das so mache.
Ja, wie gesagt: Kommt immer drauf an. Normalerweise hat man ja, während ein Puffer abgespielt hat, "genügend Zeit", den anderen zu füllen. Das muß ja nicht in der gleichen Nanosekunde passieren, wo man von Puffer1 auf Puffer2 umschaltet. Das Umschalten selbst MUß natürlich genau am Puffer-Ende passieren - aber dazu gibt's ja schließlich den Soundblaster-IRQ. Aber mit dem schalte ich eben nur von einem zum anderen Puffer um und setze/erhöhe einen Flag/Zähler. Und wenn in der Hauptschleife gefunden wird, daß dieses Flag/Zähler <>0 ist, wird neuer Sound für den anderen Puffer generiert. Das ist dann trotzdem noch zeitig genug - denn abgespielt wird er ja sowieso erst, wenn der andere wieder fertig ist.

Was Soundeffekte angeht: Ich werd's bei mir wohl nicht so kompliziert werden lassen, daß ich noch festlege, an welcher Stelle INNERHALB eines Puffers ein Soundeffekt starten soll. (Wäre zwar möglich, aber mein Irrsinn hält sich dahingehend in Grenzen.) Also starten die immer alle am Anfang eines Puffers und eine genauere Auflösung zum Starten von Effekten als eine Pufferlänge wirds bei mir wohl nicht geben.

Grund ist. Die starten sowieso IMMER verspätet. Wieso? Na, weil die Dinge, die die SFX auslösen ja passieren, während ein Puffer abgespielt wird! Und zwar der, der gerade NICHT generiert wird - sondern der davor! Also ist ein SFX, selbst wenn er am Start des neu zu generierenden Puffers mit reingemixt wird, eigentlich schon zu spät. "Live" in den gerade abgespielten Puffer reinmixen - da wüßt ich nicht, ob das überhaupt geht - d.h. ob die SB das dann überhaupt noch mit einliest. Man weiß ja nicht, wo sie gerade liest - es sei denn man ist dermaßen lunatic, daß man dafür 'n Ticker setzt oder sowas hirnverbranntes.

Aber - wegen des verspäteten SFX: Auch die Grafik ist ja verspätet - die wird nämlich auch erst angezeigt, wenn das Frame fertig gezeichnet ist und nicht "mittendrin".

Und ja, damit bin ich NICHT framegenau! - Weil meine Frames ja schon nicht feststehend sind. Denn wenn ich einen Puffer mache, der 1/50 Sekunde lang ist, mein Spiel aber auf dem Rechner eines Users nur 40 FPS schafft, dann bricht der Sound völlig ein. Also wird das bei mir wohl eher auf einen Puffer für 1/10 Sekunde hinauslaufen oder so - und einstellbar für langsame Kisten noch längere Puffer.

Die einzigen zwei Möglichkeiten, das zu vermeiden, wären:
a) Das von mir letztens beschriebene Multitasking mit 100% nebenläufigen Prozessen - das habe ich aber nicht vor (hatte auch die Gründe dargelegt).
b) Die Generierung des neuen Sounds gleich im IRQ. Da weiß ich noch nicht, ob ich das mag. Eigentlich find ich's Mist.

Und, wie gesagt: Die Grafik ist genauso "hinterher" wie der Sound - vielleicht sogar noch mehr.
Benutzeravatar
zatzen
DOS-Guru
Beiträge: 518
Registriert: Di 17. Apr 2012, 05:10
Wohnort: bei Köln
Kontaktdaten:

Re: Trackermodul-Engine (sehr einfach)

Beitrag von zatzen »

DOSferatu hat geschrieben:Was meinst Du eigentlich immer mit "Album"? Gibt's CDs von Dir zu kaufen?
Ja, wer welche haben will gerne. Aber im Selbstvertrieb und zum Selbstkostenpreis, so viel Massenerfolg dass ich einen Plattenvertrag hätte und reich werden könnte habe ich nicht. Bis vor ca. 15 Jahren bin ich auch einige CDs losgeworden, aber mittlerweile sind CDs wegen MP3, FLAC und Streaming ja out, während Vinyl wieder durchstartet, trotz der klanglichen Einschränkungen welche die CD nicht hat...
DOSferatu hat geschrieben:Wieso wird dann dieser Scheiß benutzt? - Aus Faulheit: Die heutigen OS bieten den Kram gleich als API oder Ähnliches an und es braucht nur "eingebaut" zu werden. Daß es aufgeblasene Files erzeugt und daß sowohl zum Schreiben als auch zum Lesen intern ein extremer Aufwand betrieben werden muß, scheint ja keinen zu interessieren, solange irgendwer anders schon den Code dafür geschrieben hat...
Bei heutigen Programmen (oder gehen wir ganz mit der Zeit und sagen direkt "Apps") die unter mehreren Megabyte an EXE-Größe gar nicht erst anfangen fällt es wohl wirklich kaum noch ins Gewicht wenn so viel Aufwand betrieben wird. Abartig große Codeklumpen relativieren sich dann...
DOSferatu hat geschrieben:Ich weiß nicht, ob sich irgend ein Spieler bei DOOM oder Monkey Island oder den unzähligen anderen 320x200-DOS-Spielen jemals drüber aufgeregt hat "daß die Pixel nicht 100% quadratisch sind"...
Nein, ich selber ja auch keineswegs. Die Sache ist rein pragmatischer Natur und hat einfach nur der Kompatibilität mit den heutigen Anzeigestandards zu tun. Sicherlich, wer im Emulator die Aspect Ratio nicht korrekt einstellt (bei Dosbox ist diese per Default NICHT aktiviert) mag selber Schuld sein. Einschränkend wird das ganze aber dann, wenn man Grafiken für ein Spiel machen will. Dann ist man entweder auf eine Dos-Software angewiesen, oder man hat Glück und kann irgendwie seine Systemauflösung so "verstellen", dass alles "korrekt" verzerrt ist. Du magst sagen, wofür für die Grafik etwas anderes als Dos nutzen. Aber gerade bei Grafik kann Windows-Software von Vorteil sein.
DOSferatu hat geschrieben:Außer Dir kenne ich keinen, der sich jemals über die nicht ganz quadratischen Pixel in 320x200 ereifert hätte.
Es geht ja nicht um die Form der Pixel, sondern darum dass am Ende das Bild zu breitgezogen ist und Ellipsen statt Kreise dargestellt werden. Sobald man nicht mehr mit nativem DOS arbeitet sondern es mit Emulationen zu tun bekommt, ist das alles eben nicht mehr idiotensicher. 320x240 wäre auch im Emulator idiotensicher, solange man nicht absichtlich rumpfuscht. Aber ich kann schon verstehen dass das alles keine Argumente sind, wenn man Emulatoren sowieso verabscheut. Wahrscheinlich werde ich mich irgendwann mal bequemen, mir wieder ein echtes DOS-System zuzulegen. Programmieren würde ich aber bis auf weiteres trotzdem im Emulator. Manchmal passieren derart brutale Abstürze - die möchte ich lieber nicht auf ein echtes System loslassen.
DOSferatu hat geschrieben:Ach naja - mein Zeug braucht auch alles wohl schnellen 486er. Durch Vorhandensein einer solchen Kiste wird man irgendwie versaut. Wenn ich auf 386er entwickeln würde und sehen würde, wo es bremst, wäre ich wohl ganz anders beim Optimieren.
So schlecht Dosbox sich für Benchmarks eignet - immerhin kann man da so ganz grob etwas abschätzen weil man ganz bequem die Geschwindigkeit jederzeit ändern kann. Deswegen hatten sich da bei mir diese 20000 Cycles etabliert, mit denen z.B. das XPYDERZ-Preview etwas ruckelig aber durchaus spielbar läuft, und mein Kram wie er soll, mit Luft nach oben. Natürlich passiert bei XPYDERZ wesentlich mehr als selbst bei einem 16 spurigen vollausgelasteten ZSM, mal als Beispiel. Aber diese Cycles Einstellung habe ich bisher als Richtschnur genommen, davon ausgehend dass es von der Performance einen 486er nicht übersteigen wird.
Und ja, so wie Du befürchtest dass Dein leistungsstarker Computer Performance-Engpässe verschleiert, hatte ich einen Pentium bevor ich mich überhaupt mit effizienter Programmierung auseinandergesetzt hatte.
DOSferatu hat geschrieben:Ich kann mir nicht vorstellen, daß ein Zocker 2 Spiele sieht/spielt und das eine performt mehr (FPS) als das andere und der findet das andere besser - und zwar, weil, es die komplexeren internen Routinen hat.
Natürlich. Der ganze "Konflikt" ist ja auch nur da, weil ich mich entschieden habe, nur 640K zu nutzen und keinen XMS. Mit XMS habe ich schonmal rumgespielt auf meinem Pentium, macht auch Spaß. Erst gar keine Trackermusik, sondern einfach nen längeren Sound-Loop. Und einfach ein riesiges Hintergrundbild. Alles einfach aus dem XMS. Und ein Pentium frisst sich rasend da durch.
Kannst Du hier sehen: https://youtu.be/jqx08ubMIps
Ist mit Dosbox aufgenommen, auf einem Windows System mit einem Intel i5, sowas dürfte heute jeder (Windows-User) haben. Wenn ich also nur unbedingt ein Spiel machen wollen würde, dann so. Ich würde Pentium als Grundvoraussetzung vorgeben mit XMS, und gut. Wenn es mir nur darum geht, den Spieler am Ende glücklich zu machen, sollte ich das vielleicht wieder aufgreifen, denn der hat weder von einer Beschränkung auf 640K etwas, noch etwas davon, wenn man versucht mit einem 486er auszukommen - vorausgesetzt er hat nen Pentium mit XMS, oder ein Windows-System und Dosbox.
Diese Spieldemo ist eines der wenigen Ergebnisse der Projektwoche an unserer Schule, Projekt "Spiel programmieren". Du kannst Dir denken dass die meisten ihre Zeit nicht mit Spiel programmieren sondern Spiele spielen verbrachten.
DOSferatu hat geschrieben:Die anderen sehen nur das Endergebnis - nicht den Code und auch nicht die Zeit+Mühe, die es gekostet hat.
Absolut. Das impliziert aber auch, dass überhaupt jemand sich mit dem Endergebnis beschäftigen wird. Und da ich da nicht von sonderlich viel Resonanz ausgehe, mache ich das lieber so dass ich mich selbst einfach daran erfreuen kann, wenn ich weiss was da im Hintergrund heimlich alles passiert. Sonst eben, wie oben beschrieben, kann man mit XMS und einem Pentium ziemlich einfach einiges reissen. Und der Großteil der "anderen", auch diejenigen die auf "Retro" stehen, dürften mindestens einen solchen Pentium ihr eigen nennen, oder eben ein aktuelles Windows/Mac System mit Dosbox besitzen.
Aber meine Wahl ist ja erstmal nach wie vor 486er mit 640K. Die langsamen Sprite-Routinen namens ZVID2 halte ich für sinnvoll, weil ich das Spiel auch soundmäßig eher üppig ausstatten möchte. Wie gewichtig ZVID2 zu Buche schlagen wird muss sich dann noch zeigen. Die Anzahl der Sprites auf einem Bildschirm wird nicht sehr groß sein, aber trotzdem sollen in einem Level viele verschiedene Dinge existieren und die Animationen ausführlich sein. Deshalb die Kompression. Wenn die Grafik Cartoon-artig wird könnte man sehen ob einfaches RLE effektiver und performanter ist.
Generell gefällt mir aber auch einfach die Idee eines im Grunde unniversell einsetzbaren Grafikformates, welches effiziente Kompression bietet, auch bei so Sachen wie Dithering, wo RLE versagen würde.
DOSferatu hat geschrieben:[ZSM 4 Bit Delta]Naja, hier hat der einseitige Performanceverlust ja eindeutig einen anderseitigen Gewinn: Files auf weniger als die halbe Größe (Samples) zu kriegen und Daten noch kleiner - und das, ohne daß das Endergebnis wirklich dermaßen reduziert klingt, wie es bei dieser Ersparnis eigentlich müßte - das ermöglicht mehr Platz für mehr Sound, Levels, Grafiken... Das ist also nicht "umsonst".
Bei ZVID2 ist die Ersparnis ja noch größer. Das wäre also auch nicht umsonst. Es sei denn, man hätte unkomprimiert nur z.B. 32K Sprite-Daten.
DOSferatu hat geschrieben:[Mode Q]Der Modus erzeugt ja auch kein quadratisches, sondern ein 4:3-Bild! Und sieht dann genau so geil "8-Bit-konsolen-mäßig" aus, wie man denken würde!
Daß er bei Dir quadratisch aussieht, liegt daran, daß Du einen Emulator benutzt UND (aus Performancegründen?) die Ratio ausgeschaltet hast.
Habe ich nicht. Mich würde es ja ankotzen wenn ich bei MCGA immer Breitbild im Emulator hätte. Aber vielleicht muss ich noch irgendwas einstellen um so einen extremen Modus richtig darzustellen. Oder der Code mit dem ich das getestet habe tweakt nicht richtig. Es ist der hier: http://swag.outpostbbs.net/EGAVGA/0175.PAS.html
DOSferatu hat geschrieben:Und das ist die Stelle, die ich oben erwähnt habe. Einerseits immer dieses (inzwischen zugegebenermaßen schon leicht nervige) "die Pixel sind nicht quadratisch, ogottogott, was solln die Leute denken?" - aber andererseits an dieser Stelle kein Problem mit den nichtquadratischen Konsolenmode-Pixeln haben. Daß die "Klötze" in Super Mario und Alex Kidd so cool rechteckig sind, liegt ja genau an diesem 256 breiten und 256/240 o.ä. hohen Bild, denn es sind intern natürlich 16x16-Pixel Klötze. Und ja, meine Unit, die die ganzen MCGA und Mode-X Dinge macht (verschiedenste Auflösungen) enthält auch den 256x256 (und auch 256x240 und 256x"n Haufen andere") und wenn Du willst, kannst Du das ja bei Dir testen, um zu sehen, wie das bei Dir aussieht.
Um Dich nicht weiter zu nerven schliessen wir das Thema mit einer letzten Erklärung ab:
Es geht wie gesagt nicht um die Form der Pixel sondern um die Verzerrung des Bildes wenn irgendein System das ganze interpretiert. Bei 320x200 fällt dem ungeschulten Auge erstmal kein Fehler auf, im Gegenteil, man freut sich wohl eher noch, dass das Bild breiter ist. Bei 256x256 würde man aber stutzig, da ein quadratisches Bild höchst ungewöhnlich ist.
Ich finde 256x256 mit 4:3 Pixeln allein vom Prinzip her interessant, da Röhrenfernseher auch so aufgebaut sind. Zudem wird der Modus wohl hochauflösender wirken wegen 256 Zeilen, auch wenn man horizontal dann weniger hat.
Ja, gerne würde ich Deine Unit testen.
Die bessere Adressierbarkeit des Mode Q könnte sich auch positiv auf das Block-Gedöns auswirken.
DOSferatu hat geschrieben:[8 Bit LPT DAC]Ja, den Schaltplan für so Ding hab ich schon gesehen und eigentlich wär's kein Hexenwerk. Aber Ich hab eben noch nie gelötet.
Vielleicht hab ich ja nochmal Lust so einen zu basteln. Könnte ich Dir dann zukommen lassen. Das bringt mich gerade auch auf die Idee, ZSMPLAY darauf zu erweitern.
DOSferatu hat geschrieben:Wenn man einerseits Sound-zu-Framegenaue Perfektion anstrebt, es einem aber andererseits egal ist, ob komplizierte Routinen die FPS runterziehen - dann ist das irgendwie ein Gegensatz - zu dem mir keine gescheiten Programmiertips mehr einfallen.
Notgedrungen fällt mir da kein besseres Vorgehen ein. Die Soundroutinen füllen den Puffer und brauchen dafür ihre Zeit. Währrenddessen macht die CPU nichts anderes. Außer wie oben erwähnt per Interrupt den schon fertig berechneten Puffer über den LPT auszugeben fällt mir da nichts ein was "parallel" passieren könnte. Ich will Dich mit diesem Problem nicht weiter belästigen und versuche es erstmal so.
Fraglich ist, ob ZVID2 effektiv langsamer sein wird als Deine Sprite-Routinen, die zweifelsfrei durch Skalierung und Drehung ihren Sinn haben.
DOSferatu hat geschrieben:Wenn Du a) Verzögerungen im einstelligen Millisekundenbereich hörst und b) das für Dich unerträglich ist[...]
Damit wir uns nicht falsch verstehen: Ich meine hier nicht Divergenzen zwischen Bild und Ton, sondern Soundlücken. Die können schon nervig sein wenn man die Puffer dann einzeln nacheinander gestückelt hört.
DOSferatu hat geschrieben:Was Soundeffekte angeht: Ich werd's bei mir wohl nicht so kompliziert werden lassen, daß ich noch festlege, an welcher Stelle INNERHALB eines Puffers ein Soundeffekt starten soll.
An soetwas habe ich auch noch nie gedacht.
DOSferatu hat geschrieben:Die Generierung des neuen Sounds gleich im IRQ. Da weiß ich noch nicht, ob ich das mag. Eigentlich find ich's Mist.
Das fände ich auch etwas "gruselig". Ich bin ja noch Anfänger und halte Interrupts lieber klein. Bei ZSMPLAY müssten dann je nachdem auch Patterndaten nachgelesen werden, also ein ganzer Haufen Pascal-Unterprogramme usw.
DOSferatu hat geschrieben:Ach naja - mein Zeug braucht auch alles wohl schnellen 486er. Durch Vorhandensein einer solchen Kiste wird man irgendwie versaut.[...]Manchmal schäme ich mich für mein lames Zeug. Aber zum Glück interessiert sich kaum jemand für den Müll.
Ich habe den Wettbewerb mit den besten der besten (die sich u.a. bei Szenepartys mit Demos oder Musik Wettbewerbe liefern) längst aufgegeben und habe einfach Spaß an meinem persönlichen Vorankommen. Ich werde im Leben nicht an die "Genialität" von Leuten herankommen, die meinetwegen 50% ihrer Lebenszeit dem Programmieren widmen und dort auch noch "hochbegabt" sind und mit ihren Sachen die Leute zum Staunen bringen.
Ich habe in diesem Zusammenhang aber etwas interesssantes gefunden, eine Game-Engine im Mode-X, die, wie unter dem Video steht, auf einem 8086 PC (also < 286) mit 8 MHz performt: https://youtu.be/9vdsRD3oI3c
Was auffällt ist natürlich, dass nur wenige Sprites unterwegs sind, und dass es bis auf Bodenkollision in dieser Version keinerlei Kollisionsabfragen gibt, zudem auch keine komplexeren Ebenen, sondern immer nur ein Hintergrundbild.
Es kommt einfach auf die Vorstellungen und Ideale an, die man hat. Wenn ich mir zum Ziel setze, dass genau das hier die Herausforderung sein soll, auf einem "schwachen" System etwas möglichst attraktives zu zaubern, dann ist es interessant, sogar auf 386+ Befehle zu verzichten, und auf Zeugs wie ZVID2, da die Bild- bzw. Spritedaten sowieso winzig sind.
Wenn ich aber ein Spiel mit üppigem "Multimedia" auf Amiga-Niveau haben will, dann werde ich nicht um 386er oder 486er herumkommen, da ich ja Hardware wie den Soundchip emulieren muss. Das wäre mit einer 8086 CPU unvereinbar.
Kaum hab ich das geschrieben, habe ich ein weiteres Video entdeckt, woraus hervorgeht dass auch die Gravis Ultrasound unterstützt wird, die quasi das PC-Pendant zum Soundchip des Amiga ist. Trotzdem wird hier offenbar AdLib verwendet. Falls es Dich noch weiter interessiert, hier das besagte Video auch mit ein paar Erklärungen https://youtu.be/RctvPO8WkCs

Diese minimal-anfordernden Projekte haben immer ihren Charme, trotzdem bieten nach meinem Empfinden Spiele mit ausführlicherer Grafik und evtl. Sample-Sound einen höheren Unterhaltungswert.


EDIT: Ich habe einmal ein Bild auf 256x256 resamplet und auf 4:3 gezogen. Im Vergleich mit 320x200 zeigt sich zwar eine deutlich höhere vertikale Auflösung, aber diese rechtfertigt sich für mich nicht wenn man die horizontale Einschränkung betrachtet. Insgesamt wirkt ein 256x256 Screen weniger hoch auflösend. Daraus ziehe ich nun den Schluss dass ich für mein Spiel wohl bei 320x200 bleibe. Auch weil ich die Grafik in Deluxe Paint machen werde, und das Programm bietet keinen 256x256 Modus an. Wer dann im Emulator Eier statt Kreise hat - meinetwegen selber schuld.
Deine Modi-Demo wäre nach wie vor interessant, ist aber nicht von entscheidender Bedeutung für mein Spiel.
mov ax, 13h
int 10h

while vorne_frei do vor;
Antworten