unit _Vis;

interface

uses
  Windows, SysUtils, Classes, Graphics, ExtCtrls, MMSystem,
  _PicTools,_Vis_const;

const GradAlphaCount=16;

type
  TAlphaArray=array[0..256] of TRGB;
  PAlphaArray=^TAlphaArray;

type
  PSmallIntArray = ^TSmallIntArray;
  TSmallIntArray = array[0..65536*10] of SmallInt;

type
  TKeyboardType=record
    x:integer;
    Sharp:boolean;
    FlatLeft,FlatRight:boolean;
  end;

type
  TColorCache=record
    Enabled:boolean;
    Bar0Arr,Bar1Arr:array[0..128] of TRGB;
    SpeScrAlpha:TAlphaArray;
    KeyFlatColor1,KeySharpColor1:array[0..GradAlphaCount] of TRGB;
    KeyFlatColor9,KeySharpColor9:array[0..GradAlphaCount] of TRGB;
  end;

type
  TNoteAna=record
    NoteNo: array[0..GradAlphaCount] of LongInt;
  end;
  TVis = class
  private
    { Private 宣言 }
    MemSync:TMultiReadExclusiveWriteSynchronizer;
    ClearFlag:boolean;
    VisualCount:integer;
    Visual:array of TVisual;
    VisualTick:array of dword;
    PGetVisual:PVisual;
    SetVisualCount,GetVisualCount:integer;
    RewriteCountForSystem:LongInt;
    ColorCache:TColorCache;
    ColGradAlpha:array[0..GradAlphaCount] of byte;
    VelGraLastY,VelGraLastDelay: array[0..15] of integer;
    SpeAnaLastY,SpeAnaLastDelay: array[0..63] of integer;
    KeyAna:array[0..15] of TNoteAna;
    SonarTableCreated:boolean;
    SonarSintbl,SonarCostbl:array[0..192] of integer;
    SonarAlpha:array[0..7] of TAlphaArray;
    KeyboardType:array[0..11] of TKeyboardType;
    procedure SetVisualPointer(SetTick:dword;Clear:boolean);
    procedure CreateColorCache(Renew:boolean);
    procedure CalcImageProcess(DriverMode:integer;FFTFrame:integer;var buf:array of SmallInt;offset,len:integer);
    procedure CalcImageSystem(DriverMode:integer);
    procedure CalcImageSpeana(var buf:array of SmallInt;offset,len:integer);
    procedure CalcImageSpeanaWave(var buf:array of SmallInt;offset,len:integer);
    procedure CalcImageSpeanaBar;
    procedure CalcImageSpeanaScr;
    procedure CalcImageSpeanaLine;
    procedure CalcImageVelocity;
    procedure CalcImageVisualKeyboard;
    procedure CalcImageVisualSonar;
    procedure RewriteImageProcess;
    procedure RewriteImageSystem;
    procedure RewriteImageSpeana;
    procedure RewriteImageSpeanaWave;
    procedure RewriteImageSpeanaBar;
    procedure RewriteImageSpeanaScr;
    procedure RewriteImageSpeanaLine;
    procedure RewriteImageParam;
    procedure RewriteImageVelocity;
    procedure RewriteImageVisualKeyboard(ResetAllKeys:boolean);
//    procedure RewriteImageVisualMDXDisReload;
//    procedure RewriteImageVisualMDXDis;
    procedure RewriteImageVisualSonarCreateTable;
    procedure RewriteImageVisualSonar;
    procedure ResetNoteImage;
    procedure FillBlackNoteImage;
    procedure RefreshNoteImage;
    procedure ResetSpeImage;
    procedure FillBGSpeImage;
    procedure RefreshSpeImage;
    procedure ResetVelImage;
    procedure RefreshVelImage;
    procedure ResetParaImage;
    procedure RefreshParaImage;
  public
    { Public 宣言 }
    PSetVisual:PVisual;
{
  PSetVisualは、各音源ドライバインタフェースのMMLTrapから呼び出されるが、
  MMLTrapはCalcImageから呼び出されるのでスレッド衝突回避は必要ない。
  また、DX3Dからも呼び出されるが、RewriteImage->RewriteImageProcess->DDVisなので問題ない。はず。
}
    constructor Create;
    destructor Destroy; override;
    procedure Init(_VisualCount:integer);
    procedure FreeMemory;
    function  GetVisualPointer(Latems:dword):boolean;
    procedure CalcImage(tick:dword;DriverMode:integer;Pause:boolean;SystemOnly:boolean;FFTFrame:integer;var buf:array of SmallInt;offset,len:integer);
    procedure CalcImageFFTDataSetOnly(Pause:boolean;SystemOnly:boolean;var buf:array of SmallInt;offset,len:integer);
    procedure RewriteImage(SystemOnly:boolean);
    procedure ClearImage;
    procedure SetAnalyzeVolume(Ch:byte;Volume:Byte);
    procedure SetAnalyzeNoteoff(Ch:byte);
    function  GetKDDID:byte;
    function  GetPlayTime:dword;
    function  GetTotalClock:dword;
    function  GetDispClock:dword;
    function  GetMesureClock:dword;
    function  GetLoopCount:integer;
    function  GetTimerBCount:byte;
  end;

var
  Vis:TVis;
  
implementation

uses MainWin,KDDWin,
     _Images,_MDXWinINI, _SndDrv_const,_SndDrv, _MDXDis, dx3dRap,
     _CSkin,_WinPosConst;

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

constructor TVis.Create;
  procedure InitKeyboardType;
    procedure Reg(var KeyboardType:TKeyboardType;_x:integer;_Sharp:boolean;_FlatLeft,_FlatRight:boolean);
    begin
      with KeyboardType do begin
        x:=_x;
        Sharp:=_Sharp;
        FlatLeft:=_FlatLeft;
        FlatRight:=_FlatRight;
      end;
    end;
  begin
    Reg(KeyboardType[ 0], 0,False,True ,False); // c
    Reg(KeyboardType[ 1], 1,True ,False,False); // c+
    Reg(KeyboardType[ 2], 2,False,False,False); // d
    Reg(KeyboardType[ 3], 3,True ,False,False); // d+
    Reg(KeyboardType[ 4], 4,False,False,True ); // e
    Reg(KeyboardType[ 5], 6,False,True ,False); // f
    Reg(KeyboardType[ 6], 7,True ,False,False); // f+
    Reg(KeyboardType[ 7], 8,False,False,False); // g
    Reg(KeyboardType[ 8], 9,True ,False,False); // g+
    Reg(KeyboardType[ 9],10,False,False,False); // a
    Reg(KeyboardType[10],11,True ,False,False); // a+
    Reg(KeyboardType[11],12,False,False,True ); // b
  end;
begin
  inherited Create;

  MemSync:=TMultiReadExclusiveWriteSynchronizer.Create;

  MemSync.BeginWrite;
  InitKeyboardType;
  MemSync.EndWrite;
end;

destructor TVis.Destroy;
begin
  MemSync.Free;

  inherited Destroy;
end;

procedure TVis.Init(_VisualCount:integer);
var
  cnt:integer;
  procedure VisualInit;
  var
    cnt,ch:integer;
  begin
    SetLength(Visual,VisualCount+1);
    SetLength(VisualTick,VisualCount+1);

    SetVisualCount:=0;
    GetVisualCount:=1;
    PSetVisual:=@Visual[SetVisualCount];
    PGetVisual:=@Visual[GetVisualCount];

    RewriteCountForSystem:=0;
    ClearFlag:=False;

    for cnt:=0 to VisualCount do begin
      VisualTick[cnt]:=0;
      for Ch:=0 to 15 do begin
        Visual[cnt].NoteKeyno[ch]:=0;
      end;
    end;
  end;
begin
  MemSync.BeginWrite;

  VisualCount:=_VisualCount*8+1;
  VisualInit;

  for cnt:=0 to GradAlphaCount-2 do begin
    ColGradAlpha[cnt]:=$ff-((cnt*$a0) div (GradAlphaCount-1));
  end;
  ColGradAlpha[GradAlphaCount-1]:=$00;

  ColorCache.Enabled:=False;

  MemSync.EndWrite;

  ClearImage;
end;

procedure TVis.FreeMemory;
begin
  MemSync.BeginWrite;

  SetLength(Visual,0);
  SetVisualCount:=0;
  GetVisualCount:=0;
  PSetVisual:=nil;
  PGetVisual:=nil;

  MemSync.EndWrite;
end;

procedure TVis.ClearImage;
begin
  MemSync.BeginWrite;

  CreateColorCache(True);

  ClearFlag:=False;
  if ClearFlag=False then begin
    ClearFlag:=True;
    Images.InitLastCache;
    ResetNoteImage;
    ResetSpeImage;
    ResetVelImage;
    ResetParaImage;
  end;

  MemSync.EndWrite;
end;

function TVis.GetVisualPointer(Latems:dword):boolean;
var
  LastVisualCount:integer;
  FarABS,NearVC,NearABS,GetABS:integer;
  vc:integer;
begin
  MemSync.BeginWrite;

  LastVisualCount:=GetVisualCount;

  FarABS:=Main.NowDSoundSafeTime*2;
  NearABS:=10000;
  NearVC:=-1;

  for vc:=0 to VisualCount-1 do begin
    if VisualTick[vc]<>0 then begin
      GetABS:=abs(integer(VisualTick[vc])-integer(Latems));
      if (vc<>SetVisualCount) and (GetABS<FarABS) and (NearABS>GetABS) then begin
        NearABS:=GetABS;
        NearVC:=vc;
      end;
    end;
  end;

  if NearVC=-1 then begin
    Result:=False;
    end else begin
    if LastVisualCount=NearVC then begin
      Result:=False;
      end else begin
      GetVisualCount:=NearVC;
      PGetVisual:=@Visual[GetVisualCount];
      Result:=True;
    end;
  end;

  MemSync.EndWrite;
end;

