Trackermodul-Engine (sehr einfach)

Diskussion zum Thema Programmierung unter DOS (Intel x86)
DOSferatu
DOS-Übermensch
Beiträge: 1220
Registriert: Di 25. Sep 2007, 12:05
Kontaktdaten:

Re: Trackermodul-Engine (sehr einfach)

Beitrag von DOSferatu »

wobo hat geschrieben: Du weisst, dass bei den alten Soundblastern (SB 20, SB PRO und kombatible) mit DSP Versionen 2.00-3.99 grundsätzlich der DMA - Autoinit mode ($48) nur bis Frequenzen bis 22kHz funktioniert? Erst ab DSP Version 4.xx ist laut Creative...
Ist mir bekannt. Wird von meinen Routinen auch abgefragt und wenn Version zu niedrig, supporte ich nicht über 22 kHz.
(Und das Problem tritt auch schon bei 16 kHz auf, wird mit höherer Frequenz nur auffälliger.)
Ich habe aber eine SoundBlaster AWE 64 - sollte also nicht daran liegen.
wobo hat geschrieben: Du weisst, dass der DMA - Controller nur physikalisch seitenübergreifend arbeiten kann, ...
Ist mir ebenfalls bekannt. Ich habe dafür extra eine Routine gebaut, die da relativ "tricky" arbeitet, um einen genau geeigneten Bereich zu benutzen, der keine 64k Page "wrapt".

Trotzdem Danke für Deine Ausführungen.
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 glaube ich habe auch DMA Modus $48 verwendet. Perfekt stabil läuft das aber auch nicht
immer im Zusammenhang mit dem ZSM Player, selten kommt es vor, dass es bei bestimmten
Modulen direkt am Anfang abschmiert. Und den ersten Puffer hört man immer doppelt.
Ich kann ja hier mal meine Soundblaster-Unit posten. Ich weiss gar nicht mehr ob ich mir das
ganze mit Hilfe des Soundblaster-Buchs selbst zusammengestrickt habe oder irgendwo einen
fertigen Code genommen und angepasst habe. Sieht mir aber eher nach meinem Stil aus.

Code: Alles auswählen

unit sblaster;

interface

function init_sound(bufsize: word): byte;
procedure deinit_sound;
function doublebuffer: pointer;

var
  sound_nextframe: bytebool;


implementation

uses dos, crt;

var

  sb_base: word;
  sb_irq, sb_dma: byte;

  soundbuf_byte: array[0..1] of pointer;
  linear_bufaddr: array[0..1] of longint;
  old_intvec: pointer;
  soundbuf_which: byte;
  portaddr: array[1..2] of byte;
  portval: array[1..2, 0..1] of byte;
  port_0ah: byte;
  buffersize: word;

const
  pagereg: array[0..3] of byte = ($87, $83, $81, $82);

function hexchar2nibble(c: char): longint;
begin
  if ord(c) < 65
    then
      hexchar2nibble := ord(c)-48
    else
      hexchar2nibble := ord(c)-55;
end;


function read_dsp: byte;
begin
  repeat until (port[sb_base+14] and 128) = 128;
  read_dsp := port[sb_base+10];
end;
procedure write_dsp(value: byte);
begin
  repeat until (port[sb_base + 12] and 128) = 0;
  port[sb_base + 12] := value;
end;


procedure program_dma_and_dsp2;
begin
  port[portaddr[1]] := portval[1, soundbuf_which];
  port[portaddr[1]] := portval[2, soundbuf_which];
  port[portaddr[2]] := lo(buffersize-1);
  port[portaddr[2]] := hi(buffersize-1);
  port[$0A] := port_0ah;

  write_dsp($14);
  write_dsp(lo(buffersize-1));
  write_dsp(hi(buffersize-1));
end;


procedure sb_int_rout;
interrupt;
  var dummy: byte;
begin
  program_dma_and_dsp2;
  if soundbuf_which = 1 then soundbuf_which := 0 else soundbuf_which := 1;
  sound_nextframe := true;
  dummy := port[sb_base + $00E];
  port[$20] := $20;
end;



procedure program_dma_and_dsp;
begin
  port[$0A] := 4 + (sb_dma mod 4);
  port[$0C] := $FF;
  port[$0B] := $48 + (sb_dma mod 4);
  port[(sb_dma mod 4)*2] := lo(linear_bufaddr[soundbuf_which] mod 65536);
  port[(sb_dma mod 4)*2] := hi(linear_bufaddr[soundbuf_which] mod 65536);
  port[(sb_dma mod 4)*2+1] := lo(buffersize-1);
  port[(sb_dma mod 4)*2+1] := hi(buffersize-1);
  port[pagereg[sb_dma mod 4]] := linear_bufaddr[soundbuf_which] div 65536;
  port[$0A] := (sb_dma mod 4);

  write_dsp($14); write_dsp(lo(buffersize-1)); write_dsp(hi(buffersize-1));

end;


function doublebuffer: pointer;
begin
  doublebuffer := soundbuf_byte[soundbuf_which];
end;


function init_sound(bufsize: word): byte;
var
  sb_env: string;
  i: word;
