Controlling Zynthian remotely with MIDI

I am working on connecting Rosegarden to Zynthian. I am trying to figure out which MIDI commands are acceepted. To my understanding, I can control Zynthian in two ways. First I can send commands via channel 16 (in case I have enabled and configured channel 16 in the webUI). I see that specific keys on channel 16 control the GUI (up/down cursor, select etc etc). Furthermore I can load the FluidSynth GM MIDI snapshot and send MIDI CC messages to select instruments in the list. Right?

Does that imply that I can only select other synths/banks/programs/instruments via channel 16? For instance if I want to select and configure a specific sound of ZynAddSubFX, I can only achieve that via channel 16? Best in that case to
shortcut by loading a saved snapshot with all instruments. Am I right?

Hi @Jan

Performance and sound design control of an engine is performed with MIDI continuous control (CC), e.g. adjusting a filter cutoff may be done by mapping a MIDI CC to that parameter. This is a global configuration and performed by the MIDI learning mechanism. Basically, each CC on each channel may be mapped to one or more parameters on any engine.

The master channel (default 16) is used to send CUIA messages to the Zynthian to perform (in general) non-performance actions.

1 Like

Hi Riban, I tried using CUIA messages. In fact it works but running these messages through Rosengarden does not work. When I enable channel 16 Rosengarden sends the following codes to Zynthian when selecting Zynthian in the MIDI devices screen, that results in Zynthian loading all sort of snapshots:
20:40:41.044 Ch 16, Ctrl 0, Val 1
20:40:41.044 Ch 16, Ctrl 32, Val 3
20:40:41.044 Ch 16, PrgChg 0
20:40:41.044 Ch 16, Ctrl 10, Val 64
20:40:41.045 Ch 16, Ctrl 93, Val 0
20:40:41.045 Ch 16, Ctrl 7, Val 100
20:40:41.045 Ch 16, Ctrl 91, Val 0

I tried to filter these messages in the MIDI filter rules. But even selecting:
IGNORE CH#16 PC
IGNORE CC
IGNORE PC

results in Zynthian loading a snapshot.

Furthermore I am wondering if the codes on: Zynthian UI Users Guide - ZynthianWiki

are still valid. I tried several keys, with checking in the QMIDIRoute log, but Zynthian does not respond as specified.

This selects bank 131 (1x128 + 3), program (preset) 0.

This sets pan to centre, effect 3 (usually chorus) depth to 0, volume to 100 and effect 1 (usually reverb) depth to 0.

So it is likely the bank and program change messages that are triggering the change of preset configuration. When and why are these messages being sent by Rosengarden? Is it when playback / record is started? Sometimes there are some defaults sent to reset a channel at start of playback in a DAW. It might be configurable - I haven’t looked at Rosengarden in probably a decade!!!

I think the note on codes in the guide are still correct but I haven’t played with them for a long while.

