Added support of multi-voice playback for FluidSynth
This commit is contained in:
parent
0138747e0d
commit
0948461e5c
6 changed files with 116 additions and 56 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue