Soundblasterprogrammierung

Diskussion zum Thema Programmierung unter DOS (Intel x86)
Antworten
Benutzeravatar
Dosenware
DOS-Gott
Beiträge: 3745
Registriert: Mi 24. Mai 2006, 20:29

Soundblasterprogrammierung

Beitrag von Dosenware »

Moin,

habe hier leider 2 kleinere Probleme mit dem Soundblaster:

1. Ich bekomme den Autoinitmode nicht richtig zum laufen (die Beispiele die ich im Netz gefunden habe hatten auch nur den Singlemode genutzt und dann jedes mal die Karte neu Programmiert - für mich unpraktisch)

2. Wie codiere ich Creative's 8bit zu 4 Bit ADPCM? Dazu habe ich leider auch nichts im Netz gefunden und die gängigen ADPCM Codecs arbeiten mit 16 zu 4 Bit, was dem Sound auf der Soundblaster nicht gerade zu gute kommt.

____________________________________________________________________________

Hier erstmal die Unit:
Die kann nur 8Bit, 8khz Dateien abspielen - mehr soll sie auch nicht können (evtl. mal 11-16khz wenn ADPCM läuft)
Es gibt eine kleine Besonderheit: Der Int der Soundkarte wird auch als ~30 Ints/s Taktgeber genutzt - deshalb auch die funktion PlaySilence, damit es eben auch bei Stille noch einen Takt gibt.

Das Problem ist: aktuell läuft alles klaglos durch, aber die Interruptroutine wird nicht aufgerufen. (auch nicht wenn ich beim IRQ +8 hinschreibe wie in einigen Beispielen)

Code: Alles auswählen

Unit SB_Unit;
interface
var clock:boolean;

procedure SB_Set(Base_Addr:word;IRQ,DMA:byte);
function  SB_PlayFile(F_Name:String;Continuos:boolean):boolean;
function  SB_PlaySilence:boolean;
procedure SB_Stop;

implementation
uses Dos;
type S_Buffer=array[0..32767]of byte;
type S_Chunk=array[0..8191]of byte;
type S_BufferChunk=array[0..3]of S_Chunk;{Nutzung des Soundinterrupts als Tmer ~31ips}
type S_BufferPTR=^S_Buffer;
type S_BufferChunkPTR=^S_BufferChunk;

const SB_Base:word=$220;
const SB_IRQ: byte=  7;
const SB_DMA: byte=  1;
const SB_FileEnd:      boolean=false;
const SB_FilePlaying:  boolean=false;
const SB_ContinuosPlay:boolean=false;
const SB_Run:          boolean=false;

var   SB_File:file;

var   SB_BufferPos:byte;
var   SB_BufferClock:byte;
const   SB_Buffer:S_BufferPTR=nil;
var   SB_BufferChunk:S_BufferChunkPTR absolute SB_Buffer;

var   SB_InitTest:byte;
var   SB_OldInt:procedure;

procedure SB_ServiceIRQ; interrupt;
 var Temp:Byte; Result:word;
 begin
  writeln('IRQ');         {debug}
  Temp := Port [SB_Base+$E];		{Relieve DSP}
  Port [$20] := $20;                    {Acknowledge hardware interrupt}
  if SB_IRQ >7 then Port [$A0] := $20;  {Überarbeiten IRQ 2}
  inc(SB_BufferClock);
  if SB_BufferClock>31 then
   begin
   FillChar(SB_BufferChunk^[SB_BufferPos],8192,128);
   if SB_FilePlaying then
    begin
     BlockRead(SB_File,SB_BufferChunk^[SB_BufferPos],8192, Result);
     if Result<8192 then if SB_ContinuosPlay then seek(SB_File,48) else SB_FileEnd:=true;
    end;
   SB_BufferClock:=0;
   SB_BufferPos:=(SB_BufferPos+1)and 3;
   end;
  clock:=true;
 end;

 procedure SB_WriteDSP(Value : byte);
 begin
  {Wait for the DSP to be ready to accept data}
  while Port[SB_Base+$0C] and $80 <> 0 do;
  {Send byte}
  Port[SB_Base+$0C] := value;
 end;

procedure SB_InitTimer;interrupt;
  begin;
   inc(SB_InitTest);
   SB_OldInt;
  end;

function SB_Init:boolean;
 begin
  {Starte DSP Reset und Überprüfe auf gelingen}
  SB_InitTest:=0;
  GetIntVec($1C,@SB_OldInt);
  SetIntVec($1C,@SB_InitTimer);
  Port[SB_Base+$06]:=$01;
  repeat;until SB_InitTest=2;
  Port[SB_Base+$06]:=$00;
  SB_InitTest:=0;
  repeat;until SB_InitTest=2;
  if (Port[SB_Base+$0E] and $80=$80)and(Port[SB_Base+$0A]=$AA) then
   SB_Init:=true else SB_Init:=false;
  SetIntVec($1C,@SB_OldInt);
 end;

procedure SB_Set(Base_Addr:word;IRQ,DMA:byte);
 begin;SB_Base:=Base_Addr;SB_IRQ:=IRQ;SB_DMA:=DMA;end;

procedure SB_CreateSoundbuffer;
 var wastesize:word;var waste:Pointer;
 begin
  {Buffer an Seitengrenze ausrichten für DMA}
  new(SB_Buffer);
  wastesize:=(($0FFF and seg(SB_Buffer^))shl 4)+Ofs(SB_Buffer^);
  if wastesize>$8000 then begin
    wastesize:=$FFFF-wastesize;
    inc(wastesize);
    dispose(SB_Buffer);
    getmem(waste,wastesize);
    new(SB_Buffer);
    freemem(waste,wastesize);           {wastesize aufbewahren für weitere Speicherreservierungen?}
    FillChar(SB_Buffer^,32768,128);		{mit Stille Füllen}
   end;
  {/Buffer an Seitengrenze ausrichten}
 end;

procedure SB_Start;
 var dmapage:byte;Size,Offset:word;
 begin
  SB_WriteDSP($D3); {Disable Speaker}
  SB_WriteDSP($D1); {Enable Speaker}

  {Init DMA}
  dmapage:=hi(seg(SB_Buffer^)shr 4);
  Port[$0A]:= $04 or SB_DMA;   {Mask Channel (Disable Channel)}
  Port[$0C]:=0;               {Clear byte pointer}
  Port[$0B]:=$58 or +SB_DMA;  {Set Mode}
  {01XX XXXX 00 Demand Mode, 01 Single Mode, 10 Block Mode, 11 Cascade Mode
   XX0X XXXX 0 address increment/1 Address decrement
   XXX1 XXXX 1 Autoinit
   XXXX 10XX 00 Verify, 01 Write, 10 Readmode
   XXXX XXYY - YY=DMA Channel}

  {Write Offset to DMA Controller}
  Offset:=(seg(SB_Buffer^)shl 4)+Ofs(SB_buffer^);
  Port[(SB_DMA shl 1)]:=Lo(Offset);
  Port[(SB_DMA shl 1)]:=Hi(Offset);

  {Write length of buffer}
  size:=sizeof(SB_Buffer^)-1;
  Port[(SB_DMA shl 1)+1]:=$FF; {Lo(Length-1)}
  Port[(SB_DMA shl 1)+1]:=$7F; {Hi(Length-1)}

  {Write the Page to the DMA Controller}
  case SB_DMA of
   0:port[$87]:=dmapage;
   1:port[$83]:=dmapage;
   2:port[$81]:=dmapage;
   3:port[$82]:=dmapage;
  end;

  {unmask DMA}
  Port[$0A]:=SB_DMA; {Enable DMA}
  {/InitDMA}

  {start}
  SB_BufferClock:=0;
  SB_BufferPos:=0;
  SB_FilePlaying:=false;
  SB_Run:=true;
  FillChar(SB_Buffer^,32768,128);
  GetIntVec(SB_IRQ,@SB_OldInt);
  SetIntVec(SB_IRQ,@SB_ServiceIRQ);
  Port[$21]:=Port[$21]and not (1 shl SB_IRQ);
  SB_WriteDSP($40); {set time constant}
  SB_WriteDSP(131); {256-1.000.000/Samplingrate}
  SB_WriteDSP($48); {Set Block Length}
  SB_WriteDSP($FF); {Set Block Length to 256 -> about 30 Int/s -> Timerinterrupt}
  SB_WriteDSP($00);
  SB_WriteDSP($1C); {Start Playback}
 end;