begin
  {
  Fehlercodes:
  $01 = BLASTER environment variable not found.
  $02 = DSP initialization failed.
  }

  buffersize := bufsize;
  sb_env := getenv('BLASTER');
  if ord(sb_env[0]) = 0 then begin init_sound := 1; exit; end;
  i := 1; while not (sb_env[i] = 'A') do inc(i);
  sb_base := 0; inc(i);
  inc(sb_base, hexchar2nibble(sb_env[i])*256); inc(i);
  inc(sb_base, hexchar2nibble(sb_env[i])*16); inc(i);
  inc(sb_base, hexchar2nibble(sb_env[i])); inc(i);
  while not (sb_env[i] = 'I') do inc(i);
  inc(i); sb_irq := hexchar2nibble(sb_env[i]); inc(i);
  while not (sb_env[i] = 'D') do inc(i);
  inc(i); sb_dma := hexchar2nibble(sb_env[i]);


  port[sb_base + 6] := 1; delay(1); port[sb_base + 6] := 0;
  i := 2000;
  repeat
    if i < 1 then begin init_sound := 2; exit; end;
    dec(i); delay(1);
  until read_dsp = $AA;


  for i := 0 to 1 do
  begin
    getmem(soundbuf_byte[i], bufsize);
    fillchar(soundbuf_byte[i]^, bufsize, 128);
    linear_bufaddr[i] := seg(soundbuf_byte[i]^);
    linear_bufaddr[i] := (linear_bufaddr[i] shl 4) + ofs(soundbuf_byte[i]^);
  end;


  getintvec(sb_irq + 8, old_intvec);
  port[$21] := port[$21] or (1 shl sb_irq);
  asm cli end;
  setintvec(sb_irq + 8, @sb_int_rout);
  port[$21] := port[$21] and (255-(1 shl sb_irq));


  write_dsp($D1); (* Lautsprecher einschalten *)
  write_dsp($40); write_dsp(211);
   (* Frequenz auf 22050 Hz einstellen *)

  soundbuf_which := 0;
  program_dma_and_dsp;


  portaddr[1] := (sb_dma mod 4) * 2;
  portaddr[2] := (sb_dma mod 4) * 2 + 1;
  portval[1, 0] := lo(linear_bufaddr[0] mod 65536);
  portval[1, 1] := lo(linear_bufaddr[1] mod 65536);
  portval[2, 0] := hi(linear_bufaddr[0] mod 65536);
  portval[2, 1] := hi(linear_bufaddr[1] mod 65536);
  port_0ah := sb_dma mod 4;


  init_sound := 0;

  asm sti end;
end;

procedure deinit_sound;
begin

  write_dsp($D0); (* stop sound immediately *)
  write_dsp($DA); (* stop sound at the end of current block *)

  write_dsp($D3); (* Lautsprecher ausschalten *)

  port[$21] := port[$21] or (1 shl sb_irq);
  asm cli end;
  setintvec(sb_irq+8, old_intvec);
  port[$21] := port[$21] and (255-(1 shl sb_irq));
  asm sti end; (* INT ?? ist jetzt wieder auf der alten Routine *)
end;

end.
Möglicherweise hast du das Knacken wenn du dem DMA und dem DSP die volle Puffergröße mitteilst
und nicht die Größe -1. Ich nutze ja 22.05 kHz (bzw. knapp mehr, diese Formel vom Soundblaster
erlaubt nicht eine so genaue Einstellung) und bei mir klingt alles sauber.
mov ax, 13h
int 10h

while vorne_frei do vor;
wobo
DOS-Guru
Beiträge: 613
Registriert: So 17. Okt 2010, 14:40

Re: Trackermodul-Engine (sehr einfach)

Beitrag von wobo »

@DosFeratu:
Hatte ich mir ja fast gedacht! Klickt es auch, wenn Du ein ganz normales WAV-File (o.ä.) über Deine ISR abspielst (nicht, dass es an Deiner komplizierten Soundberechnung liegt)?

Vielleicht erstellst Du mal ein Testprogramm zum Download und ein entsprechendes Wav - ich würde da gerne probe hören, ob ich das Knacken auch höre, oder - was ich vermute - meine alten, unmusikalischen Ohren da schon einen Anti-Knack-Filter eingebaut haben :-9


@Zatzen:
Wow! Ich habe mir letzthin den Player angehört (Archiv war noch ZSMP002B.zip) – bin schwer beeindruckt. Obercooler player und richtig gute Soundqualität, wie ich finde. Hat richtig Spass gemacht.

Allerdings gab es bei mir am Anfang Probleme: Zuerst ist der PC gleich zu Anfang immer eingefroren. Da Du den source beigelegt hattest, hatte ich dann Deine Routinen zum Auslesen der Blaster-Environment auskommentiert, und die Variablen sb_base, sb_irq, sb_dma von Hand gesetzt. Der Fehler trat in der Unit sblaster in der Zeile

function init_sound(bufsize: word): byte;
[...]
while not (sb_env = 'D') do inc(i);

Warum da ein Fehler aufgetreten ist, konnte ich mir nicht erklären. "i" müsste größer als 255 geworden sein, nehme ich an. Wie gesagt, ich habe es einfach auskommentiert und die Variablen direkt mit den Werten meiner Soundkarte belegt.

