Merge branch 'wx'

This commit is contained in:
Alexander Kojevnikov 2012-08-26 19:55:20 -07:00
commit 14e2cea74c
49 changed files with 2726 additions and 2128 deletions

5
.gitignore vendored

@ -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

@ -1,11 +1,8 @@
# Makefile.am
SUBDIRS = \
data \
man \
po \
src \
vapi
src
EXTRA_DIST = \
intltool-extract.in \

@ -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 "$@"
test -n "$NOCONFIGURE" || "$srcdir/configure" "$@"

@ -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])
AM_INIT_AUTOMAKE([1.11.1 foreign no-dist-gzip dist-xz])
AM_SILENT_RULES([yes])
# Enable silent rules is available
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
AM_MAINTAINER_MODE
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
AC_OUTPUT

21
dist/osx/README vendored

@ -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

34
dist/osx/README.md vendored Normal file

@ -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

@ -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

@ -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

@ -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
msgid "Cannot open input file"
msgstr ""
#: ../src/spek-audio.c:54
msgid "Cannot find stream info"
msgstr ""
#: ../src/spek-audio.c:66
msgid "The file contains no audio streams"
msgstr ""
#: ../src/spek-audio.c:73
msgid "Cannot find decoder"
msgstr ""
#: ../src/spek-audio.c:91
msgid "Unknown duration"
msgstr ""
#: ../src/spek-audio.c:95
msgid "No audio channels"
msgstr ""
#: ../src/spek-audio.c:99
msgid "Cannot open decoder"
msgstr ""
#: ../src/spek-audio.c:120
msgid "Unsupported sample format"
msgstr ""
#: ../src/spek-pipeline.vala:70
#: ../src/spek-audio-desc.cc:34
#, c-format
msgid "%d kbps"
msgstr ""
#: ../src/spek-pipeline.vala:73
#: ../src/spek-audio-desc.cc:37
#, 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-audio-desc.cc:65
msgid "Cannot open input file"
msgstr ""
#: ../src/spek-pipeline.vala:81
#, c-format
msgid "%d channel"
msgid_plural "%d channels"
msgstr[0] ""
msgstr[1] ""
#: ../src/spek-audio-desc.cc:68
msgid "Cannot find stream info"
msgstr ""
#: ../src/spek-audio-desc.cc:71
msgid "The file contains no audio streams"
msgstr ""
#: ../src/spek-audio-desc.cc:74
msgid "Cannot find decoder"
msgstr ""
#: ../src/spek-audio-desc.cc:77
msgid "Unknown duration"
msgstr ""
#: ../src/spek-audio-desc.cc:80
msgid "No audio channels"
msgstr ""
#: ../src/spek-audio-desc.cc:83
msgid "Cannot open decoder"
msgstr ""
#: ../src/spek-audio-desc.cc:86
msgid "Unsupported sample format"
msgstr ""
#. 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:"
#: ../src/spek-preferences-dialog.cc:82
msgid "Language:"
msgstr ""
#: ../src/spek-preferences-dialog.vala:86
msgid "Check for _updates"
#: ../src/spek-preferences-dialog.cc:97
msgid "Check for &updates"
msgstr ""
#. TRANSLATORS: keep "00" unchanged, it's used to calc the text width
#: ../src/spek-spectrogram.vala:200
msgid "00 kHz"
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"
#. 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:39
msgid "_Edit"
#. 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:41
msgid "_Help"
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
#: ../src/spek-window.cc:211
msgid "All files"
msgstr ""
#: ../src/spek-window.cc:213
msgid "Audio files"
msgstr ""
#: ../src/spek-window.cc:227
msgid "Open File"
msgstr ""
#: ../src/spek-window.vala:197
#: ../src/spek-window.cc:249
msgid "PNG images"
msgstr ""
#: ../src/spek-window.cc:255
msgid "Save Spectrogram"
msgstr ""
#. Suggested name is <file_name>.png
#: ../src/spek-window.vala:204
#: ../src/spek-window.cc:263
msgid "Untitled"
msgstr ""
#: ../src/spek-window.vala:259
msgid "Copyright © 2010-2011 Alexander Kojevnikov"
msgstr ""
#: ../src/spek-window.vala:264
msgid "Spek Website"
msgstr ""
#. TRANSLATORS: Add your name here
#: ../src/spek-window.vala:274
#: ../src/spek-window.cc:300
msgid "translator-credits"
msgstr ""
#: ../src/spek.vala:25
msgid "Display the version and exit"
#: ../src/spek-window.cc:306
msgid "Copyright (c) 2010-2012 Alexander Kojevnikov and contributors"
msgstr ""
#: ../src/spek.vala:42
msgid "[FILE]"
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 ""

