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:
commit
514ddcbb0a
14 changed files with 283 additions and 42 deletions
|
@ -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 {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
128
src/framework/audio/internal/soundfontrepository.cpp
Normal file
128
src/framework/audio/internal/soundfontrepository.cpp
Normal 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));
|
||||
}
|
58
src/framework/audio/internal/soundfontrepository.h
Normal file
58
src/framework/audio/internal/soundfontrepository.h
Normal 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
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
46
src/framework/audio/isoundfontrepository.h
Normal file
46
src/framework/audio/isoundfontrepository.h
Normal 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
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue