// ----
// ---- file   : synergy_config.tks
// ----
// ---- info   : Synergy configuration file
// ----
// ---- changed: 10Jun2010, 15Jun2010, 01Jul2010, 08Sep2010, 28Sep2010, 04Oct2010, 03Nov2010
// ----          07Feb2011, 08Mar2011, 19Mar2011, 27Mar2011, 30Mar2011, 23Apr2011, 16Jan2012
// ----          08Feb2012, 19Feb2012, 20Feb2012, 25Feb2012, 13May2012, 14May2012, 26Jun2012
// ----          13Nov2012, 21Nov2012, 09Jan2013, 22Apr2013, 04May2013, 09May2013, 10May2013
// ----          15May2013, 25May2013, 27May2013, 28May2013, 31May2013, 01Jun2013, 04Jun2013
// ----          05Jun2013, 26Aug2013, 27Aug2013, 12Feb2014, 15Jun2014, 09Aug2014, 12Aug2014
// ----          13Aug2014, 15Aug2014, 18Aug2014, 19Aug2014, 20Aug2014, 23Aug2014, 31Aug2014
// ----          13Sep2014, 14Sep2014, 15Sep2014, 21Sep2014, 26Sep2014, 10Jan2015, 11Jan2015
// ----          19Jan2015, 27Jan2015, 28Jan2015, 02Feb2015, 11Feb2015, 24Feb2015, 02Mar2015
// ----          04Mar2015, 09Mar2015, 16Mar2015, 17Mar2015, 23Mar2015, 09Apr2015, 14Apr2015
// ----          19Apr2015, 21Apr2015, 29Apr2015, 14May2015, 14Sep2015, 27Sep2015, 28Sep2015
// ----          03Oct2015, 04Oct2015, 05Oct2015, 08Oct2015, 12Oct2015, 23Oct2015, 06Nov2015
// ----          12Nov2015, 15Nov2015, 17Dec2015, 08Jan2016, 09Jan2016, 10Jan2016, 11Jan2016
// ----          19Jan2016, 30Jan2016, 29Feb2016, 07Mar2016, 13Mar2016, 25Mar2016, 11Jul2016
// ----          12Jul2016, 17Jul2016, 23Jul2016, 02Oct2016, 25Nov2016, 12Dec2016, 13Jan2017
// ----          20Jan2017, 27Jan2017, 28Jan2017, 30Jan2017, 09Feb2017, 03Mar2017, 04Mar2017
// ----          05Mar2017, 09Mar2017, 12Mar2017, 13Mar2017, 17Mar2017, 16Apr2017, 19Apr2017
// ----          30Apr2017, 05May2017, 22May2017, 05Jun2017, 07Jun2017, 08Jun2017, 12Jun2017
// ----          18Jun2017, 01Jul2017, 02Jul2017, 15Jul2017, 15Aug2017, 19Aug2017, 01Sep2017
// ----          17Sep2017, 21Sep2017, 12Oct2017, 02Nov2017, 10Nov2017, 01Dec2017, 21Dec2017
// ----          22Dec2017, 06Jan2018, 09Feb2018, 06Mar2018, 23May2018, 24May2018, 28May2018
// ----          03Jun2018, 06Jun2018, 10Jun2018, 13Nov2018, 16Nov2018, 24Nov2018, 26Nov2018
// ----          01Dec2018, 03Dec2018, 08Dec2018, 10Jan2019, 01Mar2019, 12May2019, 08Jun2019
// ----          25Jul2019, 02Aug2019, 07Aug2019, 08Sep2019, 15Sep2019, 21Sep2019, 22Sep2019
// ----          18Oct2019, 19Oct2019, 22Oct2019, 24Oct2019, 01Nov2019, 02Nov2019, 07Nov2019
// ----          08Nov2019, 09Nov2019, 08Apr2020, 14Apr2020, 25Apr2020, 07May2020, 29Aug2020
// ----          14Sep2020, 23Oct2020, 01Nov2020, 03Nov2020, 06Nov2020, 08Nov2020, 15Nov2020
// ----          21Nov2020, 27Nov2020, 10Feb2021, 16Apr2021, 13May2021, 14May2021, 14Jun2021
// ----          20Jun2021, 25Jul2021, 16Dec2021, 18Dec2021, 27Dec2021, 21Feb2022, 22Feb2022
// ----          18Mar2022, 08Apr2022, 21Apr2022, 23Apr2022, 06May2022, 11Aug2022, 22Oct2022
// ----          25Nov2022, 18Dec2022, 27Jan2023, 04Feb2023, 11Feb2023, 18Feb2023, 27Feb2023
// ----          18Mar2023, 01Apr2023, 08Jul2023, 16Jul2023, 22Jul2023, 18Aug2023, 02Oct2023
// ----          08Oct2023, 14Oct2023, 27Oct2023, 02Nov2023, 04Nov2023, 05Nov2023, 10Nov2023
// ----          11Nov2023, 17Nov2023, 19Nov2023
// ----
// ----
// ----
module MSTConfig;

use namespace ui;

// ----------------------------------------------------------------------------------------------
// (note) keep in mind that many settings are overridden in UseAutogeneratedConfig() (see below)
// (note) (the autogenerated config is updated when the settings are saved in the application)
// ----------------------------------------------------------------------------------------------


// true=ignore unicode and handle text edit keys as raw scan codes (US layout)
//   (note) useful for eastern/non-ASCII keyboards
//   (note) mainly affects TextEdit+TextField widgets (+some others)
// UI.SetEnableForceUSUnicode(true);

// Auto-detect keyboard layout (+auto-enable force-us-unicode if necessary)
UI.SetLocalToRawKeyTableByName("auto");

// (note) true=fast+smooth cursor movements (60hz arcade mode when repeat rate is set to 1!)
//        false=use millisec timer (~= every second frame (30Hz))
// UI.SetEnableVSyncKeyRepeat(false);    // enable millisec/timer based key repeat
UI.SetEnableVSyncKeyRepeat(true);     // enable vsync/frame based key repeat

// for US PC keyboards (which do not have VKEY_LESS(60) (\ on UK kbds) between lshift and z)
//  when enabled, (also) clear events in tracker editor with '/' (next to rshift)
UI.SetEnableKeyMapSlashToLess(true);

// macOS: swap ralt (option) / rsuper (command) key codes
//  (note) when using Macbook instead of PC USB keyboard
// UI.b_key_swap_ralt_rsuper_key = true;

UI.ConfigureKeyRepeat(300, 29);     // millisec/timer based key repeat
UI.ConfigureKeyRepeatFrames(15, 2); // vsync/frame based key repeat

// // macOS: uncomment to enable copy'n'paste via command-c/v (in addition to ctrl-c/v)
// // (note) enabling this will stop the tracker editor menu bar from being accessible via keyboard
// // UI.SetEnableKeyCmdCopyPaste(Utils.IsMacOS());

// ui::Button.b_hold_dot = true; // show dot in buttons that support hold-click events

// // UI.SetSwapStrategy(UI.SWAP_STRATEGY_EXCHANGE);
// //UI.SetSwapStrategy(UI.SWAP_STRATEGY_COPY);

// UI.b_keyboard_monitor = true;  // for video recording

// true=Bypass framebuffer object based UI compositor
// UI.b_fbo = false;

// true=show tooltips in window corners
// false=show tooltips near mouse cursor
UI.SetEnableWindowCornerToolTips(true);

// Initial (Stage1) ToolTip delay (in ms) [def=500]
UI.SetToolTipShowDelay(600);

// Stage2 ToolTip delay (in ms) [def=800]
UI.SetToolTipStage2Delay(800);

// ToolTip fast open timeout (in ms) [def=300]
UI.SetToolTipFastOpenTimeout(120);

// ToolTip fast open delay (in ms) [def=100]
UI.SetToolTipFastOpenDelay(150);

// // true=never change cursor shape
// //  (note) may be necessary for fullscreen mode (to work around disappearing cursor issue on some systems)
// // UI.b_skip_cursor = true;

// true=always grab mouse when editing floatparams
// false=only grab mouse in floatparam dial mode (RMB click)
UI.SetEnableFloatParamMouseGrab(true);

// Prevent blurry upscaling on 4k / UHD displays (Windows 10)
SDL.dpiAwareness = true;



function EnableColorGrading() {
   // Per-layer color grading (pre-compositing):
   // (note) FBO (layer) color grading does not affect overlays

   UI.SetPostFBOColorGrading256x16FromLocalFile("color_grading/image_with_gradient_256x16_darkgray_2.png");
   // UI.b_skip_bevel_border = true;
}

