spek

Acoustic spectrum analyser
git clone http://git.hanabi.in/repos/spek.git
Log | Files | Refs | README

commit 045253bd7071a99fb4d9090a0c2bc939f58c71c4
parent 72bf4ad3ea06ef327d162798ac97a5ec13bb2b21
Author: Alexander Kojevnikov <alexander@kojevnikov.com>
Date:   Sat, 16 Jul 2011 15:59:13 +0800

Untabify

Diffstat:
Mdist/osx/bundle.sh | 2+-
Mdist/osx/launcher.sh | 64++++++++++++++++++++++++++++++++--------------------------------
Mdist/osx/spek.modules.in | 70+++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/spek-audio.c | 332++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/spek-audio.h | 50+++++++++++++++++++++++++-------------------------
Msrc/spek-fft.c | 52++++++++++++++++++++++++++--------------------------
Msrc/spek-fft.h | 16++++++++--------
Msrc/spek-message-bar.vala | 98++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/spek-pipeline.vala | 550++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/spek-platform.c | 238++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/spek-preferences-dialog.vala | 150++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/spek-preferences.vala | 138++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/spek-ruler.vala | 186++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/spek-spectrogram.vala | 588++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/spek-window.vala | 588++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/spek.vala | 94++++++++++++++++++++++++++++++++++++++++----------------------------------------
Mvapi/config.vapi | 22+++++++++++-----------
Mvapi/spek-audio.vapi | 50+++++++++++++++++++++++++-------------------------
Mvapi/spek-fft.vapi | 24++++++++++++------------
Mvapi/spek-platform.vapi | 14+++++++-------
20 files changed, 1663 insertions(+), 1663 deletions(-)