function SB_PlaySilence:boolean;
 begin
  if SB_Run then
   begin;
   SB_FilePlaying:=false;
   FillChar(SB_Buffer^,32768,128);
   seek(SB_File,0);
   if IoResult=0 then close(SB_File);
   end else
    if SB_Init then
     begin;
     SB_FileEnd:=false;
     SB_ContinuosPlay:=false;
     SB_FilePlaying:=false;
     SB_PlaySilence:=true;
     SB_CreateSoundBuffer;
     SB_Start;
     end else SB_PlaySilence:=False;
 end;

function SB_PlayFile(F_Name:String;Continuos:boolean):boolean; var Result:word; {debug}
 begin
  If SB_PlaySilence then
   begin
   assign(SB_File,F_Name);
   reset(SB_File,1); {RecSize=1Byte, wichtig für Blockread}
   if IoResult=0 then
    begin;
    seek(SB_File,46);
    if IoResult=0 then
     begin;
     BlockRead(SB_File,SB_Buffer^,32768, Result); {Debug}
     writeln('Result: ', Result);                 {debug}
     SB_ContinuosPlay:=Continuos;
     SB_FilePlaying:=true;
     SB_FileEnd:=false;
     SB_PlayFile:=true;
     end else SB_PlayFile:=False
    end else SB_PlayFile:=False;
   end else SB_Playfile:=False;
 end;

procedure SB_Stop;
 begin
  SB_FileEnd:=false;
  SB_ContinuosPlay:=false;
  SB_FilePlaying:=false;
  close(SB_File);
  {Stop DMA}
  SB_WriteDSP($D0);
  SB_WriteDSP($DA);
  SetIntVec(SB_IRQ,@SB_OldInt);
  if SB_Buffer<>nil then dispose(SB_Buffer);
 end;

end.
Und das Testprogramm:
Es spielt ersteinmal Stille - bereits dabei sollte der Bildschirm mit der (Debug-) Meldung IRQ überflutet werden
Als nächstes spielt es eine Datei (Test.wav - 8khz, 8Bit) einmal ab-
Und zu guter letzt diese Datei nochmal in Dauerschleife.
stattdessen spielts nur den 32k Puffer in Dauerschleife ab.

Wenn bei euch die Soundkarte nicht auf 220,7,1 liegt, solltet ihr noch die
"procedure SB_Set(Base_Addr:word;IRQ,DMA:byte);" aufrufen.

Code: Alles auswählen

uses crt,sb_unit;
var w:char;
begin
 w:=readkey;
 writeln('Playing Silence');
 writeln(SB_PlaySilence);
 w:=readkey;
 writeln('Playing Test.wav once');
 writeln(SB_PlayFile('Test.wav',false));
 w:=readkey;
 writeln('Playing Test.wav continuos');
 writeln(SB_PlayFile('Test.wav',true));
 w:=readkey;
 SB_Stop;
end.
Benutzeravatar
Dosenware
DOS-Gott
Beiträge: 3745
Registriert: Mi 24. Mai 2006, 20:29

Re: Soundblasterprogrammierung

Beitrag von Dosenware »

Läuft jetzt, +8 beim IRQ geht jetzt - warum auch immer.

was es mit der +8 auf sich hat steht übrigens hier:
https://en.wikibooks.org/wiki/X86_Assem ... Controller (bei remapping)

Bleibt noch das encodieren von Creative's 4-8Bit ADPCM...

Edit: irgendwo ist noch ein Bug drin.
Für die korrekte Funktion brauche ich ein "writeln(ioresult);" - einfach ein leeres if (oder result:=ioresult;) reicht nicht:

Code: Alles auswählen

function SB_PlayFile(F_Name:String;Continuos:boolean):boolean; var Result:word; {debug}
 begin
  writeln(ioresult);
  If SB_PlaySilence then
   begin
   assign(SB_File,F_Name);
Ob eine Datei bereits offen ist wird durch SB_PlaySilence überprüft und offene Dateien geschlossen...

Code: Alles auswählen

function SB_PlaySilence:boolean;
 begin
  if SB_Run then
   begin;
   SB_FilePlaying:=false;
   FillChar(SB_Buffer^,32768,128);
   seek(SB_File,0);
   if IoResult=0 then close(SB_File);
insgesamt verstehe ich das Verhalten nicht so recht, vor allem da ioresult eine 0 zurückliefert...

EDIT2: Der Fehler war ganz einfach: es gab einen Pfad bei dem der Rückmeldecode von SB_PlaySilence nicht gesetzt wurde - was zum leicht zufälligen Verhalten geführt hat - hier ist der Fehler:

Code: Alles auswählen

function SB_PlaySilence:boolean;
 begin
  if SB_Run then
   begin;
   SB_FilePlaying:=false;
   FillChar(SB_Buffer^,32768,128);
   seek(SB_File,0);
   if IoResult=0 then close(SB_File);
 -> HIER GEHÖRT EIN SB_PlaySilence:=true HIN <-
   end
aktueller Code:

Code: Alles auswählen

Unit SB_Unit;
interface
var clock:boolean;

procedure SB_Set(Base_Addr:word;IRQ,DMA:byte);
function  SB_PlayFile(F_Name:String;Continuos:boolean):boolean;
function  SB_PlaySilence:boolean;
procedure SB_Stop;

implementation
uses Dos;
type S_Buffer=array[0..32767]of byte;
type S_Chunk=array[0..8191]of byte;
type S_BufferChunk=array[0..3]of S_Chunk;{Nutzung des Soundinterrupts als Tmer ~31ips}
type S_BufferPTR=^S_Buffer;
type S_BufferChunkPTR=^S_BufferChunk;

const SB_Base:word=$220;
const SB_IRQ: byte=  7;
const SB_DMA: byte=  1;
const SB_FilePlaying:  boolean=false;
const SB_ContinuosPlay:boolean=false;
const SB_Run:          boolean=false;

var   SB_File:file;

var   SB_BufferPos:byte;
var   SB_BufferClock:byte;
const   SB_Buffer:S_BufferPTR=nil;
var   SB_BufferChunk:S_BufferChunkPTR absolute SB_Buffer;

