Quelltext /~heha/ewa/Reluktanzmotor/cdesk-201007.zip/simulate.cpp

#include "cdesk.h"
#include "simulate.h"
#include "editx.h"
#include <cmath>
#include <tchar.h>

/*
Simulate::Simulate(INITPARAM*_ip,cb_t _cb,void*_cbd):
	ip(_ip),cb(_cb),cbd(_cbd),hThread(0) {}

bool Simulate::okay() const {return ip->wf.nChannels*sizeof(GENPARAM)==ip->wf.cbSize;}

bool Simulate::start() {
 if (hThread) return false;	// Thread läuft bereits
 if (!okay()) return false;	// Inkorrekte Generierungsdaten
 hTerminate=CreateEvent(0,0,0,0);
 if (hTerminate==INVALID_HANDLE_VALUE) {
  return false;			// Kein hTerminate
 }
 time=0;
 hThread=CreateThread(0,0,threadproc,this,0,&threadId);
 if (hThread==INVALID_HANDLE_VALUE) {
  hThread=0;
  CloseHandle(hTerminate);
  return false;			// kein hThread
 }
 return true;
}

bool Simulate::stop() {
 if (!hThread) return false;	// Thread läuft nicht
 SetEvent(hTerminate);
 WaitForSingleObject(hThread,INFINITE);
 CloseHandle(hTerminate);
 CloseHandle(hThread);
 hThread=0;
 return true;
}

Simulate::~Simulate() {
 stop();
}

DWORD Simulate::threadproc(void*p) {
 Simulate*self=(Simulate*)p;
 return self->threadproc();
}

DWORD Simulate::threadproc() {
 unsigned dt=MulDiv(blocklen(),10000000,ip->wf.nSamplesPerSec);
 DWORD wait_ms=dt/10000;
 char*buffer=new char[blocklen()*ip->wf.nBlockAlign];
 while (WaitForSingleObject(hTerminate,wait_ms)) {
  generate(buffer);
  cb(buffer,blocklen(),cbd);
  time+=dt;
 }
 delete[] buffer;
 return 0;
}
*/
// Äußere Schleife: Sample für Sample
// Innere Schleife: Generator für Generator
// Bezüge auf niedrigere oder gleichen Generator sind erlaubt und verzögern um 1 Sample
void Simulate::generate(char*buffer) const{
 ZeroMemory(buffer,blocklen*4);
 for (unsigned i=0; i<blocklen; i++) {
  char*cluster=buffer+4*i;
  for (unsigned j=0; j<genparams.size(); j++) generate(j,cluster);
 }
}

static double limit(double y,double min,double max) {
// TODO: INF/NAN behandeln
 if (!(y>=min)) y=min;
 else if (!(y<=max)) y=max;
 return y;
}

static double noise(double amp) {
 if (amp) amp*=rand()*2./RAND_MAX-1;
 return amp;
}

static double triangle(double p, double center=0.5) {
 if (p>=center) {p=1-p; center=1-center;}
 p*=1/center;
 return p*2-1;		// Ergebnisbereich wie bei Sinus: “1
}

static double rectangle(double p, double center=0.5) {
 return p>=center ? -1 : 1;	// simpel
}

// Typ T: beliebig mit operator*(U), Typ U: Gleitkomma
template<class T,class U> T approxLinear(const T*a,unsigned alen,U index) {
 unsigned i=(unsigned)index;	// abrunden zu ganze Zahl
 if (i>=alen) return 0/*std::numeric_limits<T>::quiet_NaN()*/;		// Fehler: Index außerhalb Array
 index-=i;	// frac()
 unsigned j=i+1; if (j==alen) j=0;	// nächster Index, ggf. um die Litfaßsäule herum
 return a[i]*(1-index)+a[j]*index;
}

