From 04c67e4397800b34abb64a8e0a66902f20f842e3 Mon Sep 17 00:00:00 2001 From: Peter Rudenko Date: Sun, 30 Oct 2022 13:36:16 +0400 Subject: [PATCH] Add built-in SoundFont player instrument (#236) --- .../BuiltIn/BuiltInSynthsPluginFormat.cpp | 14 + .../Audio/BuiltIn/BuiltInSynthsPluginFormat.h | 1 + .../BuiltIn/SoundFont/SoundFontSynth.cpp | 95 +++++ .../Audio/BuiltIn/SoundFont/SoundFontSynth.h | 20 ++ .../BuiltIn/SoundFontSynthAudioPlugin.cpp | 339 ++++++++++++++++++ .../Audio/BuiltIn/SoundFontSynthAudioPlugin.h | 90 +++++ .../Core/Audio/Instruments/PluginScanner.cpp | 6 +- Source/Core/Audio/Instruments/PluginScanner.h | 2 +- Source/Core/Serialization/SerializationKeys.h | 9 +- Source/UI/Common/CommandIDs.cpp | 2 + Source/UI/Common/CommandIDs.h | 6 +- Source/UI/Common/ModeIndicatorComponent.cpp | 66 ++-- Source/UI/Common/ModeIndicatorComponent.h | 13 +- .../Sidebars/SequencerSidebarLeft.cpp | 3 +- 14 files changed, 634 insertions(+), 32 deletions(-) create mode 100644 Source/Core/Audio/BuiltIn/SoundFontSynthAudioPlugin.cpp create mode 100644 Source/Core/Audio/BuiltIn/SoundFontSynthAudioPlugin.h diff --git a/Source/Core/Audio/BuiltIn/BuiltInSynthsPluginFormat.cpp b/Source/Core/Audio/BuiltIn/BuiltInSynthsPluginFormat.cpp index 21efcc33b..bbe3b0d42 100644 --- a/Source/Core/Audio/BuiltIn/BuiltInSynthsPluginFormat.cpp +++ b/Source/Core/Audio/BuiltIn/BuiltInSynthsPluginFormat.cpp @@ -19,6 +19,7 @@ #include "BuiltInSynthsPluginFormat.h" #include "DefaultSynthAudioPlugin.h" #include "MetronomeSynthAudioPlugin.h" +#include "SoundFontSynthAudioPlugin.h" const String BuiltInSynthsPluginFormat::formatName = "BuiltIn"; const String BuiltInSynthsPluginFormat::formatIdentifier = "BuiltIn"; @@ -30,6 +31,9 @@ BuiltInSynthsPluginFormat::BuiltInSynthsPluginFormat() MetronomeSynthAudioPlugin metronomeAudioPlugin; metronomeAudioPlugin.fillInPluginDescription(this->metronomeInstrument); + + SoundFontSynthAudioPlugin soundFontSynthAudioPlugin; + soundFontSynthAudioPlugin.fillInPluginDescription(this->soundFontPlayerInstrument); } String BuiltInSynthsPluginFormat::getName() const @@ -47,6 +51,10 @@ void BuiltInSynthsPluginFormat::findAllTypesForFile(OwnedArraymetronomeInstrument)); } + else if (id == SoundFontSynthAudioPlugin::instrumentId) + { + description.add(new PluginDescription(this->soundFontPlayerInstrument)); + } } bool BuiltInSynthsPluginFormat::fileMightContainThisPluginType(const String &fileOrIdentifier) @@ -70,5 +78,11 @@ void BuiltInSynthsPluginFormat::createPluginInstance(const PluginDescription &de return; } + if (desc.name == this->soundFontPlayerInstrument.name) + { + callback(make(), {}); + return; + } + callback(nullptr, {}); } diff --git a/Source/Core/Audio/BuiltIn/BuiltInSynthsPluginFormat.h b/Source/Core/Audio/BuiltIn/BuiltInSynthsPluginFormat.h index f16806775..e6feb615b 100644 --- a/Source/Core/Audio/BuiltIn/BuiltInSynthsPluginFormat.h +++ b/Source/Core/Audio/BuiltIn/BuiltInSynthsPluginFormat.h @@ -79,5 +79,6 @@ class BuiltInSynthsPluginFormat final : public AudioPluginFormat PluginDescription defaultInstrument; PluginDescription metronomeInstrument; + PluginDescription soundFontPlayerInstrument; }; diff --git a/Source/Core/Audio/BuiltIn/SoundFont/SoundFontSynth.cpp b/Source/Core/Audio/BuiltIn/SoundFont/SoundFontSynth.cpp index 7b749aa94..bdd0ae4ee 100644 --- a/Source/Core/Audio/BuiltIn/SoundFont/SoundFontSynth.cpp +++ b/Source/Core/Audio/BuiltIn/SoundFont/SoundFontSynth.cpp @@ -690,6 +690,49 @@ void SoundFontVoice::killNote() // SoundFontSynth //===----------------------------------------------------------------------===// +void SoundFontSynth::initSynth(const Parameters ¶meters) +{ + const File file(parameters.filePath); + if (!file.existsAsFile()) + { + return; + } + + ScopedLock locker(this->lock); + + this->allNotesOff(0, false); + + this->clearVoices(); + for (int i = SoundFontSynth::numVoices; i --> 0 ;) + { + this->addVoice(new SoundFontVoice()); + } + + this->clearSounds(); + + AudioFormatManager audioFormatManager; + audioFormatManager.registerBasicFormats(); + + if (file.getFullPathName().endsWithIgnoreCase("sf2")) + { + auto sound = make(file); + sound->loadRegions(); + sound->loadSamples(audioFormatManager); + this->sounds.add(sound.release()); + } + else if (file.getFullPathName().endsWithIgnoreCase("sbk")) + { + auto sound = make(file); + sound->loadRegions(); + sound->loadSamples(audioFormatManager); + this->sounds.add(sound.release()); + } + + // new file has been loaded so we need to set the program anyway + jassert(parameters.programIndex < this->getNumPrograms()); + this->setCurrentProgram(parameters.programIndex); +} + void SoundFontSynth::noteOn(int midiChannel, int midiNoteNumber, float velocity) { int i; @@ -837,3 +880,55 @@ void SoundFontSynth::changeProgramName(int index, const String &newName) { jassertfalse; } + +//===----------------------------------------------------------------------===// +// Synth parameters +//===----------------------------------------------------------------------===// + +SoundFontSynth::Parameters SoundFontSynth::Parameters::withSoundFontFile(const String &newFilePath) const noexcept +{ + Parameters other(*this); + other.filePath = newFilePath; + return other; +} + +SoundFontSynth::Parameters SoundFontSynth::Parameters::withProgramIndex(int newProgramIndex) const noexcept +{ + Parameters other(*this); + other.programIndex = newProgramIndex; + return other; +} + +SerializedData SoundFontSynth::Parameters::serialize() const +{ + using namespace Serialization::Audio; + + SerializedData data(SoundFont::soundFontConfig); + data.setProperty(SoundFont::filePath, this->filePath); + data.setProperty(SoundFont::programIndex, this->programIndex); + + return data; +} + +void SoundFontSynth::Parameters::deserialize(const SerializedData &data) +{ + this->reset(); + using namespace Serialization::Audio; + + const auto root = data.hasType(SoundFont::soundFontConfig) ? + data : data.getChildWithName(SoundFont::soundFontConfig); + + if (!root.isValid()) + { + return; + } + + this->filePath = root.getProperty(SoundFont::filePath); + this->programIndex = root.getProperty(SoundFont::programIndex); +} + +void SoundFontSynth::Parameters::reset() +{ + this->filePath.clear(); + this->programIndex = 0; +} diff --git a/Source/Core/Audio/BuiltIn/SoundFont/SoundFontSynth.h b/Source/Core/Audio/BuiltIn/SoundFont/SoundFontSynth.h index 9cd7ed7ae..e54274705 100644 --- a/Source/Core/Audio/BuiltIn/SoundFont/SoundFontSynth.h +++ b/Source/Core/Audio/BuiltIn/SoundFont/SoundFontSynth.h @@ -30,6 +30,24 @@ class SoundFontSynth final : public Synthesiser void noteOn(int midiChannel, int midiNoteNumber, float velocity) override; void noteOff(int midiChannel, int midiNoteNumber, float velocity, bool allowTailOff) override; + //===------------------------------------------------------------------===// + // Synth parameters + //===------------------------------------------------------------------===// + + struct Parameters final : Serializable + { + String filePath; + int programIndex = 0; + + Parameters withSoundFontFile(const String &newFilePath) const noexcept; + Parameters withProgramIndex(int newProgramIndex) const noexcept; + + SerializedData serialize() const override; + void deserialize(const SerializedData &data) override; + void reset() override; + }; + + void initSynth(const Parameters ¶meters); //===------------------------------------------------------------------===// // Presets @@ -43,6 +61,8 @@ class SoundFontSynth final : public Synthesiser private: + static constexpr auto numVoices = 16; + int noteVelocities[128]; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SoundFontSynth) diff --git a/Source/Core/Audio/BuiltIn/SoundFontSynthAudioPlugin.cpp b/Source/Core/Audio/BuiltIn/SoundFontSynthAudioPlugin.cpp new file mode 100644 index 000000000..da64528e7 --- /dev/null +++ b/Source/Core/Audio/BuiltIn/SoundFontSynthAudioPlugin.cpp @@ -0,0 +1,339 @@ +/* + This file is part of Helio Workstation. + + Helio 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. + + Helio 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 Helio. If not, see . +*/ + +#include "Common.h" +#include "SoundFontSynthAudioPlugin.h" +#include "BuiltInSynthsPluginFormat.h" +#include "DocumentHelpers.h" +#include "ModeIndicatorComponent.h" +#include "PlayButton.h" +#include "IconButton.h" +#include "SerializationKeys.h" +#include "ColourIDs.h" + +const String SoundFontSynthAudioPlugin::instrumentId = ""; +const String SoundFontSynthAudioPlugin::instrumentName = "SoundFont Player"; + +//===----------------------------------------------------------------------===// +// A simple UI allowing to pick a file and switch between presets, if any +//===----------------------------------------------------------------------===// + +class SoundFontSynthEditor final : public AudioProcessorEditor +{ +public: + + explicit SoundFontSynthEditor(WeakReference soundFontPlugin) : + AudioProcessorEditor(soundFontPlugin), + audioPlugin(soundFontPlugin) + { + this->filePath = make(); + this->filePath->setReadOnly(true); + this->filePath->setFont(Globals::UI::Fonts::M); + this->filePath->setJustification(Justification::centredLeft); + this->addAndMakeVisible(this->filePath.get()); + + this->browseButton = make(Icons::browse, CommandIDs::Browse); + this->browseButton->setMouseCursor(MouseCursor::PointingHandCursor); + this->addAndMakeVisible(this->browseButton.get()); + + this->leftArrow = make(Icons::stretchLeft, CommandIDs::SelectPreviousPreset); + this->addAndMakeVisible(this->leftArrow.get()); + + this->rightArrow = make(Icons::stretchRight, CommandIDs::SelectNextPreset); + this->addAndMakeVisible(this->rightArrow.get()); + + this->programIndexIndicator = make(); + this->addAndMakeVisible(this->programIndexIndicator.get()); + + this->programNameLabel = make