Added support of multi-voice playback for FluidSynth

This commit is contained in:
pereverzev_v 2022-12-01 04:01:23 +02:00 committed by pereverzev+v
parent 0138747e0d
commit 0948461e5c
6 changed files with 116 additions and 56 deletions

View file

@ -32,10 +32,9 @@ static constexpr note_idx_t MIN_SUPPORTED_NOTE = 12; // MIDI equivalent for C0
static constexpr mpe::pitch_level_t MAX_SUPPORTED_LEVEL = mpe::pitchLevel(PitchClass::C, 8);
static constexpr note_idx_t MAX_SUPPORTED_NOTE = 108; // MIDI equivalent for C8
void FluidSequencer::init(const ArticulationMapping& mapping, const std::unordered_map<midi::channel_t, midi::Program>& channels)
void FluidSequencer::init(const PlaybackSetupData& setupData)
{
m_articulationMapping = mapping;
m_channels = channels;
m_channels.init(setupData);
}
int FluidSequencer::currentExpressionLevel() const
@ -74,6 +73,16 @@ void FluidSequencer::updateDynamicChanges(const mpe::DynamicLevelMap& changes)
updateDynamicChangesIterator();
}
async::Channel<channel_t, Program> FluidSequencer::channelAdded() const
{
return m_channels.channelAdded;
}
const ChannelMap& FluidSequencer::channels() const
{
return m_channels;
}
void FluidSequencer::updatePlaybackEvents(EventSequenceMap& destination, const mpe::PlaybackEventsMap& changes)
{
for (const auto& pair : changes) {
@ -205,24 +214,7 @@ void FluidSequencer::appendPitchBend(EventSequenceMap& destination, const mpe::N
channel_t FluidSequencer::channel(const mpe::NoteEvent& noteEvent) const
{
for (const auto& pair : m_articulationMapping) {
if (noteEvent.expressionCtx().articulations.contains(pair.first)) {
return findChannelByProgram(pair.second);
}
}
return 0;
}
channel_t FluidSequencer::findChannelByProgram(const midi::Program& program) const
{
for (const auto& pair : m_channels) {
if (pair.second == program) {
return pair.first;
}
}
return 0;
return m_channels.resolveChannelForEvent(noteEvent);
}
note_idx_t FluidSequencer::noteIndex(const mpe::pitch_level_t pitchLevel) const

View file

@ -23,6 +23,7 @@
#ifndef MU_AUDIO_FLUIDSEQUENCER_H
#define MU_AUDIO_FLUIDSEQUENCER_H
#include "async/channel.h"
#include "midi/midievent.h"
#include "mpe/events.h"
@ -33,7 +34,7 @@ namespace mu::audio {
class FluidSequencer : public AbstractEventSequencer<midi::Event>
{
public:
void init(const ArticulationMapping& mapping, const std::unordered_map<midi::channel_t, midi::Program>& channels);
void init(const mpe::PlaybackSetupData& setupData);
int currentExpressionLevel() const;
@ -41,6 +42,10 @@ public:
void updateMainStreamEvents(const mpe::PlaybackEventsMap& changes) override;
void updateDynamicChanges(const mpe::DynamicLevelMap& changes) override;
async::Channel<midi::channel_t, midi::Program> channelAdded() const;
const ChannelMap& channels() const;
private:
void updatePlaybackEvents(EventSequenceMap& destination, const mpe::PlaybackEventsMap& changes);
@ -51,15 +56,13 @@ private:
const midi::channel_t channelIdx);
midi::channel_t channel(const mpe::NoteEvent& noteEvent) const;
midi::channel_t findChannelByProgram(const midi::Program& program) const;
midi::note_idx_t noteIndex(const mpe::pitch_level_t pitchLevel) const;
midi::tuning_t noteTuning(const mpe::NoteEvent& noteEvent, const int noteIdx) const;
midi::velocity_t noteVelocity(const mpe::NoteEvent& noteEvent) const;
int expressionLevel(const mpe::dynamic_level_t dynamicLevel) const;
int pitchBendLevel(const mpe::pitch_level_t pitchLevel) const;
ArticulationMapping m_articulationMapping;
std::unordered_map<midi::channel_t, midi::Program> m_channels;
mutable ChannelMap m_channels;
};
}

View file

@ -254,32 +254,19 @@ void FluidSynth::setupSound(const PlaybackSetupData& setupData)
fluid_synth_activate_key_tuning(m_fluid->synth, 0, 0, "standard", NULL, true);
m_channels.clear();
m_articulationMapping.clear();
m_sequencer.channelAdded().onReceive(this, [this](const midi::channel_t channelIdx, const midi::Program& program) {
fluid_synth_set_interp_method(m_fluid->synth, channelIdx, FLUID_INTERP_DEFAULT);
fluid_synth_pitch_wheel_sens(m_fluid->synth, channelIdx, 24);
fluid_synth_bank_select(m_fluid->synth, channelIdx, program.bank);
fluid_synth_program_change(m_fluid->synth, channelIdx, program.program);
fluid_synth_cc(m_fluid->synth, channelIdx, 7, DEFAULT_MIDI_VOLUME);
fluid_synth_cc(m_fluid->synth, channelIdx, 74, 0);
fluid_synth_set_portamento_mode(m_fluid->synth, channelIdx, FLUID_CHANNEL_PORTAMENTO_MODE_EACH_NOTE);
fluid_synth_set_legato_mode(m_fluid->synth, channelIdx, FLUID_CHANNEL_LEGATO_MODE_RETRIGGER);
fluid_synth_activate_tuning(m_fluid->synth, channelIdx, 0, 0, 0);
});
const Programs& programs = findPrograms(setupData);
for (const Program& program : programs) {
m_channels.emplace(static_cast<int>(m_channels.size()), program);
}
m_articulationMapping = articulationSounds(setupData);
for (const auto& pair : m_articulationMapping) {
m_channels.emplace(static_cast<int>(m_channels.size()), pair.second);
}
for (const auto& pair : m_channels) {
fluid_synth_set_interp_method(m_fluid->synth, pair.first, FLUID_INTERP_DEFAULT);
fluid_synth_pitch_wheel_sens(m_fluid->synth, pair.first, 24);
fluid_synth_bank_select(m_fluid->synth, pair.first, pair.second.bank);
fluid_synth_program_change(m_fluid->synth, pair.first, pair.second.program);
fluid_synth_cc(m_fluid->synth, pair.first, 7, DEFAULT_MIDI_VOLUME);
fluid_synth_cc(m_fluid->synth, pair.first, 74, 0);
fluid_synth_set_portamento_mode(m_fluid->synth, pair.first, FLUID_CHANNEL_PORTAMENTO_MODE_EACH_NOTE);
fluid_synth_set_legato_mode(m_fluid->synth, pair.first, FLUID_CHANNEL_LEGATO_MODE_RETRIGGER);
fluid_synth_activate_tuning(m_fluid->synth, pair.first, 0, 0, 0);
}
m_sequencer.init(m_articulationMapping, m_channels);
m_sequencer.init(setupData);
}
void FluidSynth::setupEvents(const mpe::PlaybackData& playbackData)
@ -389,8 +376,10 @@ void FluidSynth::toggleExpressionController()
int FluidSynth::setExpressionLevel(int level)
{
for (const auto& pair : m_channels) {
fluid_synth_cc(m_fluid->synth, pair.first, midi::EXPRESSION_CONTROLLER, level);
midi::channel_t lastChannelIdx = m_sequencer.channels().lastIndex();
for (midi::channel_t i = 0; i < lastChannelIdx; ++i) {
fluid_synth_cc(m_fluid->synth, i, midi::EXPRESSION_CONTROLLER, level);
}
return FLUID_OK;

View file

@ -109,9 +109,6 @@ private:
std::shared_ptr<Fluid> m_fluid = nullptr;
std::unordered_map<midi::channel_t, midi::Program> m_channels;
ArticulationMapping m_articulationMapping;
async::Channel<unsigned int> m_streamsCountChanged;
FluidSequencer m_sequencer;

View file

@ -23,6 +23,7 @@
#ifndef MU_AUDIO_SOUNDMAPPING_H
#define MU_AUDIO_SOUNDMAPPING_H
#include "async/channel.h"
#include "mpe/events.h"
#include "midi/miditypes.h"
@ -986,6 +987,84 @@ static const mpe::ArticulationTypeSet AFTERTOUCH_SUPPORTED_TYPES = {
mpe::ArticulationType::Vibrato, mpe::ArticulationType::MoltoVibrato,
mpe::ArticulationType::SenzaVibrato, mpe::ArticulationType::WideVibrato
};
struct ChannelMap {
using ChannelMapping = std::pair<midi::channel_t, midi::Program>;
using VoiceMappings = std::map<mpe::ArticulationType, ChannelMapping>;
void init(const mpe::PlaybackSetupData& setupData)
{
m_standardPrograms = findPrograms(setupData);
m_articulationMapping = articulationSounds(setupData);
}
midi::channel_t resolveChannelForEvent(const mpe::NoteEvent& event)
{
if (event.expressionCtx().articulations.contains(mpe::ArticulationType::Standard)) {
if (m_standardPrograms.empty()) {
return 0;
}
const midi::Program& standardProgram = m_standardPrograms.at(0);
return resolveChannel(event.arrangementCtx().voiceLayerIndex, mpe::ArticulationType::Standard, standardProgram);
}
if (m_articulationMapping.empty()) {
return 0;
}
for (const auto& pair : event.expressionCtx().articulations) {
auto search = m_articulationMapping.find(pair.first);
if (search == m_articulationMapping.cend()) {
continue;
}
return resolveChannel(event.arrangementCtx().voiceLayerIndex, search->first, search->second);
}
return 0;
}
midi::channel_t lastIndex() const
{
size_t result = 0;
for (const auto& pair : m_data) {
result += pair.second.size();
}
return static_cast<midi::channel_t>(result);
}
bool contains(const mpe::voice_layer_idx_t voiceIdx, const mpe::ArticulationType key) const
{
VoiceMappings& mapping = m_data[voiceIdx];
return mapping.find(key) != mapping.cend();
}
async::Channel<midi::channel_t, midi::Program> channelAdded;
private:
midi::channel_t resolveChannel(const mpe::voice_layer_idx_t voiceIdx, const mpe::ArticulationType type, const midi::Program& program)
{
if (!contains(voiceIdx, type)) {
midi::channel_t newChannelIdx = lastIndex();
m_data[voiceIdx].insert({ type, { newChannelIdx, program } });
channelAdded.send(newChannelIdx, program);
return newChannelIdx;
} else {
return m_data[voiceIdx].at(type).first;
}
}
mutable std::map<mpe::voice_layer_idx_t, VoiceMappings> m_data;
midi::Programs m_standardPrograms;
ArticulationMapping m_articulationMapping;
};
}
#endif // MU_AUDIO_SOUNDMAPPING_H

View file

@ -49,7 +49,7 @@ using Events = std::map<tick_t, std::vector<Event> >;
static constexpr int EXPRESSION_CONTROLLER = 11;
struct Program {
Program(bank_t b, program_t p)
Program(bank_t b = 0, program_t p = 0)
: bank(b), program(p) {}
bank_t bank = 0;