view WaveView.cpp @ 0:a6a46af64546

first upload
author wenx <xue.wen@eecs.qmul.ac.uk>
date Wed, 10 Aug 2011 14:55:38 +0100
parents
children
line wrap: on
line source
/*
    Harmonic Visualiser

    An audio file viewer and editor.
    Centre for Digital Music, Queen Mary, University of London.
    This file copyright 2011 Wen Xue.

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of the
    License, or (at your option) any later version. 
*/
//---------------------------------------------------------------------------
#include <vcl.h>
#include <values.h>
#pragma hdrstop

#include "WaveView.h"
#include <math.h>
#include <math.hpp>
#pragma package(smart_init)
#define HEIGHT (Height)
#define WIDTH (Width)
#define FATP FromAmplitudeToPixel
#define FPTA FromPixelToAmplitude
#define FDFTP FromDigiFreqToPixel
#define FPTDF FromPixelToDigiFreq
#define FPTS FromPixelToSample
#define FSTP FromSampleToPixel

//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

void DoubleToInt(void* out, int BytesPerSample, double* in, int Count)
{
  if (BytesPerSample==1){unsigned char* out8=(unsigned char*)out; for (int k=0; k<Count; k++) *(out8++)=floor(*(in++)+128.5);}
  else if (BytesPerSample==2) {__int16* out16=(__int16*)out; for (int k=0; k<Count; k++) *(out16++)=floor(*(in++)+0.5);}
  else {__pint24 out24=(__pint24)out; for (int k=0; k<Count; k++) *(out24++)=floor(*(in++)+0.5);}
}

//convert int array to double. truncate int24 to int16.
void IntToDouble2416(double* out, void* in, int BytesPerSample, int Count)
{
  if (BytesPerSample==1){unsigned char* in8=(unsigned char*)in; for (int k=0; k<Count; k++) *(out++)=*(in8++)-128.0;}
  else if (BytesPerSample==2) {__int16* in16=(__int16*)in; for (int k=0; k<Count; k++) *(out++)=*(in16++);}
  else {__pint24 in24=(__pint24)in; for (int k=0; k<Count; k++) *(out++)=*(in24++)>>8;}
}

//dest-=src*amp
void MultiSub(void* dest, void* src, int BytesPerSample, double* amp, int Count)
{
  if (BytesPerSample==1){unsigned char *dest8=(unsigned char*)dest, *src8=(unsigned char*)src; for (int k=0; k<Count; k++) *(dest8++)-=(*(src8++)-128.0)**(amp++);}
  else if (BytesPerSample==2) {__int16 *dest16=(__int16*)dest, *src16=(__int16*)src; for (int k=0; k<Count; k++) *(dest16++)-=(*(src16++)**(amp++));}
  else {__pint24 dest24=(__pint24)dest, src24=(__pint24)src; for (int k=0; k<Count; k++) *(dest24++)-=(*(src24++)**(amp++));}
}

void AddDoubleToInt1624(void* dest, int BytesPerSample, double* src, int Count, double a)
{
  if (BytesPerSample==1){unsigned char* dest8=(unsigned char*)dest; for (int k=0; k<Count; k++) *(dest8++)+=*(src++)*a;}
  else if (BytesPerSample==2) {__int16* dest16=(__int16*)dest; for (int k=0; k<Count; k++) *(dest16++)+=*(src++)*a;}
  else {a*=256; __pint24 dest24=(__pint24)dest; for (int k=0; k<Count; k++) *(dest24++)+=*(src++)*a;}
}

void PlayNote(double f, bool semitone=true)
{
  if (f<=0) return;
  TWaveAudio* WV=new TWaveAudio(NULL); int Sps=WV->SamplesPerSec;
  double Sps25=Sps/4.0; __int16 data[48000]; int amp=2048, har=3;
  if (semitone) f=440*pow(2.0l, floor(Log2(f/440)*12+0.5)/12.0);
  if (f<200) har=600/f, amp*=pow(200/f, 0.7);
  for (int i=0; i<Sps; i++){double fdata=0; for (int j=1; j<=har; j++){if (j*f<Sps/2) fdata+=sin(2*M_PI*i*j*f/Sps)/j/j;} data[i]=amp*fdata*exp(-i/Sps25);}
  WV->WriteSamples(data, Sps);
  AnsiString FileName=ExtractFilePath(Application->ExeName)+"temp0";
  WV->SaveToFile(FileName); delete WV;
  PlaySound(FileName.c_str(), 0, SND_FILENAME|SND_ASYNC);
}

