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.net
1+/* libSoX effect: Spectrogram (c) 2008-9 robs@users.sourceforge.net
22 *
33 * This library is free software; you can redistribute it and/or modify it
44 * under the terms of the GNU Lesser General Public License as published by
@@ -15,11 +15,6 @@ · 10 unmodified lines
1515 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1616 */
1717
18−/* TODO
19− * o Two-channel support.
20− * o Option for a larger font (for use with image down-scaling).
21− */
22−
2318 #ifdef NDEBUG /* Enable assert always. */
2419 #undef NDEBUG /* Must undef above assert.h or other that might include it. */
2520 #endif
@@ -31,14 +26,8 @@ · 5 unmodified lines
3126 #include <math.h>
3227 #include <png.h>
3328
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))
4231
4332 typedef enum {Window_Hann, Window_Hamming, Window_Bartlett, Window_Rectangular, Window_Kaiser} win_type_t;
4433 static lsx_enum_item const window_options[] = {
@@ -51,69 +40,104 @@ static lsx_enum_item const window_options[] = { · 6 unmodified lines
5140
5241 typedef struct {
5342 /* 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;
5645 sox_bool monochrome, light_background, high_colour, slack_overlap, no_axes;
46+ sox_bool alt_palette, truncate;
5747 win_type_t win_type;
5848 char const * out_name, * title, * comment;
5949
60− /* Work area */
50+ /* Shared work area */
51+ double * shared, * * shared_ptr;
52+
53+ /* Per-channel work area */
6154 int WORK; /* Start of work area is marked by this dummy variable. */
55+ size_t skip;
6256 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;
6458 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];
6961 float * dBfs;
7062 } priv_t;
7163
7264 #define secs(cols) \
7365 ((double)(cols) * p->step_size * p->block_steps / effp->in_signal.rate)
7466
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+
7588 static int getopts(sox_effect_t * effp, int argc, char **argv)
7689 {
7790 priv_t * p = (priv_t *)effp->priv;
78− size_t duration_1e5;
91+ size_t duration;
7992 char const * next;
8093 int c;
8194
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 */
8696 p->out_name = "spectrogram.png", p->comment = "Created by SoX";
8797
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)
92103 GETOPT_NUMERIC('z', dB_range , 20 , 180)
93104 GETOPT_NUMERIC('Z', gain ,-100, 100)
94105 GETOPT_NUMERIC('q', spectrum_points, 0 , p->spectrum_points)
95106 GETOPT_NUMERIC('p', perm , 1 , 6)
107+ GETOPT_NUMERIC('W', window_adjust , -10, 10)
96108 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);
112125 default: lsx_fail("invalid option `-%c'", optopt); return lsx_usage(effp);
113126 }
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+ }
114135 p->gain = -p->gain;
115− --p->y_size, --p->perm;
136+ --p->perm;
116137 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;
117141 return lsx_optind !=argc || p->win_type == INT_MAX? lsx_usage(effp) : SOX_SUCCESS;
118142 }
119143
@@ -129,7 +153,8 @@ static double make_window(priv_t * p, int end) · 9 unmodified lines
129153 case Window_Hamming: lsx_apply_hamming(w, n); break;
130154 case Window_Bartlett: lsx_apply_bartlett(w, n); break;
131155 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)));
133158 }
134159 for (i = 0; i < p->dft_size; ++i) sum += p->window[i];
135160 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
137162 return sum;
138163 }
139164
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+
140186 static int start(sox_effect_t * effp)
141187 {
142188 priv_t * p = (priv_t *)effp->priv;
143− double actual;
189+ double actual, duration = p->duration, pixels_per_sec = p->pixels_per_sec;
144190
145− if (effp->in_signal.channels != 1) {
146− lsx_fail("only 1 channel is supported");
147− return SOX_EOF;
148− }
149191 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;
151229 p->rows = (p->dft_size >> 1) + 1;
152230 actual = make_window(p, p->last_end = 0);
153231 lsx_debug("window_density=%g", actual / p->dft_size);
154232 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;
156234 p->step_size = p->block_steps / ceil((double)p->block_steps / p->step_size) +.5;
157235 p->block_steps = floor((double)p->block_steps / p->step_size +.5);
158236 p->block_norm = 1. / p->block_steps;
159237 actual = effp->in_signal.rate / p->step_size / p->block_steps;
160− if (actual != p->pixels_per_sec)
238+ if (actual != pixels_per_sec)
161239 lsx_report("actual pixels/s = %g", actual);
162240 lsx_debug("step_size=%i block_steps=%i", p->step_size, p->block_steps);
163241 p->max = -p->dB_range;
@@ -170,13 +248,14 @@ static int do_column(sox_effect_t * effp) · 6 unmodified lines
170248 priv_t * p = (priv_t *)effp->priv;
171249 int i;
172250
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) {
175252 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;
177256 }
178257 ++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));
180259 for (i = 0; i < p->rows; ++i) {
181260 double dBfs = 10 * log10(p->magnitudes[i] * p->block_norm);
182261 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
192271 size_t * isamp, size_t * osamp)
193272 {
194273 priv_t * p = (priv_t *)effp->priv;
195− size_t len = min(*isamp, *osamp);
274+ size_t len = *isamp = *osamp = min(*isamp, *osamp);
196275 int i;
197276
198277 memcpy(obuf, ibuf, len * sizeof(*obuf)); /* Pass on audio unaffected */
199− *isamp = *osamp = len;
200278
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) {
202289 SOX_SAMPLE_LOCALS;
203290 if (p->read == p->step_size) {
204291 memmove(p->buf, p->buf + p->step_size,
@@ -206,19 +293,22 @@ static int flow(sox_effect_t * effp, · 1 unmodified line
206293 p->read = 0;
207294 }
208295 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++,);
210298 if (p->read != p->step_size)
211299 break;
212300
213301 if ((p->end = max(p->end, p->end_min)) != p->last_end)
214302 make_window(p, p->last_end = p->end);
215303 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);
222312 if (++p->block_num == p->block_steps && do_column(effp) == SOX_EOF)
223313 return SOX_EOF;
224314 }
@@ -230,8 +320,8 @@ static int drain(sox_effect_t * effp, sox_sample_t * obuf_, size_t * osamp) · 5 unmodified lines
230320 priv_t * p = (priv_t *)effp->priv;
231321
232322 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));
235325 size_t isamp = (p->dft_size - p->step_size) / 2;
236326 int left_over = (isamp + p->read) % p->step_size;
237327
@@ -255,7 +345,7 @@ enum {Background, Text, Labels, Grid, fixed_palette}; · 17 unmodified lines
255345
256346 static unsigned colour(priv_t const * p, double x)
257347 {
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 :
259349 1 + (1 + x / p->dB_range) * (p->spectrum_points - 2);
260350 return fixed_palette + c;
261351 }
@@ -277,7 +367,7 @@ static void make_palette(priv_t const * p, png_color * palette) · 15 unmodified lines
277367 }
278368 for (i = 0; i < p->spectrum_points; ++i) {
279369 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;
281371 if (p->monochrome) {
282372 c[2] = c[1] = c[0] = x;
283373 if (p->high_colour) {
@@ -302,6 +392,11 @@ static void make_palette(priv_t const * p, png_color * palette) · 18 unmodified lines
302392 case 4: c[j] = 7 * x - phase_num; break;
303393 case 5: c[j] = 1 - (7 * x - phase_num); break;
304394 }
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.;
305400 } else {
306401 if (x < .13) c[0] = 0;
307402 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
368463
369464 static int axis(double to, int max_steps, double * limit, char * * prefix)
370465 {
371− double scale = 1, step = 1;
466+ double scale = 1, step = max(1, 10 * to);
372467 int i, prefix_num = 0;
373468 if (max_steps) {
374469 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
393488 priv_t * p = (priv_t *) effp->priv;
394489 FILE * file = fopen(p->out_name, "wb");
395490 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;
397494 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));
400497 png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0,0);
401498 png_infop png_info = png_create_info_struct(png);
402499 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;
404501 char text[200], * prefix;
405502 double limit;
406503
504+ free(p->shared);
407505 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;
414508 }
415509 lsx_debug("signal-max=%g", p->max);
416− font = malloc(font_len);
510+ font = lsx_malloc(font_len);
417511 assert(uncompress(font, &font_len, fixed, sizeof(fixed)-1) == Z_OK);
418512 make_palette(p, palette);
419513 memset(pixels, Background, cols * rows * sizeof(*pixels));
@@ -431,14 +525,18 @@ static int stop(sox_effect_t * effp) · 11 unmodified lines
431525 print_at(1, font_y, Text, p->comment);
432526
433527 /* 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;
439539 }
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;
442540
443541 /* X-axis */
444542 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
447545 for (i = 0; i <= limit; i += step) {
448546 int y, x = limit? (double)i / limit * p->cols + .5 : 0;
449547 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;
451549 if (step == 5 && (i%10))
452550 continue;
453551 sprintf(text, "%g", .1 * i); /* Tick labels */
454552 x = left + x - 3 * strlen(text);
455553 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);
457555 }
458556
459557 /* Y-axis */
460558 step = axis(effp->in_signal.rate / 2,
461559 (p->rows - 1) / ((font_y * 3 + 1) >> 1), &limit, &prefix);
462560 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+ }
474575 }
475576
476577 /* 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));
480583 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;
482585 }
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;
485589 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);
487591 }
488592
489593 free(font);
490594 png_set_rows(png, png_info, png_rows);
491595 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);
493598 free(png_rows);
494599 free(pixels);
495− fclose(file);
496600 free(p->dBfs);
497601 return SOX_SUCCESS;
498602 }
499603
604+static int end(sox_effect_t * effp) {return effp->flow? SOX_SUCCESS:stop(effp);}
605+
500606 sox_effect_handler_t const * lsx_spectrogram_effect_fn(void)
501607 {
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)};
504610 static char const * lines[] = {
505611 "[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",
513619 "\t-w name\tWindow: Hann (default), Hamming, Bartlett, Rectangular, Kaiser",
620+ "\t-W num\tWindow adjust parameter (-10 - 10); applies only to Kaiser",
514621 "\t-s\tSlack overlap of windows",
515622 "\t-a\tSuppress axis lines",
516623 "\t-l\tLight background",
517624 "\t-m\tMonochrome",
518625 "\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)",
520628 "\t-t text\tTitle text",
521629 "\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",
523633 };
524634 static char * usage;
525635 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