procedure TVis.CalcImage(tick:dword;DriverMode:integer;Pause:boolean;SystemOnly:boolean;FFTFrame:integer;var buf:array of SmallInt;offset,len:integer);
begin
  SetVisualPointer(tick,Pause);

  CalcImageSystem(DriverMode);

  if SystemOnly=False then begin
    if Pause=False then begin
      sdGetVisual(PSetVisual);
      if VisINI.UseEffect=True then begin // DisplayEffect
        SndEff.CalcFFT(FFTFrame,PSetVisual,buf,offset,len);
      end;
    end;
    if VisINI.UseEffect=True then begin // DisplayEffect
      CalcImageProcess(DriverMode,FFTFrame,buf,offset,len);
    end;
  end;
end;

procedure TVis.CalcImageFFTDataSetOnly(Pause:boolean;SystemOnly:boolean;var buf:array of SmallInt;offset,len:integer);
begin
  if SystemOnly=False then begin
    if Pause=False then begin
      if VisINI.UseEffect=True then begin // DisplayEffect
        SndEff.CalcFFTDataSetOnly(buf,offset,len);
      end;
    end;
  end;
end;

procedure TVis.RewriteImage(SystemOnly:boolean);
begin
  RewriteImageSystem;
  if SystemOnly=False then begin
    RewriteImageProcess;
  end;
end;

procedure TVis.SetAnalyzeVolume(Ch:byte;Volume:Byte);
var
  Vol:byte;
begin
  Vol:=Volume div 4;
  if Vol>31 then Vol:=31;
  PSetVisual.VelGra[Ch].NowY:=Vol;
end;

procedure TVis.SetAnalyzeNoteoff(Ch:byte);
begin
  PSetVisual.VelGra[Ch].NowY:=0;
  PSetVisual.NoteKeyno[ch]:=0;
end;

function TVis.GetKDDID:byte;
begin
  Result:=PGetVisual.KDDID;
end;

function TVis.GetPlayTime:dword;
begin
  Result:=PGetVisual.PlayTime;
end;

function TVis.GetTotalClock:dword;
begin
  Result:=PGetVisual.TotalClock;
end;

function TVis.GetDispClock:dword;
begin
  Result:=PGetVisual.DispClock;
end;

function TVis.GetMesureClock:dword;
begin
  Result:=PGetVisual.MesureClock;
end;

function TVis.GetLoopCount:integer;
begin
  Result:=PGetVisual.LoopCount
end;

function TVis.GetTimerBCount:byte;
begin
  Result:=PGetVisual.Tempo;
end;

procedure TVis.ResetNoteImage;
var
  Ch,x,y:integer;
begin
  MemSync.BeginWrite;
  if NoteBGBM<>nil then begin
    BitBlt(NoteBM.Canvas.Handle,0,0,NotePos.Width,NotePos.Height,NoteBGBM.Canvas.Handle,0,0,SRCCOPY);
    Main.ChangeDriverMode;
    RewriteImageVisualKeyboard(True);
    for Ch:=0 to 7 do begin
      x:=12*6;
      y:=ch*34;
      Images.WriteNoteSmallStringLine(x,y+0,StringOfChar(' ',42),Ch*2+0,CSkin.ColVisKeyInfoText);
      Images.WriteNoteSmallStringLine(x,y+8,StringOfChar(' ',42),Ch*2+1,CSkin.ColVisKeyInfoText);
    end;
    BitBlt(Images.ScreenBM.Canvas.Handle,NotePos.Left,NotePos.Top,NotePos.Width,NotePos.Height,NoteBM.Canvas.Handle,0,0,SRCCOPY);
  end;
  MemSync.EndWrite;
  RefreshNoteImage;
end;

procedure TVis.FillBlackNoteImage;
begin
  with NoteBM.Canvas do begin
    Brush.Color:=$000000;
    Brush.Style:=bsSolid;
    FillRect(Rect(0,0,NotePos.Width,NotePos.Height));
  end;
end;

procedure TVis.RefreshNoteImage;
begin
  MemSync.BeginWrite;
  with NotePos do begin
    BitBlt(Main.Canvas.Handle,Left,Top,Width,Height,NoteBM.Canvas.Handle,0,0,SRCCOPY);
  end;
  MemSync.EndWrite;
end;

procedure TVis.ResetSpeImage;
begin
  MemSync.BeginWrite;
  if Images.BGBM<>nil then begin
    with SpePos do begin
      BitBlt(SpeBGBM.Canvas.Handle,0,0,Width,Height,Images.BGBM.Canvas.Handle,Left,Top,SRCCOPY);
      BitBlt(SpeBM.Canvas.Handle,0,0,Width,Height,SpeBGBM.Canvas.Handle,0,0,SRCCOPY);
      BitBlt(Images.ScreenBM.Canvas.Handle,Left,Top,Width,Height,SpeBM.Canvas.Handle,0,0,SRCCOPY);
    end;
  end;
  MemSync.EndWrite;
  RefreshSpeImage;
end;

procedure TVis.FillBGSpeImage;
begin
  MemSync.BeginWrite;
  if Images.BGBM<>nil then begin
    with SpePos do begin
      BitBlt(SpeBM.Canvas.Handle,0,0,Width,Height,SpeBGBM.Canvas.Handle,0,0,SRCCOPY);
    end;
  end;
  MemSync.EndWrite;
end;

procedure TVis.RefreshSpeImage;
begin
  MemSync.BeginWrite;
  with SpePos do begin
    BitBlt(Main.Canvas.Handle,Left,Top,Width,Height,SpeBM.Canvas.Handle,0,0,SRCCOPY);
  end;
  MemSync.EndWrite;
end;

procedure TVis.ResetVelImage;
begin
  MemSync.BeginWrite;
  if Images.BGBM<>nil then begin
    with VelPos do begin
      BitBlt(VelBM.Canvas.Handle,0,0,Width,Height,Images.BGBM.Canvas.Handle,Left,Top,SRCCOPY);
      BitBlt(Images.ScreenBM.Canvas.Handle,Left,Top,Width,Height,VelBM.Canvas.Handle,0,0,SRCCOPY);
    end;
  end;
  MemSync.EndWrite;
  RefreshVelImage;
end;

procedure TVis.RefreshVelImage;
begin
  MemSync.BeginWrite;
  with VelPos do begin
    BitBlt(Main.Canvas.Handle,Left,Top,Width,Height,VelBM.Canvas.Handle,0,0,SRCCOPY);
  end;
  MemSync.EndWrite;
end;

procedure TVis.ResetParaImage;
begin
  MemSync.BeginWrite;
  if Images.BGBM<>nil then begin
    with ParaPos do begin
      BitBlt(ParaBGBM.Canvas.Handle,0,0,Width,Height,Images.BGBM.Canvas.Handle,Left,Top,SRCCOPY);
      BitBlt(ParaBM.Canvas.Handle,0,0,Width,Height,ParaBGBM.Canvas.Handle,0,0,SRCCOPY);
      BitBlt(Images.ScreenBM.Canvas.Handle,Left,Top,Width,Height,ParaBM.Canvas.Handle,0,0,SRCCOPY);
    end;
  end;
  MemSync.EndWrite;
  RefreshParaImage;
end;

procedure TVis.RefreshParaImage;
begin
  MemSync.BeginWrite;
  with ParaPos do begin
    BitBlt(Main.Canvas.Handle,Left,Top,Width,Height,ParaBM.Canvas.Handle,0,0,SRCCOPY);
  end;
  MemSync.EndWrite;
end;

// -------------------------------------------------------------------------
// Private
// -------------------------------------------------------------------------

procedure TVis.SetVisualPointer(SetTick:dword;Clear:boolean);
var
  ch,cnt:integer;
  PBackVisual:PVisual;
begin
  MemSync.BeginWrite;

  PBackVisual:=PSetVisual;
  inc(SetVisualCount);
  if SetVisualCount=VisualCount then SetVisualCount:=0;
  PSetVisual:=@Visual[SetVisualCount];

  VisualTick[SetVisualCount]:=SetTick;

  MemSync.EndWrite;

  PSetVisual.PlayTime:=PBackVisual.PlayTime;
  PSetVisual.TotalClock:=PBackVisual.TotalClock;
  PSetVisual.MesureClock:=PBackVisual.MesureClock;
  PSetVisual.DispClock:=PBackVisual.DispClock;
  PSetVisual.Tempo:=PBackVisual.Tempo;
  PSetVisual.LoopCount:=PBackVisual.LoopCount;

  if Clear=False then begin
    for ch:=0 to 15 do begin
      PSetVisual.VelGra[ch]:=PBackVisual.VelGra[ch];
    end;
    for cnt:=0 to 63 do begin
      PSetVisual.SpeAna[cnt]:=PBackVisual.SpeAna[cnt];
    end;
    for ch:=0 to 15 do begin
      PSetVisual.NoteKeyno[Ch]:=PBackVisual.NoteKeyno[ch];
    end;
    for ch:=0 to 15 do begin
      PSetVisual.Channel[ch]:=PBackVisual.Channel[ch];
    end;
    PSetVisual.SonarLastClock:=PBackVisual.SonarLastClock;
    for ch:=0 to 15 do begin
      for cnt:=0 to 7 do begin
        PSetVisual.Sonar[ch].Body[cnt]:=PBackVisual.Sonar[ch].Body[cnt];
      end;
    end;
    PSetVisual.SpeWave:=PBackVisual.SpeWave;
    end else begin // Visual Fadeout
    for ch:=0 to 15 do begin
      with PBackVisual.VelGra[ch] do begin
        PSetVisual.VelGra[ch].NowY:=0;
        PSetVisual.VelGra[ch].Delay:=Delay;
        PSetVisual.VelGra[ch].DelayTime:=DelayTime;
        PSetVisual.VelGra[ch].DelaySpeed:=DelaySpeed;
      end;
    end;
    for cnt:=0 to 63 do begin
      with PBackVisual.SpeAna[cnt] do begin
        PSetVisual.SpeAna[cnt].NowY:=0;
        PSetVisual.SpeAna[cnt].Delay:=Delay;
        PSetVisual.SpeAna[cnt].DelayTime:=DelayTime;
        PSetVisual.SpeAna[cnt].DelaySpeed:=DelaySpeed;
      end;
    end;
    for ch:=0 to 15 do begin
      PSetVisual.NoteKeyno[Ch]:=0;
    end;
    for ch:=0 to 15 do begin
      with PBackVisual.Channel[ch] do begin
        PSetVisual.Channel[ch].mes0:=mes0;
        PSetVisual.Channel[ch].mes1:=mes1;
        PSetVisual.Channel[ch].Panpot:=Panpot;
        PSetVisual.Channel[ch].Voice:=Voice;
        PSetVisual.Channel[ch].Keycode:=0;
      end;
    end;
    PBackVisual.SonarLastClock:=0;
    for ch:=0 to 15 do begin
      for cnt:=0 to 7 do begin
        with PBackVisual.Sonar[ch].Body[cnt] do begin
          note:=0;
          len:=0;
          delay:=0;
        end;
      end;
    end;
    for cnt:=0 to 288-1 do begin
      PBackVisual.SpeWave[cnt]:=0;
    end;
  end;
