From 75f9f840f7e12d61f1fa8bd3bdcc17096eee4a6f Mon Sep 17 00:00:00 2001 From: Andrzej Rybczak Date: Fri, 28 Aug 2009 00:38:29 +0200 Subject: [PATCH] new screen: music visualizer --- configure.in | 14 +++++ doc/config | 17 ++++++ doc/keys | 2 + doc/ncmpcpp.1 | 3 ++ src/Makefile.am | 6 ++- src/help.cpp | 3 ++ src/ncmpcpp.cpp | 18 +++++++ src/settings.cpp | 9 ++++ src/settings.h | 2 + src/visualizer.cpp | 131 +++++++++++++++++++++++++++++++++++++++++++++ src/visualizer.h | 73 +++++++++++++++++++++++++ 11 files changed, 276 insertions(+), 2 deletions(-) create mode 100644 src/visualizer.cpp create mode 100644 src/visualizer.h diff --git a/configure.in b/configure.in index 9807a1b0..a01a9ea8 100644 --- a/configure.in +++ b/configure.in @@ -10,6 +10,7 @@ AC_PROG_CXX AM_PROG_LIBTOOL AC_ARG_ENABLE(outputs, AS_HELP_STRING([--enable-outputs], [Enable outputs screen @<:@default=no@:>@]), [outputs=$enableval], [outputs=no]) +AC_ARG_ENABLE(visualizer, AS_HELP_STRING([--enable-visualizer], [Enable music visualizer screen @<:@default=no@:>@]), [visualizer=$enableval], [visualizer=no]) AC_ARG_ENABLE(clock, AS_HELP_STRING([--enable-clock], [Enable clock screen @<:@default=no@:>@]), [clock=$enableval], [clock=no]) AC_ARG_ENABLE(unicode, AS_HELP_STRING([--enable-unicode], [Enable utf8 support @<:@default=yes@:>@]), [unicode=$enableval], [unicode=yes]) AC_ARG_WITH(curl, AS_HELP_STRING([--with-curl], [Enable fetching lyrics from the Internet @<:@default=auto@:>@]), [curl=$withval], [curl=auto]) @@ -88,6 +89,19 @@ if test "$pdcurses" != "no" ; then fi AC_CHECK_HEADERS([curses.h], , AC_MSG_ERROR([missing ncurses.h header])) +dnl ====================== +dnl = checking for fftw3 = +dnl ====================== +if test "$visualizer" = "yes" ; then + PKG_CHECK_MODULES([fftw3], [fftw3 >= 3], , AC_MSG_ERROR([fftw3 library is required!])) + AC_SUBST(fftw3_LIBS) + AC_SUBST(fftw3_CFLAGS) + CPPFLAGS="$CPPFLAGS $fftw3_CFLAGS" + LDFLAGS="$LDFLAGS $fftw3_LIBS" + AC_CHECK_HEADERS([fftw3.h], , AC_MSG_ERROR([missing fftw3.h header])) + AC_DEFINE([ENABLE_VISUALIZER], [1], [enables music visualizer screen]) +fi + dnl ================================= dnl = checking for curl and pthread = dnl ================================= diff --git a/doc/config b/doc/config index 7c75ae78..f2f559f0 100644 --- a/doc/config +++ b/doc/config @@ -17,6 +17,23 @@ # #mpd_crossfade_time = "5" # +##### music visualizer ##### +## +## Note: In order to make music visualizer work you'll +## need to use mpd fifo output, whose format parameter +## has to be set to 44100:16:1. Example configuration: +## (it has to be put into mpd.conf) +## +## audio_output { +## type "fifo" +## name "My FIFO" +## path "/tmp/mpd.fifo" +## format "44100:16:1" +## } +## +# +#visualizer_fifo_path = "" +# ##### system encoding ##### ## ## if you use encoding other than utf8, set it in diff --git a/doc/keys b/doc/keys index 62db6047..338f1045 100644 --- a/doc/keys +++ b/doc/keys @@ -58,6 +58,8 @@ # #key_outputs = '8' 272 # +#key_music_visualizer = '9' 273 +# #key_clock = '0' 274 # #key_stop = 's' diff --git a/doc/ncmpcpp.1 b/doc/ncmpcpp.1 index 6894e222..2ccef2cf 100644 --- a/doc/ncmpcpp.1 +++ b/doc/ncmpcpp.1 @@ -66,6 +66,9 @@ Set connection timeout to MPD to given value. .B mpd_crossfade_time = SECONDS Default number of seconds to crossfade, if enabled by ncmpcpp. .TP +.B fifo_visualizer_path = PATH +Path to mpd fifo output. This is needed to make music visualizer work (note that output sound format of this fifo has to be 44100:16:1) +.TP .B system_encoding = ENCODING If you use encoding other than utf8, set it in order to handle utf8 encoded strings properly. .TP diff --git a/src/Makefile.am b/src/Makefile.am index e329b887..6f972125 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2,7 +2,8 @@ bin_PROGRAMS = ncmpcpp ncmpcpp_SOURCES = browser.cpp charset.cpp clock.cpp display.cpp help.cpp \ helpers.cpp info.cpp libmpdclient.c lyrics.cpp media_library.cpp menu.cpp misc.cpp \ mpdpp.cpp ncmpcpp.cpp outputs.cpp playlist.cpp playlist_editor.cpp scrollpad.cpp \ - search_engine.cpp settings.cpp song.cpp status.cpp str_pool.c tag_editor.cpp window.cpp + search_engine.cpp settings.cpp song.cpp status.cpp str_pool.c tag_editor.cpp \ + visualizer.cpp window.cpp # set the include path found by configure INCLUDES= $(all_includes) @@ -11,4 +12,5 @@ INCLUDES= $(all_includes) ncmpcpp_LDFLAGS = $(all_libraries) noinst_HEADERS = browser.h charset.h clock.h display.h global.h help.h \ helpers.h home.h info.h lyrics.h media_library.h menu.h mpdpp.h outputs.h \ - playlist_editor.h screen.h scrollpad.h search_engine.h settings.h song.h tag_editor.h window.h + playlist_editor.h screen.h scrollpad.h search_engine.h settings.h song.h tag_editor.h \ + visualizer.h window.h diff --git a/src/help.cpp b/src/help.cpp index 9d21fcd2..54f42c5f 100644 --- a/src/help.cpp +++ b/src/help.cpp @@ -147,6 +147,9 @@ void Help::GetKeybindings() # ifdef ENABLE_OUTPUTS *w << DisplayKeys(Key.Outputs) << "Outputs\n"; # endif // ENABLE_OUTPUTS +# ifdef ENABLE_VISUALIZER + *w << DisplayKeys(Key.Visualizer) << "Music visualizer\n"; +# endif // ENABLE_VISUALIZER # ifdef ENABLE_CLOCK *w << DisplayKeys(Key.Clock) << "Clock screen\n"; # endif // ENABLE_CLOCK diff --git a/src/ncmpcpp.cpp b/src/ncmpcpp.cpp index c31bd785..2df82800 100644 --- a/src/ncmpcpp.cpp +++ b/src/ncmpcpp.cpp @@ -48,6 +48,7 @@ #include "outputs.h" #include "status.h" #include "tag_editor.h" +#include "visualizer.h" #define CHECK_PLAYLIST_FOR_FILTERING \ if (myPlaylist->Main()->isFiltered()) \ @@ -248,6 +249,9 @@ int main(int argc, char *argv[]) # ifdef HAVE_TAGLIB_H *wHeader << " " << fmtBold << char(Key.TagEditor[0]) << fmtBoldEnd << ":Tag editor"; # endif // HAVE_TAGLIB_H +# ifdef ENABLE_VISUALIZER + *wHeader << " " << fmtBold << char(Key.Visualizer[0]) << fmtBoldEnd << ":Music visualizer"; +# endif // ENABLE_VISUALIZER # ifdef ENABLE_CLOCK *wHeader << " " << fmtBold << char(Key.Clock[0]) << fmtBoldEnd << ":Clock"; # endif // ENABLE_CLOCK @@ -435,6 +439,14 @@ int main(int argc, char *argv[]) myTagEditor->hasToBeResized = 1; # endif // HAVE_TAGLIB_H +# ifdef ENABLE_VISUALIZER + myVisualizer->hasToBeResized = 1; +# endif // ENABLE_VISUALIZER + +# ifdef ENABLE_OUTPUTS + myOutputs->hasToBeResized = 1; +# endif // ENABLE_OUTPUTS + # ifdef ENABLE_CLOCK myClock->hasToBeResized = 1; # endif // ENABLE_CLOCK @@ -1983,6 +1995,12 @@ int main(int argc, char *argv[]) myOutputs->SwitchTo(); } # endif // ENABLE_OUTPUTS +# ifdef ENABLE_VISUALIZER + else if (Keypressed(input, Key.Visualizer)) + { + myVisualizer->SwitchTo(); + } +# endif // ENABLE_VISUALIZER # ifdef ENABLE_CLOCK else if (Keypressed(input, Key.Clock)) { diff --git a/src/settings.cpp b/src/settings.cpp index 6068efe0..83b44a5f 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -117,6 +117,7 @@ void DefaultKeys(ncmpcpp_keys &keys) keys.PlaylistEditor[0] = '6'; keys.TagEditor[0] = '7'; keys.Outputs[0] = '8'; + keys.Visualizer[0] = '9'; keys.Clock[0] = '0'; keys.Stop[0] = 's'; keys.Pause[0] = 'P'; @@ -187,6 +188,7 @@ void DefaultKeys(ncmpcpp_keys &keys) keys.PlaylistEditor[1] = 270; keys.TagEditor[1] = 271; keys.Outputs[1] = 272; + keys.Visualizer[1] = 273; keys.Clock[1] = 274; keys.Stop[1] = null_key; keys.Pause[1] = null_key; @@ -369,6 +371,8 @@ void ReadKeys(ncmpcpp_keys &keys) GetKeys(key, keys.TagEditor); else if (key.find("key_outputs ") != std::string::npos) GetKeys(key, keys.Outputs); + else if (key.find("key_music_visualizer ") != std::string::npos) + GetKeys(key, keys.Visualizer); else if (key.find("key_clock ") != std::string::npos) GetKeys(key, keys.Clock); else if (key.find("key_stop ") != std::string::npos) @@ -496,6 +500,11 @@ void ReadConfiguration(ncmpcpp_config &conf) conf.mpd_music_dir = v + "/"; } } + if (cl.find("visualizer_fifo_path") != std::string::npos) + { + if (!v.empty()) + conf.visualizer_fifo_path = v; + } else if (cl.find("mpd_port") != std::string::npos) { if (StrToInt(v)) diff --git a/src/settings.h b/src/settings.h index 844cb099..86c9e5ba 100644 --- a/src/settings.h +++ b/src/settings.h @@ -71,6 +71,7 @@ struct ncmpcpp_keys int PlaylistEditor[2]; int TagEditor[2]; int Outputs[2]; + int Visualizer[2]; int Clock[2]; int Stop[2]; int Pause[2]; @@ -126,6 +127,7 @@ struct ncmpcpp_config { std::string mpd_host; std::string mpd_music_dir; + std::string visualizer_fifo_path; std::string empty_tag; std::string song_list_columns_format; std::string song_list_format; diff --git a/src/visualizer.cpp b/src/visualizer.cpp new file mode 100644 index 00000000..224d147d --- /dev/null +++ b/src/visualizer.cpp @@ -0,0 +1,131 @@ +/*************************************************************************** + * Copyright (C) 2008-2009 by Andrzej Rybczak * + * electricityispower@gmail.com * + * * + * This program 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 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "visualizer.h" + +#ifdef ENABLE_VISUALIZER + +#include "global.h" + +#include +#include +#include +#include +#include + +using Global::myScreen; +using Global::MainStartY; +using Global::MainHeight; + +Visualizer *myVisualizer = new Visualizer; + +const unsigned Visualizer::Samples = 2048; +const unsigned Visualizer::FFTResults = Samples/2+1; + +void Visualizer::Init() +{ + w = new Window(0, MainStartY, COLS, MainHeight, "", Config.main_color, brNone); + w->SetTimeout(Config.visualizer_fifo_path.empty() ? ncmpcpp_window_timeout : 40 /* this gives us 25 fps */); + + itsFifo = -1; + itsFreqsMagnitude = new unsigned[FFTResults]; + itsInput = static_cast(fftw_malloc(sizeof(double)*Samples)); + itsOutput = static_cast(fftw_malloc(sizeof(fftw_complex)*FFTResults)); + itsPlan = fftw_plan_dft_r2c_1d(Samples, itsInput, itsOutput, FFTW_ESTIMATE); + + isInitialized = 1; +} + +void Visualizer::SwitchTo() +{ + if (myScreen == this) + return; + + if (!isInitialized) + Init(); + + if (hasToBeResized) + Resize(); + + myScreen = this; + w->Clear(); + + if (itsFifo < 0 && (itsFifo = open(Config.visualizer_fifo_path.c_str(), O_RDONLY | O_NONBLOCK)) < 0) + ShowMessage("Couldn't open fifo for reading PCM data: %s", strerror(errno)); + + Global::RedrawHeader = 1; +} + +void Visualizer::Resize() +{ + w->Resize(COLS, MainHeight); + w->MoveTo(0, MainStartY); + hasToBeResized = 0; +} + +std::basic_string Visualizer::Title() +{ + return U("Music visualizer"); +} + +void Visualizer::Update() +{ + if (itsFifo < 0) + return; + + // if mpd is stopped, clear the screen + if (Mpd.GetState() < MPD::psPlay) + { + w->Clear(); + return; + } + + // it supports only PCM in format 44100:16:1 + static int16_t buf[Samples]; + ssize_t data = read(itsFifo, buf, sizeof(buf)); + if (data < 0) // no data available in fifo + return; + + // zero old values + std::fill(buf+data/sizeof(int16_t), buf+Samples, 0); + for (unsigned i = 0; i < Samples; ++i) + itsInput[i] = buf[i]; + + fftw_execute(itsPlan); + + // count magnitude of each frequency and scale it to fit the screen + for (unsigned i = 0; i < FFTResults; ++i) + itsFreqsMagnitude[i] = sqrt(itsOutput[i][0]*itsOutput[i][0] + itsOutput[i][1]*itsOutput[i][1])/1e5*LINES/5; + + w->Clear(0); + const int freqs_per_col = FFTResults/COLS /* cut bandwidth a little to achieve better look */ * 4/5; + for (int i = 0; i < COLS; ++i) + { + size_t bar_height = 0; + for (int j = 0; j < freqs_per_col; ++j) + bar_height += itsFreqsMagnitude[i*freqs_per_col+j]; + bar_height = std::min(bar_height/freqs_per_col, Global::MainHeight); + mvwvline(w->Raw(), Global::MainHeight-bar_height, i, 0, bar_height); + } + w->Refresh(); +} + +#endif // ENABLE_VISUALIZER + diff --git a/src/visualizer.h b/src/visualizer.h new file mode 100644 index 00000000..fb6fa702 --- /dev/null +++ b/src/visualizer.h @@ -0,0 +1,73 @@ +/*************************************************************************** + * Copyright (C) 2008-2009 by Andrzej Rybczak * + * electricityispower@gmail.com * + * * + * This program 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 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _VISUALIZER_H +#define _VISUALIZER_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef ENABLE_VISUALIZER + +#include "window.h" +#include "screen.h" + +#include + +class Visualizer : public Screen +{ + public: + virtual void SwitchTo(); + virtual void Resize(); + + virtual std::basic_string Title(); + + virtual void Update(); + virtual void Scroll(Where, const int *) { } + + virtual void EnterPressed() { } + virtual void SpacePressed() { } + virtual void MouseButtonPressed(MEVENT) { } + + virtual NCurses::List *GetList() { return 0; } + + virtual bool allowsSelection() { return false; } + + protected: + virtual void Init(); + + private: + int itsFifo; + unsigned *itsFreqsMagnitude; + double *itsInput; + fftw_complex *itsOutput; + fftw_plan itsPlan; + + static const unsigned Samples; + static const unsigned FFTResults; +}; + +extern Visualizer *myVisualizer; + +#endif // ENABLE_VISUALIZER + +#endif +