@@ -663,10 +663,57 @@ void MidiHandlerFluidsynth::ApplyChannelMessage(const std::vector<uint8_t>& msg)
663
663
664
664
// clang-format off
665
665
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
+
670
717
case MidiStatus::ProgramChange: fluid_synth_program_change ( synth.get (), channel, msg[1 ]); break ;
671
718
case MidiStatus::ChannelPressure: fluid_synth_channel_pressure (synth.get (), channel, msg[1 ]); break ;
672
719
case MidiStatus::PitchBend: fluid_synth_pitch_bend ( synth.get (), channel, msg[1 ] + (msg[2 ] << 7 )); break ;
0 commit comments