Dann gab es erstmal pures Sound- und Player-Vergnügen. Nur bei hamster.zsm ist mein PC wieder eingefroren, und zwar an der Stelle Sequence=3, pattern=3, pattern row=98, offset pattern data=139, position pattern data 56. Hier wird offensichtlich ein Sample=0 angeschlagen, was in der Routine „read_row“ (unit playzsm3.pas) einen Überlauf gegeben hat. Ich hatte dann dafür zwei Zeilen Abfrage geschrieben, dann lief alles wie am Schnürchen. Ich habe mich dann aber nicht mehr gemeldet, weil ich gesehen habe, dass Du schon ZSMP003B.zip rausgehauen hast und da die Routine read_row schon anders verfasst war und der problematische Code da nicht mehr vorhanden war.

Aber nochmal: Die typische Zatzen-Kreativität bei Deinem Player hat mich schon ordentlich beeindruckt und die Soundqualität insbesondere bei den Sachen mit 12+ channels war ebenfalls sehr beeindruckend. Dass die Lautstärke/Qualität da einfach nicht einbricht, kommt das nur vom Clipping (im Vergleich zum Dividieren durch die Anzahl der Kanäle)?
wobo
DOS-Guru
Beiträge: 613
Registriert: So 17. Okt 2010, 14:40

Re: Trackermodul-Engine (sehr einfach)

Beitrag von wobo »

@Zatzen

In der Procedure "procedure program_dma_and_dsp;" prüfst Du übrigens nicht, ob der Soundbuffer eine 64k überschreitest. Allerdings kann das keinen Absturz verursachen, sondern nur ein fürchterliches Knacken bei der Soundausgabe, falls der Fall eintritt, dass Dein DMA-Buffer irgendwo die 64k-Grenze überschreitet. Dann nämlich schickt der DMA die Daten vom Anfang der 64k-Page an die SB, die dann natürlich keine Sound-Daten, sondern Datenmüll vom Heap ausgibt...

Edit: in pseudo code

die Zeilen aus deiner procedure function init_sound(bufsize: word): byte; (sblaster.tpu)
for i := 0 to 1 do
begin
getmem(soundbuf_byte, bufsize);
fillchar(soundbuf_byte^, bufsize, 128);
linear_bufaddr := seg(soundbuf_byte^);
linear_bufaddr := (linear_bufaddr shl 4) + ofs(soundbuf_byte^);
end;


würde ich ändern in etwas wie (Pseudo-Code):

