Glissando effect seems to affect all operators differently [solved by on-the-fly patching]
@skerjanc reported:
Glissando SYSEX catches the right function, but the glissando effect seems to affect all operators differently, thus it sounds detuned in steps. An issue of Dexed also?
Glissando on directly influences the detune parameter, i.e. they are no longer usable then
This is also true when you are not using Performance Sysex but the menu.
I think we need a way of testing this on MiniDexed and on real hardware for comparison.
Would you be able to run the script below with MiniDexed and module 1 of your TX816 for comparison? Do you hear any differences between the two? (I know it is a bit much to ask, but if you hear differences, could you record them for comparison?)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Test MIDI portamento and glissando features of T816
import time
import sys
import signal
import rtmidi
def ask(question, play_fn=None):
while True:
reply = input(f"{question} (y/n/r): ").strip().lower()
if reply == 'r':
if play_fn:
play_fn()
continue
if reply in ['y', 'n']:
return reply == 'y'
def send_sysex(midiout, msg, label=""):
# Force all SysEx messages to MIDI channel 0 (0x10)
if len(msg) > 2 and msg[0] == 0xF0 and msg[1] == 0x43:
msg = msg.copy()
msg[2] = 0x10
midiout.send_message(msg)
print(f"Sent: {label}")
def send_parameter(midiout, parameter, value, label):
"""Helper function to send SysEx for a single parameter."""
msg = [0xF0, 0x43, 0x10, 0x04, parameter, value, 0xF7]
send_sysex(midiout, msg, label)
def note_on(midiout, note, velocity=100, channel=0):
midiout.send_message([0x90 | 0, note, velocity])
def note_off(midiout, note, channel=0):
midiout.send_message([0x80 | 0, note, 0])
def play_legato(midiout, notes, delay=0.5, channel=0):
midiout.send_message([0x90 | 0, notes[0], 100])
time.sleep(delay)
midiout.send_message([0x90 | 0, notes[1], 100])
time.sleep(delay)
midiout.send_message([0x80 | 0, notes[0], 0])
midiout.send_message([0x80 | 0, notes[1], 0])
def play_sequential(midiout, notes, delay=0.5, channel=0):
note_on(midiout, notes[0], channel=0)
time.sleep(delay)
note_off(midiout, notes[0], channel=0)
time.sleep(0.1)
note_on(midiout, notes[1], channel=0)
time.sleep(delay)
note_off(midiout, notes[1], channel=0)
def play_chord(midiout, notes, velocity=100, duration=1.0, channel=0):
for note in notes:
note_on(midiout, note, velocity, 0)
time.sleep(duration)
for note in notes:
note_off(midiout, note, 0)
def get_port():
midiout = rtmidi.MidiOut()
ports = midiout.get_ports()
if not ports:
print("No MIDI output ports found.")
sys.exit(1)
for i, port in enumerate(ports):
print(f"{i}: {port}")
index = int(input("Select MIDI output port: "))
midiout.open_port(index)
return midiout
def set_initial_values(midiout):
# Set each parameter to its default value
# Poly/Mono (0 = Poly)
send_parameter(midiout, 2, 0, "Poly/Mono")
# Pitch Bend Range
send_parameter(midiout, 3, 0, "Pitch Bend Range")
# Pitch Bend Step
send_parameter(midiout, 4, 0, "Pitch Bend Step")
# Portamento Time (0 = fast)
send_parameter(midiout, 5, 0, "Portamento Time")
# Portamento/Glissando (0 = Portamento)
send_parameter(midiout, 6, 0, "Portamento/Glissando")
# Portamento Mode (0 = Retain)
send_parameter(midiout, 7, 0, "Portamento Mode")
# Modulation Wheel Sensitivity
send_parameter(midiout, 9, 0, "Modulation Wheel Sensitivity")
# Foot Controller Sensitivity
send_parameter(midiout, 11, 0, "Foot Controller Sensitivity")
# After Touch Sensitivity
send_parameter(midiout, 13, 0, "After Touch Sensitivity")
# Breath Controller Sensitivity
send_parameter(midiout, 15, 0, "Breath Controller Sensitivity")
# Audio Output Level Attenuator
send_parameter(midiout, 26, 0, "Audio Output Level Attenuator")
# Master Tuning (64 = concert pitch)
send_parameter(midiout, 64, 64, "Master Tuning")
def wait_for_key():
input("Press Enter to continue...")
def test_sequence(midiout, is_mono):
mode = "MONO" if is_mono else "POLY"
results = []
# Initialize values
set_initial_values(midiout)
time.sleep(1)
# Switch to correct mode before starting tests
if is_mono:
send_sysex(midiout, [0xF0, 0x43, 0x10, 0x04, 0x02, 0x01, 0xF7], "Set MONO mode")
else:
send_sysex(midiout, [0xF0, 0x43, 0x10, 0x04, 0x02, 0x00, 0xF7], "Set POLY mode")
time.sleep(0.2)
print(f"\nYou are now in {mode} mode.")
print("For each test, listen for how the pitch changes between two notes.")
# --- Poly/Mono mode difference test ---
def play_poly_mono_difference():
print(f"Test 0: Poly/Mono Mode Difference ({mode})")
print("A C major chord (C-E-G) will be played.")
if is_mono:
print("Listen: You should hear only one note at a time (not a chord).")
else:
print("Listen: You should hear all three notes together as a chord.")
print("Press Enter to start the test.")
wait_for_key()
play_chord(midiout, [60, 64, 67], duration=1.5)
play_poly_mono_difference()
if is_mono:
result = ask("Did you hear only one note at a time (not a chord)?", play_poly_mono_difference)
else:
result = ask("Did you hear all three notes together as a chord?", play_poly_mono_difference)
results.append(("0: Poly/Mono Mode Difference", result, []))
# --- Test 1: Chord (basic poly/mono difference) ---
def test_chord():
print("Test 1: Chord (C-E-G)")
print("Playing C major chord. Listen for chord vs single note.")
wait_for_key()
play_chord(midiout, [60, 64, 67], duration=1.5)
test_chord()
results.append(("1: Chord (C-E-G)", True, []))
# --- Test 2: Portamento Off/On ---
def test_portamento():
print("Test 2: Portamento Off/On (chord)")
print("First, portamento OFF. Then, portamento ON.")
send_parameter(midiout, 5, 0, "Portamento Time 0 (off)")
wait_for_key()
play_chord(midiout, [60, 64, 67], duration=1.5)
send_parameter(midiout, 5, 63, "Portamento Time 63 (on)")
wait_for_key()
play_chord(midiout, [60, 64, 67], duration=1.5)
send_parameter(midiout, 5, 0, "Portamento Time 0 (reset)")
test_portamento()
results.append(("2: Portamento Off/On (chord)", True, []))
# --- Test 3: Detune Off/On ---
def test_detune():
print("Test 3: Detune Off/On (chord)")
print("First, detune OFF. Then, detune ON.")
send_parameter(midiout, 40, 0x20, "Detune 0 (off)")
wait_for_key()
play_chord(midiout, [60, 64, 67], duration=1.5)
send_parameter(midiout, 40, 0x40, "Detune 64 (on)")
wait_for_key()
play_chord(midiout, [60, 64, 67], duration=1.5)
send_parameter(midiout, 40, 0x20, "Detune 0 (reset)")
test_detune()
results.append(("3: Detune Off/On (chord)", True, []))
# --- Test 4: Reverb Off/On ---
def test_reverb():
print("Test 4: Reverb Off/On (chord)")
print("First, reverb OFF. Then, reverb ON.")
send_parameter(midiout, 26, 0, "Reverb 0 (off)")
wait_for_key()
play_chord(midiout, [60, 64, 67], duration=1.5)
send_parameter(midiout, 26, 99, "Reverb 99 (on)")
wait_for_key()
play_chord(midiout, [60, 64, 67], duration=1.5)
send_parameter(midiout, 26, 0, "Reverb 0 (reset)")
test_reverb()
results.append(("4: Reverb Off/On (chord)", True, []))
# --- Test 5: Sustain Pedal Off/On ---
def test_sustain():
print("Test 5: Sustain Pedal Off/On (chord)")
print("First, sustain pedal OFF. Then, sustain pedal ON.")
print("Release keys quickly after playing chord.")
# Sustain OFF
midiout.send_message([0xB0, 64, 0])
wait_for_key()
play_chord(midiout, [60, 64, 67], duration=0.5)
# Sustain ON
midiout.send_message([0xB0, 64, 127])
wait_for_key()
play_chord(midiout, [60, 64, 67], duration=0.5)
midiout.send_message([0xB0, 64, 0])
test_sustain()
results.append(("5: Sustain Pedal Off/On (chord)", True, []))
# --- Test 6: Velocity Sensitivity Off/On ---
def test_velocity():
print("Test 6: Velocity Sensitivity Off/On (chord)")
print("First, velocity sensitivity OFF. Then, velocity sensitivity ON.")
send_parameter(midiout, 8, 0, "Velocity Sensitivity 0 (off)")
print("Play softly, then loudly.")
wait_for_key()
play_chord(midiout, [60, 64, 67], velocity=40, duration=1.0)
play_chord(midiout, [60, 64, 67], velocity=120, duration=1.0)
send_parameter(midiout, 8, 99, "Velocity Sensitivity 99 (on)")
print("Play softly, then loudly.")
wait_for_key()
play_chord(midiout, [60, 64, 67], velocity=40, duration=1.0)
play_chord(midiout, [60, 64, 67], velocity=120, duration=1.0)
send_parameter(midiout, 8, 0, "Velocity Sensitivity 0 (reset)")
test_velocity()
results.append(("6: Velocity Sensitivity Off/On (chord)", True, []))
# Print summary
print("\nTest Summary:")
for name, passed, sysex in results:
if passed:
print(f"PASS: {name}")
else:
print(f"FAIL: {name}")
if isinstance(sysex[0], list):
for msg in sysex:
hexstr = ' '.join(f'{b:02X}' for b in msg)
print(f" To reproduce: {hexstr}")
else:
hexstr = ' '.join(f'{b:02X}' for b in sysex)
print(f" To reproduce: {hexstr}")
if __name__ == "__main__":
signal.signal(signal.SIGINT, lambda s, f: sys.exit(0))
midiout = get_port()
try:
print("\n--- POLY MODE TESTS ---")
test_sequence(midiout, is_mono=False)
print("\n--- MONO MODE TESTS ---")
test_sequence(midiout, is_mono=True)
finally:
del midiout
Sorry, I am not the python expert. I am getting errors like: File "D:\temp\TestGlissando.py", line 66, in get_port midiout = rtmidi.MidiOut() AttributeError: module 'rtmidi' has no attribute 'MidiOut'. Did you mean: 'RtMidiOut'?
Oops, I think I had only tested the Python script on Linux. So let's not use it for now.
Can you come up with a sequence of MIDI sysex that you can share and could run/record on both MiniDexed and TX816 so that we hear the difference? I think that would be a tremendous help. Thanks!
You can simply hear it on MiniDexed following these steps:
- TG1>Portamento>Time = 70 : you will hear a long portamento between two notes.
- TG1>Portamento>Glissando On: you will hear the portamento quantized in semitones as expected, but totally detuned.
In this state the detune parameters of the operators are no longer properly working, causing crazy settings.
Can reproduce it now, thanks @Skerjanc. Minimal viable reproduction:
No SysEx involved. Al done using the menus.
- Performance -> Bank -> Laboratory
- Performance -> Load -> 128:003 pure DX7
- TG1 -> Portamento -> Time -> 1
- Play only C note (no other notes)
- TG1 -> Portamento -> Glissando -> On
- Play only C note (no other notes) again. __The note is now deeper (detuned)! This is the bug
- TG1 -> Portamento -> Time -> 0
- Play only C note (no other notes) again. __The note is now higher again (as it was originally). Changing other values for Time other than 0 detunes the note, but the amount of time does not change the amount of detuning.
I believe this bug to be in Synth_Dexed/src/dx7note.cpp.
Proposed patch:
https://github.com/probonopd/MiniDexed/blob/c2bedd11038be59a74ee649376bf57b1d44f7b6a/src/patches/dx7note.patch
If the theory is correct, then also other projects using the Synth_Dexed engine would be affected by this, and the fix would need to be made there.
Thanks @Skerjanc for having confirmed that this patch works. I am not sure that it is the best possible patch, but at least it fixes the issue.
For now, we are patching the Synth_Dexed engine on the fly, but a proper solution would be to get this fixed there. So maybe @dcoredump or @soyersoyer (who has sent successful Pull Requests to Synth_Dexed in the past) want to have a look at this? That'd be tremendous. Thanks!
(Leaving this open until a fix is in Synth_Dexed and we can remove our on-the-fly patching.)
Thanks for this patch. I will try to adapt it asap to Synth_Dexed.
I don't have any hardware to test at the moment and hope I haven't done anything wrong. Can you please test 2ad9e43095?
Tested, works. Thanks 👍
This is solved, right?
Yes. Thank you very much.