void Simulate::generate(unsigned j,char*cluster) const{
 const GENPARAM&gp=genparams[j];
 GENSTATE&gs=genstates[j];
 unsigned idx,nch=unsigned(genparams.size());
 double p;	// Phase
 if (gp.curve!=GENPARAM::noise) {	// Bei weißem Rauschen keine Frequenz- und Phasenberechnung
  p=gs.pha;
  double f=gp.freq;
  if ((idx=gp.fm-1)<nch) f+=genstates[idx].amp;
  double dt=this->dt;
  dt*=f;		// Zeit in Perioden
  p+=dt;
  p-=floor(p);		// p (Phase) ist nun im Intervall [0..+1)
  gs.pha=p;
  if ((idx=gp.pm-1)<nch) {
   p+=genstates[idx].amp;	// Phasenmodulation dazu
   p-=floor(p);
  }
 }
 double y;		// Amplitude
 switch (gp.curve) {
  case GENPARAM::sine:		y=sin(p*2*PI); break;
  case GENPARAM::triangle:	y=triangle(p,gp.sym); break;
  case GENPARAM::rectangle:	y=rectangle(p,gp.sym); break;
  case GENPARAM::noise:		y=noise(1); break;	// Phase egal!!
  case GENPARAM::arb_step:	y=gp.arb[int(p*gp.arblen)]; break;	// Ohne Approximation
  case GENPARAM::arb_lin:	y=approxLinear(gp.arb,gp.arblen,p*gp.arblen); break;	// Mit linearer Approximation
  default: y=0;
 }			// y ist nun im Intervall [-1..+1] (Arbiträrwerte müssen / sollten so angelegt sein.)
 y*=gp.amp;
 y+=gp.ofs;
 if ((idx=gp.am-1)<nch) y*=genstates[idx].amp;	// Amplitudenmodulation dazu
 if ((idx=gp.om-1)<nch) y+=genstates[idx].amp;	// Offsetmodulation (Addition) dazu
 gs.amp=y;		// Im aktuellen Generator-Status ablegen
 switch (gp.contribute) {
  case 1:
  case 2: {
   short&wfd=((short*)cluster)[gp.contribute-1];		// Waveform-Ziel
   wfd=(short)lrint(limit(wfd+y,-32767,+32767));	// Kontributoren addieren
  }break;
 }
/*
 char*base=gp.datatype&0x3F
 ?cluster+gp.offset		// Offset in Bytes für "normale" Zahlen
 :cluster+(gp.offset>>3);		// Offset in Bits für Booleans
 switch (gp.datatype) {
  case 0x00: {
    BYTE mask=1<<(gp.offset&7);
    if (y>=0.5) *base|=mask; else *base&=~mask;
  }break;
  case 0x01: *(BYTE*) base=limit(y,0,255); break;
  case 0x41: *(char*) base=limit(y,-127,127); break;
  case 0x02: *(WORD*) base=limit(y,0,65535); break;
  case 0x42: *(short*)base=limit(y,-32767,32767); break;
  case 0x04: *(DWORD*)base=limit(y,0,0xFFFFFFFF); break;
  case 0x44: *(long*) base=limit(y,-0x7FFFFFFF,0x7FFFFFFF); break;
  case 0xC4: *(float*)base=y; break;
  case 0xC8: *(double*)base=y; break;
 }
*/
}

void Simulate::onListChange(HWND Wnd) {
 HWND hList=GetDlgItem(Wnd,100);
 unsigned i,j,idx=ListBox_GetCurSel(hList);
 ListBox_ResetContent(hList);
 for (i=0; i<genparams.size(); i++) {
  ListBox_AddString(hList,genparams[i].name);
 }
 while (ListBox_SetCurSel(hList,idx)<0) idx=0;	// max. 2 Runden

 for (j=0; j<4; j++) {
  HWND hCombo=GetDlgItem(Wnd,17+j);
  ComboBox_ResetContent(hCombo);
  TCHAR s[16];
  LoadString(ghInstance,113,s,elemof(s));	// "keine" (Modulation)
  ComboBox_AddString(hCombo,s);
  for (i=0; i<genparams.size(); i++) {
   ComboBox_AddString(hCombo,genparams[i].name);
  }
 }
 EnableWindow(GetDlgItem(Wnd,8),genparams.size()>=2);	// "Löschen": Minimal 1 Generator
 EnableWindow(GetDlgItem(Wnd,9),genparams.size()<250);	// "Einfügen": Maximal 250 Generatoren (das ist schon krass!!)
 onListItemChange(Wnd);
}

void Simulate::onListItemChange(HWND Wnd) {
 HWND hList=GetDlgItem(Wnd,100);
 unsigned i,idx=ListBox_GetCurSel(hList);
 TCHAR s[32],t[32];
 LoadString(ghInstance,115,t,elemof(t));	// "Index %u"
 _sntprintf(s,elemof(s),t,idx);
 SetDlgItemText(Wnd,101,s);	// Index anzeigen
 GENPARAM&gp=genparams[idx];
 GetDlgItemText(Wnd,10,s,elemof(s));
 if (lstrcmp(s,gp.name)) SetDlgItemText(Wnd,10,gp.name);	// Nur bei Änderung setzen, sonst verschwindet der Kursor bei Namensänderung
 for (i=0; i<elemof(gp.dvalues); i++) {
  static const short f[]={1,1,1,360,100};
  EditX::getObj(Wnd,12+i)->setDouble(gp.dvalues[i]*f[i]);
 }
 for (i=0; i<elemof(gp.xm); i++) {
  HWND hCombo=GetDlgItem(Wnd,17+i);
  while (ComboBox_SetCurSel(hCombo,gp.xm[i])<0) gp.xm[i]=0;	// max. 2 Runden
 }
 HWND w=GetDlgItem(Wnd,103);
 ComboBox_SetCurSel(w,gp.contribute);
 w=GetDlgItem(Wnd,11);
 ComboBox_SetCurSel(w,gp.curve);
 onCurveChange(Wnd,gp);
}