end;

procedure TVis.CreateColorCache(Renew:boolean);
var
  x,y,cnt:integer;
  NoteAlpha:integer;
begin
  if Renew=True then ColorCache.Enabled:=False;

  if ColorCache.Enabled=True then exit;

  for x:=0 to 128-1 do begin
    ColorCache.Bar0Arr[x]:=CSkin.ColVisBar0;
    ColorCache.Bar1Arr[x]:=CSkin.ColVisBar1;
  end;

  for y:=$00 to $ff do begin
    ColorCache.SpeScrAlpha[y]:=RGBModulate(CSkin.ColWinSpeBG,CSkin.ColVisSpeScr,y);
  end;

  for cnt:=0 to GradAlphaCount-1 do begin
    NoteAlpha:=$ff-(($ff*cnt) div (GradAlphaCount-1));
    ColorCache.KeyFlatColor1[cnt]:=RGBModulate(CSkin.ColVisKeyFlat,CSkin.ColVisKey1to8,NoteAlpha);
    ColorCache.KeySharpColor1[cnt]:=RGBModulate(CSkin.ColVisKeySharp,CSkin.ColVisKey1to8,NoteAlpha);
    ColorCache.KeyFlatColor9[cnt]:=RGBModulate(CSkin.ColVisKeyFlat,CSkin.ColVisKey9to16,NoteAlpha);
    ColorCache.KeySharpColor9[cnt]:=RGBModulate(CSkin.ColVisKeySharp,CSkin.ColVisKey9to16,NoteAlpha);
  end;
end;

// -------------------------------------------------------------------------
// CalcImage
// -------------------------------------------------------------------------

procedure TVis.CalcImageSystem;
begin
  sdGetVisualSystem(pSetVisual);

  if Main.KDDLoaded=True then begin
    PSetVisual.KDDID:=KDD.KDDID;
    end else begin
    PSetVisual.KDDID:=$ff;
  end;
end;

procedure TVis.CalcImageProcess(DriverMode:integer;FFTFrame:integer;var buf:array of SmallInt;offset,len:integer);
begin
  if VisINI.UseEffect=True then begin // DisplayEffect
    CalcImageSpeAna(buf,offset,len);
    CalcImageVelocity;
  end;

  case VisINI.VisualMode of
    VisualModeKeyboard: CalcImageVisualKeyboard;
    VisualModeSonar:    CalcImageVisualSonar;
  end;
end;

procedure TVis.CalcImageSpeana(var buf:array of SmallInt;offset,len:integer);
var
  Ch:Byte;
  SetDelayTime:integer;
begin
  SetDelayTime:=0;
  case VisINI.SpeanaMode of
    SpeanaModeWave: SetDelayTime:=0;
    SpeanaModeBar:  SetDelayTime:=6;
    SpeanaModeScr:  SetDelayTime:=0;
    SpeanaModeLine: SetDelayTime:=0;
  end;

  for Ch:=0 to 63 do begin
    with PSetVisual.SpeAna[Ch] do begin
      if Delay<=NowY then begin
        Delay:=NowY;
        DelayTime:=SetDelayTime;
        DelaySpeed:=0;
      end;
      if DelayTime<>0 then begin
        dec(DelayTime);
        end else begin
        inc(DelaySpeed);
        if Delay>DelaySpeed then
          Delay:=Delay-DelaySpeed
          else
          Delay:=0;
      end;
    end;
  end;

  case VisINI.SpeanaMode of
    SpeanaModeWave: CalcImageSpeanaWave(buf,offset,len);
    SpeanaModeBar:  CalcImageSpeanaBar;
    SpeanaModeScr:  CalcImageSpeanaScr;
    SpeanaModeLine: CalcImageSpeanaLine;
  end;
end;

procedure TVis.CalcImageSpeanaWave(var buf:array of SmallInt;offset,len:integer);
var
  cnt:integer;
  WaveAdjust:integer;
  WaveAdjustFlag:boolean;
  SpeWidth:integer;
  Limit:integer;
begin
  SpeWidth:=288-1;

  WaveAdjust:=offset;
  if VisINI.SpeanaWaveZeroAjust=False then begin
    end else begin
    Limit:=offset+len-(SpeWidth*2);
    if Limit<offset then begin
      WaveAdjust:=offset;
      end else begin
      WaveAdjustFlag:=False;
      while (WaveAdjustFlag=False) do begin
        inc(WaveAdjust,1);
        if ((buf[WaveAdjust*2]<0) and (buf[WaveAdjust*2+2]>0)) then WaveAdjustFlag:=True;
        if WaveAdjust>=Limit then begin
          WaveAdjust:=Limit-1;
          WaveAdjustFlag:=True;
        end;
      end;
    end;
  end;

  for cnt:=0 to SpeWidth-1 do begin
    if ((offset+len)*2)<(WaveAdjust*2+cnt*4) then begin
      PSetVisual.SpeWave[cnt]:=$40;
      end else begin
      PSetVisual.SpeWave[cnt]:=(buf[WaveAdjust*2+cnt*4] div $80) div 4;
    end;
  end;
end;

procedure TVis.CalcImageSpeanaBar;
begin
end;

procedure TVis.CalcImageSpeanaScr;
begin
end;

procedure TVis.CalcImageSpeanaLine;
begin
end;

procedure TVis.CalcImageVelocity;
var
  Ch:byte;
begin
  for Ch:=0 to 15 do begin
    with PSetVisual.VelGra[Ch] do begin
      if NowY>=1 then dec(NowY);
      if Delay<NowY then begin
        Delay:=NowY;
        DelayTime:=10;
      end;
      if DelayTime=0 then begin
        if Delay<>0 then dec(Delay);
        end else begin
        dec(DelayTime);
      end;
    end;
  end;
end;

procedure TVis.CalcImageVisualKeyboard;
begin
end;

procedure TVis.CalcImageVisualSonar;
var
  tclk:dword;
  ch:integer;
  cnt:integer;
  LenClock:dword;
begin
  tclk:=PSetVisual.DispClock;
  if sdGetDriverMode=DriverModeSPC then tclk:=tclk*192 div 1000;

  if tclk<PSetVisual.SonarLastClock then begin
    PSetVisual.SonarLastClock:=tclk;
    exit;
  end;

  LenClock:=tclk-PSetVisual.SonarLastClock;
  PSetVisual.SonarLastClock:=tclk;

  for Ch:=0 to 7 do begin
    with PSetVisual.Sonar[Ch] do begin
      for cnt:=1 to 8-1 do begin
        inc(Body[cnt].delay,LenClock);
      end;
      inc(Body[0].len,LenClock);
      if (PSetVisual.NoteKeyno[Ch]<>0) or (Body[0].note<>0) then begin
        if ((PSetVisual.NoteKeyno[Ch] div 64)<>(Body[0].note div 64)) then begin //and ((PSetVisual.NoteKeyno[Ch] div 64)<>(Body[1].note div 64)) then begin
          for cnt:=8-1 downto 1 do begin
            Body[cnt]:=Body[cnt-1];
          end;
          with Body[0] do begin
            note:=PSetVisual.NoteKeyno[Ch];
            len:=0;
            delay:=0;
          end;
        end;
      end;
    end;
  end;
end;

// -------------------------------------------------------------------------
// RewriteImage
// -------------------------------------------------------------------------

procedure TVis.RewriteImageSystem;
begin
  if (RewriteCountForSystem mod 4)=0 then begin
    Images.WritePlayTime(PGetVisual.PlayTime);
    Images.WriteTimerBClock(PGetVisual.DispClock);
  end;

  if RewriteCountForSystem=0 then begin
    Images.WriteTimerBCycle(PGetVisual.Tempo);
    Images.WriteLoopCount(PGetVisual.LoopCount);
    if sdGetQuietMode=True then begin
      ResetNoteImage;
      ResetSpeImage;
      ResetVelImage;
      ResetParaImage;
    end;
  end;

  RewriteCountForSystem:=(RewriteCountForSystem+1) mod 16;
end;

procedure TVis.RewriteImageProcess;
var
  Ch:byte;
  cnt:integer;
