Showing the changes to src/spectrogram.c (1 of 2 files). Show all
Unified · Split
| @@ -89,18 +89,16 @@ static enum_item const window_options[] = { · 88 unmodified lines | ||
| 89 | 89 | |
| 90 | 90 | typedef struct { |
| 91 | 91 | int y_size, dft_size, rows, cols, step_size, block_steps, block_num; |
| 92 | − double buf[MAX_DFT_SIZE]; | |
| 93 | − double dft_buf[MAX_DFT_SIZE]; | |
| 94 | − double window[MAX_DFT_SIZE]; | |
| 95 | − int bit_rev_table[100]; /* For Ooura fft */ | |
| 96 | − double sin_cos_table[dft_sc_len(MAX_DFT_SIZE)]; /* ditto */ | |
| 97 | − double magnitudes[(MAX_DFT_SIZE >> 1) + 1]; | |
| 98 | − float * dBfs; | |
| 99 | − double pixels_per_sec, block_norm, max; | |
| 100 | 92 | int dB_range, gain, style, read, end, end_min, last_end; |
| 93 | + int spectrum_points, perm; | |
| 101 | 94 | char const * out_name, * title, * comment; |
| 102 | − sox_bool slack_overlap, no_axes; | |
| 95 | + sox_bool high_colour, slack_overlap, no_axes, truncated; | |
| 103 | 96 | win_type_t win_type; |
| 97 | + double buf[MAX_DFT_SIZE], dft_buf[MAX_DFT_SIZE], window[MAX_DFT_SIZE]; | |
| 98 | + double pixels_per_sec, block_norm, max, magnitudes[(MAX_DFT_SIZE>>1) + 1]; | |
| 99 | + int bit_rev_table[100]; /* For Ooura fft */ | |
| 100 | + double sin_cos_table[dft_sc_len(MAX_DFT_SIZE)]; /* ditto */ | |
| 101 | + float * dBfs; | |
| 104 | 102 | } priv_t; |
| 105 | 103 | |
| 106 | 104 | #define GETOPT_NUMERIC(ch, name, min, max) case ch:{ \ |
| @@ -142,13 +140,16 @@ static int getopts(sox_effect_t * effp, int argc, char **argv) · 35 unmodified lines | ||
| 142 | 140 | |
| 143 | 141 | --argv, ++argc, optind = 1, opterr = 0; /* re-jig for getopt */ |
| 144 | 142 | p->pixels_per_sec = 100, p->y_size = 2, p->dB_range = 120;/* non-0 defaults */ |
| 143 | + p->spectrum_points = 249, p->perm = 1; | |
| 145 | 144 | p->style = 1, p->out_name = "spectrogram.png", p->comment = "Created by SoX"; |
| 146 | 145 | |
| 147 | − while ((c = getopt(argc, argv, "+x:y:z:g:w:st:c:amlo:")) != -1) switch (c) { | |
| 146 | + while ((c = getopt(argc, argv, "+x:y:z:Z:q:p:w:st:c:amlho:")) != -1) switch (c) { | |
| 148 | 147 | GETOPT_NUMERIC('x', pixels_per_sec, 1 , 5000) |
| 149 | 148 | GETOPT_NUMERIC('y', y_size , 1 , 1 + MAX_DFT_SIZE_SHIFT) |
| 150 | − GETOPT_NUMERIC('z', dB_range , 40 , 180) | |
| 151 | − GETOPT_NUMERIC('g', gain ,-100, 100) | |
| 149 | + GETOPT_NUMERIC('z', dB_range , 20 , 180) | |
| 150 | + GETOPT_NUMERIC('Z', gain ,-100, 100) | |
| 151 | + GETOPT_NUMERIC('q', spectrum_points, 0 , p->spectrum_points) | |
| 152 | + GETOPT_NUMERIC('p', perm , 1 , 6) | |
| 152 | 153 | case 'w': p->win_type = enum_option(c, window_options); break; |
| 153 | 154 | case 's': p->slack_overlap = sox_true; break; |
| 154 | 155 | case 't': p->title = optarg; break; |
| @@ -156,11 +157,14 @@ static int getopts(sox_effect_t * effp, int argc, char **argv) · 1 unmodified line | ||
| 156 | 157 | case 'a': p->no_axes = sox_true; break; |
| 157 | 158 | case 'm': monochrome = sox_true; break; |
| 158 | 159 | case 'l': light_background = sox_true; break; |
| 160 | + case 'h': p->high_colour = sox_true; break; | |
| 159 | 161 | case 'o': p->out_name = optarg; break; |
| 160 | 162 | default: sox_fail("unknown option `-%c'", optopt); return lsx_usage(effp); |
| 161 | 163 | } |
| 162 | − p->y_size -= 1; | |
| 164 | + p->gain = - p->gain; | |
| 165 | + --p->y_size, --p->perm; | |
| 163 | 166 | p->style = 2 * monochrome + light_background; |
| 167 | + p->spectrum_points += 2; | |
| 164 | 168 | argc -= optind, optind = callers_optind, opterr = callers_opterr; |
| 165 | 169 | return argc || p->win_type == INT_MAX? lsx_usage(effp) : SOX_SUCCESS; |
| 166 | 170 | } |
| @@ -219,6 +223,7 @@ static int do_column(sox_effect_t * effp) · 52 unmodified lines | ||
| 219 | 223 | |
| 220 | 224 | if (p->cols == MAX_COLS) { |
| 221 | 225 | sox_warn("PNG truncated at %g seconds", (double)MAX_COLS * p->step_size * p->block_steps / effp->in_signal.rate); |
| 226 | + p->truncated = sox_true; | |
| 222 | 227 | return SOX_EOF; |
| 223 | 228 | } |
| 224 | 229 | ++p->cols; |
| @@ -275,7 +280,7 @@ static int drain(sox_effect_t * effp, sox_sample_t * obuf_, sox_size_t * osamp) · 50 unmodified lines | ||
| 275 | 280 | { |
| 276 | 281 | priv_t * p = (priv_t *)effp->priv; |
| 277 | 282 | |
| 278 | − if (p->cols != MAX_COLS) { | |
| 283 | + if (!p->truncated) { | |
| 279 | 284 | sox_sample_t * ibuf = calloc(p->dft_size, sizeof(*ibuf)); |
| 280 | 285 | sox_sample_t * obuf = calloc(p->dft_size, sizeof(*obuf)); |
| 281 | 286 | sox_size_t isamp = (p->dft_size - p->step_size) / 2; |
| @@ -298,52 +303,72 @@ static int drain(sox_effect_t * effp, sox_sample_t * obuf_, sox_size_t * osamp) · 16 unmodified lines | ||
| 298 | 303 | return SOX_SUCCESS; |
| 299 | 304 | } |
| 300 | 305 | |
| 301 | −#define spectrum_points (1 + 250) | |
| 302 | −enum {Background = spectrum_points, Text, Labels, Grid, palette_size}; | |
| 306 | +enum {Background, Text, Labels, Grid, fixed_palette}; | |
| 303 | 307 | |
| 304 | −static unsigned colour(double x, int range) | |
| 308 | +static unsigned colour(priv_t const * p, double x) | |
| 305 | 309 | { |
| 306 | − x = range_limit(x, -range, 0); | |
| 307 | − return (1 + x / range) * (spectrum_points - 1) + .5; | |
| 310 | + unsigned c = x < -p->dB_range? 0 : x >= 0? p->spectrum_points - 1 : | |
| 311 | + 1 + (1 + x / p->dB_range) * (p->spectrum_points - 2); | |
| 312 | + return fixed_palette + c; | |
| 308 | 313 | } |
| 309 | 314 | |
| 310 | −static void make_palette(png_byte * palette, int style) | |
| 315 | +static void make_palette(priv_t const * p, png_color * palette) | |
| 311 | 316 | { |
| 312 | − int i, j, at; | |
| 313 | − for (i = j = 0; j < spectrum_points; i += 3, ++j) { | |
| 314 | − double r, g, b, x = (double)j / (spectrum_points - 1); | |
| 315 | − if (style > 1) | |
| 316 | − palette[i+2] = palette[i+1] = palette[i+0] = style == 2? j : 255 - j; | |
| 317 | − else { | |
| 318 | − at = style? (spectrum_points - 1) * 3 - i : i; | |
| 319 | − if (x < .16) r = 0; | |
| 320 | − else if (x < .72) r = .99 * sin((x - .16) / .56 * M_PI / 2); | |
| 321 | − else r = .01 * (x - .72) / .28 + .99; | |
| 322 | − if (x < .52) g = 0; | |
| 323 | − else if (x < .86) g = .99 * .5 * (1 - cos((x - .52) / .34 * M_PI)); | |
| 324 | − else g = .01 * (x - .86) / .14 + .99; | |
| 325 | − if (x < .34) b = .5 * sin((x - .00) / .34 * M_PI / 2); | |
| 326 | − else if (x < .63) b = .5 * .5 * (1 + cos((x - .34) / .29 * M_PI)); | |
| 327 | − else b = (1 - cos((x - .63) / .37 * M_PI / 2)); | |
| 328 | − palette[at + 0] = r * 255 + .5; | |
| 329 | − palette[at + 1] = g * 255 + .5; | |
| 330 | − palette[at + 2] = b * 255 + .5; | |
| 331 | − } | |
| 317 | + int i; | |
| 318 | + | |
| 319 | + if (p->style & 1) { | |
| 320 | + memcpy(palette++, (p->style & 2)? "\337\337\337":"\335\330\320", 3); | |
| 321 | + memcpy(palette++, "\0\0\0" , 3); | |
| 322 | + memcpy(palette++, "\077\077\077", 3); | |
| 323 | + memcpy(palette++, "\077\077\077", 3); | |
| 324 | + } else { | |
| 325 | + memcpy(palette++, "\0\0\0" , 3); | |
| 326 | + memcpy(palette++, "\377\377\377", 3); | |
| 327 | + memcpy(palette++, "\277\277\277", 3); | |
| 328 | + memcpy(palette++, "\177\177\177", 3); | |
| 332 | 329 | } |
| 333 | − i = spectrum_points * 3; | |
| 334 | − switch (style) { | |
| 335 | − case 0: case 2: | |
| 336 | − memcpy(palette + i, "\0\0\0" , 3), i+= 3; | |
| 337 | − memcpy(palette + i, "\377\377\377", 3), i+= 3; | |
| 338 | − memcpy(palette + i, "\277\277\277", 3), i+= 3; | |
| 339 | − memcpy(palette + i, "\177\177\177", 3), i+= 3; | |
| 340 | − return; | |
| 341 | − case 1: memcpy(palette + i, "\335\330\320", 3), i+= 3; break; | |
| 342 | − default: memcpy(palette + i, "\337\337\337", 3), i+= 3; | |
| 330 | + for (i = 0; i < p->spectrum_points; ++i) { | |
| 331 | + double c[3] , x = (double)i / (p->spectrum_points - 1); | |
| 332 | + int at = (p->style & 1)? p->spectrum_points - 1 - i : i; | |
| 333 | + if (p->style > 1) { | |
| 334 | + c[2] = c[1] = c[0] = x; | |
| 335 | + if (p->high_colour) { | |
| 336 | + c[(1 + p->perm) % 3] = x < .4? 0 : 5 / 3. * (x - .4); | |
| 337 | + if (p->perm < 3) | |
| 338 | + c[(2 + p->perm) % 3] = x < .4? 0 : 5 / 3. * (x - .4); | |
| 339 | + } | |
| 340 | + palette[at].red = .5 + 255 * c[0]; | |
| 341 | + palette[at].green= .5 + 255 * c[1]; | |
| 342 | + palette[at].blue = .5 + 255 * c[2]; | |
| 343 | + continue; | |
| 344 | + } | |
| 345 | + if (p->high_colour) { | |
| 346 | + static const int states[3][7] = { | |
| 347 | + {4,5,0,0,2,1,1}, {0,0,2,1,1,3,2}, {4,1,1,3,0,0,2}}; | |
| 348 | + int j, phase_num = min(6, x * 7); | |
| 349 | + for (j = 0; j < 3; ++j) switch (states[j][phase_num]) { | |
| 350 | + case 0: c[j] = 0; break; | |
| 351 | + case 1: c[j] = 1; break; | |
| 352 | + case 2: c[j] = sin((7 * x - phase_num) * M_PI / 2); break; | |
| 353 | + case 3: c[j] = cos((7 * x - phase_num) * M_PI / 2); break; | |
| 354 | + case 4: c[j] = 7 * x - phase_num; break; | |
| 355 | + case 5: c[j] = 1 - (7 * x - phase_num); break; | |
| 356 | + } | |
| 357 | + } else { | |
| 358 | + if (x < .16) c[0] = 0; | |
| 359 | + else if (x < .72) c[0] = .99 * sin((x - .16) / .56 * M_PI / 2); | |
| 360 | + else c[0] = .01 * (x - .72) / .28 + .99; | |
| 361 | + if (x < .52) c[1] = 0; | |
| 362 | + else if (x < .86) c[1] = .99 * .5 * (1 - cos((x - .52) / .34 * M_PI)); | |
| 363 | + else c[1] = .01 * (x - .86) / .14 + .99; | |
| 364 | + if (x < .34) c[2] = .5 * sin((x - .00) / .34 * M_PI / 2); | |
| 365 | + else if (x < .63) c[2] = .5 * .5 * (1 + cos((x - .34) / .29 * M_PI)); | |
| 366 | + else c[2] = (1 - cos((x - .63) / .37 * M_PI / 2)); | |
| 367 | + } | |
| 368 | + palette[at].red = .5 + 255 * c[p->perm % 3]; | |
| 369 | + palette[at].green= .5 + 255 * c[(1 + p->perm + (p->perm % 2)) % 3]; | |
| 370 | + palette[at].blue = .5 + 255 * c[(2 + p->perm - (p->perm % 2)) % 3]; | |
| 343 | 371 | } |
| 344 | − memcpy(palette + i, "\0\0\0" , 3), i+= 3; | |
| 345 | − memcpy(palette + i, "\077\077\077", 3), i+= 3; | |
| 346 | − memcpy(palette + i, "\077\077\077", 3), i+= 3; | |
| 347 | 372 | } |
| 348 | 373 | |
| 349 | 374 | static const Bytef fixed[] = |
| @@ -405,7 +430,7 @@ static int axis(double to, int max_steps, double * limit, char * * prefix) · 55 unmodified lines | ||
| 405 | 430 | scale = pow(10., -3. * prefix_num); |
| 406 | 431 | } |
| 407 | 432 | *prefix = "pnum-kMGTPE" + prefix_num + (prefix_num? 4 : 11); |
| 408 | − *limit = to * scale + .5; | |
| 433 | + *limit = to * scale; | |
| 409 | 434 | return step * scale + .5; |
| 410 | 435 | } |
| 411 | 436 | |
| @@ -427,7 +452,7 @@ static int stop(sox_effect_t * effp) · 15 unmodified lines | ||
| 427 | 452 | png_bytepp png_rows = malloc(rows * sizeof(*png_rows)); |
| 428 | 453 | png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0,0); |
| 429 | 454 | png_infop png_info = png_create_info_struct(png); |
| 430 | − png_byte palette[palette_size * 3]; | |
| 455 | + png_color palette[256]; | |
| 431 | 456 | int i, j, step, tick_len = 2 + do_frame; |
| 432 | 457 | char text[200], * prefix; |
| 433 | 458 | double limit; |
| @@ -443,10 +468,10 @@ static int stop(sox_effect_t * effp) · 9 unmodified lines | ||
| 443 | 468 | sox_debug("signal-max=%g", p->max); |
| 444 | 469 | font = malloc(font_len); |
| 445 | 470 | assert(uncompress(font, &font_len, fixed, sizeof(fixed)-1) == Z_OK); |
| 446 | − make_palette(palette, p->style); | |
| 471 | + make_palette(p, palette); | |
| 447 | 472 | memset(pixels, Background, cols * rows * sizeof(*pixels)); |
| 448 | 473 | png_init_io(png, file); |
| 449 | − png_set_PLTE(png, png_info, (png_colorp)palette, palette_size); | |
| 474 | + png_set_PLTE(png, png_info, palette, fixed_palette + p->spectrum_points); | |
| 450 | 475 | png_set_IHDR(png, png_info, (size_t)cols, (size_t)rows, 8, |
| 451 | 476 | PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, |
| 452 | 477 | PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); |
| @@ -461,7 +486,7 @@ static int stop(sox_effect_t * effp) · 8 unmodified lines | ||
| 461 | 486 | /* Spectrogram */ |
| 462 | 487 | for (j = 0; j < p->rows; ++j) { |
| 463 | 488 | for (i = 0; i < p->cols; ++i) |
| 464 | − pixel(left + i, below + j) = colour(p->dBfs[i*p->rows + j], p->dB_range); | |
| 489 | + pixel(left + i, below + j) = colour(p, p->dBfs[i*p->rows + j]); | |
| 465 | 490 | if (do_frame) /* Frame verticals */ |
| 466 | 491 | pixel(left - 1, below + j) = pixel(left + p->cols,below + j) = Grid; |
| 467 | 492 | } |
| @@ -503,7 +528,7 @@ static int stop(sox_effect_t * effp) · 35 unmodified lines | ||
| 503 | 528 | /* Z-axis */ |
| 504 | 529 | print_at(cols - right - 2 - font_X, below - 13, Text, "dBFS");/* Axis label */ |
| 505 | 530 | for (j = 0; j < p->rows; ++j) { /* Spectrum */ |
| 506 | − png_byte b = colour(p->dB_range * (j / (p->rows - 1.) - 1), p->dB_range); | |
| 531 | + png_byte b = colour(p, p->dB_range * (j / (p->rows - 1.) - 1)); | |
| 507 | 532 | for (i = 0; i < spectrum_width; ++i) |
| 508 | 533 | pixel(cols - right - 1 - i, below + j) = b; |
| 509 | 534 | } |
| @@ -528,18 +553,21 @@ sox_effect_handler_t const * sox_spectrogram_effect_fn(void) · 18 unmodified lines | ||
| 528 | 553 | { |
| 529 | 554 | static sox_effect_handler_t handler = { |
| 530 | 555 | "spectrogram", "[options]\n" |
| 531 | − " -x num X-axis pixels/second, default 100\n" | |
| 532 | − " -y num Y-axis resolution (1 - 4), default 2\n" | |
| 533 | − " -z num Z-axis (colour) range in dB, default 120\n" | |
| 534 | − " -g num Apply num dB gain; to shift Z-axis\n" | |
| 535 | − " -w name Window: Hann (default), Hamming, Bartlett, Rectangular, Kaiser\n" | |
| 536 | − " -s Slack overlap\n" | |
| 537 | − " -a Suppress axes' lines\n" | |
| 538 | − " -t text Title text\n" | |
| 539 | − " -c text Comment text\n" | |
| 540 | − " -l Light background\n" | |
| 541 | − " -m Monochrome display\n" | |
| 542 | − " -o text output file name, default `spectrogram.png'\n", | |
| 556 | + "\t-x num\tX-axis pixels/second, default 100\n" | |
| 557 | + "\t-y num\tY-axis resolution (1 - 4), default 2\n" | |
| 558 | + "\t-z num\tZ-axis range in dB, default 120\n" | |
| 559 | + "\t-Z num\tZ-axis maximum in dBFS, default 0\n" | |
| 560 | + "\t-q num\tZ-axis quantisation, default 249\n" | |
| 561 | + "\t-w name\tWindow: Hann (default), Hamming, Bartlett, Rectangular, Kaiser\n" | |
| 562 | + "\t-s\tSlack overlap\n" | |
| 563 | + "\t-a\tSuppress axis lines\n" | |
| 564 | + "\t-l\tLight background\n" | |
| 565 | + "\t-m\tMonochrome\n" | |
| 566 | + "\t-h\tHigh colour\n" | |
| 567 | + "\t-p num\tPermute colours\n" | |
| 568 | + "\t-t text\tTitle text\n" | |
| 569 | + "\t-c text\tComment text\n" | |
| 570 | + "\t-o text\toutput file name, default `spectrogram.png'\n", | |
| 543 | 571 | 0, getopts, start, flow, drain, stop, NULL, sizeof(priv_t)}; |
| 544 | 572 | return &handler; |
| 545 | 573 | } |