Source file: /~heha/basteln/PC/oszi/oszi.zip/src/quelle.cpp

#include "wutils.h"
#include "quelle.h"
#include <math.h>
#include <stdlib.h>	//random
#include <stdio.h>	//sscanf

// Globales
extern HWND MainWnd;
extern TCHAR HelpFileName[];
extern TCHAR StdProfile[];
extern WAVEHDR WaveHdr;
extern QUELLE* quelle;


extern void Float2String(PTSTR s, float v, WORD minmax, PCTSTR Einheit,int precis=3);
extern bool String2Float(PCTSTR s, float&z, PTSTR e);
#define ELEN 8

SOFT_TRIGGER st;	// Globale Variable

/*********************************************************
 ** Die folgenden Triggersuchroutinen dürfen nie	**
 ** mit <len>=0 oder ungültigem <buf> gerufen werden!	**
 ** <len> und Rückgabewert sind stets in Bytes gemeint.	**
 *********************************************************/
// Eigentlich wäre hier auch die Erkennung von Übersteuerung praktisch...

// DC-Präprozessor für nicht zu triggernde Daten
void NEAR DcProcess(LPSTR buf,UINT len) {
 len-=st.blockalign;
 buf+=st.blockadd+len;
 st.y=st.getsample(buf,st.mask);	// Letztes Sample setzen
};
// DC-Hauptprozessor für zu triggernde Daten
UINT NEAR DcTrigger(LPSTR buf, UINT len) {
 int cmp,xor;
 LPSTR p=buf+st.blockadd;
 xor=st.xor;		// <xor> =0: steigend, =-1: fallend (=Negation)
 cmp=st.cmp1^xor;	// <cmp1> muss bei FALLEND größer als <cmp2> sein!
			// <y> enthält den NICHT-NEGIERTEN alten Wert
			// <y> sollte mit MAXINT~xor initialisiert werden
 if ((st.y^xor)<=cmp) {xor=~xor; cmp=st.cmp2^xor;}// Zweiten Vergleich laden
 do{
  st.y=st.getsample(p,st.mask);
  if ((st.y^xor)<=cmp) {
   if (xor!=st.xor) break;		// Flanke gefunden
   {xor=~xor; cmp=st.cmp2^xor;}	// Zweiten Vergleich laden
  }
  p+=st.blockalign;
 }while (len-=st.blockalign);
 return (UINT)(p-st.blockadd-buf);
}

// AC-Präprozessor: Gleichspannungspegel mitführen
void NEAR AcProcess(LPSTR buf, UINT len) {
 buf+=st.blockadd;
 len/=st.blockalign;
 do{
  st.y=st.getsample(buf,st.mask);
  st.makesub();
  buf+=st.blockalign;
 }while (--len);
}
// AC-Hauptprozessor: Wie DcTrigger + AcProcess
// Auch geeignet für TVH, TVL, mit entsprechendem "makesub"
UINT NEAR AcTrigger(LPSTR buf, UINT len) {
 int cmp,xor;
 LPSTR p=buf+st.blockadd;
 len/=st.blockalign;
 xor=st.xor;		// <xor> =0: steigend, =-1: fallend (=Negation)
 cmp=st.cmp1^xor;	// <cmp1> muss bei FALLEND größer als <cmp2> sein!
			// <y> enthält den NICHT-NEGIERTEN alten Wert
			// <y> sollte mit MAXINT~xor initialisiert werden
 if (((st.y-GET_SUB(st.sub))^xor)<=cmp) {
  xor=~xor; cmp=st.cmp2^xor;	// Zweiten Vergleich laden
 }
 do{
  st.y=st.getsample(p,st.mask);
  if (((st.y-GET_SUB(st.sub))^xor)<=cmp) {
   if (xor!=st.xor) break;		// Flanke gefunden
   {xor=~xor; cmp=st.cmp2^xor;}	// Zweiten Vergleich laden
  }
  st.makesub();
  p+=st.blockalign;
 }while (--len);
 return (UINT)(p-st.blockadd-buf);
}

UINT NEAR ExtTrigger(LPSTR, UINT) {return 0;}	// sofort triggern lassen

