Source file: /~heha/Mikrocontroller/Sternhimmel/PC.zip/Stern.cpp

#include <windows.h>
#include <windowsx.h>
#include <shlwapi.h>
#include <commctrl.h>
#include "usbcalls.h"
#define nobreak
#define elemof(x) (sizeof(x)/sizeof(*(x)))
#define T(x) TEXT(x)

HINSTANCE ghInst;
HANDLE ghHID;
HWND ghMainWnd;

struct{
 char id1;
 char Hell;
 char id2;
 char Prog;
 char id3;
 char lht[100];
 char id4;
 char bootloadjmp;
}HidFeatures;

static WORD randseed;

static _declspec(naked) BYTE random() { _asm{
	mov	ah,8
	mov	ecx,dword ptr[randseed]
l1:	mov	al,ch
	and	al,0xB4
	setpo	al
	shr	al,1
	rcl	ecx,1
	dec	ah
	jnz	l1
	xchg	eax,ecx
	mov	[randseed],ax
	ret
}}

static BYTE random(BYTE max) {
 BYTE r;
 do r=random(); while (r>max);
 return r;
}

static BYTE random(BYTE min, BYTE max) {
 return random(max-min)+min;
}

static BYTE random(BYTE min, BYTE max, const BYTE*na, BYTE nalen) {
 BYTE r;
 do r=random(min,max); while (memchr(na,r,nalen));
 return r;
}

// Zuordnung einer LED zu einem Sternbild
static const BYTE Sternbild[100]={
 0x00,0x00,0x01,0x00,0x0B,0x00,0x09,0x03,0x0A,0x03,
 0x0E,0x0E,0x0D,0x03,0x01,0x0B,0x03,0x0A,0x0C,0x0C,
 0x0D,0x00,0x00,0x08,0x09,0x03,0x00,0x0A,0x0E,0x0B,
 0x01,0x03,0x0E,0x09,0x08,0x0A,0x0B,0x00,0x00,0x0A,
 0x00,0x02,0x00,0x02,0x0D,0x01,0x0C,0x06,0x0C,0x0B,
 0x02,0x08,0x09,0x03,0x01,0x0A,0x0C,0x06,0x0D,0x02,
 0x0E,0x0D,0x08,0x01,0x04,0x02,0x06,0x06,0x06,0x01,
 0x0D,0x09,0x04,0x07,0x05,0x04,0x05,0x06,0x05,0x00,
 0x09,0x04,0x07,0x07,0x07,0x05,0x0F,0x05,0x0F,0x00,
 0x04,0x00,0x04,0x07,0x0F,0x01,0x02,0x0F,0x0F,0x02};
/* Low-Nibble = Sternbild-Zuordnung, High-Nibble = relative Helligkeit?
 0 = frei
 1 = Orion
 2 = großer Bär
 3 = kleiner Bär
 4 = großer Hund
 5 = Himmels-W
 6 = Schwan
 7 = Kreuz des Südens
 8 = Zwillinge
 9 = Schlange
 A = Löwe
 B = Stier
 C = Perseus
 D = Pegasus
 E = Zentaur
 F = Skorpion
*/

static struct{
 WORD phase;
 BYTE bild;
 BYTE bild0;	// vorhergehendes Bild (zum Ausblenden bei kleinen Phasenwerten)
 BYTE quasar[8];
 BYTE super;	// Stern mit Supernova, 100..255 = kein Stern
 BYTE quasph[8];// Phasen (Frequenz gleich)
 BYTE quasi;	// Index für auszuwechselnden Quasar
 BYTE disko;	// Phase für Disko, Helligkeitssteller = Freuenz
 BYTE flimmer[16];	// 16 Flimmer-Frequenzen
 BYTE langsam[16];	// 16 langsame Frequenzen
 BYTE wasser;	// Richtung der Wellenfront (0..7);
 BYTE automode;	// Automatischer Themenwechsel
 BYTE prog1;	// Programm-Nummer, 0 = gleiche Helligkeit, 1 = Sternbilder zeigen usw.
 BYTE teil1;	// Anteil der Helligkeit bei der Mischung (1..4)
 BYTE prog2;	// Sekundär-Programm
 BYTE teil2;	// Anteil der Helligkeit bei der Mischung (0..3)
}s;
/*	Prog	prog1	teil1	prog2	teil2
	0	0	4	-	0
	1	0	3	1	1
	2	0	2	1	2
	3	0	1	1	3
	4	1	4	-	0
	...
	28	7	4	-	0
	29	7	3	0	1
	30	7	2	0	2
	31	+	4			// schaltet prog1 0..7, teil1=4, bei Programmwechsel Sekundärprogramm = 0?
*/

