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