var   SB_InitTest:byte;
var   SB_OldInt:procedure;

procedure SB_ServiceIRQ; interrupt;
 var Temp:Byte; Result:word;
 begin
  Temp := Port [SB_Base+$E];		{Relieve DSP}
  Port [$20] := $20;                    {Acknowledge hardware interrupt}
  if SB_IRQ >7 then Port [$A0] := $20;  {Überarbeiten IRQ 2}
  inc(SB_BufferClock);
  if SB_BufferClock>31 then
   begin
   FillChar(SB_BufferChunk^[SB_BufferPos],8192,128);
   if SB_FilePlaying then
    begin
     BlockRead(SB_File,SB_BufferChunk^[SB_BufferPos],8192, Result);
     if Result<8192 then if SB_ContinuosPlay then seek(SB_File,48) else SB_FilePlaying:=false;
    end;
   SB_BufferClock:=0;
   SB_BufferPos:=(SB_BufferPos+1)and 3;
   end;
  clock:=true;
 end;

 procedure SB_WriteDSP(Value : byte);
 begin
  {Wait for the DSP to be ready to accept data}
  while Port[SB_Base+$0C] and $80 <> 0 do;
  {Send byte}
  Port[SB_Base+$0C] := value;
 end;

procedure SB_InitTimer;interrupt;
  begin;
   inc(SB_InitTest);
   SB_OldInt;
  end;

function SB_Init:boolean;
 begin
  {Starte DSP Reset und Überprüfe auf gelingen}
  SB_InitTest:=0;
  GetIntVec($1C,@SB_OldInt);
  SetIntVec($1C,@SB_InitTimer);
  Port[SB_Base+$06]:=$01;
  repeat;until SB_InitTest=2;
  Port[SB_Base+$06]:=$00;
  SB_InitTest:=0;
  repeat;until SB_InitTest=2;
  if (Port[SB_Base+$0E] and $80=$80)and(Port[SB_Base+$0A]=$AA) then
   SB_Init:=true else SB_Init:=false;
  SetIntVec($1C,@SB_OldInt);
 end;

procedure SB_Set(Base_Addr:word;IRQ,DMA:byte);
 begin;SB_Base:=Base_Addr;SB_IRQ:=IRQ;SB_DMA:=DMA;end;

procedure SB_CreateSoundbuffer;
 var wastesize:word;var waste:Pointer;
 begin
  {Buffer an Seitengrenze ausrichten für DMA}
  new(SB_Buffer);
  wastesize:=(($0FFF and seg(SB_Buffer^))shl 4)+Ofs(SB_Buffer^);
  if wastesize>$8000 then begin
    wastesize:=$FFFF-wastesize;
    inc(wastesize);
    dispose(SB_Buffer);
    getmem(waste,wastesize);
    new(SB_Buffer);
    freemem(waste,wastesize);           {wastesize aufbewahren für weitere Speicherreservierungen?}
    FillChar(SB_Buffer^,32768,128);		{mit Stille Füllen}
   end;
  {/Buffer an Seitengrenze ausrichten}
 end;

procedure SB_Start;
 var dmapage:byte;Size,Offset:word;
 begin
  SB_WriteDSP($D3); {Disable Speaker}
  SB_WriteDSP($D1); {Enable Speaker}

  {Init DMA}
  dmapage:=hi(seg(SB_Buffer^)shr 4);
  Port[$0A]:= $04 or SB_DMA;   {Mask Channel (Disable Channel)}
  Port[$0C]:=0;               {Clear byte pointer}
  Port[$0B]:=$58 or +SB_DMA;  {Set Mode}
  {01XX XXXX 00 Demand Mode, 01 Single Mode, 10 Block Mode, 11 Cascade Mode
   XX0X XXXX 0 address increment/1 Address decrement
   XXX1 XXXX 1 Autoinit
   XXXX 10XX 00 Verify, 01 Write, 10 Readmode
   XXXX XXYY - YY=DMA Channel}

  {Write Offset to DMA Controller}
  Offset:=(seg(SB_Buffer^)shl 4)+Ofs(SB_buffer^);
  Port[(SB_DMA shl 1)]:=Lo(Offset);
  Port[(SB_DMA shl 1)]:=Hi(Offset);

  {Write length of buffer}
  size:=sizeof(SB_Buffer^)-1;
  Port[(SB_DMA shl 1)+1]:=$FF; {Lo(Length-1)}
  Port[(SB_DMA shl 1)+1]:=$7F; {Hi(Length-1)}

  {Write the Page to the DMA Controller}
  case SB_DMA of
   0:port[$87]:=dmapage;
   1:port[$83]:=dmapage;
   2:port[$81]:=dmapage;
   3:port[$82]:=dmapage;
  end;

  {unmask DMA}
  Port[$0A]:=SB_DMA; {Enable DMA}
  {/InitDMA}

  {start}
  SB_BufferClock:=0;
  SB_BufferPos:=0;
  SB_FilePlaying:=false;
  SB_Run:=true;
  FillChar(SB_Buffer^,32768,128);
  GetIntVec(SB_IRQ+8,@SB_OldInt);
  SetIntVec(SB_IRQ+8,@SB_ServiceIRQ);
  Port[$21]:=Port[$21]and not (1 shl SB_IRQ); {Enable Ints?}
  SB_WriteDSP($40); {set time constant}
  SB_WriteDSP(131); {256-1.000.000/Samplingrate}
  SB_WriteDSP($48); {Set Block Length}
  SB_WriteDSP($FF); {Set Block Length to 256 -> about 30 Int/s -> Timerinterrupt}
  SB_WriteDSP($00);
  SB_WriteDSP($1C); {Start Playback}
 end;

function SB_PlaySilence:boolean;
 begin
  if SB_Run then
   begin;
   SB_FilePlaying:=false;
   FillChar(SB_Buffer^,32768,128);
   seek(SB_File,0);
   if IoResult=0 then close(SB_File);
   SB_PlaySilence:=true;
   end else
    if SB_Init then
     begin;
     SB_ContinuosPlay:=false;
     SB_FilePlaying:=false;
     SB_PlaySilence:=true;
     SB_CreateSoundBuffer;
     SB_Start;
     end else SB_PlaySilence:=False;
 end;

function SB_PlayFile(F_Name:String;Continuos:boolean):boolean; var Result:word; {debug}
 begin
  If SB_PlaySilence then
   begin
   assign(SB_File,F_Name);
   reset(SB_File,1); {RecSize=1Byte, wichtig für Blockread}
   if IoResult=0 then
    begin;
    seek(SB_File,46);
    if IoResult=0 then
     begin;
     SB_ContinuosPlay:=Continuos;
     SB_FilePlaying:=true;
     SB_PlayFile:=true;
     end else SB_PlayFile:=False
    end else SB_PlayFile:=False;
   end else SB_Playfile:=False;
 end;

procedure SB_Stop;
 begin
  SB_ContinuosPlay:=false;
  SB_FilePlaying:=false;
  close(SB_File);
  {Stop DMA}
  SB_WriteDSP($D0);
  SB_WriteDSP($DA);
  SetIntVec(SB_IRQ+8,@SB_OldInt);
  if SB_Buffer<>nil then dispose(SB_Buffer);
 end;