Code: Alles auswählen

 
GetMem (SoundDMABuffer, BufSize*4);
Linear_DMABufAddr := Seg(SoundDMABuffer^) shl 4 + Ofs(SoundDMABuffer^);
if (Linear_DMABufAddr DIV 65536) = ((Linear_DMABufAddr+(BufSize*2)-1) DIV 65536 then
begin
    Linear_bufAddr[0] := SoundDMABuffer;
    Linear_bufAddr[1] := SoundDMABuffer; 
    Inc( word(Linear_bufAddr[1]),BufSize);
end
else begin
    Linear_bufAddr[0] := SoundDMABuffer;
    Inc( word(Linear_bufAddr[0]),BufSize*2);
    Linear_bufAddr[1] := SoundDMABuffer; 
    Inc( word(Linear_bufAddr[1]),BufSize*3);  
end;


Wenn du verstehst, was ich meine ?!? (sorry, bin programmiertechnisch sehr [Edit2: SEHR] eingerostet)


Und ja, Du verwendest auch den AutoInit Mode ($48), der auf ganz alten Soundblaster nicht funktioniert.

[Edit1, Edit2... usw.,]
Zuletzt geändert von wobo am Sa 17. Sep 2016, 11:51, insgesamt 1-mal geändert.
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 wobo!

Ich setze den Soundblaster Pro voraus, denn den hatte ich damals 1992.
Er kann glaube ich mono bis 44.1 kHz, stereo aber nur 22.05 kHz - ich
nutze ja nur mono, 22 kHz. Ich hatte auch erst gedacht 16 kHz würden
genügen, von den Höhen her hätte es mit 8 kHz höchste Frequenz vielleicht
auch gepasst, allerdings gibt es bei 22 kHz Mixfrequenz deutlich weniger
Aliasing-Artefakte beim pitchen der Samples.

Ich weiss nicht ob ich das richtig verstehe, mit dem 64k Überschreiten des
Soundpuffers. Kann das auch passieren wenn der Puffer generell maximal
sagen wir nur 2 K groß ist?
function init_sound(bufsize: word): byte;
[...]
while not (sb_env = 'D') do inc(i);

Warum da ein Fehler aufgetreten ist, konnte ich mir nicht erklären. "i" müsste größer als 255 geworden sein, nehme ich an. Wie gesagt, ich habe es einfach auskommentiert und die Variablen direkt mit den Werten meiner Soundkarte belegt.

Hältst du es für möglich dass hier schlicht in deiner Environment Variable die Buchstaben klein geschrieben sind?

Interessant dass du dich so mit dem Code beschäftigt hast.
Ja, ich hatte zwischendrin auch noch was am Format geändert, ich habe die Möglichkeit integriert, nur einen
Lautstärkebefehl ohne sonstiges zu speichern. Vorher war das immer an einen Notenbefehl gebunden.

Beim Regeln der Lautstärke gehe ich einfach von realistischen Bedingungen aus, und von meiner Erfahrung
wie laut Module im Schnitt werden. Meistens reicht dann eine Dämpfung von 6dB, also auf 50% vollkommen
aus. Und die paar Spitzen die über die Grenze hinausschiessen clippe ich dann, was man so gut wie gar
nicht als Verzerrung wahrnimmt.
Module mit sehr vielen Kanälen sind auch oft nicht lauter, sondern eher leiser als typische 4-Kanal
MODs, denn man verwendet dort z.B. für eine Bassdrum auch nur einen Kanal und gestaltet alles
andere im Verhältnis passend von der Lautstärke her.
Die technische Voraussetzung ist dafür aber, dass ich alles ersteinmal in einem 16 Bit Puffer
summiere. Gleichzeitig gestalte ich das so, dass die Kanäle in dieser Phase möglichst noch
keinerlei Bittiefe einbüßen. Ganz geht das nicht, ich habe 16 Kanäle, und somit hätte jeder
Kanal theoretisch ersteinmal 12 Bit. Wenn man die Lautstärkeregelung mit einbezieht,
die bis auf 1/128tel runtergeht, bleiben in so einem Extremfall jedem Kanal noch 5 Bit,
also +/- 16 Stufen.
Wie auch immer, die interne Bitauflösung ist höher als wenn man vor dem Mischen skaliert
und direkt in einen 8 Bit Puffer reinmischt, und dadurch rauscht es weniger, und es ist
auch detailreicher. Angenommen ich habe 16 Kanäle und mache diese vor dem Zusammen-
mischen leiser, dann ensteht je nachdem eine hohe Verzerrung, ein Gebritzel, das sich dann
aufsummiert. Wenn man vorher in einen 16 Bit Puffer mischt und die Kanäle noch eine gute
Bittiefe haben, dann summiert sich ordentlicher Klang statt Gebritzel, und die Bitreduktion
und Verzerrung entsteht dann erst bei der Überführung in den 8 Bit Puffer, bei dem man
die Lautstärke unter Einbeziehung von Clipping wählen kann, und abwägen kann zwischen
Verzerrung durch Bitreduktion und somit Rauschen, oder Verzerrung durch Übersteuerung,
also Clipping. Clipping klingt immer eher wie das Übersteuern von Transistorvorstufen,
krachen, knacken und heftiges Rauschen wie es bei Überläufen passieren würde wird ja vermieden.
mov ax, 13h
int 10h

while vorne_frei do vor;
wobo
DOS-Guru
Beiträge: 613
Registriert: So 17. Okt 2010, 14:40

Re: Trackermodul-Engine (sehr einfach)

Beitrag von wobo »

So, ZSMP003b.zip angeschaut - bin immer noch begeistert!

Aber auch die neue Version friert auf meinem PC gleich bei der Abfrage der Soundblaster-Environment-Variable ein.
Ursache ist, dass Du beim Auslesen dieser Variable, die Reihenfolge Axxx Ix Dx voraussetzt. Creative hat aber wohl gar nicht eine bestimmte Reihenfolge definiert, sondern eben die Erkennungscodes A, I und D zur Bestimmung der POsition der einzelnen Werte.

Meine SB-Pro kompatible ESS-Soundkarte ist ein plug 'n play-Teil. Dort wird nicht von Hand durch Eintrag in die AUTOEXEC.BAT die Blaster-Variable gesetzt, sondern es wird - ohne dass ich dagegen etwas tun - die Blaster-Variable automatisch durch das pnp-Initialisierungsprogramm gesetzt. Und dieses setzt meine Blaster-Variable nach Initialisierung der Soundkarte auf folgende Werte in dieser Reihenfolge "A220 D1 I5 ...".

Deine Abfrageroutine kommt damit nicht klar, weil sie eben voraussetzt, dass der IRQ-Wert vor dem DMA-Wert in der Blaster-Var eingetragen ist, das aber anscheinend nicht zwingend der Fall sein muss.
(Benutze doch den tp-Befehl "pos(..)", da bist du von der Reihenfolge komplett unabhängig)
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 für diesen Hinweis!
Werde ich dann bei der nächsten Version ändern.
Die Patterndarstellung ist übrigens ziemlich langsam, da sie
aus simplen write-Anweisungen besteht.
Kann man aber abschalten.

Dann bleibt noch die Frage, warum man den ersten Puffer doppelt hört.
Darum habe ich mich bisher nicht gekümmert, weil ich schonmal so zufrieden
war dass sonst alles funktioniert. Vielleicht liegt daran aber auch, dass manche
Module das Programm sofort abstürzen lassen.

Bei mir ist das aktuell mit diesem Modul der Fall: http://www.zatzen.net/NO_COKE.zip
mov ax, 13h
int 10h

while vorne_frei do vor;
wobo
DOS-Guru
Beiträge: 613
Registriert: So 17. Okt 2010, 14:40

Re: Trackermodul-Engine (sehr einfach)

Beitrag von wobo »

Den ersten Puffer höre ich nicht doppelt - kann aber auch an meinen Ohren liegen.

Abstürze gibt es bei mir keine mehr, auch nicht bei no-coke.zsm. Alle zsm laufen nach unten stehender Modifikation sauber durch. Ich musste zuvor aber auch bei ZSMP003b.zip wieder in der Unit PLAYZSM3.PAS, dort in der PROCEDURE Read_Row zweimal ein Sample<>0 abfangen, da es sonst bei Hamster.ZSM und Melodie.ZSM Abstürze gab:

Einmal vor dem Aufruf

Code: Alles auswählen

    play_sample_looped[track] :=  smpinfo[pattern_row[track].smp].loopstart
       < smpinfo[pattern_row[track].smp].length;
ein "if (pattern_row[track].smp > 0) then"

und dann noch einmal in der Schleife

Code: Alles auswählen

  for track := 0 to 15 do
   if trk_active[track] then
   if pattern_row[track].event then
     for i := 1 to zsmheader.sample_entries do
{--->} if (pattern_row[track].smp > 0) then {<---}
           sync[smpinfo[pattern_row[track].smp].marker] := pattern_row[track].vol;
Ich weiß natürlich nicht, ob das von der Player-Logik richtig ist. Ich habe nur gesehen, dass Du im Code an anderen Stellen immer ein Sample=0 abgefangen hast...

Hier noch einmal die ganze proc. read_row mit den beiden Modifikationen:

Code: Alles auswählen

procedure read_row;
  var
    track, tblpos, i: byte;
    read_onlyvol: bytebool;
  label
    skip, skip2;
begin

  patternrow_act := actual_row;

  fillchar(npattline, 16, 0);

  fillchar(sync, 256, 0);

  if use_emptyrow_flags then inc(emptyflagbits);

  for track := 0 to 15 do
    pattern_row[track].event := false;


  (* Leerzeilen Flag optional *)
  if use_emptyrow_flags then if readbits(1) = 0 then goto skip2;

  for track := 0 to 15 do
  if trk_active[track] then (* nur aktive Kanaele abfragen *)
  if readbits(1) = 1 then (* Wenn Event auf Kanal stattfindet *)
  begin
    pattern_row[track].event := true; (* Event markieren *)

    inc(emptyflagbits);

    (* Bei nur Lautstaerke nicht triggern *)
    read_onlyvol := false;
    if onlyvol_exist[track] then
    begin
    inc(purebits);
    if readbits(1) = 0 then
    begin
      read_onlyvol := true;
      goto skip
    end;
    end;
     fillchar(smp_position[track], 4, 0); (* triggern *)

    (* Repeat abfragen, falls genutzt *)
    if do_repeat[track] then
    begin
     inc(purebits);
     if readbits(1) = 1 then
     begin
       pattern_row[track].smp := last_pattern_row[track].smp;
       pattern_row[track].fixvor := last_pattern_row[track].fixvor;
       pattern_row[track].fixnach := last_pattern_row[track].fixnach;
       goto skip;
     end
    end;

     (* Sampnr *)
     if sampnr_table_len[track] = 0 then
     begin
       inc(purebits, bitsneeded[zsmheader.sample_entries+1]);
       pattern_row[track].smp := readbits(bitsneeded[zsmheader.sample_entries+1])
     end
     else
     begin
       inc(purebits, bitsneeded[sampnr_table_len[track]]);
       pattern_row[track].smp
         := sampnr_table[track, readbits(bitsneeded[sampnr_table_len[track]])];
     end; (* SampNr 0 zusammen mit event bedeutet Sample Stop *)

     (* Pitch *)
     if pattern_row[track].smp > 0 then
     begin
       tblpos := readbits(bitsneeded[pitch_table_len[pattern_row[track].smp]]);

       inc(purebits, bitsneeded[pitch_table_len[pattern_row[track].smp]]);

       move(pitch_tables^[pitch_table_offset[pattern_row[track].smp] + tblpos],
         pattern_row[track].fixvor, 3);

     end;

     if pattern_row[track].smp > 0 then
       move(pattern_row[track], last_pattern_row[track], 4);

     skip:

     if (pattern_row[track].smp > 0) or read_onlyvol then
     begin
       pattern_row[track].vol
        := volume_table[track, readbits(bitsneeded[volume_table_len[track]])];
       inc(purebits, bitsneeded[volume_table_len[track]]);
     end;

{wobo:}
{!!}if (pattern_row[track].smp > 0) then
{!!!!!}
    play_sample_looped[track] :=  smpinfo[pattern_row[track].smp].loopstart
       < smpinfo[pattern_row[track].smp].length;
  end;

  skip2:

  for track := 0 to 15 do (* Nur fr den Player als Anzeige *)
    chan_event[track] := pattern_row[track].event;

  for track := 0 to 15 do
  begin
    if pattern_row[track].event then
      npattline[track] := pattern_row[track].vol;
    if pattern_row[track].event and (pattern_row[track].smp = 0) then
      npattline[track] := 0;
    {
    if trk_active[track] and (actual_row = 0) and
    (pattern_row[track].smp > 0) and pattern_row[track].event then inc(npattline[track], 8);
    end;
    }
    if actual_row = 0 then inc(npattline[track], 8);
   end;

   for track := 0 to 15 do
   if trk_active[track] then
   if pattern_row[track].event then
     for i := 1 to zsmheader.sample_entries do
{wobo:}
{!!!!!} if (pattern_row[track].smp > 0) then
{!!!!!}
           sync[smpinfo[pattern_row[track].smp].marker]
           := pattern_row[track].vol;

  new_pattline;
  inc(actual_row);
end;
Ein Mysterium bleibt aber noch: Bei no_coke.zsm sagt mir der "dancer in a box" immer "no drums detected yet", obwohl ich schon meine, dass da Trommeln auch bei sein könnten.
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 »

Das Mysterium ist nicht wirklich eines... Ich habe keine intelligenten Sample-Klang-Erkennungsroutinen da drin,
sondern die Samples müssen mit "bassdrum" "snare" etc. gelabelt, also so genannt werden, bevor das Modul
konvertiert wird. Bei NO_COKE hatte ich das noch nicht getan.
Dass ich dennoch im Player "detected" schreibe liegt daran, dass sozusagen auf ein gelabeltes Sample gewartet
wird, also bis es gespielt wird.

Komischerweise laufen HAMSTER und MELODIE bei mir mit meiner Version des Programmes problemlos durch.
Vielleicht liegen die Sample-Info Arrays bei dir irgendwie anders im Speicher, ich glaube den Index 0 habe
ich nicht definiert, er liegt also nicht in einem reservierten Speicherbereich, und so könnte es mehr oder
weniger zufällig sein was passiert wenn auf diesen undefinierten Bereich zugegriffen wird.

Ich verwende allerdings DOSBOX.

Ich bin momentan nicht so drin in der Materie, seit einigen Wochen mache ich nichts mehr mit Programmieren.
Aber ich komme auf die Sache zurück wenn ich mal wieder Lust drauf habe.

Geplant habe ich, neben der Umsetzung deiner Verbesserungen, die Patterndarstellung direkt in den Speicher
zu schreiben und nicht mit "write()", und den Scope Mode mittels ZVID zu erweitern, mit einer Hintergrundgrafik
und vielleicht einem "Prominenten" aus einem Spiel, der sich dann zur Musik bewegt.

Für mich muss ich aber klären, warum bspw. NO_COKE neuerdings einen Absturz verursacht, was es bisher
nicht tat, und das, ohne dass ich den Player verändert habe, ebenso nicht die ZSM.
Da scheint also noch ein bisschen etwas unsauber zu sein und mehr oder weniger per Zufall
Abstürze zu verursachen.
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 »

Das Array smpinfo geht von 1 - 63.
Bei mir führt es zu keinem Absturz wenn auch mal Index 0 abgefragt wird.
Hast du einen anderen Compiler als ich (BP70) oder hast du irgendwie eingestellt
dass zur Laufzeit die Datenfeldgrenzen geprüft werden?

Keine Frage ist das eine Unsauberkeit im Code, es bleibt nur die Frage, ob
man das Array auf einen 0 Index erweitert und den so definiert dass die Routinen
laufen, statt der zwei IF-Abfragen, da diese ja etwas Zeit beanspruchen, wenn auch minimal.


Wenn man es mal gedanklich durchgeht:

Code: Alles auswählen

play_sample_looped[track] :=  smpinfo[pattern_row[track].smp].loopstart
       < smpinfo[pattern_row[track].smp].length;
Der Fehler kommt hier wohl zustande, wenn ein Event nur aus einer Lautstärke-
änderung besteht. Möglicherweise kann man diese Abfrage einfach oberhalb vom
label "skip" unterbringen. Ach, aber ich bin da immer noch nicht wieder ganz drin...
Aber bei mir verläuft die Sache wohl unkritisch, ich vermute dass im undefinierten
Bereich vom Array smpinfo, also Index 0, bei mir nur Nullen sind.

Weiterhin:

Code: Alles auswählen

sync[smpinfo[pattern_row[track].smp].marker]
           := pattern_row[track].vol;
Das dürfte eigentlich unkritisch sein. Es mag falsche Syncs setzen,
aber diese dürften nicht zu einem Absturz führen, und das Sync Array ist
von 0-255 definiert und somit bei 1 Byte Indizierung immer sicher.
Diese Zeile habe ich aber auch eher schnell reingeschrieben.


Was die Marker (Synchronisation, "drums detected") angeht, hier eine Procedure aus dem Konverter:

Code: Alles auswählen

procedure make_marker(sample: byte);
begin

  with smpinfo[sample] do
  begin
    if dmf_sample_name[sample] = 'bassdrum' then marker := $80;
    if dmf_sample_name[sample] = 'snare' then marker := $81;
    if dmf_sample_name[sample] = 'hihat' then marker := $82;
    if dmf_sample_name[sample] = 'cymbal' then marker := $83;
    if dmf_sample_name[sample] = 'bass' then marker := $90;
  end;

end;
Ähnlich wie bei General MIDI habe ich hier eine Standard-Belegung ab $80 geplant, die ist
natürlich so noch nicht fertig. Die Werte unterhalb $80 hat man dann noch zur individuellen
Belegung frei, hier müsste man dann bei der Namensgebung der Samples z.B. SYNC063
schreiben und kann damit allerlei Sample-abhängige Synchronität erzeugen, z.B. auch
bei Vocals. In der Player-Routine werden die Marker mit dem Lautstärkewert des gerade
angespielten Samples eingelesen, so sind sie nicht nur ein Boolean, sondern enthalten direkt
auch die Lautstärke-Intensität.
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 mir noch einmal besagte Stelle bei HAMSTER.ZSM im original DMF angesehen.
Dort tritt bei Sequencer 3 und Zeile 99 ein Note-Off auf. Das habe ich im Converter
als Sample-Nr. 0 umgesetzt. Das wollte ich nur mal festhalten. Ich sehe immer noch nicht
ganz klar.
Zuletzt geändert von zatzen am Do 22. Sep 2016, 01:30, insgesamt 1-mal geändert.
mov ax, 13h
int 10h

while vorne_frei do vor;
wobo
DOS-Guru
Beiträge: 613
Registriert: So 17. Okt 2010, 14:40

Re: Trackermodul-Engine (sehr einfach)

Beitrag von wobo »

Ich bin momentan nicht so drin in der Materie, seit einigen Wochen mache ich nichts mehr mit Programmieren.
Aber ich komme auf die Sache zurück wenn ich mal wieder Lust drauf habe.
Mach Dir da keinen Stress. Ich kenne das zur genüge und habe gerade eine dreijährige-Programmierpause hinter mir. Und es ist auch jetzt nicht so, dass ich gerade vor Programmierlust glühe… ...und wenn ich viel um die Ohren habe, habe ich meist nicht einmal den Nerv mich auch nur passiv mit Retro-PCs und -Games etc. zu beschäftigen!
Das Mysterium ist nicht wirklich eines... Ich habe keine intelligenten Sample-Klang-Erkennungsroutinen da drin, …
Danke für die Info. Dieses Geheimnis hättest Du aber nicht lüften müssen, da ich eh nie drauf gekommen wäre, wie das funktionierte :-)
Das Array smpinfo geht von 1 - 63.
Bei mir führt es zu keinem Absturz wenn auch mal Index 0 abgefragt wird.
Hast du einen anderen Compiler als ich (BP70) oder hast du irgendwie eingestellt
dass zur Laufzeit die Datenfeldgrenzen geprüft werden?
Ich habe als Compiler Turbo Pascal 7.00 (also nicht Turbo Pascal 7.01 und auch nicht Borland Pascal 7.0). Habe ich RangeChecks an, dann kommt an den beiden Stellen die Fehlermeldung „Range Check Error“. Habe ich nichts an, dann friert bei mir der PC an dieser Stelle ein, so dass ich die aktuelle Patternposition vom Bildschirm abschreiben konnte. Das Einfrieren war übrigens jedes Mal der Fall, wenn ich Hamster und Melodie auf meinem Laptop (Celeron 433 Mhz mit ESS onboard-Soundchip) abspielen wollte.
Keine Frage ist das eine Unsauberkeit im Code, es bleibt nur die Frage, ob
man das Array auf einen 0 Index erweitert und den so definiert dass die Routinen
laufen, statt der zwei IF-Abfragen, da diese ja etwas Zeit beanspruchen, wenn auch minimal.
Array auf 0 erweitern dürfte klappen. Aber die zwei IF-Abfragen kosten meines Erachtens keine relevante Zeit. Beide Lösungen erscheinen mir sinnvoll. Und mach Dir wegen des Tempos keine Sorgen: Auch mein langsamster PC (486sx25 isa) geht bei Deinem Player überhaupt nicht „in die Knie“, egal welche Visualisierungen (patterndisplay etc.) an sind. Bei 4-stimmigen Modulen ist Dein Player mindestens doppelt so schnell wie (wahrscheinlich sogar bis zu 4 Mal schneller als) mein damaliger Mod-Player anno 2012 (der kann jetzt ja sowas von einpacken ;-=) Ehrlich!

