UNIT synth;
{$IFDEF FPC}
{$MODE DELPHI}
{$HINTS OFF}
{$ELSE}
{$O+}
{$ENDIF}
{$DEFINE CLEAN}

INTERFACE

USES Windows,MMSystem;

TYPE PSINGLE=^SINGLE;
     PBYTE=^BYTE;

     TWAVEHDR=WAVEHDR;

CONST Max4kChannels=16;
      AddDenormalNoise=1.0E-24;

TYPE T4kFileData=PACKED RECORD
      WaveForm:ARRAY[0..Max4kChannels-1] OF BYTE;
      Link:ARRAY[0..Max4kChannels-1] OF BYTE;
      LinkFactor:ARRAY[0..Max4kChannels-1] OF SINGLE;
      OutFactor:ARRAY[0..Max4kChannels-1] OF SINGLE;
      StartVolume:ARRAY[0..Max4kChannels-1] OF SINGLE;
      StartVolumeFactor:ARRAY[0..Max4kChannels-1] OF SINGLE;
      StartPhaseFactor:ARRAY[0..Max4kChannels-1] OF SINGLE;
      Panning:ARRAY[0..Max4kChannels-1] OF BYTE;
      EventOffset:ARRAY[0..Max4kChannels-1] OF INTEGER;
      NoteOffset:ARRAY[0..Max4kChannels-1] OF INTEGER;
      VolumeOffset:ARRAY[0..Max4kChannels-1] OF INTEGER;
     END;

     T4kFilter=PACKED RECORD
      FD1,FD2,FD3,FD4:SINGLE;
      FBLP,FBHP:SINGLE;
      CLP,CHP:SINGLE;
      RLP,RHP:SINGLE;
     END;

     T4kADSRData=PACKED RECORD
      AttackStep:ARRAY[0..Max4kChannels-1] OF SINGLE;
      DecayStep:ARRAY[0..Max4kChannels-1] OF SINGLE;
      DestDecay:ARRAY[0..Max4kChannels-1] OF SINGLE;
      Sustain:ARRAY[0..Max4kChannels-1] OF BOOLEAN;
      ReleaseStep:ARRAY[0..Max4kChannels-1] OF SINGLE;
     END;

     T4kRealTimeData=PACKED RECORD
      LinkFirst:ARRAY[0..Max4kChannels-1] OF BOOLEAN;
      LinkValues:ARRAY[0..Max4kChannels-1] OF SINGLE;
      Volume:ARRAY[0..Max4kChannels-1] OF SINGLE;
      VolumeValue:ARRAY[0..Max4kChannels-1] OF SINGLE;
      VolumeFactor:ARRAY[0..Max4kChannels-1] OF SINGLE;
      ChannelLastLeft:ARRAY[0..Max4kChannels-1] OF SINGLE;
      ChannelLastRight:ARRAY[0..Max4kChannels-1] OF SINGLE;
      ADSRMode:ARRAY[0..Max4kChannels-1] OF INTEGER;
      ADSRValue:ARRAY[0..Max4kChannels-1] OF SINGLE;
      Phase:ARRAY[0..Max4kChannels-1] OF SINGLE;
      PhaseIncrement:ARRAY[0..Max4kChannels-1] OF SINGLE;
      PhaseFactor:ARRAY[0..Max4kChannels-1] OF SINGLE;
      Filter:ARRAY[0..Max4kChannels-1] OF T4kFilter;
      LastLeft:SINGLE;
      LastRight:SINGLE;
      TickCounter:ARRAY[0..Max4kChannels-1] OF LONGINT;
      EventOffset:ARRAY[0..Max4kChannels-1] OF PBYTE;
      NoteOffset:ARRAY[0..Max4kChannels-1] OF PBYTE;
      VolumeOffset:ARRAY[0..Max4kChannels-1] OF PBYTE;
      RestartEventOffset:ARRAY[0..Max4kChannels-1] OF PBYTE;
      RestartNoteOffset:ARRAY[0..Max4kChannels-1] OF PBYTE;
      RestartVolumeOffset:ARRAY[0..Max4kChannels-1] OF PBYTE;
      BPMSamples:LONGWORD;
      CurrentBPMSamples:LONGWORD;
      ShouldExit:BOOLEAN;
     END;

     T4kEventData=ARRAY[0..$1FFFFF] OF BYTE;

