#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;
}
Detected encoding: ANSI (CP1252) | 4
|
|