begin
  if ClearFlag=True then begin
    ClearFlag:=False;

    Main.ChangeDriverMode;

    for cnt:=0 to VisualCount-1 do begin
      VisualTick[cnt]:=0;
    end;

    for Ch:=0 to 15 do begin
      PGetVisual.NoteKeyno[ch]:=0;
      VelGraLastY[Ch]:=0;
      with PGetVisual.VelGra[Ch] do begin
        NowY:=31;
        Delay:=31;
        DelayTime:=0;
        DelaySpeed:=0;
      end;
    end;
    for Ch:=0 to 63 do begin
      SpeAnaLastY[Ch]:=0;
      with PGetVisual.SpeAna[Ch] do begin
        NowY:=255;
        Delay:=255;
        DelayTime:=0;
        DelaySpeed:=0;
      end;
    end;

    for ch:=0 to 15 do begin
      with PGetVisual.Channel[ch] do begin
        PGetVisual.Channel[ch].mes0:='';
        PGetVisual.Channel[ch].mes1:='';
        PGetVisual.Channel[ch].Panpot:=0;
        PGetVisual.Channel[ch].Voice:=0;
        PGetVisual.Channel[ch].Keycode:=0;
      end;
    end;

    if VisINI.UseEffect=True then begin // DisplayEffect
      RewriteImageSpeAna;
      RewriteImageParam;
      RewriteImageVelocity;
    end;

    for Ch:=0 to 15 do begin
      PGetVisual.NoteKeyno[ch]:=0;
      VelGraLastY[Ch]:=31;
      with PGetVisual.VelGra[Ch] do begin
        NowY:=0;
        Delay:=0;
        DelayTime:=0;
        DelaySpeed:=0;
      end;
    end;
    for Ch:=0 to 63 do begin
      SpeAnaLastY[Ch]:=255;
      with PGetVisual.SpeAna[Ch] do begin
        NowY:=0;
        Delay:=0;
        DelayTime:=0;
        DelaySpeed:=0;
      end;
    end;
  end;

  CreateColorCache(False);

  if VisINI.UseEffect=True then begin // DisplayEffect
    RewriteImageSpeAna;
    RewriteImageParam;
    RewriteImageVelocity;
  end;

  case VisINI.VisualMode of
    VisualModeKeyboard: RewriteImageVisualKeyboard(False);
//    VisualModeMDXDis:   RewriteImageVisualMDXDis;
    VisualModeWire:     dx3dDDVis;
    VisualModeWave:     dx3dDDVis;
    VisualModeFly:      dx3dDDVis;
    VisualModePool:     dx3dDDVis;
    VisualModeBox:      dx3dDDVis;
    VisualModeSonar:    RewriteImageVisualSonar;
    VisualModeRain:     dx3dDDVis;
  end;
end;

procedure TVis.RewriteImageSpeana;
begin
  case VisINI.SpeanaMode of
    SpeanaModeWave: RewriteImageSpeanaWave;
    SpeanaModeBar:  RewriteImageSpeanaBar;
    SpeanaModeScr:  RewriteImageSpeanaScr;
    SpeanaModeLine: RewriteImageSpeanaLine;
  end;

  RefreshSpeImage;
end;

procedure TVis.RewriteImageSpeanaWave;
var
  Antialias:boolean;
  SpeWidth,SpeHeight:integer;
  x,y,ly:integer;
  rgb:TRGB;
  procedure DrawLine(x1,y1,x2,y2:integer);
  var
    xcnt,ycnt,xoff:integer;
    nx,ax,ny,ay:single;
    pb:PRGBArray;
  begin
    if x1<2 then x1:=2;
    if (SpeWidth-2)<x2 then x2:=SpeWidth-2;
    if y1<2 then y1:=2;
    if (SpeHeight-2)<y1 then y1:=SpeHeight-2;
    if y2<2 then y2:=2;
    if (SpeHeight-2)<y2 then y2:=SpeHeight-2;

    if x2<=x1 then exit;

    if y1=y2 then begin
      pb:=SpePtr[y1];
      xoff:=x1;
      for xcnt:=0 to x2-x1-1 do begin
        pb[xoff]:=rgb;
        inc(xoff);
      end;
      end else begin
      if abs(y2-y1)<abs(x2-x1) then begin
        ny:=y1;
        ay:=(y2-y1)/(x2-x1);
        xoff:=x1;
        for xcnt:=0 to x2-x1-1 do begin
          pb:=SpePtr[trunc(ny)];
          pb[xoff]:=rgb;
          ny:=ny+ay;
          inc(xoff);
        end;
        end else begin
        if y2<y1 then begin
          xcnt:=x1;
          x1:=x2;
          x2:=xcnt;
          ycnt:=y1;
          y1:=y2;
          y2:=ycnt;
        end;
        nx:=x1;
        ax:=(x2-x1)/(y2-y1);
        for ycnt:=0 to y2-y1-1 do begin
          pb:=SpePtr[y1+ycnt];
          xoff:=trunc(nx);
          pb[xoff+0]:=rgb;
          nx:=nx+ax;
        end;
      end;
    end;
  end;
  procedure DrawLineAntialias(x1,y1,x2,y2:integer);
  var
    xcnt,ycnt,xoff:integer;
    nx,ax,ny,ay:single;
    Alpha:integer;
    pb,pbg:PRGBArray;
  begin
    if x1<2 then x1:=2;
    if (SpeWidth-2)<x2 then x2:=SpeWidth-2;
    if y1<2 then y1:=2;
    if (SpeHeight-2)<y1 then y1:=SpeHeight-2;
    if y2<2 then y2:=2;
    if (SpeHeight-2)<y2 then y2:=SpeHeight-2;

    if x2<=x1 then exit;

    if y1=y2 then begin
      pb:=SpePtr[y1-1];
      xoff:=x1;
      for xcnt:=0 to x2-x1-1 do begin
        pb[xoff]:=rgb;
        inc(xoff);
      end;
      pb:=SpePtr[y1];
      xoff:=x1;
      for xcnt:=0 to x2-x1-1 do begin
        pb[xoff]:=rgb;
        inc(xoff);
      end;
      end else begin
      if abs(y2-y1)<abs(x2-x1) then begin
        ny:=y1;
        ay:=(y2-y1)/(x2-x1);
        xoff:=x1;
        for xcnt:=0 to x2-x1-1 do begin
          Alpha:=trunc(frac(ny)*$ff);

          pb:=SpePtr[trunc(ny)-1];
          pbg:=SpeBGPtr[trunc(ny)-1];
          pb[xoff]:=RGBModulate(pbg[xoff],rgb,$ff-Alpha);

          pb:=SpePtr[trunc(ny)];
          pb[xoff]:=rgb;

          pb:=SpePtr[trunc(ny)+1];
          pbg:=SpeBGPtr[trunc(ny)+1];
          pb[xoff]:=RGBModulate(pbg[xoff],rgb,Alpha);

          ny:=ny+ay;
          inc(xoff);
        end;
        end else begin
        if y2<y1 then begin
          xcnt:=x1;
          x1:=x2;
          x2:=xcnt;
          ycnt:=y1;
          y1:=y2;
          y2:=ycnt;
        end;
        nx:=x1;
        ax:=(x2-x1)/(y2-y1);
        for ycnt:=0 to y2-y1-1 do begin
          Alpha:=trunc(frac(nx)*$ff);

          pb:=SpePtr[y1+ycnt];
          pbg:=SpeBGPtr[y1+ycnt];
          xoff:=trunc(nx);

          pb[xoff-1]:=RGBModulate(pbg[xoff-1],rgb,$ff-Alpha);
          pb[xoff+0]:=rgb;
          pb[xoff+1]:=RGBModulate(pbg[xoff+1],rgb,Alpha);
          nx:=nx+ax;
        end;
      end;
    end;
  end;
begin
  SpeWidth:=SpePos.Width-1;
  SpeHeight:=SpePos.Height-1;

  FillBGSpeImage;

  rgb:=CSkin.ColVisSpeScr;

  Antialias:=VisINI.SpeanaWaveAntialias;
  ly:=(SpeHeight div 2)-PGetVisual.SpeWave[0];
  if Antialias then begin
    for x:=1 to SpeWidth-1 do begin
      y:=(SpeHeight div 2)-PGetVisual.SpeWave[x];
      DrawLineAntialias(x-1,ly,x,y);
      ly:=y;
    end;
    end else begin
    for x:=1 to SpeWidth-1 do begin
      y:=(SpeHeight div 2)-PGetVisual.SpeWave[x];
      DrawLine(x-1,ly,x,y);
      ly:=y;
    end;
  end;
end;

procedure TVis.RewriteImageSpeanaBar;
var
  Ch,ChCnt,y:LongInt;
  LinePtr:PRGBArray;
  SpeMod,SpeWidth:integer;
  CopyLen:integer;
  Offset:integer;
  LastY,NY:integer;
begin
  if VisINI.SpeanaBarDoubleSide then begin
    SpeMod:=1;
    SpeWidth:=5;
    CopyLen:=(SpeWidth-1)*3;
    ChCnt:=57;
    end else begin
    SpeMod:=2;
    SpeWidth:=9;
    CopyLen:=(SpeWidth-1)*3;
    ChCnt:=32;
  end;
  for Ch:=0 to ChCnt-1 do begin
    Offset:=Ch*SpeWidth;
    with PGetVisual.SpeAna[Ch*SpeMod] do begin
      LastY:=(255-SpeAnaLastY[Ch*SpeMod]) div 8;
      NY:=(255-NowY) div 8;
      if LastY<>NY then begin
        if LastY<NY then begin
          for y:=LastY to NY do begin
            move(ColorCache.Bar1Arr[0],SpePtr[y*2][Offset],CopyLen);
          end;
          end else begin
          for y:=NY to LastY do begin
            move(ColorCache.Bar0Arr[0],SpePtr[y*2][Offset],CopyLen);
          end;
        end;
      end;
      SpeAnaLastY[Ch*SpeMod]:=NowY;

      LinePtr:=SpePtr[((255-SpeAnaLastDelay[Ch*SpeMod]) div 8)*2];
      if SpeAnaLastDelay[Ch*SpeMod]>NowY then begin
        move(ColorCache.Bar1Arr[0],LinePtr[Offset],CopyLen);
        end else begin
        move(ColorCache.Bar0Arr[0],LinePtr[Offset],CopyLen);
      end;
      SpeAnaLastDelay[Ch*SpeMod]:=Delay;
      LinePtr:=SpePtr[((255-Delay) div 8)*2];
      move(ColorCache.Bar0Arr[0],LinePtr[Offset],CopyLen);
    end;
  end;
