|
@@ -1,6 +1,6 @@
|
|
|
/*
|
|
|
sspect - A simple spectrogram
|
|
|
- Copyright (C) 2018 Emily, emily@countermail.com
|
|
|
+ Copyright (C) 2018, 2019 Emily, emily@countermail.com
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
it under the terms of the GNU Affero General Public License as
|
|
@@ -48,6 +48,10 @@ struct Param { // parameters object
|
|
|
int devicenumber; // ALSA device number
|
|
|
int windowtype; // DFT windowing function type
|
|
|
int twowinsize; // power of 2 giving DFT win_size (N) in samples
|
|
|
+ float freqline[100]; // at which frequencies to draw a line
|
|
|
+ bool drawline = false; // has freqline been used?
|
|
|
+ int nfreqlines = 0; // number of actual frequencies added to freqline
|
|
|
+ bool drawcentroid = false; // has freqline been used?
|
|
|
}; // note trailing ;
|
|
|
Param param; // global parameters object
|
|
|
|
|
@@ -81,7 +85,7 @@ void smallText(float x, float y, char *string) {
|
|
|
class audioInput {
|
|
|
public:
|
|
|
char *chunk;
|
|
|
- float *b, *specslice, *bwin, *winf, *sg;
|
|
|
+ float *b, *specslice, *bwin, *winf, *sg, *centroids;
|
|
|
char *sgb;
|
|
|
int b_ind, b_size, n_f, n_tw, sg_size, win_size;
|
|
|
float dt, t_memory, Hz_per_pixel;
|
|
@@ -162,6 +166,9 @@ class audioInput {
|
|
|
n_f = 560; // # freqs ...spectrogram stuff
|
|
|
specslice = new float[n_f];
|
|
|
n_tw = 940; // # time windows: should be multiple of 4 for glDrawPixels
|
|
|
+ centroids = new float[n_tw]; // array for storing centroids
|
|
|
+ for (int i=0; i<n_tw; ++i)
|
|
|
+ centroids[i]=0;
|
|
|
sg_size = n_f * n_tw;
|
|
|
sg = new float[sg_size]; // spectrogram array float
|
|
|
sgb = new char[sg_size]; // spectrogram array 8-bit
|
|
@@ -463,9 +470,6 @@ class scene
|
|
|
for (i=0;i<n_tics;++i) {
|
|
|
glVertex2d(ytic_bot, tics[i]); glVertex2d(ytic_top, tics[i]);
|
|
|
}
|
|
|
- glVertex2d(ytic_top,175); glVertex2d(x1,175); // lower female range, F3
|
|
|
- glVertex2d(ytic_top,220); glVertex2d(x1,220); // average female range, A3
|
|
|
- glVertex2d(ytic_top,262); glVertex2d(x1,262); // middle C, C4
|
|
|
glEnd();
|
|
|
for (i=0;i<n_tics;++i) {
|
|
|
sprintf(label,"%.6g",tics[i]);
|
|
@@ -474,6 +478,32 @@ class scene
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ void drawLines(float x0, float x1) // draws lines in [x0,x1]x{param.freqlines}
|
|
|
+ {
|
|
|
+ glColor4f(0.7, 1.0, 1.0, 1);
|
|
|
+ glDisable(GL_LINE_SMOOTH); glLineWidth(1);
|
|
|
+ glBegin(GL_LINES);
|
|
|
+ if(param.drawline) {
|
|
|
+ for (int n=0; n<param.nfreqlines; ++n) {
|
|
|
+ glVertex2d(x0,param.freqline[n]); glVertex2d(x1,param.freqline[n]); // draw line at frequency freqline[n]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ glEnd();
|
|
|
+ }
|
|
|
+
|
|
|
+ void drawCentroids(float x0, float x1) // draws lines in [x0,x1]x{param.freqlines}
|
|
|
+ {
|
|
|
+ glColor4f(0.7, 1.0, 1.0, 1);
|
|
|
+ glDisable(GL_LINE_SMOOTH); glLineWidth(1);
|
|
|
+ glBegin(GL_LINES);
|
|
|
+ float dt = (x1-x0)/ai->n_tw;
|
|
|
+ for (int i=0; i < ai->n_tw; ++i) {
|
|
|
+ glVertex2d(x0+i*dt, ai->centroids[i]);
|
|
|
+ glVertex2d(x0+(i+1)*dt, ai->centroids[i+1]);
|
|
|
+ }
|
|
|
+ glEnd();
|
|
|
+ }
|
|
|
+
|
|
|
void spectrogram() // show spectrogram as 2D pixel array, w/ axes............
|
|
|
{
|
|
|
float x0=0.05, y0=0.22; // bot-left location in viewport (as unit square)
|
|
@@ -512,6 +542,9 @@ class scene
|
|
|
// plots now occur in physical t (s) and f (Hz) units, relative to start_t..
|
|
|
char xlab[] = "t(s)", ylab[] = "f(Hz)";
|
|
|
drawAxes(run_time-max_t, run_time, 0 - 1e-7, max_Hz, 2.0, 2.0, 2, 0, xlab, ylab);
|
|
|
+ drawLines(run_time-max_t, run_time);
|
|
|
+ if(param.drawcentroid)
|
|
|
+ drawCentroids(run_time-max_t, run_time);
|
|
|
// 1e-7 is hack to show 0 Hz
|
|
|
if (freqind) { // horiz freq readout line, in Hz/sec coords, 1/17/16
|
|
|
// reverse-engineer the freq from current mouse y in pixels (yuk)...
|
|
@@ -672,22 +705,39 @@ void computespecslice(audioInput *ai) // windowed spectral slice!
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-void scroll_sg(audioInput *ai, float *newcol)
|
|
|
+void scroll_sg(audioInput *ai, float *newcol, float *centroids)
|
|
|
// Scroll spectrogram data 1 pixel in t direction, add new col & make 8bit
|
|
|
{
|
|
|
int i, j, n = ai->n_tw;
|
|
|
|
|
|
- for ( i=0; i<n-1; ++i) // float: NB x (ie t) is the fast storage direc
|
|
|
+ for ( i=0; i<n; ++i) // float: NB x (ie t) is the fast storage direc
|
|
|
for ( j=0; j<ai->n_f; ++j)
|
|
|
ai->sg[j*n + i] = ai->sg[j*n + i + 1];
|
|
|
|
|
|
for ( i=0; i<n; ++i) // scroll the 8bit char data too
|
|
|
for ( j=0; j<ai->n_f; ++j)
|
|
|
ai->sgb[j*n + i] = ai->sgb[j*n + i + 1];
|
|
|
-
|
|
|
- for ( j=0; j<ai->n_f; ++j) // set the last col of float data
|
|
|
+
|
|
|
+
|
|
|
+ for ( i=0; i<n; ++i)
|
|
|
+ centroids[i] = centroids[i+1];
|
|
|
+
|
|
|
+ // Calculate the new centroid
|
|
|
+ float num = 0;
|
|
|
+ float denom = 0;
|
|
|
+ float curr_freq;
|
|
|
+ for (j=0; j<ai->n_f; ++j) {
|
|
|
+ curr_freq = ai->Hz_per_pixel * j; // frequency whose amplitude is newcol[j]
|
|
|
+ denom += newcol[j];
|
|
|
+ num += newcol[j] * curr_freq;
|
|
|
+ }
|
|
|
+ float centroid = num / denom;
|
|
|
+ centroids[n] = centroid;
|
|
|
+
|
|
|
+ for ( j=0; j<ai->n_f; ++j) { // set the last col of float data
|
|
|
ai->sg[j*n + n - 1] = newcol[j];
|
|
|
-
|
|
|
+ }
|
|
|
+
|
|
|
for ( j=0; j<ai->n_f; ++j) { // color xform for last col of 8bit data
|
|
|
ai->sgb[j*n + n - 1] = scn.color_byte(newcol[j]);
|
|
|
}
|
|
@@ -713,7 +763,7 @@ void idle(void) // put numerical intensive part here? only scrolling
|
|
|
|
|
|
++scn.scroll_count;
|
|
|
if (scn.scroll_count==scn.scroll_fac) {
|
|
|
- scroll_sg(scn.ai, scn.ai->specslice); // add spec slice to sg & scroll
|
|
|
+ scroll_sg(scn.ai, scn.ai->specslice, scn.ai->centroids); // add spec slice to sg & scroll
|
|
|
scn.scroll_count = 0;
|
|
|
}
|
|
|
}
|
|
@@ -792,14 +842,18 @@ void motion(int x, int y) // handles mouse drag effects
|
|
|
}
|
|
|
|
|
|
const char *helptext[] = {
|
|
|
- " glSpect: real-time OpenGL spectrogram. Alex Barnett, Dec 2010\n",
|
|
|
- " (based on Luke Campagnola glScope)\n\n",
|
|
|
- "Usage: glspect [-f] [-v] [-d <device_number>] [-sf <scroll_factor>] [-w <windowtype>] [-t twowinsize]\n\n",
|
|
|
+ " Simple Spectrogram: real-time OpenGL spectrogram.\n",
|
|
|
+ " (based on Alex Barnett glSpect, Dec 2010 and Luke Campagnola glScope)\n\n",
|
|
|
+ "Usage: sspect [-f] [-v] [-d <device_number>] [-sf <scroll_factor>] [-w <windowtype>] [-t twowinsize] [-fl freqline] [-c]\n\n",
|
|
|
"Command line arguments:\n",
|
|
|
+ "-f fullscreen\n",
|
|
|
+ "-v verbose mode\n",
|
|
|
"device_number = 0,1,... the ALSA input device number (default 0)\n",
|
|
|
"windowtype = \t0 (no window)\n\t\t1 (Hann)\n\t\t2 (Gaussian trunc at +-4sigma) (default)\n",
|
|
|
"scroll_factor = 1,2,... # vSyncs (60Hz) to wait per scroll pixel (default 1)\n",
|
|
|
- "twowinsize = 11,12,...,16 is power of 2 giving FFT win_size N (default 12)\n\t(Note: this controls the vertical frequency resolution and range)\n\n",
|
|
|
+ "twowinsize = 11,12,...,16 is power of 2 giving FFT win_size N (default 12)\n\t(Note: this controls the vertical frequency resolution and range)\n",
|
|
|
+ "freqline draws at line at frequency freqline. Several -fl options can be used to draw several lines\n",
|
|
|
+ "-c draws the centroid line\n\n",
|
|
|
"Keys & mouse: \tarrows or middle button drag - brightness/contrast\n",
|
|
|
"\t\tleft button shows horizontal frequency readoff line\n",
|
|
|
"\t\tright button shows horizontal frequency readoff with multiples\n",
|
|
@@ -817,6 +871,7 @@ int main(int argc, char** argv)
|
|
|
param.devicenumber = 0; // ALSA input device number
|
|
|
param.windowtype = 2; // Gaussian
|
|
|
param.twowinsize = 12; // 4096 samples
|
|
|
+ param.drawcentroid = false;
|
|
|
|
|
|
for (int i=1; i<argc; ++i) { // .....Parse cmd line options....
|
|
|
if (!strcmp(argv[i], "-f")) // option -f makes full screen
|
|
@@ -836,6 +891,12 @@ int main(int argc, char** argv)
|
|
|
} else if (!strcmp(argv[i], "-d")) {
|
|
|
sscanf(argv[++i], "%d", ¶m.devicenumber); // read in devicenumber
|
|
|
if (param.devicenumber<0) param.devicenumber=0; // sanitize it
|
|
|
+ } else if (!strcmp(argv[i], "-fl")) {
|
|
|
+ sscanf(argv[++i], "%f", ¶m.freqline[param.nfreqlines]); // read in at which frequency to draw a line
|
|
|
+ param.drawline = true;
|
|
|
+ param.nfreqlines++;
|
|
|
+ } else if (!strcmp(argv[i], "-c")) {
|
|
|
+ param.drawcentroid = true;
|
|
|
} else { // misuse (or -h): print out usage text...
|
|
|
fprintf(stderr, "bad command line option %s\n\n", argv[i]);
|
|
|
for (int i=0; helptext[i]; i++) fprintf(stderr, "%s", helptext[i]);
|
|
@@ -852,7 +913,7 @@ int main(int argc, char** argv)
|
|
|
glutEnterGameMode(); // start fullscreen game mode
|
|
|
} else {
|
|
|
glutInitWindowSize(1024, 768); // window same size as XGA
|
|
|
- int mainWindow = glutCreateWindow("glSpect by Alex Barnett, Dec 2010");
|
|
|
+ int mainWindow = glutCreateWindow("sspect");
|
|
|
// glutFullScreen(); // maximizes window, but is not game mode
|
|
|
}
|
|
|
|