Richtig war auch die Entscheidung auf mehr als 4 Stimmen zu setzen. Ich habe gerade gemerkt, wieviel mehr „Volumen“ die Musik bekommt, wenn Du z.B. 12 oder 14 Stimmen einsetzt. Ich bin momentan total „hin und weg“ :-). Ich dachte damals ja noch, dass 4 Stimmen eigentlich genügen. Jetzt denke ich da anders, als ich mit bekommen, wie Hamster im Endpart an „Volumen“ gewinnt (weiss nicht, wie ich das formulieren soll).

Beeindruckend auch, wie Du die Patterndaten auf teilweise 10% der ursprünglichen Größe zusammengestampft hast. Die Patternreduktion kommt ja noch einmal umso mehr zur Geltung, umso mehr Stimmen Du verwendest. Mod-mäßig hättest Du ja bei z.B. 12 Stimmen und 30 verschiedenen Patterns ca. 90kb (was schon out of spec für die Mod-Patterns ist (max. 64kb)). Du kommst da wohl mit ZSM auf nur 9 bis vielleicht 15kb – echt beeindruckend.

Ich finde, Dir ist hier wirklich eine sinnige Weiterentwicklung des Standard 4-channel-Formats gelungen. Absolute Existenzberechtigung meinerseits und der sinnvolle Zwischenschritt von 4-channel Mod und den High-End-Formaten ab S3M etc.