void Simulate::onCurveChange(HWND Wnd,GENPARAM&gp) {
 HWND w=GetDlgItem(Wnd,102);		// "Editieren"
 EnableWindow(w,gp.curve>=GENPARAM::arb_step);
// TODO: Arbiträr-Kurve setzen erzwingen, wenn noch nicht da
// static const BYTE enables[]={0x0F,0x1F,0x1F,0x06,0x0F};
 BYTE enables=0x0F;	// Standard: Alles außer Symmetrie
 switch (gp.curve) {
  case GENPARAM::triangle:
  case GENPARAM::rectangle: enables|=0x10; break;	// Symmetrie dazu
  case GENPARAM::noise: enables&=~0x09; break;		// Frequenz, Phase weg
 }
 if (gp.fm) enables&=~0x08;	// Phase weg
 for (unsigned i=0; i<elemof(gp.dvalues);i++,enables>>=1) {
  EnableWindow(GetDlgItem(Wnd,12+i),enables&1);
 }
}

// gp dient zum Ausklammern des gerade aktiven GENPARAM-Satzes beim Namensvergleich
bool Simulate::NameUniq(GENPARAM&gp,const TCHAR*s) const {
 for (std::vector<GENPARAM>::const_iterator gpp=genparams.begin(); gpp!=genparams.end(); gpp++) {
  if (&*gpp!=&gp && !lstrcmp(gpp->name,s)) return false;	// Gleiche Namen: Nicht uniq
 }
 lstrcpyn(gp.name,s,elemof(gp.name));
 return true;
}

void Simulate::onNameChange(HWND Wnd,GENPARAM&gp,bool autoassign) {
 TCHAR s[elemof(gp.name)];
 int l=GetDlgItemText(Wnd,10,s,elemof(s));
 if (NameUniq(gp,s)) return;
 if (autoassign) {
  if (l>elemof(s)-4) l=elemof(s)-4;	// mindestens 3 Zeichen und \0 Platz
  for (unsigned i=0; i<999; i++) {
   _sntprintf(s+l,4,TEXT("%u"),i);
   if (NameUniq(gp,s)) {
    SetDlgItemText(Wnd,10,s);	// anzeigen
    return;		// geschafft
   }
  }
 }
 MessageBeep(MB_ICONEXCLAMATION);
}


