Skip to content

Commit 4d35f25

Browse files
committed
Do not send portamento messages (MIDI CC 5, 65 & 84) to FluidSynth
Roland SC-55 style Portamento cannot be emulated on FluidSynth, and out-of-tune sounding portamento is a much bigger problem than the lack of subtle portamento effects in a handful of synth-oriented game soundtracks. This fixes the weirdly out-of-tune synth parts in the Level 8 music of Descent. See the code comments for further details.
1 parent 96c40a9 commit 4d35f25

File tree

1 file changed

+51
-4
lines changed

1 file changed

+51
-4
lines changed

src/midi/midi_fluidsynth.cpp

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -663,10 +663,57 @@ void MidiHandlerFluidsynth::ApplyChannelMessage(const std::vector<uint8_t>& msg)
663663

664664
// clang-format off
665665
switch (status) {
666-
case MidiStatus::NoteOff: fluid_synth_noteoff( synth.get(), channel, msg[1]); break;
667-
case MidiStatus::NoteOn: fluid_synth_noteon( synth.get(), channel, msg[1], msg[2]); break;
668-
case MidiStatus::PolyKeyPressure: fluid_synth_key_pressure( synth.get(), channel, msg[1], msg[2]); break;
669-
case MidiStatus::ControlChange: fluid_synth_cc( synth.get(), channel, msg[1], msg[2]); break;
666+
case MidiStatus::NoteOff: fluid_synth_noteoff( synth.get(), channel, msg[1]); break;
667+
case MidiStatus::NoteOn: fluid_synth_noteon( synth.get(), channel, msg[1], msg[2]); break;
668+
case MidiStatus::PolyKeyPressure: fluid_synth_key_pressure(synth.get(), channel, msg[1], msg[2]); break;
669+
670+
case MidiStatus::ControlChange: {
671+
const auto controller = msg[1];
672+
const auto value = msg[2];
673+
674+
if (controller == MidiController::Portamento ||
675+
controller == MidiController::PortamentoTime ||
676+
controller == MidiController::PortamentoControl) {
677+
678+
// The Roland SC-55 and its clones (Yamaha MU80 or Roland's own
679+
// later modules that emulate the SC-55) handle portamento (pitch
680+
// glides between consecutive notes on the same channel) in a very
681+
// specific and unique way, just like most synthesisers.
682+
//
683+
// The SC-55 accepts only 7-bit Portamento Time values via MIDI
684+
// CC5, where the min value of 0 sets the fastest portamento time
685+
// (effectively turns it off), and the max value of 127 the
686+
// slowest (up to 8 minutes!). There is an exponential mapping
687+
// between the CC values and the duration of the portamento (pitch
688+
// slides/glides); this custom curve is apparently approximated by
689+
// multiple linear segments. Moreover, the distance between the
690+
// source and destination notes also affect the portamento time,
691+
// making portamento dynamic and highly dependent on the notes
692+
// being played.
693+
//
694+
// FluidSynth, on the other hand, implements a very different
695+
// portamento model. Portament Time values are set via 14-bit CC
696+
// messages (via MIDI CC5 (coarse) and CC37 (fine)), and there is
697+
// a linear mapping between CC values and the portamento time as
698+
// per the following formula:
699+
//
700+
// (CC5 * 127 ms) + (CC37 ms)
701+
//
702+
// Because of these fundamental differences, emulating Roland
703+
// SC-55 style portamento on FluidSynth is practically not
704+
// possible. Music written for the SC-55 that use portamento
705+
// sounds weirdly out of tune on FluidSynth (e.g. the Level 8
706+
// music of Descent), and "mapping" SC-55 portamento behaviour to
707+
// the FluidSynth range is not possible due to dynamic nature of
708+
// the SC-55 portamento handling. All in all, it's for the best to
709+
// ignore portamento altogether. This is not a great loss as it's
710+
// used rarely and usually only to add some subtle flair to the
711+
// start of the notes in synth-oriented soundtracks.
712+
} else {
713+
fluid_synth_cc(synth.get(), channel, controller, value);
714+
}
715+
} break;
716+
670717
case MidiStatus::ProgramChange: fluid_synth_program_change( synth.get(), channel, msg[1]); break;
671718
case MidiStatus::ChannelPressure: fluid_synth_channel_pressure(synth.get(), channel, msg[1]); break;
672719
case MidiStatus::PitchBend: fluid_synth_pitch_bend( synth.get(), channel, msg[1] + (msg[2] << 7)); break;

0 commit comments

Comments
 (0)