@ -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)
VALAFLAGS = \
--thread \
--vapidir=$(srcdir)/../vapi \
--pkg config \
--pkg spek-audio \
--pkg spek-fft \
--pkg spek-platform \
@SPEK_PACKAGES@
spek_CFLAGS = \
$(FFMPEG_CFLAGS) \
$(WX_CFLAGS_ONLY)
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

95
src/spek-audio-desc.cc Normal file

@ -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;
}

28
src/spek-audio-desc.hh Normal file

@ -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

@ -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();
}
SpekAudioContext * spek_audio_open (const gchar *file_name) {
SpekAudioContext *cx;
int i;
const struct spek_audio_properties * spek_audio_get_properties(struct spek_audio_context *cx)
{
return &cx->properties;
}
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
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, file_name, NULL, NULL) != 0) {
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);
}

@ -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

45
src/spek-events.cc Normal file

@ -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);
}
}

53
src/spek-events.hh Normal file

@ -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

@ -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;
void spek_fft_execute(struct spek_fft_plan *p)
{
av_rdft_calc(p->cx, p->input);
// Calculate magnitudes.
int n = p->n;
av_rdft_calc (p->cx, p->input);
/* Calculate magnitudes */
p->output[0] = p->input[0] * p->input[0] / (n * n);
p->output[n / 2] = p->input[1] * p->input[1] / (n * n);
for (i = 1; i < n / 2; i++) {
gfloat val;
val = p->input[i * 2] * p->input[i * 2] + p->input[i * 2 + 1] * p->input[i * 2 + 1];
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);
}

@ -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
typedef struct {
/* Internal data */
RDFTContext *cx;
gint n;
gint threshold;
struct RDFTContext;
/* Exposed properties */
gfloat *input;
gfloat *output;
} SpekFftPlan;
struct spek_fft_plan
{
// Internal data.
struct RDFTContext *cx;
int n;
int threshold;
/* Allocate buffers and create a new FFT plan */
SpekFftPlan * spek_fft_plan_new (gint n, gint threshold);
// Exposed properties.
float *input;
float *output;
};
/* Execute the FFT on plan->input */
void spek_fft_execute (SpekFftPlan *p);
// Allocate buffers and create a new FFT plan.
struct spek_fft_plan * spek_fft_plan_new(int n, int threshold);
/* Destroy the plan and de-allocate buffers */
void spek_fft_destroy (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_delete(struct spek_fft_plan *p);
#ifdef __cplusplus
}
#endif
#endif

61
src/spek-palette.c Normal file

@ -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;
}

34
src/spek-palette.h Normal file

@ -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

368
src/spek-pipeline.c Normal file

@ -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;
}

44
src/spek-pipeline.h Normal file

@ -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

@ -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;
}
}
}

@ -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;
}

79
src/spek-platform.cc Normal file

@ -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;
}

@ -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

52
src/spek-platform.hh Normal file

@ -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

@ -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());
}

@ -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

@ -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 ();
}
}
}

100
src/spek-preferences.cc Normal file

@ -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();
}

47
src/spek-preferences.hh Normal file

@ -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

@ -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);
}
}
}
}

94
src/spek-ruler.cc Normal file

@ -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);
}
}

61
src/spek-ruler.hh Normal file

@ -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

@ -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 ();
}
}
}
}

371
src/spek-spectrogram.cc Normal file

@ -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);
}

57
src/spek-spectrogram.hh Normal file

@ -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

@ -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;
}
}
}

385
src/spek-window.cc Normal file

@ -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;
}

50
src/spek-window.hh Normal file

@ -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

@ -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;
}
}
}

112
src/spek.cc Normal file

@ -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();
}

@ -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;
}
}

@ -1,8 +0,0 @@
noinst_DATA = \
config.vapi \
spek-audio.vapi \
spek-fft.vapi \
spek-platform.vapi
EXTRA_DIST = \
$(noinst_DATA)

@ -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 */
}

@ -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 ();
}

@ -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 ();
}
}

@ -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 ();
}