/* die Monsterroutine */
bool FindTrigger(/*GETPROC getproc,NPVOID getdata/*,UINT timeout*/) {
#define Flags (*(BYTE*)&WaveHdr.dwFlags)
#define pNext WaveHdr.lpNext
 bool r=false;
 if (Flags&WHDR_DONE) return r;		// geht nicht!
 UINT Len,LenA;	// Entnahme-Bytes: Vorhanden, entnommen
 LPSTR Start;	// Entnahme-Start
 if (pNext) {
// Len = Entnahme-Menge, Start = Entnahme-Adresse
  Start=(LPSTR)pNext->dwUser;	// der "Saug-Stand"
  Len=(UINT)(pNext->lpData-Start)+(UINT)pNext->dwBytesRecorded;	// Länge ergibt sich
  if ((long)Len<0) DebugBreak();	// Notbremse
 }
 UINT tic=(UINT)GetTickCount();
#ifndef WIN32
 LenA=0;			// Compiler-Warning abstellen
#endif
 for(;(UINT)GetTickCount()-tic<100; Start+=LenA, Len-=LenA){	// ... wird bei continue ausgeführt
// Platz = feier Speicher am Ziel
  size_t Platz=WaveHdr.dwBufferLength-WaveHdr.dwBytesRecorded;
  if (!pNext || !Len) {
// Wenn kein Puffer vorhanden, oder Puffer leer gesaugt...
   quelle->RelayMsg(Q_POLL,&WaveHdr);
//   getproc(getdata,&pNext);
   if (!pNext) break;		// keine weiteren Daten
   Flags=(BYTE)((Flags&~WHDR_PREPARED)|(pNext->dwFlags&WHDR_PREPARED));
   Start=pNext->lpData;
   Len=(UINT)pNext->dwBytesRecorded;
   if (!Len) {			// Leerer Block markiert Speicherende
    Flags|=WHDR_DONE;
    break;
   }
  }
  LenA=Len;
  if (Flags&WHDR_ENDLOOP) {	// Trigger bereits gefunden?
   {DWORD weg=-(long)WaveHdr.reserved;
    if ((long)weg>0) {		// Samples vernichten?
     if (LenA>weg) LenA=(UINT)weg;
     WaveHdr.reserved+=LenA;	// negativen Trigger vorrücken
     continue;
    }
   }				// sonst: Samples kopieren
   if (!Platz) break;		// Puffer voll! Kein Wrap-Around!
kopieren:
   if (LenA>Platz) LenA=(UINT)Platz;	// Minimum
   st.praeproc(Start,LenA);
   CopyMemory((char huge*)WaveHdr.lpData+WaveHdr.dwBytesRecorded,Start,LenA);
   r=true;
   WaveHdr.dwBytesRecorded+=LenA;		// DWORD-Inkrement
   if (WaveHdr.dwUser<WaveHdr.dwBytesRecorded)	// alte Daten verschwunden?
       WaveHdr.dwUser=WaveHdr.dwBytesRecorded;	// OldData-Index vorschieben
   continue;
  }				// sonst: Prä-Trigger-Daten aufsammeln
  if (!(Flags&WHDR_BEGINLOOP)) {
   Platz=WaveHdr.reserved-WaveHdr.dwBytesRecorded;	// Bereich A
    if ((long)Platz>0) goto kopieren;
   Flags|=WHDR_BEGINLOOP;
   WaveHdr.dwLoops=0;
  }  				// sonst: Trigger suchen
  Platz=-(long)WaveHdr.reserved;
  if ((long)Platz<0) Platz=0;
  Platz+=WaveHdr.dwBufferLength;	// Maximale Triggersuch-Länge
  if (LenA>Platz) LenA=(UINT)Platz;
  UINT TrPos=st.trigproc(Start,LenA);
  if (TrPos!=LenA) Flags|=WHDR_ENDLOOP;	// Trigger gefunden
// Jetzt die Daten VOR dem Trigger verarbeiten
  Platz=WaveHdr.reserved;		// Prätrigger-Bereich
  if ((long)Platz<0) Platz=0;
  if (TrPos>Platz) {		// einige Prätrigger-Daten vernichten:
   LenA=TrPos-(UINT)Platz;	// Was weggeworfen werden muss
   Start+=LenA;
   Len-=LenA;
   TrPos-=LenA;			// Jetzt ist TrPos <= Platz
  }
// Daten in Bereich A (zwischen dwLoops und reserved)
  Platz=WaveHdr.reserved-WaveHdr.dwLoops;
  LenA=TrPos;
  if (LenA>Platz) LenA=(UINT)Platz;
  if (LenA) {
kop2:
   CopyMemory((char huge*)WaveHdr.lpData+WaveHdr.dwLoops,Start,LenA);
   r=true;
   WaveHdr.dwLoops+=LenA;
   TrPos-=LenA;
  }
  if (TrPos) {		// ansonsten siehe Schleifenkopf
   Start+=LenA;		// Rest der Prätrigger-Daten in Bereich B
   Len-=LenA;		// (muss immer passen)
   WaveHdr.dwLoops=0;	// (zwischen Anfang und dwLoops)
   LenA=TrPos;
   goto kop2;
  }
 }
 if (pNext) pNext->dwUser=(LPARAM)Start;
// wh->lpNext=pNext;
// wh->dwFlags=Flags;
 return r;
#undef pNext
#undef Flags
}
int _fastcall GetCharSample(LPSTR p, BYTE xor) {// irgendwas, xor-zentriert
 return (signed char)(*p^xor);
}
int _fastcall GetShortSample(LPSTR p, BYTE) {	// 16-bit-Soundkarte
 return (short)(*(LPWORD)p);
}
int _fastcall GetBitSample(LPSTR p, BYTE mask) {// Logikanalysator
 return *p&mask?1:0;
}
static void _fastcall AcMakeSub(void) {	// Soft-Trigger-Version
 st.sub+=st.y-GET_SUB(st.sub);
}
/************************
 ** 0. Die Basisklasse **
 ************************/
bool QUELLE::RelayMsg(Q_MSG Msg,LPVOID lParam) {
 switch (Msg) {
  case Q_INIT: {
   st.getsample=GetCharSample;
   st.trigproc=DcTrigger;
   st.praeproc=DcProcess;
   st.makesub=AcMakeSub; // wird von DcTrigger, DcProcess nicht verwendet
   st.mask=0;		// für 0-zentrierte Daten
   st.blockadd=0;
   st.xor=0;
   st.sub=0;
   st.cmp1=0;
   st.cmp2=1;
   st.blockalign=2;	// meistens!
   st.pre=0;
   st.min=-128;
   st.max=127;		// 8 bit, meistens
   state=0;
   sd.hDlg=0;
  }return true;

  case Q_SETUPDLG: {
#define di ((LPDLGINFO)lParam)
   if (!di) return false;
   if (sd.hDlg) {SetActiveWindow(sd.hDlg); return true; }
   sd.kbHand=di->kbHand;
   if (sd.kbHand) sd.hDlg=CreateDialogParam(HInstance,MAKEINTRESOURCE(sd.helpId),
     di->parent,sd.dlgProc,(LPARAM)this);
   else if (DialogBoxParam(HInstance,MAKEINTRESOURCE(sd.helpId),
     di->parent,sd.dlgProc,(LPARAM)this)!=IDOK) return false;
#undef di
  }return true;

  case Q_DONE: {
   if (state&Q_ARMED) RelayMsg(Q_UNARM,0);	// idiotensicher!
   if (sd.hDlg) DestroyWindow(sd.hDlg);
  }break;

  case Q_ARM: {
   state|=Q_ARMED;
  }break;

  case Q_UNARM: {
   state&=(BYTE)~Q_ARMED;
  }break;

  case Q_SETTRIG: {	// wertet COUPLING, EDGE und LEVEL aus (für EDGE_TRIGGER)
#define t ((LPTRIG)lParam)
   if (!t) return false;
   if (t->what&TRIG_COUPLING) {
    switch (t->coupling) {
     case TRIG_DC: st.trigproc=DcTrigger; st.praeproc=DcProcess; break;
     case TRIG_AC: st.trigproc=AcTrigger; st.praeproc=AcProcess; break;
     default: return false;
    }
    trcoupling=t->coupling;
   }
   if (t->what&TRIG_EDGE) {
    if (t->edge>=2) return false;
    st.xor=t->edge?-1:0;
   }
   if (t->what&TRIG_LEVEL) {
    st.cmp2=Limit(t->level,st.min,st.max);
   }
   st.cmp1=st.cmp2-1;
   if (st.xor) st.cmp1+=2;
   t->coupling=trcoupling;
   t->edge=(BYTE)(st.xor&1);
   t->level=st.cmp2;
#undef t
  }return true;



  case Q_POLL: {
   if (!(state&Q_ARMED)) return false;
#define wh ((LPWAVEHDR)lParam)
   if (!wh) return false;
   if (wh->dwFlags&WHDR_INQUEUE) return true;
   wh->dwFlags=WHDR_INQUEUE;
//   wh->dwBytesRecorded=0;
//   wh->dwUser=0;
//   if (st.pre>(long)wh->dwBufferLength) st.pre=wh->dwBufferLength;
//   wh->reserved=st.pre;		// begrenzen!
//   st.Reset();
#undef wh
  }return true;
 }
 return false;
}