{$IFDEF tracker}
     T4kOscil=ARRAY[0..$FFFF] OF SINGLE;
{$ENDIF}

     P4kSynthData=^T4kSynthData;
     T4kSynthData=PACKED RECORD
      FileData:T4kFileData;
      ADSRData:T4kADSRData;
      InfoData:LONGWORD;
      EventData:T4kEventData;
      RealtimeData:T4kRealtimeData;
{$IFDEF tracker}
      Oscil:ARRAY[0..$F] OF T4kOscil;
{$ENDIF}
     END;

     P4kInfoData=^T4kInfoData;
     T4kInfoData=PACKED RECORD
      NameLength:LONGWORD;
      AuthorLength:LONGWORD;
      MessageLength:LONGWORD;
     END;

CONST SampleRate=44100;
      SampleRateFactor=440/SampleRate;
      PortamentoFactor=SampleRateFactor/16;
      Div8=1/8;
      Div12=1/12;
      Div64=1/64;
      Div127=1/127;
      Div255=1/255;
      Div256=1/256;
      Div65536=1/65536;
      CutOffLowPassStartValue=255*Div256;
      BufferSize=4096;

FUNCTION SynthGetName(VAR Data):STRING; STDCALL;
FUNCTION SynthGetAuthor(VAR Data):STRING; STDCALL;
FUNCTION SynthGetMessage(VAR Data):STRING; STDCALL;
PROCEDURE SynthInit(VAR SynthData:T4kSynthData;TrackData:POINTER;TrackSize:INTEGER); STDCALL;
PROCEDURE SynthPoll(VAR SynthData:T4kSynthData); STDCALL;
PROCEDURE SynthDone(VAR SynthData:T4kSynthData); STDCALL;

IMPLEMENTATION

CONST WaveFormat:TWaveFormatEx=(wFormatTag:3;nChannels:2;nSamplesPerSec:SampleRate;
                                nAvgBytesPerSec:SampleRate*8;nBlockAlign:8;
                                wBitsPerSample:32;cbSize:0);

VAR WaveOutHandle:LONGWORD;
    WaveHandler:ARRAY[0..3] OF TWAVEHDR;
    BufferCounter:LONGWORD;
    WhiteNoiseSeed:LONGWORD;
    Buffers:ARRAY[0..3,0..BufferSize,0..1] OF SINGLE;

FUNCTION F_POWER(Number,Exponent:SINGLE):SINGLE; ASSEMBLER; STDCALL; {$IFDEF FPC}[public, alias : 'F_POWER'];{$ENDIF}
ASM
 FLD Exponent
 FLD Number
 FYL2X
 FLD1
 FLD ST(1)
 FPREM
 F2XM1
 FADDP ST(1),ST
 FSCALE
 FSTP ST(1)
END;

FUNCTION WhiteNoiseRandom:SINGLE; STDCALL; {$IFDEF FPC}[public, alias : 'WhiteNoiseRandom'];{$ENDIF}
VAR WhiteNoiseValue:LONGWORD;
BEGIN
 WhiteNoiseSeed:=(WhiteNoiseSeed*$524281)+$3133731;
 WhiteNoiseValue:=(WhiteNoiseSeed AND $7FFFFF) OR $40000000;
 RESULT:=SINGLE(POINTER(@WhiteNoiseValue)^)-3;
END;

FUNCTION FRAC(X:SINGLE):SINGLE; STDCALL; {$IFDEF FPC}[public, alias : 'FRAC'];{$ENDIF}
VAR Y:SINGLE;
BEGIN
 Y:=X;
 ASM
  FLD DWORD PTR Y
  FRNDINT
  FSTP DWORD PTR Y
 END;
 RESULT:=X-Y;
END;

