Skip to content

Add global portamento time mode setting to fix compatibility with older MIDI files#1656

Merged
derselbst merged 25 commits intomasterfrom
copilot/fix-14b2d641-7d0c-432f-bb0f-a14b68601b23
Oct 4, 2025
Merged

Add global portamento time mode setting to fix compatibility with older MIDI files#1656
derselbst merged 25 commits intomasterfrom
copilot/fix-14b2d641-7d0c-432f-bb0f-a14b68601b23

Conversation

Copy link
Contributor

Copilot AI commented Sep 29, 2025

Problem

FluidSynth currently always uses 14-bit portamento time calculation (MSB * 128 + LSB), which causes compatibility issues with older MIDI files that only send MSB values and expect 7-bit behavior. This affects classic MIDI files like those mentioned in issues #705, #1232, #1311, and #1456.

Solution

This PR implements a new global tri-state setting synth.portamento-time with three modes:

  • auto (default): Start with 7-bit MSB only, automatically switch to 14-bit when an LSB portamento event is detected on any MIDI channel
  • xg-gs: Always use 7-bit MSB only (for older MIDI files compatibility)
  • linear: Always use 14-bit MSB+LSB

Additionally, it changes the default portamento mode to FLUID_CHANNEL_PORTAMENTO_MODE_EACH_NOTE, causing portamento to be applied to every note, and not just those notes played in succession (=legato) as before.

Backward Compatibility

The default auto mode maintains existing behavior - files that use both MSB and LSB will work exactly as before, while files that only use MSB will get the expected 7-bit behavior.

This resolves the portamento compatibility issues with older MIDI files while preserving full functionality for modern usage.

Fixes #705, #1232, #1311, #1456, #1495, #1517

Copilot AI and others added 2 commits September 29, 2025 19:07
Co-authored-by: derselbst <8152480+derselbst@users.noreply.github.com>
Co-authored-by: derselbst <8152480+derselbst@users.noreply.github.com>
Copilot AI changed the title [WIP] Addressing the portamento issue Add global portamento time mode setting to fix compatibility with older MIDI files Sep 29, 2025
Copilot AI requested a review from derselbst September 29, 2025 19:12
Copy link
Member

@derselbst derselbst left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually works, I'm surprised! Some minor issues below.

@derselbst
Copy link
Member

TODO: complete unit test

@derselbst derselbst marked this pull request as ready for review September 30, 2025 07:50
@derselbst
Copy link
Member

@spessasus I have solved the portamento issue, in case you want to have a look / listen. Portamento is now also audible in 6 - Galactic Invasion (XG).mid - for me, actually, for the first time to hear proper portamento in fluidsynth, thanks to your test files! (Previously, fluidsynth only played portamento for legato notes, which was rarely the case.)

@derselbst derselbst linked an issue Sep 30, 2025 that may be closed by this pull request
res = res < Max ? res : Max;
// Apply a similar scaling hack as SpessaSynth to fix Descent Game08, it's unclear why exactly
// https://github.com/spessasus/spessasynth_core/blob/0b2d44f48065d3d6bbca24a1d40223b1255dab00/src/synthesizer/audio_engine/engine_methods/portamento_time.ts#L84-L86
res = (unsigned int)(res * abs(tokey - fromkey) / 24.0f + 0.5f);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that I changed that scaling factor to two octaves / 24 semitones. Sounds more natural to me, at least given those few test files that I have.

@spessasus
Copy link
Contributor

Note that I changed that scaling factor to two octaves / 24 semitones. Sounds more natural to me, at least given those few test files that I have.

Actually, it's the other way around. I have access to Sound Canvas VA and SYXG-50 VSTi, and the portamento is way faster there. I have changed my calculation just now to divide by 36 and galactic invasion, cybergate and mm6 all sound much closer to the syxg50.

@derselbst
Copy link
Member

Ok, fine I guess, still enough portamento left.

@derselbst derselbst added this to the 2.5 milestone Oct 1, 2025
Copy link
Contributor

@spessasus spessasus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@derselbst
I tried it and the portamento sounds too slow,, esp. in Galactic invasion.
I recommend using the DLSbyXG.dls file which uses a sine wave there (LSB 66) instead of a square, making the portamento more clear. Try comparing it with spessasynth or syxg50.

This is probably caused by the fact that you aren't actually using John Novak's SC-55 table but rather approximating it with a concave function.

Also one more thing, though it relates to DLS: drum bank selection seems broken there too. IIRC the preset selection logic is different there . In galactic invasion, the XG drum presets have a finger snap which is played at the start multiple times, yet it's absent when running fluidsynth with DLSbyXG.dls, indicating that it uses GS drums...

derselbst and others added 3 commits October 1, 2025 23:53
@derselbst
Copy link
Member

derselbst commented Oct 1, 2025

