History

multi-channel support for spectrogram

robs committed 17 years ago

Commit 0daa8871 parent 8025dd75 files · +378 −220Browse files

Showing the changes to src/spectrogram.c (1 of 5 files). Show all

Unified · Split

src/spectrogram.c+236−126
@@ -1,4 +1,4 @@
1/* libSoX effect: Spectrogram (c) 2008 robs@users.sourceforge.net1/* 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 it3 * 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 by4 * 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 USA15 * 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#endif20#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_malloc29#define MAX_FFT_SIZE 4096
35#define calloc lsx_calloc30#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
43typedef enum {Window_Hann, Window_Hamming, Window_Bartlett, Window_Rectangular, Window_Kaiser} win_type_t;32typedef enum {Window_Hann, Window_Hamming, Window_Bartlett, Window_Rectangular, Window_Kaiser} win_type_t;
44static lsx_enum_item const window_options[] = {33static lsx_enum_item const window_options[] = {
@@ -51,69 +40,104 @@ static lsx_enum_item const window_options[] = { · 6 unmodified lines
51 40
52typedef struct {41typedef 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
67static 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
75static int getopts(sox_effect_t * effp, int argc, char **argv)88static 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 * 1e5112 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 / sum160 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
165static 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++,
175static 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
140static int start(sox_effect_t * effp)186static 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
256static unsigned colour(priv_t const * p, double x)346static 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
369static int axis(double to, int max_steps, double * limit, char * * prefix)464static 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);
597error: 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
604static int end(sox_effect_t * effp) {return effp->flow? SOX_SUCCESS:stop(effp);}
605
500sox_effect_handler_t const * lsx_spectrogram_effect_fn(void)606sox_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));

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t