summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Denker <jsd@av8n.com>2024-03-16 11:21:23 -0700
committerJohn Denker <jsd@av8n.com>2024-03-16 11:42:15 -0700
commit9852b855db2a65ea6eb5e877411634820214ddf0 (patch)
treec4c4504b34ef81d67891ed137ec2cfaabb4d6d04
parent634d365a03cb0581a062cd3cf4db9ae69f1cde26 (diff)
initial setupHEADmaster
-rw-r--r--src/CMakeLists.txt33
-rw-r--r--src/Getopt.cxx31
-rw-r--r--src/Getopt.h4
-rw-r--r--src/alsa_pcm.cxx355
-rw-r--r--src/alsa_pcm.h46
-rw-r--r--src/bad_thing.h12
-rw-r--r--src/bin/.gitignore0
-rw-r--r--src/biquad.cxx80
-rw-r--r--src/biquad.h42
-rw-r--r--src/gui.cxx755
-rw-r--r--src/gui.h13
-rw-r--r--src/gui_class.cxx5
-rw-r--r--src/gui_class.h217
-rw-r--r--src/iir_bp.cxx133
-rw-r--r--src/iir_bp.h17
-rw-r--r--src/krunch.cxx308
-rw-r--r--src/krunch.h24
-rw-r--r--src/lockin.cxx748
-rw-r--r--src/lockin.h99
-rw-r--r--src/makefile16
-rw-r--r--src/refout.cxx247
-rw-r--r--src/refout.h32
-rw-r--r--src/sources.txt10
-rw-r--r--src/thrower.cxx12
-rw-r--r--src/thrower.h27
25 files changed, 3266 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..8b0d72a
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,33 @@
+cmake_minimum_required(VERSION 3.13) # CMake version check
+project(lockin) # Create project "simple_example"
+set(CMAKE_CXX_STANDARD 17) # Enable c++17 standard (20 would not work)
+add_definitions(-DQT_NO_VERSION_TAGGING)
+# do not use: -Wstrict-overflow=2 # disaster for Qt include-files
+SET(CMAKE_CXX_FLAGS "-g2 -fPIC -Wall" )
+
+set(CMAKE_AUTOMOC ON) # hackish module object compiler
+
+# All the .cxx source files of the project:
+file(STRINGS sources.txt SOURCE_FILES)
+
+# Add executable target with source files listed in SOURCE_FILES variable
+add_executable(lockin ${SOURCE_FILES})
+
+target_link_libraries(lockin "-lQt5Gui -lQt5Core -lQt5Widgets -lqwt-qt5")
+target_link_libraries(lockin "-lasound -lgsl")
+
+target_include_directories(lockin PUBLIC
+ /usr/include/x86_64-linux-gnu/qt5/
+ /usr/include/x86_64-linux-gnu/qt5/QtWidgets
+ /usr/include/x86_64-linux-gnu/qt5/QtGui
+ /usr/include/x86_64-linux-gnu/qt5/QtCore
+ /usr/include/qwt/
+)
+
+############ find_package(ALSA) # not necessary, not helpful AFAICT
+
+find_package(Qt5 COMPONENTS Widgets) # necessary
+
+include(CMakePrintHelpers)
+message("files: a b c ${SOURCE_FILES}")
+cmake_print_variables(SOURCE_FILES)
diff --git a/src/Getopt.cxx b/src/Getopt.cxx
new file mode 100644
index 0000000..49673bc
--- /dev/null
+++ b/src/Getopt.cxx
@@ -0,0 +1,31 @@
+//////////////////////////////////////////////////////////////////////
+// Provide a C++ interface to getopt.
+// Calculate things that can be calculated, to ensure consistency,
+// and so the programmer doesn't need to duplicate effort.
+
+using namespace std;
+
+#include <iostream> /* needed by some compilers */
+#include "Getopt.h"
+#include <iostream>
+#include <string>
+
+int getopt_long(int argc, char * const argv[],
+ const struct option *longopts, int *longindex){
+
+ string optstring;
+ const struct option * pp;
+ for (pp = longopts; pp->name; pp++){
+ if (pp->val < 0 || pp->val > 255) {
+ cerr << "Cannot handle val " << pp->val
+ << " for option '" << pp->name << "'" << endl;
+ } else {
+// cerr << pp->name << "'" << char(pp->val) << "'" << endl;
+ optstring += char(pp->val);
+ if (pp->has_arg == 1) optstring += ':';
+ if (pp->has_arg == 2) optstring += "::";
+ }
+ }
+// cerr << optstring << endl;
+ return getopt_long(argc, argv, optstring.c_str(), longopts, longindex);
+}
diff --git a/src/Getopt.h b/src/Getopt.h
new file mode 100644
index 0000000..ed759be
--- /dev/null
+++ b/src/Getopt.h
@@ -0,0 +1,4 @@
+#include <getopt.h>
+
+int getopt_long(int argc, char * const argv[],
+ const struct option *longopts, int *longindex);
diff --git a/src/alsa_pcm.cxx b/src/alsa_pcm.cxx
new file mode 100644
index 0000000..2bbe5e6
--- /dev/null
+++ b/src/alsa_pcm.cxx
@@ -0,0 +1,355 @@
+#include "alsa_pcm.h"
+#include <iostream>
+#include <iomanip>
+#include <sstream>
+
+extern int verbosity;
+
+// note that if the error code is -999,
+// we exit with non-error status (0)
+void alsa_pcm::error(const char* msg,
+ const int ecode, snd_pcm_hw_params_t* hwparams) {
+ using namespace std;
+ if (ecode != -999) {
+ cerr << "Device won't accept parameter '" << msg << "'"
+ << " ecode '" << ecode << "'" << endl;
+ }
+ else {
+ cerr << msg << " for " << moniker << endl;
+ }
+ snd_pcm_hw_params_dump(hwparams, errlog);
+ int sync = snd_pcm_hw_params_can_sync_start(hwparams);
+ cout << "sync_start: " << sync << endl;
+ int sbits = snd_pcm_hw_params_get_sbits(hwparams);
+ if (sbits <= 0) try_formats(hwparams); // ignore errors from here
+ sbits = snd_pcm_hw_params_get_sbits(hwparams); // try again
+ if (sbits > 0) {
+ cerr << "Allegedly can do "
+ << sbits << " significant bits per sample." << endl;
+ } else {
+// Can't determine sigbits if the card is capable of
+// multiple sample-sizes and we haven't chosen one yet.
+ cerr << "Can't (yet) determine sigbits per sample. Result was: "
+ << sbits << " == "
+ << snd_strerror(sbits) << endl;
+ sbits = 0;
+ }
+
+ snd_pcm_close(phndl);
+ snd_output_close(errlog);
+ if (ecode == -999) exit(0); // weird code for non-error
+ exit(1);
+}
+
+// Try various sample formats.
+int alsa_pcm::try_formats(snd_pcm_hw_params_t* hwparams){
+ int err;
+ format = SND_PCM_FORMAT_S32_LE;
+ err = snd_pcm_hw_params_set_format(phndl, hwparams, format);
+ if (err >= 0) return 0;
+
+ format = SND_PCM_FORMAT_S16_LE;
+ err = snd_pcm_hw_params_set_format(phndl, hwparams, format);
+ if (err >= 0) return 0;
+
+ format = SND_PCM_FORMAT_S8;
+ err = snd_pcm_hw_params_set_format(phndl, hwparams, format);
+ if (err >= 0) return 0;
+
+ return -999;
+}
+
+int alsa_pcm::getrate(const std::string carddev,
+ snd_pcm_stream_t stream_dir,
+ const int dump_flag){
+ int err;
+
+ direction = stream_dir;
+// Connect error reporting to stderr
+ if (!errlog) snd_output_stdio_attach(&errlog, stderr, 0);
+
+ int mode = 0; // could have been SND_PCM_NONBLOCK;
+ err = snd_pcm_open(&phndl, carddev.c_str(), direction, mode);
+ if (err < 0){
+ fprintf(stderr, "Error opening soundcard %s for %s: %s\n",
+ carddev.c_str(), moniker, snd_strerror(err));
+ snd_output_close(errlog);
+ return 1;
+ }
+
+// now that we've grabbed the device, possibly without blocking
+// if it was busy, we now switch to blocking mode for all I/O:
+ err = snd_pcm_nonblock(phndl, 0);
+
+// Allocate a hwparams struct...
+ snd_pcm_hw_params_t* hwparams;
+ snd_pcm_hw_params_alloca(&hwparams);
+
+// ... and initialize it to the "wide open" ranges
+// allowed by phndl:
+ err = snd_pcm_hw_params_any(phndl, hwparams);
+ if (err < 0) error("Initialization error", -999, hwparams);
+
+// User can force a dump of parameters,
+// here at a nice early stage:
+ if (dump_flag < 0) error("Possibilities are:", -999, hwparams);
+
+ snd_pcm_hw_params_get_rate_max(hwparams, &max_rate, 0);
+ return 0;
+}
+
+int alsa_pcm::setup(const int _rate){
+ rate = _rate;
+ int err;
+
+// Allocate a hwparams struct...
+ snd_pcm_hw_params_t* hwparams;
+ snd_pcm_hw_params_alloca(&hwparams);
+
+// ... and initialize it to the "wide open" ranges
+// allowed by phndl:
+ err = snd_pcm_hw_params_any(phndl, hwparams);
+ if (err < 0) error("Initialization error", -999, hwparams);
+
+// Access method, interleaved or non-interleaved
+ err = snd_pcm_hw_params_set_access(phndl, hwparams,
+ SND_PCM_ACCESS_RW_INTERLEAVED);
+ if (err < 0) error("access method", SND_PCM_ACCESS_RW_INTERLEAVED,
+ hwparams);
+
+// Try various sample formats:
+ int rslt = try_formats(hwparams);
+ if (rslt < 0)
+ error("DSP error: format not S32_LE nor S16_LE nor S8", rslt,
+ hwparams);
+
+ if (!phndl) return -998;
+ err = snd_pcm_hw_params_set_rate(phndl, hwparams, rate, 0);
+ if (err < 0) error("rate", rate, hwparams);
+
+// Number of channels we want, stereo = 2
+ unsigned int nchan_max;
+ snd_pcm_hw_params_get_channels_max(hwparams, &nchan_max);
+ nchan = nchan_max;
+ if (direction == SND_PCM_STREAM_PLAYBACK && nchan_max > 2){
+ nchan = 2; // calibrator doesn't need more than 2 channels
+ }
+ err = snd_pcm_hw_params_set_channels(phndl, hwparams, nchan);
+ if (err < 0) error("Cannot set number of channels: ", nchan, hwparams);
+
+// The period size. For all practical purposes this is synonymous
+// to OSS/Free's fragment size.
+// Note that this in frames (frame = nr_channels * sample_width)
+// For example, a setup using FPP=1024, stereo, 16-bit format
+// gives a period size of 4096 bytes (1024 x 2 x 2 bytes).
+ if (1) {
+ err = snd_pcm_hw_params_set_period_size(phndl, hwparams, FPP, 0);
+ if (err < 0) error("period-size", FPP, hwparams);
+ }
+
+// The number of periods we want to allocate
+ {
+ unsigned int periods = 4; // reasonable guess
+ unsigned int minper, maxper;
+ if (snd_pcm_hw_params_get_periods_min(hwparams, &minper, 0)==0
+ && periods < minper) periods = minper;
+ if (snd_pcm_hw_params_get_periods_max(hwparams, &maxper, 0)==0
+ && periods > maxper) periods = maxper;
+ err = snd_pcm_hw_params_set_periods(phndl, hwparams, periods, 0);
+#ifdef testing
+ fprintf(stderr, "periods: %d (%d -- %d) \n",
+ periods, minper, maxper);
+#endif
+ if (periods <= 1) {
+ fprintf(stderr, "Warning: %d periods is not really enough; "
+ "we should have at least 2 for double buffering\n", periods);
+ if (periods==0) exit(1);
+ fprintf(stderr, "Continuing anyway....\n");
+ }
+ if (err < 0) error("#-of-periods", periods, hwparams);
+ }
+
+// Finally set up our hardware with the selected values
+ err = snd_pcm_hw_params(phndl, hwparams);
+ if (err < 0) {
+ fprintf(stderr, "Unable to set hardware parameter:\n");
+ snd_pcm_hw_params_dump(hwparams, errlog);
+ return 2;
+ }
+
+ err = sbits = snd_pcm_hw_params_get_sbits(hwparams);
+ if (sbits <= 0 || sbits > 32) {
+// if driver won't tell us how many are significant,
+// assume they all are:
+ sbits = snd_pcm_samples_to_bytes(phndl, 8);
+ fprintf(stderr, "Trouble %d determining sig bits; assuming %d\n",
+ err, sbits);
+ }
+
+ if (verbosity >= 2) {
+ fprintf(stderr, ">>>>>> After setup (%s)\n", moniker);
+ snd_pcm_hw_params_dump(hwparams, errlog);
+ }
+
+// At this point you can start sending PCM data to the device
+ return 0;
+}
+
+// the last time the stream was started or stopped
+snd_htimestamp_t alsa_pcm::ss_time(){
+ snd_pcm_status_t* sts(0);
+ snd_pcm_status_alloca(&sts);
+ snd_htimestamp_t tstamp; // this is a struct timespec
+ int err = snd_pcm_status(phndl, sts);
+ if (err < 0) {
+ fprintf(stderr, "Setup: can't get status?\n");
+ tstamp.tv_sec = 0;
+ tstamp.tv_nsec = 0;
+ return tstamp;
+ }
+ snd_pcm_status_get_trigger_htstamp(sts, &tstamp);
+ return tstamp;
+}
+
+// same as above, but returns current time
+snd_htimestamp_t alsa_pcm::now(){
+ snd_pcm_status_t* sts(0);
+ snd_pcm_status_alloca(&sts);
+ snd_htimestamp_t tstamp; // this is a struct timespec
+ int err = snd_pcm_status(phndl, sts);
+ if (err < 0) {
+ fprintf(stderr, "Setup: can't get status?\n");
+ tstamp.tv_sec = 0;
+ tstamp.tv_nsec = 0;
+ return tstamp;
+ }
+ snd_pcm_status_get_htstamp(sts, &tstamp);
+ return tstamp;
+}
+
+std::string alsa_pcm::alsa_state_name() {
+
+ snd_pcm_state_t state = snd_pcm_state(phndl);
+
+#define checkit(XX) if (state == XX) return #XX
+
+ checkit(SND_PCM_STATE_OPEN);
+ checkit(SND_PCM_STATE_SETUP);
+ checkit(SND_PCM_STATE_PREPARED);
+ checkit(SND_PCM_STATE_RUNNING);
+ checkit(SND_PCM_STATE_XRUN);
+ checkit(SND_PCM_STATE_DRAINING);
+ checkit(SND_PCM_STATE_PAUSED);
+ checkit(SND_PCM_STATE_SUSPENDED);
+ checkit(SND_PCM_STATE_DISCONNECTED);
+ return "??unknown state??";
+}
+
+#define command "whatever"
+#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
+#define error(...) do {\
+ fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+ fprintf(stderr, __VA_ARGS__); \
+ putc('\n', stderr); \
+} while (0)
+#else
+#define error(args...) do {\
+ fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \
+ fprintf(stderr, ##args); \
+ putc('\n', stderr); \
+} while (0)
+#endif
+
+int dummy(int const xx);
+
+void device_list(const snd_pcm_stream_t direction){
+#define _(XX) XX
+ snd_ctl_t *handle;
+ int card, err, dev, idx;
+ snd_ctl_card_info_t *info;
+ snd_pcm_info_t *pcminfo;
+ snd_ctl_card_info_alloca(&info);
+ snd_pcm_info_alloca(&pcminfo);
+
+ card = -1;
+ if (snd_card_next(&card) < 0 || card < 0) {
+ error(_("no soundcards found..."));
+ return;
+ }
+ printf(_("**** List of %s Hardware Devices ****\n"),
+ snd_pcm_stream_name(direction));
+
+// loop over all cards:
+ for (int ii=0;; ii++) {
+ using namespace std;
+ if (card < 0) break;
+// use dummy to defend against false compiler warning,
+// and possibly wrong code generation:
+// "assuming signed overflow does not occur when simplifying conditional to constant"
+ if (dummy(ii)) fprintf(stdout, "\n");
+ stringstream name;
+ name << "hw:" << card;
+ if ((err = snd_ctl_open(&handle, name.str().c_str(), 0)) < 0) {
+ error("control open (%i): %s", card, snd_strerror(err));
+ goto next_card;
+ }
+ if ((err = snd_ctl_card_info(handle, info)) < 0) {
+ error("control hardware info (%i): %s", card, snd_strerror(err));
+ snd_ctl_close(handle);
+ goto next_card;
+ }
+ cout << "card " << setw(2) << card
+ << " ID: " << snd_ctl_card_info_get_id(info)
+ << " ... name: " << snd_ctl_card_info_get_name(info)
+ << endl;
+
+ cout << " driver: " << snd_ctl_card_info_get_driver(info)
+ << endl;
+ cout << " mixer: " << snd_ctl_card_info_get_mixername(info)
+ << endl;
+ dev = -1;
+// loop over all devices on the card
+ for (int ndev=0; ; ndev++) {
+ unsigned int count;
+ if (snd_ctl_pcm_next_device(handle, &dev)<0)
+ error("snd_ctl_pcm_next_device");
+ if (dev < 0)
+ break;
+ snd_pcm_info_set_device(pcminfo, dev);
+ snd_pcm_info_set_subdevice(pcminfo, 0);
+ snd_pcm_info_set_stream(pcminfo, direction);
+ if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) {
+ if (err != -ENOENT)
+ error("control digital audio info (%i): %s", card, snd_strerror(err));
+ continue;
+ }
+ cout << " device " << setw(2) << dev
+ << " ID: " << snd_pcm_info_get_id(pcminfo)
+ << " ... name: " << snd_pcm_info_get_name(pcminfo)
+ << endl;
+ count = snd_pcm_info_get_subdevices_count(pcminfo);
+ int avail = snd_pcm_info_get_subdevices_avail(pcminfo);
+ cout << " subdevices available: " << setw(2) << avail
+ << " total: " << count
+ << endl;
+ for (idx = 0; idx < (int)count; idx++) {
+ snd_pcm_info_set_subdevice(pcminfo, idx);
+ if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) {
+ error("control digital audio playback info (%i): %s",
+ card, snd_strerror(err));
+ } else {
+ cout << " subdevice " << setw(2) << idx
+ << " ... name: "
+ << snd_pcm_info_get_subdevice_name(pcminfo)
+ << endl;
+ }
+ }
+ }
+ snd_ctl_close(handle);
+ next_card:
+ if (snd_card_next(&card) < 0) {
+ error("snd_card_next");
+ break;
+ }
+ }
+}
diff --git a/src/alsa_pcm.h b/src/alsa_pcm.h
new file mode 100644
index 0000000..fafd857
--- /dev/null
+++ b/src/alsa_pcm.h
@@ -0,0 +1,46 @@
+#ifndef ALSA_PCM__H
+#define ALSA_PCM__H
+
+#include <string>
+#include <alsa/asoundlib.h>
+
+const int FPP(1024); // frames per period
+
+class alsa_pcm{
+public:
+ snd_pcm_t* phndl; // pcm handle
+#if 0
+ DO NOT save a pointer to any snd_pcm_hw_params_t object;
+ alsa allocates space _on the stack_ and it goes out of
+ scope quickly.
+ snd_pcm_hw_params_t *hwparams;
+#endif
+ snd_output_t* errlog;
+ unsigned int nchan; // samples per frame; 2 ==> stereo
+ unsigned int max_rate;
+ unsigned int rate; // frames per second
+ int sbits; // significant bits
+ snd_pcm_stream_t direction;
+// int sizeofsamp; // bytes per sample
+ snd_pcm_format_t format;
+ const char* moniker;
+ snd_htimestamp_t ss_time(); // start or stop time (usually start time)
+ snd_htimestamp_t now(); // current time
+ void error(const char*, const int, snd_pcm_hw_params_t*);
+ int getrate(const std::string carddevice,
+ snd_pcm_stream_t stream_dir,
+ const int dumpflag);
+ int setup(const int rate);
+// Constructor doesn't do much,
+// so it is cheap to allocate one that you're not
+// necessarily going to use:
+ inline alsa_pcm(const char* mm)
+ : phndl(0), errlog(0), moniker(mm)
+ {}
+ int try_formats(snd_pcm_hw_params_t*);
+ std::string alsa_state_name();
+};
+
+void device_list(const snd_pcm_stream_t direction);
+
+#endif /* ALSA_PCM__H */
diff --git a/src/bad_thing.h b/src/bad_thing.h
new file mode 100644
index 0000000..ec13f1c
--- /dev/null
+++ b/src/bad_thing.h
@@ -0,0 +1,12 @@
+#ifndef BAD_THING__H
+#define BAD_THING__H
+class bad_thing: public std::exception{
+ const char* msg;
+ virtual const char* what() const throw() {
+ return msg;
+ }
+public:
+ bad_thing(const char* _msg)
+ : msg(_msg) {}
+};
+#endif
diff --git a/src/bin/.gitignore b/src/bin/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/bin/.gitignore
diff --git a/src/biquad.cxx b/src/biquad.cxx
new file mode 100644
index 0000000..372b89e
--- /dev/null
+++ b/src/biquad.cxx
@@ -0,0 +1,80 @@
+#include <iostream>
+#include "biquad.h"
+#include "bad_thing.h"
+using namespace std;
+
+// gory-detail constructor
+biquad::biquad(vector<double> const _c, vector<double> const _d,
+ vector<C> _zeros, vector<C> _poles)
+: c(_c), d(_d), zeros(_zeros), poles(_poles),
+ ws1(0), ws2(0)
+{
+ if (d[0] != 1.) throw bad_thing("biquad: d0 must be 1.");
+}
+
+// constructor in terms of zero and pole position,
+// assuming conjugate pairs:
+biquad::biquad(C const zero, C const pole)
+///// nume = (z-zero)(z-zerobar)
+///// = z^2 - 2 zero.real + |zero|^2
+: biquad({1., -2.*zero.real(), norm(zero)},
+ {1., -2.*pole.real(), norm(pole)},
+ {zero, conj(zero)},
+ {pole, conj(pole)})
+{}
+
+C constexpr biquad::C0;
+C constexpr biquad::C1;
+
+vector<C> solve(vector<double> const poly) {
+ double disc(1 - 4. * poly[0] * poly[2] / poly[1] / poly[1]);
+ C inner(biquad::C1 + sqrt(C(disc, 0)));
+ C root1 = C(-poly[1]/2/poly[0], 0) * inner;
+ return { root1,
+ C(poly[2]/poly[0], 0) / root1 };
+}
+
+// constructor in terms of coefficients:
+biquad::biquad(vector<double> const _c, vector<double> const _d)
+: biquad(_c, _d,
+ {C0, C0}, {C0, C0})
+{
+ zeros = solve(c);
+ poles = solve(d);
+}
+
+double biquad::step(double const Vin){
+ double q1(ws1);
+ double q2(ws2);
+ double Vout = c[0]*Vin + q1;
+ ws1 = c[1]*Vin - d[1]*Vout + q2;
+ ws2 = c[2]*Vin - d[2]*Vout;
+ return Vout;
+}
+
+// transfer function
+C biquad::xfunc(C const z) const {
+ C nume = (c[0]*z + c[1])*z + c[2];
+ C denom = (z + d[1])*z + d[2];
+ return nume / denom;
+}
+
+void biquad::please_normalize(C const z) {
+ double denom(abs(xfunc(z)));
+// cout << "denom: " << denom << endl;
+// cout << "c2: " << c[2] << endl;
+// cout << "z: " << z << endl;
+ for (auto &coeff : c) {
+ coeff /= denom;
+ }
+}
+
+// same as above, but with some error checking:
+
+void biquad::normalize(C const z) {
+ if (abs(abs(z) - 1.) > 1e-10) {
+//
+ throw bad_thing("normalizing to z not on unit circle");
+ }
+ please_normalize(z);
+}
diff --git a/src/biquad.h b/src/biquad.h
new file mode 100644
index 0000000..d897062
--- /dev/null
+++ b/src/biquad.h
@@ -0,0 +1,42 @@
+#ifndef BIQUAD__H
+#define BIQUAD__H
+#include <complex>
+#include <vector>
+
+// typical usage:
+// biquad butterworth({1., 2., 1.},
+// {1., -1.99911142347079540116, 0.99911181807963833634});
+
+typedef std::complex<double> C;
+
+class biquad{
+public:
+// d0 must always be 1
+ std::vector<double> c, d;
+ std::vector<C> zeros, poles;
+ double ws1, ws2; // remembered weighted sums
+
+ C static constexpr C0 = C(0, 0);
+ C static constexpr C1 = C(1, 0);
+// gory-detail constructor
+biquad(std::vector<double> const _c, std::vector<double> const _d,
+ std::vector<C> _zeros, std::vector<C> _poles);
+
+// constructor in terms of coefficients:
+biquad(std::vector<double> const _c, std::vector<double> const _d);
+
+// constructor in terms of zero and pole position,
+// assuming conjugate pairs:
+ biquad(C const zero, C const pole);
+
+// time-domain evaluation:
+ double step(double const Vin);
+
+// z-plane transfer function
+ C xfunc(const C z) const;
+
+ void normalize(C const z = 1);
+ void please_normalize(C const z = 1);
+};
+
+#endif
diff --git a/src/gui.cxx b/src/gui.cxx
new file mode 100644
index 0000000..75f1f9c
--- /dev/null
+++ b/src/gui.cxx
@@ -0,0 +1,755 @@
+#include <iostream>
+#include <iomanip>
+#include "gui_class.h"
+#include <gsl/gsl_fit.h>
+#include <iostream>
+using namespace std;
+
+#define STRETCH /* nothing */
+
+extern int verbosity; // should be in a .h file somewhere
+
+/////////////////
+// some kludgey global variables,
+// mostly for inter-thread communication
+int pcmRate(0);
+double cpkp(0); // cycles per krunch period
+double fpkp(0); // frames per krunch period:
+double actOutFreq(0); // actual refout frequency
+double refOutAmp_dB(-15.); // default should not be not too loud
+double VoCal_dB(0.);
+double ViCal_dB(0.);
+double timeShift(0.); // in seconds
+double phaseShift(0.); // in degrees
+double lockerPhase(0.); // in degrees
+int repaintFrame(0);
+//
+// Note: more globals can be found in lockin.cxx
+
+class colorer {
+public:
+ Qt::GlobalColor code;
+ const char* name;
+
+ colorer(const Qt::GlobalColor _code, const char* const _name)
+ : code(_code), name(_name)
+ {}
+};
+
+#define X(foo) colorer(Qt::foo, #foo)
+
+static colorer color_list[] = {X(red), X(blue), X(black)};
+
+#undef X
+
+static int color_list_size = sizeof(color_list) / sizeof(color_list[0]);
+
+#if 0 /* code not needed at present */
+double now(){
+ timespec xx;
+
+#ifdef _POSIX_MONOTONIC_CLOCK
+ clock_gettime(CLOCK_MONOTONIC, &xx);
+#else
+ clock_gettime(CLOCK_MONOTONIC, &xx);
+#endif
+
+ return xx.tv_sec + xx.tv_nsec*1e-9;
+}
+#endif
+
+myWindow::myWindow() {
+// The top myWindow and its layout : horizontal:
+ // topWindow == this
+ topLayout = new QHBoxLayout;
+ setLayout(topLayout);
+
+ ctrlCol = new ctrl_column(this);
+ topLayout->addWidget(ctrlCol->group);
+
+ for (int ii = 0; ii < myWindow::numRsltPanels; ii++){
+ rsltPanel[ii] = new rslt_panel(this);
+ topLayout->addWidget(rsltPanel[ii]->group);
+ }
+
+// setting the indication to its current value won't
+// change the indicator, but will cause it to update
+// the associated plot
+ for (unsigned int ii = 0; ii < ndc8r.size(); ii++){
+ indicator* foo = ndc8r[ii];
+ foo->setIndication(0,0, foo->rpBox->value(), foo->ipBox->value());
+ }
+
+ frameTimer = new timer;
+}
+
+void myWindow::flush() {
+ if (!repaintFrame) return;
+ repaintFrame = 0;
+ for (int ii = 0; ii < numRsltPanels; ii++){
+ rsltPanel[ii]->plot->flush();
+ }
+}
+
+ctrl_column::ctrl_column(myWindow* topwin){
+ group = new QGroupBox;
+ group->setFlat(0);
+ layout = new QVBoxLayout;
+ group->setLayout(layout);
+
+ refOutGroup = new refOut_grouper(topwin);
+ layout->addWidget(refOutGroup->group);
+
+ layout->insertStretch(-1);
+
+ entrailsGroup = new entrails_grouper(topwin);
+ layout->addWidget(entrailsGroup->group);
+}
+
+
+// the refOut group and its layout: vertical:
+refOut_grouper::refOut_grouper(myWindow* _topwin)
+: topwin(_topwin),
+ VLabel(0)
+{
+// the freq box
+ group = new QGroupBox("Ref Out");
+ layout = new QVBoxLayout;
+ group->setLayout(layout);
+//group->setStyleSheet("background-color: lavenderblush;");
+//group->setStyleSheet("background-color: rgba(100, 0, 0, 150);");
+//group->setStyleSheet("background-color: lightcyan;");
+ group->setStyleSheet(".QGroupBox{background-color: #E8ffFF}");
+ freqLabel = new QLabel;
+ freqLabel->setText("Frequency");
+
+ freqBox = new QDoubleSpinBox;
+ freqBox->setRange(0.0, 10000.0);
+ freqBox->setSingleStep(1);
+ freqBox->setValue(440);
+ freqBox->setKeyboardTracking(0);
+ freqBox->setSuffix(" Hz");
+ connect(freqBox, SIGNAL(valueChanged(double)),
+ this, SLOT(freqChanged(double)));
+
+ actFreqLabel = new QLabel;
+ actFreqLabel->setText("Actual");
+
+ actFreqBox = new QDoubleSpinBox;
+ actFreqBox->setRange(0.0, 10000.0);
+ actFreqBox->setValue(440);
+ actFreqBox->setReadOnly(1);
+ actFreqBox->setButtonSymbols(QAbstractSpinBox::NoButtons);
+ actFreqBox->setSuffix(" Hz");
+
+ dBLabel = new QLabel;
+ dBLabel->setText("Amplitude");
+
+ dBBox = new QDoubleSpinBox;
+ dBBox->setRange(-1000.0, 1000.0);
+ dBBox->setDecimals(3);
+ dBBox->setSingleStep(1);
+//later: dBBox->setValue(refOutAmp_dB);
+ dBBox->setKeyboardTracking(0);
+ dBBox->setSuffix(" dBV");
+ connect(dBBox, SIGNAL(valueChanged(double)),
+ this, SLOT(ampChanged(double)));
+
+// express amplitude in V, not just in dB:
+//?? VLabel = new QLabel;
+//?? VLabel->setText("???");
+
+ VBox = new QDoubleSpinBox;
+ VBox->setRange(-1e300, +1e300);
+ VBox->setReadOnly(1);
+ VBox->setDecimals(6);
+ VBox->setButtonSymbols(QAbstractSpinBox::NoButtons);
+ VBox->setSuffix(" V");
+
+ dBBox->setValue(refOutAmp_dB); // will throw ampChanged signal
+
+ layout->addWidget(freqLabel STRETCH);
+ layout->addWidget(freqBox STRETCH);
+ layout->addWidget(actFreqLabel STRETCH);
+ layout->addWidget(actFreqBox STRETCH);
+ layout->addWidget(dBLabel STRETCH);
+ layout->addWidget(dBBox STRETCH);
+ if (VLabel) layout->addWidget(VLabel STRETCH);
+ layout->addWidget(VBox STRETCH);
+}
+
+// the entrails group and its layout: vertical:
+entrails_grouper::entrails_grouper(myWindow* _topwin)
+: topwin(_topwin),
+ tweakFreq(tweakSize),
+ tweakPhase(tweakSize),
+ tweakPtr(0),
+ howmany(0)
+{
+ group = new QGroupBox("Entrails");
+ layout = new QVBoxLayout;
+ group->setLayout(layout);
+ group->setStyleSheet("QGroupBox{background-color: #ffffe0}");
+
+ VoCalLabel = new QLabel;
+ VoCalLabel->setText("Vo Calibration");
+
+ VoCalBox = new QDoubleSpinBox;
+ VoCalBox->setRange(-1e300, +1e300);
+ VoCalBox->setSingleStep(1);
+ VoCalBox->setKeyboardTracking(0);
+ VoCalBox->setSuffix(" dBV FS");
+ connect(VoCalBox, SIGNAL(valueChanged(double)),
+ this, SLOT(VoChanged(double)));
+ VoCalBox->setValue(VoCal_dB);
+
+ ViCalLabel = new QLabel;
+ ViCalLabel->setText("Vi Calibration");
+
+ ViCalBox = new QDoubleSpinBox;
+ ViCalBox->setRange(-1e300, +1e300);
+ ViCalBox->setSingleStep(1);
+ ViCalBox->setKeyboardTracking(0);
+ ViCalBox->setSuffix(" dBV FS");
+ connect(ViCalBox, SIGNAL(valueChanged(double)),
+ this, SLOT(ViChanged(double)));
+ ViCalBox->setValue(ViCal_dB);
+
+ rateLabel = new QLabel;
+ rateLabel->setText("Krunch Rate");
+
+ rateBox = new QDoubleSpinBox;
+ rateBox->setRange(0.0, 1e300);
+ rateBox->setSingleStep(1);
+ rateBox->setValue(6.);
+ rateBox->setKeyboardTracking(0);
+ rateBox->setSuffix(" Hz");
+ connect(rateBox, SIGNAL(valueChanged(double)),
+ this, SLOT(rateChanged(double)));
+
+ actRateLabel = new QLabel;
+ actRateLabel->setText("Actual");
+
+ actRateBox = new QDoubleSpinBox;
+ actRateBox->setRange(0.0, 1e300);
+ actRateBox->setValue(6.);
+ actRateBox->setReadOnly(1);
+ actRateBox->setSuffix(" Hz");
+ actRateBox->setButtonSymbols(QAbstractSpinBox::NoButtons);
+
+ pcmLabel = new QLabel;
+ pcmLabel->setText("PCM Rate");
+
+ pcmBox = new QDoubleSpinBox;
+ pcmBox->setRange(-1e300, +1e300);
+ pcmBox->setValue(440);
+ pcmBox->setReadOnly(1);
+ pcmBox->setSuffix(" Hz");
+ pcmBox->setButtonSymbols(QAbstractSpinBox::NoButtons);
+
+ timeShiftLabel = new QLabel;
+ timeShiftLabel->setText("Time Shift");
+
+ timeShiftBox = new QDoubleSpinBox;
+ timeShiftBox->setRange(-1000.0, 1000.0);
+ timeShiftBox->setDecimals(6);
+ timeShiftBox->setSingleStep(1e-6);
+ timeShiftBox->setValue(timeShift);
+ timeShiftBox->setKeyboardTracking(0);
+ timeShiftBox->setSuffix(" s");
+ connect(timeShiftBox, SIGNAL(valueChanged(double)),
+ this, SLOT(timeShiftChanged(double)));
+
+ phaseShiftLabel = new QLabel;
+ phaseShiftLabel->setText("Phase Shift");
+
+ phaseShiftBox = new QDoubleSpinBox;
+ phaseShiftBox->setRange(-1e300, +1e300);
+ phaseShiftBox->setDecimals(2);
+ phaseShiftBox->setSingleStep(1);
+ phaseShiftBox->setValue(phaseShift);
+ phaseShiftBox->setKeyboardTracking(0);
+ phaseShiftBox->setSuffix(QString::fromUtf8(" °"));
+ connect(phaseShiftBox, SIGNAL(valueChanged(double)),
+ this, SLOT(phaseShiftChanged(double)));
+
+ tweakButton = new QPushButton("&Tweak");
+ connect(tweakButton, SIGNAL(clicked()),
+ this, SLOT(tweakButtonClicked()));
+
+ layout->addWidget(VoCalLabel STRETCH);
+ layout->addWidget(VoCalBox STRETCH);
+
+ layout->addWidget(ViCalLabel STRETCH);
+ layout->addWidget(ViCalBox STRETCH);
+
+ layout->addWidget(rateLabel STRETCH);
+ layout->addWidget(rateBox STRETCH);
+ layout->addWidget(actRateLabel STRETCH);
+ layout->addWidget(actRateBox STRETCH);
+ layout->addWidget(pcmLabel STRETCH);
+ layout->addWidget(pcmBox STRETCH);
+ layout->addWidget(timeShiftLabel STRETCH);
+ layout->addWidget(timeShiftBox STRETCH);
+ layout->addWidget(phaseShiftLabel STRETCH);
+ layout->addWidget(phaseShiftBox STRETCH);
+ layout->addWidget(tweakButton STRETCH);
+}
+
+rslt_panel::rslt_panel(myWindow* _topWin)
+: topWin(_topWin)
+{
+ group = new QGroupBox;
+ group->setFlat(0);
+ layout = new QHBoxLayout;
+ group->setLayout(layout);
+ plot = new myPlot("plotname");
+ block = new blockOfIndicators(topWin, plot);
+// layout->addWidget(plot, 0, Qt::AlignHCenter);
+ layout->addWidget(plot);
+ layout->addWidget(block->group);
+ layout->insertStretch(-1);
+}
+
+double quantize125(double arg){
+ if (arg < sqrt(2.)) return 1.;
+ if (arg < sqrt(10.)) return 2.;
+ if (arg < sqrt(50.)) return 5.;
+ return 10.;
+}
+
+double logstep(double arg){
+ double ctc = pow(10., floor(log10(arg)));
+ double mant = arg / ctc;
+ return quantize125(mant) * ctc;
+}
+
+scaleBoxer::scaleBoxer(indicator* _parent)
+: parent(_parent)
+{}
+
+void scaleBoxer::stepBy(int steps){
+ double newval(1);
+ if (steps == 1) newval = logstep(2.0 * logstep(value()));
+ if (steps == -1) newval = logstep(0.5 * logstep(value()));
+
+ if (steps == 10) newval = logstep(10.0 * value());
+ if (steps == -10) newval = logstep( 0.1 * value());
+ parent->setDecim(newval);
+ setValue(newval);
+}
+
+void indicator::setDecim(const double newval){
+ int decim = -int(floor(log10(newval)));
+ scaleBox->setDecimals(std::max(0, decim));
+ rpBox->setDecimals(std::max(0, 2+decim));
+ ipBox->setDecimals(std::max(0, 2+decim));
+ magBox->setDecimals(std::max(0, 2+decim));
+}
+
+
+// An indicator and its internal layout: grid:
+indicator::indicator(myPlot* _plot, int _locker)
+: plot(_plot),
+ plotcur(0),
+ locker(_locker)
+{
+ if (plot) {
+ plotcur = plot->assign_curve();
+ }
+
+ group = new QGroupBox;
+ group->setFlat(0);
+ QString style = ".QGroupBox{";
+ if (plot) {
+ style += "border-top: 4px solid ";
+ style += color_list[plotcur % color_list_size].name;
+ style += ";";
+ }
+ style += "background-color: #D0ffD0;";
+ style += "}";
+ group->setStyleSheet(style);
+
+ layout = new QGridLayout;
+ group->setLayout(layout);
+
+ scaleLabel = new QLabel;
+ scaleLabel->setText("Scale");
+
+ scaleBox = new scaleBoxer(this);
+ scaleBox->setKeyboardTracking(0);
+ scaleBox->setRange(-1e300, +1e300);
+ double sc(pow(10., ViCal_dB/20.));
+ sc /= 2.5; // half scale, where full scale is 5 divisions
+ sc = logstep(sc);
+ scaleBox->setDecimals(10);
+ scaleBox->setValue(sc);
+ scaleBox->setSuffix(" V/div");
+
+ rpLabel = new QLabel;
+ rpLabel->setText("Rp");
+
+ rpBox = new QDoubleSpinBox;
+ rpBox->setReadOnly(1);
+ rpBox->setButtonSymbols(QAbstractSpinBox::NoButtons);
+ rpBox->setRange(-1e300, +1e300);
+ rpBox->setValue(0);
+ rpBox->setSuffix(" V");
+
+ ipLabel = new QLabel;
+ ipLabel->setText("Ip");
+
+ ipBox = new QDoubleSpinBox;
+ ipBox->setReadOnly(1);
+ ipBox->setButtonSymbols(QAbstractSpinBox::NoButtons);
+ ipBox->setRange(-1e300, +1e300);
+ ipBox->setValue(0);
+ ipBox->setSuffix(" V");
+
+ magLabel = new QLabel;
+ magLabel->setText("Mag");
+
+ magBox = new QDoubleSpinBox;
+ magBox->setReadOnly(1);
+ magBox->setButtonSymbols(QAbstractSpinBox::NoButtons);
+ magBox->setRange(-1e300, +1e300);
+ magBox->setValue(0.1234);
+ magBox->setSuffix(" V");
+
+ phaseLabel = new QLabel;
+ phaseLabel->setText("Phase");
+
+ phaseBox = new QDoubleSpinBox;
+ phaseBox->setReadOnly(1);
+ phaseBox->setButtonSymbols(QAbstractSpinBox::NoButtons);
+ phaseBox->setRange(-1e300, +1e300);
+ phaseBox->setSuffix(QString::fromUtf8(" °"));
+
+ setDecim(sc);
+
+ int row(0);
+ layout->addWidget(scaleLabel, row, 0);
+ row++;
+ layout->addWidget(scaleBox, row, 0);
+ row++;
+ layout->addWidget(rpLabel, row, 0);
+ layout->addWidget(ipLabel, row, 1);
+ row++;
+ layout->addWidget(rpLabel, row, 0);
+ layout->addWidget(ipLabel, row, 1);
+ row++;
+ layout->addWidget(rpBox, row, 0);
+ layout->addWidget(ipBox, row, 1);
+ row++;
+ layout->addWidget(magLabel, row, 0);
+ layout->addWidget(phaseLabel, row, 1);
+ row++;
+ layout->addWidget(magBox, row, 0);
+ layout->addWidget(phaseBox, row, 1);
+}
+
+blockOfIndicators::blockOfIndicators(myWindow* topwin, myPlot* plot)
+{
+ group = new QGroupBox;
+ group->setFlat(0);
+ group->setStyleSheet("border:0;");
+ layout = new QVBoxLayout;
+ indicator* temp;
+ group->setLayout(layout);
+
+ temp = new indicator(plot, 1/* phaselock */);
+ topwin->ndc8r.push_back(temp);
+ layout->addWidget(temp->group);
+
+ temp = new indicator(plot);
+ topwin->ndc8r.push_back(temp);
+ layout->addWidget(temp->group);
+
+ temp = new indicator(plot);
+ topwin->ndc8r.push_back(temp);
+ layout->addWidget(temp->group);
+
+ layout->insertStretch(-1);
+}
+
+void indicator::setIndication(const double rp0, const double ip0,
+ const double rp1, const double ip1){
+ double rp(rp1-rp0);
+ double ip(ip1-ip0);
+ double mag(sqrt(rp*rp + ip*ip));
+ double phase(0);
+ if (mag) phase = atan2(ip, rp);
+ rpBox->setValue(rp);
+ ipBox->setValue(ip);
+ magBox->setValue(mag);
+ double phDeg = phase * 180 / M_PI;
+ phaseBox->setValue(phDeg);
+ if (locker) lockerPhase = phDeg;
+ if (plot) {
+ double denom = scaleBox->value(); // volts per division
+ denom *= plot->divsPerUnit; // volts full scale
+ plot->setReading(plotcur, rp0/denom, ip0/denom, rp1/denom, ip1/denom);
+ }
+}
+
+void myPlot::flush() {
+ if (need_replot) replot();
+}
+
+void myPlot::setReading(const int ndx,
+ const double rp0, const double ip0,
+ const double rp1, const double ip1){
+ int npts(2);
+ double xxx[npts];
+ double yyy[npts];
+ xxx[0] = rp0;
+ yyy[0] = ip0;
+ xxx[1] = rp1;
+ yyy[1] = ip1;
+////////////////// curve[ndx]->setData(xxx, yyy, npts);
+ curve[ndx]->setSamples(xxx, yyy, npts);
+ need_replot = 1;
+}
+
+// There is no such thing as a scale change on the plot.
+// Rescale the data instead.
+#ifdef OLD_SCALE_IDEA
+void myPlot::scaleChange(double newScale){
+ setAxisScale(QwtPlot::xBottom, -newScale, newScale);
+ setAxisScale(QwtPlot::yLeft, -newScale, newScale);
+ replot();
+}
+#endif
+
+// Unless you set keyboardTracking to false,
+// this is useless when typing in digits :
+// signals too early and too often.
+void refOut_grouper::freqChanged(double /* newfreq not used */)
+{
+ topwin->actualFreqs();
+}
+
+void entrails_grouper::VoChanged(double newVo)
+{
+ VoCal_dB = newVo;
+//wrong topwin->ctrlCol->refOutGroup->VBox->
+//wrong setValue(pow(10., (refOutAmp_dB + VoCal_dB)/20.));
+}
+
+void entrails_grouper::ViChanged(double newVi)
+{
+ ViCal_dB = newVi;
+}
+
+void refOut_grouper::ampChanged(double newAmp)
+{
+ refOutAmp_dB = newAmp;
+//wrong: VBox->setValue(pow(10., (refOutAmp_dB + VoCal_dB)/20.));
+ VBox->setValue(pow(10., (refOutAmp_dB)/20.));
+}
+
+void entrails_grouper::rateChanged(double /* newRate not used */)
+{
+ topwin->actualFreqs();
+}
+
+// beware the fmod of a negative number is negative
+double pval(const double angle){
+ double rslt = fmod(angle, 360.);
+ if (rslt > 180.) rslt -= 360.;
+ if (rslt < -180.) rslt += 360.;
+ return rslt;
+}
+
+void entrails_grouper::timeShiftChanged(double newTimeShift)
+{
+//-- std::cout << "timeShiftChange: " << newTimeShift << std::endl;
+
+// Calculate new phase such that changing the timeShift
+// doesn't change the phase; it is only supposed to
+// change d(phase)/d(frequency).
+ double newPhase = phaseShift - (newTimeShift - timeShift) * actOutFreq * 360.;
+ newPhase = pval(newPhase);
+ timeShift = newTimeShift;
+ phaseShiftBox->setValue(newPhase);
+}
+
+void entrails_grouper::phaseShiftChanged(double newPhaseShift)
+{
+//-- std::cout << "phaseShiftChange: " << newPhaseShift << std::endl;
+
+ while (newPhaseShift > 360.) newPhaseShift -= 360.;
+ while (newPhaseShift < -360.) newPhaseShift += 360.;
+
+ phaseShift = newPhaseShift;
+}
+
+class bad_thing: public std::exception{
+ const char* msg;
+ virtual const char* what() const throw() {
+ return msg;
+ }
+public:
+ bad_thing(const char* _msg)
+ : msg(_msg) {}
+};
+
+double max(const std::valarray<double>& foo){
+ unsigned int howmany(foo.size());
+ if (howmany == 0) throw bad_thing("max of empty list");
+ double rslt = foo[0];
+ for (unsigned int ii = 1; ii < howmany; ii++) {
+ rslt = std::max(rslt, foo[ii]);
+ }
+ return rslt;
+}
+
+double min(const std::valarray<double>& foo){
+ unsigned int howmany(foo.size());
+ if (howmany == 0) throw bad_thing("min of empty list");
+ double rslt = foo[0];
+ for (unsigned int ii = 1; ii < howmany; ii++) {
+ rslt = std::min(rslt, foo[ii]);
+ }
+ return rslt;
+}
+
+void entrails_grouper::tweakButtonClicked(){
+ using namespace std;
+ double rawPhase = (lockerPhase + phaseShift)
+ + actOutFreq*timeShift * 360.;
+
+ if (tweakPtr >= 10) tweakPtr = 0;
+ tweakFreq[tweakPtr] = actOutFreq;
+ tweakPhase[tweakPtr] = rawPhase;
+ tweakPtr++;
+ if (howmany < tweakPtr) howmany = tweakPtr;
+ // otherwise howmany stays at its maximum, i.e. tweakSize.
+
+ valarray<double> myFreq(&tweakFreq[0], howmany);
+ valarray<double> myPhase(&tweakPhase[0], howmany);
+
+ double big = max(myFreq);
+ double little = min(myFreq);
+ if (big == little) {
+ double newPhase = myPhase.sum() / howmany - timeShift*myFreq[0]*360.;
+// This will change the value in the box, and raise
+// the valueChanged signal:
+ phaseShiftBox->setValue(pval(newPhase));
+ } else do {
+ valarray<double> cookedPhase(myPhase);
+ valarray<double> cookedFreq(myFreq);
+ for (unsigned int ii = 0; ii < howmany; ii++) {
+ cookedFreq[ii] -= actOutFreq;
+ cookedPhase[ii] -= phaseShift + timeShift*myFreq[ii]*360.;
+ cookedPhase[ii] = pval(cookedPhase[ii]);
+ }
+ if (max(cookedPhase) > 90. || min(cookedPhase) < -90.) {
+ cout << "Phase range too big; can't tweak." << endl;
+ break;
+ }
+ double intercept, slope;
+ double cv00, cv01, cv11;
+ double sumsq;
+ gsl_fit_linear(&cookedFreq[0], 1,
+ &cookedPhase[0], 1, howmany,
+ &intercept, &slope,
+ &cv00, &cv01, &cv11,
+ &sumsq);
+
+ double newTime = timeShift + slope / 360.;
+ double newPhase = phaseShift + intercept
+ - (newTime - timeShift) * actOutFreq * 360.;
+// must do time shift first:
+ timeShiftBox->setValue(newTime);
+ phaseShiftBox->setValue(pval(newPhase));
+
+ } while (0);
+}
+
+void myWindow::actualFreqs() {
+ using namespace std;
+ double d_out_freq = ctrlCol->refOutGroup->freqBox->value();
+ { // calculatte cycles per krunch period
+ double d_krunch_rate = ctrlCol->entrailsGroup->rateBox->value();
+ if (d_krunch_rate == 0.) cpkp = 1;
+ else cpkp = round(d_out_freq/d_krunch_rate);
+ if (cpkp < 1.) cpkp = 1.;
+ }
+// frames per krunch period:
+ fpkp = round(cpkp * pcmRate / d_out_freq);
+ double actUp = pcmRate / fpkp;
+ ctrlCol->entrailsGroup->actRateBox->setValue(actUp);
+ ctrlCol->entrailsGroup->pcmBox->setValue(pcmRate);
+
+ actOutFreq = cpkp * actUp;
+ ctrlCol->refOutGroup->actFreqBox->setValue(actOutFreq);
+ if (verbosity > 0) {
+ cout.precision(4);
+ cout << fixed;
+ cout << " d_out_freq: " << d_out_freq
+ << " cpkp: " << cpkp
+ << " fpkp: " << fpkp
+ << " actUp: " << setw(10) << actUp
+ << " actOutFreq: " << actOutFreq
+ << endl;
+ }
+}
+
+myPlot::myPlot(const QString name) : QwtPlot(QwtText(name)),
+ grid(), divsPerUnit(5), need_replot(0)
+ {
+// FIXME : should calculate these sizes:
+ setMinimumWidth(500); setMaximumWidth(500);
+ setMinimumHeight(500); setMaximumHeight(500);
+
+// Set up axis, permanently -1 to 1 in both directions.
+// We are ASSUMING the library function will give us
+// five minor divisions per unit (ten total).
+ setAxisScale(QwtPlot::xBottom,-1.0, 1.0, 1.);
+ setAxisScale(QwtPlot::yLeft, -1.0, 1.0, 1.);
+ enableAxis(QwtPlot::xBottom, 0);
+ enableAxis(QwtPlot::yLeft, 0);
+ setStyleSheet("border:0;");
+
+ grid.enableXMin(true);
+ grid.enableYMin(true);
+ grid.setMajorPen(QPen(Qt::white, 2));
+ grid.setMinorPen(QPen(Qt::white, 1));
+// was: (QPen(Qt::white, 1, Qt::DotLine));
+ grid.attach(this);
+}
+
+int myPlot::assign_curve(){
+ int ndx = curve.size();
+
+ curve.push_back(new QwtPlotCurve);
+
+ int npts(2);
+ double xxx[npts];
+ double yyy[npts];
+ xxx[0] = 0;
+ yyy[0] = 0;
+ xxx[1] = sin(.1 * ndx);
+ yyy[1] = cos(.2 * ndx);
+
+///////////// curve[ndx]->setData(xxx, yyy, npts);
+ curve[ndx]->setSamples(xxx, yyy, npts);
+ curve[ndx]->setPen(QPen(color_list[ndx % color_list_size].code, 4));
+ curve[ndx]->attach(this);
+
+ return ndx;
+}
+
+////////////////////////////////////
+
+timer::timer(QWidget *parent)
+: QWidget(parent)
+{
+ startTimer(33); // 30.303 repaints per second
+}
+
+void timer::timerEvent(QTimerEvent* /* event not used */){
+ using namespace std;
+ repaintFrame = 1;
+}
diff --git a/src/gui.h b/src/gui.h
new file mode 100644
index 0000000..92d0bdc
--- /dev/null
+++ b/src/gui.h
@@ -0,0 +1,13 @@
+#ifndef GUI__H
+#define GUI__H
+extern double actOutFreq;
+extern double refOutAmp_dB;
+extern double VoCal_dB;
+extern double ViCal_dB;
+extern double cpkp;
+extern double fpkp;
+extern int pcmRate;
+extern double timeShift;
+extern double phaseShift;
+extern int repaintFrame;
+#endif /* GUI__H */
diff --git a/src/gui_class.cxx b/src/gui_class.cxx
new file mode 100644
index 0000000..ea69d35
--- /dev/null
+++ b/src/gui_class.cxx
@@ -0,0 +1,5 @@
+#include "gui_class.h"
+
+void foobar() {
+ ctrl_column foobar(0);
+}
diff --git a/src/gui_class.h b/src/gui_class.h
new file mode 100644
index 0000000..2b17ff3
--- /dev/null
+++ b/src/gui_class.h
@@ -0,0 +1,217 @@
+#ifndef GUI_CLASS__H
+#define GUI_CLASS__H
+
+#include <cmath>
+#include <vector>
+#include <valarray>
+#include <QApplication>
+#include <QTextEdit>
+#include <QtGui>
+#include <QLabel>
+
+#include <qwt_plot.h>
+#include <qwt_plot_curve.h>
+#include <qwt_plot_grid.h>
+
+#include <QDoubleSpinBox>
+#include <QGroupBox>
+#include <QGridLayout>
+#include <QPushButton>
+
+class myWindow; // forward reference
+class indicator;
+
+////////////////////////////////////////
+class myPlot : public QwtPlot {
+public:
+ std::vector<QwtPlotCurve*> curve;
+ QwtPlotGrid grid;
+ int divsPerUnit;
+ int need_replot;
+
+ myPlot(const QString);
+// the following could be a slot, but doesn't need to be
+ void setReading(const int, const double, const double,
+ const double, const double);
+ int assign_curve();
+ void flush();
+};
+
+////////////////////////////////////////
+class scaleBoxer : public QDoubleSpinBox {
+public:
+ indicator* parent;
+ virtual void stepBy(int);
+
+ scaleBoxer(indicator* parent);
+};
+
+////////////////////////////////////////
+class indicator : public QWidget {
+
+ Q_OBJECT;
+
+// Note: Q_SLOTS is #defined to be nothing.
+// Slots are handlers for inbound signals.
+public Q_SLOTS:
+ void setIndication(const double, const double, const double, const double);
+
+public:
+ QGroupBox* group;
+ QGridLayout* layout;
+ QLabel* rpLabel;
+ QDoubleSpinBox* rpBox;
+ QLabel* ipLabel;
+ QDoubleSpinBox* ipBox;
+ QLabel* magLabel;
+ QDoubleSpinBox* magBox;
+ QLabel* phaseLabel;
+ QDoubleSpinBox* phaseBox;
+ QLabel* scaleLabel;
+ scaleBoxer* scaleBox;
+ myPlot* plot;
+ int plotcur; // which "curve" to use within the plot
+ int locker;
+
+ indicator(myPlot* plot, int locker = 0);
+ void setDecim(const double newval);
+};
+
+////////////////////////////////////////
+class blockOfIndicators : public QWidget {
+public:
+ QGroupBox* group;
+ QVBoxLayout* layout;
+
+ blockOfIndicators(myWindow*, myPlot*);
+};
+
+////////////////////////////////////////
+class rslt_panel : public QWidget {
+public:
+ myWindow* topWin;
+ QGroupBox* group;
+ QHBoxLayout* layout;
+ blockOfIndicators* block;
+ myPlot* plot;
+
+ rslt_panel(myWindow*);
+};
+
+////////////////////////////////////////
+class entrails_grouper : public QWidget {
+
+ Q_OBJECT
+
+public Q_SLOTS:
+ void VoChanged(double);
+ void ViChanged(double);
+ void rateChanged(double);
+ void timeShiftChanged(double);
+ void phaseShiftChanged(double);
+ void tweakButtonClicked();
+
+public:
+ myWindow* topwin;
+ QGroupBox* group;
+ QVBoxLayout* layout;
+
+ QLabel* VoCalLabel;
+ QDoubleSpinBox* VoCalBox;
+ QLabel* ViCalLabel;
+ QDoubleSpinBox* ViCalBox;
+
+ QLabel* rateLabel;
+ QDoubleSpinBox* rateBox; // goal rate
+
+ QLabel* actRateLabel;
+ QDoubleSpinBox* actRateBox; // actual rate
+ QLabel* pcmLabel;
+ QDoubleSpinBox* pcmBox;
+ QLabel* timeShiftLabel;
+ QDoubleSpinBox* timeShiftBox; // loopback time delay
+ QLabel* phaseShiftLabel;
+ QDoubleSpinBox* phaseShiftBox; // loopback phase lag
+ QPushButton* tweakButton;
+
+ static const int tweakSize = 10;
+ std::valarray<double> tweakFreq;
+ std::valarray<double> tweakPhase;
+ unsigned int tweakPtr;
+ unsigned int howmany;
+
+ entrails_grouper(myWindow*);
+};
+
+////////////////////////////////////////
+class refOut_grouper : public QWidget {
+ Q_OBJECT
+
+public Q_SLOTS:
+ void freqChanged(double);
+ void ampChanged(double);
+
+public:
+ myWindow* topwin;
+ QGroupBox* group;
+ QVBoxLayout* layout;
+ QLabel* freqLabel;
+ QDoubleSpinBox* freqBox; // goal frequency
+ QLabel* actFreqLabel;
+ QDoubleSpinBox* actFreqBox; // actual frequency
+ QLabel* dBLabel;
+ QDoubleSpinBox* dBBox; // amplitude in dB
+ QLabel* VLabel;
+ QDoubleSpinBox* VBox; // amplitude in V
+
+ refOut_grouper(myWindow*);
+};
+
+////////////////////////////////////////
+class ctrl_column : public QWidget {
+ Q_OBJECT;
+public:
+ QGroupBox* group;
+ QVBoxLayout* layout;
+ refOut_grouper* refOutGroup;
+ entrails_grouper* entrailsGroup;
+
+ ctrl_column(myWindow* _parent);
+// hack
+// something trivial we can instantiate, to persuade the compiler to
+// instantiate (emit) "vtables" for everything in this file:
+ ctrl_column(int){};
+};
+
+//////////////////////////////////////
+
+class timer : public QWidget{
+public:
+ timer(QWidget *parent = 0);
+ void timerEvent(QTimerEvent *event);
+};
+
+////////////////////////////////////////
+class myWindow : public QWidget {
+ Q_OBJECT
+
+public:
+ myWindow();
+ QHBoxLayout* topLayout;
+
+ QDoubleSpinBox* updRateBox;
+ QLabel* updRateLabel;
+
+ static const int numRsltPanels = 1;
+ ctrl_column* ctrlCol;
+ rslt_panel* rsltPanel[numRsltPanels];
+ std::vector<indicator*> ndc8r;
+ timer* frameTimer;
+
+ void actualFreqs();
+
+public slots:
+ void flush();
+};
+
+#endif /* GUI_CLASS__H */
diff --git a/src/iir_bp.cxx b/src/iir_bp.cxx
new file mode 100644
index 0000000..b742dd1
--- /dev/null
+++ b/src/iir_bp.cxx
@@ -0,0 +1,133 @@
+#include "iir_bp.h"
+
+////////////////////////////////////////
+// negative of a vector, component-by-component:
+//
+std::vector<C> neg(const std::vector<C>& foo) {
+ using namespace std;
+ int siz = foo.size();
+ vector<C> rslt(siz);
+ for (int ii = 0; ii < siz; ii++) {
+ rslt[ii] = -foo[ii];
+ }
+ return rslt;
+}
+
+////////////////////////////////////////
+// expand_poly - multiplies a set of binomials together and returns
+// the coefficients of the resulting polynomial.
+//
+// All polynomials (input and output) are represented using the
+// /reduced/ representation, meaning the coefficient of the highest
+// power of x is assumed to be 1, and is not included in the
+// representation.
+//
+// The multiplication has the following form:
+//
+// (x+c[0]) * (x+c[1]) *...* (x+c[n-1])
+//
+// On input, the c[i] is the coefficient of x^0 in the ith binomial.
+// Each c[i] is a complex number.
+//
+// The resulting polynomial has the following form:
+//
+// x^n + a[0]*x^n-1 + a[1]*x^n-2 +...+ a[n-2]*x + a[n-1]
+//
+// The a[i] coefficients can in general be complex but in typical
+// digital-filter applications should turn out to be real.
+//
+std::vector<C> expand_poly(const std::vector<C>& cvec) {
+ int n = cvec.size();
+
+ std::vector<C> a(n);
+ for (int ii = 0; ii < n; ii++) a[ii] = 0.;
+
+ for (int i = 0; i < n; ++i) {
+ for (int j = i; j > 0; --j) a[j] += cvec[i] * a[j-1];
+ a[0] += cvec[i];
+ }
+ return a;
+}
+
+////////////////////////////////////////
+// Calculate poles for Butterworth low pass filter.
+//
+std::vector<C> dpole_bwlp (int n, double fcf, double extraPole)
+{
+ using namespace std;
+
+ int needed(n);
+ if (extraPole) needed += 2;
+ vector<C> dpole(needed);
+
+ double theta = 2. * M_PI * fcf;
+ double st = sin(theta);
+ double ct = cos(theta);
+
+ for (int k = 0; k < n; ++k) {
+ // angle, as seen from (1,0)
+ // as we go around the small circlet of poles:
+ double polang = M_PI * (double)(2*k+1)/(double)(2*n);
+ double denom = 1.0 + st*sin(polang);
+ dpole[k] = C(ct/denom, st*cos(polang)/denom);
+ }
+ if (extraPole) {
+ double thetaZero = 2. * M_PI * extraPole;
+ dpole[n] = C(cos(thetaZero), sin(thetaZero));
+ dpole[n+1] = C(cos(thetaZero), -sin(thetaZero));
+ }
+ return dpole;
+}
+
+////////////////////////////////////////
+// return the real part of a vector of complex numbers
+// also convert from reduced to non-reduced representation,
+// by inserting rslt[0] = 1 and shifting everything one place
+std::vector<double> rxpoly(const std::vector<C>& foo){
+ std::vector<double> rslt(1+foo.size());
+
+ rslt[0] = 1.;
+ for (unsigned int k = 0; k < foo.size(); ++k)
+ rslt[1+k] = foo[k].real();
+ return (rslt);
+}
+
+
+////////////////////////////////////////
+// calculate the d coefficients for a butterworth lowpass filter.
+//
+// Returns result in the non-reduced representation,
+// i.e. a vector of n+1 doubles,
+// where dcof[0] is the coefficient in front of x^n
+// However, dcof[0] will always be 1.0.
+//
+std::vector<double> dcof_bwlp (int n, double fcf, double extraPole) {
+ return rxpoly(expand_poly(neg(dpole_bwlp(n, fcf, extraPole))));
+}
+
+////////////////////////////////////////
+// calculate the c coefficients for a butterworth lowpass filter.
+// details same as above.
+//
+std::vector<double> ccof_bwlp(const int n, const double extraZero) {
+ return rxpoly(expand_poly(neg(croot_bwlp(n, extraZero))));
+}
+
+////////////////////////////////////////
+std::vector<C> croot_bwlp (int n, double extraZero) {
+ using namespace std;
+
+ int needed(n);
+ if (extraZero) needed += 2;
+ vector<C> dpole(needed);
+
+ for (int k = 0; k < n; ++k) {
+ dpole[k] = -1;
+ }
+ if (extraZero) {
+ double thetaZero = 2. * M_PI * extraZero;
+ dpole[n] = C(cos(thetaZero), sin(thetaZero));
+ dpole[n+1] = C(cos(thetaZero), -sin(thetaZero));
+ }
+ return dpole;
+}
diff --git a/src/iir_bp.h b/src/iir_bp.h
new file mode 100644
index 0000000..762c25c
--- /dev/null
+++ b/src/iir_bp.h
@@ -0,0 +1,17 @@
+#ifndef IIR_BP_H
+#define IIR_BP_H
+
+#include <vector>
+#include <complex>
+
+typedef std::complex<double> C;
+
+std::vector<C> neg(const std::vector<C>& foo);
+std::vector<C> expand_poly(const std::vector<C>& cvec);
+std::vector<double> rxpoly(const std::vector<C>& foo);
+std::vector<C> dpole_bwlp( int n, double fcf, double extraPole=0.);
+std::vector<double> dcof_bwlp( int n, double fcf, double extraPole=0.);
+std::vector<double> ccof_bwlp(const int n, const double extraZero=0.);
+std::vector<C> croot_bwlp (int n, double extraZero=0.);
+
+#endif
diff --git a/src/krunch.cxx b/src/krunch.cxx
new file mode 100644
index 0000000..a7adf8f
--- /dev/null
+++ b/src/krunch.cxx
@@ -0,0 +1,308 @@
+#include "krunch.h"
+#include "thrower.h"
+#include <valarray>
+#include <iostream>
+#include <iomanip>
+#include "refout.h" /* for class ref_to_krunch */
+#include "gui.h"
+//??? #include "iir_bp.h"
+#include "bad_thing.h"
+//?? #include "biquad.h"
+
+// forward reference:
+void dumpit_cout(const double rp, const double ip);
+
+// typical usage:
+//?? biquad butterworth({1., 2., 1.},
+//?? {1., -1.99911142347079540116, 0.99911181807963833634});
+
+//////////////////////////////////////////////////////////////////////
+// main entry point for krunch job
+//
+void* krunch(void* _arg){
+ using namespace std;
+ krunch_arg* arg((krunch_arg*) _arg);
+
+ int num_indic = arg->topwin->ndc8r.size();
+
+ thrower* toss = new thrower[num_indic];
+
+ thrower* flushy = new thrower;
+
+ for (int ii = 0; ii < num_indic; ii++) {
+ QMetaObject::Connection rslt = QObject::connect(
+ &toss[ii], SIGNAL(_newReading(double,double, double,double)),
+ arg->topwin->ndc8r[ii],
+ SLOT(setIndication(double,double, double,double)));
+
+ if (!rslt) {
+ cout << "Krunch: Failed to connect, column " << ii
+ << " rslt: " << rslt << endl;
+ exit(3);
+ }
+ }
+
+ {
+ QMetaObject::Connection rslt = QObject::connect( flushy, SIGNAL(_flush()),
+ arg->topwin, SLOT(flush()) );
+
+ if (!rslt) {
+ cout << "Krunch: Failed to connect flush: "
+ << rslt << endl;
+ exit(3);
+ }
+ }
+
+ alsa_pcm* pcm = arg->pcm;
+ int nframe = arg->setup->nframe;
+
+ int left_idx = 0;
+ int right_idx = 1;
+
+/// Kludge test: left_idx = 10; right_idx = 11;
+// Be careful to index outside array boundaries:
+ int topchan = pcm->nchan - 1;
+ if (left_idx > topchan) left_idx = topchan;
+ if (right_idx > topchan) right_idx = topchan;
+
+#if 0
+ if (snd_pcm_state(arg->otherpcm->phndl) != SND_PCM_STATE_RUNNING) {
+ cout << "Krunch waits: "
+ << arg->otherpcm->alsa_state_name()
+ << endl;
+ }
+#endif
+ while (snd_pcm_state(arg->otherpcm->phndl) != SND_PCM_STATE_RUNNING) {
+ usleep(1000);
+ }
+
+ double left_real(0);
+ double left_ineg(0); // the *negative* of the imaginary part
+ double right_real(0);
+ double right_ineg(0); // the *negative* of the imaginary part
+
+//#define PHASE_CHANGE
+#ifdef PHASE_CHANGE
+ double old_phase(0);
+#endif
+ double old_cpkp(0), old_fpkp(0);
+ double theta(0);
+ double dtheta(0);
+ int kperno(0);
+
+//////////////////
+// main krunch loop
+//
+// this is a SINGLE loop over TWO variables;
+// the cap_buffer is filled in units of nframe
+// and emptied (processed) in units of fpkp.
+// Parallel code appears in refout.cxx
+ int cap_end(nframe * pcm->nchan);
+ valarray<datum> cap_buffer(cap_end);
+ int cap_idx(cap_end);
+ int ref_end(123456);
+ int ref_idx(ref_end);
+ double decalage(0);
+
+ for (;;) { // loop over all samples
+
+// loop control for capture (read) process:
+ if (cap_idx >= nframe) {
+ int didread = arg->setup->read_stuff(pcm, &cap_buffer[0]);
+ if (didread != nframe) {
+ fprintf(stderr, "Krunch: ignoring peculiar buffer size %d not %d\n",
+ didread, nframe);
+ continue;
+ }
+ cap_idx = 0;
+ }
+
+// Loop control for reference process.
+// Note that frequency and phase-offset only changes at
+// the boundary between krunch periods.
+ if (ref_idx == ref_end) {
+ if (kperno == 0) {
+ decalage = arg->otherpcm->ss_time() - pcm->ss_time();
+ }
+
+#if 0
+ cout << "declalage: " << decalage
+ << " samples: " << decalage * pcm->rate
+ << " skipme: " << skipme
+ << endl;
+#endif
+ ref_to_krunch kper;
+ int didread = read(arg->pipefd, &kper, sizeof(kper));
+ if (didread != sizeof(kper)) {
+ fprintf(stderr, "Krunch: could not read from pipe: ");
+ perror(0);
+ exeunt(1);
+ }
+ if (kperno != kper.period) {
+ fprintf(stderr, "Krunch: phase error: expecting %d got %d\n",
+ kperno, kper.period);
+ exeunt(1);
+ }
+ double freq = double(pcm->rate) * double(kper.cpkp)
+ / double(kper.fpkp);
+// the phase offset:
+ theta = (decalage + timeShift) * freq * 2. * M_PI;
+ theta += phaseShift * M_PI / 180.;
+#ifdef PHASE_CHANGE
+ if (theta != old_phase){
+ cout << "Krunch: new phase: " << theta
+ << " decalage: " << decalage
+ << " timeShift: " << timeShift
+ << " phaseShift: " << timeShift
+ << " freq: " << freq
+ << endl;
+ old_phase = theta;
+ }
+#endif
+ left_real = left_ineg = 0.;
+ right_real = right_ineg = 0.;
+ ref_idx = 0;
+ ref_end = kper.fpkp;
+ dtheta = 2. * M_PI * kper.cpkp / kper.fpkp;
+ // cpkp == cycles per krunch period
+ // fpkp == frames per krunch period
+ if (old_cpkp != kper.cpkp || old_fpkp != kper.fpkp){
+#if 0
+ cout.precision(6);
+ cout << fixed;
+ cout << "Krunch switching to: " << freq
+ << setprecision(1)
+ << " cpkp: " << kper.cpkp
+ << " fpkp: " << kper.fpkp
+ << " dtheta: " << setprecision(20) << dtheta
+ << endl;
+#endif
+ old_cpkp = kper.cpkp;
+ old_fpkp = kper.fpkp;
+ }
+ kperno++;
+ }
+ double left = cap_buffer[cap_idx * pcm->nchan + left_idx ];
+ double right = cap_buffer[cap_idx * pcm->nchan + right_idx];
+
+ left_real += left * cos(theta);
+ left_ineg += left * sin(theta);
+
+ right_real += right * cos(theta);
+ right_ineg += right * sin(theta);
+
+ theta += dtheta;
+ cap_idx++;
+ ref_idx++;
+
+// Do a little post-processing.
+// This does not replace or even affect the
+// loop-control above.
+ if (ref_idx >= ref_end) {
+
+// put in the minus sign here, to convert ineg to the
+// actual imaginary part with the proper sign.
+ do {
+ double unit = pow(10., ViCal_dB / 20.);
+ unit /= full_swing;
+// account for the number of points of points in this krunch period:
+ unit /= double(ref_end);
+// account for the fact that integral(sin^2) is only 0.5, not 1:
+ unit *= 2.;
+ if (num_indic <= 0) break;
+ toss[0].newReading(0,0, left_real * unit, -left_ineg * unit);
+ if (num_indic <= 1) break;
+ toss[1].newReading(0,0, right_real * unit, -right_ineg * unit);
+ if (num_indic <= 2) break;
+ toss[2].newReading(right_real * unit, -right_ineg * unit,
+ left_real * unit, -left_ineg * unit);
+ } while (0);
+ flushy->flush();
+ }
+ }
+ // should never reach here
+ return 0;
+}
+
+////////////////////
+void dumpit_cout(const double rp, const double ip){
+ using namespace std;
+ cout.precision(4);
+ int wid(12);
+ double mag(sqrt(rp*rp + ip*ip));
+ double phase(0);
+ if (mag) phase = atan2(ip, rp);
+ cout
+ << " : " << fixed << setw(wid) << rp
+ << " " << fixed << setw(wid) << ip
+ << " " << fixed << setw(wid) << mag
+ << " " << fixed << setw(wid) << phase * 180 / M_PI
+ << endl;
+}
+
+// Just like snd_pcm_readi(),
+// except the buff is always of the type of datum (int32_t),
+// no matter what type the raw hardware uses.
+// Return value:
+// nonnegative: # of frames read
+// negative: error code
+int readi(const alsa_pcm* pcm, datum* buff, const int nframe){
+ using namespace std;
+ if (pcm->format == SND_PCM_FORMAT_S32_LE) {
+ return snd_pcm_readi(pcm->phndl, buff, nframe);
+ } else if (pcm->format == SND_PCM_FORMAT_S16_LE) {
+ valarray<int16_t> tmp(nframe * pcm->nchan);
+ int rslt = snd_pcm_readi(pcm->phndl, &tmp[0], nframe);
+ if (rslt <= 0) return rslt;
+ int16_t* from (&tmp[0]);
+ datum* to (buff);
+ int fudge(1<<16);
+ for (unsigned int ii = 0; ii < rslt * pcm->nchan; ii++){
+ *to++ = *from++ * fudge;
+ }
+ return rslt;
+ } else {
+ cerr << "Don't know how to convert pcm data from format "
+ << snd_pcm_format_name(pcm->format) << endl;
+ exeunt(1);
+ }
+ return 0; // defeat stupid compiler warning
+}
+
+////////////////////////////////////////
+// Read some data and maybe write checkfile
+int snark::read_stuff(alsa_pcm* pcm, datum* cap_buffer){
+
+ int sts;
+ int frames_read;
+
+ for (;;){ // keep trying to read
+ frames_read = readi(pcm, &cap_buffer[0], nframe);
+ if (frames_read == nframe) break; // good!
+ if (frames_read > 0 ||
+ frames_read == -EPIPE) { // recover from overruns
+ if (xrun_verbosity) fprintf(stderr,
+ "Read_stuff overrun: requested %i got %i: %s" NL,
+ nframe, frames_read, snd_strerror(frames_read));
+ snd_pcm_prepare(pcm->phndl);
+ } else { // unrecoverable error
+ fprintf(stderr, "Read_stuff requested %i got %i: %s" NL,
+ nframe, frames_read, snd_strerror(frames_read));
+ exeunt(1);
+ }
+ }
+
+ int gotbytes = snd_pcm_frames_to_bytes(pcm->phndl, frames_read);
+
+ if (wcheck_fd >= 0) {
+ sts = write(wcheck_fd, &cap_buffer[0], gotbytes);
+ if (sts != gotbytes) {
+ fprintf(stderr, "Write error on raw output file (%i): %m" NL, wcheck_fd);
+ exeunt(1);
+ }
+ close(wcheck_fd);
+ wcheck_fd = -1;
+ }
+
+ return frames_read;
+}
diff --git a/src/krunch.h b/src/krunch.h
new file mode 100644
index 0000000..f24413b
--- /dev/null
+++ b/src/krunch.h
@@ -0,0 +1,24 @@
+#ifndef KRUNCH__H
+#define KRUNCH__H
+
+#include "gui_class.h"
+#include "lockin.h"
+
+void* krunch(void* _arg); // thread entry point
+
+class krunch_arg {
+public:
+ myWindow* topwin;
+ alsa_pcm* pcm;
+ alsa_pcm* otherpcm;
+ snark* setup;
+ int pipefd;
+ krunch_arg(myWindow* const _topwin,
+ alsa_pcm* const _pcm, alsa_pcm* const _otherpcm,
+ snark* const _setup, const int _pipefd)
+ : topwin(_topwin), pcm(_pcm), otherpcm(_otherpcm),
+ setup(_setup), pipefd(_pipefd)
+ {}
+};
+
+#endif /* KRUNCH__H */
diff --git a/src/lockin.cxx b/src/lockin.cxx
new file mode 100644
index 0000000..09f6afd
--- /dev/null
+++ b/src/lockin.cxx
@@ -0,0 +1,748 @@
+// 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 <iostream>
+#include <iomanip>
+#include <stdlib.h>
+#include <stdio.h>
+#include "Getopt.h"
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <asm/types.h>
+#include <time.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <string>
+#include <vector>
+#include <valarray>
+
+// 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 <alsa/asoundlib.h>
+
+#include <qwt_plot.h>
+#include <qwt_plot_curve.h>
+#include <qwt_plot_grid.h>
+
+#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<int16_t> 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<char> 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<int> 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<getdtablesize(); ii++){
+ int rslt = fcntl(ii, F_GETFL);
+ if (rslt != -1) {
+ cout << "FD " << ii << " flags " << rslt << endl;
+ } else if (0) {
+ cout << "FD " << ii << " errno " << errno
+ << " == " << strerror(errno) << endl;
+ }
+ }
+}
+
+// Process command-line switches, et cetera
+void snark::cmdline(int argc, char **argv) {
+
+// Note basic default values were set up in constructor.
+// Now process options:
+ int ch;
+ time_t mytime;
+ struct tm* now;
+ const int ALT(128);
+ static struct option long_options[] = {
+ {"buffer-size", 1, NULL, 'b'},
+ {"card", 1, NULL, 'c'},
+ {"help", 0, NULL, 'h'},
+ {"mixer-ctl", 1, NULL, 'm'},
+ {"output-fifo", 1, NULL, 'o'},
+ {"verbose", 0, NULL, 'v'},
+ {"write", 1, NULL, 'w'},
+ {"amplitude", 1, NULL, 'A'},
+ {"Amplitude", 1, NULL, 'A'},
+ {"channel-mask", 1, NULL, 'C'},
+ {"Channel-mask", 1, NULL, 'C'},
+ {"frequency", 1, NULL, 'F'},
+ {"Frequency", 1, NULL, 'F'},
+ {"delta-f", 1, NULL, 'D'},
+ {"delta-f", 1, NULL, 'D'},
+ {"Vo-cal", 1, NULL, 'O'},
+ {"Vi-cal", 1, NULL, 'I'},
+ {"Time-shift", 1, NULL, 'T'},
+ {"time-shift", 1, NULL, 'T'},
+ {"Phase-shift", 1, NULL, 'P'},
+ {"phase-shift", 1, NULL, 'P'},
+ {"kout", 1, NULL, 'K'},
+ {"Kout", 1, NULL, 'K'},
+ {"frame-rate", 1, NULL, 'N'},
+ {"quantum-sig", 1, NULL, 'Q'},
+ {"Quantum-sig", 1, NULL, 'Q'},
+ {"rin", 1, NULL, 'R'},
+ {"Rin", 1, NULL, 'R'},
+ {"zref", 1, NULL, 'Z'},
+ {"Zref", 1, NULL, 'Z'},
+ {"1pass", 0, NULL, '1'},
+ {"xrun-verbose", 0, NULL, ALT|'x'},
+ {"hack", 0, NULL, ALT|'h'},
+ {"snooze", 1, NULL, ALT|'s'},
+ {NULL, 0, NULL, 0}
+ };
+
+//////////////////////////////////////////////////////////////////////
+
+ mytime = time(0);
+ srand(mytime);
+ mixer_ctlfile = "<>"; // fill in default, later
+ now = localtime (&mytime);
+ if (now) {};
+ int helpme(0);
+
+// Process commandline options
+ while(1) {
+// int getopt_long(int argc, char * const argv[],
+// const struct option *longopts, int *longindex);
+
+ ch = getopt_long (argc, argv, long_options, NULL);
+ if (ch == -1)
+ break;
+
+ if (optarg) if (*optarg == ':' || *optarg == '=') optarg++;
+ switch(ch) {
+ case '1':
+ onepass = 1;
+ break;
+ case 'b':
+ nframe = atoi(optarg);
+ break;
+ case 'c':
+ card_device = optarg;
+ if (card_device == "?"
+ || card_device == ""
+ || card_device == "-1") {
+ device_list(SND_PCM_STREAM_CAPTURE);
+ exit(0);
+ }
+ if (card_device == "-2") {
+ device_list(SND_PCM_STREAM_PLAYBACK);
+ exit(0);
+ }
+ break;
+ case 'm':
+ mixer_ctlfile = optarg;
+ break;
+ case 'w':
+ wcheck_fname = optarg;
+ break;
+ case 'N':
+ desired_rate = atoi(optarg);
+ break;
+ case 'C':
+ channel_mask = atoi(optarg);
+ break;
+ case 'K':
+ kout = atof(optarg);
+ break;
+ case 'Z':
+ zref = atof(optarg);
+ break;
+ case 'F':
+ reffreq = atof(optarg);
+ break;
+ case 'I':
+ ViCal_dB = atof(optarg);
+ break;
+ case 'O':
+ VoCal_dB = atof(optarg);
+ break;
+ case 'T':
+ timeShift = atof(optarg);
+ break;
+ case 'P':
+ phaseShift = atof(optarg);
+ break;
+ case 'A':
+ refOutAmp_dB = atof(optarg);
+ break;
+ case 'D':
+ refival = atof(optarg);
+ break;
+ case 'v':
+ verbosity ++;
+ break;
+ case 'h':
+ helpme++;
+ goto helper;
+ case ALT|'x':
+ xrun_verbosity++;
+ break;
+ case ALT|'h':
+ dump_io();
+ exit(0);
+ break;
+ case ALT|'s':
+ snooze = atof(optarg);
+ break;
+ case '?': // optarg() uses this for any unrecognized
+ // option, and has already complained about it.
+ cerr << "For help, try\n " << argv[0]
+ << " --help" << endl;
+ exit(1);
+ default:
+ int chx(ch&~ALT);
+ fprintf(stderr, "Sorry, option %s'%c' not implemented.\n",
+ ch!=chx? "ALT+" : "", chx);
+ exit(1);
+ }
+ }
+helper:;;;;
+
+ if (!helpme) if (optind < argc) {
+ printf ("Extraneous verbiage: ");
+ while (optind < argc) cerr << argv[optind++] << " " ;
+ cerr << endl;
+ exit(1);
+ }
+
+ string::size_type where = card_device.find_first_not_of(digits);
+ if (where == card_device.npos) justcard = card_device;
+ else justcard = card_device.substr(0, where);
+
+ if(helpme) {
+ usage(0);
+ exit(0); // no need to exeunt()
+ }
+
+}// cmdline
+
+
+void config_mixer(const string ctlfilestr, const string justcard){
+ if (!ctlfilestr.length()) return;
+
+ string ctlfile = fixup_ctlfile(ctlfilestr, justcard);
+
+// probe for existence:
+ int rslt = open(ctlfile.c_str(), O_RDONLY);
+ if (rslt < 0) {
+ fprintf(stderr, "Could not open alsactl file '%s': %m\n",
+ ctlfile.c_str());
+
+ string mixerfile = fixup_ctlfile(ctlfilestr, justcard, 1).c_str();
+ int retry = open(mixerfile.c_str(), O_RDONLY);
+ if (retry < 0) {
+ fprintf(stderr, "... will use inherited mixer settings.\n");
+ return;
+ }
+ fprintf(stderr, "... using fallback: %s\n", mixerfile.c_str());
+ ctlfile = mixerfile;
+ close(retry);
+ } else close(rslt); // probe has been completed
+
+
+#ifdef ALSAHACK
+ int load = load_state(ctlfile.c_str(), justcard.c_str(), 1, 1);
+
+ if (load) {
+ fprintf(stderr, "error in alsa load_state ... " NL);
+ fprintf(stderr, " ... using ctlfile '%s' " NL, ctlfile.c_str());
+ fprintf(stderr, " ... using card '%s' " NL, justcard.c_str());
+ exit(1);
+ }
+#endif
+}
+
+// Open miscellaneous i/o features:
+void snark::open_aux_io(){
+ if (wcheck_fname.length()) {
+ wcheck_fd = open(wcheck_fname.c_str(), O_WRONLY|O_CREAT|O_TRUNC, 0600);
+ if (wcheck_fd == -1) {
+ fprintf(stderr, "Couldn't open checkfile for writing: %m" NL);
+ exit(1);
+ }
+ }
+}
+
+void snark::printx(
+ const int colored,
+ const char* name,
+ const double foo_1,
+ const double foo_2,
+ double plogp,
+ const alsa_pcm* /*pcm*/
+) {
+
+ double stdev = sqrt(foo_2 - foo_1*foo_1);
+
+ double dbdev(-999);
+ if (stdev != 0.0) dbdev = 20 * log10(stdev / full_swing_2);
+ if (colored) cout << crtRed;
+ printf("%6s: 1st %9.3g, 2nd %9.3g, stdev %9.3g "
+ "= %7.3fdB, plp %6.3f",
+ name, foo_1, foo_2, stdev, dbdev, plogp);
+// switch back to black BEFORE printing the newline;
+// otherwise bad things happen when piping to /bin/head
+ if (colored) cout << crtNormal;
+ cout << endl;
+}
+
+const double microsec(1e-6);
+
+void cleanup(int sts){
+ for (string::size_type ii = 0; ii < clean_me.size(); ii++) {
+ int pid = clean_me[ii];
+ if (pid > 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;
+}
diff --git a/src/lockin.h b/src/lockin.h
new file mode 100644
index 0000000..9a6a515
--- /dev/null
+++ b/src/lockin.h
@@ -0,0 +1,99 @@
+#ifndef LOCKIN_H
+#define LOCKIN_H
+#include <limits.h> /* for INT_MAX */
+#include <valarray>
+#include <cmath>
+#include <string>
+#include "alsa_pcm.h"
+
+// used when converting between syslog and fprintf:
+#define NL "\n"
+
+const double two16(1<<16);
+const double two32(two16*two16);
+const int bPB(8); // bits per byte
+void exeunt(int signum);
+std::string invent_driverfile(const std::string justcard);
+std::string invent_ctlfile(const std::string justcard);
+
+typedef int32_t datum; // all internal calculations are 32bit
+
+const double pi(M_PI);
+const double kT(1.38e-23 * (273.15 + 20));
+const double twopi = 2 * pi;
+
+// reference level is saturation minus 15 dB
+const double full_swing(INT_MAX);
+
+// sine wave RMS is sqrt(2) less than peak:
+const double full_swing_2(full_swing/sqrt(2.0));
+
+// some non-constant global variables,
+// for inter-thread communication:
+extern std::string progname;
+extern int verbosity;
+extern timespec prog_start_time;
+extern int xrun_verbosity; // be verbose about underrun and overrun
+//
+// Note: more globals are found in gui.h
+
+inline double operator-(const timespec a, const timespec b){
+ return (a.tv_sec-b.tv_sec) + (a.tv_nsec - b.tv_nsec)*1e-9;
+}
+
+class Keeper{
+public:
+ const alsa_pcm* pcm;
+ const int fpp; // frames per period
+ std::valarray<datum> buf;
+ int finbuf; // frames in buffer at the moment
+ int nchan;
+ Keeper(const alsa_pcm*, const int fsize);
+};
+
+snd_pcm_sframes_t snd_pcm_writei(Keeper& kk, const datum* buffer,
+ snd_pcm_uframes_t nframes);
+
+
+class snark {
+public:
+ snark(); // constructor
+ std::string card_device; // principal input, e.g. "0,0"
+ std::string wcheck_fname; // place to write raw data (checkfile)
+ int nframe;
+ int onepass; // exit after one pass
+ int desired_rate;
+ int channel_mask;
+ double kout; // output voltage scale, assuming sine-wave
+ // ... which has RMS 3dB below full_swing
+ double zref; // reference resistor
+ double reffreq;
+ double refival;
+ double refamp;
+ std::string mixer_ctlfile;
+
+
+// not commandline options, but derived therefrom:
+ int wcheck_fd;
+ std::string justcard; // the "card" part of that, before the comman
+ double sps; // entropy (bits per sample)
+ double snooze; // time to snooze between actions if not
+ // actually writing to output fifo,
+ // e.g. during calibration.
+
+//// functions
+ void usage(const int err);
+ void cmdline(int argc, char** argv);
+ void open_aux_io();
+ int read_stuff(alsa_pcm*, datum*);
+ void printx(
+ const int colored,
+ const char* name,
+ const double foo_1,
+ const double foo_2,
+ double plogp,
+ const alsa_pcm* pcm
+ );
+};
+
+#endif
diff --git a/src/makefile b/src/makefile
new file mode 100644
index 0000000..5ae971e
--- /dev/null
+++ b/src/makefile
@@ -0,0 +1,16 @@
+
+
+all:
+ cmake --build bin
+
+clean:
+ cd bin; make -f Makefile clean
+
+shipme := $(shell cat sources.txt) makefile bin CMakeLists.txt sources.txt
+
+togo : ALWAYS
+ echo $(shipme:%=src/%) > $@
+
+.PHONY: ALWAYS
+
+##tar -c --no-rec $(shipme) | gzip > $@
diff --git a/src/refout.cxx b/src/refout.cxx
new file mode 100644
index 0000000..3642380
--- /dev/null
+++ b/src/refout.cxx
@@ -0,0 +1,247 @@
+using namespace std;
+#include <iostream>
+#include <iomanip>
+#include <unistd.h>
+#include "refout.h"
+#include "lockin.h"
+#include "gui.h"
+#include <alsa/asoundlib.h>
+#ifdef TUNER
+# include <sys/ioctl.h> /* for ioctl, TCGETA */
+# include <termios.h> /* for ECHO */
+#endif
+
+datum smash(double foo) {
+ if (foo > INT_MAX) return INT_MAX;
+ if (foo < -INT_MAX) return -INT_MAX;
+ return datum(foo);
+}
+
+#ifdef TUNER
+
+int fn = fileno(stdin);
+string kbd("\t1q2we4r5t6yu8i9op-[=]");
+string keymap("C+D+EF+G+A+BC");
+const double halfstep(pow(2.0, 1.0/12.0));
+int octave(4);
+int kbkey(9); // A4 = 440 Hz
+int abskey(57);
+
+void dokey(int newkey){
+ abskey = newkey;
+ ofreq = 440*pow(halfstep, abskey-57);
+ int myoct = abskey / 12;
+ int mykey = abskey - myoct*12;
+ string keyname;
+ char keylet = keymap[mykey];
+ if (keylet == '+') {
+ keyname += keymap[mykey-1];
+ keyname += '#';
+ } else keyname += keylet;
+
+ fprintf(stderr, "\r\033[K %2s%1d %6.2f\r", keyname.c_str(), myoct, ofreq);
+ fflush(stderr);
+}
+
+int escmode(0);
+
+void chkey(){
+ char cc;
+ int nn = read(fn, &cc, 1);
+ if (nn <= 0) return;
+ if (cc == 033) {
+ escmode = 1;
+ return;
+ }
+ if (escmode) {
+ if (cc == '~') escmode = 0;
+ if (cc != 'O' && isupper(cc)) escmode = 0;
+ if (cc == 'B' || cc == 'D') dokey(abskey-1);
+ else if (cc == 'A' || cc == 'C') dokey(abskey+1);
+ return;
+ }
+ string::size_type where = kbd.find(cc);
+ if (where != kbd.npos) {
+ kbkey = where;
+ } else if (cc == ',') {
+ octave--;
+ if (octave < 0) octave = 0;
+ } else if (cc == '.') {
+ octave++;
+ if (octave > 8) octave = 8;
+ } else {
+ int foo;
+ foo = cc;
+ fprintf(stderr, "%04o\n", foo);
+ return;
+ }
+ dokey(12*octave + kbkey);
+}
+
+void setkey(){
+ kbd += char(127); // the "backspace" key
+ kbd += '\\';
+ kbd += char(10); // the "return" key
+}
+
+#else
+inline void chkey(){} // does nothing
+inline void setkey(){} // does nothing
+#endif
+
+void* refout(void* _arg){
+
+ ref_arg* arg = (ref_arg*) _arg;
+
+ setkey(); // FIXME (doesn't do anything at the moment)
+
+ double reftime = .2; // # of seconds in output buffer
+ int refframe = int(arg->pcm->rate * reftime); // # of frames
+ int bufsize(refframe * arg->pcm->nchan);
+
+ if (verbosity) cerr << "Refout starts, ofreq: " << actOutFreq
+ << " INT_MAX: " << INT_MAX
+ << " sizeof(datum): " << sizeof(datum)
+ << " refframe: " << refframe
+ << " bufsize: " << bufsize
+ << endl;
+
+#ifdef TUNER
+ {
+ termio save;
+ // If ioctl fails, we're probably not connected to a terminal.
+ int rslt;
+ rslt = ioctl(fn, TCGETA, &save);
+ termio t(save);
+ t.c_lflag &= ~ECHO;
+ t.c_lflag &= ~ICANON;
+ t.c_cc[VMIN] = 1;
+ t.c_cc[VTIME] = 0;
+ t.c_cc[VERASE] = 0;
+
+ ioctl(fn, TCSETA, &t);
+ int temp(1);
+ ioctl(fn, FIONBIO, &temp);
+ }
+#endif
+
+ Keeper keep(arg->pcm, FPP);
+
+ datum* ptr;
+ datum foo[2];
+ int flip;
+
+/////////////////
+// main refout loop
+//
+// this is a SINGLE loop over TWO variables;
+// the cap_buffer is emptied in units of refframe
+// and filled in units of fpkp.
+// Parallel code appears in krunch.cxx
+ valarray<datum> refout_buffer(bufsize);
+ int out_idx(0);
+ int ref_end(fpkp);
+ int ref_idx(0);
+ double theta0(0);
+ double theta1(0);
+ double old_cpkp(0), old_fpkp(0);
+ double dtheta0(0);
+ double dtheta1(0);
+ int kperno(0);
+
+ for (;;) { // loop over all samples
+
+// loop control for output (write) process
+ if (out_idx >= refframe) {
+ int todo = out_idx;
+ out_idx = 0;
+ datum* obuf_ptr(&refout_buffer[0]);
+
+ while (todo) { // keep trying to get this buffer out
+ int rslt = snd_pcm_writei(keep, obuf_ptr, todo);
+ if (rslt == todo) break; // good!
+ if (rslt > 0) { // short write = harmless
+ // but should be rare
+ cout << "Refout: short write; probably harmless." << endl;
+ todo -= rslt;
+ obuf_ptr += rslt;
+ continue;
+ }
+ if (rslt == -EPIPE) { // explicit underrrun
+ if (xrun_verbosity) fprintf(stderr,
+ "Refout underrun: wrote %d got %d" NL, todo, rslt);
+ snd_pcm_prepare(arg->pcm->phndl); // recover from underruns
+ continue;
+ }
+ // some error we don't understand
+ fprintf(stderr, "Refout wrote %d got %d" NL, todo, rslt);
+ snd_pcm_prepare(arg->pcm->phndl); // try to recover
+ }
+ }
+
+// Loop control for reference process.
+// Note that dtheta0 only changes at the boundary
+// between krunch periods.
+ if (ref_idx == ref_end) {
+ theta0 = 0.;
+ // theta1 is free-running
+ ref_idx = 0;
+ ref_end = fpkp;
+ dtheta0 = 2. * M_PI * cpkp / fpkp;
+ // cpkp == cycles per krunch period
+ // fpkp == frames per krunch period
+ if (old_cpkp != cpkp || old_fpkp != fpkp){
+ if (verbosity > 0) {
+ cout.precision(6);
+ cout << fixed;
+ double freq = arg->pcm->rate / fpkp * cpkp;
+ cout << "Refout switching to: " << freq
+ << setprecision(1)
+ << " cpkp: " << cpkp
+ << " fpkp: " << fpkp
+ << " dtheta: " << setprecision(20) << dtheta0
+ << " rate: " << arg->pcm->rate
+ << endl;
+ }
+ old_cpkp = cpkp;
+ old_fpkp = fpkp;
+ }
+ }
+ if (ref_idx == 0) {
+ ref_to_krunch kper(kperno, int(cpkp), int(fpkp));
+ int didwrite = write(arg->pipefd, &kper, sizeof(kper));
+ if (didwrite != sizeof(kper)) {
+ fprintf(stderr, "refout: write to pipe failed:");
+ perror(0);
+ exeunt(1);
+ }
+ kperno++;
+ }
+
+
+ dtheta1 = dtheta0 * arg->ival;
+
+ chkey(); // FIXME
+
+ double expo((refOutAmp_dB - VoCal_dB)/20);
+ if (expo > 100) expo = 100;
+ double gainFactor(pow(10, expo));
+ double amplitude(full_swing * gainFactor);
+
+ foo[0] = smash(amplitude * cos(theta0));
+ foo[1] = smash(amplitude * cos(theta1));
+
+// Fill all channels in frame (not just the first two):
+ ptr = &refout_buffer[out_idx * arg->pcm->nchan];
+ for (unsigned int jj = 0; jj < arg->pcm->nchan; jj++) {
+ flip = jj & 1; // copy foo[0] or foo[1]
+ *ptr++ = foo[flip];
+ }
+
+ theta0 += dtheta0;
+ theta1 += dtheta1;
+ out_idx++;
+ ref_idx++;
+ }
+ return 0;
+}
diff --git a/src/refout.h b/src/refout.h
new file mode 100644
index 0000000..b12e404
--- /dev/null
+++ b/src/refout.h
@@ -0,0 +1,32 @@
+#include "lockin.h"
+
+class ref_arg {
+public:
+ double ival;
+ alsa_pcm* pcm;
+ int pipefd;
+ ref_arg(const double _ival, alsa_pcm* const _pcm,
+ const int _pipefd)
+ : ival(_ival), pcm(_pcm),
+ pipefd(_pipefd)
+ {}
+};
+
+void* refout(void* arg);
+
+// for communication with krunch:
+
+class ref_to_krunch{
+public:
+ int period; // sequence number
+ int cpkp; // cycles in this krunch period
+ int fpkp; // frames in this krunch period
+
+// trivial constructor:
+ ref_to_krunch(){}
+
+// nontrivial constructor:
+ ref_to_krunch(const int _period, const int _cpkp, const int _fpkp)
+ : period(_period), cpkp(_cpkp), fpkp(_fpkp)
+ {}
+};
diff --git a/src/sources.txt b/src/sources.txt
new file mode 100644
index 0000000..7bc1954
--- /dev/null
+++ b/src/sources.txt
@@ -0,0 +1,10 @@
+lockin.cxx
+Getopt.cxx
+alsa_pcm.cxx
+gui.cxx
+refout.cxx
+krunch.cxx
+biquad.cxx
+iir_bp.cxx
+gui_class.cxx
+thrower.cxx
diff --git a/src/thrower.cxx b/src/thrower.cxx
new file mode 100644
index 0000000..9369ab9
--- /dev/null
+++ b/src/thrower.cxx
@@ -0,0 +1,12 @@
+#include "thrower.h"
+
+ void thrower::newReading(const double rp0, const double ip0,
+ const double rp1, const double ip1)
+ {
+ emit _newReading(rp0, ip0, rp1, ip1);
+ }
+
+
+ void thrower::flush() {
+ emit _flush();
+ }
diff --git a/src/thrower.h b/src/thrower.h
new file mode 100644
index 0000000..5e4c4fb
--- /dev/null
+++ b/src/thrower.h
@@ -0,0 +1,27 @@
+#ifndef THROWER_H
+#define THROWER_H
+
+#include <QApplication>
+
+/////////////////////////////////
+// This exists only for the purpose of being something to throw.
+// It is essentially private to krunch.cxx
+// Apparently it has to exist in a .h file (not in krunch.cxx)
+// because of the way the meta-object-compiler works.
+//
+class thrower : public QObject{
+
+ Q_OBJECT
+
+Q_SIGNALS: // outbound signals
+ void _newReading(const double, const double, const double, const double);
+ void _flush();
+
+public:
+ void newReading(const double rp0, const double ip0,
+ const double rp1, const double ip1);
+
+ void flush();
+};
+
+#endif /* THROWER_H */