/* This file is part of Clementine. Copyright 2010, David Sansome Clementine 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. Clementine 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 Clementine. If not, see . */ #include "projectmvisualisation.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "projectmpresetmodel.h" #include "visualisationcontainer.h" #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) #include #endif #ifdef USE_SYSTEM_PROJECTM #include #else #include "projectM.hpp" #endif #ifdef Q_OS_MAC #include #include "core/mac_startup.h" #else #include #endif ProjectMVisualisation::ProjectMVisualisation(VisualisationContainer* container) : QGraphicsScene(container), preset_model_(nullptr), mode_(Random), duration_(15), texture_size_(512), pixel_ratio_(container->devicePixelRatio()), container_(container) { connect(this, SIGNAL(sceneRectChanged(QRectF)), SLOT(SceneRectChanged(QRectF))); for (int i = 0; i < TOTAL_RATING_TYPES; ++i) default_rating_list_.push_back(3); } ProjectMVisualisation::~ProjectMVisualisation() {} void ProjectMVisualisation::InitProjectM() { // Find the projectM presets QStringList paths = QStringList() #ifdef USE_INSTALL_PREFIX << CMAKE_INSTALL_PREFIX "/share/clementine/projectm-presets" << CMAKE_INSTALL_PREFIX "/share/projectM/presets" #endif << "/usr/share/clementine/projectm-presets" << "/usr/local/share/clementine/projectm-presets" << "/usr/share/projectM/presets" << "/usr/local/share/projectM/presets"; #if defined(Q_OS_WIN32) paths.prepend(QCoreApplication::applicationDirPath() + "/projectm-presets"); #elif defined(Q_OS_MAC) paths.prepend(mac::GetResourcesPath() + "/projectm-presets"); #endif QString preset_path; for (const QString& path : paths) { if (!QFile::exists(path)) continue; // Don't use empty directories if (QDir(path).entryList(QDir::Files | QDir::NoDotAndDotDot).isEmpty()) continue; preset_path = path; break; } // Write an empty font out to a temporary directory. libprojectM dies if it's // compiled with FTGL support and you pass it an empty font URL, so we have // to give it a dummy font even though we won't use it. temporary_font_.reset(QTemporaryFile::createLocalFile(":blank.ttf")); const QString font_path = temporary_font_->fileName(); // Create projectM settings projectM::Settings s; s.meshX = 32; s.meshY = 24; s.textureSize = texture_size_; s.fps = 35; s.windowWidth = 512; s.windowHeight = 512; s.smoothPresetDuration = 5; s.presetDuration = duration_; s.presetURL = preset_path.toStdString(); s.shuffleEnabled = true; s.easterEgg = 0; // ?? s.softCutRatingsEnabled = false; s.menuFontURL = font_path.toStdString(); s.titleFontURL = font_path.toStdString(); projectm_.reset(new projectM(s)); preset_model_ = new ProjectMPresetModel(this, this); Load(); // Start at a random preset. if (projectm_->getPlaylistSize() > 0) { #if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0)) int selection = qrand() % projectm_->getPlaylistSize(); #else int selection = QRandomGenerator::global()->bounded(projectm_->getPlaylistSize()); #endif projectm_->selectPreset(selection, true); } if (font_path.isNull()) { qWarning("ProjectM presets could not be found, search path was:\n %s", paths.join("\n ").toLocal8Bit().constData()); QMessageBox::warning( nullptr, tr("Missing projectM presets"), tr("Clementine could not load any projectM visualisations. Check that " "you have installed Clementine properly.")); } } void ProjectMVisualisation::drawBackground(QPainter* p, const QRectF&) { p->beginNativePainting(); if (!projectm_) { InitProjectM(); } projectm_->projectM_resetGL(sceneRect().width() * pixel_ratio_, sceneRect().height() * pixel_ratio_); projectm_->renderFrame(); p->endNativePainting(); } void ProjectMVisualisation::SceneRectChanged(const QRectF& rect) { // NOTE: This should be updated on a QScreen dpi change signal. Accessing the // QScreen becomes a lot easier in Qt 5.14 with QWidget::screen(). pixel_ratio_ = container_->devicePixelRatio(); if (projectm_) projectm_->projectM_resetGL(rect.width() * pixel_ratio_, rect.height() * pixel_ratio_); } void ProjectMVisualisation::SetTextureSize(int size) { texture_size_ = size; if (projectm_) projectm_->changeTextureSize(texture_size_); } void ProjectMVisualisation::SetDuration(int seconds) { duration_ = seconds; if (projectm_) projectm_->changePresetDuration(duration_); Save(); } void ProjectMVisualisation::ConsumeBuffer(GstBuffer* buffer, int) { GstMapInfo map; gst_buffer_map(buffer, &map, GST_MAP_READ); const int samples_per_channel = map.size / sizeof(short) / 2; const short* data = reinterpret_cast(map.data); if (projectm_) { projectm_->pcm()->addPCM16Data(data, samples_per_channel); } gst_buffer_unmap(buffer, &map); gst_buffer_unref(buffer); } void ProjectMVisualisation::SetSelected(const QStringList& paths, bool selected) { for (const QString& path : paths) { int index = IndexOfPreset(path); if (selected && index == -1) { projectm_->addPresetURL(path.toStdString(), std::string(), default_rating_list_); } else if (!selected && index != -1) { projectm_->removePreset(index); } } Save(); } void ProjectMVisualisation::ClearSelected() { projectm_->clearPlaylist(); Save(); } int ProjectMVisualisation::IndexOfPreset(const QString& path) const { for (uint i = 0; i < projectm_->getPlaylistSize(); ++i) { if (QString::fromStdString(projectm_->getPresetURL(i)) == path) return i; } return -1; } void ProjectMVisualisation::Load() { QSettings s; s.beginGroup(VisualisationContainer::kSettingsGroup); mode_ = Mode(s.value("mode", 0).toInt()); duration_ = s.value("duration", duration_).toInt(); projectm_->changePresetDuration(duration_); projectm_->clearPlaylist(); switch (mode_) { case Random: for (int i = 0; i < preset_model_->all_presets_.count(); ++i) { projectm_->addPresetURL( preset_model_->all_presets_[i].path_.toStdString(), std::string(), default_rating_list_); preset_model_->all_presets_[i].selected_ = true; } break; case FromList: { QStringList paths(s.value("preset_paths").toStringList()); for (const QString& path : paths) { projectm_->addPresetURL(path.toStdString(), std::string(), default_rating_list_); preset_model_->MarkSelected(path, true); } } } } void ProjectMVisualisation::Save() { QStringList paths; for (const ProjectMPresetModel::Preset& preset : preset_model_->all_presets_) { if (preset.selected_) paths << preset.path_; } QSettings s; s.beginGroup(VisualisationContainer::kSettingsGroup); s.setValue("preset_paths", paths); s.setValue("mode", mode_); s.setValue("duration", duration_); } void ProjectMVisualisation::SetMode(Mode mode) { mode_ = mode; Save(); } QString ProjectMVisualisation::preset_url() const { return QString::fromStdString(projectm_->settings().presetURL); } void ProjectMVisualisation::SetImmediatePreset(const QString& path) { int index = IndexOfPreset(path); if (index == -1) { index = projectm_->addPresetURL(path.toStdString(), std::string(), default_rating_list_); } projectm_->selectPreset(index, true); } void ProjectMVisualisation::Lock(bool lock) { projectm_->setPresetLock(lock); if (!lock) Load(); }