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 | * This library is free software; you can redistribute it and/or modify it | 3 | * This library is free software; you can redistribute it and/or modify it |
| 4 | * under the terms of the GNU Lesser General Public License as published by | 4 | * under the terms of the GNU Lesser General Public License as published by |
| @@ -15,11 +15,6 @@ · 10 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 | |||
| 23 | #ifdef NDEBUG /* Enable assert always. */ | 18 | #ifdef NDEBUG /* Enable assert always. */ |
| 24 | #undef NDEBUG /* Must undef above assert.h or other that might include it. */ | 19 | #undef NDEBUG /* Must undef above assert.h or other that might include it. */ |
| 25 | #endif | 20 | #endif |
| @@ -31,14 +26,8 @@ · 5 unmodified lines | |||
| 31 | #include <math.h> | 26 | #include <math.h> |
| 32 | #include <png.h> | 27 | #include <png.h> |
| 33 | 28 | ||
| 34 | #define malloc lsx_malloc | 29 | #define MAX_FFT_SIZE 4096 |
| 35 | #define calloc lsx_calloc | 30 | #define is_p2(x) !(x & (x - 1)) |
| 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 */ | ||
| 42 | 31 | ||
| 43 | typedef enum {Window_Hann, Window_Hamming, Window_Bartlett, Window_Rectangular, Window_Kaiser} win_type_t; | 32 | typedef enum {Window_Hann, Window_Hamming, Window_Bartlett, Window_Rectangular, Window_Kaiser} win_type_t; |
| 44 | static lsx_enum_item const window_options[] = { | 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 | typedef struct { | 41 | typedef struct { |
| 53 | /* Parameters */ | 42 | /* Parameters */ |
| 54 | double pixels_per_sec; | 43 | double pixels_per_sec, duration, start_time, window_adjust; |
| 55 | int y_size, dB_range, gain, spectrum_points, perm, max_cols; | 44 | int x_size0, y_size, Y_size, dB_range, gain, spectrum_points, perm; |
| 56 | sox_bool monochrome, light_background, high_colour, slack_overlap, no_axes; | 45 | sox_bool monochrome, light_background, high_colour, slack_overlap, no_axes; |
| 46 | sox_bool alt_palette, truncate; | ||
| 57 | win_type_t win_type; | 47 | win_type_t win_type; |
| 58 | char const * out_name, * title, * comment; | 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 | int WORK; /* Start of work area is marked by this dummy variable. */ | 54 | int WORK; /* Start of work area is marked by this dummy variable. */ |
| 55 | size_t skip; | ||
| 62 | int dft_size, step_size, block_steps, block_num, rows, cols, read; | 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 | sox_bool truncated; | 58 | sox_bool truncated; |
| 65 | double buf[MAX_DFT_SIZE], dft_buf[MAX_DFT_SIZE], window[MAX_DFT_SIZE]; | 59 | double buf[MAX_FFT_SIZE], dft_buf[MAX_FFT_SIZE], window[MAX_FFT_SIZE]; |
| 66 | double block_norm, max, magnitudes[(MAX_DFT_SIZE>>1) + 1]; | 60 | double block_norm, max, magnitudes[(MAX_FFT_SIZE>>1) + 1]; |
| 67 | int bit_rev_table[100]; /* For Ooura fft */ | ||
| 68 | double sin_cos_table[dft_sc_len(MAX_DFT_SIZE)]; /* ditto */ | ||
| 69 | float * dBfs; | 61 | float * dBfs; |
| 70 | } priv_t; | 62 | } priv_t; |
| 71 | 63 | ||
| 72 | #define secs(cols) \ | 64 | #define secs(cols) \ |
| 73 | ((double)(cols) * p->step_size * p->block_steps / effp->in_signal.rate) | 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 | static int getopts(sox_effect_t * effp, int argc, char **argv) | 88 | static int getopts(sox_effect_t * effp, int argc, char **argv) |
| 76 | { | 89 | { |
| 77 | priv_t * p = (priv_t *)effp->priv; | 90 | priv_t * p = (priv_t *)effp->priv; |
| 78 | size_t duration_1e5; | 91 | size_t duration; |
| 79 | char const * next; | 92 | char const * next; |
| 80 | int c; | 93 | int c; |
| 81 | 94 | ||
| 82 | assert(array_length(p->bit_rev_table) >= (size_t)dft_br_len(MAX_DFT_SIZE)); | 95 | p->dB_range = 120, p->spectrum_points = 249, p->perm = 1; /* Non-0 defaults */ |
| 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; | ||
| 86 | p->out_name = "spectrogram.png", p->comment = "Created by SoX"; | 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) { | 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) { |
| 89 | GETOPT_NUMERIC('M', max_cols , 100, 2000) | 99 | GETOPT_NUMERIC('x', x_size0 , 100, 5000) |
| 90 | GETOPT_NUMERIC('x', pixels_per_sec, 1 , 5000) | 100 | GETOPT_NUMERIC('X', pixels_per_sec, 1 , 5000) |
| 91 | GETOPT_NUMERIC('y', y_size , 1 , 1 + MAX_DFT_SIZE_SHIFT) | 101 | GETOPT_NUMERIC('y', y_size , 64 , 1200) |
| 102 | GETOPT_NUMERIC('Y', Y_size , 130, MAX_FFT_SIZE / 2 + 2) | ||
| 92 | GETOPT_NUMERIC('z', dB_range , 20 , 180) | 103 | GETOPT_NUMERIC('z', dB_range , 20 , 180) |
| 93 | GETOPT_NUMERIC('Z', gain ,-100, 100) | 104 | GETOPT_NUMERIC('Z', gain ,-100, 100) |
| 94 | GETOPT_NUMERIC('q', spectrum_points, 0 , p->spectrum_points) | 105 | GETOPT_NUMERIC('q', spectrum_points, 0 , p->spectrum_points) |
| 95 | GETOPT_NUMERIC('p', perm , 1 , 6) | 106 | GETOPT_NUMERIC('p', perm , 1 , 6) |
| 107 | GETOPT_NUMERIC('W', window_adjust , -10, 10) | ||
| 96 | case 'w': p->win_type = lsx_enum_option(c, window_options); break; | 108 | case 'w': p->win_type = lsx_enum_option(c, window_options); break; |
| 97 | case 's': p->slack_overlap = sox_true; break; | 109 | case 's': p->slack_overlap = sox_true; break; |
| 98 | case 'X': | 110 | case 'A': p->alt_palette = sox_true; break; |
| 99 | next = lsx_parsesamples(1e5, lsx_optarg, &duration_1e5, 't'); | 111 | case 'a': p->no_axes = sox_true; break; |
| 100 | if (next == NULL || *next != '\0' || duration_1e5 > p->max_cols * 1e5 | 112 | case 'm': p->monochrome = sox_true; break; |
| 101 | || duration_1e5 < 1e5 / 5000 * p->max_cols) | 113 | case 'l': p->light_background = sox_true; break; |
| 102 | return lsx_usage(effp); | 114 | case 'h': p->high_colour = sox_true; break; |
| 103 | p->pixels_per_sec = p->max_cols * 1e5 / duration_1e5; | 115 | case 'T': p->truncate = sox_true; break; |
| 104 | break; | 116 | case 't': p->title = lsx_optarg; break; |
| 105 | case 't': p->title = lsx_optarg; break; | 117 | case 'c': p->comment = lsx_optarg; break; |
| 106 | case 'c': p->comment = lsx_optarg; break; | 118 | case 'o': p->out_name = lsx_optarg; break; |
| 107 | case 'a': p->no_axes = sox_true; break; | 119 | case 'S': next = lsx_parsesamples(1e5, lsx_optarg, &duration, 't'); |
| 108 | case 'm': p->monochrome = sox_true; break; | 120 | if (next && !*next) {p->start_time = duration * 1e-5; break;} |
| 109 | case 'l': p->light_background = sox_true; break; | 121 | return lsx_usage(effp); |
| 110 | case 'h': p->high_colour = sox_true; break; | 122 | case 'd': next = lsx_parsesamples(1e5, lsx_optarg, &duration, 't'); |
| 111 | case 'o': p->out_name = lsx_optarg; break; | 123 | if (next && !*next) {p->duration = duration * 1e-5; break;} |
| 124 | return lsx_usage(effp); | ||
| 112 | default: lsx_fail("invalid option `-%c'", optopt); return lsx_usage(effp); | 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 | p->gain = -p->gain; | 135 | p->gain = -p->gain; |
| 115 | --p->y_size, --p->perm; | 136 | --p->perm; |
| 116 | p->spectrum_points += 2; | 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 | return lsx_optind !=argc || p->win_type == INT_MAX? lsx_usage(effp) : SOX_SUCCESS; | 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 | case Window_Hamming: lsx_apply_hamming(w, n); break; | 153 | case Window_Hamming: lsx_apply_hamming(w, n); break; |
| 130 | case Window_Bartlett: lsx_apply_bartlett(w, n); break; | 154 | case Window_Bartlett: lsx_apply_bartlett(w, n); break; |
| 131 | case Window_Rectangular: break; | 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 | for (i = 0; i < p->dft_size; ++i) sum += p->window[i]; | 159 | for (i = 0; i < p->dft_size; ++i) sum += p->window[i]; |
| 135 | for (i = 0; i < p->dft_size; ++i) p->window[i] *= 2 / sum | 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 | return sum; | 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 | static int start(sox_effect_t * effp) | 186 | static int start(sox_effect_t * effp) |
| 141 | { | 187 | { |
| 142 | priv_t * p = (priv_t *)effp->priv; | 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 | memset(&p->WORK, 0, sizeof(*p) - field_offset(priv_t, WORK)); | 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 | p->rows = (p->dft_size >> 1) + 1; | 229 | p->rows = (p->dft_size >> 1) + 1; |
| 152 | actual = make_window(p, p->last_end = 0); | 230 | actual = make_window(p, p->last_end = 0); |
| 153 | lsx_debug("window_density=%g", actual / p->dft_size); | 231 | lsx_debug("window_density=%g", actual / p->dft_size); |
| 154 | p->step_size = (p->slack_overlap? sqrt(actual * p->dft_size) : actual) + .5; | 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 | p->step_size = p->block_steps / ceil((double)p->block_steps / p->step_size) +.5; | 234 | p->step_size = p->block_steps / ceil((double)p->block_steps / p->step_size) +.5; |
| 157 | p->block_steps = floor((double)p->block_steps / p->step_size +.5); | 235 | p->block_steps = floor((double)p->block_steps / p->step_size +.5); |
| 158 | p->block_norm = 1. / p->block_steps; | 236 | p->block_norm = 1. / p->block_steps; |
| 159 | actual = effp->in_signal.rate / p->step_size / p->block_steps; | 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 | lsx_report("actual pixels/s = %g", actual); | 239 | lsx_report("actual pixels/s = %g", actual); |
| 162 | lsx_debug("step_size=%i block_steps=%i", p->step_size, p->block_steps); | 240 | lsx_debug("step_size=%i block_steps=%i", p->step_size, p->block_steps); |
| 163 | p->max = -p->dB_range; | 241 | p->max = -p->dB_range; |
| @@ -170,13 +248,14 @@ static int do_column(sox_effect_t * effp) · 6 unmodified lines | |||
| 170 | priv_t * p = (priv_t *)effp->priv; | 248 | priv_t * p = (priv_t *)effp->priv; |
| 171 | int i; | 249 | int i; |
| 172 | 250 | ||
| 173 | if (p->cols == p->max_cols) { | 251 | if (p->cols == p->x_size) { |
| 174 | lsx_warn("PNG truncated at %g seconds", secs(p->cols)); | ||
| 175 | p->truncated = sox_true; | 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 | ++p->cols; | 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 | for (i = 0; i < p->rows; ++i) { | 259 | for (i = 0; i < p->rows; ++i) { |
| 181 | double dBfs = 10 * log10(p->magnitudes[i] * p->block_norm); | 260 | double dBfs = 10 * log10(p->magnitudes[i] * p->block_norm); |
| 182 | p->dBfs[(p->cols - 1) * p->rows + i] = dBfs + p->gain; | 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 | size_t * isamp, size_t * osamp) | 271 | size_t * isamp, size_t * osamp) |
| 193 | { | 272 | { |
| 194 | priv_t * p = (priv_t *)effp->priv; | 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 | int i; | 275 | int i; |
| 197 | 276 | ||
| 198 | memcpy(obuf, ibuf, len * sizeof(*obuf)); /* Pass on audio unaffected */ | 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 | SOX_SAMPLE_LOCALS; | 289 | SOX_SAMPLE_LOCALS; |
| 203 | if (p->read == p->step_size) { | 290 | if (p->read == p->step_size) { |
| 204 | memmove(p->buf, p->buf + p->step_size, | 291 | memmove(p->buf, p->buf + p->step_size, |
| @@ -206,19 +293,22 @@ static int flow(sox_effect_t * effp, · 1 unmodified line | |||
| 206 | p->read = 0; | 293 | p->read = 0; |
| 207 | } | 294 | } |
| 208 | for (; len && p->read < p->step_size; --len, ++p->read, --p->end) | 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 | if (p->read != p->step_size) | 298 | if (p->read != p->step_size) |
| 211 | break; | 299 | break; |
| 212 | 300 | ||
| 213 | if ((p->end = max(p->end, p->end_min)) != p->last_end) | 301 | if ((p->end = max(p->end, p->end_min)) != p->last_end) |
| 214 | make_window(p, p->last_end = p->end); | 302 | make_window(p, p->last_end = p->end); |
| 215 | for (i = 0; i < p->dft_size; ++i) p->dft_buf[i] = p->buf[i] * p->window[i]; | 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); | 304 | if (is_p2(p->dft_size)) { |
| 217 | p->magnitudes[0] += sqr(p->dft_buf[0]); | 305 | lsx_rdft(p->dft_size, 1, p->dft_buf, lsx_fft_br, lsx_fft_sc); |
| 218 | for (i = 1; i < p->dft_size >> 1; ++i) | 306 | p->magnitudes[0] += sqr(p->dft_buf[0]); |
| 219 | p->magnitudes[i] += sqr(p->dft_buf[2*i]) + sqr(p->dft_buf[2*i+1]); | 307 | for (i = 1; i < p->dft_size >> 1; ++i) |
| 220 | p->magnitudes[p->dft_size >> 1] += sqr(p->dft_buf[1]); | 308 | p->magnitudes[i] += sqr(p->dft_buf[2*i]) + sqr(p->dft_buf[2*i+1]); |
| 221 | 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 | if (++p->block_num == p->block_steps && do_column(effp) == SOX_EOF) | 312 | if (++p->block_num == p->block_steps && do_column(effp) == SOX_EOF) |
| 223 | return SOX_EOF; | 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 | priv_t * p = (priv_t *)effp->priv; | 320 | priv_t * p = (priv_t *)effp->priv; |
| 231 | 321 | ||
| 232 | if (!p->truncated) { | 322 | if (!p->truncated) { |
| 233 | sox_sample_t * ibuf = calloc(p->dft_size, sizeof(*ibuf)); | 323 | sox_sample_t * ibuf = lsx_calloc(p->dft_size, sizeof(*ibuf)); |
| 234 | sox_sample_t * obuf = calloc(p->dft_size, sizeof(*obuf)); | 324 | sox_sample_t * obuf = lsx_calloc(p->dft_size, sizeof(*obuf)); |
| 235 | size_t isamp = (p->dft_size - p->step_size) / 2; | 325 | size_t isamp = (p->dft_size - p->step_size) / 2; |
| 236 | int left_over = (isamp + p->read) % p->step_size; | 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 | static unsigned colour(priv_t const * p, double x) | 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 | 1 + (1 + x / p->dB_range) * (p->spectrum_points - 2); | 349 | 1 + (1 + x / p->dB_range) * (p->spectrum_points - 2); |
| 260 | return fixed_palette + c; | 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 | for (i = 0; i < p->spectrum_points; ++i) { | 368 | for (i = 0; i < p->spectrum_points; ++i) { |
| 279 | double c[3], x = (double)i / (p->spectrum_points - 1); | 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 | if (p->monochrome) { | 371 | if (p->monochrome) { |
| 282 | c[2] = c[1] = c[0] = x; | 372 | c[2] = c[1] = c[0] = x; |
| 283 | if (p->high_colour) { | 373 | if (p->high_colour) { |
| @@ -302,6 +392,11 @@ static void make_palette(priv_t const * p, png_color * palette) · 18 unmodified lines | |||
| 302 | case 4: c[j] = 7 * x - phase_num; break; | 392 | case 4: c[j] = 7 * x - phase_num; break; |
| 303 | case 5: c[j] = 1 - (7 * x - phase_num); break; | 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 | } else { | 400 | } else { |
| 306 | if (x < .13) c[0] = 0; | 401 | if (x < .13) c[0] = 0; |
| 307 | else if (x < .73) c[0] = 1 * sin((x - .13) / .60 * M_PI / 2); | 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 | static int axis(double to, int max_steps, double * limit, char * * prefix) | 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 | int i, prefix_num = 0; | 467 | int i, prefix_num = 0; |
| 373 | if (max_steps) { | 468 | if (max_steps) { |
| 374 | double try, log_10 = HUGE_VAL, min_step = (to *= 10) / max_steps; | 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 | priv_t * p = (priv_t *) effp->priv; | 488 | priv_t * p = (priv_t *) effp->priv; |
| 394 | FILE * file = fopen(p->out_name, "wb"); | 489 | FILE * file = fopen(p->out_name, "wb"); |
| 395 | uLong font_len = 96 * font_y; | 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 | int cols = left + p->cols + between + spectrum_width + right; | 494 | int cols = left + p->cols + between + spectrum_width + right; |
| 398 | png_byte * pixels = malloc(cols * rows * sizeof(*pixels)); | 495 | png_byte * pixels = lsx_malloc(cols * rows * sizeof(*pixels)); |
| 399 | png_bytepp png_rows = malloc(rows * sizeof(*png_rows)); | 496 | png_bytepp png_rows = lsx_malloc(rows * sizeof(*png_rows)); |
| 400 | png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0,0); | 497 | png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0,0); |
| 401 | png_infop png_info = png_create_info_struct(png); | 498 | png_infop png_info = png_create_info_struct(png); |
| 402 | png_color palette[256]; | 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 | char text[200], * prefix; | 501 | char text[200], * prefix; |
| 405 | double limit; | 502 | double limit; |
| 406 | 503 | ||
| 504 | free(p->shared); | ||
| 407 | if (!file) { | 505 | if (!file) { |
| 408 | lsx_fail("failed to create `%s' :(", p->out_name); | 506 | lsx_fail("failed to create `%s': %s", p->out_name, strerror(errno)); |
| 409 | png_destroy_write_struct(&png, &png_info); | 507 | goto error; |
| 410 | free(png_rows); | ||
| 411 | free(pixels); | ||
| 412 | free(p->dBfs); | ||
| 413 | return SOX_EOF; | ||
| 414 | } | 508 | } |
| 415 | lsx_debug("signal-max=%g", p->max); | 509 | lsx_debug("signal-max=%g", p->max); |
| 416 | font = malloc(font_len); | 510 | font = lsx_malloc(font_len); |
| 417 | assert(uncompress(font, &font_len, fixed, sizeof(fixed)-1) == Z_OK); | 511 | assert(uncompress(font, &font_len, fixed, sizeof(fixed)-1) == Z_OK); |
| 418 | make_palette(p, palette); | 512 | make_palette(p, palette); |
| 419 | memset(pixels, Background, cols * rows * sizeof(*pixels)); | 513 | memset(pixels, Background, cols * rows * sizeof(*pixels)); |
| @@ -431,14 +525,18 @@ static int stop(sox_effect_t * effp) · 11 unmodified lines | |||
| 431 | print_at(1, font_y, Text, p->comment); | 525 | print_at(1, font_y, Text, p->comment); |
| 432 | 526 | ||
| 433 | /* Spectrogram */ | 527 | /* Spectrogram */ |
| 434 | for (j = 0; j < p->rows; ++j) { | 528 | for (k = 0; k < chans; ++k) { |
| 435 | for (i = 0; i < p->cols; ++i) | 529 | priv_t * q = (priv_t *)(effp - effp->flow + k)->priv; |
| 436 | pixel(left + i, below + j) = colour(p, p->dBfs[i*p->rows + j]); | 530 | base = below + (chans - 1 - k) * (p->rows + 1); |
| 437 | if (!p->no_axes) /* Y-axis lines */ | 531 | for (j = 0; j < p->rows; ++j) { |
| 438 | pixel(left - 1, below + j) = pixel(left + p->cols,below + j) = Grid; | 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 | /* X-axis */ | 541 | /* X-axis */ |
| 444 | step = axis(secs(p->cols), p->cols / (font_X * 9 / 2), &limit, &prefix); | 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 | for (i = 0; i <= limit; i += step) { | 545 | for (i = 0; i <= limit; i += step) { |
| 448 | int y, x = limit? (double)i / limit * p->cols + .5 : 0; | 546 | int y, x = limit? (double)i / limit * p->cols + .5 : 0; |
| 449 | for (y = 0; y < tick_len; ++y) /* Ticks */ | 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 | if (step == 5 && (i%10)) | 549 | if (step == 5 && (i%10)) |
| 452 | continue; | 550 | continue; |
| 453 | sprintf(text, "%g", .1 * i); /* Tick labels */ | 551 | sprintf(text, "%g", .1 * i); /* Tick labels */ |
| 454 | x = left + x - 3 * strlen(text); | 552 | x = left + x - 3 * strlen(text); |
| 455 | print_at(x, below - 6, Labels, text); | 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 | /* Y-axis */ | 557 | /* Y-axis */ |
| 460 | step = axis(effp->in_signal.rate / 2, | 558 | step = axis(effp->in_signal.rate / 2, |
| 461 | (p->rows - 1) / ((font_y * 3 + 1) >> 1), &limit, &prefix); | 559 | (p->rows - 1) / ((font_y * 3 + 1) >> 1), &limit, &prefix); |
| 462 | sprintf(text, "Frequency (%.1sHz)", prefix); /* Axis label */ | 560 | sprintf(text, "Frequency (%.1sHz)", prefix); /* Axis label */ |
| 463 | print_up(10, below + (p->rows - font_X * (int)strlen(text)) / 2, Text, text); | 561 | print_up(10, below + (c_rows - font_X * (int)strlen(text)) / 2, Text, text); |
| 464 | for (i = 0; i <= limit; i += step) { | 562 | for (k = 0; k < chans; ++k) { |
| 465 | int x, y = limit? (double)i / limit * (p->rows - 1) + .5 : 0; | 563 | base = below + k * (p->rows + 1); |
| 466 | for (x = 0; x < tick_len; ++x) /* Ticks */ | 564 | for (i = 0; i <= limit; i += step) { |
| 467 | pixel(left-1-x, below+y) = pixel(left+p->cols+x, below+y) = Grid; | 565 | int x, y = limit? (double)i / limit * (p->rows - 1) + .5 : 0; |
| 468 | if (step == 5 && (i%10)) | 566 | for (x = 0; x < tick_len; ++x) /* Ticks */ |
| 469 | continue; | 567 | pixel(left-1-x, base+y) = pixel(left+p->cols+x, base+y) = Grid; |
| 470 | sprintf(text, i?"%5g":" DC", .1 * i); /* Tick labels */ | 568 | if ((step == 5 && (i%10)) || (!i && k && chans > 1)) |
| 471 | print_at(left - 4 - font_X * 5, below + y + 5, Labels, text); | 569 | continue; |
| 472 | sprintf(text, i?"%g":"DC", .1 * i); | 570 | sprintf(text, i?"%5g":" DC", .1 * i); /* Tick labels */ |
| 473 | print_at(left + p->cols + 6, below + y + 5, Labels, text); | 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 | /* Z-axis */ | 577 | /* Z-axis */ |
| 477 | print_at(cols - right - 2 - font_X, below - 13, Text, "dBFS");/* Axis label */ | 578 | k = min(400, c_rows); |
| 478 | for (j = 0; j < p->rows; ++j) { /* Spectrum */ | 579 | base = below + (c_rows - k) / 2; |
| 479 | png_byte b = colour(p, p->dB_range * (j / (p->rows - 1.) - 1)); | 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 | for (i = 0; i < spectrum_width; ++i) | 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 */ | 586 | step = 10 * ceil(p->dB_range / 10. * (font_y + 2) / (k - 1)); |
| 484 | int y = (double)i / p->dB_range * (p->rows - 1) + .5; | 587 | for (i = 0; i <= p->dB_range; i += step) { /* (Tick) labels */ |
| 588 | int y = (double)i / p->dB_range * (k - 1) + .5; | ||
| 485 | sprintf(text, "%+i", i - p->gain - p->dB_range); | 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 | free(font); | 593 | free(font); |
| 490 | png_set_rows(png, png_info, png_rows); | 594 | png_set_rows(png, png_info, png_rows); |
| 491 | png_write_png(png, png_info, PNG_TRANSFORM_IDENTITY, NULL); | 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 | free(png_rows); | 598 | free(png_rows); |
| 494 | free(pixels); | 599 | free(pixels); |
| 495 | fclose(file); | ||
| 496 | free(p->dBfs); | 600 | free(p->dBfs); |
| 497 | return SOX_SUCCESS; | 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 | sox_effect_handler_t const * lsx_spectrogram_effect_fn(void) | 606 | sox_effect_handler_t const * lsx_spectrogram_effect_fn(void) |
| 501 | { | 607 | { |
| 502 | static sox_effect_handler_t handler = { | 608 | static sox_effect_handler_t handler = {"spectrogram", 0, SOX_EFF_MODIFY, |
| 503 | "spectrogram", 0, SOX_EFF_MODIFY, getopts, start, flow, drain, stop, 0, sizeof(priv_t)}; | 609 | getopts, start, flow, drain, end, 0, sizeof(priv_t)}; |
| 504 | static char const * lines[] = { | 610 | static char const * lines[] = { |
| 505 | "[options]", | 611 | "[options]", |
| 506 | "\t-M num\tMaximum width of spectrogram in pixels, default 1000", | 612 | "\t-x num\tX-axis size in pixels; default derived or 800", |
| 507 | "\t-x num\tX-axis pixels/second, default 100. -x & -X are alternatives", | 613 | "\t-X num\tX-axis pixels/second; default derived or 100", |
| 508 | "\t-X time\tAudio duration to fit to X-axis e.g. $(soxi -D file)", | 614 | "\t-y num\tY-axis size in pixels (per channel); slow if not 1 + 2^n", |
| 509 | "\t-y num\tY-axis resolution (1 - 4), default 2", | 615 | "\t-Y num\tY-height total (i.e. not per channel); default 550", |
| 510 | "\t-z num\tZ-axis range in dB, default 120", | 616 | "\t-z num\tZ-axis range in dB; default 120", |
| 511 | "\t-Z num\tZ-axis maximum in dBFS, default 0", | 617 | "\t-Z num\tZ-axis maximum in dBFS; default 0", |
| 512 | "\t-q num\tZ-axis quantisation (0 - 249), default 249", | 618 | "\t-q num\tZ-axis quantisation (0 - 249); default 249", |
| 513 | "\t-w name\tWindow: Hann (default), Hamming, Bartlett, Rectangular, Kaiser", | 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 | "\t-s\tSlack overlap of windows", | 621 | "\t-s\tSlack overlap of windows", |
| 515 | "\t-a\tSuppress axis lines", | 622 | "\t-a\tSuppress axis lines", |
| 516 | "\t-l\tLight background", | 623 | "\t-l\tLight background", |
| 517 | "\t-m\tMonochrome", | 624 | "\t-m\tMonochrome", |
| 518 | "\t-h\tHigh colour", | 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 | "\t-t text\tTitle text", | 628 | "\t-t text\tTitle text", |
| 521 | "\t-c text\tComment text", | 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 | static char * usage; | 634 | static char * usage; |
| 525 | handler.usage = lsx_usage_lines(&usage, lines, array_length(lines)); | 635 | handler.usage = lsx_usage_lines(&usage, lines, array_length(lines)); |