end.
Benutzeravatar
Dosenware
DOS-Gott
Beiträge: 3745
Registriert: Mi 24. Mai 2006, 20:29

Re: Soundblasterprogrammierung

Beitrag von Dosenware »

Bleibt noch das encodieren von Creative's 4-8Bit ADPCM...
Benutzeravatar
Dosenware
DOS-Gott
Beiträge: 3745
Registriert: Mi 24. Mai 2006, 20:29

Re: Soundblasterprogrammierung

Beitrag von Dosenware »

Hmm,


habe jetzt die 16 zu 4 Codierung nachgebaut (brauche aber die 8 zu 4 codierung): http://www.cs.columbia.edu/~hgs/audio/dvi/


Es scheint schonmal Grundsätzlich zu funktionieren, leider lässt mich das Forum das Soudfile nicht hochladen, aber Ton ist da, wenn auch stark verrauscht.

was auffällt ist der starke DC-Anteil

oben Columbia, IMA ADPCM, "Original" (eben zu 8khzm Mono konvertiert), Ima ADPCM per Winamp
pasted-from-clipboard.png
pasted-from-clipboard.png (349.17 KiB) 5279 mal betrachtet
Benutzeravatar
Dosenware
DOS-Gott
Beiträge: 3745
Registriert: Mi 24. Mai 2006, 20:29

Re: Soundblasterprogrammierung

Beitrag von Dosenware »

Jetzt mit Protected Mode:

Die Unit DPMI - für den Protected Mode:

Code: Alles auswählen

unit DPMI;
interface
 var DPMI_active:boolean;                                               {wird beim Start Initialisiert}

 function Allocate_Dos_Memory_Block(size:word;var PoinTR:pointer;var Realseg:word):boolean;
                                                                        {Reserviert Speicher im Bereich <1MB nötig für RM Ints und DMA,
                                                                         liefert einen Pascal Pointer und das dazugehörige
                                                                         RealMode Segment zurück}

 function Free_Dos_Memory_Block(var PoinTR:pointer):boolean;            {gibt den Speicher wieder frei}

implementation

function Allocate_Dos_Memory_Block(size:word;var PoinTR:pointer;var Realseg:word):boolean;
 var helper:array[0..1]of word absolute PoinTR;                         {muss nochmal nachschauen wie asm mit var Parametern funktioniert}
 var help2:word;                                                        {wurden afair als Zeiger behandelt}
 var help3:word;
 var ret:boolean;
 begin
  asm
  mov ret,00
  mov ax,$0100
  mov bx,size
  shr bx,4
  int $31
  jc  @end
  mov ret,$FF
  mov help2,ax
  mov help3,dx
 @end:
  end;
  helper[1]:=help3;                                                     {helper liegt per absolute auf der Gleichen Speicheradresse wie}
  helper[0]:=0;                                                         {PoinTR - einfachere Typkonvertierung}
                                                                        {Protected Mode Pointer in Pascal sind 16+16Bit->Selektor+Offset}
  realseg:=help2;                                                       {Das Realmoderegister}
  Allocate_Dos_Memory_Block:=ret;                                       {Rückgabecode - Bool: $00=false;$FF=true}
 end;

function Free_Dos_Memory_Block(var PoinTR:pointer):boolean;
 var helper:array[0..1]of word absolute PoinTR;
 var help2:word;
 var ret:boolean;
 begin
  help2:=helper[1];
  asm
  mov ret,00
  mov ax,$0101
  mov dx,help2
  int $31
  jc  @end
  mov ret,$FF
 @end:
  end;
  PoinTR:=nil;
  Free_Dos_Memory_Block:=ret;
 end;

begin
 asm
 mov ax,$0400                                                     {Versionsabfrage für den DPMI Server}
 int $31
 mov DPMI_active,$FF                                              {bool: $FF=true; $00=false}
 cmp ax,90                                                        {BP7 hat einen DPMI Server in Version 0.90, darauf teste ich}
 jz @end
 mov DPMI_active,$00
@end:
 end;
end.

(*                                                                 {DPMI Server Installcheck}
function DPMI_active:boolean;assembler;                            {liefert aus irgendeinem Grund im realmode}
 asm                                                               {einen installierten DMPI zurück, aber im protected mode nichts}
 mov ax,$1687
 int $2F
 mov test,ax
 mov testproc,cl
 mov testmaj,dh
 mov testmin,dl
 end;

*)
Die SB_Unit - besonders Create Buffer wurde angepasst:

Code: Alles auswählen

{Speicherproblem umgehen mit Interrupts Alokieren und lesen?}
Unit SB_Unit;
interface
var SB_Clock:boolean;                                           {Als Taktgeber genutzt,
                                                                 wird vom IRQ Handler auf true gesetzt ~30i/s}

procedure SB_Set(Base_Addr:word;IRQ,DMA:byte);                  {Setzt die Ressourcen der Soundkarte}
function  SB_PlaySilence:boolean;                               {Spielt Stille ab - notwendig damit SB_Clock auch
                                                                 ohne Sound funktioniert}
function  SB_PlayFile(F_Name:String;Continuos:boolean):boolean; {Spielt eine Datei ab (Wave 8 Bit,Mono,8khz,PCM),
                                                                 optional kontinuierlich}
procedure SB_Stop;                                              {Beendet die Wiedergabe, gibt Ram, DMA + IRQ frei,
                                                                 setzt Soundkarte zurück}

implementation
uses Dos,DPMI;
type S_Buffer=array[0..32767]of byte;                           {Soundpuffer 32kb in 4x8kb Blöcke unterteilt}
type S_Chunk=array[0..8191]of byte;
type S_BufferChunk=array[0..3]of S_Chunk;
type S_BufferPTR=record
                  PoinTR :^S_Buffer;                            {Die entsprechenden Zeiger auf den Puffer}
                  RSeg   :word;                                 {RSeg wird für den protected Mode benötigt}
                 end;
type S_BufferChunkPTR=^S_BufferChunk;

const SB_Base:word=$220;                                        {Die Ressourcen der Karte, mit defaultwerten Initialisiert}
const SB_IRQ: byte=  7;
const SB_DMA: byte=  1;
const SB_FilePlaying:  boolean=false;                           {interne Flags: SB_FilePlaying=eine Datei wird abgespielt}
const SB_ContinuosPlay:boolean=false;                           {SB_ContinuosPlay=Looped Datei}
const SB_Run:          boolean=false;                           {Soundblaster ist Initialisiert und läuft - Clock aktiv}

var   SB_File:file;                                             {abzuspielende Datei}

var     SB_BufferPos:byte;                                      {Welcher Block ist gerade aktiv (8kb 0-3)
                                                                 evtl. ersetzen durch Abfrage DMAC}
var     SB_BufferClock:byte;                                    {Taktzähler aktuell sind es 32 Takte pro 8kb Block}
var     SB_Buffer:S_BufferPTR;                                   {Die Zeiger für den Soundpuffer}
var     SB_BufferChunk:S_BufferChunkPTR absolute SB_Buffer;     {Absolute = gleiche Adresse,
                                                                 ergibt praktisch eine Variable mit 2 Datentypen}

