History

spectrogram effect

robs committed 18 years ago

Commit 52f4fa01 parent 7cacd5310 files · +677 −7Browse files

Unified · Split

10 files
  1. AUTHORS +2 −2
  2. CMakeLists.txt +1 −0
  3. ChangeLog +1 −0
  4. README +4 −4
  5. configure.ac +21 −0
  6. soxeffect.7 +104 −0
  7. src/Makefile.am +4 −1
  8. src/effects.h +3 −0
  9. src/soxconfig.h.cmake +1 −0
  10. src/spectrogram.c +536 −0
AUTHORS+2−2
@@ -9,7 +9,7 @@ Creator: · 8 unmodified lines
9Mantainers:9Mantainers:
10 Chris Bagwell cbagwell@users.sourceforge.net10 Chris Bagwell cbagwell@users.sourceforge.net
11 OSS and Sun players, bugfixes, ADPCM support,11 OSS and Sun players, bugfixes, ADPCM support,
12 patch collection and maintance.12 patch collection and maintenance.
13 Reuben Thomas rrt@sc3d.org13 Reuben Thomas rrt@sc3d.org
14 Build system makeover, libsndfile and ffmpeg sound14 Build system makeover, libsndfile and ffmpeg sound
15 format support, libao playback, Secret Rabbit Code15 format support, libao playback, Secret Rabbit Code
@@ -21,7 +21,7 @@ Mantainers: · 5 unmodified lines
21 EFFECTS: key, tempo, pad, bass, treble, delay, new reverb, new21 EFFECTS: key, tempo, pad, bass, treble, delay, new reverb, new
22 flanger, soft-knee companding, speed via resampling, filters22 flanger, soft-knee companding, speed via resampling, filters
23 makeover inc. gnuplot & octave plotting, splice, remix, norm,23 makeover inc. gnuplot & octave plotting, splice, remix, norm,
24 contrast, new rate;24 contrast, new rate, spectrogram;
25 new effects chain with buffering and any # channels.25 new effects chain with buffering and any # channels.
26 OTHERS: open input files via URL, file merge, play26 OTHERS: open input files via URL, file merge, play
27 multiple files with mixed format types, play with replay-gain,27 multiple files with mixed format types, play with replay-gain,
CMakeLists.txt+1−0
@@ -73,6 +73,7 @@ optional(HAVE_LAME_LAME_H lame/lame.h mp3lame lame_init mp3) · 72 unmodified lines
73if (NOT HAVE_LAME_LAME_H)73if (NOT HAVE_LAME_LAME_H)
74 optional(HAVE_LAME_LAME_H lame.h mp3lame lame_init mp3)74 optional(HAVE_LAME_LAME_H lame.h mp3lame lame_init mp3)
75endif (NOT HAVE_LAME_LAME_H)75endif (NOT HAVE_LAME_LAME_H)
76optional(HAVE_PNG png.h png png_set_rows spectrogram)
76optional(HAVE_SAMPLERATE_H samplerate.h samplerate src_simple rabbit)77optional(HAVE_SAMPLERATE_H samplerate.h samplerate src_simple rabbit)
77optional(HAVE_SNDFILE_H sndfile.h sndfile sf_open sndfile)78optional(HAVE_SNDFILE_H sndfile.h sndfile sf_open sndfile)
78optional(HAVE_SNDFILE_H sndfile.h sndfile sf_open fap)79optional(HAVE_SNDFILE_H sndfile.h sndfile sf_open fap)
ChangeLog+1−0
@@ -57,6 +57,7 @@ Effects: · 56 unmodified lines
57 o New `delay' effect; delay one or more channels. (robs)57 o New `delay' effect; delay one or more channels. (robs)
58 o New `contrast' enhancement effect [FR 708923]. (robs)58 o New `contrast' enhancement effect [FR 708923]. (robs)
59 o Rewrite of rate; resample, polyphase, & rabbit now deprecated. (robs)59 o Rewrite of rate; resample, polyphase, & rabbit now deprecated. (robs)
60 o New `spectrogram' effect; creates a PNG. (robs)
60 o `synth' can now sweep linearly (as well as logarithmically). (robs)61 o `synth' can now sweep linearly (as well as logarithmically). (robs)
61 o Fix synth max. level setting for some output encodings. (robs)62 o Fix synth max. level setting for some output encodings. (robs)
62 o Fix crash on 64-bit arch. with tempo & key effects. (Sami Liedes)63 o Fix crash on 64-bit arch. with tempo & key effects. (Sami Liedes)
README+4−4
@@ -71,9 +71,11 @@ The sound effects include: · 70 unmodified lines
71 o Band-reject filter71 o Band-reject filter
72 o Bass tone control72 o Bass tone control
73 o Compress and Expand (compand) the dynamic range of a sample73 o Compress and Expand (compand) the dynamic range of a sample
74 o Contrast enhancement effect
74 o Chorus effect75 o Chorus effect
75 o DC-shift audio - useful to get the best volume adjustments76 o DC-shift audio - useful to get the best volume adjustments
76 o De-emphasis filter77 o De-emphasis filter
78 o Delay one or more channels
77 o Move sound stage of CD audio to in front of you (for headphone79 o Move sound stage of CD audio to in front of you (for headphone
78 use)80 use)
79 o Add an echo81 o Add an echo
@@ -91,15 +93,13 @@ The sound effects include: · 11 unmodified lines
91 o Apply a phaser effect93 o Apply a phaser effect
92 o Change the key (pitch) of a sound file without affecting its speed94 o Change the key (pitch) of a sound file without affecting its speed
93 o Repeat audio data95 o Repeat audio data
94 o Change sampling rates using several different algorithms. A96 o Change sampling rate
95 'resample' and 'polyphase' effect use high-grade signal rate
96 changes using real signal theory!
97 o Apply a reverb effect97 o Apply a reverb effect
98 o Reverse the sound samples (to search for Satanic messages ;-)98 o Reverse the sound samples (to search for Satanic messages ;-)
99 o Detect periods of silence and start and stop processing based on99 o Detect periods of silence and start and stop processing based on
100 it100 it
101 o Create a spectrogram PNG
101 o Change speed (pitch and tempo together)102 o Change speed (pitch and tempo together)
102 motor on a tape recorder)
103 o Splice together audio sections103 o Splice together audio sections
104 o Display general stats on a sound sample104 o Display general stats on a sound sample
105 o Change the tempo an audio file (without affecting pitch)105 o Change the tempo an audio file (without affecting pitch)
configure.ac+21−0
@@ -414,6 +414,26 @@ fi · 413 unmodified lines
414AM_CONDITIONAL(HAVE_WAVPACK, test x$using_wavpack = xyes)414AM_CONDITIONAL(HAVE_WAVPACK, test x$using_wavpack = xyes)
415AC_SUBST(WAVPACK_LIBS)415AC_SUBST(WAVPACK_LIBS)
416 416
417dnl Check for png libraries
418AC_ARG_WITH(png,
419 AC_HELP_STRING([--without-png],
420 [Don't try to use png]))
421using_png=no
422if test "$with_png" != "no"; then
423 using_png=yes
424 AC_CHECK_HEADER(png.h,
425 [AC_CHECK_LIB(png, png_set_rows, PNG_LIBS="$PNG_LIBS -lpng -lz" ,using_png=no)],
426 using_png=no)
427 if test "$with_png" = "yes" -a "$using_png" = "no"; then
428 AC_MSG_FAILURE([cannot find png])
429 fi
430fi
431if test "$using_png" = yes; then
432 AC_DEFINE(HAVE_PNG, 1, [Define to 1 if you have PNG.])
433fi
434AM_CONDITIONAL(HAVE_PNG, test x$using_png = xyes)
435AC_SUBST(PNG_LIBS)
436
417dnl Test for libsamplerate.437dnl Test for libsamplerate.
418AC_ARG_WITH(samplerate,438AC_ARG_WITH(samplerate,
419 AC_HELP_STRING([--without-samplerate],439 AC_HELP_STRING([--without-samplerate],
@@ -483,6 +503,7 @@ echo "AMR-WB format..................... $using_amr_wb" · 63 unmodified lines
483echo "AMR-NB format..................... $using_amr_nb"503echo "AMR-NB format..................... $using_amr_nb"
484echo "WavPack format.................... $using_wavpack"504echo "WavPack format.................... $using_wavpack"
485echo "LADSPA effects.................... $using_ladspa"505echo "LADSPA effects.................... $using_ladspa"
506echo "PNG support....................... $using_png"
486echo "Secret Rabbit Code resampling..... $using_samplerate"507echo "Secret Rabbit Code resampling..... $using_samplerate"
487echo "pkg-config location............... $pkgconfig_option"508echo "pkg-config location............... $pkgconfig_option"
488echo509echo
soxeffect.7+104−0
@@ -1027,6 +1027,110 @@ effect using its default quality/speed. For higher quality or higher speed · 1026 unmodified lines
1027resampling, in addition to the \fBspeed\fR effect, specify1027resampling, in addition to the \fBspeed\fR effect, specify
1028the \fBrate\fR effect with the desired quality options.1028the \fBrate\fR effect with the desired quality options.
1029.TP1029.TP
1030\fBspectrogram \fR[options]
1031Create a spectrogram of the audio. This effect is optional; type \fBsox
1032\-\-help\fR and check the list of supported effects to see if it has
1033been included.
1034.SP
1035The spectrogram is rendered in a Portable Network Graphic (PNG) file,
1036and shows time in the X-axis, frequency in the Y-axis, and audio signal
1037magnitude in the Z-axis. Z-axis values are represented by the colour
1038(or intensity) of the pixels in the X-Y plane.
1039.RS
1040.IP \fB-x\ \fInum\fR
1041X-axis pixels/second, default 100. This controls the width of the
1042spectrogram;
1043.I num
1044can be from 1 (low time resolution) to 5000 (high time resolution). SoX
1045may make a slight adjustment to the given number for processing
1046quantisation reasons; if so, SoX will report the actual number used
1047(viewable when
1048.B \-\-verbose
1049is in effect).
1050.SP
1051The maximum width of the spectrogram is 999 pixels; if the audio length
1052and the given
1053.B \-x
1054number are such that this would be exceeded, then the spectrogram (and
1055the effects chain) will be truncated. To move the spectrogram to a
1056point later in the audio stream, first invoke the
1057.B trim
1058effect; e.g.
1059.EX
1060 sox audio.ogg -n trim 1:00 spectrogram
1061.EE
1062starts the spectrogram at 1 minute through the audio.
1063.IP \fB-y\ \fInum\fR
1064Y-axis resolution (1 - 4), default 2.
1065This controls the height of the spectrogram;
1066.I num
1067can be from 1 (low frequency resolution) to 4 (high frequency
1068resolution). For values greater than 2, the resulting image may be too
1069tall to display on the screen; if so, a graphic manipulation package
1070(such as
1071.BR
1072ImageMagick (1))
1073can be used to re-size the image.
1074.SP
1075To increase the frequency resolution without increasing the height of
1076the spectrogram, the
1077.B rate
1078effect may be invoked to reduce the sampling rate of the signal before
1079invoking
1080.BR spectrogram ;
1081e.g.
1082.EX
1083 sox audio.ogg -r 4k -n rate spectrogram
1084.EE
1085allows detailed analysis of frequencies up to 2kHz (half the sampling
1086rate).
1087.IP \fB-z\ \fInum\fR
1088Z-axis (colour) range in dB, default 120. This sets the dynamic-range
1089of the spectrogram to be \-\fInum\fR\ dBFS to 0\ dBFS.
1090.I Num
1091may range from 40 to 180. Decreasing dynamic-range, effectively
1092increases the `contrast' of the spectrogram display, and vice versa.
1093.IP \fB-g\ \fInum\fR
1094Apply
1095.I num
1096dB gain (or attenuation if
1097.I num
1098is negative) to the audio before displaying it. Appling gain or
1099attenuation shifts the the range of the Z-axis and effectively controls
1100the `brightness' of the spectrogram display. Note that the audio in the
1101SoX effects chain remains unaffected by this option (cf. the
1102.B gain
1103effect).
1104.IP \fB-w\ \fIname\fR
1105Window: Hann (default), Hamming, Bartlett, Rectangular, Kaiser. The
1106spectrogram is produced using the Discrete Fourier Transform (DFT)
1107algorithm. A significant paramater to this algorithm is the choice of
1108`window function'. By default, SoX uses the Hann window which has good
1109all-round frequency-resolution and dynamic-range properties. For better
1110frequency resolution (but lower dynamic-range), select a Hamming window;
1111for higher dynamic-range (but poorer frequency-resolution), select a
1112Kaiser window. Selecting a window other than Hann will usually require
1113a corresponding
1114.B \-z
1115setting.
1116.IP \fB-s\ \fInum\fR
1117Display style (1 - 4), default 1. There are 2 colour and 2 monochrome
1118styles; select the style that works best with the given audio signal and
1119your display or printer.
1120.IP \fB-a\fR
1121Toggle the presence of the axes lines. This is sometimes useful in
1122helping to discern artefacts at the spectrogram edges.
1123.IP \fB-t\ \fItext\fR
1124Text to display above the spectrogram.
1125.IP \fB-c\ \fItext\fR
1126Text to display below and to the left of the spectrogram.
1127.IP \fB-o\ \fItext\fR
1128Name of the spectrogram output PNG file, default `spectrogram.png'.
1129.RE
1130For the ability to perform off-line processing of spectral data, see the
1131.B stat
1132effect.
1133.TP
1030\fBsplice \fR { \fIposition\fR[\fB,\fIexcess\fR[\fB,\fIleeway\fR]] }1134\fBsplice \fR { \fIposition\fR[\fB,\fIexcess\fR[\fB,\fIleeway\fR]] }
1031Splice together audio sections. This effect provides two things over1135Splice together audio sections. This effect provides two things over
1032simple audio concatenation: a (usually short) cross-fade is applied at1136simple audio concatenation: a (usually short) cross-fade is applied at
src/Makefile.am+4−1
@@ -231,9 +231,12 @@ libsfx_la_SOURCES = \ · 230 unmodified lines
231 remix.c repeat.c resample.c reverb.c reverse.c silence.c skeleff.c \231 remix.c repeat.c resample.c reverb.c reverse.c silence.c skeleff.c \
232 speed.c splice.c stat.c stretch.c swap.c synth.c tempo.c tremolo.c \232 speed.c splice.c stat.c stretch.c swap.c synth.c tempo.c tremolo.c \
233 trim.c vol.c233 trim.c vol.c
234if HAVE_PNG
235 libsfx_la_SOURCES += spectrogram.c
236endif
234 237
235libsfx_la_CFLAGS = @SAMPLERATE_CFLAGS@238libsfx_la_CFLAGS = @SAMPLERATE_CFLAGS@
236libsfx_la_LIBADD = @SAMPLERATE_LIBS@ libsox.la239libsfx_la_LIBADD = @PNG_LIBS@ @SAMPLERATE_LIBS@ libsox.la
237 240
238############################################241############################################
239# libsox - file format and utility library #242# libsox - file format and utility library #
src/effects.h+3−0
@@ -64,6 +64,9 @@ · 63 unmodified lines
64 EFFECT(reverb)64 EFFECT(reverb)
65 EFFECT(reverse)65 EFFECT(reverse)
66 EFFECT(silence)66 EFFECT(silence)
67#ifdef HAVE_PNG
68 EFFECT(spectrogram)
69#endif
67 EFFECT(speed)70 EFFECT(speed)
68 EFFECT(splice)71 EFFECT(splice)
69 EFFECT(stat)72 EFFECT(stat)
src/soxconfig.h.cmake+1−0
@@ -19,6 +19,7 @@ · 18 unmodified lines
19#cmakedefine HAVE_MAD_H 119#cmakedefine HAVE_MAD_H 1
20#cmakedefine HAVE_OGG_VORBIS 120#cmakedefine HAVE_OGG_VORBIS 1
21#cmakedefine HAVE_POPEN 121#cmakedefine HAVE_POPEN 1
22#cmakedefine HAVE_PNG 1
22#cmakedefine HAVE_SAMPLERATE_H 123#cmakedefine HAVE_SAMPLERATE_H 1
23#cmakedefine HAVE_SNDFILE_1_0_12 124#cmakedefine HAVE_SNDFILE_1_0_12 1
24#cmakedefine HAVE_SNDFILE_H 125#cmakedefine HAVE_SNDFILE_H 1
src/spectrogram.c+536−0
@@ -0,0 +1,536 @@
1/* libSoX effect: Spectrogram (c) 2008 robs@users.sourceforge.net
2 *
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
5 * the Free Software Foundation; either version 2.1 of the License, or (at
6 * your option) any later version.
7 *
8 * This library is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
11 * General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this library; if not, write to the Free Software Foundation,
15 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 */
17
18#ifdef NDEBUG /* Enable assert always. */
19#undef NDEBUG /* Must undef above assert.h or other that might include it. */
20#endif
21
22#include "sox_i.h"
23#include "fft4g.h"
24#include "getopt.h"
25#include <assert.h>
26#include <math.h>
27#include <png.h>
28
29#define malloc lsx_malloc
30#define calloc lsx_calloc
31#define realloc lsx_realloc
32#define FROM_SOX SOX_SAMPLE_TO_FLOAT_64BIT
33#define DFT_BASE_SIZE 512
34#define MAX_DFT_SIZE_SHIFT 3
35#define MAX_DFT_SIZE (DFT_BASE_SIZE << MAX_DFT_SIZE_SHIFT)
36#define MAX_COLS 999 /* Also max seconds */
37
38static void apply_hann(double h[], const int num_points)
39{
40 int i, m = num_points - 1;
41 for (i = 0; i < num_points; ++i) {
42 double x = 2 * M_PI * i / m;
43 h[i] *= .5 - .5 * cos(x);
44 }
45}
46
47static void apply_hamming(double h[], const int num_points)
48{
49 int i, m = num_points - 1;
50 for (i = 0; i < num_points; ++i) {
51 double x = 2 * M_PI * i / m;
52 h[i] *= .53836 - .46164 * cos(x);
53 }
54}
55
56static void apply_bartlett(double h[], const int num_points)
57{
58 int i, m = num_points - 1;
59 for (i = 0; i < num_points; ++i) {
60 h[i] *= 2. / m * (m / 2. - fabs(i - m / 2.));
61 }
62}
63
64static double kaiser_beta(double att)
65{
66 if (att > 100 ) return .1117 * att - 1.11;
67 if (att > 50 ) return .1102 * (att - 8.7);
68 if (att > 20.96) return .58417 * pow(att -20.96, .4) + .07886 * (att - 20.96);
69 return 0;
70}
71
72static void apply_kaiser(double h[], const int num_points, double beta)
73{
74 int i, m = num_points - 1;
75 for (i = 0; i <= m; ++i) {
76 double x = 2. * i / m - 1;
77 h[i] *= bessel_I_0(beta * sqrt(1 - x * x)) / bessel_I_0(beta);
78 }
79}
80
81typedef enum {Window_Hann, Window_Hamming, Window_Bartlett, Window_Rectangular, Window_Kaiser} win_type_t;
82static enum_item const window_options[] = {
83 ENUM_ITEM(Window_,Hann)
84 ENUM_ITEM(Window_,Hamming)
85 ENUM_ITEM(Window_,Bartlett)
86 ENUM_ITEM(Window_,Rectangular)
87 ENUM_ITEM(Window_,Kaiser)
88 {0, 0}};
89
90typedef struct {
91 int y_size, dft_size, rows, cols, step_size, block_steps, block_num;
92 double buf[MAX_DFT_SIZE];
93 double dft_buf[MAX_DFT_SIZE];
94 double window[MAX_DFT_SIZE];
95 int bit_rev_table[100]; /* For Ooura fft */
96 double sin_cos_table[dft_sc_len(MAX_DFT_SIZE)]; /* ditto */
97 double magnitudes[(MAX_DFT_SIZE >> 1) + 1];
98 float * dBfs;
99 double pixels_per_sec, block_norm, max;
100 int dB_range, gain, style, read, end, end_min, last_end;
101 char const * out_name, * title, * comment;
102 sox_bool no_axes;
103 win_type_t win_type;
104} priv_t;
105
106#define GETOPT_NUMERIC(ch, name, min, max) case ch:{ \
107 char * end_ptr; \
108 double d = strtod(optarg, &end_ptr); \
109 if (end_ptr == optarg || d < min || d > max || *end_ptr != '\0') {\
110 sox_fail("parameter `%s' must be between %g and %g", #name, (double)min, (double)max); \
111 return lsx_usage(effp); \
112 } \
113 p->name = d; \
114 break; \
115}
116
117static int enum_option(int c, enum_item const * items)
118{
119 enum_item const * p = find_enum_text(optarg, items);
120 if (p == NULL) {
121 unsigned len = 1;
122 char * set = lsx_malloc(len);
123 *set = 0;
124 for (p = items; p->text; ++p) {
125 set = lsx_realloc(set, len += 2 + strlen(p->text));
126 strcat(set, ", "); strcat(set, p->text);
127 }
128 sox_fail("-%c: '%s' is not one of: %s.", c, optarg, set + 2);
129 free(set);
130 return INT_MAX;
131 }
132 return p->value;
133}
134
135static int getopts(sox_effect_t * effp, int argc, char **argv)
136{
137 priv_t * p = (priv_t *)effp->priv;
138 int c, callers_optind = optind, callers_opterr = opterr;
139
140 assert(array_length(p->bit_rev_table) >= (size_t)dft_br_len(MAX_DFT_SIZE));
141
142 --argv, ++argc, optind = 1, opterr = 0; /* re-jig for getopt */
143 p->pixels_per_sec = 100, p->y_size = 2, p->dB_range = 120;/* non-0 defaults */
144 p->style = 1, p->out_name = "spectrogram.png", p->comment = "Created by SoX";
145
146 while ((c = getopt(argc, argv, "+x:y:z:g:s:w:t:c:o:a")) != -1) switch (c) {
147 GETOPT_NUMERIC('x', pixels_per_sec, 1 , 5000)
148 GETOPT_NUMERIC('y', y_size , 1 , 1 + MAX_DFT_SIZE_SHIFT)
149 GETOPT_NUMERIC('z', dB_range , 40 , 180)
150 GETOPT_NUMERIC('g', gain ,-100, 100)
151 GETOPT_NUMERIC('s', style , 1 , 4)
152 case 'w': p->win_type = enum_option(c, window_options); break;
153 case 't': p->title = optarg; break;
154 case 'c': p->comment = optarg; break;
155 case 'o': p->out_name = optarg; break;
156 case 'a': p->no_axes = sox_true; break;
157 default: sox_fail("unknown option `-%c'", optopt); return lsx_usage(effp);
158 }
159 p->y_size -= 1, p->style -= 1;
160 argc -= optind, optind = callers_optind, opterr = callers_opterr;
161 return argc || p->win_type == INT_MAX? lsx_usage(effp) : SOX_SUCCESS;
162}
163
164static int make_window(priv_t * p, int end)
165{
166 double sum = 0, * w = end < 0? p->window : p->window + end;
167 int i, n = p->dft_size - abs(end);
168
169 if (end) memset(p->window, 0, sizeof(p->window));
170 for (i = 0; i < n; ++i) w[i] = 1;
171 switch (p->win_type) {
172 case Window_Hann: apply_hann(w, n); break;
173 case Window_Hamming: apply_hamming(w, n); break;
174 case Window_Bartlett: apply_bartlett(w, n); break;
175 case Window_Rectangular: break;
176 default: apply_kaiser(w, n, kaiser_beta(p->dB_range + 10.));
177 }
178 for (i = 0; i < p->dft_size; ++i) sum += p->window[i];
179 for (i = 0; i < p->dft_size; ++i) p->window[i] *= 2 / sum
180 * sqr((double)n / p->dft_size); /* empirical small window adjustment */
181 return sum + .5;
182}
183
184static int start(sox_effect_t * effp)
185{
186 priv_t * p = (priv_t *)effp->priv;
187 double actual;
188
189 if (effp->in_signal.channels != 1) {
190 sox_fail("only 1 channel is supported");
191 return SOX_EOF;
192 }
193 p->end = p->dft_size = DFT_BASE_SIZE << p->y_size;
194 p->rows = (p->dft_size >> 1) + 1;
195 p->step_size = make_window(p, p->last_end = 0);
196 p->block_steps = effp->in_signal.rate / p->pixels_per_sec;
197 p->step_size = p->block_steps / ceil((double)p->block_steps / p->step_size) +.5;
198 p->block_steps = floor((double)p->block_steps / p->step_size +.5);
199 p->block_norm = 1. / p->block_steps;
200 actual = effp->in_signal.rate / p->step_size / p->block_steps;
201 if (actual != p->pixels_per_sec)
202 sox_report("actual pixels/s = %g", actual);
203 sox_debug("step_size=%i block_steps=%i", p->step_size, p->block_steps);
204 p->max = -p->dB_range;
205 p->read = (p->step_size - p->dft_size) / 2;
206 return SOX_SUCCESS;
207}
208
209static int do_column(sox_effect_t * effp)
210{
211 priv_t * p = (priv_t *)effp->priv;
212 int i;
213
214 if (p->cols == MAX_COLS) {
215 sox_warn("PNG truncated at %g seconds", (double)MAX_COLS * p->step_size * p->block_steps / effp->in_signal.rate);
216 return SOX_EOF;
217 }
218 ++p->cols;
219 p->dBfs = realloc(p->dBfs, p->cols * p->rows * sizeof(*p->dBfs));
220 for (i = 0; i < p->rows; ++i) {
221 double dBfs = 10 * log10(p->magnitudes[i] * p->block_norm);
222 p->dBfs[(p->cols - 1) * p->rows + i] = dBfs + p->gain;
223 if (dBfs > p->max)
224 p->max = dBfs;
225 }
226 memset(p->magnitudes, 0, p->rows * sizeof(*p->magnitudes));
227 p->block_num = 0;
228 return SOX_SUCCESS;
229}
230
231static int flow(sox_effect_t * effp,
232 const sox_sample_t * ibuf, sox_sample_t * obuf,
233 sox_size_t * isamp, sox_size_t * osamp)
234{
235 priv_t * p = (priv_t *)effp->priv;
236 sox_size_t len = min(*isamp, *osamp), dummy = 0; /* No need to clip count */
237 int i;
238
239 memcpy(obuf, ibuf, len * sizeof(*obuf)); /* Pass on audio unaffected */
240 *isamp = *osamp = len;
241
242 while (sox_true) {
243 if (p->read == p->step_size) {
244 memmove(p->buf, p->buf + p->step_size,
245 (p->dft_size - p->step_size) * sizeof(*p->buf));
246 p->read = 0;
247 }
248 for (; len && p->read < p->step_size; --len, ++p->read, --p->end)
249 p->buf[p->dft_size - p->step_size + p->read] = FROM_SOX(*ibuf++, dummy);
250 if (p->read != p->step_size)
251 break;
252
253 if ((p->end = max(p->end, p->end_min)) != p->last_end)
254 make_window(p, p->last_end = p->end);
255 for (i = 0; i < p->dft_size; ++i) p->dft_buf[i] = p->buf[i] * p->window[i];
256 rdft(p->dft_size, 1, p->dft_buf, p->bit_rev_table, p->sin_cos_table);
257 p->magnitudes[0] += sqr(p->dft_buf[0]);
258 for (i = 1; i < p->dft_size >> 1; ++i)
259 p->magnitudes[i] += sqr(p->dft_buf[2*i]) + sqr(p->dft_buf[2*i+1]);
260 p->magnitudes[p->dft_size >> 1] += sqr(p->dft_buf[1]);
261
262 if (++p->block_num == p->block_steps && do_column(effp) == SOX_EOF)
263 return SOX_EOF;
264 }
265 return SOX_SUCCESS;
266}
267
268static int drain(sox_effect_t * effp, sox_sample_t * obuf_, sox_size_t * osamp)
269{
270 priv_t * p = (priv_t *)effp->priv;
271
272 if (p->cols != MAX_COLS) {
273 sox_sample_t * ibuf = calloc(p->dft_size, sizeof(*ibuf));
274 sox_sample_t * obuf = calloc(p->dft_size, sizeof(*obuf));
275 sox_size_t isamp = (p->dft_size - p->step_size) / 2;
276 int left_over = (isamp + p->read) % p->step_size;
277
278 if (left_over >= p->step_size >> 1)
279 isamp += p->step_size - left_over;
280 sox_debug("cols=%i left=%i end=%i", p->cols, p->read, p->end);
281 p->end = 0;
282 p->end_min = -p->dft_size;
283 if (flow(effp, ibuf, obuf, &isamp, &isamp) == SOX_SUCCESS && p->block_num) {
284 p->block_norm *= (double)p->block_steps / p->block_num;
285 do_column(effp);
286 }
287 sox_debug("flushed cols=%i left=%i end=%i", p->cols, p->read, p->end);
288 free(obuf);
289 free(ibuf);
290 }
291 (void)obuf_, *osamp = 0;
292 return SOX_SUCCESS;
293}
294
295#define spectrum_points (1 + 250)
296enum {Background = spectrum_points, Text, Labels, Grid, palette_size};
297
298static unsigned colour(double x, int range)
299{
300 x = range_limit(x, -range, 0);
301 return (1 + x / range) * (spectrum_points - 1) + .5;
302}
303
304static void make_palette(png_byte * palette, int style)
305{
306 int i, j, at;
307 for (i = j = 0; j < spectrum_points; i += 3, ++j) {
308 double r, g, b, x = (double)j / (spectrum_points - 1);
309 if (style > 1)
310 palette[i+2] = palette[i+1] = palette[i+0] = style == 2? j : 255 - j;
311 else {
312 at = style? (spectrum_points - 1) * 3 - i : i;
313 if (x < .16) r = 0;
314 else if (x < .72) r = .99 * sin((x - .16) / .56 * M_PI / 2);
315 else r = .01 * (x - .72) / .28 + .99;
316 if (x < .52) g = 0;
317 else if (x < .86) g = .99 * .5 * (1 - cos((x - .52) / .34 * M_PI));
318 else g = .01 * (x - .86) / .14 + .99;
319 if (x < .34) b = .5 * sin((x - .00) / .34 * M_PI / 2);
320 else if (x < .63) b = .5 * .5 * (1 + cos((x - .34) / .29 * M_PI));
321 else b = (1 - cos((x - .63) / .37 * M_PI / 2));
322 palette[at + 0] = r * 255 + .5;
323 palette[at + 1] = g * 255 + .5;
324 palette[at + 2] = b * 255 + .5;
325 }
326 }
327 i = spectrum_points * 3;
328 switch (style) {
329 case 0: case 2:
330 memcpy(palette + i, "\0\0\0" , 3), i+= 3;
331 memcpy(palette + i, "\377\377\377", 3), i+= 3;
332 memcpy(palette + i, "\277\277\277", 3), i+= 3;
333 memcpy(palette + i, "\177\177\177", 3), i+= 3;
334 return;
335 case 1: memcpy(palette + i, "\335\330\320", 3), i+= 3; break;
336 default: memcpy(palette + i, "\337\337\337", 3), i+= 3;
337 }
338 memcpy(palette + i, "\0\0\0" , 3), i+= 3;
339 memcpy(palette + i, "\077\077\077", 3), i+= 3;
340 memcpy(palette + i, "\077\077\077", 3), i+= 3;
341}
342
343static const Bytef fixed[] =
344 "x\332eT\241\266\2450\fDVV>Y\371$re%2\237\200|2\22YY\211D\"+\337'<y\345\312"
345 "\375\fd\345f\222\224\313\236\235{\270\344L\247a\232\4\246\351\201d\230\222"
346 "\304D\364^ \352\362S\"m\347\311\237\237\27\64K\243\2302\265\35\v\371<\363y"
347 "\354_\226g\354\214)e \2458\341\17\20J4\215[z<\271\277\367\0\63\64@\177\330c"
348 "\227\204 Ir.\5$U\200\260\224\326S\17\200=\\k\20QA\334%\342\20*\303P\234\211"
349 "\366\36#\370R\276_\316s-\345\222Dlz\363my*;\217\373\346z\267\343\236\364\246"
350 "\236\365\2419\305p\333\267\23(\207\265\333\233\325Y\342\243\265\357\262\215"
351 "\263t\271$\276\226ea\271.\367&\320\347\202_\234\27\377\345\222\253?\3422\364"
352 "\207y\256\236\229\331\33\f\376\227\266\"\356\253j\366\363\347\334US\34]\371?"
353 "\255\371\336\372z\265v\34\226\247\32\324\217\334\337\317U4\16\316{N\370\31"
354 "\365\357iL\231y\33y\264\211D7\337\4\244\261\220D\346\1\261\357\355>\3\342"
355 "\223\363\0\303\277\f[\214A,p\34`\255\355\364\37\372\224\342\277\f\207\255\36"
356 "_V\7\34\241^\316W\257\177\b\242\300\34\f\276\33?/9_\331f\346\36\25Y)\2301"
357 "\257\2414|\35\365\237\3424k\3\244\3\242\261\6\b\275>z$\370\215:\270\363w\36/"
358 "\265kF\v\20o6\242\301\364\336\27\325\257\321\364fs\231\215G\32=\257\305Di"
359 "\304^\177\304R\364Q=\225\373\33\320\375\375\372\200\337\37\374}\334\337\20"
360 "\364\310(]\304\267b\177\326Yrj\312\277\373\233\37\340";
361static unsigned char * font;
362#define font_x 5
363#define font_y 12
364#define font_X (font_x + 1)
365
366#define pixel(x,y) pixels[(y) * cols + (x)]
367#define print_at(x,y,c,t) print_at_(pixels,cols,x,y,c,t,0)
368#define print_up(x,y,c,t) print_at_(pixels,cols,x,y,c,t,1)
369
370static void print_at_(png_byte * pixels, int cols, int x, int y, int c, char const * text, int orientation)
371{
372 for (;*text; ++text) {
373 int pos = (*text - ' ') * font_y;
374 int i, j;
375 for (i = 0; i < font_y; ++i) {
376 unsigned line = font[pos++];
377 for (j = 0; j < font_x; ++j, line <<= 1)
378 if (line & 0x80) switch (orientation) {
379 case 0: pixel(x + j, y - i) = c; break;
380 case 1: pixel(x + i, y + j) = c; break;
381 }
382 }
383 switch (orientation) {
384 case 0: x += font_X; break;
385 case 1: y += font_X; break;
386 }
387 }
388}
389
390static int axis(double to, int max_steps, int * limit, char * * prefix)
391{
392 double scale = 1, step = 1;
393 int i, prefix_num = 0;
394 if (max_steps) {
395 double try, log_10 = HUGE_VAL, min_step = (to *= 10) / max_steps;
396 for (i = 5; i; i >>= 1) if ((try = ceil(log10(min_step * i))) <= log_10)
397 step = pow(10., log_10 = try) / i, log_10 -= i > 1;
398 prefix_num = floor(log_10 / 3);
399 scale = pow(10., -3. * prefix_num);
400 }
401 *prefix = "pnum-kMGTPE" + prefix_num + (prefix_num? 4 : 11);
402 *limit = to * scale + .5;
403 return step * scale + .5;
404}
405
406#define below 48
407#define left 58
408#define between 37
409#define spectrum_width 14
410#define right 35
411
412static int stop(sox_effect_t * effp)
413{
414 priv_t * p = (priv_t *) effp->priv;
415 FILE * file = fopen(p->out_name, "w");
416 uLong font_len = 96 * font_y;
417 int rows = below + p->rows + 30 + 20 * !!p->title;
418 int cols = left + p->cols + between + spectrum_width + right;
419 sox_bool do_frame = !p->no_axes;
420 png_byte * pixels = malloc(cols * rows * sizeof(*pixels));
421 png_bytepp png_rows = malloc(rows * sizeof(*png_rows));
422 png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0,0);
423 png_infop png_info = png_create_info_struct(png);
424 png_byte palette[palette_size * 3];
425 int i, j, step, limit, tick_len = 2 + do_frame;
426 char text[200], * prefix;
427
428 if (!file) {
429 sox_fail("failed to create `%s' :(", p->out_name);
430 png_destroy_write_struct(&png, &png_info);
431 free(png_rows);
432 free(pixels);
433 free(p->dBfs);
434 return SOX_EOF;
435 }
436 sox_debug("signal-max=%g", p->max);
437 font = malloc(font_len);
438 assert(uncompress(font, &font_len, fixed, sizeof(fixed)-1) == Z_OK);
439 make_palette(palette, p->style);
440 memset(pixels, Background, cols * rows * sizeof(*pixels));
441 png_init_io(png, file);
442 png_set_PLTE(png, png_info, (png_colorp)palette, palette_size);
443 png_set_IHDR(png, png_info, (size_t)cols, (size_t)rows, 8,
444 PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
445 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
446 for (j = 0; j < rows; ++j)
447 png_rows[rows - 1 - j] = (png_bytep)(pixels + j * cols);
448
449 if (p->title && (i = (int)strlen(p->title) * font_X) < cols + 1) /* Title */
450 print_at((cols - i) / 2, rows - font_y, Text, p->title);
451 if ((int)strlen(p->comment) * font_X < cols + 1) /* Footer comment */
452 print_at(1, font_y, Text, p->comment);
453
454 /* Spectrogram */
455 for (j = 0; j < p->rows; ++j) {
456 for (i = 0; i < p->cols; ++i)
457 pixel(left + i, below + j) = colour(p->dBfs[i*p->rows + j], p->dB_range);
458 if (do_frame) /* Frame verticals */
459 pixel(left - 1, below + j) = pixel(left + p->cols,below + j) = Grid;
460 }
461 if (do_frame) for (i = -1; i <= p->cols; ++i) /* Frame horizontals */
462 pixel(left + i, below - 1) = pixel(left + i, below + p->rows) = Grid;
463
464 /* X-axis */
465 step = axis((double)p->cols * p->step_size * p->block_steps / effp->in_signal.rate, p->cols / (font_X * 9 / 2), &limit, &prefix);
466 sprintf(text, "Time (%.1ss)", prefix); /* Axis label */
467 print_at(left + (p->cols - font_X * (int)strlen(text)) / 2, 24, Text, text);
468 for (i = 0; i <= limit; i += step) {
469 int y, x = limit? (double)i / limit * p->cols + .5 : 0;
470 for (y = 0; y < tick_len; ++y) /* Ticks */
471 pixel(left - 1 + x, below - 1 - y) = pixel(left - 1 + x, below + p->rows + y) = Grid;
472 if (step == 5 && (i%10))
473 continue;
474 sprintf(text, "%g", .1 * i); /* Tick labels */
475 x = left + x - 3 * strlen(text);
476 print_at(x, below - 6, Labels, text);
477 print_at(x, below + p->rows + 14, Labels, text);
478 }
479
480 /* Y-axis */
481 step = axis(effp->in_signal.rate / 2, (p->rows - 1) / ((font_y * 3 + 1) >> 1), &limit, &prefix);
482 sprintf(text, "Frequency (%.1sHz)", prefix); /* Axis label */
483 print_up(10, below + (p->rows - font_X * (int)strlen(text)) / 2, Text, text);
484 for (i = 0; i <= limit; i += step) {
485 int x, y = limit? (double)i / limit * (p->rows - 1) + .5 : 0;
486 for (x = 0; x < tick_len; ++x) /* Ticks */
487 pixel(left - 1 - x, below + y) = pixel(left + p->cols + x, below + y) = Grid;
488 if (step == 5 && (i%10))
489 continue;
490 sprintf(text, i?"%5g":" DC", .1 * i); /* Tick labels */
491 print_at(left - 4 - font_X * 5, below + y + 5, Labels, text);
492 sprintf(text, i?"%g":"DC", .1 * i);
493 print_at(left + p->cols + 6, below + y + 5, Labels, text);
494 }
495
496 /* Z-axis */
497 print_at(cols - right - 2 - font_X, below - 13, Text, "dBFS");/* Axis label */
498 for (j = 0; j < p->rows; ++j) { /* Spectrum */
499 png_byte b = colour(p->dB_range * (j / (p->rows - 1.) - 1), p->dB_range);
500 for (i = 0; i < spectrum_width; ++i)
501 pixel(cols - right - 1 - i, below + j) = b;
502 }
503 for (i = 0; i <= p->dB_range; i += 10) { /* (Tick) labels */
504 int y = (double)i / p->dB_range * (p->rows - 1) + .5;
505 sprintf(text, "%+i", i - p->gain - p->dB_range);
506 print_at(cols - right + 1, below + y + 5, Labels, text);
507 }
508
509 free(font);
510 png_set_rows(png, png_info, png_rows);
511 png_write_png(png, png_info, PNG_TRANSFORM_IDENTITY, NULL);
512 png_destroy_write_struct(&png, &png_info);
513 free(png_rows);
514 free(pixels);
515 fclose(file);
516 free(p->dBfs);
517 return SOX_SUCCESS;
518}
519
520sox_effect_handler_t const * sox_spectrogram_effect_fn(void)
521{
522 static sox_effect_handler_t handler = {
523 "spectrogram", "[options]\n"
524 " -x num X-axis pixels/second, default 100\n"
525 " -y num Y-axis resolution (1 - 4), default 2\n"
526 " -z num Z-axis (colour) range in dB, default 120\n"
527 " -g num Apply num dB gain; to shift Z-axis\n"
528 " -w name Window: Hann (default), Hamming, Bartlett, Rectangular, Kaiser\n"
529 " -s num Display style (1 - 4), default 1\n"
530 " -a Toggle the presence of the axes' lines\n"
531 " -t text Title text\n"
532 " -c text Comment text\n"
533 " -o text Name of the spectrogram output file, default `spectrogram.png'\n",
534 0, getopts, start, flow, drain, stop, NULL, sizeof(priv_t)};
535 return &handler;
536}

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