INT_PTR CALLBACK Simulate::SimulDlgProc(HWND Wnd,UINT msg,WPARAM wParam,LPARAM lParam) {
 LONG_PTR lp=GetWindowLongPtr(Wnd,DWLP_USER);
 std::vector<Simulate>::iterator sim=*(std::vector<Simulate>::iterator*)&lp;	// Zum Teufel mit std::vector!!
// Dreh- und Angelpunkt ist die Liste!
 switch (msg) {
  case WM_INITDIALOG: {
   SetWindowLongPtr(Wnd,DWLP_USER,lParam);
   sim=*reinterpret_cast<std::vector<Simulate>::iterator*>(&lParam);
   FillCombo(Wnd,11,112);
   FillCombo(Wnd,103,114);
   static const char nk[5]={0,0,0,2,2};
   for (unsigned i=0; i<elemof(nk); i++) {
    EditX::getObj(GetDlgItem(Wnd,12+i))->nk=nk[i];
   }
   sim->onListChange(Wnd);
  }return true;
  case WM_COMMAND: {
   int idx=(int)SendDlgItemMessage(Wnd,100,LB_GETCURSEL,0,0);
   GENPARAM&gp=sim->genparams[idx];
   switch (wParam) {
    case MAKELONG(100,LBN_SELCHANGE): sim->onListItemChange(Wnd); break;
    case MAKELONG(12,EN_CHANGE):	// Frequenz/Hz
    case MAKELONG(13,EN_CHANGE):	// Amplitude
    case MAKELONG(14,EN_CHANGE):	// Offset
    case MAKELONG(15,EN_CHANGE):	// Phase/°
    case MAKELONG(16,EN_CHANGE): {	// Symmetrie/%
     double&v=gp.dvalues[LOBYTE(wParam)-12];
     double before=v;
     if (Edit_GetModify((HWND)lParam)
     && EditX::getObj((HWND)lParam)->getDouble(v)) {
      switch (LOBYTE(wParam)) {
       case 15: {
        v/=360;				// Grad-in-Perioden
        double diff=v-before;		// Änderung um…
        sim->genstates[idx].pha+=diff;	// in den Generatorzustand einfließen lassen: Sollte umgehend stimmen!
       }break;
       case 16: v/=100; break;		// Prozent
      }
      Edit_SetModify((HWND)lParam,false);	// sollte sofort wirken!
     }
    }break;
    case MAKELONG(17,CBN_SELCHANGE):
    case MAKELONG(18,CBN_SELCHANGE):
    case MAKELONG(19,CBN_SELCHANGE):
    case MAKELONG(20,CBN_SELCHANGE): {
     gp.xm[LOBYTE(wParam)-17]=ComboBox_GetCurSel((HWND)lParam);
    }break;
    case MAKELONG(11,CBN_SELCHANGE): {
     gp.curve=ComboBox_GetCurSel((HWND)lParam);
     sim->onCurveChange(Wnd,gp);
    }break;
    case MAKELONG(103,CBN_SELCHANGE): {
     gp.contribute=ComboBox_GetCurSel((HWND)lParam);
    }break;
    case MAKELONG(10,EN_CHANGE): if (Edit_GetModify((HWND)lParam)) {
     Edit_SetModify((HWND)lParam,false);
     sim->onNameChange(Wnd,gp);
     sim->onListChange(Wnd);
    }break;
    case 6:
    case 7: {	// Element nach vorn/hinten tauschen
     if (sim->genparams.size()<2) break;	// nichts tun, aber auch nicht meckern
     int idx2=wParam==7?idx+1:idx-1;
     if (idx2>=int(sim->genparams.size())) idx2-=int(sim->genparams.size());
     else if (idx2<0) idx2+=int(sim->genparams.size());
     std::swap(gp,sim->genparams[idx2]);
     SendDlgItemMessage(Wnd,100,LB_SETCURSEL,idx2,0);
// Referenzen anpassen
     idx++;idx2++;	// 1-basiert
     for (std::vector<GENPARAM>::iterator gpp=sim->genparams.begin(); gpp!=sim->genparams.end(); gpp++) {
      for (unsigned j=0; j<elemof(gpp->xm); j++) {
       BYTE&xm=gpp->xm[j];
       if (xm==idx) xm=idx2;
       else if (xm==idx2) xm=idx;	// "nach unten" anpassen
      }
     }
     sim->onListChange(Wnd);
    }break;
    case 8: {	// erase: Oszillator löschen
     if (sim->genparams.size()<2) break;	// 1 Element muss verbleiben, sonst geht InsertBefore nicht. Button sollte dann disabled sein.
     sim->genparams.erase(sim->genparams.begin()+idx);
     sim->genstates.erase(sim->genstates.begin()+idx);
// Referenzen anpassen
     for (std::vector<GENPARAM>::iterator gpp=sim->genparams.begin(); gpp!=sim->genparams.end(); gpp++) {
      for (unsigned j=0; j<elemof(gpp->xm); j++) {
       BYTE&xm=gpp->xm[j];
       if (xm==idx) xm=0xFF;	// auf "keine" stellen
       else if (xm>idx) --xm;	// "nach unten" anpassen
      }
     }
     sim->onListChange(Wnd);
    }break;
    case 9: {	// insert: Oszillator duplizieren (=insertBefore) und eindeutigen Namen vergeben
     if (sim->genparams.size()>=250) break;
     sim->genparams.insert(sim->genparams.begin()+idx,gp);	// Copy-Konstruktor
     GENSTATE&gs=sim->genstates[idx];
     sim->genstates.insert(sim->genstates.begin()+idx,gs);	// Noch ein Copy-Konstruktor
// Referenzen anpassen
     for (std::vector<GENPARAM>::iterator gpp=sim->genparams.begin(); gpp!=sim->genparams.end(); gpp++) {
      for (unsigned j=0; j<elemof(gpp->xm); j++) {
       BYTE&xm=gpp->xm[j];
       if (xm>=idx) ++xm;	// "nach oben" anpassen
      }
     }
     sim->onNameChange(Wnd,gp,true);
     sim->onListChange(Wnd);
    }break;
    case IDOK:
    case IDCANCEL: EndDialog(Wnd,(int)wParam);
    break;
   }
  }break;
 }
 return false;
}
Vorgefundene Kodierung: UTF-80