var   SB_InitTest:byte;                                         {für den Init benötige ich ein Delay, allerdings will ich die
                                                                 Unit CRT vermeiden, daher nutze ich für den Init den
                                                                 Interrupt $1C welcher standardmäßig 18.2 mal pro Sekunde
                                                                 aufgerufen wird - Das ich die Soundkarte als Taktgeber
                                                                 verwende liegt daran, dass an diesem Timer noch andere TSRs
                                                                 dranhängen, die durch eine änderung der Frequenz
                                                                 aus dem Tritt geraten könnten}

var   SB_OldInt:procedure;                                      {Speicher für den alten Interruptvektor}

procedure SB_ServiceIRQ; interrupt;                             {Interruptroutine}
 var Temp:Byte; Result:word;                                    {etwas überarbeiten für schnellere Reaktion auf neue Datei}
 begin
  Temp := Port [SB_Base+$E];		{Relieve DSP}
  Port [$20] := $20;
  if SB_IRQ >7 then Port [$A0] := $20;
  inc(SB_BufferClock);
  if SB_BufferClock>31 then
   begin
   FillChar(SB_BufferChunk^[SB_BufferPos],8192,128);
   if SB_FilePlaying then
    begin
     BlockRead(SB_File,SB_BufferChunk^[SB_BufferPos],8192, Result);
     if Result<8192 then if SB_ContinuosPlay then seek(SB_File,48) else SB_FilePlaying:=false;
    end;
   SB_BufferClock:=0;
   SB_BufferPos:=(SB_BufferPos+1)and 3;
   end;
  SB_Clock:=true;
 end;

 procedure SB_WriteDSP(Value : byte);
 begin
  {Wait for the DSP to be ready to accept data}
  while Port[SB_Base+$0C] and $80 <> 0 do;
  {Send byte}
  Port[SB_Base+$0C] := value;
 end;

procedure SB_InitTimer;interrupt;
  begin;
   inc(SB_InitTest);
   SB_OldInt;
  end;

function SB_Init:boolean;
 begin
  {Starte DSP Reset und Überprüfe auf gelingen}
  SB_InitTest:=0;
  GetIntVec($1C,@SB_OldInt);
  SetIntVec($1C,@SB_InitTimer);
  Port[SB_Base+$06]:=$01;
  repeat;until SB_InitTest=2;
  Port[SB_Base+$06]:=$00;
  SB_InitTest:=0;
  repeat;until SB_InitTest=2;
  if (Port[SB_Base+$0E] and $80=$80)and(Port[SB_Base+$0A]=$AA) then
   SB_Init:=true else SB_Init:=false;
  SetIntVec($1C,@SB_OldInt);
 end;

procedure SB_Set(Base_Addr:word;IRQ,DMA:byte);
 begin;SB_Base:=Base_Addr;SB_IRQ:=IRQ;SB_DMA:=DMA;end;

procedure SB_CreateSoundbuffer;
 var wastesize,help:word;var waste:Pointer;
 begin
  {Buffer an Seitengrenze ausrichten für DMA}
  if DPMI_active then begin                                     {Für Protectedmode, RSeg ist eine Realmodeadresse, nötig für DMA}
   if Allocate_Dos_Memory_Block(sizeof(SB_Buffer.PoinTR^),Pointer(SB_Buffer.PoinTR),SB_Buffer.RSeg)then begin
    wastesize:=SB_Buffer.RSeg shl 4;
    if wastesize>$8000 then begin
     wastesize:=$FFFF-wastesize;
     Free_Dos_Memory_Block(Pointer(SB_Buffer.PoinTR));
     Allocate_Dos_Memory_Block(wastesize,waste,help);
     Allocate_Dos_Memory_Block(sizeof(SB_Buffer.PoinTR^),Pointer(SB_Buffer.PoinTR),SB_Buffer.RSeg);
     Free_Dos_Memory_Block(Pointer(waste));
    end;
   end;
  end
  else
  begin                                                         {Für Realmode}
   new(SB_Buffer.PoinTR);
   wastesize:=(($0FFF and seg(SB_Buffer.PoinTR^))shl 4)+Ofs(SB_Buffer.PoinTR^);
   if wastesize>$8000 then begin
    wastesize:=$FFFF-wastesize;
    inc(wastesize);
    dispose(SB_Buffer.PoinTR);
    getmem(waste,wastesize);
    new(SB_Buffer.PoinTR);
    freemem(waste,wastesize);                           {wastesize aufbewahren für weitere Speicherreservierungen?}
    FillChar(SB_Buffer.PoinTR^,32768,128);		{mit Stille Füllen}
    SB_Buffer.RSeg:=seg(SB_Buffer.PoinTR^)+(Ofs(SB_Buffer.PoinTR^)shr 4);
    end;
   SB_Buffer.RSeg:=seg(SB_Buffer.PoinTR^)+(Ofs(SB_Buffer.PoinTR^)shr 4);
  end;
  {/Buffer an Seitengrenze ausrichten}
 end;

procedure SB_Start;
 var dmapage:byte;Size,Offset:word;
 begin
  SB_WriteDSP($D3); {Disable Speaker}
  SB_WriteDSP($D1); {Enable Speaker}

  {Init DMA}
  dmapage:=hi(SB_Buffer.RSeg shr 4);
  Port[$0A]:= $04 or SB_DMA;   {Mask Channel (Disable Channel)}
  Port[$0C]:=0;               {Clear byte pointer}
  Port[$0B]:=$58 or +SB_DMA;  {Set Mode}
  {01XX XXXX 00 Demand Mode, 01 Single Mode, 10 Block Mode, 11 Cascade Mode
   XX0X XXXX 0 address increment/1 Address decrement
   XXX1 XXXX 1 Autoinit
   XXXX 10XX 00 Verify, 01 Write, 10 Readmode
   XXXX XXYY - YY=DMA Channel}

  {Write Offset to DMA Controller}
  Offset:=(SB_Buffer.RSeg)shl 4;
  Port[(SB_DMA shl 1)]:=Lo(Offset);
  Port[(SB_DMA shl 1)]:=Hi(Offset);

  {Write length of buffer}
  size:=sizeof(SB_Buffer.PoinTR^)-1;
  Port[(SB_DMA shl 1)+1]:=$FF; {Lo(Length-1)}
  Port[(SB_DMA shl 1)+1]:=$7F; {Hi(Length-1)}

  {Write the Page to the DMA Controller}
  case SB_DMA of
   0:port[$87]:=dmapage;
   1:port[$83]:=dmapage;
   2:port[$81]:=dmapage;
   3:port[$82]:=dmapage;
  end;

  {unmask DMA}
  Port[$0A]:=SB_DMA; {Enable DMA}
  {/InitDMA}

  {start}
  SB_BufferClock:=0;
  SB_BufferPos:=0;
  SB_FilePlaying:=false;
  SB_Run:=true;
  FillChar(SB_Buffer.PoinTR^,32768,128);
  GetIntVec(SB_IRQ+8,@SB_OldInt);
  SetIntVec(SB_IRQ+8,@SB_ServiceIRQ);
  Port[$21]:=Port[$21]and not (1 shl SB_IRQ); {Enable Ints?}
  SB_WriteDSP($40); {set time constant}
  SB_WriteDSP(131); {256-1.000.000/Samplingrate}
  SB_WriteDSP($48); {Set Block Length}
  SB_WriteDSP($FF); {Set Block Length to 256 -> about 30 Int/s -> Timerinterrupt}
  SB_WriteDSP($00);
  SB_WriteDSP($1C); {Start Playback}
 end;

