Merge pull request #11828 from RomanPudashkin/install_sound_font

[MU4] Fix #11549: Reinstate drag-and-drop interaction for SoundFont installation
This commit is contained in:
RomanPudashkin 2022-06-02 12:42:51 +02:00 committed by GitHub
commit 514ddcbb0a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 283 additions and 42 deletions

View file

@ -27,8 +27,9 @@
#include <QWindow>
#include <QMimeData>
#include "translation.h"
#include "audio/synthtypes.h"
#include "translation.h"
#include "log.h"
using namespace mu::appshell;
@ -70,9 +71,10 @@ void ApplicationActionController::onDragMoveEvent(QDragMoveEvent* event)
const QMimeData* mime = event->mimeData();
QList<QUrl> urls = mime->urls();
if (urls.count() > 0) {
QString file = urls.first().toLocalFile();
LOGD() << file;
if (projectFilesController()->isFileSupported(io::path_t(file))) {
io::path_t filePath = io::path_t(urls.first().toLocalFile());
LOGD() << filePath;
if (projectFilesController()->isFileSupported(filePath) || audio::synth::isSoundFont(filePath)) {
event->setDropAction(Qt::LinkAction);
event->acceptProposedAction();
}
@ -84,9 +86,17 @@ void ApplicationActionController::onDropEvent(QDropEvent* event)
const QMimeData* mime = event->mimeData();
QList<QUrl> urls = mime->urls();
if (urls.count() > 0) {
QString file = urls.first().toLocalFile();
LOGD() << file;
Ret ret = projectFilesController()->openProject(io::path_t(file));
io::path_t filePath = io::path_t(urls.first().toLocalFile());
LOGD() << filePath;
Ret ret = make_ok();
if (projectFilesController()->isFileSupported(filePath)) {
ret = projectFilesController()->openProject(filePath);
} else if (audio::synth::isSoundFont(filePath)) {
ret = soundFontRepository()->addSoundFont(filePath);
}
if (ret) {
event->accept();
} else {

View file

@ -37,6 +37,7 @@
#include "iappshellconfiguration.h"
#include "multiinstances/imultiinstancesprovider.h"
#include "project/iprojectfilescontroller.h"
#include "audio/isoundfontrepository.h"
#include "istartupscenario.h"
namespace mu::appshell {
@ -50,6 +51,7 @@ class ApplicationActionController : public QObject, public IApplicationActionCon
INJECT(appshell, IAppShellConfiguration, configuration)
INJECT(appshell, mi::IMultiInstancesProvider, multiInstancesProvider)
INJECT(appshell, project::IProjectFilesController, projectFilesController)
INJECT(appshell, audio::ISoundFontRepository, soundFontRepository)
INJECT(appshell, IStartupScenario, startupScenario)
public:

View file

@ -73,6 +73,7 @@ set(MODULE_SRC
${CMAKE_CURRENT_LIST_DIR}/isynthresolver.h
${CMAKE_CURRENT_LIST_DIR}/ifxresolver.h
${CMAKE_CURRENT_LIST_DIR}/iplayback.h
${CMAKE_CURRENT_LIST_DIR}/isoundfontrepository.h
# Common internal
${CMAKE_CURRENT_LIST_DIR}/internal/iaudiobuffer.h
@ -86,6 +87,8 @@ set(MODULE_SRC
${CMAKE_CURRENT_LIST_DIR}/internal/audiothread.h
${CMAKE_CURRENT_LIST_DIR}/internal/audiosanitizer.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/audiosanitizer.h
${CMAKE_CURRENT_LIST_DIR}/internal/soundfontrepository.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/soundfontrepository.h
# Driver
${DRIVER_SRC}

View file

@ -36,6 +36,8 @@
#include "internal/worker/audioengine.h"
#include "internal/worker/playback.h"
#include "internal/soundfontrepository.h"
// synthesizers
#include "internal/synthesizers/fluidsynth/fluidresolver.h"
#include "internal/synthesizers/synthresolver.h"
@ -63,6 +65,8 @@ static std::shared_ptr<SynthResolver> s_synthResolver = std::make_shared<SynthRe
static std::shared_ptr<Playback> s_playbackFacade = std::make_shared<Playback>();
static std::shared_ptr<SoundFontRepository> s_soundFontRepository = std::make_shared<SoundFontRepository>();
#ifdef Q_OS_LINUX
#include "internal/platform/lin/linuxaudiodriver.h"
static std::shared_ptr<IAudioDriver> s_audioDriver = std::shared_ptr<IAudioDriver>(new LinuxAudioDriver());
@ -109,6 +113,8 @@ void AudioModule::registerExports()
ioc()->registerExport<ISynthResolver>(moduleName(), s_synthResolver);
ioc()->registerExport<IFxResolver>(moduleName(), s_fxResolver);
ioc()->registerExport<ISoundFontRepository>(moduleName(), s_soundFontRepository);
}
void AudioModule::registerResources()
@ -158,6 +164,7 @@ void AudioModule::onInit(const framework::IApplication::RunMode& mode)
// Init configuration
s_audioConfiguration->init();
s_soundFontRepository->init();
s_audioBuffer->init(s_audioConfiguration->audioChannelsCount());
@ -195,8 +202,7 @@ void AudioModule::onInit(const framework::IApplication::RunMode& mode)
AudioEngine::instance()->setSampleRate(activeSpec.sampleRate);
AudioEngine::instance()->setReadBufferSize(activeSpec.samples);
auto fluidResolver = std::make_shared<FluidResolver>(s_audioConfiguration->soundFontDirectories(),
s_audioConfiguration->soundFontDirectoriesChanged());
auto fluidResolver = std::make_shared<FluidResolver>();
s_synthResolver->registerResolver(AudioSourceType::Fluid, fluidResolver);
s_synthResolver->init(s_audioConfiguration->defaultAudioInputParams());

View file

@ -0,0 +1,128 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* 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, see <https://www.gnu.org/licenses/>.
*/
#include "soundfontrepository.h"
#include "translation.h"
using namespace mu::audio;
using namespace mu::audio::synth;
using namespace mu::framework;
using namespace mu::async;
void SoundFontRepository::init()
{
loadSoundFontPaths();
configuration()->soundFontDirectoriesChanged().onReceive(this, [this](const io::paths_t&) {
loadSoundFontPaths();
});
}
void SoundFontRepository::loadSoundFontPaths()
{
TRACEFUNC;
m_soundFontPaths.clear();
static const QStringList filters = { "*.sf2", "*.sf3" };
io::paths_t dirs = configuration()->soundFontDirectories();
for (const io::path_t& dir : dirs) {
RetVal<io::paths_t> soundFonts = fileSystem()->scanFiles(dir, filters);
if (!soundFonts.ret) {
LOGE() << soundFonts.ret.toString();
continue;
}
m_soundFontPaths.insert(m_soundFontPaths.end(), soundFonts.val.begin(), soundFonts.val.end());
}
}
SoundFontPaths SoundFontRepository::soundFontPaths() const
{
return m_soundFontPaths;
}
Notification SoundFontRepository::soundFontPathsChanged() const
{
return m_soundFontPathsChanged;
}
mu::Ret SoundFontRepository::addSoundFont(const SoundFontPath& path)
{
std::string title = qtrc("audio", "Do you want to add the SoundFont: %1?")
.arg(io::filename(path).toQString()).toStdString();
IInteractive::Button btn = interactive()->question(title, "", {
IInteractive::Button::No,
IInteractive::Button::Yes
}).standardButton();
if (btn == IInteractive::Button::No) {
return mu::make_ret(Ret::Code::Cancel);
}
RetVal<SoundFontPath> newPath = resolveInstallationPath(path);
if (!newPath.ret) {
return newPath.ret;
}
if (fileSystem()->exists(newPath.val)) {
title = qtrc("audio", "File \"%1\" already exists.")
.arg(newPath.val.toQString()).toStdString();
btn = interactive()->question(title, trc("audio", "Do you want to overwrite it?"), {
IInteractive::Button::No,
IInteractive::Button::Yes
}, IInteractive::Button::Yes, IInteractive::WithIcon).standardButton();
if (btn == IInteractive::Button::No) {
return mu::make_ret(Ret::Code::Cancel);
}
}
Ret ret = fileSystem()->copy(path, newPath.val, true /* replace */);
if (ret) {
m_soundFontPaths.push_back(newPath.val);
m_soundFontPathsChanged.notify();
interactive()->info(trc("audio", "SoundFont installed"),
trc("audio", "You can assign soundfonts to instruments using the mixer panel."),
{}, 0, IInteractive::Option::WithIcon);
}
return ret;
}
mu::RetVal<SoundFontPath> SoundFontRepository::resolveInstallationPath(const SoundFontPath& path) const
{
io::paths_t dirs = configuration()->userSoundFontDirectories();
for (const io::path_t& dir : dirs) {
if (fileSystem()->isWritable(dir)) {
SoundFontPath newPath = dir + "/" + io::filename(path);
return RetVal<SoundFontPath>::make_ok(newPath);
}
}
return RetVal<SoundFontPath>(make_ret(Ret::Code::UnknownError));
}

View file

@ -0,0 +1,58 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* 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, see <https://www.gnu.org/licenses/>.
*/
#ifndef MU_AUDIO_SOUNDFONTREPOSITORY_H
#define MU_AUDIO_SOUNDFONTREPOSITORY_H
#include "audio/isoundfontrepository.h"
#include "modularity/ioc.h"
#include "iinteractive.h"
#include "audio/iaudioconfiguration.h"
#include "io/ifilesystem.h"
#include "async/asyncable.h"
namespace mu::audio {
class SoundFontRepository : public ISoundFontRepository, public async::Asyncable
{
INJECT(audio, framework::IInteractive, interactive)
INJECT(audio, IAudioConfiguration, configuration)
INJECT(audio, io::IFileSystem, fileSystem)
public:
void init();
synth::SoundFontPaths soundFontPaths() const override;
async::Notification soundFontPathsChanged() const override;
mu::Ret addSoundFont(const synth::SoundFontPath& path) override;
private:
void loadSoundFontPaths();
mu::RetVal<synth::SoundFontPath> resolveInstallationPath(const synth::SoundFontPath& path) const;
synth::SoundFontPaths m_soundFontPaths;
mu::async::Notification m_soundFontPathsChanged;
};
}
#endif // MU_AUDIO_SOUNDFONTREPOSITORY_H

View file

@ -29,23 +29,14 @@
using namespace mu::audio;
using namespace mu::audio::synth;
static const std::map<SoundFontFormat, std::string> FLUID_SF_FILE_EXTENSIONS =
{
{ SoundFontFormat::SF2, "*.sf2" },
{ SoundFontFormat::SF3, "*.sf3" }
};
static const AudioResourceVendor FLUID_VENDOR_NAME = "Fluid";
FluidResolver::FluidResolver(const io::paths_t& soundFontDirs, async::Channel<io::paths_t> sfDirsChanges)
FluidResolver::FluidResolver()
{
ONLY_AUDIO_WORKER_THREAD;
m_soundFontDirs = soundFontDirs;
refresh();
sfDirsChanges.onReceive(this, [this](const io::paths_t& newSfDirs) {
m_soundFontDirs = newSfDirs;
soundFontRepository()->soundFontPathsChanged().onNotify(this, [this]() {
refresh();
});
}
@ -99,21 +90,7 @@ void FluidResolver::refresh()
m_resourcesCache.clear();
for (const auto& pair : FLUID_SF_FILE_EXTENSIONS) {
updateCaches(pair.second);
}
}
void FluidResolver::updateCaches(const std::string& fileExtension)
{
for (const io::path_t& path : m_soundFontDirs) {
RetVal<io::paths_t> files = fileSystem()->scanFiles(path, { QString::fromStdString(fileExtension) });
if (!files.ret) {
continue;
}
for (const io::path_t& filePath : files.val) {
m_resourcesCache.emplace(io::basename(filePath).toStdString(), filePath);
}
for (const SoundFontPath& path : soundFontRepository()->soundFontPaths()) {
m_resourcesCache.emplace(io::basename(path).toStdString(), path);
}
}

View file

@ -26,9 +26,8 @@
#include <unordered_map>
#include "async/asyncable.h"
#include "async/channel.h"
#include "modularity/ioc.h"
#include "io/ifilesystem.h"
#include "audio/isoundfontrepository.h"
#include "isynthresolver.h"
#include "fluidsynth.h"
@ -36,9 +35,9 @@
namespace mu::audio::synth {
class FluidResolver : public ISynthResolver::IResolver, public async::Asyncable
{
INJECT(audio, io::IFileSystem, fileSystem)
INJECT(audio, ISoundFontRepository, soundFontRepository)
public:
explicit FluidResolver(const io::paths_t& soundFontDirs, async::Channel<io::paths_t> sfDirsChanges);
explicit FluidResolver();
ISynthesizerPtr resolveSynth(const audio::TrackId trackId, const audio::AudioInputParams& params) const override;
bool hasCompatibleResources(const audio::PlaybackSetupData& setup) const override;
@ -49,9 +48,7 @@ public:
private:
FluidSynthPtr createSynth(const audio::AudioResourceId& resourceId) const;
void updateCaches(const std::string& fileExtension);
io::paths_t m_soundFontDirs;
std::unordered_map<AudioResourceId, io::path_t> m_resourcesCache;
};
}

View file

@ -0,0 +1,46 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* 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, see <https://www.gnu.org/licenses/>.
*/
#ifndef MU_AUDIO_ISOUNDFONTREPOSITORY_H
#define MU_AUDIO_ISOUNDFONTREPOSITORY_H
#include "modularity/imoduleexport.h"
#include "ret.h"
#include "async/channel.h"
#include "synthtypes.h"
namespace mu::audio {
class ISoundFontRepository : MODULE_EXPORT_INTERFACE
{
INTERFACE_ID(ISoundFontRepository)
public:
virtual ~ISoundFontRepository() = default;
virtual synth::SoundFontPaths soundFontPaths() const = 0;
virtual async::Notification soundFontPathsChanged() const = 0;
virtual mu::Ret addSoundFont(const synth::SoundFontPath& path) = 0;
};
}
#endif // MU_AUDIO_ISOUNDFONTREPOSITORY_H

View file

@ -33,6 +33,12 @@ namespace mu::audio::synth {
using SoundFontPath = io::path_t;
using SoundFontPaths = std::vector<SoundFontPath>;
inline bool isSoundFont(const io::path_t& filePath)
{
std::string ext = io::suffix(filePath);
return ext == "sf2" || ext == "sf3";
}
enum class SoundFontFormat {
Undefined = 0,
SF2,

View file

@ -74,6 +74,7 @@ public:
virtual io::path_t absolutePath(const io::path_t& filePath) const = 0;
virtual QDateTime birthTime(const io::path_t& filePath) const = 0;
virtual QDateTime lastModified(const io::path_t& filePath) const = 0;
virtual bool isWritable(const io::path_t& filePath) const = 0;
};
}

View file

@ -339,3 +339,8 @@ QDateTime FileSystem::lastModified(const io::path_t& filePath) const
{
return QFileInfo(filePath.toQString()).lastModified();
}
bool FileSystem::isWritable(const io::path_t& filePath) const
{
return QFileInfo(filePath.toQString()).isWritable();
}

View file

@ -56,6 +56,7 @@ public:
io::path_t absolutePath(const io::path_t& filePath) const override;
QDateTime birthTime(const io::path_t& filePath) const override;
QDateTime lastModified(const io::path_t& filePath) const override;
bool isWritable(const path_t& filePath) const override;
private:
Ret removeFile(const io::path_t& path) const;

View file

@ -55,6 +55,7 @@ public:
MOCK_METHOD(io::path_t, absolutePath, (const io::path_t& filePath), (const, override));
MOCK_METHOD(QDateTime, birthTime, (const io::path_t& filePath), (const, override));
MOCK_METHOD(QDateTime, lastModified, (const io::path_t& filePath), (const, override));
MOCK_METHOD(bool, isWritable, (const io::path_t& filePath), (const, override));
};
}