{$A+,B-,D+,E-,F-,G+,I-,L+,N-,O-,P-,Q-,R-,S-,T-,V-,X+,Y+}

PROGRAM VOCPlay;
{
  Ejemplo de reproducci¢n de ficheros VOC, WAV, etc.
  Versi¢n 2, que env¡a las muestras al DAC de la SB por DMA.
  Por Luis Crespo, FidoNet 2:343/108.21, Internet d8089110@est.fib.upc.es
}

USES
  DOS,
  SB,DAC,Speaker;

TYPE
  PByte=^Byte;
  TBarr=ARRAY [0..0] OF Byte;

VAR
  Tamanyo:Word;          {Tama¤o del VOC en memoria}
  pVoc,                  {Puntero al VOC}
  pBufDMA,               {Puntero al buffer}
  pBufTotDMA :PByte;     {Puntero al buffer total}
  VocPos:Word;           {Posici¢n actual del VOC}
  FinVoz:Boolean;
  PrimeraMitad,
  HaySB:Boolean;


CONST
  Frec:Word = 11000; {Frecuencia de muestreo}
  BufSize   = 512;   {Tama¤o del buffer de DMA}




  FUNCTION Hex(w:Word):String;
  CONST
    Cifra:ARRAY[0..15] OF Char=
      ('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
  BEGIN
    Hex:= Cifra[Hi(w) SHR  4] +
      Cifra[Hi(w) AND 15] +
      Cifra[Lo(w) SHR  4] +
      Cifra[Lo(w) AND 15];
  END;

  FUNCTION LeadingZero(w:Word):String;
  VAR
    s:String;
  BEGIN
    Str(w:0,s);
    IF Length(s)=1 THEN s:='0'+s;
    LeadingZero:=s;
  END;

  FUNCTION NoZero(s:String):String;
  BEGIN
    WHILE (Length(s)>0) AND (s[1]='0') DO Delete(s,1,1);
    NoZero:=s;
  END;

  FUNCTION TeclaPulsada:Boolean;
  {Esta funci¢n mira si se ha pulsado una tecla pero SIN llamadas a la BIOS,
   de forma que no se inhabilitan las interrupciones en cada llamada, cosa
   que bajar¡a mucho la calidad de reproducci¢n con el altavoz interno.}
  BEGIN
    TeclaPulsada:= MemW[$40:$1A]<>MemW[$40:$1C];
  END;



  PROCEDURE CargaVoc(Nombre:String; VAR pVoc:PByte; VAR Tamanyo:Word);
  {Carga el fichero "Nombre" a partir del puntero "pVoc"}
  VAR
    Fichero  : FILE;
    Cabecera : ARRAY [1..32] OF Byte;
  BEGIN
    Assign(Fichero,Nombre);
    Reset(Fichero,1);  {Abrimos el fichero, con tama¤o de registro de 1 byte}
    BlockRead(Fichero,Cabecera,32); {Leemos (y descartamos) la cabecera}
    Tamanyo:=FileSize(Fichero)-32;
    GetMem(pVoc,Tamanyo); {Reservamos memoria}
    BlockRead(Fichero,pVoc^,Tamanyo); {Leemos los datos}
    Close(Fichero);
  END;

  PROCEDURE ShowConfig;
  {Muestra la configuraci¢n de la Sound Blaster}
  BEGIN
    WriteLn;
    IF HaySB THEN
    BEGIN
      WriteLn('Sound Blaster detectada.');
      WriteLn('Puerto Base:     ',NoZero(Hex(SBBase)),'h');
      WriteLn('Versi¢n del DSP: ',DSPVerMajor,'.',LeadingZero(DSPVerMinor));
      WriteLn('IRQ:             ',SBIRQ);
      WriteLn('DMA de 8 bits:   ',SB8DMA);
      IF DSPVerMajor>=4 THEN WriteLn('DMA de 16 bits:  ',SB16DMA);
    END
    ELSE WriteLn('Sound Blaster no detectada');
    WriteLn;
  END;


  PROCEDURE ReservaBufferDMA(Size:Word);
  {Reserva un buffer para transferencia de DMA, controlando que no haya
   solapamiento de p ginas.}
  VAR
    PagIni,PagDest:Byte;
    DirFisIni,          {Direcci¢n f¡sica de inicio del buffer de DMA}
    DirFisDest:LongInt; {Direcci¢n f¡sica final del buffer de DMA}
  BEGIN
    GetMem(pBufTotDMA,Size*2); {Se reserva el doble de memoria necesaria}
    pBufDMA:=pBufTotDMA;
    DirFisIni:=LongInt(Seg(pBufDMA^)) SHL 4 + Ofs(pBufDMA^);
    DirFisDest:=DirFisIni+Size;
    PagIni:=DirFisIni SHR 16;
    PagDest:=DirFisDest SHR 16;
    IF PagIni<>PagDest THEN Inc(pBufDMA,Size);
    {Si hay solapamiento en la primera mitad, se toma la segunda}
  END;

  PROCEDURE NextBuffer; FAR;
  {Esta rutina es llamada cada vez que se recorre medio buffer, y se
   encarga de actualizar el buffer de DMA.}
  BEGIN
    Move(TBarr(pVoc^)[VocPos],pBufDMA^,BufSize);
    IF VocPos>Tamanyo THEN FinVoz:=TRUE;
    Inc(VocPos,BufSize);
    IF PrimeraMitad THEN Inc(pBufDMA,BufSize) ELSE Dec(pBufDMA,BufSize);
    PrimeraMitad:=NOT PrimeraMitad;
  END;


BEGIN
  IF ParamCount<1 THEN
  BEGIN
    WriteLn('Error: falta par metro necesario.');
    Halt(1);
  END;
  HaySB:=SBDetect;
  ShowConfig;
  CargaVoc(ParamStr(1),pVoc,Tamanyo);
  ReservaBufferDMA(BufSize*2);
  FinVoz:=FALSE;
  VocPos:=0;
  PrimeraMitad:=TRUE;
  FillChar(pBufDMA^,BufSize*2,128);
  IF HaySB THEN
  BEGIN
    SBPlayBuf(pBufDMA,BufSize,Frec,NextBuffer);
    REPEAT UNTIL FinVoz OR TeclaPulsada;
    SBStopBuf;
  END
  ELSE BEGIN
    {Si se tiene un DAC casero, cambiar SpkPlayBuf por DACPlayBuf,
     y SpkStopBuf por DACStopBuf}
    SpkPlayBuf(pBufDMA,BufSize,Frec,NextBuffer);
    REPEAT UNTIL FinVoz OR TeclaPulsada;
    SpkStopBuf;
  END;
  FreeMem(pBufTotDMA,BufSize*4);
  FreeMem(pVoc,Tamanyo);
END.