function SB_PlaySilence:boolean;
 begin
  if SB_Run then
   begin;
   SB_FilePlaying:=false;
   FillChar(SB_Buffer.PoinTR^,32768,128);
   seek(SB_File,0);
   if IoResult=0 then close(SB_File);
   SB_PlaySilence:=true;
   end else
    if SB_Init then
     begin;
     SB_ContinuosPlay:=false;
     SB_FilePlaying:=false;
     SB_PlaySilence:=true;
     SB_CreateSoundBuffer;
     SB_Start;
     end else SB_PlaySilence:=False;
 end;

function SB_PlayFile(F_Name:String;Continuos:boolean):boolean; var Result:word; {debug}
 begin
  If SB_PlaySilence then
   begin
   assign(SB_File,F_Name);
   reset(SB_File,1); {RecSize=1Byte, wichtig für Blockread}
   if IoResult=0 then
    begin;
    seek(SB_File,46);
    if IoResult=0 then
     begin;
     SB_ContinuosPlay:=Continuos;
     SB_FilePlaying:=true;
     SB_PlayFile:=true;
     end else SB_PlayFile:=False
    end else SB_PlayFile:=False;
   end else SB_Playfile:=False;
 end;

procedure SB_Stop;
 begin
  SB_ContinuosPlay:=false;
  SB_FilePlaying:=false;
  close(SB_File);
  {Stop DMA}
  SB_WriteDSP($D0);
  SB_WriteDSP($DA);
  SetIntVec(SB_IRQ+8,@SB_OldInt);
  if DPMI_active then Free_Dos_Memory_Block(Pointer(SB_Buffer.PoinTR))          {Protected/Realmode}
  else if SB_Buffer.PoinTR<>nil then dispose(SB_Buffer.PoinTR);
 end;

end.
und zu guter letzt, das einfache Beispielprogramm:

Code: Alles auswählen

uses crt,sb_unit;
var w:char;
begin
 w:=readkey;
 writeln('Playing Silence');
 writeln(SB_PlaySilence);
 w:=readkey;
 writeln('Playing Test.wav once');
 writeln(SB_PlayFile('Test.wav',false));
 w:=readkey;
 writeln('Playing Test.wav continuos');
 writeln(SB_PlayFile('Test.wav',true));
 w:=readkey;
 SB_Stop;
end.
Todo: verschiedene Sampleraten unterstützen, Voc Support einbauen (Creative 8 to 4 Bit Kompression habe ich zum laufen gebracht, muss aber hier noch eingebaut werden) und die Nachladeroutine überarbeiten damit sie schneller auf neue Dateien reagiert.
Benutzeravatar
Dosenware
DOS-Gott
Beiträge: 3745
Registriert: Mi 24. Mai 2006, 20:29

Re: Soundblasterprogrammierung

Beitrag von Dosenware »

Hier die neueste Version:

neu ist: es kann zumindest bei Wavdateien das Format erkennen.
Es bleibt jedoch auf 8Bit, Mono, 4-23kHz beschränkt - einfach weil dies ab DSP2.0 unterstützt wird und Wavdateien eh gewaltig sind.

Code: Alles auswählen

{Vocdateien: Blöcke ausschneiden, damit nur noch minimalheader und Sounddaten vorhanden sind}

Unit SB_Unit;
interface

function SB_Set(Base_Addr:word;IRQ,DMA:byte):boolean;           {Setzt die Ressourcen der Soundkarte}

function SB_PlayFile(F_Name:String;Continuos:boolean):boolean;  {Spielt eine Datei ab}

procedure SB_Stop;					        {Beendet die Wiedergabe}

procedure SB_Done;                                              {Beendet die Wiedergabe, gibt Ram, DMA + IRQ frei,
                                                                 setzt Soundkarte zurück}
implementation
uses Dos,DPMI,TIMER;

const SB_Base:word=$220;                                        {Die Ressourcen der Karte, mit defaultwerten Initialisiert}
const SB_IRQ: byte=  7;
const SB_DMA: byte=  1;

const SB_FilePlaying:  boolean=false;                           {interne Flags: SB_FilePlaying=eine Datei wird abgespielt}
const SB_ContinuosPlay:boolean=false;                           {SB_ContinuosPlay=Looped Datei}
const SB_FileEnd      :boolean=false;                           {SB_ContinuosPlay=Looped Datei}

var   SB_File:file;                                             {abzuspielende Datei}
var   SB_File_Data_Offset:longint;				{Startoffset der Daten - 48 bei Wav}

const SB_Buffersize=32768;                                      {Puffergröße}
const SB_Bufferhalf=SB_Buffersize div 2;
type  S_Buffer     =array[0..(SB_Buffersize-1)]of byte;         {Soundpuffer in 2 Blöcke unterteilt}
type  S_Chunk      =array[0..(SB_Bufferhalf-1)]of byte;
type  S_BufferChunk =array[0..1]of S_Chunk;
type  S_BufferPTR   =record
                      PoinTR :^S_Buffer;                        {Die entsprechenden Zeiger auf den Puffer}
                      RSeg   :word;                             {RSeg wird für den protected Mode benötigt}
                     end;
type  S_BufferChunkPTR=^S_BufferChunk;
var   SB_BufferPos:byte;                                        {Welcher Block ist gerade aktiv 0-1)
                                                                 evtl. ersetzen durch Abfrage DMAC}
const SB_Buffer:S_BufferPTR=(PoinTR:nil);                       {Die Zeiger für den Soundpuffer}
var   SB_BufferChunk:S_BufferChunkPTR absolute SB_Buffer;       {Absolute = gleiche Adresse,
                                                                 ergibt praktisch eine Variable mit 2 Datentypen}

var   SB_OldInt:procedure;                                      {Speicher für den alten Interruptvektor}