diff --git a/dist/osx/bundle.sh b/dist/osx/bundle.sh @@ -66,7 +66,7 @@ hdiutil convert -quiet -format UDBZ -o $DMG_FILE $DMG_FILE.master echo "Done." if [ ! "x$1" = "x-m" ]; then - rm $DMG_FILE.master + rm $DMG_FILE.master fi cd ../.. diff --git a/dist/osx/launcher.sh b/dist/osx/launcher.sh @@ -46,27 +46,27 @@ if test "$APPLELANGUAGES"; then # A language ordering exists. # Test, item per item, to see whether there is an corresponding locale. for L in $APPLELANGUAGES; do - #test for exact matches: + #test for exact matches: if test -f "$I18NDIR/${L}/LC_MESSAGES/$APP.mo"; then - export LANG=$L + export LANG=$L break fi - #This is a special case, because often the original strings are in US - #English and there is no translation file. - if test "x$L" == "xen_US"; then - export LANG=$L - break - fi - #OK, now test for just the first two letters: + #This is a special case, because often the original strings are in US + #English and there is no translation file. + if test "x$L" == "xen_US"; then + export LANG=$L + break + fi + #OK, now test for just the first two letters: if test -f "$I18NDIR/${L:0:2}/LC_MESSAGES/$APP.mo"; then - export LANG=${L:0:2} - break - fi - #Same thing, but checking for any english variant. - if test "x${L:0:2}" == "xen"; then - export LANG=$L - break - fi; + export LANG=${L:0:2} + break + fi + #Same thing, but checking for any english variant. + if test "x${L:0:2}" == "xen"; then + export LANG=$L + break + fi; done fi unset APPLELANGUAGES L @@ -75,7 +75,7 @@ unset APPLELANGUAGES L APPLECOLLATION=`defaults read .GlobalPreferences AppleCollationOrder` if test -z ${LANG} -a -n $APPLECOLLATION; then if test -f "$I18NDIR/${APPLECOLLATION:0:2}/LC_MESSAGES/$APP.mo"; then - export LANG=${APPLECOLLATION:0:2} + export LANG=${APPLECOLLATION:0:2} fi fi if test ! -z $APPLECOLLATION; then @@ -104,21 +104,21 @@ if test -n $LANG; then #locale; otherwise, if it's longer than two characters, then it's #probably a good message locale and we'll go with it. if test $LANG == ${APPLELOCALE:0:5} -o $LANG != ${LANG:0:2}; then - export LC_MESSAGES=$LANG + export LC_MESSAGES=$LANG #Next try if the Applelocale is longer than 2 chars and the language #bit matches $LANG elif test $LANG == ${APPLELOCALE:0:2} -a $APPLELOCALE > ${APPLELOCALE:0:2}; then - export LC_MESSAGES=${APPLELOCALE:0:5} + export LC_MESSAGES=${APPLELOCALE:0:5} #Fail. Get a list of the locales in $PREFIX/share/locale that match #our two letter language code and pick the first one, special casing #english to set en_US elif test $LANG == "en"; then - export LC_MESSAGES="en_US" + export LC_MESSAGES="en_US" else - LOC=`find $PREFIX/share/locale -name $LANG???` - for L in $LOC; do - export LC_MESSAGES=$L - done + LOC=`find $PREFIX/share/locale -name $LANG???` + for L in $LOC; do + export LC_MESSAGES=$L + done fi else #All efforts have failed, so default to US english @@ -129,15 +129,15 @@ CURRENCY=`echo $APPLELOCALE | sed -En 's/.*currency=([[:alpha:]]+).*/\1/p'` if test "x$CURRENCY" != "x"; then #The user has set a special currency. Gtk doesn't install LC_MONETARY files, but Apple does in /usr/share/locale, so we're going to look there for a locale to set LC_CURRENCY to. if test -f /usr/local/share/$LC_MESSAGES/LC_MONETARY; then - if test -a `cat /usr/local/share/$LC_MESSAGES/LC_MONETARY` == $CURRENCY; then - export LC_MONETARY=$LC_MESSAGES - fi + if test -a `cat /usr/local/share/$LC_MESSAGES/LC_MONETARY` == $CURRENCY; then + export LC_MONETARY=$LC_MESSAGES + fi fi if test -z "$LC_MONETARY"; then - FILES=`find /usr/share/locale -name LC_MONETARY -exec grep -H $CURRENCY {} \;` - if test -n "$FILES"; then - export LC_MONETARY=`echo $FILES | sed -En 's%/usr/share/locale/([[:alpha:]_]+)/LC_MONETARY.*%\1%p'` - fi + FILES=`find /usr/share/locale -name LC_MONETARY -exec grep -H $CURRENCY {} \;` + if test -n "$FILES"; then + export LC_MONETARY=`echo $FILES | sed -En 's%/usr/share/locale/([[:alpha:]_]+)/LC_MONETARY.*%\1%p'` + fi fi fi #No currency value means that the AppleLocale governs: diff --git a/dist/osx/spek.modules.in b/dist/osx/spek.modules.in @@ -2,44 +2,44 @@ <!DOCTYPE moduleset SYSTEM "moduleset.dtd"> <?xml-stylesheet type="text/xsl" href="moduleset.xsl"?> <moduleset> - <include href="http://github.com/jralls/gtk-osx-build/raw/master/modulesets-stable/gtk-osx.modules"/> + <include href="http://github.com/jralls/gtk-osx-build/raw/master/modulesets-stable/gtk-osx.modules"/> - <repository type="tarball" name="spek" default="yes" href="http://spek.googlecode.com/files/"/> - <repository type="tarball" name="libav" href="http://libav.org/releases/"/> - <repository type="tarball" name="cairographics-dev" href="http://cairographics.org/snapshots/"/> - <repository type="git" name="github.com" href="git://github.com/"/> + <repository type="tarball" name="spek" default="yes" href="http://spek.googlecode.com/files/"/> + <repository type="tarball" name="libav" href="http://libav.org/releases/"/> + <repository type="tarball" name="cairographics-dev" href="http://cairographics.org/snapshots/"/> + <repository type="git" name="github.com" href="git://github.com/"/> - <autotools id="cairo" autogen-sh="configure" autogenargs="--enable-pdf --enable-quartz --enable-xlib=no --without-x"> - <branch module="cairo-1.11.2.tar.gz" version="1.11.2" repo="cairographics-dev"/> - <dependencies> - <dep package="pixman"/> - </dependencies> - <after> - <dep package="meta-gtk-osx-bootstrap"/> - <dep package="fontconfig"/> - <dep package="freetype"/> - </after> - </autotools> + <autotools id="cairo" autogen-sh="configure" autogenargs="--enable-pdf --enable-quartz --enable-xlib=no --without-x"> + <branch module="cairo-1.11.2.tar.gz" version="1.11.2" repo="cairographics-dev"/> + <dependencies> + <dep package="pixman"/> + </dependencies> + <after> + <dep package="meta-gtk-osx-bootstrap"/> + <dep package="fontconfig"/> + <dep package="freetype"/> + </after> + </autotools> - <autotools id="gtk-quartz-engine"> - <branch module="jralls/gtk-quartz-engine" repo="github.com"/> - <after> - <dep package="meta-gtk-osx-core"/> - </after> - </autotools> + <autotools id="gtk-quartz-engine"> + <branch module="jralls/gtk-quartz-engine" repo="github.com"/> + <after> + <dep package="meta-gtk-osx-core"/> + </after> + </autotools> - <autotools id="libav" autogenargs="--disable-static --enable-shared --enable-gpl --enable-version3 --disable-doc --disable-ffmpeg --disable-ffplay --disable-ffprobe --disable-ffserver --disable-avdevice --disable-swscale --enable-pthreads --disable-encoders --disable-muxers --disable-devices --disable-filters" autogen-sh="configure" autogen-template="%(srcdir)s/%(autogen-sh)s --prefix=%(prefix)s --libdir=%(libdir)s %(autogenargs)s"> - <branch module="libav-0.6.2.tar.bz2" version="0.6.2" repo="libav" hash="sha1:b79dc56a08f4ef07b41d1a78b2251f21fde8b81d"/> - </autotools> + <autotools id="libav" autogenargs="--disable-static --enable-shared --enable-gpl --enable-version3 --disable-doc --disable-ffmpeg --disable-ffplay --disable-ffprobe --disable-ffserver --disable-avdevice --disable-swscale --enable-pthreads --disable-encoders --disable-muxers --disable-devices --disable-filters" autogen-sh="configure" autogen-template="%(srcdir)s/%(autogen-sh)s --prefix=%(prefix)s --libdir=%(libdir)s %(autogenargs)s"> + <branch module="libav-0.6.2.tar.bz2" version="0.6.2" repo="libav" hash="sha1:b79dc56a08f4ef07b41d1a78b2251f21fde8b81d"/> + </autotools> - <autotools id="spek" autogen-sh="configure"> - <branch module="spek-@VERSION@.tar.bz2" version="@VERSION@" repo="spek"/> - <dependencies> - <dep package="meta-gtk-osx-bootstrap"/> - <dep package="meta-gtk-osx-core"/> - <dep package="cairo"/> - <dep package="gtk-quartz-engine"/> - <dep package="libav"/> - </dependencies> - </autotools> + <autotools id="spek" autogen-sh="configure"> + <branch module="spek-@VERSION@.tar.bz2" version="@VERSION@" repo="spek"/> + <dependencies> + <dep package="meta-gtk-osx-bootstrap"/> + <dep package="meta-gtk-osx-core"/> + <dep package="cairo"/> + <dep package="gtk-quartz-engine"/> + <dep package="libav"/> + </dependencies> + </autotools> </moduleset> diff --git a/src/spek-audio.c b/src/spek-audio.c @@ -22,188 +22,188 @@ #include "spek-audio.h" void spek_audio_init () { - avcodec_init (); - /* TODO: register only audio decoders */ - av_register_all (); + avcodec_init (); + /* TODO: register only audio decoders */ + av_register_all (); } SpekAudioContext * spek_audio_open (const gchar *file_name) { - SpekAudioContext *cx; - int i; + SpekAudioContext *cx; + int i; - cx = g_new0 (SpekAudioContext, 1); - cx->file_name = g_strdup (file_name); + cx = g_new0 (SpekAudioContext, 1); + cx->file_name = g_strdup (file_name); #ifdef G_OS_WIN32 - /* av_open_input_file() cannot open files with Unicode chars in it - * when running under Windows. When this happens we will re-try - * using the corresponding short file name. - */ - cx->short_name = g_win32_locale_filename_from_utf8 (file_name); + /* av_open_input_file() cannot open files with Unicode chars in it + * when running under Windows. When this happens we will re-try + * using the corresponding short file name. + */ + cx->short_name = g_win32_locale_filename_from_utf8 (file_name); #endif - if (av_open_input_file (&cx->format_context, file_name, NULL, 0, NULL) != 0) { - if (!cx->short_name || - av_open_input_file (&cx->format_context, cx->short_name, NULL, 0, NULL) != 0 ) { - cx->error = _("Cannot open input file"); - return cx; - } - } - if (av_find_stream_info (cx->format_context) < 0) { - /* 24-bit APE returns an error but parses the stream info just fine */ - if (cx->format_context->nb_streams <= 0) { - cx->error = _("Cannot find stream info"); - return cx; - } - } - cx->audio_stream = -1; - for (i = 0; i < cx->format_context->nb_streams; i++) { - if (cx->format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { - cx->audio_stream = i; - break; - } - } - if (cx->audio_stream == -1) { - cx->error = _("The file contains no audio streams"); - return cx; - } - cx->stream = cx->format_context->streams[cx->audio_stream]; - cx->codec_context = cx->stream->codec; - cx->codec = avcodec_find_decoder (cx->codec_context->codec_id); - if (cx->codec == NULL) { - cx->error = _("Cannot find decoder"); - return cx; - } - /* We can already fill in the stream info even if the codec won't be able to open it */ - cx->codec_name = g_strdup (cx->codec->long_name); - cx->bit_rate = cx->codec_context->bit_rate; - cx->sample_rate = cx->codec_context->sample_rate; - cx->bits_per_sample = cx->codec_context->bits_per_raw_sample; - if (!cx->bits_per_sample) { - /* APE uses bpcs, FLAC uses bprs. */ - cx->bits_per_sample = cx->codec_context->bits_per_coded_sample; - } - cx->channels = cx->codec_context->channels; - cx->duration = cx->stream->duration * av_q2d (cx->stream->time_base); - if (cx->channels <= 0) { - cx->error = _("No audio channels"); - return cx; - } - if (avcodec_open (cx->codec_context, cx->codec) < 0) { - cx->error = _("Cannot open decoder"); - return cx; - } - switch (cx->codec_context->sample_fmt) { - case SAMPLE_FMT_S16: - cx->width = 16; - cx->fp = FALSE; - break; - case SAMPLE_FMT_S32: - cx->width = 32; - cx->fp = FALSE; - break; - case SAMPLE_FMT_FLT: - cx->width = 32; - cx->fp = TRUE; - break; - case SAMPLE_FMT_DBL: - cx->width = 64; - cx->fp = TRUE; - break; - default: - cx->error = _("Unsupported sample format"); - return cx; - } - cx->buffer_size = (AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2; - cx->buffer = av_malloc (cx->buffer_size); - cx->packet = av_mallocz (sizeof (AVPacket)); - av_init_packet (cx->packet); - cx->offset = 0; - return cx; + if (av_open_input_file (&cx->format_context, file_name, NULL, 0, NULL) != 0) { + if (!cx->short_name || + av_open_input_file (&cx->format_context, cx->short_name, NULL, 0, NULL) != 0 ) { + cx->error = _("Cannot open input file"); + return cx; + } + } + if (av_find_stream_info (cx->format_context) < 0) { + /* 24-bit APE returns an error but parses the stream info just fine */ + if (cx->format_context->nb_streams <= 0) { + cx->error = _("Cannot find stream info"); + return cx; + } + } + cx->audio_stream = -1; + for (i = 0; i < cx->format_context->nb_streams; i++) { + if (cx->format_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { + cx->audio_stream = i; + break; + } + } + if (cx->audio_stream == -1) { + cx->error = _("The file contains no audio streams"); + return cx; + } + cx->stream = cx->format_context->streams[cx->audio_stream]; + cx->codec_context = cx->stream->codec; + cx->codec = avcodec_find_decoder (cx->codec_context->codec_id); + if (cx->codec == NULL) { + cx->error = _("Cannot find decoder"); + return cx; + } + /* We can already fill in the stream info even if the codec won't be able to open it */ + cx->codec_name = g_strdup (cx->codec->long_name); + cx->bit_rate = cx->codec_context->bit_rate; + cx->sample_rate = cx->codec_context->sample_rate; + cx->bits_per_sample = cx->codec_context->bits_per_raw_sample; + if (!cx->bits_per_sample) { + /* APE uses bpcs, FLAC uses bprs. */ + cx->bits_per_sample = cx->codec_context->bits_per_coded_sample; + } + cx->channels = cx->codec_context->channels; + cx->duration = cx->stream->duration * av_q2d (cx->stream->time_base); + if (cx->channels <= 0) { + cx->error = _("No audio channels"); + return cx; + } + if (avcodec_open (cx->codec_context, cx->codec) < 0) { + cx->error = _("Cannot open decoder"); + return cx; + } + switch (cx->codec_context->sample_fmt) { + case SAMPLE_FMT_S16: + cx->width = 16; + cx->fp = FALSE; + break; + case SAMPLE_FMT_S32: + cx->width = 32; + cx->fp = FALSE; + break; + case SAMPLE_FMT_FLT: + cx->width = 32; + cx->fp = TRUE; + break; + case SAMPLE_FMT_DBL: + cx->width = 64; + cx->fp = TRUE; + break; + default: + cx->error = _("Unsupported sample format"); + return cx; + } + cx->buffer_size = (AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2; + cx->buffer = av_malloc (cx->buffer_size); + cx->packet = av_mallocz (sizeof (AVPacket)); + av_init_packet (cx->packet); + cx->offset = 0; + return cx; } void spek_audio_start (SpekAudioContext *cx, gint samples) { - gint64 rate = cx->sample_rate * (gint64) cx->stream->time_base.num; - cx->error_base = samples * (gint64) cx->stream->time_base.den; - cx->frames_per_interval = av_rescale_rnd ( - cx->stream->duration, rate, cx->error_base, AV_ROUND_DOWN); - cx->error_per_interval = (cx->stream->duration * rate) % cx->error_base; + gint64 rate = cx->sample_rate * (gint64) cx->stream->time_base.num; + cx->error_base = samples * (gint64) cx->stream->time_base.den; + cx->frames_per_interval = av_rescale_rnd ( + cx->stream->duration, rate, cx->error_base, AV_ROUND_DOWN); + cx->error_per_interval = (cx->stream->duration * rate) % cx->error_base; } gint spek_audio_read (SpekAudioContext *cx) { - gint buffer_size; - gint len; - gint res; + gint buffer_size; + gint len; + gint res; - if (cx->error) { - return -1; - } + if (cx->error) { + return -1; + } - for (;;) { - while (cx->packet->size > 0) { - buffer_size = cx->buffer_size; - len = avcodec_decode_audio3 ( - cx->codec_context, (int16_t *) cx->buffer, &buffer_size, cx->packet); - if (len < 0) { - /* Error, skip the frame. */ - cx->packet->size = 0; - break; - } - cx->packet->data += len; - cx->packet->size -= len; - cx->offset += len; - if (buffer_size <= 0) { - /* No data yet, get more frames */ - continue; - } - /* We have data, return it and come back for more later */ - return buffer_size; - } - if (cx->packet->data) { - cx->packet->data -= cx->offset; - cx->packet->size += cx->offset; - cx->offset = 0; - av_free_packet (cx->packet); - } - while ((res = av_read_frame (cx->format_context, cx->packet)) >= 0) { - if (cx->packet->stream_index == cx->audio_stream) { - break; - } - av_free_packet (cx->packet); - } - if (res < 0) { - /* End of file or error. */ - return 0; - } - } + for (;;) { + while (cx->packet->size > 0) { + buffer_size = cx->buffer_size; + len = avcodec_decode_audio3 ( + cx->codec_context, (int16_t *) cx->buffer, &buffer_size, cx->packet); + if (len < 0) { + /* Error, skip the frame. */ + cx->packet->size = 0; + break; + } + cx->packet->data += len; + cx->packet->size -= len; + cx->offset += len; + if (buffer_size <= 0) { + /* No data yet, get more frames */ + continue; + } + /* We have data, return it and come back for more later */ + return buffer_size; + } + if (cx->packet->data) { + cx->packet->data -= cx->offset; + cx->packet->size += cx->offset; + cx->offset = 0; + av_free_packet (cx->packet); + } + while ((res = av_read_frame (cx->format_context, cx->packet)) >= 0) { + if (cx->packet->stream_index == cx->audio_stream) { + break; + } + av_free_packet (cx->packet); + } + if (res < 0) { + /* End of file or error. */ + return 0; + } + } } void spek_audio_close (SpekAudioContext *cx) { - if (cx->file_name != NULL) { - g_free (cx->file_name); - } - if (cx->short_name != NULL) { - g_free (cx->short_name); - } - if (cx->codec_name != NULL) { - g_free (cx->codec_name); - } - if (cx->buffer) { - av_free (cx->buffer); - } - if (cx->packet) { - if (cx->packet->data) { - cx->packet->data -= cx->offset; - cx->packet->size += cx->offset; - cx->offset = 0; - av_free_packet (cx->packet); - } - av_free (cx->packet); - } - if (cx->codec_context != NULL) { - avcodec_close (cx->codec_context); - } - if (cx->format_context != NULL) { - av_close_input_file (cx->format_context); - } - g_free (cx); + if (cx->file_name != NULL) { + g_free (cx->file_name); + } + if (cx->short_name != NULL) { + g_free (cx->short_name); + } + if (cx->codec_name != NULL) { + g_free (cx->codec_name); + } + if (cx->buffer) { + av_free (cx->buffer); + } + if (cx->packet) { + if (cx->packet->data) { + cx->packet->data -= cx->offset; + cx->packet->size += cx->offset; + cx->offset = 0; + av_free_packet (cx->packet); + } + av_free (cx->packet); + } + if (cx->codec_context != NULL) { + avcodec_close (cx->codec_context); + } + if (cx->format_context != NULL) { + av_close_input_file (cx->format_context); + } + g_free (cx); } diff --git a/src/spek-audio.h b/src/spek-audio.h @@ -24,32 +24,32 @@ #include <libavcodec/avcodec.h> typedef struct { - /* Internal data */ - gchar *short_name; - AVFormatContext *format_context; - gint audio_stream; - AVCodecContext *codec_context; - AVStream *stream; - AVCodec *codec; - gint buffer_size; - AVPacket *packet; - gint offset; + /* Internal data */ + gchar *short_name; + AVFormatContext *format_context; + gint audio_stream; + AVCodecContext *codec_context; + AVStream *stream; + AVCodec *codec; + gint buffer_size; + AVPacket *packet; + gint offset; - /* Exposed properties */ - gchar *file_name; - gchar *codec_name; - gchar *error; - gint bit_rate; - gint sample_rate; - gint bits_per_sample; - gint width; /* number of bits used to store a sample */ - gboolean fp; /* floating-point sample representation */ - gint channels; - gdouble duration; - guint8 *buffer; - gint64 frames_per_interval; - gint64 error_per_interval; - gint64 error_base; + /* Exposed properties */ + gchar *file_name; + gchar *codec_name; + gchar *error; + gint bit_rate; + gint sample_rate; + gint bits_per_sample; + gint width; /* number of bits used to store a sample */ + gboolean fp; /* floating-point sample representation */ + gint channels; + gdouble duration; + guint8 *buffer; + gint64 frames_per_interval; + gint64 error_per_interval; + gint64 error_base; } SpekAudioContext; /* Initialise FFmpeg, should be called once on start up */ diff --git a/src/spek-fft.c b/src/spek-fft.c @@ -22,38 +22,38 @@ #include "spek-fft.h" SpekFftPlan * spek_fft_plan_new (gint n, gint threshold) { - gint bits; - SpekFftPlan *p = g_new0 (SpekFftPlan, 1); - p->input = av_mallocz (sizeof (gfloat) * n); - p->output = av_mallocz (sizeof (gfloat) * (n / 2 + 1)); - p->threshold = threshold; - for (bits = 0; n; n >>= 1, bits++); - p->n = 1 << --bits; - p->cx = av_rdft_init (bits, DFT_R2C); - return p; + gint bits; + SpekFftPlan *p = g_new0 (SpekFftPlan, 1); + p->input = av_mallocz (sizeof (gfloat) * n); + p->output = av_mallocz (sizeof (gfloat) * (n / 2 + 1)); + p->threshold = threshold; + for (bits = 0; n; n >>= 1, bits++); + p->n = 1 << --bits; + p->cx = av_rdft_init (bits, DFT_R2C); + return p; } void spek_fft_execute (SpekFftPlan *p) { - int i; - int n = p->n; + int i; + int n = p->n; - av_rdft_calc (p->cx, p->input); + av_rdft_calc (p->cx, p->input); - /* Calculate magnitudes */ - p->output[0] = p->input[0] * p->input[0] / (n * n); - p->output[n / 2] = p->input[1] * p->input[1] / (n * n); - for (i = 1; i < n / 2; i++) { - gfloat val; - val = p->input[i * 2] * p->input[i * 2] + p->input[i * 2 + 1] * p->input[i * 2 + 1]; - val /= n * n; - val = 10.0 * log10f (val); - p->output[i] = val < p->threshold ? p->threshold : val; - } + /* Calculate magnitudes */ + p->output[0] = p->input[0] * p->input[0] / (n * n); + p->output[n / 2] = p->input[1] * p->input[1] / (n * n); + for (i = 1; i < n / 2; i++) { + gfloat val; + val = p->input[i * 2] * p->input[i * 2] + p->input[i * 2 + 1] * p->input[i * 2 + 1]; + val /= n * n; + val = 10.0 * log10f (val); + p->output[i] = val < p->threshold ? p->threshold : val; + } } void spek_fft_destroy (SpekFftPlan *p) { - av_rdft_end (p->cx); - av_free (p->input); - av_free (p->output); - g_free (p); + av_rdft_end (p->cx); + av_free (p->input); + av_free (p->output); + g_free (p); } diff --git a/src/spek-fft.h b/src/spek-fft.h @@ -23,14 +23,14 @@ #include <libavcodec/avfft.h> typedef struct { - /* Internal data */ - RDFTContext *cx; - gint n; - gint threshold; - - /* Exposed properties */ - gfloat *input; - gfloat *output; + /* Internal data */ + RDFTContext *cx; + gint n; + gint threshold; + + /* Exposed properties */ + gfloat *input; + gfloat *output; } SpekFftPlan; /* Allocate buffers and create a new FFT plan */ diff --git a/src/spek-message-bar.vala b/src/spek-message-bar.vala @@ -22,61 +22,61 @@ using Gtk; namespace Spek { - public class MessageBar : HBox { - private Label label; - private Gtk.Window win; + public class MessageBar : HBox { + private Label label; + private Gtk.Window win; - public MessageBar (string message) { - homogeneous = false; - spacing = 0; - border_width = 6; + public MessageBar (string message) { + homogeneous = false; + spacing = 0; + border_width = 6; - win = new Gtk.Window (WindowType.POPUP); - win.name = "gtk-tooltips"; - win.ensure_style (); - win.style_set.connect (() => style = win.style); + win = new Gtk.Window (WindowType.POPUP); + win.name = "gtk-tooltips"; + win.ensure_style (); + win.style_set.connect (() => style = win.style); - label = new Label (null); - label.use_markup = true; - label.set_markup (message); - label.ellipsize = Pango.EllipsizeMode.END; - label.xalign = 0f; - label.activate_link.connect (uri => { Platform.show_uri (uri); return true; }); - var button_box = new HBox (false, 0); - button_box.spacing = 3; - var close_button = new Button (); - close_button.image = new Gtk.Image.from_stock (Stock.CLOSE, IconSize.MENU); - close_button.relief = ReliefStyle.NONE; - close_button.clicked.connect (() => hide ()); + label = new Label (null); + label.use_markup = true; + label.set_markup (message); + label.ellipsize = Pango.EllipsizeMode.END; + label.xalign = 0f; + label.activate_link.connect (uri => { Platform.show_uri (uri); return true; }); + var button_box = new HBox (false, 0); + button_box.spacing = 3; + var close_button = new Button (); + close_button.image = new Gtk.Image.from_stock (Stock.CLOSE, IconSize.MENU); + close_button.relief = ReliefStyle.NONE; + close_button.clicked.connect (() => hide ()); - pack_start (label, true, true, 0); - pack_start (button_box, false, false, 0); - pack_start (close_button, false, false, 0); - } + pack_start (label, true, true, 0); + pack_start (button_box, false, false, 0); + pack_start (close_button, false, false, 0); + } - private bool changing_style = false; - protected override void style_set (Style? previous_style) { - if (changing_style) { - return; - } + private bool changing_style = false; + protected override void style_set (Style? previous_style) { + if (changing_style) { + return; + } - changing_style = true; - style = win.style; - label.style = style; - changing_style = false; - } + changing_style = true; + style = win.style; + label.style = style; + changing_style = false; + } - protected override bool expose_event (Gdk.EventExpose event) { - if (!is_drawable ()) { - return false; - } + protected override bool expose_event (Gdk.EventExpose event) { + if (!is_drawable ()) { + return false; + } - var cr = Gdk.cairo_create (event.window); - var color = style.bg[StateType.NORMAL]; - cr.set_source_rgb (color.red / 65535.0, color.green / 65535.0, color.blue / 65535.0); - cr.rectangle (allocation.x, allocation.y, allocation.width, allocation.height); - cr.fill (); - return base.expose_event (event); - } - } + var cr = Gdk.cairo_create (event.window); + var color = style.bg[StateType.NORMAL]; + cr.set_source_rgb (color.red / 65535.0, color.green / 65535.0, color.blue / 65535.0); + cr.rectangle (allocation.x, allocation.y, allocation.width, allocation.height); + cr.fill (); + return base.expose_event (event); + } + } } diff --git a/src/spek-pipeline.vala b/src/spek-pipeline.vala @@ -24,279 +24,279 @@ */ namespace Spek { - public class Pipeline { - public string description { get; private set; } - public int sample_rate { get; private set; } - public double duration { get { return cx.duration; } } - public delegate void Callback (int sample, float[] values); - - private Audio.Context cx; - private int bands; - private int samples; - private int threshold; - private Callback cb; - - private Fft.Plan fft; - private int nfft; // Size of the FFT transform. - private float[] coss; // Pre-computed cos table. - private const int NFFT = 64; // Number of FFTs to pre-fetch. - private int input_size; - private int input_pos; - private float[] input; - private float[] output; - - private unowned Thread<void*> reader_thread = null; - private unowned Thread<void*> worker_thread; - private Mutex reader_mutex; - private Cond reader_cond; - private Mutex worker_mutex; - private Cond worker_cond; - private bool worker_done = false; - private bool quit = false; - - public Pipeline (string file_name, int bands, int samples, int threshold, Callback cb) { - this.cx = new Audio.Context (file_name); - this.bands = bands; - this.samples = samples; - this.threshold = threshold; - this.cb = cb; - - // Build the description string. - string[] items = {}; - if (cx.codec_name != null) { - items += cx.codec_name; - } - if (cx.bit_rate != 0) { - items += _("%d kbps").printf ((cx.bit_rate + 500) / 1000); - } - if (cx.sample_rate != 0) { - items += _("%d Hz").printf (cx.sample_rate); - } - // Show bits per sample only if there is no bitrate. - if (cx.bits_per_sample != 0 && cx.bit_rate == 0) { - items += ngettext ( - "%d bit", "%d bits", cx.bits_per_sample).printf (cx.bits_per_sample); - } - if (cx.channels != 0) { - items += ngettext ("%d channel", "%d channels", cx.channels). - printf (cx.channels); - } - description = items.length > 0 ? string.joinv (", ", items) : ""; - - if (cx.error != null) { - // TRANSLATORS: first %s is the error message, second %s is stream description. - description = _("%s: %s").printf (cx.error, description); - } else { - this.sample_rate = cx.sample_rate; - this.nfft = 2 * bands - 2; - this.coss = new float[nfft]; - float cf = 2f * (float) Math.PI / this.nfft; - for (int i = 0; i < this.nfft; i++) { - this.coss[i] = Math.cosf (cf * i); - } - this.fft = new Fft.Plan (nfft, threshold); - this.input_size = nfft * (NFFT * 2 + 1); - this.input = new float[input_size]; - this.output = new float[bands]; - this.cx.start (samples); - } - } - - ~Pipeline () { - stop (); - } - - public void start () { - stop (); - - if (cx.error != null) return; - - input_pos = 0; - reader_mutex = new Mutex (); - reader_cond = new Cond (); - worker_mutex = new Mutex (); - worker_cond = new Cond (); - - try { - reader_thread = Thread.create<void*> (reader_func, true); - } catch (ThreadError e) { - stop (); - } - } - - public void stop () { - if (reader_thread != null) { - lock (quit) { - quit = true; - } - reader_thread.join (); - quit = false; - reader_thread = null; - } - } - - private void * reader_func () { - var timeval = TimeVal (); - timeval.get_current_time (); - - int pos = 0, prev_pos = 0; - int block_size = cx.width * cx.channels / 8; - int size; - - try { - worker_thread = Thread.create<void*> (worker_func, true); - } catch (ThreadError e) { - return null; - } - - while ((size = cx.read ()) > 0) { - lock (quit) if (quit) break; - - uint8 *buffer = (uint8 *) cx.buffer; - while (size >= block_size) { - input[pos] = average_input (buffer); - buffer += block_size; - size -= block_size; - pos = (pos + 1) % input_size; - - // Wake up the worker if we have enough data. - if ((pos > prev_pos ? pos : pos + input_size) - prev_pos == nfft * NFFT) { - reader_sync (prev_pos = pos); - } - } - assert (size == 0); - } - - if (pos != prev_pos) { - // Process the remaining data. - reader_sync (pos); - } - // Force the worker to quit. - reader_sync (-1); - worker_thread.join (); - return null; - } - - private void reader_sync (int pos) { - reader_mutex.lock (); - while (!worker_done) reader_cond.wait (reader_mutex); - worker_done = false; - reader_mutex.unlock (); - - worker_mutex.lock (); - input_pos = pos; - worker_cond.signal (); - worker_mutex.unlock (); - } - - private void * worker_func () { - int sample = 0; - int64 frames = 0; - int64 num_fft = 0; - int64 acc_error = 0; - int head = 0, tail = 0; - int prev_head = 0; - - Memory.set (output, 0, sizeof (float) * bands); - - while (true) { - reader_mutex.lock (); - worker_done = true; - reader_cond.signal (); - reader_mutex.unlock (); - - worker_mutex.lock (); - while (tail == input_pos) worker_cond.wait (worker_mutex); - tail = input_pos; - worker_mutex.unlock (); - - if (tail == -1) { - return null; - } - - while (true) { - head = (head + 1) % input_size; - if (head == tail) { - head = prev_head; - break; - } - frames++; - - // If we have enough frames for an FFT or we have - // all frames required for the interval run and FFT. - bool int_full = acc_error < cx.error_base && frames == cx.frames_per_interval; - bool int_over = acc_error >= cx.error_base && frames == 1 + cx.frames_per_interval; - if (frames % nfft == 0 || ((int_full || int_over) && num_fft == 0)) { - prev_head = head; - for (int i = 0; i < nfft; i++) { - float val = input[(input_size + head - nfft + i) % input_size]; - // TODO: allow the user to chose the window function - // Hamming window. -// val *= 0.53836f - 0.46164f * coss[i]; - // Hann window. - val *= 0.5f * (1f - coss[i]); - fft.input[i] = val; - } - fft.execute (); - num_fft++; - for (int i = 0; i < bands; i++) { - output[i] += fft.output[i]; - } - } - // Do we have the FFTs for one interval? - if (int_full || int_over) { - if (int_over) { - acc_error -= cx.error_base; - } else { - acc_error += cx.error_per_interval; - } - - for (int i = 0; i < bands; i++) { - output[i] /= num_fft; - } - - if (sample == samples) break; - cb (sample++, output); - - Memory.set (output, 0, sizeof (float) * bands); - frames = 0; - num_fft = 0; - } - } - } - } - - private float average_input (uint8 *buffer) { - int channels = cx.channels; - float res = 0f; - if (cx.fp) { - if (cx.width == 32) { - float *p = (float *) buffer; - for (int i = 0; i < channels; i++) { - res += p[i]; - } - } else { - assert (cx.width == 64); - double *p = (double *) buffer; - for (int i = 0; i < channels; i++) { - res += (float) p[i]; - } - } - } else { - if (cx.width == 16) { - int16 *p = (int16 *) buffer; - for (int i = 0; i < channels; i++) { - res += p[i] / (float) int16.MAX; - } - } else { - assert (cx.width == 32); - int32 *p = (int32 *) buffer; - for (int i = 0; i < channels; i++) { - res += p[i] / (float) int32.MAX; - } - } - } - return res / channels; - } - } + public class Pipeline { + public string description { get; private set; } + public int sample_rate { get; private set; } + public double duration { get { return cx.duration; } } + public delegate void Callback (int sample, float[] values); + + private Audio.Context cx; + private int bands; + private int samples; + private int threshold; + private Callback cb; + + private Fft.Plan fft; + private int nfft; // Size of the FFT transform. + private float[] coss; // Pre-computed cos table. + private const int NFFT = 64; // Number of FFTs to pre-fetch. + private int input_size; + private int input_pos; + private float[] input; + private float[] output; + + private unowned Thread<void*> reader_thread = null; + private unowned Thread<void*> worker_thread; + private Mutex reader_mutex; + private Cond reader_cond; + private Mutex worker_mutex; + private Cond worker_cond; + private bool worker_done = false; + private bool quit = false; + + public Pipeline (string file_name, int bands, int samples, int threshold, Callback cb) { + this.cx = new Audio.Context (file_name); + this.bands = bands; + this.samples = samples; + this.threshold = threshold; + this.cb = cb; + + // Build the description string. + string[] items = {}; + if (cx.codec_name != null) { + items += cx.codec_name; + } + if (cx.bit_rate != 0) { + items += _("%d kbps").printf ((cx.bit_rate + 500) / 1000); + } + if (cx.sample_rate != 0) { + items += _("%d Hz").printf (cx.sample_rate); + } + // Show bits per sample only if there is no bitrate. + if (cx.bits_per_sample != 0 && cx.bit_rate == 0) { + items += ngettext ( + "%d bit", "%d bits", cx.bits_per_sample).printf (cx.bits_per_sample); + } + if (cx.channels != 0) { + items += ngettext ("%d channel", "%d channels", cx.channels). + printf (cx.channels); + } + description = items.length > 0 ? string.joinv (", ", items) : ""; + + if (cx.error != null) { + // TRANSLATORS: first %s is the error message, second %s is stream description. + description = _("%s: %s").printf (cx.error, description); + } else { + this.sample_rate = cx.sample_rate; + this.nfft = 2 * bands - 2; + this.coss = new float[nfft]; + float cf = 2f * (float) Math.PI / this.nfft; + for (int i = 0; i < this.nfft; i++) { + this.coss[i] = Math.cosf (cf * i); + } + this.fft = new Fft.Plan (nfft, threshold); + this.input_size = nfft * (NFFT * 2 + 1); + this.input = new float[input_size]; + this.output = new float[bands]; + this.cx.start (samples); + } + } + + ~Pipeline () { + stop (); + } + + public void start () { + stop (); + + if (cx.error != null) return; + + input_pos = 0; + reader_mutex = new Mutex (); + reader_cond = new Cond (); + worker_mutex = new Mutex (); + worker_cond = new Cond (); + + try { + reader_thread = Thread.create<void*> (reader_func, true); + } catch (ThreadError e) { + stop (); + } + } + + public void stop () { + if (reader_thread != null) { + lock (quit) { + quit = true; + } + reader_thread.join (); + quit = false; + reader_thread = null; + } + } + + private void * reader_func () { + var timeval = TimeVal (); + timeval.get_current_time (); + + int pos = 0, prev_pos = 0; + int block_size = cx.width * cx.channels / 8; + int size; + + try { + worker_thread = Thread.create<void*> (worker_func, true); + } catch (ThreadError e) { + return null; + } + + while ((size = cx.read ()) > 0) { + lock (quit) if (quit) break; + + uint8 *buffer = (uint8 *) cx.buffer; + while (size >= block_size) { + input[pos] = average_input (buffer); + buffer += block_size; + size -= block_size; + pos = (pos + 1) % input_size; + + // Wake up the worker if we have enough data. + if ((pos > prev_pos ? pos : pos + input_size) - prev_pos == nfft * NFFT) { + reader_sync (prev_pos = pos); + } + } + assert (size == 0); + } + + if (pos != prev_pos) { + // Process the remaining data. + reader_sync (pos); + } + // Force the worker to quit. + reader_sync (-1); + worker_thread.join (); + return null; + } + + private void reader_sync (int pos) { + reader_mutex.lock (); + while (!worker_done) reader_cond.wait (reader_mutex); + worker_done = false; + reader_mutex.unlock (); + + worker_mutex.lock (); + input_pos = pos; + worker_cond.signal (); + worker_mutex.unlock (); + } + + private void * worker_func () { + int sample = 0; + int64 frames = 0; + int64 num_fft = 0; + int64 acc_error = 0; + int head = 0, tail = 0; + int prev_head = 0; + + Memory.set (output, 0, sizeof (float) * bands); + + while (true) { + reader_mutex.lock (); + worker_done = true; + reader_cond.signal (); + reader_mutex.unlock (); + + worker_mutex.lock (); + while (tail == input_pos) worker_cond.wait (worker_mutex); + tail = input_pos; + worker_mutex.unlock (); + + if (tail == -1) { + return null; + } + + while (true) { + head = (head + 1) % input_size; + if (head == tail) { + head = prev_head; + break; + } + frames++; + + // If we have enough frames for an FFT or we have + // all frames required for the interval run and FFT. + bool int_full = acc_error < cx.error_base && frames == cx.frames_per_interval; + bool int_over = acc_error >= cx.error_base && frames == 1 + cx.frames_per_interval; + if (frames % nfft == 0 || ((int_full || int_over) && num_fft == 0)) { + prev_head = head; + for (int i = 0; i < nfft; i++) { + float val = input[(input_size + head - nfft + i) % input_size]; + // TODO: allow the user to chose the window function + // Hamming window. +// val *= 0.53836f - 0.46164f * coss[i]; + // Hann window. + val *= 0.5f * (1f - coss[i]); + fft.input[i] = val; + } + fft.execute (); + num_fft++; + for (int i = 0; i < bands; i++) { + output[i] += fft.output[i]; + } + } + // Do we have the FFTs for one interval? + if (int_full || int_over) { + if (int_over) { + acc_error -= cx.error_base; + } else { + acc_error += cx.error_per_interval; + } + + for (int i = 0; i < bands; i++) { + output[i] /= num_fft; + } + + if (sample == samples) break; + cb (sample++, output); + + Memory.set (output, 0, sizeof (float) * bands); + frames = 0; + num_fft = 0; + } + } + } + } + + private float average_input (uint8 *buffer) { + int channels = cx.channels; + float res = 0f; + if (cx.fp) { + if (cx.width == 32) { + float *p = (float *) buffer; + for (int i = 0; i < channels; i++) { + res += p[i]; + } + } else { + assert (cx.width == 64); + double *p = (double *) buffer; + for (int i = 0; i < channels; i++) { + res += (float) p[i]; + } + } + } else { + if (cx.width == 16) { + int16 *p = (int16 *) buffer; + for (int i = 0; i < channels; i++) { + res += p[i] / (float) int16.MAX; + } + } else { + assert (cx.width == 32); + int32 *p = (int32 *) buffer; + for (int i = 0; i < channels; i++) { + res += p[i] / (float) int32.MAX; + } + } + } + return res / channels; + } + } } diff --git a/src/spek-platform.c b/src/spek-platform.c @@ -34,34 +34,34 @@ void spek_platform_init () { #ifdef G_OS_DARWIN - g_object_new (GTK_TYPE_OSX_APPLICATION, NULL); + g_object_new (GTK_TYPE_OSX_APPLICATION, NULL); #endif } void spek_platform_fix_args (gchar **argv, gint argc) { #ifdef G_OS_WIN32 - /* Because MinGW does not support Unicode arguments we are going to - * get them using Windows API. In addition, GLib's option parser - * doesn't work well with utf-8 strings on Windows, converting - * them to URIs works around this problem. - */ - int i; - gchar *s, *t; - wchar_t **wargv; - int wargc; - wargv = CommandLineToArgvW (GetCommandLineW (), &wargc); - for (i = 0; i < argc; i++) { - s = g_utf16_to_utf8 (wargv[i], -1, NULL, NULL, NULL); - if (s) { - t = g_filename_to_uri (s, NULL, NULL); - g_free (s); - if (t) { - g_free (argv[i]); - argv[i] = t; - } - } - } - LocalFree (wargv); + /* Because MinGW does not support Unicode arguments we are going to + * get them using Windows API. In addition, GLib's option parser + * doesn't work well with utf-8 strings on Windows, converting + * them to URIs works around this problem. + */ + int i; + gchar *s, *t; + wchar_t **wargv; + int wargc; + wargv = CommandLineToArgvW (GetCommandLineW (), &wargc); + for (i = 0; i < argc; i++) { + s = g_utf16_to_utf8 (wargv[i], -1, NULL, NULL, NULL); + if (s) { + t = g_filename_to_uri (s, NULL, NULL); + g_free (s); + if (t) { + g_free (argv[i]); + argv[i] = t; + } + } + } + LocalFree (wargv); #endif } @@ -69,141 +69,141 @@ void spek_platform_fix_args (gchar **argv, gint argc) { static void accel_map_foreach (gpointer data, const gchar *accel_path, guint accel_key, GdkModifierType accel_mods, gboolean changed) { - if (accel_mods & GDK_CONTROL_MASK) { - accel_mods &= ~GDK_CONTROL_MASK; - accel_mods |= GDK_META_MASK; - gtk_accel_map_change_entry (accel_path, accel_key, accel_mods, FALSE); - } + if (accel_mods & GDK_CONTROL_MASK) { + accel_mods &= ~GDK_CONTROL_MASK; + accel_mods |= GDK_META_MASK; + gtk_accel_map_change_entry (accel_path, accel_key, accel_mods, FALSE); + } } #endif void spek_platform_fix_ui (GtkUIManager *ui) { #ifdef G_OS_DARWIN - GtkOSXApplication *app = NULL; - GtkOSXApplicationMenuGroup *group = NULL; - GtkWidget *menubar = NULL; - GtkWidget *file_quit = NULL; - GtkWidget *edit_preferences = NULL; - GtkWidget *help_about = NULL; - - app = g_object_new (GTK_TYPE_OSX_APPLICATION, NULL); - menubar = gtk_ui_manager_get_widget (ui, "/MenuBar"); - file_quit = gtk_ui_manager_get_widget (ui, "/MenuBar/File/FileQuit"); - edit_preferences = gtk_ui_manager_get_widget (ui, "/MenuBar/Edit/EditPreferences"); - help_about = gtk_ui_manager_get_widget (ui, "/MenuBar/Help/HelpAbout"); - - gtk_widget_hide (menubar); - gtk_widget_hide (file_quit); - gtk_osxapplication_set_menu_bar (app, GTK_MENU_SHELL (menubar)); - - group = gtk_osxapplication_add_app_menu_group (app); - gtk_osxapplication_add_app_menu_item (app, group, GTK_MENU_ITEM (help_about)); - group = gtk_osxapplication_add_app_menu_group (app); - gtk_osxapplication_add_app_menu_item (app, group, GTK_MENU_ITEM (edit_preferences)); - - gtk_accel_map_foreach (NULL, accel_map_foreach); - - gtk_osxapplication_ready (app); + GtkOSXApplication *app = NULL; + GtkOSXApplicationMenuGroup *group = NULL; + GtkWidget *menubar = NULL; + GtkWidget *file_quit = NULL; + GtkWidget *edit_preferences = NULL; + GtkWidget *help_about = NULL; + + app = g_object_new (GTK_TYPE_OSX_APPLICATION, NULL); + menubar = gtk_ui_manager_get_widget (ui, "/MenuBar"); + file_quit = gtk_ui_manager_get_widget (ui, "/MenuBar/File/FileQuit"); + edit_preferences = gtk_ui_manager_get_widget (ui, "/MenuBar/Edit/EditPreferences"); + help_about = gtk_ui_manager_get_widget (ui, "/MenuBar/Help/HelpAbout"); + + gtk_widget_hide (menubar); + gtk_widget_hide (file_quit); + gtk_osxapplication_set_menu_bar (app, GTK_MENU_SHELL (menubar)); + + group = gtk_osxapplication_add_app_menu_group (app); + gtk_osxapplication_add_app_menu_item (app, group, GTK_MENU_ITEM (help_about)); + group = gtk_osxapplication_add_app_menu_group (app); + gtk_osxapplication_add_app_menu_item (app, group, GTK_MENU_ITEM (edit_preferences)); + + gtk_accel_map_foreach (NULL, accel_map_foreach); + + gtk_osxapplication_ready (app); #endif } gchar *spek_platform_locale_dir () { - static gchar *locale_dir = NULL; + static gchar *locale_dir = NULL; - if (!locale_dir) { + if (!locale_dir) { #ifdef G_OS_WIN32 - gchar *win32_dir; + gchar *win32_dir; - win32_dir = g_win32_get_package_installation_directory_of_module (NULL); - locale_dir = g_build_filename (win32_dir, "share", "locale", NULL); + win32_dir = g_win32_get_package_installation_directory_of_module (NULL); + locale_dir = g_build_filename (win32_dir, "share", "locale", NULL); - g_free (win32_dir); + g_free (win32_dir); #else #ifdef G_OS_DARWIN - GtkOSXApplication *app = NULL; - const gchar *res_dir; + GtkOSXApplication *app = NULL; + const gchar *res_dir; - app = g_object_new (GTK_TYPE_OSX_APPLICATION, NULL); - res_dir = gtk_osxapplication_get_resource_path (app); - locale_dir = g_build_filename (res_dir, "share", "locale", NULL); + app = g_object_new (GTK_TYPE_OSX_APPLICATION, NULL); + res_dir = gtk_osxapplication_get_resource_path (app); + locale_dir = g_build_filename (res_dir, "share", "locale", NULL); #else - locale_dir = LOCALEDIR; + locale_dir = LOCALEDIR; #endif #endif - } + } - return locale_dir; + return locale_dir; } void spek_platform_show_uri (const gchar *uri) { #ifdef G_OS_WIN32 - /* gtk_show_uri doesn't work on Windows... */ - ShellExecuteA (NULL, "open", uri, "", NULL, SW_SHOWNORMAL); + /* gtk_show_uri doesn't work on Windows... */ + ShellExecuteA (NULL, "open", uri, "", NULL, SW_SHOWNORMAL); #else #ifdef G_OS_DARWIN - /* ...or on OS X */ - CFStringRef str = NULL; - CFURLRef url = NULL; - - str = CFStringCreateWithCString (NULL, uri, kCFStringEncodingASCII); - url = CFURLCreateWithString (NULL, str, NULL); - LSOpenCFURLRef (url, NULL); - CFRelease (url); - CFRelease (str); + /* ...or on OS X */ + CFStringRef str = NULL; + CFURLRef url = NULL; + + str = CFStringCreateWithCString (NULL, uri, kCFStringEncodingASCII); + url = CFURLCreateWithString (NULL, str, NULL); + LSOpenCFURLRef (url, NULL); + CFRelease (url); + CFRelease (str); #else - gtk_show_uri (NULL, uri, gtk_get_current_event_time (), NULL); + gtk_show_uri (NULL, uri, gtk_get_current_event_time (), NULL); #endif #endif } gchar *spek_platform_read_line (const gchar *uri) { #ifdef G_OS_DARWIN - /* GIO doesn't work on OS X */ - CFStringRef str = NULL; - CFURLRef url = NULL; - CFDataRef data = NULL; - CFIndex length = 0; - gchar *buf = NULL; - - str = CFStringCreateWithCString (NULL, uri, kCFStringEncodingASCII); - url = CFURLCreateWithString (NULL, str, NULL); - if (CFURLCreateDataAndPropertiesFromResource (NULL, url, &data, NULL, NULL, NULL)) { - length = CFDataGetLength (data); - buf = (gchar *) g_malloc (length + 1); - CFDataGetBytes (data, CFRangeMake (0, length), (UInt8 *) buf); - buf[length] = '\0'; - g_strchomp (buf); - CFRelease (data); - } - CFRelease (url); - CFRelease (str); - return buf; + /* GIO doesn't work on OS X */ + CFStringRef str = NULL; + CFURLRef url = NULL; + CFDataRef data = NULL; + CFIndex length = 0; + gchar *buf = NULL; + + str = CFStringCreateWithCString (NULL, uri, kCFStringEncodingASCII); + url = CFURLCreateWithString (NULL, str, NULL); + if (CFURLCreateDataAndPropertiesFromResource (NULL, url, &data, NULL, NULL, NULL)) { + length = CFDataGetLength (data); + buf = (gchar *) g_malloc (length + 1); + CFDataGetBytes (data, CFRangeMake (0, length), (UInt8 *) buf); + buf[length] = '\0'; + g_strchomp (buf); + CFRelease (data); + } + CFRelease (url); + CFRelease (str); + return buf; #else - gchar *line = NULL; - GFile *file = NULL; - GFileInputStream *file_stream = NULL; - - file = g_file_new_for_uri (uri); - file_stream = g_file_read (file, NULL, NULL); - if (file_stream) { - GDataInputStream *data_stream = NULL; - - data_stream = g_data_input_stream_new (G_INPUT_STREAM (file_stream)); - line = g_data_input_stream_read_line (data_stream, NULL, NULL, NULL); - - g_object_unref (data_stream); - g_object_unref (file_stream); - } - g_object_unref (file); - return line; + gchar *line = NULL; + GFile *file = NULL; + GFileInputStream *file_stream = NULL; + + file = g_file_new_for_uri (uri); + file_stream = g_file_read (file, NULL, NULL); + if (file_stream) { + GDataInputStream *data_stream = NULL; + + data_stream = g_data_input_stream_new (G_INPUT_STREAM (file_stream)); + line = g_data_input_stream_read_line (data_stream, NULL, NULL, NULL); + + g_object_unref (data_stream); + g_object_unref (file_stream); + } + g_object_unref (file); + return line; #endif } gdouble spek_platform_get_font_scale () { #ifdef G_OS_DARWIN - /* Pango/Quartz fonts are smaller than on X. */ - return 1.4; + /* Pango/Quartz fonts are smaller than on X. */ + return 1.4; #endif - return 1.0; + return 1.0; } diff --git a/src/spek-preferences-dialog.vala b/src/spek-preferences-dialog.vala @@ -19,86 +19,86 @@ using Gtk; namespace Spek { - public class PreferencesDialog : Gtk.Dialog { - // List all languages with a decent (e.g. 80%) number of translated - // strings. Don't translate language names. Keep the first line intact. - private static string[,] languages = { - {"", null}, + public class PreferencesDialog : Gtk.Dialog { + // List all languages with a decent (e.g. 80%) number of translated + // strings. Don't translate language names. Keep the first line intact. + private static string[,] languages = { + {"", null}, {"cs", "Čeština"}, - {"de", "Deutsch"}, - {"en", "English"}, - {"es", "Español"}, - {"fr", "Français"}, - {"it", "Italiano"}, - {"nl", "Nederlands"}, - {"pl", "Polski"}, - {"ru", "Русский"}, - {"sv", "Svenska"}, - {"uk", "Українська"} - }; + {"de", "Deutsch"}, + {"en", "English"}, + {"es", "Español"}, + {"fr", "Français"}, + {"it", "Italiano"}, + {"nl", "Nederlands"}, + {"pl", "Polski"}, + {"ru", "Русский"}, + {"sv", "Svenska"}, + {"uk", "Українська"} + }; - public PreferencesDialog () { - title = _("Preferences"); - modal = true; - resizable = false; - window_position = WindowPosition.CENTER_ON_PARENT; - languages[0,1] = _("(system default)"); + public PreferencesDialog () { + title = _("Preferences"); + modal = true; + resizable = false; + window_position = WindowPosition.CENTER_ON_PARENT; + languages[0,1] = _("(system default)"); - var alignment = new Alignment (0.5f, 0.5f, 1f, 1f); - alignment.set_padding (12, 12, 12, 12); - var box = new VBox (false, 0); + var alignment = new Alignment (0.5f, 0.5f, 1f, 1f); + alignment.set_padding (12, 12, 12, 12); + var box = new VBox (false, 0); - var general_box = new VBox (false, 6); - // TRANSLATORS: The name of a section in the Preferences dialog. - var general_label = new Label (_("General")); - var attributes = new Pango.AttrList (); - attributes.insert (Pango.attr_weight_new (Pango.Weight.BOLD)); - general_label.attributes = attributes; - general_label.xalign = 0; - general_box.pack_start (general_label, false, false, 0); - var general_alignment = new Alignment (0.5f, 0.5f, 1f, 1f); - general_alignment.left_padding = 12; - var general_subbox = new VBox (false, 6); - var language_box = new HBox (false, 12); - var language_label = new Label.with_mnemonic (_("_Language:")); - language_box.pack_start (language_label, false, false, 0); - var language_combo = new ComboBox.text (); - int active_language = 0; - var prefs = Preferences.instance; - for (int i = 0; i < languages.length[0]; i++) { - language_combo.append_text (languages[i,1]); - if (languages[i,0] == prefs.language) { - active_language = i; - } - } - language_combo.active = active_language; - language_combo.changed.connect ( - () => prefs.language = languages[language_combo.active,0]); - language_label.mnemonic_widget = language_combo; - language_box.pack_start (language_combo, false, false, 0); - general_subbox.pack_start(language_box, false, false, 0); - var check_update = new CheckButton.with_mnemonic (_("Check for _updates")); - check_update.active = prefs.check_update; - check_update.toggled.connect ( - () => prefs.check_update = check_update.active); - general_subbox.pack_start (check_update, false, false, 0); - general_alignment.add (general_subbox); - general_box.pack_start (general_alignment, false, false, 0); + var general_box = new VBox (false, 6); + // TRANSLATORS: The name of a section in the Preferences dialog. + var general_label = new Label (_("General")); + var attributes = new Pango.AttrList (); + attributes.insert (Pango.attr_weight_new (Pango.Weight.BOLD)); + general_label.attributes = attributes; + general_label.xalign = 0; + general_box.pack_start (general_label, false, false, 0); + var general_alignment = new Alignment (0.5f, 0.5f, 1f, 1f); + general_alignment.left_padding = 12; + var general_subbox = new VBox (false, 6); + var language_box = new HBox (false, 12); + var language_label = new Label.with_mnemonic (_("_Language:")); + language_box.pack_start (language_label, false, false, 0); + var language_combo = new ComboBox.text (); + int active_language = 0; + var prefs = Preferences.instance; + for (int i = 0; i < languages.length[0]; i++) { + language_combo.append_text (languages[i,1]); + if (languages[i,0] == prefs.language) { + active_language = i; + } + } + language_combo.active = active_language; + language_combo.changed.connect ( + () => prefs.language = languages[language_combo.active,0]); + language_label.mnemonic_widget = language_combo; + language_box.pack_start (language_combo, false, false, 0); + general_subbox.pack_start(language_box, false, false, 0); + var check_update = new CheckButton.with_mnemonic (_("Check for _updates")); + check_update.active = prefs.check_update; + check_update.toggled.connect ( + () => prefs.check_update = check_update.active); + general_subbox.pack_start (check_update, false, false, 0); + general_alignment.add (general_subbox); + general_box.pack_start (general_alignment, false, false, 0); - box.pack_start (general_box, false, false, 0); - alignment.add (box); - var vbox = (VBox) get_content_area (); - vbox.pack_start (alignment, false, false, 0); - vbox.show_all (); + box.pack_start (general_box, false, false, 0); + alignment.add (box); + var vbox = (VBox) get_content_area (); + vbox.pack_start (alignment, false, false, 0); + vbox.show_all (); - add_button (Stock.CLOSE, ResponseType.CLOSE); - set_default_response (ResponseType.CLOSE); - response.connect (on_response); - } + add_button (Stock.CLOSE, ResponseType.CLOSE); + set_default_response (ResponseType.CLOSE); + response.connect (on_response); + } - private void on_response (Dialog dialog, int response_id) { - Preferences.instance.save (); - destroy (); - } - } + private void on_response (Dialog dialog, int response_id) { + Preferences.instance.save (); + destroy (); + } + } } \ No newline at end of file diff --git a/src/spek-preferences.vala b/src/spek-preferences.vala @@ -17,80 +17,80 @@ */ namespace Spek { - public class Preferences { - private KeyFile key_file; - private string file_name; + public class Preferences { + private KeyFile key_file; + private string file_name; - private Preferences () { - file_name = Path.build_filename (Environment.get_user_config_dir (), "spek"); - DirUtils.create_with_parents (file_name, 0755); - file_name = Path.build_filename (file_name, "preferences"); - this.key_file = new KeyFile (); - try { - key_file.load_from_file (file_name, KeyFileFlags.NONE); - } catch (KeyFileError e) { - } catch (FileError e) { - } - } + private Preferences () { + file_name = Path.build_filename (Environment.get_user_config_dir (), "spek"); + DirUtils.create_with_parents (file_name, 0755); + file_name = Path.build_filename (file_name, "preferences"); + this.key_file = new KeyFile (); + try { + key_file.load_from_file (file_name, KeyFileFlags.NONE); + } catch (KeyFileError e) { + } catch (FileError e) { + } + } - ~Preferences () { - save (); - } + ~Preferences () { + save (); + } - private static Preferences _instance; - public static Preferences instance { - get { - if (_instance == null) { - _instance = new Preferences (); - } - return _instance; - } - } + private static Preferences _instance; + public static Preferences instance { + get { + if (_instance == null) { + _instance = new Preferences (); + } + return _instance; + } + } - public void save () { - var output = FileStream.open (file_name, "w+"); - if (output != null) { - output.puts (key_file.to_data ()); - } - } + public void save () { + var output = FileStream.open (file_name, "w+"); + if (output != null) { + output.puts (key_file.to_data ()); + } + } - public bool check_update { - get { - try { - return key_file.get_boolean ("update", "check"); - } catch (KeyFileError e) { - } - return true; - } - set { - key_file.set_boolean ("update", "check", value); - } - } + public bool check_update { + get { + try { + return key_file.get_boolean ("update", "check"); + } catch (KeyFileError e) { + } + return true; + } + set { + key_file.set_boolean ("update", "check", value); + } + } - public int last_update { - get { - try { - return key_file.get_integer ("update", "last"); - } catch (KeyFileError e) { - } - return 0; - } - set { - key_file.set_integer ("update", "last", value); - } - } + public int last_update { + get { + try { + return key_file.get_integer ("update", "last"); + } catch (KeyFileError e) { + } + return 0; + } + set { + key_file.set_integer ("update", "last", value); + } + } - public string language { - owned get { - try { - return key_file.get_string ("general", "language"); - } catch (KeyFileError e) { - } - return ""; - } - set { - key_file.set_string ("general", "language", value); - } - } - } + public string language { + owned get { + try { + return key_file.get_string ("general", "language"); + } catch (KeyFileError e) { + } + return ""; + } + set { + key_file.set_string ("general", "language", value); + } + } + } } \ No newline at end of file diff --git a/src/spek-ruler.vala b/src/spek-ruler.vala @@ -20,104 +20,104 @@ using Cairo; using Pango; namespace Spek { - class Ruler : GLib.Object { - public enum Position { - TOP, - RIGHT, - BOTTOM, - LEFT - } + class Ruler : GLib.Object { + public enum Position { + TOP, + RIGHT, + BOTTOM, + LEFT + } - private Position pos; - private string sample_label; - private int[] factors; - private int units; - private double spacing; - private Measure measure; - private Place place; - private FormatTick format_tick; + private Position pos; + private string sample_label; + private int[] factors; + private int units; + private double spacing; + private Measure measure; + private Place place; + private FormatTick format_tick; - public delegate double Measure (int unit); - public delegate double Place (double p); - public delegate string FormatTick (int unit); + public delegate double Measure (int unit); + public delegate double Place (double p); + public delegate string FormatTick (int unit); - public Ruler ( - Position pos, string sample_label, - int[] factors, int units, double spacing, - Measure measure, Place place, FormatTick format_tick) { - this.pos = pos; - this.sample_label = sample_label; - this.factors = factors; - this.units = units; - this.spacing = spacing; - this.measure = measure; - this.place = place; - this.format_tick = format_tick; - } + public Ruler ( + Position pos, string sample_label, + int[] factors, int units, double spacing, + Measure measure, Place place, FormatTick format_tick) { + this.pos = pos; + this.sample_label = sample_label; + this.factors = factors; + this.units = units; + this.spacing = spacing; + this.measure = measure; + this.place = place; + this.format_tick = format_tick; + } - public void draw (Cairo.Context cr, Pango.Layout layout) { - // Mesure the sample label. - int w, h; - layout.set_text (sample_label, -1); - layout.get_pixel_size (out w, out h); - var size = pos == Position.TOP || pos == Position.BOTTOM ? w : h; + public void draw (Cairo.Context cr, Pango.Layout layout) { + // Mesure the sample label. + int w, h; + layout.set_text (sample_label, -1); + layout.get_pixel_size (out w, out h); + var size = pos == Position.TOP || pos == Position.BOTTOM ? w : h; - // Select the factor to use, we want some space between the labels. - int factor = 0; - foreach (var f in factors) { - if (measure (f) >= spacing * size) { - factor = f; - break; - } - } + // Select the factor to use, we want some space between the labels. + int factor = 0; + foreach (var f in factors) { + if (measure (f) >= spacing * size) { + factor = f; + break; + } + } - // Add the ticks. - int[] ticks = { 0, units }; - if (factor > 0) { - for (var tick = factor; tick < units; tick += factor) { - if (measure (units - tick) < size * 1.2) { - break; - } - ticks += tick; - } - // TODO: `ticks = ticks[0:-1]` crashes, file a bug. - } + // Add the ticks. + int[] ticks = { 0, units }; + if (factor > 0) { + for (var tick = factor; tick < units; tick += factor) { + if (measure (units - tick) < size * 1.2) { + break; + } + ticks += tick; + } + // TODO: `ticks = ticks[0:-1]` crashes, file a bug. + } - // Draw the ticks. - double GAP = 10; - double TICK_LEN = 4; - foreach (var tick in ticks) { - var label = format_tick (tick); - var p = place (measure ( - pos == Position.TOP || pos == Position.BOTTOM - ? tick : units - tick)); - layout.set_text (label, -1); - layout.get_pixel_size (out w, out h); - if (pos == Position.TOP) { - cr.move_to (p - w / 2, -GAP - h); - } else if (pos == Position.RIGHT){ - cr.move_to (GAP, p + h / 4); - } else if (pos == Position.BOTTOM) { - cr.move_to (p - w / 2, GAP + h); - } else if (pos == Position.LEFT){ - cr.move_to (-w - GAP, p + h / 4); - } - cairo_show_layout_line (cr, layout.get_line (0)); - if (pos == Position.TOP) { - cr.move_to (p, 0); - cr.rel_line_to (0, -TICK_LEN); - } else if (pos == Position.RIGHT) { - cr.move_to (0, p); - cr.rel_line_to (TICK_LEN, 0); - } else if (pos == Position.BOTTOM) { - cr.move_to (p, 0); - cr.rel_line_to (0, TICK_LEN); - } else if (pos == Position.LEFT) { - cr.move_to (0, p); - cr.rel_line_to (-TICK_LEN, 0); - } - cr.stroke (); - } - } - } + // Draw the ticks. + double GAP = 10; + double TICK_LEN = 4; + foreach (var tick in ticks) { + var label = format_tick (tick); + var p = place (measure ( + pos == Position.TOP || pos == Position.BOTTOM + ? tick : units - tick)); + layout.set_text (label, -1); + layout.get_pixel_size (out w, out h); + if (pos == Position.TOP) { + cr.move_to (p - w / 2, -GAP - h); + } else if (pos == Position.RIGHT){ + cr.move_to (GAP, p + h / 4); + } else if (pos == Position.BOTTOM) { + cr.move_to (p - w / 2, GAP + h); + } else if (pos == Position.LEFT){ + cr.move_to (-w - GAP, p + h / 4); + } + cairo_show_layout_line (cr, layout.get_line (0)); + if (pos == Position.TOP) { + cr.move_to (p, 0); + cr.rel_line_to (0, -TICK_LEN); + } else if (pos == Position.RIGHT) { + cr.move_to (0, p); + cr.rel_line_to (TICK_LEN, 0); + } else if (pos == Position.BOTTOM) { + cr.move_to (p, 0); + cr.rel_line_to (0, TICK_LEN); + } else if (pos == Position.LEFT) { + cr.move_to (0, p); + cr.rel_line_to (-TICK_LEN, 0); + } + cr.stroke (); + } + } + } } \ No newline at end of file diff --git a/src/spek-spectrogram.vala b/src/spek-spectrogram.vala @@ -22,298 +22,298 @@ using Gtk; using Pango; namespace Spek { - class Spectrogram : DrawingArea { - - public string file_name { get; private set; } - private Pipeline pipeline; - private string info; - private const int THRESHOLD = -92; - private const int NFFT = 2048; - private const int BANDS = NFFT / 2 + 1; - - private ImageSurface image; - private ImageSurface palette; - - private const int LPAD = 60; - private const int TPAD = 60; - private const int RPAD = 80; - private const int BPAD = 40; - private const int GAP = 10; - private const int RULER = 10; - private double FONT_SCALE = Platform.get_font_scale (); - - public Spectrogram () { - // Pre-draw the palette. - palette = new ImageSurface (Format.RGB24, RULER, BANDS); - for (int y = 0; y < BANDS; y++) { - var color = get_color (y / (double) BANDS); - for (int x = 0; x < RULER; x++) { - put_pixel (palette, x, y, color); - } - } - show_all (); - } - - public void open (string file_name) { - this.file_name = file_name; - this.info = ""; - - start (); - } - - public void save (string file_name) { - Allocation allocation; - get_allocation (out allocation); - var surface = new ImageSurface (Format.RGB24, allocation.width, allocation.height); - draw (new Cairo.Context (surface)); - surface.write_to_png (file_name); - } - - private void start () { - if (pipeline != null) { - pipeline.stop (); - } - - // The number of samples is the number of pixels available for the image. - // The number of bands is fixed, FFT results are very different for - // different values but we need some consistency. - Allocation allocation; - get_allocation (out allocation); - int samples = allocation.width - LPAD - RPAD; - if (samples > 0) { - image = new ImageSurface (Format.RGB24, samples, BANDS); - pipeline = new Pipeline (file_name, BANDS, samples, THRESHOLD, data_cb); - pipeline.start (); - info = pipeline.description; - } else { - image = null; - pipeline = null; - } - - queue_draw (); - } - - private int prev_width = -1; - protected override void size_allocate (Gdk.Rectangle allocation) { - base.size_allocate (allocation); - - bool width_changed = prev_width != allocation.width; - prev_width = allocation.width; - - if (file_name != null && width_changed) { - start (); - } - } - - private double log10_threshold = Math.log10 (-THRESHOLD); - private void data_cb (int sample, float[] values) { - for (int y = 0; y < BANDS; y++) { - var level = double.min ( - 1.0, Math.log10 (1.0 - THRESHOLD + values[y]) / log10_threshold); - put_pixel (image, sample, y, get_color (level)); - } - Idle.add (() => { queue_draw (); return false; }); - } - - protected override bool expose_event (EventExpose event) { - var window = get_window (); - var cr = cairo_create (window); - - // Clip to the exposed area. - cr.rectangle (event.area.x, event.area.y, event.area.width, event.area.height); - cr.clip (); - - draw (cr); - return true; - } - - private void draw (Cairo.Context cr) { - Allocation allocation; - get_allocation (out allocation); - double w = allocation.width; - double h = allocation.height; - int text_width, text_height; - - // Clean the background. - cr.set_source_rgb (0, 0, 0); - cr.paint (); - - // Spek version - cr.set_source_rgb (1, 1, 1); - var layout = cairo_create_layout (cr); - layout.set_font_description (FontDescription.from_string ( - "Sans " + (9 * FONT_SCALE).to_string ())); - layout.set_width (RPAD * Pango.SCALE); - layout.set_text ("dummy", -1); - layout.get_pixel_size (out text_width, out text_height); - int line_height = text_height; - layout.set_font_description (FontDescription.from_string ( - "Sans Bold " + (10 * FONT_SCALE).to_string ())); - layout.set_text (Config.PACKAGE_NAME + " ", -1); - layout.get_pixel_size (out text_width, out text_height); - cr.move_to (w - RPAD + GAP, TPAD - 2 * GAP - line_height); - cairo_show_layout_line (cr, layout.get_line (0)); - layout.set_font_description (FontDescription.from_string ( - "Sans " + (9 * FONT_SCALE).to_string ())); - layout.set_text (Config.PACKAGE_VERSION, -1); - cr.rel_move_to (text_width, 0); - cairo_show_layout_line (cr, layout.get_line (0)); - - if (image != null) { - // Draw the spectrogram. - cr.translate (LPAD, h - BPAD); - cr.scale (1, -(h - TPAD - BPAD) / image.get_height ()); - cr.set_source_surface (image, 0, 0); - cr.paint (); - cr.identity_matrix (); - - // Prepare to draw the rulers. - cr.set_source_rgb (1, 1, 1); - cr.set_line_width (1); - cr.set_antialias (Antialias.NONE); - layout.set_font_description (FontDescription.from_string ( - "Sans " + (8 * FONT_SCALE).to_string ())); - layout.set_width (-1); - - // Time ruler. - var duration_seconds = (int) pipeline.duration; - var time_ruler = new Ruler ( - Ruler.Position.BOTTOM, - // TODO: i18n - "00:00", - {1, 2, 5, 10, 20, 30, 1*60, 2*60, 5*60, 10*60, 20*60, 30*60}, - duration_seconds, - 1.5, - unit => (w - LPAD - RPAD) * unit / duration_seconds, - p => p, - // TODO: i18n - unit => "%d:%02d".printf (unit / 60, unit % 60)); - cr.translate (LPAD, h - BPAD); - time_ruler.draw (cr, layout); - cr.identity_matrix (); - - // Frequency ruler. - var freq = pipeline.sample_rate / 2; - var rate_ruler = new Ruler ( - Ruler.Position.LEFT, - // TRANSLATORS: keep "00" unchanged, it's used to calc the text width - _("00 kHz"), - {1000, 2000, 5000, 10000, 20000}, - freq, - 3.0, - unit => (h - TPAD - BPAD) * unit / freq, - p => p, - unit => _("%d kHz").printf (unit / 1000)); - cr.translate (LPAD, TPAD); - rate_ruler.draw (cr, layout); - cr.identity_matrix (); - - // File properties. - cr.move_to (LPAD, TPAD - GAP); - layout.set_font_description (FontDescription.from_string ( - "Sans " + (9 * FONT_SCALE).to_string ())); - layout.set_width ((int) (w - LPAD - RPAD) * Pango.SCALE); - layout.set_ellipsize (EllipsizeMode.END); - layout.set_text (info, -1); - cairo_show_layout_line (cr, layout.get_line (0)); - layout.get_pixel_size (out text_width, out text_height); - - // File name. - cr.move_to (LPAD, TPAD - 2 * GAP - text_height); - layout.set_font_description (FontDescription.from_string ( - "Sans Bold " + (10 * FONT_SCALE).to_string ())); - layout.set_width ((int) (w - LPAD - RPAD) * Pango.SCALE); - layout.set_ellipsize (EllipsizeMode.START); - layout.set_text (file_name, -1); - cairo_show_layout_line (cr, layout.get_line (0)); - } - - // Border around the spectrogram. - cr.set_source_rgb (1, 1, 1); - cr.set_line_width (1); - cr.set_antialias (Antialias.NONE); - cr.rectangle (LPAD, TPAD, w - LPAD - RPAD, h - TPAD - BPAD); - cr.stroke (); - - // The palette. - cr.translate (w - RPAD + GAP, h - BPAD); - cr.scale (1, -(h - TPAD - BPAD + 1) / palette.get_height ()); - cr.set_source_surface (palette, 0, 0); - cr.paint (); - cr.identity_matrix (); - - // Prepare to draw the ruler. - cr.set_source_rgb (1, 1, 1); - cr.set_line_width (1); - cr.set_antialias (Antialias.NONE); - layout.set_font_description (FontDescription.from_string ( - "Sans " + (8 * FONT_SCALE).to_string ())); - layout.set_width (-1); - - // Spectral density. - var density_ruler = new Ruler ( - Ruler.Position.RIGHT, - // TRANSLATORS: keep "-00" unchanged, it's used to calc the text width - _("-00 dB"), - {1, 2, 5, 10, 20, 50}, - -THRESHOLD, - 3.0, - unit => -(h - TPAD - BPAD) * unit / THRESHOLD, - p => h - TPAD - BPAD - p, - unit => _("%d dB").printf (-unit)); - cr.translate (w - RPAD + GAP + RULER, TPAD); - density_ruler.draw (cr, layout); - cr.identity_matrix (); - } - - private void put_pixel (ImageSurface surface, int x, int y, uint32 color) { - var i = y * surface.get_stride () + x * 4; - unowned uchar[] data = surface.get_data (); - - // Translate uchar* to uint32* to avoid dealing with endianness. - uint32 *p = (uint32 *) (&data[i]); - *p = color; - } - - // Modified version of Dan Bruton's algorithm: - // http://www.physics.sfasu.edu/astro/color/spectra.html - private uint32 get_color (double level) { - level *= 0.6625; - double r = 0.0, g = 0.0, b = 0.0; - if (level >= 0 && level < 0.15) { - r = (0.15 - level) / (0.15 + 0.075); - g = 0.0; - b = 1.0; - } else if (level >= 0.15 && level < 0.275) { - r = 0.0; - g = (level - 0.15) / (0.275 - 0.15); - b = 1.0; - } else if (level >= 0.275 && level < 0.325) { - r = 0.0; - g = 1.0; - b = (0.325 - level) / (0.325 - 0.275); - } else if (level >= 0.325 && level < 0.5) { - r = (level - 0.325) / (0.5 - 0.325); - g = 1.0; - b = 0.0; - } else if (level >= 0.5 && level < 0.6625) { - r = 1.0; - g = (0.6625 - level) / (0.6625 - 0.5f); - b = 0.0; - } - - // Intensity correction. - double cf = 1.0; - if (level >= 0.0 && level < 0.1) { - cf = level / 0.1; - } - cf *= 255.0; - - // Pack RGB values into Cairo-happy format. - uint32 rr = (uint32) (r * cf + 0.5); - uint32 gg = (uint32) (g * cf + 0.5); - uint32 bb = (uint32) (b * cf + 0.5); - return (rr << 16) + (gg << 8) + bb; - } - } + class Spectrogram : DrawingArea { + + public string file_name { get; private set; } + private Pipeline pipeline; + private string info; + private const int THRESHOLD = -92; + private const int NFFT = 2048; + private const int BANDS = NFFT / 2 + 1; + + private ImageSurface image; + private ImageSurface palette; + + private const int LPAD = 60; + private const int TPAD = 60; + private const int RPAD = 80; + private const int BPAD = 40; + private const int GAP = 10; + private const int RULER = 10; + private double FONT_SCALE = Platform.get_font_scale (); + + public Spectrogram () { + // Pre-draw the palette. + palette = new ImageSurface (Format.RGB24, RULER, BANDS); + for (int y = 0; y < BANDS; y++) { + var color = get_color (y / (double) BANDS); + for (int x = 0; x < RULER; x++) { + put_pixel (palette, x, y, color); + } + } + show_all (); + } + + public void open (string file_name) { + this.file_name = file_name; + this.info = ""; + + start (); + } + + public void save (string file_name) { + Allocation allocation; + get_allocation (out allocation); + var surface = new ImageSurface (Format.RGB24, allocation.width, allocation.height); + draw (new Cairo.Context (surface)); + surface.write_to_png (file_name); + } + + private void start () { + if (pipeline != null) { + pipeline.stop (); + } + + // The number of samples is the number of pixels available for the image. + // The number of bands is fixed, FFT results are very different for + // different values but we need some consistency. + Allocation allocation; + get_allocation (out allocation); + int samples = allocation.width - LPAD - RPAD; + if (samples > 0) { + image = new ImageSurface (Format.RGB24, samples, BANDS); + pipeline = new Pipeline (file_name, BANDS, samples, THRESHOLD, data_cb); + pipeline.start (); + info = pipeline.description; + } else { + image = null; + pipeline = null; + } + + queue_draw (); + } + + private int prev_width = -1; + protected override void size_allocate (Gdk.Rectangle allocation) { + base.size_allocate (allocation); + + bool width_changed = prev_width != allocation.width; + prev_width = allocation.width; + + if (file_name != null && width_changed) { + start (); + } + } + + private double log10_threshold = Math.log10 (-THRESHOLD); + private void data_cb (int sample, float[] values) { + for (int y = 0; y < BANDS; y++) { + var level = double.min ( + 1.0, Math.log10 (1.0 - THRESHOLD + values[y]) / log10_threshold); + put_pixel (image, sample, y, get_color (level)); + } + Idle.add (() => { queue_draw (); return false; }); + } + + protected override bool expose_event (EventExpose event) { + var window = get_window (); + var cr = cairo_create (window); + + // Clip to the exposed area. + cr.rectangle (event.area.x, event.area.y, event.area.width, event.area.height); + cr.clip (); + + draw (cr); + return true; + } + + private void draw (Cairo.Context cr) { + Allocation allocation; + get_allocation (out allocation); + double w = allocation.width; + double h = allocation.height; + int text_width, text_height; + + // Clean the background. + cr.set_source_rgb (0, 0, 0); + cr.paint (); + + // Spek version + cr.set_source_rgb (1, 1, 1); + var layout = cairo_create_layout (cr); + layout.set_font_description (FontDescription.from_string ( + "Sans " + (9 * FONT_SCALE).to_string ())); + layout.set_width (RPAD * Pango.SCALE); + layout.set_text ("dummy", -1); + layout.get_pixel_size (out text_width, out text_height); + int line_height = text_height; + layout.set_font_description (FontDescription.from_string ( + "Sans Bold " + (10 * FONT_SCALE).to_string ())); + layout.set_text (Config.PACKAGE_NAME + " ", -1); + layout.get_pixel_size (out text_width, out text_height); + cr.move_to (w - RPAD + GAP, TPAD - 2 * GAP - line_height); + cairo_show_layout_line (cr, layout.get_line (0)); + layout.set_font_description (FontDescription.from_string ( + "Sans " + (9 * FONT_SCALE).to_string ())); + layout.set_text (Config.PACKAGE_VERSION, -1); + cr.rel_move_to (text_width, 0); + cairo_show_layout_line (cr, layout.get_line (0)); + + if (image != null) { + // Draw the spectrogram. + cr.translate (LPAD, h - BPAD); + cr.scale (1, -(h - TPAD - BPAD) / image.get_height ()); + cr.set_source_surface (image, 0, 0); + cr.paint (); + cr.identity_matrix (); + + // Prepare to draw the rulers. + cr.set_source_rgb (1, 1, 1); + cr.set_line_width (1); + cr.set_antialias (Antialias.NONE); + layout.set_font_description (FontDescription.from_string ( + "Sans " + (8 * FONT_SCALE).to_string ())); + layout.set_width (-1); + + // Time ruler. + var duration_seconds = (int) pipeline.duration; + var time_ruler = new Ruler ( + Ruler.Position.BOTTOM, + // TODO: i18n + "00:00", + {1, 2, 5, 10, 20, 30, 1*60, 2*60, 5*60, 10*60, 20*60, 30*60}, + duration_seconds, + 1.5, + unit => (w - LPAD - RPAD) * unit / duration_seconds, + p => p, + // TODO: i18n + unit => "%d:%02d".printf (unit / 60, unit % 60)); + cr.translate (LPAD, h - BPAD); + time_ruler.draw (cr, layout); + cr.identity_matrix (); + + // Frequency ruler. + var freq = pipeline.sample_rate / 2; + var rate_ruler = new Ruler ( + Ruler.Position.LEFT, + // TRANSLATORS: keep "00" unchanged, it's used to calc the text width + _("00 kHz"), + {1000, 2000, 5000, 10000, 20000}, + freq, + 3.0, + unit => (h - TPAD - BPAD) * unit / freq, + p => p, + unit => _("%d kHz").printf (unit / 1000)); + cr.translate (LPAD, TPAD); + rate_ruler.draw (cr, layout); + cr.identity_matrix (); + + // File properties. + cr.move_to (LPAD, TPAD - GAP); + layout.set_font_description (FontDescription.from_string ( + "Sans " + (9 * FONT_SCALE).to_string ())); + layout.set_width ((int) (w - LPAD - RPAD) * Pango.SCALE); + layout.set_ellipsize (EllipsizeMode.END); + layout.set_text (info, -1); + cairo_show_layout_line (cr, layout.get_line (0)); + layout.get_pixel_size (out text_width, out text_height); + + // File name. + cr.move_to (LPAD, TPAD - 2 * GAP - text_height); + layout.set_font_description (FontDescription.from_string ( + "Sans Bold " + (10 * FONT_SCALE).to_string ())); + layout.set_width ((int) (w - LPAD - RPAD) * Pango.SCALE); + layout.set_ellipsize (EllipsizeMode.START); + layout.set_text (file_name, -1); + cairo_show_layout_line (cr, layout.get_line (0)); + } + + // Border around the spectrogram. + cr.set_source_rgb (1, 1, 1); + cr.set_line_width (1); + cr.set_antialias (Antialias.NONE); + cr.rectangle (LPAD, TPAD, w - LPAD - RPAD, h - TPAD - BPAD); + cr.stroke (); + + // The palette. + cr.translate (w - RPAD + GAP, h - BPAD); + cr.scale (1, -(h - TPAD - BPAD + 1) / palette.get_height ()); + cr.set_source_surface (palette, 0, 0); + cr.paint (); + cr.identity_matrix (); + + // Prepare to draw the ruler. + cr.set_source_rgb (1, 1, 1); + cr.set_line_width (1); + cr.set_antialias (Antialias.NONE); + layout.set_font_description (FontDescription.from_string ( + "Sans " + (8 * FONT_SCALE).to_string ())); + layout.set_width (-1); + + // Spectral density. + var density_ruler = new Ruler ( + Ruler.Position.RIGHT, + // TRANSLATORS: keep "-00" unchanged, it's used to calc the text width + _("-00 dB"), + {1, 2, 5, 10, 20, 50}, + -THRESHOLD, + 3.0, + unit => -(h - TPAD - BPAD) * unit / THRESHOLD, + p => h - TPAD - BPAD - p, + unit => _("%d dB").printf (-unit)); + cr.translate (w - RPAD + GAP + RULER, TPAD); + density_ruler.draw (cr, layout); + cr.identity_matrix (); + } + + private void put_pixel (ImageSurface surface, int x, int y, uint32 color) { + var i = y * surface.get_stride () + x * 4; + unowned uchar[] data = surface.get_data (); + + // Translate uchar* to uint32* to avoid dealing with endianness. + uint32 *p = (uint32 *) (&data[i]); + *p = color; + } + + // Modified version of Dan Bruton's algorithm: + // http://www.physics.sfasu.edu/astro/color/spectra.html + private uint32 get_color (double level) { + level *= 0.6625; + double r = 0.0, g = 0.0, b = 0.0; + if (level >= 0 && level < 0.15) { + r = (0.15 - level) / (0.15 + 0.075); + g = 0.0; + b = 1.0; + } else if (level >= 0.15 && level < 0.275) { + r = 0.0; + g = (level - 0.15) / (0.275 - 0.15); + b = 1.0; + } else if (level >= 0.275 && level < 0.325) { + r = 0.0; + g = 1.0; + b = (0.325 - level) / (0.325 - 0.275); + } else if (level >= 0.325 && level < 0.5) { + r = (level - 0.325) / (0.5 - 0.325); + g = 1.0; + b = 0.0; + } else if (level >= 0.5 && level < 0.6625) { + r = 1.0; + g = (0.6625 - level) / (0.6625 - 0.5f); + b = 0.0; + } + + // Intensity correction. + double cf = 1.0; + if (level >= 0.0 && level < 0.1) { + cf = level / 0.1; + } + cf *= 255.0; + + // Pack RGB values into Cairo-happy format. + uint32 rr = (uint32) (r * cf + 0.5); + uint32 gg = (uint32) (g * cf + 0.5); + uint32 bb = (uint32) (b * cf + 0.5); + return (rr << 16) + (gg << 8) + bb; + } + } } diff --git a/src/spek-window.vala b/src/spek-window.vala @@ -20,29 +20,29 @@ using Gdk; using Gtk; namespace Spek { - public class Window : Gtk.Window { - - private UIManager ui; - private MessageBar message_bar; - private Spectrogram spectrogram; - private string description; - private string cur_dir; - private FileFilter filter_all; - private FileFilter filter_audio; - private FileFilter filter_png; - - private const ActionEntry[] ACTION_ENTRIES = { - { "File", null, N_("_File") }, - { "FileOpen", Stock.OPEN, null, null, null, on_file_open }, - { "FileSave", Stock.SAVE, null, null, null, on_file_save }, - { "FileQuit", Stock.QUIT, null, null, null, on_file_quit }, - { "Edit", null, N_("_Edit") }, - { "EditPreferences", Stock.PREFERENCES, null, "<Ctrl>E", null, on_edit_preferences }, - { "Help", null, N_("_Help") }, - { "HelpAbout", Stock.ABOUT, null, "F1", null, on_help_about } - }; - - private const string UI = """ + public class Window : Gtk.Window { + + private UIManager ui; + private MessageBar message_bar; + private Spectrogram spectrogram; + private string description; + private string cur_dir; + private FileFilter filter_all; + private FileFilter filter_audio; + private FileFilter filter_png; + + private const ActionEntry[] ACTION_ENTRIES = { + { "File", null, N_("_File") }, + { "FileOpen", Stock.OPEN, null, null, null, on_file_open }, + { "FileSave", Stock.SAVE, null, null, null, on_file_save }, + { "FileQuit", Stock.QUIT, null, null, null, on_file_quit }, + { "Edit", null, N_("_Edit") }, + { "EditPreferences", Stock.PREFERENCES, null, "<Ctrl>E", null, on_edit_preferences }, + { "Help", null, N_("_Help") }, + { "HelpAbout", Stock.ABOUT, null, "F1", null, on_help_about } + }; + + private const string UI = """ <ui> <menubar name='MenuBar'> <menu action='File'> @@ -68,275 +68,275 @@ namespace Spek { </ui> """; - private const Gtk.TargetEntry[] DEST_TARGET_ENTRIES = { - { "text/uri-list", 0, 0 } - }; - - public Window (string? file_name) { - description = title = _("Spek - Acoustic Spectrum Analyser"); - set_default_icon_name ("spek"); - set_default_size (640, 480); - destroy.connect (Gtk.main_quit); - - var actions = new Gtk.ActionGroup ("Actions"); - actions.set_translation_domain (Config.GETTEXT_PACKAGE); - actions.add_actions (ACTION_ENTRIES, this); - ui = new UIManager (); - ui.insert_action_group (actions, 0); - add_accel_group (ui.get_accel_group ()); - try { - ui.add_ui_from_string (UI, -1); - } catch (Error e) { - warning ("Could not load the UI: %s\n", e.message); - } - - var menubar = ui.get_widget ("/MenuBar"); - var toolbar = (Toolbar) ui.get_widget ("/ToolBar"); - toolbar.set_style (ToolbarStyle.BOTH_HORIZ); - ((ToolItem) ui.get_widget ("/ToolBar/FileOpen")).is_important = true; - ((ToolItem) ui.get_widget ("/ToolBar/FileSave")).is_important = true; - ((ToolItem) ui.get_widget ("/ToolBar/HelpAbout")).is_important = true; - - message_bar = new MessageBar (_("A new version of Spek is available on <a href=\"http://www.spek-project.org\">www.spek-project.org</a>")); - - spectrogram = new Spectrogram (); - cur_dir = Environment.get_home_dir (); - - filter_all = new FileFilter (); - filter_all.set_name (_("All files")); - filter_all.add_pattern ("*"); - filter_png = new FileFilter (); - filter_png.set_name (_("PNG images")); - filter_png.add_pattern ("*.png"); - filter_audio = new FileFilter (); - filter_audio.set_name (_("Audio files")); - foreach (var ext in audio_extensions) { - filter_audio.add_pattern (ext); - } - - var vbox = new VBox (false, 0); - vbox.pack_start (menubar, false, true, 0); - vbox.pack_start (toolbar, false, true, 0); - vbox.pack_start (message_bar, false, true, 0); - vbox.pack_start (spectrogram, true, true, 0); - add (vbox); - menubar.show_all (); - toolbar.show_all (); - spectrogram.show_all (); - vbox.show (); - - Platform.fix_ui (ui); - show (); - - // Set up Drag and Drop - drag_dest_set (this, DestDefaults.ALL, DEST_TARGET_ENTRIES, DragAction.COPY); - drag_data_received.connect (on_dropped); - - if (file_name != null) { - open_file (file_name); - } - - try { - Thread.create<void*> (check_version, false); - } catch (ThreadError e) { - } - } - - void on_dropped (DragContext cx, int x, int y, SelectionData data, uint info, uint time) { - if (data.get_length () > 0 && data.get_format () == 8) { - string[] files = data.get_uris (); - if (files.length > 0) { - try { - open_file (Filename.from_uri (files[0])); - drag_finish (cx, true, false, time); - return; - } catch (ConvertError e) {} - } - } - drag_finish (cx, false, false, time); - } - - private void open_file (string file_name) { - cur_dir = Path.get_dirname (file_name); - spectrogram.open (file_name); - - // TRANSLATORS: window title, %s is replaced with the file name - title = _("Spek - %s").printf (Path.get_basename (file_name)); - } - - private void on_file_open () { - var chooser = new FileChooserDialog ( - _("Open File"), this, FileChooserAction.OPEN, - Stock.CANCEL, ResponseType.CANCEL, - Stock.OPEN, ResponseType.ACCEPT, null); - chooser.set_default_response (ResponseType.ACCEPT); - chooser.select_multiple = false; - chooser.set_current_folder (cur_dir); - chooser.add_filter (filter_all); - chooser.add_filter (filter_audio); - chooser.set_filter (filter_audio); - if (chooser.run () == ResponseType.ACCEPT) { - open_file (chooser.get_filename ()); - } - chooser.destroy (); - } - - private void on_file_save () { - var chooser = new FileChooserDialog ( - _("Save Spectrogram"), this, FileChooserAction.SAVE, - Stock.CANCEL, ResponseType.CANCEL, - Stock.SAVE, ResponseType.ACCEPT, null); - chooser.set_default_response (ResponseType.ACCEPT); - chooser.set_current_folder (cur_dir); - - // Suggested name is <file_name>.png - var file_name = Path.get_basename (spectrogram.file_name ?? _("Untitled")); - file_name += ".png"; - chooser.set_current_name (file_name); - chooser.add_filter (filter_png); - chooser.set_filter (filter_png); - if (chooser.run () == ResponseType.ACCEPT) { - file_name = chooser.get_filename (); - cur_dir = Path.get_dirname (file_name); - spectrogram.save (file_name); - } - chooser.destroy (); - } - - private void on_file_quit () { - destroy (); - } - - private void on_edit_preferences () { - var dlg = new PreferencesDialog (); - dlg.transient_for = this; - dlg.run (); - } - - private void on_help_about () { - string[] authors = { - "Primary Development:", - "\tAlexander Kojevnikov (maintainer)", - "", - "Contributors:", - "\tFabian Deutsch", - "\tJonathan Gonzalez V", - "\tStefan Kost", - "\tThibault North" - }; - string[] artists = { - "Olga Vasylevska" - }; - string license = "Copyright (C) 2010-2011 Alexander Kojevnikov"; - license += "\n\n"; - license += "Spek is free software: you can redistribute it and/or modify "; - license += "it under the terms of the GNU General Public License as published by "; - license += "the Free Software Foundation, either version 3 of the License, or "; - license += "(at your option) any later version."; - license += "\n\n"; - license += "Spek is distributed in the hope that it will be useful, "; - license += "but WITHOUT ANY WARRANTY; without even the implied warranty of "; - license += "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "; - license += "GNU General Public License for more details."; - license += "\n\n"; - license += "You should have received a copy of the GNU General Public License "; - license += "along with Spek. If not, see http://www.gnu.org/licenses/"; - - var dlg = new AboutDialog (); - dlg.program_name = "Spek"; - dlg.version = Config.PACKAGE_VERSION; - dlg.copyright = _("Copyright \xc2\xa9 2010-2011 Alexander Kojevnikov"); - dlg.comments = description; - dlg.set ("authors", authors); -// dlg.set ("documenters", documenters); - dlg.set ("artists", artists); - dlg.website_label = _("Spek Website"); - dlg.website = "http://www.spek-project.org/"; - dlg.license = license; - dlg.wrap_license = true; - try { - dlg.logo = IconTheme.get_default ().load_icon ("spek", 128, IconLookupFlags.FORCE_SVG); - } catch (Error e) { - dlg.logo_icon_name = "spek"; - } - dlg.translator_credits = _("translator-credits"); - dlg.set_transient_for (this); - dlg.destroy_with_parent = true; - dlg.response.connect (id => dlg.destroy ()); - dlg.set_url_hook (url_hook); - dlg.modal = true; - dlg.present (); - } - - private void url_hook (AboutDialog about, string link) { - Platform.show_uri (link); - } - - // TODO: s/audio/media/ - private string[] audio_extensions = { - "*.3gp", - "*.aac", - "*.aif", - "*.aifc", - "*.aiff", - "*.amr", - "*.awb", - "*.ape", - "*.au", - "*.dts", - "*.flac", - "*.gsm", - "*.m4a", - "*.m4p", - "*.mp3", - "*.mp4", - "*.mp+", - "*.mpc", - "*.mpp", - "*.oga", - "*.ogg", - "*.ra", - "*.ram", - "*.snd", - "*.wav", - "*.wma", - "*.wv" - }; - - private void * check_version () { - // Does the user want to check for updates? - var prefs = Preferences.instance; - var check = prefs.check_update; - if (!check) { - return null; - } - - // When was the last update? - var time_val = TimeVal (); - time_val.get_current_time (); - Date today = Date (); - today.set_time_val (time_val); - int day = prefs.last_update; - int diff = (int) today.get_julian () - day; - if (diff < 7) { - return null; - } - - // Get the version number. - var version = Platform.read_line ("http://www.spek-project.org/version"); - if (version == null) { - return null; - } - - if (version != null && version > Config.PACKAGE_VERSION) { - Idle.add (() => { message_bar.show_all (); return false; }); - } - - // Update the preferences. - prefs.check_update = check; - prefs.last_update = (int) today.get_julian (); - prefs.save (); - return null; - } - } + private const Gtk.TargetEntry[] DEST_TARGET_ENTRIES = { + { "text/uri-list", 0, 0 } + }; + + public Window (string? file_name) { + description = title = _("Spek - Acoustic Spectrum Analyser"); + set_default_icon_name ("spek"); + set_default_size (640, 480); + destroy.connect (Gtk.main_quit); + + var actions = new Gtk.ActionGroup ("Actions"); + actions.set_translation_domain (Config.GETTEXT_PACKAGE); + actions.add_actions (ACTION_ENTRIES, this); + ui = new UIManager (); + ui.insert_action_group (actions, 0); + add_accel_group (ui.get_accel_group ()); + try { + ui.add_ui_from_string (UI, -1); + } catch (Error e) { + warning ("Could not load the UI: %s\n", e.message); + } + + var menubar = ui.get_widget ("/MenuBar"); + var toolbar = (Toolbar) ui.get_widget ("/ToolBar"); + toolbar.set_style (ToolbarStyle.BOTH_HORIZ); + ((ToolItem) ui.get_widget ("/ToolBar/FileOpen")).is_important = true; + ((ToolItem) ui.get_widget ("/ToolBar/FileSave")).is_important = true; + ((ToolItem) ui.get_widget ("/ToolBar/HelpAbout")).is_important = true; + + message_bar = new MessageBar (_("A new version of Spek is available on <a href=\"http://www.spek-project.org\">www.spek-project.org</a>")); + + spectrogram = new Spectrogram (); + cur_dir = Environment.get_home_dir (); + + filter_all = new FileFilter (); + filter_all.set_name (_("All files")); + filter_all.add_pattern ("*"); + filter_png = new FileFilter (); + filter_png.set_name (_("PNG images")); + filter_png.add_pattern ("*.png"); + filter_audio = new FileFilter (); + filter_audio.set_name (_("Audio files")); + foreach (var ext in audio_extensions) { + filter_audio.add_pattern (ext); + } + + var vbox = new VBox (false, 0); + vbox.pack_start (menubar, false, true, 0); + vbox.pack_start (toolbar, false, true, 0); + vbox.pack_start (message_bar, false, true, 0); + vbox.pack_start (spectrogram, true, true, 0); + add (vbox); + menubar.show_all (); + toolbar.show_all (); + spectrogram.show_all (); + vbox.show (); + + Platform.fix_ui (ui); + show (); + + // Set up Drag and Drop + drag_dest_set (this, DestDefaults.ALL, DEST_TARGET_ENTRIES, DragAction.COPY); + drag_data_received.connect (on_dropped); + + if (file_name != null) { + open_file (file_name); + } + + try { + Thread.create<void*> (check_version, false); + } catch (ThreadError e) { + } + } + + void on_dropped (DragContext cx, int x, int y, SelectionData data, uint info, uint time) { + if (data.get_length () > 0 && data.get_format () == 8) { + string[] files = data.get_uris (); + if (files.length > 0) { + try { + open_file (Filename.from_uri (files[0])); + drag_finish (cx, true, false, time); + return; + } catch (ConvertError e) {} + } + } + drag_finish (cx, false, false, time); + } + + private void open_file (string file_name) { + cur_dir = Path.get_dirname (file_name); + spectrogram.open (file_name); + + // TRANSLATORS: window title, %s is replaced with the file name + title = _("Spek - %s").printf (Path.get_basename (file_name)); + } + + private void on_file_open () { + var chooser = new FileChooserDialog ( + _("Open File"), this, FileChooserAction.OPEN, + Stock.CANCEL, ResponseType.CANCEL, + Stock.OPEN, ResponseType.ACCEPT, null); + chooser.set_default_response (ResponseType.ACCEPT); + chooser.select_multiple = false; + chooser.set_current_folder (cur_dir); + chooser.add_filter (filter_all); + chooser.add_filter (filter_audio); + chooser.set_filter (filter_audio); + if (chooser.run () == ResponseType.ACCEPT) { + open_file (chooser.get_filename ()); + } + chooser.destroy (); + } + + private void on_file_save () { + var chooser = new FileChooserDialog ( + _("Save Spectrogram"), this, FileChooserAction.SAVE, + Stock.CANCEL, ResponseType.CANCEL, + Stock.SAVE, ResponseType.ACCEPT, null); + chooser.set_default_response (ResponseType.ACCEPT); + chooser.set_current_folder (cur_dir); + + // Suggested name is <file_name>.png + var file_name = Path.get_basename (spectrogram.file_name ?? _("Untitled")); + file_name += ".png"; + chooser.set_current_name (file_name); + chooser.add_filter (filter_png); + chooser.set_filter (filter_png); + if (chooser.run () == ResponseType.ACCEPT) { + file_name = chooser.get_filename (); + cur_dir = Path.get_dirname (file_name); + spectrogram.save (file_name); + } + chooser.destroy (); + } + + private void on_file_quit () { + destroy (); + } + + private void on_edit_preferences () { + var dlg = new PreferencesDialog (); + dlg.transient_for = this; + dlg.run (); + } + + private void on_help_about () { + string[] authors = { + "Primary Development:", + "\tAlexander Kojevnikov (maintainer)", + "", + "Contributors:", + "\tFabian Deutsch", + "\tJonathan Gonzalez V", + "\tStefan Kost", + "\tThibault North" + }; + string[] artists = { + "Olga Vasylevska" + }; + string license = "Copyright (C) 2010-2011 Alexander Kojevnikov"; + license += "\n\n"; + license += "Spek is free software: you can redistribute it and/or modify "; + license += "it under the terms of the GNU General Public License as published by "; + license += "the Free Software Foundation, either version 3 of the License, or "; + license += "(at your option) any later version."; + license += "\n\n"; + license += "Spek is distributed in the hope that it will be useful, "; + license += "but WITHOUT ANY WARRANTY; without even the implied warranty of "; + license += "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the "; + license += "GNU General Public License for more details."; + license += "\n\n"; + license += "You should have received a copy of the GNU General Public License "; + license += "along with Spek. If not, see http://www.gnu.org/licenses/"; + + var dlg = new AboutDialog (); + dlg.program_name = "Spek"; + dlg.version = Config.PACKAGE_VERSION; + dlg.copyright = _("Copyright \xc2\xa9 2010-2011 Alexander Kojevnikov"); + dlg.comments = description; + dlg.set ("authors", authors); +// dlg.set ("documenters", documenters); + dlg.set ("artists", artists); + dlg.website_label = _("Spek Website"); + dlg.website = "http://www.spek-project.org/"; + dlg.license = license; + dlg.wrap_license = true; + try { + dlg.logo = IconTheme.get_default ().load_icon ("spek", 128, IconLookupFlags.FORCE_SVG); + } catch (Error e) { + dlg.logo_icon_name = "spek"; + } + dlg.translator_credits = _("translator-credits"); + dlg.set_transient_for (this); + dlg.destroy_with_parent = true; + dlg.response.connect (id => dlg.destroy ()); + dlg.set_url_hook (url_hook); + dlg.modal = true; + dlg.present (); + } + + private void url_hook (AboutDialog about, string link) { + Platform.show_uri (link); + } + + // TODO: s/audio/media/ + private string[] audio_extensions = { + "*.3gp", + "*.aac", + "*.aif", + "*.aifc", + "*.aiff", + "*.amr", + "*.awb", + "*.ape", + "*.au", + "*.dts", + "*.flac", + "*.gsm", + "*.m4a", + "*.m4p", + "*.mp3", + "*.mp4", + "*.mp+", + "*.mpc", + "*.mpp", + "*.oga", + "*.ogg", + "*.ra", + "*.ram", + "*.snd", + "*.wav", + "*.wma", + "*.wv" + }; + + private void * check_version () { + // Does the user want to check for updates? + var prefs = Preferences.instance; + var check = prefs.check_update; + if (!check) { + return null; + } + + // When was the last update? + var time_val = TimeVal (); + time_val.get_current_time (); + Date today = Date (); + today.set_time_val (time_val); + int day = prefs.last_update; + int diff = (int) today.get_julian () - day; + if (diff < 7) { + return null; + } + + // Get the version number. + var version = Platform.read_line ("http://www.spek-project.org/version"); + if (version == null) { + return null; + } + + if (version != null && version > Config.PACKAGE_VERSION) { + Idle.add (() => { message_bar.show_all (); return false; }); + } + + // Update the preferences. + prefs.check_update = check; + prefs.last_update = (int) today.get_julian (); + prefs.save (); + return null; + } + } } diff --git a/src/spek.vala b/src/spek.vala @@ -17,59 +17,59 @@ */ namespace Spek { - bool version = false; - [CCode (array_length = false, array_null_terminated = true)] - string[] files = null; + bool version = false; + [CCode (array_length = false, array_null_terminated = true)] + string[] files = null; - const OptionEntry[] options = { - { "version", 'V', 0, OptionArg.NONE, ref version, N_("Display the version and exit"), null }, - { "", 0, 0, OptionArg.FILENAME_ARRAY, ref files, null, null }, - { null } - }; + const OptionEntry[] options = { + { "version", 'V', 0, OptionArg.NONE, ref version, N_("Display the version and exit"), null }, + { "", 0, 0, OptionArg.FILENAME_ARRAY, ref files, null, null }, + { null } + }; - int main (string[] args) { - Platform.fix_args (args); + int main (string[] args) { + Platform.fix_args (args); - if (Preferences.instance.language.length > 0) { - Environment.set_variable ("LANGUAGE", Preferences.instance.language, true); - } + if (Preferences.instance.language.length > 0) { + Environment.set_variable ("LANGUAGE", Preferences.instance.language, true); + } - Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Platform.locale_dir ()); - Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8"); - Intl.textdomain (Config.GETTEXT_PACKAGE); + Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Platform.locale_dir ()); + Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8"); + Intl.textdomain (Config.GETTEXT_PACKAGE); - try { - Gtk.init_with_args (ref args, _("[FILE]"), (OptionEntry[]) options, Config.GETTEXT_PACKAGE); - } catch (Error e) { - print (e.message); - print ("\n"); - print (_("Run `%s --help` to see a full list of available command line options.\n"), args[0]); - return 1; - } + try { + Gtk.init_with_args (ref args, _("[FILE]"), (OptionEntry[]) options, Config.GETTEXT_PACKAGE); + } catch (Error e) { + print (e.message); + print ("\n"); + print (_("Run `%s --help` to see a full list of available command line options.\n"), args[0]); + return 1; + } - if (version) { - // TRANSLATORS: first %s is the package name, second %s is the package version. - print (_("%s version %s\n"), Config.PACKAGE_NAME, Config.PACKAGE_VERSION); - return 0; - } + if (version) { + // TRANSLATORS: first %s is the package name, second %s is the package version. + print (_("%s version %s\n"), Config.PACKAGE_NAME, Config.PACKAGE_VERSION); + return 0; + } - if (files != null && files.length != 1) { - print (_("Specify a single file\n")); - return 1; - } + if (files != null && files.length != 1) { + print (_("Specify a single file\n")); + return 1; + } - Platform.init (); - Audio.init (); - var file_name = files == null ? null : files[0]; - if (file_name != null && file_name.has_prefix ("file://")) { - try { - file_name = Filename.from_uri (file_name); - } catch (ConvertError e) { - } - } - var window = new Window (file_name); - Gtk.main (); - window.destroy (); - return 0; - } + Platform.init (); + Audio.init (); + var file_name = files == null ? null : files[0]; + if (file_name != null && file_name.has_prefix ("file://")) { + try { + file_name = Filename.from_uri (file_name); + } catch (ConvertError e) { + } + } + var window = new Window (file_name); + Gtk.main (); + window.destroy (); + return 0; + } } \ No newline at end of file diff --git a/vapi/config.vapi b/vapi/config.vapi @@ -1,16 +1,16 @@ [CCode (prefix = "", lower_case_cprefix = "", cheader_filename = "config.h")] namespace Config { - /* Package information */ - public const string PACKAGE_NAME; - public const string PACKAGE_STRING; - public const string PACKAGE_VERSION; + /* Package information */ + public const string PACKAGE_NAME; + public const string PACKAGE_STRING; + public const string PACKAGE_VERSION; - /* Gettext package */ - public const string GETTEXT_PACKAGE; + /* Gettext package */ + public const string GETTEXT_PACKAGE; - /* Configured paths - these variables are not present in config.h, they are - * passed to underlying C code as cmd line macros. */ - public const string LOCALEDIR; /* /usr/local/share/locale */ - public const string PKGDATADIR; /* /usr/local/share/spek */ - public const string PKGLIBDIR; /* /usr/local/lib/spek */ + /* Configured paths - these variables are not present in config.h, they are + * passed to underlying C code as cmd line macros. */ + public const string LOCALEDIR; /* /usr/local/share/locale */ + public const string PKGDATADIR; /* /usr/local/share/spek */ + public const string PKGLIBDIR; /* /usr/local/lib/spek */ } diff --git a/vapi/spek-audio.vapi b/vapi/spek-audio.vapi @@ -1,29 +1,29 @@ [CCode (cprefix = "SpekAudio", lower_case_cprefix = "spek_audio_", cheader_filename = "spek-audio.h")] namespace Spek.Audio { - [Compact] - [CCode (free_function = "spek_audio_close")] - public class Context { - public string file_name; - public string codec_name; - public string error; - public int bit_rate; - public int sample_rate; - public int bits_per_sample; - public int width; - public bool fp; - public int channels; - public double duration; - public uint8 *buffer; - public int64 frames_per_interval; - public int64 error_per_interval; - public int64 error_base; + [Compact] + [CCode (free_function = "spek_audio_close")] + public class Context { + public string file_name; + public string codec_name; + public string error; + public int bit_rate; + public int sample_rate; + public int bits_per_sample; + public int width; + public bool fp; + public int channels; + public double duration; + public uint8 *buffer; + public int64 frames_per_interval; + public int64 error_per_interval; + public int64 error_base; - [CCode (cname = "spek_audio_open")] - public Context (string file_name); - [CCode (cname = "spek_audio_start")] - public int start (int samples); - [CCode (cname = "spek_audio_read")] - public int read (); - } - public static void init (); + [CCode (cname = "spek_audio_open")] + public Context (string file_name); + [CCode (cname = "spek_audio_start")] + public int start (int samples); + [CCode (cname = "spek_audio_read")] + public int read (); + } + public static void init (); } diff --git a/vapi/spek-fft.vapi b/vapi/spek-fft.vapi @@ -1,16 +1,16 @@ [CCode (cprefix = "SpekFft", lower_case_cprefix = "spek_fft_", cheader_filename = "spek-fft.h")] namespace Spek.Fft { - [Compact] - [CCode (free_function = "spek_fft_destroy")] - public class Plan { - [CCode (array_length = false)] - public unowned float[] input; - [CCode (array_length = false)] - public unowned float[] output; + [Compact] + [CCode (free_function = "spek_fft_destroy")] + public class Plan { + [CCode (array_length = false)] + public unowned float[] input; + [CCode (array_length = false)] + public unowned float[] output; - [CCode (cname = "spek_fft_plan_new")] - public Plan (int n, int threshold); - [CCode (cname = "spek_fft_execute")] - public void execute (); - } + [CCode (cname = "spek_fft_plan_new")] + public Plan (int n, int threshold); + [CCode (cname = "spek_fft_execute")] + public void execute (); + } } diff --git a/vapi/spek-platform.vapi b/vapi/spek-platform.vapi @@ -1,10 +1,10 @@ [CCode (cprefix = "SpekPlatform", lower_case_cprefix = "spek_platform_", cheader_filename = "spek-platform.h")] namespace Spek.Platform { - public static void init (); - public static void fix_args (string[] args); - public static void fix_ui (Gtk.UIManager ui); - public static unowned string locale_dir (); - public static void show_uri (string uri); - public static string read_line (string uri); - public static double get_font_scale (); + public static void init (); + public static void fix_args (string[] args); + public static void fix_ui (Gtk.UIManager ui); + public static unowned string locale_dir (); + public static void show_uri (string uri); + public static string read_line (string uri); + public static double get_font_scale (); }