[Edit] I just tried sending note 63 (D4#) on channel 16 and it triggered the short back operation so that seems to work as documented.

The problem starts when adding MIDI devices in the “Manage MIDI devices” window. When I add a bank + program the MIDI codes mixes up Zynthian. Yesterday Zynthian started loading a snapshot when hitting a note in channel 3.

I am fine with using another DAW, as long it’s linux and has ease of use. I like Rosegarden because of the simplicity. And Ardour seems buggy: https://discourse.ardour.org/t/my-ardour-midi-rant/104367/22

It seems to me odd that Rosegarden wants to select Bank 131. I guess I need some help understanding the MIDI messages and how Zynthian interpretes these kind of messages. I’m struggling this a few days with this and the only solution is to reboot Zynthian. Sometimes the Rosegarden messages results in complete lockups of the Zynthian GUI. Removing the power is then the only solution.

The Ardour rant is a bit old. I love Ardour but don’t use a DAW often. If I am recording something then I will generally use Ardour for a full track or Audacity for a quick mono/stereo recording or directly to Zynthian for an even quicker - sketchpad recording. I am installing Rosegarden on a VM now to remind myself of what it looks like… If you are comfortable with it then carry on using it - I am sure there will be config that resolves your issues.

I assume you mean in Rosegarden.

Is Zynthian in stage mode or multi-timbral mode (see admin menu)? Stage mode is not necessarily intuitive. All MIDI input influences the currently selected layer only, i.e. any MIDI input is passed to just one engine. You probably want multi-timbral mode to allow Rosegarden to control different engines simultaneously.

Are you running Rosegarden on a separate PC? How is that PC connected to Zynthian?

Okay - I just had a quick play with Rosegarden. When you select or enable a MIDI output, Rosegarden sends a load of messages to that device in an attempt to reset it. By default these are based around the General MIDI device with Volumne, sustain, Reverb, etc. and a bank (0 - I don’t know why I saw bank 131 in the logs). I haven’t seen how to disable this other than to use a different "MIDI Playback configuration, e.g. create a new device and remove the default CC mapping but this undermines Rosegarden functionality. Maybe you can ask the Rosegarden developer how to disable sending of these messages when the MIDI output is opened.

Yes Manage MIDI devices window in Rosegarden.
Zynthian is in multi-timbral mode.
Rosegarden is running on a separate Debian/KDE laptop.
Zynthian is connected to that laptop via the USB-C-MIDI interface
Launchkey 61 is connected to another USB in that laptop.
I used Jack to connect Launchkey → qmidiroute (translating some input) → Rosegarden → Zynthian.

Could 131 be calculated with MSB = 1 and LSB = 3? That is my General MIDI setup in the Bank sub window (after pressing Banks) in the ‘Manage MIDI devices’ window.

What I do not understand is why Zynthian gets ‘corrupted’. I indeed added other banks that correspond with the banks in ZynSubAddFX. However if I add these banks Zynthian’ GUI goes all over the place. Is Rosegarden the only DAW that tries the connected Synths to preprogram before playing the midi file?

I’m not entirely sure what is happening but Rosegarden sends bank select, program change and several CC messages on all 16 channels when a port is opened (with default GM config). I suspect that some of these messages are triggering some CUIA action or possibly ZS3 action. (Do you have ZS3 enabled in Zynthian?) Do you have any MIDI filters applied in Zynthian? Post a snapshot of your MIDI options from webconf.

These are my webconf settings:

off = unchecked
Stage Mode (Active Layer receives all external events): off
ZS3 (Capture Program Change events for SubSnapShots): off
Preload Presets with Note-On events: off
Route MIDI to Output Ports: off
Enable System Messages (Transport): off
Autodetect CC relative mode: off
Enable RTP-MIDI (AppleMIDI network): off
Enable QmidiNet (IP Multicast): off
Enable TouchOSC MIDI Bridge: off
Enable AubioNotes (Audio2MIDI): off
MIDI fine tuning (Hz): 440
Master MIDI channel: None
Master change type: Custom
Master Bank change mode: MSB
Master Program change-up: [empty]
Master Program change-down: [empty]
Master Bank change-up: [empty]
Master Bank change-down: [empty]
Midi filter rules: [empty]*
MIDI Ports:
DISABLED_IN=
ENABLED_OUT=ttymidi:MIDI_out
ENABLED_FB=

*) Earlier it was:
IGNORE CH#16 PC
IGNORE CC
IGNORE PC

Is there a wiki page that explains these settings in detail. For instance what values can I fill in in Master Program change-up and what is the effect in Zynthian?

Today I made a loop in Rosegarden (to show improvisation possibilities to my daughter). This resulted in spontaneously lowering my headphone and DAC volume to 1, at the start of every loop. I am completely lost why Rosegarden generated events in channel 1-6 + 10 triggers these actions in Zynthian.

Still finding out what is happening between my DAW and Zynthian. First I noticed that the Zynthian is not reflecting the actual input. An example. An example. I to Zynthian for each channel Sustain Pedal: 0 and All notes: Off (snippet coming from qmidiroutelogging) :
12:58:30.238 Ch 1, Ctrl 64, Val 0
12:58:30.239 Ch 2, Ctrl 64, Val 0
12:58:30.239 Ch 3, Ctrl 64, Val 0
12:58:30.240 Ch 4, Ctrl 64, Val 0
12:58:30.241 Ch 5, Ctrl 64, Val 0
12:58:30.241 Ch 6, Ctrl 64, Val 0
12:58:30.241 Ch 7, Ctrl 64, Val 0
12:58:30.242 Ch 8, Ctrl 64, Val 0
12:58:30.246 Ch 9, Ctrl 64, Val 0
12:58:30.247 Ch 10, Ctrl 64, Val 0
12:58:30.248 Ch 11, Ctrl 64, Val 0
12:58:30.248 Ch 12, Ctrl 64, Val 0
12:58:30.249 Ch 13, Ctrl 64, Val 0
12:58:30.249 Ch 14, Ctrl 64, Val 0
12:58:30.249 Ch 15, Ctrl 64, Val 0
12:58:30.250 Ch 16, Ctrl 64, Val 0
12:58:30.250 Ch 1, Ctrl 123, Val 0
12:58:30.251 Ch 2, Ctrl 123, Val 0
12:58:30.251 Ch 3, Ctrl 123, Val 0
12:58:30.252 Ch 4, Ctrl 123, Val 0
12:58:30.252 Ch 5, Ctrl 123, Val 0
12:58:30.253 Ch 6, Ctrl 123, Val 0
12:58:30.253 Ch 7, Ctrl 123, Val 0
12:58:30.254 Ch 8, Ctrl 123, Val 0
12:58:30.254 Ch 9, Ctrl 123, Val 0
12:58:30.255 Ch 10, Ctrl 123, Val 0
12:58:30.255 Ch 11, Ctrl 123, Val 0
12:58:30.255 Ch 12, Ctrl 123, Val 0
12:58:30.256 Ch 13, Ctrl 123, Val 0
12:58:30.261 Ch 14, Ctrl 123, Val 0
12:58:30.261 Ch 15, Ctrl 123, Val 0
12:58:30.262 Ch 16, Ctrl 123, Val 0

