Vital standalone not reading keyboard layout

I’m using Vital 1.5.5 on Linux, but this issue applies to all versions and OSs and even the Vitalium fork.

I like to use the standalone version to develop patches, because it allows me to quickly check the sound with the computer keyboard. Unfortunately, I’m using a german keyboard where the y and z keys are swapped and US semicolon and single quote keys are umlauts instead.
Changing the keyboard layout to US English somehow doesn’t work, even before I launch Vital, but that might be a Juce issue.

By looking at the available source code I found out that Vital theoretically has the ability to read custom keyboard layouts from the Vital.config file, it is just not used.
In the LoadSave class there is a method which accepts a StringLayout parameter that is, when it is not a nullptr, apparently being filled from some JSON structure like this:

“keyboad_layout”: {
“chromatic_layout”:“awsedftgzhujkolpöä”,
“octave_up”:“x”,
“octave_down”:“y”
}

In Startup::doStartupChecks also a StringLayout parameter can be passed, it defaults to a nulltptr.
This method is called from the SynthBase constructor, but without the layout parameter, so the keyboard layout never gets loaded.

In the constructor of the SynthComputerKeyboard class from the standalone code, the keyboard is always initialized to the default US english keyboard layout.

I think fixing this would be quite easy and it would be a nice feature for people with non-US keyboards.

1 Like

FWIW, I have made a source code patch for the open source version which will load computer keyboard mappings from the config file.

I had to split up a method in load_save.cpp and do some string conversion because the JSON library does not support wide strings.
All that I then had to do was to insert a single line in the SynthEditor class.

In the config file I added this for german keyboards:

“keyboard_layout”:{“chromatic_layout”:“awsedftgzhujkolpöä+#”,“octave_down”:“y”,“octave_up”:“x”}

(There are even two more keys now than the US keyboard has: + and #)

I cannot attach patches here in the forum, so I’ll just copy it in here.

index 751e6d7..a0f2ec8 100644
--- a/src/common/load_save.cpp
+++ b/src/common/load_save.cpp
@@ -23,6 +23,8 @@
 #include "synth_constants.h"
 #include "synth_oscillator.h"
 
+#include <codecvt>
+
 #define QUOTE(x) #x
 #define STRINGIFY(x) QUOTE(x)
 
@@ -1399,17 +1401,9 @@ void LoadSave::saveMidiMapConfig(MidiManager* midi_manager) {
   saveJsonToConfig(data);
 }
 
-void LoadSave::loadConfig(MidiManager* midi_manager, vital::StringLayout* layout) {
+void LoadSave::loadConfigMidiManager(MidiManager* midi_manager) {
   json data = getConfigJson();
 
-  // Computer Keyboard Layout
-  if (layout) {
-    layout->setLayout(getComputerKeyboardLayout());
-    std::pair<wchar_t, wchar_t> octave_controls = getComputerKeyboardOctaveControls();
-    layout->setDownKey(octave_controls.first);
-    layout->setUpKey(octave_controls.second);
-  }
-
   // Midi Learn Map
   if (data.count("midi_learn")) {
     MidiManager::midi_map midi_learn_map = midi_manager->getMidiLearnMap();
@@ -1430,6 +1424,23 @@ void LoadSave::loadConfig(MidiManager* midi_manager, vital::StringLayout* layout
   }
 }
 
+void LoadSave::loadConfigLayout(vital::StringLayout* layout) {
+  json data = getConfigJson();
+
+  // Computer Keyboard Layout
+  if (layout) {
+    layout->setLayout(getComputerKeyboardLayout());
+    std::pair<wchar_t, wchar_t> octave_controls = getComputerKeyboardOctaveControls();
+    layout->setDownKey(octave_controls.first);
+    layout->setUpKey(octave_controls.second);
+  }
+}
+
+void LoadSave::loadConfig(MidiManager* midi_manager, vital::StringLayout* layout) {
+  loadConfigMidiManager(midi_manager);
+  loadConfigLayout(layout);
+}
+
 bool LoadSave::hasDataDirectory() {
   json data = getConfigJson();
   if (data.count("data_directory")) {
@@ -1667,8 +1678,13 @@ std::wstring LoadSave::getComputerKeyboardLayout() {
   if (data.count("keyboard_layout")) {
     json layout = data["keyboard_layout"];
 
-    if (layout.count("chromatic_layout"))
-      return layout["chromatic_layout"];
+    if (layout.count("chromatic_layout")) {
+      std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
+
+      std::string l = layout["chromatic_layout"].get<std::string>();
+
+      return converter.from_bytes(l);
+    }
   }
 
   return vital::kDefaultKeyboard;
@@ -1699,10 +1715,11 @@ std::pair<wchar_t, wchar_t> LoadSave::getComputerKeyboardOctaveControls() {
 
   if (data.count("keyboard_layout")) {
     json layout = data["keyboard_layout"];
-    std::wstring down = layout["octave_down"];
-    std::wstring up = layout["octave_up"];
-    octave_controls.first = down[0];
-    octave_controls.second = up[0];
+    std::string down = layout["octave_down"].get<std::string>();
+    std::string up = layout["octave_up"].get<std::string>();
+    std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
+    octave_controls.first = converter.from_bytes(down)[0];
+    octave_controls.second = converter.from_bytes(up)[0];
   }
 
   return octave_controls;
diff --git a/src/common/load_save.h b/src/common/load_save.h
index be192da..384a6e4 100644
--- a/src/common/load_save.h
+++ b/src/common/load_save.h
@@ -138,6 +138,8 @@ class LoadSave {
     static void saveAuthenticated(bool authenticated);
     static void saveWindowSize(float window_size);
     static void saveMidiMapConfig(MidiManager* midi_manager);
+    static void loadConfigMidiManager(MidiManager* midi_manager);
+    static void loadConfigLayout(vital::StringLayout* layout);
     static void loadConfig(MidiManager* midi_manager, vital::StringLayout* layout = nullptr);
 
     static std::wstring getComputerKeyboardLayout();
diff --git a/src/standalone/synth_editor.cpp b/src/standalone/synth_editor.cpp
index d9c2af2..347f984 100644
--- a/src/standalone/synth_editor.cpp
+++ b/src/standalone/synth_editor.cpp
@@ -73,6 +73,8 @@ SynthEditor::SynthEditor(bool use_gui) : SynthGuiInterface(this, use_gui) {
     int height = std::round(window_size * vital::kDefaultWindowHeight);
     setSize(width, height);
 
+    LoadSave::loadConfigLayout(computer_keyboard_.get());
+
     setWantsKeyboardFocus(true);
     addKeyListener(computer_keyboard_.get());
     setOpaque(true);