end;

procedure TVis.RewriteImageSpeanaScr;
var
  Ch,y:LongInt;
  Offset:integer;
  rgb:TRGB;
  pSpe:PRGB;
begin
  BitBlt(SpeBM.Canvas.Handle,0,0,SpePos.Width-2,SpePos.Height,SpeBM.Canvas.Handle,2,0,SRCCOPY);

  Offset:=(SpePos.Width-2);
  for Ch:=0 to 63 do begin
    with PGetVisual.SpeAna[Ch] do begin
      y:=NowY;
      if y<$00 then y:=$00;
      if y>$FF then y:=$FF;
      rgb:=ColorCache.SpeScrAlpha[y];
      pSpe:=@SpePtr[63-Ch][Offset];
      pSpe^:=rgb;
      inc(pSpe);
      pSpe^:=rgb;
    end;
  end;
end;

procedure TVis.RewriteImageSpeanaLine;
var
  Antialias:boolean;
  SpeWidth,SpeHeight:integer;
  Ch:LongInt;
  NowY,Delay,LastNowY,LastDelay:integer;
  x,y,lx,ly:integer;
  rgb:TRGB;
  procedure DrawLine(x1,y1,x2,y2:integer);
  var
    xcnt,ycnt,xoff:integer;
    nx,ax,ny,ay:single;
    pSpe:PRGB;
  begin
    if x1<2 then x1:=2;
    if (SpeWidth-2)<x2 then x2:=SpeWidth-2;
    if y1<2 then y1:=2;
    if (SpeHeight-2)<y1 then y1:=SpeHeight-2;
    if y2<2 then y2:=2;
    if (SpeHeight-2)<y2 then y2:=SpeHeight-2;

    if x2<=x1 then exit;

    if y1=y2 then begin
      pSpe:=@SpePtr[y1][x1];
      for xcnt:=0 to x2-x1-1 do begin
        pSpe^:=rgb;
        inc(pSpe);
      end;
      end else begin
      if abs(y2-y1)<abs(x2-x1) then begin
        ny:=y1;
        ay:=(y2-y1)/(x2-x1);
        xoff:=x1;
        for xcnt:=0 to x2-x1-1 do begin
          SpePtr[trunc(ny)][xoff]:=rgb;
          ny:=ny+ay;
          inc(xoff);
        end;
        end else begin
        if y2<y1 then begin
          xcnt:=x1;
          x1:=x2;
          x2:=xcnt;
          ycnt:=y1;
          y1:=y2;
          y2:=ycnt;
        end;
        nx:=x1;
        ax:=(x2-x1)/(y2-y1);
        for ycnt:=0 to y2-y1-1 do begin
          SpePtr[y1+ycnt][trunc(nx)]:=rgb;
          nx:=nx+ax;
        end;
      end;
    end;
  end;
  procedure DrawLineAntialias(x1,y1,x2,y2:integer);
  var
    xcnt,ycnt:integer;
    xoff,yoff:integer;
    nx,ax,ny,ay:single;
    Alpha:integer;
    pSpe,pSpeM:PRGB;
  begin
    if x1<2 then x1:=2;
    if (SpeWidth-2)<x2 then x2:=SpeWidth-2;
    if y1<2 then y1:=2;
    if (SpeHeight-2)<y1 then y1:=SpeHeight-2;
    if y2<2 then y2:=2;
    if (SpeHeight-2)<y2 then y2:=SpeHeight-2;

    if x2<=x1 then exit;

    if y1=y2 then begin
      pSpeM:=@SpePtr[y1-1][x1];
      pSpe:=@SpePtr[y1][x1];
      for xcnt:=0 to x2-x1-1 do begin
        pSpeM^:=rgb;
        pSpe^:=rgb;
        inc(pSpeM);
        inc(pSpe);
      end;
      end else begin
      if abs(y2-y1)<abs(x2-x1) then begin
        ny:=y1;
        ay:=(y2-y1)/(x2-x1);
        xoff:=x1;
        for xcnt:=0 to x2-x1-1 do begin
          Alpha:=trunc(frac(ny)*$ff);
          yoff:=trunc(ny);

          RGBModulateVar(SpePtr[yoff-1][xoff],rgb,$ff-Alpha);
          SpePtr[yoff+0][xoff]:=rgb;
          RGBModulateVar(SpePtr[yoff+1][xoff],rgb,Alpha);

          inc(xoff);
          ny:=ny+ay;
        end;
        end else begin
        if y2<y1 then begin
          xcnt:=x1;
          x1:=x2;
          x2:=xcnt;
          ycnt:=y1;
          y1:=y2;
          y2:=ycnt;
        end;
        nx:=x1;
        ax:=(x2-x1)/(y2-y1);
        yoff:=y1;

        for ycnt:=0 to y2-y1-1 do begin
          Alpha:=trunc(frac(nx)*$ff);

          xoff:=trunc(nx);

          pSpe:=@SpePtr[yoff][xoff-1];

          RGBModulateVar(pSpe^,rgb,$ff-Alpha);
          inc(pSpe);
          pSpe^:=rgb;
          inc(pSpe);
          RGBModulateVar(pSpe^,rgb,Alpha);

          nx:=nx+ax;
          inc(yoff);
        end;
      end;
    end;
  end;
begin
  Antialias:=VisINI.SpeanaLineAntialias;

  SpeWidth:=SpePos.Width-1;
  SpeHeight:=SpePos.Height-1;

  FillBGSpeImage;

  LastNowY:=PGetVisual.SpeAna[0].NowY;
  lx:=0;
  ly:=($ff-LastNowY) div 4;
  rgb:=CSkin.ColVisLine1;
  for Ch:=1 to 63 do begin
    NowY:=PGetVisual.SpeAna[Ch].NowY;
    x:=Ch*4+(Ch div 2);
    y:=($ff-((NowY+LastNowY) div 2)) div 4;
    if Antialias then begin
      DrawLineAntialias(lx,ly,x,y);
      end else begin
      DrawLine(lx,ly,x,y);
    end;
    lx:=x;
    ly:=y;
    LastNowY:=NowY;
  end;

  LastDelay:=PGetVisual.SpeAna[0].Delay;
  lx:=0;
  ly:=($ff-LastDelay) div 4;
  rgb:=CSkin.ColVisLine0;
  for Ch:=1 to 63 do begin
    Delay:=PGetVisual.SpeAna[Ch].Delay;
    x:=Ch*4+(Ch div 2);
    y:=($ff-((Delay+LastDelay) div 2)) div 4;
    if Antialias then begin
      DrawLineAntialias(lx,ly,x,y);
      end else begin
      DrawLine(lx,ly,x,y);
    end;
    lx:=x;
    ly:=y;
    LastDelay:=Delay;
  end;
end;

procedure TVis.RewriteImageParam;
var
  Ch:LongInt;
  Offset:integer;
  PanIdx:integer;
  x,y:integer;
  SrcPtr:PGrayArray;
  BGPtr,DstPtr:PRGBArray;
  Alpha:byte;
  rgb:TRGB;
begin
  rgb:=CSkin.ColVisParaText;

  for Ch:=0 to 15 do begin
    Offset:=Ch*18;
    with PGetVisual.Channel[Ch] do begin
      if sdGetMute(Ch)=False then
        PanIdx:=Panpot*PanpotSize
        else
        PanIdx:=0;
      for y:=0 to PanpotSize-1 do begin
        SrcPtr:=Images.PanpotBM.ScanLine[y];
        BGPtr:=ParaBGPtr[y];
        DstPtr:=ParaPtr[y];
        for x:=0 to PanpotSize-1 do begin
          Alpha:=SrcPtr[PanIdx+x];
          DstPtr[Offset+x]:=RGBModulate(BGPtr[Offset+x],rgb,Alpha);
        end;
      end;
      Images.WriteParaSmallMath(Offset,16,Ch*2+0,rgb,Voice);
      Images.WriteParaSmallMath(Offset,24,Ch*2+1,rgb,Keycode);
    end;
  end;

  RefreshParaImage;
end;

procedure TVis.RewriteImageVelocity;
var
  Ch,y:LongInt;
  LinePtr:PRGBArray;
  VelWidth:integer;
  CopyLen:integer;
  Offset:integer;
  LastY:integer;
begin
  VelWidth:=18;
  CopyLen:=(VelWidth-2)*3;
  for Ch:=0 to 15 do begin
    Offset:=Ch*VelWidth;
    with PGetVisual.VelGra[Ch] do begin
      LastY:=VelGraLastY[Ch];
      if LastY<>NowY then begin
        if LastY<NowY then begin
          for y:=LastY to NowY do begin
            move(ColorCache.Bar0Arr[0],VelPtr[(31-y)*2][Offset],CopyLen);
          end;
          end else begin
          for y:=NowY to LastY do begin
            move(ColorCache.Bar1Arr[0],VelPtr[(31-y)*2][Offset],CopyLen);
          end;
        end;
      end;
      VelGraLastY[Ch]:=NowY;

      LinePtr:=VelPtr[(31-VelGraLastDelay[Ch])*2];
      if VelGraLastDelay[Ch]>NowY then begin
        move(ColorCache.Bar1Arr[0],LinePtr[Offset],CopyLen);
        end else begin
        move(ColorCache.Bar0Arr[0],LinePtr[Offset],CopyLen);
      end;
      VelGraLastDelay[Ch]:=Delay;
      LinePtr:=VelPtr[(31-Delay)*2];
      move(ColorCache.Bar0Arr[0],LinePtr[Offset],CopyLen);
    end;
  end;

  RefreshVelImage;
end;