PROCEDURE SynthReinit(VAR SynthData:T4kSynthData); STDCALL; {$IFDEF FPC}[public, alias : 'SynthReinit'];{$ENDIF}
VAR Channel:INTEGER;
BEGIN
 FOR Channel:=0 TO Max4kChannels-1 DO BEGIN
  SynthData.RealtimeData.EventOffset[Channel]:=SynthData.RealtimeData.RestartEventOffset[Channel];
  SynthData.RealtimeData.NoteOffset[Channel]:=SynthData.RealtimeData.RestartNoteOffset[Channel];
  SynthData.RealtimeData.VolumeOffset[Channel]:=SynthData.RealtimeData.RestartVolumeOffset[Channel];
  SynthData.RealtimeData.TickCounter[Channel]:=0; 
 END;
 SynthData.RealtimeData.ShouldExit:=TRUE;
END;

PROCEDURE SynthRecalcFilter(VAR Filter:T4kFilter); STDCALL; {$IFDEF FPC}[public, alias : 'SynthRecalcFilter'];{$ENDIF}
BEGIN
 WITH Filter DO BEGIN
  FBLP:=(RLP+RLP/(1-CLP))+AddDenormalNoise;
  FBHP:=(RHP+RHP/(1-CLP))+AddDenormalNoise;
 END;
END;

PROCEDURE SynthInitData(VAR SynthData:T4kSynthData); STDCALL; {$IFDEF FPC}[public, alias : 'SynthInitData'];{$ENDIF}
VAR Channel:INTEGER;
BEGIN
 FOR Channel:=0 TO Max4kChannels-1 DO BEGIN
  IF SynthData.FileData.EventOffset[Channel]>0 THEN BEGIN
   SynthData.RealtimeData.RestartEventOffset[Channel]:=POINTER(LONGWORD(@SynthData)+LONGWORD(SynthData.FileData.EventOffset[Channel]));
   SynthData.RealtimeData.RestartNoteOffset[Channel]:=POINTER(LONGWORD(@SynthData)+LONGWORD(SynthData.FileData.NoteOffset[Channel]));
   SynthData.RealtimeData.RestartVolumeOffset[Channel]:=POINTER(LONGWORD(@SynthData)+LONGWORD(SynthData.FileData.VolumeOffset[Channel]));
  END;
  SynthData.RealtimeData.Filter[Channel].CLP:=CutOffLowPassStartValue;
  SynthRecalcFilter(SynthData.RealtimeData.Filter[Channel]);
 END;
 SynthReinit(SynthData);
 SynthData.RealtimeData.ShouldExit:=FALSE;
END;

PROCEDURE SynthCalcNote(VAR SynthData:T4kSynthData;Channel,Note:INTEGER); STDCALL; {$IFDEF FPC}[public, alias : 'SynthCalcNote'];{$ENDIF}
BEGIN
 SynthData.RealtimeData.PhaseIncrement[Channel]:=F_POWER(2,(Note-45)*Div12)*SampleRateFactor;
END;

PROCEDURE SynthFillBuffer(VAR SynthData:T4kSynthData;Buffer:PSINGLE;StartPosition,BufferSize:INTEGER); STDCALL; {$IFDEF FPC}[public, alias : 'SynthFillBuffer'];{$ENDIF}
VAR Position,Channel,Count,WaveForm:INTEGER;
    Phase,Left,Right,PanningValue,OscValue,Value:SINGLE;
    PhaseCasted:LONGWORD ABSOLUTE Phase;
    Note,Volume:BYTE;