// Gleichmäßige Helligkeit
static BYTE ProgHell0(BYTE) {
 return HidFeatures.Hell;
}

// Sternbilder zeigen
static BYTE ProgHell1(BYTE i) {
 BYTE anteil=s.phase&255;
 if (anteil>=64) anteil=64;
 if ((Sternbild[i]^s.bild)&0x0F) {	// nicht aktuelles Sternbild
  if (anteil>=64 || (Sternbild[i]^s.bild0)&0x0F) return 0;	// vorhergehendes? nein!
  else anteil=63-anteil;
 }
 return (HidFeatures.Hell*anteil)>>6;
}

// Quasare
static BYTE ProgHell2(BYTE i) {
 for (int j=0; j<8; j++) if (s.quasar[j]==i) break;
 if (j==8) return 0;		// kein Quasar
 BYTE ph=BYTE(s.phase)+s.quasph[j];
 BYTE anteil=ph&7;		// 0..7
 if (ph&8) anteil=8-anteil;	// 8..1
 return (HidFeatures.Hell*anteil)>>3;
}

// Supernova
// Problem: - Sollte auch gemischt mit maximaler Helligkeit leuchten
static BYTE ProgHell3(BYTE i) {
 if (i!=s.super) return HidFeatures.Hell>>1;	// halbe Grundhelligkeit
 if (s.phase&0x200) return 31;			// Maximum
 BYTE anteil=BYTE(s.phase>>1);
 return anteil>>3;				// 0..31;
}

// Disko
static BYTE ProgHell4(BYTE) {
 return ~s.disko>>3;		// Fallende Intensität
}

// Flimmern
static BYTE ProgHell5(BYTE i) {
 BYTE posrand=(i^Sternbild[i])&0x0F;	// 16 Frequenzen, determiniert aber verstreut verteilt
 BYTE anteil=s.flimmer[posrand];
 if (anteil&0x80) anteil=-anteil;	// 0..128
 return (HidFeatures.Hell*anteil)>>7;	// Zwei Flanken
}

// Langsam
static BYTE ProgHell6(BYTE i) {
 BYTE posrand=(~i^Sternbild[i])&0x0F;	// 16 Frequenzen, determiniert aber verstreut verteilt
 BYTE anteil=s.langsam[posrand];
 if (anteil&0x80) anteil=-anteil;	// 0..128
 return (HidFeatures.Hell*anteil)>>7;	// Zwei Flanken
}

// Wasserfront (hell oder dunkel je nach Helligkeitssteller)
static BYTE ProgHell7(BYTE i) {
 div_t d=div(i,10);			// Mikrocontroller-Implementierung per Subtraktion!
 signed char logpos;
 switch (s.wasser&3) {
  case 0: logpos=d.quot; break;		// gerade (0..9)
  case 1: logpos=d.rem; break;
  case 2: logpos=d.quot+d.rem; break;	// diagonal (0..18)
  case 3: logpos=d.quot-d.rem; break;	// andersherum diagonal (-9..9)
 }
 if (s.wasser&4) logpos=-logpos;	// Gegenrichtung (-18..18 möglich)
 signed char h=0;
 if ((s.phase&255)<64) {
  h=(s.phase&255)-32;			// -32 .. 31
  h+=logpos;				// -50 .. 49
  if (h>=0) h=~h;			// Bei Null maximale Helligkeit, neg. Betrag bilden (-50..0)
  h+=31;				// -19 .. 31
  if (h<0) h=0;				// alles Negative wegschneiden
 }
 if (HidFeatures.Hell>=16) {
  h=31-h;
  if (h>HidFeatures.Hell) h=HidFeatures.Hell;
 }else{
  if (h<HidFeatures.Hell) h=HidFeatures.Hell;
 }
 return h;
}

