unit _FMP;

interface

uses
  Windows, SysUtils, Classes, _WinFMP_Moonlight, _SndDrv_const, _Vis_const;

type
  TInfomation=record
    FileSize:integer;
    Comment:array[0..2] of string;
    PCMMode,PCMFilename:array[0..2] of string;
    PCMLoadFlag:array[0..2] of boolean;
  end;

type
  TFMP = class
  private
    { Private 宣言 }
    MemSync:TMultiReadExclusiveWriteSynchronizer;
    MesureClock:integer;
    FMPWork:PWorks;
    MCh:array[0..15] of TMCh;
    function VolumeNormlize(Ch,Volume,VolTemp:integer):integer;
    function GetChPart(Ch:integer):TCPATS;
    function GetParamStr0(Ch:integer):string;
    function GetParamStr1(Ch:integer):string;
    function GetTotalms:integer;
    function GetTempo:integer;
    function GetLoopCount:integer;
  public
    { Public 宣言 }
    RhythmSampleLoaded:boolean;
    Infomation:TInfomation;
    TransTempo:integer;
    function  GetPVIFilename(FMPFilename:string):string;
    function  GetPPZFilename(FMPFilename:string):string;
    function  GetTitle(Filename:string):string;
    function  GetTitleBuf(buf:PByteArray;bufcount:integer;Filename:string):string;
    function  GetTitleInESC(Filename:string):string;
    function  GetTitleInESCBuf(buf:PByteArray;bufcount:integer;Filename:string):string;
    function  Init(PCMRate:integer):boolean;
    function  ChangeRate(rate:integer):boolean;
    procedure RefrectionSetting(PlayOnlyFlag:boolean);
    procedure FreeDLL;
    procedure GetPCMData(var buf:SmallInt;len:integer);
    procedure SetPCMLoadPath;
    procedure LoadRhythm;
    function  FMPPlay:boolean;
    procedure FMPStop;
    procedure StartFadeout;
    procedure MMLSeek(SeekClock:integer);
    procedure MMLTrap;
    procedure SetMute(ch:integer;flag:boolean);
    procedure GetVisual(var pSetVisual:pVisual);
    procedure GetVisualSystem(var pSetVisual:pVisual);
    function  GetTotalClock:integer;
    function  GetMesureClock:integer;
    function  GetDispClock:integer;
    function  GetCopyright:string;
    procedure SetTransTempo(_TransTempo:integer);
    procedure LoadInfomation;
  end;

implementation

uses MainWin,
     _MDXWinINI,_m_Tools,_Vis,_const;

procedure writelog(x:string);
var
  Filename:string;
  T:TextFile;
begin
  Filename:='_fmp.log';
  AssignFile(T,Filename);
  if FileExists(Filename)=False then
    Rewrite(T)
    else
    Append(T);
  Writeln(T,inttostr(gettickcount)+x);
  Flush(T);
  CloseFile(T);
end;

// ---------------------
// ThreadFree
// ---------------------

function TFMP.GetPVIFilename(FMPFilename:string):string;
var
  tmp:array[0..13] of char;
  cnt:integer;