AnsiString SemitoneToPitch(double f)
{
  static char* notename[]={"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
  int fd=floor(f+0.5);
  double fr=f-fd;
  int oct=floor(fd/12.0);
  int note=fd-oct*12;
  int fr2=floor(fr*100+0.5);
  if (fr2==0) return AnsiString().sprintf("%s%d", notename[note], oct+4, fr2);
  else if (fr2>0)
  {
    if (fr2%10==0) {fr2/=10; return AnsiString().sprintf("%s%d+.%d", notename[note], oct+4, fr2);}
    else return AnsiString().sprintf("%s%d+.%02d", notename[note], oct+4, fr2);
  }
  else
  {
    fr2=-fr2;
    if (fr2%10==0) {fr2/=10; return AnsiString().sprintf("%s%d-.%d", notename[note], oct+4, fr2);}
    else return AnsiString().sprintf("%s%d-.%02d", notename[note], oct+4, fr2);
  }
}

void TextOutline(TCanvas* Canv, AnsiString Text, int X, int Y, TColor FC, TColor BC)
{
  Canv->Font->Color=BC; Canv->TextOut(X-1, Y, Text); Canv->TextOut(X+1, Y, Text); Canv->TextOut(X, Y-1, Text);
  Canv->TextOut(X, Y+1, Text); Canv->Font->Color=FC; Canv->TextOut(X, Y, Text);
}

//*
//TFFilter: TF-filtering with cosinal interpolation
//Identical data and dataout allowed.
void TFFilter(TWaveView* WV, int Channel, TWaveViewSelections* Selections, bool Pass, bool allduration=false)
{
  int Count=WV->Length, Wid=WV->SpecRes, Offst=WV->SpecOffst;
  int hWid=Wid/2, Order=Log2(Wid), frst, fren;
  {
    int Fr=(Count-Wid)/Offst+1;
    if (allduration) frst=0, fren=Fr;
    else
    {
      frst=floor((WV->StartPos-hWid)*1.0/Offst+0.5); if (frst<0) frst=0;
      fren=floor((WV->EndPos-hWid)*1.0/Offst+0.5); if (fren>Fr) fren=Fr;
    }
  }
  int frcount=fren-frst;
  int** filter=new int*[frcount]; //signals if man atom is kept or discarded
  filter[0]=new int[frcount*Wid]; for (int i=1; i<frcount; i++) filter[i]=&filter[0][i*Wid];

  //fill local filter table: entries "1" in the table are kept, "0" are discarded
  if (Pass) memset(filter[0], 0, sizeof(int)*frcount*Wid);
  else for (int i=0; i<frcount; i++) for (int j=0; j<Wid; j++) filter[i][j]=1;
  for (int i=0; i<Selections->Count; i++)
  {
    int lx1, lx2, ly1, ly2;
    lx1=floor((Selections->Items[i].StartPos-hWid)*1.0/Offst+0.5)-frst;
    lx2=floor((Selections->Items[i].EndPos-hWid)*1.0/Offst+0.5)-frst;
    ly1=Selections->Items[i].StartDigiFreq*Wid;
    ly2=Selections->Items[i].EndDigiFreq*Wid;
    if (lx1<0) lx1=0; if (lx2>=frcount) lx2=frcount-1;
    if (ly1<0) ly1=0; if (ly1>hWid) ly1=hWid;
    if (Pass) for (int x=lx1; x<=lx2; x++) for (int y=ly1; y<=ly2; y++) filter[x][y]=1;
    else for (int x=lx1; x<=lx2; x++) for (int y=ly1; y<=ly2; y++) filter[x][y]=0;
  }
  double* lxfr=new double[frcount]; //the ratio of kept atoms of each frame
  for (int i=0; i<frcount; i++)
  {
    lxfr[i]=0;
    for (int j=0; j<=hWid; j++) if (filter[i][j]) lxfr[i]=lxfr[i]+1;
    lxfr[i]/=(hWid+1);
  }

  //construct the window used for interpolation
  double* winf=WV->fwin;  //this is the one used to compute Spec
  double* wini=NewWindow8(wtHann, Wid, NULL, NULL, 0); //this is an idea COLA window
  if (hWid!=Offst){double tmp=Offst*1.0/hWid; for (int i=0; i<Wid; i++) wini[i]*=tmp;} //this normalizes COLA to perfect reconstruction OLA
  double* winrec; //this window is the one actually to be applied after IFFT
  if (WV->SpecWindowType==wtHann) winrec=0;
  else
  {
    winrec=new double[Wid];
    for (int i=0; i<Wid; i++)
      if (winf[i]!=0) winrec[i]=wini[i]/winf[i];
      else winrec[i]=0;
  }
  AllocateFFTBuffer(Wid, ldata, w, x);
  int* hbitinv=CreateBitInvTable(Order-1);

  int bps=WV->BytesPerSample;
  char* data8=&WV->Data8[Channel][frst*Offst*bps];

  for (int fr=0; fr<frcount; fr++)
  {
    if (lxfr[fr]!=0 && lxfr[fr]!=1) WV->Spec[Channel][frst+fr];
  }

  int prefetchcount=Wid/Offst;
  char** prefetch=new char*[prefetchcount]; prefetch[0]=new char[prefetchcount*bps*Wid]; for (int i=1; i<prefetchcount; i++) prefetch[i]=&prefetch[0][i*Wid*bps];

  for (int fr=0; fr<frcount; fr++)
  {
    if (fr==0)
    {
      //prefetch frames
      for (int i=0; i<prefetchcount; i++)
        if (i<frcount && lxfr[i]==0) memcpy(prefetch[i], &data8[i*Offst*bps], Wid*bps);
    }
    //prefetch[fr%prefetchcount] now contains the fr-th frame
    char* wvdata=&data8[fr*Offst*bps];
    char* fdata=prefetch[fr%prefetchcount];
    if (lxfr[fr]==0)
    {
      //remove original frame (windowed) from signal
      MultiSub(wvdata, fdata, bps, wini, Wid);
    }
    else if (lxfr[fr]==1)
    {
      //do nothing = leave originl frame (windowed) as is
    }
    else
    {
      //replace original frame (windowed) with syntheszed frame, but here
      //  is a subtractive approach: you synthesize what is subtracted instead
      //  and remove it directly without windowing original data
      cmplx<QSPEC_FORMAT>* spec=WV->Spec[Channel][frst+fr];
      for (int i=0; i<=hWid; i++)
        if (filter[fr][i]==0) x[i]=spec[i];
        else x[i]=0;
      CIFFTR(x, Order, w, ldata, hbitinv);
      if (winrec) for (int i=0; i<Wid; i++) ldata[i]*=winrec[i];
      AddDoubleToInt1624(wvdata, bps, ldata, Wid, -1);
    }
    //prefetch next
    if (fr+prefetchcount<frcount && lxfr[fr+prefetchcount]==0) memcpy(fdata, &data8[(fr+prefetchcount)*Offst*bps], bps*Wid);
  }

  FreeFFTBuffer(ldata);
  free(hbitinv);

  delete[] filter[0]; delete[] filter;
  delete[] prefetch[0]; delete[] prefetch;
  delete[] lxfr;
	delete[] winrec;
	free8(wini);
}  //*/

//---------------------------------------------------------------------------
void FeedFFTBuffers(int Id, cdouble* &w, cdouble* &x, double* &win, int* &hbi, void* Parent)
{
  TWaveView* WV=(TWaveView*)Parent;
  w=WV->fw;
  x=WV->fx;
  win=WV->fwin;
  hbi=WV->fhbi;
}

//---------------------------------------------------------------------------
static inline void ValidCtrCheck(TWaveView *)
{
  new TWaveView(NULL);
}
//---------------------------------------------------------------------------
__fastcall TWaveView::TWaveView(TComponent* Owner, bool usex, bool usep)
  : TCustomControl(Owner)
{
  if (!dynamic_cast<TCustomForm*>(Owner))
    Parent=dynamic_cast<TWinControl*>(Owner);

  DisableMouseWheelZoom=false;

  FTools.Clear();
  FPitchScalePart=1;
  FSectionProgress=-1;

  FShowCursor=true;
  FShowCursorText=true;
  FShowInfo=true;
  FShowPaneInfo=true;
  FPlayNoteInSemitone=true;

  FAutoSpecAmp=false;
  FSpecAmp=1;

  for (int i=0; i<WV2_MAX_CHANNEL; i++) FSpectrogram[i]=new TQuickSpectrogram(this, i, usex, usep, FeedFFTBuffers);

  FSpecRes=1024;
  FSpecOffst=FSpecRes/2;
  FSpecWindowType=wtHamming;
  FSpecWindowParamD[0]=32*log(2.0)/(M_PI*M_PI);

  FLocalDataTimeGrid=true;
  FForceHamming=true;
  FCurrentPane=-1;
  FCalculateFrameCount=0;
  FSamplesPerSec=44100;
  FBlockSize=8192;
  FCaption="";
  FOnGetOpMode=0;
  FOnGetPlaybackStartAndEndPos=0;
  FOnInfoDblClick=0;
  FOnMousePointer=0;
  FOnMouseLocalData=0;
  FOnSelectedChange=0;
  FCustomCursorText=0;
  FOnCustomPaint=0;
  FCustomInfo=0;
  FCustomPaneInfo=0;
  FCustomProperty=0;
  FOnPageChange=0;
  FOnScaleChange=0;
  FOnPlaybackDone=0;
  FOnPaint=0;
  FCustomItemExtractClick=0;
  FCustomXZoomClick=0;
  FCustomYZoomClick=0;
  FLength=0;
  InfoRectAtPointer=-1;
  ObjectAtPointer=0;
  StartObject=0;

  FSection=new TDataAudio(this);
  PlayBuffer0=0;
  PlayBuffer1=0;

  FSelectionBorderWidth=1;

  FBytesPerSample=2;
  FStereoMode=wvpStereo;
  TimeStamp1=0;
  TimeStamp2=0;

  FAxisColor=clBlack;
  FBackColor=clLtGray;
  FCursorColorBright=clLime;
  FCursorColorDim=clRed;
  FInfoColor0=clWhite;
  FInfoColor1=clBlack;
  FInfoColor2=clRed;
  FWaveColor=clBlack;
  WaveBackColor=clWhite;
  Brush->Color=FBackColor;

  FSelectedAreaColorX=clWhite;
  FSelectedFrameColorX=clWhite;
  FSelectingFrameColorX=clWhite;

  Height=150;
  Width=150;
  FWaveAudio=0;
  
  InfoLeft=0;
  InfoTop=0;

  FExtractMode=WV2_HSELECT;
  FSelectMode=WV2_HSELECT;
  FOpMode=wopIdle;
  FAutoExtractMode=true;
  FRulerAlignX=alTop;
  FRulerAlignY=alLeft;

  FLocalDataOn=false;
  FClickFocus=false;
  FMultiSelect=false;
  FDefaultPopupMenu=true;
  FDefaultPropertyItems=true;
  FProgressCursor=true;
  FYZoomRate=1;
  FStartDigiFreq=0;
	FEndDigiFreq=0.5;

  FSelections=new TWaveViewSelections;

  FFonts[0]=new TFont; FFonts[0]->Color=clBlue; FFonts[0]->Height=12; FFonts[0]->Name="Ariel";
  FFonts[1]=new TFont; FFonts[1]->Color=clYellow; FFonts[1]->Height=12; FFonts[1]->Name="Ariel";
  FFonts[2]=new TFont; FFonts[2]->Color=clYellow; FFonts[2]->Height=12; FFonts[2]->Name="Ariel";
  FFonts[3]=new TFont; FFonts[3]->Color=clWhite; FFonts[3]->Height=12; FFonts[3]->Name="Ariel";
  FFonts[4]=new TFont; FFonts[4]->Color=clBlack; FFonts[4]->Height=12; FFonts[4]->Name="Ariel";
	FFonts[5]=new TFont; FFonts[5]->Color=clBlack; FFonts[5]->Height=12; FFonts[5]->Name="Ariel";

	TWaveViewRulerSetting ASetting={clRed, clWhite, clBlack, TColor(clGray/2), clSilver, 4, 3, FFonts[0], true};
	DefaultRulerSetting[0]=ASetting;
	TWaveViewRulerSetting ASetting1={clLime, clBlack, clYellow, clSilver, clBlack, 4, 3, FFonts[1], false};
	DefaultRulerSetting[1]=ASetting1;
	TWaveViewRulerSetting ASetting2={clLime, clBlack, clYellow, clSilver, clBlack, 4, 3, FFonts[2], false};
	DefaultRulerSetting[2]=ASetting2;
	TWaveViewRulerSetting ASetting3={clRed, clBlack, clYellow, clRed, clBlack, 4, 3, FFonts[3], false};
	DefaultRulerSetting[3]=ASetting3;
	FRulerUnitTime=0;
	FRulerUnitFreq=0;
	FRulerUnitAmp=0;

  DefaultPaneInfoFont=FFonts[4];
  DefaultInfoFont=FFonts[5];

  ItemExtract=NewItem(WV2_STRINGS_Extract,NULL,false,true,NULL,NULL,"ItemExtract");
  ItemPlay=NewItem(WV2_STRINGS_Play, NULL,false,true,NULL,NULL,"ItemPlay");
  ItemProperty=NewItem(WV2_STRINGS_Properties,NULL,false, true,NULL,NULL,"ItemProperty");
  ItemSeparator1=NewItem("-",NULL,false,false,NULL,NULL,"ItemSeparator1");
  ItemXZoomRestore=NewItem(WV2_STRINGS_X_zoom_all,NULL,false,true,NULL,NULL,"ItemXZoomRestore");
  ItemYZoomRestore=NewItem(WV2_STRINGS_Y_zoom_all,NULL,false,true,NULL,NULL,"ItemYZoomRestore");

  TMenuItem** Items=new TMenuItem*[6];
  Items[0]=ItemExtract;
  Items[1]=ItemPlay;
  Items[2]=ItemProperty;
  Items[3]=ItemSeparator1;
  Items[4]=ItemXZoomRestore;
	Items[5]=ItemYZoomRestore;
	FMenu=NewPopupMenu(this, "FMenu", paLeft, true, Items, 5);
  FMenu->AutoHotkeys=maManual;
  FMenu->OnPopup=FMenuPopup;
  delete[] Items;
  PopupMenu=FMenu;

  ItemExtract->OnClick=ItemExtractClick;
  ItemPlay->OnClick=ItemPlayClick;
  ItemProperty->OnClick=ItemPropertyClick;
  ItemXZoomRestore->OnClick=ItemXZoomClick;
  ItemYZoomRestore->OnClick=ItemYZoomClick;
  ItemXZoomRestore->Tag=ITEMXZOOMRESTORE_TAG;
  ItemYZoomRestore->Tag=ITEMYZOOMRESTORE_TAG;
  FScrollBar=0;

  CheckWaveOutDevCaps(this);

  DoubleBuffered=true;

  fw=(cdouble*)malloc8(sizeof(cdouble)*FSpecRes*2); //fft buffer
  fx=&fw[FSpecRes/2]; famp=(double*)&fx[FSpecRes];  //fft buffer
  SetTwiddleFactors(FSpecRes, fw);
  fwin=NewWindow8(FSpecWindowType, FSpecRes, FSpecWindowParamI, FSpecWindowParamD, 0); //window function
  fhbi=CreateBitInvTable(Log2(FSpecRes)-1);

  memset(Basic0, 0, sizeof(Graphics::TBitmap*)*WV2_MAX_CHANNEL);
  memset(Basic1, 0, sizeof(Graphics::TBitmap*)*WV2_MAX_CHANNEL);



  AutoScroll=false;
  ForceOLA=false;
  LoopPlay=false;
  LoopMode=0;
}

__fastcall TWaveView::~TWaveView()
{
  delete FSelections;

  FreeData(FChannels);
  FreeInternalBitmaps(FChannels);
  FreeSpectrograms();

	free8(fwin);
	free8(fw);
	free(fhbi);

  for (int i=0; i<WV2_FONT_NUMBER; i++) delete FFonts[i];
}

//This counts the number of frames to be computed for displaying dX columns of pixels located as frames xx[0:dX-1].
int TWaveView::CalculateSpectrogramX(int channel, double* xx, int dX, bool interpolate)
{
  TQuickSpectrogram* FS=FSpectrogram[channel];
  if (FS->Capacity==0) FS->SetFrCapacity((FLength-FSpecRes)/FSpecOffst+2);
  int calculatefrcount=0;
  for (int x=0; x<dX; x++)
  {
    double ffr=xx[x];
    int fr=floor(ffr);
    if (fr>=0 && fr<FS->Capacity && (interpolate || ffr-fr<=0.5))
    {
      if (FS->Frame[fr]<0 || FS->Valid[fr]==0) calculatefrcount++;
    }
    if (fr+1>=0 && fr+1<FS->Capacity && (interpolate || ffr-fr>=0.5))
    {
      if (FS->Frame[fr+1]<0 || FS->Valid[fr+1]==0) calculatefrcount++;
    }
  }
  return calculatefrcount;  
}

//Set the state of Play menuitem according to device capacity
void __fastcall TWaveView::CheckWaveOutDevCaps(TObject* Sender)
{
  WAVEOUTCAPS woc;
  if (waveOutGetDevCaps(WAVE_MAPPER, &woc, sizeof(woc))==MMSYSERR_NOERROR)
    ItemPlay->Enabled=true;
  else
    ItemPlay->Enabled=false;
}

void __fastcall TWaveView::ClearSelections(TObject* Sender)
{
  FSelections->Clear();
}

void __fastcall TWaveView::Click()
{
  if (ObjectAtPointer && ObjectAtPointer->OnClick) ObjectAtPointer->OnClick(this);
  TControl::Click();
}

void TWaveView::ClearSpectrogram(int index)
{
  FSpectrogram[index]->FreeBuffers();
}

void TWaveView::ClearSpectrograms()
{
  for (int i=0; i<FChannels; i++) FSpectrogram[i]->FreeBuffers();
}

TCursor __fastcall TWaveView::ControlCursorAtPos(int X, int Y)
{
  TWaveViewSelHitTest ht=SelHitTest(X, Y);
  if (ht==selInner)
    return crSizeAll;
  if (ht==selLeft || ht==selRight)
    return crSizeWE;
  if (ht==selTop || ht==selBottom)
    return crSizeNS;
  if (ht==selTopLeft || ht==selBottomRight)
    return crSizeNWSE;
  if (ht==selTopRight || ht==selBottomLeft)
    return crSizeNESW;
  return crArrow;
}

int __fastcall TWaveView::CreatePanes(int X, int Y)
{
  return FPanes.CreatePanes(ClientRect, X, Y);
}

void __fastcall TWaveView::DblClick()
{
  if (FShowInfo && InfoRectAtPointer>=0 && FOnInfoDblClick) FOnInfoDblClick(this);
  if (ObjectAtPointer && ObjectAtPointer->OnDblClick) ObjectAtPointer->OnDblClick(this);
  TControl::DblClick();
}


void __fastcall TWaveView::DefaultShowProperty(bool selection)
{
  if (!selection)
    ShowMessage(AnsiString().sprintf(WV2_STRINGS_1,
                WV2_STRINGS_Properties_current_audio,
                WV2_STRINGS_Time, FEndPos-FStartPos, (FEndPos-FStartPos)*1.0/FSamplesPerSec,
                WV2_STRINGS_From, FStartPos, FStartPos*1.0/FSamplesPerSec,
                WV2_STRINGS_To, FEndPos, FEndPos*1.0/FSamplesPerSec,
                WV2_STRINGS_Frequency, FEndDigiFreq-FStartDigiFreq,
                WV2_STRINGS_From, FStartDigiFreq,
                WV2_STRINGS_To, FEndDigiFreq,
                WV2_STRINGS_Total_time, FLength, FLength*1.0/FSamplesPerSec,
                WV2_STRINGS_Samples_per_second, FSamplesPerSec,
                WV2_STRINGS_Bits_per_sample, FWaveAudio->BitsPerSample
                ));
  else
    ShowMessage(AnsiString().sprintf(WV2_STRINGS_1,
                WV2_STRINGS_Properties_selection,
                WV2_STRINGS_Time, FSelections->Length, (FSelections->Length)*1.0/FSamplesPerSec,
                WV2_STRINGS_From, FSelections->StartPos, FSelections->StartPos*1.0/FSamplesPerSec,
                WV2_STRINGS_To, FSelections->EndPos, FSelections->EndPos*1.0/FSamplesPerSec,
                WV2_STRINGS_Frequency, FSelections->EndDigiFreq-FSelections->StartDigiFreq,
                WV2_STRINGS_From, FSelections->StartDigiFreq,
                WV2_STRINGS_To, FSelections->EndDigiFreq,
                WV2_STRINGS_Total_time, FLength, FLength*1.0/FSamplesPerSec,
                WV2_STRINGS_Samples_per_second, FSamplesPerSec,
                WV2_STRINGS_Bits_per_sample, FWaveAudio->BitsPerSample
                ));
}

void __fastcall TWaveView::DoExtract(TObject* Sender)
{
  if (FCustomItemExtractClick) FCustomItemExtractClick(Sender);
  else
  {
    UndoExtractSelection=GetCurrentRange();
    bool pagechange=false;
    bool spanchange=false;
    if (FExtractMode & WV2_HSELECT)
    {
      if (FEndPos!=FSelections->EndPos) FEndPos=FSelections->EndPos, pagechange=spanchange=true;
      if (FStartPos!=FSelections->StartPos) FStartPos=FSelections->StartPos, pagechange=spanchange=true;
    }
    if (FExtractMode & WV2_VSELECT)
    {
      if (FEndDigiFreq!=FSelections->EndDigiFreq) FEndDigiFreq=FSelections->EndDigiFreq, spanchange=true;
      if (FStartDigiFreq!=FSelections->StartDigiFreq) FStartDigiFreq=FSelections->StartDigiFreq, spanchange=true;
    }
    if (spanchange) Invalidate();
    if (pagechange) PageChange();
  }
}

void __fastcall TWaveView::DrawBitmap(TObject* Sender)
{
  Graphics::TBitmap* bmp=(Graphics::TBitmap*)Sender;
  Canvas->Draw(0, 0, bmp);
}

void __fastcall TWaveView::DrawCaption(AnsiString ACaption)
{
  TSize ASize=Canvas->TextExtent(ACaption);
  SetBkMode(Canvas->Handle, TRANSPARENT);
  Canvas->TextOut(Width-ASize.cx-4, 2, ACaption);
}

void __fastcall TWaveView::DrawCursor(int PaneIndex, int X, int Y)
{
  int m=FPanes.Margin;
  TRect Rect=FPanes.Rect[PaneIndex];
  Canvas->Brush->Style=bsSolid; Canvas->Pen->Mode=pmCopy;
  TColor AColor=(FPanes.Type[PaneIndex]==0)?FCursorColorDim:FCursorColorBright;
  Canvas->Brush->Color=AColor; Canvas->Pen->Color=AColor;

  int xtip, xbott, ytip, ybott;
  TPoint Points[3];
  if (FRulerAlignX==alTop) xtip=Rect.top+2, xbott=Rect.top-m+1;
  else xtip=Rect.bottom-2, xbott=Rect.bottom+m-1;
  Points[0]=Point(X, xtip); Points[1]=Point(X-m/2, xbott); Points[2]=Point(X+m/2, xbott);
  Canvas->Polygon(Points, 2);
  if (FRulerAlignY==alLeft) ytip=Rect.left+2, ybott=Rect.left-m+1;
  else ytip=Rect.right-2, ybott=Rect.right+m-1;
  Points[0]=Point(ytip, Y); Points[1]=Point(ybott, Y-m/2); Points[2]=Point(ybott, Y+m/2);
  Canvas->Polygon(Points, 2);

  Canvas->Brush->Style=bsClear;
  if (FShowCursorText)
  {
    int type=FPanes.Type[PaneIndex];
    TWaveViewRulerSetting RS=DefaultRulerSetting[type];
    Canvas->Font=RS.UnitFont;
    TColor FC=RS.FrontColor, BC=RS.BackColor;
    int textheight=Canvas->TextHeight("0")-2;

    TStringList* List;
    if (FCustomCursorText) List=(TStringList*)FCustomCursorText(this);
    else
    {
      List=new TStringList;
      List->Add(CurrentTime);
      List->Add(AnsiString().sprintf("%.4gs", CurrentTime*1.0/FSamplesPerSec));
      if (FPanes.HasFreqAxis[PaneIndex])
      {
        List->Add(AnsiString().sprintf("%.1ffr", (this->CurrentTime-FSpecRes/2)*1.0/FSpecOffst));
      }
      List->Add("");
      if (FPanes.HasFreqAxis[PaneIndex])
      {
        double f=CurrentDigiFreq*FSamplesPerSec;
        List->Add(AnsiString().sprintf("%.6ghz", f));
        List->Add(AnsiString().sprintf("%.5gbin", CurrentDigiFreq*FSpecRes));
        if (f<WV2_MIN_LOG_FREQ) f=WV2_MIN_LOG_FREQ;
        List->Add(SemitoneToPitch(12*Log2(f/C4)));
      }
      else if (FPanes.Type[PaneIndex]==0)
      {
        int a=floor(FPTA(PaneIndex, Y)+0.5);
        List->Add(AnsiString().sprintf("%d", a));
      }
    }

    int i=0, y=(FRulerAlignX==alTop)?xtip:(xtip-textheight), dtextheight=(FRulerAlignX==alTop)?textheight:-textheight;
    while (i<List->Count && List->Strings[i]!="")
    {
      TextOutline(Canvas, List->Strings[i], X-Canvas->TextWidth(List->Strings[i])/2+1, y, FC, BC);
      y+=dtextheight; i++;
    }
    while (i<List->Count && List->Strings[i]=="") i++;
    y=Y-textheight*(List->Count-i)*0.5;
    while (i<List->Count)
    {
      if (FRulerAlignY==alLeft) TextOutline(Canvas, List->Strings[i], ytip, y, FC, BC);
      else TextOutline(Canvas, List->Strings[i], ytip-Canvas->TextWidth(List->Strings[i]), y, FC, BC);
      y+=textheight; i++;
    }

    delete List;
  }
}

void __fastcall TWaveView::DrawInfo()
{
  TStringList* List;
  if (FCustomInfo) List=(TStringList*)FCustomInfo(this);
  else
  {
    List=new TStringList;
    List->Add(AnsiString().sprintf(" %d-channel, %dhz, %dbit. ", FChannels, FSamplesPerSec, FBytesPerSample*8));
    double fs=FStartPos*1.0/FSamplesPerSec, fe=FEndPos*1.0/FSamplesPerSec;
    List->Add(AnsiString().sprintf(" Time(%.4gs): from %.4gs to %.4gs. ", fe-fs, fs, fe));
    List->Add(AnsiString().sprintf(" Frequency: from %.1fhz to %.1fhz. ", FStartDigiFreq*FSamplesPerSec, FEndDigiFreq*FSamplesPerSec));
  }
  InfoRectCount=List->Count;
  Canvas->Font=DefaultInfoFont;
  Canvas->Brush->Style=bsSolid; Canvas->Font->Color=FInfoColor0;
  int textheight=Canvas->TextHeight("0");
  int left=InfoLeft, right, top=InfoTop, bottom=top+textheight;
  for (int i=0; i<InfoRectCount; i++)
  {
    if (i==InfoRectAtPointer)
    {
      Canvas->Brush->Color=FInfoColor2;
      Canvas->TextOut(left, top, List->Strings[i]);
    }
    else
    {
      Canvas->Brush->Color=FInfoColor1;
      Canvas->TextOut(left, top, List->Strings[i]);
    }
    right=left+Canvas->TextWidth(List->Strings[i]);
    if (i<WV2_INFORECTCOUNT) InfoRect[i]=TRect(left, top, right, bottom);
    left=right;
  }

  delete List;
}

  void DrawRuler(double t1, double t2, TCanvas* Canv, TRect& Rect, TWaveViewRulerSetting RS, TAlign Align, bool ticking)
  {
    double trange=fabs(t2-t1);
    if (trange<=0) return;
    double unit=pow(10, floor(Log10(trange))), tick;
    trange/=unit;

    int tickperunit;
    if (trange<2) {unit*=0.5; tickperunit=5;}
    else if (trange<5) {tickperunit=5;}
    else {unit*=2; tickperunit=4;}
    tick=unit/tickperunit;

    if (!ticking)
    {
      int Y0=Rect.top, Y1=Rect.bottom;
      Canv->Pen->Color=RS.GridColor;
      int u1=ceil(t1/tick), u2=floor(t2/tick);
      if (u1>=u2) {int u3=u1; u1=u2; u2=u3;}
      for (int u=u1; u<=u2; u++)
      {
        double t=(u*tick-t1)/(t2-t1);
        int pos=Rect.left+(Rect.right-Rect.left)*t;
        Canv->MoveTo(pos, Y0);
        Canv->LineTo(pos, Y1);
      }
    }
    else
    {
      Canv->Brush->Style=bsClear;
      Canv->Font=RS.UnitFont;
      int Y0, Y1tick, Y1unit, YText;
      if (Align==alTop) Y0=Rect.top, Y1tick=Rect.top+RS.TickSize, Y1unit=Rect.top+RS.UnitTickSize, YText=Y1unit;
      else Y0=Rect.bottom, Y1tick=Rect.bottom-RS.TickSize, Y1unit=Rect.bottom-RS.UnitTickSize, YText=Y1unit-Canv->TextHeight("0");

      int u1=ceil(t1/tick), u2=floor(t2/tick);
      if (u1>=u2) {int u3=u1; u1=u2; u2=u3;}
      for (int u=u1; u<=u2; u++)
      {
        double t=(u*tick-t1)/(t2-t1);
        int pos=Rect.left+(Rect.right-Rect.left)*t;
        Canv->MoveTo(pos, Y0);
        if (u%tickperunit==0)
        {
          Canv->Pen->Color=RS.UnitTickColor; Canv->LineTo(pos, Y1unit);
          AnsiString text=u*tick;
          TextOutline(Canv, text, pos-Canv->TextWidth(text)/2+1, YText, RS.UnitFont->Color, RS.BackColor);
        }
        else {Canv->Pen->Color=RS.TickColor; Canv->LineTo(pos, Y1tick);}
      }
    }
  }

  void DrawRulerV(double t1, double t2, TCanvas* Canv, TRect& Rect, TWaveViewRulerSetting RS, TAlign Align, bool ticking, AnsiString (*CustomText)(double)=NULL, void (*CustomUnit)(double, double&, int&)=0)
  {
    double trange=fabs(t2-t1);
    if (trange<=0) return;
    double unit=pow(10, floor(Log10(trange)));
    int tickperunit;

    trange/=unit;
    if (CustomUnit) CustomUnit(trange, unit, tickperunit);
    else
    {
      if (trange<2) {unit*=0.2; tickperunit=4;}
      else if (trange<5) {unit*=0.5; tickperunit=5;}
      else {tickperunit=5;}
    }

    double tick=unit/tickperunit;
    if (!ticking)
    {
      int X0=Rect.left, X1=Rect.right;
      Canv->Pen->Color=RS.GridColor;
      int u1=ceil(t1/tick), u2=floor(t2/tick);
      if (u1>=u2) {int u3=u1; u1=u2; u2=u3;}
      for (int u=u1; u<=u2; u++)
      {
        double t=(u*tick-t1)/(t2-t1);
        int pos=Rect.top+(Rect.bottom-Rect.top)*t;
        Canv->MoveTo(X0, pos);
        Canv->LineTo(X1, pos);
      }
    }
    else
    {
      Canv->Brush->Style=bsClear;
      Canv->Font=RS.UnitFont;
      int X0, X1tick, X1unit, XText;
      if (Align==alLeft) X0=Rect.left, X1tick=Rect.left+RS.TickSize, X1unit=Rect.left+RS.UnitTickSize, XText=X1unit;
      else X0=Rect.right, X1tick=Rect.right-RS.TickSize, X1unit=Rect.right-RS.UnitTickSize, XText=X1unit;

      int u1=ceil(t1/tick), u2=floor(t2/tick);
      if (u1>=u2) {int u3=u1; u1=u2; u2=u3;}
      for (int u=u1; u<=u2; u++)
      {
        double t=(u*tick-t1)/(t2-t1);
        int pos=Rect.top+(Rect.bottom-Rect.top)*t;
        Canv->MoveTo(X0, pos);
        if (u%tickperunit==0)
        {
          Canv->Pen->Color=RS.UnitTickColor; Canv->LineTo(X1unit, pos);
          AnsiString text=CustomText?CustomText(u*tick):AnsiString(u*tick);
          TextOutline(Canv, text, (Align==alLeft)?XText:XText-Canv->TextWidth(text), pos-Canv->TextHeight(text)/2, RS.UnitFont->Color, RS.BackColor);
        }
        else {Canv->Pen->Color=RS.TickColor; Canv->LineTo(X1tick, pos);}
      }
    }
  }

  AnsiString SemitoneToHz(double f)
  {
    return AnsiString().sprintf("%.1f", 440*pow(2, f/12));
  }

  void SemitoneUnit(double range, double& unit, int& tickperunit)
  {
    if (range<2) {unit*=0.2; tickperunit=4;}
    else if (range<5) {unit*=0.5; tickperunit=5;}
    else {tickperunit=5;}

    if (unit>2)
    {
      if (unit<6) unit=6, tickperunit=6;
      else if (unit<12) unit=12, tickperunit=6;
      else if (unit<24) unit=24, tickperunit=6;
      else if (unit<36) unit=36, tickperunit=6;
      else if (unit<48) unit=48, tickperunit=4;
      else unit=60, tickperunit=5;
    }
    else if (unit<0.01)
    {
      unit=0.01;
      tickperunit=5;
    }
  }

void __fastcall TWaveView::DrawPane(int Channel, int Type, int YScale, int Rulers, TCanvas* Canv, TRect& Rect)
{
  if (Channel>=FChannels) return;

  Canv->Pen->Mode=pmCopy; Canv->Pen->Style=psSolid; Canv->Brush->Style=bsSolid;

  if (Type==0) {Canv->Brush->Color=FWaveBackColor; Canv->FillRect(Rect);}

  HRGN Rgn=CreateRectRgn(Rect.left, Rect.top, Rect.right, Rect.bottom);
  SelectClipRgn(Canv->Handle, Rgn);

  double TStart, TEnd;
  if (FRulerUnitTime==0) TStart=FStartPos, TEnd=FEndPos;
  else TStart=FStartPos*1.0/FSamplesPerSec, TEnd=FEndPos*1.0/FSamplesPerSec;
  double AStart, AEnd;
  if (FRulerUnitAmp==0) {AStart=-1.0/YZoomRate; AEnd=-AStart;}
  else {AStart=-(1<<(FBytesPerSample*8-1))/YZoomRate; AEnd=-AStart;}
  double FStart, FEnd; AnsiString (*ATranslateFreq)(double); void (*AUnit)(double, double&, int&);
  if (YScale==0)
  {
    if (FRulerUnitFreq==0) FStart=FStartDigiFreq*FSamplesPerSec, FEnd=FEndDigiFreq*FSamplesPerSec;
    else if (FRulerUnitFreq==1) FStart=FStartDigiFreq*FSpecRes, FEnd=FEndDigiFreq*FSpecRes;
    ATranslateFreq=0; AUnit=0;
  }
  else
  {
    FStart=12*Log2(WV2_LOG_FREQ(FStartDigiFreq)*FSamplesPerSec/C4), FEnd=12*Log2(WV2_LOG_FREQ(FEndDigiFreq)*FSamplesPerSec/C4);
    if (FRulerUnitFreq==0) ATranslateFreq=SemitoneToHz;
    else ATranslateFreq=SemitoneToPitch;
    AUnit=SemitoneUnit;
  }

  if (DefaultRulerSetting[Type].pre_drawing)
  {
    if (Rulers&WV2_HSELECT) DrawRuler(TStart, TEnd, Canv, Rect, DefaultRulerSetting[Type], FRulerAlignX, false);
    if (Rulers&WV2_VSELECT && TWaveViewPanes::FreqAxis(Type)) {}
    if (Rulers&WV2_AMP && !TWaveViewPanes::FreqAxis(Type)) DrawRulerV(AEnd, AStart, Canv, Rect, DefaultRulerSetting[Type], FRulerAlignY, false);
  }
  switch(Type)
  {
    case 0:
      DrawWaveForm(Channel, Canv, Rect, FStartPos, FEndPos, FYZoomRate, true);
      break;
    case 1:
    case 2:
      DrawSpectrogramX(Channel, CurrentRange, YScale, Canv, Rect, FSpecAmp, false, true, Type);
      break;
  }

  if (FSelections->Count) DrawSelections(Type, Canv, Rect, YScale);
  if (TWaveViewPanes::FreqAxis(Type) && FTools.Contains(wvtPitchScale)) DrawPitchScale(Type, Canv, Rect, YScale);
  if (FSectionProgress>0 && FProgressCursor) DrawPlaybackCursor(FSectionProgress, Canv, Rect, Type);
  {
    if (Rulers&WV2_HSELECT) DrawRuler(TStart, TEnd, Canv, Rect, DefaultRulerSetting[Type], FRulerAlignX, true);
    if (Rulers&WV2_VSELECT && TWaveViewPanes::FreqAxis(Type)) DrawRulerV(FEnd, FStart, Canv, Rect, DefaultRulerSetting[Type], FRulerAlignY, true, ATranslateFreq, AUnit);
    if (Rulers&WV2_AMP && !TWaveViewPanes::FreqAxis(Type)) DrawRulerV(AEnd, AStart, Canv, Rect, DefaultRulerSetting[Type], FRulerAlignY, true);
  }

  SelectClipRgn(Canv->Handle, NULL);
  DeleteObject(Rgn);
}

void __fastcall TWaveView::DrawPaneInfo(int Channel, int Type, int YScale, TCanvas* Canv, TRect& Rect)
{
  TStringList* List;
  if (FCustomPaneInfo) List=(TStringList*)FCustomPaneInfo(this);
  else
  {
    List=new TStringList;
    if (FSelections->Count>0 && FSelections->Focus>=0)
    {
      List->Add("Current Selection");
      List->Add(AnsiString().sprintf("Time: from %d to %d", FSelections->StartPos, FSelections->EndPos));
      List->Add(AnsiString().sprintf("Frequency: from %.1fhz to %.1fhz", FSelections->StartDigiFreq*FSamplesPerSec, FSelections->EndDigiFreq*FSamplesPerSec));
    }
  }

  if (List->Count>0)
  {
    Canvas->Font=DefaultPaneInfoFont;
    int textheight=Canvas->TextHeight("0"), textwidth=Canvas->TextWidth("0");
    int height=textheight*List->Count+textheight;
    int width=Canvas->TextWidth(List->Strings[0]); for (int i=1; i<List->Count; i++) {int AW=Canvas->TextWidth(List->Strings[i]); if (width<AW) width=AW;} width+=textwidth*2;
    int left, right, top, bottom;
    if (FRulerAlignY==alLeft) {right=Rect.right*0.95+Rect.left*0.05; left=right-width;}
    else {left=Rect.left*0.95+Rect.right*0.05; right=left+width;}
    top=Rect.top*0.9+Rect.bottom*0.1; bottom=top+height;

    Canvas->Pen->Style=psClear; Canvas->Pen->Mode=pmMerge; Canvas->Brush->Style=bsSolid;
    if (Type==0) Canvas->Brush->Color=(TColor)RGB(224, 224, 224);
    else Canvas->Brush->Color=TColor(clGray/2);
    Canvas->Rectangle(left, top, right, bottom);

    Canvas->Brush->Style=bsClear;
    if (Type==0) Canvas->Font->Color=FCursorColorDim;
    else Canvas->Font->Color=FCursorColorBright;
    for (int i=0; i<List->Count; i++)
    {
      if (FRulerAlignY==alLeft) Canvas->TextOut(right-textwidth-Canvas->TextWidth(List->Strings[i]), top+(i+0.5)*textheight, List->Strings[i]);
      else Canvas->TextOut(left+textwidth, top+(i+0.5)*textheight, List->Strings[i]);
    }
  }
  delete List;
}

void __fastcall TWaveView::DrawPanes(int Type)
{
  for (int i=0; i<FPanes.Count; i++)
  {
    int Content=FPanes.Content[i];
    if (Content<0) {Canvas->Brush->Color=FBackColor; Canvas->FillRect(FPanes.Rect[i]);}
    else
    {
      int type=Content/WV2_MAX_CHANNEL, channel=Content%WV2_MAX_CHANNEL;
      if (Type<0 || Type==type) DrawPane(channel, type, FPanes.YScale[i], FPanes.Rulers[i], Canvas, FPanes.Rect[i]);
    }
  }
  if (FCurrentPane>=0 && FShowPaneInfo) DrawPaneInfo(FCurrentPane, FPanes.Type[FCurrentPane], FPanes.YScale[FCurrentPane], Canvas, FPanes.Rect[FCurrentPane]);
}

void __fastcall TWaveView::DrawPitchScale(int Type, TCanvas* Canv, TRect& Rect, int yscale)
{
  int pswidth=100;
  int NumberOfTicks=25;
  double endl=0.7;
  int X=FSTP(CurrentTime, Rect.left, Rect.right);
  int X1=X-pswidth, X2=X+pswidth;
  if (X1-Rect.left<(X-Rect.left)*2/3) X1=Rect.left+(X-Rect.left)*2/3;
  if (Rect.right-X2<(Rect.right-X)*2/3) X2=Rect.right-(Rect.right-X)*2/3;
  int psl=X1-X;
  int psr=X2-X;

  double f0=CurrentDigiFreq/FPitchScalePart;
  int Y0=FDFTP(f0, FStartDigiFreq, FEndDigiFreq, Rect.top, Rect.bottom, yscale);

  double f6=f0*(NumberOfTicks+3);
  int Y6=FDFTP(f6, FStartDigiFreq, FEndDigiFreq, Rect.top, Rect.bottom, yscale);

  Canvas->Pen->Color=TColor(clGray/2);
  Canvas->Pen->Style=psDot;
  Canvas->MoveTo(X, Y0+5);
  Canvas->LineTo(X, Y6);
  Canvas->MoveTo(X1, Y0);
  Canvas->LineTo(X2, Y0);
  Canvas->Pen->Color=clGray;

  for (int k=2; k<=NumberOfTicks; k++)
  {
    f6=(NumberOfTicks-k*endl+1.0)/NumberOfTicks;
    X1=X+psl*f6;
    X2=X+psr*f6;
    Y6=FDFTP(f0*k, FStartDigiFreq, FEndDigiFreq, Rect.top, Rect.bottom, yscale);
    Canvas->MoveTo(X1, Y6);
    Canvas->LineTo(X2, Y6);
  }

  Canvas->Pen->Style=psSolid;
}

void __fastcall TWaveView::DrawPlaybackCursor(int Position, TCanvas* Canv, TRect& Rect, int Type)
{
  int X=FSTP(Position, Rect.left, Rect.right);
  if (X>=Rect.left && X<Rect.right)
  {
    Canv->Pen->Mode=pmCopy;
    Canv->Pen->Style=psSolid;
    Canv->Pen->Color=clGreen;
    if (TWaveViewPanes::FreqAxis(Type))
    {
      Canv->MoveTo(X, Rect.top*0.5+Rect.bottom*0.5);
      Canv->LineTo(X, Rect.bottom);
    }
    else
    {
      Canv->MoveTo(X, Rect.top*0.75+Rect.bottom*0.25);
      Canv->LineTo(X, Rect.top*0.25+Rect.bottom*0.75);
    }
  }
}

void __fastcall TWaveView::DrawSelection(int Type, TCanvas* Canv, TRect& Rect, int yscale, int Index, TColor FrameColorX)
{
  if (Index<0 || Index>=FSelections->Count) return;
  TWaveViewSelection sel=FSelections->Items[Index];

  double x1=FSTP(sel.StartPos, Rect.left, Rect.right);
  double x2=FSTP(sel.EndPos, Rect.left, Rect.right);

  if (!TWaveViewPanes::FreqAxis(Type))
  {
    Canvas->Pen->Mode=pmCopy;
//    Canvas->Pen->Style=psSolid;
    Canvas->Pen->Color=FrameColorX;
    Canvas->MoveTo(x1, Rect.top); Canvas->LineTo(x1, Rect.bottom);
    Canvas->MoveTo(x2, Rect.top); Canvas->LineTo(x2, Rect.bottom);
  }
  else
  {
    int y2=ceil(FDFTP(sel.StartDigiFreq, FStartDigiFreq, FEndDigiFreq, Rect.top, Rect.bottom, yscale));
    int y1=floor(FDFTP(sel.EndDigiFreq, FStartDigiFreq, FEndDigiFreq, Rect.top, Rect.bottom, yscale));
    Canvas->Pen->Mode=pmCopy;
//    Canvas->Pen->Style=psSolid;
    Canvas->Pen->Color=FrameColorX;
    Canvas->Brush->Style=bsClear;
    if (x1==x2 || y1==y2) {Canvas->MoveTo(x1, y1); Canvas->LineTo(x2, y2);}
    else Canvas->Rectangle(x1, y1, x2+1, y2+1);
  }
}

void __fastcall TWaveView::DrawSelections(int Type, TCanvas* Canv, TRect& Rect, int yscale)
{
  for (int i=0; i<FSelections->Count; i++)
  {
    if (i==FSelections->Focus && FOpMode<=wopReselect)
      Canvas->Pen->Style=psSolid;
    else
      Canvas->Pen->Style=psDot;
    DrawSelection(Type, Canv, Rect, yscale, i, FSelectedFrameColorX);
  }
}

void __fastcall TWaveView::DrawSemitones(int start, int end, TCanvas* Canv, TRect& Rect, int yscale)
{
  for (int i=start; i<end; i++)
  {
    int f=FDFTP(440*pow(2, i/12.0)/FSamplesPerSec, FStartDigiFreq, FEndDigiFreq, Rect.top, Rect.bottom, yscale);
    Canv->Pen->Mode=pmXor;
    Canv->Pen->Style=psSolid;
    if (i%12==0)
      Canv->Pen->Color=clGray;
    else
      Canv->Pen->Color=TColor(clGray/2);
    Canv->MoveTo(0, f);
    Canv->LineTo(Width, f);
  }
}

void __fastcall TWaveView::DrawSpectrogramX(int channel, TWaveViewSelection Sel, int yscale, TCanvas* ACanvas, TRect ARect, double amp, bool forceresample, bool basic, int specstyle)
{
  int dX=ARect.Width(), dY=ARect.Height(), HWid=FSpecRes/2;
  double *xx=(double*)malloc8(sizeof(double)*(dX+dY)), *yy=&xx[dX];
  FPTS(xx, dX, Sel.StartPos, Sel.EndPos); for (int x=0; x<dX; x++) xx[x]=(xx[x]-HWid)/FSpecOffst;

  double framepersample=(Sel.EndPos-Sel.StartPos)*1.0/FSpecOffst/dX;
  bool interpolate=(framepersample<1);
  int Fr=CalculateSpectrogramX(channel, xx, dX, interpolate);
  double ampnorm=1.0/(1<<(BytesPerSample*8-4))/sqrt(1.0*FSpecRes);
  if (basic)
  {
  //draw on the internal bitmap Basic1, ignoring ARect and Sel arguments
  //check is the content of Basic1 should be resampled since last time
    bool resample=forceresample|(Fr>0);
    if (!Basic1[channel])
    {
      Graphics::TBitmap* NewBmp=new Graphics::TBitmap;
      NewBmp->PixelFormat=pf32bit;
      NewBmp->Transparent=true;
      NewBmp->TransparentMode=tmAuto;
      Basic1[channel]=NewBmp;
      resample=true;
    }
    sBasic1Settings BS; BS.X=ARect.Width(), BS.Y=ARect.Height(), BS.Sel=Sel, BS.amp=amp, BS.yscale=yscale, BS.specstyle=specstyle;
    if (Basic1Settings[channel]!=BS) {Basic1Settings[channel]=BS; Basic1[channel]->Width=BS.X; Basic1[channel]->Height=BS.Y; resample=true;}

    //resample records sampled range change and picture size change
    if (resample)
    {
      if (FAutoSpecAmp) SetAutomaticSpecAmp(channel, xx, Sel.StartDigiFreq, Sel.EndDigiFreq, BS.X, BS.Y);
      QSPEC_FORMAT maxvisiblespec;
      if (specstyle==1)
      {
        FPTDF(yscale, yy, dY, Sel.StartDigiFreq, Sel.EndDigiFreq); for (int y=0; y<dY; y++) yy[y]=FSpecRes*yy[y];
        maxvisiblespec=SampleSpectrogramX(channel, Basic1[channel], xx, yy, BS.X, BS.Y, amp*ampnorm, interpolate);
      }
      else if (specstyle==2) maxvisiblespec=SamplePeakSpectrogramX(yscale, channel, Sel.StartDigiFreq, Sel.EndDigiFreq, Basic1[channel], xx, BS.X, BS.Y, amp*ampnorm);
      else if (specstyle==3) maxvisiblespec=SampleSinuSpectrogramX(yscale, channel, Sel.StartDigiFreq, Sel.EndDigiFreq, Basic1[channel], xx, BS.X, BS.Y, amp*ampnorm);
      if (!FAutoSpecAmp) maxv_specamp=maxvisiblespec*FSpecAmp;      
    }
    ACanvas->CopyRect(ARect, Basic1[channel]->Canvas, TRect(0, 0, BS.X, BS.Y));
  }
  else
  {
    Graphics::TBitmap* bmp=new Graphics::TBitmap;
    bmp->PixelFormat=pf32bit; bmp->Width=ARect.Width(); bmp->Height=ARect.Height();
    if (specstyle==1)
    {
      FPTDF(yscale, yy, dY, Sel.StartDigiFreq, Sel.EndDigiFreq); for (int y=0; y<dY; y++) yy[y]=FSpecRes*yy[y];
      SampleSpectrogramX(channel, bmp, xx, yy, bmp->Width, bmp->Height, amp*ampnorm, interpolate);
    }
    else if (specstyle==2) SamplePeakSpectrogramX(yscale, channel, Sel.StartDigiFreq, Sel.EndDigiFreq, bmp, xx, bmp->Width, bmp->Height, amp*ampnorm);
    else if (specstyle==3) SampleSinuSpectrogramX(yscale, channel, Sel.StartDigiFreq, Sel.EndDigiFreq, bmp, xx, bmp->Width, bmp->Height, amp*ampnorm);
    ACanvas->CopyRect(ARect, bmp->Canvas, TRect(0, 0, bmp->Width, bmp->Height));
    delete bmp;
  }
  free8(xx);
}

  void __fastcall DrawWaveForm(TCanvas* Canv, TRect ARect, void* data, int BytesPerSample, int startpos, int endpos, double amp, TColor Color1, TColor Color2)
  {
    int i, j, X=ARect.Width(), Y=ARect.Height(), Xs=ARect.left, Ys=ARect.top, Y0=Y/2, hundred=100;
    double Ymax, Ymin;
    int LengthRatio=(endpos-startpos)/X;
    Canv->Pen->Color=Color1;

    amp=Y*amp/(1<<(BytesPerSample*8));

    bool b8=(BytesPerSample==1), b16=(BytesPerSample==2), b24=(BytesPerSample==3);

    if (false)
    {
      unsigned char* Data8=(unsigned char*)data;
      Canv->MoveTo(Xs+0, Ys+Y0-(Data8[startpos]-0x80)*amp);
      if (LengthRatio<4)
        for (i=1; i<X; i++)
          Canv->LineTo(Xs+i, Ys+Y0-(Data8[__int32(startpos+__int64(endpos-startpos)*i/X)]-0x80)*amp);
      else
      {
        for (i=0; i<X; i++)
        {
          int localstart=startpos+__int64(endpos-startpos)*i/X;
          int vlocal, vlocalmin=Data8[localstart];
          int vlocalmax=vlocalmin;

          for (j=1; j<LengthRatio; j++)
          {
            vlocal=Data8[localstart+j];
            if (vlocal<vlocalmin) vlocalmin=vlocal;
            if (vlocal>vlocalmax) vlocalmax=vlocal;
          }
          Ymax=Y0-(vlocalmin-0x80)*amp;
          Ymin=Y0-(vlocalmax-0x80)*amp;
          if (Canv->PenPos.y*2>Ymin+Ymax)
          {
            Canv->LineTo(Xs+i, Ys+Ymax);
            Canv->LineTo(Xs+i, Ys+Ymin);
          }
          else
          {
            Canv->LineTo(Xs+i, Ys+Ymin);
            Canv->LineTo(Xs+i, Ys+Ymax);
          }
        }
      }
    }
    else
    {
      unsigned __int8* data8; __int16* data16; __pint24 data24;

      if (b16) {data16=(__int16*)data; Canv->MoveTo(Xs, Ys+Y0-data16[startpos]*amp);}
      else if (b24) {data24=(__pint24)data; Canv->MoveTo(Xs, Ys+Y0-data24[startpos]*amp);}
      else if (b8) {data8=(unsigned __int8*)data; Canv->MoveTo(Xs, Ys+Y0-(data8[startpos]-0x80)*amp);}

      if (LengthRatio<1)
        for (i=1; i<X; i++)
        {
          if (b16) Canv->LineTo(Xs+i, Ys+Y0-data16[__int32(startpos+__int64(endpos-startpos)*i/X)]*amp);
          else if (b24) Canv->LineTo(Xs+i, Ys+Y0-data24[__int32(startpos+__int64(endpos-startpos)*i/X)]*amp);
          else if (b8) Canv->LineTo(Xs+i, Ys+Y0-(data8[__int32(startpos+__int64(endpos-startpos)*i/X)]-0x80)*amp);
        }
      else
      {
        for (i=0; i<X; i++)
        {
          int vlocalmin, vlocalmax;
          int localstart=startpos+__int64(endpos-startpos)*i/X;
          int vlocal, jump=LengthRatio/hundred+1;
          if (b16) vlocalmin=vlocalmax=data16[localstart];
          else if (b24) vlocalmin=vlocalmax=data24[localstart]; //(*data24)[localstart];
          else if (b8) vlocalmin=vlocalmax=data8[localstart]-0x80;
          for (j=LengthRatio-1; j>0; j-=jump)
          {
            if (b16) vlocal=data16[localstart+j];
            else if (b24) vlocal=data24[localstart+j]; //(*data24)[localstart+j];
            else if (b8) vlocal=data8[localstart+j]-0x80;
            if (vlocal<vlocalmin) vlocalmin=vlocal;
            if (vlocal>vlocalmax) vlocalmax=vlocal;
          }
          double rmin=vlocalmin*amp, rmax=vlocalmax*amp;
          Ymax=Y0-rmin, Ymin=Y0-rmax;
          if (rmin<0 && rmax>0)
          {
            int Y1=(rmax-rmin)/4;
            int Y2=Y0+Y1;
            Y1=Y0-Y1;
            if (Canv->PenPos.y*2>Ymin+Ymax)
            {
              Canv->LineTo(Xs+i, Ys+Ymax);
              Canv->LineTo(Xs+i, Ys+Y2);
              Canv->Pen->Color=Color2;
              Canv->LineTo(Xs+i, Ys+Y1);
              Canv->Pen->Color=Color1;
              Canv->LineTo(Xs+i, Ys+Ymin);
            }
            else
            {
              Canv->LineTo(Xs+i, Ys+Ymin);
              Canv->LineTo(Xs+i, Ys+Y1);
              Canv->Pen->Color=Color2;
              Canv->LineTo(Xs+i, Ys+Y2);
              Canv->Pen->Color=Color1;
              Canv->LineTo(Xs+i, Ys+Ymax);
            }
          }
          else
          {
            if (Canv->PenPos.y*2>Ymin+Ymax)
            {
              Canv->LineTo(Xs+i, Ys+Ymax);
              Canv->LineTo(Xs+i, Ys+Ymin);
            }
            else
            {
              Canv->LineTo(Xs+i, Ys+Ymin);
              Canv->LineTo(Xs+i, Ys+Ymax);
            }
          }
        }
      }
    }
  }

/*
  The internal bitmap Basic0 is used to contain the default waveform bitmap.
  Whenever this method is called with FCanvas assigned to ACanvas, it draws the
  waveform of the current section to Basic1, regardless of AStartPos or AnEndPos.
*/
void __fastcall TWaveView::DrawWaveForm(int channel, TCanvas* ACanvas, TRect ARect, int AStartPos, int AnEndPos, double amp, bool basic)
{
  if (AnEndPos<=AStartPos) return;
  TRect bmpRect;
  Graphics::TBitmap* bmp;
  if (basic)
  {
    bool resample=false;
    if (!Basic0[channel])
    {
      Graphics::TBitmap* NewBmp=new Graphics::TBitmap;
      NewBmp->PixelFormat=pf32bit;
      NewBmp->Transparent=true;
      NewBmp->TransparentMode=tmFixed;
      Basic0[channel]=NewBmp;
      resample=true;
    }
    sBasic0Settings BS={ARect.Width(), ARect.Height(), FStartPos, FEndPos, FYZoomRate};
    if (Basic0Settings[channel]!=BS)
    {
      Basic0Settings[channel]=BS;
      Basic0[channel]->Width=BS.X;
      Basic0[channel]->Height=BS.Y;
      resample=true;
    }
    if (!resample)
    {
      ACanvas->CopyRect(ARect, Basic0[channel]->Canvas, TRect(0, 0, BS.X, BS.Y));
//      ACanvas->Draw(ARect.left, ARect.top, Basic0[channel]);
      return;
    }
    AStartPos=FStartPos, AnEndPos=FEndPos;
    bmp=Basic0[channel];
    bmpRect=TRect(0, 0, BS.X, BS.Y);
  }
  else
  {
    bmp=new Graphics::TBitmap;
    bmp->PixelFormat=pf32bit;
    bmp->Width=ARect.Width();
    bmp->Height=ARect.Height();
    bmpRect=TRect(0, 0, bmp->Width, bmp->Height);
  }

  TCanvas* Canv=bmp->Canvas;
  if (ACanvas==Canvas) Canv->CopyRect(bmpRect, ACanvas, ARect);
  else {Canv->Brush->Color=FWaveBackColor; Canv->FillRect(bmpRect);}
  Canv->Pen->Mode=pmCopy;
  Canv->Pen->Style=psSolid;
  int Y=bmpRect.Height(), X=bmpRect.Width(), Y0=Y/2;
  Canv->Pen->Color=FAxisColor; Canv->MoveTo(0, Y0); Canv->LineTo(X, Y0);
  ::DrawWaveForm(Canv, bmpRect, FData[channel], FBytesPerSample, AStartPos, AnEndPos, FYZoomRate, FWaveColor, FWaveColor2);

  ACanvas->CopyRect(ARect, Canv, bmpRect);
  if (!basic) delete bmp;
}

//Call this when the waveview data buffer is updated externally
void __fastcall TWaveView::ExtDataChange(TObject* Sender)
{
  ClearSpectrograms();
  InvalidateBasic(-1, 0);
}

void __fastcall TWaveView::ExtDataChange(TObject* Sender, int Channel, int From, int To)
{
  FSpectrogram[Channel]->Invalidate(From, To);
  InvalidateBasic(Channel, 0);
  Invalidate();
}

  void WaveViewPlayLoadFrame(double* frame, int n, int readfrom, char* data, int bps, int datalen, int datast, int dataen, bool loop, double* win, double* &loopframe)
  {
    int hn=n/2;
    if (readfrom+n<dataen) IntToDouble2416(frame, &data[readfrom*bps], bps, n);
    else if (readfrom<dataen)
    {
			IntToDouble2416(frame, &data[readfrom*bps], bps, dataen-readfrom);
		}
    else memset(frame, 0, sizeof(double)*n);  //this line shall never be reached

    if (readfrom+n>dataen-hn)
    {                  
      if (!loop)
      {

				if (readfrom>dataen-hn)
				{
					double* lwin=&win[n-(dataen-readfrom)];
					for (int i=0; i<dataen-readfrom; i++) frame[i]*=lwin[i];
				}
				else if (readfrom>dataen-n)
				{
					double* lwin=&win[n-(dataen-readfrom)];
					for (int i=dataen-hn-readfrom; i<dataen-readfrom; i++) frame[i]*=lwin[i];
				}
				else
				{
					double *lframe=&frame[dataen-hn-readfrom], *lwin=&win[hn];
					for (int i=0; i<hn; i++) lframe[i]*=lwin[i];
				}
			}
			else if (readfrom+n>dataen-hn/2) //notice it is during looped playback
			{
				if (readfrom>dataen-hn/2 && loopframe)
				{
					memcpy(frame, &loopframe[readfrom-(dataen-hn/2)], sizeof(double)*(hn-readfrom+dataen-hn/2));
					IntToDouble2416(&frame[dataen-readfrom+hn/2], &data[(datast+hn/2)*bps], bps, n-dataen+readfrom-hn/2);
				}
				else if (readfrom+hn>dataen-hn/2 && loopframe)
				{
					memcpy(&frame[dataen-hn/2-readfrom], loopframe, sizeof(double)*hn);
					IntToDouble2416(&frame[dataen+hn/2-readfrom], &data[(datast+hn/2)*bps], bps, n-dataen+readfrom-hn/2);
				}
				else
				{
				//calculate loopframe, of size hn
					free8(loopframe);
					loopframe=(double*)malloc8(sizeof(double)*n);
					double* looped=&loopframe[hn];
					if (dataen+hn/2<=datalen) IntToDouble2416(loopframe, &data[(dataen-hn/2)*bps], bps, hn);
					else
					{
						IntToDouble2416(loopframe, &data[(dataen-hn/2)*bps], bps, datalen-(dataen-hn/2));
						memset(&loopframe[datalen-(dataen-hn/2)], 0, sizeof(double)*(dataen+hn/2-datalen));
					}
          if (datast>hn/2) IntToDouble2416(looped, &data[(datast-hn/2)*bps], bps, hn);
          else
          {
            memset(looped, 0, sizeof(double)*(hn/2-datast));
            IntToDouble2416(&looped[hn/2-datast], data, bps, datast+hn/2);
          }
          for (int i=0; i<hn; i++) loopframe[i]=loopframe[i]*(1-win[i])+looped[i]*(win[i]);

          memcpy(&frame[dataen-readfrom-hn/2], loopframe, sizeof(double)*(readfrom+n-(dataen-hn/2)));
        }
      }
    }
    else if (loop && readfrom<datast+hn/2 && loopframe)
    {
      memcpy(frame, &loopframe[hn/2+readfrom-datast], sizeof(double)*(hn/2-readfrom+datast));
    }
  }

//playback filter used for unbuffered play has in-frame time alias
int __fastcall TWaveView::FillBlock(void* ABlock)
{
  char* Block=(char*)ABlock;
  FSectionBlocks++;
  bool stereo=(FSection->Channels==2);
  int PlayBytesPerSample=(FBytesPerSample<3)?FBytesPerSample:2;

  int BlockSize=FSection->BlockSize/PlayBytesPerSample/FSection->Channels;
  if (LoopPlay && LoopMode)
  {
    if (LoopMode==1) //loop visible
      FSectionStartPos=FStartPos, FSectionEndPos=FEndPos;
    else if (LoopMode==2)//loop selection
			FSectionStartPos=FSelections->StartPos, FSectionEndPos=FSelections->EndPos;
		if (PBPR<FSectionStartPos) PBPR=FSectionStartPos, ForceOLA=true;
  }

  char *lData0, *lData1;

  if (stereo)
  {
    if (FStereoMode==wvpSwap) lData0=(char*)FData[1], lData1=(char*)FData[0];
    else lData0=(char*)FData[0], lData1=(char*)FData[1];
  }
  else lData0=(char*)((FChannels>1 && FStereoMode==wvpRight)?FData[1]:FData[0]);

  int Wid=FSpecRes, HWid=Wid/2;
  while (PBPA<BlockSize && PBPR<FSectionEndPos)
  {
    int newPBPR=PBPR+HWid, newPBPA;
    if (newPBPR>=FSectionEndPos)
    {
      if (!LoopPlay) newPBPR=FSectionEndPos, newPBPA=PBPA+FSectionEndPos-PBPR;
      else newPBPR=newPBPR-FSectionEndPos+FSectionStartPos, newPBPA=PBPA+HWid;// looping=true;
    }
    else newPBPA=PBPA+HWid;

    double k=GetFMask(famp, PBPR, FPlaybackFilter);

    if (k==0)
    {
      if (prevk==1)
      {
        for (int i=0; i<HWid; i++) PlayBuffer0[PBPA+i]*=fw2[HWid+i];
        if (stereo) for (int i=0; i<HWid; i++) PlayBuffer1[PBPA+i]*=fw2[HWid+i];
      }
      memset(&PlayBuffer0[PBPA+HWid], 0, sizeof(double)*HWid);
      if (stereo) memset(&PlayBuffer1[PBPA+HWid], 0, sizeof(double)*HWid);
    }
    else
    {
      double* dbfx=(double*)fx;
      WaveViewPlayLoadFrame(dbfx, Wid, PBPR, lData0, FBytesPerSample, FLength, FSectionStartPos, FSectionEndPos, LoopPlay, fw2, loopframe0);

      //filtering
      if (k!=1)
      {
        RFFTCW(dbfx, fw1, NULL, NULL, Log2(Wid), fw, fx, fhbi);
        for (int i=0; i<=HWid; i++) fx[i].x*=famp[i], fx[i].y*=famp[i];
        CIFFTR(fx, Log2(Wid), fw, dbfx, fhbi);
      }

      //first half frame
      if (k==1 && prevk==1 && !ForceOLA) {}
      else
      {
        if (prevk==1) for(int i=0; i<HWid; i++) PlayBuffer0[PBPA+i]*=fw2[HWid+i];
        if (k==1) for (int i=0; i<HWid; i++) PlayBuffer0[PBPA+i]+=dbfx[i]*fw2[i];
        else for (int i=0; i<HWid; i++) PlayBuffer0[PBPA+i]+=dbfx[i]*fw2_fw1[i];
        ForceOLA=false;
      }

      //second half frame
      if (k==1) for (int i=HWid; i<Wid; i++) PlayBuffer0[PBPA+i]=dbfx[i];
      else for (int i=HWid; i<Wid; i++) PlayBuffer0[PBPA+i]=dbfx[i]*fw2_fw1[i];

      if (stereo)
      {
        //load frame
        WaveViewPlayLoadFrame(dbfx, Wid, PBPR, lData1, FBytesPerSample, FLength, FSectionStartPos, FSectionEndPos, LoopPlay, fw2, loopframe1);

        //filtering
        if (k!=1)
        {
          RFFTCW(dbfx, fw1, NULL, NULL, Log2(Wid), fw, fx, fhbi);
          for (int i=0; i<=HWid; i++) fx[i].x*=famp[i], fx[i].y*=famp[i];
          CIFFTR(fx, Log2(Wid), fw, dbfx, fhbi);
        }

        //first half frame
        if (k==1 && prevk==1 && !ForceOLA) {}
        else
        {
          if (prevk==1) for(int i=0; i<HWid; i++) PlayBuffer1[PBPA+i]*=fw2[HWid+i];
          if (k==1) for (int i=0; i<HWid; i++) PlayBuffer1[PBPA+i]+=dbfx[i]*fw2[i];
          else for (int i=0; i<HWid; i++) PlayBuffer1[PBPA+i]+=dbfx[i]*fw2_fw1[i];
        }

        //second half frame
        if (k==1) for (int i=HWid; i<Wid; i++) PlayBuffer1[PBPA+i]=dbfx[i];
        else for (int i=HWid; i<Wid; i++) PlayBuffer1[PBPA+i]=dbfx[i]*fw2_fw1[i];
      }
    }
    PBPR=newPBPR;
    PBPA=newPBPA;
    prevk=k;
  }

  if (stereo) DoubleToIntInterleave(Block, PlayBytesPerSample, &PlayBuffer0[PBPW], &PlayBuffer1[PBPW], BlockSize);
  else DoubleToInt(Block, PlayBytesPerSample, &PlayBuffer0[PBPW], BlockSize);

  PBPW+=BlockSize;
  if (PBPA-PBPW<BlockSize)
  {
    if (PBPA-PBPW+HWid>0)
    {
      memmove(PlayBuffer0, &PlayBuffer0[PBPW], sizeof(double)*(PBPA-PBPW+HWid));
      if (stereo) memmove(PlayBuffer1, &PlayBuffer1[PBPW], sizeof(double)*(PBPA-PBPW+HWid));
    }
    PBPA-=PBPW; PBPW=0;
  }
  if (PBPA>0) return FSection->BlockSize;
  else return (BlockSize+PBPA)*PlayBytesPerSample*FSection->Channels;
}

void __fastcall TWaveView::FMenuPopup(TObject* Sender)
{
  ItemExtract->Visible=(FLength>0 && FSelections->Count>0 && FSelections->Focus>=0);
  ItemPlay->Visible=(FLength>0);
  ItemProperty->Visible=(FLength>0);
  ItemXZoomRestore->Visible=(FLength>0 && (FStartPos!=0 || FEndPos!=FLength));
  bool hfa=FPanes.HasFreqAxis[FCurrentPane];
  ItemYZoomRestore->Visible=(FLength>0 && (!hfa && YZoomRate!=1 || hfa && (FStartDigiFreq!=0 || FEndDigiFreq!=0.5)));
}

void __fastcall TWaveView::FocusSelection(int Index)
{
  if (Index>=0 && Index<FSelections->Count && Index!=FSelections->Focus)
  {
    FSelections->Focus=Index;
    if (FOnSelectedChange) FOnSelectedChange(this);
    Invalidate();
  }
}

void __fastcall TWaveView::FreeData(int ch)
{
  if (ch>=FChannels || ch<0)
  {
    for (int i=0; i<FChannels; i++)
    {
      delete[] FData[i];
      FData[i]=0;
    }
  }
  else
  {
    delete[] FData[ch];
    FData[ch]=0;
  }
}

void __fastcall TWaveView::FreeInternalBitmaps(int ch)
{
  if (ch>=FChannels || ch<0)
  {
    for (int i=0; i<FChannels; i++)
    {
      delete Basic0[i]; Basic0[i]=0;
      delete Basic1[i]; Basic1[i]=0;
    }
  }
  else
  {
    delete Basic0[ch]; Basic0[ch]=0;
    delete Basic1[ch]; Basic1[ch]=0;
  }
}

void __fastcall TWaveView::FreeSpectrograms()
{
  for (int i=0; i<WV2_MAX_CHANNEL; i++)
  {
    delete FSpectrogram[i]; FSpectrogram[i]=0;
  }
}

double __fastcall TWaveView::FromAmplitudeToPixel(int PaneIndex, double Amplitude)
{
  if (FPanes.Type[PaneIndex]) return 0;
  int top=FPanes.Rect[PaneIndex].top, height=FPanes.Rect[PaneIndex].Height();
  if (FBytesPerSample==0) Amplitude-=128;
  return top+height/2-Amplitude*YZoomRate*height/(1<<(FBytesPerSample*8));
}

double __fastcall TWaveView::FromPixelToAmplitude(int PaneIndex, double Y)
{
  if (FPanes.Type[PaneIndex]) return 0;
  int top=FPanes.Rect[PaneIndex].top, height=FPanes.Rect[PaneIndex].Height();
  double ap=-1.0*(Y-(top+height/2))/height;
  double result=(1<<(FBytesPerSample*8))*ap/YZoomRate;
  if (FBytesPerSample==1) result+=128;
  return result;
}

double __fastcall TWaveView::FromDigiFreqToPixel(int PaneIndex, double DigiFreq)
{
  if (FEndDigiFreq-FStartDigiFreq<=0) return 0;
  if (!FPanes.HasFreqAxis[PaneIndex]) return 0;
  if (FPanes.YScale[PaneIndex]==1) //log scale
  {
    double AStartDigiFreq=WV2_LOG_FREQ(FStartDigiFreq),
           AnEndDigiFreq=WV2_LOG_FREQ(FEndDigiFreq);
    DigiFreq=WV2_LOG_FREQ(DigiFreq);
    return FPanes.Rect[PaneIndex].top+FPanes.Rect[PaneIndex].Height()*log(DigiFreq/AnEndDigiFreq)/log(AStartDigiFreq/AnEndDigiFreq);
  }
  else
    return FPanes.Rect[PaneIndex].top+FPanes.Rect[PaneIndex].Height()*(DigiFreq-FEndDigiFreq)/(FStartDigiFreq-FEndDigiFreq);
}

double __fastcall TWaveView::FromDigiFreqToPixel(double DigiFreq, double AStartDigiFreq, double AnEndDigiFreq, int Y1, int Y2, int YScale)
{
  if (FEndDigiFreq-FStartDigiFreq<=0) return 0;
  if (YScale==1) //log scale
  {
    AStartDigiFreq=WV2_LOG_FREQ(AStartDigiFreq),
    AnEndDigiFreq=WV2_LOG_FREQ(AnEndDigiFreq);
    DigiFreq=WV2_LOG_FREQ(DigiFreq);
    return Y1+(Y2-Y1)*log(DigiFreq/AnEndDigiFreq)/log(AStartDigiFreq/AnEndDigiFreq);
  }
  else
    return Y1+(Y2-Y1)*(DigiFreq-AnEndDigiFreq)/(AStartDigiFreq-AnEndDigiFreq);
}

double __fastcall TWaveView::FromPixelToDigiFreq(int PaneIndex, double Y)
{
  if (FEndDigiFreq-FStartDigiFreq<0) return 0;
  if (!FPanes.HasFreqAxis[PaneIndex]) return 0;
  double ap=1.0*(Y-FPanes.Rect[PaneIndex].top)/FPanes.Rect[PaneIndex].Height();
  if (FPanes.YScale[PaneIndex]==1) //log scale
  {
    double AStartDigiFreq=WV2_LOG_FREQ(FStartDigiFreq),
           AnEndDigiFreq=WV2_LOG_FREQ(FEndDigiFreq);
    return AnEndDigiFreq*pow(AStartDigiFreq/AnEndDigiFreq, ap);
  }
  else
    return FEndDigiFreq+(FStartDigiFreq-FEndDigiFreq)*ap;
}

void __fastcall TWaveView::FromPixelToDigiFreq(int YScale, double* DigiFreq, int Count, double AStartDigiFreq, double AnEndDigiFreq)
{
  if (FEndDigiFreq-FStartDigiFreq<0) {memset (DigiFreq, 0, sizeof(double)*Count); return;}
  if (YScale==0) //linear scale
  {
    double StartEndDigiFreq=(AnEndDigiFreq-AStartDigiFreq)/Count;
    double AS=AStartDigiFreq+StartEndDigiFreq*0.5;
    for (int i=0; i<Count; i++) DigiFreq[i]=AS+StartEndDigiFreq*i;
  }
  else if (YScale==1) //log scale
  {
    if (AStartDigiFreq<WV2_MIN_LOG_FREQ) AStartDigiFreq=WV2_MIN_LOG_FREQ;
    if (AnEndDigiFreq<WV2_MIN_LOG_FREQ) AnEndDigiFreq=WV2_MIN_LOG_FREQ;
    double StartEndDigiFreq=pow(AnEndDigiFreq/AStartDigiFreq, 1.0/Count);
    for (int i=0; i<Count; i++) DigiFreq[i]=AStartDigiFreq*pow(StartEndDigiFreq, i+0.5);
  }
}

double __fastcall TWaveView::FromPixelToSample(int PaneIndex, double X)
{
  if (PaneIndex<0) return 0;
  if (FEndPos-FStartPos<0) return 0;
  else if (FEndPos-FStartPos==0) return FStartPos;
  else return FStartPos+(X-FPanes.Rect[PaneIndex].left)*(FEndPos-FStartPos)/FPanes.Rect[PaneIndex].Width();
}

double __fastcall TWaveView::FromPixelToSample(double X, int AStartPos, int AnEndPos, int X1, int X2)
{
  if (AnEndPos-AStartPos<0) return 0;
  else if (AnEndPos-AStartPos==0) return FStartPos;
  else return AStartPos+(X-X1)*(AnEndPos-AStartPos)/(X2-X1);
}

void __fastcall TWaveView::FromPixelToSample(double* Sample, int Count, int AStartPos, int AnEndPos)
{
  if (AnEndPos-AStartPos<0) memset(Sample, 0, sizeof(double)*Count);
  else if (AnEndPos-AStartPos==0) for (int i=0; i<Count; i++) Sample[i]=AStartPos;
  else
  {
    double EndStartCount=(AnEndPos-AStartPos)*1.0/Count;
    double As=AStartPos+0.5*EndStartCount;
    for (int i=0; i<Count; i++) Sample[i]=As+i*EndStartCount;
  }
}

double __fastcall TWaveView::FromSampleToPixel(int PaneIndex, double Pos)
{
  return FPanes.Rect[PaneIndex].left+(Pos-FStartPos)*1.0*FPanes.Rect[PaneIndex].Width()/(FEndPos-FStartPos);
}

double __fastcall TWaveView::FromSampleToPixel(double Pos, int X1, int X2)
{
  return X1+(Pos-FStartPos)*(X2-X1)/(FEndPos-FStartPos);
}

int __fastcall TWaveView::GetCurrentChannel()
{
  return FPanes.Channel[FCurrentPane];
}

double __fastcall TWaveView::GetCurrentDigiFreq()
{
  double result=(FCurrentDigiFreq1+FCurrentDigiFreq2)/2;
  if (result<0) return 0;
  else if (result>0.5) return 0.5;
  else return result;
}

QSPEC_FORMAT* __fastcall TWaveView::GetA(int Channel, int fr)
{
  return FSpectrogram[Channel]->A(fr);
}

double __fastcall TWaveView::GetCurrentAmplitude()
{
  return FPTA(FCurrentPane, FY);
}

//There is no SetCurrentRange() method. Use SetArea().
TWaveViewSelection __fastcall TWaveView::GetCurrentRange()
{
  TWaveViewSelection sel={FStartPos, FEndPos, FStartDigiFreq, FEndDigiFreq};
  return sel;
}

__int16 __fastcall TWaveView::GetCurrentSample16()
{
  return Data16[FPanes.Channel[FCurrentPane]][CurrentTime];
}

int __fastcall TWaveView::GetCurrentSampleInPixel()
{
  if (FBytesPerSample==1) return FATP(FCurrentPane, Data8[FPanes.Channel[FCurrentPane]][CurrentTime]);
  else if (FBytesPerSample==2) return FATP(FCurrentPane, Data16[FPanes.Channel[FCurrentPane]][CurrentTime]);
  else return FATP(FCurrentPane, Data24[FPanes.Channel[FCurrentPane]][CurrentTime]);
}

int __fastcall TWaveView::GetCurrentTimeEx()
{
  return (FCurrentTime1+FCurrentTime2)/2;//+0.5;
}

void* __fastcall TWaveView::GetData(int Channel)
{
  return FData[Channel];
}

__int16* __fastcall TWaveView::GetData16(int Channel)
{
  return (__int16*)(FData[Channel]);
}

__pint24 __fastcall TWaveView::GetData24(int Channel)
{
  return (__pint24)(FData[Channel]);
}

char* __fastcall TWaveView::GetData8(int Channel)
{
  return (char*)(FData[Channel]);
}

double __fastcall TWaveView::GetFMask(double* mask, int t, TWaveViewPlaybackFilter Filter)
{
  double result=0;

  if (Filter==wvfPass) memset(mask, 0, FSpecRes*sizeof(double));
  else for (int i=0; i<FSpecRes; i++) mask[i]=1;

  if (Filter==wvfNone) result=1;
  else
  {
    int LF1, LF2;
    for (int i=0; i<FSelections->Count; i++)
    {
      TWaveViewSelection sel=FSelections->Items[i];
      if (t<sel.StartPos || t>sel.EndPos) continue;
      LF1=sel.StartDigiFreq*FSpecRes+0.5;
      LF2=sel.EndDigiFreq*FSpecRes+0.5;
      if (LF1<0) LF1=0;
      if (LF2>FSpecRes/2+1) LF2=FSpecRes/2+1;
      if (Filter==wvfPass) for (int j=LF1; j<=LF2; j++) mask[j]=1;
      else for (int j=LF1; j<=LF2; j++) mask[j]=0;
    }
    for (int i=FSpecRes/2+1; i<FSpecRes; i++) mask[i]=mask[FSpecRes-i];
    for (int i=0; i<FSpecRes; i++) result+=mask[i];
    result/=FSpecRes;
  }
  return result;
}

void __fastcall TWaveView::GetObjectAtPointer(int X, int Y)
{
  ObjectAtPointer=0;
  for (int i=0; i<FObjects.Count; i++)
  {
    if (FObjects.Items[i].Options & wvoNoMouseFocus) continue;
    TRect Rect=FObjects.Items[i].Rect;
    if (X>=Rect.left && X<Rect.right && Y>=Rect.top && Y<Rect.bottom)
    {
      ObjectAtPointer=&FObjects.Items[i];
      break;
    }
  }
}

//WaveView.cpp implements basic idle mode (-1), drag mode (0), select mode (1) and re-select mode (2)
//other modes are defined externally
void __fastcall TWaveView::GetOpMode(Word Key, TMouseButton Button, TShiftState Shift)
{
  if (FCurrentPane>=0)
  {
    FOpMode=wopSelect;
    if (FOnGetOpMode) FOnGetOpMode(this, Shift, FOpMode);
  }
  else
    FOpMode=wopIdle; //Idle mode

  TCursor ACursor;
  if (FOpMode==wopDrag) ACursor=crHandPoint;
  else if (FOpMode==wopSelect) ACursor=crArrow;
  else ACursor=Cursor;
  if (ACursor!=Cursor) {Cursor=ACursor;::SetCursor(Screen->Cursors[Cursor]);}
}

QSPEC_FORMAT* __fastcall TWaveView::GetPh(int Channel, int fr)
{
  return FSpectrogram[Channel]->Ph(fr);
}

cmplx<QSPEC_FORMAT>* __fastcall TWaveView::GetSpec(int Channel, int fr)
{
  return FSpectrogram[Channel]->Spec(fr);
}

TQuickSpectrogram* __fastcall TWaveView::GetSpectrogram(int Channel)
{
	return FSpectrogram[Channel];
}

bool __fastcall TWaveView::GetPlaying()
{
  return FSection->Playing;
}

double __fastcall TWaveView::GetSpecWindowParamD(int Index)
{
  return FSpecWindowParamD[Index];
}

void __fastcall TWaveView::InvalidateBasic(int channel, int basic)
{
  if (basic==0)
    if (channel>=0) Basic0Settings[channel].Y=0;
    else for (int i=0; i<WV2_MAX_CHANNEL; i++) Basic0Settings[i].Y=0;
  if (basic==1)
    if (channel>=0) Basic1Settings[channel].Y=0;
    else for (int i=0; i<WV2_MAX_CHANNEL; i++) Basic1Settings[i].Y=0;
}

void __fastcall TWaveView::InvalidateBasics(int channel)
{
  Basic0Settings[channel].Y=0;
  Basic1Settings[channel].Y=0;
}

void __fastcall TWaveView::ItemExtractClick(TObject* Sender)
{
  if (FLength>0)
  {
    bool Shift=(GetKeyState(VK_SHIFT)<0);
    DoExtract(Sender);
    if (!Shift) RemoveSelection(-1);
  }
}

void __fastcall TWaveView::ItemPlayClick(TObject* Sender)
{
  StartPlayback(Sender);
}

void __fastcall TWaveView::ItemPropertyClick(TObject* Sender)
{
  if (FLength>0)
  {
    if (FStartSelR<FSelections->StartPos ||FStartSelR>=FSelections->EndPos
        ||FPanes.HasFreqAxis[FStartPane] && (FVStartSelR<FSelections->StartDigiFreq||FVStartSelR>=FSelections->EndDigiFreq))
    {
      if (DefaultPropertyItems) DefaultShowProperty(false);
      else if (FCustomProperty) FCustomProperty(this, false);
    }
    else
    {
      if (DefaultPropertyItems) DefaultShowProperty(true);
      else if (FCustomProperty) FCustomProperty(this, true);
    }
  }
}

void __fastcall TWaveView::ItemXZoomClick(TObject* Sender)
{
  if (FCustomXZoomClick) FCustomXZoomClick(Sender);
  else
  {
    if (dynamic_cast<TComponent*>(Sender)->Tag==ITEMXZOOMIN_TAG)
      Zoom(FStartSelR, 0.8);
    else if (dynamic_cast<TComponent*>(Sender)->Tag==ITEMXZOOMOUT_TAG)
      Zoom(FStartSelR, 3);
    else if (dynamic_cast<TComponent*>(Sender)->Tag==ITEMXZOOMRESTORE_TAG)
      Zoom(FStartSelR, 0);
  }
}

void __fastcall TWaveView::ItemYZoomClick(TObject* Sender)
{
  if (FCustomYZoomClick) FCustomYZoomClick(Sender);
  else
  {
    if (dynamic_cast<TComponent*>(Sender)->Tag==ITEMYZOOMIN_TAG) YZoomRate*=1.5;
    else if (dynamic_cast<TComponent*>(Sender)->Tag==ITEMYZOOMRESTORE_TAG)
    {
      YZoomRate=1;
      FStartDigiFreq=0;
      FEndDigiFreq=0.5;
    }
  }
}

void __fastcall TWaveView::KeyDown(Word &Key, TShiftState Shift)
{
  GetOpMode(0, TMouseButton(0), Shift);
  if (FLength>0)
  {
    if (FOpMode<=wopReselect && FCurrentPane>=0)
    {
      switch(Key)
      {
        case VK_BACK:
        {
          int k=Selections->Count-1;
          RemoveSelection(k);
          break;
        }
        case VK_DELETE:
          RemoveSelection(-1);
          break;
        case VK_ESCAPE:
          ClearSelections(this);
          break;
        case VK_NEXT:
          if (Selections->Count>0)
          {
            int k=(Selections->Focus+1)%Selections->Count;
            FocusSelection(k);
          }
          break;
        case VK_PRIOR:
          if (Selections->Count>0)
          {
            int k=(Selections->Focus+Selections->Count-1)%Selections->Count;
            FocusSelection(k);
          }
          break;
      }
    }
    else if (ObjectAtPointer && ObjectAtPointer->OnKeyDown) ObjectAtPointer->OnKeyDown(this, Key, Shift);
  }

  TWinControl::KeyDown(Key, Shift);
}

void __fastcall TWaveView::KeyUp(Word &Key, TShiftState Shift)
{
  GetOpMode(0, TMouseButton(0), Shift);
  TWinControl::KeyUp(Key, Shift);
}

void __fastcall TWaveView::MouseCursor(TShiftState Shift, int X, int Y)
{
  FX=X, FY=Y, FLastShiftState=Shift;
}

void __fastcall TWaveView::MouseDown(TMouseButton Button, TShiftState Shift, int X, int Y)
{
  WaveViewHitTest(X, Y);
  GetOpMode(0, Button, Shift);

  MouseCursor(Shift, X, Y);
  if (Button==mbLeft && FOnMousePointer) MousePointer(Shift, X, Y);

  if (Shift.Contains(ssShift) && FTools.Contains(wvtPlayNote)){double f=FSamplesPerSec*CurrentDigiFreq; if (f>0){if (FTools.Contains(wvtPitchScale)) PlayNote(f/FPitchScalePart, FPlayNoteInSemitone); else PlayNote(f, FPlayNoteInSemitone);}}

  if (FLength>=1)
  {
    FStartSel=CurrentTime;
    FVStartSel=CurrentDigiFreq;
    FStartPane=FCurrentPane;
    if (FStartPane>=0)
    {
      FStartSelR=FPTS(FCurrentPane, X);
      FVStartSelR=FPTDF(FCurrentPane, Y);
    }
    FStartSelX=X;
    FStartSelY=Y;

    if (FOpMode==wopSelect)
    {
      if (Button==mbLeft && FCurrentPane>=0)
      {
        if (FMultiSelect)
        {
          int slp=SelectionAtPos(CurrentTime, CurrentDigiFreq);
          FLastFocus=(slp>=0)?slp:FSelections->Focus;
        }

        if (FSelections->Count && !MultiSelect) Selections->Clear();

        FStartSelX=X;
        FStartSelY=Y;

        TWaveViewSelection sel;
        if (FSelectMode==WV2_HSELECT)
        {
          sel.StartPos=sel.EndPos=FStartSel;
          sel.StartDigiFreq=FStartDigiFreq, sel.EndDigiFreq=FEndDigiFreq;
          FSelections->Add(sel);
        }
        else if (FSelectMode==WV2_VSELECT)
        {
          sel.StartPos=FStartPos, sel.EndPos=FEndPos;
          sel.StartDigiFreq=sel.EndDigiFreq=FVStartSel;
          FSelections->Add(sel);
        }
        else if (FSelectMode==(WV2_HSELECT|WV2_VSELECT))
        {
          sel.StartPos=sel.EndPos=FStartSel;
          sel.StartDigiFreq=sel.EndDigiFreq=FVStartSel;
          FSelections->Add(sel);
        }
        else {}
        if (FOnMouseLocalData && !FLocalDataOn) MouseLocalData(X, Y, 1);
      }
    }
    else if (FOpMode==wopDrag) //drag mode
    {
      StartDrag(X, Y);
    }
    else if (FOpMode==wopReselect && Button==mbLeft && FCurrentPane>=0)
    {
      if (Button==mbLeft)
      {
        FStartSelHitTest=SelHitTest(X, Y);
        FStartSelSel=(*FSelections)[FSelections->Focus];
      }
    }
    else //external mode
    {
      if (ObjectAtPointer && ObjectAtPointer->OnMouseDown)
      {
        StartObject=ObjectAtPointer;
        ObjectAtPointer->OnMouseDown(this, Button, Shift, X, Y);
      }
      else
      {
        StartObject=0;
      }
    }
  }
  TControl::MouseDown(Button, Shift, X, Y);
  FLastX=X, FLastY=Y;
  Invalidate();
}

//Down=1: mouse down; -1, -2: mouse move pair; -3: mouse up
void __fastcall TWaveView::MouseLocalData(int X, int Y, int Down)
{
  int t=CurrentTime, Wid=FSpecRes, HWid=Wid/2;
  int FLength_Wid=FLength-Wid;

  int t0=t-HWid, tp=t, tn=t-Wid;

  if (FLocalDataTimeGrid)
  {
    int hoffst=FSpecOffst/2;
    t0=(t0+hoffst)/FSpecOffst*FSpecOffst;
    tp=(tp+hoffst)/FSpecOffst*FSpecOffst;
    tn=(tn+hoffst)/FSpecOffst*FSpecOffst;
    t=(t+hoffst)/FSpecOffst*FSpecOffst;
  }

  if (t0<0) t0=0;
  else if (t0>FLength_Wid) t0=FLength_Wid;
  if (tp<0) tp=0;
  else if (tp>FLength_Wid) tp=FLength_Wid;
  if (tn<0) tn=0;
  else if (tn>FLength_Wid) tn=FLength_Wid;

  FLocalDataOn=true;
  FOnMouseLocalData(this, &Data8[0][t0*FBytesPerSample], &Data8[0][tp*FBytesPerSample], &Data8[0][tn*FBytesPerSample], Wid, FBytesPerSample, CurrentTime, CurrentDigiFreq, Down);
  FLocalDataOn=false;
}

void __fastcall TWaveView::MousePointer(TShiftState Shift, int X, int Y)
{
  FOnMousePointer(this, FCurrentPane, CurrentTime, CurrentDigiFreq);
}

void __fastcall TWaveView::MouseMove(TShiftState Shift, int X, int Y)
{
  WaveViewHitTest(X, Y);
  MouseCursor(Shift, X, Y);
  if (Shift.Contains(ssLeft) && FOnMousePointer) if (X!=FLastX || Y!=FLastY) MousePointer(Shift, X, Y);

  if (!Shift.Contains(ssLeft)) GetOpMode(0, (TMouseButton)0, Shift);

  if (FLength>=1)
  {
    if (FOpMode==wopSelect && FStartPane>=0) //select mode
    {
      if ((X!=FLastX || Y!=FLastY) && Shift.Contains(ssLeft))
      {
        TWaveViewSelection sel;
        int ACurrentTime=FPTS(FStartPane, X+0.5)+0.5; if (ACurrentTime<0) ACurrentTime=0; else if (ACurrentTime>FLength) ACurrentTime=FLength;
        double ACurrentDigiFreq=FPTDF(FStartPane, Y+0.5); if (ACurrentDigiFreq<0) ACurrentDigiFreq=0; else if (ACurrentDigiFreq>0.5) ACurrentDigiFreq=0.5;
        if (FSelectMode==WV2_HSELECT)
        {
          if (ACurrentTime>FStartSel) sel.StartPos=FStartSel, sel.EndPos=ACurrentTime;
          else sel.StartPos=ACurrentTime, sel.EndPos=FStartSel;
          sel.StartDigiFreq=FStartDigiFreq, sel.EndDigiFreq=FEndDigiFreq;
          FSelections->Items[FSelections->Focus]=sel;
        }
        else if (FSelectMode==WV2_VSELECT)
        {
          sel.StartPos=FStartPos, sel.EndPos=FEndPos;
          if (ACurrentDigiFreq>FVStartSel) sel.StartDigiFreq=FVStartSel, sel.EndDigiFreq=ACurrentDigiFreq;
          else sel.StartDigiFreq=ACurrentDigiFreq, sel.EndDigiFreq=FVStartSel;
          FSelections->Items[FSelections->Focus]=sel;
        }
        else if (FSelectMode==(WV2_HSELECT|WV2_VSELECT))
        {
          if (ACurrentTime>FStartSel) sel.StartPos=FStartSel, sel.EndPos=ACurrentTime;
          else sel.StartPos=ACurrentTime, sel.EndPos=FStartSel;
          if (ACurrentDigiFreq>FVStartSel) sel.StartDigiFreq=FVStartSel, sel.EndDigiFreq=ACurrentDigiFreq;
          else sel.StartDigiFreq=ACurrentDigiFreq, sel.EndDigiFreq=FVStartSel;
          FSelections->Items[FSelections->Focus]=sel;
        }
        if (FOnMouseLocalData && !FLocalDataOn) MouseLocalData(X, Y, -2);
      }
    }
    else if (FOpMode==wopDrag && FStartPane>=0) //drag mode
    {
      if ((Shift.Contains(ssLeft)||true)  && (X!=FLastX || Y!=FLastY))
      {
        double lLength=FStartSelSel.EndPos-FStartSelSel.StartPos;
        if (lLength>0)
        {
          double lWidth=FPanes.Rect[FStartPane].Width();
          FStartPos=FStartSelSel.StartPos-(X-FStartSelX)*lLength/lWidth;
          if (FStartPos<0) FStartPos=0;
          FEndPos=FStartPos+lLength;
          if (FEndPos>FLength) {FEndPos=FLength; FStartPos=FEndPos-lLength;}
          if (!FPanes.HasFreqAxis[FStartPane]) {}//waveform
          else
          {
            if (FPanes.YScale[FStartPane]==0) //linear scale
            {
              lLength=FStartSelSel.EndDigiFreq-FStartSelSel.StartDigiFreq;
              lWidth=FPanes.Rect[FStartPane].Height();
              FStartDigiFreq=FStartSelSel.StartDigiFreq-(FStartSelY-Y)*lLength/lWidth;
              if (FStartDigiFreq<0) FStartDigiFreq=0;
              FEndDigiFreq=FStartDigiFreq+lLength;
              if (FEndDigiFreq>0.5) {FEndDigiFreq=0.5; FStartDigiFreq=FEndDigiFreq-lLength;}
            }
            else //log scale
            {
              lLength=WV2_LOG_FREQ(FStartSelSel.EndDigiFreq)/WV2_LOG_FREQ(FStartSelSel.StartDigiFreq);
              lWidth=FPanes.Rect[FStartPane].Height();
              FStartDigiFreq=WV2_LOG_FREQ(FStartSelSel.StartDigiFreq)*pow(lLength, (Y-FStartSelY)/lWidth);
              FEndDigiFreq=FStartDigiFreq*lLength;
              if (FEndDigiFreq>0.5) {FEndDigiFreq=0.5; FStartDigiFreq=FEndDigiFreq/lLength;}
            }
          }
          PageChange();
        }
      }
    }
    else if (FOpMode==wopReselect && FStartPane>=0) //re-selecting mode
    {
      if (Shift.Contains(ssLeft) && FStartSelHitTest!=selOuter)
      {
        int NewStartPos, NewEndPos;
        double NewStartDigiFreq, NewEndDigiFreq;

        if ((FStartSelHitTest & WV2_LEFT) && FStartSelX!=X) NewStartPos=FPTS(FStartPane, FSTP(FStartPane, FStartSelSel.StartPos)+X-FStartSelX)+0.5;
        else NewStartPos=FStartSelSel.StartPos;
        if ((FStartSelHitTest & WV2_RIGHT) && FStartSelX!=X) NewEndPos=FPTS(FStartPane, FSTP(FStartPane, FStartSelSel.EndPos)+X-FStartSelX)+0.5;
        else NewEndPos=FStartSelSel.EndPos;
        if ((FStartSelHitTest & WV2_TOP) && FPanes.HasFreqAxis[FStartPane] && FStartSelY!=Y) NewEndDigiFreq=FPTDF(FStartPane, FDFTP(FStartPane, FStartSelSel.EndDigiFreq)+Y-FStartSelY);
        else NewEndDigiFreq=FStartSelSel.EndDigiFreq;
        if ((FStartSelHitTest & WV2_BOTTOM) && FPanes.HasFreqAxis[FStartPane] && FStartSelY!=Y) NewStartDigiFreq=FPTDF(FStartPane, FDFTP(FStartPane, FStartSelSel.StartDigiFreq)+Y-FStartSelY);
        else NewStartDigiFreq=FStartSelSel.StartDigiFreq;

        TWaveViewSelection sel={NewStartPos, NewEndPos, NewStartDigiFreq, NewEndDigiFreq};
        if (NewStartPos>NewEndPos) sel.StartPos=NewEndPos, sel.EndPos=NewStartPos;
        if (NewStartDigiFreq>NewEndDigiFreq) sel.StartDigiFreq=NewEndDigiFreq, sel.EndDigiFreq=NewStartDigiFreq;
        if (sel.StartPos<0) sel.StartPos=0; else if (sel.EndPos>FLength) sel.EndPos=FLength;
        if (sel.StartDigiFreq<0) sel.StartDigiFreq=0; else if (sel.EndDigiFreq>0.5) sel.EndDigiFreq=0.5;
        FSelections->Items[FSelections->Focus]=sel;
      }
      else 
      {
        TCursor ACursor=ControlCursorAtPos(X, Y);;
        if (Cursor!=ACursor)
        {
          Cursor=ACursor;
          ::SetCursor(Screen->Cursors[Cursor]);
        }
      }
    }
    else //external mode
    {
      if (Shift.Contains(ssLeft) && StartObject)
      {
        if (StartObject->OnMouseMove) StartObject->OnMouseMove(this, Shift, X, Y);
      }
      else if (ObjectAtPointer && ObjectAtPointer->OnMouseMove)
        ObjectAtPointer->OnMouseMove(this, Shift, X, Y);
    }

    if (Shift.Contains(ssLeft) || Shift.Contains(ssRight)) FLastX=X, FLastY=Y;
  }
  if (FClickFocus && !Focused()) SetFocus();
  TControl::MouseMove(Shift, X, Y);
  Invalidate();
}

void __fastcall TWaveView::MouseUp(TMouseButton Button, TShiftState Shift, int X, int Y)
{
  WaveViewHitTest(X, Y);
  MouseCursor(Shift, X, Y);
  if (FLength>=1)
  {
    if (FOpMode==wopSelect && FStartPane>=0)
    {
      if (Button==mbLeft)
      {
        if (FSelections->Focus>=0)
        {
          TWaveViewSelection sel=FSelections->Items[FSelections->Focus];
          if (sel.StartPos==sel.EndPos || sel.StartDigiFreq==sel.EndDigiFreq)
          {
            FSelections->Delete(FSelections->Focus);
            if (FMultiSelect) FSelections->Focus=FLastFocus;
          }
        }
      }
    }
    else if (FOpMode==wopReselect && FStartPane>=0)
    {
      if (FSelections->Focus>=0  && FStartSelHitTest!=selOuter)
      {
        TWaveViewSelection sel=FSelections->Items[FSelections->Focus];
        if (FSTP(FStartPane, sel.EndPos)-FSTP(FStartPane, sel.StartPos)<0.01
          || FPanes.HasFreqAxis[FStartPane] && FDFTP(FStartPane, sel.StartDigiFreq)-FDFTP(FStartPane, sel.EndDigiFreq)<0.01)
        {
          FSelections->Delete(FSelections->Focus);
        }
      }
    }
    else //external mode
    {
      if (StartObject)
      {
        if (StartObject->OnMouseUp) StartObject->OnMouseUp(this, Button, Shift, X, Y);
        StartObject=0;
      }
      else if (ObjectAtPointer && ObjectAtPointer->OnMouseUp)
        ObjectAtPointer->OnMouseUp(this, Button, Shift, X, Y);
    }
  }

  if (Button==mbLeft || Button==mbRight) FLastX=X, FLastY=Y;
  TControl::MouseUp(Button, Shift, X, Y);
  Invalidate();
}

void __fastcall TWaveView::MouseWheelHandler(TMessage& Msg)
{
  TWMMouseWheel* WMsg=(TWMMouseWheel*)&Msg;

  bool Handled=false; TShiftState Shift; memcpy(&Shift, &WMsg->Keys, 1);
  if (ObjectAtPointer && ObjectAtPointer->OnMouseWheel) ObjectAtPointer->OnMouseWheel(this, Shift, WMsg->WheelDelta, TPoint(WMsg->Pos.x, WMsg->Pos.y), Handled);
  else if (FCurrentPane>=0 && !DisableMouseWheelZoom)
  {
    if (Shift.Empty())
    {
      int X=CurrentTime;

      if (X>=FStartPos && X<=FEndPos)
      {
        if (WMsg->WheelDelta<0) Zoom(X, 0.9);
        else Zoom(X, 10.0/9);
      }
    }
    else
    {
      if (!FPanes.HasFreqAxis[FCurrentPane])
      {
        if (WMsg->WheelDelta>0) ZoomY(0.9);
        else ZoomY(10.0/9);
      }
      else
      {
        double Y=CurrentDigiFreq;
        if (Y>=FStartDigiFreq && Y<=FEndDigiFreq)
        {
          if (WMsg->WheelDelta<0) ZoomF(Y, 0.9, FPanes.YScale[FCurrentPane]);
          else ZoomF(Y, 10.0/9, FPanes.YScale[FCurrentPane]);
        }
      }
    }
  }
  if (OnMouseWheel) OnMouseWheel(this, Shift, WMsg->WheelDelta, TPoint(WMsg->Pos.x, WMsg->Pos.y), Handled);
  if (!Handled) TCustomControl::MouseWheelHandler(Msg);
}

void __fastcall TWaveView::PageChange(bool updatescrollbar)
{
  if (FScrollBar && updatescrollbar) UpdateScrollBar(this);
  if (FOnPageChange) FOnPageChange(this);
}

//The Paint() methos draws form StartPos to EndPos in the
//WaveAudio stream, stretching it to fit the whole width
//Selected area is highlighted.
void __fastcall TWaveView::Paint()
{
  if (FOnCustomPaint)
  {
    bool Done;
    FOnCustomPaint(this, Done);
    if (Done)
    {
      if (FOnPaint) FOnPaint(this);
      return;
    }
  }

  if (FLength>0)
  {
    Canvas->Brush->Color=FBackColor; Canvas->FillRect(ClientRect);
    DrawPanes();
    if (FOnPaint) FOnPaint(this);
    if (FShowInfo) DrawInfo();
  }
  if (FObjects.Count>0) for (int i=0; i<FObjects.Count; i++) if (FObjects.Items[i].DrawObject) FObjects.Items[i].DrawObject(this, FObjects.Items[i]);

  if (FShowCursor && FCurrentPane>=0) DrawCursor(FCurrentPane, FX, FY);
}

void __fastcall TWaveView::PausePlayback(TObject* Sender)
{
  if (FSection && FSection->Playing) FSection->PausePlayback(this);
}

void __fastcall TWaveView::Post(int Mode)
{
  if (!FWaveAudio) return;
  FWaveAudio->OnAudioChange=NULL;

  //for a waveaudio using file stream, set file mode to RW
  if (!FWaveAudio->UseMemoryStream)
  {
    int pos=FWaveAudio->FFileStream->Position;
    delete FWaveAudio->FFileStream->File;
    FWaveAudio->FFileStream->File=new TFileStream(FWaveAudio->FileName, fmOpenReadWrite);
    FWaveAudio->FFileStream->Position=pos;
  }

  int Channels=FWaveAudio->Channels;

  int writefrom, writelength;
  if (Mode==0)
  {
    writefrom=0;
    writelength=FLength;
  }
  else if (Mode==1)
  {
    writefrom=FStartPos;
    writelength=FEndPos-FStartPos;
  }
  int writefrombytes=writefrom*FBytesPerSample*Channels;

  if (Channels==1)
  {
    FWaveAudio->Seek(writefrombytes, soFromBeginning);
    FWaveAudio->Write(&Data8[0][writefrombytes], writelength*FBytesPerSample);
  }
  else
  {
    //-------------------------
    {
      if (FWaveAudio->UseMemoryStream)
      {
        char* databuf=&((unsigned char*)((TMemoryStream*)FWaveAudio->WaveStream)->Memory)[writefrombytes];
        char* data=&((char*)FData)[writefrom*FBytesPerSample];
        for (int k=0; k<writelength; k++)
        {
          memcpy(data, databuf, FBytesPerSample);
          data+=FBytesPerSample;
          databuf+=FBytesPerSample*Channels;
        }
      }
      else
      {
        FWaveAudio->Seek(writefrombytes, soFromCurrent);
        char* data=&((char*)FData)[writefrom*FBytesPerSample];
        for (int k=0; k<writelength; k++)
        {
          FWaveAudio->WaveStream->Read(data, FBytesPerSample);
          data+=FBytesPerSample;
          FWaveAudio->Seek(FBytesPerSample*(Channels-1), soFromCurrent);
        }
      }
    }
    //---------------------------
  }

  //for a waveaudio using file stream, set file mode to back to R after posting
  if (!FWaveAudio->UseMemoryStream)
  {
    int pos=FWaveAudio->FFileStream->Position;
    delete FWaveAudio->FFileStream->File;
    FWaveAudio->FFileStream->File=new TFileStream(FWaveAudio->FileName, fmOpenRead);
    FWaveAudio->FFileStream->Position=pos;
  }
  FWaveAudio->OnAudioChange=WaveViewAudioChange;
}

void __fastcall TWaveView::RemoveSelection(int Index)
{
  if (Index==-1) Index=FSelections->Focus;
  if (Index<0 || Index>=FSelections->Count) return;
  if (Index==FSelections->Focus)
  {
    FSelections->Delete(Index);
    if (FOnSelectedChange) FOnSelectedChange(this);
  }
  else
  {
    FSelections->Delete(Index);
  }
  Invalidate();
}

void __fastcall TWaveView::Resize()
{
  FPanes.ResizePanes(ClientRect);
  TControl::Resize();
}

void __fastcall TWaveView::Retrieve(int Mode, int from, int length)
{
  if (!FWaveAudio || FWaveAudio->Length==0) return;

  int readfrom, readlength;
  if (Mode==0)
  {
    readfrom=0;
    readlength=FLength;
  }
  else if (Mode==1)
  {
    readfrom=FStartPos;
    readlength=FEndPos-FStartPos;
  }
  else if (Mode==2)
  {
    readfrom=from;
    readlength=length;
  }
  int readfrombytes=readfrom*FBytesPerSample*FChannels;
  FWaveAudio->Seek(readfrombytes, soFromBeginning);

  if (FChannels==1)
  {
    FWaveAudio->Read(&Data8[0][readfrombytes], readlength*FBytesPerSample);
    ExtDataChange(this, 0, readfrom, readfrom+readlength);
  }
  else if (FChannels==2)
  {
    void *FFData0=&Data8[0][readfrom*FBytesPerSample],
         *FFData1=&Data8[1][readfrom*FBytesPerSample];
    FWaveAudio->ReadSamplesInterleave(FFData0, FFData1, readlength);
    ExtDataChange(this, 0, readfrom, readfrom+readlength);
    ExtDataChange(this, 1, readfrom, readfrom+readlength);
  }
  else
  {
    void* FFData[WV2_MAX_CHANNEL];
    for (int i=0; i<FChannels; i++) FFData[i]=&Data8[i][readfrom*FBytesPerSample];
    FWaveAudio->ReadSamplesMultiChannel(FChannels, FFData, readlength);
    for (int i=0; i<FChannels; i++) ExtDataChange(this, i, readfrom, readfrom+readlength);
  }
}

#define MyRGB(R, G, B) RGB(B, G, R)
//#define MyRGB(R, G, B) RGB(R, G, B)

  void SampleImageLine(double* vyy, int dY, double* yy, TQuickSpectrogram* Spectrogram, int fr)
  {
    QSPEC_FORMAT* Spec=Spectrogram->A(fr);
    if (!Spec) {memset(vyy, 0, sizeof(double)*dY); return;}
    else
    {
      int yyd, oldyyd=-2, hWid=Spectrogram->Wid/2;
      double yyr, v0, v1, v2, v3, a, b, c;
      for (int y=0; y<dY; y++)
      {
        if (yy[y]<0 || yy[y]>hWid) vyy[y]=0;
        else
        {
          yyd=floor(yy[y]); yyr=yy[y]-yyd;
          if (yyr==0) vyy[y]=Spec[yyd];
          else
          {
            if (yyd!=oldyyd)
            {
              oldyyd=yyd; v1=Spec[yyd], v2=Spec[yyd+1];
              if (yyd==0) v0=Spec[1]; else v0=Spec[yyd-1];
              if (yyd==hWid-1) v3=Spec[hWid-1]; else v3=Spec[yyd+2];

              if (v1>v0 && v1>v3 && v2>v0 && v2>v3) //a local peak -> use quadratic interpolation
              {
                if (v0<v3) //use v1, v2, v3
                {
                  c=v1; a=(v3+c)/2-v2; b=v2-a-c;
                }
                else //use v0, v1, v2
                {
                  b=(v2-v0)/2; c=v1; a=v2-b-c;
                }
              }
              else //use linear interpolation
              {
                a=0; b=v2-v1; c=v1;
              }
            }
            vyy[y]=(a*yyr+b)*yyr+c;
          }
        }
      }
    }
  }

QSPEC_FORMAT __fastcall SampleImageX(TQuickSpectrogram* Spectrogram, Graphics::TBitmap* ABmp, int dX, int dY, double* xx, double* yy, double amp, bool interpolate)
{
  BITMAPFILEHEADER bh;
  bh.bfType=0x4d42;
  bh.bfSize=0x36+4*dX*dY;
  bh.bfReserved1=bh.bfReserved2=0;
  bh.bfOffBits=0x36;

  BITMAPINFOHEADER bi;
  memset(&bi, 0, sizeof(bi));
  bi.biSize=0x28;
  bi.biWidth=dX;
  bi.biHeight=dY;
  bi.biPlanes=1;
  bi.biBitCount=0x20;
  bi.biSizeImage=4*dX*dY;

  TMemoryStream* AS=new TMemoryStream;
  AS->Size=bh.bfSize;
  char* ASM=(char*)AS->Memory;
  memcpy(ASM, &bh, sizeof(bh));
  memcpy(&ASM[sizeof(bh)], &bi, sizeof(bi));
  int* pixs=(int*)&ASM[sizeof(bh)+sizeof(bi)];

  double xxr, maxv=0;
  int xxd, sampr, sampg, sampb;

  double *vyys0=(double*)malloc8(sizeof(double)*(dY+dY)), *vyys1=&vyys0[dY];

  int oldxxd=-3;

  if (interpolate)
  {
    for (int x=0; x<dX; x++)
    {
      xxd=floor(xx[x]);
      xxr=xx[x]-xxd;
      if (xxd<-1 || xxd>=Spectrogram->Capacity)
      {
        for (int y=0; y<dY; y++) pixs[x+y*dX]=0;
        continue;
      }

      if (xxd==oldxxd) {}
      else if (xxd==oldxxd+1)
      {
        memcpy(vyys0, vyys1, sizeof(double)*dY);
        for (int i=0; i<dY; i++) if (maxv<vyys0[i]) maxv=vyys0[i];
        if (xxd+1<Spectrogram->Capacity) SampleImageLine(vyys1, dY, yy, Spectrogram, xxd+1);
        else memset(vyys1, 0, sizeof(double)*dY);
      }
      else
      {
        if (xxd>=0 && xxd<Spectrogram->Capacity)
        {
          SampleImageLine(vyys0, dY, yy, Spectrogram, xxd);
          for (int i=0; i<dY; i++) if (maxv<vyys0[i]) maxv=vyys0[i];
        }
        else memset(vyys0, 0, sizeof(double)*dY);
        if (xxd+1<Spectrogram->Capacity) SampleImageLine(vyys1, dY, yy, Spectrogram, xxd+1);
        else memset(vyys1, 0, sizeof(double)*dY);
      }

      for (int y=0; y<dY; y++)
      {
        double vyy=vyys0[y]+(vyys1[y]-vyys0[y])*xxr;
        double samp=amp*vyy;
        if (samp>=1)
          sampr=255, sampg=samp*32, sampb=0;
        else
          sampg=0, sampr=255*samp, sampb=0;
        pixs[x+y*dX]=TColor(MyRGB(sampr, sampg, sampb));
      }
      oldxxd=xxd;
    }
  }
  else
  {
    for (int x=0; x<dX; x++)
    {
      xxd=floor(xx[x]+0.5);
      if (xxd<-1 || xxd>=Spectrogram->Capacity)
      {
        for (int y=0; y<dY; y++) pixs[x+y*dX]=0;
        continue;
      }

      if (xxd==oldxxd)
      {
        for (int y=0; y<dY; y++) pixs[x+y*dX]=pixs[x-1+y*dX]; //this shall rarely be executed
        continue;
      }
      else
      {
        if (xxd>=0 && xxd<Spectrogram->Capacity)
        {
          SampleImageLine(vyys0, dY, yy, Spectrogram, xxd);
          for (int i=0; i<dY; i++) if (maxv<vyys0[i]) maxv=vyys0[i];
        }
        else memset(vyys0, 0, sizeof(double)*dY); //this shall be never executed
        for (int y=0; y<dY; y++)
        {
          double samp=amp*vyys0[y];
          if (samp>=1)
            sampr=255, sampg=samp*32, sampb=0;
          else
            sampg=0, sampr=255*samp, sampb=0;
          pixs[x+y*dX]=TColor(MyRGB(sampr, sampg, sampb));
        }
      }
      oldxxd=xxd;
    }
  }

  AS->Position=0;
  ABmp->LoadFromStream(AS);
  delete AS;

  free8(vyys0);
  return maxv;
}

QSPEC_FORMAT __fastcall TWaveView::SampleSpectrogramX(int channel, Graphics::TBitmap* ABmp, double* xx, double* yy, int dX, int dY, double amp, bool interpolate)
{
  return SampleImageX(FSpectrogram[channel], ABmp, dX, dY, xx, yy, amp, interpolate);
}

QSPEC_FORMAT __fastcall TWaveView::SamplePeakSpectrogramX(int yscale, int channel, double AStartDigiFreq, double AnEndDigiFreq, Graphics::TBitmap* ABmp, double* xx, int dX, int dY, double amp)
{
  TQuickSpectrogram* Spectrogram=FSpectrogram[channel];

  BITMAPFILEHEADER bh;
  bh.bfType=0x4d42;
  bh.bfSize=0x36+4*dX*dY;
  bh.bfReserved1=bh.bfReserved2=0;
  bh.bfOffBits=0x36;

  BITMAPINFOHEADER bi;
  memset(&bi, 0, sizeof(bi));
  bi.biSize=0x28;
  bi.biWidth=dX;
  bi.biHeight=dY;
  bi.biPlanes=1;
  bi.biBitCount=0x20;
  bi.biSizeImage=4*dX*dY;

  TMemoryStream* AS=new TMemoryStream;
  AS->Size=bh.bfSize;
  char* ASM=(char*)AS->Memory;
  memcpy(ASM, &bh, sizeof(bh));
  memcpy(&ASM[sizeof(bh)], &bi, sizeof(bi));
  int* pixs=(int*)&ASM[sizeof(bh)+sizeof(bi)];

  int xxd, sampr, sampg, sampb;

  double *vyys0=(double*)malloc8(sizeof(double)*dY), maxv=0;

  int oldxxd=-3;

  for (int x=0; x<dX; x++)
  {
    xxd=floor(xx[x]+0.5);
    if (xxd<0 || xxd>=Spectrogram->Capacity)
    {
      for (int y=0; y<dY; y++) pixs[x+y*dX]=0;
      continue;
    }

    if (xxd==oldxxd) {}
    else
    {
      memset(vyys0, 0, sizeof(double)*dY);
      QSPEC_FORMAT* spec=Spectrogram->A(xxd);
      int istart=AStartDigiFreq*FSpecRes-1;
      int iend=AnEndDigiFreq*FSpecRes+1;
      if (istart<1) istart=1;
      if (iend>FSpecRes/2) iend=FSpecRes/2;
      for (int i=istart; i<iend; i++)
      {
        if (spec[i]>spec[i-1] && spec[i]>spec[i+1])
        {
          if (maxv<spec[i]) maxv=spec[i];
          double digif=(i+0.5*(spec[i-1]-spec[i+1])/(spec[i-1]+spec[i+1]-2*spec[i]))/FSpecRes;
          int p=FDFTP(digif, AStartDigiFreq, AnEndDigiFreq, dY, 0, yscale);
          if (p>=0 && p<dY) vyys0[p]=spec[i];
        }
      }
    }

    for (int y=0; y<dY; y++)
    {
      double samp=amp*vyys0[y];
      if (samp>=1) sampr=255, sampg=samp*32, sampb=0;
      else sampg=0, sampr=255*samp, sampb=0;
      pixs[x+y*dX]=TColor(MyRGB(sampr, sampg, sampb));
    }
    oldxxd=xxd;
  }

  AS->Position=0;
  ABmp->LoadFromStream(AS);
  delete AS;

  free8(vyys0);
  return maxv;
}

QSPEC_FORMAT __fastcall TWaveView::SampleSinuSpectrogramX(int yscale, int channel, double AStartDigiFreq, double AnEndDigiFreq, Graphics::TBitmap* ABmp, double* xx, int dX, int dY, double amp)
{
  TQuickSpectrogram* Spectrogram=FSpectrogram[channel];

  BITMAPFILEHEADER bh;
  bh.bfType=0x4d42;
  bh.bfSize=0x36+4*dX*dY;
  bh.bfReserved1=bh.bfReserved2=0;
  bh.bfOffBits=0x36;

  BITMAPINFOHEADER bi;
  memset(&bi, 0, sizeof(bi));
  bi.biSize=0x28;
  bi.biWidth=dX;
  bi.biHeight=dY;
  bi.biPlanes=1;
  bi.biBitCount=0x20;
  bi.biSizeImage=4*dX*dY;

  TMemoryStream* AS=new TMemoryStream;
  AS->Size=bh.bfSize;
  char* ASM=(char*)AS->Memory;
  memcpy(ASM, &bh, sizeof(bh));
  memcpy(&ASM[sizeof(bh)], &bi, sizeof(bi));
  int* pixs=(int*)&ASM[sizeof(bh)+sizeof(bi)];

  int xxd, sampr, sampg, sampb;

  double *vyys0=(double*)malloc8(sizeof(double)*dY), maxv=0;

  int oldxxd=-3;

  for (int x=0; x<dX; x++)
  {
    xxd=floor(xx[x]+0.5);
    if (xxd<0 || xxd>=Spectrogram->Capacity)
    {
      for (int y=0; y<dY; y++) pixs[x+y*dX]=0;
      continue;
    }

    if (xxd==oldxxd) {}
    else
    {
      memset(vyys0, 0, sizeof(double)*dY);
      QSPEC_FORMAT* spec=Spectrogram->A(xxd);
      int istart=AStartDigiFreq*FSpecRes-1;
      int iend=AnEndDigiFreq*FSpecRes+1;
      if (istart<1) istart=1;
      if (iend>FSpecRes/2) iend=FSpecRes/2;
      for (int i=istart; i<iend; i++)
      {
        if (spec[i]>spec[i-1] && spec[i]>spec[i+1])
        {
          if (maxv<spec[i]) maxv=spec[i];
          double digif=(i+0.5*(spec[i-1]-spec[i+1])/(spec[i-1]+spec[i+1]-2*spec[i]))/FSpecRes;
          int p=FDFTP(digif, AStartDigiFreq, AnEndDigiFreq, dY, 0, yscale);
          if (p>=0 && p<dY) vyys0[p]=spec[i];
        }
      }
    }

    for (int y=0; y<dY; y++)
    {
      double samp=amp*vyys0[y];
      if (samp>=1) sampr=255, sampg=samp*32, sampb=0;
      else sampg=0, sampr=255*samp, sampb=0;
      pixs[x+y*dX]=TColor(MyRGB(sampr, sampg, sampb));
    }
    oldxxd=xxd;
  }

  AS->Position=0;
  ABmp->LoadFromStream(AS);
  delete AS;

  free8(vyys0);
  return maxv;
}

void __fastcall TWaveView::ScrollBarChange(TObject* Sender)
{
  int ALength=FEndPos-FStartPos;
  if (ALength==0) return;
  if (FScrollBar->Position>FScrollBar->Max-FScrollBar->PageSize)
    FScrollBar->Position=FScrollBar->Max-FScrollBar->PageSize;
  int startpos=FScrollBar->Position;
  bool willdozoom=true;
  if (willdozoom)
  {
    FStartPos=startpos;
    FEndPos=FStartPos+ALength;
    Invalidate();
    PageChange(false);
  }
  else UpdateScrollBar(this);
}

int __fastcall TWaveView::SelectionAtPos(int Pos, double DigiFreq)
{
  int result=-1;
  double marea;
  for (int i=0; i<FSelections->Count; i++)
  {
    TWaveViewSelection sel=FSelections->Items[i];
    if (Pos>=sel.StartPos && Pos<sel.EndPos && DigiFreq>=sel.StartDigiFreq && DigiFreq<sel.EndDigiFreq)
    {
      if (result==-1) result=i, marea=(sel.EndDigiFreq-sel.StartDigiFreq)*(sel.EndPos-sel.StartPos);
      else
      {
        double area=(sel.EndDigiFreq-sel.StartDigiFreq)*(sel.EndPos-sel.StartPos);
        if (area<marea) result=i, marea=area;
      }
    }
  }
  return result;
}

TWaveViewSelHitTest __fastcall TWaveView::SelHitTest(int X, int Y)
{
  TWaveViewSelHitTest result=selNone;
  int BW=FSelectionBorderWidth;

  if (FSelections->Count==0) result=selOuter;
  else
  {
    int lleft=FSTP(FCurrentPane, FSelections->StartPos);
    int lright=FSTP(FCurrentPane, FSelections->EndPos), ltop, lbottom;
    if (FPanes.HasFreqAxis[FCurrentPane])
    {
      ltop=FDFTP(FCurrentPane, FSelections->EndDigiFreq);
      lbottom=FDFTP(FCurrentPane, FSelections->StartDigiFreq);
    }
    else
    {
      ltop=FPanes.Rect[FCurrentPane].top-2*BW-1, lbottom=FPanes.Rect[FCurrentPane].bottom+2*BW+1;
    }

    enum {sOuter, sIBorder, sInner, sSBorder} sx, sy;
    if (X<lleft-BW || X>lright+BW) sx=sOuter;
    else if (X<=lleft+BW) sx=sIBorder;
    else if (X<lright-BW) sx=sInner;
    else sx=sSBorder;
    if (Y<ltop-BW || Y>lbottom+BW) sy=sOuter;
    else if (Y<=ltop+BW) sy=sIBorder;
    else if (Y<lbottom-BW) sy=sInner;
    else sy=sSBorder;

    if (sx==sOuter || sy==sOuter) result=selOuter;
    else if (sx==sInner)
    {
      if (sy==sInner) result=selInner;
      else if (sy==sIBorder) result=selTop;
      else result=selBottom;
    }
    else if (sx==sIBorder)
    {
      if (sy==sInner) result=selLeft;
      else if (sy==sIBorder) result=selTopLeft;
      else result=selBottomLeft;
    }
    else
    {
      if (sy==sInner) result=selRight;
      else if (sy==sIBorder) result=selTopRight;
      else result=selBottomRight;
    }
  }
  return result;
}

void __fastcall TWaveView::SetArea(int AStartPos, int AnEndPos, double AStartDigiFreq, double AnEndDigiFreq)
{
  if (FStartPos==AStartPos && FEndPos==AnEndPos && FStartDigiFreq==AStartDigiFreq && FEndDigiFreq==AnEndDigiFreq) return;
  if (AStartPos>AnEndPos) throw (EWriteError("TWaveView err: StartPos must be no bigger than EndPos."));
  bool willdozoom=true;
  if (willdozoom)
  {
    FStartPos=AStartPos;
    FEndPos=AnEndPos;
    if (AStartDigiFreq>AnEndDigiFreq) throw (EWriteError("TWaveView err: StartPos must be no bigger than EndPos."));
    FStartDigiFreq=AStartDigiFreq;
    FEndDigiFreq=AnEndDigiFreq;

    Invalidate();
    PageChange();
  }
}

void __fastcall TWaveView::SetAutoExtractMode(bool AnAutoExtractMode)
{
  if (FAutoExtractMode!=AnAutoExtractMode)
  {
    FAutoExtractMode=AnAutoExtractMode;
    if (AnAutoExtractMode) FExtractMode=FSelectMode;
  }
}

void __fastcall TWaveView::SetAutomaticSpecAmp(int channel, double* xx, double AStartDigiFreq, double AnEndDigiFreq, int dX, int dY)
{
  if (maxv_specamp<=0) return;
  TQuickSpectrogram* Spectrogram=FSpectrogram[channel];

  int xxd, sampr, sampg, sampb;
  int oldxxd=-3;
  double maxv=0;
  for (int x=0; x<dX; x++)
  {
    xxd=floor(xx[x]+0.5);
    if (xxd<0 || xxd>=Spectrogram->Capacity) continue;

    if (xxd==oldxxd) {}
    else
    {
      QSPEC_FORMAT* spec=Spectrogram->A(xxd);
      int istart=AStartDigiFreq*FSpecRes-1;
      int iend=AnEndDigiFreq*FSpecRes+1;
      if (istart<1) istart=1;
      if (iend>FSpecRes/2) iend=FSpecRes/2;
      for (int i=istart; i<iend; i++)
      {
        if (maxv<spec[i]) maxv=spec[i];
      }
    }
    oldxxd=xxd;
  }
  if (maxv>0) FSpecAmp=maxv_specamp/maxv;
}


void __fastcall TWaveView::SetAutoSpecAmp(bool AnAutoSpecAmp)
{
  if (FAutoSpecAmp!=AnAutoSpecAmp)
    FAutoSpecAmp=AnAutoSpecAmp;
}

void __fastcall TWaveView::SetAxisColor(TColor AnAxisColor)
{
  FAxisColor=AnAxisColor;
}

void __fastcall TWaveView::SetBackColor(TColor ABackColor)
{
  if (FBackColor!=ABackColor)
  {
    FBackColor=ABackColor;
    Invalidate();
  }
}

void __fastcall TWaveView::SetCaption(AnsiString ACaption)
{
  if (ACaption!=FCaption)
  {
    FCaption=ACaption;
    Invalidate();
  }
}

void __fastcall TWaveView::SetClickFocus(bool AClickFocus)
{
  FClickFocus=AClickFocus;
}

void __fastcall TWaveView::SetContent(int index, int channel, int type)
{                                             
  FPanes.Content[index]=type*WV2_MAX_CHANNEL+channel;
  if (Visible) Invalidate();
}

void __fastcall TWaveView::SetContent(int X, int Y, int channel, int type)
{
  FPanes.Content[Y*FPanes.FX+X]=type*WV2_MAX_CHANNEL+channel;
  if (Visible) Invalidate();
}

void __fastcall TWaveView::SetCursorTF(int PaneIndex, int t, double digif)
{
  int X=FSTP(PaneIndex, t);
  int Y=FDFTP(PaneIndex, digif);
  TPoint P=ClientToScreen(TPoint(X, Y));
  SetCursorPos(P.x, P.y);
}

void __fastcall TWaveView::SetData(int index, void* AData)
{
  if (FData[index] && FData[index]!=AData) delete[] FData[index];
  FData[index]=AData;
  FSpectrogram[index]->Data=AData;
  FSpectrogram[index]->FreeBuffers();
}

void __fastcall TWaveView::SetDefaultPopupMenu(bool ADefaultPopupMenu)
{
  FDefaultPopupMenu=ADefaultPopupMenu;
  if (FLength>0 && ADefaultPopupMenu) PopupMenu=FMenu;
}

void __fastcall TWaveView::SetEndPos(int AnEndPos)
{
  if (FEndPos==AnEndPos) return;
  if (AnEndPos<FStartPos) throw(EWriteError("TWaveView err: EndPos must be no smaller than StartPos"));
  bool willdozoom=true;
  if (willdozoom)
  {
    FEndPos=AnEndPos;
    Invalidate();
    PageChange();
  }
}

void __fastcall TWaveView::SetExtractMode(int AnExtractMode)
{
  FExtractMode=AnExtractMode;
}

void __fastcall TWaveView::SetForceHamming(bool AForceHamming)
{
  FForceHamming=AForceHamming;
}

void __fastcall TWaveView::SetMultiSelect(bool AMultiSelect)
{
  FMultiSelect=AMultiSelect;
}

void __fastcall TWaveView::SetPitchScalePart(int APart)
{
  if (APart<1) APart=1;
  FPitchScalePart=APart;
}

void __fastcall TWaveView::SetPlaybackFilter(TWaveViewPlaybackFilter APlaybackFilter)
{
  FPlaybackFilter=APlaybackFilter;
}

void __fastcall TWaveView::SetRulers(int PaneIndex, int Rulers)
{
  if (FPanes.Rulers[PaneIndex]!=Rulers)
  FPanes.Rulers[PaneIndex]=Rulers;
  if (FPanes.Type[PaneIndex]==0) {Basic0Settings[FPanes.Channel[PaneIndex]].Y=0;}
}

void __fastcall TWaveView::SetRulerUnit(int UnitTime, int UnitFreq, int UnitAmp)
{
  if (FRulerUnitTime!=UnitTime || FRulerUnitFreq!=UnitFreq || FRulerUnitAmp!=UnitAmp)
  {
    FRulerUnitTime=UnitTime;
    FRulerUnitFreq=UnitFreq;
    FRulerUnitAmp=UnitAmp;
  }
}

void __fastcall TWaveView::SetSamplesPerSec(int ASamplesPerSec)
{
  FSamplesPerSec=ASamplesPerSec;
}

void __fastcall TWaveView::SetScrollBar(TScrollBar* AScrollBar)
{
  if (!AScrollBar)
    FScrollBar=0;
  else
  {
    FScrollBar=AScrollBar;
    FScrollBar->TabStop=false;
    if (FLength>0)
    {
      FScrollBar->OnChange=ScrollBarChange;
      UpdateScrollBar(this);
    }
  }
}

void __fastcall TWaveView::SetSelection(int Start, int End, double VStart, double VEnd)
{
  TWaveViewSelection sel={Start, End, VStart, VEnd};
  if (!FMultiSelect) FSelections->Clear();
  FSelections->Add(sel);
  Invalidate();
  if (FOnSelectedChange) FOnSelectedChange(this);
}

void __fastcall TWaveView::SetSelectedAreaColorX(TColor ASelectedAreaColorX)
{
  if (FSelectedAreaColorX!=ASelectedAreaColorX)
  {
    FSelectedAreaColorX=ASelectedAreaColorX;
    Invalidate();
  }
}

void __fastcall TWaveView::SetSelectedFrameColorX(TColor ASelectedFrameColorX)
{
  if (FSelectedFrameColorX!=ASelectedFrameColorX)
  {
    FSelectedFrameColorX=ASelectedFrameColorX;
    Invalidate();
  }
}

void __fastcall TWaveView::SetSelectingFrameColorX(TColor ASelectingFrameColorX)
{
  FSelectingFrameColorX=ASelectingFrameColorX;
}

void __fastcall TWaveView::SetSelectMode(int ASelectMode)
{
  FSelectMode=ASelectMode;
  if (FAutoExtractMode) FExtractMode=ASelectMode;
}

void __fastcall TWaveView::SetSpecOffst(int ASpecOffst)
{
  if (FSpecOffst!=ASpecOffst)
  {
    FSpecOffst=ASpecOffst;
    ClearSpectrograms();
    Invalidate();
    for (int i=0; i<FChannels; i++) FSpectrogram[i]->Offst=ASpecOffst;
  }
}

void __fastcall TWaveView::SetSpecRes(int ASpecRes)
{
  if (FSpecRes!=ASpecRes)
  {
    FSpecOffst=FSpecOffst*ASpecRes/FSpecRes;
    FSpecRes=ASpecRes;
    ClearSpectrograms();
		free8(fw); free(fhbi); free8(fwin);

    fw=(cdouble*)malloc8(sizeof(cdouble)*FSpecRes*2); SetTwiddleFactors(FSpecRes, fw);
    fx=&fw[FSpecRes/2]; famp=(double*)&fx[FSpecRes];

    fwin=NewWindow8(FSpecWindowType, FSpecRes, FSpecWindowParamI, FSpecWindowParamD, 0);
    fhbi=CreateBitInvTable(Log2(FSpecRes)-1);

    for (int i=0; i<FChannels; i++) FSpectrogram[i]->Wid=ASpecRes, FSpectrogram[i]->Offst=FSpecOffst;

    Invalidate();
  }
}

void __fastcall TWaveView::SetSpecWindowParamD(int Index, double AParamD)
{
  if (FSpecWindowParamD[Index]!=AParamD)
  {
    FSpecWindowParamD[Index]=AParamD;
    ClearSpectrograms();
    if (Index==0) for (int i=0; i<Channels; i++) FSpectrogram[i]->WinParam=AParamD;
    fwin=NewWindow8(FSpecWindowType, FSpecRes, FSpecWindowParamI, FSpecWindowParamD, fwin);
    Invalidate();
  }
}

void __fastcall TWaveView::SetSpecWindowType(WindowType ASpecWindowType)
{
  if (FSpecWindowType!=ASpecWindowType)
  {
    FSpecWindowType=ASpecWindowType;
    ClearSpectrograms();
    for (int i=0; i<Channels; i++) FSpectrogram[i]->WinType=ASpecWindowType;
    fwin=NewWindow8(FSpecWindowType, FSpecRes, FSpecWindowParamI, FSpecWindowParamD, fwin);
    Invalidate();
  }
}

void __fastcall TWaveView::SetSpecAmp(double ASpecAmp)
{
  if (FSpecAmp!=ASpecAmp)
  {
    FSpecAmp=ASpecAmp;
    Invalidate();
  }
}

void __fastcall TWaveView::SetStartPos(int AStartPos)
{
  if (FStartPos==AStartPos) return;
  if (AStartPos>FEndPos) throw(EWriteError("TWaveView err: StartPos must be no bigger than EndPos."));
  bool willdozoom=true;
  if (willdozoom)
  {
    FStartPos=AStartPos;
    Invalidate();
    PageChange();
  }
}

void __fastcall TWaveView::SetStartAndEndPos(int AStartPos, int AnEndPos)
{
  if (FStartPos==AStartPos && FEndPos==AnEndPos) return;
  if (AStartPos>AnEndPos) throw (EWriteError("TWaveView err: StartPos must be no bigger than EndPos."));
  bool willdozoom=true;
  if (willdozoom)
  {
    FStartPos=AStartPos;
    FEndPos=AnEndPos;
    Invalidate();
    PageChange();
  }
}

void __fastcall TWaveView::SetTools(TWaveViewTools ATools)
{
  FTools=ATools;
}

void __fastcall TWaveView::SetStartAndEndDigiFreq(double AStartDigiFreq, double AnEndDigiFreq)
{
  if (FStartDigiFreq==AStartDigiFreq && FEndDigiFreq==AnEndDigiFreq) return;
  if (AStartDigiFreq>AnEndDigiFreq) throw (EWriteError("TWaveView err: StartDigiFreq must be no bigger than EndDigiFreq."));
  FStartDigiFreq=AStartDigiFreq;
  FEndDigiFreq=AnEndDigiFreq;
  Invalidate();
}

void __fastcall TWaveView::SetWaveAudio(TWaveAudio* AWaveAudio)
{
//*  tends to cause comfliction, use only when FWaveAudio!=AWaveAudio
  if (FWaveAudio) FWaveAudio->OnAudioChange=NULL;

  FWaveAudio=AWaveAudio;

  if (FWaveAudio)
  {
    if (FStartDigiFreq<0) FStartDigiFreq=0, FEndDigiFreq=0.5;
    FWaveAudio->OnAudioChange=WaveViewAudioChange;

    FreeData(FChannels);
    FreeInternalBitmaps(FChannels);
    ClearSpectrograms();

    FChannels=FWaveAudio->Channels;
    FBytesPerSample=FWaveAudio->BitsPerSample/8;
    FLength=FWaveAudio->WaveStream->Size/FChannels/FBytesPerSample;
    FBlockSize=FWaveAudio->BlockSize;
    SamplesPerSec=FWaveAudio->SamplesPerSec;

    if (FLength)
    {
      for (int i=0; i<FChannels; i++)
      {
        FData[i]=new unsigned char[FLength*FBytesPerSample];
        FSpectrogram[i]->Data=FData[i];
        FSpectrogram[i]->WinType=FSpecWindowType;
        FSpectrogram[i]->Wid=FSpecRes;
        FSpectrogram[i]->Offst=FSpecOffst;
        FSpectrogram[i]->WinParam=FSpecWindowParamD[0];
        FSpectrogram[i]->BytesPerSample=FBytesPerSample;
        FSpectrogram[i]->DataLength=FLength;
      }
    }

    Retrieve();

    FStartPos=0;
    FEndPos=FLength;
    FSelections->Clear();

    if (FScrollBar)
    {
      FScrollBar->PageSize=0;
      FScrollBar->OnChange=NULL;
      FScrollBar->SetParams(0, 0, (FLength-1>0)?FLength-1:1);
      FScrollBar->OnChange=ScrollBarChange;
      FScrollBar->LargeChange=FLength/10;
    }
  }
  UndoExtractSelection.EndPos=-1;
  PageChange();
  Invalidate();
}

void __fastcall TWaveView::SetWaveBackColor(TColor AWaveBackColor)
{
  if (FWaveBackColor!=AWaveBackColor)
  {
    FWaveBackColor=AWaveBackColor;
    FWaveColor2=TColor((AWaveBackColor+FWaveColor)/2);
    Invalidate();
  }
}

void __fastcall TWaveView::SetWaveColor(TColor AWaveColor)
{
  if (FWaveColor!=AWaveColor)
  {
    FWaveColor=AWaveColor;
    FWaveColor2=TColor((AWaveColor+FWaveBackColor)/2);
  }
}

void __fastcall TWaveView::SetYScale(int index, int yscale)
{
  FPanes.YScale[index]=yscale;
  if (Visible) Invalidate();
}

void __fastcall TWaveView::SetYZoomRate(double AYZoomRate)
{
  FYZoomRate=AYZoomRate;
  Invalidate();
  if (FOnScaleChange) FOnScaleChange(this);
}

void TWaveView::StartDrag(int aX, int aY)
{
  FStartSelX=aX;
  FStartSelY=aY;
  FStartSelSel=CurrentRange;
}

void __fastcall TWaveView::StartPlayback(TObject* Sender)
{
  if (FBeforePlayback) FBeforePlayback(this);
  if (FLength<1) return;

  if (!FSection->Playing)
  {
    int PlayBytesPerSample=(FBytesPerSample<3)?FBytesPerSample:2;

    loopframe0=0;
    loopframe1=0;
    prevk=-1;
    
    FSection->CustomFillBlock=FillBlock;
    FSectionBlocks=0;

    FSection->Clear(this);
    FSection->Channels=FChannels;
    FSection->BlockSize=FBlockSize;
    FSection->BitsPerSample=PlayBytesPerSample*8;
    FSection->SamplesPerSec=FSamplesPerSec;
    if (FSamplesPerSec>0) FSection->SamplesPerSec=FSamplesPerSec;
    if (FStereoMode==wvpLeft || FStereoMode==wvpRight) FSection->Channels=1;
    if (FSection->Channels>2) FSection->Channels=2;
    FSection->OnPlaybackProg=WaveViewSectionPlaybackProg;
    FSectionProgress=-1;

    //if the playback starts from outside the focused selection according to mouse pointer
    if (FStartSelR<FSelections->StartPos||FStartSelR>=FSelections->EndPos||
        FPanes.HasFreqAxis[FStartPane] && (FVStartSelR<FSelections->StartDigiFreq||FVStartSelR>=FSelections->EndDigiFreq))
    {
      if (FOnGetPlaybackStartAndEndPos) FOnGetPlaybackStartAndEndPos(this, FSectionStartPos, FSectionEndPos, false);
      else FSectionStartPos=FStartPos, FSectionEndPos=FLength;
      if (LoopPlay) LoopMode=1;
    }
    else //the playback starts from inside the focused selection
    {
      if (FOnGetPlaybackStartAndEndPos) FOnGetPlaybackStartAndEndPos(this, FSectionStartPos, FSectionEndPos, true);
      else
      {
        FSectionStartPos=FSelections->StartPos; if (FSectionStartPos<0) FSectionStartPos=0;
        FSectionEndPos=FSelections->EndPos; if (FSectionEndPos>FLength) FSectionEndPos=FLength;
      }
      if (LoopPlay) LoopMode=2;
    }

    if (FSectionEndPos>FSectionStartPos)
    {
      int PlayBufferSize=FSection->BlockSize/PlayBytesPerSample+FSpecRes;
      if (FSection->Channels!=2)
      {
        PlayBuffer0=(double*)malloc8(sizeof(double)*PlayBufferSize); PlayBuffer1=0;
        memset(PlayBuffer0, 0, sizeof(double)*FSpecRes);
      }
      else
      {
        PlayBuffer0=(double*)malloc8(sizeof(double)*PlayBufferSize*2); PlayBuffer1=&PlayBuffer0[PlayBufferSize];
        memset(PlayBuffer0, 0, sizeof(double)*FSpecRes); memset(PlayBuffer1, 0, sizeof(double)*FSpecRes);
      }
      PBPR=FSectionStartPos, PBPA=0, PBPW=0;
      if (FForceHamming) fw1=NewWindow8(wtHamming, FSpecRes, FSpecWindowParamI, FSpecWindowParamD, 0);
      else fw1=NewWindow8(FSpecWindowType, FSpecRes, FSpecWindowParamI, FSpecWindowParamD, 0);
			fw2=NewWindow8(wtHann, FSpecRes, NULL, NULL, 0); fw2_fw1=(double*)malloc8(sizeof(double)*FSpecRes*2); ifw1=&fw2_fw1[FSpecRes];
      if (fw1[0]==0) ifw1[0]=fw2_fw1[0]=0; else {ifw1[0]=1.0/fw1[0]; fw2_fw1[0]=fw2[0]*ifw1[0];}
      for (int i=1; i<FSpecRes; i++) {ifw1[i]=1.0/fw1[i]; fw2_fw1[i]=fw2[i]*ifw1[i];}

      FSection->Play(&WaveViewSectionPlaybackDone);
      ItemPlay->Caption=WV2_STRINGS_Stop_playback;
      if (FOnPlaybackStart) FOnPlaybackStart(this);
    }
  }
  else
  {
    PausePlayback(this);
  }
}

void __fastcall TWaveView::TFFilter(int channel, bool pass, bool wholelength)
{
  ::TFFilter(this, channel, FSelections, pass, wholelength);
}

void __fastcall TWaveView::UndoExtract(TObject* Sender)
{
  SetArea(UndoExtractSelection.StartPos, UndoExtractSelection.EndPos, UndoExtractSelection.StartDigiFreq, UndoExtractSelection.EndDigiFreq);
  UndoExtractSelection.EndPos=-1;
}

void __fastcall TWaveView::UpdateScrollBar(TObject* Sender)
{
  TNotifyEvent SBChange=FScrollBar->OnChange;
  FScrollBar->OnChange=NULL;
  if (FEndPos-FStartPos>=FLength)
  {
    FScrollBar->PageSize=FLength;
    FScrollBar->SetParams(FStartPos, 0, FLength);
    ScrollBar->Visible=false;
  }
  else
  {
    FScrollBar->SetParams(FStartPos, 0, FLength);
    FScrollBar->PageSize=FEndPos-FStartPos;
    int change=FScrollBar->PageSize/10;
    if (change>FSamplesPerSec/2) change=FSamplesPerSec/2;
    FScrollBar->SmallChange=change;
    change=FScrollBar->PageSize*0.9;
    if (change>FSamplesPerSec) change=FSamplesPerSec;
    FScrollBar->LargeChange=change;
    FScrollBar->Visible=true;
  }
  FScrollBar->OnChange=SBChange;
}

void __fastcall TWaveView::WaveViewAudioChange(TObject* Sender)
{
  WaveAudio=FWaveAudio;
  if (WaveAudio==FWaveAudio && FSection->Playing && true)
  {
    FSectionStartPos=FStartPos;
    FSectionEndPos=FEndPos;
  }
}

void __fastcall TWaveView::WaveViewHitTest(int X, int Y)
{
  FCurrentPane=-1;
  for (int i=0; i<FPanes.Count; i++)
  {
    TRect Rect=FPanes.Rect[i];
    if (X>=Rect.left && X<Rect.right && Y>=Rect.top && Y<Rect.bottom)
    {
      FCurrentPane=i;
      FCurrentTime1=FPTS(i, X); FCurrentTime2=FPTS(i, X+1);
      FCurrentDigiFreq2=FPTDF(i, Y); FCurrentDigiFreq1=FPTDF(i, Y+1);
      break;
    }
  }

  InfoRectAtPointer=-1;
  if (FShowInfo)
  {
    for (int i=0; i<InfoRectCount; i++)
    {
      TRect Rect=InfoRect[i];
      if (X>=Rect.left && X<Rect.right && Y>=Rect.top && Y<Rect.bottom)
      {
        InfoRectAtPointer=i;
        break;
      }
    }
  }

  GetObjectAtPointer(X, Y);
}

void __fastcall TWaveView::WaveViewSectionPlaybackDone(TObject* Sender)
{
  ItemPlay->Caption=WV2_STRINGS_Play;

  if (PlayBuffer0)
  {
    free8(PlayBuffer0);
    PlayBuffer0=0;
		free8(fw1); free8(fw2); free8(fw2_fw1);
  }

  FSectionProgress=-1;
  free8(loopframe0);
	free8(loopframe1);
  if (FOnPlaybackDone) FOnPlaybackDone(this);
}

void __fastcall TWaveView::WaveViewSectionPlaybackProg(TObject* Sender, double Progress)
{
  if (FProgressCursor)
  {
    FSectionProgress=PBPR-FSpecRes/2;
    if (AutoScroll && PBPR>FEndPos)
    {
      int len=FEndPos-FStartPos;
      int newen=FEndPos+len;
      if (newen>FLength) newen=FLength;
      SetStartAndEndPos(newen-len, newen);
    }
    Invalidate();
  }
}

void __fastcall TWaveView::Zoom(int X, double Rate)
{
  int X1,X2;
  bool pagechange=false;

  if (Rate<=0){X1=0; X2=FLength;}
  else
  {
    X1=FStartPos-X; X2=FEndPos-X;
    X1=floor(X1*Rate); X2=ceil(X2*Rate);
    X1+=X; X2+=X;
    if (X1<0) X1=0; if (X2>Length) X2=Length;
    if (X1>=X2) X1=X2-1;
  }

  if (FStartPos!=X1) FStartPos=X1, pagechange=true;
  if (FEndPos!=X2) FEndPos=X2, pagechange=true;
  if (pagechange)
  {
    Invalidate();
    PageChange();
  }
}

void __fastcall TWaveView::ZoomF(double Y, double Rate, int yscale)
{
  double Y1, Y2;

  if (Rate<=0) {Y1=0; Y2=0.5;}
  else
  {
    if (yscale==0) //linear scale
    {
      Y1=FStartDigiFreq-Y; Y2=FEndDigiFreq-Y;
      Y1=Y1*Rate; Y2=Y2*Rate;
      Y1+=Y; Y2+=Y;
      if (Y1<0) Y1=0; if (Y2>0.5) Y2=0.5;
    }
    else //log scale
    {
      double AStartDigiFreq=(FStartDigiFreq>WV2_MIN_LOG_FREQ)?FStartDigiFreq:WV2_MIN_LOG_FREQ;
      double AnEndDigiFreq=(FEndDigiFreq>WV2_MIN_LOG_FREQ)?FEndDigiFreq:WV2_MIN_LOG_FREQ;
      if (Y<WV2_MIN_LOG_FREQ) Y=WV2_MIN_LOG_FREQ;
      Y1=AStartDigiFreq/Y; Y2=AnEndDigiFreq/Y;
      Y1=pow(Y1, Rate); Y2=pow(Y2, Rate);
      Y1*=Y; Y2*=Y;
      if (Y1<0) Y1=0; if (Y2>0.5) Y2=0.5;
    }
  }

  if (FStartDigiFreq!=Y1 || FEndDigiFreq!=Y2)
  {
    FStartDigiFreq=Y1, FEndDigiFreq=Y2;
    Invalidate();
  }
}

void __fastcall TWaveView::ZoomY(double Rate)
{
  FYZoomRate*=Rate;
  Invalidate();
}

//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// ValidCtrCheck is used to assure that the components created do not have
// any pure virtual functions.
//
namespace Waveview
{
  void __fastcall PACKAGE Register()
  {
    TComponentClass classes[1] = {__classid(TWaveView)};
    RegisterComponents("Samples", classes, 0);
  }
}
//---------------------------------------------------------------------------

#undef USE_ASM