static BYTE(*ProgHell[8])(BYTE)={
 ProgHell0,
 ProgHell1,
 ProgHell2,
 ProgHell3,
 ProgHell4,
 ProgHell5,
 ProgHell6,
 ProgHell7};

static void PeriodicAction() {
 if (HidFeatures.Prog==31) {	// variable Programme
  s.automode=1;
 }else{		// feste Programme und Anteile
  s.automode=0;
  s.prog1=HidFeatures.Prog>>2;	// 0..7
  s.teil2=HidFeatures.Prog&3;	// 0..3
  s.prog2=(s.prog1+1)&7;	// 0..7
 }
 s.teil1=4-s.teil2;		// 1..4
 for (int i=0; i<100; i++) {
  BYTE h=ProgHell[s.prog1](i)*s.teil1;
  if (s.teil2) h+=ProgHell[s.prog2](i)*s.teil2;
  h>>=2;
  if ((HidFeatures.lht[i]^h)&0x1F) HidFeatures.lht[i]=h;
 }
 s.phase++;			// 10 ms * 256 = 2,56 Sekunden pro Sternbild (vorerst)
 if (!(s.phase&255)) {
  s.bild0=s.bild;
  s.bild=random(1,15,&s.bild0,1);
 }
 if (!(s.phase&63)) {
  s.quasar[s.quasi]=random(0,99,s.quasar,9);
  s.quasph[s.quasi]=random();
  s.quasi=(s.quasi+1)&7;
 }
 if (!(s.phase&1023)) {
  s.super=random(0,99,s.quasar,9);	// nicht immer ist eine Supernova zu sehen?
  if (s.automode) {
   s.prog2=random(0,7,&s.teil1,2);	// die "4" (Disko), die in s.teil1 steht, trickreich ausschließen!
  }
 }
 s.disko+=16+HidFeatures.Hell;	// Stellverhältnis 3:1
 for (i=0; i<16; i++) s.flimmer[i]+=i+3;
 if (!(s.phase&15)) for (i=0; i<16; i++) s.langsam[i]+=(i>>2)+1;	// 4 Geschwindigkeiten
 if (!(s.phase&255)) s.wasser=random(0,7,&s.wasser,1);
 if (s.automode && (s.phase&1023)<64 && !(s.phase&15)) {
  s.teil2=((s.phase>>4)+1)&3;		// 1-2-3-0	(Intensität des neuen Motivs)
  if (!s.teil2) s.prog1=s.prog2;	// neues Motiv = erstes Motiv
 }
}

HBRUSH brushes[32];
bool DrawNumber;

static void DrawLed(HDC dc, const RECT*R, int index) {
 TCHAR buf[3];
 int mx=((R->right+R->left)>>1)+1;
 if (DrawNumber)
   ExtTextOut(dc,mx,R->top,0,R,buf,wnsprintf(buf,elemof(buf),T("%d"),index),NULL);
 index=HidFeatures.lht[index]&0x1F;
 SelectBrush(dc,brushes[index]);
 Ellipse(dc,R->left+1,R->top+12,R->right-1,R->bottom);
 if (DrawNumber) {
  COLORREF oc=SetTextColor(dc,index>=24?0x000000:0xFFFFFF);
  ExtTextOut(dc,mx,R->top+15,0,R,buf,wnsprintf(buf,elemof(buf),T("%d"),index),NULL);
  SetTextColor(dc,oc);
 }
}
struct DIS:DRAWITEMSTRUCT {
 void DrawLeds() const;
};

