| @@ -89,18 +89,16 @@ static enum_item const window_options[] = { · 88 unmodified lines |
| 89 | | 89 | |
| 90 | typedef struct { | 90 | typedef struct { |
| 91 | int y_size, dft_size, rows, cols, step_size, block_steps, block_num; | 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 | int dB_range, gain, style, read, end, end_min, last_end; | 92 | int dB_range, gain, style, read, end, end_min, last_end; |
| | 93 | int spectrum_points, perm; |
| 101 | char const * out_name, * title, * comment; | 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 | win_type_t win_type; | 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 | } priv_t; | 102 | } priv_t; |
| 105 | | 103 | |
| 106 | #define GETOPT_NUMERIC(ch, name, min, max) case ch:{ \ | 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 | --argv, ++argc, optind = 1, opterr = 0; /* re-jig for getopt */ | 141 | --argv, ++argc, optind = 1, opterr = 0; /* re-jig for getopt */ |
| 144 | p->pixels_per_sec = 100, p->y_size = 2, p->dB_range = 120;/* non-0 defaults */ | 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 | p->style = 1, p->out_name = "spectrogram.png", p->comment = "Created by SoX"; | 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 | GETOPT_NUMERIC('x', pixels_per_sec, 1 , 5000) | 147 | GETOPT_NUMERIC('x', pixels_per_sec, 1 , 5000) |
| 149 | GETOPT_NUMERIC('y', y_size , 1 , 1 + MAX_DFT_SIZE_SHIFT) | 148 | GETOPT_NUMERIC('y', y_size , 1 , 1 + MAX_DFT_SIZE_SHIFT) |
| 150 | GETOPT_NUMERIC('z', dB_range , 40 , 180) | 149 | GETOPT_NUMERIC('z', dB_range , 20 , 180) |
| 151 | GETOPT_NUMERIC('g', gain ,-100, 100) | 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 | case 'w': p->win_type = enum_option(c, window_options); break; | 153 | case 'w': p->win_type = enum_option(c, window_options); break; |
| 153 | case 's': p->slack_overlap = sox_true; break; | 154 | case 's': p->slack_overlap = sox_true; break; |
| 154 | case 't': p->title = optarg; break; | 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 | case 'a': p->no_axes = sox_true; break; | 157 | case 'a': p->no_axes = sox_true; break; |
| 157 | case 'm': monochrome = sox_true; break; | 158 | case 'm': monochrome = sox_true; break; |
| 158 | case 'l': light_background = sox_true; break; | 159 | case 'l': light_background = sox_true; break; |
| | 160 | case 'h': p->high_colour = sox_true; break; |
| 159 | case 'o': p->out_name = optarg; break; | 161 | case 'o': p->out_name = optarg; break; |
| 160 | default: sox_fail("unknown option `-%c'", optopt); return lsx_usage(effp); | 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 | p->style = 2 * monochrome + light_background; | 166 | p->style = 2 * monochrome + light_background; |
| | 167 | p->spectrum_points += 2; |
| 164 | argc -= optind, optind = callers_optind, opterr = callers_opterr; | 168 | argc -= optind, optind = callers_optind, opterr = callers_opterr; |
| 165 | return argc || p->win_type == INT_MAX? lsx_usage(effp) : SOX_SUCCESS; | 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 | if (p->cols == MAX_COLS) { | 224 | if (p->cols == MAX_COLS) { |
| 221 | sox_warn("PNG truncated at %g seconds", (double)MAX_COLS * p->step_size * p->block_steps / effp->in_signal.rate); | 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 | return SOX_EOF; | 227 | return SOX_EOF; |
| 223 | } | 228 | } |
| 224 | ++p->cols; | 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 | priv_t * p = (priv_t *)effp->priv; | 281 | priv_t * p = (priv_t *)effp->priv; |
| 277 | | 282 | |
| 278 | if (p->cols != MAX_COLS) { | 283 | if (!p->truncated) { |
| 279 | sox_sample_t * ibuf = calloc(p->dft_size, sizeof(*ibuf)); | 284 | sox_sample_t * ibuf = calloc(p->dft_size, sizeof(*ibuf)); |
| 280 | sox_sample_t * obuf = calloc(p->dft_size, sizeof(*obuf)); | 285 | sox_sample_t * obuf = calloc(p->dft_size, sizeof(*obuf)); |
| 281 | sox_size_t isamp = (p->dft_size - p->step_size) / 2; | 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 | return SOX_SUCCESS; | 303 | return SOX_SUCCESS; |
| 299 | } | 304 | } |
| 300 | | 305 | |
| 301 | #define spectrum_points (1 + 250) | 306 | enum {Background, Text, Labels, Grid, fixed_palette}; |
| 302 | enum {Background = spectrum_points, Text, Labels, Grid, palette_size}; | | |
| 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); | 310 | unsigned c = x < -p->dB_range? 0 : x >= 0? p->spectrum_points - 1 : |
| 307 | return (1 + x / range) * (spectrum_points - 1) + .5; | 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; | 317 | int i; |
| 313 | for (i = j = 0; j < spectrum_points; i += 3, ++j) { | 318 | |
| 314 | double r, g, b, x = (double)j / (spectrum_points - 1); | 319 | if (p->style & 1) { |
| 315 | if (style > 1) | 320 | memcpy(palette++, (p->style & 2)? "\337\337\337":"\335\330\320", 3); |
| 316 | palette[i+2] = palette[i+1] = palette[i+0] = style == 2? j : 255 - j; | 321 | memcpy(palette++, "\0\0\0" , 3); |
| 317 | else { | 322 | memcpy(palette++, "\077\077\077", 3); |
| 318 | at = style? (spectrum_points - 1) * 3 - i : i; | 323 | memcpy(palette++, "\077\077\077", 3); |
| 319 | if (x < .16) r = 0; | 324 | } else { |
| 320 | else if (x < .72) r = .99 * sin((x - .16) / .56 * M_PI / 2); | 325 | memcpy(palette++, "\0\0\0" , 3); |
| 321 | else r = .01 * (x - .72) / .28 + .99; | 326 | memcpy(palette++, "\377\377\377", 3); |
| 322 | if (x < .52) g = 0; | 327 | memcpy(palette++, "\277\277\277", 3); |
| 323 | else if (x < .86) g = .99 * .5 * (1 - cos((x - .52) / .34 * M_PI)); | 328 | memcpy(palette++, "\177\177\177", 3); |
| 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 | } | | |
| 332 | } | 329 | } |
| 333 | i = spectrum_points * 3; | 330 | for (i = 0; i < p->spectrum_points; ++i) { |
| 334 | switch (style) { | 331 | double c[3] , x = (double)i / (p->spectrum_points - 1); |
| 335 | case 0: case 2: | 332 | int at = (p->style & 1)? p->spectrum_points - 1 - i : i; |
| 336 | memcpy(palette + i, "\0\0\0" , 3), i+= 3; | 333 | if (p->style > 1) { |
| 337 | memcpy(palette + i, "\377\377\377", 3), i+= 3; | 334 | c[2] = c[1] = c[0] = x; |
| 338 | memcpy(palette + i, "\277\277\277", 3), i+= 3; | 335 | if (p->high_colour) { |
| 339 | memcpy(palette + i, "\177\177\177", 3), i+= 3; | 336 | c[(1 + p->perm) % 3] = x < .4? 0 : 5 / 3. * (x - .4); |
| 340 | return; | 337 | if (p->perm < 3) |
| 341 | case 1: memcpy(palette + i, "\335\330\320", 3), i+= 3; break; | 338 | c[(2 + p->perm) % 3] = x < .4? 0 : 5 / 3. * (x - .4); |
| 342 | default: memcpy(palette + i, "\337\337\337", 3), i+= 3; | 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 | static const Bytef fixed[] = | 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 | scale = pow(10., -3. * prefix_num); | 430 | scale = pow(10., -3. * prefix_num); |
| 406 | } | 431 | } |
| 407 | *prefix = "pnum-kMGTPE" + prefix_num + (prefix_num? 4 : 11); | 432 | *prefix = "pnum-kMGTPE" + prefix_num + (prefix_num? 4 : 11); |
| 408 | *limit = to * scale + .5; | 433 | *limit = to * scale; |
| 409 | return step * scale + .5; | 434 | return step * scale + .5; |
| 410 | } | 435 | } |
| 411 | | 436 | |
| @@ -427,7 +452,7 @@ static int stop(sox_effect_t * effp) · 15 unmodified lines |
| 427 | png_bytepp png_rows = malloc(rows * sizeof(*png_rows)); | 452 | png_bytepp png_rows = malloc(rows * sizeof(*png_rows)); |
| 428 | png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0,0); | 453 | png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0,0); |
| 429 | png_infop png_info = png_create_info_struct(png); | 454 | png_infop png_info = png_create_info_struct(png); |
| 430 | png_byte palette[palette_size * 3]; | 455 | png_color palette[256]; |
| 431 | int i, j, step, tick_len = 2 + do_frame; | 456 | int i, j, step, tick_len = 2 + do_frame; |
| 432 | char text[200], * prefix; | 457 | char text[200], * prefix; |
| 433 | double limit; | 458 | double limit; |
| @@ -443,10 +468,10 @@ static int stop(sox_effect_t * effp) · 9 unmodified lines |
| 443 | sox_debug("signal-max=%g", p->max); | 468 | sox_debug("signal-max=%g", p->max); |
| 444 | font = malloc(font_len); | 469 | font = malloc(font_len); |
| 445 | assert(uncompress(font, &font_len, fixed, sizeof(fixed)-1) == Z_OK); | 470 | assert(uncompress(font, &font_len, fixed, sizeof(fixed)-1) == Z_OK); |
| 446 | make_palette(palette, p->style); | 471 | make_palette(p, palette); |
| 447 | memset(pixels, Background, cols * rows * sizeof(*pixels)); | 472 | memset(pixels, Background, cols * rows * sizeof(*pixels)); |
| 448 | png_init_io(png, file); | 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 | png_set_IHDR(png, png_info, (size_t)cols, (size_t)rows, 8, | 475 | png_set_IHDR(png, png_info, (size_t)cols, (size_t)rows, 8, |
| 451 | PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, | 476 | PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, |
| 452 | PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); | 477 | PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); |
| @@ -461,7 +486,7 @@ static int stop(sox_effect_t * effp) · 8 unmodified lines |
| 461 | /* Spectrogram */ | 486 | /* Spectrogram */ |
| 462 | for (j = 0; j < p->rows; ++j) { | 487 | for (j = 0; j < p->rows; ++j) { |
| 463 | for (i = 0; i < p->cols; ++i) | 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 | if (do_frame) /* Frame verticals */ | 490 | if (do_frame) /* Frame verticals */ |
| 466 | pixel(left - 1, below + j) = pixel(left + p->cols,below + j) = Grid; | 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 | /* Z-axis */ | 528 | /* Z-axis */ |
| 504 | print_at(cols - right - 2 - font_X, below - 13, Text, "dBFS");/* Axis label */ | 529 | print_at(cols - right - 2 - font_X, below - 13, Text, "dBFS");/* Axis label */ |
| 505 | for (j = 0; j < p->rows; ++j) { /* Spectrum */ | 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 | for (i = 0; i < spectrum_width; ++i) | 532 | for (i = 0; i < spectrum_width; ++i) |
| 508 | pixel(cols - right - 1 - i, below + j) = b; | 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 | static sox_effect_handler_t handler = { | 554 | static sox_effect_handler_t handler = { |
| 530 | "spectrogram", "[options]\n" | 555 | "spectrogram", "[options]\n" |
| 531 | " -x num X-axis pixels/second, default 100\n" | 556 | "\t-x num\tX-axis pixels/second, default 100\n" |
| 532 | " -y num Y-axis resolution (1 - 4), default 2\n" | 557 | "\t-y num\tY-axis resolution (1 - 4), default 2\n" |
| 533 | " -z num Z-axis (colour) range in dB, default 120\n" | 558 | "\t-z num\tZ-axis range in dB, default 120\n" |
| 534 | " -g num Apply num dB gain; to shift Z-axis\n" | 559 | "\t-Z num\tZ-axis maximum in dBFS, default 0\n" |
| 535 | " -w name Window: Hann (default), Hamming, Bartlett, Rectangular, Kaiser\n" | 560 | "\t-q num\tZ-axis quantisation, default 249\n" |
| 536 | " -s Slack overlap\n" | 561 | "\t-w name\tWindow: Hann (default), Hamming, Bartlett, Rectangular, Kaiser\n" |
| 537 | " -a Suppress axes' lines\n" | 562 | "\t-s\tSlack overlap\n" |
| 538 | " -t text Title text\n" | 563 | "\t-a\tSuppress axis lines\n" |
| 539 | " -c text Comment text\n" | 564 | "\t-l\tLight background\n" |
| 540 | " -l Light background\n" | 565 | "\t-m\tMonochrome\n" |
| 541 | " -m Monochrome display\n" | 566 | "\t-h\tHigh colour\n" |
| 542 | " -o text output file name, default `spectrogram.png'\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 | 0, getopts, start, flow, drain, stop, NULL, sizeof(priv_t)}; | 571 | 0, getopts, start, flow, drain, stop, NULL, sizeof(priv_t)}; |
| 544 | return &handler; | 572 | return &handler; |
| 545 | } | 573 | } |