What is logged in the WebConf:
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0
CH#01 CONTROL_CHANGE 64 => 0

On the second attempt with exactly the same input:
CH#01 CONTROL_CHANGE 64 => 0
CH#02 CONTROL_CHANGE 64 => 0
CH#03 CONTROL_CHANGE 64 => 0
CH#04 CONTROL_CHANGE 64 => 0
CH#05 CONTROL_CHANGE 64 => 0
CH#06 CONTROL_CHANGE 64 => 0
CH#07 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0
CH#08 CONTROL_CHANGE 64 => 0

Number of logitems are correct, but the data is incorrect.

Just checked with: amidi -p hw:0,0 -d after killing jackd (otherwise the amidi cannot access the port and input is in accordance with output of the DAW. Must be an issue with the Zynthian stack, somehow.

It’s the source code :smiley:

Joking apart, we don’t have a test rig that can confirm responses , which is certainly something that we should have.

The zynthian, inevitably, since it uses MIDI as a communication channel, has a wake up procedure, which is (to my knowledge ) undefined.
Quite how the addition of layers affects this chat, also needs to be considered,as cloning and such mechanisms could produce MIDI howl around, which even in the presence of zmidinet proved to be quite rugged. So we are in an area that is worth an analysis.

Duly noted :slight_smile: I have cloned zynthian-webconf. Guess something with the midi_log_handler code. Now finding out how the Zynthian stack has been designed to better understand how the code connects to Jack… Not an expert in Python unfortunately.

Hmm seems that the input message from mido is already in error. Setting a logline for msg in the function:
def on_midi_in(self, msg):
message = ZynthianWebSocketMessage(‘MidiLogMessageHandler’, msg)
self.websocket.write_message(jsonpickle.encode(message))

…results in:
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 0}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 1}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 2}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 3}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}
INFO:root:{“py/object”: “mido.messages.messages.Message”, “type”: “control_change”, “time”: 0, “control”: 64, “value”: 0, “channel”: 4}

Another evening of bugfixing, while keeping in mind that Zynthian is not a finished product. It would be really nice if at least the readers provide some helpful feedback to my now spending approx 40 hours on bugfixing. Currently I get the feeling that spending time on this project is not really appreciated by some key-members here…

Anyway. The bug (at least in my image) is that sending a load of control messages instantly causes the MIDI buffer in Zynthian to ‘overload’ and erroneously repeating the last last messages instead of executing the correct MIDI input.

The Zynthian stack is built up of a set of layers:
6. Head (html + javascript)
5. midi_log_handler python library
4. MIDO library
3. RTMIDI library
2. Jack
1, Alsa

Executed the following tests 10x. Input data as shown in the previous posts. Hence input data from Rosegarden when pressing stop after play. The data is:
a. Every channel (1-16) receives 64 (sustain pedal 0)
b. Every channel (1-16) receives 123 (note off)

  1. Killing the Jack process and directly connecting to layer 1 (ALSA) with the following script:
    midi_in = rtmidi.MidiIn()
    midi_in.open_port(1)
    while True:
    message = midi_in.get_message()
    if message:
    print(message)

Simulated load up to 99%. Simulated the load with 4x process yes > /dev/null &
Result: input 10x correct with and without load.

ps: unfortunately unable connect to ALSA when Jack running. I assumed that layer 2 cannot change the feed of layer 1.

  1. Connecting to Jack while Zynthian using Jack, with the following script:
    mido.set_backend(‘mido.backends.rtmidi/UNIX_JACK’)
    msg = mido.open_input(‘system:midi_capture_1’)
    with mido.open_input(‘system:midi_capture_1’) as inport:
    for msg in inport:
    print(msg)

This test is with creating 13 Layers of ZY and FS with a cpu load up to 25%.

Result: first test correct message, second and beyond incorrect messages. Higher load correlated with error rate.

  1. Connecting to Jack while Zynthian using Jack, with the same script:

This test is with zero layers, hence a load between 5 - 10% max.

Result: 9 out of 10 tests correct data. 1x in error.

  1. Connecting to Jack while Zynthian service stopped, with the same script:

Result: 5 out of 10 tests correct data. 1x in error.

The results are erroneous data fed into the Zynthian system. The consequence Zynthian having completely unpredictable behavior. I have experienced changes in learned controller ID’s (e.g. from 74 → 121), changed controller values, spontaneous loading of snapshots in the middle of a MIDI data, Note on/off data interpreted as CUIA: Callable UI Actions and even spontaneous resets in the middle of a song.

Sorry, I do not want to be picky but this bug really needs having proper attention not to kill such a great development in it’s infancy and keep it in an innovator phase not crossing the chasm (Rogers, adoption curve).