void DIS::DrawLeds() const {
 if (DrawNumber) {
  SetTextAlign(hDC,TA_TOP|TA_CENTER);
  SetBkMode(hDC,TRANSPARENT);
 }
// gleichmäßiges Gitter machen
 SIZE delta;
 delta.cx=(rcItem.right-rcItem.left)/10;
 delta.cy=(rcItem.bottom-rcItem.top)/10;
// LEDs einzeln malen
 int i,xx,yy;
 RECT R;
 for (i=yy=0,R.bottom=rcItem.bottom; yy<10; yy++,R.bottom=R.top) {
  R.top=R.bottom-delta.cy;
  for (xx=0,R.left=0; xx<10; xx++,i++,R.left=R.right) {
   R.right=R.left+delta.cx;
   if (!(HidFeatures.lht[i]&0x80)) {
    DrawLed(hDC,&R,i);
    HidFeatures.lht[i]^=0x80;
   }
  }
 }
}

static void RedrawLeds(void) {
 InvalidateRect(GetDlgItem(ghMainWnd,103),NULL,FALSE);
}

static LONG_PTR CALLBACK MainDlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
 switch (Msg) {
  case WM_INITDIALOG: {
   ghMainWnd=Wnd;
   do; while (!(randseed=(WORD)GetTickCount()));
   HidFeatures.id1=1;
   HidFeatures.id2=2;
   HidFeatures.id3=3;
   HidFeatures.id4=4;
   ghHID=usbOpenDevice(0,0x16c0,NULL,1503,T("h#s Sternhimmel"));
   brushes[0]=GetStockBrush(BLACK_BRUSH);
   brushes[31]=GetStockBrush(WHITE_BRUSH);
   for (int i=1; i<31; i++) {
//    const BYTE gamma[30]={16,32,48,64,74,84,94,104,110,116,122,128,134,140,146,152,158,164,170,176,182,198,202,206,210,214,218,222,226,230,234,238,242,248,252};
    BYTE v=(i<<3)|(i>>2);
    brushes[i]=CreateSolidBrush(RGB(v,v,v));
   }
   SendDlgItemMessage(Wnd,101,TBM_SETRANGE,FALSE,MAKELONG(0,31));
   SendDlgItemMessage(Wnd,102,TBM_SETRANGE,FALSE,MAKELONG(0,31));
   HidFeatures.Hell=HidFeatures.Prog=31;
   for (i=0; i<32; i++) s.flimmer[i]=random();
   SetTimer(Wnd,1,10,NULL);
  }return TRUE;
  case WM_TIMER: {
   PeriodicAction();
   RedrawLeds();
  }break;
  case WM_ERASEBKGND: {
   for (int i=0; i<100; i++) HidFeatures.lht[i]&=0x1F;
  }break;
  case WM_DRAWITEM: {
   ((DIS*)lParam)->DrawLeds();
  }break;
  case WM_VSCROLL: switch (LOWORD(wParam)) {
   case TB_THUMBTRACK:
   case TB_ENDTRACK: {
    int i=31-(int)SendMessage((HWND)lParam,TBM_GETPOS,0,0);
    switch (GetDlgCtrlID((HWND)lParam)) {
     case 101: HidFeatures.Hell=i; break;
     case 102: HidFeatures.Prog=i; break;
    }
    HidFeatures.lht[98]=HidFeatures.Hell;
    HidFeatures.lht[99]=HidFeatures.Prog;
    RedrawLeds();
   }break;
  }break;
  case WM_COMMAND: switch (LOWORD(wParam)) {
   case IDCANCEL: EndDialog(Wnd,wParam); break;
  }break;
 }
 return FALSE;
}

void CALLBACK WinMainCRTStartup() {
 ghInst=GetModuleHandle(NULL);
 InitCommonControls();
 ExitProcess((UINT)DialogBox(ghInst,MAKEINTRESOURCE(100),0,(DLGPROC)MainDlgProc));
}

/*
Programm-Ideen für Programm-Wähler
* Sternbilder zeigen (ca. 10 s)
* Quasare (ca. 1 Hz, bis zu 10 Sterne)
* Supernovas (1 Stern, ca. 30 s)
* Disko (alle Sterne synchron, Frequenz einstellbar)
* Flimmern (wie Disko, jedoch verschiedene Frequenzen)
* langsame Helligkeitänderung (wie Flimmern)
* "Wasserfront" (helle Welle), Wolken (dunkle Welle)
* alles (außer Disko)
*/
Detected encoding: UTF-80