Showing the changes to src/spectrogram.c (1 of 3 files). Show all
Unified · Split
| @@ -15,6 +15,11 @@ · 14 unmodified lines | |||
| 15 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | 15 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| 16 | */ | 16 | */ |
| 17 | 17 | ||
| 18 | /* TODO | ||
| 19 | * o Two-channel support. | ||
| 20 | * o Option for a larger font (for use with image down-scaling). | ||
| 21 | */ | ||
| 22 | |||
| 18 | #ifdef NDEBUG /* Enable assert always. */ | 23 | #ifdef NDEBUG /* Enable assert always. */ |
| 19 | #undef NDEBUG /* Must undef above assert.h or other that might include it. */ | 24 | #undef NDEBUG /* Must undef above assert.h or other that might include it. */ |
| 20 | #endif | 25 | #endif |
| @@ -88,19 +93,28 @@ static enum_item const window_options[] = { · 67 unmodified lines | |||
| 88 | {0, 0}}; | 93 | {0, 0}}; |
| 89 | 94 | ||
| 90 | typedef struct { | 95 | typedef struct { |
| 91 | int y_size, dft_size, rows, cols, step_size, block_steps, block_num; | 96 | /* Parameters */ |
| 92 | int dB_range, gain, style, read, end, end_min, last_end; | 97 | double pixels_per_sec; |
| 93 | int spectrum_points, perm; | 98 | int y_size, dB_range, gain, spectrum_points, perm; |
| 94 | char const * out_name, * title, * comment; | 99 | sox_bool monochrome, light_background, high_colour, slack_overlap, no_axes; |
| 95 | sox_bool high_colour, slack_overlap, no_axes, truncated; | ||
| 96 | win_type_t win_type; | 100 | win_type_t win_type; |
| 101 | char const * out_name, * title, * comment; | ||
| 102 | |||
| 103 | /* Work area */ | ||
| 104 | int WORK; /* Start of work area is marked by this dummy variable. */ | ||
| 105 | int dft_size, step_size, block_steps, block_num, rows, cols, read; | ||
| 106 | int end, end_min, last_end; | ||
| 107 | sox_bool truncated; | ||
| 97 | double buf[MAX_DFT_SIZE], dft_buf[MAX_DFT_SIZE], window[MAX_DFT_SIZE]; | 108 | 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]; | 109 | double block_norm, max, magnitudes[(MAX_DFT_SIZE>>1) + 1]; |
| 99 | int bit_rev_table[100]; /* For Ooura fft */ | 110 | int bit_rev_table[100]; /* For Ooura fft */ |
| 100 | double sin_cos_table[dft_sc_len(MAX_DFT_SIZE)]; /* ditto */ | 111 | double sin_cos_table[dft_sc_len(MAX_DFT_SIZE)]; /* ditto */ |
| 101 | float * dBfs; | 112 | float * dBfs; |
| 102 | } priv_t; | 113 | } priv_t; |
| 103 | 114 | ||
| 115 | #define secs(cols) \ | ||
| 116 | ((double)(cols) * p->step_size * p->block_steps / effp->in_signal.rate) | ||
| 117 | |||
| 104 | #define GETOPT_NUMERIC(ch, name, min, max) case ch:{ \ | 118 | #define GETOPT_NUMERIC(ch, name, min, max) case ch:{ \ |
| 105 | char * end_ptr; \ | 119 | char * end_ptr; \ |
| 106 | double d = strtod(optarg, &end_ptr); \ | 120 | double d = strtod(optarg, &end_ptr); \ |
| @@ -134,14 +148,13 @@ static int getopts(sox_effect_t * effp, int argc, char **argv) · 27 unmodified lines | |||
| 134 | { | 148 | { |
| 135 | priv_t * p = (priv_t *)effp->priv; | 149 | priv_t * p = (priv_t *)effp->priv; |
| 136 | int c, callers_optind = optind, callers_opterr = opterr; | 150 | int c, callers_optind = optind, callers_opterr = opterr; |
| 137 | sox_bool monochrome = sox_false, light_background = sox_false; | ||
| 138 | 151 | ||
| 139 | assert(array_length(p->bit_rev_table) >= (size_t)dft_br_len(MAX_DFT_SIZE)); | 152 | assert(array_length(p->bit_rev_table) >= (size_t)dft_br_len(MAX_DFT_SIZE)); |
| 140 | 153 | ||
| 141 | --argv, ++argc, optind = 1, opterr = 0; /* re-jig for getopt */ | 154 | --argv, ++argc, optind = 1, opterr = 0; /* re-jig for getopt */ |
| 142 | p->pixels_per_sec = 100, p->y_size = 2, p->dB_range = 120;/* non-0 defaults */ | 155 | p->pixels_per_sec = 100, p->y_size = 2, p->dB_range = 120;/* non-0 defaults */ |
| 143 | p->spectrum_points = 249, p->perm = 1; | 156 | p->spectrum_points = 249, p->perm = 1; |
| 144 | p->style = 1, p->out_name = "spectrogram.png", p->comment = "Created by SoX"; | 157 | p->out_name = "spectrogram.png", p->comment = "Created by SoX"; |
| 145 | 158 | ||
| 146 | while ((c = getopt(argc, argv, "+x:y:z:Z:q:p:w:st:c:amlho:")) != -1) switch (c) { | 159 | while ((c = getopt(argc, argv, "+x:y:z:Z:q:p:w:st:c:amlho:")) != -1) switch (c) { |
| 147 | GETOPT_NUMERIC('x', pixels_per_sec, 1 , 5000) | 160 | GETOPT_NUMERIC('x', pixels_per_sec, 1 , 5000) |
| @@ -155,15 +168,14 @@ static int getopts(sox_effect_t * effp, int argc, char **argv) · 7 unmodified lines | |||
| 155 | case 't': p->title = optarg; break; | 168 | case 't': p->title = optarg; break; |
| 156 | case 'c': p->comment = optarg; break; | 169 | case 'c': p->comment = optarg; break; |
| 157 | case 'a': p->no_axes = sox_true; break; | 170 | case 'a': p->no_axes = sox_true; break; |
| 158 | case 'm': monochrome = sox_true; break; | 171 | case 'm': p->monochrome = sox_true; break; |
| 159 | case 'l': light_background = sox_true; break; | 172 | case 'l': p->light_background = sox_true; break; |
| 160 | case 'h': p->high_colour = sox_true; break; | 173 | case 'h': p->high_colour = sox_true; break; |
| 161 | case 'o': p->out_name = optarg; break; | 174 | case 'o': p->out_name = optarg; break; |
| 162 | default: sox_fail("unknown option `-%c'", optopt); return lsx_usage(effp); | 175 | default: sox_fail("unknown option `-%c'", optopt); return lsx_usage(effp); |
| 163 | } | 176 | } |
| 164 | p->gain = - p->gain; | 177 | p->gain = -p->gain; |
| 165 | --p->y_size, --p->perm; | 178 | --p->y_size, --p->perm; |
| 166 | p->style = 2 * monochrome + light_background; | ||
| 167 | p->spectrum_points += 2; | 179 | p->spectrum_points += 2; |
| 168 | argc -= optind, optind = callers_optind, opterr = callers_opterr; | 180 | argc -= optind, optind = callers_optind, opterr = callers_opterr; |
| 169 | return argc || p->win_type == INT_MAX? lsx_usage(effp) : SOX_SUCCESS; | 181 | return argc || p->win_type == INT_MAX? lsx_usage(effp) : SOX_SUCCESS; |
| @@ -198,6 +210,7 @@ static int start(sox_effect_t * effp) · 28 unmodified lines | |||
| 198 | sox_fail("only 1 channel is supported"); | 210 | sox_fail("only 1 channel is supported"); |
| 199 | return SOX_EOF; | 211 | return SOX_EOF; |
| 200 | } | 212 | } |
| 213 | memset(&p->WORK, 0, sizeof(*p) - field_offset(priv_t, WORK)); | ||
| 201 | p->end = p->dft_size = DFT_BASE_SIZE << p->y_size; | 214 | p->end = p->dft_size = DFT_BASE_SIZE << p->y_size; |
| 202 | p->rows = (p->dft_size >> 1) + 1; | 215 | p->rows = (p->dft_size >> 1) + 1; |
| 203 | actual = make_window(p, p->last_end = 0); | 216 | actual = make_window(p, p->last_end = 0); |
| @@ -222,7 +235,7 @@ static int do_column(sox_effect_t * effp) · 18 unmodified lines | |||
| 222 | int i; | 235 | int i; |
| 223 | 236 | ||
| 224 | if (p->cols == MAX_COLS) { | 237 | if (p->cols == MAX_COLS) { |
| 225 | sox_warn("PNG truncated at %g seconds", (double)MAX_COLS * p->step_size * p->block_steps / effp->in_signal.rate); | 238 | sox_warn("PNG truncated at %g seconds", secs(p->cols)); |
| 226 | p->truncated = sox_true; | 239 | p->truncated = sox_true; |
| 227 | return SOX_EOF; | 240 | return SOX_EOF; |
| 228 | } | 241 | } |
| @@ -231,8 +244,7 @@ static int do_column(sox_effect_t * effp) · 2 unmodified lines | |||
| 231 | for (i = 0; i < p->rows; ++i) { | 244 | for (i = 0; i < p->rows; ++i) { |
| 232 | double dBfs = 10 * log10(p->magnitudes[i] * p->block_norm); | 245 | double dBfs = 10 * log10(p->magnitudes[i] * p->block_norm); |
| 233 | p->dBfs[(p->cols - 1) * p->rows + i] = dBfs + p->gain; | 246 | p->dBfs[(p->cols - 1) * p->rows + i] = dBfs + p->gain; |
| 234 | if (dBfs > p->max) | 247 | p->max = max(dBfs, p->max); |
| 235 | p->max = dBfs; | ||
| 236 | } | 248 | } |
| 237 | memset(p->magnitudes, 0, p->rows * sizeof(*p->magnitudes)); | 249 | memset(p->magnitudes, 0, p->rows * sizeof(*p->magnitudes)); |
| 238 | p->block_num = 0; | 250 | p->block_num = 0; |
| @@ -289,8 +301,7 @@ static int drain(sox_effect_t * effp, sox_sample_t * obuf_, sox_size_t * osamp) · 50 unmodified lines | |||
| 289 | if (left_over >= p->step_size >> 1) | 301 | if (left_over >= p->step_size >> 1) |
| 290 | isamp += p->step_size - left_over; | 302 | isamp += p->step_size - left_over; |
| 291 | sox_debug("cols=%i left=%i end=%i", p->cols, p->read, p->end); | 303 | sox_debug("cols=%i left=%i end=%i", p->cols, p->read, p->end); |
| 292 | p->end = 0; | 304 | p->end = 0, p->end_min = -p->dft_size; |
| 293 | p->end_min = -p->dft_size; | ||
| 294 | if (flow(effp, ibuf, obuf, &isamp, &isamp) == SOX_SUCCESS && p->block_num) { | 305 | if (flow(effp, ibuf, obuf, &isamp, &isamp) == SOX_SUCCESS && p->block_num) { |
| 295 | p->block_norm *= (double)p->block_steps / p->block_num; | 306 | p->block_norm *= (double)p->block_steps / p->block_num; |
| 296 | do_column(effp); | 307 | do_column(effp); |
| @@ -316,8 +327,8 @@ static void make_palette(priv_t const * p, png_color * palette) · 19 unmodified lines | |||
| 316 | { | 327 | { |
| 317 | int i; | 328 | int i; |
| 318 | 329 | ||
| 319 | if (p->style & 1) { | 330 | if (p->light_background) { |
| 320 | memcpy(palette++, (p->style & 2)? "\337\337\337":"\335\330\320", 3); | 331 | memcpy(palette++, (p->monochrome)? "\337\337\337":"\335\330\320", 3); |
| 321 | memcpy(palette++, "\0\0\0" , 3); | 332 | memcpy(palette++, "\0\0\0" , 3); |
| 322 | memcpy(palette++, "\077\077\077", 3); | 333 | memcpy(palette++, "\077\077\077", 3); |
| 323 | memcpy(palette++, "\077\077\077", 3); | 334 | memcpy(palette++, "\077\077\077", 3); |
| @@ -328,9 +339,9 @@ static void make_palette(priv_t const * p, png_color * palette) · 4 unmodified lines | |||
| 328 | memcpy(palette++, "\177\177\177", 3); | 339 | memcpy(palette++, "\177\177\177", 3); |
| 329 | } | 340 | } |
| 330 | for (i = 0; i < p->spectrum_points; ++i) { | 341 | for (i = 0; i < p->spectrum_points; ++i) { |
| 331 | double c[3] , x = (double)i / (p->spectrum_points - 1); | 342 | double c[3], x = (double)i / (p->spectrum_points - 1); |
| 332 | int at = (p->style & 1)? p->spectrum_points - 1 - i : i; | 343 | int at = (p->light_background)? p->spectrum_points - 1 - i : i; |
| 333 | if (p->style > 1) { | 344 | if (p->monochrome) { |
| 334 | c[2] = c[1] = c[0] = x; | 345 | c[2] = c[1] = c[0] = x; |
| 335 | if (p->high_colour) { | 346 | if (p->high_colour) { |
| 336 | c[(1 + p->perm) % 3] = x < .4? 0 : 5 / 3. * (x - .4); | 347 | c[(1 + p->perm) % 3] = x < .4? 0 : 5 / 3. * (x - .4); |
| @@ -345,13 +356,13 @@ static void make_palette(priv_t const * p, png_color * palette) · 8 unmodified lines | |||
| 345 | if (p->high_colour) { | 356 | if (p->high_colour) { |
| 346 | static const int states[3][7] = { | 357 | 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}}; | 358 | {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); | 359 | int j, phase_num = min(7 * x, 6); |
| 349 | for (j = 0; j < 3; ++j) switch (states[j][phase_num]) { | 360 | for (j = 0; j < 3; ++j) switch (states[j][phase_num]) { |
| 350 | case 0: c[j] = 0; break; | 361 | case 0: c[j] = 0; break; |
| 351 | case 1: c[j] = 1; break; | 362 | case 1: c[j] = 1; break; |
| 352 | case 2: c[j] = sin((7 * x - phase_num) * M_PI / 2); break; | 363 | 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; | 364 | case 3: c[j] = cos((7 * x - phase_num) * M_PI / 2); break; |
| 354 | case 4: c[j] = 7 * x - phase_num; break; | 365 | case 4: c[j] = 7 * x - phase_num; break; |
| 355 | case 5: c[j] = 1 - (7 * x - phase_num); break; | 366 | case 5: c[j] = 1 - (7 * x - phase_num); break; |
| 356 | } | 367 | } |
| 357 | } else { | 368 | } else { |
| @@ -447,13 +458,12 @@ static int stop(sox_effect_t * effp) · 89 unmodified lines | |||
| 447 | uLong font_len = 96 * font_y; | 458 | uLong font_len = 96 * font_y; |
| 448 | int rows = below + p->rows + 30 + 20 * !!p->title; | 459 | int rows = below + p->rows + 30 + 20 * !!p->title; |
| 449 | int cols = left + p->cols + between + spectrum_width + right; | 460 | int cols = left + p->cols + between + spectrum_width + right; |
| 450 | sox_bool do_frame = !p->no_axes; | ||
| 451 | png_byte * pixels = malloc(cols * rows * sizeof(*pixels)); | 461 | png_byte * pixels = malloc(cols * rows * sizeof(*pixels)); |
| 452 | png_bytepp png_rows = malloc(rows * sizeof(*png_rows)); | 462 | png_bytepp png_rows = malloc(rows * sizeof(*png_rows)); |
| 453 | png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0,0); | 463 | png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0,0); |
| 454 | png_infop png_info = png_create_info_struct(png); | 464 | png_infop png_info = png_create_info_struct(png); |
| 455 | png_color palette[256]; | 465 | png_color palette[256]; |
| 456 | int i, j, step, tick_len = 2 + do_frame; | 466 | int i, j, step, tick_len = 3 - p->no_axes; |
| 457 | char text[200], * prefix; | 467 | char text[200], * prefix; |
| 458 | double limit; | 468 | double limit; |
| 459 | 469 | ||
| @@ -475,7 +485,7 @@ static int stop(sox_effect_t * effp) · 15 unmodified lines | |||
| 475 | png_set_IHDR(png, png_info, (size_t)cols, (size_t)rows, 8, | 485 | png_set_IHDR(png, png_info, (size_t)cols, (size_t)rows, 8, |
| 476 | PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, | 486 | PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE, |
| 477 | PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); | 487 | PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); |
| 478 | for (j = 0; j < rows; ++j) | 488 | for (j = 0; j < rows; ++j) /* Put (0,0) at bottom-left of PNG */ |
| 479 | png_rows[rows - 1 - j] = (png_bytep)(pixels + j * cols); | 489 | png_rows[rows - 1 - j] = (png_bytep)(pixels + j * cols); |
| 480 | 490 | ||
| 481 | if (p->title && (i = (int)strlen(p->title) * font_X) < cols + 1) /* Title */ | 491 | if (p->title && (i = (int)strlen(p->title) * font_X) < cols + 1) /* Title */ |
| @@ -487,20 +497,20 @@ static int stop(sox_effect_t * effp) · 5 unmodified lines | |||
| 487 | for (j = 0; j < p->rows; ++j) { | 497 | for (j = 0; j < p->rows; ++j) { |
| 488 | for (i = 0; i < p->cols; ++i) | 498 | for (i = 0; i < p->cols; ++i) |
| 489 | pixel(left + i, below + j) = colour(p, p->dBfs[i*p->rows + j]); | 499 | pixel(left + i, below + j) = colour(p, p->dBfs[i*p->rows + j]); |
| 490 | if (do_frame) /* Frame verticals */ | 500 | if (!p->no_axes) /* Y-axis lines */ |
| 491 | pixel(left - 1, below + j) = pixel(left + p->cols,below + j) = Grid; | 501 | pixel(left - 1, below + j) = pixel(left + p->cols,below + j) = Grid; |
| 492 | } | 502 | } |
| 493 | if (do_frame) for (i = -1; i <= p->cols; ++i) /* Frame horizontals */ | 503 | if (!p->no_axes) for (i = -1; i <= p->cols; ++i) /* X-axis lines */ |
| 494 | pixel(left + i, below - 1) = pixel(left + i, below + p->rows) = Grid; | 504 | pixel(left + i, below - 1) = pixel(left + i, below + p->rows) = Grid; |
| 495 | 505 | ||
| 496 | /* X-axis */ | 506 | /* X-axis */ |
| 497 | step = axis((double)p->cols * p->step_size * p->block_steps / effp->in_signal.rate, p->cols / (font_X * 9 / 2), &limit, &prefix); | 507 | step = axis(secs(p->cols), p->cols / (font_X * 9 / 2), &limit, &prefix); |
| 498 | sprintf(text, "Time (%.1ss)", prefix); /* Axis label */ | 508 | sprintf(text, "Time (%.1ss)", prefix); /* Axis label */ |
| 499 | print_at(left + (p->cols - font_X * (int)strlen(text)) / 2, 24, Text, text); | 509 | print_at(left + (p->cols - font_X * (int)strlen(text)) / 2, 24, Text, text); |
| 500 | for (i = 0; i <= limit; i += step) { | 510 | for (i = 0; i <= limit; i += step) { |
| 501 | int y, x = limit? (double)i / limit * p->cols + .5 : 0; | 511 | int y, x = limit? (double)i / limit * p->cols + .5 : 0; |
| 502 | for (y = 0; y < tick_len; ++y) /* Ticks */ | 512 | for (y = 0; y < tick_len; ++y) /* Ticks */ |
| 503 | pixel(left - 1 + x, below - 1 - y) = pixel(left - 1 + x, below + p->rows + y) = Grid; | 513 | pixel(left-1+x, below-1-y) = pixel(left-1+x, below+p->rows+y) = Grid; |
| 504 | if (step == 5 && (i%10)) | 514 | if (step == 5 && (i%10)) |
| 505 | continue; | 515 | continue; |
| 506 | sprintf(text, "%g", .1 * i); /* Tick labels */ | 516 | sprintf(text, "%g", .1 * i); /* Tick labels */ |
| @@ -510,13 +520,14 @@ static int stop(sox_effect_t * effp) · 3 unmodified lines | |||
| 510 | } | 520 | } |
| 511 | 521 | ||
| 512 | /* Y-axis */ | 522 | /* Y-axis */ |
| 513 | step = axis(effp->in_signal.rate / 2, (p->rows - 1) / ((font_y * 3 + 1) >> 1), &limit, &prefix); | 523 | step = axis(effp->in_signal.rate / 2, |
| 524 | (p->rows - 1) / ((font_y * 3 + 1) >> 1), &limit, &prefix); | ||
| 514 | sprintf(text, "Frequency (%.1sHz)", prefix); /* Axis label */ | 525 | sprintf(text, "Frequency (%.1sHz)", prefix); /* Axis label */ |
| 515 | print_up(10, below + (p->rows - font_X * (int)strlen(text)) / 2, Text, text); | 526 | print_up(10, below + (p->rows - font_X * (int)strlen(text)) / 2, Text, text); |
| 516 | for (i = 0; i <= limit; i += step) { | 527 | for (i = 0; i <= limit; i += step) { |
| 517 | int x, y = limit? (double)i / limit * (p->rows - 1) + .5 : 0; | 528 | int x, y = limit? (double)i / limit * (p->rows - 1) + .5 : 0; |
| 518 | for (x = 0; x < tick_len; ++x) /* Ticks */ | 529 | for (x = 0; x < tick_len; ++x) /* Ticks */ |
| 519 | pixel(left - 1 - x, below + y) = pixel(left + p->cols + x, below + y) = Grid; | 530 | pixel(left-1-x, below+y) = pixel(left+p->cols+x, below+y) = Grid; |
| 520 | if (step == 5 && (i%10)) | 531 | if (step == 5 && (i%10)) |
| 521 | continue; | 532 | continue; |
| 522 | sprintf(text, i?"%5g":" DC", .1 * i); /* Tick labels */ | 533 | sprintf(text, i?"%5g":" DC", .1 * i); /* Tick labels */ |