PageRenderTime 82ms CodeModel.GetById 15ms app.highlight 62ms RepoModel.GetById 2ms app.codeStats 0ms

/xbmc/guilib/GUIVisualisationControl.cpp

http://github.com/xbmc/xbmc
C++ | 459 lines | 361 code | 77 blank | 21 comment | 66 complexity | 776662c9a66ceb2abf8836d2eb3247a5 MD5 | raw file
  1/*
  2 *  Copyright (C) 2005-2018 Team Kodi
  3 *  This file is part of Kodi - https://kodi.tv
  4 *
  5 *  SPDX-License-Identifier: GPL-2.0-or-later
  6 *  See LICENSES/README.md for more information.
  7 */
  8
  9#include "GUIVisualisationControl.h"
 10
 11#include "Application.h"
 12#include "GUIComponent.h"
 13#include "GUIInfoManager.h"
 14#include "GUIUserMessages.h"
 15#include "GUIWindowManager.h"
 16#include "ServiceBroker.h"
 17#include "cores/AudioEngine/Engines/ActiveAE/ActiveAE.h"
 18#include "filesystem/SpecialProtocol.h"
 19#include "guilib/guiinfo/GUIInfoLabels.h"
 20#include "input/Key.h"
 21#include "music/tags/MusicInfoTag.h"
 22#include "settings/AdvancedSettings.h"
 23#include "settings/Settings.h"
 24#include "settings/SettingsComponent.h"
 25#include "utils/URIUtils.h"
 26#include "utils/log.h"
 27
 28using namespace ADDON;
 29
 30#define LABEL_ROW1 10
 31#define LABEL_ROW2 11
 32#define LABEL_ROW3 12
 33
 34CAudioBuffer::CAudioBuffer(int iSize)
 35{
 36  m_iLen = iSize;
 37  m_pBuffer = new float[iSize];
 38}
 39
 40CAudioBuffer::~CAudioBuffer()
 41{
 42  delete [] m_pBuffer;
 43}
 44
 45const float* CAudioBuffer::Get() const
 46{
 47  return m_pBuffer;
 48}
 49
 50int CAudioBuffer::Size() const
 51{
 52  return m_iLen;
 53}
 54
 55void CAudioBuffer::Set(const float* psBuffer, int iSize)
 56{
 57  if (iSize < 0)
 58    return;
 59
 60  memcpy(m_pBuffer, psBuffer, iSize * sizeof(float));
 61  for (int i = iSize; i < m_iLen; ++i)
 62    m_pBuffer[i] = 0;
 63}
 64
 65CGUIVisualisationControl::CGUIVisualisationControl(int parentID, int controlID, float posX, float posY, float width, float height)
 66  : CGUIControl(parentID, controlID, posX, posY, width, height),
 67    m_callStart(false),
 68    m_alreadyStarted(false),
 69    m_attemptedLoad(false),
 70    m_updateTrack(false),
 71    m_instance(nullptr)
 72{
 73  ControlType = GUICONTROL_VISUALISATION;
 74}
 75
 76CGUIVisualisationControl::CGUIVisualisationControl(const CGUIVisualisationControl &from)
 77  : CGUIControl(from),
 78    m_callStart(false),
 79    m_alreadyStarted(false),
 80    m_attemptedLoad(false),
 81    m_instance(nullptr)
 82{
 83  ControlType = GUICONTROL_VISUALISATION;
 84}
 85
 86std::string CGUIVisualisationControl::Name()
 87{
 88  if (m_instance == nullptr)
 89    return "";
 90  return m_instance->Name();
 91}
 92
 93bool CGUIVisualisationControl::OnMessage(CGUIMessage &message)
 94{
 95  if (m_alreadyStarted)
 96  {
 97    switch (message.GetMessage())
 98    {
 99    case GUI_MSG_GET_VISUALISATION:
100      message.SetPointer(this);
101      return true;
102    case GUI_MSG_VISUALISATION_RELOAD:
103      FreeResources(true);
104      return true;
105    case GUI_MSG_PLAYBACK_STARTED:
106      m_updateTrack = true;
107      return true;
108    default:
109      break;
110    }
111  }
112  return CGUIControl::OnMessage(message);
113}
114
115bool CGUIVisualisationControl::OnAction(const CAction &action)
116{
117  if (m_alreadyStarted)
118  {
119    switch (action.GetID())
120    {
121    case ACTION_VIS_PRESET_NEXT:
122      m_instance->OnAction(VIS_ACTION_NEXT_PRESET, nullptr);
123      break;
124    case ACTION_VIS_PRESET_PREV:
125      m_instance->OnAction(VIS_ACTION_PREV_PRESET, nullptr);
126      break;
127    case ACTION_VIS_PRESET_RANDOM:
128      m_instance->OnAction(VIS_ACTION_RANDOM_PRESET, nullptr);
129      break;
130    case ACTION_VIS_RATE_PRESET_PLUS:
131      m_instance->OnAction(VIS_ACTION_RATE_PRESET_PLUS, nullptr);
132      break;
133    case ACTION_VIS_RATE_PRESET_MINUS:
134      m_instance->OnAction(VIS_ACTION_RATE_PRESET_MINUS, nullptr);
135      break;
136    case ACTION_VIS_PRESET_LOCK:
137      m_instance->OnAction(VIS_ACTION_LOCK_PRESET, nullptr);
138      break;
139    default:
140      break;
141    }
142    return true;
143  }
144
145  return CGUIControl::OnAction(action);
146}
147
148void CGUIVisualisationControl::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
149{
150  if (g_application.GetAppPlayer().IsPlayingAudio())
151  {
152    if (m_bInvalidated)
153      FreeResources(true);
154
155    if (!m_instance && !m_attemptedLoad)
156    {
157      InitVisualization();
158
159      m_attemptedLoad = true;
160    }
161    else if (m_callStart && m_instance)
162    {
163      CServiceBroker::GetWinSystem()->GetGfxContext().CaptureStateBlock();
164      if (m_alreadyStarted)
165      {
166        m_instance->Stop();
167        m_alreadyStarted = false;
168      }
169
170      std::string songTitle = URIUtils::GetFileName(g_application.CurrentFile());
171      const MUSIC_INFO::CMusicInfoTag* tag = CServiceBroker::GetGUI()->GetInfoManager().GetCurrentSongTag();
172      if (tag && !tag->GetTitle().empty())
173        songTitle = tag->GetTitle();
174      m_alreadyStarted = m_instance->Start(m_channels, m_samplesPerSec, m_bitsPerSample, songTitle);
175      CServiceBroker::GetWinSystem()->GetGfxContext().ApplyStateBlock();
176      m_callStart = false;
177      m_updateTrack = true;
178    }
179    else if (m_updateTrack)
180    {
181      /* Initial update of currently processed track */
182      UpdateTrack();
183      m_updateTrack = false;
184    }
185
186    if (m_instance && m_instance->IsDirty())
187      MarkDirtyRegion();
188  }
189
190  CGUIControl::Process(currentTime, dirtyregions);
191}
192
193void CGUIVisualisationControl::Render()
194{
195  if (m_instance && m_alreadyStarted)
196  {
197    /*
198     * set the viewport - note: We currently don't have any control over how
199     * the addon renders, so the best we can do is attempt to define
200     * a viewport??
201     */
202    CServiceBroker::GetWinSystem()->GetGfxContext().SetViewPort(m_posX, m_posY, m_width, m_height);
203    CServiceBroker::GetWinSystem()->GetGfxContext().CaptureStateBlock();
204    m_instance->Render();
205    CServiceBroker::GetWinSystem()->GetGfxContext().ApplyStateBlock();
206    CServiceBroker::GetWinSystem()->GetGfxContext().RestoreViewPort();
207  }
208
209  CGUIControl::Render();
210}
211
212void CGUIVisualisationControl::UpdateVisibility(const CGUIListItem *item/* = nullptr*/)
213{
214  // if made invisible, start timer, only free addonptr after
215  // some period, configurable by window class
216  CGUIControl::UpdateVisibility(item);
217  if (!IsVisible() && m_attemptedLoad)
218    FreeResources();
219}
220
221bool CGUIVisualisationControl::CanFocusFromPoint(const CPoint &point) const
222{ // mouse is allowed to focus this control, but it doesn't actually receive focus
223  return IsVisible() && HitTest(point);
224}
225
226void CGUIVisualisationControl::FreeResources(bool immediately)
227{
228  DeInitVisualization();
229
230  CGUIControl::FreeResources(immediately);
231
232  CLog::Log(LOGDEBUG, "FreeVisualisation() done");
233}
234
235void CGUIVisualisationControl::OnInitialize(int channels, int samplesPerSec, int bitsPerSample)
236{
237  m_channels = channels;
238  m_samplesPerSec = samplesPerSec;
239  m_bitsPerSample = bitsPerSample;
240  m_callStart = true;
241}
242
243void CGUIVisualisationControl::OnAudioData(const float* audioData, unsigned int audioDataLength)
244{
245  if (!m_instance || !m_alreadyStarted || !audioData || audioDataLength == 0)
246    return;
247
248  // Save our audio data in the buffers
249  std::unique_ptr<CAudioBuffer> pBuffer(new CAudioBuffer(audioDataLength));
250  pBuffer->Set(audioData, audioDataLength);
251  //m_vecBuffers.push_back(pBuffer.release());
252  m_vecBuffers.emplace_back(std::move(pBuffer));
253
254  if (m_vecBuffers.size() < m_numBuffers)
255    return;
256
257  std::unique_ptr<CAudioBuffer> ptrAudioBuffer = std::move(m_vecBuffers.front());
258  m_vecBuffers.pop_front();
259
260  // Fourier transform the data if the vis wants it...
261  if (m_wantsFreq)
262  {
263    const float *psAudioData = ptrAudioBuffer->Get();
264
265    if (!m_transform)
266      m_transform.reset(new RFFT(AUDIO_BUFFER_SIZE/2, false)); // half due to stereo
267
268    m_transform->calc(psAudioData, m_freq);
269
270    // Transfer data to our visualisation
271    m_instance->AudioData(psAudioData, ptrAudioBuffer->Size(), m_freq, AUDIO_BUFFER_SIZE/2); // half due to complex-conjugate
272  }
273  else
274  { // Transfer data to our visualisation
275    m_instance->AudioData(ptrAudioBuffer->Get(), ptrAudioBuffer->Size(), nullptr, 0);
276  }
277}
278
279void CGUIVisualisationControl::UpdateTrack()
280{
281  if (!m_instance || !m_alreadyStarted)
282    return;
283
284  // get the current album art filename
285  m_albumThumb = CSpecialProtocol::TranslatePath(CServiceBroker::GetGUI()->GetInfoManager().GetImage(MUSICPLAYER_COVER, WINDOW_INVALID));
286  if (m_albumThumb == "DefaultAlbumCover.png")
287    m_albumThumb = "";
288  else
289    CLog::Log(LOGDEBUG, "Updating visualization albumart: %s", m_albumThumb.c_str());
290
291  m_instance->OnAction(VIS_ACTION_UPDATE_ALBUMART, (const void*)(m_albumThumb.c_str()));
292
293  const MUSIC_INFO::CMusicInfoTag* tag = CServiceBroker::GetGUI()->GetInfoManager().GetCurrentSongTag();
294  if (!tag)
295    return;
296
297  std::string artist(tag->GetArtistString());
298  std::string albumArtist(tag->GetAlbumArtistString());
299  std::string genre(StringUtils::Join(tag->GetGenre(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator));
300
301  VisTrack track = {0};
302  track.title       = tag->GetTitle().c_str();
303  track.artist      = artist.c_str();
304  track.album       = tag->GetAlbum().c_str();
305  track.albumArtist = albumArtist.c_str();
306  track.genre       = genre.c_str();
307  track.comment     = tag->GetComment().c_str();
308  track.lyrics      = tag->GetLyrics().c_str();
309  track.trackNumber = tag->GetTrackNumber();
310  track.discNumber  = tag->GetDiscNumber();
311  track.duration    = tag->GetDuration();
312  track.year        = tag->GetYear();
313  track.rating      = tag->GetUserrating();
314
315  m_instance->OnAction(VIS_ACTION_UPDATE_TRACK, &track);
316}
317
318bool CGUIVisualisationControl::IsLocked()
319{
320  if (m_instance && m_alreadyStarted)
321    return m_instance->IsLocked();
322
323  return false;
324}
325
326bool CGUIVisualisationControl::HasPresets()
327{
328  if (m_instance && m_alreadyStarted)
329    return m_instance->HasPresets();
330
331  return false;
332}
333
334int CGUIVisualisationControl::GetActivePreset()
335{
336  if (m_instance && m_alreadyStarted)
337    return m_instance->GetActivePreset();
338
339  return -1;
340}
341
342void CGUIVisualisationControl::SetPreset(int idx)
343{
344  if (m_instance && m_alreadyStarted)
345    m_instance->OnAction(VIS_ACTION_LOAD_PRESET, static_cast<void*>(&idx));
346}
347
348std::string CGUIVisualisationControl::GetActivePresetName()
349{
350  if (m_instance && m_alreadyStarted)
351    return m_instance->GetActivePresetName();
352
353  return "";
354}
355
356bool CGUIVisualisationControl::GetPresetList(std::vector<std::string> &vecpresets)
357{
358  if (m_instance && m_alreadyStarted)
359    return m_instance->GetPresetList(vecpresets);
360
361  return false;
362}
363
364bool CGUIVisualisationControl::InitVisualization()
365{
366  const ADDON::BinaryAddonBasePtr addonBase = CServiceBroker::GetBinaryAddonManager().GetInstalledAddonInfo(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_MUSICPLAYER_VISUALISATION), ADDON::ADDON_VIZ);
367  if (!addonBase)
368    return false;
369
370  CServiceBroker::GetActiveAE()->RegisterAudioCallback(this);
371
372  CServiceBroker::GetWinSystem()->GetGfxContext().CaptureStateBlock();
373
374  float x = CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalXCoord(GetXPosition(), GetYPosition());
375  float y = CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalYCoord(GetXPosition(), GetYPosition());
376  float w = CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalXCoord(GetXPosition() + GetWidth(), GetYPosition() + GetHeight()) - x;
377  float h = CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalYCoord(GetXPosition() + GetWidth(), GetYPosition() + GetHeight()) - y;
378  if (x < 0)
379    x = 0;
380  if (y < 0)
381    y = 0;
382  if (x + w > CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth())
383    w = CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth() - x;
384  if (y + h > CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight())
385    h = CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight() - y;
386
387  m_instance = new ADDON::CVisualization(addonBase, x, y, w, h);
388  CreateBuffers();
389
390  m_alreadyStarted = false;
391  CServiceBroker::GetWinSystem()->GetGfxContext().ApplyStateBlock();
392  return true;
393}
394
395void CGUIVisualisationControl::DeInitVisualization()
396{
397  if (!m_attemptedLoad)
398    return;
399
400  IAE * ae = CServiceBroker::GetActiveAE();
401  if (ae)
402    ae->UnregisterAudioCallback(this);
403
404  m_attemptedLoad = false;
405
406  CGUIMessage msg(GUI_MSG_VISUALISATION_UNLOADING, m_controlID, 0);
407  CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
408
409  CLog::Log(LOGDEBUG, "FreeVisualisation() started");
410
411  if (m_instance)
412  {
413    if (m_alreadyStarted)
414    {
415      CServiceBroker::GetWinSystem()->GetGfxContext().CaptureStateBlock();
416      m_instance->Stop();
417      CServiceBroker::GetWinSystem()->GetGfxContext().ApplyStateBlock();
418      m_alreadyStarted = false;
419    }
420
421    delete m_instance;
422    m_instance = nullptr;
423  }
424
425  ClearBuffers();
426}
427
428void CGUIVisualisationControl::CreateBuffers()
429{
430  ClearBuffers();
431
432  // Get the number of buffers from the current vis
433  VIS_INFO info { false, 0 };
434
435  if (m_instance)
436    m_instance->GetInfo(&info);
437
438  m_numBuffers = info.iSyncDelay + 1;
439  m_wantsFreq = info.bWantsFreq;
440  if (m_numBuffers > MAX_AUDIO_BUFFERS)
441    m_numBuffers = MAX_AUDIO_BUFFERS;
442  if (m_numBuffers < 1)
443    m_numBuffers = 1;
444}
445
446void CGUIVisualisationControl::ClearBuffers()
447{
448  m_wantsFreq = false;
449  m_numBuffers = 0;
450  m_vecBuffers.clear();
451
452  for (float& freq : m_freq)
453  {
454    freq = 0.0f;
455  }
456
457  if (m_transform)
458    m_transform.reset();
459}