// https://doc.qt.io/qt-6/qtexamplesandtutorials.html // especially: // https://doc.qt.io/qt-6/qtcharts-qmloscilloscope-example.html // code: // https://code.qt.io/qt/qtcharts.git // https://code.qt.io/cgit/qt/qtcharts.git/tree/examples/charts/qmloscilloscope?h=6.6 // efficiency: // https://stackoverflow.com/questions/54993973/efficient-curve-plotting-using-qwtplotcurve /////////////////////////////////////////////////////////////////// // software lockin // i.e. software lock-in amplifier // i.e. software synchronous detector // i.e. software synchronous analyzer // i.e. software synchronous wave analyzer using namespace std; #include #include #include #include #include "Getopt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // On debian, you need to install the development package, // i.e. libasound2-dev (not just libasound2) to provide // alsa/asoundlib.h and the associated runtime libraries: #include #include #include #include #include "lockin.h" #include "gui.h" #include "gui_class.h" #include "refout.h" #include "lockin.h" #include "krunch.h" #ifdef FOOBAR #include "alsactl.h" #else /* backwards compatibility */ //# warning You may want to use ./excl.patch to implement ALSA exclusive access. # define SND_CTL_RDONLY 0 #endif // a few non-constant global variables: int verbosity=0; int xrun_verbosity(1); // be verbose about underrun and overrun string progname; timespec prog_start_time; // Forward references: void config_mixer(const string ctlfile, const string justcard); void discard(int signum); void trap_exit(int signum); void trap_detach(int signum); void drain(const char* draindev); void drain(const int fd); int isfile(const char* fname); // Functions string fixup_ctlfile(string ctlfile, const string justcard, const int mode=0); void snark::usage(const int err) { (err ? cerr : cout) << "Harvest entropy from the audio I/O system.\n" "Usage: " << progname << " [options]\n\n" "Options:\n" "--buffer-size, -b [] Number of frames to read at a time. [= " << nframe << "].\n" "--card, -c [] Sound card, subdevice to use. [= " << card_device << "].\n" " -c -1 List available capture cards.\n" " -c -2 List available playback cards.\n" "--mixer-ctl, -m [] Mixer set-up from alsactl file. " "[= " << fixup_ctlfile(mixer_ctlfile, justcard) << "];\n" " -m \"\" ==> don't set up; just use inherited settings.\n" "--Amplitude, -A [] RefOut amplitude / dBV " "[= " << refOutAmp_dB << "]\n" "--Channel-mask -C [] 1==>left 2==>right 3==> stereo etc.\n" "--Frequency, -F [] RefOut frequency / Hz (useful= 440).\n" "--Delta-f, -D [] RefOut 2nd chan freq relative to 1st" " [= " << refival << "].\n" "--Vo-cal, -O [] Output voltage calibration / dBV full scale [0]\n" "--Vi-cal, -I [] Input voltage calibration / dBV full scale [0]\n" " (-45 dbV FS is plausible for mic input)\n" "--Time-shift -T [] Advance reference by ... sec [0].\n" "--Phase-shift, -P [] Advance reference by ... degrees [0].\n" "--frame-rate, -N [] Audio measurement rate, frames per second;\n" " -N 0 ==> default ==> max rate supported by the hardware.\n" " -N -1 ==> probe for capture capabilities.\n" " -N -2 ==> probe for output capabilities.\n" "--Zref, -Z [] External reference resistor / Ohms " "[= " << zref << "]\n" "--verbose, -v Print extra debugging info (-vv ==> even more).\n" "--write-check, -w [] Write checkfile containing one buffer of audio.\n" "--1pass, -1 Exit after one pass through main loop.\n" << endl; } // mode 0 is normal string fixup_ctlfile(string ctlfile, const string justcard, const int mode){ if (ctlfile == "<>") { ctlfile = mode==0 ? invent_ctlfile(justcard) : invent_driverfile(justcard); } // else cmdline has explict ctlfile, use that // prepend explicit path, if needed: if (ctlfile.length()) { char mx = ctlfile[0]; if (mx != '.' && mx != '/') { ctlfile = "/etc/" + progname + "/" + ctlfile; const char* homish = getenv("HOME"); if (!homish) homish = "/root"; string home(homish); if (home != "/root") { ctlfile = home + ctlfile; } } // else . or / means use it verbatim } return ctlfile; } // Just like snd_pcm_writei(), // except the buff is always of the type ofdatum (int32_t), // no matter what type the raw hardware uses. // Return value: // nonnegative: # of frames written // negative: error code int writei(const alsa_pcm* pcm, const datum* buff, const int nframe){ if (pcm->format == SND_PCM_FORMAT_S32_LE) { return snd_pcm_writei(pcm->phndl, buff, nframe); } else if (pcm->format == SND_PCM_FORMAT_S16_LE) { valarray tmp(nframe * pcm->nchan); int16_t* to (&tmp[0]); const datum* from (buff); const int fudge(1<<16); for (unsigned int ii = 0; ii < nframe * pcm->nchan; ii++){ *to++ = *from++ / fudge; } return snd_pcm_writei(pcm->phndl, &tmp[0], nframe); } else if (pcm->format == SND_PCM_FORMAT_S8) { valarray tmp(nframe * pcm->nchan); char* to (&tmp[0]); const datum* from (buff); const int fudge(1<<24); for (unsigned int ii = 0; ii < nframe * pcm->nchan; ii++){ *to++ = *from++ / fudge; } return snd_pcm_writei(pcm->phndl, &tmp[0], nframe); } else { cerr << "Don't know how to convert pcm data to format " << snd_pcm_format_name(pcm->format) << endl; exeunt(1); } return 0; // defeat stupid compiler warning } // Just like the library routine of the same name, except: // 1) The first arg is a "keeper", // which means that we will accept writes that are // not an integer multiple of the period. // 2) The second arg is a datum* (not void*). // We call writei() to convert things to the sample-size // of the actual device. snd_pcm_sframes_t snd_pcm_writei(Keeper& kk, const datum* buffer, snd_pcm_uframes_t nframes){ int rslt; int todo(nframes); int head(0); // frames taken from head of buffer const datum* mybuf(buffer); datum* start = &kk.buf[0]; if (kk.finbuf) { head = kk.fpp - kk.finbuf; memcpy(start + kk.nchan*kk.finbuf, buffer, kk.nchan*sizeof(datum)*head); rslt = writei(kk.pcm, start, kk.fpp); if (rslt <= 0) return rslt; // no change in kk if (rslt != kk.fpp) return -98; todo -= head; kk.finbuf = 0; } mybuf += kk.nchan*head; rslt = writei(kk.pcm, mybuf, todo); if (rslt < 0) return rslt; // some error, let caller deal with it if (rslt == 0) return head; // head got written, nothing more // OK, at this point rslt must be positive. int left(todo - rslt); // number of frames left to do.... if (!left) return nframes; // Nothing? Great! if (left < kk.fpp) { // can we keep it for next time? kk.finbuf = left; memcpy(start, mybuf + kk.nchan*rslt, kk.nchan*sizeof(datum)*left); return nframes; } // here with a short write that doesn't fit in our keep-buffer: kk.finbuf = 0; return head + rslt; } // Constructor Keeper::Keeper(const alsa_pcm* _dsp, const int _fpp) : pcm(_dsp), // remember the args fpp(_fpp), buf(pcm->nchan * _fpp), // allocate the buffer finbuf(0), nchan(_dsp->nchan) {} // no code, just initializers (above) // Constructor snark::snark() : card_device("PCH"), wcheck_fname(""), nframe(2048), onepass(0), desired_rate(0), channel_mask(-1), kout(0), zref(1e6), // assume reference = 1 megohm reffreq(440.), // default : concert A440 refival(440.5/440.), mixer_ctlfile(""), wcheck_fd(-1), sps(0), snooze(.5) {} // no code, just the initializers (above) string crtRed ("\033[1;31m"); string crtNormal("\033[0;39m"); vector clean_me; void trapme(){ signal(SIGHUP, trap_exit); // hangup signal(SIGINT, trap_exit); // interrupt signal(SIGTERM, trap_exit); // terminate signal(SIGPIPE, trap_exit); // broken pipe signal(SIGCHLD, SIG_DFL); } double rms(const int* ptr, const int nn){ if (!nn) return 0; double val; double sum(0); for (int ii = 0; ii < nn; ii++){ val = ptr[ii]; sum += val*val; } sum /= nn; return sqrt(sum); } int main(int argc, char** argv) { // first, initialize the gui, so it can calculate // some things (e.g. actOutFreq) that other tasks // (e.g. refout) will need. int xargc(1); char tmpfoo[5]; strncpy(tmpfoo, "foo", sizeof tmpfoo); char * xargv[xargc] = {tmpfoo}; QApplication app(xargc, xargv); app.setStyle("plastique"); progname = argv[0]; string::size_type where = progname.rfind('/'); // find final slash if (where != progname.npos) { progname = progname.substr(1+where); } snark foo; // holder for cmdline options ... foo.cmdline(argc, argv); // ... process them trapme(); // install trap handlers // be sure to setup the capture handler first, so // on a soundblaster16 capture gets the 16-bit DMA // channel and refout gets the 8-bit channel alsa_pcm in("capture"); int err; if (foo.desired_rate >= -1) { err = in.getrate("hw:" + foo.card_device, SND_PCM_STREAM_CAPTURE, foo.desired_rate); if (err) exit(1); // msgs have already been printed } alsa_pcm out("refout"); if (foo.reffreq // off ==> don't even initialize || foo.desired_rate < 0) { // unless we are just dumping params err = out.getrate("hw:" + foo.card_device, SND_PCM_STREAM_PLAYBACK, foo.desired_rate); if (err) exit(1); // msgs have already been printed } int rate = min(in.max_rate, out.max_rate); if (foo.desired_rate) rate = min(rate, foo.desired_rate); pcmRate = rate; in.setup(rate); out.setup(rate); // synchronize the two alsa_pcm sub-devices: int linkerr = snd_pcm_link(in.phndl, out.phndl); if (linkerr < 0) { cout << "Lockin: synchronization link failed: " << snd_strerror(linkerr) << endl; } // Be sure to configure mixer _after_ opening the pcm handlers, // because we want to hold exclusive access to the mixer (control // device) but snd_pcm_open wants to temporarily open the control // device for its own reasons. config_mixer(foo.mixer_ctlfile, foo.justcard); foo.open_aux_io(); // checkfile, mostly prog_start_time = out.now(); if (verbosity) { fprintf(stderr, "starting %s" NL, progname.c_str()); } app.setStyle("plastique"); QWidget ancestor; QwtPlot asdf(&ancestor); // myWindow is derived from QWidget .... class myWindow : public QWidget myWindow topWindow; topWindow.ctrlCol->refOutGroup->freqBox->setValue(foo.reffreq); topWindow.actualFreqs(); // initialize actual freqs topWindow.show(); int pipefd[2]; if (pipe(pipefd)) { fprintf(stderr, "Could not open pipe: "); perror(0); exeunt(1); } ref_arg refarg(foo.refival, &out, pipefd[1/* writing */]); pthread_t refout_id; pthread_create(&refout_id, 0, &refout, &refarg); pthread_t krunch_id; krunch_arg karg(&topWindow, &in, &out, &foo, pipefd[0/* reading */]); pthread_create(&krunch_id, 0, &krunch, &karg); int rslt = app.exec(); cout << "Qt returns: " << rslt << endl; exeunt(0); } string purify(const string foo) { string rslt; for (string::size_type ii = 0; ii < foo.length(); ii++) { char ch = foo[ii]; if (isupper(ch)) ch += 'a' - 'A'; if (isalnum(ch)) rslt += ch; } return rslt; } // return a string like "usbaudio.ctl" // based on ALSA's notion of the DRIVER name string invent_driverfile(const string justcard){ string driver("unknown"); snd_ctl_t* chndl; // control handle string xxx = "hw:" + justcard; int err = snd_ctl_open(&chndl, xxx.c_str(), SND_CTL_RDONLY); if (err < 0) { chndl = 0; cerr << "Error opening control channel to card " << justcard << endl; // proceed with driver = "unknown" } else { snd_ctl_card_info_t *info; snd_ctl_card_info_t **info_p; /* defeat paranoid compiler warning */ info_p = &info; snd_ctl_card_info_alloca(info_p); err = snd_ctl_card_info(chndl, info); if (err < 0) { cerr << "Can't get card info: " << err << endl; } else { #if 0 int wid(18); cerr << left; // example: hw:0 cerr << setw(wid) << "Ctl name:" << snd_ctl_name(chndl) << endl; // example: card0 (but could be changed in modules.conf): cerr << setw(wid) << "Card ID:" << snd_ctl_card_info_get_id(info) << endl; // example: Sound Blaster Extigy cerr << setw(wid) << "Card name:" << snd_ctl_card_info_get_name(info) << endl; // example: M Audio Delta 1010 at 0x1400, irq 22 cerr << setw(wid) << "Card longname:" << snd_ctl_card_info_get_longname(info) << endl; // example: ICE1712 cerr << setw(wid) << "Driver:" << snd_ctl_card_info_get_driver(info) << endl; cerr << setw(wid) << "Mixer name:" << snd_ctl_card_info_get_mixername(info) << endl; #endif driver = snd_ctl_card_info_get_driver(info); } } return purify(driver) + ".ctl"; } // return a string like "soundblasterextigy.ctl" // based on ALSA's notion of the CTL name string invent_ctlfile(const string justcard){ string driver("unknown"); snd_ctl_t* chndl; // control handle string xxx = "hw:" + justcard; int err = snd_ctl_open(&chndl, xxx.c_str(), SND_CTL_RDONLY); if (err < 0) { chndl = 0; cerr << "Error opening control channel to card " << justcard << endl; // proceed with driver = "unknown" } else { snd_ctl_card_info_t *info; snd_ctl_card_info_t **info_p; /* defeat paranoid compiler warning */ info_p = &info; snd_ctl_card_info_alloca(info_p); err = snd_ctl_card_info(chndl, info); if (err < 0) { cerr << "Can't get card ID info: " << err << endl; } else { driver = snd_ctl_card_info_get_name(info); } } return purify(driver) + ".ctl"; } static string digits = "0123456789"; void dump_io() { for (int ii = 0; ii 0) kill(pid, SIGQUIT); } cout << crtNormal; exit(sts); } void trap_exit(int signum) { fprintf(stderr, "%s stopping on signal %i" NL, progname.c_str(), signum); cleanup(0); } void discard(int signum) { fprintf(stderr, "Discarding signal %i" NL, signum); } void exeunt(int sts){ fprintf(stderr, "%s exiting, status %i" NL, progname.c_str(), sts); cleanup(sts); } int dummy(const int xx) { return xx; }