void DefDlgBottom(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam, SETUPDLG *sd) {
// "untere Hälfte" aller Dialoge behandelt OK und Abbrechen
// sowie Hilfe und das Aktivieren für moduslose Dialoge
 switch (Msg) {
  case WM_ACTIVATE: if (sd->kbHand) *sd->kbHand=wParam?Wnd:0; break;
  case WM_COMMAND: switch (LOWORD(wParam)) {
   case IDOK:
   case IDCANCEL: {
    if (sd->kbHand) DestroyWindow(Wnd);	// Moduslos
    else EndDialog(Wnd,LOWORD(wParam));	// Modal
   }break;
   case IDHELP: WinHelp(Wnd,HelpFileName,HELP_CONTEXT,sd->helpId);
  }break;
#ifdef WIN32
  case WM_HELP: {
   DWORD a[4];
   a[0]=((LPHELPINFO)lParam)->iCtrlId;
   a[1]=MAKELONG(((LPHELPINFO)lParam)->iCtrlId,sd->helpId);
   a[2]=0;
   a[3]=0;
   WinHelp((HWND)((LPHELPINFO)lParam)->hItemHandle,HelpFileName,HELP_WM_HELP,(DWORD)a);
  }break;
  case WM_CONTEXTMENU: {
   WinHelp((HWND)wParam,HelpFileName,HELP_CONTEXTMENU,0);
  }break;
#endif
  case WM_DESTROY: sd->hDlg=0; break;
 }
}

 /*****************************************************
 ** 1. Eine 2kanalige Datenquelle für Zufallszahlen **
 *****************************************************/
#ifndef __BORLANDC__
# define random(x) (rand()*(x)/(RAND_MAX+1))	// Winzigweich
#endif

void ZufallPuffer(NPVOID,LPWAVEHDR FAR*whp) {
 static char buf[620];	// schön krumm zum Test
 static WAVEHDR wh={buf,sizeof(buf),sizeof(buf)};
/* if (*whp) *whp=NULL;		// im Wechsel: Puffer liefern und aussetzen (besser: FindTrigger hat TimeOut!!)
 else*/{
  LPSTR p=buf;
  for (UINT i=0; i<elemof(buf)/2; i++) {
   *p=(CHAR)(random(10)-5); p++; // nicht *p++ : dem Compiler leicht machen
   *p=(char)(random(2)-28+rndint(50*sin((double)i/310*6.28))); p++;
  }
  *whp=&wh;
 }
}

static INT_PTR CALLBACK ZufallDlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
// Eigentlich ein ganzer Mehrkanal-Funktionsgenerator, wenn's fertig ist!
 Q_ZUFALL *q=(Q_ZUFALL*)GetWindowLongPtr(Wnd,DWLP_USER);
 switch (Msg) {
  case WM_INITDIALOG: {
//   q=(Q_ZUFALL*)lParam;
   SetWindowLongPtr(Wnd,DWLP_USER,lParam); 
  }return TRUE;

  case WM_COMMAND: switch (LOWORD(wParam)) {
   case IDOK: ;
  }
 }
 DefDlgBottom(Wnd,Msg,wParam,lParam,&q->sd);
 return FALSE;
}

bool Q_ZUFALL::RelayMsg(Q_MSG Msg,LPVOID lParam) {
 switch (Msg) {
  case Q_INIT: return QUELLE::RelayMsg(Msg,lParam);

  case Q_GETSYSINFO: {
#define si ((LPSYSINFO)lParam)
   if (!si) return false;
   *(LPDWORD)si=MAKELONG(MAKEWORD(Q_SOFTTRIGGER|Q_CONTINUOUS|Q_ASYNC,8),
     MAKEWORD(2,2));
   si->rateminmax[0]=si->rateminmax[1]=10000;
   si->depth=0;
   si->getsample=GetCharSample;
   si->blockalign=2;
#undef si
  }return true;

  case Q_SETUPDLG: {
   sd.dlgProc=ZufallDlgProc;
   sd.helpId=121;
  }return QUELLE::RelayMsg(Msg,lParam);

  case Q_GETCHANINFO: {
#define ci ((LPCHANINFO)lParam)
   if (!ci) return false;
   if (ci->ch>=2) return false;
   ci->couplings=CHANINFO_DC;
   ci->byteoffset=ci->ch;
   ci->mask=0;
   ci->voltminmax[0]=ci->voltminmax[1]=0.01F;
#undef ci
  }return true;

  case Q_SETCHAN: {
#define c ((LPCHAN)lParam)
   if (!c) return false;
   if (c->ch>=2) return false;
//   if (c->what&CHAN_COUPLING && c->coupling!=CHAN_DC) return false;
   c->coupling=CHAN_DC;
   c->resistance=CHAN_R1M;
   c->volt=0.01F;
   c->dcoffset=0;
#undef c
  }return true;

  case Q_GETTRIGINFO: {
#define ti ((LPTRIGINFO)lParam)
   if (!ti) return false;
   *(LPDWORD)ti=MAKELONG(MAKEWORD(TRIGINFO_DC|TRIGINFO_AC,TRIGINFO_AC),
     MAKEWORD(TRIGINFO_RISE|TRIGINFO_FALL,TRIGINFO_CHAN));
#undef ti
  }return true;

  case Q_SETTRIG: {
   if (!QUELLE::RelayMsg(Msg,lParam)) return false;
#define t ((LPTRIG)lParam)
   if (t->what&TRIG_SOURCE) {
    if (t->source>=2) return false;
    st.blockadd=t->source;
   }
   if (t->what&TRIG_PRE) st.pre=t->pre*2;
   t->source=st.blockadd;
   t->pre=st.pre/2;
#undef t
  }return true;

  case Q_ARM:
  case Q_UNARM:
  case Q_DONE: { QUELLE::RelayMsg(Msg,lParam); }break;

  case Q_POLL: //return (bool)(QUELLE::RelayMsg(Msg,lParam)
//    && FindTrigger(ZufallPuffer,this));
   ZufallPuffer(this,&((LPWAVEHDR)lParam)->lpNext); break;
 }
 return true;
}

/******************************************************
 ** 2. Die ISA-Einsteckkarte als 2-Kanal-Oszilloskop **
 ******************************************************/
// Volt pro LSB für jede der 7 Verstärkungseinstellungen
static const float VoltList[7]={
 (float)(0.05/32),
 (float)(0.1/32),
 (float)(0.2/32),
 (float)(0.5/32),
 (float)(1.0/32),
 (float)(2.0/32),
 (float)(5.0/32)};

int GetNearestVolt(float Wert, const float*Liste, int ListLen) {
 float error=1E36F;
 int i,j;
 for (i=0; i<ListLen; i++,Liste++) {
  float e=(float)fabs((Wert-*Liste)/ *Liste);	// relative Abweichung
  if (error>e) {error=e; j=i;}		// Minimum: NICHT RICHTIG!!
 }
 return j;
}

