Showing the changes to src/spectrogram.c (1 of 5 files). Show all
Unified · Split
| @@ -1,4 +1,4 @@ | ||
| 1 | −/* libSoX effect: Spectrogram (c) 2008 robs@users.sourceforge.net | |
| 1 | +/* libSoX effect: Spectrogram (c) 2008-9 robs@users.sourceforge.net | |
| 2 | 2 | * |
| 3 | 3 | * This library is free software; you can redistribute it and/or modify it |
| 4 | 4 | * under the terms of the GNU Lesser General Public License as published by |
| @@ -15,11 +15,6 @@ · 10 unmodified lines | ||
| 15 | 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 | − | |
| 23 | 18 | #ifdef NDEBUG /* Enable assert always. */ |
| 24 | 19 | #undef NDEBUG /* Must undef above assert.h or other that might include it. */ |
| 25 | 20 | #endif |
| @@ -31,14 +26,8 @@ · 5 unmodified lines | ||
| 31 | 26 | #include <math.h> |
| 32 | 27 | #include <png.h> |
| 33 | 28 | |
| 34 | −#define malloc lsx_malloc | |
| 35 | −#define calloc lsx_calloc | |
| 36 | −#define realloc lsx_realloc | |
| 37 | −#define FROM_SOX SOX_SAMPLE_TO_FLOAT_64BIT | |
| 38 | −#define DFT_BASE_SIZE 512 | |
| 39 | −#define MAX_DFT_SIZE_SHIFT 3 | |
| 40 | −#define MAX_DFT_SIZE (DFT_BASE_SIZE << MAX_DFT_SIZE_SHIFT) | |
| 41 | −#define MAX_COLS 1000 /* Also max seconds */ | |
| 29 | +#define MAX_FFT_SIZE 4096 | |
| 30 | +#define is_p2(x) !(x & (x - 1)) | |
| 42 | 31 | |
| 43 | 32 | typedef enum {Window_Hann, Window_Hamming, Window_Bartlett, Window_Rectangular, Window_Kaiser} win_type_t; |
| 44 | 33 | static lsx_enum_item const window_options[] = { |
| @@ -51,69 +40,104 @@ static lsx_enum_item const window_options[] = { · 6 unmodified lines | ||
| 51 | 40 | |
| 52 | 41 | typedef struct { |
| 53 | 42 | /* Parameters */ |
| 54 | − double pixels_per_sec; | |
| 55 | − int y_size, dB_range, gain, spectrum_points, perm, max_cols; | |
| 43 | + double pixels_per_sec, duration, start_time, window_adjust; | |
| 44 | + int x_size0, y_size, Y_size, dB_range, gain, spectrum_points, perm; | |
| 56 | 45 | sox_bool monochrome, light_background, high_colour, slack_overlap, no_axes; |
| 46 | + sox_bool alt_palette, truncate; | |
| 57 | 47 | win_type_t win_type; |
| 58 | 48 | char const * out_name, * title, * comment; |
| 59 | 49 | |
| 60 | − /* Work area */ | |
| 50 | + /* Shared work area */ | |
| 51 | + double * shared, * * shared_ptr; | |
| 52 | + | |
| 53 | + /* Per-channel work area */ | |
| 61 | 54 | int WORK; /* Start of work area is marked by this dummy variable. */ |
| 55 | + size_t skip; | |
| 62 | 56 | int dft_size, step_size, block_steps, block_num, rows, cols, read; |
| 63 | − int end, end_min, last_end; | |
| 57 | + int x_size, end, end_min, last_end; | |
| 64 | 58 | sox_bool truncated; |
| 65 | − double buf[MAX_DFT_SIZE], dft_buf[MAX_DFT_SIZE], window[MAX_DFT_SIZE]; | |
| 66 | − double block_norm, max, magnitudes[(MAX_DFT_SIZE>>1) + 1]; | |
| 67 | − int bit_rev_table[100]; /* For Ooura fft */ | |
| 68 | − double sin_cos_table[dft_sc_len(MAX_DFT_SIZE)]; /* ditto */ | |
| 59 | + double buf[MAX_FFT_SIZE], dft_buf[MAX_FFT_SIZE], window[MAX_FFT_SIZE]; | |
| 60 | + double block_norm, max, magnitudes[(MAX_FFT_SIZE>>1) + 1]; | |
| 69 | 61 | float * dBfs; |
| 70 | 62 | } priv_t; |
| 71 | 63 | |
| 72 | 64 | #define secs(cols) \ |
| 73 | 65 | ((double)(cols) * p->step_size * p->block_steps / effp->in_signal.rate) |
| 74 | 66 | |
| 67 | +static unsigned char const alt_palette[] = | |
| 68 | + "\0\0\0\0\0\3\0\1\5\0\1\10\0\1\12\0\1\13\0\1\16\1\2\20\1\2\22\1\2\25\1\2\26" | |
| 69 | + "\1\2\30\1\3\33\1\3\35\1\3\37\1\3\40\1\3\"\1\3$\1\3%\1\3'\1\3(\1\3*\1\3,\1" | |
| 70 | + "\3.\1\3/\1\3""0\1\3""2\1\3""4\2\3""6\4\3""8\5\3""9\7\3;\11\3=\13\3?\16\3" | |
| 71 | + "A\17\2B\21\2D\23\2F\25\2H\27\2J\30\2K\32\2M\35\2O\40\2Q$\2S(\2U+\2W0\2Z3" | |
| 72 | + "\2\\7\2_;\2a>\2cB\2eE\2hI\2jM\2lQ\2nU\2pZ\2r_\2tc\2uh\2vl\2xp\3zu\3|z\3}" | |
| 73 | + "~\3~\203\3\200\207\3\202\214\3\204\220\3\205\223\3\203\226\3\200\230\3~\233" | |
| 74 | + "\3|\236\3z\240\3x\243\3u\246\3s\251\3q\253\3o\256\3m\261\3j\263\3h\266\3" | |
| 75 | + "f\272\3b\274\3^\300\3Z\303\3V\307\3R\312\3N\315\3J\321\3F\324\3C\327\3>\333" | |
| 76 | + "\3:\336\3""6\342\3""2\344\3/\346\7-\350\15,\352\21+\354\27*\355\33)\356\40" | |
| 77 | + "(\360&'\362*&\364/$\3654#\3669#\370>!\372C\40\374I\40\374O\"\374V&\374]*" | |
| 78 | + "\374d,\374k0\374r3\374z7\375\201;\375\210>\375\217B\375\226E\375\236I\375" | |
| 79 | + "\245M\375\254P\375\261T\375\267X\375\274\\\375\301a\375\306e\375\313i\375" | |
| 80 | + "\320m\376\325q\376\332v\376\337z\376\344~\376\351\202\376\356\206\376\363" | |
| 81 | + "\213\375\365\217\374\366\223\373\367\230\372\367\234\371\370\241\370\371" | |
| 82 | + "\245\367\371\252\366\372\256\365\372\263\364\373\267\363\374\274\361\375" | |
| 83 | + "\300\360\375\305\360\376\311\357\376\314\357\376\317\360\376\321\360\376" | |
| 84 | + "\324\360\376\326\360\376\330\360\376\332\361\377\335\361\377\337\361\377" | |
| 85 | + "\341\361\377\344\361\377\346\362\377\350\362\377\353"; | |
| 86 | +#define alt_palette_len ((array_length(alt_palette) - 1) / 3) | |
| 87 | + | |
| 75 | 88 | static int getopts(sox_effect_t * effp, int argc, char **argv) |
| 76 | 89 | { |
| 77 | 90 | priv_t * p = (priv_t *)effp->priv; |
| 78 | − size_t duration_1e5; | |
| 91 | + size_t duration; | |
| 79 | 92 | char const * next; |
| 80 | 93 | int c; |
| 81 | 94 | |
| 82 | − assert(array_length(p->bit_rev_table) >= (size_t)dft_br_len(MAX_DFT_SIZE)); | |
| 83 | − | |
| 84 | − p->pixels_per_sec = 100, p->y_size = 2, p->dB_range = 120;/* non-0 defaults */ | |
| 85 | − p->spectrum_points = 249, p->perm = 1, p->max_cols = MAX_COLS; | |
| 95 | + p->dB_range = 120, p->spectrum_points = 249, p->perm = 1; /* Non-0 defaults */ | |
| 86 | 96 | p->out_name = "spectrogram.png", p->comment = "Created by SoX"; |
| 87 | 97 | |
| 88 | − while ((c = lsx_getopt(argc, argv, "+M:x:X:y:z:Z:q:p:w:st:c:amlho:")) != -1) switch (c) { | |
| 89 | − GETOPT_NUMERIC('M', max_cols , 100, 2000) | |
| 90 | − GETOPT_NUMERIC('x', pixels_per_sec, 1 , 5000) | |
| 91 | − GETOPT_NUMERIC('y', y_size , 1 , 1 + MAX_DFT_SIZE_SHIFT) | |
| 98 | + while ((c = lsx_getopt(argc, argv, "+S:d:x:X:y:Y:z:Z:q:p:W:w:st:c:AamlhTo:")) != -1) switch (c) { | |
| 99 | + GETOPT_NUMERIC('x', x_size0 , 100, 5000) | |
| 100 | + GETOPT_NUMERIC('X', pixels_per_sec, 1 , 5000) | |
| 101 | + GETOPT_NUMERIC('y', y_size , 64 , 1200) | |
| 102 | + GETOPT_NUMERIC('Y', Y_size , 130, MAX_FFT_SIZE / 2 + 2) | |
| 92 | 103 | GETOPT_NUMERIC('z', dB_range , 20 , 180) |
| 93 | 104 | GETOPT_NUMERIC('Z', gain ,-100, 100) |
| 94 | 105 | GETOPT_NUMERIC('q', spectrum_points, 0 , p->spectrum_points) |
| 95 | 106 | GETOPT_NUMERIC('p', perm , 1 , 6) |
| 107 | + GETOPT_NUMERIC('W', window_adjust , -10, 10) | |
| 96 | 108 | case 'w': p->win_type = lsx_enum_option(c, window_options); break; |
| 97 | − case 's': p->slack_overlap = sox_true; break; | |
| 98 | − case 'X': | |
| 99 | − next = lsx_parsesamples(1e5, lsx_optarg, &duration_1e5, 't'); | |
| 100 | − if (next == NULL || *next != '\0' || duration_1e5 > p->max_cols * 1e5 | |
| 101 | − || duration_1e5 < 1e5 / 5000 * p->max_cols) | |
| 102 | − return lsx_usage(effp); | |
| 103 | − p->pixels_per_sec = p->max_cols * 1e5 / duration_1e5; | |
| 104 | − break; | |
| 105 | − case 't': p->title = lsx_optarg; break; | |
| 106 | − case 'c': p->comment = lsx_optarg; break; | |
| 107 | − case 'a': p->no_axes = sox_true; break; | |
| 108 | − case 'm': p->monochrome = sox_true; break; | |
| 109 | − case 'l': p->light_background = sox_true; break; | |
| 110 | − case 'h': p->high_colour = sox_true; break; | |
| 111 | − case 'o': p->out_name = lsx_optarg; break; | |
| 109 | + case 's': p->slack_overlap = sox_true; break; | |
| 110 | + case 'A': p->alt_palette = sox_true; break; | |
| 111 | + case 'a': p->no_axes = sox_true; break; | |
| 112 | + case 'm': p->monochrome = sox_true; break; | |
| 113 | + case 'l': p->light_background = sox_true; break; | |
| 114 | + case 'h': p->high_colour = sox_true; break; | |
| 115 | + case 'T': p->truncate = sox_true; break; | |
| 116 | + case 't': p->title = lsx_optarg; break; | |
| 117 | + case 'c': p->comment = lsx_optarg; break; | |
| 118 | + case 'o': p->out_name = lsx_optarg; break; | |
| 119 | + case 'S': next = lsx_parsesamples(1e5, lsx_optarg, &duration, 't'); | |
| 120 | + if (next && !*next) {p->start_time = duration * 1e-5; break;} | |
| 121 | + return lsx_usage(effp); | |
| 122 | + case 'd': next = lsx_parsesamples(1e5, lsx_optarg, &duration, 't'); | |
| 123 | + if (next && !*next) {p->duration = duration * 1e-5; break;} | |
| 124 | + return lsx_usage(effp); | |
| 112 | 125 | default: lsx_fail("invalid option `-%c'", optopt); return lsx_usage(effp); |
| 113 | 126 | } |
| 127 | + if (!!p->x_size0 + !!p->pixels_per_sec + !!p->duration > 2) { | |
| 128 | + lsx_fail("only two of -x, -X, -d may be given"); | |
| 129 | + return SOX_EOF; | |
| 130 | + } | |
| 131 | + if (p->y_size && p->Y_size) { | |
| 132 | + lsx_fail("only one of -y, -Y may be given"); | |
| 133 | + return SOX_EOF; | |
| 134 | + } | |
| 114 | 135 | p->gain = -p->gain; |
| 115 | − --p->y_size, --p->perm; | |
| 136 | + --p->perm; | |
| 116 | 137 | p->spectrum_points += 2; |
| 138 | + if (p->alt_palette) | |
| 139 | + p->spectrum_points = min(p->spectrum_points, (int)alt_palette_len); | |
| 140 | + p->shared_ptr = &p->shared; | |
| 117 | 141 | return lsx_optind !=argc || p->win_type == INT_MAX? lsx_usage(effp) : SOX_SUCCESS; |
| 118 | 142 | } |
| 119 | 143 | |
| @@ -129,7 +153,8 @@ static double make_window(priv_t * p, int end) · 9 unmodified lines | ||
| 129 | 153 | case Window_Hamming: lsx_apply_hamming(w, n); break; |
| 130 | 154 | case Window_Bartlett: lsx_apply_bartlett(w, n); break; |
| 131 | 155 | case Window_Rectangular: break; |
| 132 | − default: lsx_apply_kaiser(w, n, lsx_kaiser_beta(p->dB_range + 20.)); | |
| 156 | + default: lsx_apply_kaiser(w, n, lsx_kaiser_beta( | |
| 157 | + (p->dB_range + p->gain) * (1.1 + p->window_adjust / 50))); | |
| 133 | 158 | } |
| 134 | 159 | for (i = 0; i < p->dft_size; ++i) sum += p->window[i]; |
| 135 | 160 | for (i = 0; i < p->dft_size; ++i) p->window[i] *= 2 / sum |
| @@ -137,27 +162,80 @@ static double make_window(priv_t * p, int end) · 1 unmodified line | ||
| 137 | 162 | return sum; |
| 138 | 163 | } |
| 139 | 164 | |
| 165 | +static double * rdft_init(int n) | |
| 166 | +{ | |
| 167 | + double * q = lsx_malloc(2 * (n / 2 + 1) * n * sizeof(*q)), * p = q; | |
| 168 | + int i, j; | |
| 169 | + for (j = 0; j <= n / 2; ++j) for (i = 0; i < n; ++i) | |
| 170 | + *p++ = cos(2 * M_PI * j * i / n), *p++ = sin(2 * M_PI * j * i / n); | |
| 171 | + return q; | |
| 172 | +} | |
| 173 | + | |
| 174 | +#define _ re += in[i] * *q++, im += in[i++] * *q++, | |
| 175 | +static void rdft_p(double const * q, double const * in, double * out, int n) | |
| 176 | +{ | |
| 177 | + int i, j; | |
| 178 | + for (j = 0; j <= n / 2; ++j) { | |
| 179 | + double re = 0, im = 0; | |
| 180 | + for (i = 0; i < (n & ~7);) _ _ _ _ _ _ _ _ 0; | |
| 181 | + while (i < n) _ 0; | |
| 182 | + *out++ += re * re + im * im; | |
| 183 | + } | |
| 184 | +} | |
| 185 | + | |
| 140 | 186 | static int start(sox_effect_t * effp) |
| 141 | 187 | { |
| 142 | 188 | priv_t * p = (priv_t *)effp->priv; |
| 143 | − double actual; | |
| 189 | + double actual, duration = p->duration, pixels_per_sec = p->pixels_per_sec; | |
| 144 | 190 | |
| 145 | − if (effp->in_signal.channels != 1) { | |
| 146 | − lsx_fail("only 1 channel is supported"); | |
| 147 | − return SOX_EOF; | |
| 148 | − } | |
| 149 | 191 | memset(&p->WORK, 0, sizeof(*p) - field_offset(priv_t, WORK)); |
| 150 | − p->end = p->dft_size = DFT_BASE_SIZE << p->y_size; | |
| 192 | + | |
| 193 | + p->skip = p->start_time * effp->in_signal.rate + .5; | |
| 194 | + p->x_size = p->x_size0; | |
| 195 | + while (sox_true) { | |
| 196 | + if (!pixels_per_sec && p->x_size && duration) | |
| 197 | + pixels_per_sec = min(5000, p->x_size / duration); | |
| 198 | + else if (!p->x_size && pixels_per_sec && duration) | |
| 199 | + p->x_size = min(5000, (int)(pixels_per_sec * duration + .5)); | |
| 200 | + if (!duration && effp->in_signal.length) { | |
| 201 | + duration = effp->in_signal.length / (effp->in_signal.rate * effp->in_signal.channels); | |
| 202 | + duration -= p->start_time; | |
| 203 | + if (duration <= 0) | |
| 204 | + duration = 1; | |
| 205 | + continue; | |
| 206 | + } else if (!p->x_size) { | |
| 207 | + p->x_size = 800; | |
| 208 | + continue; | |
| 209 | + } else if (!pixels_per_sec) { | |
| 210 | + pixels_per_sec = 100; | |
| 211 | + continue; | |
| 212 | + } | |
| 213 | + break; | |
| 214 | + } | |
| 215 | + | |
| 216 | + if (p->y_size) { | |
| 217 | + p->dft_size = 2 * (p->y_size - 1); | |
| 218 | + if (!is_p2(p->dft_size) && !effp->flow) | |
| 219 | + p->shared = rdft_init(p->dft_size); | |
| 220 | + } else { | |
| 221 | + int y = max(32, (p->Y_size? p->Y_size : 550) / effp->in_signal.channels - 2); | |
| 222 | + for (p->dft_size = 128; p->dft_size <= y; p->dft_size <<= 1); | |
| 223 | + } | |
| 224 | + if (is_p2(p->dft_size) && !effp->flow) | |
| 225 | + lsx_safe_rdft(p->dft_size, 1, p->dft_buf); | |
| 226 | + lsx_debug("duration=%g x_size=%i pixels_per_sec=%g dft_size=%i", duration, p->x_size, pixels_per_sec, p->dft_size); | |
| 227 | + | |
| 228 | + p->end = p->dft_size; | |
| 151 | 229 | p->rows = (p->dft_size >> 1) + 1; |
| 152 | 230 | actual = make_window(p, p->last_end = 0); |
| 153 | 231 | lsx_debug("window_density=%g", actual / p->dft_size); |
| 154 | 232 | p->step_size = (p->slack_overlap? sqrt(actual * p->dft_size) : actual) + .5; |
| 155 | − p->block_steps = effp->in_signal.rate / p->pixels_per_sec; | |
| 233 | + p->block_steps = effp->in_signal.rate / pixels_per_sec; | |
| 156 | 234 | p->step_size = p->block_steps / ceil((double)p->block_steps / p->step_size) +.5; |
| 157 | 235 | p->block_steps = floor((double)p->block_steps / p->step_size +.5); |
| 158 | 236 | p->block_norm = 1. / p->block_steps; |
| 159 | 237 | actual = effp->in_signal.rate / p->step_size / p->block_steps; |
| 160 | − if (actual != p->pixels_per_sec) | |
| 238 | + if (actual != pixels_per_sec) | |
| 161 | 239 | lsx_report("actual pixels/s = %g", actual); |
| 162 | 240 | lsx_debug("step_size=%i block_steps=%i", p->step_size, p->block_steps); |
| 163 | 241 | p->max = -p->dB_range; |
| @@ -170,13 +248,14 @@ static int do_column(sox_effect_t * effp) · 6 unmodified lines | ||
| 170 | 248 | priv_t * p = (priv_t *)effp->priv; |
| 171 | 249 | int i; |
| 172 | 250 | |
| 173 | − if (p->cols == p->max_cols) { | |
| 174 | − lsx_warn("PNG truncated at %g seconds", secs(p->cols)); | |
| 251 | + if (p->cols == p->x_size) { | |
| 175 | 252 | p->truncated = sox_true; |
| 176 | − return SOX_EOF; | |
| 253 | + if (!effp->flow) | |
| 254 | + lsx_report("PNG truncated at %g seconds", secs(p->cols)); | |
| 255 | + return p->truncate? SOX_EOF : SOX_SUCCESS; | |
| 177 | 256 | } |
| 178 | 257 | ++p->cols; |
| 179 | − p->dBfs = realloc(p->dBfs, p->cols * p->rows * sizeof(*p->dBfs)); | |
| 258 | + p->dBfs = lsx_realloc(p->dBfs, p->cols * p->rows * sizeof(*p->dBfs)); | |
| 180 | 259 | for (i = 0; i < p->rows; ++i) { |
| 181 | 260 | double dBfs = 10 * log10(p->magnitudes[i] * p->block_norm); |
| 182 | 261 | p->dBfs[(p->cols - 1) * p->rows + i] = dBfs + p->gain; |
| @@ -192,13 +271,21 @@ static int flow(sox_effect_t * effp, · 9 unmodified lines | ||
| 192 | 271 | size_t * isamp, size_t * osamp) |
| 193 | 272 | { |
| 194 | 273 | priv_t * p = (priv_t *)effp->priv; |
| 195 | − size_t len = min(*isamp, *osamp); | |
| 274 | + size_t len = *isamp = *osamp = min(*isamp, *osamp); | |
| 196 | 275 | int i; |
| 197 | 276 | |
| 198 | 277 | memcpy(obuf, ibuf, len * sizeof(*obuf)); /* Pass on audio unaffected */ |
| 199 | − *isamp = *osamp = len; | |
| 200 | 278 | |
| 201 | − while (sox_true) { | |
| 279 | + if (p->skip) { | |
| 280 | + if (p->skip >= len) { | |
| 281 | + p->skip -= len; | |
| 282 | + return SOX_SUCCESS; | |
| 283 | + } | |
| 284 | + ibuf += p->skip; | |
| 285 | + len -= p->skip; | |
| 286 | + p->skip = 0; | |
| 287 | + } | |
| 288 | + while (!p->truncated) { | |
| 202 | 289 | SOX_SAMPLE_LOCALS; |
| 203 | 290 | if (p->read == p->step_size) { |
| 204 | 291 | memmove(p->buf, p->buf + p->step_size, |
| @@ -206,19 +293,22 @@ static int flow(sox_effect_t * effp, · 1 unmodified line | ||
| 206 | 293 | p->read = 0; |
| 207 | 294 | } |
| 208 | 295 | for (; len && p->read < p->step_size; --len, ++p->read, --p->end) |
| 209 | − p->buf[p->dft_size - p->step_size + p->read] = FROM_SOX(*ibuf++,); | |
| 296 | + p->buf[p->dft_size - p->step_size + p->read] = | |
| 297 | + SOX_SAMPLE_TO_FLOAT_64BIT(*ibuf++,); | |
| 210 | 298 | if (p->read != p->step_size) |
| 211 | 299 | break; |
| 212 | 300 | |
| 213 | 301 | if ((p->end = max(p->end, p->end_min)) != p->last_end) |
| 214 | 302 | make_window(p, p->last_end = p->end); |
| 215 | 303 | for (i = 0; i < p->dft_size; ++i) p->dft_buf[i] = p->buf[i] * p->window[i]; |
| 216 | − lsx_rdft(p->dft_size, 1, p->dft_buf, p->bit_rev_table, p->sin_cos_table); | |
| 217 | − p->magnitudes[0] += sqr(p->dft_buf[0]); | |
| 218 | − for (i = 1; i < p->dft_size >> 1; ++i) | |
| 219 | − p->magnitudes[i] += sqr(p->dft_buf[2*i]) + sqr(p->dft_buf[2*i+1]); | |
| 220 | − p->magnitudes[p->dft_size >> 1] += sqr(p->dft_buf[1]); | |
| 221 | − | |
| 304 | + if (is_p2(p->dft_size)) { | |
| 305 | + lsx_rdft(p->dft_size, 1, p->dft_buf, lsx_fft_br, lsx_fft_sc); | |
| 306 | + p->magnitudes[0] += sqr(p->dft_buf[0]); | |
| 307 | + for (i = 1; i < p->dft_size >> 1; ++i) | |
| 308 | + p->magnitudes[i] += sqr(p->dft_buf[2*i]) + sqr(p->dft_buf[2*i+1]); | |
| 309 | + p->magnitudes[p->dft_size >> 1] += sqr(p->dft_buf[1]); | |
| 310 | + } | |
| 311 | + else rdft_p(*p->shared_ptr, p->dft_buf, p->magnitudes, p->dft_size); | |
| 222 | 312 | if (++p->block_num == p->block_steps && do_column(effp) == SOX_EOF) |
| 223 | 313 | return SOX_EOF; |
| 224 | 314 | } |
| @@ -230,8 +320,8 @@ static int drain(sox_effect_t * effp, sox_sample_t * obuf_, size_t * osamp) · 5 unmodified lines | ||
| 230 | 320 | priv_t * p = (priv_t *)effp->priv; |
| 231 | 321 | |
| 232 | 322 | if (!p->truncated) { |
| 233 | − sox_sample_t * ibuf = calloc(p->dft_size, sizeof(*ibuf)); | |
| 234 | − sox_sample_t * obuf = calloc(p->dft_size, sizeof(*obuf)); | |
| 323 | + sox_sample_t * ibuf = lsx_calloc(p->dft_size, sizeof(*ibuf)); | |
| 324 | + sox_sample_t * obuf = lsx_calloc(p->dft_size, sizeof(*obuf)); | |
| 235 | 325 | size_t isamp = (p->dft_size - p->step_size) / 2; |
| 236 | 326 | int left_over = (isamp + p->read) % p->step_size; |
| 237 | 327 | |
| @@ -255,7 +345,7 @@ enum {Background, Text, Labels, Grid, fixed_palette}; · 17 unmodified lines | ||
| 255 | 345 | |
| 256 | 346 | static unsigned colour(priv_t const * p, double x) |
| 257 | 347 | { |
| 258 | − unsigned c = x < -p->dB_range? 0 : x >= 0? p->spectrum_points - 1 : | |
| 348 | + unsigned c = x < -p->dB_range? 0 : x >= 0? p->spectrum_points - 1 : | |
| 259 | 349 | 1 + (1 + x / p->dB_range) * (p->spectrum_points - 2); |
| 260 | 350 | return fixed_palette + c; |
| 261 | 351 | } |
| @@ -277,7 +367,7 @@ static void make_palette(priv_t const * p, png_color * palette) · 15 unmodified lines | ||
| 277 | 367 | } |
| 278 | 368 | for (i = 0; i < p->spectrum_points; ++i) { |
| 279 | 369 | double c[3], x = (double)i / (p->spectrum_points - 1); |
| 280 | − int at = (p->light_background)? p->spectrum_points - 1 - i : i; | |
| 370 | + int at = p->light_background? p->spectrum_points - 1 - i : i; | |
| 281 | 371 | if (p->monochrome) { |
| 282 | 372 | c[2] = c[1] = c[0] = x; |
| 283 | 373 | if (p->high_colour) { |
| @@ -302,6 +392,11 @@ static void make_palette(priv_t const * p, png_color * palette) · 18 unmodified lines | ||
| 302 | 392 | case 4: c[j] = 7 * x - phase_num; break; |
| 303 | 393 | case 5: c[j] = 1 - (7 * x - phase_num); break; |
| 304 | 394 | } |
| 395 | + } else if (p->alt_palette) { | |
| 396 | + int n = (double)i / (p->spectrum_points - 1) * (alt_palette_len - 1) + .5; | |
| 397 | + c[0] = alt_palette[3 * n + 0] / 255.; | |
| 398 | + c[1] = alt_palette[3 * n + 1] / 255.; | |
| 399 | + c[2] = alt_palette[3 * n + 2] / 255.; | |
| 305 | 400 | } else { |
| 306 | 401 | if (x < .13) c[0] = 0; |
| 307 | 402 | else if (x < .73) c[0] = 1 * sin((x - .13) / .60 * M_PI / 2); |
| @@ -368,7 +463,7 @@ static void print_at_(png_byte * pixels, int cols, int x, int y, int c, char con · 60 unmodified lines | ||
| 368 | 463 | |
| 369 | 464 | static int axis(double to, int max_steps, double * limit, char * * prefix) |
| 370 | 465 | { |
| 371 | − double scale = 1, step = 1; | |
| 466 | + double scale = 1, step = max(1, 10 * to); | |
| 372 | 467 | int i, prefix_num = 0; |
| 373 | 468 | if (max_steps) { |
| 374 | 469 | double try, log_10 = HUGE_VAL, min_step = (to *= 10) / max_steps; |
| @@ -393,27 +488,26 @@ static int stop(sox_effect_t * effp) · 18 unmodified lines | ||
| 393 | 488 | priv_t * p = (priv_t *) effp->priv; |
| 394 | 489 | FILE * file = fopen(p->out_name, "wb"); |
| 395 | 490 | uLong font_len = 96 * font_y; |
| 396 | − int rows = below + p->rows + 30 + 20 * !!p->title; | |
| 491 | + int chans = effp->in_signal.channels; | |
| 492 | + int c_rows = p->rows * chans + chans - 1; | |
| 493 | + int rows = below + c_rows + 30 + 20 * !!p->title; | |
| 397 | 494 | int cols = left + p->cols + between + spectrum_width + right; |
| 398 | − png_byte * pixels = malloc(cols * rows * sizeof(*pixels)); | |
| 399 | − png_bytepp png_rows = malloc(rows * sizeof(*png_rows)); | |
| 495 | + png_byte * pixels = lsx_malloc(cols * rows * sizeof(*pixels)); | |
| 496 | + png_bytepp png_rows = lsx_malloc(rows * sizeof(*png_rows)); | |
| 400 | 497 | png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0,0); |
| 401 | 498 | png_infop png_info = png_create_info_struct(png); |
| 402 | 499 | png_color palette[256]; |
| 403 | − int i, j, step, tick_len = 3 - p->no_axes; | |
| 500 | + int i, j, k, base, step, tick_len = 3 - p->no_axes; | |
| 404 | 501 | char text[200], * prefix; |
| 405 | 502 | double limit; |
| 406 | 503 | |
| 504 | + free(p->shared); | |
| 407 | 505 | if (!file) { |
| 408 | − lsx_fail("failed to create `%s' :(", p->out_name); | |
| 409 | − png_destroy_write_struct(&png, &png_info); | |
| 410 | − free(png_rows); | |
| 411 | − free(pixels); | |
| 412 | − free(p->dBfs); | |
| 413 | − return SOX_EOF; | |
| 506 | + lsx_fail("failed to create `%s': %s", p->out_name, strerror(errno)); | |
| 507 | + goto error; | |
| 414 | 508 | } |
| 415 | 509 | lsx_debug("signal-max=%g", p->max); |
| 416 | − font = malloc(font_len); | |
| 510 | + font = lsx_malloc(font_len); | |
| 417 | 511 | assert(uncompress(font, &font_len, fixed, sizeof(fixed)-1) == Z_OK); |
| 418 | 512 | make_palette(p, palette); |
| 419 | 513 | memset(pixels, Background, cols * rows * sizeof(*pixels)); |
| @@ -431,14 +525,18 @@ static int stop(sox_effect_t * effp) · 11 unmodified lines | ||
| 431 | 525 | print_at(1, font_y, Text, p->comment); |
| 432 | 526 | |
| 433 | 527 | /* Spectrogram */ |
| 434 | − for (j = 0; j < p->rows; ++j) { | |
| 435 | − for (i = 0; i < p->cols; ++i) | |
| 436 | − pixel(left + i, below + j) = colour(p, p->dBfs[i*p->rows + j]); | |
| 437 | − if (!p->no_axes) /* Y-axis lines */ | |
| 438 | − pixel(left - 1, below + j) = pixel(left + p->cols,below + j) = Grid; | |
| 528 | + for (k = 0; k < chans; ++k) { | |
| 529 | + priv_t * q = (priv_t *)(effp - effp->flow + k)->priv; | |
| 530 | + base = below + (chans - 1 - k) * (p->rows + 1); | |
| 531 | + for (j = 0; j < p->rows; ++j) { | |
| 532 | + for (i = 0; i < p->cols; ++i) | |
| 533 | + pixel(left + i, base + j) = colour(p, q->dBfs[i*p->rows + j]); | |
| 534 | + if (!p->no_axes) /* Y-axis lines */ | |
| 535 | + pixel(left - 1, base + j) = pixel(left + p->cols, base + j) = Grid; | |
| 536 | + } | |
| 537 | + if (!p->no_axes) for (i = -1; i <= p->cols; ++i) /* X-axis lines */ | |
| 538 | + pixel(left + i, base - 1) = pixel(left + i, base + p->rows) = Grid; | |
| 439 | 539 | } |
| 440 | − if (!p->no_axes) for (i = -1; i <= p->cols; ++i) /* X-axis lines */ | |
| 441 | − pixel(left + i, below - 1) = pixel(left + i, below + p->rows) = Grid; | |
| 442 | 540 | |
| 443 | 541 | /* X-axis */ |
| 444 | 542 | step = axis(secs(p->cols), p->cols / (font_X * 9 / 2), &limit, &prefix); |
| @@ -447,79 +545,91 @@ static int stop(sox_effect_t * effp) · 2 unmodified lines | ||
| 447 | 545 | for (i = 0; i <= limit; i += step) { |
| 448 | 546 | int y, x = limit? (double)i / limit * p->cols + .5 : 0; |
| 449 | 547 | for (y = 0; y < tick_len; ++y) /* Ticks */ |
| 450 | − pixel(left-1+x, below-1-y) = pixel(left-1+x, below+p->rows+y) = Grid; | |
| 548 | + pixel(left-1+x, below-1-y) = pixel(left-1+x, below+c_rows+y) = Grid; | |
| 451 | 549 | if (step == 5 && (i%10)) |
| 452 | 550 | continue; |
| 453 | 551 | sprintf(text, "%g", .1 * i); /* Tick labels */ |
| 454 | 552 | x = left + x - 3 * strlen(text); |
| 455 | 553 | print_at(x, below - 6, Labels, text); |
| 456 | − print_at(x, below + p->rows + 14, Labels, text); | |
| 554 | + print_at(x, below + c_rows + 14, Labels, text); | |
| 457 | 555 | } |
| 458 | 556 | |
| 459 | 557 | /* Y-axis */ |
| 460 | 558 | step = axis(effp->in_signal.rate / 2, |
| 461 | 559 | (p->rows - 1) / ((font_y * 3 + 1) >> 1), &limit, &prefix); |
| 462 | 560 | sprintf(text, "Frequency (%.1sHz)", prefix); /* Axis label */ |
| 463 | − print_up(10, below + (p->rows - font_X * (int)strlen(text)) / 2, Text, text); | |
| 464 | − for (i = 0; i <= limit; i += step) { | |
| 465 | − int x, y = limit? (double)i / limit * (p->rows - 1) + .5 : 0; | |
| 466 | − for (x = 0; x < tick_len; ++x) /* Ticks */ | |
| 467 | − pixel(left-1-x, below+y) = pixel(left+p->cols+x, below+y) = Grid; | |
| 468 | − if (step == 5 && (i%10)) | |
| 469 | − continue; | |
| 470 | − sprintf(text, i?"%5g":" DC", .1 * i); /* Tick labels */ | |
| 471 | − print_at(left - 4 - font_X * 5, below + y + 5, Labels, text); | |
| 472 | − sprintf(text, i?"%g":"DC", .1 * i); | |
| 473 | − print_at(left + p->cols + 6, below + y + 5, Labels, text); | |
| 561 | + print_up(10, below + (c_rows - font_X * (int)strlen(text)) / 2, Text, text); | |
| 562 | + for (k = 0; k < chans; ++k) { | |
| 563 | + base = below + k * (p->rows + 1); | |
| 564 | + for (i = 0; i <= limit; i += step) { | |
| 565 | + int x, y = limit? (double)i / limit * (p->rows - 1) + .5 : 0; | |
| 566 | + for (x = 0; x < tick_len; ++x) /* Ticks */ | |
| 567 | + pixel(left-1-x, base+y) = pixel(left+p->cols+x, base+y) = Grid; | |
| 568 | + if ((step == 5 && (i%10)) || (!i && k && chans > 1)) | |
| 569 | + continue; | |
| 570 | + sprintf(text, i?"%5g":" DC", .1 * i); /* Tick labels */ | |
| 571 | + print_at(left - 4 - font_X * 5, base + y + 5, Labels, text); | |
| 572 | + sprintf(text, i?"%g":"DC", .1 * i); | |
| 573 | + print_at(left + p->cols + 6, base + y + 5, Labels, text); | |
| 574 | + } | |
| 474 | 575 | } |
| 475 | 576 | |
| 476 | 577 | /* Z-axis */ |
| 477 | − print_at(cols - right - 2 - font_X, below - 13, Text, "dBFS");/* Axis label */ | |
| 478 | − for (j = 0; j < p->rows; ++j) { /* Spectrum */ | |
| 479 | − png_byte b = colour(p, p->dB_range * (j / (p->rows - 1.) - 1)); | |
| 578 | + k = min(400, c_rows); | |
| 579 | + base = below + (c_rows - k) / 2; | |
| 580 | + print_at(cols - right - 2 - font_X, base - 13, Text, "dBFS");/* Axis label */ | |
| 581 | + for (j = 0; j < k; ++j) { /* Spectrum */ | |
| 582 | + png_byte b = colour(p, p->dB_range * (j / (k - 1.) - 1)); | |
| 480 | 583 | for (i = 0; i < spectrum_width; ++i) |
| 481 | − pixel(cols - right - 1 - i, below + j) = b; | |
| 584 | + pixel(cols - right - 1 - i, base + j) = b; | |
| 482 | 585 | } |
| 483 | − for (i = 0; i <= p->dB_range; i += 10) { /* (Tick) labels */ | |
| 484 | − int y = (double)i / p->dB_range * (p->rows - 1) + .5; | |
| 586 | + step = 10 * ceil(p->dB_range / 10. * (font_y + 2) / (k - 1)); | |
| 587 | + for (i = 0; i <= p->dB_range; i += step) { /* (Tick) labels */ | |
| 588 | + int y = (double)i / p->dB_range * (k - 1) + .5; | |
| 485 | 589 | sprintf(text, "%+i", i - p->gain - p->dB_range); |
| 486 | − print_at(cols - right + 1, below + y + 5, Labels, text); | |
| 590 | + print_at(cols - right + 1, base + y + 5, Labels, text); | |
| 487 | 591 | } |
| 488 | 592 | |
| 489 | 593 | free(font); |
| 490 | 594 | png_set_rows(png, png_info, png_rows); |
| 491 | 595 | png_write_png(png, png_info, PNG_TRANSFORM_IDENTITY, NULL); |
| 492 | − png_destroy_write_struct(&png, &png_info); | |
| 596 | + fclose(file); | |
| 597 | +error: png_destroy_write_struct(&png, &png_info); | |
| 493 | 598 | free(png_rows); |
| 494 | 599 | free(pixels); |
| 495 | − fclose(file); | |
| 496 | 600 | free(p->dBfs); |
| 497 | 601 | return SOX_SUCCESS; |
| 498 | 602 | } |
| 499 | 603 | |
| 604 | +static int end(sox_effect_t * effp) {return effp->flow? SOX_SUCCESS:stop(effp);} | |
| 605 | + | |
| 500 | 606 | sox_effect_handler_t const * lsx_spectrogram_effect_fn(void) |
| 501 | 607 | { |
| 502 | − static sox_effect_handler_t handler = { | |
| 503 | − "spectrogram", 0, SOX_EFF_MODIFY, getopts, start, flow, drain, stop, 0, sizeof(priv_t)}; | |
| 608 | + static sox_effect_handler_t handler = {"spectrogram", 0, SOX_EFF_MODIFY, | |
| 609 | + getopts, start, flow, drain, end, 0, sizeof(priv_t)}; | |
| 504 | 610 | static char const * lines[] = { |
| 505 | 611 | "[options]", |
| 506 | − "\t-M num\tMaximum width of spectrogram in pixels, default 1000", | |
| 507 | − "\t-x num\tX-axis pixels/second, default 100. -x & -X are alternatives", | |
| 508 | − "\t-X time\tAudio duration to fit to X-axis e.g. $(soxi -D file)", | |
| 509 | − "\t-y num\tY-axis resolution (1 - 4), default 2", | |
| 510 | − "\t-z num\tZ-axis range in dB, default 120", | |
| 511 | − "\t-Z num\tZ-axis maximum in dBFS, default 0", | |
| 512 | − "\t-q num\tZ-axis quantisation (0 - 249), default 249", | |
| 612 | + "\t-x num\tX-axis size in pixels; default derived or 800", | |
| 613 | + "\t-X num\tX-axis pixels/second; default derived or 100", | |
| 614 | + "\t-y num\tY-axis size in pixels (per channel); slow if not 1 + 2^n", | |
| 615 | + "\t-Y num\tY-height total (i.e. not per channel); default 550", | |
| 616 | + "\t-z num\tZ-axis range in dB; default 120", | |
| 617 | + "\t-Z num\tZ-axis maximum in dBFS; default 0", | |
| 618 | + "\t-q num\tZ-axis quantisation (0 - 249); default 249", | |
| 513 | 619 | "\t-w name\tWindow: Hann (default), Hamming, Bartlett, Rectangular, Kaiser", |
| 620 | + "\t-W num\tWindow adjust parameter (-10 - 10); applies only to Kaiser", | |
| 514 | 621 | "\t-s\tSlack overlap of windows", |
| 515 | 622 | "\t-a\tSuppress axis lines", |
| 516 | 623 | "\t-l\tLight background", |
| 517 | 624 | "\t-m\tMonochrome", |
| 518 | 625 | "\t-h\tHigh colour", |
| 519 | − "\t-p num\tPermute colours (1 - 6)", | |
| 626 | + "\t-p num\tPermute colours (1 - 6); default 1", | |
| 627 | + "\t-A\tAlternative, inferior, fixed colour-set (for compatibility only)", | |
| 520 | 628 | "\t-t text\tTitle text", |
| 521 | 629 | "\t-c text\tComment text", |
| 522 | − "\t-o text\tOutput file name, default `spectrogram.png'", | |
| 630 | + "\t-o text\tOutput file name; default `spectrogram.png'", | |
| 631 | + "\t-d time\tAudio duration to fit to X-axis; e.g. 1:00, 48", | |
| 632 | + "\t-S time\tStart the spectrogram at the given time through the input", | |
| 523 | 633 | }; |
| 524 | 634 | static char * usage; |
| 525 | 635 | handler.usage = lsx_usage_lines(&usage, lines, array_length(lines)); |