sintonia/library/ecasound-2.7.2/ecatools/ecasignalview.cpp

594 lines
17 KiB
C++

// ------------------------------------------------------------------------
// ecasignalview.cpp: A simple command-line tools for monitoring
// signal amplitude.
// Copyright (C) 1999-2005,2007,2008 Kai Vehmanen
// Copyright (C) 2005 Jeffrey Cunningham
//
// 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
// ------------------------------------------------------------------------
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>
#include <cassert>
#include <cmath>
#include <cstdio>
#include <signal.h>
#include <unistd.h>
#include <sys/time.h> /* POSIX: select() */
#include <sys/select.h> /* POSIX: timeval struct */
#include <kvutils/kvu_com_line.h>
#include <kvutils/kvu_utils.h>
#include <kvutils/kvu_numtostr.h>
#include <eca-control-interface.h>
#include "ecicpp_helpers.h"
#ifdef HAVE_TERMIOS_H
/* see: http://www.opengroup.org/onlinepubs/007908799/xsh/termios.h.html */
#include <termios.h>
#endif
#if defined(ECA_USE_NCURSES_H) || defined(ECA_USE_NCURSES_NCURSES_H) || defined(ECA_USE_CURSES_H)
#define ECASV_USE_CURSES 1
#ifdef ECA_USE_NCURSES_H
#include <ncurses.h>
#include <term.h> /* for setupterm() */
#elif ECA_USE_NCURSES_NCURSES_H
#include <ncurses/ncurses.h>
#include <ncurses/term.h> /* for setupterm() */
#else
#include <curses.h>
#include <term.h> /* for setupterm() */
#endif
#endif /* ECA_*CURSES_H */
#include <cstring>
/**
* Import namespaces
*/
using namespace std;
/**
* Type definitions
*/
struct ecasv_channel_stats {
double last_peak;
double drawn_peak;
double max_peak;
long int clipped_samples;
vector<double> avg_peak; // jkc: addition
int avg_peak_ptr; // jkc: addition
double avg_peak_val; // jkc: addition
};
/**
* Function declarations
*/
int main(int argc, char *argv[]);
void ecasv_parse_command_line(ECA_CONTROL_INTERFACE* cop, int argc, char *argv[]);
void ecasv_fill_defaults(void);
std::string ecasv_cop_to_string(ECA_CONTROL_INTERFACE* cop);
void ecasv_output_init(void);
void ecasv_output_cleanup(void);
int ecasv_print_vu_meters(ECA_CONTROL_INTERFACE* eci,
std::vector<struct ecasv_channel_stats>* chstats);
void ecasv_update_chstats(std::vector<struct ecasv_channel_stats>* chstats,
int ch, double value);
void ecasv_create_bar(double value, int barlen, unsigned char* barbuf);
void ecasv_print_usage(void);
void ecasv_signal_handler(int signum);
void reset_stats_fcn(vector<struct ecasv_channel_stats>* chstats); // jkc: addition
float dB(float v) { return 10.0*log10(v*v); } // jkc: addition
void ecasv_set_buffered(void);
void ecasv_set_unbuffered(void);
int ecasv_kbhit();
/**
* Static global variables
*/
static const string ecatools_signalview_version = "20051112-10";
static bool ecasv_log_display_mode = false; // jkc: addition
static const double ecasv_clipped_threshold_const = 1.0f - 1.0f / 16384.0f;
static const int ecasv_bar_length_const = 32;
static const int ecasv_header_height_const = 9;
static const long int ecasv_rate_default_const = 50;
static const long int ecasv_buffersize_default_const = 128;
static unsigned char ecasv_bar_buffer[ecasv_bar_length_const + 1] = { 0 };
static bool ecasv_enable_debug, ecasv_enable_cumulative_mode;
static long int ecasv_buffersize, ecasv_rate_msec;
static string ecasv_input, ecasv_output, ecasv_format_string;
static int ecasv_chcount = 0;
static ECA_CONTROL_INTERFACE* ecasv_eci_repp = 0;
static sig_atomic_t done = 0;
static sig_atomic_t reset_stats = 0;
static int avg_peak_buffer_sz=100; // jkc: addition
#ifdef HAVE_TERMIOS_H
struct termios old_term, new_term;
#endif
/**
* Function definitions
*/
int main(int argc, char *argv[])
{
int res;
struct sigaction es_handler;
es_handler.sa_handler = ecasv_signal_handler;
sigemptyset(&es_handler.sa_mask);
es_handler.sa_flags = 0;
sigaction(SIGTERM, &es_handler, 0);
sigaction(SIGINT, &es_handler, 0);
sigaction(SIGQUIT, &es_handler, 0);
sigaction(SIGABRT, &es_handler, 0);
sigaction(SIGHUP, &es_handler, 0);
struct sigaction ign_handler;
ign_handler.sa_handler = SIG_IGN;
sigemptyset(&ign_handler.sa_mask);
ign_handler.sa_flags = 0;
/* ignore the following signals */
sigaction(SIGPIPE, &ign_handler, 0);
sigaction(SIGFPE, &ign_handler, 0);
ECA_CONTROL_INTERFACE eci;
eci.command("cs-add default");
eci.command("c-add default");
/* set engine buffersize */
eci.command("cs-set-param -b:" + kvu_numtostr(ecasv_buffersize));
/* in case JACK is used, do not send nor receive transport events */
eci.command("cs-set-param -G:jack,ecasignalview,notransport");
/* note: might change the cs options (-G, -z, etc) */
ecasv_parse_command_line(&eci,argc,argv);
if (ecasv_format_string.size() > 0) {
eci.command("cs-set-audio-format " + ecasv_format_string);
}
string format;
if (ecicpp_add_input(&eci, ecasv_input, &format) < 0) return -1;
cout << "Using audio format -f:" << format << "\n";
ecasv_chcount = ecicpp_format_channels(format);
cout << "Setting up " << ecasv_chcount << " separate channels for analysis." << endl;
if (ecicpp_add_output(&eci, ecasv_output, format) < 0) return -1;
ecasv_eci_repp = &eci;
vector<struct ecasv_channel_stats> chstats;
eci.command("cop-add -evp");
eci.command("cop-add -ev");
if (ecasv_enable_cumulative_mode == true) {
eci.command("cop-set 2,1,1");
}
eci.command("cop-select 1");
if (ecicpp_connect_chainsetup(&eci, "default") < 0) {
return -1;
}
int secs = 0, msecs = ecasv_rate_msec;
while(msecs > 999) {
++secs;
msecs -= 1000;
}
ecasv_output_init();
eci.command("start");
int chr=0; // jkc: addition
int rv=0; // jkc: addition
while(! done ) {
kvu_sleep(secs, msecs * 1000000);
res = ecasv_print_vu_meters(&eci, &chstats);
if (res < 0)
break;
#if defined(ECASV_USE_CURSES)
// jkc: addition until noted
if (ecasv_kbhit()) {
/* note: getch() is a curses.h function */
switch (chr=getch()) {
case 'q':
case 27: /* Esc */
case 'Q':
done=true;
break;
case ' ':
reset_stats_fcn(&chstats);
break;
}
}
// jkc: end of addition
#endif
}
ecasv_output_cleanup();
#ifdef ECASV_USE_CURSES
endwin();
#endif
return rv;
}
void ecasv_parse_command_line(ECA_CONTROL_INTERFACE *eci, int argc, char *argv[])
{
COMMAND_LINE cline = COMMAND_LINE (argc, argv);
if (cline.size() == 0 ||
cline.has("--version") ||
cline.has("--help") ||
cline.has("-h")) {
ecasv_print_usage();
exit(1);
}
ecasv_enable_debug = false;
ecasv_enable_cumulative_mode = false;
ecasv_rate_msec = 0;
ecasv_buffersize = 0;
cline.begin();
cline.next(); // 1st argument
while (cline.end() != true) {
string arg = cline.current();
if (arg.size() > 0) {
if (arg[0] != '-') {
if (ecasv_input == "")
ecasv_input = arg;
else
if (ecasv_output == "")
ecasv_output = arg;
}
else {
string prefix = kvu_get_argument_prefix(arg);
if (prefix == "b")
ecasv_buffersize = atol(kvu_get_argument_number(1, arg).c_str());
if (prefix == "c") ecasv_enable_cumulative_mode = true;
if (prefix == "d") ecasv_enable_debug = true;
if (prefix == "f")
ecasv_format_string = string(arg.begin() + 3, arg.end());
if (prefix == "I") ecasv_log_display_mode = false; // jkc: addition
if (prefix == "L") ecasv_log_display_mode = true; // jkc: addition
if (prefix == "r")
ecasv_rate_msec = atol(kvu_get_argument_number(1, arg).c_str());
if (prefix == "G" ||
prefix == "B" ||
(prefix.size() > 0 && prefix[0] == 'M') ||
prefix == "r" ||
prefix == "z") {
eci->command("cs-option " + arg);
}
}
}
cline.next();
}
ecasv_fill_defaults();
}
void ecasv_fill_defaults(void)
{
// ECA_RESOURCES ecarc;
if (ecasv_input.size() == 0) ecasv_input = "/dev/dsp";
if (ecasv_output.size() == 0) ecasv_output = "null";
if (ecasv_buffersize == 0) ecasv_buffersize = ecasv_buffersize_default_const;
if (ecasv_rate_msec == 0) ecasv_rate_msec = ecasv_rate_default_const;
if (ecasv_format_string.size() == 0) ecasv_format_string = "s16_le,2,44100,i";
// ecarc.resource("default-audio-format");
}
string ecasv_cop_to_string(ECA_CONTROL_INTERFACE* eci)
{
eci->command("cop-status");
return(eci->last_string());
}
void ecasv_output_init(void)
{
#ifdef ECASV_USE_CURSES
initscr();
erase();
int r=0; // jkc: added r for row indexing here and below
mvprintw(r++, 0, "ecasignalview v%s (%s) -- (C) K.Vehmanen, J.Cunningham", ecatools_signalview_version.c_str(), VERSION);
//mvprintw(r++, 0, "* (C) 1999-2005 Kai Vehmanen, Jeff Cunningham *\n");
//mvprintw(r++, 0, "******************************************************\n\n");
++r;
mvprintw(r++, 2, "Input/output: \"%s\" => \"%s\"",
ecasv_input.c_str(),ecasv_output.c_str());
double avg_length = (double)ecasv_rate_msec * avg_peak_buffer_sz;
mvprintw(r++, 2,
"Settings: %s refresh=%ldms bsize=%ld avg-length=%.0fms",
ecasv_format_string.c_str(), ecasv_rate_msec, ecasv_buffersize, avg_length);
/* mvprintw(r++, 0, "refresh rate = %ld (msec), buffer size = %ld, "
"avg-length = %.0f (msec)",
ecasv_rate_msec, ecasv_buffersize, avg_length); */
++r;
const char* bar="------------------------------------------------------------------------------\n";
mvprintw(r++, 0, bar);
mvprintw(r, 0, "channel");
if (ecasv_log_display_mode)
mvprintw(r++,38, "%s avg-peak dB max-peak dB clipped\n", ecasv_bar_buffer);
else
mvprintw(r++,38, "%s avg-peak max-peak clipped\n", ecasv_bar_buffer);
mvprintw(r++, 0, bar);
memset(ecasv_bar_buffer, ' ', ecasv_bar_length_const - 4);
ecasv_bar_buffer[ecasv_bar_length_const - 4] = 0;
mvprintw(r + ecasv_chcount + 3, 0, "Press spacebar to reset stats"); // jkc: addition
move(r + ecasv_chcount - 2, 0);
// 13 + 12
refresh();
#endif
}
void ecasv_output_cleanup(void)
{
#ifdef ECASV_USE_CURSES
endwin();
#endif
// FIXME: should be enabled
#if 0
if (ecasv_eci_repp != 0) {
cout << endl << endl << endl;
ecasv_eci_repp->command("cop-status");
}
#endif
}
// jkc: addition of reset_stats function
void reset_stats_fcn(vector<struct ecasv_channel_stats>* chstats)
{
#ifdef ECASV_USE_CURSES
vector<struct ecasv_channel_stats>::iterator s=chstats->begin();
while (s!=chstats->end()) {
s->last_peak=0;
s->max_peak=0;
s->drawn_peak=0;
s->clipped_samples=0;
s++;
}
#endif
}
// jkc: end of addition
int ecasv_print_vu_meters(ECA_CONTROL_INTERFACE* eci, vector<struct ecasv_channel_stats>* chstats)
{
int result = 0;
/* check wheter to reset peaks */
if (reset_stats) {
reset_stats = 0;
for(int n = 0; n < ecasv_chcount; n++) {
(*chstats)[n].max_peak = 0;
(*chstats)[n].clipped_samples = 0;
}
}
#ifdef ECASV_USE_CURSES
for(int n = 0; n < ecasv_chcount; n++) {
eci->command("copp-select " + kvu_numtostr(n + 1));
eci->command("copp-get");
if (eci->error()) {
result = -1;
break;
}
double value = eci->last_float();
ecasv_update_chstats(chstats, n, value);
ecasv_create_bar((*chstats)[n].drawn_peak, ecasv_bar_length_const, ecasv_bar_buffer);
// jkc: commented out following two lines and substituted what follows until noted
// mvprintw(ecasv_header_height_const+n, 0, "Ch-%02d: %s| %.5f %ld\n",
// n + 1, ecasv_bar_buffer, (*chstats)[n].max_peak, (*chstats)[n].clipped_samples);
// Calculate average peak value
if (ecasv_log_display_mode)
mvprintw(ecasv_header_height_const+n, 0,
"Ch-%02d: %s %.2f %.2f %ld\n",
n+1, ecasv_bar_buffer,
dB((*chstats)[n].avg_peak_val),
dB((*chstats)[n].max_peak),
(*chstats)[n].clipped_samples);
else
mvprintw(ecasv_header_height_const+n, 0,
"Ch-%02d: %s %.5f %.5f %ld\n",
n+1, ecasv_bar_buffer,
(*chstats)[n].avg_peak_val,
(*chstats)[n].max_peak,
(*chstats)[n].clipped_samples);
// jkc: end of substitution
}
move(ecasv_header_height_const + 2 + ecasv_chcount, 0);
refresh();
#else
cout << ecasv_cop_to_string(eci) << endl;
#endif
return result;
}
void ecasv_update_chstats(vector<struct ecasv_channel_stats>* chstats, int ch, double value)
{
/* 1. in case a new channel is encoutered */
if (static_cast<int>(chstats->size()) <= ch) {
chstats->resize(ch + 1);
// jkc: added until noted
(*chstats)[ch].last_peak=0;
(*chstats)[ch].drawn_peak=0;
(*chstats)[ch].max_peak=0;
(*chstats)[ch].clipped_samples=0;
(*chstats)[ch].avg_peak.resize(avg_peak_buffer_sz,0);
(*chstats)[ch].avg_peak_ptr=0;
// jkc: end of additions
}
/* 2. update last_peak and drawn_peak */
(*chstats)[ch].last_peak = value;
if ((*chstats)[ch].last_peak < (*chstats)[ch].drawn_peak) {
(*chstats)[ch].drawn_peak *= ((*chstats)[ch].last_peak / (*chstats)[ch].drawn_peak);
}
else {
(*chstats)[ch].drawn_peak = (*chstats)[ch].last_peak;
}
/* 3. update max_peak */
if (value > (*chstats)[ch].max_peak) {
(*chstats)[ch].max_peak = value;
}
/* 4. update clipped_samples counter */
if (value > ecasv_clipped_threshold_const) {
(*chstats)[ch].clipped_samples++;
}
// jkc: added until noted
/* 5. update running average vector */
(*chstats)[ch].avg_peak[(*chstats)[ch].avg_peak_ptr] = value;
(*chstats)[ch].avg_peak_ptr = ((*chstats)[ch].avg_peak_ptr == avg_peak_buffer_sz-1)?
0 : (*chstats)[ch].avg_peak_ptr+1;
vector<double>::iterator p=(*chstats)[ch].avg_peak.begin();
(*chstats)[ch].avg_peak_val=0;
while (p!=(*chstats)[ch].avg_peak.end()) { (*chstats)[ch].avg_peak_val+=*p++; }
(*chstats)[ch].avg_peak_val/=avg_peak_buffer_sz;
// jkc; end of addition
}
void ecasv_create_bar(double value, int barlen, unsigned char* barbuf)
{
int curlen = static_cast<int>(rint(((value / 1.0f) * barlen)));
for(int n = 0; n < barlen; n++) {
if (n <= curlen)
barbuf[n] = '*';
else
barbuf[n] = ' ';
}
}
/**
* Sets terminal to unbuffered mode (no echo,
* non-canonical input). -jkc
*/
void ecasv_set_unbuffered(void)
{
#ifdef HAVE_TERMIOS_H
tcgetattr( STDIN_FILENO, &old_term );
new_term = old_term;
new_term.c_lflag &= ~( ICANON | ECHO );
tcsetattr( STDIN_FILENO, TCSANOW, &new_term );
#endif
}
/**
* Sets terminal to buffered mode -jkc
*/
void ecasv_set_buffered(void)
{
#ifdef HAVE_TERMIOS_H
tcsetattr( STDIN_FILENO, TCSANOW, &old_term );
#endif
}
/**
* Reads a character from the terminal console. -jkc
*/
int ecasv_kbhit(void)
{
int result;
fd_set set;
struct timeval tv;
FD_ZERO(&set);
FD_SET(STDIN_FILENO,&set); /* watch stdin */
tv.tv_sec = 0;
tv.tv_usec = 0; /* don't wait */
/* quick peek at the input, to see if anything is there */
ecasv_set_unbuffered();
result = select( STDIN_FILENO+1,&set,NULL,NULL,&tv);
ecasv_set_buffered();
return result == 1;
}
void ecasv_print_usage(void)
{
cerr << "****************************************************************************\n";
cerr << "* ecasignalview, v" << ecatools_signalview_version << " (" << VERSION << ")\n";
cerr << "* Copyright 1999-2005 Kai Vehmanen, Jeffrey Cunningham\n";
cerr << "* Licensed under the terms of the GNU General Public License\n";
cerr << "****************************************************************************\n";
cerr << "\nUSAGE: ecasignalview [options] [input] [output] \n";
cerr << "\nOptions:\n";
cerr << "\t-b:buffersize\n";
// cerr << "\t\t-c (cumulative mode)\n";
cerr << "\t-d (debug mode)\n";
cerr << "\t-f:bits,channels,samplerate\n";
cerr << "\t-r:refresh_msec\n\n";
cerr << "\t-I (linear-scale)\n";
cerr << "\t-L (logarithmic-scale)\n";
}
void ecasv_signal_handler(int signum)
{
if (signum == SIGHUP) {
reset_stats = 1;
}
else {
cerr << "Interrupted... cleaning up.\n";
done=1;
}
}