static UINT PortBase=0x2C0;

static void access3(LPBYTE buf, UINT buflen, UINT start) {
 WORD*p=(WORD*)buf;
 UINT a=PortBase+2;
 if (buflen) {
  goto l1;
  do{
   if (LOBYTE(start)) outb(a,LOBYTE(start));
   else l1: outw(a,start);
   *p++=inw(a);
   start++;
  }while (--buflen);
 }
}


static INT_PTR CALLBACK DSO220SetupDlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
// Die Portadresse ist ja noch festlegbar...
 Q_DSO220 *qs=(Q_DSO220*)GetWindowLongPtr(Wnd,DWLP_USER);
 switch (Msg) {
  case WM_INITDIALOG: {
//   qs=(Q_DSO220*)lParam;
   SetWindowLongPtr(Wnd,DWLP_USER,lParam);	// Q_DSO220 wegspeichern
   UINT idx=(PortBase-0x280)>>3;
   if (idx&~0xF) idx=0;		// Außerhalb liegende Portadresse...???
   CheckDlgButton(Wnd,100+idx,TRUE);
   SetCheckboxGroup(Wnd,120,123,15-idx);
   for (UINT i=100,p=0x280; i<116; i++,p+=8) {
    TCHAR buf[32];
    wsprintf(buf,T("%3Xh"),p);
    SetDlgItemText(Wnd,i,buf);	// Hexzahlen selber an die Radioknöpfe malen!?
   }
  }return TRUE;

  case WM_COMMAND: switch (LOWORD(wParam)) {
   case 120:
   case 121:
   case 122:
   case 123: {	// Markierfelder für gesetzte Jumper: Radioknöpfe nachführen
    CheckRadioButton(Wnd,100,115,115-GetCheckboxGroup(Wnd,120,123));
   }break;
   case IDOK: {
    TCHAR buf[32];
    PortBase=(GetRadioCheck(Wnd,100,115)<<3)+0x280;
    wsprintf(buf,T("0x%3X"),PortBase);
    WriteString(T("DSO220"),T("Adresse"),buf);
   }nobreak;
   case IDCANCEL:
   case IDHELP: break;
   default: {	// Radioknöpfe für Adressen: Markierfelder nachführen
    SetCheckboxGroup(Wnd,120,123,15-GetRadioCheck(Wnd,100,115));
   }
  }
 }
 DefDlgBottom(Wnd,Msg,wParam,lParam,&qs->sd);
 return FALSE;
}

void Q_DSO220::SetDSO() {
 if (state&Q_ARMED) {
  stop();
  init(&ip);
  outb(7,0);
  start();
 }
}

void DsoBlock(NPVOID p, LPWAVEHDR FAR*whp) {
 static WAVEHDR wh={NULL,4000,4000};
 if (*whp) {			// Nur 1 Block
  (*whp)->dwFlags|=WHDR_DONE;
  return;
 }
 wh.lpData=(LPSTR)(((Q_DSO220*)p)->p);
 access3((LPBYTE)wh.lpData,2000,0);
 *whp=&wh;
}