Ich habe übrigens letztes Wochenenden noch schnell Deinen ZSM-Player nach BonnyDos (Brueggis nicht Dos-kompatibles Betriebssystem) geforked. Mann, macht Dein Player Spass! Das ZSM-Format ist jetzt in der Welt, ohne dass Du es rückgängig machen kannst ;-) (Doch, kannst Du: Du musst nur ein Veto einlegen!).

Mir gefällt wirklich nicht nur die bombastische Soundqualität, sondern auch der Player an sich. Ich habe selten einen so sinnigen und informativen Einsatz des ASCII-Zeichensatzes und der zur Verfügung stehenden Farben gesehen.


Beim Forken habe ich übrigens auch den DMABuffer so wie oben beschrieben angelegt. Auf dem 486sx25 hatte ich – im Gegensatz zu meinem Laptop – tatsächlich zum ersten Mal bei der Soundblaster-Programmierung die Konstellation, dass ein Page-Überlauf an einer 64k-Grenze eingetreten war. Die Soundausgabe hörte sich ohne den Workaround an wie „Blähungen“ beim Abspielen.

Ich habe auch die anderen (kosmetischen) Modifikationen, die ich in früheren Posts angesprochen habe, in meinen Fork übernommen und noch ein paar andere kosmetische Sachen verändert. Die eigentlichen Vermix- und Pattern-Handling-Routinen sind aber komplett unangetastet. Ich habe da noch nicht einmal den Versuch unternommen, das zu entschlüsseln. Sieht aber code-mäßig beeindruckend aus. Ich hoffe jetzt nur, dass Du nicht doch noch die Abspiellogik allzu sehr veränderst :-), sondern lieber viele neue ZSMs herausbringst :-) (Aber auch hier kein Stress!!)

