/* * Copyright (C) 2005-2013 Team XBMC * http://xbmc.org * * 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, 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 XBMC; see the file COPYING. If not, see * . * */ #include "system.h" #include "Visualisation.h" #include "utils/fft.h" #include "GUIInfoManager.h" #include "Application.h" #include "guilib/GraphicContext.h" #include "guilib/WindowIDs.h" #include "music/tags/MusicInfoTag.h" #include "settings/Settings.h" #include "settings/AdvancedSettings.h" #include "settings/DisplaySettings.h" #include "windowing/WindowingFactory.h" #include "utils/URIUtils.h" #include "utils/StringUtils.h" #include "cores/IPlayer.h" #include "cores/AudioEngine/AEFactory.h" #ifdef TARGET_POSIX #include #include "filesystem/SpecialProtocol.h" #endif using namespace std; using namespace MUSIC_INFO; using namespace ADDON; CAudioBuffer::CAudioBuffer(int iSize) { m_iLen = iSize; m_pBuffer = new float[iSize]; } CAudioBuffer::~CAudioBuffer() { delete [] m_pBuffer; } const float* CAudioBuffer::Get() const { return m_pBuffer; } void CAudioBuffer::Set(const float* psBuffer, int iSize) { if (iSize<0) return; memcpy(m_pBuffer, psBuffer, iSize * sizeof(float)); for (int i = iSize; i < m_iLen; ++i) m_pBuffer[i] = 0; } bool CVisualisation::Create(int x, int y, int w, int h, void *device) { m_pInfo = new VIS_PROPS; #ifdef HAS_DX m_pInfo->device = g_Windowing.Get3DDevice(); #else m_pInfo->device = NULL; #endif m_pInfo->x = x; m_pInfo->y = y; m_pInfo->width = w; m_pInfo->height = h; m_pInfo->pixelRatio = g_graphicsContext.GetResInfo().fPixelRatio; m_pInfo->name = strdup(Name().c_str()); m_pInfo->presets = strdup(CSpecialProtocol::TranslatePath(Path()).c_str()); m_pInfo->profile = strdup(CSpecialProtocol::TranslatePath(Profile()).c_str()); m_pInfo->submodule = NULL; if (CAddonDll::Create() == ADDON_STATUS_OK) { // Start the visualisation std::string strFile = URIUtils::GetFileName(g_application.CurrentFile()); CLog::Log(LOGDEBUG, "Visualisation::Start()\n"); try { m_pStruct->Start(m_iChannels, m_iSamplesPerSec, m_iBitsPerSample, strFile.c_str()); } catch (std::exception e) { HandleException(e, "m_pStruct->Start() (CVisualisation::Create)"); return false; } GetPresets(); if (GetSubModules()) m_pInfo->submodule = strdup(CSpecialProtocol::TranslatePath(m_submodules.front()).c_str()); else m_pInfo->submodule = NULL; CreateBuffers(); CAEFactory::RegisterAudioCallback(this); return true; } return false; } void CVisualisation::Start(int iChannels, int iSamplesPerSec, int iBitsPerSample, const std::string &strSongName) { // notify visz. that new song has been started // pass it the nr of audio channels, sample rate, bits/sample and offcourse the songname if (Initialized()) { try { m_pStruct->Start(iChannels, iSamplesPerSec, iBitsPerSample, strSongName.c_str()); } catch (std::exception e) { HandleException(e, "m_pStruct->Start (CVisualisation::Start)"); } } } void CVisualisation::AudioData(const float* pAudioData, int iAudioDataLength, float *pFreqData, int iFreqDataLength) { // pass audio data to visz. // audio data: is short audiodata [channel][iAudioDataLength] containing the raw audio data // iAudioDataLength = length of audiodata array // pFreqData = fft-ed audio data // iFreqDataLength = length of pFreqData if (Initialized()) { try { m_pStruct->AudioData(pAudioData, iAudioDataLength, pFreqData, iFreqDataLength); } catch (std::exception e) { HandleException(e, "m_pStruct->AudioData (CVisualisation::AudioData)"); } } } void CVisualisation::Render() { // ask visz. to render itself g_graphicsContext.BeginPaint(); if (Initialized()) { try { m_pStruct->Render(); } catch (std::exception e) { HandleException(e, "m_pStruct->Render (CVisualisation::Render)"); } } g_graphicsContext.EndPaint(); } void CVisualisation::Stop() { CAEFactory::UnregisterAudioCallback(); if (Initialized()) { CAddonDll::Stop(); } } void CVisualisation::GetInfo(VIS_INFO *info) { if (Initialized()) { try { m_pStruct->GetInfo(info); } catch (std::exception e) { HandleException(e, "m_pStruct->GetInfo (CVisualisation::GetInfo)"); } } } bool CVisualisation::OnAction(VIS_ACTION action, void *param) { if (!Initialized()) return false; // see if vis wants to handle the input // returns false if vis doesnt want the input // returns true if vis handled the input try { if (action != VIS_ACTION_NONE && m_pStruct->OnAction) { // if this is a VIS_ACTION_UPDATE_TRACK action, copy relevant // tags from CMusicInfoTag to VisTag if ( action == VIS_ACTION_UPDATE_TRACK && param ) { const CMusicInfoTag* tag = (const CMusicInfoTag*)param; std::string artist(StringUtils::Join(tag->GetArtist(), g_advancedSettings.m_musicItemSeparator)); std::string albumArtist(StringUtils::Join(tag->GetAlbumArtist(), g_advancedSettings.m_musicItemSeparator)); std::string genre(StringUtils::Join(tag->GetGenre(), g_advancedSettings.m_musicItemSeparator)); VisTrack track; track.title = tag->GetTitle().c_str(); track.artist = artist.c_str(); track.album = tag->GetAlbum().c_str(); track.albumArtist = albumArtist.c_str(); track.genre = genre.c_str(); track.comment = tag->GetComment().c_str(); track.lyrics = tag->GetLyrics().c_str(); track.trackNumber = tag->GetTrackNumber(); track.discNumber = tag->GetDiscNumber(); track.duration = tag->GetDuration(); track.year = tag->GetYear(); track.rating = tag->GetRating(); return m_pStruct->OnAction(action, &track); } return m_pStruct->OnAction((int)action, param); } } catch (std::exception e) { HandleException(e, "m_pStruct->OnAction (CVisualisation::OnAction)"); } return false; } void CVisualisation::OnInitialize(int iChannels, int iSamplesPerSec, int iBitsPerSample) { if (!m_pStruct) return ; CLog::Log(LOGDEBUG, "OnInitialize() started"); m_iChannels = iChannels; m_iSamplesPerSec = iSamplesPerSec; m_iBitsPerSample = iBitsPerSample; UpdateTrack(); CLog::Log(LOGDEBUG, "OnInitialize() done"); } void CVisualisation::OnAudioData(const float* pAudioData, int iAudioDataLength) { if (!m_pStruct) return ; // FIXME: iAudioDataLength should never be less than 0 if (iAudioDataLength<0) return; // Save our audio data in the buffers unique_ptr pBuffer ( new CAudioBuffer(AUDIO_BUFFER_SIZE) ); pBuffer->Set(pAudioData, iAudioDataLength); m_vecBuffers.push_back( pBuffer.release() ); if ( (int)m_vecBuffers.size() < m_iNumBuffers) return ; unique_ptr ptrAudioBuffer ( m_vecBuffers.front() ); m_vecBuffers.pop_front(); // Fourier transform the data if the vis wants it... if (m_bWantsFreq) { const float *psAudioData = ptrAudioBuffer->Get(); memcpy(m_fFreq, psAudioData, AUDIO_BUFFER_SIZE * sizeof(float)); // FFT the data twochanwithwindow(m_fFreq, AUDIO_BUFFER_SIZE); // Normalize the data float fMinData = (float)AUDIO_BUFFER_SIZE * AUDIO_BUFFER_SIZE * 3 / 8 * 0.5 * 0.5; // 3/8 for the Hann window, 0.5 as minimum amplitude float fInvMinData = 1.0f/fMinData; for (int i = 0; i < AUDIO_BUFFER_SIZE + 2; i++) { m_fFreq[i] *= fInvMinData; } // Transfer data to our visualisation AudioData(psAudioData, AUDIO_BUFFER_SIZE, m_fFreq, AUDIO_BUFFER_SIZE); } else { // Transfer data to our visualisation AudioData(ptrAudioBuffer->Get(), AUDIO_BUFFER_SIZE, NULL, 0); } return ; } void CVisualisation::CreateBuffers() { ClearBuffers(); // Get the number of buffers from the current vis VIS_INFO info; m_pStruct->GetInfo(&info); m_iNumBuffers = info.iSyncDelay + 1; m_bWantsFreq = (info.bWantsFreq != 0); if (m_iNumBuffers > MAX_AUDIO_BUFFERS) m_iNumBuffers = MAX_AUDIO_BUFFERS; if (m_iNumBuffers < 1) m_iNumBuffers = 1; } void CVisualisation::ClearBuffers() { m_bWantsFreq = false; m_iNumBuffers = 0; while (!m_vecBuffers.empty()) { CAudioBuffer* pAudioBuffer = m_vecBuffers.front(); delete pAudioBuffer; m_vecBuffers.pop_front(); } for (int j = 0; j < AUDIO_BUFFER_SIZE*2; j++) { m_fFreq[j] = 0.0f; } } bool CVisualisation::UpdateTrack() { bool handled = false; if (Initialized()) { // get the current album art filename m_AlbumThumb = CSpecialProtocol::TranslatePath(g_infoManager.GetImage(MUSICPLAYER_COVER, WINDOW_INVALID)); // get the current track tag const CMusicInfoTag* tag = g_infoManager.GetCurrentSongTag(); if (m_AlbumThumb == "DefaultAlbumCover.png") m_AlbumThumb = ""; else CLog::Log(LOGDEBUG,"Updating visualisation albumart: %s", m_AlbumThumb.c_str()); // inform the visualisation of the current album art if (OnAction( VIS_ACTION_UPDATE_ALBUMART, (void*)( m_AlbumThumb.c_str() ) ) ) handled = true; // inform the visualisation of the current track's tag information if ( tag && OnAction( VIS_ACTION_UPDATE_TRACK, (void*)tag ) ) handled = true; } return handled; } bool CVisualisation::GetPresetList(std::vector &vecpresets) { vecpresets = m_presets; return !m_presets.empty(); } bool CVisualisation::GetPresets() { m_presets.clear(); char **presets = NULL; unsigned int entries = 0; try { entries = m_pStruct->GetPresets(&presets); } catch (std::exception e) { HandleException(e, "m_pStruct->OnAction (CVisualisation::GetPresets)"); return false; } if (presets && entries > 0) { for (unsigned i=0; i < entries; i++) { if (presets[i]) { m_presets.push_back(presets[i]); } } } return (!m_presets.empty()); } bool CVisualisation::GetSubModuleList(std::vector &vecmodules) { vecmodules = m_submodules; return !m_submodules.empty(); } bool CVisualisation::GetSubModules() { m_submodules.clear(); char **modules = NULL; unsigned int entries = 0; try { entries = m_pStruct->GetSubModules(&modules); } catch (...) { CLog::Log(LOGERROR, "Exception in Visualisation::GetSubModules()"); return false; } if (modules && entries > 0) { for (unsigned i=0; i < entries; i++) { if (modules[i]) { m_submodules.push_back(modules[i]); } } } return (!m_submodules.empty()); } std::string CVisualisation::GetFriendlyName(const std::string& strVisz, const std::string& strSubModule) { // should be of the format "moduleName (visName)" return strSubModule + " (" + strVisz + ")"; } bool CVisualisation::IsLocked() { if (!m_presets.empty()) { if (!m_pStruct) return false; return m_pStruct->IsLocked(); } return false; } void CVisualisation::Destroy() { // Free what was allocated in method CVisualisation::Create if (m_pInfo) { free((void *) m_pInfo->name); free((void *) m_pInfo->presets); free((void *) m_pInfo->profile); free((void *) m_pInfo->submodule); delete m_pInfo; m_pInfo = NULL; } CAddonDll::Destroy(); } unsigned CVisualisation::GetPreset() { unsigned index = 0; try { index = m_pStruct->GetPreset(); } catch(...) { return 0; } return index; } std::string CVisualisation::GetPresetName() { if (!m_presets.empty()) return m_presets[GetPreset()]; else return ""; }