bool Q_DSO220::RelayMsg(Q_MSG Msg,LPVOID lParam) {
 switch (Msg) {
  case Q_INIT: {
   static INITPARAMS test={20000U,{{4,0},{4,0}},{2,0}};
   TCHAR buf[32];
   hPortTalk=INVALID_HANDLE_VALUE;
#ifdef WIN32
   OSVERSIONINFO os;	// Umstandskasten Win32
   os.dwOSVersionInfoSize=sizeof(os);
   if (GetVersionEx(&os) && os.dwPlatformId==VER_PLATFORM_WIN32_NT) {
#else
   if (GetWinFlags() & WF_WINNT) {
#endif
    hPortTalk=LoadLibrary(T("inpout32.dll"));
    if (!hPortTalk) return false;		// Kann erforderlichen Treiber nicht laden
   }
   GetString(T("DSO220"),T("Adresse"),T(""),buf,elemof(buf));
   if (_stscanf(buf,T("%i"),&PortBase)!=1) {	// hex. oder dez.!
    DLGINFO di;
    PortBase=0x280;
    di.parent=MainWnd;
    di.kbHand=NULL;
    if (!RelayMsg(Q_SETUPDLG,&di)) return false;	// Abbruch-Wunsch des Users
   }
   QUELLE::RelayMsg(Msg,lParam);
//   EnablePorts(hPortTalk,PortBase,8);	// bei NT wirksam
   ip=test;			// Struktur zum Start (Quatsch!)
   st.mask=0x80;		// Daten sind 80h-zentriert
  }return true;

  case Q_SETUPDLG: {
   sd.dlgProc=DSO220SetupDlgProc;
   sd.helpId=124;
  }return QUELLE::RelayMsg(Msg,lParam);

  case Q_GETSYSINFO: {
#define si ((LPSYSINFO)lParam)
   if (!si) return false;
   *(LPDWORD)si=MAKELONG(MAKEWORD(Q_SOFTTRIGGER|Q_ASYNC,8),MAKEWORD(2,3));
   si->rateminmax[1]=20000000L;
   si->rateminmax[0]=20000000L/65535;
   si->depth=32768U;
   si->getsample=GetCharSample;
   si->blockalign=2;
#undef si
  }return true;

  case Q_GETCHANINFO: {
#define ci ((LPCHANINFO)lParam)
   if (!ci) return false;
   if (ci->ch>=2) return false;
   ci->couplings=CHANINFO_DC|CHANINFO_AC|CHANINFO_CAL;
   ci->byteoffset=(BYTE)(1-ci->ch);	// hier Rückvertauschung vornehmen!
   ci->mask=0x80;		// (Kanäle ab sofort nicht mehr vertauscht!)
   ci->voltminmax[0]=VoltList[0];
   ci->voltminmax[1]=VoltList[6];
#undef ci
  }return true;

  case Q_SETCHAN: {
   CHANNEL *channel;
#define c ((LPCHAN)lParam)
   if (!c) return false;
   if (c->ch>=2) return false;
   channel=ip.channel+c->ch;
   if (c->what&CHAN_COUPLING) {
    if (c->coupling>=2) return false;	// Kopplung wird nicht "gerundet"!
    channel->coupling=c->coupling;	// CHAN_DC(0) oder CHAN_AC(1)
   }
   if (c->what&CHAN_VOLT) {	// Etwas anderes kann die Karte ja nicht!
    channel->gain
      =(BYTE)GetNearestVolt((float)fabs(c->volt),VoltList,elemof(VoltList));
   }
   if (c->what&(CHAN_COUPLING|CHAN_VOLT)) SetDSO();
   c->coupling=channel->coupling;
   c->resistance=CHAN_R1M;
   c->volt=VoltList[channel->gain];
   c->dcoffset=0;
#undef c
  }return true;

  case Q_GETTRIGINFO: {
#define ti ((LPTRIGINFO)lParam)
   if (!ti) return false;
   *(LPDWORD)ti=MAKELONG(MAKEWORD(TRIGINFO_DC|TRIGINFO_AC,TRIGINFO_AC),
     MAKEWORD(TRIGINFO_RISE|TRIGINFO_FALL,TRIGINFO_CHAN));
#undef ti
  }return true;

  case Q_SETTRIG: {
   if (!QUELLE::RelayMsg(Msg,lParam)) return false;
#define t ((LPTRIG)lParam)
   if (t->what&TRIG_SOURCE) {
    if (t->source>=3) return false;
    ip.trigger.source=t->source;
    if (ip.trigger.source==2) st.trigproc=ExtTrigger;
    else{
     st.trigproc=trcoupling?AcTrigger:DcTrigger;
     st.blockadd=(BYTE)(1-t->source);
    }
   }
   if (t->what&TRIG_PRE) st.pre=t->pre*2;
   if (t->what&TRIG_EDGE) ip.trigger.edge=t->edge;
   if (t->what&(TRIG_SOURCE|TRIG_EDGE)) SetDSO();
   t->source=ip.trigger.source;
   t->pre=ip.trigger.source<2?st.pre/2:0;	// Bei "extern" kein Prätrigger
#undef t
  }return true;

  case Q_DONE: {
   if (hPortTalk) {
//    EnablePorts(hPortTalk,0,0);
    CloseHandle(hPortTalk);
   }
  }break;

  case Q_ARM: {
   p=GlobalAlloc(LMEM_FIXED,4000);	// Oh Gott, was für'n Kode!
   QUELLE::RelayMsg(Msg,lParam);
   init(&ip);
   start();
  }break;

  case Q_UNARM: {
   stop();
   QUELLE::RelayMsg(Msg,lParam);
   GlobalFree(p);
  }break;

  case Q_SETRATE: {
#define f ((LPFLOAT)lParam)
   if (!f) return false;
   if (*f>0) {	// setzen
    float samplediv=20E6F/ *f;
    if (samplediv>65536L) samplediv=65536L;
    WORD z=rndint(samplediv-1);	// Runden lassen
    if (ip.samplediv!=z) {
     ip.samplediv=z;
     SetDSO();
    }
   }
   *f=20E6F/((long)ip.samplediv+1);
#undef f
  }return true;

  case Q_POLL: {
   if (!QUELLE::RelayMsg(Msg,lParam)) return false;
#define wh ((LPWAVEHDR)lParam)
   wh->lpNext=NULL;		// Neuen Block ziehen (diskontinuierlich)
   if ((UINT)inw(0)<2000) return false;	// weiter wursteln
   stop();
   if (ip.trigger.source==2) wh->reserved=0;
//   MessageBeep((UINT)-1);
   DsoBlock(this,&wh->lpNext);
   start();	// Quatsch!
#undef wh
  }return true;
 }
 return false;
}

/***********************************
 ** 3. Soundkarte als Datenquelle **
 ***********************************/
static INT_PTR CALLBACK WaveInDlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
// Wenn "mal eben schnell" die richtige Soundkarte auszuwählen ist...
 switch (Msg) {
  case WM_INITDIALOG: {
   UINT k=waveInGetNumDevs();
   HWND w=GetDlgItem(Wnd,101);
   SetWindowLongPtr(Wnd,DWLP_USER,lParam);
   for (UINT i=0; i<k; i++) {
    WAVEINCAPS wic;
    waveInGetDevCaps(i,&wic,sizeof(wic));
    ListBox_AddString(w,wic.szPname);
   }
  }return TRUE;
  case WM_COMMAND: switch (LOWORD(wParam)) {
   case IDOK: {
    lParam=GetWindowLongPtr(Wnd,DWLP_USER);
    int k=(int)SendDlgItemMessage(Wnd,101,LB_GETCURSEL,0,0);
    if (k<0) break;
    *(int*)lParam=k;
    if (IsDlgButtonChecked(Wnd,102)) {
     TCHAR buf[16];
     wsprintf(buf,T("%d"),k);
     WriteString(T("Soundkarte"),T("Nummer"),buf);
    }
   }nobreak;
   case IDCANCEL: EndDialog(Wnd,wParam); break;
   case IDHELP: WinHelp(Wnd,HelpFileName,HELP_CONTEXT,122);
  }break;
 }
 return FALSE;
}

static INT_PTR CALLBACK WaveSetupDlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
// "Kalibrierung" des Ablenkkoeffizienten (ist ja auch noch von den aktuellen
// Mixereinstellungen abhängig), sowie Angabe, ob die lästigen
// Eingangs-Kondensatoren schon ausgelötet sind...
 Q_SOUND *qs=(Q_SOUND*)GetWindowLongPtr(Wnd,DWLP_USER);
 switch (Msg) {
  case WM_INITDIALOG: {
   TCHAR buf[32];
   qs=(Q_SOUND*)lParam;
   SetWindowLongPtr(Wnd,DWLP_USER,lParam);	// Q_SOUND wegspeichern
   Float2String(buf,qs->lsb_volt,MAKEWORD(-2,0),T("V"));
   SetDlgItemText(Wnd,101,buf);
   CheckDlgButton(Wnd,102+qs->coupling,TRUE);	// DC oder AC
//   SendDlgItemMessage(Wnd,10,TBM_SETRANGE,FALSE,MAKELONG(0,65535));
  }return TRUE;
#ifdef WIN32
  case WM_HSCROLL: {/*switch (LOWORD(wParam)){	// vom TrackBar (Schieberegler)
   case TB_THUMBTRACK:	// Maus
   case TB_ENDTRACK: {	// Tastatur*/
   UINT i=(UINT)SendMessage((HWND)lParam,TBM_GETPOS,0,0);
   if (qs->hMixer && qs->dwLineID) {
    MIXERCONTROLDETAILS_UNSIGNED val;
    MIXERCONTROLDETAILS mcd;
    mcd.cbStruct=sizeof(mcd);
    mcd.dwControlID=qs->dwControlID;	// Schon eindeutig?
    mcd.cChannels=1;			// alle Kanäle
    mcd.cMultipleItems=0;
    mcd.cbDetails=sizeof(val);
    mcd.paDetails=&val;
    val.dwValue=MulDiv(i,65535,100);
    mixerSetControlDetails((HMIXEROBJ)qs->hMixer,&mcd,MIXER_SETCONTROLDETAILSF_VALUE|MIXER_OBJECTF_HMIXER);
   }
//   _asm int 3;
//   }break;
  }break;
#endif
  case WM_COMMAND: switch (LOWORD(wParam)) {
   case IDOK: {
    TCHAR buf[32],e[ELEN];
    GetDlgItemText(Wnd,101,buf,elemof(buf));
    if (!String2Float(buf,qs->lsb_volt,e)) {
     SetEditFocus(Wnd,101);
     MBox(Wnd,201,MB_OK|MB_ICONEXCLAMATION);
     return FALSE;
    }
    qs->coupling=(BYTE)IsDlgButtonChecked(Wnd,103);
    WriteString(T("Soundkarte"),T("VoltProLsb"),buf);
    WriteString(T("Soundkarte"),T("Kopplung"),qs->coupling?T("AC"):T("DC"));
#ifdef WIN32
    _sntprintf(buf,elemof(buf),T("%i"),SendDlgItemMessage(Wnd,10,TBM_GETPOS,0,0));
    WriteString(T("Soundkarte"),T("Mixer"),buf);
#endif
   }break;
  }
 }
 DefDlgBottom(Wnd,Msg,wParam,lParam,&qs->sd);
 return FALSE;
}