BEGIN
{$IFDEF tracker}
 FOR Position:=0 TO BufferSize-1 DO BEGIN
{$ELSE}
 FOR Position:=1 TO BufferSize DO BEGIN
{$ENDIF}
  Count:=Max4kChannels;
  Left:=0;
  Right:=0;
  FILLCHAR(SynthData.RealtimeData.LinkFirst,16,#1);
  FOR Channel:=0 TO Max4kChannels-1 DO BEGIN
   IF SynthData.RealtimeData.CurrentBPMSamples=0 THEN BEGIN
    WHILE ASSIGNED(SynthData.RealtimeData.EventOffset[Channel]) AND (SynthData.RealtimeData.TickCounter[Channel]>=SynthData.RealtimeData.EventOffset[Channel]^) DO BEGIN
     SynthData.RealtimeData.TickCounter[Channel]:=0;
     Note:=SynthData.RealtimeData.NoteOffset[Channel]^;
     Volume:=SynthData.RealtimeData.VolumeOffset[Channel]^;
     INC(SynthData.RealtimeData.EventOffset[Channel]);
     INC(SynthData.RealtimeData.NoteOffset[Channel]);
     INC(SynthData.RealtimeData.VolumeOffset[Channel]);
     IF Note<=$81 THEN BEGIN
      IF Note<$80 THEN BEGIN
       SynthData.RealtimeData.LastLeft:=SynthData.RealtimeData.LastLeft+SynthData.RealtimeData.ChannelLastLeft[Channel];
       SynthData.RealtimeData.LastRight:=SynthData.RealtimeData.LastRight+SynthData.RealtimeData.ChannelLastRight[Channel];
       SynthData.RealtimeData.ChannelLastLeft[Channel]:=0;
       SynthData.RealtimeData.ChannelLastRight[Channel]:=0;
       SynthCalcNote(SynthData,Channel,Note);
       SynthData.RealtimeData.PhaseFactor[Channel]:=SynthData.FileData.StartPhaseFactor[Channel];
       SynthData.RealtimeData.Volume[Channel]:=SynthData.FileData.StartVolume[Channel];
       SynthData.RealtimeData.VolumeFactor[Channel]:=SynthData.FileData.StartVolumeFactor[Channel];
       SynthData.RealtimeData.ADSRMode[Channel]:=1;
       SynthData.RealtimeData.ADSRValue[Channel]:=0;
      END ELSE IF Note=$80 THEN BEGIN
       SynthData.RealtimeData.ADSRMode[Channel]:=4;
      END;
      IF Volume<=64 THEN BEGIN
       SynthData.RealtimeData.VolumeValue[Channel]:=Volume*Div64;
      END;
     END ELSE BEGIN
      Value:=Volume*Div256;
      CASE Note OF
       $82:SynthData.RealtimeData.BPMSamples:=(SampleRate*5*128) DIV (Volume SHL 8);
       $83:SynthData.RealtimeData.EventOffset[Channel]:=NIL;
       $84,$85:BEGIN
        OscValue:=Volume*PortamentoFactor;
        IF Note=$84 THEN OscValue:=-OscValue;
        SynthData.RealtimeData.PhaseIncrement[Channel]:=SynthData.RealtimeData.PhaseIncrement[Channel]*(1+OscValue);
       END;
       $86:SynthData.FileData.Panning[Channel]:=Volume;
       $87:BEGIN
        SynthData.RealtimeData.Filter[Channel].CLP:=Value;
        SynthRecalcFilter(SynthData.RealtimeData.Filter[Channel]);
       END;
       $88:BEGIN
        SynthData.RealtimeData.Filter[Channel].CHP:=Value;
        SynthRecalcFilter(SynthData.RealtimeData.Filter[Channel]);
       END;
       $89:BEGIN
        SynthData.RealtimeData.Filter[Channel].RLP:=Value;
        SynthRecalcFilter(SynthData.RealtimeData.Filter[Channel]);
       END;
       $90:BEGIN
        SynthData.RealtimeData.Filter[Channel].RHP:=Value;
        SynthRecalcFilter(SynthData.RealtimeData.Filter[Channel]);
       END;
       $91:SynthData.RealTimeData.Phase[Channel]:=Value*2*PI;
       $92:SynthCalcNote(SynthData,Channel,Volume);
       $93:BEGIN
        SynthData.RealtimeData.RestartEventOffset[Channel]:=SynthData.RealtimeData.EventOffset[Channel];
        SynthData.RealtimeData.RestartNoteOffset[Channel]:=SynthData.RealtimeData.NoteOffset[Channel];
        SynthData.RealtimeData.RestartVolumeOffset[Channel]:=SynthData.RealtimeData.VolumeOffset[Channel];
       END;
      END;
     END;
    END;
    IF NOT ASSIGNED(SynthData.RealtimeData.EventOffset[Channel]) THEN DEC(Count);
    INC(SynthData.RealtimeData.TickCounter[Channel]);
   END;
   Phase:=FRAC(SynthData.RealtimeData.Phase[Channel]+SynthData.RealtimeData.LinkValues[Channel]);
   WaveForm:=SynthData.FileData.WaveForm[Channel];
   CASE WaveForm OF
    0:OscValue:=SIN(Phase*2*PI); // Sinus
    1:OscValue:=ABS((Phase-0.5)*4)-1; // Triangle
    2:BEGIN // Square
     Phase:=Phase-0.5;
     OscValue:=LONGWORD((PhaseCasted SHR 31) SHL 1);
     OscValue:=1-OscValue;
    END;
    3,4:OscValue:=((Phase-0.5)*2)*INTEGER(1-((WaveForm-3)*2)); // Sawtooth Up/Down
    5:OscValue:=WhiteNoiseRandom; // White Noise
    ELSE OscValue:=0; // Nothing ;-)
   END;
   OscValue:=(OscValue*SynthData.RealtimeData.Volume[Channel]*SynthData.RealtimeData.VolumeValue[Channel]*SynthData.RealtimeData.ADSRValue[Channel])+AddDenormalNoise;
   WITH SynthData.RealtimeData.Filter[Channel] DO BEGIN
    FD1:=(FD1+CLP*(OscValue-FD1+FBLP*(FD1-FD2)))+AddDenormalNoise;
    FD2:=(FD2+CLP*(FD1-FD2))+AddDenormalNoise;
    FD3:=(FD3+CHP*(FD2-FD3+FBHP*(FD3-FD4)))+AddDenormalNoise;
    FD4:=(FD4+CHP*(FD3-FD4))+AddDenormalNoise;
    OscValue:=FD2-FD4;
   END;
   CASE SynthData.RealtimeData.ADSRMode[Channel] OF
    0:BEGIN
    END;
    1:BEGIN
     IF SynthData.RealtimeData.ADSRValue[Channel]<1 THEN BEGIN
      SynthData.RealtimeData.ADSRValue[Channel]:=SynthData.RealtimeData.ADSRValue[Channel]+SynthData.ADSRData.AttackStep[Channel];
     END ELSE BEGIN
      SynthData.RealtimeData.ADSRValue[Channel]:=1;
      SynthData.RealtimeData.ADSRMode[Channel]:=2;
     END;
    END;
    2:BEGIN
     IF SynthData.RealtimeData.ADSRValue[Channel]>SynthData.ADSRData.DestDecay[Channel] THEN BEGIN
      SynthData.RealtimeData.ADSRValue[Channel]:=SynthData.RealtimeData.ADSRValue[Channel]+SynthData.ADSRData.DecayStep[Channel];
     END ELSE BEGIN
      SynthData.RealtimeData.ADSRValue[Channel]:=SynthData.ADSRData.DestDecay[Channel];
      IF SynthData.ADSRData.Sustain[Channel] THEN BEGIN
       SynthData.RealtimeData.ADSRMode[Channel]:=3;
      END ELSE BEGIN
       SynthData.RealtimeData.ADSRMode[Channel]:=4;
      END;
     END;
    END;
    3:BEGIN
    END;
    4:BEGIN
     IF SynthData.RealtimeData.ADSRValue[Channel]>0 THEN BEGIN
      SynthData.RealtimeData.ADSRValue[Channel]:=SynthData.RealtimeData.ADSRValue[Channel]+SynthData.ADSRData.ReleaseStep[Channel];
     END ELSE BEGIN
      SynthData.RealtimeData.ADSRMode[Channel]:=0;
     END;
    END;
   END;
   SynthData.RealtimeData.ADSRValue[Channel]:=SynthData.RealtimeData.ADSRValue[Channel]+AddDenormalNoise;
   IF SynthData.RealtimeData.ADSRValue[Channel]<0 THEN SynthData.RealtimeData.ADSRValue[Channel]:=0;
   IF SynthData.RealtimeData.ADSRValue[Channel]>1 THEN SynthData.RealtimeData.ADSRValue[Channel]:=1;
   IF (SynthData.FileData.Link[Channel] AND $7F)<Max4kChannels THEN BEGIN
    IF SynthData.RealtimeData.LinkFirst[SynthData.FileData.Link[Channel] AND $7F] THEN BEGIN
     SynthData.RealtimeData.LinkValues[SynthData.FileData.Link[Channel] AND $7F]:=0;
     SynthData.RealtimeData.LinkFirst[SynthData.FileData.Link[Channel] AND $7F]:=FALSE;
    END;
    SynthData.RealtimeData.LinkValues[SynthData.FileData.Link[Channel] AND $7F]:=SynthData.RealtimeData.LinkValues[SynthData.FileData.Link[Channel] AND $7F]+(OscValue*SynthData.FileData.LinkFactor[Channel]);
   END;
   IF (SynthData.FileData.Link[Channel] AND $80)<>0 THEN BEGIN
    Value:=OscValue*SynthData.FileData.OutFactor[Channel];
    PanningValue:=(SynthData.FileData.Panning[Channel]*Div255)+AddDenormalNoise;;
{$IFDEF tracker}
    SynthData.Oscil[Channel,StartPosition+Position]:=Value;
{$ENDIF}
    OscValue:=Value*PanningValue;
    SynthData.RealtimeData.ChannelLastLeft[Channel]:=OscValue+AddDenormalNoise;
    Left:=Left+OscValue;
    OscValue:=Value*(1-PanningValue);
    SynthData.RealtimeData.ChannelLastRight[Channel]:=OscValue+AddDenormalNoise;
    Right:=Right+OscValue;
{$IFDEF tracker}
   END ELSE BEGIN
    SynthData.Oscil[Channel,StartPosition+Position]:=0;
{$ENDIF}
   END;
   SynthData.RealtimeData.Phase[Channel]:=FRAC(SynthData.RealtimeData.Phase[Channel]+SynthData.RealtimeData.PhaseIncrement[Channel]);
   SynthData.RealtimeData.PhaseIncrement[Channel]:=(SynthData.RealtimeData.PhaseIncrement[Channel]*SynthData.RealtimeData.PhaseFactor[Channel])+AddDenormalNoise;
   SynthData.RealtimeData.Volume[Channel]:=(SynthData.RealtimeData.Volume[Channel]*SynthData.RealtimeData.VolumeFactor[Channel])+AddDenormalNoise;
  END;
  IF SynthData.RealtimeData.CurrentBPMSamples=0 THEN BEGIN
   IF Count=0 THEN SynthReinit(SynthData);
   SynthData.RealtimeData.CurrentBPMSamples:=SynthData.RealtimeData.BPMSamples;
  END;
  DEC(SynthData.RealtimeData.CurrentBPMSamples);
  Left:=Left+SynthData.RealtimeData.LastLeft;
  SynthData.RealtimeData.LastLeft:=(SynthData.RealtimeData.LastLeft*0.9)+AddDenormalNoise;
  Right:=Right+SynthData.RealtimeData.LastRight;
  SynthData.RealtimeData.LastRight:=(SynthData.RealtimeData.LastRight*0.9)+AddDenormalNoise;
  Buffer^:=Left*Div8;
  INC(Buffer);
  Buffer^:=Right*Div8;
  INC(Buffer);
 END;
END;

PROCEDURE SynthLoad(VAR SynthData:T4kSynthData;TrackData:POINTER;TrackSize:INTEGER); STDCALL; {$IFDEF FPC}[public, alias : 'SynthLoad'];{$ENDIF}
BEGIN
 FILLCHAR(SynthData,SIZEOF(T4kSynthData),#0);
 MOVE(TrackData^,SynthData,TrackSize);
 SynthInitData(SynthData);
END;

FUNCTION SynthGetName(VAR Data):STRING; STDCALL; {$IFDEF FPC}[public, alias : 'SynthGetName'];{$ENDIF}
VAR SynthData:P4kSynthData;
    InfoData:P4kInfoData;
    B:PBYTE;
BEGIN
 SynthData:=@Data;
 InfoData:=POINTER(LONGWORD(LONGWORD(@Data)+SynthData^.InfoData));
 IF InfoData^.NameLength>0 THEN BEGIN
  B:=POINTER(LONGWORD(LONGWORD(InfoData)+SIZEOF(T4kInfoData)));
  SETLENGTH(RESULT,InfoData^.NameLength);
  MOVE(B^,RESULT[1],InfoData^.NameLength);
 END ELSE BEGIN
  RESULT:='';
 END;
END;

FUNCTION SynthGetAuthor(VAR Data):STRING; STDCALL; {$IFDEF FPC}[public, alias : 'SynthGetAuthor'];{$ENDIF}
VAR SynthData:P4kSynthData;
    InfoData:P4kInfoData;
    B:PBYTE;
BEGIN
 SynthData:=@Data;
 InfoData:=POINTER(LONGWORD(LONGWORD(@Data)+SynthData^.InfoData));
 IF InfoData^.AuthorLength>0 THEN BEGIN
  B:=POINTER(LONGWORD(LONGWORD(InfoData)+SIZEOF(T4kInfoData))+InfoData^.NameLength);
  SETLENGTH(RESULT,InfoData^.AuthorLength);
  MOVE(B^,RESULT[1],InfoData^.AuthorLength);
 END ELSE BEGIN
  RESULT:='';
 END;
END;

FUNCTION SynthGetMessage(VAR Data):STRING; STDCALL; {$IFDEF FPC}[public, alias : 'SynthGetMessage'];{$ENDIF}
VAR SynthData:P4kSynthData;
    InfoData:P4kInfoData;
    B:PBYTE;
BEGIN
 SynthData:=@Data;
 InfoData:=POINTER(LONGWORD(LONGWORD(@Data)+SynthData^.InfoData));
 IF InfoData^.MessageLength>0 THEN BEGIN
  B:=POINTER(LONGWORD(LONGWORD(InfoData)+SIZEOF(T4kInfoData))+InfoData^.NameLength+InfoData^.AuthorLength);
  SETLENGTH(RESULT,InfoData^.MessageLength);
  MOVE(B^,RESULT[1],InfoData^.MessageLength);
 END ELSE BEGIN
  RESULT:='';
 END;
END;

PROCEDURE SynthInit(VAR SynthData:T4kSynthData;TrackData:POINTER;TrackSize:INTEGER); STDCALL; {$IFDEF FPC}[public, alias : 'SynthInit'];{$ENDIF}
CONST cwChop:WORD=$F7B;
VAR I:INTEGER;
BEGIN
 ASM
  FLDCW cwChop
 END;
 SynthLoad(SynthData,TrackData,TrackSize);

 WhiteNoiseSeed:=$12345678;

 waveOutOpen(@WaveOutHandle,WAVE_MAPPER,@WaveFormat,0,0,0);

 FOR I:=0 TO 3 DO BEGIN
  WaveHandler[I].dwFlags:=WHDR_DONE;
  WaveHandler[I].lpData:=@Buffers[I];
  WaveHandler[I].dwBufferLength:=BufferSize*8;
 END;

 BufferCounter:=0;
END;

PROCEDURE SynthPoll(VAR SynthData:T4kSynthData); STDCALL; {$IFDEF FPC}[public, alias : 'SynthPoll'];{$ENDIF}
BEGIN
 IF (WaveHandler[BufferCounter].dwFlags AND WHDR_DONE)<>0 THEN BEGIN
  IF waveOutUnprepareHeader(WaveOutHandle,@WaveHandler[BufferCounter],SIZEOF(TWAVEHDR))<>WAVERR_STILLPLAYING THEN BEGIN
   WaveHandler[BufferCounter].dwFlags:=WaveHandler[BufferCounter].dwFlags AND NOT WHDR_DONE;
  SynthFillBuffer(SynthData,@WaveHandler[BufferCounter].lpData^,0,BufferSize);
   waveOutPrepareHeader(WaveOutHandle,@WaveHandler[BufferCounter],SIZEOF(TWAVEHDR));
   waveOutWrite(WaveOutHandle,@WaveHandler[BufferCounter],SIZEOF(TWAVEHDR));
   BufferCounter:=(BufferCounter+1) MOD 4;
  END;
 END;
END;

PROCEDURE SynthDone(VAR SynthData:T4kSynthData); STDCALL; {$IFDEF FPC}[public, alias : 'SynthDone'];{$ENDIF}
{$IFDEF CLEAN}
VAR I:INTEGER;
{$ENDIF}
BEGIN
{$IFDEF CLEAN}
 FOR I:=0 TO 3 DO BEGIN
  WHILE waveOutUnprepareHeader(WaveOutHandle,@WaveHandler[I],SIZEOF(TWAVEHDR))=WAVERR_STILLPLAYING DO BEGIN
  END;
 END;
 waveOutReset(WaveOutHandle);
 waveOutClose(WaveOutHandle);
{$ENDIF}
END;

END.