Nochmal: Wirklich gute Arbeit!
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 »

Array auf 0 erweitern dürfte klappen. Aber die zwei IF-Abfragen kosten meines Erachtens keine relevante Zeit. Beide Lösungen erscheinen mir sinnvoll.
Wenn ich da etwas ändere, möchte ich mich aber nocheinmal reinarbeiten und verstehen,
was da genau passiert, um dann evtl. eine wirklich elegante und saubere Lösung zu finden.

Vergleich mit deinem Player: Da muss berücksichtigt werden, dass mein Format keinerlei Effekte
unterstützt, ausser Lautstärke. Und diese nicht von 0-63, sondern exponentiell gestuft von 0 bis 7.
Das kommt noch gut hin, aber sobald irgendwo Portamento oder anderes auftritt, sind die Melodien
nicht mehr richtig.
Deshalb lassen sich nur ein Bruchteil der existierenden MODs sauber in ein ZSM umsetzen.
Aber das war ja eben meine Motivation: Von Anfang an wenn ich an die Schaffung eines MOD Players
dachte, war mir die Umsetzung der ganzen Effekte einfach zu viel Aufwand. Und letztlich, weil
ich selber mittlerweile beim Trackern von Musikstücken kaum mehr Effekte als Lautstärke benutze,
habe ich es derart reduziert. So kann auch im Player der interne Puffer der Länge einer Zeile
entsprechen, und braucht nicht, wie bei MODs z.B. auf 1/50tel Sekunde eingestellt sein.
So ergeben sich auch nebenbei kompaktere Patterns, wobei man mehr Effekte auch optional
einbinden könnte ohne dass sich das Format übermäßig aufbläht, aber ich wollte es ja wie
gesagt so halten, dass ich bequem eine Zeilenlänge als internen Puffer haben kann.