MMRESULT Q_SOUND::WaveOpen() {
// Samplerate, Kanalzahl, ByteProSample sind bereits gesetzt,
// die übrigen Felder werden "nachgerechnet"
 if (handle) {waveInClose(handle); handle=0;}
 wf.wFormatTag=WAVE_FORMAT_PCM;
 wf.wBitsPerSample=(WORD)(1<<(byteshift+3));
 wf.nBlockAlign=(WORD)(wf.nChannels<<byteshift);
 wf.nAvgBytesPerSec=wf.nSamplesPerSec*wf.nBlockAlign;
 wf.cbSize=0;
 return waveInOpen(&handle,DevID,&wf,(UINT)MainWnd,0,CALLBACK_WINDOW);
}	// der (DWORD)-Cast produziert bei STRICT ein "push ds"!

// Ermittelt die nächstliegende gängige Samplerate
MMRESULT Q_SOUND::NearestRate(DWORD rate) {
// Lt. Dr.Kakuschke unterstützen Soundkarten bis zu 96 kSa/s,
// vielleicht auch noch mehr Kanäle, auch bei Aufnahme?
 static DWORD Rates[]={96000L,48000L,44100L,22050L,11025L,8000L,4000L,2000L,1000L};
 int i;
 DWORD oldrate=wf.nSamplesPerSec;	// Retten zum Vergleich
// ...dem Spuk idiotischer Soundkarten-Treiber eine Grenze setzen!
// Auch in mittelferner Zukunft wird es keine Soundkarten mit mehr als 1 MSa/s
// geben. Aber die "Avance AC97 Audio" spinnt unter Win9x gewaltig!
 if (bekloppt) {
  if (rate>44100U) rate=44100U;		// stärker einschränken!
  if (rate<11025U) rate=11025U;
 }
 wf.nSamplesPerSec=rate;		// Erst mal direkt probieren
 if (!WaveOpen()) {
  if (rate==1000000L) bekloppt=true;
  else{
   if (MinRate>rate) MinRate=rate;
   if (MaxRate<rate) MaxRate=rate;
  }return 0;		// Treiber == Allesfresser!
 }
 if (rate>MaxRate) { wf.nSamplesPerSec=MaxRate; if (!WaveOpen()) return 0;}
 if (rate<MinRate) { wf.nSamplesPerSec=MinRate; if (!WaveOpen()) return 0;}
 for (i=elemof(Rates); --i>=0;) if (Rates[i]>rate) {
  wf.nSamplesPerSec=Rates[i];		// Nur die größeren probieren, erst mal
  if (!WaveOpen()) return 0;
 }
 for (i=0; i<elemof(Rates); i++) if (Rates[i]<rate) {
  wf.nSamplesPerSec=Rates[i];		// Jetzt die kleineren probieren
  if (!WaveOpen()) return 0;
 }
 wf.nSamplesPerSec=oldrate;	// Wenn nichts mehr hilft, unverändert lassen
 return WaveOpen();
}
// Probiert für gegebene Samplerate und Bits die Anzahl verfügbarer Kanäle durch
void Q_SOUND::MaxChannels() {
 for (int i=8; i; i-=2) {	// 8, 6, 4, 2 ausprobieren
  wf.nChannels=(WORD)i;
  if (!WaveOpen()) return;
 }
 wf.nChannels=1;		// sonst nur 1 Kanal
 WaveOpen();
}
// Probiert für gefundene Kanalzahl die Bitbreite (8 oder 16) durch
void Q_SOUND::MaxBits() {
 byteshift=1;			// 16 bit
 if (!WaveOpen()) return;
 byteshift--;			// 0 = 8 bit
}