{######################## SB_WriteDSP ########################}
procedure SB_WriteDSP(Value : byte);
 begin
  while Port[SB_Base+$0C] and $80 <> 0 do;			{Warte bis der DSP bereit ist}
  Port[SB_Base+$0C]:=value;					{sende byte}
  end;

{######################## SB_ServiceIRQ ########################}
procedure SB_ServiceIRQ; interrupt;                             {Interruptroutine}
 var Temp:Byte; Result,Result2:word;
 begin
  Temp := Port [SB_Base+$E];					{Relieve DSP}
  Port [$20] := $20;						{Relieve IRQ}
  if SB_IRQ >7 then Port [$A0] := $20;
  If SB_FileEnd then SB_Stop;					{Bei Ende der Datei wiedergabe stoppen}
  BlockRead(SB_File,SB_BufferChunk^[SB_BufferPos],SB_Bufferhalf, Result);
  if Result<SB_Bufferhalf then if SB_ContinuosPlay then
   begin
   seek(SB_File,SB_File_Data_Offset);                           {sollte so nahtlos weiterspielen}
   BlockRead(SB_File,SB_BufferChunk^[SB_BufferPos][Result],SB_Bufferhalf-Result, Result2);
   end
  else
   begin;
    SB_FileEnd:=true;
	dec(Result,2);						{Damit die Wiedergabe beendet werden kann,}
	SB_WriteDSP($48); 					{bevor die Soundkarte Müll spielt}
    SB_WriteDSP(Lo(Result)); 					{Set Block Length to Result}
    SB_WriteDSP(Hi(Result));
	end;
  SB_BufferPos:=(SB_BufferPos+1)and 1;
  SB_OldInt;
 end;

{######################## SB_Init ########################}
function SB_Init:boolean; var T_help:word;
 begin
 T_help:=TIMER_Clock;
 repeat;until TIMER_Clock<>T_help;
 Port[SB_Base+$06]:=$00;
 repeat;until TIMER_Clock=T_help;
 if (Port[SB_Base+$0E] and $80=$80)and(Port[SB_Base+$0A]=$AA) then
  SB_Init:=true else SB_Init:=false;
 end;

{######################## SB_Set ########################}
function SB_Set(Base_Addr:word;IRQ,DMA:byte):boolean;
 begin;
 SB_Base:=Base_Addr;
 SB_IRQ:=IRQ;
 SB_DMA:=DMA;
 SB_Set:=SB_Init
 end;

procedure SB_CreateSoundbuffer;
 var wastesize,help:word;var waste:Pointer;
 begin
 {Buffer an Seitengrenze ausrichten für DMA}
 if DPMI_active then begin                                      {Für Protected, RSeg ist eine Realmodeadresse, nötig für DMA}
  if Allocate_Dos_Memory_Block(sizeof(SB_Buffer.PoinTR^),Pointer(SB_Buffer.PoinTR),SB_Buffer.RSeg)then begin
   wastesize:=SB_Buffer.RSeg shl 4;                             {Berechnung der Entfernung bis zur nächsten Seitengrenze}
   if wastesize>$8000 then begin                                {DMA kann nicht über diese Grenze hinweg arbeiten}
    wastesize:=$FFFF-wastesize;                                 {Eine Seite entspricht den obersten 4 Bit eines Segments}
    Free_Dos_Memory_Block(Pointer(SB_Buffer.PoinTR));           {Freigeben des Speichers und Reservieren eines Speicherblocks}
    Allocate_Dos_Memory_Block(wastesize,waste,help);            {um den Ram bis zur nächten Seitengrenze zu belegen}
    Allocate_Dos_Memory_Block(sizeof(SB_Buffer.PoinTR^),Pointer(SB_Buffer.PoinTR),SB_Buffer.RSeg);
    Free_Dos_Memory_Block(Pointer(waste));                      {und schon ist mein Puffer an der Grenze ausgerichtet}
    end;
   end;
  end
  else
  begin                                                          {Für Realmode}
  new(SB_Buffer.PoinTR);
  wastesize:=(($0FFF and seg(SB_Buffer.PoinTR^))shl 4)+Ofs(SB_Buffer.PoinTR^);
  if wastesize>$8000 then begin
   wastesize:=$FFFF-wastesize;
   inc(wastesize);
   dispose(SB_Buffer.PoinTR);
   getmem(waste,wastesize);
   new(SB_Buffer.PoinTR);
   freemem(waste,wastesize);                           	         {wastesize aufbewahren für weitere Speicherreservierungen?}
   SB_Buffer.RSeg:=seg(SB_Buffer.PoinTR^)+(Ofs(SB_Buffer.PoinTR^)shr 4);
   end;
  SB_Buffer.RSeg:=seg(SB_Buffer.PoinTR^)+(Ofs(SB_Buffer.PoinTR^)shr 4);
  end;
  {/Buffer an Seitengrenze ausrichten}
 end;

procedure SB_Start(Samplerate,Format:word);
 var dmapage:byte;Size,Offset,Result:word;
 begin
  SB_WriteDSP($D3);                                              {Disable Speaker}
  SB_WriteDSP($D1);                                              {Enable Speaker}

  {Init DMA}
  dmapage:=hi(SB_Buffer.RSeg shr 4);
  Port[$0A]:=$04 or SB_DMA;                                      {Mask Channel (Disable Channel)}
  Port[$0C]:=0;                                                  {Clear byte pointer}
  Port[$0B]:=$58 or +SB_DMA;                                     {Set Mode}
                                                                 {01XX XXXX 00 Demand Mode, 01 Single Mode,
                                                                            10 Block Mode,11 Cascade Mode
                                                                  XX0X XXXX 0 address increment/1 Address decrement
                                                                  XXX1 XXXX 1 Autoinit
                                                                  XXXX 10XX 00 Verify, 01 Write, 10 Readmode
                                                                  XXXX XXYY - YY=DMA Channel}

  Offset:=(SB_Buffer.RSeg)shl 4;                                 {Write Offset to DMA Controller}
  Port[(SB_DMA shl 1)]:=Lo(Offset);
  Port[(SB_DMA shl 1)]:=Hi(Offset);
  size:=SB_Buffersize-1;                                         {Write length-1 of buffer}
  Port[(SB_DMA shl 1)+1]:=Lo(Size);
  Port[(SB_DMA shl 1)+1]:=Hi(Size);
  case SB_DMA of                                                 {Write the Page to the DMA Controller}
   0:port[$87]:=dmapage;
   1:port[$83]:=dmapage;
   2:port[$81]:=dmapage;
   3:port[$82]:=dmapage;
  end;
  Port[$0A]:=SB_DMA;                                             {Unmask/Enable DMA}
  {/InitDMA}

  {start}
  SB_BufferPos:=0;                                               {Rücksetzen der Pufferposition und umleiten des Interrupts}
  SB_FilePlaying:=false;
  GetIntVec(SB_IRQ+8,@SB_OldInt);
  SetIntVec(SB_IRQ+8,@SB_ServiceIRQ);
  Port[$21]:=Port[$21]and not (1 shl SB_IRQ);                    {Enable Ints?}

  SB_WriteDSP($40); 						 {set time constant}
  SB_WriteDSP(byte(256-1000000 div Samplerate));     		 {256-1.000.000/Samplingrate}

  BlockRead(SB_File,SB_Buffer.PoinTR^,SB_Buffersize, Result);    {vorladen des Puffers}

  If Result=SB_Buffersize then
   begin
   size:=SB_Bufferhalf-1;
   SB_WriteDSP($48);                                             {Set Block Length-1}
   SB_WriteDSP(Lo(Size)); 															{Set Block Length to 8192 }
   SB_WriteDSP(Hi(Size));
   SB_FileEnd:=false;
   end
   else                                                          {Wenn die Datei kleiner als der Puffer ist, wird das}
    begin                                                        {Dateiende signalisiert und die Soundkarte so eingestellt}
    dec(Result,4);                                               {dass sie erst nach Ende der Sounddaten einen Int auslöst}
                                                                 {evtl. nochmal überarbeiten wegen loop}

    SB_WriteDSP($48);                                            {Set Block Length-1}
    SB_WriteDSP(Lo(Result)); 															{Set Block Length to 8192 }
    SB_WriteDSP(Hi(Result));
    SB_FileEnd:=true;
    end;
  Case Format of
   1:SB_WriteDSP($1C);                                           {Start Playback PCM}
   2:SB_WriteDSP($7D);                                           {Start Playback CADPCM4}
   3:SB_WriteDSP($7F);                                           {Start Playback CADPCM3}
   4:SB_WriteDSP($1F);                                           {Start Playback CADPCM2}
  end;
 end;

function SB_PlayFile(F_Name:String;Continuos:boolean):boolean;
 var fileheader:array[0..39]of char;
 var fileheader2:array[0..19]of word absolute fileheader;
 var Fileheadhelp:array[0..3]of char absolute fileheader;
 var Samplerate,Format,Result:word;
 begin
  If SB_FilePlaying then SB_Stop;
  if SB_Buffer.PoinTR=nil then SB_CreateSoundBuffer;
  assign(SB_File,F_Name);
  reset(SB_File,1);                                              {RecSize=1Byte, wichtig fürr Blockread}
  if IoResult=0 then
   begin;
   BlockRead(SB_File,fileheader,40,Result);
   if Result=40 then
    begin
    if Fileheadhelp='RIFF' then
     begin
     SB_File_Data_Offset:=44;
     seek(SB_File,SB_File_Data_Offset);
     Format:=fileheader2[10];
     Samplerate:=fileheader2[12];
     if (Format=1)and(fileheader2[11]=1)and(fileheader2[17]=8)and(Samplerate<23000)and(Samplerate>4000)then
        SB_Start(Samplerate,Format);;
     SB_ContinuosPlay:=Continuos;
     SB_FilePlaying:=true;
     end
     else
      begin
      SB_File_Data_Offset:=5;
      seek(SB_File,SB_File_Data_Offset-1);
      Format:=fileheader2[0];
      Samplerate:=fileheader2[12];
      if (Samplerate>4000)and(((Format=1)and(Samplerate<23000))or	   {PCM}
                              ((Format=2)and(Samplerate<12000))or      {CADPCM4}
                              ((Format=3)and(Samplerate<13000))or      {CADPCM3}
                              ((Format=4)and(Samplerate<11000)))then   {CADPCM2}
	                       SB_Start(Samplerate,Format);
      SB_ContinuosPlay:=Continuos;
      SB_FilePlaying:=true;
      end;
    end;
   end;
 end;

procedure SB_Stop;
 begin
  SB_ContinuosPlay:=false;
  SB_FilePlaying:=false;
  close(SB_File);
  {Stop DMA}
  SB_WriteDSP($D0);
  SB_WriteDSP($DA);
  SetIntVec(SB_IRQ+8,@SB_OldInt);
 end;

procedure SB_Done;
 begin
  SB_ContinuosPlay:=false;
  SB_FilePlaying:=false;
  close(SB_File);
  {Stop DMA}
  SB_WriteDSP($D0);
  SB_WriteDSP($DA);
  SetIntVec(SB_IRQ+8,@SB_OldInt);
  if DPMI_active then Free_Dos_Memory_Block(Pointer(SB_Buffer.PoinTR))          {Protected/Realmode}
  else if SB_Buffer.PoinTR<>nil then dispose(SB_Buffer.PoinTR);
 end;

end.
TerrySoba
Windows 3.11-Benutzer
Beiträge: 3
Registriert: Mi 8. Dez 2021, 20:32

Re: Soundblasterprogrammierung

Beitrag von TerrySoba »

Hallo Dosenware,

mit ADPCM für Soundblasterkarten habe ich mich in den letzten Monaten auch beschäftigt.
Ich habe längere Zeit damit verbracht einen Encoder für 4bit Creative ADPCM zu finden (also 8bit -> 4bit).
Ich habe nur den VOCEdit2 gefunden, der mit dem Soundblaster Pro 2 mitgeliefert wird. Leider kann man den VOCEdit2 nicht per Kommandozeile benutzen, sondern man muss ihn über die GUI per Maus bedienen.

Letztendlich habe ich dann meinen eigenen ADPCM-Encoder geschrieben. Ich bin aber kein Experte für ADPCM, daher würde mich interessieren wie du die ADPCM-Encodierung gemacht hast. Eventuell können wir die Encoder mal vergleichen um zu sehen ob man da noch was optimieren kann.


Viele Grüße,
TerrySoba
Benutzeravatar
ChrisR3tro
Administrator
Beiträge: 1979
Registriert: Mo 7. Mär 2005, 23:33
Wohnort: NRW
Kontaktdaten:

Re: Soundblasterprogrammierung

Beitrag von ChrisR3tro »

Mal so am Rande. Es gibt ein ziemlich mächtiges Audio-Konvertierungstool namens "SoX".

http://sox.sourceforge.net/Docs/Features

Hat zwar thematisch wenig mit DOS zu tun, aber evtl. kann das Teil ja die gewünschte Konvertierung und dann könnte man da mal in den Source schauen. Aber nur so eine Idee.

Gruß
ChrisR3tro
TerrySoba
Windows 3.11-Benutzer
Beiträge: 3
Registriert: Mi 8. Dez 2021, 20:32

Re: Soundblasterprogrammierung

Beitrag von TerrySoba »

SoX habe ich mir bereits angesehen, das kann leider kein Creative ADPCM codieren.
Tatsächlich benutze ich das aber bereits für die Konvertierung der Samplerate in meinem Buildsystem für mein DOS-Spiel.
Erst konvertiere ich die Sounds mit SoX von WAV 16bit 44100Hz nach RAW 8bit 11000Hz und dann lasse ich meinen ADPCM-Encoder darauf los.

Hier der Link auf den Encoder:
https://github.com/TerrySoba/DosPlatfor ... pcmEncoder

Viele Grüße,
TerrySoba
Benutzeravatar
Dosenware
DOS-Gott
Beiträge: 3745
Registriert: Mi 24. Mai 2006, 20:29

Re: Soundblasterprogrammierung

Beitrag von Dosenware »

Habe letztlich Vocedit genutzt, muss aber noch schauen wie ich den Klang besser hinbekomme - irgendwo auf dem Weg von 44khz 16Bit zu 22 (und kleiner)kHz und 8Bit wird da irsinnig viel Rauschen hinzugefügt - und ich normalisiere bereits das Audiosignal vor der Konvertierung um möglichst viel Auflösung hinüberzuretten... (50% Aussteuerung bei 16Bit sind immernoch 32768 mögliche Pegel, bei 8Bit sinds bei 50% nur noch 128 - das hört man dann auch recht schnell)
TerrySoba
Windows 3.11-Benutzer
Beiträge: 3
Registriert: Mi 8. Dez 2021, 20:32

Re: Soundblasterprogrammierung

Beitrag von TerrySoba »

Mit dem Rauschen hatte ich auch Probleme, aber ich habe es wesentlich reduzieren können indem ich das Dithering abgeschaltet habe. Meine Kette sieht dabei folgendermaßen aus:

WAV (16bit 44.1kHz) -> SoX -> RAW (8bit 11kHz) -> Mein ADPCM Encoder -> VOC (4bit ADPCM 11kHz)

Die Kommandozeile sieht so aus:

Code: Alles auswählen

sox input.wav -b 8 -e unsigned-integer -D -r 11000 -c 1 -t raw output.raw
adpcm_encoder output.raw 11000 output.voc
Hierbei ist die "-D" Option von SoX sehr wichtig, da es das Dithering abschaltet. Per Default ist Dithering bei SoX aktiviert.

Hier kannst du ein paar Beispiele finden wie das Original (WAV) und die ADPCM codierte Version (VOC) dann klingen:
https://github.com/TerrySoba/DosPlatfor ... main/sound

Viele Grüße,
TerrySoba
Antworten