procedure TVis.RewriteImageVisualKeyboard(ResetAllKeys:boolean);
var
  ChCount,Ch,x,y,note:LongInt;
  NCnt:LongInt;
  yoff:integer;
  procedure WriteNote(ch:integer;Alpha:byte;x:integer);
  var
    pc:PRGB;
    pbc:PRGB;
    DrawColor:TRGB;
    ycnt,y:integer;
  begin
    if ch<8 then begin
      DrawColor:=ColorCache.KeyFlatColor1[0];
      end else begin
      DrawColor:=ColorCache.KeyFlatColor9[0];
    end;
    x:=(x*3) div 64;
    if (0<=x) and (x<=(NotePos.Width-4)) then begin
      case Alpha of
        $00: begin
          y:=yoff;
          for ycnt:=0 to 14 do begin
            pc:=@NotePtr[y][x];
            pbc:=@NoteBGPtr[y][x];
            pc^:=pbc^;
            inc(pc); inc(pbc);
            pc^:=pbc^;
            inc(pc); inc(pbc);
            pc^:=pbc^;
            inc(y);
          end;
        end;
        $ff: begin
          y:=yoff;
          for ycnt:=0 to 14 do begin
            pc:=@NotePtr[y][x];
            pc^:=DrawColor;
            inc(pc);
            pc^:=DrawColor;
            inc(pc);
            pc^:=DrawColor;
            inc(y);
          end;
        end;
        else begin
          y:=yoff;
          for ycnt:=0 to 14 do begin
            pc:=@NotePtr[y][x];
            pbc:=@NoteBGPtr[y][x];
            pc^:=RGBModulate(pbc^,DrawColor,Alpha);
            inc(pc); inc(pbc);
            pc^:=RGBModulate(pbc^,DrawColor,Alpha);
            inc(pc); inc(pbc);
            pc^:=RGBModulate(pbc^,DrawColor,Alpha);
            inc(y);
          end;
        end;
      end;
    end;
  end;
  procedure WriteNoteKeyAlphaFF(ch:integer;Alpha:integer;noteno:integer);
  var
    y:integer;
    cx:integer;
    pc:PRGB;
    DrawColor:TRGB;
  begin
    with KeyboardType[noteno mod 12] do begin
      cx:=((noteno div 12)*28)+(x*2)+2;
      if (0<=cx) and (cx<=(NotePos.Width-3)) then begin
        if Sharp=True then begin
          if ch<8 then begin
            DrawColor:=ColorCache.KeySharpColor1[Alpha];
            end else begin
            DrawColor:=ColorCache.KeySharpColor9[Alpha];
          end;
          for y:=0 to 7-1 do begin
            pc:=@NotePtr[yoff+y][cx];
            pc^:=DrawColor;
            inc(pc);
            pc^:=DrawColor;
            inc(pc);
            pc^:=DrawColor;
          end;
          end else begin
          if ch<8 then begin
            DrawColor:=ColorCache.KeyFlatColor1[Alpha];
            end else begin
            DrawColor:=ColorCache.KeyFlatColor9[Alpha];
          end;
          for y:=0 to 7-1 do begin
            pc:=@NotePtr[yoff+y][cx];
            if FlatLeft=True then pc^:=DrawColor;
            inc(pc);
            pc^:=DrawColor;
            inc(pc);
            if FlatRight=True then pc^:=DrawColor;
          end;
          for y:=7 to 14-1 do begin
            pc:=@NotePtr[yoff+y][cx];
            pc^:=DrawColor;
            inc(pc);
            pc^:=DrawColor;
            inc(pc);
            pc^:=DrawColor;
          end;
        end;
      end;
    end;
  end;
  procedure WriteNoteKey(ch:integer;Alpha:integer;noteno:integer);
  var
    y:integer;
    cx:integer;
    pc,pBG:PRGB;
    DrawColor0,DrawColor1:TRGB;
    NoteKeyAlpha,NoteAlpha:integer;
  begin
    NoteKeyAlpha:=CSkin.VisKeyNoteAlpha;
    if NoteKeyAlpha=$ff then begin
      WriteNoteKeyAlphaFF(ch,Alpha,noteno);
      exit;
    end;

    NoteAlpha:=$ff-(($ff*Alpha) div (GradAlphaCount-1));

    with KeyboardType[noteno mod 12] do begin
      cx:=((noteno div 12)*28)+(x*2)+2;
      if (0<=cx) and (cx<=(NotePos.Width-3)) then begin
        if Sharp=True then begin
          DrawColor0:=CSkin.ColVisKeySharp;
          if ch<8 then begin
            DrawColor1:=CSkin.ColVisKey1to8;
            end else begin
            DrawColor1:=CSkin.ColVisKey9to16;
          end;
          for y:=0 to 7-1 do begin
            pc:=@NotePtr[yoff+y][cx];
            pBG:=@NoteBGPtr[yoff+y][cx];
            pc^:=RGBModulate(pBG^,DrawColor0,NoteKeyAlpha);
            RGBModulateVar(pc^,DrawColor1,NoteAlpha);
            inc(pc);
            inc(pBG);
            pc^:=RGBModulate(pBG^,DrawColor0,NoteKeyAlpha);
            RGBModulateVar(pc^,DrawColor1,NoteAlpha);
            inc(pc);
            inc(pBG);
            pc^:=RGBModulate(pBG^,DrawColor0,NoteKeyAlpha);
            RGBModulateVar(pc^,DrawColor1,NoteAlpha);
          end;
          end else begin
          DrawColor0:=CSkin.ColVisKeyFlat;
          if ch<8 then begin
            DrawColor1:=CSkin.ColVisKey1to8;
            end else begin
            DrawColor1:=CSkin.ColVisKey9to16;
          end;
          for y:=0 to 7-1 do begin
            pc:=@NotePtr[yoff+y][cx];
            pBG:=@NoteBGPtr[yoff+y][cx];
            if FlatLeft=True then begin
              pc^:=RGBModulate(pBG^,DrawColor0,NoteKeyAlpha);
              RGBModulateVar(pc^,DrawColor1,NoteAlpha);
            end;
            inc(pc);
            inc(pBG);
            pc^:=RGBModulate(pBG^,DrawColor0,NoteKeyAlpha);
            RGBModulateVar(pc^,DrawColor1,NoteAlpha);
            inc(pc);
            inc(pBG);
            if FlatRight=True then begin
              pc^:=RGBModulate(pBG^,DrawColor0,NoteKeyAlpha);
              RGBModulateVar(pc^,DrawColor1,NoteAlpha);
            end;
          end;
          for y:=7 to 14-1 do begin
            pc:=@NotePtr[yoff+y][cx];
            pBG:=@NoteBGPtr[yoff+y][cx];
            pc^:=RGBModulate(pBG^,DrawColor0,NoteKeyAlpha);
            RGBModulateVar(pc^,DrawColor1,NoteAlpha);
            inc(pc);
            inc(pBG);
            pc^:=RGBModulate(pBG^,DrawColor0,NoteKeyAlpha);
            RGBModulateVar(pc^,DrawColor1,NoteAlpha);
            inc(pc);
            inc(pBG);
            pc^:=RGBModulate(pBG^,DrawColor0,NoteKeyAlpha);
            RGBModulateVar(pc^,DrawColor1,NoteAlpha);
          end;
        end;
      end;
    end;
  end;
begin
  if ResetAllKeys=True then begin
    if VisINI.KeyboardRealKeys=True then begin
      for Ch:=0 to 7 do begin
        yoff:=Ch*34+18;
        for note:=0 to 10*12-1 do begin
          WriteNoteKey(Ch,GradAlphaCount-1,note);
        end;
      end;
    end;
    exit;
  end;

  if VisINI.GradEnabled=False then exit;

  for Ch:=0 to 7 do begin
    x:=12*6;
    y:=ch*34;
    with PGetVisual.Channel[Ch] do begin
      Images.WriteNoteSmallStringLine(x,y+0,copy(mes0,1,42),Ch*2+0,CSkin.ColVisKeyInfoText);
      Images.WriteNoteSmallStringLine(x,y+8,copy(mes1,1,42),Ch*2+1,CSkin.ColVisKeyInfoText);
    end;
  end;

  if VisINI.KeyboardDraw9to16trk=False then begin
    ChCount:=8;
    end else begin
    ChCount:=16;
  end;

  for Ch:=0 to ChCount-1 do begin
    with KeyAna[Ch] do begin
      for NCnt:=GradAlphaCount-1 downto 1 do begin
        NoteNo[NCnt]:=NoteNo[NCnt-1];
      end;
      NoteNo[0]:=PGetVisual.NoteKeyno[Ch];
    end;
  end;

  if VisINI.KeyboardRealKeys=False then begin
    if VisINI.KeyboardUseFadeout=True then begin
      for Ch:=ChCount-1 downto 0 do begin
        yoff:=(Ch mod 8)*34+17;
        with KeyAna[Ch] do begin
          NCnt:=GradAlphaCount-1;
          if NoteNo[NCnt]>0 then WriteNote(Ch,ColGradAlpha[NCnt],NoteNo[NCnt]);
          for NCnt:=GradAlphaCount-2 downto 0 do begin
            if ((((NoteNo[NCnt+1]*3) div 64)<>((NoteNo[NCnt]*3) div 64)) or (NCnt=0)) then begin
              if NoteNo[NCnt]>0 then WriteNote(Ch,ColGradAlpha[NCnt],NoteNo[NCnt]);
            end;
          end;
        end;
      end;
      end else begin
      for Ch:=ChCount-1 downto 0 do begin
        yoff:=(Ch mod 8)*34+17;
        with KeyAna[Ch] do begin
          if NoteNo[1]>0 then WriteNote(Ch,ColGradAlpha[GradAlphaCount-1],NoteNo[1]);
          if NoteNo[0]>0 then WriteNote(Ch,ColGradAlpha[0],NoteNo[0]);
        end;
      end;
    end;
    end else begin // RealKeyboard
    // Note+3は、1pixelではなく、基底ノートがd+のため。
    if VisINI.KeyboardUseFadeout=True then begin
      for Ch:=ChCount-1 downto 0 do begin
        yoff:=(Ch mod 8)*34+18;
        with KeyAna[Ch] do begin
          NCnt:=GradAlphaCount-1;
          if NoteNo[NCnt]>0 then WriteNoteKey(Ch,NCnt,((NoteNo[NCnt]+32) div 64)+3);
          for NCnt:=GradAlphaCount-2 downto 0 do begin
            if ((((NoteNo[NCnt+1]*3) div 64)<>((NoteNo[NCnt]*3) div 64)) or (NCnt=0)) then begin
              if NoteNo[NCnt]>0 then WriteNoteKey(Ch,NCnt,((NoteNo[NCnt]+32) div 64)+3);
            end;
          end;
        end;
      end;
      end else begin
      for Ch:=ChCount-1 downto 0 do begin
        yoff:=(Ch mod 8)*34+18;
        with KeyAna[Ch] do begin
          if NoteNo[1]>0 then WriteNoteKey(Ch,GradAlphaCount-1,((NoteNo[1]+32) div 64)+3);
          if NoteNo[0]>0 then WriteNoteKey(Ch,0,((NoteNo[0]+32) div 64)+3);
        end;
      end;
    end;
  end;

  RefreshNoteImage;