function EnableColorGrading_Mono() {
   // *** unused ***
   // monochrome - enter the matrix :^)
   // UI.SetPostFBOColorGrading256x16FromLocalFile("color_grading/image_with_gradient_256x16_green_1.png");
   // UI.SetPostFBOColorGrading256x16FromLocalFile("color_grading/image_with_gradient_256x16_blue_2.png");
   UI.SetPostFBOColorGrading256x16FromLocalFile("color_grading/image_with_gradient_256x16_green_2.png");
   // // UI.SetPostFBOColorGrading256x16FromLocalFile("color_grading/image_with_gradient_256x16_mono_2.png");
   // UI.SetPostFBOColorGrading256x16FromLocalFile("color_grading/image_with_gradient_256x16_invert_3.png"); // weird but actually usable (purple)
   UI.SetForcedTextColor32(#ff222222);
   UI.b_skip_bevel_border = true;
   UI.lnf.setColor(LookAndFeel.COLOR_POPUPMENUBUTTON_MOUSEOVER_BG, #ff959296);
   UI.lnf.setColor(LookAndFeel.COLOR_POPUPMENUBUTTON_ACTIVE_BG, #ff757276);
   UI.lnf.setColor(LookAndFeel.COLOR_TABLEVIEW_CELL_BG_CURSOR, #ff959296);
   UI.lnf.setColor(LookAndFeel.COLOR_TABLEVIEW_CELL_BG_LEADSELECTION, #ffa5a2a6);
   UI.lnf.setColor(LookAndFeel.COLOR_TEXT_EDITING_SELECT_BG, #ff909090);
   UI.lnf.setColor(LookAndFeel.COLOR_TEXT_EDIT_BG, #ffa0a0a0);
   // // UI.lnf.setColor(LookAndFeel.COLOR_CONTROL_INACTIVE_LT_BG, #fff0f0f0);  // non-editable textfield bg
   UI.lnf.setColor(LookAndFeel.COLOR_STATUSBAR_DEFAULT_BG, #fff0f0f0);
   UI.lnf.setColor(LookAndFeel.COLOR_BUTTON_MOUSEOVER_TINT, #4f000000);
   UI.lnf.setColor(LookAndFeel.COLOR_CONTROL_INACTIVE_LT_BG, #4f909090);  // button mouseover, not pressed
   UI.lnf.setColor(LookAndFeel.COLOR_CONTROL_INACTIVE_RT_BG, #4f909090);  // button mouseover, not pressed
   UI.lnf.setColor(LookAndFeel.COLOR_CONTROL_INACTIVE_RB_BG, #4f909090);  // button mouseover, not pressed
   UI.lnf.setColor(LookAndFeel.COLOR_CONTROL_INACTIVE_LB_BG, #4f909090);  // button mouseover, not pressed
}

function PostFBOColorGradingCycleCbk(int _idx) {
   trace "[dbg] PostFBOColorGradingCycleCbk: idx="+_idx;

   UI.SetForcedTextColor32(0);
   UI.b_skip_bevel_border = false;
   UI.lnf.revertColors();

   if(10 == _idx) // invert_7
   {
      UI.b_skip_bevel_border = true;
      UI.SetForcedTextColor32(#ff000000);
   }
   else if(14 == _idx) // green_2
   {
      if(null == UI.GetRootLayer())
      {
         // Force LookAndFeel when called during startup
         UI.SetLookAndFeel(LookAndFeel.LIGHT);
         STConfig.lnf_idx = LookAndFeel.IDX_LIGHT;
      }

      if((UI.lnf.getName()) == LookAndFeel.LIGHT)
         UI.SetForcedTextColor32(#ff222222);
      else
         UI.SetForcedTextColor32(#ffffffff);
      UI.b_skip_bevel_border = true;
      UI.lnf.setColor(LookAndFeel.COLOR_POPUPMENUBUTTON_MOUSEOVER_BG, #ff959296);
      UI.lnf.setColor(LookAndFeel.COLOR_POPUPMENUBUTTON_ACTIVE_BG, #ff757276);
      UI.lnf.setColor(LookAndFeel.COLOR_TABLEVIEW_CELL_BG_CURSOR, #ff959296);
      UI.lnf.setColor(LookAndFeel.COLOR_TABLEVIEW_CELL_BG_LEADSELECTION, #ffa5a2a6);
      UI.lnf.setColor(LookAndFeel.COLOR_TEXT_EDITING_SELECT_BG, #ff909090);
      UI.lnf.setColor(LookAndFeel.COLOR_TEXT_EDIT_BG, #ffa0a0a0);
      // // UI.lnf.setColor(LookAndFeel.COLOR_CONTROL_INACTIVE_LT_BG, #fff0f0f0);  // non-editable textfield bg
      UI.lnf.setColor(LookAndFeel.COLOR_STATUSBAR_DEFAULT_BG, #fff0f0f0);
      UI.lnf.setColor(LookAndFeel.COLOR_BUTTON_MOUSEOVER_TINT, #4f000000);
      UI.lnf.setColor(LookAndFeel.COLOR_CONTROL_INACTIVE_LT_BG, #4f909090);  // button mouseover, not pressed
      UI.lnf.setColor(LookAndFeel.COLOR_CONTROL_INACTIVE_RT_BG, #4f909090);  // button mouseover, not pressed
      UI.lnf.setColor(LookAndFeel.COLOR_CONTROL_INACTIVE_RB_BG, #4f909090);  // button mouseover, not pressed
      UI.lnf.setColor(LookAndFeel.COLOR_CONTROL_INACTIVE_LB_BG, #4f909090);  // button mouseover, not pressed

      if(null == UI.GetRootLayer())
         STConfig.b_node_tracker_draweq = false;
   }

   STConfig.lnf_colorgrading_initial_idx = _idx;
}


class STConfigBase {

   // Main music projects path that contains
   //   audiotool_temp/
   //   eureka_data/
   //   synergy_data/
   //   recordings/
   //   samples/
   // (note) not used outside this configuration file
   // (note) it is highly recommended to copy the default "mProjects/" folder to another directory
   //         so that it won't be lost when the application dir is updated / replaced
   static String my_mproj_path = "$(PROGRAM_DIR)/mProjects";
   // static String my_mproj_path = "f:/mProjects";
   // static String my_mproj_path = "~/mProjects";

   // Root path ($(ROOT))
   static String rootpath = my_mproj_path + "/synergy_data/";

   // Temp dir ($(TMP)
   //  (note) c:/temp/ not available on Windows 11 => use custom tmp/ dir
   static String temp = rootpath + "/tmp";

   // $(PROJECT_DIR) : Project file directory
   static String project_rootpath = rootpath + "/projects/";

   // Node preset file directory
   //  (note) each node class has its own subdirectory, i.e. "tracker", "modmatrix", "trigseq", ..
   //  (note) each node class subdirectory can have arbitrary subdirectories, e.g. "percussion", "keyboard", ..
   static String node_rootpath = rootpath + "/presets/node/";

   // true=try to auto-load "<node_rootpath>/<nodetype>/default.sno" node preset after node instantiation
   static boolean b_autoload_node_default_preset = true;
   // static boolean b_autoload_node_default_preset = false;

   // Pipe preset file directory
   //  (note) the directory can have arbitrary subdirectories, e.g. "percussion", "keyboard", ..
   static String pipe_rootpath = rootpath + "/presets/pipe/";

   // Groove preset file directory
   //  (note) stores TrigSeq timing track files
   static String groove_rootpath = rootpath + "/presets/groove/";

   // BezierEdit curve preset directory
   //  (note) files use ".curve" suffix
   static String curve_rootpath = rootpath + "/presets/curve/";

   // MIDIMorphScene preset directory
   //  (note) files use ".curve" suffix
   static String mms_rootpath = rootpath + "/presets/mms/";

   // Tracker Randomizer preset pattern file
   static String randomizer_pattern_pathname = rootpath + "/presets/randomizer_patterns.ini";

   static SetRootPath(String _path) {
      rootpath = _path;
      temp                        = rootpath + "/tmp/";
      project_rootpath            = rootpath + "/projects/";
      node_rootpath               = rootpath + "/presets/node/";
      pipe_rootpath               = rootpath + "/presets/pipe/";
      groove_rootpath             = rootpath + "/presets/groove/";
      curve_rootpath              = rootpath + "/presets/curve/";
      mms_rootpath                = rootpath + "/presets/mms/";
      randomizer_pattern_pathname = rootpath + "/presets/randomizer_patterns.ini";
   }

   // Tiny Synergy Replay temporary sampleinfo import filename prefix (from Eureka)
   static String tsr_sampleinfo_import_pathname_prefix = temp+"/eureka_tsr";

   // System text editor command
   //  (note) used for editing this config file
   static String textedit_cmd =
      Utils.IsMacOS()
      ? "/System/Applications/TextEdit.app/Contents/MacOS/TextEdit $(FILE)"
      : "notepad.exe $(FILE)"
      ;



   /***
    ***
    *** Multi-core configuration
    ***
    ***
    ***/

   // true=ignore all CPU core / affinity mask settings and let the OS figure it out
   static boolean b_ignore_thread_affinity = false || Utils.IsMacOS();

   // Process affinity mask. Each set bit represents one process core.
   //  (note) should include all cores used for UI/replay/track threads + cores allowed for (temporary) worker threads
   static String process_affinity_mask = "";

   // UI+replay thread affinity mask.
   static String ui_thread_affinity_mask = "";

   static method SetAffinity_i9_7980xe_no_ht() {
      // 18 logical cores (hyperthreading disabled in BIOS)
      //                         1   4   7   10  13  16..18
      process_affinity_mask   = "... ... ... ... ... .xx";
      ui_thread_affinity_mask = "... ... ... ... ... .xx";
      ;
   }

   static method SetAffinity_i7_13700H_ht() {
      // 6 performance cores with hyperthreading + 8 efficiency cores => 20 logical cores
      //  - reserve all performance cores + efficiency cores 1..5 for Eureka
      //  - reserve last efficiency core for OS
      //                         1  3  5  7  9  11 13 .. 20
      process_affinity_mask   = ".. .. .. .. .. .. .....xx.";
      ui_thread_affinity_mask = ".. .. .. .. .. .. .....xx.";
      ;
   }

   static method SetAffinity_m2_pro() {
      // 8 performance cores + 4 efficiency cores => 12 logical cores
      //  - reserve all performance cores + efficiency cores 1..5 for Eureka
      //  - reserve last efficiency core for OS
      //                         1 .. 8   9..12
      process_affinity_mask   = "........ .xx.";
      ui_thread_affinity_mask = "........ .xx.";
      ;
   }

   static method SetAffinity() {
      if(Utils.IsMacOS())
         SetAffinity_m2_pro();
      else
      {
         // SetAffinity_i9_7980xe_no_ht();
         SetAffinity_i7_13700H_ht();
      }
   }




   /***
    ***
    *** Window / UI setup
    ***
    ***/

   // GUI size
   //
   //   -1: custom display+cursor size
   //    0: small    (1k)
   //    1: small x2 (2k)
   //    2: large    (4k)
   //
   //   (note) also affects a number of other things (bevel borders, font+icon sizes, ..)
   //   (note) see synergy_config_local.tks
   //
   static int ui_size_preset = -1;

   // (note) this is used by Utils.KeycodeToHex() when capslock key is enabled (resp. shift is pressed)
   // (note) currently supported setting is "de". any other setting will default to a "us" layout (raw key codes).
   static String kbd_layout = "de";

   // Overall font-scaling (1.0 = optimized for 800x480 WVGA display)
   //  (note) don't change this
   static float font_scaling = 1.0;

   // Overall zoom level (scales all graphics by given factors)
   //  (note) window_sx/sy resp. fullscreen_sx/sy should be scaled by the same factor
   //  (note) non-integer zoom factors will result in badly readable fonts
   //  (note) the window size will automatically be scaled accordingly
   static float zoom_x = 1.0;
   static float zoom_y = 1.0;

   // Mouse cursor zoom level
   //  (note) e.g. for Linux 4k screens
   static int cursor_zoom = 1;

   // Window width (pixels)
   //  (note) at least 960
   static int window_sx = 960;

   // Window height (pixels)
   //  (note) at least 600
   static int window_sy = 600;

   // Override window position (pixels). -1=default pos
   //  (note) can be used to move window to secondary screen (set window_x to e.g. -1920)
   static int window_x = -1;
   static int window_y = -1;

   // static int window_x = 288;
   // static int window_y = 224;

   // static int window_x = 460;
   // static int window_y = 559;

   // Start in fullscreen mode
   static boolean b_fullscreen = false;
   //static boolean b_fullscreen = true;

   // Fullscreen width (pixels)
   static int fullscreen_sx = 1920;

   // Fullscreen height (pixels)
   static int fullscreen_sy = 1200;

   // Fullscreen bits-per-pixel (16, 24, 32)
   static int fullscreen_bits = 32;

   // set fullscreen resolution to display size
   static boolean b_fullscreen_displaysize = true;

   // When true, open+close small window during startup
   //  (note) work-around for certain ATI graphics drivers (may not be needed anymore)
   static boolean b_open_prerun_window = 0 && Utils.IsWindows();

   // When true, render UI 60 times per second. When false, limit FPS to 30.
   static boolean b_force_high_framerate = false;

   // When true, synchronize buffer swap to vertical retrace (for smooth cursor movements)
   static boolean b_vsync = true;

   // When true, windows and dialogs have transparent backgrounds
   static boolean b_transparent_layers = true;

   // Post processing effect (fullscreen mode) (0=none, 1=solid, 2=scanlines)
   static int postfx_fs_mode = 2;

   // Post processing color / opacity (fullscreen mode)
   static int postfx_fs_color = #2f003863;
   // #2f005071
   // #5f000000

   // Post processing effect (window mode) (0=none)
   static int postfx_win_mode = 0;

   // post processing color / opacity (window mode)
   static int postfx_win_color = #1f001823;

   // Default mode for EditMidiMap page (except for global mapping)
   static boolean b_midimap_automovecursor = true;

   // KbdFocusSelectionMode behaviour: true=hold, false=toggle
   //  (note) kbdfocus mode is enabled/toggled with right windows key
   static boolean b_kbdfocusselectionmode_behaviour = false;

   // Which UI page to start with
   //  available options: (must be prefixed with "RootForm.")
   //
   //    PAGE_START
   //    PAGE_CONFIG
   //    PAGE_MIDIMAPPER
   //    PAGE_MIDIMON
   //    PAGE_SYSEX
   //    PAGE_AUDIO
   //    PAGE_PROJECT
   //    PAGE_PIPEROOT
   //    PAGE_MIDIDEVS
   //    PAGE_WAVEFORMS
   //    PAGE_SUBSONGS
   //    PAGE_PREFS
   //    PAGE_PIPEMAP
   //
   // static int start_page = RootForm.PAGE_START;
   // static int start_page = RootForm.PAGE_WAVEFORMS;
   static int start_page = RootForm.PAGE_PIPEMAP;

   // 0="light" LookAndFeel
   // 1="dark" LookAndFeel
   // 2="mono" LookAndFeel
   static int lnf_idx = LookAndFeel.IDX_LIGHT;

   // see EnableColorGrading_Cycle() (usually overridden below)
   static int lnf_colorgrading_initial_idx = -1; // prepare but don't enable
   // static int lnf_colorgrading_initial_idx =  2; // desaturated
   // static int lnf_colorgrading_initial_idx =  5; // cold
   // static int lnf_colorgrading_initial_idx = 14; // green / monochromatic

   static IntArray lnf_preferred_colorgradings = [
      -1,  // light
      1,   // dark
      34,  // mono (1=dark_mono_3a, 20=mono_desaturated_2, 21=mono_gray_1, 31=mono_red_2, 33=mono_yellow_2, ..)
                                                  ];

   // true=RALT (next to space) toggles song replay (in addition to =/´ and numpad-enter)
   static boolean b_ralt_toggles_replay = true;

   // true=swap HOME/END keys (previous/next pattern)
   //  (note) recommended on Macbooks (or other notebooks) where HOME/END is mapped to fn-left/right
   //  (note) also affects SysEx editors (select prev/next patch)
   static boolean b_key_swap_pattern_home_end = false;



   /***
    ***
    *** MIDI setup
    ***
    ***/

   // Don't set this to false!
   //  (note) disabling MIDI can only be useful for debug purposes
   static boolean b_enable_midi = true;
   // static boolean b_enable_midi = false;

   // true=use LocalMIDI (built-in) loopback device emulation
   // false=use external MIDI loopback device (e.g. loopmidi on Windows, IAC driver on macOS)
   //  (note) MUST be enabled in Linux build or app won't be able to connect to Eureka audio host
   static boolean b_enable_localmidi = true;

   // 8 entries, def="vst_[a..h]"
   static StringArray localmidi_out_devicenames = ["vst_a",
                                                   "vst_b",
                                                   "vst_c",
                                                   "vst_d",
                                                   "vst_e",
                                                   "vst_f",
                                                   "vst_g",
                                                   "vst_h"
                                                   ];

   // // Enable special Korg Radias driver
   // //  (if true, "RADIAS 1 KBD/KNOB" MIDI in, and "RADIAS 1 SOUND" MIDI out devices are added automatically)
   // //  (note) please keep this disabled, for now
   // static boolean b_enable_korg_radias  = false;


   // // (note) Linux/ALSA portmidi<>alsa virmidi device mapping:
   // //         "VirMIDI 2-0"/*portmidi*/ == "Virtual Raw MIDI 0,1"/*alsa*/
   // //         "VirMIDI 2-1"/*portmidi*/ == "Virtual Raw MIDI 1,1"/*alsa*/
   // //         "VirMIDI 2-2"/*portmidi*/ == "Virtual Raw MIDI 2,1"/*alsa*/
   // //         "VirMIDI 2-3"/*portmidi*/ == "Virtual Raw MIDI 3,1"/*alsa*/

   // (note) Linux/ALSA snd_seq_dummy module configuration:
   //         - as root, create "/etc/modprobe.d/alsa_snd_seq_dummy.conf" with the following content:
   //            "options snd-seq-dummy ports=16"

   // Device used for incoming SysEx messages (sequencer<>audio process link)
   //  (note) [deprecated] Linux: e.g. "Midi Through Port-0" (when using tkmidi_portmidi + snd_seq_dummy)
   //  (note) [deprecated] Linux: $ /sbin/modprobe snd_seq_dummy ports=16
   //  (note) Linux: must set "b_enable_localmidi = true"
   // static String sysex_in_devicename = Utils.IsLinux() ? "Midi Through Port-0" : "remote_seq";
   static String sysex_in_devicename = "remote_seq";

   // Device used for outgoing SysEx messages (sequencer<>audio process link)
   //  (note) it's recommended to set pagesysex_auto_devicename to the same device (auto-route events to current synth editor)
   //  (note) [deprecated] Linux: e.g. "Midi Through Port-1"  (when using tkmidi_portmidi + snd_seq_dummy)
   //  (note) [deprecated] Linux: $ /sbin/modprobe snd_seq_dummy ports=16
   //  (note) Linux: must set "b_enable_localmidi = true"
   // static String sysex_out_devicename = Utils.IsLinux() ? "Midi Through Port-1" : "remote_audio";
   static String sysex_out_devicename = "remote_audio";

   // Redirect SysEx out device (usually "remote_audio") channel 1 events to "play" frame
   //  so they won't be transformed in e.g. node editors
   //  => prioritize Eureka sampler 'keyjazz' playback
   //  (note) can be toggled at runtime via lshift-F12
   static boolean b_sysex_out_ch1_to_play_frame = false;


   // Comma separated list of MIDI in device names
   //  (note) see device list in the console window
   static StringArray midi_in_devicenames = ["USB Keystation 49e",
                                             "MIDIIN2 (MIDIFACE 8x8)",
                                             "DSI Tetra",

                                             ""
                                             ];
   //static StringArray midi_in_devicenames = [];

   // true=add joysticks as virtual MIDI input devices
   static boolean b_midi_in_joysticks = true;

   // Index of masterkeyboard midiin device (empty String disable it)
   //  (note) this will be removed in future releases
   static String midi_in_master_name = "";

   // Hash of hashes (key=device name, value=attribute hashtable (filter settings))
   static HashTable midi_in_filter;

   // true=check for device disconnect/reconnect every few seconds
   //  (note) this may take a few milliseconds (usually ~2) and may therefore alter the midi timing a little bit
   //  (note) this automatically reverts to false if the check takes more than 5 milliseconds
   static boolean b_midi_in_keepalive = true;

   //  true=always use MIDI overflow buffers (restrict output to 48 bytes per tick)
   // false=never use MIDI overflow buffers
   // maybe=check "overflow" setting from first MIDI synth profile (usually channel 1)
   //
   //  (note) for the Sequential Pro 3 (USB MIDI), this MUST be set to either 'maybe' (=> enabled via synth profile)
   //          or 'true' (activate for all devs)
   //          (otherwise the synth will glitch, resulting in all sorts of erratic behaviour)
   //
   static boolean b_midi_overflow_buffers = maybe;

   // Comma separated list of MIDI out device names
   //  (note) see device list in the console window
   //static StringArray midi_out_devicenames = ["LoopBe Internal MIDI"]; // "Delta 1010LT MIDI"
   //static StringArray midi_out_devicenames = [ ];
   static StringArray midi_out_devicenames = [ "MIDIFACE 8x8",
                                               "MIDIOUT2 (MIDIFACE 8x8)",
                                               "MIDIOUT3 (MIDIFACE 8x8)",
                                               "MIDIOUT4 (MIDIFACE 8x8)",
                                               "MIDIOUT5 (MIDIFACE 8x8)",
                                               "DSI Tetra",
                                               ""
                                               ];

   // Hash of strings (key=device name, value=device alias)
   static HashTable midi_out_device_aliases;
   static HashTable midi_in_device_aliases;

   // Hash of strings (key=device name, value="0" or "1")
   static HashTable midi_device_clocks;

   // Hash of strings (key=device name, value="0" or "1")
   //  (note) bypasses MIDI Morph Scenes
   static HashTable midi_device_echos;

   // Hash of strings (key=device name, value="0" or "1")
   static HashTable midi_device_globals;

   // Hash of strings (key=device name, value="0" or "1")
   //  Enable/disable (N)RPN parser (CC#6/38/96..101), default=on
   static HashTable midi_device_param;

   // Hash of strings (key=device name, value="0" or "1")
   static HashTable midi_device_mmcrec;

   // Hash of strings (key=device name, value="0" or "1")
   static HashTable midi_device_mmcplay;

   // Hash of strings (key=device name, value="0" or "1")
   static HashTable midi_device_mtc;

   // Hash of strings (key=device name, value="0" or "1")
   //  (send song position pointer switches)
   static HashTable midi_device_spp;

   // Hash of strings (key=device name, value="0".."3")
   //   0: None
   //   1: AllNotesOff
   //   2: Indiv
   //   3: AllNotesOff + Indiv
   static HashTable midi_device_noteoffmodes;

   // Hash of strings (key=device name, value="0" (disable) or "1" (enable))
   static HashTable midi_device_midicache;

   // Hash of StringArrays (key=device name, value=channel profile ids (16 entries))
   static HashTable midi_device_profile_ids;

   // timeout after which a midi controller event recording transaction ends (milliseconds)
   //  (this is to prevent creation of a lot of undo entries while tweaking knobs)
   static int midi_step_record_undo_timeout = 250;

   static String midi_profiles_path = "midi_synth_profiles/";
   static String midi_profile_data_path = "midi_synth_profile_data/";
   static String midi_note_names_path = "midi_note_names/";

   // Maps input device alias or name to out device (alias or name)
   //  This is used to determine the matching output device for a given input device
   static HashTable midi_in_out_device_mapping;

   // Number of virtual MIDI output devices (used as internal routing buses)
   //   (note) totally number of individually addressable devices = num_virtual * 16
   //   (note) do NOT decrease this number if there are any projects that require this many
   //   (note) default is 16.
   static int midi_num_virtual_out_devices = 16;

   // Context-sensitive auto device A/B device names / aliases (node editor)
   //  (note) must match virtual device alias(es) ("internal_<0..n>")  (e.g. internal_15=autodev_a + internal_16=autodev_b)
   //  (note) then create a MIDI morph scene and map events from actual MIDI ports to the virtual device(s)
   //  (note) when a node editor is active, autodev_a events will be transformed to the actual node auto device a (if enabled)
   //  (note) output capturing records these transformed events (mute, prgchg, transpose, ..)
   static String autodev_a_devicename = "<autodev_a>";
   static String autodev_b_devicename = "<autodev_b>";

   // Context-sensitive <editfocus> device events will be forwarded to current node editor focus item (e.g. NodeModular module)
   //  (note) see st2::nodeeditor_editfocus_dev
   static String editfocus_devicename = "<editfocus>";

   // <swing> virtual device name
   //  (note) used when converting NodeSeq selection to temporary tracker node
   //          (when autodev B is set to <swing> device, port B will be copied to temporary node autodev B)
   static String autodev_swing_devicename = "<swing>";

   // forward incoming events to current sysex editor output device
   static String pagesysex_auto_devicename = "remote_audio";
   // true=redirect <sysex_out_devicename> events to current SysEx output device (e.g. note on+off)
   //  (note) can be toggled at runtime in the "stop-button" menu
   static boolean b_pagesysex_redirect_ev = true;

   // Maximum number of patch param diffs to send per replay tick while song is playing [8]
   //  (note) too many will choke the (USB) MIDI pipe
   static int pagesysex_montage_max_diffs_replay = 16;

   // Maximum number of patch param diffs to send per replay tick while song is not playing [64]
   static int pagesysex_montage_max_diffs_idle   = 64;

   // Workaround for disappearing / renamed-after-reboot MIDI devices
   //  (sometimes device appears under a new name after reboot, e.g.
   //    "nanoKONTROL2 1 SLIDER/KNOB" => "nanoKONTROL2 2 SLIDER/KNOB")
   //  If no device is found exactly matching the name and the searched-for name starts
   //   with any of these strings, it is compared to all open device names which also
   //   begin with that string.
   static StringArray midi_in_fuzzy_name_match = [
      "nanoKONTROL1",
      "nanoKONTROL2",
      "microKEY"
                                                  ];

   static StringArray midi_out_fuzzy_name_match = [
      "nanoKONTROL1",
      "nanoKONTROL2",
      "microKEY"
                                                  ];

   // Default auto device that will be selected the first time an auto filter is enabled
   //  (note) internal device names have to be enclosed in "<>", e.g. "<play_a>"
   static String midi_default_auto_filter_a_dev_name = "<play_a>";
   static String midi_default_auto_filter_b_dev_name = "<play_b>";

   // Controller interpolation timeout (milliseconds)
   //  (note) setting this to 0 disables any interpolation
   static int midi_ctl_ipol_timeout = 100;

   // true=send MIDI songpos when replay starts, song/cycle loops, or when the position is changed manually.
   //   (note) this setting confuses the Propellerheads Reason MIDI clock handler (clock will jitter)
   //           when looping short patterns (e.g. one beat)
   // maybe=only send songpos when it's changed manually or when replay starts.
   // false=never send songpos
   static boolean b_midi_send_songpos = maybe;
   // static boolean b_midi_send_songpos = false;
   // static boolean b_midi_send_songpos = true;

   // true=send MMC play/start (sysex) when replay starts. send MMC stop when replay stops.
   //  (note) this is configurable per device
   static boolean b_midi_send_mmc = true;

   // true=send MMC stop twice (causes Propellerhead Reason and Logic to rewind song pos to play start)
   static boolean b_midi_send_mmc_stop_2x = true;

   // true=send MTC (midi-time-code) hh:mm:sc:fr message when replay starts
   //  (note) recognized by Studio One 5 (although: as of [01Nov2020], it always crashes when recording was enabled and replay is stopped)
   //  (note) also requires per-device MTC option to be enabled (see MIDI device configuration page)
   static boolean b_midi_send_mtc = true;

   // shift MTC/SMPTE start position
   //   e.g. when MTC-slave (tape recorder) first actual bar is shifted by two bars, a
   //   value of 2*4*4=32 would mean that sequencer position 0 corresponds to MTC slave bar 3 in 4/4 measure
   static float midi_mtc_offset_16th = 2 * (4/*16th_per_beat*/ * 4/*quarter notes per bar*/);

   // MTC start-shift (start tape-recorder ahead of actual sequencer start position)
   //  (note) this will delay the sequencer start by <n> millisec
   static float midi_mtc_prerun_ms = 4*250.0f;

   // fine-tune sequencer start alignment to MTC slave (tape recorder) (millisecond delta)
   static float midi_mtc_seq_align_ms = -61.0f;

   // workaround for Presonus Studio One 5.1.0.61423 crash-after-mmc-rec-when-replay-is-stopped issue:
   //  - in LoopMIDI, add another loopback device "studio_one_remote"  (can't use "studio_one_sync" for this)
   //  - in s1, MIDI learn CC#1 (type "button on/off"), then right-click and "add command" "transport / record"
   //  - in Synergy, enable this option to send CC#1 value=127 (on CH#1) when replay is started via lshift-KP_enter (song replay+send MMC)
   //     (do NOT enable "Rec" or "Play" MMC options in "studio_one_sync" device config !)
   //     (note) CC is sent / recording starts when sequencer is started (after midi_mtc_prerun_ms+midi_mtc_seq_align_ms)
   static boolean b_mtc_studio_one_record_workaround = true;
   static String  mtc_studio_one_remote_devname = "studio_one_remote";
   static int     mtc_studio_one_rec_pre_ms = 100;   // start record <n> millisec before sequencer start

   // workaround for MIDI SPP issue in Logic:
   //  - control playhead with alternative pitchbend message [$E0 sppLo sppHi] instead of [$F2 sppLo sppHi]
   //  - in Synergy, the alias or device name of the (virtual) MIDI device that sends MMC/SPP to Logic must be called "logic"
   //  - in the Logic "Controller Surface => Controller Assignments" "Expert View" (no zone, no mode) add
   //        Parameter: "Playhead (beats)"
   //             Part: "Beats"
   //            Input: "logic"
   //     Value Change: "E0 Lo7 Hi7"
   //  => The Logic playhead then follows the Synergy playhead in increments/granularity of one beat (i.e. 4 16th in 4/4)
   //  (see STX_MIDIOutDevice.tks)
   // static boolean b_midi_logic_spp_alt_pb_message = false;
   static boolean b_midi_logic_spp_alt_pb_message = true;

   // workaround for MIDI MMC rec issue in Logic (does not start recording but simply arms current track):
   //  - send alternative MIDI message [90 7F 7F] to "logic" device (CC#110 value = 127)
   //  - in the Logic "Controller Surface => Controller Assignments" "Expert View" (no zone, no mode) add
   //            Class: "Key Command"
   //          Command: "Global Commands" "Record"
   //            Input: "logic"
   //     Value Change: "90 7F 7F"
   //
   //  (note) sending SPP followed by rec msg causes Logic to immediately stop recording (a bug)
   //          => don't send SPP when starting to record
   //               => seems to "fix" regular MMC rec msg (i.e. set this option to false)
   //
   //  (note) when "Autopunch" is enabled in Logic, simply arm your tracks and start the replay in Synergy
   //          (otherwise start the replay with lshift to send the MMC rec/punch-in message)
   //
   static boolean b_midi_logic_mmc_rec_alt_message = false;
   // static boolean b_midi_logic_mmc_rec_alt_message = true;

   // workaround for MIDI rec-start issue in Logic (using b_midi_logic_mmc_rec_alt_message=true):
   //  recording stops immediately after start
   //  => set this to true to skip midiContinue()+midiStart() after mmcRec()
   static boolean b_midi_logic_dont_send_cont_play_after_mmc_rec = true;

   // DAW remote-ctrl MIDI device name or alias
   //  - send "90 7E 7F" note-on message when "(NUMPAD) lctrl-x -" is pressed
   //  (note) in Logic, map this to
   //           Command: "Various Windows" "Undo"
   //             Input: "logic"
   //             Value: "90 7E 7F"
   static String midi_daw_remote_ctl_dev = "logic";

   // true=clear all MIDI caches when replay stops (e.g. to sync with DAW, which may reset controllers when stop cmd is recv'd)
   static boolean b_clear_midi_cache_when_replay_stops = true;

   // MIDI metronome device alias or name
   static String midi_metronome_device = "vst_d";//d:drums

   // MIDI metronome channel (0..15)
   static int midi_metronome_ch = 15;

   // MIDI metronome 'bar' note (0..127)
   static int midi_metronome_note_bar = 5*12;

   // MIDI metronome 'beat' note (0..127)
   static int midi_metronome_note_beat = 6*12;

   // Enable MIDI metronome
   static boolean b_midi_metronome = true;

   // Hexdump MIDI output buffer(s) to console (skip clock)
   static boolean b_midi_debug_out_cmd = false;
   // static boolean b_midi_debug_out_cmd = true;

   // Hexdump MIDI output buffer(s) to console (note data)
   static boolean b_midi_debug_out_note = false;
   // static boolean b_midi_debug_out_note = true;

   // Output monitor background capture (def=true)
   //  (note) can be toggled in UI
   //  (note) capturing does not start until output monitor page is shown for the first time
   //  (note) skips buffers that are shorter than 2 bytes (timing clock)
   static boolean b_output_mon_bg = true;

   // Output monitor history size (def=4096)
   static int output_mon_max_log_lines = 4096;

   // Output monitor history size (num buffers since last monitor UI update, per device) (def=256)
   static int output_mon_max_log_entries = 256;

   // Output monitor hexdump width (num columns) (def=24)
   static int output_mon_hexdump_cols = 24;

   // Initial editor selection after startup (MIDI synth profile id)
   // static String sysex_preferred_editor_profile_id = "dsi_prophet_12";
   static String sysex_preferred_editor_profile_id = "novation_peak";
   // static String sysex_preferred_editor_profile_id = "yamaha_fs1r";
   // static String sysex_preferred_editor_profile_id = "dsi_prophet_08";
   // static String sysex_preferred_editor_profile_id = "korg_radias";
   // static String sysex_preferred_editor_profile_id = "moog_sub37";
   // static String sysex_preferred_editor_profile_id = "yamaha_montage";
   // static String sysex_preferred_editor_profile_id = "pro_3";

   // PatternManager: Base note for MIDI pattern selection (def=C-4) (can be changed in UI)
   static int patternmgr_midi_base_note = 12*4;

   // PatternManager: MIDI pattern selection mode (def=white only) (can be changed in UI)
   static int patternmgr_midi_mode = PatternManager.MIDI_MODE_USED_WHITE;

   // MIDIMorph: scene selection device (see internal.msp RPNs 410..418)
   //  (note) default is "<global>"
   static String midimorph_scene_selection_device_name = "<global>";

   // MIDIMorph: scene selection device midi channel
   //  (note) default is 0 (channel 1)
   static int midimorph_scene_selection_device_ch = 0;

   // true=lctrl-x 0..9 selects multi morph scene 1..10
   // false=lctrl-x 0..9 selects subsong 1..10
   static boolean b_lctrlx_kp_select_multimorph = false;

   // true=Show incoming ctl info in status bar
   static boolean b_show_ctl_info = true;

   // true=Show poly+channel pressure and MPE brightness (cc#74) ctl info
   static boolean b_show_ctl_info_hifreq = false;

   // >=1: replace MIDI Map Context target encoder click modulo
   //   (note) the click modulo thins out encoder events by only letting every nth event pass through
   static int mmt_force_encoder_delta_mod = 6;

   // note octave offset
   //    0: no offset (octave = (MIDI note nr / 12)   ) => middle C(5*12=60) = C-5
   //   -1: Roland    (octave = (MIDI note nr / 12) -1) => middle C(5*12=60) = C-4
   //   -2: Yamaha    (octave = (MIDI note nr / 12) -2) => middle C(5*12=60) = C-3
   static int midi_note_octave_offset = 0;

   // true=use '-1' and '-2' glyphs in NodeTrackerPatternView
   // false=use 'C~1', 'C=2' style note display (when midi_note_octave_offset is < 0)
   static boolean b_node_tracker_note_octave_offset_glyphs = true;

   // Montage / MODX SysEx editor model selection
   //  0=Montage (model id = 0x02)
   //  1=MODX (model id = 0x07)
   static boolean b_pagesysex_modx = 0;



   /***
    ***
    *** Node classification
    ***
    ***/

   // Dynamic library (TSL) dependencies
   //  - indexed by node class name
   static HashTable node_type_tsls = #[
      "NodeAnalogRytm" = "node_analogrytm.tsl",
      "NodeBuffer"     = "node_buffer.tsl",
      "NodeCtlMorph"   = "node_ctlmorph.tsl",
      "NodeDelay"      = "node_delay.tsl",
      "NodeKeymap"     = "node_keymap.tsl",
      "NodeModular"    = "node_modular.tsl",
      "NodeMonoPoly"   = "node_monopoly.tsl",
      "NodeProxy"      = "node_proxy.tsl",
      "NodeScript"     = "node_script.tsl"
                                      ];

   // "New Node" page options
   static HashTable node_type_display_names = #[
      "NodeAnalogRytm" = "AnalogRytm",
      "NodeArranger"   = "Arranger",
      "NodeBuffer"     = "Buffer",
      "NodeCtlMorph"   = "CtlMorph",
      "NodeDelay"      = "Delay",
      "NodeKeymap"     = "Keymap",
      "NodeModMatrix"  = "ModMatrix",
      "NodeModular"    = "Modular",
      "NodeMonitor"    = "Monitor",
      "NodeMonoPoly"   = "MonoPoly",
      "NodeProxy"      = "Proxy",
      "NodeScript"     = "Script",
      "NodeSeq"        = "Freeform",
      "NodeTracker"    = "Tracker",
      "NodeTrigSeq"    = "TrigSeq"
                                                ];

   // "New Node" page node type icon ids
   //   (note) "mono" LNF version will be postfixed with "_mono"
   static HashTable node_type_icons = #[
      "NodeAnalogRytm" = "node_analogrytm",
      "NodeArranger"   = "node_arranger",
      "NodeBuffer"     = "node_buffer",
      "NodeCtlMorph"   = "node_ctlmorph",
      "NodeDelay"      = "node_delay",
      "NodeKeymap"     = "node_keymap",
      "NodeModMatrix"  = "node_modmatrix",
      "NodeModular"    = "node_modular",
      "NodeMonitor"    = "node_monitor",
      "NodeMonoPoly"   = "node_monopoly",
      "NodeProxy"      = "node_proxy",
      "NodeScript"     = "node_script",
      "NodeSeq"        = "node_seq",
      "NodeTracker"    = "node_tracker",
      "NodeTrigSeq"    = "node_trigseq"
                                        ];


   // Node classification
   //  (note) The names should be short
   //  (note) Node classes are saved as strings in project files
   static StringArray node_class_names = [
      "Arps",
      "Arps.mod",
      "Audio",
      "Audio.mod",
      "Bass",
      "Bass.mod",
      "Chords",
      "Chords.mod",
      "Drone",
      "Drone.mod",
      "Drums",
      "Drums.mod",
      "FX",
      "FX.mod",
      "Keys",
      "Keys.mod",
      "Lead",
      "Lead.mod",
      "Misc",
      "Misc.mod",
      "Patch",
      "Vocal",
      "Vocal.mod",
                                          ];

   // Preferences for NewTrackDialog
   //  - Trackname / automation port tuples
   //     - trackname is used as pipe name and initial node name (postfixed with 1..n)
   //     - the (optional) '.' delimited sub-name is used for node names only
   //  - The number after the device name determines the preferred midi ch
   //
   //  See also: STConfig.node_class_names
   //

   // Selects which node types will be listed in UI (when "All Types" checkbox is unchecked)
   static StringArray newtrackdialog_node_types_instr = [
      "Freeform",
      "Modular",
      // "Monitor",
      "Tracker",
      "TrigSeq",
                                                   ];

   // Selects which node types will be listed in UI (when "All Types" checkbox is checked)
   static StringArray newtrackdialog_node_types_all = [
      "AnalogRytm",
      "Arranger",
      "Buffer",
      "CtlMorph",
      "Delay",
      "Freeform",
      "Keymap",
      "ModMatrix",
      "Modular",
      "Monitor",
      "Monopoly",
      "Proxy",
      "Script",
      "Tracker",
      "TrigSeq",
                                                   ];

   // Preferred automation ports (per track category)
   static StringArray newtrackdialog_track_categories = [
      "Audio",           "<play_h>:8",  // for clips, stems, atmos, .. (long samples in general)
      "Audio.mod",       "<play_h>:9",
      "Arps",            "<play_c>:1",
      "Arps.mod",        "<play_c>:2",
      "Bass",            "<play_b>:1",
      "Bass.mod",        "<play_b>:2",
      "Chords",          "<play_c>:1",
      "Chords.mod",      "<play_c>:2",
      "Drone",           "<play_g>:1",
      "Drone.mod" ,      "<play_g>:2",
      "Drums",           "<play_d>:1",
      "Drums.mod",       "<play_d>:2",
      "Drums.bd_sd_cp",  "<play_d>:1",
      "Drums.cb_cl_ma",  "<play_d>:2",
      "Drums.ch_oh_cy",  "<play_d>:3",
      "Drums.lt_mt_ht",  "<play_d>:4",
      "Drums.zap_noise", "<play_d>:5",
      "FX",              "<play_f>:1",
      "FX.mod",          "<play_f>:2",
      "Keys",            "<play_a>:1",
      "Keys.mod",        "<play_a>:2",  // e.g. for polymetric parameter modulation
      "Lead",            "<play_e>:1",
      "Lead.mod",        "<play_e>:2",
      "Misc",            "<play_g>:1",
      "Misc.mod" ,       "<play_g>:2",
      "Vocal",           "<play_h>:1",
      "Vocal.mod" ,      "<play_h>:2",
                                                           ];

   // Fallback automation port (e.g. if category is [current pipe] and name cannot be matched)
   static String newtrackdialog_default_autodev = "<pipe>";

   // User defined preset filter history
   static StringArray newtrackdialog_user_filter_history = [
      "p12",
      "sub37",
      "tempest",
      "vst",
      "bd_sd",
      "ch",
      "",
                                                            ];

   // true=set initial kbdfocus to "Filter" ComboField
   // false=set initial kbdfocus to "Name" TextField
   static boolean b_newtrackdialog_prefer_filter_focus = true;

   // true=initially show all node types
   // false=show instrument types only
   static boolean b_newtrackdialog_type_all = true;

   // true=first pipe node default name equals pipe name
   static boolean b_first_node_name_follows_pipe = true;



   /***
    ***
    *** MIDI Setup // Push
    ***
    ***/

   // Support for Ableton Push controller. "Push" MIDI input device alias must exist.
   static boolean b_push = true;

   // Push mode (mmc, kbd, ctl, usr)
   // static int push_mode = Push.MODE_MMC;
   static int push_mode = Push.MODE_KBD;

   //
   // Push MODE_KBD
   //
   static int push_kbd_base_oct   = 4;  // octave of left/bottom note
   static int push_kbd_row_add    = 5;  // note delta to row above
   static int push_kbd_key        = 0;  // 0=c, 11=b
   static int push_kbd_mode       = Scale.MODE_MAJOR;
   static int push_kbd_shift_x    = 0;  // shifts note pads left or right
   // static int push_kbd_split_mode = PushKbd.SPLIT_NONE;
   static int push_kbd_split_mode = PushKbd.SPLIT_DUAL_H;
   // static int push_kbd_split_mode = PushKbd.SPLIT_DUAL_V;
   // static int push_kbd_split_mode = PushKbd.SPLIT_QUAD;

   static int push_kbd_color_key_note      = 45;
   static int push_kbd_color_key_note_hl   = 41;
   static int push_kbd_color_scale_note    = 22;
   static int push_kbd_color_scale_note_hl = 25;
   static int push_kbd_color_other_note    = 0;
   static int push_kbd_color_other_note_hl = 1;




   /***
    ***
    *** MIDI Setup // FaderPort (8/16)
    ***
    ***/

   // true= Enable support for Presonus Faderport (v2) controller. "FaderPort" MIDI input+output device aliases must exist.
   static boolean b_faderport = true;

   // FaderPort model (8 or 16)
   static int faderport_model = 16;

   // 64=highest precision (full 14bit)
   // 32=thirty-two times the coarse precision (13 bit)
   // 16=sixteen times the coarse precision (12 bit)
   //  8=eight times the coarse precision (11 bit)
   //  4=four times the coarse precision (10 bit)
   static int faderport_detailedit_div = 8;


   //
   // PagePipeMap
   //

   // Default pattern-nr display type
   //  (note) can also be changed by clicking on the "Pattern" LabelButton
   static int pipemap_patnr_display = 0;

   // Delta time since undo save after which a warning dialog is shown when trying to restore the undo state
   //  (note) default = (5 * 60 * 1000) = 5 min
   static int pipemap_undo_state_warning_delta_ms = 5 * 60 * 1000;

   // Pre-count when starting recording via RSHIFT
   //  (note) 0=no precount
   static float pipemap_precount_num_bars = 1.0;

   // true=start pre-count
   //  (note) can be toggled with lctrl-x RCTRL at runtime
   static boolean b_pipemap_precount = true;

   // number of nodes per row (0=auto)
   static int pipemap_num_nodes_per_row = 0;

   // true=draw replay position
   static boolean b_pipemap_progress = true;

   // >0: highlight node button when play offset approaches play/loop len
   static int pipemap_progress_warn_16th = 8;

   // (max) node highlight alpha (0..255)
   static byte pipemap_progress_warn_alpha = #70;



   /***
    ***
    *** NodeTracker / PatternView
    ***
    ***
    ***/

   // Recording Precount (0=off)
   static float node_tracker_precount_num_bars = 1.0;

   // true=start pre-count when recording starts (RSHIFT)
   //  (note) can be toggled with lctrl-x RCTRL at runtime
   static boolean b_node_tracker_precount = true;

   // Enable MIDI KeyJazz
   static boolean b_node_tracker_midi_keyjazz = false;

   // true=don't use '=' (US) / '´' (DE) key (next to backspace) for keyjazz F#*
   //       (and instead use it for toggling song replay on keyboards without numpads (default/fallback))
   static boolean b_node_tracker_reserve_equals_key_for_replay = true;

   // Initial pattern length (num 16th)
   //  (old) default=4096 ( (4096/4) = 1024 beats = 256 4/4 bars => 256*2sec ~= 8.5min @120bpm)
   //  default=256 ( (256/4) = 64 beats = 16 4/4 bars => 16*2sec ~= 32sec @120bpm)
   static int node_tracker_initial_pattern_length_16th = 16;
   // static int node_tracker_initial_pattern_length_16th = 256;

   // // // true=auto reset MMC checkbox after recording was started
   // // static boolean b_node_tracker_midi_mmcrec_autoreset = true;

   // true= immediately play pattern when it is selected
   // false= only change edit pattern and play it only when requested (e.g. via lctrl-return)
   static boolean b_node_tracker_selectandplay = false;

   // true=delegate chpressure,polyat,modwheel,breathcontrol,expression,sustain,softpedal events to keyjazz instrument
   static boolean b_node_tracker_delegate_perf_events = true;

   // true=ignore instr/envelope duration setting
   static boolean b_node_tracker_keyjazz_manual_noteoff = true;

   // true=advance step only when all (MIDI) note keys have been released (useful for entering chords in step-edit mode)
   static boolean b_node_tracker_advance_allnotesreleased = true;

   // true=record note off events
   static boolean b_node_tracker_recordnoteoff = true;

   // true=record note off events only when replay is enabled
   static boolean b_node_tracker_recordnoteoff_replay = true;

   // true=record CTL_DURn events
   static boolean b_node_tracker_recordnoteoff_dur = true;

   // true=quantize events to step resolution(zoom) while recording
   static boolean b_node_tracker_recordquantize = false;

   // undo/record restart quantization
   // <0: restart immediately
   // >0: quantize record restart (num beats)
   //  0: restart recording when pattern restarts
   //static float node_tracker_record_restart_quantization = -1;
   static int node_tracker_record_restart_quantization = 2;
   // static float node_tracker_record_restart_quantization = 0;

   // true=always quantize last step (useful for placing notes exactly on first step)
   static boolean b_node_tracker_always_quantize_last_step = true;

   // latency compensation (num millisec) when recording actual MIDI controller ("frameRec")
   //  (note) e.g. 5ms when reference audio (metronome, beat etc) is played through a 5ms audio buffer
   static float node_tracker_rec_latency = 10;
   static boolean b_node_tracker_rec_latency = false;

   // true=ignore program changes (pattern select), RPN_COMMON_SEEK_OFFSET, RPN_COMMON_MUTE_TEMP when
   //       replay is started from pattern editor
   //  (note) these events are usually sent by Arranger nodes and can get in the way when recording long patterns
   //  (note) this setting will only have an effect if the replay is started via SPACE, RSHIFT, or RETURN
   static boolean b_node_tracker_ignore_seek_prgchg_mute_during_pattern_replay = true;

   // true=follow arranger / replay pattern
   // false=edit pattern is independent of replay pattern
   static boolean b_node_tracker_edit_pattern_follows_replay_pattern = false;

   // Micro-shift defaults (selected with lctrl-lshift-0..9)
   static IntArray node_tracker_microshift_ticks = [96, 1, 2, 3, 4, 6, 8, 12, 24, 48];

   // true=show track names
   static boolean b_node_tracker_show_track_names = true;

   // true=double center row height
   static boolean b_node_tracker_doublecenter_hidpi = true;
   static boolean b_node_tracker_doublecenter_lodpi = true;

   // true=double char width
   static boolean b_node_tracker_doublewidth_hidpi = true;
   static boolean b_node_tracker_doublewidth_lodpi = false;

   // true=double char height
   static boolean b_node_tracker_doubleheight_hidpi = true;
   static boolean b_node_tracker_doubleheight_lodpi = false;

   // true=auto-double width in timing view
   static boolean b_node_tracker_timing_view_autodouble_width = false;

   // true=auto-double height in timing view
   static boolean b_node_tracker_timing_view_autodouble_height = false;

   static int node_tracker_celcolor_alpha = 256 * 0.5; // overall celcolor alpha scaling (0..256)

   // dim-level of wrapped pattern steps (text-color) (def=0.77).
   static float node_tracker_wrap_dim = 0.77;

   // dim-level of wrapped pattern steps (cel bg alpha) (def=0.5).
   static float node_tracker_wrap_dim_bgalpha = 0.5 * 0.5;

   // dim-level of timing mode events
   static float node_tracker_timing_dim = 0.3;
   static float node_tracker_wrap_timing_dim = 0.84;

   // dim-level of ghost pattern steps
   static float node_tracker_ghost_dim = 0.6;
   static float node_tracker_ghost_wrap_dim = 0.89;
   static float node_tracker_ghost_timing_dim = 0.7;
   static float node_tracker_ghost_wrap_timing_dim = 0.94;

   // true=auto-colorize cels each time a cell is added (preserves custom colors)
   static boolean b_node_tracker_autocolorize_cels = true;

   // true=use R,G,B,A float color arrays
   // false=use glColorPointer(4, GL_UNSIGNED_BYTE, ..) (faster but broken in macOS 13.5: red is always 0)
   static boolean b_node_tracker_use_colorarray4f = Utils.IsMacOS();

   // Palette for STYLE_COLOR cels.
   //  (note) the number of entries must be at least 1
   static IntArray node_tracker_ctlcolor_palette = [

      // ST² colors:
      #ff000000,  // 0 , 0x00
      #ff9F9F9F,  // 1 , 0x01
      #ffFFFFFF,  // 2 , 0x02
      #ff009F9F,  // 3 , 0x03
      #ff00FFFF,  // 4 , 0x04
      #fff7a200,  // 5 , 0x05
      #ffFFFF00,  // 6 , 0x06
      #ff009F00,  // 7 , 0x07
      #ff00FF00,  // 8 , 0x08
      #ff9F0000,  // 9 , 0x09
      #ffFF0000,  // A , 0x0A
      #ff0000FF,  // B , 0x0B
      #ff5f5fFF,  // C , 0x0C
      #ff7a8be3,  // D , 0x0D
      #ff9F009F,  // E , 0x0E
      #ffFF00FF,  // F , 0x0F

      // VICE C=64 colors (by LuN^a^TiC):
      #ff000000,  // G , 0x10
      #ffFFFFFF,  // H , 0x11
      #ffAB3126,  // I , 0x12
      #ff66DAFF,  // J , 0x13
      #ffBB3FB8,  // K , 0x14
      #ff55CE58,  // L , 0x15
      #ff1D0E97,  // M , 0x16
      #ffEAF57C,  // N , 0x17
      #ffB97418,  // O , 0x18
      #ff785300,  // P , 0x19
      #ffDD9387,  // Q , 0x1A
      #ff5B5B5B,  // R , 0x1B
      #ff8B8B8B,  // S , 0x1C
      #ffB0F4AC,  // T , 0x1D
      #ffAA9DEF,  // U , 0x1E
      #ffB8B8B8   // V , 0x1F

      // VICE C=64 colors (v2.2):
      #ff000000,  // W , 0x20
      #ffFFFFFF,  // X , 0x21
      #ff68372b,  // Y , 0x22
      #ff70a4b2,  // Z , 0x23
      #ff6f3d86,  // a , 0x24
      #ff588d43,  // b , 0x25
      #ff352879,  // c , 0x26
      #ffb8c76f,  // d , 0x27
      #ff6f4f25,  // e , 0x28
      #ff433900,  // f , 0x29
      #ff9a6759,  // g , 0x2A
      #ff444444,  // h , 0x2B
      #ff6c6c6c,  // i , 0x2C
      #ff9ad284,  // j , 0x2D
      #ff6c5eb5,  // k , 0x2E
      #ff959595,  // l , 0x2F
                                                    ];

   // true=cursor follows replay position
   static boolean b_node_tracker_autoscroll = true;

   // true=enable cursor wrap-around
   static boolean b_node_tracker_wraparound = true;

   // true=redraw pattern 30/60 fps vsync, false=use timer, also see b_force_high_framerate
   static boolean b_node_tracker_smoothscroll = true;

   // true=briefly show zoom/step resolution note length as text, false=show icons only
   static boolean b_node_tracker_zoomicon_anim = true;

   // true=prefer text when changing zoom (1/16, ..).
   // false=prefer note icon
   static boolean b_node_tracker_zoomicon_prefer_text = true;

   // true=reverse zoom keys (default=false, i.e. left([/ü]=zoom out, right(]/+)=zoom in)
   static boolean b_node_tracker_reverse_zoom_keys = false;

   // zoom presets (#ticks) for new nodes
   //  (note) toggle with ''' (us/en) / 'Ä' (de) key, or 'f' key
   //  (note) these are just the reset values. they can be edited and will be saved per node.
   static int node_tracker_zoom_preset_1 = 48;
   static int node_tracker_zoom_preset_2 =  1;

   // true=also add CTL_VELn when adding new note-cel via "addnotecel" (ctrl-x n)
   static boolean b_node_tracker_addnotecel_vel = true;

   // true=also add CTL_DURn when adding new note-cel via "addnotecel" (ctrl-x n)
   static boolean b_node_tracker_addnotecel_dur = true;

   // true=auto-group note,vel,dur when adding note cels (note1,vel1,dur1,note2,vel2,dur2,..)
   // false= note1,note2,..,vel1,vel2,..,vel5,dur1,dur2,..
   static boolean b_node_tracker_addnotecel_autogroup = true;

   // true=mouseover highlights row
   static boolean b_node_tracker_highlight_mouseover_col = true;

   // true=mouseover shows cel info in status bar
   static boolean b_node_tracker_info_mouseover_col = true;

   // true=cursor move increases row highlight
   static boolean b_node_tracker_highlight_cursormove = true;

   // true=cursor move left/right increases row highlight
   static boolean b_node_tracker_highlight_cursormovex = false;

   // true=draw soundtracker style note-on equalizer (lots of redraws!)
   static boolean b_node_tracker_draweq = true;

   // true=show RMS of audio generator, if possible
   // static boolean patternview_eqrms = true;

   // equalizer opacity (0..255)
   static float node_tracker_eq_opacity = 127;

   // false = SPACE jumps to bookmarked position, lctrl-SPACE stops at current position
   //  true = SPACE stops at current position, lctrl-SPACE jumps to bookmarked position
   static boolean b_node_tracker_swapstopbookmark = false;

   // Number of steps (page up/down, mousewheel) (node default)
   static int node_tracker_default_step_alignment = 8;

   // Display pattern offset column as decimal number (instead of hex) [default=false]
   static boolean b_node_tracker_decimal_patternoffset = false;

   // true=pattern offset display starts with 1
   // false=pattern offset display starts with 0 [default]
   static boolean b_node_tracker_shift_patternoffset = false;

   // RMB-drag scroll speed
   static float node_tracker_rmbdrag_speed = 0.25;
   // static float patternview_rmbdrag_maxdelta_y = 3;
   static float node_tracker_rmbdrag_maxdelta_y = 8;

   // Controller dependent fine inc-/decrements for lctrl-x left/right keyboard shortcut
   //  (note) if a delta value is positive, an integer, and >1.0, the respective controller will be aligned/rounded
   //          (e.g. in case of delta=8.0, value 5.0 becomes 8.0 when inc'd)
   static FloatArray node_tracker_incdec_fine_deltas = [
      1.0f, 1.0f, 1.0f, 1.0f, 1.0f, // CTL_NOTE1..5
      1.0f, 1.0f, 1.0f, 1.0f, 1.0f, // CTL_VEL1..5
      1.0f, 1.0f, 1.0f, 1.0f, 1.0f, // CTL_DUR1..5
      1.0f, 1.0f, 1.0f, 1.0f, 1.0f, // CTL_POLYAT1..5
      1.0f,  // CTL_INSTR
      1.0f,  // CTL_PC
      1.0f,  // CTL_AT
      1.0f,  // CTL_PB
      1.0f,  // CTL_SLIDE
      1.0f,  // CTL_DELAY
      1.0f,  // CTL_RETRIG
      0.5f,  // CTL_BPM

      1.0f,  // CTL_28 (unused)
      1.0f,  // CTL_29 (unused)
      1.0f,  // CTL_30 (unused)

      1.0f,  // CTL_COLOR

      1.0f, 1.0f, 1.0f, 1.0f,  // CTL_USR1..4
      1.0f, 1.0f, 1.0f, 1.0f,  // CTL_USR5..8
      1.0f, 1.0f, 1.0f, 1.0f,  // CTL_USR9..12
      1.0f, 1.0f, 1.0f, 1.0f   // CTL_USR13..16
                                                       ];

   // Controller dependent coarse inc-/decrements for lctrl-x left/right keyboard shortcut
   //  (note) if a delta value is positive, an integer, and >1.0, the respective controller will be aligned/rounded
   //          (e.g. in case of delta=8.0, value 5.0 becomes 8.0 when inc'd)
   static FloatArray node_tracker_incdec_coarse_deltas = [
      -12.0f, -12.0f, -12.0f, -12.0f, -12.0f, // CTL_NOTE1..5
      8.0f, 8.0f, 8.0f, 8.0f, 8.0f, // CTL_VEL1..5
      4.0f, 4.0f, 4.0f, 4.0f, 4.0f, // CTL_DUR1..5
      8.0f, 8.0f, 8.0f, 8.0f, 8.0f, // CTL_POLYAT1..5
      1.0f,  // CTL_INSTR
      1.0f,  // CTL_PC
      8.0f,  // CTL_AT
      4.0f,  // CTL_PB
      8.0f,  // CTL_SLIDE
      3.0f,  // CTL_DELAY
      3.0f,  // CTL_RETRIG
      4.0f,  // CTL_BPM

      1.0f,  // CTL_28 (unused)
      1.0f,  // CTL_29 (unused)
      1.0f,  // CTL_30 (unused)

      1.0f,  // CTL_COLOR

      8.0f, 8.0f, 8.0f, 8.0f,  // CTL_USR1..4
      8.0f, 8.0f, 8.0f, 8.0f,  // CTL_USR4..7
      8.0f, 8.0f, 8.0f, 8.0f,  // CTL_USR8..11
      8.0f, 8.0f, 8.0f, 8.0f   // CTL_USR12..15
                                                       ];

   // true=auto stop MIDI learn instrument once one instrument has been added
   // false=keep learning until timer expires (can learn multiple instruments in one go)
   static boolean b_node_tracker_midilearn_instr_single = true;

   // true=show undo/redo debug messages
   static boolean b_node_tracker_undo_debug = false;

   // size of undo transaction stack (must not be less than 1)
   static int node_tracker_undo_stack_size = 1024;

   // true=treat pattern modifications during replay with edit-mode enable as one transaction/undo entry (recommended)
   // false=every single modification done while recording can be undone (not very well tested)
   static boolean b_node_tracker_undo_record_single_transaction = true;

   // lowest note for tap mode step selection (notes below this increment the current source step nr)
   static int node_tracker_tap_mode_low_note = 48; // c-4

   // NT_ManageColumnsDialog geometry
   static Geometry4f node_tracker_dlg_mgrcol_select_geo_lodpi = [ -1, -1, -1, -1 ]; // SELECT mode
   static Geometry4f node_tracker_dlg_mgrcol_select_geo_hidpi = [ -1, -1, -1, -1 ]; // SELECT mode
   static Geometry4f node_tracker_dlg_mgrcol_add_geo_lodpi    = [ -1, -1, -1, -1 ]; // ADD mode
   static Geometry4f node_tracker_dlg_mgrcol_add_geo_hidpi    = [ -1, -1, -1, -1 ]; // ADD mode
   static Geometry4f node_tracker_dlg_mgrcol_remove_geo_lodpi = [ -1, -1, -1, -1 ]; // REMOVE mode
   static Geometry4f node_tracker_dlg_mgrcol_remove_geo_hidpi = [ -1, -1, -1, -1 ]; // REMOVE mode
   static Geometry4f node_tracker_dlg_mgrcol_manage_geo_lodpi = [ -1, -1, -1, -1 ]; // MANAGE mode
   static Geometry4f node_tracker_dlg_mgrcol_manage_geo_hidpi = [ -1, -1, -1, -1 ]; // MANAGE mode
   static float node_tracker_dlg_mgrcol_select_split_hidpi = 0.5;
   static float node_tracker_dlg_mgrcol_select_split_lodpi = 0.5;
   static float node_tracker_dlg_mgrcol_add_split_hidpi    = 0.5;
   static float node_tracker_dlg_mgrcol_add_split_lodpi    = 0.5;
   static float node_tracker_dlg_mgrcol_remove_split_hidpi = 0.5;
   static float node_tracker_dlg_mgrcol_remove_split_lodpi = 0.5;
   static float node_tracker_dlg_mgrcol_manage_split_hidpi = 0.5;
   static float node_tracker_dlg_mgrcol_manage_split_lodpi = 0.5;
   static boolean node_tracker_dlg_mgrcol_addremove_autohide = true; // hide dialog after ADD or REMOVE action

   // Audio-Clip Start/Stop note (def=C-5)
   static int node_tracker_audioclip_note = 12*5;

   // Swap lshift INSERT/delete [def=false]
   //   false=DELETE deletes ctl and lshift-DELETE clears entire step
   //         INSERT insert ctl and lshift-INSERT insert entire step
   //    true=DELETE deletes entire step and lshift-DELETE deletes current ctl only
   //         INSERT inserts entire steps and lshift-INSERT insert current ctl only
   //  (note) also swaps lshift-INSERT / INSERT
   static boolean b_node_tracker_swap_lshift = true;

   // Quantization (dialog) settings (NT_QuantizeDialog)
   static int     node_tracker_quantization_last_method = 0;  // (0=dialog, 1=100%, 2=50%, 3=25%)
   static int     node_tracker_quantization_forced_range_ticks = 0;
   static int     node_tracker_quantization_min_threshold_ticks = 0;
   static float   node_tracker_quantization_amount = 0.5;
   static boolean b_node_tracker_quantization_wrap_around = true;

   // NT_AudioDialog geometry
   static Geometry4f nt_audio_dialog_geo_hidpi = [ -1, -1, -1, -1 ];
   static Geometry4f nt_audio_dialog_geo_lodpi = [ -1, -1, -1, -1 ];

   // AudioLiveRecDialog geometry
   static Geometry4f audio_liverec_dialog_geo_hidpi = [ -1, -1, -1, -1 ];
   static Geometry4f audio_liverec_dialog_geo_lodpi = [ -1, -1, -1, -1 ];

   // AudioRecordDialog geometry
   static Geometry4f audio_record_dialog_geo_hidpi = [ -1, -1, -1, -1 ];
   static Geometry4f audio_record_dialog_geo_lodpi = [ -1, -1, -1, -1 ];




   /***
    ***
    *** NodeCtlMorph
    ***
    ***
    ***/

   // Default device for new input entries
   static String node_ctlmorph_default_input_dev = "<play_a>";

   // Default MIDI channel for new input entries (0..15)
   static int node_ctlmorph_default_input_ch = 0;

   // Default type for new input entries
   static int node_ctlmorph_default_input_type = MIDIMapDefs.TYPE_CC;

   // // Default ext type for new input entries (CC#, (N)RPN#, ..)
   static int node_ctlmorph_default_input_ext_type = 1/*modwheel*/;

   // Default device for new output entries
   static String node_ctlmorph_default_output_dev = "vst_a";

   // Default MIDI channel for new output entries (0..15)
   static int node_ctlmorph_default_output_ch = 0;

   // Default type for new output entries
   static int node_ctlmorph_default_output_type = MIDIMapDefs.TYPE_CC;

   // Default ext type for new output entries (CC#, (N)RPN#, ..)
   static int node_ctlmorph_default_output_ext_type = 1/*modwheel*/;




   /***
    ***
    *** NodeArranger
    ***
    ***
    ***/

   // Undo/redo history size
   static int node_arranger_undo_history_size = 50;

   // When checked, force-mute arranger when node is edited
   static boolean b_arranger_force_mute_edit = false;

   // Timeline recording clip alignment. 1=quarter, 0.5=8th, 0.25=16th, ..  (def=8th/0.5)
   static float arranger_timeline_rec_alignment = 0.5;

   // Minimum height of tracks area in timeline editor
   //  (note) make sure to keep enough space for AudioLiveRecForm (below timeline) if the Eureka audio host is connected
   static int arranger_timeline_min_num_tracks = 7;

   // Base note for marker clips (auto-dev) (def=C-3)
   static int arranger_trigger_base_note = 3*12;

   // Visible area around replay position (0.1..0.5, def=0.25)
   //  (note) smaller values = less frequent paging
   static float node_arranger_follow_paging_percent = 0.25;

   // true=insert snapshot of currently playing patterns when recording is started from arranger editor
   static boolean b_node_arranger_editor_insert_snapshot_when_recording_starts = true;

   // default audio-clip level (dB) (def=0.0dB)
   //  (note) also applies to NT_AudioDialog
   static float node_arranger_audio_clip_level = 0.0;

   // Beat / Bar label display offset
   //  0= display as 0, 4, 8, 12, 16, ..  (the 4th bar ends at "4")
   //  1= display as 1, 5, 9, 13, 17, ..  (the 5th bar starts at "5")
   static int arranger_beatbar_label_offset = 0;

   // AudioLiveRecForm voice modulo stereo-spread pan presets
   static PointerArray liverec_voice_pan_presets = [
      /*1 voice */ [ 0.0],
      /*2 voices*/ [-0.5,  0.5],
      /*3 voices*/ [ 0.0, -0.5,   0.5],
      /*4 voices*/ [-0.25, 0.25, -0.5, 0.5],
      /*5 voices*/ [-0.25, 0.25, -0.5, 0.0,  0.5],
      /*6 voices*/ [-0.25, 0.25, -0.5, 0.5, -0.75, 0.75],
      /*7 voices*/ [-0.25, 0.25, -0.5, 0.0,  0.5, -0.75, 0.75],
      /*8 voices*/ [-0.2,  0.2,  -0.4, 0.4, -0.6,  0.6, -0.8, 0.8]
                                                    ];




   /***
    ***
    *** NodeTrigSeq
    ***
    ***
    ***/

   // 0=display step nrs as 0,1,2,3,4,..
   // 1=display step nrs as 1,2,3,4,5,..
   static int node_trig_seq_step_label_offset = 0;

   // true=follow arranger / replay pattern
   // false=edit pattern is independent of replay pattern
   //  (note) can be toggled via lctrl-lshift-SPACE in (any) TrigSeq editor
   static boolean b_node_trig_seq_edit_pattern_follows_replay_pattern = false;

   // 16=fit up to 16 steps into available width, add hscroller when pattern/track uses more steps
   // 0=fit all steps into available width
   static int node_trig_seq_grid_steps_x = 0;

   // When checked, draw step nrs on set steps
   static boolean b_node_trig_seq_draw_step_nr_set = true;

   // When checked, draw step nrs on cleared steps
   static boolean b_node_trig_seq_draw_step_nr_cleared = false;

   // Recording base note (rec + auto a/b)
   static int node_trig_seq_rec_base_note = (3 * 12);  // C-3..D#4

   // Global mute note (rec + auto a/b)
   static int node_trig_seq_rec_global_mute = (4*12)+7;  // G-4

   // Global repeat-if-trig note (rec + auto a/b)
   static int node_trig_seq_rec_global_repeat_if_trig = (4*12)+9;  // A-4

   // Global repeat (rec + auto a/b)
   static int node_trig_seq_rec_global_repeat = (4*12)+11;  // B-4

   // Recording roll note (rec + auto a/b)
   static int node_trig_seq_rec_roll_base_note = (5 * 12);  // C-5..D#6

   // Recording mute note (rec + auto a/b)
   //  (note) this flips the current mute_temp_track_mask state, i.e.
   //          when a track is unmuted, pressing a note will mute it
   //          and when a track is muted, pressing a note will unmute it
   static int node_trig_seq_rec_mute_base_note = (7 * 12);  // C-7..D#8

   // Default track color (name bg)
   static int node_trig_seq_default_track_color_light = #2cebc107;  // ochre
   static int node_trig_seq_default_track_color_dark  = #2cf5ff87;
   //static int node_trig_seq_default_track_color = #2cebee07;  // yellow-ish
   //static int node_trig_seq_default_track_color = #2cfdff00;  // bright yellow

   // true=sustain pedal forces note step recording (CHROMATIC output modes)
   static boolean b_node_trig_seq_force_step_record_sustain_pedal = true;

   // true=place menu on left side of step area (right-handed)
   // false=right side (left-handed)
   static boolean b_node_trig_seq_linnstrument_menu_left = true;

   // (note) when name ends with "Scale", also init track names (note names)
   static PointerArray node_trig_seq_preset_arg3 = [
      // 0: Clear
      ["Clear (fill 0)",
       "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0"
       ],

      // 1: 0,1,2,..
      ["0,1,2,..",
       "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"
       ],

      // 2: 0,4,8,..
      ["0,4,8,..",
       "0", "4", "8", "12", "16", "20", "24", "28", "32", "36", "40", "44", "48", "52", "56", "60"
       ],

      // 3: Major
      ["Major/Ionian Scale",
       "0",  "2",  "4",  "5",  "7",  "9",  "11",
       "12", "14", "16", "17", "19", "21", "23",
       "24", "26"
       ],

      // 4: Natural Minor
      ["Natural Minor Scale",
       "0",  "2",  "3",  "5",  "7",  "8",  "10",
       "12", "14", "15", "17", "19", "20", "22",
       "24", "26"
       ],

      // 5: Harmonic Minor
      ["Harmonic Minor Scale",
       "0",  "2",  "3",  "5",  "7",  "8",  "11",
       "12", "14", "15", "17", "19", "20", "23",
       "24", "26"
       ],

      // 6: Melodic Minor
      ["Melodic Minor Scale",
       "0",  "2",  "3",  "5",  "7",  "9",  "11",
       "12", "14", "15", "17", "19", "21", "23",
       "24", "26"
       ],

      // 7: Major Pentatonic
      ["Major Pentatonic Scale",
       "0",  "2",  "4",  "7",  "9",
       "12", "14", "16", "19", "21",
       "24", "26", "28", "31", "33",
       "36"
       ],

      // 8: Minor Pentatonic
      ["Minor Pentatonic Scale",
       "0",  "3",  "5",  "7",  "10",
       "12", "15", "17", "19", "22",
       "24", "27", "29", "31", "34",
       "36"
       ],

      // 9: Blues
      ["Blues Scale",
       "0",  "3",  "5",  "6",  "7",  "10",
       "12", "15", "17", "18", "19", "22",
       "24", "27", "29", "30"
       ],

      // 10: Dorian
      ["Dorian Scale",
       "0",  "2",  "3",  "5",  "7",  "9",  "10",
       "12", "14", "15", "17", "19", "21", "22",
       "24", "26"
       ],

      // 11: Phrygian
      ["Phrygian Scale",
       "0",  "1",  "3",  "5",  "7",  "8",  "10",
       "12", "13", "15", "17", "19", "20", "22",
       "24", "25"
       ],

      // 12: Phrygian Dominant
      ["Phrygian Dominant Scale",
       "0",  "1",  "4",  "6",  "7",  "8",  "10",
       "12", "13", "16", "18", "19", "20", "22",
       "24", "25"
       ],

      // 12: Lydian
      ["Lydian Scale",
       "0",  "2",  "4",  "6",  "7",  "9",  "11",
       "12", "14", "16", "18", "19", "21", "23",
       "24", "26"
       ],

      // 13: Mixolydian
      ["Mixolydian Scale",
       "0",  "2",  "4",  "5",  "7",  "10",
       "12", "14", "16", "17", "19", "22",
       "24", "26", "28", "29"
       ],

      // 14: Aeolian
      ["Aeolian Scale",
       "0",  "2",  "3",  "5",  "7",  "8",  "10",
       "12", "14", "15", "17", "19", "20", "22",
       "24", "26"
       ],

      // 15: Locrian
      ["Locrian Scale",
       "0",  "1",  "3",  "5",  "6",  "8",  "10",
       "12", "13", "15", "17", "18", "20", "22",
       "24", "25"
       ],

      // 16: Hungarian Gypsy
      ["Hungarian Gypsy Scale",
       "0",  "2",  "3",  "6",  "7",  "8",  "10",
       "12", "14", "15", "18", "19", "20", "22",
       "24", "26"
       ],

      // 17: Ukrainian Minor
      ["Ukrainian Minor Scale",
       "0",  "2",  "3",  "6",  "7",  "9",  "10",
       "12", "14", "15", "18", "19", "21", "22",
       "24", "26"
       ],

      // 18: Persian
      ["Persian Scale",
       "0",  "1",  "4",  "5",  "6",  "8",  "11",
       "12", "13", "16", "17", "18", "20", "23",
       "24", "25"
       ]

      // <add custom presets here>
                                                    ];

   static PointerArray node_trig_seq_preset_track_names = [
      // 0: Default
      ["Default",
       "Trk 1", "Trk 2",  "Trk 3",  "Trk 4",  "Trk 5",  "Trk 6",  "Trk 7",  "Trk 8",
       "Trk 9", "Trk 10", "Trk 11", "Trk 12", "Trk 13", "Trk 14", "Trk 15", "Trk 16"
       ],

      ["Drum Layout A", // A1..A16
       "BD Short",
       "BD Long",
       "SD Short",
       "SD Long",
       "CP Short",
       "CP Long",
       "Low Tom",
       "Mid Tom",
       "High Tom",
       "Maracas",
       "Claves",
       "Cowbell",
       "Rim Shot",
       "Hihat Closed",
       "Hihat Open",
       "Cymbal Ride"
       ],

      ["Drum Layout B", // B1..B16
       "Bass Tom 1",
       "Bass Tom 2",
       "Zap 1",
       "Zap 2",
       "FX 1",
       "FX 2",
       "Conga Open",
       "Conga Muff",
       "Conga Slap",
       "Maracas 2",
       "Claves 2",
       "Cowbell 2",
       "Rim Shot 2",
       "Noise Short",
       "Noise Long",
       "Cymbal Crash"
       ],

      // 1: Elektron Analog Rytm
      ["Elektron Analog Rytm",
       "BD 1", "SD 2", "RS 3", "CP 4", "BT 5", "LT 6", "MT 7", "HT 8", "CH 9", "OH 10", "CY 11", "CB 12"
       ],

      // 2: Elektron Machine Drum
      ["Elektron Machine Drum",
       "1 BD", "2 SD", "3 HT", "4 MT", "5 LT", "6 CP", "7 RS", "8 CB", "9 CH", "10 OH", "11 RC", "12 CC", "13 M1", "14 M2", "15 M3", "16 M4"
       ],

      // 3: MFB Tanzbaer
      ["MFB Tanzbaer",
       "1 CV1", "2 CV2/3", "3 BD1", "4 BD2", "5 SD", "6 RS", "7 CY", "8 OH", "9 HH", "10 CL", "11 CP", "12 LTC", "13 MTC", "14 HTC", "15 CB", "16 MA"
       ],

      // 4: Chromatic
      ["Chromatic",
       "C-0", "C#1", "D-0", "D#1", "E-0", "F-0", "F#0", "G-0", "G#0", "A-0", "A#0", "B-0",
       "C-1", "C#1", "D-1", "D#2"
       ],

      // 5: Minor
      ["Minor",
       "C-0", "D-0", "D#0", "F-0", "G-0", "G#0", "A#0",
       "C-1", "D-1", "D#1", "F-1", "G-1", "G#1", "A#1",
       "C-2", "D-2"
       ]

      // <add custom presets here>
                                                           ];


   // Used by Linnstrument plugin in performance mode (x-pos controls repeat rate)
   //  (note) a value outside of 0..64 means "reset back to track default"
   //  (note) a sensible number of entries is ~3..5
   static IntArray node_trig_seq_plugin_repeat_notelen_tbl = [
      NTS_Track.NOTELEN_12,
      NTS_Track.NOTELEN_16,
      NTS_Track.NOTELEN_24,
      NTS_Track.NOTELEN_32,
      NTS_Track.NOTELEN_64,
                                                              ];

   // Used by Linnstrument plugin in performance mode (y-pos of repeat or trig pads controls velocity scaling)
   //  (note) 0..255 => /8 .. *8
   static float node_trig_seq_plugin_velscl_min = 0;
   static float node_trig_seq_plugin_velscl_max = 128;

   // Used by Linnstrument plugin in performance mode (x-pos of trig pads controls duration scaling)
   //  (note) 0..255 => /4 .. *4
   static float node_trig_seq_plugin_durscl_min = 0;
   static float node_trig_seq_plugin_durscl_max = 255;

   // Queued pattern restart quantization (num16th) (def=2 == 1/8 note)
   static float node_trig_seq_plugin_pattern_restart_quantization_16th = 2.0;

   // Used for AR pattern import to find output device
   static String node_trig_seq_analogrytm_device_name = "AnalogRytm";



   /***
    ***
    *** NodeKeymap
    ***
    ***/

   // Number of milliseconds a note must be held down to add a new entry
   static int node_keymap_hold_note_threshold = 250;

   // First note assigned in learn mode
   static int node_keymap_first_match_note = 12*4;

   // true=learn both black and white keys
   // false=learn white keys only
   static boolean b_node_keymap_next_match_note_black_and_white = false;




   /***
    ***
    *** NodeAnalogRytm
    ***
    ***
    ***/

   // Sound category preset sub-folder names
   //  (note) in STConfig.node_rootpath+"/analogrytm/sound_presets/"
   static StringArray node_analog_rytm_preset_folder_names = [
      "00_noisegen_impulse",
      "01_bd_sd_sy",
      "02_rs_cp",
      "03_bt",
      "04_lt_mt_ht",
      "05_ch_oh",
      "06_cy_cb"
                                                              ];

   // true=find matching preset for current track sound (in kit editor)
   static boolean b_node_analog_rytm_find_preset = true;


   /***
    ***
    *** NodeMonitor
    ***
    ***
    ***/

   // Event colors
   static IntArray node_monitor_event_colors = [
      // // #3000006f,  // 0: Note-Off
      // // #380408ff,  // 1: Note-On
      // // #3002045f,  // 0: Note-Off
      // // #404048df,  // 1: Note-On
      #28005f00,  // 0: Note-Off
      #1208ff10,  // 1: Note-On
      #2000ffff,  // 2: Poly Pressure
      #20ffff00,  // 3: CC
      // // #2000ff00,  // 4: Program Change
      #80a2a4cf,  // 4: Program Change
      // // #30009f9f,  // 5: Channel Pressure
      #40009f9f,  // 5: Channel Pressure
      #26af00af,  // 6: Pitchbend
      #20ff0000,  // 7: SysEx
      #307f7f7f,  // 8: RPN
      #40ffffff   // 9: NRPN
                                                ];

   // true=follow arranger / replay pattern
   // false=edit pattern is independent of replay pattern
   //  (note) can be toggled via lctrl-lshift-SPACE in (any) Monitor editor
   static boolean b_node_monitor_edit_pattern_follows_replay_pattern = false;

   // Maximum number of recorded MIDI event frames
   static int node_monitor_max_frames = 20000;

   // Continue-record time offset for next recorded frame (in milliseconds)
   //  (note) to prevent overly greedy auto-merging
   static int node_monitor_record_continue_time_offset = 2000;

   // Timeout (in milliseconds) after which events are removed in RECORD* modes
   //  (note) set to 0 to never auto-remove events
   static int node_monitor_autoremove_timeout = 17000;

   // true=start new row for each controller type
   static boolean b_node_monitor_line_break = true;

   // Default merge threshold (in milliseconds)
   static int node_monitor_merge_threshold = 50;

   // Default merge threshold (in milliseconds) (markers only)
   static int node_monitor_merge_marker_threshold = 130;

   // Default fixed duration (in milliseconds)
   static int node_monitor_fixed_duration = 100;

   // Default fixed velocity
   static int node_monitor_fixed_velocity = 100;

   // true=automatically merge markers before trimming to punch in/out
   static boolean b_node_monitor_trim_auto_merge_markers = true;
   // static boolean b_node_monitor_trim_auto_merge_markers = false;

   // true=automatically stop replay before trimming to punch in/out
   static boolean b_node_monitor_trim_auto_stop_replay = true;

   // true=align punch in/out to beat grid when replay is running
   static boolean b_node_monitor_replay_align_punch_in_out = true;

   // true=display values in hexadecimal notation. false=use decimal notation
   static boolean b_node_monitor_hex = true;

   // true=display duration values in milliseconds. false=display as number of ticks
   static boolean b_node_monitor_dur_millisec = true;

   // base note for frame triggers (auto-dev)
   static int node_monitor_trigger_base_note = 3*12;  // c-3

   // Highlight relative time ranges (value range is [0.0 .. 1.0]). Must be sorted by value (highest..lowest).
   static FloatArray node_monitor_highlight_ranges = [
      0.97,
      0.85,
      0.6,
      0.25
                                                      ];

   // Event label highlight border colors
   static IntArray node_monitor_highlight_border_colors = [
      #1f000000,
      #3f000000,
      #5f000000,
      #8f000000,
      #ff000000
                                                    ];

   // Event label background fade
   static IntArray node_monitor_highlight_bg_alpha = [
      #00000000,
      #00000000,
      #3f000000,
      #6f000000,
      #8f000000
                                                    ];

   // Event label highlight text colors
   static IntArray node_monitor_highlight_text_colors = [
      #ff000000, //#ffffffff,
      #cf000000,
      #6f000000,
      #3f000000,
      #1f000000
                                                    ];

   // When auto marker is active, this defines the number of milliseconds
   //  after which a marker is set when the next event is recorded
   static int node_monitor_automarker_threshold_ms = 10000;

   // Initial auto marker enable state for new nodes
   static boolean b_node_monitor_automarker_enable = true;

   // Initial b_trim_marker_calc_shift_ms enable state for new nodes
   static boolean b_node_monitor_trim_marker_calc_replay_shift_ms = true;

   // true=quantize last 16th note to avoid shift by almost one bar when first recorded note is slightly early
   //  (note) e.g. when num16th/bar is 16 and calculated shift is >=15.5, snap to 0
   static boolean b_node_monitor_trim_marker_calc_replay_shift_ms_quantize_last_16th = true;

   // millisec timeout for (chord) autoclear mode
   static int node_monitor_autoclear_ms_threshold = 1000;

   // true=esc hides pattern view
   // false=immediately leave node editor
   static boolean b_node_monitor_autohide_patternview_on_esc = false;

   // true=use ticks instead of milliseconds as timebase in (tracker) output capture mode
   //  (note) fixes (rare) off-by-one timing "issues" but timing modulations will be lost
   //  (note) can be toggled at runtime in Tracker editor Pattern->Capture menu
   // static boolean b_node_monitor_capture_tick_timebase = false;
   static boolean b_node_monitor_capture_tick_timebase = true;

   // SysEx request check interval in milliseconds [100]
   static int node_monitor_sysex_check_interval = 100;

   // Maximum number of SysEx checks before time out
   //  (note) timeout = (node_monitor_sysex_check_interval * node_monitor_sysex_max_checks)
   static int node_monitor_sysex_max_checks = 30;




   /***
    ***
    *** NodeScript
    ***
    ***
    ***/

   // Default script watchfile name
   static String node_script_default_script_watchfile_name = "$(TMP)/script.tks";

   // Default UI watchfile name
   static String node_script_default_xfm_watchfile_name = "$(TMP)/script.xfm";

   // Watchfile reload interval (millisec)
   static int node_script_watchfile_interval = 1000;

   // UI params refresh interval (millisec)
   static int node_script_refreshuiparams_interval = 250;

   // Debug: enable/disable user script execution
   static boolean b_node_script_exec = false;



   /***
    ***
    *** NodeSeq (Freeform)
    ***
    ***
    ***/

   // Number of right-hand side bars when autoscroll is enabled
   static int node_seq_autoscroll_border = 6;

   // Undo/redo history size
   static int node_seq_undo_history_size = 50;

   // Swap mousewheel direction (false: DOWN=right, UP=left.  true: DOWN=left, UP=right)
   static boolean b_node_seq_swap_mousewheel_dir = false;

   // true=ignore program changes (pattern select), RPN_COMMON_SEEK_OFFSET, RPN_COMMON_MUTE_TEMP when
   //       replay is started from node editor
   //  (note) these events are usually sent by Arranger nodes and can get in the way when recording long patterns
   //  (note) this setting will only have an effect if the replay is started via SPACE, RSHIFT, or RETURN
   static boolean b_node_seq_ignore_seek_prgchg_mute_during_pattern_replay = true;

   // true=reset smooth scroll offset when selecting bars
   //  (note) can be toggled in the UI via the arranger context menu
   static boolean b_node_seq_autoreset_smooth_scroll = true;

   // true=initially use bar-style velocity cels in temporary tracker editor
   // false=show hex values
   //  (note) can be toggled with lctrl-x p at runtime
   static boolean b_node_seq_tracker_vel_bars = true;




   /***
    ***
    *** NodeModular
    ***
    ***
    ***/
   static int modular_num_modules_per_menu_col = 13;

   // regular / higlight pairs
   //  (note) cycled with mousewheel on input port
   static IntArray *[]  modular_cable_colors = [
      // light
      [
         #007f7f, #30Dfdf,  // 0: cyan
         #007f00, #30Df70,  // 1: green
         #8f0f0f, #df2020,  // 2: red
         #df9f03, #ffbf18,  // 3: orange
         #030faf, #0750df,  // 4: blue
       ],

      // dark
      [
         #05a4a4, #30efef,  // 0: cyan
         #009f00, #30ef70,  // 1: green
         #b01515, #f22424,  // 2: red
         #df9f03, #ffbf18,  // 3: orange
         #0a60f0, #1c76ff,  // 4: blue
       ],

      // mono
      [
         #05a4a4, #30efef,  // 0: cyan
         #009f00, #30ef70,  // 1: green
         #b01515, #f22424,  // 2: red
         #df9f03, #ffbf18,  // 3: orange
         #0a60f0, #1c76ff,  // 4: blue
       ],

                                           ];

   // default port/cable colors (palette indices):
   static IntArray modular_ioclass_color_indices = [
      0,  // IO_ANY    any/other
      1,  // IO_CV     note/freq
      2,  // IO_GATE   trigs and gates
      2,  // IO_CLOCK  clock signals
      3,  // IO_SCALE  e.g. VCA scale/amp input
      4,  // IO_OFF    e.g. VCA bias/offset input
      4,  // IO_LEVEL  e.g. ADSR sustain level
      0,  // IO_RATE   e.g. ADSR attack rate
      4,  // IO_DUR    e.g. trigtogate duration
                                               ];

   static int modular_cable_opacity    = #90;
   static int modular_cable_opacity_hl = #d0;




   /***
    ***
    *** Audio Record Dialog
    ***
    ***/

   // Initial AudioRecordDialog sample name prefix options (ComboField)
   static StringArray audio_record_dialog_prefix_history = [
      "drum",
      "perc",
      "key",
      "pad",
      "lead",
      "chord",
      "fx"
                                                            ];

   // Send "sync record restart" "n" ticks ahead of time
   static int atrk_record_tick_shift = 0;




   /***
    ***
    *** Audio setup (TODO: REMOVE)
    ***
    ***/

   // (TODO) REMOVE, UNUSED
   // Portaudio/SDL audio mixrate
   // static int     audio_mixrate = 96000;
   // static int     audio_mixrate = 48000;
   static int     audio_mixrate = 44100;




   /***
    ***
    *** Audio.Recorder (TODO: REMOVE)
    ***
    ***/

   // recorder/sampleview redraw interval (millisec)
   static int audio_recorder_redrawinterval = 300;

   // enable background capturing to scratch buffer (Audio->Recorder)
   static boolean audio_recorder_capture = true;

   // scratchbuffer size (#stereo sample frames)
   // static int audio_recorder_scratchbuf_numframes = (48000*60*5); // ~5min
   // static int audio_recorder_scratchbuf_numframes = (48000*10); // ~10 sec
   static int audio_recorder_scratchbuf_numframes = (48000*60*2); // ~2min

   // auto start/stop capturing when signal exceeds minval_threshold
   static float audio_recorder_minval_threshold = 0.01f;

   // auto stop capturing when signal is below minval_threshold for "timeout" seconds
   static float audio_recorder_timeout = 3.0;

   // true=show audio recorder options by default (increases minimum recorder window height)
   static boolean b_audio_recorder_options = false;



   /***
    ***
    *** Audio.SampleView
    ***
    ***/

   // Maximum SampleView undo/redo stack size (per waveform)
   //  (note) this should not be a config option (pls leave unchanged)
   static int sampleview_undo_stack_size = 8192;

   // Maximum SampleView undo memory usage (in MBytes) (all waveforms)
   //  (if threshold is crossed, auto-purge old entries)
   //  (note) set to 0 to disable autopurge (very careful there!)
   static float sampleview_undo_autopurge_threshold = 250.0f;

   // Maximum SampleView redo memory usage (in MBytes) (all waveforms)
   //  (if threshold is crossed, auto-purge old entries)
   //  (note) set to 0 to disable autopurge (very careful there!)
   static float sampleview_redo_autopurge_threshold = 100.0f;




   /***
    ***
    *** Default Project Settings
    ***
    ***/

   // // // Default step resolution (usually the duration of a 1/16th note, i.e. ppq/4)
   // // define int DEFAULT_ZOOM = 6;

   // Default beats per minute (BPM)
   define int DEFAULT_BPM = 125;

   // Default precision per quarter (PPQ)
   define int DEFAULT_PPQ = 192;

   // Default time signature #beats
   define int DEFAULT_SIG_BEATS = 4;

   // Default time signature notelen
   define int DEFAULT_SIG_NOTELEN = 4;


   /***
    ***
    *** Sample repositories
    ***
    ***/

   // $(SAMPLE_DIR)
   // (TODO) REMOVE, UNUSED
   static String sample_rootpath = "h:/samples";

   // (TODO) REMOVE, UNUSED
   // Default waveform storage type (used in "New Instrument" dialog)
   //   true = only store sample repository relative pathname
   //  false = embed waveform data in project
   static boolean sample_external = true;


   /***
    ***
    *** PageCtlTag
    ***
    ***/

   // Push pad colors: Group selection (1;1..1;6)
   static int push_page_ctl_tag_col_group     = 45; // light blue
   static int push_page_ctl_tag_col_group_sel =  3; // white

   // Push pad colors: discardctl/discardctl_collect buttons (1;7/1;8)
   static int push_page_ctl_tag_col_discardctl_alt     =  38; // dark turquoise
   static int push_page_ctl_tag_col_discardctl_last    =  33; // bright turquoise
   static int push_page_ctl_tag_col_discardctl_active  =  60; // bright red

   // Push pad colors: Entry/pad selection
   static int push_page_ctl_tag_col_pad_sel =  3; // white
   static int push_page_ctl_tag_cursor_blink_interval_on = 2;
   static int push_page_ctl_tag_cursor_blink_interval_off = 14;



   /***
    ***
    *** PageAudioTool
    ***
    ***/

   // (note) currently unused
   static StringArray audiotool_sample_locations = [
   // Shortname    Directory
      "All",       "$(SAMPLE_DIR)",
      "Combi",     my_mproj_path+"/reason/combi/"
      "Drums",     "$(SAMPLE_DIR)/percussion",
      "Osc",       "$(SAMPLE_DIR)/waveforms",
      "Rec",       my_mproj_path+"/recordings",
      "Temp",      my_mproj_path+"/audiotool_temp",
      "WT",        "$(SAMPLE_DIR)/wavetables",
      ""
                                                    ];

   // Where to place the wavs and the AR kit syx file
   static String audiotool_default_output_dir = my_mproj_path+"/audiotool_temp";

   static String audiotool_default_output_prefix = "out";

   // Path to "sox" / "sox.exe"
   static String audiotool_sox_pathname =
      // Utils.IsWindows() ? "c:\\Program Files (x86)\\sox-14-4-2\\sox.exe"
      Utils.IsWindows() ? "$(PROGRAM_DIR)/opt/sox-14-4-2/sox.exe"
      // : Utils.IsMacOS() ? "/opt/homebrew/bin/sox"  // arm64
      // : Utils.IsMacOS() ? "/usr/local/homebrew/bin/sox"  // x86_64
      : Utils.IsMacOS() ? "$(PROGRAM_DIR)/../opt/sox-14.4.2_5/sox"  // app dir
      : "sox"
      ;

   // Sox args (not including input/output filenames)
   static String audiotool_sox_args = "-r 48000 -b 16 -c 1";

   // Path to the directory that contains "sxt.tkx"
   // static String audiotool_sxt_pathname = "$(PROGRAM_DIR)/applications/";
   // static String audiotool_sxt_pathname = "$(PROGRAM_DIR)/../nnxt-rev";
   static String audiotool_sxt_pathname = "$(PROGRAM_DIR)/opt/sxt";

   // Path to the directory that contains "sds.tkx"
   // static String audiotool_sds_pathname = "$(PROGRAM_DIR)/applications/";
   // static String audiotool_sds_pathname = "$(PROGRAM_DIR)/../sds";
   static String audiotool_sds_pathname = "$(PROGRAM_DIR)/opt/sds";



   /***
    ***
    *** General setup
    ***
    ***/

   // if true, print (low frequency) debug messages to console (prefixed with "[dbg]")
   //  (note) Global.Debug()
   static boolean b_debug_lofreq = true;
   // static boolean b_debug_lofreq = false;

   // if true, print (high frequency) debug messages to console (prefixed with "[trc]")
   //  (note) Global.Debug2()
   static boolean b_debug_hifreq = true;
   // static boolean b_debug_hifreq = false;

   // if true, print (realtime) debug messages to console (prefixed with "[>>>]")
   //  (note) Global.Debug3()
   static boolean b_debug_rt = false;
   // static boolean b_debug_rt = true;

   // when true, print profiling messages ("[pro]")
   static boolean b_debug_profile = true;

   // when true, print warning when MIDI device is not present
   static boolean b_debug_mididev_unavail = false;

   // when true, print info about added MIDI synth profiles
   static boolean b_debug_msp_add = false;

   // filename of project to load during startup
   static String autoload_filename = "";

   // true=update autoload_filename each time a project is loaded/saved
   static boolean b_autoload_filename_autoupdate = false;

   // filename to load when "new project" button is clicked
   //  (note) when file does not exist, use builtin default project (warning: no mmc mappings)
   //  (note) the filename is relative to the project_rootpath
   static String newproject_template_filename = "project_template.flx";

   // if true, show project comment after loading a project (if the comment is not empty)
   static boolean b_autoshowcomment = true;

   // if true, save current project to "crash_autosave-<date>-<time>.st2" in case the program UI crashes
   //  Please think twice before disabling this feature.
   // static boolean b_crash_autosave = true;
   static boolean b_crash_autosave = false;

   // Default directory for autosaves. Use current directory when empty ("") (careful: the CWD may change while the app. is running)
   //  (note) $(PROGRAM_DIR) will be substituted by the application startup directory
   //  (note) $(PROJECT_DIR) will be substituted by the project_rootpath
   static String autosave_dir="$(PROJECT_DIR)/autosaves/";

   // Number of periodical auto-save files (cycle through 'n' save files)
   //  (note) the current autosave file nr is stored in Global.AUTOSAVE_CURRENT_FILENR_FILE (in project_rootpath)
   //  (note) clamped to [1;100] (unless it's set to 0)
   static int num_autosave_files = 20;

   // Autosave interval (number of minutes)
   //  (note) clamped to [1;30]
   static int autosave_interval = 1;

   // Enable/disable autosaves. 'maybe'=only do autosaves when replay is not running
   static boolean b_autosave = true;

   // When true, visualize internal/external timing clock by flashing the status bar time display every quarter note
   static boolean b_visualize_timing_clock = true;

   // When true, wrap MIDI morph scene idx. when false, clamp to min/max.
   static boolean b_scene_wrap = false;

   // When true, always assume that pattern has been playing since the song start
   //  (can still override pattern start positions)
   static boolean b_ignore_last_pattern_start_song_offset = false;

   // When true, send (internal) program change for current play pattern when replay starts
   //  (note) this is useful for recording pattern change sequences that include the
   //          initial (pre-record) pattern selection
   static boolean b_node_send_play_pattern_prgchg_when_replay_starts = false;

   // Base note for conversion of notes to program changes
   static int default_noteon_to_prgchg_base_note = (4 * 12);

   // Program/Note offset for note-offs (127=select last pattern (clamped), 0=don't change pattern)
   static int default_noteoff_to_prgchg_note_offset = 127;

   // true=silently ignore unknown nodes (when loading node state)
   // false=abort project load on first error
   static boolean b_ignore_node_load_error = true;


   static EnableColorGrading_Cycle(boolean _initialIdx) {

      UI.SetPostFBOColorGradingCycleList(
         [
            /* 0*/ "image_with_gradient_256x16_original.png",    // good, slightly darker
            /* 1*/ "dark_image_with_gradient_256x16_mono_3a.png",// good
            /* 2*/ "dark_image_with_gradient_256x16_desaturated_1.png",  // good, slightly darker
            /* 3*/ "image_with_gradient_256x16_mono_1.png",      // good (dark) BUT: mute(red) not visible
            /* 4*/ "image_with_gradient_256x16_mono_1d.png",     // good (dark blue-ish) BUT: mute(red) not visible
            /* 5*/ "image_with_gradient_256x16_cold_1.png",      // good
            /* 6*/ "image_with_gradient_256x16_darkgray_2.png",  // good (dark)
            /* 7*/ "image_with_gradient_256x16_darkgray_1.png",  // ok (very dark)
            /* 8*/ "image_with_gradient_256x16_bright_1.png",    // good ("autumn" theme)
            /* 9*/ "image_with_gradient_256x16_bright_2.png",    // good ("autumn" theme, darker)
            /*10*/ "image_with_gradient_256x16_invert_7.png",    // bright+almost monochromatic
            /*11*/ "image_with_gradient_256x16_invert_3.png",    // very bright + purple
            /*12*/ "image_with_gradient_256x16_invert_1.png",    // bright+purple
            /*13*/ "image_with_gradient_256x16_invert_8.png",    // weird but usable
            /*14*/ "image_with_gradient_256x16_green_2.png",     // "old terminal". works in dark theme but better in light one (needs restart)

            /*15*/ "mono_blue_1.png",
            /*16*/ "mono_blue_2.png",
            /*17*/ "mono_blue_3.png",
            /*18*/ "mono_blue_4.png",
            /*19*/ "mono_desaturated_1.png",
            /*20*/ "mono_desaturated_2.png",
            /*21*/ "mono_gray_1.png",
            /*22*/ "mono_gray_2.png",
            /*23*/ "mono_green_1.png",
            /*24*/ "mono_green_2.png",
            /*25*/ "mono_invert_1.png",
            /*26*/ "mono_invert_2.png",
            /*27*/ "mono_invert_3.png",
            /*28*/ "mono_invert_4.png",
            /*29*/ "mono_pink_1.png",
            /*30*/ "mono_red_1.png",
            /*31*/ "mono_red_2.png",
            /*32*/ "mono_yellow_1.png",
            /*33*/ "mono_yellow_2.png",
            /*34*/ "mono_desaturated_3.png",
            /*35*/ "mono_desaturated_4.png",
            /*36*/ "dark_desaturated_2.png",
            /*37*/ "dark_desaturated_3.png",
            /*38*/ "dark_desaturated_4.png",
            /*39*/ "light_contrast_1.png",
            /*40*/ "light_contrast_2.png",
            /*41*/ "light_contrast_3.png",
            /*42*/ "light_contrast_4.png",

            ""                                            // "random" GPU mem
          ],
         tkfileutils_getcwd()+"/color_grading/"
                                         );

      UI.SetPostFBOColorGradingCycleCbk(PostFBOColorGradingCycleCbk);

      if(-1 != _initialIdx)
         UI.SetPostFBOColorGrading256x16ByCycleIdx(_initialIdx);
   }

   protected static ApplySizePreset() {
      boolean bForceSizeAndZoom = 0;  // debug
      if(0)
      {
         // Force scaled LoDPI window (debug)
         bForceSizeAndZoom = 1;
         ui_size_preset = 1;
      }
      if(0/*force MONO lookandfeel*/)
      {
         lnf_idx = LookAndFeel.IDX_MONO;
         lnf_colorgrading_initial_idx = 24; // mono_green_2
      }
      if(0/*force DARK lookandfeel*/)
      {
         lnf_idx = LookAndFeel.IDX_DARK;
         lnf_colorgrading_initial_idx = 33; // mono_yellow_2
      }

      // window_sy = 1200;

      switch(ui_size_preset)
      {
         case 0:
            // LoDPI Window (>= 1024x768(XGA) screens)
            //  (note)     minimum window_sx/y: 960x500
            //  (note) recommended window_sx/y: 960x600
            //  (note)    recommended zoom_x/y: 1
            if(bForceSizeAndZoom)
            {
               window_sx = 960;
               window_sy = 600;
               zoom_x = 1;
               zoom_y = 1;
            }
            // // cursor_zoom = 1;
            UI.SetTextViewFontScaling(1.0);
            break;

         case 1:
            // Upscaled LoDPI Window (>= 1920x1200(WUXGA) screens)
            //  (note)     minimum window_sx/y: 960x500
            //  (note) recommended window_sx/y: 960x600
            //  (note)    recommended zoom_x/y: 2
            if(bForceSizeAndZoom)
            {
               window_sx = 960;
               window_sy = 600;
               zoom_x = 2;
               zoom_y = 2;
            }
            // // cursor_zoom = 2;
            UI.SetTextViewFontScaling(1.0);
            break;

         case 2:
            // "4k" HiDPI Window (font+icon-scaled) (>= 2560x1600(WQXGA) screens)
            //  (note)     minimum window_sx/y: 1900x1200
            //  (note) recommended window_sx/y: 1900x1600
            //  (note)    recommended zoom_x/y: 1
            if(bForceSizeAndZoom)
            {
               window_sx = 1900;
               window_sy = 1600;
               zoom_x = 1;
               zoom_y = 1;
            }
            // // cursor_zoom = 1;
            UI.SetFontScaling(2.0);
            UI.SetTextViewFontScaling(1.0);
            UI.SetEnableTextViewHiDPIFont(true);
            // (note) in actual 4k, the UI looks better w/o smoothing. in 2k "big" mode or in dark/mono mode, it's the other way around.
            //         (feel free to edit)
            UI.SetEnableFontSmoothing(window_sy <= 1200 ||
                                      (LookAndFeel.IDX_LIGHT != lnf_idx) // dark or mono ?
                                      );
            UI.SetIconScaling(2.0);
            UI.SetPadScaling(2.0);
            UI.SetWindowBorderScaling(2.0);
            UI.SetTableMinimumColumnWidthScaling(0.68);
            b_node_tracker_doublecenter_hidpi = true;
            b_node_tracker_doublewidth_hidpi  = true;
            b_node_tracker_doubleheight_hidpi = true;
            pipemap_num_nodes_per_row = 10;
            break;
      }
   }

   static InitPre() {
      // called in main() (before InitUI())
      //  (after the global statements in this script have been run)

      ApplySizePreset();

      // UI.SetEnableForceMonoIcons(true, false/*bInvert*/);
   }

   static GlobalInitPre() {
      // called in main() (after InitUI() and before Global.Init())

      // if(0)
      // {
      //    // BEGIN test code (dark/mono timingview colors)
      //    AppLookAndFeel lnf <= st2::app_lnf;
      //    lnf.setColor(AppLookAndFeel.COLOR_NT_PV_TIMINGBOX_NOTE_TOP_EVEN,  #af004f6f);
      //    lnf.setColor(AppLookAndFeel.COLOR_NT_PV_TIMINGBOX_NOTE_TOP_ODD,   #af006f4f);
      //    lnf.setColor(AppLookAndFeel.COLOR_NT_PV_TIMINGBOX_NOTE_FILL_EVEN, #3f00afaf);
      //    lnf.setColor(AppLookAndFeel.COLOR_NT_PV_TIMINGBOX_NOTE_FILL_ODD,  #3f00af3f);
      //    lnf.setColor(AppLookAndFeel.COLOR_NT_PV_TIMINGBOX_NOTE_LEFT,      #7f000000);
      //    lnf.setColor(AppLookAndFeel.COLOR_NT_PV_TIMINGBOX_OTHER,          #afff0000);  // line above non-note events
      //    // END test code
      // }

      if(UI.IsHiDPI())
      {
         ui::LookAndFeel lnf <= UI.GetLookAndFeel(LookAndFeel.LIGHT);
         // UI.b_skip_bevel_border = true;
         // lnf.b_combobox_bevel_border = false;
         // lnf.b_button_bevel_border   = false;
         // lnf.checkbox_icon_suffix    = "_dark";
      }

      // EnableColorGrading_Mono();
      // EnableColorGrading();

      // (note) Select color grading preset here (requires app restart)
      // (note) Pass -1 as initialIdx to set cycle list but not enable color grading immediately
      // (note) DEBUG: use lshift-sysbrk to toggle color grading
      // (note) DEBUG: use lctrl+sysbrk to select next preset
      // (note) DEBUG: use lctrl-lshift-sysbrk to select previous preset
      // (note) DEBUG: use sysbrk to toggle LookAndFeel (dark/light) (visible layers only!)
      // (note) The currently selected index becomes persistent when the configuration is saved
      STConfig.EnableColorGrading_Cycle(STConfig.lnf_colorgrading_initial_idx);
   }

   static InitPost() {
      // called in main() (after Global.Init())
   }

}

STConfig.SetAffinity();
