#include "zqr.h"
#include <tchar.h>
#include <shlwapi.h> // PathAppend
#include <shlobj.h> // BROWSEINFO
#include <objbase.h>
#include <stdio.h> // _snprintf
#include <Ks.h>
#include <Ksmedia.h> // GUID: KSDATAFORMAT_SUBTYPE_PCM
#include <math.h> // pow, log
CONFIG config;
int CONFIG::NIN=2; // Anzahl Mixer-Eingänge
static TCHAR path[MAX_PATH]; // Pfad zu den MP3- und Vorbis-DLLs
TCHAR file[MAX_PATH]; // Ton-Dateiname mit Tokens
static TCHAR note[MAX_PATH]; // Notizen-Dateiname
HINSTANCE hInstance;
HWND hMainWnd,hDialog,hSettingsDlg,hNotizDlg;
bool CONFIG::load() {
HKEY key;
bool ret=false;
if (!RegOpenKeyEx(HKEY_CURRENT_USER,T("Software\\h#s"),0,KEY_READ,&key)) {
TCHAR s[MAX_PATH];
GetModuleFileName(hInstance,s,elemof(s));
TCHAR*n=PathFindFileName(s);
TCHAR*e=PathFindExtension(n); if (e!=n) *e=0;
HKEY key2;
if (!RegOpenKeyEx(key,n,0,KEY_READ,&key2)) {
DWORD siz=sizeof config;
BYTE block[sizeof config];
if (!RegQueryValueEx(key2,T("config"),0,0,block,&siz)) {
div_t d=div(int(siz-(sizeof config-sizeof config.q)),int(sizeof*config.q));
if (1<=d.quot && d.quot<=MAXIN && !d.rem) { // Länge im gültigen Bereich?
config.NIN=d.quot;
memcpy(&config,block,siz);
ret=true;
} // sonst bleibt's bei der Vorgabe NCH=2
}
siz=sizeof file;
RegQueryValueEx(key2,T("file"),0,0,(BYTE*)file,&siz);
siz=sizeof path;
RegQueryValueEx(key2,T("path"),0,0,(BYTE*)path,&siz);
siz=sizeof note;
RegQueryValueEx(key2,T("note"),0,0,(BYTE*)note,&siz);
RegCloseKey(key2);
}
RegCloseKey(key);
}
return ret;
}
// Speichert unter HKCU\Software\h#s\<exename>,
// sodass der Anwender unter verschiedenen EXE-Dateinamen verschiedene Konfigurationen ablegen kann.
// Einerseits kann das durch mehrfache EXE-Dateikopien geschehen,
// unter NTFS eleganter mit symbolischen oder Hardlinks, wie unter Linux üblich
void CONFIG::save() {
HKEY key;
if (!RegCreateKeyEx(HKEY_CURRENT_USER,T("Software\\h#s"),0,0,0,KEY_WRITE,0,&key,0)) {
TCHAR s[MAX_PATH];
int l=LoadString(hInstance,108,s,elemof(s)); // "haftmann#software"
RegSetValueEx(key,0,0,REG_SZ,(BYTE*)s,(l+1)*sizeof(TCHAR)); // Beschreibung für "h#s"
GetModuleFileName(hInstance,s,elemof(s));
TCHAR*n=PathFindFileName(s);
TCHAR*e=PathFindExtension(n); if (e!=n) *e=0;
HKEY key2;
if (!RegCreateKeyEx(key,n,0,0,0,KEY_WRITE,0,&key2,0)) {
l=GetWindowText(hMainWnd,s,elemof(s)); // "Zweiquellen-Rekorder" oder je nach Anzahl Quellen und Sprache
RegSetValueEx(key2,0,0,REG_SZ,(BYTE*)s,(l+1)*sizeof(TCHAR)); // Beschreibung für <exename>
RegSetValueEx(key2,T("config"),0,REG_BINARY,(BYTE*)&config,sizeof config-sizeof config.q+config.NIN*sizeof*config.q);
RegSetValueEx(key2,T("file"),0,REG_SZ,(BYTE*)file,(lstrlen(file)+1)*sizeof(TCHAR));
RegSetValueEx(key2,T("path"),0,REG_SZ,(BYTE*)path,(lstrlen(path)+1)*sizeof(TCHAR));
RegSetValueEx(key2,T("note"),0,REG_SZ,(BYTE*)note,(lstrlen(note)+1)*sizeof(TCHAR));
RegCloseKey(key2);
}
RegCloseKey(key);
}
}
static NOTIFYICONDATA nid;
static TCHAR sDecimal[2]; // Punkt oder Komma
#define MBoxTitle nid.szTip
int vMBox(HWND Wnd, int id, UINT type, va_list va) {
TCHAR fmt[256],s[512];
LoadString(hInstance,id,fmt,elemof(fmt));
_vsntprintf(s,elemof(s),fmt,va);
return MessageBox(Wnd,s,MBoxTitle,type);
}
int _cdecl MBox(HWND Wnd, int id, UINT type, ...) {
va_list va;
va_start(va,type);
int ret=vMBox(Wnd,id,type,va);
va_end(va);
return ret;
}
// Der Fehler-String in der Ressource muss %u %s oder %u %s %s enthalten!
void Error(HWND Wnd, int id, const TCHAR*s=0) {
TCHAR*va[3];
int i=0;
DWORD e=GetLastError();
va[i++]=(TCHAR*)(LONG_PTR)e;
if (s) va[i++]=const_cast<TCHAR*>(s);
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_ALLOCATE_BUFFER,0,e,0,(TCHAR*)(va+i),0,0);
vMBox(Wnd,id,MB_OK|MB_ICONEXCLAMATION,(va_list)va);
LocalFree(va[i]);
}
inline void EnableDlgItem(HWND Wnd,int id,bool ena=true) {
EnableWindow(GetDlgItem(Wnd,id),ena);
}
// für CBS_DROPDOWNLIST
static void FillCombo(HWND wnd,int id1,int id2,BYTE def) {
TCHAR s[256];
s[LoadString(hInstance,id2,s,elemof(s)-1)+1]=0;
wnd=GetDlgItem(wnd,id1);
ComboBox_ResetContent(wnd);
for(const TCHAR*sp=s;*sp;sp+=lstrlen(sp)+1) {
ComboBox_AddString(wnd,sp);
}
ComboBox_SetCurSel(wnd,def);
}
// für CBS_DROPDOWN
static void FillCombo2(HWND wnd,int id1,int id2,BYTE def) {
TCHAR s[256];
s[LoadString(hInstance,id2,s,elemof(s)-1)+1]=0;
wnd=GetDlgItem(wnd,id1);
ComboBox_ResetContent(wnd);
for(const TCHAR*sp=s;*sp;sp+=lstrlen(sp)+1) {
int i=ComboBox_AddString(wnd,sp);
if (_ttoi(sp)==def) ComboBox_SetCurSel(wnd,i);
}
if (ComboBox_GetCurSel(wnd)<0) {
_sntprintf(s,elemof(s),T("%u"),def);
ComboBox_SetText(wnd,s);
}
}
static void FillCombo2(HWND wnd,int id,BYTE def) { FillCombo2(wnd,id,id,def);}
static HIMAGELIST imagelist, // 3 Bilder 21×21 Pixel: Lautsprecher, Mikrofon, Cinchstecker
imagelist11; // Raumklang-Symbolik
// für ComboBoxEx, hier für Raumklang
static void FillComboEx(HWND wnd,int id1,BYTE def) {
wnd=GetDlgItem(wnd,id1);
ComboBox_ResetContent(wnd);
SendMessage(wnd,CBEM_SETIMAGELIST,0,(LPARAM)imagelist11);
TCHAR s[256];
LoadString(hInstance,109,s,elemof(s));
COMBOBOXEXITEM cbei;
cbei.mask=CBEIF_TEXT|CBEIF_IMAGE|CBEIF_SELECTEDIMAGE|CBEIF_LPARAM;
for(cbei.iItem=0,cbei.pszText=s;cbei.iItem<MAXCH;cbei.pszText+=lstrlen(cbei.pszText)+1,cbei.iItem++) {
cbei.lParam=LPARAM(cbei.iSelectedImage=cbei.iImage=int(cbei.iItem));
SendMessage(wnd,CBEM_INSERTITEM,0,(LPARAM)&cbei);
}
ComboBox_SetCurSel(wnd,def);
}
inline int popcount(unsigned x) { // Anzahl 1-Bits zählen (heutzutage=2020 Standard)
int r=0; for(;x;x>>=1) r+=x&1; return r;
}
void registerhotkey(HWND Wnd,int id,WORD key=0) { // Wnd ist nur für die Fehlermeldungen!
UnregisterHotKey(hMainWnd,id);
if (!key) return;
BYTE modifier=HIBYTE(key);
if (popcount(modifier)<2) MBox(Wnd,96,MB_OK|MB_ICONINFORMATION);
else if (!RegisterHotKey(hMainWnd,id,modifier,LOBYTE(key))) Error(Wnd,97);
/*
BYTE m=0;
if (modifier&HOTKEYF_ALT) m|=MOD_ALT; // Bit 0
if (modifier&HOTKEYF_CONTROL) m|=MOD_CONTROL;
if (modifier&HOTKEYF_SHIFT) m|=MOD_SHIFT;
if (modifier&HOTKEYF_EXT) m|=MOD_WIN; // Gleiche Bits??
*/
}
DWORD DSTCONFIG::getrate() const {
switch (rate) {
case 11: return 11025;
case 22: return 22050;
case 44: return 44100;
}
return rate*1000;
}
void setabsmax(int&a,int b) {b=abs(b); if (a<b) a=b;}
// Zahlenwert -120..120 = -20dB .. +20dB = Faktor 0,1 .. 10
// Die Gleitkommazahl wird als Festkommazahl mit 16 Nachkommabits ausgegeben
int gain2fak(char v) {
return toInt(pow(10,float(v)/120)*65536);
}
// Eine 32-Bit-Festkommazahl mit 23 Nachkommabits in Lautstärkestufen umwandeln
// Da (mein) toInt() Schwierigkeiten mit -Inf hat wird -999 für 0 geliefert
inline double db(int f) {return f?log10(float(f)/0x7FFFFF)*20:-999;}
// und auf 0..100 begrenzen
BYTE fak2vol(int f) {
int r=toInt(db(f)*100/config.z.dbumfang()); // 0.01 => -100
if (r<-100) r=-100; else if (r>0) r=0; // die 0 sollte nie überschritten werden
return BYTE(r+100);
}
struct Statistik{ // Zur Berechnung von Spitzen- und Effektivwert (nur vorzeichenlos, nullzentriert)
int n,maxi;
__int64 sumq;
Statistik():n(0),maxi(0),sumq(0) {}
void operator<<(int v) { // v = 23-Bit-Wert
++n;
if (v<0) v=-v;
if (maxi<v) maxi=v;
sumq+=Int32x32To64(v,v); // Mittels 32x32=64-Bit-Multiplikation quadrieren und summieren
}
int rms() const{return n?toInt(sqrt(double(sumq)/n)):0;}
void reset() {memset(this,0,sizeof*this);}
};
struct VU{
mutable BYTE pik[MAXCH]; // Spitzenwert links/rechts 0..100
mutable BYTE rms[MAXCH];
mutable bool ovl[MAXCH]; // Übersteuerung
mutable int error; // Fehlerkode (aus String-Resource) zur Anzeige bringen wenn !=0
HWND hVU; // VU-Meter
WAVEFORMATEXTENSIBLE wf; // nChannels wird benötigt, später der Platz für die GUID
void initwf(DWORD rate,BYTE channels,BYTE bits) {InitWaveFormat(wf,rate,channels,bits);}
mutable Statistik stat[MAXCH];
bool enough_time() const{
if (config.z.buftime()>=100) return true;
DWORD toc=GetTickCount();
DWORD age=toc-tic;
if (age<100) return false;
tic=toc; return true;
}
private:
mutable DWORD tic; // Zeitpunkt des Beginns der Statistikauffüllung
};
static struct MIXER:public VU{
int*buffer; //Datenpuffer, 1..8 Kanäle, stets 24 Bit = FullScale
unsigned lenS; //Länge Datenpuffer in Mehrkanal-Samples
unsigned use; //Bitmaske der Füll-Teilnehmer, jedes Bit für 1 Quelle, bis 32 Quellen
unsigned expect; // Bitmaske der Füll-Teilnehmer
CRITICAL_SECTION critsec;
HWAVEOUT hwo;
WAVEHDR wh[4]; // Arraylänge muss Zweierpotenz sein!
unsigned whi; // Wave-Header-Index
unsigned initial; // <initial> Blöcke aufheben, dann (mit dem 2. Block) absenden, um Lücken zu vermeiden
bool allowput;
void init(HWND h) {hVU=h; InitializeCriticalSection(&critsec); reset(); allowput=false;}
void reset() {if (buffer) memset(buffer,0,lenS*config.z.getchan()*sizeof*buffer); use=0;}
// Was tun wenn von 1 Quelle 2 Blöcke kommen und von einer anderen keine?
void realloc(unsigned new_len) {
if (lenS==new_len) return;
int*b=buffer; // sicherheitshalber erst allozieren, dann den alten freigeben
if (new_len) {
lenS=new_len;
buffer=new int[lenS*config.z.getchan()];
reset();
}else{
lenS=0;
buffer=0;
}
if (b) delete[] b;
}
unsigned realloc() {unsigned len=config.z.getrate()*config.z.buftime()/1000; realloc(len); return len;}
// Dann einfach überschreiben!
// Der Puffer wird auf echte 24 Bit limitiert, die Lautstärke ermittelt und Überläufe ermittelt
void limit() {
unsigned i,j,nch=config.z.getchan();
memset(ovl,0,sizeof ovl);
for (i=j=0;i<lenS*nch;i++,j++) {
int sa=buffer[i];
if (j==nch) j=0;
if (sa<=-0x7FFFFE) {sa=-0x7FFFFE; ovl[j]=true;}
if (sa>= 0x7FFFFF) {sa= 0x7FFFFF; ovl[j]=true;}
stat[j]<<sa;
buffer[i]=sa;
}
if (enough_time()) {
for (j=0; j<nch; j++) {
pik[j]=fak2vol(stat[j].maxi);
rms[j]=fak2vol(stat[j].rms());
stat[j].reset();
}
if (hVU) InvalidateRect(hVU,0,false);
}
}
void putout(bool force=false) {
if (force || use==expect) { // Puffer von allen Quellen gefüllt?
limit(); // Puffer auswerten
if (allowput) put();
if (save) {
save->put(buffer,lenS); // Puffer zum Speichern schicken (zurzeit im Workerthread-Kontext, aber im Gänsemarsch)
PostMessage(hMainWnd,WM_USER+23,0,0); // Zeitanzeige updaten
}
reset(); // Puffer leeren (besser: Neuen Puffer bereitstellen)
}
}
bool connect() { // TODO: Auf WASAPI umstellen, ist vielleicht schneller
if (waveOutOpen(&hwo,config.z.sel,&wf.Format,(LONG_PTR)waveOutProc,(LONG_PTR)this,CALLBACK_FUNCTION)) return false;
if (!lenS) realloc();
unsigned bytelen=lenS*wf.Format.nBlockAlign;
for (size_t i=0; i<elemof(wh); i++) {
WAVEHDR&w=wh[i];
w.lpData=new char[bytelen]; // insgesamt besteht 40 bzw. 400 ms Pufferzeit
w.dwBufferLength=bytelen;
w.dwUser=i;
w.dwFlags=0;
waveOutPrepareHeader(hwo,&w,sizeof w);
w.dwFlags|=WHDR_DONE;
}
whi=0;
initial=2; // Einen WAVEHDR puffern = 100 ms hörbarer Versatz dazu (weitere 100..200 ms sind's ohnehin schon)
allowput=true;
return true;
}
void disconnect() {
allowput=false;
EnterCriticalSection(&critsec); // Warten bis der Füller-Thread fertig ist
waveOutReset(hwo);
WAVEHDR*w;
for (w=wh; w!=wh+elemof(wh); w++) {
waveOutUnprepareHeader(hwo,w,sizeof*w);
}
waveOutClose(hwo);
for (w=wh; w!=wh+elemof(wh); w++) {
char*d=w->lpData;
w->lpData=0;
delete[] d; // jetzt erst freigeben
}
LeaveCriticalSection(&critsec);
}
void put() {
WAVEHDR&w=wh[whi];
if (w.dwFlags&WHDR_DONE) { // Frei zum Absenden?
w.dwFlags&=~WHDR_DONE;
convertWav(buffer,lenS*config.z.getchan(),w.lpData);
if (initial && !--initial) for (WAVEHDR*wp=wh; wp!=&w; wp++) {
waveOutWrite(hwo,wp,sizeof*wp); // Gepufferte WAVEHDR ausspucken
} // sonst <initial-1> WAVEHDR puffern = aufheben und nichts tun
if (!initial) waveOutWrite(hwo,&w,sizeof w); // im Normalfall den einen Puffer sofort ausschreiben
whi=(whi+1)&(elemof(wh)-1);
}
}
static void CALLBACK waveOutProc(HWAVEOUT,UINT msg,const MIXER&mixer,WAVEHDR&wh,void*) { // (Achtung Multithreading)
if (msg!=WOM_DONE) return;
// Hier ließe sich nichts weiter machen als die Anzahl benutzter/freier Puffer anzuzeigen
}
}mixer;
// Mehrfach-Recorder, daher NIN Instanzen (Speicher für MAXIN)
static struct REC:public VU{
WAVEHDR wh[4];
HWAVEIN h;
wasapi::RECORD*wasapiRecord; // Zeiger auf WASAPI-Objekt, um COM aus zqr.cpp herauszuhalten (würde Compilefehler produzieren)
static void wasapiInProc(const char*,DWORD,void*); // Habe-Datenpaket-Callback für Wasapi-Thread (Achtung Multithreading)
void init(HWND=0,DWORD rate=0,BYTE channels=0,BYTE bits=0); // Was !=0 ist wird initialisiert
bool connect(char);
bool connect();
void disconnect();
bool alive(); // zyklischer Aufruf vom Hauptfenster
static void CALLBACK waveInProc(HWAVEIN,UINT,const REC&,WAVEHDR&,void*); // (Achtung Multithreading)
void gotWave(const char*,DWORD) const; // (Achtung Multithreading)
int (*getSample)(const char*); // Rechengröße: 24 Bit (-8M .. 8M)
int maxvalue; // 0x7F0000 für 8 Bit, 0x7FFF00 für 16 Bit, 0x7FFFFF für 24 Bit
static int regelval; // Zielwert bei AGC mit quellenanzahlabhängigem Wert
HWND hGain;
// mutable int samax[2]; // Maximale Amplitude eines Mixer-Datenblocks (links/rechts oder Mono)
// Basis sind 24-Bit-Samples in 32-Bit-Containern. Das läuft nicht so schnell über.
static int getByteSample(const char*p) {return (*(BYTE*)p-128)<<16;}
static int getInt16Sample(const char*p) {return *(short*)p<<8;}
static int getInt24Sample(const char*p) {return *(int*)p<<8>>8;} // Naja: Lesezugriff 1 Byte außerhalb Array-Grenze möglich
static int getInt32Sample(const char*p) {return *(int*)p>>8;}
}rec[MAXIN];
int REC::regelval; // Zielwert (gleich für alle Kanäle) für AGC
void InitWaveFormat(WAVEFORMATEXTENSIBLE&wf,DWORD rate,BYTE channels,BYTE bits) {
if (channels) {
wf.Format.nChannels=channels;
wf.Format.wFormatTag=channels>2?WAVE_FORMAT_EXTENSIBLE:WAVE_FORMAT_PCM; // Windows besteht auf die komische GUID für nChannels>2
}
if (bits) {
wf.Format.wBitsPerSample=bits;
}
if (rate) wf.Format.nSamplesPerSec=rate;
wf.Format.nBlockAlign=wf.Format.nChannels*((wf.Format.wBitsPerSample+7)>>3);
wf.Format.nAvgBytesPerSec=wf.Format.nSamplesPerSec*wf.Format.nBlockAlign;
if (wf.Format.nChannels>2) {
wf.Format.cbSize=22;
wf.Samples.wValidBitsPerSample=wf.Format.wBitsPerSample;
static const DWORD speakers[6]={0x7,0x33,0x37,0x3F,0x13F,0x1013F};
wf.dwChannelMask=speakers[wf.Format.nChannels-3];
wf.SubFormat=KSDATAFORMAT_SUBTYPE_PCM;
}else{
wf.Format.cbSize=0;
}
}
void REC::init(HWND hvu,DWORD rate,BYTE channels,BYTE bits) {
if (hvu) hVU=hvu;
InitWaveFormat(wf,rate,channels,bits);
if (bits) {
getSample=bits>24?getInt32Sample:bits>16?getInt24Sample:bits>8?getInt16Sample:getByteSample;
if (bits>24) bits=24;
maxvalue=((1<<(bits-1))-1)<<(24-bits); // 0x7F0000 oder 0x7FFF00 oder 0x7FFFFF, das was getIntXXSample() maximal liefern kann (24 Bit vzb.)
}
}
bool REC::connect(char devid) {
DWORD bufferlen=mixer.realloc(); // in Blocksamples für 100 ms, ohne ×2 für Stereo
if (config.z.wasapi()) {
wasapiRecord=new wasapi::RECORD(devid,&wf,bufferlen,wasapiInProc,this);
bool ret=wasapiRecord->start();
if (ret) mixer.expect|=1U<<unsigned(this-rec);
return ret;
}
for(MMRESULT mmr;mmr=waveInOpen(&h,devid,&wf.Format,(LONG_PTR)waveInProc,(LONG_PTR)this,CALLBACK_FUNCTION);) {
if (mmr!=WAVERR_BADFORMAT) return false;
// Kanäle herunterschrauben (Test??)
if (wf.Format.nChannels==1) return false; // Mono müsste immer gehen!
init(0,0,wf.Format.nChannels-1);
int idx=int(this-rec);
config.q[idx].setchan((BYTE)wf.Format.nChannels);
SendDlgItemMessage(hMainWnd,MAKELONG(30,idx),CB_SETCURSEL,wf.Format.nChannels-1,0);
}
bufferlen*=wf.Format.nBlockAlign; // Zehntelsekunde Blocklänge = Reaktionszeit VU-Meter
for (size_t i=0; i<elemof(wh); i++) {
WAVEHDR&w=wh[i];
w.lpData=new char[bufferlen];
w.dwBufferLength=bufferlen;
w.dwUser=i;
w.dwFlags=0;
waveInPrepareHeader(h,&w,sizeof w);
waveInAddBuffer(h,&w,sizeof w);
}
mixer.expect|=1U<<unsigned(this-rec);
waveInStart(h);
return true;
}
bool REC::connect() {return connect(config.q[this-rec].sel);} // automatisch aus globaler Konfiguration
bool REC::alive() {
bool r;
if (wasapiRecord) { // mit WASAPI
r=wasapiRecord->alive();
}else r=!!h; // mit mmsystem (da wäre gesondert zu testen)
int err=r?0:110; // "cannot grab audio"
if (error!=111 && error!=err) {
error=err;
InvalidateRect(hVU,0,false); // Bei Fehleränderung aktualisieren lassen
}
return r;
}
void REC::disconnect() {
mixer.expect&=~(1U<<unsigned(this-rec)); // Diesen Aufnahmekanal beim Mixer abmelden
if (wasapiRecord) {
wasapiRecord->stop();
delete wasapiRecord;
wasapiRecord=0;
return;
}
if (!h) return;
waveInStop(h);
WAVEHDR*w;
for (w=wh; w!=wh+elemof(wh); w++) {
waveInUnprepareHeader(h,w,sizeof*w);
}
waveInClose(h);
for (w=wh; w!=wh+elemof(wh); w++) {
char*d=w->lpData;
w->lpData=0; // Zeiger ungültig machen
delete[] d; // threadsicher in dieser Reihenfolge
}
}
/*=========================================================================================*
* Achtung! WAVEIN-Datenverarbeitung (die folgenden 3 Memberfunktionen) in Worker-Threads! *
*=========================================================================================*/
void REC::wasapiInProc(const char*data,DWORD len,void*cbd) { // Mittlerweile kommen die Daten nur noch in Wunschlänge = ganzer Mixer-Puffer
REC*rec=((REC*)cbd);
rec->gotWave(data,len*rec->wf.Format.nBlockAlign); // schickt den vollen mixer.buffer „hinaus“
}
void REC::waveInProc(HWAVEIN h,UINT msg,const REC&rec,WAVEHDR&wh,void*) {
if (msg!=WIM_DATA) return;
rec.gotWave(wh.lpData,wh.dwBytesRecorded); // dwBytesRecorded passt im Normalfall genau auf die MixBuffer-Größe
waveInAddBuffer(h,&wh,sizeof wh);
}
void REC::gotWave(const char*lpData,DWORD dwBytesRecorded) const{
error=lpData?0:111;
const char*e=lpData+dwBytesRecorded;
int idx=int(this-rec);
DWORD mask=1<<idx;
EnterCriticalSection(&mixer.critsec);
if (mixer.use&mask) {
debugmsg(T("zqr:overlap: mixer.use=%u\n"),mixer.use);
mixer.putout(true); // Nicht von allen Teilnehmern („halb“) befüllten Puffer trotzdem ausgeben
}
ovl[0]=ovl[1]=false; // links/rechts
int*mix=mixer.buffer;
SRCCONFIG&q=config.q[idx];
int fakL=gain2fak(q.gain); // Wertebereich 6K4..640K, exponenziell
int samaxAll=0; // Maximum-Sample über alle Kanäle (für AGC)
// TODO: Für alle Kombinationen aus Quell- und Zielkanalzahlen auslegen
// Quelle/Ziel 1 2 3 4 5 6
// 1 1:1 Pano PanoC Pano PanoC PanoC Pano = Panorama Links/Rechts, PanoC = Panorama Links/Center/Rechts
// 2 mix 1:1 Z:0 Z:0 Z:0 Z:0 Z:0 = Zusätzliche Kanäle Null
// 3 mix C->LR 1:1 C->LR Z:0 Z:0 C->LR = Center auf links + rechts addieren
// 4 mix m m 1:1 C:0 C,S:0 m = hinten nach vorn seitenrichtig addieren, C:0 = kein Center, S:0 = kein Subwoofer
// 5 mix m,C->LR m C->LR 1:1 S:0
// 6 mix m,C->LR m C->LR 1:1 Der Subwoofer wird auf C oder hälftig auf L+R addiert
unsigned nch=wf.Format.nChannels;
bool enough=enough_time();
if (nch==2) {
// int samax[2]={0,0};
unsigned bpc=wf.Format.nBlockAlign>>1;
for(const char*p=lpData;p!=e;p+=wf.Format.nBlockAlign) {
int sal=getSample(p);
int sar=getSample(p+bpc);
stat[0]<<sal;
stat[1]<<sar;
sal=int(Int32x32To64(sal,fakL)>>16);
sar=int(Int32x32To64(sar,fakL)>>16);
if (config.z.getchan()==2) { // Stereo-Senke
*mix+++=sal; // TODO: Wäre "*mix+=" ein atomarer Befehl könnte man sich hier den hässlichen kritischen Abschnitt sparen!
*mix+++=sar; // Andererseits ist beim Mixer gemeinsamer Speicher logisch und über den L1-Cache bremsend bei gleichzeitiger Nutzung.
}else{
*mix+++=(sal+sar)>>1; // Mono = Summensignal
}
}
if (enough) for (int ch=0; ch<2; ch++) {
pik[ch]=fak2vol(stat[ch].maxi); // logarithmisch 0..100 auf -40..0 dB
rms[ch]=fak2vol(stat[ch].rms());
if (stat[ch].maxi>=maxvalue) ovl[ch]=true;
if (samaxAll<stat[ch].maxi) samaxAll=stat[ch].maxi;
stat[ch].reset();
}
}else{
int pano=(config.q[idx].panorama<<16)/100; // Wertebereich -64K .. 64K
for(const char*p=lpData;p!=e;p+=wf.Format.nBlockAlign) {
int sa=getSample(p);
stat[0]<<sa;
// setabsmax(samaxAll,sa);
sa=int(Int32x32To64(sa,fakL)>>16);
if (config.z.getchan()==2) { // Stereo-Senke
// TODO: Bei den Zielkanalzahlen 3, 5 und 6 sollte das Monosignal zum Center-Lautsprecher zugeordnet werden statt anteilig links/rechts!
*mix+++=int(Int32x32To64(sa,65536-pano)>>17); // Bei pano = -100 voll nach links
*mix+++=int(Int32x32To64(sa,65536+pano)>>17); // Bei pano = +100 voll nach rechts
}else{
*mix+++=sa;
}
}
if (enough) {
samaxAll=stat[0].maxi;
pik[0]=fak2vol(samaxAll); // logarithmisch 0..100 auf -40..0 dB
rms[0]=fak2vol(stat[0].rms());
if (samaxAll>=maxvalue) ovl[0]=true;
stat[0].reset();
}
}
mixer.use|=mask;
mixer.putout(); // Mglw. sinnvolle Idee: Ausgabe per PostMessage serialisieren statt alles im Workerthread (mglw. blockierend) zu tun.
LeaveCriticalSection(&mixer.critsec);
if (enough && hVU) InvalidateRect(hVU,0,false);
if (enough && q.getAGC()) {
samaxAll=int(Int32x32To64(samaxAll,fakL)>>16); // Das Maximum (welches noch unmultipliziert ist) mit Lautstärke skalieren
int v=q.gain;
if (samaxAll>regelval) { // schnell aber nicht schlagartig abregeln
v+=(regelval-samaxAll)>>18; // TODO: Um Knackgeräusche zu vermindern müsste der Faktor stetig-gleitend sein
}else{
v+=(regelval-samaxAll)>>20; // langsam aufregeln, abhängig von der Differenz zur Zielvorgabe „Vollaussteuerung“
}
if (v<-120) v=-120;
else if (v>120) v=120;
if (q.gain!=v) {
q.gain=char(v);
PostMessage(hGain,TBM_SETPOS,1,v);
}
}
}
/*== Ende Workerthread-Kontext ==*/
bool dllLoad(HINSTANCE&hLib,const TCHAR*name) {
if (hLib && FreeLibrary(hLib)) hLib=0;
if (!name) return true;
TCHAR dll[MAX_PATH];
lstrcpyn(dll,path,elemof(dll));
PathAppend(dll,name);
hLib=LoadLibrary(dll);
if (!hLib) hLib=LoadLibrary(name);
if (!hLib) {
#ifdef _DEBUG
Error(hMainWnd,99,dll);
#endif
return false;
}
return true;
}
// Gruppe aus Hotkey-Editor und Win-Modifizier-Checkbox setzen
void sethotkey(HWND Wnd, int id, WORD hotkey) {
HWND w=GetDlgItem(Wnd,id);
SendMessage(w,HKM_SETRULES,HKCOMB_NONE,HOTKEYF_ALT);
SendMessage(w,HKM_SETHOTKEY,hotkey&~0x800,0);
CheckDlgButton(Wnd,id+1,hotkey>>11);
}
WORD gethotkey(HWND Wnd, int id) {
return WORD(SendDlgItemMessage(Wnd,id,HKM_GETHOTKEY,0,0)
|IsDlgButtonChecked(Wnd,id+1)<<11);
}
// Aufruf nur erlaubt wenn keine Aufnahme läuft!
void setoutputsel(BYTE sel=0xFF) {
if (sel>=3) sel=config.z.getfileformat();
else{
if (config.z.getfileformat()==sel) return;
TCHAR*p=PathFindExtension(file);
if (p && *p=='.') {
++p;
lstrcpyn(p,TEXT("wav\0mp3\0ogg")+(sel<<2),int(file+elemof(file)-p));
SetDlgItemText(hMainWnd,104,file);
}
}
config.z.setfileformat(sel);
CheckRadioButton(hMainWnd,121,123,121+sel); // WAV/MP3/Vorbis
HWND w=GetDlgItem(hMainWnd,102); // Bits
EnableWindow(w,!sel); // Bits wählbar nur bei WAV
if (sel) {
config.z.setbits(16);
ComboBox_SetCurSel(w,1); // Nur 16 Bits bei MP3 und Vorbis
}
w=GetDlgItem(hMainWnd,124); // Qualität (Bitrate)
EnableWindow(w,!!sel); // wählbar nur bei MP3 und Vorbis
}
void enableoutputsel(bool e=true) {
for (int i=0; i<3; i++) {
bool en=saveobj[i] && saveobj[i]->okay();
EnableDlgItem(hMainWnd,121+i,e&en);
if (!en && config.z.getfileformat()==i) setoutputsel(0);
}
}
static void setWindowTitle() {
TCHAR s[128],t[128],*p=t;
LoadString(hInstance,107,t,elemof(t));
for(int k=0; k<config.NIN; k++) p+=lstrlen(p)+1;
_sntprintf(s,elemof(s),t,p);
TCHAR*u=s;
if (config.z.recording()) {
_sntprintf(t,elemof(t),TEXT("%s - %s"),PathFindFileName(currentfile),s);
u=t;
}
SetWindowText(hMainWnd,u);
lstrcpyn(nid.szTip,u,sizeof nid.szTip);
Shell_NotifyIcon(NIM_MODIFY,&nid);
}
static void recordchange() {
bool en=config.z.recording();
EnableDlgItem(hMainWnd,120,en||currentfile[0]); // Notiz (kann auch nach der Aufnahme angehangen werden)
ShowWindow(GetDlgItem(hMainWnd,118),en?SW_SHOW:SW_HIDE); // Zeitanzeige
nid.hIcon=LoadIcon(hInstance,MAKEINTRESOURCE(100+en)); // Icon (rote LED)
setWindowTitle();
// Shell_NotifyIcon(NIM_MODIFY,&nid);
SetClassLongPtr(hMainWnd,GCLP_HICON,(LONG_PTR)nid.hIcon);
SetClassLongPtr(hMainWnd,GCLP_HICONSM,(LONG_PTR)nid.hIcon);
en=!en;
EnableDlgItem(hMainWnd,101,en); // Rate
EnableDlgItem(hMainWnd,102,en && !config.z.getfileformat()); // Bits (bei MP3 und Vorbis stets 16)
EnableDlgItem(hMainWnd,103,en); // Stereo
EnableDlgItem(hMainWnd,124,en && !!config.z.getfileformat()); // Qualität (nur bei MP3 und Vorbis)
enableoutputsel(en); // Auswahl Kompressionsformat
EnableDlgItem(hMainWnd,104,en); // Dateiname
EnableDlgItem(hMainWnd,105,en); // Dateiauswahl
}
static INT CALLBACK BrowseCallbackProc(HWND Wnd, UINT msg, LPARAM lp, LPARAM pData) {
if (msg==BFFM_INITIALIZED) SendMessage(Wnd,BFFM_SETSELECTION,TRUE,pData);
return 0;
}
static void FillWaveinCombos() {
HWND h[MAXIN];
char sel[MAXIN];
int idx,i;
for (idx=0; idx<config.NIN; idx++) {
h[idx]=GetDlgItem(hMainWnd,MAKEWORD(10,idx));
ComboBox_ResetContent(h[idx]);
SendMessage(h[idx],CBEM_SETIMAGELIST,0,(LPARAM)imagelist);
sel[idx]=config.q[idx].sel;
}
COMBOBOXEXITEM cbei;
cbei.mask=CBEIF_TEXT|CBEIF_IMAGE|CBEIF_SELECTEDIMAGE|CBEIF_LPARAM;
cbei.iItem=-1; // = anhängen
if (config.z.wasapi() && wasapi::FillCombos(h,sel,&cbei)) return;
config.z.wasapi(false); // Kein WASAPI
// cbei.iImage=cbei.iSelectedImage=1; // Mikrofon
int n=waveInGetNumDevs();
WAVEINCAPS wic;
struct CFC{HWND*h;char*sel;COMBOBOXEXITEM*cbei;}cfc={h,sel,&cbei};
for (i=WAVE_MAPPER; i<n; i++) {
if (!waveInGetDevCaps(i,&wic,sizeof wic)) {
ComboFillCallback((UINT)i,true,wic.szPname,&cfc);
}
}
}
static void FillWaveoutCombo(HWND hCombo) {
ComboBox_ResetContent(hCombo);
SendMessage(hCombo,CBEM_SETIMAGELIST,0,(LPARAM)imagelist);
COMBOBOXEXITEM cbei;
cbei.mask=CBEIF_TEXT|CBEIF_IMAGE|CBEIF_SELECTEDIMAGE|CBEIF_LPARAM;
cbei.iItem=-1; // = anhängen
cbei.iImage=cbei.iSelectedImage=0; // Lautsprecher
int n=waveOutGetNumDevs();
WAVEOUTCAPS woc;
cbei.pszText=woc.szPname;
for (int i=WAVE_MAPPER; i<n; i++) {
if (!waveOutGetDevCaps(i,&woc,sizeof woc)) {
cbei.lParam=(LPARAM)i;
int k=int(SendMessage(hCombo,CBEM_INSERTITEM,0,(LPARAM)&cbei));
if (i==config.z.sel) ComboBox_SetCurSel(hCombo,k); // bei Übereinstimmung mit <sel>
}
}
}
struct CLONEINFO{
int left,top,width,height;
DWORD style,exstyle;
ATOM wndclass;
UINT id;
HFONT font;
LPVOID lParam;
int idx;
TCHAR text[128];
};
static bool CloneWindowsCallback(CLONEINFO&ci,void*cbd) {
int dx=LOWORD((LPARAM)cbd);
int j=HIWORD((LPARAM)cbd);
ci.left+=j*dx; // Position
ci.id+=j<<8; // ID
switch (ci.idx) {
case 0: {
TCHAR*f=_tcschr(ci.text,'&');
if (f) {
if (j<9) ++f; // "&" erhalten, höhere Kanalnummern ohne Tasten-Hotkey
_sntprintf(f,ci.text+elemof(ci.text)-f,TEXT("%u"),j+1); // Kanalnummer 1-basiert
}
}break;
case 1:
case 3:
case 4: {
ci.height=200; // Höhenfehler beim Klonen von Comboboxen ausmerzen
}break;
}
return ci.idx<8;
}
// Kindfenster in Dialogen klonen, ab <w>, einsetzen nach <after>.
// Die Anzahl ergibt sich aus der Rückgabe von <cb>, true für weiteres Fenster.
// Beim Aufruf von <cb> enthält CLONEINFO& folgende manipulierbare Daten:
// * ci.left, ci.top = Position links-oben relativ zum Parent
// * ci.width, ci.height = Breite bzw. Höhe der Vorlage. Achtung: Höhe falsch bei Comboboxen!
// * ci.style und ci.exstyle die Fensterstilbits
// * ci.wndclass = Fensterklasse
// * ci.id = Fenster-ID
// * ci.font = Fenster-Schriftart
// * ci.text = Fenstertext
// * ci.lParam = zusätzlicher Create-Parameter
// Der Callback muss zumindest die Position ändern. <cbd> wird durchgereicht.
// * ci.idx sollte nicht geändert werden!
static HWND clonewindows(HWND w,HWND after,bool(*cb)(CLONEINFO&,void*),void*cbd) {
HWND wn=w;
HWND p=GetParent(w);
CLONEINFO ci;
WINDOWINFO wi;
wi.cbSize=sizeof wi;
for(ci.idx=0;w;ci.idx++,w=GetNextSibling(w)) {
GetWindowInfo(w,&wi);
ci.width=wi.rcWindow.right-wi.rcWindow.left;
ci.height=wi.rcWindow.bottom-wi.rcWindow.top;
ci.left=wi.rcWindow.left;
ci.top=wi.rcWindow.top;
ScreenToClient(p,(POINT*)&ci.left);
ci.style=wi.dwStyle;
ci.exstyle=wi.dwExStyle&0x3FFFFFFF; // Bei Gerald kommen die oberen 2 Bits gesetzt, und CreateWindowEx() versagt!
ci.wndclass=wi.atomWindowType;
ci.font=GetWindowFont(w);
ci.id=GetWindowID(w);
ci.lParam=0;
GetWindowText(w,ci.text,elemof(ci.text));
bool cont=cb(ci,cbd);
wn=CreateWindowEx(ci.exstyle,MAKEINTATOM(ci.wndclass),ci.text,ci.style,
ci.left,ci.top,ci.width,ci.height,
p,(HMENU)(LONG_PTR)ci.id,0,ci.lParam);
#ifdef _DEBUG // der Compiler wirft sonst den unbenutzten String nicht heraus, hm, Problem??
if (!wn) {
debugmsg(T("CreateWindowEx(%X,%X,%s,%X, %d,%d,%d,%d, %X,%d,0,%X) failed\n"),
ci.exstyle,ci.wndclass,ci.text,ci.style,
ci.left,ci.top,ci.width,ci.height,
p,ci.id,ci.lParam);
}
#endif
SetWindowFont(wn,ci.font,false);
SetWindowPos(wn,after,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);
after=wn;
if (!cont) break;
}
return after;
}
static void restorePos(HWND Wnd,const POINTS&pos) {
if (!pos.x && !pos.y) return; // sieht so aus als wär's uninitialisiert: Nichts tun! Besser: Konstruktor mit INAN
WINDOWPLACEMENT wp;
wp.length=sizeof wp;
GetWindowPlacement(Wnd,&wp);
OffsetRect(&wp.rcNormalPosition,pos.x-wp.rcNormalPosition.left,pos.y-wp.rcNormalPosition.top);
SetWindowPlacement(Wnd,&wp); // SetWindowPlacement sollte gegenüber SetWindowPos die Monitorausdehnung beachten und das Fenster keinesfalls unsichtbar außerhalb platzieren
// SetWindowPos(Wnd,0,config.posx,config.posy,0,0,SWP_NOSIZE|SWP_NOZORDER);
}
static void savePos(HWND Wnd,POINTS&pos) {
WINDOWPLACEMENT wp;
wp.length=sizeof wp;
GetWindowPlacement(Wnd,&wp);
pos.x=(short)wp.rcNormalPosition.left;
pos.y=(short)wp.rcNormalPosition.top;
}
static int mainWndWidth;
static void onNinChange() {
int i;
for (i=MAXIN; --i;) {
HWND w=GetDlgItem(hMainWnd,MAKEWORD(10,i));
if (w) {
w=GetPrevSibling(w);
int j=8;
do{
HWND wn=GetNextSibling(w);
DestroyWindow(w);
w=wn;
}while(--j);
}
}
RECT rc;
GetWindowRect(GetFirstChild(hMainWnd),&rc);
const int dx=rc.right-rc.left+6; // 12 Pixel Luft
for (i=0; i<config.NIN; i++) {
TCHAR r[3]=T("&1");
r[1]+=i;
HWND wndafter=i?clonewindows(GetFirstChild(hMainWnd),wndafter,CloneWindowsCallback,(void*)(LPARAM)MAKELONG(dx,i)):GetDlgItem(hMainWnd,60);
}
GetWindowRect(hMainWnd,&rc);
rc.bottom-=rc.top; // Höhe bleibt
rc.right=mainWndWidth+(config.NIN>2?(config.NIN-2)*dx:0);
SetWindowPos(hMainWnd,0,0,0,rc.right,rc.bottom,SWP_NOMOVE|SWP_NOZORDER); // Fensterbreite anpassen
setWindowTitle();
}
//static HWND hTooltip;
static void onNinChange(HWND Wnd) {
for (int i=0; i<config.NIN; i++) {
SRCCONFIG&q=config.q[i];
HWND w=GetDlgItem(Wnd,MAKEWORD(50,i));
CheckDlgButton(Wnd,MAKEWORD(40,i),q.getAGC());
EnableWindow(w,!q.getAGC());
SendMessage(w,TBM_SETRANGE,0,MAKELONG(-120,120));
SendMessage(w,TBM_SETTICFREQ,30,0);
SendMessage(w,TBM_SETPAGESIZE,0,30);
SendMessage(w,TBM_SETPOS,1,q.gain);
// SendMessage(w,TBM_SETTOOLTIPS,(WPARAM)hTooltip,0);
rec[i].hGain=w; // Zielfenster bei aktivem AGC
w=GetDlgItem(Wnd,MAKEWORD(60,i));
EnableWindow(w,q.getchan()<=1 && config.z.getchan()>=2); // Panoramaregler NUR bei Mono-Quelle und Stereo-Senke
SendMessage(w,TBM_SETRANGE,0,MAKELONG(-100,100));
SendMessage(w,TBM_SETTICFREQ,20,0);
SendMessage(w,TBM_SETPAGESIZE,0,20);
SendMessage(w,TBM_SETPOS,1,q.panorama);
FillCombo(Wnd,MAKEWORD(70,i),102,q.getbytes()-1);
FillComboEx(Wnd,MAKEWORD(30,i),q.getspat());
}
static const BYTE regelfak[MAXIN-1]={80,50,30,25,22,21,20}; // Index 0 für NIN==1
REC::regelval=MulDiv(0x7F7F7F,regelfak[config.NIN-1],100);
}
static void CloseOpenAllWaveIn(DWORD rate=0,int nin=0) {
REC*rp;
for (rp=rec; rp!=rec+config.NIN; rp++) rp->disconnect(); // erst alle trennen
if (config.z.echoing()) mixer.disconnect();
if (nin) config.NIN=nin; // neuzuweisen im Falle der Kanalzahländerung
if (rate) mixer.initwf(rate,0,0);
if (config.z.echoing() && !mixer.connect()) {
config.z.echoing(false);
CheckDlgButton(hMainWnd,126,0);
}
if (rate) for (rp=rec; rp<rec+config.NIN; rp++) rp->init(0,rate);
for (rp=rec; rp!=rec+config.NIN; rp++) rp->connect();
}
static HFONT hfntAlias; // Kleine Schrift ohne AntiAlias für Skalenbeschriftung
static HBITMAP hbmVuXor[2]; // Vorgefertigte Bitmaps für Aussteuerungsanzeigen (verschiedene Größen)
INT_PTR CALLBACK SettingsDlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
switch (Msg) {
case WM_INITDIALOG: {
HFONT f=GetWindowFont(Wnd);
LOGFONT lf;
GetObject(f,sizeof lf,&lf);
lf.lfHeight=MulDiv(lf.lfHeight,14,9);
lf.lfCharSet=SYMBOL_CHARSET;
lstrcpyn(lf.lfFaceName,TEXT("Wingdings"),sizeof lf.lfFaceName);
HFONT f2=CreateFontIndirect(&lf);
SendDlgItemMessage(Wnd,11,WM_SETFONT,(WPARAM)f2,0);
SendDlgItemMessage(Wnd,13,WM_SETFONT,(WPARAM)f2,0);
restorePos(Wnd,config.posSettings);
sethotkey(Wnd,10,config.hotkey[0]);
sethotkey(Wnd,12,config.hotkey[1]);
SetDlgItemText(Wnd,20,path);
SetDlgItemText(Wnd,22,note);
CheckDlgButton(Wnd,24,config.z.wasapi());
CheckDlgButton(Wnd,27,config.z.flags>>4&1);
CheckDlgButton(Wnd,28,config.z.flags>>5&1);
CreateUpDownControl(
WS_CHILD|WS_BORDER|WS_VISIBLE|UDS_ALIGNRIGHT|UDS_ARROWKEYS|UDS_HOTTRACK|UDS_SETBUDDYINT,
0,0,0,0,Wnd,26,0,GetDlgItem(Wnd,25),MAXIN,1,config.NIN);
}return TRUE;
case WM_MOVE: savePos(Wnd,config.posSettings); break;
case WM_ACTIVATE: if (wParam) hDialog=Wnd; break;
case WM_COMMAND: switch (wParam) {
case 5: // Übernehmen
case 1:{ // OK
registerhotkey(Wnd,1,config.hotkey[0]=gethotkey(Wnd,10));
registerhotkey(Wnd,120,config.hotkey[1]=gethotkey(Wnd,12));
GetDlgItemText(Wnd,20,path,elemof(path));
GetDlgItemText(Wnd,22,note,elemof(note));
BYTE flags=0;
if (IsDlgButtonChecked(Wnd,24)) flags|=0x02;
if (IsDlgButtonChecked(Wnd,27)) flags|=0x10;
if (IsDlgButtonChecked(Wnd,28)) flags|=0x20;
int nin=GetDlgItemInt(Wnd,25,0,0);
if (config.NIN!=nin) {
CloseOpenAllWaveIn(0,nin); // Alle WaveIn-Devices schließen und öffnen
onNinChange(); // Teil 1: Klonen
onNinChange(hMainWnd); // Teil 2: Füllen
FillWaveinCombos();
}
BYTE change=flags^config.z.flags;
config.z.flags = config.z.flags&0xCD|flags; // Neue Flags setzen
if (change&0x02) {
FillWaveinCombos();
if ((flags^config.z.flags)&0x02) { // Kann von FillWaveinCombos nur von 1 auf 0 fallen
CheckDlgButton(Wnd,24,0); // Beim „Übernehmen“ anzeigen, dass das nicht klappt (Windows XP)
change&=~0x02;
}
}
if (change&0x12) {
CloseOpenAllWaveIn(); // TODO: Reicht das für die Latenzumschaltung?
}
if (change&0x20) { // dB-Umschaltung
DeleteBitmap(hbmVuXor[0]); hbmVuXor[0]=0;
DeleteBitmap(hbmVuXor[1]); hbmVuXor[1]=0;
}
if (!config.z.recording()) {
SAVE::deleteall();
SAVE::newall();
enableoutputsel();
}
if (wParam!=1) break; // kein DestroyWindow
}
case 2: {
DestroyWindow(Wnd);
}break;
case 21: { // Verzeichniswahl
HWND hEdit=GetPrevSibling((HWND)lParam);
TCHAR p[MAX_PATH];
GetWindowText(hEdit,p,elemof(p));
BROWSEINFO br;
ZeroMemory(&br,sizeof br);
br.lpfn=BrowseCallbackProc; // erforderlich(!) um das aktuelle Verzeichnis zu setzen
br.ulFlags=BIF_RETURNONLYFSDIRS/*|BIF_NEWDIALOGSTYLE*/; // Erlaubt "Neuer Ordner": Hier Quatsch! Ohne NEWDIALOG wird das aktuelle Verzeichnis gleich fokussiert!
br.hwndOwner=Wnd;
//br.lpszTitle=
br.lParam=(LPARAM)p;
LPITEMIDLIST pidl = SHBrowseForFolder(&br);
if (pidl) {
SHGetPathFromIDList(pidl,p);
SetWindowText(hEdit,p);
CoTaskMemFree(pidl);
}
}break;
case 23: { // Dateiauswahl
HWND hEdit=GetPrevSibling((HWND)lParam);
TCHAR p[MAX_PATH];
GetWindowText(hEdit,p,elemof(p));
OPENFILENAME ofn;
ZeroMemory(&ofn,sizeof ofn);
ofn.lStructSize=sizeof ofn;
ofn.hwndOwner=Wnd;
ofn.Flags=OFN_PATHMUSTEXIST|OFN_HIDEREADONLY;
ofn.lpstrFile=p;
ofn.nMaxFile=elemof(p);
if (GetSaveFileName(&ofn)) {
SetWindowText(hEdit,p);
}
}break;
case MAKELONG(0xF5,1): SetForegroundWindow(hMainWnd); break;
}break;
case WM_DESTROY: {
HFONT f2=(HFONT)SendDlgItemMessage(Wnd,11,WM_GETFONT,0,0);
DeleteFont(f2);
hSettingsDlg=0;
}break;
}
return FALSE;
}
bool write_utf8(HANDLE f,const char*fmt,const TCHAR*arg) {
#ifdef UNICODE
int l=WideCharToMultiByte(CP_UTF8,0,arg,-1,0,0,0,0);
char*p=new char[l];
WideCharToMultiByte(CP_UTF8,0,arg,-1,p,l,0,0);
char s[MAX_PATH];
l=_snprintf(s,sizeof s,fmt,p);
DWORD bw;
bool ret=!!WriteFile(f,s,l,&bw,0);
delete[]p;
return ret;
#else // ANSI, kein UTF-8
char s[MAX_PATH];
int l=_snprintf(s,sizeof s,fmt,arg);
DWORD bw;
return !!WriteFile(f,s,l,&bw,0);
#endif
}
bool NotizAppend(HWND Wnd) {
HANDLE f=CreateFile(note,GENERIC_WRITE,FILE_SHARE_READ,0,OPEN_ALWAYS,0,0);
if (f==INVALID_HANDLE_VALUE) return false;
DWORD fp=SetFilePointer(f,0,0,FILE_END);
bool ret=true;
if (newfile) {
if (!write_utf8(f,"\n[%s]\n"+(fp?0:1),currentfile)) ret=false;
// Leerzeile vor neuer Section generieren, aber nicht am Dateianfang
newfile=false;
}
TCHAR s[512];
GetDlgItemText(Wnd,11,s,elemof(s));
if (!write_utf8(f,"%s=",s)) ret=false;
GetDlgItemText(Wnd,12,s,elemof(s));
if (!write_utf8(f,"%s\n",s)) ret=false;
if (!CloseHandle(f)) ret=false;
return ret;
}
static void Zeitmarke(HWND Wnd,UINT id,bool zehntel) {
FILETIME t;
GetSystemTimeAsFileTime(&t);
*(__int64*)&t-=*(__int64*)&starttime;
SYSTEMTIME st;
FileTimeToSystemTime(&t,&st);
TCHAR s[32];
int j=GetTimeFormat(LOCALE_USER_DEFAULT,TIME_FORCE24HOURFORMAT,&st,0,s,elemof(s))-1;
if (zehntel) _sntprintf(s+j,elemof(s)-j,TEXT("%c%u"),sDecimal[0],st.wMilliseconds/100); // Zehntelsekunden anhängen
SetDlgItemText(Wnd,id,s);
}
static INT_PTR CALLBACK NotizDlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
switch (Msg) {
case WM_INITDIALOG: {
hNotizDlg=Wnd;
restorePos(Wnd,config.posNotice);
if (!lParam) SetForegroundWindow(Wnd); // gestartet mit Hotkey
SetDlgItemText(Wnd,10,currentfile);
Zeitmarke(Wnd,11,false);
}return TRUE;
case WM_MOVE: savePos(Wnd,config.posNotice); break;
case WM_ACTIVATE: if (wParam) hDialog=Wnd; break;
case WM_COMMAND: switch (wParam) {
case 1: if (!NotizAppend(Wnd)) {MessageBeep(0); break;}
case 2: DestroyWindow(Wnd); break;
case MAKELONG(0xF5,1): SetForegroundWindow(hMainWnd); break;
}break;
case WM_DESTROY: {
hNotizDlg=0;
}break;
}
return FALSE;
}
static void overlayVuXor(HDC hdc,int x, int y, int w, int h,bool bm2) {
HDC dc=CreateCompatibleDC(hdc);
HBITMAP obm,&hbmXor=hbmVuXor[bm2];
if (!hbmXor) {
hbmXor=CreateBitmap(w,h,1,1,0); // Monochrome Bitmap um Skale mittels XorPut zu zeichnen
obm=SelectBitmap(dc,hbmXor); // Zeichenfläche einsetzen
PatBlt(dc,0,0,w,h,BLACKNESS); // Bitmap theoretisch undefiniert, praktisch (Win10) schwarz. Sicherheitshalber schwarz (Bits=0) setzen
HFONT ofont=SelectFont(dc,hfntAlias);
TEXTMETRIC tm;
GetTextMetrics(dc,&tm);
int ty=(h-tm.tmHeight)>>1;
SetTextColor(dc,RGB(255,255,255));
SetBkColor(dc,0);
HPEN open=SelectPen(dc,GetStockPen(WHITE_PEN));
const int scalestart=config.z.dbumfang(),// Anzeigeumfang -40 db .. 0
majortick=10, // langer Strich alle 10 dB
minortick=2, // kurzer Strich alle 2 dB
gap=2; // Pixel Platz für Text zum Rand
for (int i=0; i<=scalestart; i+=minortick) {
int xx=MulDiv(i,w-1,scalestart);
int yy=h-ty; if (i%majortick) yy+=ty>>1;
MoveToEx(dc,xx,yy,0);
LineTo(dc,xx,h); // Endpixel wird nicht gezeichnet
if (!(i%majortick)) {
TCHAR s[4];
int l=_sntprintf(s,elemof(s),T("%d"),i-scalestart);
SetTextAlign(dc,i?i==scalestart?TA_RIGHT:TA_CENTER:TA_LEFT);
TextOut(dc,i?i==scalestart?w-gap:xx:gap,ty,s,l);
}
}
SetTextAlign(dc,TA_CENTER);
const int spacex=scalestart*2/majortick; // 8: Bei 1/8, 3/8, 5/8 und 7/8 ist Platz für die Einheit
TextOut(dc,MulDiv(w,spacex-1,spacex),0,TEXT("dB"),2);
SelectFont(dc,ofont);
SelectPen(dc,open);
}else obm=SelectBitmap(dc,hbmXor); // Bereits erstellte Quellbitmap einsetzen
BitBlt(hdc,x,y,w,h,dc,0,0,SRCINVERT);
SelectBitmap(dc,obm);
DeleteDC(dc);
} // Dasselbe mit Pfaden zu erreichen führte zu hässlichen ausgefransten Buchstaben und Ziffern!
INT_PTR CALLBACK MainDlgProc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
static HBRUSH hbrBlue,hbrCyan,hbrRed;
static HMENU mym;
static HFONT hfntBig;
switch (Msg) {
case WM_INITDIALOG: {
hMainWnd=Wnd;
// hTooltip=CreateWindowEx(WS_EX_TOPMOST,TOOLTIPS_CLASS,0,WS_POPUP|TTS_NOPREFIX|WS_VISIBLE,0,0,0,0,Wnd,0,0,0);
HMENU sysm=GetSystemMenu(Wnd,false);
mym=LoadMenu(hInstance,MAKEINTRESOURCE(100));
TCHAR s[64];
GetMenuString(mym,119,s,elemof(s),MF_BYCOMMAND);
DestroyMenu(mym);
AppendMenu(sysm,MF_STRING,0x40,s);
DeleteMenu(sysm,SC_MAXIMIZE,MF_BYCOMMAND);
DeleteMenu(sysm,SC_SIZE,MF_BYCOMMAND);
DeleteMenu(sysm,SC_RESTORE,MF_BYCOMMAND);
nid.cbSize=sizeof nid;
nid.hWnd=Wnd;
nid.uID=100;
nid.uCallbackMessage=WM_USER+42;
nid.uFlags=NIF_ICON|NIF_TIP|NIF_MESSAGE;
nid.hIcon=LoadIcon(hInstance,MAKEINTRESOURCE(100));
SetClassLongPtr(Wnd,GCLP_HICON,(LONG_PTR)nid.hIcon);
SetClassLongPtr(Wnd,GCLP_HICONSM,(LONG_PTR)nid.hIcon);
// GetWindowText(Wnd,nid.szTip,elemof(nid.szTip));
Shell_NotifyIcon(NIM_ADD,&nid); // Tip und MBoxTitel kommt später in onNinChange()
hbrBlue=CreateSolidBrush(RGB(0,0,255)); // Effektivwert
hbrCyan=CreateSolidBrush(RGB(0,96,255)); // Spitzenwert
hbrRed=CreateSolidBrush(RGB(255,0,0)); // Übersteuerung
HFONT fnt=GetWindowFont(Wnd);
// SetWindowFont(hTooltip,fnt,false);
LOGFONT lf;
GetObject(fnt,sizeof lf,&lf);
long save=lf.lfHeight;
lf.lfHeight=MulDiv(lf.lfHeight,12,8);
hfntBig=CreateFontIndirect(&lf);
SendDlgItemMessage(Wnd,118,WM_SETFONT,(WPARAM)hfntBig,true);
lf.lfHeight=save+3;
lf.lfQuality=NONANTIALIASED_QUALITY; // erforderlich für Pfad und Pfadfüllen, aber wirkungslos
hfntAlias=CreateFontIndirect(&lf);
config.z.sel=-1; // AUDIO_MAPPER
config.z.nch_bits=0x11; // Stereo, 16 Bit
config.z.flags=0x42; // WASAPI, MP3
config.z.rate=44; // CD-Qualität
config.z.mp3min=16; // Byte(!) pro Sekunde
config.z.mp3max=16; // Byte(!) pro Sekunde
for(SRCCONFIG*q=config.q; q!=config.q+MAXIN; q++) {
q->sel=char(q-config.q); // Laufender Index als Vorgabe
q->nch_bits=0x11; // Stereo, 16 Bit
q->gain=-36; // Lautstärke halbieren: -6 dB
q->panorama=q-config.q&1?100:-100; // ungerade: rechts zumischen, sonst links zumischen
}
PostMessage(Wnd,WM_USER+20,0,0); // ContinueInit (TODO: Wieder weg!)
}return TRUE;
case WM_USER+20: {
GetProfileString(T("Intl"),T("sDecimal"),T("."),sDecimal,elemof(sDecimal));
WINDOWPLACEMENT wp;
wp.length=sizeof wp;
GetWindowPlacement(Wnd,&wp);
mainWndWidth=wp.rcNormalPosition.right-wp.rcNormalPosition.left; // Fensterbreite für 1..2 Quellen
if (config.load()) {
OffsetRect(&wp.rcNormalPosition,config.posMain.x-wp.rcNormalPosition.left,config.posMain.y-wp.rcNormalPosition.top);
SetWindowPlacement(Wnd,&wp); // SetWindowPlacement sollte gegenüber SetWindowPos die Monitorausdehnung beachten und das Fenster keinesfalls unsichtbar außerhalb platzieren
// SetWindowPos(Wnd,0,config.posx,config.posy,0,0,SWP_NOSIZE|SWP_NOZORDER);
}
// Quellenbezogene Fenster klonen für NCH Quellen
onNinChange();
SAVE::newall();
if (!(saveobj[1]->okay() || saveobj[2]->okay())) PostMessage(Wnd,WM_SYSCOMMAND,0x40,0); // Anwender muss/sollte Pfad festlegen!
registerhotkey(Wnd,1,config.hotkey[0]);
registerhotkey(Wnd,120,config.hotkey[1]);
SetDlgItemText(Wnd,104,file);
imagelist=ImageList_LoadImage(hInstance,MAKEINTRESOURCE(10),21,0,CLR_DEFAULT,IMAGE_BITMAP,0); // Lautsprecher, Mikrofon
imagelist11=ImageList_LoadImage(hInstance,MAKEINTRESOURCE(11),16,0,CLR_DEFAULT,IMAGE_BITMAP,0); // Raumklangsymbolik
FillWaveinCombos();
FillCombo2(Wnd,101,config.z.rate);
FillCombo(Wnd,102,102,config.z.getbytes()-1);
FillComboEx(Wnd,103,config.z.getspat());
FillCombo2(Wnd,124,106,config.z.mp3min);
FillWaveoutCombo(GetDlgItem(Wnd,127));
mixer.init(GetDlgItem(Wnd,99));
mixer.initwf(config.z.getrate(),config.z.getchan(),config.z.getbits());
enableoutputsel();
setoutputsel();
if (config.z.echoing() && mixer.connect()) {
CheckDlgButton(Wnd,126,MF_CHECKED); // "Parallele Audioausgabe"
}
onNinChange(Wnd); // 2. Teil des Klonens: Mit Inhalten füllen
for (int i=0; i<config.NIN; i++) {
SRCCONFIG&q=config.q[i];
rec[i].init(GetDlgItem(Wnd,MAKEWORD(20,i)),config.z.getrate(),q.getchan(),q.getbits());
rec[i].connect();
}
if (config.z.recording() && save_start()) CheckDlgButton(Wnd,1,true);
else config.z.recording(false);
recordchange();
if (config.z.maininvis()) ShowWindow(Wnd,SW_MINIMIZE);
SetTimer(Wnd,0,250,0);
}return TRUE;
case WM_DEVICECHANGE: {
SetTimer(Wnd,1,1500,0); // WaveIn- und WaveOut-Geräteliste nach 1,5 s aktualisieren lassen
}break;
case WM_MOVE: savePos(Wnd,config.posMain); break;
case WM_SIZE: {
if (wParam==SIZE_MINIMIZED) {
ShowWindow(Wnd,SW_HIDE);
config.z.maininvis(true);
return TRUE;
}
}break;
case WM_ACTIVATE: if (wParam) hDialog=Wnd; break;
case WM_DRAWITEM: { // VU-Meter
const DRAWITEMSTRUCT&dis=*(const DRAWITEMSTRUCT*)lParam;
const bool sumsignal=wParam==99;
const VU&vu=sumsignal?VU(mixer):rec[HIBYTE(wParam)]; // Anzeigedaten
const HBRUSH hbrWhite=GetStockBrush(WHITE_BRUSH);
const int k=vu.wf.Format.nChannels;
if (unsigned(k-1)>=8) vu.error=111;
if (vu.error) {
TCHAR s[64];
int l=LoadString(hInstance,vu.error,s,elemof(s));
bool warning=vu.error==111;
FillRect(dis.hDC,&dis.rcItem,warning?hbrWhite:hbrRed);
// SetTextAlign(dis.hDC,TA_CENTER|TA_BASELINE);
if (!warning) SetTextColor(dis.hDC,RGB(255,255,255));
SetBkMode(dis.hDC,TRANSPARENT);
DrawText(dis.hDC,s,l,const_cast<RECT*>(&dis.rcItem),
DT_SINGLELINE|DT_CENTER|DT_VCENTER|DT_NOPREFIX);
}else{
int w=dis.rcItem.right-dis.rcItem.left, // Gesamtbreite
h=dis.rcItem.bottom-dis.rcItem.top, // Gesamthöhe
j=0;
RECT rc; rc.bottom=dis.rcItem.top;
do{ // Für jeden der bis zu 8 Kanäle
rc.top=rc.bottom; // direkt untereinander
rc.bottom=dis.rcItem.top+MulDiv(j+1,h,k); // Balkenhöhe
rc.left=dis.rcItem.left;
rc.right=dis.rcItem.left+MulDiv(vu.rms[j],w,100); // Balkenlänge
FillRect(dis.hDC,&rc,vu.ovl[j]?hbrRed:hbrBlue); // Effektivwert
rc.left=rc.right;
rc.right=dis.rcItem.left+MulDiv(vu.pik[j],w,100); // Balken rechts
FillRect(dis.hDC,&rc,vu.ovl[j]?hbrRed:hbrCyan); // Spitzenwert (immer > Effekivwert)
rc.left=rc.right;
rc.right=dis.rcItem.right; // Weißraum rechts
FillRect(dis.hDC,&rc,hbrWhite);
}while(++j<k);
overlayVuXor(dis.hDC,dis.rcItem.left,dis.rcItem.top,w,h,sumsignal);
}
}break;
case WM_SYSCOMMAND: switch (wParam&~15) {
case 0x40: SendMessage(Wnd,WM_COMMAND,119,0); break;
}break;
case WM_COMMAND: {
int idx=HIBYTE(wParam);
SRCCONFIG&q=config.q[idx];
switch (wParam&~0xFF00) {
case 1:{
config.z.recording(2); // toggle
if (!lParam) CheckDlgButton(Wnd,1,config.z.recording());
if (config.z.recording()) {
config.save(); // bevor's abstürzt
if (!save_start()) {
MBox(Wnd,98,MB_OK|MB_ICONEXCLAMATION,currentfile);
config.z.recording(false);
CheckDlgButton(Wnd,1,BST_UNCHECKED);
}
}else save_stop();
recordchange();
}break;
case 2: if (config.z.recording() && MBox(Wnd,103,MB_YESNO|MB_DEFBUTTON2)!=IDYES) break;
case 3: {
KillTimer(Wnd,0);
if (config.z.recording()) save_stop();
for (REC*rp=rec;rp!=rec+config.NIN;rp++) rp->disconnect();
if (config.z.echoing()) mixer.disconnect();
mixer.realloc(0);
config.save();
DeleteBrush(hbrRed);
DeleteBrush(hbrBlue);
DeleteBrush(hbrCyan);
DeleteFont(hfntBig);
DeleteFont(hfntAlias);
DeleteBitmap(hbmVuXor[0]);
DeleteBitmap(hbmVuXor[1]);
// DestroyWindow(hTooltip);
Shell_NotifyIcon(NIM_DELETE,&nid);
registerhotkey(Wnd,1);
registerhotkey(Wnd,120);
ImageList_Destroy(imagelist11);
ImageList_Destroy(imagelist);
SAVE::deleteall();
DestroyWindow(Wnd);
}break;
case 4: if (config.z.recording()) { // Lückenloser Dateiwechsel (nur wenn Aufnahme läuft)
save_stop();
if (!save_start()) {
MBox(Wnd,98,MB_OK|MB_ICONEXCLAMATION,currentfile);
config.z.recording(false);
CheckDlgButton(Wnd,1,BST_UNCHECKED);
}
}break;
case MAKELONG(10,CBN_SELCHANGE): {
COMBOBOXEXITEM cbei;
cbei.mask=CBEIF_LPARAM;
cbei.iItem=ComboBox_GetCurSel((HWND)lParam);
SendMessage((HWND)lParam,CBEM_GETITEM,0,(LPARAM)&cbei);
q.sel=(char)cbei.lParam;
rec[idx].disconnect();
rec[idx].connect();
}break;
case MAKELONG(30,CBN_SELCHANGE): { // Raumklang
BYTE spa=(BYTE)(ComboBox_GetCurSel((HWND)lParam));
q.setspat(spa);
EnableDlgItem(Wnd,MAKEWORD(60,idx),spa==0 && config.z.getchan()>=2); // Panoramaregler ein wenn Quelle mono UND Ziel stereo
rec[idx].disconnect();
rec[idx].init(0,0,q.getchan());
rec[idx].connect();
}break;
case MAKELONG(40,BN_CLICKED): { // AGC
q.setAGC(2);
EnableDlgItem(Wnd,MAKEWORD(50,idx),!q.getAGC()); // Lautstärkeregler aus wenn AGC aktiv
}break;
case MAKELONG(70,CBN_EDITCHANGE):
case MAKELONG(101,CBN_EDITCHANGE):
case MAKELONG(124,CBN_EDITCHANGE): {
SetTimer(Wnd,LOWORD(wParam),500,0); // Edit-Element verzögert auslesen (für mehrstellige Zahlen)
}break;
case MAKELONG(70,CBN_SELCHANGE):
case MAKELONG(101,CBN_SELCHANGE):
case MAKELONG(124,CBN_SELCHANGE): {
SetTimer(Wnd,LOWORD(wParam),100,0); // Edit-Element updaten lassen, dann auslesen
}break;
case MAKELONG(102,CBN_SELCHANGE): { // Bitbreite Senke: Nichts weiter tun, wirkt sich erst auf nächste Aufnahme aus
config.z.setbytes(ComboBox_GetCurSel((HWND)lParam)+1);
mixer.initwf(0,0,config.z.getbits());
mixer.realloc();
}break;
case MAKELONG(103,CBN_SELCHANGE): { // Raumklang (ComboBoxEx mit Symbolik)
config.z.setspat(ComboBox_GetCurSel((HWND)lParam));
mixer.initwf(0,config.z.getchan(),0);
mixer.realloc();
for (int idx=0; idx<config.NIN; idx++) {
EnableDlgItem(Wnd,MAKEWORD(60,idx),config.q[idx].getchan()==1 && config.z.getchan()>=2); // Panoramaregler ein/aus
}
}break;
case MAKELONG(104,EN_UPDATE): {
GetWindowText((HWND)lParam,file,elemof(file));
SetTimer(Wnd,LOWORD(wParam),500,0); // verzögert speichern
}break;
case MAKELONG(105,BN_CLICKED): {
OPENFILENAME ofn;
TCHAR filter[128];
TCHAR f[MAX_PATH];
sprintDateTime(f,elemof(f),file);
memset(&ofn,0,sizeof ofn);
ofn.lStructSize=sizeof ofn;
ofn.lpstrFile=f;
ofn.nMaxFile=elemof(file);
ofn.Flags=OFN_HIDEREADONLY|OFN_PATHMUSTEXIST;
ofn.lpstrFilter=filter;
ofn.nFilterIndex=config.z.getfileformat()+1;
filter[LoadString(hInstance,105,filter,elemof(filter)-1)+1]=0;
if (GetSaveFileName(&ofn)) {
lstrcpyn(file,f,elemof(file));
SetDlgItemText(Wnd,104,file);
config.save();
}
}break;
case 119: if (hSettingsDlg) SetForegroundWindow(hSettingsDlg);
else hSettingsDlg=CreateDialog(hInstance,MAKEINTRESOURCE(119),Wnd,SettingsDlgProc); break;
case 120: if (hNotizDlg) SetForegroundWindow(hNotizDlg);
else if (currentfile[0]) {
hNotizDlg=CreateDialogParam(hInstance,MAKEINTRESOURCE(120),
lParam?Wnd:0,NotizDlgProc,lParam); // Position bzgl. Fenster oder absolut
}break;
case 121:
case 122:
case 123: setoutputsel(LOBYTE(wParam)-121); break;
case 126: { // Chechbox "Parallele Ausgabe"
if (config.z.echoing()) {
mixer.disconnect();
config.z.echoing(false);
}else{
config.z.echoing(true);
mixer.connect();
}
}break;
case MAKELONG(127,CBN_SELCHANGE): { // ComboBoxEx "Paralleles Ausgabegerät"
COMBOBOXEXITEM cbei;
cbei.mask=CBEIF_LPARAM;
cbei.iItem=ComboBox_GetCurSel((HWND)lParam);
SendMessage((HWND)lParam,CBEM_GETITEM,0,(LPARAM)&cbei);
config.z.sel=(char)cbei.lParam;
if (config.z.echoing()) {
mixer.disconnect();
mixer.connect();
}
}break;
case 100: ShowWindow(Wnd,SW_SHOWNORMAL); config.z.maininvis(false); break;
case MAKELONG(0xF5,1): if (hNotizDlg) SetForegroundWindow(hNotizDlg);
else if (hSettingsDlg) SetForegroundWindow(hSettingsDlg);
break;
case MAKELONG(0xF7,1): SendMessage(Wnd,WM_COMMAND,119,0); break; // Einstellungen per Accelerator
case MAKELONG(0xF8,1): SendMessage(Wnd,WM_COMMAND,120,0); break; // Notiz per Accelerator
}
}break;
case WM_HSCROLL: {
switch (LOBYTE(wParam)) {
case SB_ENDSCROLL:
case SB_THUMBTRACK: {
int pos=(int)SendMessage((HWND)lParam,TBM_GETPOS,0,0);
int id=GetDlgCtrlID((HWND)lParam);
SRCCONFIG&q=config.q[HIBYTE(id)];
switch (LOBYTE(id)) {
case 50: q.gain=(char)pos; break;
case 60: q.panorama=(char)pos; break;
}
}break;
}
}break;
case WM_NOTIFY: {
NMHDR&nmhdr=*(NMHDR*)lParam;
if (nmhdr.code==TTN_NEEDTEXT) {
TOOLTIPTEXT&ttt=*(TOOLTIPTEXT*)lParam;
if (ttt.uFlags&TTF_IDISHWND) {
DWORD id=GetDlgCtrlID((HWND)ttt.hdr.idFrom);
if (LOBYTE(id)==50) { // Verstärkungs-Slider (1 pro Quelle)
int pos=(int)SendMessage((HWND)ttt.hdr.idFrom,TBM_GETPOS,0,0);
_sntprintf(ttt.szText,elemof(ttt.szText),T("%d dB"),MulDiv(pos,20,120));
}
}
}
}break;
case WM_TIMER: if (wParam) { // Combobox-Edit-Eingaben und Combobox-Auswahl verzögert behandeln
KillTimer(Wnd,wParam);
int idx=HIBYTE(wParam);
SRCCONFIG&q=config.q[idx];
REC&r=rec[idx];
BYTE v=(BYTE)GetDlgItemInt(Wnd,int(wParam),0,false); // entweder Bits (wParam==102, 70) oder Rate (wParam==101)
BYTE cb; // Vorhergehender Wert
switch (LOBYTE(wParam)) {
case 1: FillWaveinCombos(); FillWaveoutCombo(GetDlgItem(Wnd,127)); return FALSE;
case 101: cb=config.z.rate; break;
case 124: cb=config.z.mp3min; break;
default: cb=q.getbits();
}
if (cb==v && !lParam) break; // Nichts tun wenn sich nichts ändert
switch (LOBYTE(wParam)) {
case 101: { // Rate: Alle Quellen schließen und öffnen
config.z.rate=v;
CloseOpenAllWaveIn(config.z.getrate());
}break;
case 124: {
config.z.mp3min=v;
}break;
case 104: break; // Dateiname … nur speichern!
default: { // Bitbreite Quelle: Nur betreffende Quelle schließen und öffnen
q.setbits(v);
r.disconnect();
r.init(0,0,0,v);
r.connect();
}
}
config.save();
}else{
for (int i=0; i<config.NIN; i++) rec[i].alive(); // Überlebensstatus anzeigen
}break;
case WM_HOTKEY: SendMessage(Wnd,WM_COMMAND,wParam,0); break; // Mit 3 Modifiern geht's
case WM_USER+42: switch (lParam) {
case WM_LBUTTONDOWN: //wasapi::record(); // DEBUG!!
case WM_RBUTTONDOWN: {
HMENU m=LoadMenu(hInstance,MAKEINTRESOURCE(100));
EnableMenuItem(m,120,config.z.recording()?MF_ENABLED:MF_GRAYED);
POINT pt;
GetCursorPos(&pt);
TrackPopupMenu(GetSubMenu(m,0),TPM_RIGHTBUTTON,pt.x,pt.y,0,Wnd,0);
DestroyMenu(m);
}break;
case WM_LBUTTONDBLCLK: SendMessage(Wnd,WM_COMMAND,100,0); break;
}break;
case WM_USER+23: {
Zeitmarke(Wnd,118,true);
}break;
case WM_QUERYENDSESSION: if (config.z.recording()) switch (MBox(Wnd,103,MB_YESNO|MB_DEFBUTTON2)) {
case IDNO: {
SetWindowLongPtr(Wnd,DWLP_MSGRESULT,0); // deny termination
}return TRUE;
}break;
case WM_ENDSESSION: {
if (config.z.recording()) save_stop();
config.save();
}break;
case WM_DESTROY: PostQuitMessage(0); break;
}
return FALSE;
}
void WinMainCRTStartup() {
hInstance=GetModuleHandle(0);
INITCOMMONCONTROLSEX ice={sizeof ice,ICC_USEREX_CLASSES|ICC_WIN95_CLASSES};
InitCommonControlsEx(&ice);
UINT exitcode=IDCANCEL;
if (!CoInitializeEx(0,COINIT_MULTITHREADED)) {
CreateDialog(hInstance,MAKEINTRESOURCE(100),0,MainDlgProc);
HACCEL hAccel=LoadAccelerators(hInstance,MAKEINTRESOURCE(100));
MSG Msg;
while (GetMessage(&Msg,0,0,0)) {
if (hDialog && TranslateAccelerator(hDialog,hAccel,&Msg)) continue;
if (hDialog && IsDialogMessage(hDialog,&Msg)) continue;
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
exitcode=(UINT)Msg.wParam;
CoUninitialize();
}
ExitProcess(exitcode);
}
// VS2019-64-Bit-Compiler bei /NODEFAULTLIB glücklich machen
#if _MSC_VER >= 1900
void _cdecl operator delete(void*a,size_t) {free(a);}
#endif
#ifdef _M_IX86
extern "C"{
#if _MSC_VER<1500
// mangels Laufzeitbibliothek "longlong"-Routinen nachreichen,
// diese erwartet in EDX:EAX das "longlong" und in CL die Schiebeweite
// "_cdecl" notwendig, schaltet Namensgarnierung zurück auf '_'-Präfix
// Int64ShrlMod32 usw. sind weniger effektiv als diese Routinen!
LONGLONG _declspec(naked) _cdecl _allshr(LONGLONG ll, BYTE shift) {
_asm{
// test cl,32
// jnz l1
shrd eax,edx,cl
sar edx,cl
ret
//l1: mov eax,edx
// xor edx,edx
// sar eax,cl
// ret
}
}
#else
_declspec(dllimport) int _cdecl localtime();
FARPROC _imp___localtime64=(FARPROC)localtime;
_declspec(dllimport) int _cdecl time();
FARPROC _imp___time64=(FARPROC)time;
#endif
}
#endif
Detected encoding: ANSI (CP1252) | 4
|
|