end;

type
  TMDXDisDatas=array of TMDXDisData;

{
var
  MDXDisFilename:string;
  MDXDisDatasCount:array[0..7] of integer;
  MDXDisDatas:array[0..7] of TMDXDisDatas;

procedure TVis.RewriteImageVisualMDXDisReload;
var
  Ch,pos:integer;
begin
  for Ch:=0 to 7 do begin
    MDXDisDatasCount[Ch]:=0;
    SetLength(MDXDisDatas[Ch],128);

    with Main.MXDRVm.MMLChannel[Ch] do begin
      pos:=0;
      while (pos<=(integer(MMLSize)-5)) do begin
        if (MDXDisDatasCount[Ch] mod 128)=0 then SetLength(MDXDisDatas[Ch],MDXDisDatasCount[Ch]+128);
        with MDXDisDatas[Ch][MDXDisDatasCount[Ch]] do begin
          Offset:=pos;
          Cmd[0]:=Buffer[pos+0];
          Cmd[1]:=Buffer[pos+1];
          Cmd[2]:=Buffer[pos+2];
          Cmd[3]:=Buffer[pos+3];
          Cmd[4]:=Buffer[pos+4];
          Cmd[5]:=Buffer[pos+5];
        end;
        MDXDisAnalyze(MDXDisDatas[Ch][MDXDisDatasCount[Ch]]);
        inc(pos,MDXDisDatas[Ch][MDXDisDatasCount[Ch]].len+1);
        inc(MDXDisDatasCount[Ch]);
      end;
    end;
  end;
end;

procedure TVis.RewriteImageVisualMDXDis;
var
  Ch,cnt,DataCnt,ax,ay:LongInt;
  dy:integer;
  pos:integer;
begin
  if MainINI.GradEnabled=False then exit;
  
  with Main.NoteImage do begin
    Canvas.Brush.Color:=$000000;
    Canvas.CopyMode:=cmSrcPaint;
    Canvas.FillRect(Rect(0,0,Width,Height));
    Canvas.Pen.Color:=$a04040;
  end;

  if Main.DriverMode<>DriverModeMXDRVm then exit;
  if Main.FilereadBusyFlag=True then exit;

  if MDXDisFilename<>Main.gFileInfo.GetFullPath then begin
    MDXDisFilename:=Main.gFileInfo.GetFullPath;
    RewriteImageVisualMDXDisReload;
  end;

  for Ch:=0 to 7 do begin
    ax:=(Ch mod 2)*(282 div 2);
    ay:=(Ch div 2)*(272 div 4);

    with Main.MXDRVm.MMLChannel[Ch],Main.NoteImage do begin
      pos:=Main.MXDRVm.MMLChannel[Ch].NowPos;
      if pos<>-1 then begin
        DataCnt:=-1;
        for cnt:=0 to MDXDisDatasCount[Ch]-1 do begin
          if MDXDisDatas[Ch][cnt].Offset=pos then DataCnt:=cnt;
        end;
        if DataCnt<>-1 then begin
          for dy:=-2 to 4 do begin
            if (0<=(DataCnt+dy)) and ((DataCnt+dy)<=(MDXDisDatasCount[Ch]-1)) then begin
              with MDXDisDatas[Ch][DataCnt+dy] do begin
                Images.WriteSmallString(Canvas,ax,ay+(dy+2)*9,IntToHex(offset,4)+':'+mes);
              end;
              if dy=0 then begin
                Canvas.MoveTo(ax,ay+(dy+2)*9+7);
                Canvas.LineTo(ax+(282 div 2),ay+(dy+2)*9+7);
              end;
            end;
          end;
        end;
      end;
    end;
  end;

  with Main.NoteImage do
    BitBlt(Main.Canvas.Handle,Left,Top,Width,Height,Canvas.Handle,0,0,SRCCOPY);
end;
}

procedure TVis.RewriteImageVisualSonarCreateTable;
var
  ch,cnt:integer;
  chcol:TRGB;
  c:integer;
  basergb:TRGB;
  basecolormorph:integer;
  Col000000:TRGB;
begin
  if SonarTableCreated=True then exit;
  SonarTableCreated:=True;

  for cnt:=0 to 192-1 do begin
    SonarSintbl[cnt]:=trunc(sin((cnt+96)/192*PI*2)*$ff);
    SonarCostbl[cnt]:=trunc(cos((cnt+96)/192*PI*2)*$ff);
  end;

  basergb:=dword2rgb(VisINI.SonarBaseColor);
  basecolormorph:=VisINI.SonarBaseColorMorph;

  Col000000:=dword2rgb($000000);

  for ch:=0 to 8-1 do begin
    c:=integer(basergb.b)+(random(basecolorMorph)-(basecolorMorph div 2));
    if c<$00 then c:=$00;
    if c>$ff then c:=$ff;
    chcol.b:=c;
    c:=integer(basergb.g)+(random(basecolorMorph)-(basecolorMorph div 2));
    if c<$00 then c:=$00;
    if c>$ff then c:=$ff;
    chcol.g:=c;
    c:=integer(basergb.r)+(random(basecolorMorph)-(basecolorMorph div 2));
    if c<$00 then c:=$00;
    if c>$ff then c:=$ff;
    chcol.r:=c;
    for cnt:=$00 to $ff do begin
      SonarAlpha[ch][cnt]:=RGBModulate(Col000000,chcol,cnt);
    end;
  end;
end;

procedure TVis.RewriteImageVisualSonar;
var
  Antialias:boolean;
  Width,Height:integer;
  Ch,x,y,lx,ly:LongInt;
  pow:integer;
  bright:word;
  basecol:PAlphaArray;
  col1,col2:TRGB;
  cnt:integer;
  NCnt:LongInt;
  NoteTmpPtr,NoteTmpPtrm1,NoteTmpPtrp1:PRGBArray;
  Offset:integer;
  ClockBase:integer;
  r:integer;
  drawlen,drawdelay:integer;
  procedure DrawLine(x1,y1,x2,y2:integer);
  var
    xcnt,ycnt,xoff,yoff,xadd,yadd:integer;
    nx,ax,ny,ay:single;
    pb:PRGBArray;
    procedure AddPixel;
    var
      c:word;
    begin
      c:=pb[xoff].b+col1.b;
      if c>=$ff then begin
        pb[xoff].b:=$ff;
        end else begin
        pb[xoff].b:=c;
      end;
      c:=pb[xoff].g+col1.g;
      if c>=$ff then begin
        pb[xoff].g:=$ff;
        end else begin
        pb[xoff].g:=c;
      end;
      c:=pb[xoff].r+col1.r;
      if c>=$ff then begin
        pb[xoff].r:=$ff;
        end else begin
        pb[xoff].r:=c;
      end;
    end;
  begin
    if (x1<1) or ((Width-1)<=x1) then exit;
    if (y1<1) or ((Height-1)<=y1) then exit;
    if (x2<1) or ((Width-1)<=x2) then exit;
    if (y2<1) or ((Height-1)<=y2) then exit;
    if (x1=x2) and (y1=y2) then exit;

    if abs(y2-y1)<abs(x2-x1) then begin
      if x2<x1 then begin
        xadd:=-1;
        xoff:=x1;
        ycnt:=y1;
        y1:=y2;
        y2:=ycnt;
        ny:=y2;
        end else begin
        xadd:=1;
        xoff:=x1;
        ny:=y1;
      end;
      ay:=(y2-y1)/(x2-x1);
      for xcnt:=0 to abs(x2-x1)-1 do begin
        pb:=NotePtr[trunc(ny)];
        AddPixel;
        ny:=ny+ay;
        inc(xoff,xadd);
      end;
      end else begin
      if y2<y1 then begin
        yadd:=-1;
        yoff:=y1;
        xcnt:=x1;
        x1:=x2;
        x2:=xcnt;
        nx:=x2;
        end else begin
        yadd:=1;
        yoff:=y1;
        nx:=x1;
      end;
      ax:=(x2-x1)/(y2-y1);
      for ycnt:=0 to abs(y2-y1)-1 do begin
        pb:=NotePtr[yoff];
        xoff:=trunc(nx);
        AddPixel;
        nx:=nx+ax;
        inc(yoff,yadd);
      end;
    end;
  end;
  procedure DrawLineAntialias(x1,y1,x2,y2:integer);
  var
    xcnt,ycnt,xoff,yoff,xadd,yadd:integer;
    nx,ax,ny,ay:single;
    pb:PRGBArray;
    procedure AddPixel;
    var
      c:word;
    begin
      c:=pb[xoff].b+col1.b;
      if c>=$ff then begin
        pb[xoff].b:=$ff;
        end else begin
        pb[xoff].b:=c;
      end;
      c:=pb[xoff].g+col1.g;
      if c>=$ff then begin
        pb[xoff].g:=$ff;
        end else begin
        pb[xoff].g:=c;
      end;
      c:=pb[xoff].r+col1.r;
      if c>=$ff then begin
        pb[xoff].r:=$ff;
        end else begin
        pb[xoff].r:=c;
      end;
    end;
    procedure AddPixelDiv2;
    var
      c:word;
    begin
      c:=pb[xoff].b+col2.b;
      if c>=$ff then begin
        pb[xoff].b:=$ff;
        end else begin
        pb[xoff].b:=c;
      end;
      c:=pb[xoff].g+col2.g;
      if c>=$ff then begin
        pb[xoff].g:=$ff;
        end else begin
        pb[xoff].g:=c;
      end;
      c:=pb[xoff].r+col2.r;
      if c>=$ff then begin
        pb[xoff].r:=$ff;
        end else begin
        pb[xoff].r:=c;
      end;
    end;
  begin
    if (x1<2) or ((Width-2)<=x1) then exit;
    if (y1<2) or ((Height-2)<=y1) then exit;
    if (x2<2) or ((Width-2)<=x2) then exit;
    if (y2<2) or ((Height-2)<=y2) then exit;
    if (x1=x2) and (y1=y2) then exit;

    if abs(y2-y1)<abs(x2-x1) then begin
      if x2<x1 then begin
        xadd:=-1;
        xoff:=x1;
        ycnt:=y1;
        y1:=y2;
        y2:=ycnt;
        ny:=y2;
        end else begin
        xadd:=1;
        xoff:=x1;
        ny:=y1;
      end;
      ay:=(y2-y1)/(x2-x1);
      for xcnt:=0 to abs(x2-x1)-1 do begin
        pb:=NotePtr[trunc(ny)-1];
        AddPixelDiv2;
        pb:=NotePtr[trunc(ny)];
        AddPixel;
        pb:=NotePtr[trunc(ny)+1];
        AddPixelDiv2;
        ny:=ny+ay;
        inc(xoff,xadd);
      end;
      end else begin
      if y2<y1 then begin
        yadd:=-1;
        yoff:=y1;
        xcnt:=x1;
        x1:=x2;
        x2:=xcnt;
        nx:=x2;
        end else begin
        yadd:=1;
        yoff:=y1;
        nx:=x1;
      end;
      ax:=(x2-x1)/(y2-y1);
      for ycnt:=0 to abs(y2-y1)-1 do begin
        pb:=NotePtr[yoff];
        xoff:=trunc(nx)-1;
        AddPixelDiv2;
        xoff:=trunc(nx);
        AddPixel;
        xoff:=trunc(nx)+1;
        AddPixelDiv2;
        nx:=nx+ax;
        inc(yoff,yadd);
      end;
    end;
  end;