The curve was indeed a bit off for small CCs. I've tuned it a bit. John only used two MIDIs to derive this curve, I think that should be fine now. Speaking of him, he might be interested in what we're haggling about. I'll give you and @johnnovak a chance to reply before fixing the unit tests yet again ;)

Details
Index Old [s] New [s]
0 0 0
1 0.024 0.012
2 0.05 0.025
3 0.066 0.039
4 0.078 0.053
5 0.088 0.067
6 0.097 0.082
7 0.106 0.098
8 0.114 0.114
9 0.123 0.126
10 0.131 0.138
11 0.14 0.151
12 0.149 0.164
13 0.159 0.176
14 0.168 0.188
15 0.179 0.201
16 0.189 0.215
17 0.201 0.228
18 0.212 0.242
19 0.225 0.257
20 0.238 0.272
21 0.252 0.288
22 0.266 0.304
23 0.281 0.321
24 0.297 0.339
25 0.314 0.357
26 0.331 0.376
27 0.349 0.396
28 0.369 0.417
29 0.389 0.439
30 0.41 0.461
31 0.432 0.485
32 0.455 0.51
33 0.479 0.535
34 0.504 0.562
35 0.531 0.59
36 0.558 0.619
37 0.587 0.649
38 0.617 0.68
39 0.648 0.713
40 0.68 0.747
41 0.714 0.782
42 0.75 0.819
43 0.787 0.857
44 0.825 0.897
45 0.865 0.939
46 0.907 0.982
47 0.95 1.027
48 0.995 1.073
49 1.042 1.122
50 1.091 1.173
51 1.142 1.225
52 1.195 1.28
53 1.25 1.336
54 1.308 1.395
55 1.367 1.457
56 1.43 1.521
57 1.494 1.587
58 1.562 1.656
59 1.632 1.728
60 1.705 1.803
61 1.781 1.881
62 1.86 1.962
63 1.942 2.046
64 2.027 2.134
65 2.117 2.225
66 2.21 2.32
67 2.306 2.419
68 2.407 2.523
69 2.513 2.63
70 2.622 2.742
71 2.737 2.859
72 2.856 2.981
73 2.98 3.108
74 3.11 3.241
75 3.246 3.38
76 3.388 3.524
77 3.536 3.676
78 3.691 3.834
79 3.853 3.999
80 4.022 4.172
81 4.2 4.354
82 4.386 4.543
83 4.58 4.743
84 4.785 4.951
85 5 5.171
86 5.225 5.401
87 5.462 5.643
88 5.712 5.897
89 5.975 6.165
90 6.251 6.448
91 6.543 6.746
92 6.852 7.061
93 7.178 7.394
94 7.523 7.747
95 7.889 8.12
96 8.278 8.517
97 8.69 8.938
98 9.13 9.387
99 9.599 9.865
100 10.099 10.376
101 10.635 10.924
102 11.209 11.511
103 11.827 12.142
104 12.492 12.822
105 13.211 13.557
106 13.991 14.353
107 14.838 15.219
108 15.763 16.164
109 16.777 17.201
110 17.891 18.342
111 19.124 19.603
112 20.496 21.008
113 22.031 22.579
114 23.762 24.353
115 25.732 26.372
116 27.995 28.693
117 30.625 31.391
118 33.729 34.579
119 37.454 38.408
120 42.031 43.118
121 47.822 49.085
122 55.444 56.954
123 66.079 67.965
124 82.338 84.873
125 111.73 115.703
126 194.186 204.935
127 480 480

@johnnovak
Copy link

johnnovak commented Oct 1, 2025

@derselbst Great to see this getting implemented 🎉 I don't have too much time to do testing on this, but if the new values track my "reverse-engineered" curve closely, it should be good. @spessasus has generalised my measurements—again, don't have time to spend too much time on checking that, but I trust his ears 😄

I agree that the start of the curve is very important—composers tend to use those lower values when the portamento is used as a subtle embellishment to create a subtle "snap" at the start of the notes. If the effective portamento times are too long for those low CC values, we'll get audible pitch glides instead of the "snap" effect. This might sound like "out of tune" music in more extreme scenarios with fast notes.

If you think about it, it's almost certain the hardware uses a custom curve built up using a few linear segments (via some table lookup, perhaps), so probably a similar approach is the easiest in the emulation code. But if you managed to find a single function that fits closely, that's good too 😄

@sonarqubecloud
Copy link

sonarqubecloud bot commented Oct 4, 2025

@derselbst derselbst merged commit ed1cef8 into master Oct 4, 2025
109 checks passed
@derselbst derselbst deleted the copilot/fix-14b2d641-7d0c-432f-bb0f-a14b68601b23 branch October 4, 2025 09:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Addressing the portamento issue Messed up sawtooth channel in Descent game08.mid

4 participants