spek

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

commit 14e2cea74cac8d969b1655c33dfbda535ff1a991
parent a5b3451b622cc0eeef4470b9c0cd5a170512956f
Author: Alexander Kojevnikov <alexander@kojevnikov.com>
Date:   Sun, 26 Aug 2012 19:55:20 -0700

Merge branch 'wx'

Diffstat:
M.gitignore | 5-----
MMakefile.am | 5+----
Mautogen.sh | 7++++---
Mconfigure.ac | 78++++++++++++++++++++++++++++++++++++++++++------------------------------------
Ddist/osx/README | 21---------------------
Adist/osx/README.md | 34++++++++++++++++++++++++++++++++++
Mpo/POTFILES.in | 13+++++--------
Mpo/POTFILES.skip | 6------
Mpo/spek.pot | 170++++++++++++++++++++++++++++++++-----------------------------------------------
Msrc/Makefile.am | 64++++++++++++++++++++++++++++++++++++----------------------------
Asrc/spek-audio-desc.cc | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/spek-audio-desc.hh | 28++++++++++++++++++++++++++++
Msrc/spek-audio.c | 216++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Msrc/spek-audio.h | 110+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Asrc/spek-events.cc | 45+++++++++++++++++++++++++++++++++++++++++++++
Asrc/spek-events.hh | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/spek-fft.c | 50++++++++++++++++++++++++++++----------------------
Msrc/spek-fft.h | 48++++++++++++++++++++++++++++--------------------
Asrc/spek-palette.c | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/spek-palette.h | 34++++++++++++++++++++++++++++++++++
Asrc/spek-pipeline.c | 368+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/spek-pipeline.h | 44++++++++++++++++++++++++++++++++++++++++++++
Dsrc/spek-pipeline.vala | 302------------------------------------------------------------------------------
Dsrc/spek-platform.c | 209-------------------------------------------------------------------------------
Asrc/spek-platform.cc | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/spek-platform.h | 45---------------------------------------------
Asrc/spek-platform.hh | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/spek-preferences-dialog.cc | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/spek-preferences-dialog.hh | 38++++++++++++++++++++++++++++++++++++++
Dsrc/spek-preferences-dialog.vala | 111-------------------------------------------------------------------------------
Asrc/spek-preferences.cc | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/spek-preferences.hh | 47+++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/spek-preferences.vala | 103-------------------------------------------------------------------------------
Asrc/spek-ruler.cc | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/spek-ruler.hh | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/spek-ruler.vala | 124-------------------------------------------------------------------------------
Asrc/spek-spectrogram.cc | 371+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/spek-spectrogram.hh | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/spek-spectrogram.vala | 319-------------------------------------------------------------------------------
Asrc/spek-window.cc | 385+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/spek-window.hh | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/spek-window.vala | 354-------------------------------------------------------------------------------
Asrc/spek.cc | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/spek.vala | 76----------------------------------------------------------------------------
Dvapi/Makefile.am | 8--------
Dvapi/config.vapi | 16----------------
Dvapi/spek-audio.vapi | 29-----------------------------
Dvapi/spek-fft.vapi | 16----------------
Dvapi/spek-platform.vapi | 10----------
49 files changed, 2715 insertions(+), 2122 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -32,15 +32,10 @@ po/.intltool-merge-cache po/POTFILES po/stamp-it samples/ -src/*.c src/*.o src/spek -!src/spek-audio.c -!src/spek-fft.c -!src/spek-platform.c src/*.stamp stamp-h1 -.svn test* web/version xmldocs.make diff --git a/Makefile.am b/Makefile.am @@ -1,11 +1,8 @@ -# Makefile.am - SUBDIRS = \ data \ man \ po \ - src \ - vapi + src EXTRA_DIST = \ intltool-extract.in \ diff --git a/autogen.sh b/autogen.sh @@ -5,6 +5,7 @@ test -n "$srcdir" || srcdir=$(dirname "$0") test -n "$srcdir" || srcdir=. ( cd "$srcdir" && - AUTOPOINT='intltoolize --automake --copy' autoreconf -fiv -Wall + touch config.rpath && + AUTOPOINT='intltoolize --automake --copy' autoreconf -fiv ) || exit -test -n "$NOCONFIGURE" || "$srcdir/configure" --enable-maintainer-mode "$@" -\ No newline at end of file +test -n "$NOCONFIGURE" || "$srcdir/configure" "$@" +\ No newline at end of file diff --git a/configure.ac b/configure.ac @@ -1,48 +1,56 @@ -# configure.ac - -AC_INIT([spek],[0.7]) -AC_CONFIG_SRCDIR([src/spek.vala]) +AC_INIT([spek],[0.8.0]) +AC_CONFIG_SRCDIR([src/spek.cc]) AC_CONFIG_HEADERS([config.h]) -AM_INIT_AUTOMAKE([foreign no-dist-gzip dist-xz]) - -# Enable silent rules is available -m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) - -AM_MAINTAINER_MODE +AM_INIT_AUTOMAKE([1.11.1 foreign no-dist-gzip dist-xz]) +AM_SILENT_RULES([yes]) -AC_PROG_CC -AC_PROG_CC_STDC -AM_PROG_VALAC([0.12.0]) +AC_PROG_CC_C99 +AC_PROG_CXX AC_PROG_INSTALL -IT_PROG_INTLTOOL([0.35]) +IT_PROG_INTLTOOL([0.40.0]) -pkg_modules="gtk+-2.0 >= 2.18.0 libavformat >= 52.111 libavcodec >= 52.123 libavutil" -PKG_CHECK_MODULES(SPEK, [$pkg_modules]) -AC_SUBST(SPEK_CFLAGS) -AC_SUBST(SPEK_LIBS) - -SPEK_PACKAGES="--pkg gtk+-2.0 --pkg gio-2.0" -AC_SUBST(SPEK_PACKAGES) +AC_CANONICAL_HOST +AC_MSG_CHECKING([the OS]) +AS_CASE([$host], + [*-*-mingw*], [ + os="WIN" + AC_DEFINE([OS_WIN], [1], [Windows]) + ], + [*-*-darwin*], [ + os="OSX" + AC_DEFINE([OS_OSX], [1], [OS X]) + ], + [*], [ + os="UNIX" + AC_DEFINE([OS_UNIX], [1], [Unix]) + ] +) +AC_MSG_RESULT([$os]) AC_CHECK_LIB(m, log10) -AC_CHECK_LIB(gthread-2.0, g_thread_init) -# Check for GDK Quartz and MacOSX integration package -_gdk_tgt=`$PKG_CONFIG --variable=target gdk-2.0` -AM_CONDITIONAL([GDK_TARGET_QUARTZ], [test x$_gdk_tgt = xquartz]) -if test "x$_gdk_tgt" = xquartz; then - PKG_CHECK_MODULES(IGE_MAC, ige-mac-integration) - IGE_MAC_LIBS="$IGE_MAC_LIBS -framework CoreFoundation -framework ApplicationServices" - AC_SUBST(IGE_MAC_LIBS) - AC_SUBST(IGE_MAC_CFLAGS) - AC_DEFINE(G_OS_DARWIN, 1, [Platform detection macro missing in GLib]) +PKG_CHECK_MODULES(FFMPEG, [libavformat >= 52.111 libavcodec >= 52.123 libavutil]) + +AM_OPTIONS_WXCONFIG +reqwx=2.8.0 +AM_PATH_WXCONFIG($reqwx, wx=1) +if test "$wx" != 1; then + AC_MSG_ERROR([ + wxWidgets must be installed on your system. + + Please check that wx-config is in path, the directory + where wxWidgets libraries are installed (returned by + 'wx-config --libs' or 'wx-config --static --libs' command) + is in LD_LIBRARY_PATH or equivalent variable and + wxWidgets version is $reqwx or above. + ]) fi GETTEXT_PACKAGE=spek AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE], ["$GETTEXT_PACKAGE"], [Gettext Package]) AC_SUBST(GETTEXT_PACKAGE) -AM_GNU_GETTEXT_VERSION([1.11]) -AM_GLIB_GNU_GETTEXT +AM_GNU_GETTEXT_VERSION([0.18.1]) +AM_GNU_GETTEXT([external]) AC_CONFIG_FILES([ Makefile @@ -61,7 +69,6 @@ AC_CONFIG_FILES([ man/spek.1 po/Makefile.in src/Makefile - vapi/Makefile web/version ]) -AC_OUTPUT -\ No newline at end of file +AC_OUTPUT diff --git a/dist/osx/README b/dist/osx/README @@ -1,21 +0,0 @@ -Building the OS X bundle -======================== - -Install and configure [jhbuild][1] and [ige-mac-bundler][2]. - -Get the release tarball and unpack it somewhere. Also copy the tarball to ~/gtk/source/pkgs/. - -Add this line to ~/.jhbuildrc-custom: - - moduleset=os.environ['HOME'] + 'path/to/spek/dist/osx/spek.modules' - -Run: - - $ jhbuild build spek - $ jhbuild shell - $ dist/osx/bundle.sh - -Note: On Lion, built in libiconv conflicts with the bundled version. To work around, accompany all jhbuild commands with `--skip=libiconv` so that everything uses the system version of the library (see issue 51). - -[1]: http://sourceforge.net/apps/trac/gtk-osx/wiki/Build -[2]: http://sourceforge.net/apps/trac/gtk-osx/wiki/Bundle diff --git a/dist/osx/README.md b/dist/osx/README.md @@ -0,0 +1,34 @@ +# Building the OS X bundle + +Using MacPorts install build dependencies: + + port install git-core autoconf automake intltool yasm. + +Download and build wxWidgets, example configure flags: + + ./configure --prefix=$HOME/usr --disable-shared --with-osx_cocoa \ + --with-jpeg=builtin --with-png=builtin --with-regex=builtin \ + --with-tiff=builtin --with-zlib=builtin --with-expat=builtin + make && make install + +Copy the wxWidgets m4 macro to the MacPorts tree: + + sudo cp $HOME/usr/share/aclocal/wxwin.m4 /opt/local/share/aclocal/ + +Download and build FFmpeg, example configure flags: + + ./configure --prefix=$HOME/usr --disable-shared --disable-debug --disable-doc \ + --enable-gpl --enable-version3 --disable-nonfree --disable-ffmpeg --disable-ffplay \ + --disable-ffprobe --disable-ffserver --disable-avdevice --disable-swscale \ + --disable-postproc --enable-pthreads --disable-encoders --disable-muxers \ + --disable-devices --disable-filters --disable-swresample + make && make install + +Clone and build Spek, example configure flags: + + PKG_CONFIG_PATH=$HOME/usr/lib/pkgconfig ./autogen.sh --with-wx-config=$HOME/usr/bin/wx-config + +Bundle Spek: + + ./dist/osx/bundle.sh + diff --git a/po/POTFILES.in b/po/POTFILES.in @@ -1,10 +1,7 @@ [encoding: UTF-8] data/spek.desktop.in.in -src/spek-audio.c -src/spek-pipeline.vala -src/spek-preferences-dialog.vala -src/spek-preferences.vala -src/spek-ruler.vala -src/spek-spectrogram.vala -src/spek-window.vala -src/spek.vala +src/spek-audio-desc.cc +src/spek-preferences-dialog.cc +src/spek-spectrogram.cc +src/spek-window.cc +src/spek.cc diff --git a/po/POTFILES.skip b/po/POTFILES.skip @@ -1,7 +1 @@ data/spek.desktop.in -src/spek.c -src/spek-pipeline.c -src/spek-preferences-dialog.c -src/spek-preferences.c -src/spek-spectrogram.c -src/spek-window.c diff --git a/po/spek.pot b/po/spek.pot @@ -8,15 +8,14 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-08-26 19:30-0700\n" +"POT-Creation-Date: 2012-08-26 19:50-0700\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: \n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" +"Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" #: ../data/spek.desktop.in.in.h:1 msgid "Spectrum Analyser" @@ -34,194 +33,161 @@ msgstr "" msgid "View spectrograms of your audio files" msgstr "" -#: ../src/spek-audio.c:47 +#: ../src/spek-audio-desc.cc:34 +#, c-format +msgid "%d kbps" +msgstr "" + +#: ../src/spek-audio-desc.cc:37 +#, c-format +msgid "%d Hz" +msgstr "" + +#: ../src/spek-audio-desc.cc:65 msgid "Cannot open input file" msgstr "" -#: ../src/spek-audio.c:54 +#: ../src/spek-audio-desc.cc:68 msgid "Cannot find stream info" msgstr "" -#: ../src/spek-audio.c:66 +#: ../src/spek-audio-desc.cc:71 msgid "The file contains no audio streams" msgstr "" -#: ../src/spek-audio.c:73 +#: ../src/spek-audio-desc.cc:74 msgid "Cannot find decoder" msgstr "" -#: ../src/spek-audio.c:91 +#: ../src/spek-audio-desc.cc:77 msgid "Unknown duration" msgstr "" -#: ../src/spek-audio.c:95 +#: ../src/spek-audio-desc.cc:80 msgid "No audio channels" msgstr "" -#: ../src/spek-audio.c:99 +#: ../src/spek-audio-desc.cc:83 msgid "Cannot open decoder" msgstr "" -#: ../src/spek-audio.c:120 +#: ../src/spek-audio-desc.cc:86 msgid "Unsupported sample format" msgstr "" -#: ../src/spek-pipeline.vala:70 -#, c-format -msgid "%d kbps" -msgstr "" - -#: ../src/spek-pipeline.vala:73 -#, c-format -msgid "%d Hz" -msgstr "" - -#: ../src/spek-pipeline.vala:78 -#, c-format -msgid "%d bit" -msgid_plural "%d bits" -msgstr[0] "" -msgstr[1] "" - -#: ../src/spek-pipeline.vala:81 -#, c-format -msgid "%d channel" -msgid_plural "%d channels" -msgstr[0] "" -msgstr[1] "" - #. TRANSLATORS: first %s is the error message, second %s is stream description. -#: ../src/spek-pipeline.vala:88 +#: ../src/spek-audio-desc.cc:91 #, c-format msgid "%s: %s" msgstr "" -#: ../src/spek-preferences-dialog.vala:47 +#: ../src/spek-preferences-dialog.cc:62 msgid "Preferences" msgstr "" -#: ../src/spek-preferences-dialog.vala:51 +#: ../src/spek-preferences-dialog.cc:67 msgid "(system default)" msgstr "" #. TRANSLATORS: The name of a section in the Preferences dialog. -#: ../src/spek-preferences-dialog.vala:59 +#: ../src/spek-preferences-dialog.cc:74 msgid "General" msgstr "" -#: ../src/spek-preferences-dialog.vala:69 -msgid "_Language:" -msgstr "" - -#: ../src/spek-preferences-dialog.vala:86 -msgid "Check for _updates" +#: ../src/spek-preferences-dialog.cc:82 +msgid "Language:" msgstr "" -#. TRANSLATORS: keep "00" unchanged, it's used to calc the text width -#: ../src/spek-spectrogram.vala:200 -msgid "00 kHz" +#: ../src/spek-preferences-dialog.cc:97 +msgid "Check for &updates" msgstr "" -#: ../src/spek-spectrogram.vala:206 +#: ../src/spek-spectrogram.cc:152 #, c-format msgid "%d kHz" msgstr "" -#. TRANSLATORS: keep "-00" unchanged, it's used to calc the text width -#: ../src/spek-spectrogram.vala:257 -msgid "-00 dB" -msgstr "" - -#: ../src/spek-spectrogram.vala:263 +#: ../src/spek-spectrogram.cc:157 #, c-format msgid "%d dB" msgstr "" -#: ../src/spek-window.vala:35 -msgid "_File" -msgstr "" - -#: ../src/spek-window.vala:39 -msgid "_Edit" +#. TRANSLATORS: keep "00" unchanged, it's used to calc the text width +#: ../src/spek-spectrogram.cc:259 +msgid "00 kHz" msgstr "" -#: ../src/spek-window.vala:41 -msgid "_Help" +#. TRANSLATORS: keep "-00" unchanged, it's used to calc the text width +#: ../src/spek-spectrogram.cc:287 +msgid "-00 dB" msgstr "" -#: ../src/spek-window.vala:76 +#: ../src/spek-window.cc:70 msgid "Spek - Acoustic Spectrum Analyser" msgstr "" -#: ../src/spek-window.vala:103 -msgid "" -"A new version of Spek is available on <a href=\"http://www.spek-project.org" -"\">www.spek-project.org</a>" +#: ../src/spek-window.cc:84 +msgid "&File" msgstr "" -#: ../src/spek-window.vala:117 -msgid "All files" +#: ../src/spek-window.cc:91 +msgid "&Edit" msgstr "" -#: ../src/spek-window.vala:120 -msgid "PNG images" +#: ../src/spek-window.cc:98 +msgid "&Help" msgstr "" -#: ../src/spek-window.vala:123 -msgid "Audio files" +#: ../src/spek-window.cc:125 +msgid "A new version of Spek is available, click to download." msgstr "" #. TRANSLATORS: window title, %s is replaced with the file name -#: ../src/spek-window.vala:175 +#: ../src/spek-window.cc:164 #, c-format msgid "Spek - %s" msgstr "" -#: ../src/spek-window.vala:180 -msgid "Open File" +#: ../src/spek-window.cc:211 +msgid "All files" msgstr "" -#: ../src/spek-window.vala:197 -msgid "Save Spectrogram" +#: ../src/spek-window.cc:213 +msgid "Audio files" msgstr "" -#. Suggested name is <file_name>.png -#: ../src/spek-window.vala:204 -msgid "Untitled" +#: ../src/spek-window.cc:227 +msgid "Open File" msgstr "" -#: ../src/spek-window.vala:259 -msgid "Copyright © 2010-2011 Alexander Kojevnikov" +#: ../src/spek-window.cc:249 +msgid "PNG images" msgstr "" -#: ../src/spek-window.vala:264 -msgid "Spek Website" +#: ../src/spek-window.cc:255 +msgid "Save Spectrogram" msgstr "" -#. TRANSLATORS: Add your name here -#: ../src/spek-window.vala:274 -msgid "translator-credits" +#. Suggested name is <file_name>.png +#: ../src/spek-window.cc:263 +msgid "Untitled" msgstr "" -#: ../src/spek.vala:25 -msgid "Display the version and exit" +#. TRANSLATORS: Add your name here +#: ../src/spek-window.cc:300 +msgid "translator-credits" msgstr "" -#: ../src/spek.vala:42 -msgid "[FILE]" +#: ../src/spek-window.cc:306 +msgid "Copyright (c) 2010-2012 Alexander Kojevnikov and contributors" msgstr "" -#: ../src/spek.vala:46 -#, c-format -msgid "Run `%s --help` to see a full list of available command line options.\n" +#: ../src/spek-window.cc:309 +msgid "Spek Website" msgstr "" #. TRANSLATORS: first %s is the package name, second %s is the package version. -#: ../src/spek.vala:52 +#: ../src/spek.cc:90 #, c-format -msgid "%s version %s\n" -msgstr "" - -#: ../src/spek.vala:57 -msgid "Specify a single file\n" +msgid "%s version %s" msgstr "" diff --git a/src/Makefile.am b/src/Makefile.am @@ -1,39 +1,47 @@ bin_PROGRAMS = spek spek_SOURCES = \ - spek.vala \ + spek-audio-desc.cc \ + spek-audio-desc.hh \ spek-audio.c \ + spek-audio.h \ + spek-events.cc \ + spek-events.hh \ spek-fft.c \ - spek-pipeline.vala \ - spek-platform.c \ - spek-preferences.vala \ - spek-preferences-dialog.vala \ - spek-ruler.vala \ - spek-spectrogram.vala \ - spek-window.vala + spek-fft.h \ + spek-palette.c \ + spek-palette.h \ + spek-pipeline.c \ + spek-pipeline.h \ + spek-platform.cc \ + spek-platform.hh \ + spek-preferences-dialog.cc \ + spek-preferences-dialog.hh \ + spek-preferences.cc \ + spek-preferences.hh \ + spek-ruler.cc \ + spek-ruler.hh \ + spek-spectrogram.cc \ + spek-spectrogram.hh \ + spek-window.cc \ + spek-window.hh \ + spek.cc -AM_CPPFLAGS = \ +spek_CPPFLAGS = \ -include config.h \ - $(SPEK_CFLAGS) \ - $(IGE_MAC_CFLAGS) \ - -DLOCALEDIR=\""$(localedir)"\" \ - -DPKGDATADIR=\""$(pkgdatadir)"\" \ - -DPKGLIBDIR=\""$(pkglibdir)"\" + -pthread \ + $(WX_CPPFLAGS) + +spek_CFLAGS = \ + $(FFMPEG_CFLAGS) \ + $(WX_CFLAGS_ONLY) -VALAFLAGS = \ - --thread \ - --vapidir=$(srcdir)/../vapi \ - --pkg config \ - --pkg spek-audio \ - --pkg spek-fft \ - --pkg spek-platform \ - @SPEK_PACKAGES@ +spek_CXXFLAGS = \ + $(WX_CXXFLAGS_ONLY) spek_LDADD = \ - $(SPEK_LIBS) \ - $(IGE_MAC_LIBS) + $(FFMPEG_LIBS) \ + $(WX_LIBS) -EXTRA_DIST = \ - spek-audio.h \ - spek-fft.h \ - spek-platform.h +spek_LDFLAGS = \ + -pthread diff --git a/src/spek-audio-desc.cc b/src/spek-audio-desc.cc @@ -0,0 +1,95 @@ +/* spek-audio-desc.cc + * + * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * + * Spek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Spek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Spek. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <wx/arrstr.h> +#include <wx/intl.h> + +#include "spek-audio.h" + +#include "spek-audio-desc.hh" + +wxString spek_audio_desc(const struct spek_audio_properties *properties) +{ + wxArrayString items; + + if (properties->codec_name) { + items.Add(wxString::FromUTF8(properties->codec_name)); + } + if (properties->bit_rate) { + items.Add(wxString::Format(_("%d kbps"), (properties->bit_rate + 500) / 1000)); + } + if (properties->sample_rate) { + items.Add(wxString::Format(_("%d Hz"), properties->sample_rate)); + } + // Include bits per sample only if there is no bitrate. + if (properties->bits_per_sample && !properties->bit_rate) { + items.Add(wxString::Format( + wxPLURAL("%d bit", "%d bits", properties->bits_per_sample), + properties->bits_per_sample + )); + } + if (properties->channels) { + items.Add(wxString::Format( + wxPLURAL("%d channel", "%d channels", properties->channels), + properties->channels + )); + } + + wxString desc; + for (int i = 0; i < items.GetCount(); ++i) { + if (i) { + desc += wxT(", "); + } + desc += items[i]; + } + + if (properties->error) { + wxString error; + switch (properties->error) { + case SPEK_AUDIO_CANNOT_OPEN_FILE: + error = _("Cannot open input file"); + break; + case SPEK_AUDIO_NO_STREAMS: + error = _("Cannot find stream info"); + break; + case SPEK_AUDIO_NO_AUDIO: + error = _("The file contains no audio streams"); + break; + case SPEK_AUDIO_NO_DECODER: + error = _("Cannot find decoder"); + break; + case SPEK_AUDIO_NO_DURATION: + error = _("Unknown duration"); + break; + case SPEK_AUDIO_NO_CHANNELS: + error = _("No audio channels"); + break; + case SPEK_AUDIO_CANNOT_OPEN_DECODER: + error = _("Cannot open decoder"); + break; + case SPEK_AUDIO_BAD_SAMPLE_FORMAT: + error = _("Unsupported sample format"); + break; + } + + // TRANSLATORS: first %s is the error message, second %s is stream description. + desc = wxString::Format(_("%s: %s"), error.c_str(), desc.c_str()); + } + + return desc; +} diff --git a/src/spek-audio-desc.hh b/src/spek-audio-desc.hh @@ -0,0 +1,28 @@ +/* spek-audio-desc.hh + * + * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * + * Spek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Spek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Spek. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SPEK_AUDIO_DESC_HH_ +#define SPEK_AUDIO_DESC_HH_ + +#include <wx/string.h> + +struct spek_audio_properties; + +wxString spek_audio_desc(const struct spek_audio_properties *properties); + +#endif diff --git a/src/spek-audio.c b/src/spek-audio.c @@ -1,6 +1,6 @@ /* spek-audio.c * - * Copyright (C) 2010 Alexander Kojevnikov <alexander@kojevnikov.com> + * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com> * * Spek is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,142 +16,165 @@ * along with Spek. If not, see <http://www.gnu.org/licenses/>. */ -#include <glib.h> -#include <glib/gi18n.h> +#include <string.h> + +#include <libavformat/avformat.h> +#include <libavcodec/avcodec.h> #include <libavutil/mathematics.h> +#include "spek-platform.hh" + #include "spek-audio.h" -void spek_audio_init () { - /* TODO: register only audio decoders */ - av_register_all (); +struct spek_audio_context +{ + char *file_name; // TODO: needed? + char *short_name; + AVFormatContext *format_context; + int audio_stream; + AVCodecContext *codec_context; + AVStream *stream; + AVCodec *codec; + int buffer_size; + AVPacket *packet; + int offset; + + struct spek_audio_properties properties; +}; + + +void spek_audio_init() +{ + // TODO: register only audio decoders. + av_register_all(); +} + +const struct spek_audio_properties * spek_audio_get_properties(struct spek_audio_context *cx) +{ + return &cx->properties; } -SpekAudioContext * spek_audio_open (const gchar *file_name) { - SpekAudioContext *cx; - int i; - - 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); -#endif - - if (avformat_open_input (&cx->format_context, file_name, NULL, NULL) != 0) { +struct spek_audio_context * spek_audio_open(const char *path) +{ + // TODO: malloc and initialise explicitely + struct spek_audio_context *cx = calloc(1, sizeof(struct spek_audio_context)); + cx->file_name = strdup(path); + // 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. + // TODO: test if it's already fixed in FFmpeg + cx->short_name = spek_platform_short_path(path); + + if (avformat_open_input(&cx->format_context, path, NULL, NULL) != 0) { if (!cx->short_name || - avformat_open_input (&cx->format_context, cx->short_name, NULL, NULL) != 0 ) { - cx->error = _("Cannot open input file"); + avformat_open_input(&cx->format_context, cx->short_name, NULL, NULL) != 0 ) { + cx->properties.error = SPEK_AUDIO_CANNOT_OPEN_FILE; return cx; } } - if (avformat_find_stream_info (cx->format_context, NULL) < 0) { - /* 24-bit APE returns an error but parses the stream info just fine */ + if (avformat_find_stream_info(cx->format_context, NULL) < 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"); + cx->properties.error = SPEK_AUDIO_NO_STREAMS; return cx; } } cx->audio_stream = -1; - for (i = 0; i < cx->format_context->nb_streams; i++) { + for (int 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"); + cx->properties.error = SPEK_AUDIO_NO_AUDIO; 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); + cx->codec = avcodec_find_decoder(cx->codec_context->codec_id); if (cx->codec == NULL) { - cx->error = _("Cannot find decoder"); + cx->properties.error = SPEK_AUDIO_NO_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; + // We can already fill in the stream info even if the codec won't be able to open it. + cx->properties.codec_name = strdup(cx->codec->long_name); + cx->properties.bit_rate = cx->codec_context->bit_rate; + cx->properties.sample_rate = cx->codec_context->sample_rate; + cx->properties.bits_per_sample = cx->codec_context->bits_per_raw_sample; + if (!cx->properties.bits_per_sample) { + // APE uses bpcs, FLAC uses bprs. + cx->properties.bits_per_sample = cx->codec_context->bits_per_coded_sample; } - cx->channels = cx->codec_context->channels; + cx->properties.channels = cx->codec_context->channels; if (cx->stream->duration != AV_NOPTS_VALUE) { - cx->duration = cx->stream->duration * av_q2d (cx->stream->time_base); + cx->properties.duration = cx->stream->duration * av_q2d(cx->stream->time_base); } else if (cx->format_context->duration != AV_NOPTS_VALUE) { - cx->duration = cx->format_context->duration / (double) AV_TIME_BASE; + cx->properties.duration = cx->format_context->duration / (double) AV_TIME_BASE; } else { - cx->error = _("Unknown duration"); + cx->properties.error = SPEK_AUDIO_NO_DURATION; return cx; } - if (cx->channels <= 0) { - cx->error = _("No audio channels"); + if (cx->properties.channels <= 0) { + cx->properties.error = SPEK_AUDIO_NO_CHANNELS; return cx; } - if (avcodec_open2 (cx->codec_context, cx->codec, NULL) < 0) { - cx->error = _("Cannot open decoder"); + if (avcodec_open2(cx->codec_context, cx->codec, NULL) < 0) { + cx->properties.error = SPEK_AUDIO_CANNOT_OPEN_DECODER; return cx; } switch (cx->codec_context->sample_fmt) { - case SAMPLE_FMT_S16: - cx->width = 16; - cx->fp = FALSE; + case AV_SAMPLE_FMT_S16: + cx->properties.width = 16; + cx->properties.fp = false; break; - case SAMPLE_FMT_S32: - cx->width = 32; - cx->fp = FALSE; + case AV_SAMPLE_FMT_S32: + cx->properties.width = 32; + cx->properties.fp = false; break; - case SAMPLE_FMT_FLT: - cx->width = 32; - cx->fp = TRUE; + case AV_SAMPLE_FMT_FLT: + cx->properties.width = 32; + cx->properties.fp = true; break; - case SAMPLE_FMT_DBL: - cx->width = 64; - cx->fp = TRUE; + case AV_SAMPLE_FMT_DBL: + cx->properties.width = 64; + cx->properties.fp = true; break; default: - cx->error = _("Unsupported sample format"); + cx->properties.error = SPEK_AUDIO_BAD_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->properties.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; - gint64 duration = (gint64) (cx->duration * cx->stream->time_base.den / cx->stream->time_base.num); - cx->error_base = samples * (gint64) cx->stream->time_base.den; - cx->frames_per_interval = av_rescale_rnd (duration, rate, cx->error_base, AV_ROUND_DOWN); - cx->error_per_interval = (duration * rate) % cx->error_base; +void spek_audio_start(struct spek_audio_context *cx, int samples) +{ + int64_t rate = cx->properties.sample_rate * (int64_t) cx->stream->time_base.num; + int64_t duration = (int64_t) + (cx->properties.duration * cx->stream->time_base.den / cx->stream->time_base.num); + cx->properties.error_base = samples * (int64_t)cx->stream->time_base.den; + cx->properties.frames_per_interval = av_rescale_rnd( + duration, rate, cx->properties.error_base, AV_ROUND_DOWN); + cx->properties.error_per_interval = (duration * rate) % cx->properties.error_base; } -gint spek_audio_read (SpekAudioContext *cx) { - gint buffer_size; - gint len; - gint res; - - if (cx->error) { +int spek_audio_read(struct spek_audio_context *cx) { + if (cx->properties.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); + int buffer_size = cx->buffer_size; + int len = avcodec_decode_audio3( + cx->codec_context, (int16_t *)cx->properties.buffer, &buffer_size, cx->packet); if (len < 0) { - /* Error, skip the frame. */ + // Error, skip the frame. cx->packet->size = 0; break; } @@ -159,10 +182,10 @@ gint spek_audio_read (SpekAudioContext *cx) { cx->packet->size -= len; cx->offset += len; if (buffer_size <= 0) { - /* No data yet, get more frames */ + // No data yet, get more frames. continue; } - /* We have data, return it and come back for more later */ + // We have data, return it and come back for more later. return buffer_size; } if (cx->packet->data) { @@ -171,46 +194,49 @@ gint spek_audio_read (SpekAudioContext *cx) { cx->offset = 0; av_free_packet (cx->packet); } - while ((res = av_read_frame (cx->format_context, cx->packet)) >= 0) { + + int res = 0; + 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); + av_free_packet(cx->packet); } if (res < 0) { - /* End of file or error. */ + // End of file or error. return 0; } } } -void spek_audio_close (SpekAudioContext *cx) { +void spek_audio_close (struct spek_audio_context *cx) +{ if (cx->file_name != NULL) { - g_free (cx->file_name); + free(cx->file_name); } if (cx->short_name != NULL) { - g_free (cx->short_name); + free(cx->short_name); } - if (cx->codec_name != NULL) { - g_free (cx->codec_name); + if (cx->properties.codec_name != NULL) { + free(cx->properties.codec_name); } - if (cx->buffer) { - av_free (cx->buffer); + if (cx->properties.buffer) { + av_free(cx->properties.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_packet(cx->packet); } - av_free (cx->packet); + av_free(cx->packet); } if (cx->codec_context != NULL) { - avcodec_close (cx->codec_context); + avcodec_close(cx->codec_context); } if (cx->format_context != NULL) { - av_close_input_file (cx->format_context); + av_close_input_file(cx->format_context); } - g_free (cx); + free(cx); } diff --git a/src/spek-audio.h b/src/spek-audio.h @@ -1,6 +1,6 @@ /* spek-audio.h * - * Copyright (C) 2010 Alexander Kojevnikov <alexander@kojevnikov.com> + * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com> * * Spek is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,62 +16,72 @@ * along with Spek. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef __SPEK_AUDIO_H__ -#define __SPEK_AUDIO_H__ +#ifndef SPEK_AUDIO_H_ +#define SPEK_AUDIO_H_ -#include <glib.h> -#include <libavformat/avformat.h> -#include <libavcodec/avcodec.h> +#ifdef __cplusplus +extern "C" { +#endif -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; +#include <stdbool.h> +#include <stdint.h> - /* 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; +struct spek_audio_context; -/* Initialise FFmpeg, should be called once on start up */ -void spek_audio_init (); +enum spek_audio_error +{ + SPEK_AUDIO_OK = 0, + SPEK_AUDIO_CANNOT_OPEN_FILE, + SPEK_AUDIO_NO_STREAMS, + SPEK_AUDIO_NO_AUDIO, + SPEK_AUDIO_NO_DECODER, + SPEK_AUDIO_NO_DURATION, + SPEK_AUDIO_NO_CHANNELS, + SPEK_AUDIO_CANNOT_OPEN_DECODER, + SPEK_AUDIO_BAD_SAMPLE_FORMAT, +}; -/* Open the file, check if it has an audio stream which can be decoded. - * On error, initialises the `error` field in the returned context. - */ -SpekAudioContext * spek_audio_open (const gchar *file_name); +struct spek_audio_properties +{ + char *codec_name; + enum spek_audio_error error; + int bit_rate; + int sample_rate; + int bits_per_sample; + int width; // number of bits used to store a sample + bool fp; // floating-point sample representation + int channels; + double duration; + // TODO: these four guys don't belong here, move them somewhere else when revamping the pipeline + uint8_t *buffer; + int64_t frames_per_interval; + int64_t error_per_interval; + int64_t error_base; +}; -/* Prepare the context for reading audio samples. */ -void spek_audio_start (SpekAudioContext *cx, gint samples); +// Initialise FFmpeg, should be called once on start up. +void spek_audio_init(); -/* Read and decode the opened audio stream. - * Returns -1 on error, 0 if there's nothing left to read - * or the number of bytes decoded into the buffer. - */ -gint spek_audio_read (SpekAudioContext *cx); +// Open the file, check if it has an audio stream which can be decoded. +// On error, initialises the `error` field in the returned context. +struct spek_audio_context * spek_audio_open(const char *path); -/* Closes the file opened with spek_audio_open, - * frees all allocated buffers and the context - */ -void spek_audio_close (SpekAudioContext *cx); +const struct spek_audio_properties * spek_audio_get_properties(struct spek_audio_context *cs); + +// Prepare the context for reading audio samples. +void spek_audio_start(struct spek_audio_context *cx, int samples); + +// Read and decode the opened audio stream. +// Returns -1 on error, 0 if there's nothing left to read +// or the number of bytes decoded into the buffer. +int spek_audio_read(struct spek_audio_context *cx); + +// Closes the file opened with spek_audio_open, +// frees all allocated buffers and the context +void spek_audio_close(struct spek_audio_context *cx); + +#ifdef __cplusplus +} +#endif #endif diff --git a/src/spek-events.cc b/src/spek-events.cc @@ -0,0 +1,45 @@ +/* spek-events.cc + * + * Copyright (C) 2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * + * Spek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Spek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Spek. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "spek-events.hh" + +//IMPLEMENT_DYNAMIC_CLASS(SpekHaveSampleEvent, wxEvent) +DEFINE_EVENT_TYPE(SPEK_HAVE_SAMPLE) + +SpekHaveSampleEvent::SpekHaveSampleEvent(int bands, int sample, float *values, bool free_values) + : wxEvent(), bands(bands), sample(sample), values(values), free_values(free_values) +{ + SetEventType(SPEK_HAVE_SAMPLE); +} + +SpekHaveSampleEvent::SpekHaveSampleEvent(const SpekHaveSampleEvent& other) +{ + SetEventType(SPEK_HAVE_SAMPLE); + this->bands = other.bands; + this->sample = other.sample; + this->values = (float *)malloc(this->bands * sizeof(float)); + memcpy(this->values, other.values, this->bands * sizeof(float)); + this->free_values = true; +} + +SpekHaveSampleEvent::~SpekHaveSampleEvent() +{ + if (this->free_values) { + free(this->values); + } +} diff --git a/src/spek-events.hh b/src/spek-events.hh @@ -0,0 +1,53 @@ +/* spek-events.hh + * + * Copyright (C) 2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * + * Spek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Spek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Spek. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SPEK_EVENTS_HH_ +#define SPEK_EVENTS_HH_ + +#include <wx/wx.h> + +class SpekHaveSampleEvent: public wxEvent +{ +public: + SpekHaveSampleEvent(int bands, int sample, float *values, bool free_values); + SpekHaveSampleEvent(const SpekHaveSampleEvent& other); + ~SpekHaveSampleEvent(); + + int get_bands() const { return this->bands; } + int get_sample() const { return this->sample; } + const float *get_values() const { return this->values; } + + wxEvent *Clone() const { return new SpekHaveSampleEvent(*this); } +// DECLARE_DYNAMIC_CLASS(SpekHaveSampleEvent); + +private: + int bands; + int sample; + float *values; + bool free_values; +}; + +typedef void (wxEvtHandler::*SpekHaveSampleEventFunction)(SpekHaveSampleEvent&); + +DECLARE_EVENT_TYPE(SPEK_HAVE_SAMPLE, wxID_ANY) + +#define SPEK_EVT_HAVE_SAMPLE(fn) \ + DECLARE_EVENT_TABLE_ENTRY(SPEK_HAVE_SAMPLE, -1, -1, \ + (wxObjectEventFunction) (SpekHaveSampleEventFunction) &fn, (wxObject *) NULL ), + +#endif diff --git a/src/spek-fft.c b/src/spek-fft.c @@ -1,6 +1,6 @@ /* spek-fft.c * - * Copyright (C) 2010 Alexander Kojevnikov <alexander@kojevnikov.com> + * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com> * * Spek is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,43 +17,49 @@ */ #include <math.h> +#include <libavcodec/avfft.h> #include <libavutil/mem.h> #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)); +struct spek_fft_plan * spek_fft_plan_new(int n, int threshold) +{ + struct spek_fft_plan *p = malloc(sizeof(struct spek_fft_plan)); + p->input = av_mallocz(sizeof(float) * n); + p->output = av_mallocz(sizeof(float) * (n / 2 + 1)); p->threshold = threshold; - for (bits = 0; n; n >>= 1, bits++); + int bits = 0; + while (n) { + n >>= 1; + ++bits; + } p->n = 1 << --bits; - p->cx = av_rdft_init (bits, DFT_R2C); + p->cx = av_rdft_init(bits, DFT_R2C); return p; } -void spek_fft_execute (SpekFftPlan *p) { - int i; - int n = p->n; - - av_rdft_calc (p->cx, p->input); +void spek_fft_execute(struct spek_fft_plan *p) +{ + av_rdft_calc(p->cx, p->input); - /* Calculate magnitudes */ + // Calculate magnitudes. + int n = p->n; 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]; + for (int i = 1; i < n / 2; i++) { + float 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); +void spek_fft_delete(struct spek_fft_plan *p) +{ + av_rdft_end(p->cx); + av_free(p->input); + av_free(p->output); + free(p); } diff --git a/src/spek-fft.h b/src/spek-fft.h @@ -1,6 +1,6 @@ /* spek-fft.h * - * Copyright (C) 2010 Alexander Kojevnikov <alexander@kojevnikov.com> + * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com> * * Spek is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,30 +16,38 @@ * along with Spek. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef __SPEK_FFT_H__ -#define __SPEK_FFT_H__ +#ifndef SPEK_FFT_H_ +#define SPEK_FFT_H_ -#include <glib.h> -#include <libavcodec/avfft.h> +#ifdef __cplusplus +extern "C" { +#endif + +struct RDFTContext; -typedef struct { - /* Internal data */ - RDFTContext *cx; - gint n; - gint threshold; +struct spek_fft_plan +{ + // Internal data. + struct RDFTContext *cx; + int n; + int threshold; - /* Exposed properties */ - gfloat *input; - gfloat *output; -} SpekFftPlan; + // Exposed properties. + float *input; + float *output; +}; -/* Allocate buffers and create a new FFT plan */ -SpekFftPlan * spek_fft_plan_new (gint n, gint threshold); +// Allocate buffers and create a new FFT plan. +struct spek_fft_plan * spek_fft_plan_new(int n, int threshold); -/* Execute the FFT on plan->input */ -void spek_fft_execute (SpekFftPlan *p); +// Execute the FFT on plan->input. +void spek_fft_execute(struct spek_fft_plan *p); -/* Destroy the plan and de-allocate buffers */ -void spek_fft_destroy (SpekFftPlan *p); +// Destroy the plan and de-allocate buffers. +void spek_fft_delete(struct spek_fft_plan *p); + +#ifdef __cplusplus +} +#endif #endif diff --git a/src/spek-palette.c b/src/spek-palette.c @@ -0,0 +1,61 @@ +/* spek-palette.c + * + * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * + * Spek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Spek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Spek. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "spek-palette.h" + +// Modified version of Dan Bruton's algorithm: +// http://www.physics.sfasu.edu/astro/color/spectra.html +uint32_t spek_palette_spectrum(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 a 32-bit uint. + uint32_t rr = (uint32_t) (r * cf + 0.5); + uint32_t gg = (uint32_t) (g * cf + 0.5); + uint32_t bb = (uint32_t) (b * cf + 0.5); + return (rr << 16) + (gg << 8) + bb; +} diff --git a/src/spek-palette.h b/src/spek-palette.h @@ -0,0 +1,34 @@ +/* spek-palette.h + * + * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * + * Spek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Spek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Spek. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SPEK_PALETTE_HH_ +#define SPEK_PALETTE_HH_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdint.h> + +uint32_t spek_palette_spectrum(double level); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/spek-pipeline.c b/src/spek-pipeline.c @@ -0,0 +1,368 @@ +/* spek-pipeline.c + * + * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * + * Spek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Spek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Spek. If not, see <http://www.gnu.org/licenses/>. + * + * Conversion of decoded samples into an FFT-happy format is heavily + * influenced by GstSpectrum which is part of gst-plugins-good. + * The original code: + * (c) 1999 Erik Walthinsen <omega@cse.ogi.edu> + * (c) 2006 Stefan Kost <ensonic@users.sf.net> + * (c) 2007-2009 Sebastian Dröge <sebastian.droege@collabora.co.uk> + */ + +#include <assert.h> +#include <math.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> + +#include "spek-audio.h" +#include "spek-fft.h" + +#include "spek-pipeline.h" + +enum +{ + NFFT = 64 // Number of FFTs to pre-fetch. +}; + +struct spek_pipeline +{ + struct spek_audio_context *cx; + const struct spek_audio_properties *properties; + int bands; + int samples; + int threshold; + spek_pipeline_cb cb; + void *cb_data; + + struct spek_fft_plan *fft; + float *coss; // Pre-computed cos table. + int nfft; // Size of the FFT transform. + int input_size; + int input_pos; + float *input; + float *output; + + pthread_t reader_thread; + bool has_reader_thread; + pthread_mutex_t reader_mutex; + bool has_reader_mutex; + pthread_cond_t reader_cond; + bool has_reader_cond; + pthread_t worker_thread; + bool has_worker_thread; + pthread_mutex_t worker_mutex; + bool has_worker_mutex; + pthread_cond_t worker_cond; + bool has_worker_cond; + bool worker_done; + volatile bool quit; +}; + +// Forward declarations. +static void * reader_func(void *); +static void * worker_func(void *); +static void reader_sync(struct spek_pipeline *p, int pos); +static float average_input(const struct spek_pipeline *p, void *buffer); + +struct spek_pipeline * spek_pipeline_open( + const char *path, int bands, int samples, int threshold, spek_pipeline_cb cb, void *cb_data) +{ + struct spek_pipeline *p = malloc(sizeof(struct spek_pipeline)); + p->cx = spek_audio_open(path); + p->properties = spek_audio_get_properties(p->cx); + p->bands = bands; + p->samples = samples; + p->threshold = threshold; + p->cb = cb; + p->cb_data = cb_data; + + p->coss = NULL; + p->fft = NULL; + p->input = NULL; + p->output = NULL; + p->has_reader_thread = false; + p->has_reader_mutex = false; + p->has_reader_cond = false; + p->has_worker_thread = false; + p->has_worker_mutex = false; + p->has_worker_cond = false; + + if (!p->properties->error) { + p->nfft = 2 * bands - 2; + p->coss = malloc(p->nfft * sizeof(float)); + float cf = 2.0f * (float)M_PI / p->nfft; + for (int i = 0; i < p->nfft; ++i) { + p->coss[i] = cosf(cf * i); + } + p->fft = spek_fft_plan_new(p->nfft, threshold); + p->input_size = p->nfft * (NFFT * 2 + 1); + p->input = malloc(p->input_size * sizeof(float)); + p->output = malloc(bands * sizeof(float)); + spek_audio_start(p->cx, samples); + } + + return p; +} + +const struct spek_audio_properties * spek_pipeline_properties(struct spek_pipeline *pipeline) +{ + return pipeline->properties; +} + +void spek_pipeline_start(struct spek_pipeline *p) +{ + if (p->properties->error) return; + + p->input_pos = 0; + p->worker_done = false; + p->quit = false; + + p->has_reader_mutex = !pthread_mutex_init(&p->reader_mutex, NULL); + p->has_reader_cond = !pthread_cond_init(&p->reader_cond, NULL); + p->has_worker_mutex = !pthread_mutex_init(&p->worker_mutex, NULL); + p->has_worker_cond = !pthread_cond_init(&p->worker_cond, NULL); + + p->has_reader_thread = !pthread_create(&p->reader_thread, NULL, &reader_func, p); + if (!p->has_reader_thread) { + spek_pipeline_close(p); + } +} + +void spek_pipeline_close(struct spek_pipeline *p) +{ + if (p->has_reader_thread) { + p->quit = true; + pthread_join(p->reader_thread, NULL); + p->has_reader_thread = false; + } + if (p->has_worker_cond) { + pthread_cond_destroy(&p->worker_cond); + p->has_worker_cond = false; + } + if (p->has_worker_mutex) { + pthread_mutex_destroy(&p->worker_mutex); + p->has_worker_mutex = false; + } + if (p->has_reader_cond) { + pthread_cond_destroy(&p->reader_cond); + p->has_reader_cond = false; + } + if (p->has_reader_mutex) { + pthread_mutex_destroy(&p->reader_mutex); + p->has_reader_mutex = false; + } + if (p->output) { + free(p->output); + p->output = NULL; + } + if (p->input) { + free(p->input); + p->input = NULL; + } + if (p->fft) { + spek_fft_delete(p->fft); + p->fft = NULL; + } + if (p->coss) { + free(p->coss); + p->coss = NULL; + } + if (p->cx) { + spek_audio_close(p->cx); + p->cx = NULL; + } + free(p); +} + +static void * reader_func(void *pp) +{ + struct spek_pipeline *p = pp; + + p->has_worker_thread = !pthread_create(&p->worker_thread, NULL, &worker_func, p); + if (!p->has_worker_thread) { + return NULL; + } + + int pos = 0, prev_pos = 0; + int block_size = p->properties->width * p->properties->channels / 8; + int size; + while ((size = spek_audio_read(p->cx)) > 0) { + if (p->quit) break; + + uint8_t *buffer = p->properties->buffer; + while (size >= block_size) { + p->input[pos] = average_input(p, buffer); + buffer += block_size; + size -= block_size; + pos = (pos + 1) % p->input_size; + + // Wake up the worker if we have enough data. + if ((pos > prev_pos ? pos : pos + p->input_size) - prev_pos == p->nfft * NFFT) { + reader_sync(p, prev_pos = pos); + } + } + assert(size == 0); + } + + if (pos != prev_pos) { + // Process the remaining data. + reader_sync(p, pos); + } + + // Force the worker to quit. + reader_sync(p, -1); + pthread_join(p->worker_thread, NULL); + return NULL; +} + +static void reader_sync(struct spek_pipeline *p, int pos) +{ + pthread_mutex_lock(&p->reader_mutex); + while (!p->worker_done) { + pthread_cond_wait(&p->reader_cond, &p->reader_mutex); + } + p->worker_done = false; + pthread_mutex_unlock(&p->reader_mutex); + + pthread_mutex_lock(&p->worker_mutex); + p->input_pos = pos; + pthread_cond_signal(&p->worker_cond); + pthread_mutex_unlock(&p->worker_mutex); +} + +static void * worker_func(void *pp) +{ + struct spek_pipeline *p = pp; + + int sample = 0; + int64_t frames = 0; + int64_t num_fft = 0; + int64_t acc_error = 0; + int head = 0, tail = 0; + int prev_head = 0; + + memset(p->output, 0, sizeof(float) * p->bands); + + while (true) { + pthread_mutex_lock(&p->reader_mutex); + p->worker_done = true; + pthread_cond_signal(&p->reader_cond); + pthread_mutex_unlock(&p->reader_mutex); + + pthread_mutex_lock(&p->worker_mutex); + while (tail == p->input_pos) { + pthread_cond_wait(&p->worker_cond, &p->worker_mutex); + } + tail = p->input_pos; + pthread_mutex_unlock(&p->worker_mutex); + + if (tail == -1) { + return NULL; + } + + while (true) { + head = (head + 1) % p->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 < p->properties->error_base && + frames == p->properties->frames_per_interval; + bool int_over = + acc_error >= p->properties->error_base && + frames == 1 + p->properties->frames_per_interval; + + if (frames % p->nfft == 0 || ((int_full || int_over) && num_fft == 0)) { + prev_head = head; + for (int i = 0; i < p->nfft; i++) { + float val = p->input[(p->input_size + head - p->nfft + i) % p->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 * (1.0f - p->coss[i]); + p->fft->input[i] = val; + } + spek_fft_execute(p->fft); + num_fft++; + for (int i = 0; i < p->bands; i++) { + p->output[i] += p->fft->output[i]; + } + } + + // Do we have the FFTs for one interval? + if (int_full || int_over) { + if (int_over) { + acc_error -= p->properties->error_base; + } else { + acc_error += p->properties->error_per_interval; + } + + for (int i = 0; i < p->bands; i++) { + p->output[i] /= num_fft; + } + + if (sample == p->samples) break; + p->cb(sample++, p->output, p->cb_data); + + memset(p->output, 0, sizeof(float) * p->bands); + frames = 0; + num_fft = 0; + } + } + } +} + +static float average_input(const struct spek_pipeline *p, void *buffer) +{ + int channels = p->properties->channels; + float res = 0.0f; + if (p->properties->fp) { + if (p->properties->width == 32) { + float *b = buffer; + for (int i = 0; i < channels; i++) { + res += b[i]; + } + } else { + assert(p->properties->width == 64); + double *b = buffer; + for (int i = 0; i < channels; i++) { + res += (float) b[i]; + } + } + } else { + if (p->properties->width == 16) { + int16_t *b = buffer; + for (int i = 0; i < channels; i++) { + res += b[i] / (float) INT16_MAX; + } + } else { + assert (p->properties->width == 32); + int32_t *b = buffer; + for (int i = 0; i < channels; i++) { + res += b[i] / (float) INT32_MAX; + } + } + } + return res / channels; +} diff --git a/src/spek-pipeline.h b/src/spek-pipeline.h @@ -0,0 +1,44 @@ +/* spek-pipeline.h + * + * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * + * Spek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Spek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Spek. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SPEK_PIPELINE_H_ +#define SPEK_PIPELINE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +struct spek_pipeline; +struct spek_audio_properties; + +typedef void (*spek_pipeline_cb)(int sample, float *values, void *cb_data); + +struct spek_pipeline * spek_pipeline_open( + const char *path, int bands, int samples, int threshold, spek_pipeline_cb cb, void *cb_data); + +const struct spek_audio_properties * spek_pipeline_properties(struct spek_pipeline *pipeline); + +void spek_pipeline_start(struct spek_pipeline *pipeline); + +void spek_pipeline_close(struct spek_pipeline *pipeline); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/spek-pipeline.vala b/src/spek-pipeline.vala @@ -1,302 +0,0 @@ -/* spek-pipeline.vala - * - * Copyright (C) 2010 Alexander Kojevnikov <alexander@kojevnikov.com> - * - * Spek is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Spek is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Spek. If not, see <http://www.gnu.org/licenses/>. - * - * Conversion of decoded samples into an FFT-happy format is heavily - * influenced by GstSpectrum which is part of gst-plugins-good. - * The original code: - * (c) 1999 Erik Walthinsen <omega@cse.ogi.edu> - * (c) 2006 Stefan Kost <ensonic@users.sf.net> - * (c) 2007-2009 Sebastian Dröge <sebastian.droege@collabora.co.uk> - */ - -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; - } - } -} diff --git a/src/spek-platform.c b/src/spek-platform.c @@ -1,209 +0,0 @@ -/* spek-platform.c - * - * Copyright (C) 2010,2011 Alexander Kojevnikov <alexander@kojevnikov.com> - * - * Spek is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Spek is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Spek. If not, see <http://www.gnu.org/licenses/>. - */ - -#include <glib.h> -#include <gtk/gtk.h> - -#ifdef G_OS_WIN32 -#include <windows.h> -#include <shellapi.h> -#endif - -#ifdef G_OS_DARWIN -#include <gtkosxapplication.h> -#include <CoreFoundation/CoreFoundation.h> -#include <ApplicationServices/ApplicationServices.h> -#endif - -#include "spek-platform.h" - -void spek_platform_init () { -#ifdef G_OS_DARWIN - 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); -#endif -} - -#ifdef G_OS_DARWIN -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); - } -} -#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); -#endif -} - -gchar *spek_platform_locale_dir () { - static gchar *locale_dir = NULL; - - if (!locale_dir) { -#ifdef G_OS_WIN32 - gchar *win32_dir; - - 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); -#else -#ifdef G_OS_DARWIN - 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); -#else - locale_dir = LOCALEDIR; -#endif -#endif - } - - 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); -#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); -#else - 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; -#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; -#endif -} - -gdouble spek_platform_get_font_scale () { -#ifdef G_OS_DARWIN - /* Pango/Quartz fonts are smaller than on X. */ - return 1.4; -#endif - return 1.0; -} diff --git a/src/spek-platform.cc b/src/spek-platform.cc @@ -0,0 +1,79 @@ +/* spek-platform.cc + * + * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * + * Spek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Spek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Spek. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <cstring> + +#ifdef OS_OSX +#include <ApplicationServices/ApplicationServices.h> +#endif + +#include <wx/filename.h> +#include <wx/stdpaths.h> +#include <wx/utils.h> + +#include "spek-platform.hh" + +void SpekPlatform::init() +{ +#ifdef OS_OSX + ProcessSerialNumber PSN; + GetCurrentProcess(&PSN); + TransformProcessType(&PSN, kProcessTransformToForegroundApplication); +#endif +} + +wxString SpekPlatform::config_path(const wxString& app_name) +{ +#ifdef OS_WIN + wxFileName file_name(wxStandardPaths::Get().GetUserConfigDir(), wxEmptyString); +#else + wxFileName file_name(wxGetHomeDir(), wxEmptyString); + file_name.AppendDir(wxT(".config")); +#endif + file_name.AppendDir(app_name); + file_name.Mkdir(0755, wxPATH_MKDIR_FULL); + file_name.SetFullName(wxT("preferences")); + return file_name.GetFullPath(); +} + +bool SpekPlatform::can_change_language() +{ +#ifdef OS_UNIX + return false; +#else + return true; +#endif +} + +double SpekPlatform::font_scale() +{ +#ifdef OS_OSX + return 1.3; +#else + return 1.0; +#endif +} + +char * spek_platform_short_path(const char *path) +{ +#ifdef OS_WIN + wxFileName file_name(wxString(path, wxConvUTF8)); + return strdup(file_name.GetShortPath().char_str(wxConvFile)); +#endif + return NULL; +} diff --git a/src/spek-platform.h b/src/spek-platform.h @@ -1,45 +0,0 @@ -/* spek-platform.h - * - * Copyright (C) 2010,2011 Alexander Kojevnikov <alexander@kojevnikov.com> - * - * Spek is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Spek is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Spek. If not, see <http://www.gnu.org/licenses/>. - */ - -#ifndef __SPEK_PLATFORM_H__ -#define __SPEK_PLATFORM_H__ - -#include <gtk/gtk.h> - -/* Platform-specific initialisation */ -void spek_platform_init (); - -/* Convert from UTF-16 to UTF-8 when running on Windows */ -void spek_platform_fix_args (gchar **argv, gint argc); - -/* OSX has its own approach to menus and accelerators */ -void spek_platform_fix_ui (GtkUIManager *ui); - -/* Platform-specific locale directory */ -gchar *spek_platform_locale_dir (); - -/* Open a link in the browser */ -void spek_platform_show_uri (const gchar *uri); - -/* Read a line from a uri */ -gchar *spek_platform_read_line (const gchar *uri); - -/* Fonts are smaller on OS X */ -gdouble spek_platform_get_font_scale (); - -#endif diff --git a/src/spek-platform.hh b/src/spek-platform.hh @@ -0,0 +1,52 @@ +/* spek-platform.hh + * + * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * + * Spek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Spek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Spek. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SPEK_PLATFORM_HH_ +#define SPEK_PLATFORM_HH_ + +#ifdef __cplusplus +#include <wx/string.h> + +class SpekPlatform +{ +public: + // Platform-specific initialisation code. + static void init(); + + // Not quite XDG-compatible, but close enough. + static wxString config_path(const wxString& app_name); + + // Setting non-default locale under GTK+ is tricky (see e.g. how FileZilla does it). We will + // just disable the language setting for GTK+ users and will always use the system locale. + static bool can_change_language(); + + // Fonts are smaller on OSX. + static double font_scale(); +}; + +extern "C" { +#endif + +// Returns a 8.3 version of the UTF8-encoded path on Windows and NULL on other platforms. +char * spek_platform_short_path(const char *path); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/spek-preferences-dialog.cc b/src/spek-preferences-dialog.cc @@ -0,0 +1,114 @@ +/* spek-preferences-dialog.cc + * + * Copyright (C) 2011-2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * + * Spek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Spek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Spek. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "spek-preferences.hh" + +#include "spek-preferences-dialog.hh" + +// List all languages with a decent (e.g. 80%) number of translated +// strings. Don't translate language names. Keep the first line intact. +static const char *available_languages[] = +{ + "", "", + "cs", "Čeština", + "da", "Dansk", + "de", "Deutsch", + "en", "English", + "eo", "Esperanto", + "es", "Español", + "fr", "Français", + "it", "Italiano", + "ja", "日本語", + "nl", "Nederlands", + "pl", "Polski", + "pt_BR", "Português brasileiro", + "ru", "Русский", + "sv", "Svenska", + "uk", "Українська", + "zh_CN", "中文(简体)", + "zh_TW", "中文(台灣)", + NULL +}; + +#ifndef wxCLOSE +// wxWidgets 2.8 doesn't have wxCLOSE, replace with wxOK for now. +#define wxCLOSE wxOK +#endif + +#define ID_LANGUAGE (wxID_HIGHEST + 1) +#define ID_CHECK (wxID_HIGHEST + 2) + +BEGIN_EVENT_TABLE(SpekPreferencesDialog, wxDialog) + EVT_CHOICE(ID_LANGUAGE, SpekPreferencesDialog::on_language) + EVT_CHECKBOX(ID_CHECK, SpekPreferencesDialog::on_check) +END_EVENT_TABLE() + +SpekPreferencesDialog::SpekPreferencesDialog(wxWindow *parent) : + wxDialog(parent, -1, _("Preferences")) +{ + for (int i = 0; available_languages[i]; ++i) { + this->languages.Add(wxString::FromUTF8(available_languages[i])); + } + this->languages[1] = _("(system default)"); + + wxSizer *sizer = new wxBoxSizer(wxVERTICAL); + wxSizer *inner_sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(inner_sizer, 1, wxALL, 12); + + // TRANSLATORS: The name of a section in the Preferences dialog. + wxStaticText *general_label = new wxStaticText(this, -1, _("General")); + wxFont font = general_label->GetFont(); + font.SetWeight(wxFONTWEIGHT_BOLD); + general_label->SetFont(font); + inner_sizer->Add(general_label); + + wxSizer *language_sizer = new wxBoxSizer(wxHORIZONTAL); + inner_sizer->Add(language_sizer, 0, wxLEFT | wxTOP, 12); + wxStaticText *language_label = new wxStaticText(this, -1, _("Language:")); + language_sizer->Add(language_label, 0, wxALIGN_CENTER_VERTICAL); + + wxChoice *language_choice = new wxChoice(this, ID_LANGUAGE); + language_sizer->Add(language_choice, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 12); + int active_index = 0; + wxString active_language = SpekPreferences::get().get_language(); + for (int i = 0; i < this->languages.GetCount(); i += 2) { + language_choice->Append(this->languages[i + 1]); + if (this->languages[i] == active_language) { + active_index = i / 2; + } + } + language_choice->SetSelection(active_index); + + wxCheckBox *check_update = new wxCheckBox(this, ID_CHECK, _("Check for &updates")); + inner_sizer->Add(check_update, 0 ,wxLEFT | wxTOP, 12); + check_update->SetValue(SpekPreferences::get().get_check_update()); + + sizer->Add(CreateButtonSizer(wxCLOSE), 0, wxALIGN_RIGHT | wxBOTTOM | wxRIGHT, 12); + sizer->SetSizeHints(this); + SetSizer(sizer); +} + +void SpekPreferencesDialog::on_language(wxCommandEvent& event) +{ + SpekPreferences::get().set_language(this->languages[event.GetSelection() * 2]); +} + +void SpekPreferencesDialog::on_check(wxCommandEvent& event) +{ + SpekPreferences::get().set_check_update(event.IsChecked()); +} diff --git a/src/spek-preferences-dialog.hh b/src/spek-preferences-dialog.hh @@ -0,0 +1,38 @@ +/* spek-preferences-dialog.hh + * + * Copyright (C) 2011-2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * + * Spek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Spek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Spek. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SPEK_PREFERENCES_DIALOG_HH_ +#define SPEK_PREFERENCES_DIALOG_HH_ + +#include <wx/wx.h> + +class SpekPreferencesDialog : public wxDialog +{ +public: + SpekPreferencesDialog(wxWindow *parent); + +private: + void on_language(wxCommandEvent& event); + void on_check(wxCommandEvent& event); + + wxArrayString languages; + + DECLARE_EVENT_TABLE() +}; + +#endif diff --git a/src/spek-preferences-dialog.vala b/src/spek-preferences-dialog.vala @@ -1,110 +0,0 @@ -/* spek-preferences-dialog.vala - * - * Copyright (C) 2011 Alexander Kojevnikov <alexander@kojevnikov.com> - * - * Spek is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Spek is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Spek. If not, see <http://www.gnu.org/licenses/>. - */ - -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}, - {"cs", "Čeština"}, - {"da", "Dansk"}, - {"de", "Deutsch"}, - {"en", "English"}, - {"eo", "Esperanto"}, - {"es", "Español"}, - {"fr", "Français"}, - {"it", "Italiano"}, - {"ja", "日本語"}, - {"nl", "Nederlands"}, - {"pl", "Polski"}, - {"pt_BR", "Português brasileiro"}, - {"ru", "Русский"}, - {"sv", "Svenska"}, - {"uk", "Українська"}, - {"zh_CN", "中文(简体)"}, - {"zh_TW", "中文(台灣)"} - }; - - 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 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 (); - - 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 (); - } - } -} -\ No newline at end of file diff --git a/src/spek-preferences.cc b/src/spek-preferences.cc @@ -0,0 +1,100 @@ +/* spek-preferences.cc + * + * Copyright (C) 2011-2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * + * Spek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Spek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Spek. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <wx/string.h> + +#include "spek-platform.hh" + +#include "spek-preferences.hh" + +SpekPreferences& SpekPreferences::get() +{ + static SpekPreferences instance; + return instance; +} + +void SpekPreferences::init() +{ + if (this->locale) { + delete this->locale; + } + this->locale = new wxLocale(); + + int lang = wxLANGUAGE_DEFAULT; + wxString code = this->get_language(); + if (!code.IsEmpty()) { + const wxLanguageInfo *info = wxLocale::FindLanguageInfo(code); + if (info) { + lang = info->Language; + } + } + this->locale->Init(lang); + this->locale->AddCatalog(wxT(GETTEXT_PACKAGE)); +} + +SpekPreferences::SpekPreferences() : locale(NULL) +{ + wxString path = SpekPlatform::config_path(wxT("spek")); + this->config = new wxFileConfig( + wxEmptyString, + wxEmptyString, + path, + wxEmptyString, + wxCONFIG_USE_LOCAL_FILE, + wxConvUTF8 + ); +} + +bool SpekPreferences::get_check_update() +{ + bool result = true; + this->config->Read(wxT("/update/check"), &result); + return result; +} + +void SpekPreferences::set_check_update(bool value) +{ + this->config->Write(wxT("/update/check"), value); + this->config->Flush(); +} + +long SpekPreferences::get_last_update() +{ + long result = 0; + this->config->Read(wxT("/update/last"), &result); + return result; +} + +void SpekPreferences::set_last_update(long value) +{ + this->config->Write(wxT("/update/last"), value); + this->config->Flush(); +} + +wxString SpekPreferences::get_language() +{ + wxString result(wxT("")); + this->config->Read(wxT("/general/language"), &result); + return result; +} + +void SpekPreferences::set_language(const wxString& value) +{ + this->config->Write(wxT("/general/language"), value); + this->config->Flush(); +} diff --git a/src/spek-preferences.hh b/src/spek-preferences.hh @@ -0,0 +1,47 @@ +/* spek-preferences.hh + * + * Copyright (C) 2011-2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * + * Spek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Spek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Spek. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SPEK_PREFERENCES_HH_ +#define SPEK_PREFERENCES_HH_ + +#include <wx/fileconf.h> +#include <wx/intl.h> + +class SpekPreferences +{ +public: + static SpekPreferences& get(); + + void init(); + bool get_check_update(); + void set_check_update(bool value); + long get_last_update(); + void set_last_update(long value); + wxString get_language(); + void set_language(const wxString& value); + +private: + SpekPreferences(); + SpekPreferences(const SpekPreferences&); + void operator=(const SpekPreferences&); + + wxLocale *locale; + wxFileConfig *config; +}; + +#endif diff --git a/src/spek-preferences.vala b/src/spek-preferences.vala @@ -1,102 +0,0 @@ -/* spek-preferences.vala - * - * Copyright (C) 2011 Alexander Kojevnikov <alexander@kojevnikov.com> - * - * Spek is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Spek is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Spek. If not, see <http://www.gnu.org/licenses/>. - */ - -namespace Spek { - public class Preferences { - private KeyFile key_file; - private string file_name; - private bool can_save = true; - - private Preferences () { - file_name = Path.build_filename (Environment.get_user_config_dir (), "spek"); - if (DirUtils.create_with_parents (file_name, 0755) != 0) { - this.can_save = false; - } - 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 (); - } - - private static Preferences _instance; - public static Preferences instance { - get { - if (_instance == null) { - _instance = new Preferences (); - } - return _instance; - } - } - - public void save () { - if (!can_save) { - return; - } - 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 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); - } - } - } -} -\ No newline at end of file diff --git a/src/spek-ruler.cc b/src/spek-ruler.cc @@ -0,0 +1,94 @@ +/* spek-ruler.cc + * + * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * + * Spek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Spek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Spek. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <cmath> + +#include "spek-ruler.hh" + +SpekRuler::SpekRuler( + int x, int y, Position pos, wxString sample_label, + int *factors, int units, double spacing, + double scale, double offset, formatter_cb formatter) + : + x(x), y(y), pos(pos), sample_label(sample_label), + factors(factors), units(units), spacing(spacing), + scale(scale), offset(offset), formatter(formatter) +{ +} + +void SpekRuler::draw(wxDC& dc) +{ + // Mesure the sample label. + wxSize size = dc.GetTextExtent(sample_label); + int len = this->pos == TOP || this->pos == BOTTOM ? size.GetWidth() : size.GetHeight(); + + // Select the factor to use, we want some space between the labels. + int factor = 0; + for (int i = 0; factors[i]; ++i) { + if (fabs(this->scale * factors[i]) >= this->spacing * len) { + factor = factors[i]; + break; + } + } + + // Draw the ticks. + this->draw_tick(dc, 0); + this->draw_tick(dc, units); + + if (factor > 0) { + for (int tick = factor; tick < units; tick += factor) { + if (fabs(this->scale * (units - tick)) < len * 1.2) { + break; + } + this->draw_tick(dc, tick); + } + } +} + +void SpekRuler::draw_tick(wxDC& dc, int tick) +{ + double GAP = 10; + double TICK_LEN = 4; + + wxString label = this->formatter(tick); + int value = this->pos == TOP || this->pos == BOTTOM ? tick : this->units - tick; + double p = this->offset + this->scale * value; + wxSize size = dc.GetTextExtent(label); + int w = size.GetWidth(); + int h = size.GetHeight(); + + if (this->pos == TOP) { + dc.DrawText(label, this->x + p - w / 2, this->y - GAP - h); + } else if (this->pos == RIGHT){ + dc.DrawText(label, this->x + GAP, this->y + p - h / 2); + } else if (this->pos == BOTTOM) { + dc.DrawText(label, this->x + p - w / 2, this->y + GAP); + } else if (this->pos == LEFT){ + dc.DrawText(label, this->x - w - GAP, this->y + p - h / 2); + } + + if (this->pos == TOP) { + dc.DrawLine(this->x + p, this->y, this->x + p, this->y - TICK_LEN); + } else if (this->pos == RIGHT) { + dc.DrawLine(this->x, this->y + p, this->x + TICK_LEN, this->y + p); + } else if (this->pos == BOTTOM) { + dc.DrawLine(this->x + p, this->y, this->x + p, this->y + TICK_LEN); + } else if (this->pos == LEFT) { + dc.DrawLine(this->x, this->y + p, this->x - TICK_LEN, this->y + p); + } +} diff --git a/src/spek-ruler.hh b/src/spek-ruler.hh @@ -0,0 +1,61 @@ +/* spek-ruler.hh + * + * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * + * Spek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Spek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Spek. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SPEK_RULER_HH_ +#define SPEK_RULER_HH_ + +#include <wx/dc.h> +#include <wx/string.h> + +class SpekRuler +{ +public: + enum Position + { + TOP, + RIGHT, + BOTTOM, + LEFT + }; + + typedef wxString (*formatter_cb)(int unit); + + SpekRuler( + int x, int y, Position pos, wxString sample_label, + int *factors, int units, double spacing, + double scale, double offset, formatter_cb formatter + ); + + void draw(wxDC& dc); + +protected: + void draw_tick(wxDC& dc, int tick); + + int x; + int y; + Position pos; + wxString sample_label; + int *factors; + int units; + double spacing; + double scale; + double offset; + formatter_cb formatter; +}; + +#endif diff --git a/src/spek-ruler.vala b/src/spek-ruler.vala @@ -1,123 +0,0 @@ -/* spek-ruler.vala - * - * Copyright (C) 2010,2011 Alexander Kojevnikov <alexander@kojevnikov.com> - * - * Spek is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Spek is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Spek. If not, see <http://www.gnu.org/licenses/>. - */ - -using Cairo; -using Pango; - -namespace Spek { - 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; - - 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 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; - } - } - - // 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 (); - } - } - } -} -\ No newline at end of file diff --git a/src/spek-spectrogram.cc b/src/spek-spectrogram.cc @@ -0,0 +1,371 @@ +/* spek-spectrogram.cc + * + * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * + * Spek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Spek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Spek. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <cmath> +#include <wx/dcbuffer.h> + +#include "spek-audio.h" +#include "spek-audio-desc.hh" +#include "spek-events.hh" +#include "spek-palette.h" +#include "spek-pipeline.h" +#include "spek-platform.hh" +#include "spek-ruler.hh" + +#include "spek-spectrogram.hh" + +BEGIN_EVENT_TABLE(SpekSpectrogram, wxPanel) + EVT_IDLE(SpekSpectrogram::on_idle) + EVT_PAINT(SpekSpectrogram::on_paint) + EVT_SIZE(SpekSpectrogram::on_size) + SPEK_EVT_HAVE_SAMPLE(SpekSpectrogram::on_have_sample) +END_EVENT_TABLE() + +enum +{ + THRESHOLD = -92, + NFFT = 2048, + BANDS = NFFT / 2 + 1, + LPAD = 60, + TPAD = 60, + RPAD = 90, + BPAD = 40, + GAP = 10, + RULER = 10, +}; + +// Forward declarations. +static wxString trim(wxDC& dc, const wxString& s, int length, bool trim_end); + +SpekSpectrogram::SpekSpectrogram(wxFrame *parent) : + wxPanel(parent, -1, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE), + pipeline(NULL), + properties(NULL), + palette(RULER, BANDS), + image(1, 1), + prev_width(-1) +{ + SetBackgroundStyle(wxBG_STYLE_CUSTOM); + + // Pre-draw the palette. + for (int y = 0; y < BANDS; y++) { + uint32_t color = spek_palette_spectrum(y / (double) BANDS); + this->palette.SetRGB( + wxRect(0, BANDS - y - 1, RULER, 1), + color >> 16, + (color >> 8) & 0xFF, + color & 0xFF + ); + } +} + +SpekSpectrogram::~SpekSpectrogram() +{ + this->stop(); +} + +void SpekSpectrogram::open(const wxString& path) +{ + this->path = path; + start(); +} + +void SpekSpectrogram::save(const wxString& path) +{ + wxSize size = GetClientSize(); + wxBitmap bitmap(size.GetWidth(), size.GetHeight()); + wxMemoryDC dc(bitmap); + render(dc); + bitmap.SaveFile(path, wxBITMAP_TYPE_PNG); +} + +void SpekSpectrogram::on_idle(wxIdleEvent& evt) +{ + Update(); +} + +void SpekSpectrogram::on_paint(wxPaintEvent& evt) +{ + wxAutoBufferedPaintDC dc(this); + render(dc); +} + +void SpekSpectrogram::on_size(wxSizeEvent& evt) +{ + wxSize size = GetClientSize(); + bool width_changed = this->prev_width != size.GetWidth(); + this->prev_width = size.GetWidth(); + + if (!this->path.IsEmpty() && width_changed) { + start(); + } +} + +void SpekSpectrogram::on_have_sample(SpekHaveSampleEvent& event) +{ + static double log10_threshold = log10(-THRESHOLD); + int bands = event.get_bands(); + int sample = event.get_sample(); + const float *values = event.get_values(); + + // TODO: check image size, quit if wrong. + for (int y = 0; y < bands; y++) { + double level = log10(1.0 - THRESHOLD + values[y]) / log10_threshold; + if (level > 1.0) level = 1.0; + uint32_t color = spek_palette_spectrum(level); + this->image.SetRGB( + sample, + bands - y - 1, + color >> 16, + (color >> 8) & 0xFF, + color & 0xFF + ); + } + + // TODO: refresh only one pixel column + this->Refresh(); +} + +static wxString time_formatter(int unit) +{ + // TODO: i18n + return wxString::Format(wxT("%d:%02d"), unit / 60, unit % 60); +} + +static wxString freq_formatter(int unit) +{ + return wxString::Format(_("%d kHz"), unit / 1000); +} + +static wxString density_formatter(int unit) +{ + return wxString::Format(_("%d dB"), -unit); +} + +void SpekSpectrogram::render(wxDC& dc) +{ + wxSize size = GetClientSize(); + int w = size.GetWidth(); + int h = size.GetHeight(); + + // Initialise. + dc.SetBackground(*wxBLACK_BRUSH); + dc.SetBackgroundMode(wxTRANSPARENT); + dc.SetPen(*wxWHITE_PEN); + dc.SetBrush(*wxTRANSPARENT_BRUSH); + dc.SetTextForeground(wxColour(255, 255, 255)); + wxFont normal_font = wxFont( + (int)round(9 * SpekPlatform::font_scale()), + wxFONTFAMILY_SWISS, + wxFONTSTYLE_NORMAL, + wxFONTWEIGHT_NORMAL + ); + wxFont large_font = wxFont(normal_font); + large_font.SetPointSize((int)round(10 * SpekPlatform::font_scale())); + large_font.SetWeight(wxFONTWEIGHT_BOLD); + wxFont small_font = wxFont(normal_font); + small_font.SetPointSize((int)round(8 * SpekPlatform::font_scale())); + dc.SetFont(normal_font); + int normal_height = dc.GetTextExtent(wxT("dummy")).GetHeight(); + dc.SetFont(large_font); + int large_height = dc.GetTextExtent(wxT("dummy")).GetHeight(); + dc.SetFont(small_font); + int small_height = dc.GetTextExtent(wxT("dummy")).GetHeight(); + + // Clean the background. + dc.Clear(); + + // Spek version + dc.SetFont(large_font); + wxString package_name(wxT(PACKAGE_NAME)); + dc.DrawText( + package_name, + w - RPAD + GAP, + TPAD - 2 * GAP - normal_height - large_height + ); + int package_name_width = dc.GetTextExtent(package_name + wxT(" ")).GetWidth(); + dc.SetFont(small_font); + dc.DrawText( + wxT(PACKAGE_VERSION), + w - RPAD + GAP + package_name_width, + TPAD - 2 * GAP - normal_height - small_height + ); + + if (this->image.GetHeight() > 1) { + // Draw the spectrogram. + wxBitmap bmp(this->image.Scale(w - LPAD - RPAD, h - TPAD - BPAD)); + dc.DrawBitmap(bmp, LPAD, TPAD); + + // File name. + dc.SetFont(large_font); + dc.DrawText( + trim(dc, this->path, w - LPAD - RPAD, false), + LPAD, + TPAD - 2 * GAP - normal_height - large_height + ); + + // File properties. + dc.SetFont(normal_font); + dc.DrawText( + trim(dc, this->desc, w - LPAD - RPAD, true), + LPAD, + TPAD - GAP - normal_height + ); + + // Prepare to draw the rulers. + dc.SetFont(small_font); + + // Time ruler. + double duration = this->properties->duration; + int time_factors[] = {1, 2, 5, 10, 20, 30, 1*60, 2*60, 5*60, 10*60, 20*60, 30*60, 0}; + SpekRuler time_ruler( + LPAD, + h - BPAD, + SpekRuler::BOTTOM, + // TODO: i18n + wxT("00:00"), + time_factors, + (int)duration, + 1.5, + (w - LPAD - RPAD) / duration, + 0.0, + time_formatter + ); + time_ruler.draw(dc); + + // Frequency ruler. + int freq = this->properties->sample_rate / 2; + int freq_factors[] = {1000, 2000, 5000, 10000, 20000, 0}; + SpekRuler freq_ruler( + LPAD, + TPAD, + SpekRuler::LEFT, + // TRANSLATORS: keep "00" unchanged, it's used to calc the text width + _("00 kHz"), + freq_factors, + freq, + 3.0, + (h - TPAD - BPAD) / (double)freq, + 0.0, + freq_formatter + ); + freq_ruler.draw(dc); + } + + // Border around the spectrogram. + dc.DrawRectangle(LPAD, TPAD, w - LPAD - RPAD, h - TPAD - BPAD); + + // The palette. + wxBitmap bmp(this->palette.Scale(RULER, h - TPAD - BPAD + 1)); + dc.DrawBitmap(bmp, w - RPAD + GAP, TPAD); + + // Prepare to draw the ruler. + dc.SetFont(small_font); + + // Spectral density. + int density_factors[] = {1, 2, 5, 10, 20, 50, 0}; + SpekRuler density_ruler( + w - RPAD + GAP + RULER, + TPAD, + SpekRuler::RIGHT, + // TRANSLATORS: keep "-00" unchanged, it's used to calc the text width + _("-00 dB"), + density_factors, + -THRESHOLD, + 3.0, + (h - TPAD - BPAD) / (double)THRESHOLD, + h - TPAD - BPAD, + density_formatter + ); + density_ruler.draw(dc); +} + +static void pipeline_cb(int sample, float *values, void *cb_data) +{ + SpekHaveSampleEvent event(BANDS, sample, values, false); + SpekSpectrogram *s = (SpekSpectrogram *)cb_data; + wxPostEvent(s, event); +} + +void SpekSpectrogram::start() +{ + this->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. + wxSize size = GetClientSize(); + int samples = size.GetWidth() - LPAD - RPAD; + if (samples > 0) { + this->image.Create(samples, BANDS); + this->pipeline = spek_pipeline_open( + this->path.utf8_str(), + BANDS, + samples, + THRESHOLD, + pipeline_cb, + this + ); + spek_pipeline_start(this->pipeline); + this->properties = spek_pipeline_properties(this->pipeline); + this->desc = spek_audio_desc(this->properties); + } else { + this->image.Create(1, 1); + } + + Refresh(); +} + +void SpekSpectrogram::stop() +{ + if (this->pipeline) { + spek_pipeline_close(this->pipeline); + this->pipeline = NULL; + this->properties = NULL; + } +} + +// Trim `s` so that it fits into `length`. +static wxString trim(wxDC& dc, const wxString& s, int length, bool trim_end) +{ + if (length <= 0) { + return wxEmptyString; + } + + // Check if the entire string fits. + wxSize size = dc.GetTextExtent(s); + if (size.GetWidth() <= length) { + return s; + } + + // Binary search FTW! + wxString fix(wxT("...")); + int i = 0; + int k = s.length(); + while (k - i > 1) { + int j = (i + k) / 2; + size = dc.GetTextExtent(trim_end ? s.substr(0, j) + fix : fix + s.substr(j)); + if (trim_end != (size.GetWidth() > length)) { + i = j; + } else { + k = j; + } + } + + return trim_end ? s.substr(0, i) + fix : fix + s.substr(k); +} diff --git a/src/spek-spectrogram.hh b/src/spek-spectrogram.hh @@ -0,0 +1,57 @@ +/* spek-spectrogram.hh + * + * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * + * Spek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Spek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Spek. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SPEK_SPECTROGRAM_HH_ +#define SPEK_SPECTROGRAM_HH_ + +#include <wx/wx.h> + +class SpekHaveSampleEvent; +struct spek_audio_properties; +struct spek_pipeline; + +class SpekSpectrogram : public wxPanel +{ +public: + SpekSpectrogram(wxFrame *parent); + ~SpekSpectrogram(); + void open(const wxString& path); + void save(const wxString& path); + +private: + void on_idle(wxIdleEvent& evt); + void on_paint(wxPaintEvent& evt); + void on_size(wxSizeEvent& evt); + void on_have_sample(SpekHaveSampleEvent& evt); + void render(wxDC& dc); + + void start(); + void stop(); + + spek_pipeline *pipeline; + const spek_audio_properties *properties; + wxString path; + wxString desc; + wxImage palette; + wxImage image; + int prev_width; + + DECLARE_EVENT_TABLE() +}; + +#endif diff --git a/src/spek-spectrogram.vala b/src/spek-spectrogram.vala @@ -1,319 +0,0 @@ -/* spek-spectrogram.vala - * - * Copyright (C) 2010-2011 Alexander Kojevnikov <alexander@kojevnikov.com> - * - * Spek is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Spek is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Spek. If not, see <http://www.gnu.org/licenses/>. - */ - -using Cairo; -using Gdk; -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; - } - } -} diff --git a/src/spek-window.cc b/src/spek-window.cc @@ -0,0 +1,385 @@ +/* spek-window.cc + * + * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * + * Spek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Spek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Spek. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <pthread.h> + +#include <wx/aboutdlg.h> +#include <wx/artprov.h> +#include <wx/dnd.h> +#include <wx/filename.h> +#include <wx/protocol/http.h> +#include <wx/sstream.h> + +#include "spek-preferences-dialog.hh" +#include "spek-preferences.hh" +#include "spek-spectrogram.hh" + +#include "spek-window.hh" + +DECLARE_EVENT_TYPE(SPEK_NOTIFY_EVENT, -1) +DEFINE_EVENT_TYPE(SPEK_NOTIFY_EVENT) + +BEGIN_EVENT_TABLE(SpekWindow, wxFrame) + EVT_MENU(wxID_OPEN, SpekWindow::on_open) + EVT_MENU(wxID_SAVE, SpekWindow::on_save) + EVT_MENU(wxID_EXIT, SpekWindow::on_exit) + EVT_MENU(wxID_PREFERENCES, SpekWindow::on_preferences) + EVT_MENU(wxID_ABOUT, SpekWindow::on_about) + EVT_COMMAND(-1, SPEK_NOTIFY_EVENT, SpekWindow::on_notify) +END_EVENT_TABLE() + +// Forward declarations. +static void * check_version(void *); + +class SpekDropTarget : public wxFileDropTarget +{ +public: + SpekDropTarget(SpekWindow *window) : wxFileDropTarget(), window(window) {} + +protected: + virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames){ + if (filenames.GetCount() == 1) { + window->open(filenames[0]); + return true; + } + return false; + } + +private: + SpekWindow *window; +}; + +SpekWindow::SpekWindow(const wxString& path) : + path(path), wxFrame(NULL, -1, wxEmptyString) +{ + this->description = _("Spek - Acoustic Spectrum Analyser"); + SetTitle(this->description); + // TODO: test on all platforms + //SetIcon(wxIcon(wxT("spek"))); + + wxMenuBar *menu = new wxMenuBar(); + + wxMenu *menu_file = new wxMenu(); + wxMenuItem *menu_file_open = new wxMenuItem(menu_file, wxID_OPEN); + menu_file->Append(menu_file_open); + wxMenuItem *menu_file_save = new wxMenuItem(menu_file, wxID_SAVE); + menu_file->Append(menu_file_save); + menu_file->AppendSeparator(); + menu_file->Append(wxID_EXIT); + menu->Append(menu_file, _("&File")); // TODO: stock text + + wxMenu *menu_edit = new wxMenu(); + wxMenuItem *menu_edit_prefs = new wxMenuItem(menu_edit, wxID_PREFERENCES); + // TODO: check if this is needed + menu_edit_prefs->SetItemLabel(menu_edit_prefs->GetItemLabelText() + wxT("\tCtrl+E")); + menu_edit->Append(menu_edit_prefs); + menu->Append(menu_edit, _("&Edit")); // TODO: stock text + + wxMenu *menu_help = new wxMenu(); + wxMenuItem *menu_help_about = new wxMenuItem(menu_help, wxID_ABOUT); + // TODO: check if this is needed + menu_help_about->SetItemLabel(menu_help_about->GetItemLabelText() + wxT("\tF1")); + menu_help->Append(menu_help_about); + menu->Append(menu_help, _("&Help")); // TODO: stock text + + SetMenuBar(menu); + + wxToolBar *toolbar = CreateToolBar(); + // TODO: bundled file open/save icons suck, provide our own (tango?) + toolbar->AddTool( + wxID_OPEN, + menu_file_open->GetItemLabelText(), + wxArtProvider::GetBitmap(wxART_FILE_OPEN) + ); + toolbar->AddTool( + wxID_SAVE, + menu_file_save->GetItemLabelText(), + wxArtProvider::GetBitmap(wxART_FILE_SAVE) + ); + toolbar->Realize(); + + wxSizer *sizer = new wxBoxSizer(wxVERTICAL); + + // wxInfoBar is too limited, construct a custom one. + wxPanel *info_bar = new wxPanel(this); + info_bar->Hide(); + info_bar->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOTEXT)); + info_bar->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK)); + wxSizer *info_sizer = new wxBoxSizer(wxHORIZONTAL); + wxStaticText *label = new wxStaticText( + info_bar, -1, _("A new version of Spek is available, click to download.")); + label->SetCursor(*new wxCursor(wxCURSOR_HAND)); + label->Connect(wxEVT_LEFT_DOWN, wxCommandEventHandler(SpekWindow::on_visit)); + // This second Connect() handles clicks on the border + info_bar->Connect(wxEVT_LEFT_DOWN, wxCommandEventHandler(SpekWindow::on_visit)); + info_sizer->Add(label, 1, wxALIGN_CENTER_VERTICAL | wxALL, 6); + // TODO: gtk-close won't work on win/osx + wxBitmapButton *button = new wxBitmapButton( + info_bar, -1, wxArtProvider::GetBitmap(wxT("gtk-close")), + wxDefaultPosition, wxDefaultSize, wxNO_BORDER); + button->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(SpekWindow::on_close)); + info_sizer->Add(button, 0, wxALIGN_CENTER_VERTICAL); + info_bar->SetSizer(info_sizer); + sizer->Add(info_bar, 0, wxEXPAND); + + this->spectrogram = new SpekSpectrogram(this); + sizer->Add(this->spectrogram, 1, wxEXPAND); + + this->cur_dir = wxGetHomeDir(); + + if (!path.IsEmpty()) { + open(path); + } + + SetDropTarget(new SpekDropTarget(this)); + + SetSizer(sizer); + + pthread_t thread; + pthread_create(&thread, NULL, &check_version, this); +} + +void SpekWindow::open(const wxString& path) +{ + wxFileName file_name(path); + if (file_name.FileExists()) { + this->path = path; + wxString full_name = file_name.GetFullName(); + // TRANSLATORS: window title, %s is replaced with the file name + wxString title = wxString::Format(_("Spek - %s"), full_name.c_str()); + // TODO: make sure the above works on all platforms, both in x32 and x64. + SetTitle(title); + + this->spectrogram->open(path); + } +} + +// TODO: s/audio/media/ +static const char *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", + NULL +}; + +void SpekWindow::on_open(wxCommandEvent& event) +{ + static wxString filters = wxEmptyString; + static int filter_index = 1; + + if (filters.IsEmpty()) { + filters.Alloc(1024); + filters += _("All files"); + filters += wxT("|*.*|"); + filters += _("Audio files"); + filters += wxT("|"); + for (int i = 0; audio_extensions[i]; ++i) { + if (i) { + filters += wxT(";"); + } + filters += wxT("*."); + filters += wxString::FromAscii(audio_extensions[i]); + } + filters.Shrink(); + } + + wxFileDialog *dlg = new wxFileDialog( + this, + _("Open File"), + this->cur_dir, + wxEmptyString, + filters, + wxFD_OPEN + ); + dlg->SetFilterIndex(filter_index); + + if (dlg->ShowModal() == wxID_OK) { + this->cur_dir = dlg->GetDirectory(); + filter_index = dlg->GetFilterIndex(); + open(dlg->GetPath()); + } + + dlg->Destroy(); +} + +void SpekWindow::on_save(wxCommandEvent& event) +{ + static wxString filters = wxEmptyString; + + if (filters.IsEmpty()) { + filters = _("PNG images"); + filters += wxT("|*.png"); + } + + wxFileDialog *dlg = new wxFileDialog( + this, + _("Save Spectrogram"), + this->cur_dir, + wxEmptyString, + filters, + wxFD_SAVE + ); + + // Suggested name is <file_name>.png + wxString name = _("Untitled"); + if (!this->path.IsEmpty()) { + wxFileName file_name(this->path); + name = file_name.GetFullName(); + } + name += wxT(".png"); + dlg->SetFilename(name); + + if (dlg->ShowModal() == wxID_OK) { + this->cur_dir = dlg->GetDirectory(); + this->spectrogram->save(dlg->GetPath()); + } + + dlg->Destroy(); +} + +void SpekWindow::on_exit(wxCommandEvent& event) +{ + Close(true); +} + +void SpekWindow::on_preferences(wxCommandEvent& event) +{ + SpekPreferencesDialog dlg(this); + dlg.ShowModal(); +} + +void SpekWindow::on_about(wxCommandEvent& event) +{ + wxAboutDialogInfo info; + info.AddDeveloper(wxT("Alexander Kojevnikov")); + info.AddDeveloper(wxT("Fabian Deutsch")); + info.AddDeveloper(wxT("Jonathan Gonzalez V")); + info.AddDeveloper(wxT("Stefan Kost")); + info.AddDeveloper(wxT("Thibault North")); + info.AddArtist(wxT("Olga Vasylevska")); + // TRANSLATORS: Add your name here + wxString translator = _("translator-credits"); + if (translator != wxT("translator-credits")) { + info.AddTranslator(translator); + } + info.SetName(wxT("Spek")); + info.SetVersion(wxT(PACKAGE_VERSION)); + info.SetCopyright(_("Copyright (c) 2010-2012 Alexander Kojevnikov and contributors")); + info.SetDescription(this->description); +#ifdef OS_UNIX + info.SetWebSite(wxT("http://spek-project.org/"), _("Spek Website")); + // TODO + // info.SetIcon(); +#endif + wxAboutBox(info); +} + +void SpekWindow::on_notify(wxCommandEvent& event) +{ + this->GetSizer()->Show((size_t)0); + this->Layout(); +} + +void SpekWindow::on_visit(wxCommandEvent& event) +{ + wxLaunchDefaultBrowser(wxT("http://spek-project.org")); +} + +void SpekWindow::on_close(wxCommandEvent& event) +{ + wxWindow *self = ((wxWindow *)event.GetEventObject())->GetGrandParent(); + self->GetSizer()->Hide((size_t)0); + self->Layout(); +} + +static void * check_version(void *p) +{ + // Does the user want to check for updates? + SpekPreferences& prefs = SpekPreferences::get(); + if (!prefs.get_check_update()) { + return NULL; + } + + // Calculate the number of days since 0001-01-01, borrowed from GLib. + wxDateTime now = wxDateTime::Now(); + int year = now.GetYear() - 1; + int days = year * 365; + days += (year >>= 2); // divide by 4 and add + days -= (year /= 25); // divides original # years by 100 + days += year >> 2; // divides by 4, which divides original by 400 + days += now.GetDayOfYear(); + + // When was the last update? + int diff = days - prefs.get_last_update(); + if (diff < 7) { + return NULL; + } + + // Get the version number. + wxString version; + wxHTTP http; + if (http.Connect(wxT("spek-project.org"))) { + wxInputStream *stream = http.GetInputStream(wxT("/version")); + if (stream) { + wxStringOutputStream out(&version); + stream->Read(out); + version.Trim(); + delete stream; + } + } + + if (version.IsEmpty()) { + return NULL; + } + + if (version > wxT(PACKAGE_VERSION)) { + SpekWindow *self = (SpekWindow *)p; + wxCommandEvent event(SPEK_NOTIFY_EVENT, -1); + event.SetEventObject(self); + wxPostEvent(self, event); + } + + // Update the preferences. + prefs.set_check_update(true); + prefs.set_last_update(days); + return NULL; +} diff --git a/src/spek-window.hh b/src/spek-window.hh @@ -0,0 +1,50 @@ +/* spek-window.hh + * + * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * + * Spek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Spek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Spek. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef SPEK_WINDOW_HH_ +#define SPEK_WINDOW_HH_ + +#include <wx/wx.h> + +class SpekSpectrogram; + +class SpekWindow : public wxFrame +{ +public: + SpekWindow(const wxString& path); + void open(const wxString& path); + +private: + void on_open(wxCommandEvent& event); + void on_save(wxCommandEvent& event); + void on_exit(wxCommandEvent& event); + void on_preferences(wxCommandEvent& event); + void on_about(wxCommandEvent& event); + void on_notify(wxCommandEvent& event); + void on_visit(wxCommandEvent& event); + void on_close(wxCommandEvent& event); + + SpekSpectrogram *spectrogram; + wxString path; + wxString cur_dir; + wxString description; + + DECLARE_EVENT_TABLE() +}; + +#endif diff --git a/src/spek-window.vala b/src/spek-window.vala @@ -1,354 +0,0 @@ -/* spek-window.vala - * - * Copyright (C) 2010-2011 Alexander Kojevnikov <alexander@kojevnikov.com> - * - * Spek is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Spek is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Spek. If not, see <http://www.gnu.org/licenses/>. - */ - -using Gdk; -using Gtk; - -namespace Spek { - public class Window : Gtk.Window { - - private UIManager ui; - private InfoBar info_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 Gtk.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'> - <menuitem action='FileOpen'/> - <menuitem action='FileSave'/> - <separator/> - <menuitem action='FileQuit'/> - </menu> - <menu action='Edit'> - <menuitem action='EditPreferences'/> - </menu> - <menu action='Help'> - <menuitem action='HelpAbout'/> - </menu> - </menubar> - - <toolbar name='ToolBar'> - <toolitem action='FileOpen'/> - <toolitem action='FileSave'/> - <separator expand='true'/> - <toolitem action='HelpAbout'/> - </toolbar> -</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; - - info_bar = new InfoBar.with_buttons (Stock.OK, ResponseType.OK); - var label = new Label (null); - label.use_markup = true; - label.set_markup (_("A new version of Spek is available on <a href=\"http://www.spek-project.org\">www.spek-project.org</a>")); - label.ellipsize = Pango.EllipsizeMode.END; - label.xalign = 0f; - label.activate_link.connect (uri => { Platform.show_uri (uri); return true; }); - label.show(); - var content_area = (Container) info_bar.get_content_area(); - content_area.add(label); - info_bar.message_type = MessageType.INFO; - info_bar.response.connect(() => info_bar.hide()); - - 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 (info_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"; - } - // TRANSLATORS: Add your name here - 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 (() => { info_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.cc b/src/spek.cc @@ -0,0 +1,112 @@ +/* spek.cc + * + * Copyright (C) 2010-2012 Alexander Kojevnikov <alexander@kojevnikov.com> + * + * Spek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Spek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Spek. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <wx/cmdline.h> +#include <wx/log.h> +#include <wx/socket.h> + +#include "spek-audio.h" +#include "spek-platform.hh" +#include "spek-preferences.hh" + +#include "spek-window.hh" + +class Spek: public wxApp +{ +public: + Spek() : wxApp(), quit(false) {} + +protected: + virtual bool OnInit(); + virtual int OnRun(); + +private: + wxString path; + bool quit; +}; + +IMPLEMENT_APP(Spek) + +bool Spek::OnInit() +{ + wxInitAllImageHandlers(); + wxSocketBase::Initialize(); + + SpekPlatform::init(); + SpekPreferences::get().init(); + spek_audio_init(); + + static const wxCmdLineEntryDesc desc[] = {{ + wxCMD_LINE_SWITCH, + wxT_2("h"), + wxT_2("help"), + wxT_2("Show this help message"), + wxCMD_LINE_VAL_NONE, + wxCMD_LINE_OPTION_HELP + }, { + wxCMD_LINE_SWITCH, + wxT_2("V"), + wxT_2("version"), + wxT_2("Display the version and exit") + }, { + wxCMD_LINE_PARAM, + NULL, + NULL, + wxT_2("FILE"), + wxCMD_LINE_VAL_STRING, + wxCMD_LINE_PARAM_OPTIONAL + }, { + // TODO: use wxCMD_LINE_DESC_END after settling on wx29. + wxCMD_LINE_NONE, NULL, NULL, NULL, wxCMD_LINE_VAL_NONE, 0 + } + }; + + wxCmdLineParser parser(desc, argc, argv); + int ret = parser.Parse(true); + if (ret == 1) { + return false; + } + if (ret == -1) { + this->quit = true; + return true; + } + if (parser.Found(wxT("version"))) { + // TRANSLATORS: first %s is the package name, second %s is the package version. + wxPrintf(_("%s version %s"), wxT(PACKAGE_NAME), wxT(PACKAGE_VERSION)); + wxPrintf(wxT("\n")); + this->quit = true; + return true; + } + if (parser.GetParamCount()) { + this->path = parser.GetParam(); + } + + SpekWindow *window = new SpekWindow(this->path); + window->Show(true); + SetTopWindow(window); + return true; +} + +int Spek::OnRun() +{ + if (quit) { + return 0; + } + + return wxApp::OnRun(); +} diff --git a/src/spek.vala b/src/spek.vala @@ -1,75 +0,0 @@ -/* spek.vala - * - * Copyright (C) 2010-2011 Alexander Kojevnikov <alexander@kojevnikov.com> - * - * Spek is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Spek is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Spek. If not, see <http://www.gnu.org/licenses/>. - */ - -namespace Spek { - 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 } - }; - - int main (string[] args) { - Platform.fix_args (args); - - 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); - - 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 (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; - } -} -\ No newline at end of file diff --git a/vapi/Makefile.am b/vapi/Makefile.am @@ -1,8 +0,0 @@ -noinst_DATA = \ - config.vapi \ - spek-audio.vapi \ - spek-fft.vapi \ - spek-platform.vapi - -EXTRA_DIST = \ - $(noinst_DATA) diff --git a/vapi/config.vapi b/vapi/config.vapi @@ -1,16 +0,0 @@ -[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; - - /* 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 */ -} diff --git a/vapi/spek-audio.vapi b/vapi/spek-audio.vapi @@ -1,29 +0,0 @@ -[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; - - [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 +0,0 @@ -[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; - - [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 +0,0 @@ -[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 (); -}