begin
  RewriteImageVisualSonarCreateTable;

  if VisINI.GradEnabled=False then exit;

  FillBlackNoteImage;

  Antialias:=VisINI.SonarAntialias;
  Width:=NotePos.Width-2;
  Height:=NotePos.Height-2;

  for Ch:=0 to 7 do begin
    BaseCol:=@SonarAlpha[Ch];
    with PGetVisual.Sonar[Ch] do begin
      for cnt:=8-1 downto 0 do begin
        if (Body[cnt].note>256) and (Body[cnt].delay<=$ff) then begin
          pow:=trunc((Body[cnt].note-256)/12000*Width);
          y:=pow+Body[cnt].len+Ch;
          if y<1 then y:=1;
          if y>(Height-1) then y:=Height-1;
          NoteTmpPtrm1:=NotePtr[y-1];
          NoteTmpPtr:=NotePtr[y];
          NoteTmpPtrp1:=NotePtr[y+1];
          // Outside Fadeout
          if Body[cnt].delay<>0 then begin
            ClockBase:=0;
            dec(ClockBase,Body[cnt].delay);
            drawdelay:=Body[cnt].delay;
            if drawdelay>$ff then drawdelay:=$ff;
            if Antialias then begin
              for NCnt:=0 to drawdelay do begin
                bright:=word($ff-NCnt)*($ff-Body[cnt].delay) div $100;
                col1:=basecol[bright*2 div 3];
                col2:=basecol[bright div 2];
                offset:=(Width+ClockBase);
                if 0<=offset then begin
                  NoteTmpPtrm1[offset]:=col2;
                  NoteTmpPtr[offset]:=col1;
                  NoteTmpPtrp1[offset]:=col2;
                end;
                inc(ClockBase);
              end;
              end else begin
              for NCnt:=0 to drawdelay do begin
                bright:=word($ff-NCnt)*($ff-Body[cnt].delay) div $100;
                col1:=basecol[bright*2 div 3];
                offset:=(Width+ClockBase);
                if 0<=offset then begin
                  NoteTmpPtr[offset]:=col1;
                end;
                inc(ClockBase);
              end;
            end;
          end;
          // Inside
          ClockBase:=0;
          dec(ClockBase,Body[cnt].delay);
          drawlen:=Body[cnt].len;
          if drawlen>$ff then drawlen:=$ff;
          if Antialias=True then begin
            for NCnt:=0 to drawlen-1 do begin
              bright:=($ff-NCnt)*($ff-Body[cnt].delay) div $100;
              col1:=basecol[bright*2 div 3];
              col2:=basecol[bright div 2];
              offset:=(Width+ClockBase);
              if 0<=offset then begin
                NoteTmpPtrm1[offset]:=col2;
                NoteTmpPtr[offset]:=col1;
                NoteTmpPtrp1[offset]:=col2;
              end;
              dec(ClockBase);
            end;
            end else begin
            for NCnt:=0 to drawlen-1 do begin
              bright:=($ff-NCnt)*($ff-Body[cnt].delay) div $100;
              col1:=basecol[bright*2 div 3];
              offset:=(Width+ClockBase);
              if 0<=offset then begin
                NoteTmpPtr[offset]:=col1;
              end;
              dec(ClockBase);
            end;
          end;
        end;
      end;
    end;
  end;

  for Ch:=0 to 7 do begin
    BaseCol:=@SonarAlpha[Ch];
    with PGetVisual.Sonar[Ch] do begin
      for cnt:=8-1 downto 0 do begin
        if (Body[cnt].note>256) and (Body[cnt].delay<=$3f) then begin
          pow:=trunc((Body[cnt].note-256)/12000*Width);
          if pow>=$ff then pow:=$ff;
          // Outside Fadeout
          if Body[cnt].delay<>0 then begin
            ClockBase:=PGetVisual.SonarLastClock mod 192;
            dec(ClockBase,Body[cnt].delay);
            drawdelay:=Body[cnt].delay;
            if drawdelay>$ff then drawdelay:=$ff;
            r:=(ClockBase+1920) mod 192;
            inc(ClockBase);
            lx:=(Width div 2)+(SonarSintbl[r]*pow) div $100;
            ly:=(Height div 2)+(SonarCostbl[r]*pow) div $100;
            if Antialias then begin
              for NCnt:=1 to drawdelay do begin
                bright:=word($ff-NCnt)*($3f-Body[cnt].delay) div $40;
                col1:=basecol[bright];
                col2:=basecol[bright div 2];
                r:=(ClockBase+1920) mod 192;
                inc(ClockBase);
                x:=(Width div 2)+(SonarSintbl[r]*pow) div $100;
                y:=(Height div 2)+(SonarCostbl[r]*pow) div $100;
                DrawLineAntialias(lx,ly,x,y);
                lx:=x;
                ly:=y;
              end;
              end else begin
              for NCnt:=1 to drawdelay do begin
                bright:=word($ff-NCnt)*($3f-Body[cnt].delay) div $40;
                col1:=basecol[bright];
                r:=(ClockBase+1920) mod 192;
                inc(ClockBase);
                x:=(Width div 2)+(SonarSintbl[r]*pow) div $100;
                y:=(Height div 2)+(SonarCostbl[r]*pow) div $100;
                DrawLine(lx,ly,x,y);
                lx:=x;
                ly:=y;
              end;
            end;
          end;

          // Inside
          ClockBase:=PGetVisual.SonarLastClock mod 192;
          dec(ClockBase,Body[cnt].delay);
          drawlen:=Body[cnt].len;
          if drawlen>$ff then drawlen:=$ff;
          r:=(ClockBase+1920) mod 192;
          dec(ClockBase);
          lx:=(Width div 2)+(SonarSintbl[r]*pow) div $100;
          ly:=(Height div 2)+(SonarCostbl[r]*pow) div $100;
          if Antialias=True then begin
            for NCnt:=0 to drawlen-1 do begin
              bright:=($ff-NCnt)*($3f-Body[cnt].delay) div $40;
              col1:=basecol[bright];
              col2:=basecol[bright div 2];
              r:=(ClockBase+1920) mod 192;
              dec(ClockBase);
              x:=(Width div 2)+(SonarSintbl[r]*pow) div $100;
              y:=(Height div 2)+(SonarCostbl[r]*pow) div $100;
              DrawLineAntialias(lx,ly,x,y);
              lx:=x;
              ly:=y;
            end;
            end else begin
            for NCnt:=0 to drawlen-1 do begin
              bright:=($ff-NCnt)*($3f-Body[cnt].delay) div $40;
              col1:=basecol[bright];
              col2:=basecol[bright div 2];
              r:=(ClockBase+1920) mod 192;
              dec(ClockBase);
              x:=(Width div 2)+(SonarSintbl[r]*pow) div $100;
              y:=(Height div 2)+(SonarCostbl[r]*pow) div $100;
              DrawLine(lx,ly,x,y);
              lx:=x;
              ly:=y;
            end;
          end;
        end;
      end;
    end;
  end;

  r:=(PGetVisual.SonarLastClock+1920) mod 192;
  x:=(Width div 2)+(SonarSintbl[r]*$ff) div $100;
  y:=(Height div 2)+(SonarCostbl[r]*$ff) div $100;
  NoteBM.Canvas.Pen.Color:=$802020;
  NoteBM.Canvas.MoveTo(Width div 2,Height div 2);
  NoteBM.Canvas.LineTo(x,y);

  RefreshNoteImage;
end;

end.