begin
  tmp[0]:=#00;
  fmp_fgetdefinedpcmfilename(tmp,pchar(FMPFilename));

  cnt:=0;
  result:='';
  while (tmp[cnt]<>#00) do begin
    result:=result+tmp[cnt];
    inc(cnt);
  end;
end;

function TFMP.GetPPZFilename(FMPFilename:string):string;
var
  tmp:array[0..13] of char;
  cnt:integer;
begin
  tmp[0]:=#00;
  fmp_fgetdefinedppzfilename(tmp,pchar(FMPFilename),0);

  cnt:=0;
  result:='';
  while (tmp[cnt]<>#00) do begin
    result:=result+tmp[cnt];
    inc(cnt);
  end;
end;

function TFMP.GetTitle(Filename:string):string;
var
  fs:TFileStream;
  bufcount:integer;
  buf:PByteArray;
begin
  if FileExists(Filename)=False then begin
    Result:='';
    exit;
  end;

  fs:=TFileStream.Create(Filename,fmOpenRead or fmShareDenyWrite);
  bufcount:=fs.Size;
  GetMem(buf,BufCount);
  fs.ReadBuffer(buf[0],bufcount);
  fs.Free;
  Result:=GetTitleBuf(buf,bufcount,Filename);
  FreeMem(buf);
end;

function TFMP.GetTitleBuf(buf:PByteArray;bufcount:integer;Filename:string):string;
var
  MemoMsg0:array[0..1024] of Char;
  MemoMsg1:array[0..1024] of Char;
  MemoMsg2:array[0..1024] of Char;
  PMemoMsg:TComment;
  cnt:integer;
begin
  for cnt:=0 to 82 do begin
    MemoMsg0[cnt]:=char(0);
    MemoMsg1[cnt]:=char(0);
    MemoMsg2[cnt]:=char(0);
  end;

  PMemoMsg[0]:=MemoMsg0;
  PMemoMsg[1]:=MemoMsg1;
  PMemoMsg[2]:=MemoMsg2;
  fmp_getcomment3(PMemoMsg,buf,bufcount);

  Result:=PMemoMsg[0]+'|'+PMemoMsg[1]+'|'+PMemoMsg[2];
  if (trim(Result)='||') or (trim(Result)='') then Result:='['+ExtractFilename(Filename)+']';
end;

function TFMP.GetTitleInESC(Filename:string):string;
var
  fs:TFileStream;
  bufcount:integer;
  buf:PByteArray;
begin
  if FileExists(Filename)=False then begin
    Result:='';
    exit;
  end;

  fs:=TFileStream.Create(Filename,fmOpenRead or fmShareDenyWrite);
  bufcount:=fs.Size;
  GetMem(buf,BufCount);
  fs.ReadBuffer(buf[0],bufcount);
  fs.Free;
  Result:=GetTitleInESCBuf(buf,bufcount,Filename);
  FreeMem(buf);
end;

function TFMP.GetTitleInESCBuf(buf:PByteArray;bufcount:integer;Filename:string):string;
var
  MemoMsg:array[0..8192] of Char;
  PMemoMsg:PChar;
  cnt,lin:integer;
  testchar:char;
  isWideChar:boolean;
begin
  for cnt:=0 to 8192 do begin
    MemoMsg[cnt]:=char(0);
  end;
  PMemoMsg:=MemoMsg;
  fmp_getcomment2(PMemoMsg,buf,bufcount);

  Result:='';
  cnt:=0;
  lin:=0;
  isWideChar:=True;

  while ((PMemoMsg[cnt]<>char($00)) and (lin<=2)) do begin
    testchar:=PMemoMsg[cnt];
    if isWideChar=True then begin
      isWideChar:=False;
      end else begin
      if isAnkChar(byte(testchar))=False then begin
        isWideChar:=True;
        end else begin
        if testchar=chr(13) then begin
          inc(lin);
          if lin<=2 then testchar:='|';
        end;
        if testchar=chr(10) then testchar:=chr($00);
      end;
    end;
    if testchar<>char($00) then begin
      Result:=Result+testchar;
    end;
    inc(cnt);
  end;

  if (trim(Result)='||') or (trim(Result)='') then Result:='['+ExtractFilename(Filename)+']';
end;

// --------------------
// private
// --------------------

function TFMP.VolumeNormlize(Ch,Volume,VolTemp:integer):integer;
begin
  Result:=0;
  if VolTemp<>0 then Volume:=VolTemp;
  if (ch<=2) or (ch=8) or (ch=9) or (ch=10) then Result:=Volume*4; // FM
  if (ch=7) then Result:=Volume*2; // Drum
  if (ch=6) or (Ch>=11) then Result:=Volume; // ADPCM
  if (ch=3) or (ch=4) or (ch=5) then begin // SSG
    if VolTemp=0 then
      Result:=Volume*8
      else
      Result:=Volume div 2;
  end;
end;

function TFMP.GetChPart(Ch:integer):TCPATS;
begin
  case Ch of
    0: Result:=FMPWork._F[0].CPatS; // FM1
    1: Result:=FMPWork._F[1].CPatS; // FM2
    2: Result:=FMPWork._F[2].CPatS; // FM3
    3: Result:=FMPWork._S[0].CPatS; // SSG1
    4: Result:=FMPWork._S[1].CPatS; // SSG2
    5: Result:=FMPWork._S[2].CPatS; // SSG3
    6: Result:=FMPWork._P[0].CPatS; // PPZ81
    7: Result:=FMPWork._A.CPatS; // ADPCM
    8: Result:=FMPWork._F[3].CPatS; // FM4
    9: Result:=FMPWork._F[4].CPatS; // FM5
    10:Result:=FMPWork._F[5].CPatS; // FM6
    11:Result:=FMPWork._P[1].CPatS; // PPZ82
    12:Result:=FMPWork._P[2].CPatS; // PPZ83
    13:Result:=FMPWork._P[3].CPatS; // PPZ84
    14:Result:=FMPWork._P[4].CPatS; // PPZ85
    15:Result:=FMPWork._P[5].CPatS; // PPZ86
    else Result:=FMPWork._F[0].CPatS;
  end;
end;

function TFMP.GetParamStr0(Ch:integer):string;
var
  CPATS:TCPATS;
  cmd:PByteArray;
  cnt:integer;
  Keyno:integer;
begin
  if Ch>=8 then begin
    Result:='';
    exit;
  end;

  CPATS:=GetChPart(Ch);
  with CPATS do begin
    Keyno:=PartSbefore;
    if Keyno=97 then Keyno:=0;
    Keyno:=keyno-3;
    if Keyno<0 then Keyno:=0;
    Result:=format('K%3d D:%4d   Pitch:%4d Data:',[Keyno,PartSdetune,PartSpitch.PitSdat]);
    if integer(PartSpoint)=0 then begin
      Result:=Result+'0000:000000';
      end else begin
      cmd:=PartSpoint;
      for cnt:=0 to 4 do begin
        Result:=Result+IntToHex(cmd[cnt],2);
        if cnt=1 then Result:=Result+':';
      end;
    end;
  end;
end;

function TFMP.GetParamStr1(Ch:integer):string;
var
  CPATS:TCPATS;
begin
  if Ch>=8 then begin
    Result:='';
    exit;
  end;

  CPATS:=GetChPart(Ch);
  with CPATS do begin
    Result:=format('@%3d v%3d-%3d Freq:%5d-%5d P:%s',[PartStone,PartSvol,VolumeNormlize(Ch,CPATS.PartSvol,CPATS.PartStmpvol),PartSwave2,PartSwave,IntToHex(integer(PartSpoint),8)]);
  end;
end;

function TFMP.GetTotalms:integer;
begin
  Result:=fmp_getpos;
end;

function TFMP.GetTempo:integer;
begin
  Result:=FMPWork.MusicClockCnt;
end;

function TFMP.GetLoopCount:integer;
begin
  Result:=fmp_getloopcount;
end;

// --------------------
// public
// --------------------

function TFMP.Init(PCMRate:integer):boolean;
begin
  MemSync:=TMultiReadExclusiveWriteSynchronizer.Create;

  MemSync.BeginWrite;

  if FMP_LoadDLL=False then begin
    Result:=False;
    end else begin
    Result:=True;
    RhythmSampleLoaded:=False;
    fmp_init(nil);
    fmp_setpcmrate(PCMRate);
    RefrectionSetting(False);
    FMPWork:=fmp_getworks;
  end;

  TransTempo:=0;

  MemSync.EndWrite;
end;

function TFMP.ChangeRate(rate:integer):boolean;
begin
  MemSync.BeginWrite;
  fmp_setpcmrate(rate);
  MemSync.EndWrite;
  Result:=True;
end;

procedure TFMP.RefrectionSetting(PlayOnlyFlag:boolean);
begin
  MemSync.BeginWrite;

  if FMPMDINI.LoadInitADPCM=True then fmp_init(nil);

  if PlayOnlyFlag=False then begin
    SetPCMLoadPath;
    LoadRhythm;
    fmp_setfmcalc55k(FMPMDINI.FMCalc55k);
    fmp_setfmcalc55k(False);
  end;

  with FMPMDINI do begin
    fmp_setppzinterpolation(Interpolation);

    fmp_setadpcmppz8emulate(ADPCMPPZ8Emulate);

    fmp_setfmvoldown(VolFM);
    fmp_setssgvoldown(VolSSG);
    fmp_setrhythmvoldown(VolRhythm);
    fmp_setadpcmvoldown(VolADPCM);
    fmp_setppzvoldown(VolPPZ);
  end;

  FMPWork:=fmp_getworks;

  MemSync.EndWrite;
end;

procedure TFMP.FreeDLL;
begin
  MemSync.BeginWrite;
  FMP_FreeDLL;
  MemSync.EndWrite;

  MemSync.Free;
end;

procedure TFMP.GetPCMData(var buf:SmallInt;len:integer);
begin
  SetTransTempo(TransTempo);
  MemSync.BeginWrite;
  fmp_getpcmdata(addr(buf),len);
  MemSync.EndWrite;
end;

procedure TFMP.SetPCMLoadPath;
var
  setcnt:integer;
  cnt:integer;
  PPCMPath:array[0..PCMFMPPMDPathCount+3] of PChar;
  PCMPath:array[0..PCMFMPPMDPathCount+3] of string;
begin
  MemSync.BeginWrite;

  setcnt:=0;
  if Main.gFileInfo.isNetworkMode=True then begin
    PCMPath[setcnt]:=Main.Network.CFG.FMPPMD_PCMPath+chr(0);
    PPCMPath[setcnt]:=PChar(PCMPath[setcnt]);
    inc(setcnt);
  end;
  for cnt:=0 to PCMFMPPMDPathCount-1 do begin
    PCMPath[setcnt]:=FMPMDINI.PCMFMPPMDPath[cnt]+chr(0);
    PPCMPath[setcnt]:=PChar(PCMPath[setcnt]);
    inc(setcnt);
  end;
  PCMPath[setcnt]:=chr(0);
  PPCMPath[setcnt]:=PChar(PCMPath[setcnt]);
  fmp_setpcmdir(@PPCMPath[0]);

  MemSync.EndWrite;
end;

procedure TFMP.LoadRhythm;
begin
  MemSync.BeginWrite;

  if fmp_loadrhythmsample(PChar(FMPMDINI.PCMYM2608Path)) then
    RhythmSampleLoaded:=True
    else
    RhythmSampleLoaded:=False;

  MemSync.EndWrite;
end;

function TFMP.FMPPlay:boolean;
var
  Res:integer;
  LenClock,LoopClock:integer;
  cont:boolean;
begin
  MemSync.BeginWrite;

  RefrectionSetting(True);

  LenClock:=0;
  LoopClock:=0;
  if fmp_getlength(PChar(Main.gFileInfo.GetPlayPath),LenClock,LoopClock)=False then begin
    Main.InfomationVisible('FMPerr:曲長の取得に失敗しました。');
    Result:=False;
    end else begin
    MesureClock:=LenClock+LoopClock;

    SetPCMLoadPath;
    Res:=fmp_load(PChar(Main.gFileInfo.GetPlayPath));
    if Res=WINFMP_OK then begin
      cont:=True;
      end else begin
      case Res of
        FMP_ERR_OPEN_MUSIC_FILE: begin
          Main.InfomationVisible('FMPerr:ファイルを開けません');
          cont:=False;
        end;
        FMP_ERR_WRONG_MUSIC_FILE: begin
          Main.InfomationVisible('FMPerr:FMP以外の形式のファイルです');
          cont:=False;
        end;
        FMP_ERR_OPEN_PVI_FILE: begin
          Main.InfomationVisible('FMPerr:PVIを読み込めませんでした');
          cont:=True;
        end;
        FMP_ERR_OPEN_PPZ1_FILE: begin
          Main.InfomationVisible('FMPerr:PPZを読み込めませんでした');
          cont:=True;
        end;
        FMP_ERR_WRONG_PVI_FILE: begin
          Main.InfomationVisible('FMPerr:PVIファイルが異常です');
          cont:=True;
        end;
        FMP_ERR_WRONG_PPZ1_FILE: begin
          Main.InfomationVisible('FMPerr:PPZファイルが異常です');
          cont:=True;
        end;
        FMP_WARNING_PVI_ALREADY_LOAD: begin
//          Main.InfomationVisible('FMPerr:PVIはすでに読み込まれています');
          cont:=True;
        end;
        FMP_WARNING_PPZ1_ALREADY_LOAD: begin
//          Main.InfomationVisible('FMPerr:PVIはすでに読み込まれています');
          cont:=True;
        end;
        FMP_ERR_OUT_OF_MEMORY: begin
          Main.InfomationVisible('FMPerr:メモリが足りません');
          cont:=False;
        end;
        else begin
          Main.InfomationVisible('FMDerr('+inttostr(res)+'):未定義（メールお願いします）');
          cont:=False;
        end;
      end;
    end;

    if cont=False then begin
      Result:=False;
      end else begin
      Result:=True;
      LoadInfomation;
      fmp_start();
      FMPWork:=fmp_getworks;
    end;
  end;

  MemSync.EndWrite;
end;

procedure TFMP.FMPStop;
begin
  MemSync.BeginWrite;
  fmp_stop();
  MemSync.EndWrite;
end;

procedure TFMP.StartFadeout;
begin
  MemSync.BeginWrite;
  if sdFadeout=False then begin
    sdFadeout:=True;
    fmp_fadeout2(5000);
  end;
  MemSync.EndWrite;
end;

procedure TFMP.MMLSeek(SeekClock:integer);
begin
  MemSync.BeginWrite;
  fmp_setpos(SeekClock);
  MemSync.EndWrite;
end;

procedure TFMP.MMLTrap;
begin
  if sdEnd=True then begin
    MemSync.BeginWrite;
    sdLoop:=0;
    sdFadeout:=False;
    sdEnd:=True;
    MemSync.EndWrite;
    exit;
  end;

  MemSync.BeginWrite;
  FMPWork:=fmp_getworks;
  sdLoop:=fmp_getloopcount;
  if FMPWork.play_flg=0 then begin
    sdEnd:=True;
    end else begin
    sdEnd:=False;
  end;
  MemSync.EndWrite;
end;

procedure TFMP.SetMute(ch:integer;flag:boolean);
var
  Mode:integer;
begin
  case ch of
    0: Mode:=0; // FM1
    1: Mode:=1; // FM2
    2: Mode:=2; // FM3
    3: Mode:=18; // SSG1
    4: Mode:=19; // SSG2
    5: Mode:=20; // SSG3
    6: Mode:=-1; // PPZ81
    7: Mode:=21; // ADPCM
    8: Mode:=3; // FM4
    9: Mode:=4; // FM5
    10:Mode:=5; // FM6
    11:Mode:=-1; // PPZ82
    12:Mode:=-1; // PPZ83
    13:Mode:=-1; // PPZ84
    14:Mode:=-1; // PPZ85
    15:Mode:=-1; // PPZ86
    else Mode:=-1;
  end;

  if flag=True then begin
    fmp_maskon(False,Mode);
    end else begin
    fmp_maskoff(False,Mode);
  end;
end;

procedure TFMP.GetVisual(var pSetVisual:pVisual);
var
  CPATS:TCPATS;
  Ch:integer;
  tNoteNo,tVoiceNo:integer;
  function GetNowPanpot(ch:integer):integer;
  begin
    if (ch=3) or (ch=4) or (ch=5) or (ch=7) then begin // SSG and Drum
      Result:=3;
      end else begin
      case(CPATS.PartSpan and $c0) of
        $80: Result:=1; // L
        $40: Result:=2; // R
        $c0: Result:=3; // Mid
        else Result:=0; // Disable
      end;
    end;
  end;
begin
  MemSync.BeginRead;

  for Ch:=0 to 15 do begin
    CPATS:=GetChPart(Ch);
    with pSetVisual.Channel[ch] do begin
      mes0:=GetParamStr0(Ch);
      mes1:=GetParamStr1(Ch);
    end;
    if CPATS.PartSkeyon=0 then begin // noteoff
      MCh[Ch].NoteonFlag:=False;
      Vis.SetAnalyzeNoteoff(Ch);
      end else begin
      tNoteNo:=CPATS.PartSbefore-3;
      if tNoteNo<0 then tNoteNo:=0;
      tVoiceNo:=CPATS.PartStone;
      pSetVisual.NoteKeyno[ch]:=(tNoteNo*64)+(CPATS.PartSdetune+CPATS.PartSpitch.PitSdat)*4;
      if (MCh[Ch].NoteonFlag=False) or (MCh[Ch].Noteno<>tNoteNo) or (MCh[Ch].Voiceno<>tVoiceNo) then begin // noteon
        MCh[Ch].NoteonFlag:=True;
        MCh[Ch].Noteno:=tNoteNo;
        MCh[Ch].Voiceno:=tVoiceNo;
        Vis.SetAnalyzeVolume(Ch,byte(VolumeNormlize(Ch,CPATS.PartSvol,CPATS.PartStmpvol)));

        with PSetVisual.Channel[Ch] do begin
          Panpot:=GetNowPanpot(Ch);
          Voice:=tVoiceNo;
          Keycode:=tNoteNo;
        end;
      end;
    end;
  end;

  MemSync.EndRead;
end;

procedure TFMP.GetVisualSystem(var pSetVisual:pVisual);
begin
  MemSync.BeginRead;

  pSetVisual.PlayTime:=GetTotalms;
  pSetVisual.TotalClock:=GetTotalClock;
  pSetVisual.MesureClock:=GetMesureClock;
  pSetVisual.DispClock:=GetDispClock;
  pSetVisual.Tempo:=GetTempo;
  pSetVisual.LoopCount:=GetLoopCount;

  MemSync.EndRead;
end;

function TFMP.GetTotalClock:integer;
begin
  MemSync.BeginRead;
  Result:=fmp_getpos;
  MemSync.EndRead;
end;

function TFMP.GetMesureClock:integer;
begin
  MemSync.BeginRead;
  Result:=MesureClock;
  MemSync.EndRead;
end;

function TFMP.GetDispClock:integer;
begin
  MemSync.BeginRead;
  Result:=fmp_getpos2;
  MemSync.EndRead;
end;

function TFMP.GetCopyright:string;
begin
  MemSync.BeginRead;

  Result:=FMP_GetCopyright;
  if RhythmSampleLoaded=True then
    Result:=Result+'YM2608 RhythmSample loaded.'+CRLF
    else
    Result:=Result+'YM2608 RhythmSample not found.'+CRLF;

  MemSync.EndRead;
end;

procedure TFMP.LoadInfomation;
var
  MemoMsg:array[0..2,0..82] of Char;
  PComment:TComment;
  CFilename:array[0..13] of Char;
  PFilename:PChar;
  cnt:integer;
begin
  if FileExists(Main.gFileInfo.GetPlayPath)=False then exit;

  MemSync.BeginWrite;

  Infomation.FileSize:=GetFileSize(Main.gFileInfo.GetPlayPath);

  for cnt:=0 to 82 do begin
    MemoMsg[0][cnt]:=char(0);
    MemoMsg[1][cnt]:=char(0);
    MemoMsg[2][cnt]:=char(0);
  end;
  for cnt:=0 to 2 do
    PComment[cnt]:=PChar(@MemoMsg[cnt]);

  fmp_fgetcomment3(PComment,pchar(Main.gFileInfo.GetPlayPath));

  for cnt:=0 to 2 do
    Infomation.Comment[cnt]:=PComment[cnt];

  for cnt:=0 to 13 do
    CFilename[cnt]:=char(0);
  PFilename:=PChar(@CFilename);

  for cnt:=0 to 2 do begin
    case cnt of
      0: begin
        Infomation.PCMMode[0]:='PVI';
        fmp_getdefinedpcmfilename(PFilename,nil,0);
      end;
      1: begin
        Infomation.PCMMode[1]:='PPZ1';
        fmp_getdefinedppzfilename(PFilename,nil,0,0);
      end;
      2: begin
        Infomation.PCMMode[2]:='PPZ2';
        fmp_getdefinedppzfilename(PFilename,nil,0,1);
      end;
    end;
    Infomation.PCMFilename[cnt]:=PFilename;
    case cnt of
      0: fmp_getpcmfilename(PFilename);
      1: fmp_getppzfilename(PFilename,0);
      2: fmp_getppzfilename(PFilename,1);
    end;
    if ExtractFilename(Infomation.PCMFilename[cnt])=ExtractFilename(PFilename) then
      Infomation.PCMLoadFlag[cnt]:=True
      else
      Infomation.PCMLoadFlag[cnt]:=False;
  end;

  MemSync.EndWrite;
end;

procedure TFMP.SetTransTempo(_TransTempo:integer);
var
  t:integer;
begin
  if FMPWork=nil then exit;

  MemSync.BeginWrite;
  TransTempo:=_TransTempo;
  t:=FMPWork.ExtBuff.FmpStempo_t+TransTempo;
  if t<$00 then t:=$00;
  if t>$ff then t:=$ff;
  FMPWork.ExtBuff.FmpStempo:=t;
  MemSync.EndWrite;
end;

end.