void Q_SOUND::NextWaveHdr(LPWAVEHDR FAR&whp) {
 if (whp) {
  whp->dwFlags&=~WHDR_DONE;
  waveInAddBuffer(handle,whp,sizeof(WAVEHDR));
 }
 whp=NULL;
 if (wh[currentblock].dwFlags&WHDR_DONE) {
  whp=wh+currentblock;
  currentblock++; if (currentblock==elemof(wh)) currentblock=0;
 }
}
#ifdef WIN32
void ListLineControls(HMIXER hMixer,UINT LineID,UINT cControls) {
 MIXERCONTROL *mc=new MIXERCONTROL[cControls];
 MIXERLINECONTROLS mlc;
 mlc.cbStruct=sizeof(mlc);
 mlc.cControls=cControls;
 mlc.dwLineID=LineID;
 mlc.dwControlType=0;
 mlc.cbmxctrl=sizeof(MIXERCONTROL);
 mlc.pamxctrl=mc;
 mixerGetLineControls((HMIXEROBJ)hMixer,&mlc,MIXER_GETLINECONTROLSF_ALL|MIXER_OBJECTF_HMIXER);
 TCHAR s[1024],*p=s;
 for (UINT i=0; i<cControls; i++) {
  TCHAR typ[64],*ptyp=typ;
  wsprintf(typ,T("%X"),mc[i].dwControlType);
  switch (mc[i].dwControlType) {
   case MIXERCONTROL_CONTROLTYPE_VOLUME: ptyp=T("MIXERCONTROL_CONTROLTYPE_VOLUME"); break;
   case MIXERCONTROL_CONTROLTYPE_MIXER:  ptyp=T("MIXERCONTROL_CONTROLTYPE_MIXER");  break;
   case MIXERCONTROL_CONTROLTYPE_MUX:    ptyp=T("MIXERCONTROL_CONTROLTYPE_MUX");    break;
  }
  p+=wsprintf(p,T("%s,%s,%s,%X,%d,(%d,%d),(%d,%d)"),
    mc[i].szName,mc[i].szShortName,ptyp,mc[i].fdwControl,mc[i].cMultipleItems,
#ifdef __BORLANDC__
    mc[i].Bounds.dwReserved[0],mc[i].Bounds.dwReserved[1],
#else
    mc[i].Bounds.lMinimum,mc[i].Bounds.lMaximum,
#endif
    mc[i].Metrics.cSteps,mc[i].Metrics.cbCustomData);
  /*if (typ!=ptyp)*/ {
   int j=1; if (mc[i].fdwControl&MIXERCONTROL_CONTROLF_MULTIPLE) j=mc[i].cMultipleItems;
   PMIXERCONTROLDETAILS_UNSIGNED val=new MIXERCONTROLDETAILS_UNSIGNED[j];
   MIXERCONTROLDETAILS mcd;
   mcd.cbStruct=sizeof(mcd);
   mcd.dwControlID=mc[i].dwControlID;	// Schon eindeutig?
   mcd.cChannels=1;			// alle Kanäle
   mcd.cMultipleItems=mc[i].cMultipleItems;
   mcd.cbDetails=sizeof(val);		// Hä?
   mcd.paDetails=val;
   mixerGetControlDetails((HMIXEROBJ)hMixer,&mcd,MIXER_GETCONTROLDETAILSF_VALUE|MIXER_OBJECTF_HMIXER);
   for (int i=0; i<j; i++) {
    p+=wsprintf(p,T(",%u"),val[i].dwValue);	// Lautstärke?
   }
  }
  p+=wsprintf(p,T("\n"));
 }
 delete[] mc;
 if (cControls) MessageBox(MainWnd,s,T("LineControls"),0);
}
#endif
/*
void NextWaveHdr(NPVOID p, LPWAVEHDR FAR*whp) {
 ((Q_SOUND*)p)->NextWaveHdr(*whp);	// Memberfunktionsaufruf
}
*/
bool Q_SOUND::RelayMsg(Q_MSG Msg,LPVOID lParam) {
 switch (Msg) {
  case Q_INIT: {
   QUELLE::RelayMsg(Msg,lParam);
   switch (waveInGetNumDevs()) {
    case 0: return false;	// Keine WaveIn-Geräte
    case 1: DevID=0; break;	// ein Gerät: alles klar
    default: {
     DevID=GetInt(T("Soundkarte"),T("Nummer"),-1);
     if ((int)DevID<0 && DialogBoxParam(HInstance,MAKEINTRESOURCE(122),MainWnd,
       WaveInDlgProc,(LPARAM)&DevID)!=IDOK) return false;
    }
   }
   handle=0;
   byteshift=1;
   bekloppt=false;
   wf.nSamplesPerSec=22050;
   MaxChannels();
   if (!handle) {
    byteshift--;
    MaxChannels();
   }
   MaxBits();
   NearestRate(1000L);
   MinRate=wf.nSamplesPerSec;	// Minimale Samplerate merken
   UINT e=NearestRate(1000000L);
   MaxRate=wf.nSamplesPerSec;	// Maximale Samplerate merken
   if (e) {
    TCHAR msg[200];
    waveInGetErrorText(e,msg,elemof(msg));
    MBox(MainWnd,222,MB_OK,(LPCTSTR)msg);
    break;
   }
#ifdef WIN32
   hMixer=0; dwLineID=0;
   DWORD cControls;
   mixerOpen(&hMixer,(UINT)handle,(UINT)MainWnd,0,CALLBACK_WINDOW|MIXER_OBJECTF_HWAVEIN);
   if (hMixer) {
    MIXERCAPS mc;
    mixerGetDevCaps((UINT)hMixer,&mc,sizeof(mc));
    for (UINT i=0; i<mc.cDestinations; i++) {
     MIXERLINE ml;
     ml.cbStruct=sizeof(ml);
     ml.dwDestination=i;
     mixerGetLineInfo((HMIXEROBJ)hMixer,&ml,MIXER_GETLINEINFOF_DESTINATION|MIXER_OBJECTF_HMIXER);
     if (ml.dwComponentType==MIXERLINE_COMPONENTTYPE_DST_WAVEIN) {
      ListLineControls(hMixer,ml.dwLineID,ml.cControls);
      for (UINT i=0; i<ml.cConnections; i++) {
       MIXERLINE ml2;
       ml2.cbStruct=sizeof(ml2);
       ml2.dwDestination=ml.dwDestination;
       ml2.dwSource=i;
       mixerGetLineInfo((HMIXEROBJ)hMixer,&ml2,MIXER_GETLINEINFOF_SOURCE|MIXER_OBJECTF_HMIXER);
       if (ml2.dwComponentType==MIXERLINE_COMPONENTTYPE_SRC_AUXILIARY
        || ml2.dwComponentType==MIXERLINE_COMPONENTTYPE_SRC_LINE) {
        cControls=ml2.cControls;
        dwLineID=ml2.dwLineID;	// Gefunden!!
        break;
       }
      }
     }
    }
    if (dwLineID) {
     ListLineControls(hMixer,dwLineID,cControls);
     MIXERCONTROL mc;
     MIXERLINECONTROLS mlc;
     mlc.cbStruct=sizeof(mlc);
     mlc.cControls=1;
     mlc.dwLineID=dwLineID;	// Gefundene ID
     mlc.dwControlType=MIXERCONTROL_CONTROLTYPE_VOLUME;
     mlc.cbmxctrl=sizeof(MIXERCONTROL);
     mlc.pamxctrl=&mc;
     mixerGetLineControls((HMIXEROBJ)hMixer,&mlc,MIXER_GETLINECONTROLSF_ONEBYTYPE|MIXER_OBJECTF_HMIXER);
     dwControlID=mc.dwControlID;
     MIXERCONTROLDETAILS_UNSIGNED val;
     MIXERCONTROLDETAILS mcd;
     mcd.cbStruct=sizeof(mcd);
     mcd.dwControlID=dwControlID;	// Schon eindeutig?
     mcd.cChannels=1;			// alle Kanäle
     mcd.cMultipleItems=0;
     mcd.cbDetails=sizeof(val);
     mcd.paDetails=&val;
     mixerGetControlDetails((HMIXEROBJ)hMixer,&mcd,MIXER_GETCONTROLDETAILSF_VALUE|MIXER_OBJECTF_HMIXER);
    }
   }
#endif
   lsb_volt= byteshift ? 1E-4F : 1E-2F;
   coupling=CHAN_AC;
   {
    TCHAR buf[32],e[ELEN];
    Float2String(buf,lsb_volt,MAKEWORD(-2,0),T("V"));
    GetString(T("Soundkarte"),T("VoltProLsb"),buf,buf,elemof(buf));
    String2Float(buf,lsb_volt,e);
    GetString(T("Soundkarte"),T("Kopplung"),T("AC"),buf,elemof(buf));
    if (!lstrcmpi(buf,T("DC"))) coupling=CHAN_DC;
#ifdef WIN32
    GetString(T("Soundkarte"),T("Mixer"),buf,buf,elemof(buf));
#endif
   }
   st.mask=0x80;		// wird bei 16bit (noch) nicht ausgewertet
   st.blockalign=wf.nBlockAlign;
   st.min= byteshift ?-0x8000 :-0x80;
   st.max= byteshift ? 0x7FFF : 0x7F;
  }return true;

  case Q_SETUPDLG: {
   sd.dlgProc=WaveSetupDlgProc;
   sd.helpId=123;
  }return QUELLE::RelayMsg(Msg,lParam);

  case Q_GETSYSINFO: {
#define si ((LPSYSINFO)lParam)
   if (!si) return false;
   si->flags=Q_SOFTTRIGGER|Q_CONTINUOUS;
   si->bits=(BYTE)wf.wBitsPerSample;
   si->numchan=si->numtrig=(BYTE)wf.nChannels;
   si->rateminmax[0]=(float)MinRate;
   si->rateminmax[1]=(float)MaxRate;
   si->depth=0;		// unendlich
   si->getsample=byteshift?GetShortSample:GetCharSample;
   si->blockalign=wf.nBlockAlign;
#undef si
  }return true;

  case Q_GETCHANINFO: {
#define ci ((LPCHANINFO)lParam)
   if (!ci) return false;	// NULL-Zeiger
   if (ci->ch>=wf.nChannels) return false;
   ci->couplings=(BYTE)(coupling ? CHANINFO_AC : CHANINFO_DC);
   ci->byteoffset=(BYTE)(ci->ch<<byteshift);
   ci->mask=0x80;		// wird bei GetShortSample nicht ausgewertet
   ci->voltminmax[0]=ci->voltminmax[1]=lsb_volt;
#undef ci
  }return true;

  case Q_SETCHAN: {
#define c ((LPCHAN)lParam)
   if (!c) return false;
   if (c->ch>=wf.nChannels) return false;
//   if (c->what&CHAN_COUPLING && c->coupling==CHAN_DC) return false;
   c->coupling=coupling;
   c->resistance=CHAN_R1M;
   c->volt=lsb_volt;
   c->dcoffset=0;
#undef c
  }return true;

  case Q_GETTRIGINFO: {
#define ti ((LPTRIGINFO)lParam)
   if (!ti) return false;
   *(LPDWORD)ti=MAKELONG(MAKEWORD(TRIGINFO_AC,0),
     MAKEWORD(TRIGINFO_RISE|TRIGINFO_FALL,TRIGINFO_CHAN));
   if (coupling==CHAN_DC) ti->couplings=TRIGINFO_DC|TRIGINFO_AC;
#undef ti	// bei kontinuierlicher Quelle Sache der Software!
  }return true;

  case Q_SETTRIG: {
   if (!QUELLE::RelayMsg(Msg,lParam)) return false;
#define t ((LPTRIG)lParam)
   if (t->what&TRIG_SOURCE) {
    if (t->source>=wf.nChannels) return false;
    st.blockadd=(BYTE)(t->source<<byteshift);
   }
   if (t->what&TRIG_PRE) st.pre=t->pre*wf.nBlockAlign;
   t->source=(BYTE)(st.blockadd>>byteshift);
   t->pre=st.pre/wf.nBlockAlign;
#undef t
  }return true;

  case Q_SETRATE: {
#define f ((LPFLOAT)lParam)
   if (!f) return false;
   if (*f>0) {
    BYTE st=state;
    if (st&Q_ARMED) RelayMsg(Q_UNARM,0);
    if (!NearestRate(rndint(*f))) { // Runden lassen
     if (st&Q_ARMED) RelayMsg(Q_ARM,0);
    }else{
     MessageBeep((UINT)-1);	// Einzige Ursache: Ressource geklaut?
     return false;
    }
   }
   *f=(float)wf.nSamplesPerSec;	// Samplerate ist umstellbar!
#undef f
  }return true;

  case Q_ARM: {
   if (!handle) break;
//   filled_to=buffer;
   if (state&Q_ARMED) break;
   for (int i=0; i<elemof(wh); i++) {
    wh[i].lpData=(LPSTR)/*GlobalLock(hb[i]);*/
    /*hb[i]=*/GlobalAlloc(/*GMEM_MOVEABLE*/GMEM_FIXED|GMEM_SHARE,2048);
    wh[i].dwBufferLength=2048;
    wh[i].dwFlags=0;
    waveInPrepareHeader(handle,wh+i,sizeof(WAVEHDR));
    waveInAddBuffer(handle,wh+i,sizeof(WAVEHDR));
   }
   currentblock=0;
   waveInStart(handle);
   state|=Q_ARMED;
  }break;

  case Q_UNARM: {
   if (!handle) break;
   if (!(state&Q_ARMED)) break;
   state&=(BYTE)~Q_ARMED;
   waveInReset(handle);
   for (int i=0; i<elemof(wh); i++) {
    waveInUnprepareHeader(handle,wh+i,sizeof(WAVEHDR));
//    GlobalUnlock(hb[i]);
    GlobalFree(/*hb*/wh[i].lpData);
   }
   WaveHdr.lpNext=NULL;	// Zeiger gilt nicht mehr
  }break;

  case Q_DONE: {
   RelayMsg(Q_UNARM,0);	// idiotensicher!
#ifdef WIN32
   if (hMixer) mixerClose(hMixer);
#endif
   if (!handle) break;
   waveInClose(handle);
  }break;

  case Q_POLL:	// Nächsten Waveform-Block bereitstellen
    NextWaveHdr(((LPWAVEHDR)lParam)->lpNext); break;
/* TRACKBAR_CLASS
   for (;;) {
    LPSTR p=w->lpData;	// eigentlich LPVOID, aber MSVC stellt sich blöd
// hier: pos=SucheTrigger(p,w->dwBytesRecorded)
// Bei pos=-1 (nicht gefunden) muss nur der Prätrigger-Anteil
// (-tr.post) vom Ende her gespeichert werden.
    while (w->dwBytesRecorded) {
     UINT platz=(UINT)buffer_end-(UINT)filled_to;	// Bytes
     if (platz>w->dwBytesRecorded) platz=(UINT)w->dwBytesRecorded;
     CopyMemory(filled_to,p,platz);
     filled_to+=platz; if (filled_to==buffer_end) filled_to=buffer;
     p+=platz;
     w->dwBytesRecorded-=platz;	// verbleibende Bytes
    }
    waveInAddBuffer(handle,w,sizeof(WAVEHDR));
    currentblock++; if (currentblock==elemof(wh)) currentblock=0;
   }*/
 }
 return false;
}

Detected encoding: ANSI (CP1252)4
Wrong umlauts? - Assume file is ANSI (CP1252) encoded