Was die vielen Stimmen angeht: Im Gegensatz zu statischen Patterngrößen hat man hier
die Möglichkeit, in einem ZSM einfach wenige Kanäle zu benutzen und zwischendrin
auch mal ganz viele, man muss sich keine Gedanken machen wieviele Kanäle man
benutzt, weil alles dynamisch gehalten wird mit nur so viel Speicherplatz wie
wirklich benötigt.

Gegen das Forken hab ich nix, solange sich nicht irgendwer mit meinen Lorbeeren schmückt ;)

Dass ich das Format nicht weiterentwickle kann ich nicht garantieren, allerdings ist es so
schon ziemlich optimal. Der Converter ist noch erweiterbar, er könnte das ganze Modul
durchsimulieren und abstecken, ob jedes Sample wirklich bis zum Schluss durchgespielt
wird und wenn nicht, dieses entsprechend abschneiden um Speicher zu sparen. Auch könnte
er erkennen, wenn an einem Sample hinten nur Nullen dranhängen oder nur noch leises
Rauschen, kommt schonmal vor, und dieses ebenfalls entfernen.

Neue ZSMs kann ich machen, ich habe ein paar tausend MODs, wovon etwa 75% aber eher Müll sind,
und beim Rest kommt es dann drauf an, wie effektbeladen die sind, d.h. ob sie auch
ohne besondere Effekte noch sinnvoll klingen.
Und dann kommt noch der Musikgeschmack der Allgemeinheit dazu.
Es wimmelt von Techno in der MOD-Szene, bin mir nicht sicher ob das jedem gefällt.

Neue ZSMs machen ist keine Arbeit, höchstens vorher den Converter verbessern...

Die Player Unit ist speziell für ZSMPLAY gespickt mit diversen Abgriffen für die Anzeigen.
Wenn ZSMs später einmal für Spiele verwenden werden, dann kommen diese Abgriffe
alle raus, bis auf wenige einzelne wie die Sync-Sache.

Ich hatte so eine Version der Unit schon gemacht und einige Procedures in Assembler umgeschrieben.
mov ax, 13h
int 10h

while vorne_frei do vor;
Brueggi

Re: Trackermodul-Engine (sehr einfach)

Beitrag von Brueggi »

@Zatzen: Wobo hat den Player portiert (es wurden die Lade-Routinen angepasst), nun rennt er auch unter BonnyDOS/286 :-)
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 »

Freut mich, dass ein paar Leute Spass an dem Player haben.
Da bekomm ich doch direkt wieder Lust, die Visualisierungen noch zu verbessern
und auszubauen, ohne direkt jetzt krampfhaft ein Spiel machen zu wollen, nur
um den Player darin zu benutzen...
Und wie gesagt auch noch nen Optimizer, der die Samples nach Möglichkeit noch stutzt.
mov ax, 13h
int 10h

while vorne_frei do vor;
Antworten