double L, R, Lx, Rx; /*------------------- modified pavumeter begins here ---------------------*/ /* $Id: vumeter.cc 53 2007-09-06 21:50:05Z lennart $ */ /*** This file is part of pavumeter. pavumeter is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. pavumeter is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with pavumeter; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. ***/ /* This code has been significantly modified from the original */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #define LOGARITHMIC 1 char str[1024]; int modex; class MainWindow : public Gtk::Window { public: MainWindow(const pa_channel_map &map, const char *source_name, const char *description); virtual ~MainWindow(); protected: class ChannelInfo { public: ChannelInfo(MainWindow &w, const Glib::ustring &l); Gtk::Label *label; Gtk::ProgressBar *progress; }; Gtk::VBox vbox, titleVBox; Gtk::HBox titleHBox; Gtk::Table table; std::vector channels; Gtk::Image image; Gtk::Label titleLabel; Gtk::Label subtitleLabel; Gtk::HSeparator separator; Gtk::EventBox eventBox; float *levels; virtual void addChannel(const Glib::ustring &l); virtual bool on_delete_event(GdkEventAny* e); virtual bool on_display_timeout(); virtual bool on_calc_timeout(); virtual void decayLevels(); sigc::connection display_timeout_signal_connection; sigc::connection calc_timeout_signal_connection; pa_usec_t latency; class LevelInfo { public: LevelInfo(float *levels, pa_usec_t l); virtual ~LevelInfo(); bool elapsed(); struct timeval tv; float *levels; }; std::deque levelQueue; public: virtual void pushData(const float *d, unsigned l); virtual void showLevels(const LevelInfo& i); virtual void updateLatency(pa_usec_t l); }; MainWindow::MainWindow(const pa_channel_map &map, const char *, const char *description) : Gtk::Window(), table(1, 2), latency(0) { char t[256]; int n; for (n = 0; n < map.channels; n++) { addChannel(t); } levels = NULL; display_timeout_signal_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &MainWindow::on_display_timeout), 40); calc_timeout_signal_connection = Glib::signal_timeout().connect(sigc::mem_fun(*this, &MainWindow::on_calc_timeout), 100); } MainWindow::~MainWindow() { while (channels.size() > 0) { ChannelInfo *i = channels.back(); channels.pop_back(); delete i; } while (levelQueue.size() > 0) { LevelInfo *i = levelQueue.back(); levelQueue.pop_back(); delete i; } if (levels) delete[] levels; display_timeout_signal_connection.disconnect(); calc_timeout_signal_connection.disconnect(); } bool MainWindow::on_delete_event(GdkEventAny*) { Gtk::Main::quit(); return true; } void MainWindow::addChannel(const Glib::ustring &l) { channels.push_back(new ChannelInfo(*this, l)); } MainWindow::ChannelInfo::ChannelInfo(MainWindow &w, const Glib::ustring &l) { label = Gtk::manage(new Gtk::Label(l, 1.0, 0.5)); label->set_markup(l); progress = Gtk::manage(new Gtk::ProgressBar()); progress->set_fraction(0); w.table.resize(w.channels.size()+1, 2); w.table.attach(*label, 0, 1, w.channels.size(), w.channels.size()+1, Gtk::FILL, (Gtk::AttachOptions) 0); w.table.attach(*progress, 1, 2, w.channels.size(), w.channels.size()+1, Gtk::EXPAND|Gtk::FILL, (Gtk::AttachOptions) 0); } void MainWindow::pushData(const float *d, unsigned samples) { unsigned nchan = channels.size(); if (!levels) { levels = new float[nchan]; for (unsigned c = 0; c < nchan; c++) levels[c] = 0; } while (samples >= nchan) { for (unsigned c = 0; c < nchan; c++) { float v = fabs(d[c]); if (v > levels[c]) levels[c] = v; } d += nchan; samples -= nchan; } } void MainWindow::showLevels(const LevelInfo &i) { unsigned nchan = channels.size(); for (unsigned n = 0; n < nchan; n++) { double level,lev0; ChannelInfo *c = channels[n]; level = i.levels[n]; #ifdef LOGARITHMIC // level = log10(level*9+1); // level = log10(level*10+1); level = log10(level*11+1); #endif if (n == 0 ) L = level; else R = level; c->progress->set_fraction(level > 1 ? 1 : level); } } #define DECAY_LEVEL (0.005) void MainWindow::decayLevels() { unsigned nchan = channels.size(); for (unsigned n = 0; n < nchan; n++) { double level; ChannelInfo *c = channels[n]; level = c->progress->get_fraction(); if (level <= 0) continue; level = level > DECAY_LEVEL ? level - DECAY_LEVEL : 0; c->progress->set_fraction(level); } } bool MainWindow::on_display_timeout() { LevelInfo *i = NULL; if (levelQueue.empty()) { decayLevels(); return true; } while (levelQueue.size() > 0) { if (i) delete i; i = levelQueue.back(); levelQueue.pop_back(); if (!i->elapsed()) break; } if (i) { showLevels(*i); delete i; } return true; } bool MainWindow::on_calc_timeout() { if (levels) { levelQueue.push_front(new LevelInfo(levels, latency)); levels = NULL; } return true; } void MainWindow::updateLatency(pa_usec_t l) { latency = l; } static void timeval_add_usec(struct timeval *tv, pa_usec_t v) { uint32_t sec = v/1000000; tv->tv_sec += sec; v -= sec*1000000; tv->tv_usec += v; while (tv->tv_usec >= 1000000) { tv->tv_sec++; tv->tv_usec -= 1000000; } } MainWindow::LevelInfo::LevelInfo(float *l, pa_usec_t latency) { levels = l; gettimeofday(&tv, NULL); timeval_add_usec(&tv, latency); } MainWindow::LevelInfo::~LevelInfo() { delete[] levels; } bool MainWindow::LevelInfo::elapsed() { struct timeval now; gettimeofday(&now, NULL); if (now.tv_sec != tv.tv_sec) return now.tv_sec > tv.tv_sec; return now.tv_usec >= tv.tv_usec; } static MainWindow *mainWindow = NULL; static pa_context *context = NULL; static pa_stream *stream = NULL; static char* device_name = NULL; static char* device_description = NULL; static enum { PLAYBACK, RECORD } mode = PLAYBACK; void show_error(const char *txt, bool show_pa_error = true) { char buf[256]; if (show_pa_error) snprintf(buf, sizeof(buf), "%s: %s", txt, pa_strerror(pa_context_errno(context))); Gtk::MessageDialog dialog(show_pa_error ? buf : txt, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE, true); dialog.run(); Gtk::Main::quit(); } static void stream_update_timing_info_callback(pa_stream *s, int success, void *) { pa_usec_t t; int negative = 0; if (!success || pa_stream_get_latency(s, &t, &negative) < 0) { show_error("Failed to get latency information"); return; } if (!mainWindow) return; mainWindow->updateLatency(negative ? 0 : t); } static gboolean latency_func(gpointer) { pa_operation *o; if (!stream) return false; if (!(o = pa_stream_update_timing_info(stream, stream_update_timing_info_callback, NULL))) g_message("pa_stream_update_timing_info() failed: %s", pa_strerror(pa_context_errno(context))); else pa_operation_unref(o); return true; } static void stream_read_callback(pa_stream *s, size_t l, void *) { const void *p; if (pa_stream_peek(s, &p, &l) < 0) { g_message("pa_stream_peek() failed: %s", pa_strerror(pa_context_errno(context))); return; } mainWindow->pushData((const float*) p, l/sizeof(float)); pa_stream_drop(s); } static void stream_state_callback(pa_stream *s, void *) { switch (pa_stream_get_state(s)) { case PA_STREAM_UNCONNECTED: case PA_STREAM_CREATING: break; case PA_STREAM_READY: mainWindow = new MainWindow(*pa_stream_get_channel_map(s), device_name, device_description); g_timeout_add(100, latency_func, NULL); pa_operation_unref(pa_stream_update_timing_info(stream, stream_update_timing_info_callback, NULL)); break; case PA_STREAM_FAILED: show_error("Connection failed"); break; case PA_STREAM_TERMINATED: Gtk::Main::quit(); } } static void create_stream(const char *name, const char *description, const pa_sample_spec &ss, const pa_channel_map &cmap) { char t[256]; pa_sample_spec nss; g_free(device_name); device_name = g_strdup(name); g_free(device_description); device_description = g_strdup(description); nss.format = PA_SAMPLE_FLOAT32; nss.rate = ss.rate; nss.channels = ss.channels; g_message("Using sample format: %s", pa_sample_spec_snprint(t, sizeof(t), &nss)); g_message("Using channel map: %s", pa_channel_map_snprint(t, sizeof(t), &cmap)); stream = pa_stream_new(context, "PulseAudio Volume Meter", &nss, &cmap); pa_stream_set_state_callback(stream, stream_state_callback, NULL); pa_stream_set_read_callback(stream, stream_read_callback, NULL); pa_stream_connect_record(stream, name, NULL, (enum pa_stream_flags) 0); } static void context_get_source_info_callback(pa_context *, const pa_source_info *si, int is_last, void *) { if (is_last < 0) { show_error("Failed to get source information"); return; } if (!si) return; create_stream(si->name, si->description, si->sample_spec, si->channel_map); } static void context_get_sink_info_callback(pa_context *, const pa_sink_info *si, int is_last, void *) { if (is_last < 0) { show_error("Failed to get sink information"); return; } if (!si) return; create_stream(si->monitor_source_name, si->description, si->sample_spec, si->channel_map); } static void context_get_server_info_callback(pa_context *c, const pa_server_info*si, void *) { if (!si) { show_error("Failed to get server information"); return; } if (mode == PLAYBACK) { if (!si->default_sink_name) { show_error("No default sink set.", false); return; } pa_operation_unref(pa_context_get_sink_info_by_name(c, si->default_sink_name, context_get_sink_info_callback, NULL)); } else if (mode == RECORD) { if (!si->default_source_name) { show_error("No default source set.", false); return; } pa_operation_unref(pa_context_get_source_info_by_name(c, si->default_source_name, context_get_source_info_callback, NULL)); } } static void context_state_callback(pa_context *c, void *) { switch (pa_context_get_state(c)) { case PA_CONTEXT_UNCONNECTED: case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: break; case PA_CONTEXT_READY: if (device_name && mode == RECORD) pa_operation_unref(pa_context_get_source_info_by_name(c, device_name, context_get_source_info_callback, NULL)); else if (device_name && mode == PLAYBACK) pa_operation_unref(pa_context_get_sink_info_by_name(c, device_name, context_get_sink_info_callback, NULL)); else pa_operation_unref(pa_context_get_server_info(c, context_get_server_info_callback, NULL)); break; case PA_CONTEXT_FAILED: show_error("Connection failed"); break; case PA_CONTEXT_TERMINATED: Gtk::Main::quit(); } } /*------------------------ modified pavumeter ends here ------------------------*/ /*------------------------ modified pavumeter ends here ------------------------*/ /*------------------------ modified pavumeter ends here ------------------------*/ /*------------------------ modified pavumeter ends here ------------------------*/ /*------------------------ modified pavumeter ends here ------------------------*/ /*----------------------- spark gap meter begins here -------------------------*/ /*----------------------- spark gap meter begins here -------------------------*/ /*----------------------- spark gap meter begins here -------------------------*/ /*----------------------- spark gap meter begins here -------------------------*/ /*----------------------- spark gap meter begins here -------------------------*/ /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ #+ #+ Glade / Gtk Programming #+ #+ Copyright (C) 2019 by Kevin C. O'Kane #+ #+ Kevin C. O'Kane #+ kc.okane@gmail.com #+ https://www.cs.uni.edu/~okane #+ http://threadsafebooks.com/ #+ #+ This program is free software; you can redistribute it and/or modify #+ it under the terms of the GNU General Public License as published by #+ the Free Software Foundation; either version 2 of the License, or #+ (at your option) any later version. #+ #+ This program is distributed in the hope that it will be useful, #+ but WITHOUT ANY WARRANTY; without even the implied warranty of #+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #+ GNU General Public License for more details. #+ #+ You should have received a copy of the GNU General Public License #+ along with this program; if not, write to the Free Software #+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #+ #++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ #include #include #include #include #include #include #include #include #include #include #include #include #include GtkWidget *window; GtkWidget *fixed1; GtkWidget *draw1; GtkWidget *image1; GtkBuilder *builder; gboolean timer_handler(); void on_destroy(); gboolean on_draw1_draw (GtkDrawingArea *widget, cairo_t *cr) ; int style; int image; int main(int argc, char *argv[]) { signal(SIGPIPE, SIG_IGN); Glib::init(); // some of the following is code copied from pavumeter bool record = false; pa_glib_mainloop *m; #define NEEDLES 1 #define ARC 2 #define FILLED_ARC 3 style = NEEDLES; image = 1; for (int i = 1; i< argc; i++) { if (strcmp(argv[i],"--record") == 0) record = true; else if (strcmp(argv[i],"--playback") == 0) record = false; else if (strcmp(argv[i],"--needles") == 0) style = NEEDLES; else if (strcmp(argv[i],"--arc") == 0) style = ARC; else if (strcmp(argv[i],"--filled-arc") == 0) style = FILLED_ARC; else if (strcmp(argv[i],"--no-image") == 0) image = 0; else if (strcmp(argv[i],"--image") == 0) image = 1; else { g_message("Unrecognized option: '%s'", argv[i]); printf(" --playback *use pulse source\n"); printf(" --record use pulse sink\n"); printf(" --needles *display needles \n"); printf(" --arc display unfilled arcs \n"); printf(" --filled-arc display filled arcs \n"); printf(" --no-image display no background image \n"); printf(" --image *display background image \n"); printf("* denotes default\n"); return EXIT_FAILURE; } } gtk_init(&argc, &argv); // init Gtk mode = record ? RECORD : PLAYBACK; modex=mode; g_message("Starting in %s mode.", mode == RECORD ? "record" : "playback"); // the following worked with Gtk:Main kit but needs to be fixed // // /* Rather ugly and incomplete */ // if (argc > 1) device_name = g_strdup(argv[1]) ; // else { // char *e; // if ((e = getenv(mode == RECORD ? "PULSE_SOURCE" : "PULSE_SINK"))) // device_name = g_strdup(e); // } { // remains of the above char *e; if ((e = getenv(mode == RECORD ? "PULSE_SOURCE" : "PULSE_SINK"))) device_name = g_strdup(e); } if (device_name) g_message("Using device '%s'", device_name); m = pa_glib_mainloop_new(g_main_context_default()); context = pa_context_new(pa_glib_mainloop_get_api(m), "Volume Meter"); pa_context_set_state_callback(context, context_state_callback, NULL); pa_context_connect(context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL); // end of area containging some code copied from pavumeter builder = gtk_builder_new_from_file ("part1.glade"); window = GTK_WIDGET(gtk_builder_get_object(builder, "window")); g_signal_connect(window, "destroy", G_CALLBACK(on_destroy), NULL); gtk_builder_connect_signals(builder, NULL); fixed1 = GTK_WIDGET(gtk_builder_get_object(builder, "fixed1")); draw1 = GTK_WIDGET(gtk_builder_get_object(builder, "draw1")); image1 = GTK_WIDGET(gtk_builder_get_object(builder, "image1")); g_object_unref(builder); // The dynamic callback linkage is not working so an explicit callback is next. // Something to be investigated. As a result, there will be a warning generated // about on_draw1_draw that can be ignored. g_signal_connect (G_OBJECT (draw1), "draw", G_CALLBACK (on_draw1_draw), NULL); gtk_window_set_keep_above (GTK_WINDOW(window), TRUE); gtk_widget_show(window); g_timeout_add(100, (GSourceFunc) timer_handler, NULL); if (! image) gtk_widget_hide(image1); gtk_main(); // code copied from pavumeter if (stream) pa_stream_unref(stream); if(device_name) g_free(device_name); pa_glib_mainloop_free(m); // end of code copied from pavumeter return EXIT_SUCCESS; } void on_destroy() { gtk_main_quit(); } #define LAT 4 #define LOWER_MIN 30.0 #define UPPER_MAX 200.0 #define FACTOR 36.0 gboolean timer_handler() { static double recentR[LAT] { 0.0 }; static double recentL[LAT] { 0.0 }; static int r = 0; recentR[r] = R; recentL[r] = L; r++; if (r >= LAT) r=0; // recycle Lx = Rx = 0.0; for (int i = 0; i < LAT; i++) { Lx = Lx + recentL[i]; Rx= Rx + recentR[i]; } Rx = Rx / LAT; Lx = Lx / LAT; Lx = Lx * FACTOR + LOWER_MIN; Rx = Rx * FACTOR + LOWER_MIN; if (Lx > UPPER_MAX) Lx = UPPER_MAX; if (Rx > UPPER_MAX) Rx = UPPER_MAX; gtk_widget_queue_draw (draw1); return TRUE; } gboolean on_draw1_draw (GtkDrawingArea *widget, cairo_t *cr) { double x, y; double hor, ver, t1, len; // for future reference - not currently used // guint width, height; // width = gtk_widget_get_allocated_width (widget); // of draw window // height = gtk_widget_get_allocated_height (widget); // of draw window cairo_set_line_width(cr, 3.0); if (style == NEEDLES) { //---------------------------------------------------- // LEFT Channel //---------------------------------------------------- t1 = (Lx / 100.0) * 3.14; // position of origin point and length of needle: hor = 144.0; ver = 220.0; len = 170.0; if (! image) len = 220; // Calculate are to t1 but do not draw cairo_arc (cr, hor, ver, len, -3.14, -3.14 + t1); // Get the point of the tip of the needle from above calculation cairo_get_current_point (cr, &x, &y); // Start over cairo_new_path (cr); // Position point to (x,y) cairo_move_to(cr, x, y); // Set color (RGB) cairo_set_source_rgb(cr, 0.0, 1.0, 0.0); // green // Move to origin cairo_line_to (cr, hor, ver ); // Draw line cairo_stroke (cr); //---------------------------------------------------- // RIGHT Channel //---------------------------------------------------- t1 = (Rx / 100.0) * 3.14; // See comments above for Right channel cairo_arc (cr, hor, ver, len, -3.14, -3.14 + t1); cairo_get_current_point (cr, &x, &y); cairo_new_path (cr); cairo_move_to(cr, x, y); // Show black needle if Left and Right are approximately the same. if (abs(Lx - Rx) < 0.1) cairo_set_source_rgb(cr, 0., 0., 0.); // black else cairo_set_source_rgb(cr, 1.0, 0.0, 0.0); // red cairo_line_to (cr, hor, ver ); cairo_stroke (cr); } else { t1 = (Rx / 100.0) * 3.14; // position of origin point and length of needle: hor = 144.0; ver = 220.0; len = 155.0; if (! image) len = 220; cairo_set_source_rgb(cr, 1.0, 0.0, 0.0); // red cairo_arc (cr, hor, ver, len-2.0, -2.22, -3.16 + t1); cairo_line_to (cr, hor, ver ); if (style == FILLED_ARC) { cairo_fill(cr); // fill in arc } cairo_stroke (cr); t1 = (Lx / 100.0) * 3.14; // position of origin point and length of needle: hor = 144.0; ver = 220.0; len = 152.0; // shorter length than left if (! image) len = 190; cairo_set_source_rgb(cr, 0.0, 1.0, 0.0); // green cairo_arc (cr, hor, ver, len-2.0, -2.22, -3.16 + t1); cairo_line_to (cr, hor, ver ); if (style == FILLED_ARC) { cairo_fill(cr); // fill in arc } cairo_stroke (cr); } return FALSE; }