// SPDX-License-Identifier: Unlicense

#include "ui.h"

#include "camera.h"
#include "collection.h"
#include "data.h"
#include "file.h"
#include "global.h"
#include "helper.h"
#include "midi.h"
#include "milk.h"
#include "nuklear.h"
#include "opengl.h"
#include "player.h"
#include "sfx.h"
#include "sfxCore.h"
#include "sfxCore/sfxCoreExtensionV2/api.h"
#include "sfxPluginClap.h"
#include "shader.h"
#include "shred.h"
#include "sound.h"
#include "string_.h"
#include "texture.h"
#include "tones.h"
#include "version.h"
#include "window.h"

#include <algorithm>
#include <assert.h>
#include <limits.h>
#include <math.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#ifndef NK_SDL_GLES2_H_
#define NK_SDL_GLES2_H_

#ifdef SHR3D_SDL
#include <SDL.h>
#endif // SHR3D_SDL

static nk_context* ctx;

NK_API struct nk_context* nk_sdl_init(/*SDL_Window* win*/);
NK_API void                 nk_sdl_font_stash_begin(struct nk_font_atlas** atlas);
NK_API void                 nk_sdl_font_stash_end(void);
NK_API int                  nk_sdl_handle_event(SDL_Event* evt);
NK_API void                 nk_sdl_render(enum nk_anti_aliasing, int max_vertex_buffer, int max_element_buffer, const mat4& viewProjectionMat);
NK_API void                 nk_sdl_shutdown(void);
NK_API void                 nk_sdl_device_destroy(void);
NK_API void                 nk_sdl_device_create(void);

#endif

/*
 * ==============================================================
 *
 *                          IMPLEMENTATION
 *
 * ===============================================================
 */

#include <string.h>

struct nk_sdl_device {
  struct nk_buffer cmds;
  struct nk_draw_null_texture tex_null;
  //GLuint vbo, ebo;
  //GLuint prog;
  //GLuint vert_shdr;
  //GLuint frag_shdr;
  //GLint attrib_pos;
  //GLint attrib_uv;
  //GLint attrib_col;
  //GLint uniform_tex;
  //GLint uniform_proj;
  GLuint font_tex;
  GLsizei vs;
  size_t vp, vt, vc;
};

struct nk_sdl_vertex {
  GLfloat position[2];
  GLfloat uv[2];
  nk_byte col[4];
};

static struct nk_sdl {
  //SDL_Window* win;
  struct nk_sdl_device ogl;
  struct nk_context ctx;
  struct nk_font_atlas atlas;
} sdl;

NK_API void
nk_sdl_device_create(void)
{
  GLint status;

  struct nk_sdl_device* dev = &sdl.ogl;

  nk_buffer_init_default(&dev->cmds);
  //dev->prog = glCreateProgram();
  //dev->vert_shdr = glCreateShader(GL_VERTEX_SHADER);
  //dev->frag_shdr = glCreateShader(GL_FRAGMENT_SHADER);
  //glShaderSource(dev->vert_shdr, 1, &Glsl::uiVert, 0);
  //glShaderSource(dev->frag_shdr, 1, &Glsl::uiFrag, 0);
  //glCompileShader(dev->vert_shdr);
  //glCompileShader(dev->frag_shdr);

  //glGetShaderiv(dev->vert_shdr, GL_COMPILE_STATUS, &status);
  //assert(status == GL_TRUE);
  //glGetShaderiv(dev->frag_shdr, GL_COMPILE_STATUS, &status);
  //assert(status == GL_TRUE);
  //glAttachShader(dev->prog, dev->vert_shdr);
  //glAttachShader(dev->prog, dev->frag_shdr);
  //glLinkProgram(dev->prog);
  //glGetProgramiv(dev->prog, GL_LINK_STATUS, &status);
  //assert(status == GL_TRUE);


  //dev->uniform_tex = glGetUniformLocation(Shader::ui, "texture0");
  //dev->uniform_proj = glGetUniformLocation(Shader::ui, "modelViewProjection");
  //dev->attrib_pos = glGetAttribLocation(Shader::ui, "position");
  //dev->attrib_uv = glGetAttribLocation(Shader::ui, "texcoord");
  //dev->attrib_col = glGetAttribLocation(Shader::ui, "color");
  {
    dev->vs = sizeof(struct nk_sdl_vertex);
    dev->vp = offsetof(struct nk_sdl_vertex, position);
    dev->vt = offsetof(struct nk_sdl_vertex, uv);
    dev->vc = offsetof(struct nk_sdl_vertex, col);

    /* Allocate buffers */
    //glGenBuffers(1, &dev->vbo);
    //glGenBuffers(1, &dev->ebo);
  }
  //glBindTexture(GL_TEXTURE_2D, 0);
  //glBindBuffer(GL_ARRAY_BUFFER, 0);
  //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

NK_INTERN void
nk_sdl_device_upload_atlas(const void* image, int width, int height)
{
  struct nk_sdl_device* dev = &sdl.ogl;
  GL(glGenTextures(1, &dev->font_tex));
  GL(glBindTexture(GL_TEXTURE_2D, dev->font_tex));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
  GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)width, (GLsizei)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image));
}

NK_API void
nk_sdl_device_destroy(void)
{
  struct nk_sdl_device* dev = &sdl.ogl;
  //glDetachShader(Shader::ui, dev->vert_shdr);
  //glDetachShader(Shader::ui, dev->frag_shdr);
  //glDeleteShader(dev->vert_shdr);
  //glDeleteShader(dev->frag_shdr);
  GL(glDeleteProgram(Shader::ui));
  GL(glDeleteTextures(1, &dev->font_tex));
  //glDeleteBuffers(1, &dev->vbo);
  //glDeleteBuffers(1, &dev->ebo);
  nk_buffer_free(&dev->cmds);
}

//static mat4 cylindricalCurvatureMatrix(const mat4& m, f32 theta)
//{
//  return {
//    .m00 = cos(theta) * m.m00,
//    .m10 = m.m10,
//    .m20 = -sin(theta) * m.m20,
//    .m30 = m.m30,
//    .m01 = m.m01,
//    .m11 = m.m11,
//    .m21 = m.m21,
//    .m31 = m.m31,
//    .m02 = sin(theta) * m.m02,
//    .m12 = m.m12,
//    .m22 = cos(theta) * m.m22,
//    .m03 = m.m03,
//    .m13 = m.m13,
//    .m23 = m.m23,
//    .m33 = m.m33,
//  };
//}

NK_API void nk_sdl_render(enum nk_anti_aliasing AA, int max_vertex_buffer, int max_element_buffer, i32 width, i32 height, const mat4* viewProjectionMat)
{
  struct nk_sdl_device* dev = &sdl.ogl;

  struct nk_vec2 scale;
  scale.x = 1.0f;
  scale.y = 1.0f;

  /* setup program */
  GL(glUseProgram(Shader::ui));

#ifdef SHR3D_OPENXR
  if (viewProjectionMat != nullptr)
  {
    mat4 mvp{
      .m23 = Settings::uiXrZ
    };
    mvp = vec::multiply(*viewProjectionMat, mvp);
    GL(glUniformMatrix4fv(Shader::uiUniformModelViewProjection, 1, GL_FALSE, &mvp.m00));
  }
  else
#endif // SHR3D_OPENXR
  {
    const mat4 mvp;
    GL(glUniformMatrix4fv(Shader::uiUniformModelViewProjection, 1, GL_FALSE, &mvp.m00));
  }

  GL(glUniform2f(Shader::uiUniformResolution, f32(width), f32(height)));

  {
    /* convert from command queue into draw list and draw to screen */
    const struct nk_draw_command* cmd;
    void* vertices, * elements;
    const nk_draw_index* offset = NULL;

    /* Bind buffers */
    //glBindBuffer(GL_ARRAY_BUFFER, dev->vbo);
    //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo);

    {
      /* buffer setup */
      GL(glEnableVertexAttribArray(0));
      GL(glEnableVertexAttribArray(1));
      GL(glEnableVertexAttribArray(2));

      GL(glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, dev->vs, (void*)dev->vp));
      GL(glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, dev->vs, (void*)dev->vt));
      GL(glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, dev->vs, (void*)dev->vc));
    }

    GL(glBufferData(GL_ARRAY_BUFFER, max_vertex_buffer, NULL, GL_STREAM_DRAW));
    GL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, max_element_buffer, NULL, GL_STREAM_DRAW));

    /* load vertices/elements directly into vertex/element buffer */
    vertices = malloc((size_t)max_vertex_buffer);
    elements = malloc((size_t)max_element_buffer);
    {
      /* fill convert configuration */
      static const struct nk_draw_vertex_layout_element vertex_layout[] = {
          {NK_VERTEX_POSITION, NK_FORMAT_FLOAT, NK_OFFSETOF(struct nk_sdl_vertex, position)},
          {NK_VERTEX_TEXCOORD, NK_FORMAT_FLOAT, NK_OFFSETOF(struct nk_sdl_vertex, uv)},
          {NK_VERTEX_COLOR, NK_FORMAT_R8G8B8A8, NK_OFFSETOF(struct nk_sdl_vertex, col)},
          {NK_VERTEX_LAYOUT_END}
      };
      nk_convert_config config
      {
        .global_alpha = 1.0f,
        .line_AA = AA,
        .shape_AA = AA,
        .circle_segment_count = 22,
        .arc_segment_count = 22,
        .curve_segment_count = 22,
        .tex_null = dev->tex_null,
        .vertex_layout = vertex_layout,
        .vertex_size = sizeof(struct nk_sdl_vertex),
        .vertex_alignment = NK_ALIGNOF(struct nk_sdl_vertex),
      };

      /* setup buffers to load vertices and elements */
      {
        struct nk_buffer vbuf, ebuf;
        nk_buffer_init_fixed(&vbuf, vertices, (nk_size)max_vertex_buffer);
        nk_buffer_init_fixed(&ebuf, elements, (nk_size)max_element_buffer);
        nk_convert(&sdl.ctx, &dev->cmds, &vbuf, &ebuf, &config);
      }
    }
    GL(glBufferSubData(GL_ARRAY_BUFFER, 0, (size_t)max_vertex_buffer, vertices));
    GL(glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, (size_t)max_element_buffer, elements));
    free(vertices);
    free(elements);

    /* iterate over and execute each draw command */
    nk_draw_foreach(cmd, &sdl.ctx, &dev->cmds) {
      if (!cmd->elem_count) continue;
      GL(glBindTexture(GL_TEXTURE_2D, (GLuint)cmd->texture.id));

      if (viewProjectionMat == nullptr) // TODO this needs a fix for VR
      {
        GL(glScissor((GLint)(cmd->clip_rect.x * scale.x),
          (GLint)((height - (GLint)(cmd->clip_rect.y + cmd->clip_rect.h)) * scale.y),
          (GLint)(cmd->clip_rect.w * scale.x),
          (GLint)(cmd->clip_rect.h * scale.y)));
      }

      GL(glDrawElements(GL_TRIANGLES, (GLsizei)cmd->elem_count, GL_UNSIGNED_SHORT, offset));
      offset += cmd->elem_count;
    }
  }
}

static void
nk_sdl_clipboard_paste(nk_handle usr, struct nk_text_edit* edit)
{
#ifdef SHR3D_WINDOW_SDL
  const char* text = SDL_GetClipboardText();
  if (text) nk_textedit_paste(edit, text, nk_strlen(text));
  (void)usr;
#endif // SHR3D_WINDOW_SDL
}

static void
nk_sdl_clipboard_copy(nk_handle usr, const char* text, int len)
{
#ifdef SHR3D_WINDOW_SDL
  char* str = 0;
  (void)usr;
  if (!len) return;
  str = (char*)malloc((size_t)len + 1);
  if (!str) return;
  memcpy(str, text, (size_t)len);
  str[len] = '\0';
  SDL_SetClipboardText(str);
  free(str);
#endif // SHR3D_WINDOW_SDL
}

NK_API struct nk_context*
nk_sdl_init(/*SDL_Window* win*/)
{
  //sdl.win = win;
  nk_init_default(&sdl.ctx, 0);
  sdl.ctx.clip.copy = nk_sdl_clipboard_copy;
  sdl.ctx.clip.paste = nk_sdl_clipboard_paste;
  sdl.ctx.clip.userdata = nk_handle_ptr(0);
  nk_sdl_device_create();
  return &sdl.ctx;
}

NK_API void
nk_sdl_font_stash_begin(struct nk_font_atlas** atlas)
{
  nk_font_atlas_init_default(&sdl.atlas);
  nk_font_atlas_begin(&sdl.atlas);
  *atlas = &sdl.atlas;
}

NK_API void
nk_sdl_font_stash_end(void)
{
  const void* image; int w, h;
  image = nk_font_atlas_bake(&sdl.atlas, &w, &h, NK_FONT_ATLAS_RGBA32);
  nk_sdl_device_upload_atlas(image, w, h);
  nk_font_atlas_end(&sdl.atlas, nk_handle_id((int)sdl.ogl.font_tex), &sdl.ogl.tex_null);
  if (sdl.atlas.default_font)
    nk_style_set_font(&sdl.ctx, &sdl.atlas.default_font->handle);

}

#ifdef SHR3D_WINDOW_SDL
NK_API int
nk_sdl_handle_event(SDL_Event* evt)
{
  struct nk_context* ctx = &sdl.ctx;

  /* optional grabbing behavior */
  if (ctx->input.mouse.grab) {
    SDL_SetRelativeMouseMode(SDL_TRUE);
    ctx->input.mouse.grab = 0;
  }
  else if (ctx->input.mouse.ungrab) {
    int x = (int)ctx->input.mouse.prev.x, y = (int)ctx->input.mouse.prev.y;
    SDL_SetRelativeMouseMode(SDL_FALSE);
    SDL_WarpMouseInWindow(Global::window, x, y);
    ctx->input.mouse.ungrab = 0;
  }

  //if (evt->edit.start)
  //  SDL_StartTextInput();

  switch (evt->type)
  {
  case SDL_KEYUP: /* KEYUP & KEYDOWN share same routine */
  case SDL_KEYDOWN:
  {
    int down = evt->type == SDL_KEYDOWN;
    const Uint8* state = SDL_GetKeyboardState(0);
    switch (evt->key.keysym.sym)
    {
    case SDLK_RSHIFT: /* RSHIFT & LSHIFT share same routine */
    case SDLK_LSHIFT:    nk_input_key(ctx, NK_KEY_SHIFT, down); break;
    case SDLK_DELETE:    nk_input_key(ctx, NK_KEY_DEL, down); break;
    case SDLK_RETURN:    nk_input_key(ctx, NK_KEY_ENTER, down); break;
    case SDLK_TAB:       nk_input_key(ctx, NK_KEY_TAB, down); break;
    case SDLK_BACKSPACE: nk_input_key(ctx, NK_KEY_BACKSPACE, down); break;
    case SDLK_HOME:      nk_input_key(ctx, NK_KEY_TEXT_START, down);
      nk_input_key(ctx, NK_KEY_SCROLL_START, down); break;
    case SDLK_END:       nk_input_key(ctx, NK_KEY_TEXT_END, down);
      nk_input_key(ctx, NK_KEY_SCROLL_END, down); break;
    case SDLK_PAGEDOWN:  nk_input_key(ctx, NK_KEY_SCROLL_DOWN, down); break;
    case SDLK_PAGEUP:    nk_input_key(ctx, NK_KEY_SCROLL_UP, down); break;
    case SDLK_z:         nk_input_key(ctx, NK_KEY_TEXT_UNDO, down && state[SDL_SCANCODE_LCTRL]); break;
    case SDLK_r:         nk_input_key(ctx, NK_KEY_TEXT_REDO, down && state[SDL_SCANCODE_LCTRL]); break;
    case SDLK_c:         nk_input_key(ctx, NK_KEY_COPY, down && state[SDL_SCANCODE_LCTRL]); break;
    case SDLK_v:         nk_input_key(ctx, NK_KEY_PASTE, down && state[SDL_SCANCODE_LCTRL]); break;
    case SDLK_x:         nk_input_key(ctx, NK_KEY_CUT, down && state[SDL_SCANCODE_LCTRL]); break;
    case SDLK_b:         nk_input_key(ctx, NK_KEY_TEXT_LINE_START, down && state[SDL_SCANCODE_LCTRL]); break;
    case SDLK_e:         nk_input_key(ctx, NK_KEY_TEXT_LINE_END, down && state[SDL_SCANCODE_LCTRL]); break;
    case SDLK_UP:        nk_input_key(ctx, NK_KEY_UP, down); break;
    case SDLK_DOWN:      nk_input_key(ctx, NK_KEY_DOWN, down); break;
    case SDLK_LEFT:
      if (state[SDL_SCANCODE_LCTRL])
        nk_input_key(ctx, NK_KEY_TEXT_WORD_LEFT, down);
      else nk_input_key(ctx, NK_KEY_LEFT, down);
      break;
    case SDLK_RIGHT:
      if (state[SDL_SCANCODE_LCTRL])
        nk_input_key(ctx, NK_KEY_TEXT_WORD_RIGHT, down);
      else nk_input_key(ctx, NK_KEY_RIGHT, down);
      break;
    }
  }
  return 1;

  case SDL_MOUSEBUTTONUP: /* MOUSEBUTTONUP & MOUSEBUTTONDOWN share same routine */
  case SDL_MOUSEBUTTONDOWN:
  {
    int down = evt->type == SDL_MOUSEBUTTONDOWN;
    const int x = evt->button.x, y = evt->button.y;
    switch (evt->button.button)
    {
    case SDL_BUTTON_LEFT:
      if (evt->button.clicks > 1)
        nk_input_button(ctx, NK_BUTTON_DOUBLE, x, y, down);
      nk_input_button(ctx, NK_BUTTON_LEFT, x, y, down); break;
    case SDL_BUTTON_MIDDLE: nk_input_button(ctx, NK_BUTTON_MIDDLE, x, y, down); break;
    case SDL_BUTTON_RIGHT:  nk_input_button(ctx, NK_BUTTON_RIGHT, x, y, down); break;
    }
  }
  return 1;

  case SDL_MOUSEMOTION:
    if (ctx->input.mouse.grabbed) {
      int x = (int)ctx->input.mouse.prev.x, y = (int)ctx->input.mouse.prev.y;
      nk_input_motion(ctx, x + evt->motion.xrel, y + evt->motion.yrel);
    }
    else nk_input_motion(ctx, evt->motion.x, evt->motion.y);
    return 1;

  case SDL_TEXTINPUT:
  {
    nk_glyph glyph;
    memcpy(glyph, evt->text.text, NK_UTF_SIZE);
    nk_input_glyph(ctx, glyph);
  }
  return 1;

  case SDL_MOUSEWHEEL:
    nk_input_scroll(ctx, nk_vec2((float)evt->wheel.x, (float)evt->wheel.y));
    return 1;
  }

  return 0;
}
#endif // SHR3D_WINDOW_SDL
#ifdef SHR3D_WINDOW_WIN32
NK_API int nk_win32_handle_event(HWND wnd, u32 msg, WPARAM wparam, LPARAM lparam)
{
  i32 cursorPosX = Global::inputCursorPosX;
  i32 cursorPosY = Global::inputCursorPosY;
#ifdef SHR3D_OPENXR_PCVR
  if (Global::xrInitialized)
  {
    cursorPosX = Global::inputCursorPosXrX;
    cursorPosY = Global::inputCursorPosXrY;
  }
#endif // SHR3D_OPENXR_PCVR

  switch (msg)
  {
  case WM_KEYDOWN:
  case WM_KEYUP:
  case WM_SYSKEYDOWN:
  case WM_SYSKEYUP:
  {
    int down = !((lparam >> 31) & 1);
    int ctrl = GetKeyState(VK_CONTROL) & (1 << 15);

    switch (wparam)
    {
    case VK_SHIFT:
    case VK_LSHIFT:
    case VK_RSHIFT:
      nk_input_key(ctx, NK_KEY_SHIFT, down);
      return 1;

    case VK_DELETE:
      nk_input_key(ctx, NK_KEY_DEL, down);
      return 1;

    case VK_RETURN:
      nk_input_key(ctx, NK_KEY_ENTER, down);
      return 1;

    case VK_TAB:
      nk_input_key(ctx, NK_KEY_TAB, down);
      return 1;

    case VK_LEFT:
      if (ctrl)
        nk_input_key(ctx, NK_KEY_TEXT_WORD_LEFT, down);
      else
        nk_input_key(ctx, NK_KEY_LEFT, down);
      return 1;

    case VK_RIGHT:
      if (ctrl)
        nk_input_key(ctx, NK_KEY_TEXT_WORD_RIGHT, down);
      else
        nk_input_key(ctx, NK_KEY_RIGHT, down);
      return 1;

    case VK_BACK:
      nk_input_key(ctx, NK_KEY_BACKSPACE, down);
      return 1;

    case VK_HOME:
      nk_input_key(ctx, NK_KEY_TEXT_START, down);
      nk_input_key(ctx, NK_KEY_SCROLL_START, down);
      return 1;

    case VK_END:
      nk_input_key(ctx, NK_KEY_TEXT_END, down);
      nk_input_key(ctx, NK_KEY_SCROLL_END, down);
      return 1;

    case VK_NEXT:
      nk_input_key(ctx, NK_KEY_SCROLL_DOWN, down);
      return 1;

    case VK_PRIOR:
      nk_input_key(ctx, NK_KEY_SCROLL_UP, down);
      return 1;

    case 'C':
      if (ctrl) {
        nk_input_key(ctx, NK_KEY_COPY, down);
        return 1;
      }
      break;

    case 'V':
      if (ctrl) {
        nk_input_key(ctx, NK_KEY_PASTE, down);
        return 1;
      }
      break;

    case 'X':
      if (ctrl) {
        nk_input_key(ctx, NK_KEY_CUT, down);
        return 1;
      }
      break;

    case 'Z':
      if (ctrl) {
        nk_input_key(ctx, NK_KEY_TEXT_UNDO, down);
        return 1;
      }
      break;

    case 'R':
      if (ctrl) {
        nk_input_key(ctx, NK_KEY_TEXT_REDO, down);
        return 1;
      }
      break;
    }
    return 0;
  }

  case WM_CHAR:
    if (wparam >= 32)
    {
      nk_input_unicode(ctx, (nk_rune)wparam);
      return 1;
    }
    break;

  case WM_LBUTTONDOWN:
    nk_input_button(ctx, NK_BUTTON_LEFT, cursorPosX, cursorPosY, 1);
    SetCapture(wnd);
    return 1;

  case WM_LBUTTONUP:
    nk_input_button(ctx, NK_BUTTON_DOUBLE, cursorPosX, cursorPosY, 0);
    nk_input_button(ctx, NK_BUTTON_LEFT, cursorPosX, cursorPosY, 0);
    ReleaseCapture();
    return 1;

  case WM_RBUTTONDOWN:
    nk_input_button(ctx, NK_BUTTON_RIGHT, (i16)LOWORD(lparam), (i16)HIWORD(lparam), 1);
    SetCapture(wnd);
    return 1;

  case WM_RBUTTONUP:
    nk_input_button(ctx, NK_BUTTON_RIGHT, (i16)LOWORD(lparam), (i16)HIWORD(lparam), 0);
    ReleaseCapture();
    return 1;

  case WM_MBUTTONDOWN:
    nk_input_button(ctx, NK_BUTTON_MIDDLE, (i16)LOWORD(lparam), (i16)HIWORD(lparam), 1);
    SetCapture(wnd);
    return 1;

  case WM_MBUTTONUP:
    nk_input_button(ctx, NK_BUTTON_MIDDLE, (i16)LOWORD(lparam), (i16)HIWORD(lparam), 0);
    ReleaseCapture();
    return 1;

  case WM_MOUSEWHEEL:
    nk_input_scroll(ctx, nk_vec2(0, (float)(i16)HIWORD(wparam) / WHEEL_DELTA));
    return 1;

  case WM_MOUSEMOVE:
#ifdef SHR3D_OPENXR
    Global::inputXRActiveController = XrActiveController::mouseKeyboard;
#endif // SHR3D_OPENXR
    nk_input_motion(ctx, cursorPosX, cursorPosY);
    return 1;

  case WM_LBUTTONDBLCLK:
    nk_input_button(ctx, NK_BUTTON_DOUBLE, cursorPosX, cursorPosY, 1);
    return 1;
  }

  return 0;
}
#endif // SHR3D_WINDOW_WIN32

NK_API
void nk_sdl_shutdown(void)
{
  nk_font_atlas_clear(&sdl.atlas);
  nk_free(&sdl.ctx);
  nk_sdl_device_destroy();
  memset(&sdl, 0, sizeof(sdl));
}

#define MAX_VERTEX_MEMORY 512 * 1024 * 8
#define MAX_ELEMENT_MEMORY 128 * 1024 * 8

#ifdef INCLUDE_ALL
#define INCLUDE_STYLE
#define INCLUDE_CALCULATOR
#define INCLUDE_CANVAS
#define INCLUDE_OVERVIEW
#define INCLUDE_NODE_EDITOR
#endif

void Ui::init()
{
  ctx = nk_sdl_init();
  /* Load Fonts: if none of these are loaded a default font will be used  */
  /* Load Cursor: if you uncomment cursor loading please hide the cursor */
  {
    struct nk_font_atlas* atlas;
    nk_sdl_font_stash_begin(&atlas);
    /*struct nk_font *droid = nk_font_atlas_add_from_file(atlas, "../../../extra_font/DroidSans.ttf", 14, 0);*/
    /*struct nk_font *roboto = nk_font_atlas_add_from_file(atlas, "../../../extra_font/Roboto-Regular.ttf", 16, 0);*/
    /*struct nk_font *future = nk_font_atlas_add_from_file(atlas, "../../../extra_font/kenvector_future_thin.ttf", 13, 0);*/
    /*struct nk_font *clean = nk_font_atlas_add_from_file(atlas, "../../../extra_font/ProggyClean.ttf", 12, 0);*/
    /*struct nk_font *tiny = nk_font_atlas_add_from_file(atlas, "../../../extra_font/ProggyTiny.ttf", 10, 0);*/
    /*struct nk_font *cousine = nk_font_atlas_add_from_file(atlas, "../../../extra_font/Cousine-Regular.ttf", 13, 0);*/
    nk_sdl_font_stash_end();
    /*nk_style_load_all_cursors(ctx, atlas->cursors);*/
    /*nk_style_set_font(ctx, &roboto->handle);*/
  }

  nk_color table[NK_COLOR_COUNT];
  for (i32 i = 0; i < NK_COLOR_COUNT; ++i)
  {
    table[i] = nk_rgba_f(Settings::uiColor[i].r, Settings::uiColor[i].g, Settings::uiColor[i].b, Settings::uiColor[i].a);
  }
  nk_style_from_table(ctx, table);
}

static char* stristr(const char* str1, const char* str2)
{
  const char* p1 = str1;
  const char* p2 = str2;
  const char* r = *p2 == 0 ? str1 : 0;

  while (*p1 != 0 && *p2 != 0) {
    if (tolower(*p1) == tolower(*p2)) {
      if (r == 0) {
        r = p1;
      }
      p2++;
    }
    else {
      p2 = str2;
      if (r != 0) {
        p1 = r + 1;
      }

      if (tolower(*p1) == tolower(*p2)) {
        r = p1;
        p2++;
      }
      else {
        r = 0;
      }
    }

    p1++;
  }

  return *p2 == 0 ? (char*)r : 0;
}

static bool filterTextOut(const char* text, const char* searchText)
{
  if (searchText[0] == '\0')
    return false;

  if (stristr(text, searchText))
    return false;

  return true;
}

static bool filterSongOut(const Song::Info& songInfo, const char* searchText)
{
  if (searchText[0] == '\0')
    return false;

  if (stristr(songInfo.songName.c_str(), searchText))
    return false;
  if (stristr(songInfo.artistName.c_str(), searchText))
    return false;
  if (stristr(songInfo.albumName.c_str(), searchText))
    return false;
  if (stristr(Song::tuningName(songInfo.arrangementInfos[0].tuning), searchText))
    return false;
  if (songInfo.songYear != 0 && songInfo.songYear == atoi(searchText))
    return false;

  return true;
}

//static const char* instrumentName(Instrument instrumentFlags)
//{
//  switch (instrumentFlags)
//  {
//  case Instrument::LeadGuitar:
//    return "Lead";
//  case Instrument::RhythmGuitar:
//    return "Rhythm";
//  case Instrument::BassGuitar:
//    return "Bass";
//  case Instrument::LeadGuitar | Instrument::Second:
//    return "Lead 2";
//  case Instrument::RhythmGuitar | Instrument::Second:
//    return "Rhythm 2";
//  case Instrument::BassGuitar | Instrument::Second:
//    return "Bass 2";
//  case Instrument::LeadGuitar | Instrument::Third:
//    return "Lead 3";
//  case Instrument::RhythmGuitar | Instrument::Third:
//    return "Rhythm 3";
//  case Instrument::BassGuitar | Instrument::Third:
//    return "Bass 3";
//  case Instrument::Combo:
//    if (Settings::applicationInstrument == Instrument::RhythmGuitar)
//      return "Rhythm";
//    else
//      return "Lead";
//  case Instrument::Combo | Instrument::Second:
//    if (Settings::applicationInstrument == Instrument::RhythmGuitar)
//      return "Rhythm 2";
//    else
//      return "Lead 2";
//  case Instrument::Combo | Instrument::Third:
//    if (Settings::applicationInstrument == Instrument::RhythmGuitar)
//      return "Rhythm 3";
//    else
//      return "Lead 3";
//    //case Instrument::LeadGuitar | Instrument::Alternative:
//    //  return "Lead Alternative";
//    //case Instrument::RhythmGuitar | Instrument::Alternative:
//    //  return "Rhythm Alternative";
//    //case Instrument::BassGuitar | Instrument::Alternative:
//    //  return "Bass Alternative";
//    //case Instrument::LeadGuitar | Instrument::Bonus:
//    //  return "Lead Bonus";
//    //case Instrument::RhythmGuitar | Instrument::Bonus:
//    //  return "Rhythm Bonus";
//    //case Instrument::BassGuitar | Instrument::Bonus:
//    //  return "Bass Bonus";
//  default:
//    ASSERT(false);
//    return "";
//  }
//}

struct nk_rect calcWindowRectCentered(i32 resolutionWidth, i32 resolutionHeight, i32 posX, i32 posY, i32 width, i32 height)
{
  struct nk_rect rect;

  const f32 scaledWidth = f32(width) * Settings::uiScale;
  const f32 scaledHeight = f32(height) * Settings::uiScale;

  const f32 midX = (f32(resolutionWidth) - scaledWidth) / 2.0f;
  const f32 midY = (f32(resolutionHeight) - scaledHeight) / 2.0f;

  rect = nk_rect(midX + f32(posX) * Settings::uiScale, midY + f32(posY) * Settings::uiScale, scaledWidth, scaledHeight);

  return rect;
}

#ifdef SHR3D_SFX_PLUGIN
static void pluginWindowButtonRow(const SfxId pluginWindowIndex, const i32 pluginWindowInstance, bool& windowPluginHideUi, Shr3DWindow& windowPluginParentWindow)
{
  nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
  nk_layout_row_template_push_static(ctx, 35.0f * Settings::uiScale);
  //nk_layout_row_template_push_dynamic(ctx);
  nk_layout_row_template_end(ctx);

  {
    nk_style_item normal_button_color = ctx->style.button.normal;
    if (!windowPluginHideUi)
      ctx->style.button.normal = ctx->style.button.active;
    if (nk_button_label(ctx, "UI"))
    {
      windowPluginHideUi = !windowPluginHideUi;
      if (windowPluginHideUi)
      {
        Sfx::closeSfxPluginWindow(pluginWindowIndex, pluginWindowInstance);
        Window_::destoryPluginHostWindow(windowPluginParentWindow);
        windowPluginParentWindow = nullptr;
      }
    }
    ctx->style.button.normal = normal_button_color;
  }
}

static bool effectChainWindowPluginParentWindow(const SfxId pluginWindowIndex, const i32 pluginWindowInstance, const i32 resolutionWidth, const i32 resolutionHeight, bool& windowPluginHideUi, Shr3DWindow& windowPluginParentWindow, vec2& windowPluginPosition)
{
  //ASSERT(pluginWindowIndex >= 0);

  if (!windowPluginHideUi && Sfx::hasSfxPluginWindow(pluginWindowIndex))
  {
#ifdef SHR3D_SFX_PLUGIN_CLAP
    if (pluginWindowIndex.system == SfxSystem::clap)
    {
      const bool openNewWindow = windowPluginParentWindow == nullptr && !windowPluginHideUi;
      if (openNewWindow)
      {
        Sfx::openSfxPluginWindow(pluginWindowIndex, pluginWindowInstance, nullptr);
      }

      const Size size = Sfx::getSfxPluginWindowSize(pluginWindowIndex, pluginWindowInstance);

      const char* sfxName = Sfx::names[pluginWindowIndex.system][pluginWindowIndex.sfxIndex].c_str();
      char name[32];
      sprintf(name, "%s_%d", sfxName, pluginWindowInstance);

      const bool showWindow = nk_begin_titled(ctx, name, sfxName, calcWindowRectCentered(resolutionWidth, resolutionHeight, 0, 0, size.width + 2, size.height + 30 + 35), NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_TITLE | NK_WINDOW_CLOSABLE);
      if (showWindow)
      {
        pluginWindowButtonRow(pluginWindowIndex, pluginWindowInstance, windowPluginHideUi, windowPluginParentWindow);

        const auto pos = nk_window_get_position(ctx);

        if (openNewWindow)
        {
          ASSERT(windowPluginParentWindow == nullptr);
          const auto pos = nk_window_get_position(ctx);
          windowPluginParentWindow = Window_::createPluginHostWindow(i32(pos.x) + 1, i32(pos.y) + 29 + 35, size.width, size.height);
          SfxPluginClap::parentWindow(pluginWindowIndex.sfxIndex, pluginWindowInstance, windowPluginParentWindow);
        }

        if (openNewWindow
          || pos.x != windowPluginPosition.x
          || pos.y != windowPluginPosition.y)
        {
          Window_::setPluginHostWindowPosition(windowPluginParentWindow, i32(pos.x) + 1, i32(pos.y) + 29 + 35, size.width, size.height);

          windowPluginPosition.x = pos.x;
          windowPluginPosition.y = pos.y;
        }
      }
      else
      {
        Sfx::closeSfxPluginWindow(pluginWindowIndex, pluginWindowInstance);
        Window_::destoryPluginHostWindow(windowPluginParentWindow);
        windowPluginParentWindow = nullptr;
        windowPluginHideUi = false;
      }
      nk_end(ctx);

      return showWindow;
    }
    else
#endif // SHR3D_SFX_PLUGIN_CLAP
    {
      const Size size = Sfx::getSfxPluginWindowSize(pluginWindowIndex, pluginWindowInstance);

      const char* sfxName = Sfx::names[pluginWindowIndex.system][pluginWindowIndex.sfxIndex].c_str();
      char name[32];
      sprintf(name, "%s_%d", sfxName, pluginWindowInstance);

      const bool showWindow = nk_begin_titled(ctx, name, sfxName, calcWindowRectCentered(resolutionWidth, resolutionHeight, 0, 0, size.width + 2, size.height + 30 + 35), NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_TITLE | NK_WINDOW_CLOSABLE);
      if (showWindow)
      {
        pluginWindowButtonRow(pluginWindowIndex, pluginWindowInstance, windowPluginHideUi, windowPluginParentWindow);

        const auto pos = nk_window_get_position(ctx);

        const bool openNewWindow = windowPluginParentWindow == nullptr && !windowPluginHideUi;
        if (openNewWindow)
        {
          ASSERT(windowPluginParentWindow == nullptr);
          const auto pos = nk_window_get_position(ctx);
          windowPluginParentWindow = Window_::createPluginHostWindow(i32(pos.x) + 1, i32(pos.y) + 29 + 35, size.width, size.height);
          Sfx::openSfxPluginWindow(pluginWindowIndex, pluginWindowInstance, windowPluginParentWindow);
        }

        if (openNewWindow
          || pos.x != windowPluginPosition.x
          || pos.y != windowPluginPosition.y)
        {
          Window_::setPluginHostWindowPosition(windowPluginParentWindow, i32(pos.x) + 1, i32(pos.y) + 29 + 35, size.width, size.height);

          windowPluginPosition.x = pos.x;
          windowPluginPosition.y = pos.y;
        }
      }
      else
      {
        Sfx::closeSfxPluginWindow(pluginWindowIndex, pluginWindowInstance);
        Window_::destoryPluginHostWindow(windowPluginParentWindow);
        windowPluginParentWindow = nullptr;
        windowPluginHideUi = false;
      }
      nk_end(ctx);

      return showWindow;
    }
  }
  else
  {
    const i32 numParam = Sfx::numParams(pluginWindowIndex, pluginWindowInstance);
    const f32 windowHeight = numParam * 27.0f + 50.0f + 35.0f;

    const char* sfxName = Sfx::names[pluginWindowIndex.system][pluginWindowIndex.sfxIndex].c_str();
    char name[32];
    sprintf(name, "%s_%d", sfxName, pluginWindowInstance);

    const bool showWindow = nk_begin_titled(ctx, name, sfxName, calcWindowRectCentered(resolutionWidth, resolutionHeight, 0, 0, 800, windowHeight), NK_WINDOW_BORDER | NK_WINDOW_SCALABLE | NK_WINDOW_MOVABLE | NK_WINDOW_TITLE | NK_WINDOW_CLOSABLE);
    if (showWindow)
    {
      pluginWindowButtonRow(pluginWindowIndex, pluginWindowInstance, windowPluginHideUi, windowPluginParentWindow);

      nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
      nk_layout_row_template_push_static(ctx, 150.0f * Settings::uiScale);
      nk_layout_row_template_push_dynamic(ctx);
      nk_layout_row_template_push_static(ctx, 70.0f * Settings::uiScale);
      nk_layout_row_template_push_static(ctx, 30.0f * Settings::uiScale);
      nk_layout_row_template_end(ctx);

      for (i32 i = 0; i < numParam; ++i)
      {
        SfxParameterProperties parameterProperties;
        Sfx::getParameterProperties(pluginWindowIndex, pluginWindowInstance, i, parameterProperties);
        nk_label(ctx, parameterProperties.name, NK_TEXT_LEFT);

        const f32 oldValue = Sfx::getParameter(pluginWindowIndex, pluginWindowInstance, i);
        f32 value = oldValue;
        nk_slider_float(ctx, 0.0f, &value, 1.0f, 0.0001f);
        if (oldValue != value)
          Sfx::setParameter(pluginWindowIndex, pluginWindowInstance, i, value);

        nk_label(ctx, parameterProperties.display, NK_TEXT_RIGHT);
        nk_label(ctx, parameterProperties.label, NK_TEXT_LEFT);
      }
    }
    else
    {
      windowPluginHideUi = false;
    }
    nk_end(ctx);

    return showWindow;
  }
}
#endif // SHR3D_SFX_PLUGIN

#ifdef SHR3D_SFX
static i32 selectSfxWindow(SfxId& selectedEffect, i32 resolutionWidth, i32 resolutionHeight
#ifdef SHR3D_SFX_CORE_HEXFIN
  , bool showOnlySfxCoreTunerAndSfxPlugins = false
#endif // SHR3D_SFX_CORE_HEXFIN
)
{
  const bool showWindow = nk_begin(ctx, "Select Effect", calcWindowRectCentered(resolutionWidth, resolutionHeight, 450, 0, 440, 550), NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE | NK_WINDOW_TITLE | NK_WINDOW_CLOSABLE);
  if (showWindow)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);

    static char effectSearchText[256] = "";
    static i32 effectSearchTextLength = 0;
    nk_edit_string(ctx, NK_EDIT_SIMPLE, effectSearchText, &effectSearchTextLength, sizeof(effectSearchText), nk_filter_default);
    const std::string searchText(effectSearchText, effectSearchTextLength);

#ifdef SHR3D_SFX_CORE
    if (nk_tree_push(ctx, NK_TREE_TAB, "SFX Core", NK_MAXIMIZED))
    {
#ifdef SHR3D_SFX_CORE_HEXFIN
      if (showOnlySfxCoreTunerAndSfxPlugins)
      {
        if (!filterTextOut(Sfx::names[SfxSystem::core][to_underlying_(SfxCore::Hexfin)].c_str(), searchText.c_str()))
          if (nk_button_label(ctx, Sfx::names[SfxSystem::core][to_underlying_(SfxCore::Hexfin)].c_str()))
          {
            selectedEffect.system = SfxSystem::core;
            selectedEffect.sfxIndex = to_underlying_(SfxCore::Hexfin);
          }
      }
      else
#endif // SHR3D_SFX_CORE_HEXFIN

      {
#if defined (SHR3D_SFX_CORE_NEURALAMPMODELER) || defined (SHR3D_SFX_CORE_HEXFIN)
        for (i32 i = 0; i < i32(Sfx::names[SfxSystem::core].size()); ++i)
        {
          if (filterTextOut(Sfx::names[SfxSystem::core][i].c_str(), searchText.c_str()))
            continue;

          if (nk_button_label(ctx, Sfx::names[SfxSystem::core][i].c_str()))
          {
            selectedEffect.system = SfxSystem::core;
            selectedEffect.sfxIndex = i;
            break;
          }
        }
#endif // SHR3D_SFX_CORE_NEURALAMPMODELER || SHR3D_SFX_CORE_HEXFIN

#ifdef SHR3D_SFX_CORE_AIRWINDOWS
        if (nk_tree_push(ctx, NK_TREE_TAB, "Air Windows", NK_MAXIMIZED))
        {
          for (i32 i = 0; i < Sfx::names[SfxSystem::coreAirWindows].size(); ++i)
          {
            if (filterTextOut(Sfx::names[SfxSystem::coreAirWindows][i].c_str(), searchText.c_str()))
              continue;

            if (nk_button_label(ctx, Sfx::names[SfxSystem::coreAirWindows][i].c_str()))
            {
              selectedEffect.system = SfxSystem::coreAirWindows;
              selectedEffect.sfxIndex = i;
              break;
            }
          }
          nk_tree_pop(ctx);
        }
#endif // SHR3D_SFX_CORE_AIRWINDOWS

#ifdef SHR3D_SFX_CORE_RAKARRACK
        if (nk_tree_push(ctx, NK_TREE_TAB, "Rakarrack", NK_MAXIMIZED))
        {
          for (i32 i = 0; i < Sfx::names[SfxSystem::coreRakarrack].size(); ++i)
          {
            if (filterTextOut(Sfx::names[SfxSystem::coreRakarrack][i].c_str(), searchText.c_str()))
              continue;

            if (nk_button_label(ctx, Sfx::names[SfxSystem::coreRakarrack][i].c_str()))
            {
              selectedEffect.system = SfxSystem::coreRakarrack;
              selectedEffect.sfxIndex = i;
              break;
            }
          }
          nk_tree_pop(ctx);
        }
#endif // SHR3D_SFX_CORE_RAKARRACK
      }

      nk_tree_pop(ctx);
    }
#endif // SHR3D_SFX_CORE

#ifdef SHR3D_SFX_CORE_EXTENSION_V2
    if (nk_tree_push(ctx, NK_TREE_TAB, "ExtensionV2", NK_MAXIMIZED))
    {
      for (auto& [name, i] : Global::sfxCoreExtensionV2SortedByName)
      {
        if (filterTextOut(Sfx::names[SfxSystem::coreExtensionV2][i].c_str(), searchText.c_str()))
          continue;

        if (nk_button_label(ctx, name.c_str()))
        {
          selectedEffect.system = SfxSystem::coreExtensionV2;
          selectedEffect.sfxIndex = i;
          break;
        }
      }
      nk_tree_pop(ctx);
    }
#endif // SHR3D_SFX_CORE_EXTENSION_V2

#ifdef SHR3D_SFX_PLUGIN_CLAP
    if (nk_tree_push(ctx, NK_TREE_TAB, "CLAP Plugins", NK_MAXIMIZED))
    {
      for (i32 i = 0; i < Sfx::names[SfxSystem::clap].size(); ++i)
      {
        if (filterTextOut(Sfx::names[SfxSystem::clap][i].c_str(), searchText.c_str()))
          continue;

        if (nk_button_label(ctx, Sfx::names[SfxSystem::clap][i].c_str()))
        {
          selectedEffect.system = SfxSystem::clap;
          selectedEffect.sfxIndex = i;
        }
      }
      nk_tree_pop(ctx);
    }
#endif // SHR3D_SFX_PLUGIN_CLAP


#ifdef SHR3D_SFX_PLUGIN_VST
    if (nk_tree_push(ctx, NK_TREE_TAB, "VST Plugins", NK_MAXIMIZED))
    {
#ifdef SHR3D_SFX_PLUGIN_VST_BENCHMARK
      nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
      nk_layout_row_template_push_dynamic(ctx);
      nk_layout_row_template_push_static(ctx, 120.0f * Settings::uiScale);
      nk_layout_row_template_end(ctx);
#endif // SHR3D_SFX_PLUGIN_VST_BENCHMARK

      for (i32 i = 0; i < Sfx::names[SfxSystem::vst].size(); ++i)
      {
        if (filterTextOut(Sfx::names[SfxSystem::vst][i].c_str(), searchText.c_str()))
          continue;

        if (nk_button_label(ctx, Sfx::names[SfxSystem::vst][i].c_str()))
        {
          selectedEffect.system = SfxSystem::vst;
          selectedEffect.sfxIndex = i;
        }

#ifdef SHR3D_SFX_PLUGIN_VST_BENCHMARK
        {
          const SfxBenchmarkResult& benchmarkResult = Global::sfxPluginVstBenchmarkResults[i];
          benchmarkResult.duration;
          benchmarkResult.memoryUsageInBytes;
          char benchmarkText[32];
          sprintf(benchmarkText, "%d ms %d KB", i32(benchmarkResult.duration / 1_ms), i32(benchmarkResult.memoryUsageInBytes / 1024));
          nk_label(ctx, benchmarkText, NK_TEXT_CENTERED);
        }
#endif // SHR3D_SFX_PLUGIN_VST_BENCHMARK
      }
      nk_tree_pop(ctx);
    }
#endif // SHR3D_SFX_PLUGIN_VST

#ifdef SHR3D_SFX_PLUGIN_VST3
    if (nk_tree_push(ctx, NK_TREE_TAB, "VST3 Plugins", NK_MAXIMIZED))
    {
#ifdef SHR3D_SFX_PLUGIN_VST3_BENCHMARK
      nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
      nk_layout_row_template_push_dynamic(ctx);
      nk_layout_row_template_push_static(ctx, 120.0f * Settings::uiScale);
      nk_layout_row_template_end(ctx);
#endif // SHR3D_SFX_PLUGIN_VST3_BENCHMARK

      for (i32 i = 0; i < Sfx::names[SfxSystem::vst3].size(); ++i)
      {
        if (filterTextOut(Sfx::names[SfxSystem::vst3][i].c_str(), searchText.c_str()))
          continue;

        if (nk_button_label(ctx, Sfx::names[SfxSystem::vst3][i].c_str()))
        {
          selectedEffect.system = SfxSystem::vst3;
          selectedEffect.sfxIndex = i;
        }

#ifdef SHR3D_SFX_PLUGIN_VST3_BENCHMARK
        {
          const SfxBenchmarkResult& benchmarkResult = Global::sfxPluginVst3BenchmarkResults[i];
          benchmarkResult.duration;
          benchmarkResult.memoryUsageInBytes;
          char benchmarkText[32];
          sprintf(benchmarkText, "%d ms %d KB", i32(benchmarkResult.duration / 1_ms), i32(benchmarkResult.memoryUsageInBytes / 1024));
          nk_label(ctx, benchmarkText, NK_TEXT_CENTERED);
        }
#endif // SHR3D_SFX_PLUGIN_VST3_BENCHMARK
      }
      nk_tree_pop(ctx);
    }
#endif // SHR3D_SFX_PLUGIN_VST3
  }
  nk_end(ctx);

  return showWindow;
}

static void sfxChainEditorWindow(i32 resolutionWidth, i32 resolutionHeight)
{
  static i32 selectedEmptyEffectChainSlot = -1;

  //static bool windowOpenLastFrame;
  Global::inputSfxChain.toggled = nk_begin(ctx, "FX Chains", calcWindowRectCentered(resolutionWidth, resolutionHeight, -450, 0, 440, 570), NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_TITLE | NK_WINDOW_SCALABLE | NK_WINDOW_CLOSABLE);
  if (Global::inputSfxChain.toggled)
  {
    //if (!windowOpenLastFrame)
    //  sfxToneAutoSwitchOffsetWhenWindowOpened = Global::sfxToneAutoSwitchOffset;

    {
      nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
      nk_layout_row_template_push_static(ctx, 50.0f * Settings::uiScale);
      nk_layout_row_template_push_dynamic(ctx);
      nk_layout_row_template_push_dynamic(ctx);
      nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
      nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
      nk_layout_row_template_end(ctx);

      if (nk_button_label(ctx, "Save"))
      {
#ifndef PLATFORM_EMSCRIPTEN
        const SfxBankIndex sfxBankIndex = Global::activeSfxToneIndex / Const::sfxToneTonesPerBank;
        if (sfxBankIndex <= -2)
        {
          ASSERT(Global::bankIndex2SongIndex.contains(sfxBankIndex));
          const SongIndex songIndex = Global::bankIndex2SongIndex.at(sfxBankIndex);
          Tones::saveSongToneFile(songIndex);
        }
#endif // PLATFORM_EMSCRIPTEN
        Tones::fini(); // save tones.ini file. Saving on quit is not enough when the game crashes.
      }

      {
        SfxBankIndex sfxBankIndex = Global::activeSfxToneIndex / Const::sfxToneTonesPerBank;
        i32 toneIndex = abs(Global::activeSfxToneIndex) % Const::sfxToneTonesPerBank;

        SfxBankIndex minToneBank = Global::firstEmptyNegativeToneBank + 1;
        if (minToneBank == -1)
          minToneBank = 0;
        const SfxBankIndex oldBankIndex = sfxBankIndex;
        nk_property_int(ctx, "Bank", minToneBank, &sfxBankIndex, I32::max / Const::sfxToneTonesPerBank, 1, 1);
        if (sfxBankIndex == -1)
        {
          if (oldBankIndex > sfxBankIndex)
            --sfxBankIndex;
          else
            ++sfxBankIndex;
        }
        nk_property_int(ctx, "Tone", 0, &toneIndex, Const::sfxToneTonesPerBank - 1, 1, 1);
        if (sfxBankIndex < 0)
          toneIndex *= -1;
        Global::activeSfxToneIndex = sfxBankIndex * Const::sfxToneTonesPerBank + toneIndex;
      }
      {
        static SfxChainEffect clipboardEffectChain[ARRAY_SIZE(Global::effectChain)];
        static std::string clipboardEffectChainIndexParameters[ARRAY_SIZE(Global::effectChain)];
        if (nk_button_label(ctx, "C"))
        {
          for (i32 i = 0; i < ARRAY_SIZE(Global::effectChain); ++i)
          {
            clipboardEffectChain[i] = Global::effectChain[i];
            if (Global::effectChain[i].id.system != SfxSystem::empty)
            {
              i32 instance = 0;
              for (i32 j = 0; j < i; ++j)
                if (Global::effectChain[i].id.system == Global::effectChain[j].id.system && Global::effectChain[i].id.sfxIndex == Global::effectChain[j].id.sfxIndex)
                  ++instance;

              clipboardEffectChainIndexParameters[i] = Sfx::saveParameters(Global::effectChain[i].id, instance);
            }
            else
            {
              clipboardEffectChainIndexParameters[i].clear();
            }
          }

          // copy to clipboard. usefull when creating shred songs.
#if defined(PLATFORM_WINDOWS) || defined(PLATFORM_LINUX)
          {
            //const i32 bankIndex = Global::activeSfxToneIndex / Const::sfxToneTonesPerBank;
            const i32 toneIndex = abs(Global::activeSfxToneIndex) % Const::sfxToneTonesPerBank;
            const std::string bankToneStr = Global::clipboardLastPlayedArrangementName + ',' + to_string(toneIndex);

            std::string clipboardData = bankToneStr + '=' + Global::sfxToneNames[Global::activeSfxToneIndex] + '\n';
            for (i32 i = 0, k = 0; i < 16; ++i)
            {
              if (Global::effectChain[i].id.system == SfxSystem::empty)
                continue;

              i32 instance = 0;
              for (i32 j = 0; j < i; ++j)
                if (Global::effectChain[i].id.system == Global::effectChain[j].id.system && Global::effectChain[i].id.sfxIndex == Global::effectChain[j].id.sfxIndex)
                  ++instance;

              clipboardData += bankToneStr + ',' + to_string(k) + '=' + Sfx::names[Global::effectChain[i].id.system][Global::effectChain[i].id.sfxIndex] + ",-," + Sfx::saveParameters(Global::effectChain[i].id, instance) + '\n';
              ++k;
            }
#ifdef SHR3D_WINDOW_SDL
            SDL_SetClipboardText(clipboardData.c_str());
#endif // SHR3D_WINDOW_SDL
#ifdef SHR3D_WINDOW_WIN32
            {
              const int utf16Length = MultiByteToWideChar(CP_UTF8, 0, clipboardData.data(), -1, NULL, 0);
              const HANDLE hGlobalMemory = GlobalAlloc(GMEM_MOVEABLE, sizeof(wchar_t) * (utf16Length + 1));
              const HGLOBAL lockedMemory = GlobalLock(hGlobalMemory);
              MultiByteToWideChar(CP_UTF8, 0, clipboardData.data(), -1, reinterpret_cast<wchar_t*>(lockedMemory), utf16Length);
              GlobalUnlock(hGlobalMemory);
              if (OpenClipboard(nullptr)) {
                SetClipboardData(CF_UNICODETEXT, hGlobalMemory);
                CloseClipboard();
              }
              else {
                GlobalFree(hGlobalMemory);
              }
            }
#endif // SHR3D_WINDOW_WIN32
          }
#endif // PLATFORM_WINDOWS || PLATFORM_LINUX
        }
        if (nk_button_label(ctx, "P"))
        {
          for (i32 i = 0; i < ARRAY_SIZE(Global::effectChain); ++i)
          {
            Global::sfxTone[Global::activeSfxToneIndex][i] = clipboardEffectChain[i];
            Global::sfxParameters[Global::activeSfxToneIndex][i] = clipboardEffectChainIndexParameters[i];
            Global::effectChain[i] = clipboardEffectChain[i];
            if (Global::effectChain[i].id.system != SfxSystem::empty)
            {
              i32 instance = 0;
              for (i32 j = 0; j < i; ++j)
                if (Global::effectChain[i].id.system == Global::effectChain[j].id.system && Global::effectChain[i].id.sfxIndex == Global::effectChain[j].id.sfxIndex)
                  ++instance;

              Sfx::loadParameters(Global::effectChain[i].id, instance, clipboardEffectChainIndexParameters[i]);
            }
          }
          Tones::fini(); // save tones.ini file. Saving on quit is not enough when the game crashes.
        }
      }
    }

    {
      nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
      nk_layout_row_template_push_static(ctx, 40.0f * Settings::uiScale);
      nk_layout_row_template_push_dynamic(ctx);
      nk_layout_row_template_end(ctx);

      nk_label(ctx, "Name:", NK_TEXT_LEFT);
      std::string& currentToneName = Global::sfxToneNames[Global::activeSfxToneIndex];

      static char toneName[256];
      strcpy(toneName, currentToneName.c_str());
      i32 toneNameLength = i32(currentToneName.size());
      nk_edit_string(ctx, NK_EDIT_SIMPLE, toneName, &toneNameLength, sizeof(toneName), nk_filter_default);
      toneName[toneNameLength] = '\0';

      currentToneName = toneName;
    }

    nk_layout_row_template_begin(ctx, 421.0f * Settings::uiScale);
    nk_layout_row_template_push_dynamic(ctx);
    nk_layout_row_template_end(ctx);
    if (nk_group_begin(ctx, "Group", NK_WINDOW_BORDER | NK_WINDOW_NO_SCROLLBAR))
    {
      nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
      nk_layout_row_template_push_dynamic(ctx);
      nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
      nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
      nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
      nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
      nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
      nk_layout_row_template_end(ctx);
      for (int i = 0; i < ARRAY_SIZE(Global::effectChain); ++i)
      {
        {
          nk_style_item normal_button_color = ctx->style.button.normal;
          if (Global::effectChainWindowOpened[i])
            ctx->style.button.normal = ctx->style.button.active;

          if (nk_button_label(ctx, Global::effectChain[i].id.system == SfxSystem::empty ? "..." : Sfx::names[Global::effectChain[i].id.system][Global::effectChain[i].id.sfxIndex].c_str()))
          {
            if (Global::effectChain[i].id.system != SfxSystem::empty)
            {
#ifdef SHR3D_SFX_PLUGIN
              i32 instance = 0;
              for (i32 j = 0; j < i; ++j)
                if (Global::effectChain[i].id.system == Global::effectChain[j].id.system && Global::effectChain[i].id.sfxIndex == Global::effectChain[j].id.sfxIndex)
                  ++instance;
              if (Global::effectChainWindowOpened[i] && Sfx::sfxSystemIsPlugin(Global::effectChain[i].id.system))
              {
                Sfx::closeSfxPluginWindow(Global::effectChain[i].id, instance);
                Window_::destoryPluginHostWindow(Global::effectChainWindowPluginParentWindow[i]);
                Global::effectChainWindowPluginParentWindow[i] = nullptr;
              }
              Global::effectChainWindowPluginHideUi[i] = false;
#endif // SHR3D_SFX_PLUGIN

              Global::effectChainWindowOpened[i] = !Global::effectChainWindowOpened[i];
            }
            else
            {
              selectedEmptyEffectChainSlot = i;
            }
          }
          ctx->style.button.normal = normal_button_color;
        }
        static SfxChainEffect clipboardEffectChainIndex;
        static std::string clipboardEffectChainIndexParameters;
        if (Global::effectChain[i].id.system != SfxSystem::empty)
        {
          if (nk_button_label(ctx, "C"))
          {
            i32 instance = 0;
            for (i32 j = 0; j < i; ++j)
              if (Global::effectChain[i].id.system == Global::effectChain[j].id.system && Global::effectChain[i].id.sfxIndex == Global::effectChain[j].id.sfxIndex)
                ++instance;

            clipboardEffectChainIndex.id = Global::effectChain[i].id;
            clipboardEffectChainIndex.state = Global::effectChain[i].state;
            clipboardEffectChainIndexParameters = Sfx::saveParameters(Global::effectChain[i].id, instance);
          }
          if (nk_button_label(ctx, "X"))
          {
            Global::effectChain[i].id.system = SfxSystem::empty;
            Global::sfxTone[Global::activeSfxToneIndex][i].id.system = SfxSystem::empty;
            Global::sfxTone[Global::activeSfxToneIndex][i].id.sfxIndex = 0;
            Global::sfxTone[Global::activeSfxToneIndex][i].state = SfxEffectState::active;
            Tones::fini(); // save tones.ini file. Saving on quit is not enough when the game crashes.
          }
          {
            nk_style_item normal_button_color = ctx->style.button.normal;
            if (Global::effectChain[i].state == SfxEffectState::muted)
              ctx->style.button.normal = ctx->style.button.active;
            if (nk_button_label(ctx, "M")) // mute the sfx
            {
              Global::effectChain[i].state = Global::effectChain[i].state == SfxEffectState::muted ? SfxEffectState::active : SfxEffectState::muted;
              Global::sfxTone[Global::activeSfxToneIndex][i].state = Global::effectChain[i].state;
              Tones::fini(); // save tones.ini file. Saving on quit is not enough when the game crashes.
            }
            ctx->style.button.normal = normal_button_color;
          }
          if (i >= 1)
          {
            if (nk_button_symbol(ctx, NK_SYMBOL_TRIANGLE_UP))
            {
              {
                std::swap(Global::effectChain[i], Global::effectChain[i - 1]);
                std::swap(Global::sfxTone[Global::activeSfxToneIndex][i], Global::sfxTone[Global::activeSfxToneIndex][i - 1]);
                std::swap(Global::sfxParameters[Global::activeSfxToneIndex][i], Global::sfxParameters[Global::activeSfxToneIndex][i - 1]);
              }
            }
          }
          else
          {
            nk_spacing(ctx, 1);
          }
          if (i < ARRAY_SIZE(Global::effectChain) - 1)
          {
            if (nk_button_symbol(ctx, NK_SYMBOL_TRIANGLE_DOWN))
            {
              {
                std::swap(Global::effectChain[i], Global::effectChain[i + 1]);
                std::swap(Global::sfxTone[Global::activeSfxToneIndex][i], Global::sfxTone[Global::activeSfxToneIndex][i + 1]);
                std::swap(Global::sfxParameters[Global::activeSfxToneIndex][i], Global::sfxParameters[Global::activeSfxToneIndex][i + 1]);
              }
            }
          }
          else
          {
            nk_spacing(ctx, 1);
          }
        }
        else
        {
          if (clipboardEffectChainIndex.id.system != SfxSystem::empty)
          {
            if (nk_button_label(ctx, "P"))
            {
              Global::sfxTone[Global::activeSfxToneIndex][i] = clipboardEffectChainIndex;
              Global::sfxParameters[Global::activeSfxToneIndex][i] = clipboardEffectChainIndexParameters;
              Global::effectChain[i] = clipboardEffectChainIndex;

              i32 instance = 0;
              for (i32 j = 0; j < i; ++j)
                if (clipboardEffectChainIndex.id.system == Global::effectChain[j].id.system && clipboardEffectChainIndex.id.sfxIndex == Global::effectChain[j].id.sfxIndex)
                  ++instance;
              Sfx::loadParameters(Global::effectChain[i].id, instance, clipboardEffectChainIndexParameters);
              Tones::fini(); // save tones.ini file. Saving on quit is not enough when the game crashes.
            }
            nk_spacing(ctx, 4);
          }
          else
          {
            nk_spacing(ctx, 5);
          }
        }
      }
      nk_group_end(ctx);
    }

    if (Global::activeSfxToneIndex < 0 && Global::bankIndex2SongIndex.find(Global::activeSfxToneIndex / Const::sfxToneTonesPerBank) != Global::bankIndex2SongIndex.cend())
    {
      nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
      nk_layout_row_template_push_dynamic(ctx);
      nk_layout_row_template_push_static(ctx, 55.0f * Settings::uiScale);
      nk_layout_row_template_end(ctx);

      const SfxBankIndex sfxBankIndex = Global::activeSfxToneIndex / Const::sfxToneTonesPerBank;
      const SongIndex songIndex = Global::bankIndex2SongIndex.at(sfxBankIndex);
      const std::string& songFilePath = Global::songInfos.at(songIndex).filepath;
      const char* songFileName = File::filename(songFilePath.c_str());

      nk_label(ctx, songFileName, NK_TEXT_LEFT);

      const std::vector<Arrangement::Info>& arrangementInfos = Global::songInfos.at(songIndex).arrangementInfos;
      for (const Arrangement::Info& arrangementInfo : arrangementInfos)
      {
        if (Global::songStats.contains(arrangementInfo.persistentId) && Global::songStats.at(arrangementInfo.persistentId).sfxBankIndex == sfxBankIndex)
        {
          nk_label(ctx, arrangementInfo.arrangementName.c_str(), NK_TEXT_RIGHT);
          break;
        }
      }
    }
  }

  nk_end(ctx);

  //windowOpenLastFrame = Global::inputSfxChain.toggled;

  if (selectedEmptyEffectChainSlot >= 0)
  {
    const SfxChainEffect prevPluginIndex = Global::effectChain[selectedEmptyEffectChainSlot];
    if (!selectSfxWindow(Global::effectChain[selectedEmptyEffectChainSlot].id, resolutionWidth, resolutionHeight))
    {
      selectedEmptyEffectChainSlot = -1;
    }
    else if (prevPluginIndex.id.system != Global::effectChain[selectedEmptyEffectChainSlot].id.system || prevPluginIndex.id.sfxIndex != Global::effectChain[selectedEmptyEffectChainSlot].id.sfxIndex)
    {
      Global::sfxTone[Global::activeSfxToneIndex][selectedEmptyEffectChainSlot] = Global::effectChain[selectedEmptyEffectChainSlot];

      {
        i32 instanceBefore = 0;
        i32 instanceAfter = 0;
        for (i32 j = 0; j < selectedEmptyEffectChainSlot; ++j)
          if (Global::effectChain[selectedEmptyEffectChainSlot].id.system == Global::effectChain[j].id.system && Global::effectChain[selectedEmptyEffectChainSlot].id.sfxIndex == Global::effectChain[j].id.sfxIndex)
            ++instanceBefore;
        for (i32 j = selectedEmptyEffectChainSlot + 1; j < ARRAY_SIZE(Global::effectChain); ++j)
          if (Global::effectChain[selectedEmptyEffectChainSlot].id.system == Global::effectChain[j].id.system && Global::effectChain[selectedEmptyEffectChainSlot].id.sfxIndex == Global::effectChain[j].id.sfxIndex)
            ++instanceAfter;

        Sfx::loadParameters(Global::effectChain[selectedEmptyEffectChainSlot].id, instanceBefore + instanceAfter, "");
        Global::sfxParameters[Global::activeSfxToneIndex][selectedEmptyEffectChainSlot] = Sfx::saveParameters(Global::effectChain[selectedEmptyEffectChainSlot].id, instanceBefore + instanceAfter);

        //if (instanceAfter != 0)
        //  Sfx::fixOrder(abs(Global::effectChain[selectedEmptyEffectChainSlot]), selectedEmptyEffectChainSlot, instanceAfter);

        //for (i32 j = instanceBefore + instanceAfter; j > instanceBefore; --j)
        //{
        //  ASSERT(Global::effectChain[j] == Global::effectChain[j - 1]);
        //  ASSERT(Global::sfxTone[Global::activeSfxToneIndex][j] == Global::sfxTone[Global::activeSfxToneIndex][j - 1]);
        //  //std::swap(Global::sfxParameters[Global::activeSfxToneIndex][j], Global::sfxParameters[Global::activeSfxToneIndex][j - 1]);
        //  const std::string paramTemp = Sfx::saveParameters(abs(Global::effectChain[j - 1]), j - 1);
        //  Sfx::loadParameters(abs(Global::effectChain[j - 1]), j, paramTemp);
        //}
      }

      if (Global::effectChain[selectedEmptyEffectChainSlot].id.system != SfxSystem::empty)
        selectedEmptyEffectChainSlot = -1;

      Tones::fini(); // save tones.ini file. Saving on quit is not enough when the game crashes.
    }
  }
}

static void uiRowPathSelect(std::string& path)
{
  char tmpPath[512];
  i32 tmpPathLength;
  strcpy(tmpPath, path.c_str());
  tmpPathLength = i32(path.size());
  nk_edit_string(ctx, NK_EDIT_SIMPLE, tmpPath, &tmpPathLength, sizeof(tmpPath), nk_filter_default);
  path.assign(tmpPath, tmpPathLength);

  if (nk_button_label(ctx, "..."))
  {
    std::string dirpath = File::openDirectoryDialog();
    if (!dirpath.empty())
      path = dirpath;
  }
  if (path.empty())
    nk_spacer(ctx);
  else if (nk_button_label(ctx, "X"))
    path.clear();
}

static void uiRowFileSelect(std::string& path, const wchar_t* openFileDialogFilter)
{
  char tmpPath[512];
  i32 tmpPathLength;
  strcpy(tmpPath, path.c_str());
  tmpPathLength = i32(path.size());
  nk_edit_string(ctx, NK_EDIT_SIMPLE, tmpPath, &tmpPathLength, sizeof(tmpPath), nk_filter_default);
  path.assign(tmpPath, tmpPathLength);

  if (nk_button_label(ctx, "..."))
  {
    const std::vector<std::string> filepaths = File::openFileDialog(openFileDialogFilter, false);
    if (filepaths.size() >= 1)
      if (!filepaths[0].empty())
        path = filepaths[0];
  }
  if (path.empty())
    nk_spacer(ctx);
  else if (nk_button_label(ctx, "X"))
    path.clear();
}

#ifdef SHR3D_SFX_CORE
namespace SfxCoreUi
{
  static int preset = 0;
  static int type = 0;
  static f32 valueF = 50.0f;
  static i32 valueI = 50.0f;
  static bool check = false;
  static int lfoType = 0;
  static bool extraStereo = false;

  static const char* lfoTypeNames[] = {
    "Sine",
    "Tri",
    "Ramp Up",
    "Ramp Down",
    "ZigZag",
    "M. Square",
    "M. Saw",
    "L. Fractal",
    "L. Fractal XY",
    "S/H Random"
  };

#ifdef SHR3D_SFX_CORE_HEXFIN
  static void drawStrobe(struct nk_context* ctx, const f32 cents)
  {
    nk_command_buffer* painter = nk_window_get_canvas(ctx);

    const nk_color color0 = nk_rgba_f(Settings::uiColor[28].r, Settings::uiColor[28].g, Settings::uiColor[28].b, Settings::uiColor[28].a);
    const nk_color color1 = nk_rgba_f(Settings::uiColor[29].r, Settings::uiColor[29].g, Settings::uiColor[29].b, Settings::uiColor[29].a);

    const f32 windowWidth = nk_window_get_content_region(ctx).w;
    const i32 periodCount = i32(windowWidth / 320.0);

    const f32 x = painter->clip.x;
    const f32 y = painter->clip.y;

    static f32 scrollPosX = 0.0f;
    scrollPosX = fmodf(scrollPosX + cents * Const::sfxCoreTunerStrobeScrollSpeed * TimeNS_To_Seconds(Global::frameDelta), 320.0f);

    const f32 absCents = abs(cents);

    for (int i = -8; i < (periodCount + 2) * 8; ++i)
    {
      const f32 posX = scrollPosX + f32(i) * 40.0f + x;
      if (absCents < 10.0f)
      {
        nk_fill_rect(painter, nk_rect(posX, y, 20.0f, 20.0f), 0.0f, color0);
        nk_fill_rect(painter, nk_rect(posX + 20.0f, y, 20.0f, 20.0f), 0.0f, color1);
      }
      else
      {
        nk_fill_rect_multi_color(painter, nk_rect(posX - 10.0f, y, 20.0f, 20.0f), color1, color0, color0, color1);
        nk_fill_rect_multi_color(painter, nk_rect(posX + 10.0f, y, 20.0f, 20.0f), color0, color1, color1, color0);
      }
    }
    for (int i = -4; i < (periodCount + 2) * 4; ++i)
    {
      const f32 posX = scrollPosX + f32(i) * 80.0f + x;
      if (absCents < 20.0f)
      {
        nk_fill_rect(painter, nk_rect(posX, y + 20.0f, 40.0f, 20.0f), 0.0f, color0);
        nk_fill_rect(painter, nk_rect(posX + 40.0f, y + 20.0f, 40, 20.0f), 0.0f, color1);
      }
      else
      {
        nk_fill_rect_multi_color(painter, nk_rect(posX - 20.0f, y + 20.0f, 40.0f, 20.0f), color1, color0, color0, color1);
        nk_fill_rect_multi_color(painter, nk_rect(posX + 20.0f, y + 20.0f, 40.0f, 20.0f), color0, color1, color1, color0);
      }
    }
    for (int i = -2; i < (periodCount + 2) * 2; ++i)
    {
      const f32 posX = scrollPosX + f32(i) * 160.0f + x;
      if (absCents < 30.0f)
      {
        nk_fill_rect(painter, nk_rect(posX, y + 40.0f, 80.0f, 20.0f), 0.0f, color0);
        nk_fill_rect(painter, nk_rect(posX + 80.0f, y + 40.0f, 80.0f, 20.0f), 0.0f, color1);
      }
      else
      {
        nk_fill_rect_multi_color(painter, nk_rect(posX - 40.0f, y + 40.0f, 80.0f, 20.0f), color1, color0, color0, color1);
        nk_fill_rect_multi_color(painter, nk_rect(posX + 40.0f, y + 40.0f, 80.0f, 20.0f), color0, color1, color1, color0);
      }
    }
    for (int i = -1; i < periodCount + 2; ++i)
    {
      const f32 posX = scrollPosX + f32(i) * 320.0f + x;
      if (absCents < 40.0f)
      {
        nk_fill_rect(painter, nk_rect(posX, y + 60.0f, 160.0f, 20.0f), 0.0f, color0);
        nk_fill_rect(painter, nk_rect(posX + 160.0f, y + 60.0f, 160.0f, 20.0f), 0.0f, color1);
      }
      else
      {
        nk_fill_rect_multi_color(painter, nk_rect(posX - 80.0f, y + 60.0f, 160.0f, 20.0f), color1, color0, color0, color1);
        nk_fill_rect_multi_color(painter, nk_rect(posX + 80.0f, y + 60.0f, 160.0f, 20.0f), color0, color1, color1, color0);
      }
    }
  }

  static void TunerStringRow(const i8 string)
  {
#ifdef SHR3D_SFX_CORE_HEXFIN_DIVIDED
    if (IS_DIVIDED_PICKUP_ENABLED)
    {
      const f32 frequency = Global::frequency[string];

      if (frequency != 0.0f)
      {
        const f32 cf = -12.0f * log2(Global::a4ReferenceFrequency / frequency);

        const MidiNote note = tunerNote(cf);
        const i32 noteNameIndex = to_underlying_(note) % 12;
        const i32 noteOctave = (to_underlying_(note) / 12) - 1;
        nk_label(ctx, (Const::noteNames[noteNameIndex] + std::to_string(noteOctave)).c_str(), NK_TEXT_CENTERED);

        nk_label(ctx, to_string(frequency).c_str(), NK_TEXT_LEFT);
        const f32 referenceFrequency = tunerReferenceFrequency(cf, Global::a4ReferenceFrequency);
        f32 cents = tunerCents(frequency, referenceFrequency);
        nk_slider_float(ctx, -50.0f, &cents, 50.0f, 0.01f);
      }
      else
      {
        nk_spacing(ctx, 3);
      }
    }
    else
#endif // SHR3D_SFX_CORE_HEXFIN_DIVIDED
    {
      nk_spacing(ctx, 3);
    }
    const MidiNote targetNote = getStringNoteZeroTuning(Global::highwayCtx, string);
    nk_label(ctx, to_string(noteFrequency(targetNote, Global::a4ReferenceFrequency)).c_str(), NK_TEXT_LEFT);
    const i32 targetNoteNameIndex = to_underlying_(targetNote) % 12;
    const i32 targetNoteOctave = (to_underlying_(targetNote) / 12) - 1;
    nk_label(ctx, (Const::noteNames[targetNoteNameIndex] + std::to_string(targetNoteOctave)).c_str(), NK_TEXT_CENTERED);
  }

  static void Hexfin()
  {
    const f32 frequencyMono = Global::frequencyMono;
    const f32 monoCf = -12.0f * log2(Global::a4ReferenceFrequency / frequencyMono);
    const f32 monoReferenceFrequency = tunerReferenceFrequency(monoCf, Global::a4ReferenceFrequency);
    f32 monoCents = tunerCents(frequencyMono, monoReferenceFrequency);

    f32 monoCents2 = 0.0f;
    if (-50.0f <= monoCents && monoCents < 50.0f)
      monoCents2 = monoCents;

    nk_layout_row_dynamic(ctx, 90.0f, 1);
    drawStrobe(ctx, monoCents2);

    nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 105.0f * Settings::uiScale);
    nk_layout_row_template_push_dynamic(ctx);
    nk_layout_row_template_push_static(ctx, 105.0f * Settings::uiScale);
    nk_layout_row_template_end(ctx);

    nk_label(ctx, "Current", NK_TEXT_CENTERED);
    nk_label(ctx, "|", NK_TEXT_CENTERED);
    nk_property_float(ctx, "A4@", 300.0f, &Global::a4ReferenceFrequency, 500.0f, 0.01f, 0.04f);

    nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 35.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 70.0f * Settings::uiScale);
    nk_layout_row_template_push_dynamic(ctx);
    nk_layout_row_template_push_static(ctx, 70.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 35.0f * Settings::uiScale);
    nk_layout_row_template_end(ctx);

    {
      if (frequencyMono != 0.0f)
      {
        const MidiNote note = tunerNote(monoCf);
        const i32 noteNameIndex = to_underlying_(note) % 12;
        const i32 noteOctave = (to_underlying_(note) / 12) - 1;
        nk_label(ctx, (Const::noteNames[noteNameIndex] + std::to_string(noteOctave)).c_str(), NK_TEXT_CENTERED);

        nk_label(ctx, to_string(frequencyMono).c_str(), NK_TEXT_LEFT);
        nk_slider_float(ctx, -50.0f, &monoCents, 50.0f, 0.01f);
        nk_spacer(ctx);

        nk_spacing(ctx, 1);
      }
      else
        nk_spacing(ctx, 5);
    }

    {
      if (Global::highwayCtx.instrumentStringCount >= 6)
        TunerStringRow(5);
      if (Global::highwayCtx.instrumentStringCount >= 5)
        TunerStringRow(4);
      TunerStringRow(3);
      TunerStringRow(2);
      TunerStringRow(1);
      TunerStringRow(0);
    }
  }
#endif // SHR3D_SFX_CORE_HEXFIN

#ifdef SHR3D_SFX_CORE_NEURALAMPMODELER
  static void NeuralAmpModeler(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      const int oldValue = SFXCore::nam[instance].getpar(0);
      int value = oldValue;
      nk_value_int(ctx, "Input [dB]", value);
      nk_slider_int(ctx, -200, &value, 200, 1);
      if (value != oldValue)
        SFXCore::nam[instance].changepar(0, value);
    }
    nk_spacer(ctx);
    {
      const bool oldValue = bool(SFXCore::nam[instance].getpar(6));
      bool value = oldValue;
      nk_checkbox_label(ctx, "Gate Enabled", &value);
      if (value != oldValue)
        SFXCore::nam[instance].changepar(6, i32(value));
    }
    {
      const int oldValue = SFXCore::nam[instance].getpar(1);
      int value = oldValue;
      nk_value_int(ctx, "Gate [dB]", value);
      nk_slider_int(ctx, -1000, &value, 0, 1);
      if (value != oldValue)
        SFXCore::nam[instance].changepar(1, value);
    }
    nk_spacer(ctx);
    {
      const bool oldValue = bool(SFXCore::nam[instance].getpar(7));
      bool value = oldValue;
      nk_checkbox_label(ctx, "EQ Enabled", &value);
      if (value != oldValue)
        SFXCore::nam[instance].changepar(7, i32(value));
    }
    {
      const int oldValue = SFXCore::nam[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Bass", value);
      nk_slider_int(ctx, 0, &value, 100, 1);
      if (value != oldValue)
        SFXCore::nam[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::nam[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "Middle", value);
      nk_slider_int(ctx, 0, &value, 100, 1);
      if (value != oldValue)
        SFXCore::nam[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::nam[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "Treble", value);
      nk_slider_int(ctx, 0, &value, 100, 1);
      if (value != oldValue)
        SFXCore::nam[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::nam[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "Output [dB]", value);
      nk_slider_int(ctx, -400, &value, 400, 1);
      if (value != oldValue)
        SFXCore::nam[instance].changepar(5, value);
    }
    nk_spacer(ctx);
    {
      const bool oldValue = bool(SFXCore::nam[instance].getpar(8));
      bool value = oldValue;
      nk_checkbox_label(ctx, "Normalize", &value);
      if (value != oldValue)
        SFXCore::nam[instance].changepar(8, i32(value));
    }
    nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 40.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 40.0f * Settings::uiScale);
    nk_layout_row_template_push_dynamic(ctx);
    nk_layout_row_template_push_static(ctx, 32.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
    nk_layout_row_template_end(ctx);
    {
      const bool oldValue = bool(SFXCore::nam[instance].getpar(9));
      bool value = oldValue;
      nk_checkbox_label(ctx, "NAM", &value);
      if (nk_button_symbol(ctx, NK_SYMBOL_TRIANGLE_LEFT))
      {
        if (SFXCore::nam[instance].nextNamFileIndex >= 0)
          --SFXCore::nam[instance].nextNamFileIndex;
      }
      if (nk_button_symbol(ctx, NK_SYMBOL_TRIANGLE_RIGHT))
      {
        if (SFXCore::nam[instance].nextNamFileIndex + 1 < i32(Global::NeuralAmpModeler_NamFiles.size()))
          ++SFXCore::nam[instance].nextNamFileIndex;
      }
      if (nk_button_label(ctx, "RND"))
      {
        if (i32(Global::NeuralAmpModeler_NamFiles.size()) >= 1)
          SFXCore::nam[instance].nextNamFileIndex = rand() % i32(Global::NeuralAmpModeler_NamFiles.size());
      }
      if (value != oldValue)
        SFXCore::nam[instance].changepar(9, i32(value));
      std::string path = SFXCore::nam[instance].nextNamFileIndex >= 0 ? Global::NeuralAmpModeler_NamFiles[SFXCore::nam[instance].nextNamFileIndex] : "";
      uiRowFileSelect(path, L"Neural Amp Modeler (*.nam)\0*.nam\0All (*.*)\0*.*\0");
      if (SFXCore::nam[instance].nextNamFileIndex >= 0 && path != Global::NeuralAmpModeler_NamFiles[SFXCore::nam[instance].nextNamFileIndex])
      {
        if (path.empty())
        {
          SFXCore::nam[instance].nextNamFileIndex = -1;
        }
        else
        {
          for (i32 i = 0; i < i32(Global::NeuralAmpModeler_NamFiles.size()); ++i)
          {
            if (path.ends_with(Global::NeuralAmpModeler_NamFiles[i]))
            {
              SFXCore::nam[instance].nextNamFileIndex = i;
              break;
            }
          }
        }
      }
    }
    {
      const bool oldValue = bool(SFXCore::nam[instance].getpar(10));
      bool value = oldValue;
      nk_checkbox_label(ctx, "IR", &value);
      if (nk_button_symbol(ctx, NK_SYMBOL_TRIANGLE_LEFT))
      {
        if (SFXCore::nam[instance].nextIrFileIndex >= 0)
          --SFXCore::nam[instance].nextIrFileIndex;
      }
      if (nk_button_symbol(ctx, NK_SYMBOL_TRIANGLE_RIGHT))
      {
        if (SFXCore::nam[instance].nextIrFileIndex + 1 < i32(Global::NeuralAmpModeler_IrFiles.size()))
          ++SFXCore::nam[instance].nextIrFileIndex;
      }
      if (nk_button_label(ctx, "RND"))
      {
        if (i32(Global::NeuralAmpModeler_IrFiles.size()) >= 1)
          SFXCore::nam[instance].nextIrFileIndex = rand() % i32(Global::NeuralAmpModeler_IrFiles.size());
      }
      if (value != oldValue)
        SFXCore::nam[instance].changepar(10, i32(value));
      std::string path = SFXCore::nam[instance].nextIrFileIndex >= 0 ? Global::NeuralAmpModeler_IrFiles[SFXCore::nam[instance].nextIrFileIndex] : "";
      uiRowFileSelect(path, L"Impulse Response (*.wav)\0*.wav\0All (*.*)\0*.*\0");
      if (SFXCore::nam[instance].nextIrFileIndex >= 0 && path != Global::NeuralAmpModeler_IrFiles[SFXCore::nam[instance].nextIrFileIndex])
      {
        if (path.empty())
        {
          SFXCore::nam[instance].nextIrFileIndex = -1;
        }
        else
        {
          for (u64 i = 0; i < Global::NeuralAmpModeler_IrFiles.size(); ++i)
          {
            if (path.ends_with(Global::NeuralAmpModeler_IrFiles[i]))
            {
              SFXCore::nam[instance].nextIrFileIndex = i;
              break;
            }
          }
        }
      }
    }
  }
#endif // SHR3D_SFX_CORE_NEURALAMPMODELER

#ifdef SHR3D_SFX_CORE_RAKARRACK
  static void Reverb(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Cathedral 1",
        "Cathedral 2",
        "Cathedral 3",
        "Hall 1",
        "Hall 2",
        "Room 1",
        "Room 2",
        "Basement",
        "Tunnel",
        "Echoed 1",
        "Echoed 2",
        "Very Long 1",
        "Very Long 2"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::reverb[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::reverb[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::reverb[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::reverb[instance].getpar(1) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::reverb[instance].changepar(1, value + 64);
    }
    {
      const int oldValue = SFXCore::reverb[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Time", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::reverb[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::reverb[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "I.Del", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::reverb[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::reverb[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "Del.E/R", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::reverb[instance].changepar(4, value);
    }
    {
      nk_label(ctx, "Type", NK_TEXT_LEFT);
      const int oldValue = SFXCore::reverb[instance].getpar(8);
      int value = oldValue;
      static const char* typeNames[] = {
        "Freeverb",
        "Random"
      };
      value = nk_combo(ctx, typeNames, ARRAY_SIZE(typeNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::reverb[instance].changepar(8, value);
    }
    {
      const int oldValue = SFXCore::reverb[instance].getpar(9);
      int value = oldValue;
      nk_value_int(ctx, "R.Size", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::reverb[instance].changepar(9, value);
    }
    {
      const int oldValue = SFXCore::reverb[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "LPF", value);
      nk_slider_int(ctx, 20, &value, 26000, 1);
      if (value != oldValue)
        SFXCore::reverb[instance].changepar(5, value);
    }
    {
      const int oldValue = SFXCore::reverb[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "HPF", value);
      nk_slider_int(ctx, 20, &value, 20000, 1);
      if (value != oldValue)
        SFXCore::reverb[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::reverb[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "Damp", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::reverb[instance].changepar(7, value);
    }
  }

  static void Echo(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Echo 1",
        "Echo 2",
        "Echo 3",
        "Simple Echo",
        "Canyon",
        "Panning Echo 1",
        "Panning Echo 2",
        "Panning Echo 3",
        "Feedback Echo"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::echo[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::echo[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::echo[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::echo[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "Reverse", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::echo[instance].changepar(7, value);
    }
    {
      const int oldValue = SFXCore::echo[instance].getpar(1) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::echo[instance].changepar(1, value + 64);
    }
    {
      const int oldValue = SFXCore::echo[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Delay", value);
      nk_slider_int(ctx, 20, &value, 2000, 1);
      if (value != oldValue)
        SFXCore::echo[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::echo[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "LRdl.", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::echo[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::echo[instance].getpar(4) - 64;
      int value = oldValue;
      nk_value_int(ctx, "L/R.Cr", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::echo[instance].changepar(4, value + 64);
    }
    {
      const int oldValue = SFXCore::echo[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "Fb.", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::echo[instance].changepar(5, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::echo[instance].getpar(8);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Direct", &value);
      if (value != oldValue)
        SFXCore::echo[instance].changepar(8, value);
    }
    {
      const int oldValue = SFXCore::echo[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Damp", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::echo[instance].changepar(6, value);
    }
  }

  static void Chorus(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Chorus 1",
        "Chorus 2",
        "Chorus 3",
        "Celeste 1",
        "Celeste 2"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::chorus[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::chorus[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::chorus[instance].getpar(1) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(1, value + 64);
    }
    {
      const int oldValue = SFXCore::chorus[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Tempo", value);
      nk_slider_int(ctx, 1, &value, 600, 1);
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::chorus[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "Rnd", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(3, value);
    }
    {
      nk_label(ctx, "LFO Type", NK_TEXT_LEFT);
      const int oldValue = SFXCore::chorus[instance].getpar(4);
      int value = oldValue;
      value = nk_combo(ctx, lfoTypeNames, ARRAY_SIZE(lfoTypeNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(4, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::chorus[instance].getpar(11);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Subtract", &value);
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(11, value);
    }
    {
      const int oldValue = SFXCore::chorus[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "St.df", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(5, value);
    }
    {
      const int oldValue = SFXCore::chorus[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Depth", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::chorus[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "Delay", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(7, value);
    }
    {
      const int oldValue = SFXCore::chorus[instance].getpar(8);
      int value = oldValue;
      nk_value_int(ctx, "Fb", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(8, value);
    }
    {
      const int oldValue = SFXCore::chorus[instance].getpar(9) - 64;
      int value = oldValue;
      nk_value_int(ctx, "L/R.Cr", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(9, value + 64);
    }
  }

  static void Flanger(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Flange 1",
        "Flange 2",
        "Flange 3",
        "Flange 4",
        "Flange 5"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::chorus[instance].setpreset(value + 5);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::chorus[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::chorus[instance].getpar(1) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(1, value + 64);
    }
    {
      const int oldValue = SFXCore::chorus[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Tempo", value);
      nk_slider_int(ctx, 1, &value, 600, 1);
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::chorus[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "Rnd", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(3, value);
    }
    {
      nk_label(ctx, "LFO Type", NK_TEXT_LEFT);
      const int oldValue = SFXCore::chorus[instance].getpar(4);
      int value = oldValue;
      value = nk_combo(ctx, lfoTypeNames, ARRAY_SIZE(lfoTypeNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(4, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::chorus[instance].getpar(11);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Subtract", &value);
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(11, value);
    }
    {
      const int oldValue = SFXCore::chorus[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "St.df", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(5, value);
    }
    {
      const int oldValue = SFXCore::chorus[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Depth", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::chorus[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "Delay", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(7, value);
    }
    {
      const int oldValue = SFXCore::chorus[instance].getpar(8);
      int value = oldValue;
      nk_value_int(ctx, "Fb", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(8, value);
    }
    {
      const int oldValue = SFXCore::chorus[instance].getpar(9) - 64;
      int value = oldValue;
      nk_value_int(ctx, "L/R.Cr", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::chorus[instance].changepar(9, value + 64);
    }
  }

  static void Phaser(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Phaser 1",
        "Phaser 2",
        "Phaser 3",
        "Phaser 4",
        "Phaser 5",
        "Phaser 6"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::phaser[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::phaser[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::phaser[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::phaser[instance].getpar(1) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::phaser[instance].changepar(1, value + 64);
    }
    {
      const int oldValue = SFXCore::phaser[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Tempo", value);
      nk_slider_int(ctx, 1, &value, 600, 1);
      if (value != oldValue)
        SFXCore::phaser[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::phaser[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "Rnd", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::phaser[instance].changepar(3, value);
    }
    {
      nk_label(ctx, "LFO Type", NK_TEXT_LEFT);
      const int oldValue = SFXCore::phaser[instance].getpar(4);
      int value = oldValue;
      value = nk_combo(ctx, lfoTypeNames, ARRAY_SIZE(lfoTypeNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::phaser[instance].changepar(4, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::phaser[instance].getpar(10);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Subtract", &value);
      if (value != oldValue)
        SFXCore::phaser[instance].changepar(10, value);
    }
    {
      const int oldValue = SFXCore::phaser[instance].getpar(11);
      int value = oldValue;
      nk_value_int(ctx, "Phase", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::phaser[instance].changepar(11, value);
    }
    {
      const int oldValue = SFXCore::phaser[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "St.df", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::phaser[instance].changepar(5, value);
    }
    {
      const int oldValue = SFXCore::phaser[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Depth", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::phaser[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::phaser[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "Fb", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::phaser[instance].changepar(7, value);
    }
    {
      nk_spacer(ctx);
      const int oldValue = SFXCore::phaser[instance].getpar(8);
      int value = oldValue;
      nk_property_int(ctx, "Stages:", 1, &value, 12, 1, 1);
      if (value != oldValue)
        SFXCore::phaser[instance].changepar(8, value);
    }
    {
      const int oldValue = SFXCore::phaser[instance].getpar(9) - 64;
      int value = oldValue;
      nk_value_int(ctx, "L/R.Cr", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::phaser[instance].changepar(9, value + 64);
    }
  }

#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
  static void Overdrive(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Overdrive 1",
        "Overdrive 2"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::distorsion[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::distorsion[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::distorsion[instance].getpar(2) - 64;
      int value = oldValue;
      nk_value_int(ctx, "L/R.Cr", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(2, value + 64);
    }
    {
      const int oldValue = SFXCore::distorsion[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "Drive", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::distorsion[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "Level", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(4, value);
    }
    {
      nk_label(ctx, "Type", NK_TEXT_LEFT);
      static const char* typeNames[] = {
        "Atan",
        "Asym1",
        "Pow",
        "Sine",
        "Qnts",
        "Zigzg",
        "Lmt",
        "LmtU",
        "LmtL",
        "ILmt",
        "Clip",
        "Asym2",
        "Pow2",
        "Sgm",
        "Crunch",
        "Hard Crunch",
        "Dirty Octave+",
        "M.Square",
        "M.Saw",
        "Compress",
        "Overdrive",
        "Soft",
        "Super Soft",
        "Hard Compress",
        "Lmt-NoGain",
        "FET",
        "DynoFET",
        "Valve 1",
        "Valve 2",
        "Diode clipper"
      };
      const int oldValue = SFXCore::distorsion[instance].getpar(5);
      int value = oldValue;
      value = nk_combo(ctx, typeNames, ARRAY_SIZE(typeNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(5, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::distorsion[instance].getpar(6);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Neg.", &value);
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(6, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::distorsion[instance].getpar(9);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Stereo", &value);
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(9, value);
    }
    {
      const int oldValue = SFXCore::distorsion[instance].getpar(1) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(1, value + 64);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::distorsion[instance].getpar(10);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Pre Filter", &value);
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(10, value);
    }
    {
      const int oldValue = SFXCore::distorsion[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "LPF", value);
      nk_slider_int(ctx, 20, &value, 26000, 1);
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(7, value);
    }
    {
      const int oldValue = SFXCore::distorsion[instance].getpar(8);
      int value = oldValue;
      nk_value_int(ctx, "HPF", value);
      nk_slider_int(ctx, 20, &value, 20000, 1);
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(8, value);
    }
  }
  static void Distortion(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Distortion 1",
        "Distortion 2",
        "Distortion 3",
        "Guitar Amp"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::distorsion[instance].setpreset(value + 2);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::distorsion[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::distorsion[instance].getpar(2) - 64;
      int value = oldValue;
      nk_value_int(ctx, "L/R.Cr", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(2, value + 64);
    }
    {
      const int oldValue = SFXCore::distorsion[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "Drive", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::distorsion[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "Level", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(4, value);
    }
    {
      static const char* typeNames[] = {
        "Atan",
        "Asym1",
        "Pow",
        "Sine",
        "Qnts",
        "Zigzg",
        "Lmt",
        "LmtU",
        "LmtL",
        "ILmt",
        "Clip",
        "Asym2",
        "Pow2",
        "Sgm",
        "Crunch",
        "Hard Crunch",
        "Dirty Octave+",
        "M.Square",
        "M.Saw",
        "Compress",
        "Overdrive",
        "Soft",
        "Super Soft",
        "Hard Compress",
        "Lmt-NoGain",
        "FET",
        "DynoFET",
        "Valve 1",
        "Valve 2",
        "Diode clipper"
      };
      nk_label(ctx, "Type", NK_TEXT_LEFT);
      const int oldValue = SFXCore::distorsion[instance].getpar(5);
      int value = oldValue;
      value = nk_combo(ctx, typeNames, ARRAY_SIZE(typeNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(5, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::distorsion[instance].getpar(6);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Neg.", &value);
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(6, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::distorsion[instance].getpar(10);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Pre Filter", &value);
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(10, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::distorsion[instance].getpar(9);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Stereo", &value);
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(9, value);
    }
    {
      const int oldValue = SFXCore::distorsion[instance].getpar(1) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(1, value + 64);
    }
    {
      const int oldValue = SFXCore::distorsion[instance].getpar(12);
      int value = oldValue;
      nk_value_int(ctx, "Sub Octv", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(12, value);
    }
    {
      const int oldValue = SFXCore::distorsion[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "LPF", value);
      nk_slider_int(ctx, 20, &value, 26000, 1);
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(7, value);
    }
    {
      const int oldValue = SFXCore::distorsion[instance].getpar(8);
      int value = oldValue;
      nk_value_int(ctx, "HPF", value);
      nk_slider_int(ctx, 20, &value, 20000, 1);
      if (value != oldValue)
        SFXCore::distorsion[instance].changepar(8, value);
    }
  }
#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE

  static void EQ(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Plain",
        "Pop",
        "Jazz"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::eq[instance].setpreset_EQ(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Gain", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(13) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Q", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(13, value + 64);
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(12) - 64;
      int value = oldValue;
      nk_value_int(ctx, "31 Hz", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(12, value + 64);
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(17) - 64;
      int value = oldValue;
      nk_value_int(ctx, "63 Hz", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(17, value + 64);
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(22) - 64;
      int value = oldValue;
      nk_value_int(ctx, "125 Hz", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(22, value + 64);
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(27) - 64;
      int value = oldValue;
      nk_value_int(ctx, "250 Hz", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(27, value + 64);
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(32) - 64;
      int value = oldValue;
      nk_value_int(ctx, "500 Hz", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(32, value + 64);
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(37) - 64;
      int value = oldValue;
      nk_value_int(ctx, "1 Khz", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(37, value + 64);
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(42) - 64;
      int value = oldValue;
      nk_value_int(ctx, "2 Khz", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(42, value + 64);
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(47) - 64;
      int value = oldValue;
      nk_value_int(ctx, "4 Khz", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(47, value + 64);
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(52) - 64;
      int value = oldValue;
      nk_value_int(ctx, "8 Khz", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(52, value + 64);
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(57) - 64;
      int value = oldValue;
      nk_value_int(ctx, "16 Khz", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(57, value + 64);
    }
  }

  static void ParametricEQ(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Plain",
        "Pop",
        "Jazz"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::eq[instance].setpreset_ParametricEQ(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Gain", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(11);
      int value = oldValue;
      nk_value_int(ctx, "Low F.", value);
      nk_slider_int(ctx, 20, &value, 1000, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(11, value);
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(12) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Low G.", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(12, value + 64);
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(13) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Q", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(13, value + 64);
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(16);
      int value = oldValue;
      nk_value_int(ctx, "Mid F.", value);
      nk_slider_int(ctx, 800, &value, 8000, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(16, value);
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(17) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Mid G.", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(17, value + 64);
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(18) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Q", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(18, value + 64);
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(21);
      int value = oldValue;
      nk_value_int(ctx, "High F.", value);
      nk_slider_int(ctx, 6000, &value, 26000, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(21, value);
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(22) - 64;
      int value = oldValue;
      nk_value_int(ctx, "High G.", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(22, value + 64);
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(23) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Q", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(23, value + 64);
    }
  }

  static void Compressor(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "2:1",
        "4:1",
        "8:1"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::compressor[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::compressor[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "A. Time", value);
      nk_slider_int(ctx, 0, &value, 250, 1);
      if (value != oldValue)
        SFXCore::compressor[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::compressor[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "R. Time", value);
      nk_slider_int(ctx, 0, &value, 500, 1);
      if (value != oldValue)
        SFXCore::compressor[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::compressor[instance].getpar(1);
      int value = oldValue;
      nk_value_int(ctx, "Ratio", value);
      nk_slider_int(ctx, 2, &value, 42, 1);
      if (value != oldValue)
        SFXCore::compressor[instance].changepar(1, value);
    }
    {
      const int oldValue = SFXCore::compressor[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Knee", value);
      nk_slider_int(ctx, 0, &value, 100, 1);
      if (value != oldValue)
        SFXCore::compressor[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::compressor[instance].getpar(0);
      int value = oldValue;
      nk_value_int(ctx, "Thrhold", value);
      nk_slider_int(ctx, -60, &value, -3, 1);
      if (value != oldValue)
        SFXCore::compressor[instance].changepar(0, value);
    }
    {
      const int oldValue = SFXCore::compressor[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Output", value);
      nk_slider_int(ctx, -40, &value, 0, 1);
      if (value != oldValue)
        SFXCore::compressor[instance].changepar(2, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::compressor[instance].getpar(8);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Peak", &value);
      if (value != oldValue)
        SFXCore::compressor[instance].changepar(8, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::compressor[instance].getpar(5);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Auto Output", &value);
      if (value != oldValue)
        SFXCore::compressor[instance].changepar(5, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::compressor[instance].getpar(7);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Stereo", &value);
      if (value != oldValue)
        SFXCore::compressor[instance].changepar(7, value);
    }
  }

  static void WahWah(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "WahWah",
        "AutoWah",
        "Sweep",
        "VocalMorph1",
        "VocalMorph2"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::wahWah[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::wahWah[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::wahWah[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::wahWah[instance].getpar(1) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::wahWah[instance].changepar(1, value + 64);
    }
    {
      const int oldValue = SFXCore::wahWah[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Tempo", value);
      nk_slider_int(ctx, 1, &value, 600, 1);
      if (value != oldValue)
        SFXCore::wahWah[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::wahWah[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "Rnd", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::wahWah[instance].changepar(3, value);
    }
    {
      nk_label(ctx, "LFO Type", NK_TEXT_LEFT);
      const int oldValue = SFXCore::wahWah[instance].getpar(4);
      int value = oldValue;
      value = nk_combo(ctx, lfoTypeNames, ARRAY_SIZE(lfoTypeNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::wahWah[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::wahWah[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "St.df", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::wahWah[instance].changepar(5, value);
    }
    {
      const int oldValue = SFXCore::wahWah[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Depth", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::wahWah[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::wahWah[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "Amp.S.", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::wahWah[instance].changepar(7, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::wahWah[instance].getpar(8);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Amp.S Inv", &value);
      if (value != oldValue)
        SFXCore::wahWah[instance].changepar(8, value);
    }
    {
      const int oldValue = SFXCore::wahWah[instance].getpar(9);
      int value = oldValue;
      nk_value_int(ctx, "Smooth", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::wahWah[instance].changepar(9, value);
    }
  }

  static void AlienWah(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "AlienWah 1",
        "AlienWah 2",
        "AlienWah 3",
        "AlienWah 4"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::alienWah[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::alienWah[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::alienWah[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::alienWah[instance].getpar(1) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::alienWah[instance].changepar(1, value + 64);
    }
    {
      const int oldValue = SFXCore::alienWah[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Tempo", value);
      nk_slider_int(ctx, 1, &value, 600, 1);
      if (value != oldValue)
        SFXCore::alienWah[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::alienWah[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "Rnd", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::alienWah[instance].changepar(3, value);
    }
    {
      nk_label(ctx, "LFO Type", NK_TEXT_LEFT);
      const int oldValue = SFXCore::alienWah[instance].getpar(4);
      int value = oldValue;
      value = nk_combo(ctx, lfoTypeNames, ARRAY_SIZE(lfoTypeNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::alienWah[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::alienWah[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "St.df", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::alienWah[instance].changepar(5, value);
    }
    {
      const int oldValue = SFXCore::alienWah[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Depth", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::alienWah[instance].changepar(6, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::alienWah[instance].getpar(8);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Delay", &value);
      if (value != oldValue)
        SFXCore::alienWah[instance].changepar(8, value);
    }
    {
      const int oldValue = SFXCore::alienWah[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "Fb", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::alienWah[instance].changepar(7, value);
    }
    {
      const int oldValue = SFXCore::alienWah[instance].getpar(9);
      int value = oldValue;
      nk_value_int(ctx, "L/R.Cr", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::alienWah[instance].changepar(9, value);
    }
  }

  static void Cabinet(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Marshall-4-12",
        "Celestion G12M",
        "Jensen Alnico P12N",
        "Jensen Alnico P15N",
        "Delta Demon",
        "Celestion-EVH12",
        "Eminence Copperhead",
        "Mesa Boogie",
        "Jazz-Chorus",
        "Vox-Bright",
        "Marshall-I"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::eq[instance].setpreset_Cabinet(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::eq[instance].getpar(0);
      int value = oldValue;
      nk_value_int(ctx, "Gain", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::eq[instance].changepar(0, value);
    }
  }

  static void Pan(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "AutoPan",
        "Extra Stereo"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::pan[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::pan[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::pan[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::pan[instance].getpar(1) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::pan[instance].changepar(1, value + 64);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::pan[instance].getpar(7);
      bool value = oldValue;
      nk_checkbox_label(ctx, "AutoPan", &value);
      if (value != oldValue)
        SFXCore::pan[instance].changepar(7, value);
    }
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      const int oldValue = SFXCore::pan[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Tempo", value);
      nk_slider_int(ctx, 0, &value, 600, 1);
      if (value != oldValue)
        SFXCore::pan[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::pan[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "Rnd", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::pan[instance].changepar(3, value);
    }
    {
      nk_label(ctx, "LFO Type", NK_TEXT_LEFT);
      const int oldValue = SFXCore::pan[instance].getpar(4);
      int value = oldValue;
      value = nk_combo(ctx, lfoTypeNames, ARRAY_SIZE(lfoTypeNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::pan[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::pan[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "St.df", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::pan[instance].changepar(5, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::pan[instance].getpar(8);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Extra Stereo", &value);
      if (value != oldValue)
        SFXCore::pan[instance].changepar(8, value);
    }
    {
      const int oldValue = SFXCore::pan[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "E.S.", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::pan[instance].changepar(6, value);
    }
  }

#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
#if 0
  static void Harmonizer(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Plain",
        "Octavator",
        "3m Down"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::harmonizer[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::harmonizer[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::harmonizer[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::harmonizer[instance].getpar(3) - 12;
      int value = oldValue;
      nk_value_int(ctx, "Interval", value);
      nk_slider_int(ctx, -12, &value, 12, 1);
      if (value != oldValue)
        SFXCore::harmonizer[instance].changepar(3, value + 12);
    }
    {
      const int oldValue = SFXCore::harmonizer[instance].getpar(2) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Gain", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::harmonizer[instance].changepar(2, value + 64);
    }
    {
      const int oldValue = SFXCore::harmonizer[instance].getpar(1) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::harmonizer[instance].changepar(1, value + 64);
    }
    {
      const int oldValue = SFXCore::harmonizer[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "Freq", value);
      nk_slider_int(ctx, 20, &value, 26000, 1);
      if (value != oldValue)
        SFXCore::harmonizer[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::harmonizer[instance].getpar(8) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Gain", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::harmonizer[instance].changepar(8, value + 64);
    }
    {
      const int oldValue = SFXCore::harmonizer[instance].getpar(9) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Q", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::harmonizer[instance].changepar(9, value + 64);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::harmonizer[instance].getpar(10);
      bool value = oldValue;
      nk_checkbox_label(ctx, "MIDI", &value);
      if (value != oldValue)
        SFXCore::harmonizer[instance].changepar(10, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::harmonizer[instance].getpar(5);
      bool value = oldValue;
      nk_checkbox_label(ctx, "SEL", &value);
      if (value != oldValue)
        SFXCore::harmonizer[instance].changepar(5, value);
    }
    {
      const int oldValue = SFXCore::harmonizer[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Note", value);
      nk_slider_int(ctx, 0, &value, 23, 1);
      if (value != oldValue)
        SFXCore::harmonizer[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::harmonizer[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "Chord", value);
      nk_slider_int(ctx, 0, &value, 33, 1);
      if (value != oldValue)
        SFXCore::harmonizer[instance].changepar(7, value);
    }
    {
      nk_spacer(ctx);
      static const char* note[] = {
        "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb",
        "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
      };
      static const char* chord[] = {
        "", "6", "Maj7", "lyd", "Maj(9)", "Maj7(9)", "6/9", "+", "m",
        "m6", "m7", "m7(b5)", "m9", "m7(9)", "m7(11)", "m(Maj7)",
        "m(Maj7)(9)", "dim", "dim7", "7", "7(Sus4)", "7(b5)", "7(9)",
        "7(#11)", "7(13)", "7(b9)", "7(b13)", "7(#9)", "+Maj7", "+7",
        "1+8", "1+5", "(Sus4)", "2"
      };
      char displayNote[13];
      sprintf(displayNote, "%s%s", note[SFXCore::harmonizer[instance].getpar(6)], chord[SFXCore::harmonizer[instance].getpar(7)]);
      nk_label(ctx, displayNote, NK_TEXT_LEFT);
    }
  }
#endif
#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE

  static void MusicalDelay(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Echo 1",
        "Echo 2"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::musicalDelay[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::musicalDelay[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::musicalDelay[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::musicalDelay[instance].getpar(4) - 64;
      int value = oldValue;
      nk_value_int(ctx, "L/R.Cr", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::musicalDelay[instance].changepar(4, value + 64);
    }
    {
      const int oldValue = SFXCore::musicalDelay[instance].getpar(1) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan1", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::musicalDelay[instance].changepar(1, value + 64);
    }
    {
      const int oldValue = SFXCore::musicalDelay[instance].getpar(7) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan2", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::musicalDelay[instance].changepar(7, value + 64);
    }
    static const char* delayNames[] = {
      "1",
      "1/2",
      "1/3",
      "1/4",
      "1/5",
      "1/6",
      "0"
    };
    {
      nk_spacer(ctx);
      const int oldValue = SFXCore::musicalDelay[instance].getpar(2) - 1;
      int value = oldValue;
      value = nk_combo(ctx, delayNames, ARRAY_SIZE(delayNames) - 1, value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::musicalDelay[instance].changepar(2, value + 1);
    }
    {

      nk_spacer(ctx);
      const int oldValue = SFXCore::musicalDelay[instance].getpar(3) - 1;
      int value = oldValue;
      value = nk_combo(ctx, delayNames, ARRAY_SIZE(delayNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::musicalDelay[instance].changepar(3, value + 1);
    }
    {
      nk_spacer(ctx);
      const int oldValue = SFXCore::musicalDelay[instance].getpar(8) - 1;
      int value = oldValue;
      value = nk_combo(ctx, delayNames, ARRAY_SIZE(delayNames) - 1, value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::musicalDelay[instance].changepar(8, value + 1);
    }
    {
      const int oldValue = SFXCore::musicalDelay[instance].getpar(10);
      int value = oldValue;
      nk_value_int(ctx, "Tempo", value);
      nk_slider_int(ctx, 10, &value, 480, 1);
      if (value != oldValue)
        SFXCore::musicalDelay[instance].changepar(10, value);
    }
    {
      const int oldValue = SFXCore::musicalDelay[instance].getpar(11) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Gain1", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::musicalDelay[instance].changepar(11, value + 64);
    }
    {
      const int oldValue = SFXCore::musicalDelay[instance].getpar(12) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Gain2", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::musicalDelay[instance].changepar(12, value + 64);
    }
    {
      const int oldValue = SFXCore::musicalDelay[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "Fb1.", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::musicalDelay[instance].changepar(5, value);
    }
    {
      const int oldValue = SFXCore::musicalDelay[instance].getpar(9);
      int value = oldValue;
      nk_value_int(ctx, "Fb2.", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::musicalDelay[instance].changepar(9, value);
    }
    {
      const int oldValue = SFXCore::musicalDelay[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Damp", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::musicalDelay[instance].changepar(6, value);
    }
  }

  static void NoiseGate(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "0dB",
        "-10dB",
        "-20dB"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::noiseGate[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::noiseGate[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "A. Time", value);
      nk_slider_int(ctx, 1, &value, 250, 1);
      if (value != oldValue)
        SFXCore::noiseGate[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::noiseGate[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "R. Time", value);
      nk_slider_int(ctx, 1, &value, 250, 1);
      if (value != oldValue)
        SFXCore::noiseGate[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::noiseGate[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Range", value);
      nk_slider_int(ctx, -90, &value, 0, 1);
      if (value != oldValue)
        SFXCore::noiseGate[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::noiseGate[instance].getpar(1);
      int value = oldValue;
      nk_value_int(ctx, "Thrhold", value);
      nk_slider_int(ctx, -70, &value, 20, 1);
      if (value != oldValue)
        SFXCore::noiseGate[instance].changepar(1, value);
    }
    {
      const int oldValue = SFXCore::noiseGate[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "Hold", value);
      nk_slider_int(ctx, 2, &value, 500, 1);
      if (value != oldValue)
        SFXCore::noiseGate[instance].changepar(7, value);
    }
    {
      const int oldValue = SFXCore::noiseGate[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "LPF", value);
      nk_slider_int(ctx, 20, &value, 26000, 1);
      if (value != oldValue)
        SFXCore::noiseGate[instance].changepar(5, value);
    }
    {
      const int oldValue = SFXCore::noiseGate[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "HPF", value);
      nk_slider_int(ctx, 20, &value, 20000, 1);
      if (value != oldValue)
        SFXCore::noiseGate[instance].changepar(6, value);
    }
  }

  static void AnalogPhaser(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Phaser 1",
        "Phaser 2",
        "Phaser 3",
        "Phaser 4",
        "Phaser 5",
        "Phaser 6"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::analogPhaser[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::analogPhaser[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::analogPhaser[instance].changepar(0, value + 64);
    }
    {
      nk_label(ctx, "LFO Type", NK_TEXT_LEFT);
      const int oldValue = SFXCore::analogPhaser[instance].getpar(4);
      int value = oldValue;
      value = nk_combo(ctx, lfoTypeNames, ARRAY_SIZE(lfoTypeNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::analogPhaser[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::analogPhaser[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Tempo", value);
      nk_slider_int(ctx, 1, &value, 600, 1);
      if (value != oldValue)
        SFXCore::analogPhaser[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::analogPhaser[instance].getpar(11);
      int value = oldValue;
      nk_value_int(ctx, "Depth", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::analogPhaser[instance].changepar(11, value);
    }
    {
      const int oldValue = SFXCore::analogPhaser[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Width", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::analogPhaser[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::analogPhaser[instance].getpar(7) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Fb", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::analogPhaser[instance].changepar(7, value + 64);
    }
    {
      const int oldValue = SFXCore::analogPhaser[instance].getpar(9);
      int value = oldValue;
      nk_value_int(ctx, "Mismatch", value);
      nk_slider_int(ctx, 0, &value, 100, 1);
      if (value != oldValue)
        SFXCore::analogPhaser[instance].changepar(9, value);
    }
    {
      const int oldValue = SFXCore::analogPhaser[instance].getpar(1);
      int value = oldValue;
      nk_value_int(ctx, "Distort", value);
      nk_slider_int(ctx, 0, &value, 100, 1);
      if (value != oldValue)
        SFXCore::analogPhaser[instance].changepar(1, value);
    }
    {
      const int oldValue = SFXCore::analogPhaser[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "St.df", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::analogPhaser[instance].changepar(5, value);
    }
    {
      nk_spacer(ctx);
      const int oldValue = SFXCore::analogPhaser[instance].getpar(8);
      int value = oldValue;
      nk_property_int(ctx, "Stages:", 1, &value, 12, 1, 1);
      if (value != oldValue)
        SFXCore::analogPhaser[instance].changepar(8, value);
    }
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::analogPhaser[instance].getpar(10);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Subtract", &value);
      if (value != oldValue)
        SFXCore::analogPhaser[instance].changepar(10, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::analogPhaser[instance].getpar(12);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Hyper", &value);
      if (value != oldValue)
        SFXCore::analogPhaser[instance].changepar(12, value);
    }
  }

  static void Valve(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Valve 1",
        "Valve 2",
        "Valve 3"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::valve[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::valve[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::valve[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::valve[instance].getpar(2) - 64;
      int value = oldValue;
      nk_value_int(ctx, "L/R.Cr", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::valve[instance].changepar(2, value + 64);
    }
    {
      const int oldValue = SFXCore::valve[instance].getpar(1) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::valve[instance].changepar(1, value + 64);
    }
    {
      const int oldValue = SFXCore::valve[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "Level", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::valve[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::valve[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "Drive", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::valve[instance].changepar(3, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::valve[instance].getpar(11);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Extra Dist.", &value);
      if (value != oldValue)
        SFXCore::valve[instance].changepar(11, value);
    }
    {
      const int oldValue = SFXCore::valve[instance].getpar(10);
      int value = oldValue;
      nk_value_int(ctx, "Dist.", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::valve[instance].changepar(10, value);
    }
    {
      const int oldValue = SFXCore::valve[instance].getpar(12);
      int value = oldValue;
      nk_value_int(ctx, "Presence", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::valve[instance].changepar(12, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::valve[instance].getpar(9);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Pre Filter", &value);
      if (value != oldValue)
        SFXCore::valve[instance].changepar(9, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::valve[instance].getpar(8);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Stereo", &value);
      if (value != oldValue)
        SFXCore::valve[instance].changepar(8, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::valve[instance].getpar(5);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Neg.", &value);
      if (value != oldValue)
        SFXCore::valve[instance].changepar(5, value);
    }
    {
      const int oldValue = SFXCore::valve[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "LPF", value);
      nk_slider_int(ctx, 20, &value, 26000, 1);
      if (value != oldValue)
        SFXCore::valve[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::valve[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "HPF", value);
      nk_slider_int(ctx, 20, &value, 20000, 1);
      if (value != oldValue)
        SFXCore::valve[instance].changepar(7, value);
    }
  }

  static void DualFlange(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Dual Flange 1",
        "Flange-Wah",
        "FbFlange",
        "SoftFlange",
        "Flanger",
        "Deep Chorus",
        "Bright Chorus"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::dualFlange[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::dualFlange[instance].getpar(0);
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::dualFlange[instance].changepar(0, value);
    }
    {
      const int oldValue = SFXCore::dualFlange[instance].getpar(1);
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::dualFlange[instance].changepar(1, value);
    }
    {
      const int oldValue = SFXCore::dualFlange[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "L/R.Cr", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::dualFlange[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::dualFlange[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "Depth", value);
      nk_slider_int(ctx, 20, &value, 500, 1);
      if (value != oldValue)
        SFXCore::dualFlange[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::dualFlange[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "Width", value);
      nk_slider_int(ctx, 0, &value, 3000, 1);
      if (value != oldValue)
        SFXCore::dualFlange[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::dualFlange[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "Offset", value);
      nk_slider_int(ctx, 0, &value, 100, 1);
      if (value != oldValue)
        SFXCore::dualFlange[instance].changepar(5, value);
    }
    {
      const int oldValue = SFXCore::dualFlange[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Fb", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::dualFlange[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::dualFlange[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "LPF", value);
      nk_slider_int(ctx, 20, &value, 20000, 1);
      if (value != oldValue)
        SFXCore::dualFlange[instance].changepar(7, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::dualFlange[instance].getpar(8);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Subtract", &value);
      if (value != oldValue)
        SFXCore::dualFlange[instance].changepar(8, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::dualFlange[instance].getpar(9);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Th. zero", &value);
      if (value != oldValue)
        SFXCore::dualFlange[instance].changepar(9, value);
    }
    {
      const int oldValue = SFXCore::dualFlange[instance].getpar(10);
      int value = oldValue;
      nk_value_int(ctx, "Tempo", value);
      nk_slider_int(ctx, 1, &value, 600, 1);
      if (value != oldValue)
        SFXCore::dualFlange[instance].changepar(10, value);
    }
    {
      const int oldValue = SFXCore::dualFlange[instance].getpar(11);
      int value = oldValue;
      nk_value_int(ctx, "St.df", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::dualFlange[instance].changepar(11, value);
    }
    {
      nk_label(ctx, "LFO Type", NK_TEXT_LEFT);
      const int oldValue = SFXCore::dualFlange[instance].getpar(12);
      int value = oldValue;
      value = nk_combo(ctx, lfoTypeNames, ARRAY_SIZE(lfoTypeNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::dualFlange[instance].changepar(12, value);
    }
    {
      const int oldValue = SFXCore::dualFlange[instance].getpar(13);
      int value = oldValue;
      nk_value_int(ctx, "Rnd", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::dualFlange[instance].changepar(13, value);
    }
  }

  static void Ring(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Saw_Sin",
        "E string",
        "A string",
        "Dissonance",
        "Fast Beat",
        "Ring Amp"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::ring[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::ring[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::ring[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::ring[instance].getpar(2) - 64;
      int value = oldValue;
      nk_value_int(ctx, "L/R.Cr", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::ring[instance].changepar(2, value + 64);
    }
    {
      const int oldValue = SFXCore::ring[instance].getpar(11);
      int value = oldValue;
      nk_value_int(ctx, "Input", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::ring[instance].changepar(11, value);
    }
    {
      const int oldValue = SFXCore::ring[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "Level", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::ring[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::ring[instance].getpar(1);
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::ring[instance].changepar(1, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::ring[instance].getpar(6);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Stereo", &value);
      if (value != oldValue)
        SFXCore::ring[instance].changepar(6, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::ring[instance].getpar(12);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Auto Freq", &value);
      if (value != oldValue)
        SFXCore::ring[instance].changepar(12, value);
    }
    {
      const int oldValue = SFXCore::ring[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "Depth", value);
      nk_slider_int(ctx, 0, &value, 100, 1);
      if (value != oldValue)
        SFXCore::ring[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::ring[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "Freq", value);
      nk_slider_int(ctx, 1, &value, 20000, 1);
      if (value != oldValue)
        SFXCore::ring[instance].changepar(5, value);
    }
    {
      const int oldValue = SFXCore::ring[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "Sin", value);
      nk_slider_int(ctx, 0, &value, 100, 1);
      if (value != oldValue)
        SFXCore::ring[instance].changepar(7, value);
    }
    {
      const int oldValue = SFXCore::ring[instance].getpar(8);
      int value = oldValue;
      nk_value_int(ctx, "Tri", value);
      nk_slider_int(ctx, 0, &value, 100, 1);
      if (value != oldValue)
        SFXCore::ring[instance].changepar(8, value);
    }
    {
      const int oldValue = SFXCore::ring[instance].getpar(9);
      int value = oldValue;
      nk_value_int(ctx, "Saw", value);
      nk_slider_int(ctx, 0, &value, 100, 1);
      if (value != oldValue)
        SFXCore::ring[instance].changepar(9, value);
    }
    {
      const int oldValue = SFXCore::ring[instance].getpar(10);
      int value = oldValue;
      nk_value_int(ctx, "Squ", value);
      nk_slider_int(ctx, 0, &value, 100, 1);
      if (value != oldValue)
        SFXCore::ring[instance].changepar(10, value);
    }

  }

  static void Exiter(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Plain",
        "Loudness",
        "Exciter 1",
        "Exciter 2",
        "Exciter 3"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::exciter[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::exciter[instance].getpar(0);
      int value = oldValue;
      nk_value_int(ctx, "Gain", value);
      nk_slider_int(ctx, 0, &value, 100, 1);
      if (value != oldValue)
        SFXCore::exciter[instance].changepar(0, value);
    }
    {
      const int oldValue = SFXCore::exciter[instance].getpar(11);
      int value = oldValue;
      nk_value_int(ctx, "LPF", value);
      nk_slider_int(ctx, 20, &value, 26000, 1);
      if (value != oldValue)
        SFXCore::exciter[instance].changepar(11, value);
    }
    {
      const int oldValue = SFXCore::exciter[instance].getpar(12);
      int value = oldValue;
      nk_value_int(ctx, "HPF", value);
      nk_slider_int(ctx, 20, &value, 20000, 1);
      if (value != oldValue)
        SFXCore::exciter[instance].changepar(12, value);
    }
    {
      const int oldValue = SFXCore::exciter[instance].getpar(1);
      int value = oldValue;
      nk_value_int(ctx, "Har 1", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::exciter[instance].changepar(1, value);
    }
    {
      const int oldValue = SFXCore::exciter[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Har 2", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::exciter[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::exciter[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "Har 3", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::exciter[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::exciter[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "Har 4", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::exciter[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::exciter[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "Har 5", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::exciter[instance].changepar(5, value);
    }
    {
      const int oldValue = SFXCore::exciter[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Har 6", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::exciter[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::exciter[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "Har 7", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::exciter[instance].changepar(7, value);
    }
    {
      const int oldValue = SFXCore::exciter[instance].getpar(8);
      int value = oldValue;
      nk_value_int(ctx, "Har 8", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::exciter[instance].changepar(8, value);
    }
    {
      const int oldValue = SFXCore::exciter[instance].getpar(9);
      int value = oldValue;
      nk_value_int(ctx, "Har 9", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::exciter[instance].changepar(9, value);
    }
    {
      const int oldValue = SFXCore::exciter[instance].getpar(10);
      int value = oldValue;
      nk_value_int(ctx, "Har 10", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::exciter[instance].changepar(10, value);
    }
  }

#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
  static void DistBand(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Saturation",
        "Distortion 1",
        "Soft",
        "Modulated",
        "Crunch",
        "Distortion 2",
        "Distortion 3",
        "Distortion 4"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::distBand[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::distBand[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::distBand[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::distBand[instance].getpar(2) - 64;
      int value = oldValue;
      nk_value_int(ctx, "L/R.Cr", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::distBand[instance].changepar(2, value + 64);
    }
    {
      const int oldValue = SFXCore::distBand[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "Drive", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::distBand[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::distBand[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "Level", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::distBand[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::distBand[instance].getpar(8);
      int value = oldValue;
      nk_value_int(ctx, "L.Gain", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::distBand[instance].changepar(8, value);
    }
    {
      const int oldValue = SFXCore::distBand[instance].getpar(9);
      int value = oldValue;
      nk_value_int(ctx, "M.Gain", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::distBand[instance].changepar(9, value);
    }
    {
      const int oldValue = SFXCore::distBand[instance].getpar(10);
      int value = oldValue;
      nk_value_int(ctx, "H.Gain", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::distBand[instance].changepar(10, value);
    }
    {
      const int oldValue = SFXCore::distBand[instance].getpar(12);
      int value = oldValue;
      nk_value_int(ctx, "Cross1", value);
      nk_slider_int(ctx, 20, &value, 1000, 1);
      if (value != oldValue)
        SFXCore::distBand[instance].changepar(12, value);
    }
    {
      const int oldValue = SFXCore::distBand[instance].getpar(13);
      int value = oldValue;
      nk_value_int(ctx, "Cross2", value);
      nk_slider_int(ctx, 800, &value, 12000, 1);
      if (value != oldValue)
        SFXCore::distBand[instance].changepar(13, value);
    }
    {
      static const char* typeName[] = {
        "Atan",
        "Asym1",
        "Pow",
        "Sine",
        "Qnts",
        "Zigzg",
        "Lmt",
        "LmtU",
        "LmtL",
        "ILmt",
        "Clip",
        "Asym2",
        "Pow2",
        "Sgm",
        "Crunch",
        "Hard Crunch",
        "Dirty Octave+",
        "M.Square",
        "M.Saw",
        "Compress",
        "Overdrive",
        "Soft",
        "Super Soft",
        "Hard Compress",
        "Lmt-NoGain",
        "FET",
        "DynoFET",
        "Valve 1",
        "Valve 2",
        "Diode clipper",
      };
      {
        nk_label(ctx, "L.Type", NK_TEXT_LEFT);
        const int oldValue = SFXCore::distBand[instance].getpar(5);
        int value = oldValue;
        value = nk_combo(ctx, typeName, ARRAY_SIZE(typeName), value, 25, nk_vec2(200, 200));
        if (value != oldValue)
          SFXCore::distBand[instance].changepar(5, value);
      }
      {
        nk_label(ctx, "M.Type", NK_TEXT_LEFT);
        const int oldValue = SFXCore::distBand[instance].getpar(6);
        int value = oldValue;
        value = nk_combo(ctx, typeName, ARRAY_SIZE(typeName), value, 25, nk_vec2(200, 200));
        if (value != oldValue)
          SFXCore::distBand[instance].changepar(6, value);
      }
      {
        nk_label(ctx, "H.Type", NK_TEXT_LEFT);
        const int oldValue = SFXCore::distBand[instance].getpar(7);
        int value = oldValue;
        value = nk_combo(ctx, typeName, ARRAY_SIZE(typeName), value, 25, nk_vec2(200, 200));
        if (value != oldValue)
          SFXCore::distBand[instance].changepar(7, value);
      }
    }
    {
      const int oldValue = SFXCore::distBand[instance].getpar(1) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::distBand[instance].changepar(1, value + 64);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::distBand[instance].getpar(14);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Stereo", &value);
      if (value != oldValue)
        SFXCore::distBand[instance].changepar(14, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::distBand[instance].getpar(11);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Neg.", &value);
      if (value != oldValue)
        SFXCore::distBand[instance].changepar(11, value);
    }
  }
#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE

  static void Arpie(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Arpie 1",
        "Arpie 2",
        "Arpie 3",
        "Simple Arpie",
        "Canyon",
        "Panning Arpie 1",
        "Panning Arpie 2",
        "Panning Arpie 3",
        "Feedback Arpie"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::arpie[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::arpie[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::arpie[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::arpie[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "Arpe's", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::arpie[instance].changepar(7, value);
    }
    {
      const int oldValue = SFXCore::arpie[instance].getpar(1) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::arpie[instance].changepar(1, value + 64);
    }
    {
      const int oldValue = SFXCore::arpie[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Tempo", value);
      nk_slider_int(ctx, 1, &value, 600, 1);
      if (value != oldValue)
        SFXCore::arpie[instance].changepar(2, value);
    }
    {
      nk_label(ctx, "SubDivision", NK_TEXT_LEFT);
      const int oldValue = SFXCore::arpie[instance].getpar(10);
      int value = oldValue;
      static const char* subdivName[] = {
        "1",
        "1/2",
        "1/3",
        "1/4",
        "1/5",
        "1/6"
      };
      value = nk_combo(ctx, subdivName, ARRAY_SIZE(subdivName), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::arpie[instance].changepar(10, value);
    }
    {
      const int oldValue = SFXCore::arpie[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "LRdl.", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::arpie[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::arpie[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "L/R.Cr", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::arpie[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::arpie[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "Fb.", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::arpie[instance].changepar(5, value);
    }
    {
      nk_spacer(ctx);
      const int oldValue = SFXCore::arpie[instance].getpar(8);
      int value = oldValue;
      nk_property_int(ctx, "H:", 1, &value, 8, 1, 1);
      if (value != oldValue)
        SFXCore::arpie[instance].changepar(8, value);
    }
    {
      nk_label(ctx, "Pattern", NK_TEXT_LEFT);
      const int oldValue = SFXCore::arpie[instance].getpar(9);
      int value = oldValue;
      static const char* patternName[] = {
        "Ascending",
        "Descending",
        "UpDown",
        "Stutter",
        "Interrupted Descent",
        "Double Descend "
      };
      value = nk_combo(ctx, patternName, ARRAY_SIZE(patternName), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::arpie[instance].changepar(9, value);
    }
    {
      const int oldValue = SFXCore::arpie[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Damp", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::arpie[instance].changepar(6, value);
    }
  }

  static void Expander(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Noise Gate",
        "Boost Gate",
        "Treble swell"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::expander[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::expander[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "A.Time", value);
      nk_slider_int(ctx, 1, &value, 2000, 1);
      if (value != oldValue)
        SFXCore::expander[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::expander[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "R.Time", value);
      nk_slider_int(ctx, 10, &value, 500, 1);
      if (value != oldValue)
        SFXCore::expander[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::expander[instance].getpar(1);
      int value = oldValue;
      nk_value_int(ctx, "Shape", value);
      nk_slider_int(ctx, 1, &value, 50, 1);
      if (value != oldValue)
        SFXCore::expander[instance].changepar(1, value);
    }
    {
      const int oldValue = SFXCore::expander[instance].getpar(0);
      int value = oldValue;
      nk_value_int(ctx, "Thrhold", value);
      nk_slider_int(ctx, -80, &value, 0, 1);
      if (value != oldValue)
        SFXCore::expander[instance].changepar(0, value);
    }
    {
      const int oldValue = SFXCore::expander[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Level", value);
      nk_slider_int(ctx, 1, &value, 127, 1);
      if (value != oldValue)
        SFXCore::expander[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::expander[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "LPF", value);
      nk_slider_int(ctx, 20, &value, 26000, 1);
      if (value != oldValue)
        SFXCore::expander[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::expander[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "HPF", value);
      nk_slider_int(ctx, 20, &value, 20000, 1);
      if (value != oldValue)
        SFXCore::expander[instance].changepar(5, value);
    }
  }

  static void Shuffle(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Shuffle 1",
        "Shuffle 2",
        "Shuffle 3",
        "Remover"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::shuffle[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::shuffle[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::shuffle[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::shuffle[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "Low Freq", value);
      nk_slider_int(ctx, 20, &value, 1000, 1);
      if (value != oldValue)
        SFXCore::shuffle[instance].changepar(5, value);
    }
    {
      const int oldValue = SFXCore::shuffle[instance].getpar(1);
      int value = oldValue;
      nk_value_int(ctx, "Low Gain", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::shuffle[instance].changepar(1, value);
    }
    {
      const int oldValue = SFXCore::shuffle[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "M.L. Freq", value);
      nk_slider_int(ctx, 400, &value, 4000, 1);
      if (value != oldValue)
        SFXCore::shuffle[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::shuffle[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "M.L Gain", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::shuffle[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::shuffle[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "M.H. Freq", value);
      nk_slider_int(ctx, 1200, &value, 8000, 1);
      if (value != oldValue)
        SFXCore::shuffle[instance].changepar(7, value);
    }
    {
      const int oldValue = SFXCore::shuffle[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "M.L Gain", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::shuffle[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::shuffle[instance].getpar(8);
      int value = oldValue;
      nk_value_int(ctx, "High Freq", value);
      nk_slider_int(ctx, 6000, &value, 26000, 1);
      if (value != oldValue)
        SFXCore::shuffle[instance].changepar(8, value);
    }
    {
      const int oldValue = SFXCore::shuffle[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "High Gain", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::shuffle[instance].changepar(4, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::shuffle[instance].getpar(10);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Rev", &value);
      if (value != oldValue)
        SFXCore::shuffle[instance].changepar(10, value);
    }
    {
      const int oldValue = SFXCore::shuffle[instance].getpar(9);
      int value = oldValue;
      nk_value_int(ctx, "Q", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::shuffle[instance].changepar(9, value);
    }
  }

  static void Synthfilter(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Low Pass",
        "High Pass",
        "Band Pass",
        "Lead Synth",
        "Water",
        "Pan Filter",
        "Multi"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::synthFilter[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::synthFilter[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::synthFilter[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::synthFilter[instance].getpar(1);
      int value = oldValue;
      nk_value_int(ctx, "Distrot", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::synthFilter[instance].changepar(1, value);
    }
    {
      const int oldValue = SFXCore::synthFilter[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Tempo", value);
      nk_slider_int(ctx, 1, &value, 600, 1);
      if (value != oldValue)
        SFXCore::synthFilter[instance].changepar(2, value);
    }
    {
      nk_label(ctx, "LFO Type", NK_TEXT_LEFT);
      const int oldValue = SFXCore::synthFilter[instance].getpar(4);
      int value = oldValue;
      value = nk_combo(ctx, lfoTypeNames, ARRAY_SIZE(lfoTypeNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::synthFilter[instance].changepar(4, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::synthFilter[instance].getpar(10);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Subtract", &value);
      if (value != oldValue)
        SFXCore::synthFilter[instance].changepar(10, value);
    }
    {
      const int oldValue = SFXCore::synthFilter[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "St.df", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::synthFilter[instance].changepar(5, value);
    }
    {
      const int oldValue = SFXCore::synthFilter[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Width", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::synthFilter[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::synthFilter[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "Fb", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::synthFilter[instance].changepar(7, value);
    }
    {
      nk_spacer(ctx);
      const int oldValue = SFXCore::synthFilter[instance].getpar(8);
      int value = oldValue;
      nk_property_int(ctx, "LPF Stg.", 0, &value, 12, 1, 1);
      if (value != oldValue)
        SFXCore::synthFilter[instance].changepar(8, value);
    }
    {
      nk_spacer(ctx);
      const int oldValue = SFXCore::synthFilter[instance].getpar(9);
      int value = oldValue;
      nk_property_int(ctx, "HPF Stg", 0, &value, 12, 1, 1);
      if (value != oldValue)
        SFXCore::synthFilter[instance].changepar(9, value);
    }
    {
      const int oldValue = SFXCore::synthFilter[instance].getpar(11);
      int value = oldValue;
      nk_value_int(ctx, "Depth", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::synthFilter[instance].changepar(11, value);
    }
    {
      const int oldValue = SFXCore::synthFilter[instance].getpar(12);
      int value = oldValue;
      nk_value_int(ctx, "E.Sense", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::synthFilter[instance].changepar(12, value);
    }
    {
      const int oldValue = SFXCore::synthFilter[instance].getpar(13);
      int value = oldValue;
      nk_value_int(ctx, "A.Time", value);
      nk_slider_int(ctx, 5, &value, 1000, 1);
      if (value != oldValue)
        SFXCore::synthFilter[instance].changepar(13, value);
    }
    {
      const int oldValue = SFXCore::synthFilter[instance].getpar(14);
      int value = oldValue;
      nk_value_int(ctx, "R.Time", value);
      nk_slider_int(ctx, 5, &value, 500, 1);
      if (value != oldValue)
        SFXCore::synthFilter[instance].changepar(14, value);
    }
    {
      const int oldValue = SFXCore::synthFilter[instance].getpar(15);
      int value = oldValue;
      nk_value_int(ctx, "Offset", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::synthFilter[instance].changepar(15, value);
    }
  }

  static void VaryBand(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "VaryVol 1",
        "VaryVol 2",
        "VaryVol 3"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::varyBand[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::varyBand[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::varyBand[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::varyBand[instance].getpar(1);
      int value = oldValue;
      nk_value_int(ctx, "Tempo 1", value);
      nk_slider_int(ctx, 1, &value, 600, 1);
      if (value != oldValue)
        SFXCore::varyBand[instance].changepar(1, value);
    }
    {
      nk_label(ctx, "LFO 1 Type", NK_TEXT_LEFT);
      const int oldValue = SFXCore::varyBand[instance].getpar(2);
      int value = oldValue;
      value = nk_combo(ctx, lfoTypeNames, ARRAY_SIZE(lfoTypeNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::varyBand[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::varyBand[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "St.df 1", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::varyBand[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::varyBand[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "Tempo 2", value);
      nk_slider_int(ctx, 1, &value, 600, 1);
      if (value != oldValue)
        SFXCore::varyBand[instance].changepar(4, value);
    }
    {
      nk_label(ctx, "LFO 2 Type", NK_TEXT_LEFT);
      const int oldValue = SFXCore::varyBand[instance].getpar(5);
      int value = oldValue;
      value = nk_combo(ctx, lfoTypeNames, ARRAY_SIZE(lfoTypeNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::varyBand[instance].changepar(5, value);
    }
    {
      const int oldValue = SFXCore::varyBand[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "St.df 2", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::varyBand[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::varyBand[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "Cross1", value);
      nk_slider_int(ctx, 20, &value, 1000, 1);
      if (value != oldValue)
        SFXCore::varyBand[instance].changepar(7, value);
    }
    {
      const int oldValue = SFXCore::varyBand[instance].getpar(8);
      int value = oldValue;
      nk_value_int(ctx, "Cross2", value);
      nk_slider_int(ctx, 1000, &value, 8000, 1);
      if (value != oldValue)
        SFXCore::varyBand[instance].changepar(8, value);
    }
    {
      const int oldValue = SFXCore::varyBand[instance].getpar(9);
      int value = oldValue;
      nk_value_int(ctx, "Cross3", value);
      nk_slider_int(ctx, 2000, &value, 26000, 1);
      if (value != oldValue)
        SFXCore::varyBand[instance].changepar(9, value);
    }
    {
      nk_label(ctx, "Combi", NK_TEXT_LEFT);
      static const char* combiNames[] = {
        "1122",
        "1221",
        "1212",
        "o11o",
        "o12o",
        "x11x",
        "x12x",
        "1oo1",
        "1oo2",
        "1xx1",
        "1xx2"
      };
      const int oldValue = SFXCore::varyBand[instance].getpar(10);
      int value = oldValue;
      value = nk_combo(ctx, combiNames, ARRAY_SIZE(combiNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::varyBand[instance].changepar(10, value);
    }
  }

#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
#ifdef SHR3D_SFX_CORE_RAKARRACK_CONVOLOTRON
  static void Convolotron(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Marshall JCM200",
        "Fender Superchamp",
        "Mesa Boogie",
        "Mesa Boogie 2"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::convolotron[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::convolotron[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::convolotron[instance].changepar(0, value + 64);
    }
    {
      nk_label(ctx, "Profile", NK_TEXT_LEFT);
      static const char* preset2Names[] = {
        "Marshall JCM200",
        "Fender Superchamp",
        "Mesa Boogie",
        "Mesa Boogie 2",
        "Marshall Plexi",
        "Bassman",
        "JCM2000",
        "Ampeg",
        "Marshall2"
      };
      const int oldValue = SFXCore::convolotron[instance].getpar(8);
      int value = oldValue;
      value = nk_combo(ctx, preset2Names, ARRAY_SIZE(preset2Names), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::convolotron[instance].changepar(8, value);
    }
    {
      const int oldValue = SFXCore::convolotron[instance].getpar(1) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::convolotron[instance].changepar(1, value + 64);
    }
    {
      const int oldValue = SFXCore::convolotron[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "Level", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::convolotron[instance].changepar(7, value);
    }
    {
      const int oldValue = SFXCore::convolotron[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Damp", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::convolotron[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::convolotron[instance].getpar(10);
      int value = oldValue;
      nk_value_int(ctx, "Fb", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::convolotron[instance].changepar(10, value);
    }
    {
      const int oldValue = SFXCore::convolotron[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "Length", value);
      nk_slider_int(ctx, 5, &value, 250, 1);
      if (value != oldValue)
        SFXCore::convolotron[instance].changepar(3, value);
    }
  }
#endif // SHR3D_SFX_CORE_RAKARRACK_CONVOLOTRON
#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE

  static void MuTroMojo(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "WahWah",
        "Mutron",
        "Phase Wah",
        "Phaser",
        "Quack Quack"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::muTroMojo[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::muTroMojo[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::muTroMojo[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::muTroMojo[instance].getpar(10);
      int value = oldValue;
      nk_value_int(ctx, "LP", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::muTroMojo[instance].changepar(10, value);
    }
    {
      const int oldValue = SFXCore::muTroMojo[instance].getpar(11);
      int value = oldValue;
      nk_value_int(ctx, "BP", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::muTroMojo[instance].changepar(11, value);
    }
    {
      const int oldValue = SFXCore::muTroMojo[instance].getpar(12);
      int value = oldValue;
      nk_value_int(ctx, "HP", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::muTroMojo[instance].changepar(12, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::muTroMojo[instance].getpar(17);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Mode", &value);
      if (value != oldValue)
        SFXCore::muTroMojo[instance].changepar(17, value);
    }
    {
      nk_spacer(ctx);
      const int oldValue = SFXCore::muTroMojo[instance].getpar(13);
      int value = oldValue;
      nk_property_int(ctx, "Stages:", 1, &value, 12, 1, 1);
      if (value != oldValue)
        SFXCore::muTroMojo[instance].changepar(13, value);
    }
    {
      nk_label(ctx, "LFO Type", NK_TEXT_LEFT);
      const int oldValue = SFXCore::muTroMojo[instance].getpar(4);
      int value = oldValue;
      value = nk_combo(ctx, lfoTypeNames, ARRAY_SIZE(lfoTypeNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::muTroMojo[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::muTroMojo[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Width", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::muTroMojo[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::muTroMojo[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Tempo", value);
      nk_slider_int(ctx, 1, &value, 600, 1);
      if (value != oldValue)
        SFXCore::muTroMojo[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::muTroMojo[instance].getpar(1);
      int value = oldValue;
      nk_value_int(ctx, "Res.", value);
      nk_slider_int(ctx, 1, &value, 127, 1);
      if (value != oldValue)
        SFXCore::muTroMojo[instance].changepar(1, value);
    }
    {
      const int oldValue = SFXCore::muTroMojo[instance].getpar(14);
      int value = oldValue;
      nk_value_int(ctx, "Range", value);
      nk_slider_int(ctx, 10, &value, 6000, 1);
      if (value != oldValue)
        SFXCore::muTroMojo[instance].changepar(14, value);
    }
    {
      const int oldValue = SFXCore::muTroMojo[instance].getpar(8);
      int value = oldValue;
      nk_value_int(ctx, "Wah", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::muTroMojo[instance].changepar(8, value);
    }
    {
      const int oldValue = SFXCore::muTroMojo[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "E. Sens", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::muTroMojo[instance].changepar(7, value);
    }
    {
      const int oldValue = SFXCore::muTroMojo[instance].getpar(9);
      int value = oldValue;
      nk_value_int(ctx, "Smooth", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::muTroMojo[instance].changepar(9, value);
    }
  }

  static void Echoverse(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Echo 1",
        "Echo 2",
        "Echo 3"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::echoverse[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::echoverse[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::echoverse[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::echoverse[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "Reverse", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::echoverse[instance].changepar(7, value);
    }
    {
      const int oldValue = SFXCore::echoverse[instance].getpar(1) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::echoverse[instance].changepar(1, value + 64);
    }
    {
      const int oldValue = SFXCore::echoverse[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Tempo", value);
      nk_slider_int(ctx, 1, &value, 600, 1);
      if (value != oldValue)
        SFXCore::echoverse[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::echoverse[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "LRdl.", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::echoverse[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::echoverse[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "FB.", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::echoverse[instance].changepar(5, value);
    }
    {
      nk_label(ctx, "SubDivision", NK_TEXT_LEFT);
      const int oldValue = SFXCore::echoverse[instance].getpar(8);
      int value = oldValue;
      static const char* subdivName[] = {
        "1",
        "1/2",
        "1/3",
        "1/4",
        "1/5",
        "1/6"
      };
      value = nk_combo(ctx, subdivName, ARRAY_SIZE(subdivName), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::echoverse[instance].changepar(8, value);
    }
    {
      const int oldValue = SFXCore::echoverse[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Damp", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::echoverse[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::echoverse[instance].getpar(9);
      int value = oldValue;
      nk_value_int(ctx, "E.S.", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::echoverse[instance].changepar(9, value);
    }
    {
      const int oldValue = SFXCore::echoverse[instance].getpar(4) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Angle", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::echoverse[instance].changepar(4, value + 64);
    }
  }

  static void CoilCrafter(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "S to H",
        "H to S"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::coilCrafter[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::coilCrafter[instance].getpar(0);
      int value = oldValue;
      nk_value_int(ctx, "Gain", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::coilCrafter[instance].changepar(0, value);
    }
    {
      const int oldValue = SFXCore::coilCrafter[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "Tone", value);
      nk_slider_int(ctx, 20, &value, 4400, 1);
      if (value != oldValue)
        SFXCore::coilCrafter[instance].changepar(7, value);
    }
    static const char* pickUpName[] = {
      "Off",
      "Fender Strat (old)",
      "Fender Strat (new)",
      "Squire Strat",
      "Fender Hambucker",
      "Gibson P90",
      "Gibson Standard",
      "Gibson Mini",
      "Gibson Super L6S"
    };
    {
      nk_label(ctx, "Origin", NK_TEXT_LEFT);
      const int oldValue = SFXCore::coilCrafter[instance].getpar(1);
      int value = oldValue;
      value = nk_combo(ctx, pickUpName, ARRAY_SIZE(pickUpName), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::coilCrafter[instance].changepar(1, value);
    }
    {
      const int oldValue = SFXCore::coilCrafter[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "Freq1", value);
      nk_slider_int(ctx, 2600, &value, 4500, 1);
      if (value != oldValue)
        SFXCore::coilCrafter[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::coilCrafter[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "Q1", value);
      nk_slider_int(ctx, 10, &value, 65, 1);
      if (value != oldValue)
        SFXCore::coilCrafter[instance].changepar(4, value);
    }
    {
      nk_label(ctx, "Destiny", NK_TEXT_LEFT);
      const int oldValue = SFXCore::coilCrafter[instance].getpar(2);
      int value = oldValue;
      value = nk_combo(ctx, pickUpName, ARRAY_SIZE(pickUpName), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::coilCrafter[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::coilCrafter[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "Freq2", value);
      nk_slider_int(ctx, 2600, &value, 4500, 1);
      if (value != oldValue)
        SFXCore::coilCrafter[instance].changepar(5, value);
    }
    {
      const int oldValue = SFXCore::coilCrafter[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Q2", value);
      nk_slider_int(ctx, 10, &value, 65, 1);
      if (value != oldValue)
        SFXCore::coilCrafter[instance].changepar(6, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::coilCrafter[instance].getpar(8);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Pos", &value);
      if (value != oldValue)
        SFXCore::coilCrafter[instance].changepar(8, value);
    }
  }

  static void ShelfBoost(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Trebble",
        "Mid",
        "Low",
        "Distortion 1"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::shelfBoost[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::shelfBoost[instance].getpar(0);
      int value = oldValue;
      nk_value_int(ctx, "Gain", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::shelfBoost[instance].changepar(0, value);
    }
    {
      const int oldValue = SFXCore::shelfBoost[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "Level", value);
      nk_slider_int(ctx, 1, &value, 127, 1);
      if (value != oldValue)
        SFXCore::shelfBoost[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::shelfBoost[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Tone", value);
      nk_slider_int(ctx, 220, &value, 16000, 1);
      if (value != oldValue)
        SFXCore::shelfBoost[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::shelfBoost[instance].getpar(1);
      int value = oldValue;
      nk_value_int(ctx, "Pres.", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::shelfBoost[instance].changepar(1, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::shelfBoost[instance].getpar(3);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Stereo", &value);
      if (value != oldValue)
        SFXCore::shelfBoost[instance].changepar(3, value);
    }
  }

  static void Vocoder(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    nk_label(ctx, "Preset", NK_TEXT_LEFT);
    static const char* presetNames[] = {
      "Vocoder 1",
    };
    preset = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), preset, 25, nk_vec2(200, 200));
    nk_value_int(ctx, "Wet/Dry", valueI);
    nk_slider_int(ctx, -64, &valueI, 64, 1);
    nk_value_int(ctx, "Pan", valueI);
    nk_slider_int(ctx, -64, &valueI, 64, 1);
    nk_value_int(ctx, "Input", valueI);
    nk_slider_int(ctx, -64, &valueI, 64, 1);
    nk_value_int(ctx, "Muf.", valueI);
    nk_slider_int(ctx, -64, &valueI, 64, 1);
    nk_value_int(ctx, "Q", valueI);
    nk_slider_int(ctx, -64, &valueI, 64, 1);
    nk_value_int(ctx, "Ring", valueI);
    nk_slider_int(ctx, -64, &valueI, 64, 1);
    nk_value_int(ctx, "Level", valueI);
    nk_slider_int(ctx, -64, &valueI, 64, 1);
  }

  static void Sustainer(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Sustain 1",
        "Sustain 2",
        "Sustain 3"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::sustainer[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::sustainer[instance].getpar(0);
      int value = oldValue;
      nk_value_int(ctx, "Gain", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::sustainer[instance].changepar(0, value);
    }
    {
      const int oldValue = SFXCore::sustainer[instance].getpar(1);
      int value = oldValue;
      nk_value_int(ctx, "Sustain", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::sustainer[instance].changepar(1, value);
    }
  }

#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
  static void Sequence(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Jumpy",
        "Stair Step",
        "Mild",
        "Wah Wah",
        "Filter Pan",
        "Stepper",
        "Shifter",
        "Zeke Trem",
        "Boogie",
        "Chorus"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::sequence[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::sequence[instance].getpar(8) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::sequence[instance].changepar(8, value + 64);
    }
    {
      const int oldValue = SFXCore::sequence[instance].getpar(0);
      int value = oldValue;
      nk_value_int(ctx, "1", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::sequence[instance].changepar(0, value);
    }
    {
      const int oldValue = SFXCore::sequence[instance].getpar(1);
      int value = oldValue;
      nk_value_int(ctx, "2", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::sequence[instance].changepar(1, value);
    }
    {
      const int oldValue = SFXCore::sequence[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "3", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::sequence[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::sequence[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "4", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::sequence[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::sequence[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "5", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::sequence[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::sequence[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "6", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::sequence[instance].changepar(5, value);
    }
    {
      const int oldValue = SFXCore::sequence[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "7", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::sequence[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::sequence[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "8", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::sequence[instance].changepar(7, value);
    }
    {
      const int oldValue = SFXCore::sequence[instance].getpar(9);
      int value = oldValue;
      nk_value_int(ctx, "Tempo", value);
      nk_slider_int(ctx, 1, &value, 600, 1);
      if (value != oldValue)
        SFXCore::sequence[instance].changepar(9, value);
    }
    {
      const int oldValue = SFXCore::sequence[instance].getpar(10) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Q", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::sequence[instance].changepar(10, value + 64);
    }
    {
      const int oldValue = SFXCore::sequence[instance].getpar(12);
      int value = oldValue;
      nk_value_int(ctx, "St.df", value);
      nk_slider_int(ctx, 0, &value, 7, 1);
      if (value != oldValue)
        SFXCore::sequence[instance].changepar(12, value);
    }
    {
      nk_label(ctx, "Mode", NK_TEXT_LEFT);
      static const char* modeNames[] = {
        "Lineal",
        "UpDown",
        "Stepper",
        "Shifter",
        "Tremor",
        "Arpegiator",
        "Chorus"
      };
      const int oldValue = SFXCore::sequence[instance].getpar(13);
      int value = oldValue;
      value = nk_combo(ctx, modeNames, ARRAY_SIZE(modeNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::sequence[instance].changepar(13, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::sequence[instance].getpar(11);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Amp.", &value);
      if (value != oldValue)
        SFXCore::sequence[instance].changepar(11, value);
    }
    {
      nk_label(ctx, "Range", NK_TEXT_LEFT);
      const int oldValue = SFXCore::sequence[instance].getpar(14);
      int value = oldValue;
      nk_property_int(ctx, "", 1, &value, 8, 1, 1);
      if (value != oldValue)
        SFXCore::sequence[instance].changepar(14, value);
    }
  }

  static void Shifter(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Fast",
        "Slow Up",
        "Slow Down",
        "Chorus",
        "Trig. Chorus"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::shifter[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::shifter[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::shifter[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::shifter[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Int.", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::shifter[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::shifter[instance].getpar(2) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Gain", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::shifter[instance].changepar(2, value + 64);
    }
    {
      const int oldValue = SFXCore::shifter[instance].getpar(1) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::shifter[instance].changepar(1, value + 64);
    }
    {
      const int oldValue = SFXCore::shifter[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "Attack", value);
      nk_slider_int(ctx, 0, &value, 1000, 1);
      if (value != oldValue)
        SFXCore::shifter[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::shifter[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "Decay", value);
      nk_slider_int(ctx, 0, &value, 1000, 1);
      if (value != oldValue)
        SFXCore::shifter[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::shifter[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "Thrshold", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::shifter[instance].changepar(5, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::shifter[instance].getpar(7);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Down", &value);
      if (value != oldValue)
        SFXCore::shifter[instance].changepar(7, value);
    }
    {
      const int oldValue = SFXCore::shifter[instance].getpar(9);
      int value = oldValue;
      nk_value_int(ctx, "Whamy", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::shifter[instance].changepar(9, value);
    }
    {
      nk_label(ctx, "Mode", NK_TEXT_LEFT);
      static const char* modeNames[] = {
        "Trigger",
        "Whammy"
      };
      const int oldValue = SFXCore::shifter[instance].getpar(5);
      int value = oldValue;
      value = nk_combo(ctx, modeNames, ARRAY_SIZE(modeNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::shifter[instance].changepar(5, value);
    }
  }

  static void StompBox(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Odie",
        "Grunger",
        "Hard Dist.",
        "Ratula",
        "Classic Dist",
        "Morbid Impalement",
        "Sharp Metal",
        "Classic Fuzz"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::stompBox[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::stompBox[instance].getpar(0);
      int value = oldValue;
      nk_value_int(ctx, "Level", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::stompBox[instance].changepar(0, value);
    }
    {
      const int oldValue = SFXCore::stompBox[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "Gain", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::stompBox[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::stompBox[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "Low", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::stompBox[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::stompBox[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Mid", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::stompBox[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::stompBox[instance].getpar(1);
      int value = oldValue;
      nk_value_int(ctx, "High", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::stompBox[instance].changepar(1, value);
    }
    {
      nk_label(ctx, "Mode", NK_TEXT_LEFT);
      static const char* modeNames[] = {
        "Amp",
        "Grunge",
        "Rat",
        "Fat Cat",
        "Dist+",
        "Death",
        "Mid Elves Own",
        "Fuzz"
      };
      const int oldValue = SFXCore::stompBox[instance].getpar(5);
      int value = oldValue;
      value = nk_combo(ctx, modeNames, ARRAY_SIZE(modeNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::stompBox[instance].changepar(5, value);
    }
  }

#ifdef SHR3D_SFX_CORE_RAKARRACK_REVERBTRON
  static void Reverbtron(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Chamber",
        "Concrete Stairwell",
        "Hall",
        "Med Hall",
        "Room",
        "Hall",
        "Guitar",
        "Studio",
        "Cathedral"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::reverbtron[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::reverbtron[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::reverbtron[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::reverbtron[instance].getpar(11) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::reverbtron[instance].changepar(11, value + 64);
    }
    {
      const int oldValue = SFXCore::reverbtron[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "Level", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::reverbtron[instance].changepar(7, value);
    }
    {
      const int oldValue = SFXCore::reverbtron[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Damp", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::reverbtron[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::reverbtron[instance].getpar(10);
      int value = oldValue;
      nk_value_int(ctx, "Fb", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::reverbtron[instance].changepar(10, value);
    }
    {
      const int oldValue = SFXCore::reverbtron[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "Length", value);
      nk_slider_int(ctx, 20, &value, 1500, 1);
      if (value != oldValue)
        SFXCore::reverbtron[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::reverbtron[instance].getpar(9);
      int value = oldValue;
      nk_value_int(ctx, "Strech", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::reverbtron[instance].changepar(9, value);
    }
    {
      const int oldValue = SFXCore::reverbtron[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "I.Del", value);
      nk_slider_int(ctx, 0, &value, 500, 1);
      if (value != oldValue)
        SFXCore::reverbtron[instance].changepar(5, value);
    }
    {
      const int oldValue = SFXCore::reverbtron[instance].getpar(1);
      int value = oldValue;
      nk_value_int(ctx, "Fade", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::reverbtron[instance].changepar(1, value);
    }
    {
      const int oldValue = SFXCore::reverbtron[instance].getpar(15);
      int value = oldValue;
      nk_value_int(ctx, "Diffusion", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::reverbtron[instance].changepar(15, value);
    }
    {
      const int oldValue = SFXCore::reverbtron[instance].getpar(14);
      int value = oldValue;
      nk_value_int(ctx, "LPF", value);
      nk_slider_int(ctx, 20, &value, 26000, 1);
      if (value != oldValue)
        SFXCore::reverbtron[instance].changepar(14, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::reverbtron[instance].getpar(13);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Sh", &value);
      if (value != oldValue)
        SFXCore::reverbtron[instance].changepar(13, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::reverbtron[instance].getpar(2);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Safe", &value);
      if (value != oldValue)
        SFXCore::reverbtron[instance].changepar(2, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::reverbtron[instance].getpar(12);
      bool value = oldValue;
      nk_checkbox_label(ctx, "ES", &value);
      if (value != oldValue)
        SFXCore::reverbtron[instance].changepar(12, value);
    }
    {
      nk_label(ctx, "LFO Type", NK_TEXT_LEFT);
      static const char* fileNames[] = {
        "Chamber",
        "Conc. Stair",
        "Hall",
        "Med Hall",
        "Large Room",
        "Large Hall",
        "Guitar Ambience",
        "Studio",
        "Twilight",
        "Santa Lucia"
      };
      const int oldValue = SFXCore::reverbtron[instance].getpar(8);
      int value = oldValue;
      value = nk_combo(ctx, fileNames, ARRAY_SIZE(fileNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::reverbtron[instance].changepar(8, value);
    }
  }
#endif // SHR3D_SFX_CORE_RAKARRACK_REVERBTRON
#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE

  static void Echotron(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Summer",
        "Ambience",
        "Arranjer",
        "Suction",
        "SuctionFlange"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::echotron[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::echotron[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::echotron[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::echotron[instance].getpar(11) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::echotron[instance].changepar(11, value + 64);
    }
    {
      const int oldValue = SFXCore::echotron[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "Tempo", value);
      nk_slider_int(ctx, 1, &value, 600, 1);
      if (value != oldValue)
        SFXCore::echotron[instance].changepar(5, value);
    }
    {
      const int oldValue = SFXCore::echotron[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Damp", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::echotron[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::echotron[instance].getpar(10);
      int value = oldValue;
      nk_value_int(ctx, "Fb", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::echotron[instance].changepar(10, value);
    }
    {
      const int oldValue = SFXCore::echotron[instance].getpar(7) - 64;
      int value = oldValue;
      nk_value_int(ctx, "L/R.Cr", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::echotron[instance].changepar(7, value + 64);
    }
    {
      const int oldValue = SFXCore::echotron[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Width", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::echotron[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::echotron[instance].getpar(1) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Depth", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::echotron[instance].changepar(1, value + 64);
    }
    {
      const int oldValue = SFXCore::echotron[instance].getpar(9);
      int value = oldValue;
      nk_value_int(ctx, "St.df", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::echotron[instance].changepar(9, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::echotron[instance].getpar(15);
      bool value = oldValue;
      nk_checkbox_label(ctx, "AF", &value);
      if (value != oldValue)
        SFXCore::echotron[instance].changepar(15, value);
    }
    {
      nk_label(ctx, "LFO Type", NK_TEXT_LEFT);
      const int oldValue = SFXCore::echotron[instance].getpar(14);
      int value = oldValue;
      value = nk_combo(ctx, lfoTypeNames, ARRAY_SIZE(lfoTypeNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::echotron[instance].changepar(14, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::echotron[instance].getpar(13);
      bool value = oldValue;
      nk_checkbox_label(ctx, "MF", &value);
      if (value != oldValue)
        SFXCore::echotron[instance].changepar(13, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::echotron[instance].getpar(12);
      bool value = oldValue;
      nk_checkbox_label(ctx, "MD", &value);
      if (value != oldValue)
        SFXCore::echotron[instance].changepar(12, value);
    }
    {
      nk_label(ctx, "#", NK_TEXT_LEFT);
      const int oldValue = SFXCore::echotron[instance].getpar(3);
      int value = oldValue;
      nk_property_int(ctx, "", 1, &value, 8, 1, 1);
      if (value != oldValue)
        SFXCore::echotron[instance].changepar(3, value);
    }
    {
      nk_label(ctx, "File", NK_TEXT_LEFT);
      static const char* fileNames[] = {
        "SwingPong",
        "i16 Delays",
        "Flange + Echo",
        "Comb",
        "EchoFlange",
        "Filtered Echo",
        "Notch-Wah",
        "Multi-Chorus",
        "PingPong",
        "90-Shifter",
        "Basic LR Delay"
      };
      const int oldValue = SFXCore::echotron[instance].getpar(8);
      int value = oldValue;
      value = nk_combo(ctx, fileNames, ARRAY_SIZE(fileNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::echotron[instance].changepar(8, value);
    }
  }

#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
  static void StereoHarm(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Plain",
        "Octavator",
        "Chorus",
        "Hard Chorus"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::stereoHarm[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::stereoHarm[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::stereoHarm[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::stereoHarm[instance].getpar(2) - 12;
      int value = oldValue;
      nk_value_int(ctx, "Int L", value);
      nk_slider_int(ctx, -12, &value, 12, 1);
      if (value != oldValue)
        SFXCore::stereoHarm[instance].changepar(2, value + 12);
    }
    {
      const int oldValue = SFXCore::stereoHarm[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "Chrom L", value);
      nk_slider_int(ctx, -2000, &value, 2000, 1);
      if (value != oldValue)
        SFXCore::stereoHarm[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::stereoHarm[instance].getpar(1) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Gain L", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::stereoHarm[instance].changepar(1, value + 64);
    }
    {
      const int oldValue = SFXCore::stereoHarm[instance].getpar(5) - 12;
      int value = oldValue;
      nk_value_int(ctx, "Int R", value);
      nk_slider_int(ctx, -12, &value, 12, 1);
      if (value != oldValue)
        SFXCore::stereoHarm[instance].changepar(5, value + 12);
    }
    {
      const int oldValue = SFXCore::stereoHarm[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "Chrom R", value);
      nk_slider_int(ctx, -2000, &value, 2000, 1);
      if (value != oldValue)
        SFXCore::stereoHarm[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::stereoHarm[instance].getpar(4) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Gain R", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::stereoHarm[instance].changepar(4, value + 64);
    }
    {
      const int oldValue = SFXCore::stereoHarm[instance].getpar(11) - 64;
      int value = oldValue;
      nk_value_int(ctx, "L/R.Cr", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::stereoHarm[instance].changepar(11, value + 64);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::stereoHarm[instance].getpar(10);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Midi", &value);
      if (value != oldValue)
        SFXCore::stereoHarm[instance].changepar(10, value);
    }
    {
      nk_spacer(ctx);
      const bool oldValue = SFXCore::stereoHarm[instance].getpar(7);
      bool value = oldValue;
      nk_checkbox_label(ctx, "Sel", &value);
      if (value != oldValue)
        SFXCore::stereoHarm[instance].changepar(7, value);
    }
    {
      const int oldValue = SFXCore::stereoHarm[instance].getpar(8);
      int value = oldValue;
      nk_value_int(ctx, "Note", value);
      nk_slider_int(ctx, 0, &value, 23, 1);
      if (value != oldValue)
        SFXCore::stereoHarm[instance].changepar(8, value);
    }
    {
      const int oldValue = SFXCore::stereoHarm[instance].getpar(9);
      int value = oldValue;
      nk_value_int(ctx, "Chord", value);
      nk_slider_int(ctx, 0, &value, 33, 1);
      if (value != oldValue)
        SFXCore::stereoHarm[instance].changepar(9, value);
    }
  }
#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE

  static void CompBand(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Good Start",
        "Loudness",
        "Loudness 2"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::compBand[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::compBand[instance].getpar(0) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 64, 1);
      if (value != oldValue)
        SFXCore::compBand[instance].changepar(0, value + 64);
    }
    {
      const int oldValue = SFXCore::compBand[instance].getpar(12);
      int value = oldValue;
      nk_value_int(ctx, "Gain", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::compBand[instance].changepar(12, value);
    }
    {
      const int oldValue = SFXCore::compBand[instance].getpar(1);
      int value = oldValue;
      nk_value_int(ctx, "L Ratio", value);
      nk_slider_int(ctx, 2, &value, 42, 1);
      if (value != oldValue)
        SFXCore::compBand[instance].changepar(1, value);
    }
    {
      const int oldValue = SFXCore::compBand[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "ML Ratio", value);
      nk_slider_int(ctx, 2, &value, 42, 1);
      if (value != oldValue)
        SFXCore::compBand[instance].changepar(2, value);
    }
    {
      const int oldValue = SFXCore::compBand[instance].getpar(3);
      int value = oldValue;
      nk_value_int(ctx, "MH Ratio", value);
      nk_slider_int(ctx, 2, &value, 42, 1);
      if (value != oldValue)
        SFXCore::compBand[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::compBand[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "H Ratio", value);
      nk_slider_int(ctx, 2, &value, 42, 1);
      if (value != oldValue)
        SFXCore::compBand[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::compBand[instance].getpar(5);
      int value = oldValue;
      nk_value_int(ctx, "L Thres", value);
      nk_slider_int(ctx, -70, &value, 24, 1);
      if (value != oldValue)
        SFXCore::compBand[instance].changepar(5, value);
    }
    {
      const int oldValue = SFXCore::compBand[instance].getpar(6);
      int value = oldValue;
      nk_value_int(ctx, "ML Thres", value);
      nk_slider_int(ctx, -70, &value, 24, 1);
      if (value != oldValue)
        SFXCore::compBand[instance].changepar(6, value);
    }
    {
      const int oldValue = SFXCore::compBand[instance].getpar(7);
      int value = oldValue;
      nk_value_int(ctx, "MH Thres", value);
      nk_slider_int(ctx, -70, &value, 24, 1);
      if (value != oldValue)
        SFXCore::compBand[instance].changepar(7, value);
    }
    {
      const int oldValue = SFXCore::compBand[instance].getpar(8);
      int value = oldValue;
      nk_value_int(ctx, "H Thres", value);
      nk_slider_int(ctx, -70, &value, 24, 1);
      if (value != oldValue)
        SFXCore::compBand[instance].changepar(8, value);
    }
    {
      const int oldValue = SFXCore::compBand[instance].getpar(9);
      int value = oldValue;
      nk_value_int(ctx, "Cross 1", value);
      nk_slider_int(ctx, 20, &value, 1000, 1);
      if (value != oldValue)
        SFXCore::compBand[instance].changepar(9, value);
    }
    {
      const int oldValue = SFXCore::compBand[instance].getpar(10);
      int value = oldValue;
      nk_value_int(ctx, "Cross 2", value);
      nk_slider_int(ctx, 1000, &value, 8000, 1);
      if (value != oldValue)
        SFXCore::compBand[instance].changepar(10, value);
    }
    {
      const int oldValue = SFXCore::compBand[instance].getpar(11);
      int value = oldValue;
      nk_value_int(ctx, "Cross 3", value);
      nk_slider_int(ctx, 2000, &value, 26000, 1);
      if (value != oldValue)
        SFXCore::compBand[instance].changepar(11, value);
    }
  }

  static void OpticalTrem(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Fast",
        "Trem 2",
        "Hard Pan",
        "Soft Pan",
        "Ramp Down",
        "Hard Ramp"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::opticalTrem[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::opticalTrem[instance].getpar(0);
      int value = oldValue;
      nk_value_int(ctx, "Depth", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::opticalTrem[instance].changepar(0, value);
    }
    {
      const int oldValue = SFXCore::opticalTrem[instance].getpar(1);
      int value = oldValue;
      nk_value_int(ctx, "Tempo", value);
      nk_slider_int(ctx, 1, &value, 600, 1);
      if (value != oldValue)
        SFXCore::opticalTrem[instance].changepar(1, value);
    }
    {
      const int oldValue = SFXCore::opticalTrem[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Rnd", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::opticalTrem[instance].changepar(2, value);
    }
    {
      nk_label(ctx, "LFO Type", NK_TEXT_LEFT);
      const int oldValue = SFXCore::opticalTrem[instance].getpar(3);
      int value = oldValue;
      value = nk_combo(ctx, lfoTypeNames, ARRAY_SIZE(lfoTypeNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::opticalTrem[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::opticalTrem[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "St.df", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::opticalTrem[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::opticalTrem[instance].getpar(5) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::opticalTrem[instance].changepar(5, value + 64);
    }
  }

  static void Vibe(i32 instance)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    {
      nk_label(ctx, "Preset", NK_TEXT_LEFT);
      static const char* presetNames[] = {
        "Classic",
        "Stereo Classic",
        "Wide Vibe",
        "Classic Chorus",
        "Vibe Chorus",
        "Lush Chorus",
        "Sick Phaser",
        "Warble"
      };
      static int oldValue = 0;
      int value = oldValue;
      value = nk_combo(ctx, presetNames, ARRAY_SIZE(presetNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
      {
        SFXCore::vibe[instance].setpreset(value);
        oldValue = value;
      }
    }
    {
      const int oldValue = SFXCore::vibe[instance].getpar(6) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Wet/Dry", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::vibe[instance].changepar(6, value + 64);
    }
    {
      const int oldValue = SFXCore::vibe[instance].getpar(0);
      int value = oldValue;
      nk_value_int(ctx, "Width", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::vibe[instance].changepar(0, value);
    }
    {
      const int oldValue = SFXCore::vibe[instance].getpar(8);
      int value = oldValue;
      nk_value_int(ctx, "Depth", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::vibe[instance].changepar(8, value);
    }
    {
      const int oldValue = SFXCore::vibe[instance].getpar(1);
      int value = oldValue;
      nk_value_int(ctx, "Tempo", value);
      nk_slider_int(ctx, 1, &value, 600, 1);
      if (value != oldValue)
        SFXCore::vibe[instance].changepar(1, value);
    }
    {
      const int oldValue = SFXCore::vibe[instance].getpar(2);
      int value = oldValue;
      nk_value_int(ctx, "Rnd", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::vibe[instance].changepar(2, value);
    }
    {
      nk_label(ctx, "LFO Type", NK_TEXT_LEFT);
      const int oldValue = SFXCore::vibe[instance].getpar(3);
      int value = oldValue;
      value = nk_combo(ctx, lfoTypeNames, ARRAY_SIZE(lfoTypeNames), value, 25, nk_vec2(200, 200));
      if (value != oldValue)
        SFXCore::vibe[instance].changepar(3, value);
    }
    {
      const int oldValue = SFXCore::vibe[instance].getpar(4);
      int value = oldValue;
      nk_value_int(ctx, "St.df", value);
      nk_slider_int(ctx, 0, &value, 127, 1);
      if (value != oldValue)
        SFXCore::vibe[instance].changepar(4, value);
    }
    {
      const int oldValue = SFXCore::vibe[instance].getpar(7) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Fb", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::vibe[instance].changepar(7, value + 64);
    }
    {
      const int oldValue = SFXCore::vibe[instance].getpar(9) - 64;
      int value = oldValue;
      nk_value_int(ctx, "L/R.Cr", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::vibe[instance].changepar(9, value + 64);
    }
    {
      const int oldValue = SFXCore::vibe[instance].getpar(5) - 64;
      int value = oldValue;
      nk_value_int(ctx, "Pan", value);
      nk_slider_int(ctx, -64, &value, 63, 1);
      if (value != oldValue)
        SFXCore::vibe[instance].changepar(5, value + 64);
    }
  }
#endif // SHR3D_SFX_CORE_RAKARRACK

#ifdef SHR3D_SFX_CORE_AIRWINDOWS
  static void AirWindow(SfxCoreAirWindows_ sfxCoreAirWindows, i32 instance)
  {
    nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 60.0f * Settings::uiScale);
    nk_layout_row_template_push_dynamic(ctx);
    nk_layout_row_template_push_static(ctx, 60.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 30.0f * Settings::uiScale);
    nk_layout_row_template_end(ctx);

    for (i32 i = 0; i < SFXCore::getParameterCount(sfxCoreAirWindows); ++i)
    {
      {
        char parameterName[32];
        SFXCore::getParameterName(sfxCoreAirWindows, instance, i, parameterName);
        nk_label(ctx, parameterName, NK_TEXT_LEFT);
      }
      {
        f32 value = SFXCore::getParameter(sfxCoreAirWindows, instance, i);
        nk_slider_float(ctx, 0.0f, &value, 1.0f, 0.0001f);
        SFXCore::setParameter(sfxCoreAirWindows, instance, i, value);
      }
      {
        char parameterDisplay[32];
        SFXCore::getParameterDisplay(sfxCoreAirWindows, instance, i, parameterDisplay);
        nk_label(ctx, parameterDisplay, NK_TEXT_LEFT);
      }
      {
        char parameterLabel[32];
        SFXCore::getParameterLabel(sfxCoreAirWindows, instance, i, parameterLabel);
        nk_label(ctx, parameterLabel, NK_TEXT_LEFT);
      }
    }
  }
#endif // SHR3D_SFX_CORE_AIRWINDOWS
}

static bool sfxCoreWindow(SfxId sfxId, i32 instance, i32 resolutionWidth, i32 resolutionHeight)
{
  const char* sfxName = Sfx::names[sfxId.system][sfxId.sfxIndex].c_str();
  char name[32];
  sprintf(name, "%s_%d", sfxName, instance);

  const bool showWindow = nk_begin_titled(ctx, name, sfxName, calcWindowRectCentered(resolutionWidth, resolutionHeight, 0, 0, 440, 550), NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_TITLE | NK_WINDOW_CLOSABLE | NK_WINDOW_SCALABLE);
  if (showWindow)
  {
    switch (sfxId.system)
    {
    case SfxSystem::core:
    {
      const SfxCore sfxCore = SfxCore(sfxId.sfxIndex);
      switch (sfxCore)
      {
#ifdef SHR3D_SFX_CORE_NEURALAMPMODELER
      case SfxCore::NeuralAmpModeler:
        SfxCoreUi::NeuralAmpModeler(instance);
        break;
#endif // SHR3D_SFX_CORE_NEURALAMPMODELER

#ifdef SHR3D_SFX_CORE_HEXFIN
      case SfxCore::Hexfin:
        SfxCoreUi::Hexfin();
        break;
#endif // SHR3D_SFX_CORE_HEXFIN
      default:
        unreachable();
      }
    }
    break;
#ifdef SHR3D_SFX_CORE_AIRWINDOWS
    case SfxSystem::coreAirWindows:
    {
      const SfxCoreAirWindows_ sfxCoreAirWindows = SfxCoreAirWindows_(sfxId.sfxIndex);
      SfxCoreUi::AirWindow(sfxCoreAirWindows, instance);
    }
    break;
#endif // SHR3D_SFX_CORE_AIRWINDOWS
#ifdef SHR3D_SFX_CORE_RAKARRACK
    case SfxSystem::coreRakarrack:
    {
      const SfxCoreRakarrack sfxCoreRakarrack = SfxCoreRakarrack(sfxId.sfxIndex);
      switch (sfxCoreRakarrack)
      {
      case SfxCoreRakarrack::Reverb:
        SfxCoreUi::Reverb(instance);
        break;
      case SfxCoreRakarrack::Echo:
        SfxCoreUi::Echo(instance);
        break;
      case SfxCoreRakarrack::Chorus:
        SfxCoreUi::Chorus(instance);
        break;
      case SfxCoreRakarrack::Flanger:
        SfxCoreUi::Flanger(instance);
        break;
      case SfxCoreRakarrack::Phaser:
        SfxCoreUi::Phaser(instance);
        break;
#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      case SfxCoreRakarrack::Overdrive:
        SfxCoreUi::Overdrive(instance);
        break;
      case SfxCoreRakarrack::Distorsion:
        SfxCoreUi::Distortion(instance);
        break;
#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      case SfxCoreRakarrack::EQ:
        SfxCoreUi::EQ(instance);
        break;
      case SfxCoreRakarrack::ParametricEQ:
        SfxCoreUi::ParametricEQ(instance);
        break;
      case SfxCoreRakarrack::Compressor:
        SfxCoreUi::Compressor(instance);
        break;
      case SfxCoreRakarrack::WahWah:
        SfxCoreUi::WahWah(instance);
        break;
      case SfxCoreRakarrack::AlienWah:
        SfxCoreUi::AlienWah(instance);
        break;
      case SfxCoreRakarrack::Cabinet:
        SfxCoreUi::Cabinet(instance);
        break;
      case SfxCoreRakarrack::Pan:
        SfxCoreUi::Pan(instance);
        break;
#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
#if 0
      case SfxCore::Harmonizer:
        SfxCoreUi::Harmonizer(instance);
        break;
#endif
#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      case SfxCoreRakarrack::MusicalDelay:
        SfxCoreUi::MusicalDelay(instance);
        break;
      case SfxCoreRakarrack::NoiseGate:
        SfxCoreUi::NoiseGate(instance);
        break;
#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      case SfxCoreRakarrack::Derelict:
        break;
#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      case SfxCoreRakarrack::AnalogPhaser:
        SfxCoreUi::AnalogPhaser(instance);
        break;
      case SfxCoreRakarrack::Valve:
        SfxCoreUi::Valve(instance);
        break;
      case SfxCoreRakarrack::DualFlange:
        SfxCoreUi::DualFlange(instance);
        break;
      case SfxCoreRakarrack::Ring:
        SfxCoreUi::Ring(instance);
        break;
      case SfxCoreRakarrack::Exciter:
        SfxCoreUi::Exiter(instance);
        break;
#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      case SfxCoreRakarrack::DistBand:
        SfxCoreUi::DistBand(instance);
        break;
#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      case SfxCoreRakarrack::Arpie:
        SfxCoreUi::Arpie(instance);
        break;
      case SfxCoreRakarrack::Expander:
        SfxCoreUi::Expander(instance);
        break;
      case SfxCoreRakarrack::Shuffle:
        SfxCoreUi::Shuffle(instance);
        break;
      case SfxCoreRakarrack::Synthfilter:
        SfxCoreUi::Synthfilter(instance);
        break;
      case SfxCoreRakarrack::VaryBand:
        SfxCoreUi::VaryBand(instance);
        break;
#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
#ifdef SHR3D_SFX_CORE_RAKARRACK_CONVOLOTRON
      case SfxCoreRakarrack::Convolotron:
        SfxCoreUi::Convolotron(instance);
        break;
#endif // SHR3D_SFX_CORE_RAKARRACK_CONVOLOTRON
#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      case SfxCoreRakarrack::MuTroMojo:
        SfxCoreUi::MuTroMojo(instance);
        break;
      case SfxCoreRakarrack::Echoverse:
        SfxCoreUi::Echoverse(instance);
        break;
      case SfxCoreRakarrack::CoilCrafter:
        SfxCoreUi::CoilCrafter(instance);
        break;
      case SfxCoreRakarrack::ShelfBoost:
        SfxCoreUi::ShelfBoost(instance);
        break;
#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      case SfxCoreRakarrack::Vocoder:
        SfxCoreUi::Vocoder(instance);
        break;
#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      case SfxCoreRakarrack::Sustainer:
        SfxCoreUi::Sustainer(instance);
        break;
#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      case SfxCoreRakarrack::Sequence:
        SfxCoreUi::Sequence(instance);
        break;
      case SfxCoreRakarrack::Shifter:
        SfxCoreUi::Shifter(instance);
        break;
      case SfxCoreRakarrack::StompBox:
        SfxCoreUi::StompBox(instance);
        break;
#ifdef SHR3D_SFX_CORE_RAKARRACK_REVERBTRON
      case SfxCoreRakarrack::Reverbtron:
        SfxCoreUi::Reverbtron(instance);
        break;
#endif // SHR3D_SFX_CORE_RAKARRACK_REVERBTRON
#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      case SfxCoreRakarrack::Echotron:
        SfxCoreUi::Echotron(instance);
        break;
#ifdef SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      case SfxCoreRakarrack::StereoHarm:
        SfxCoreUi::StereoHarm(instance);
        break;
#endif // SHR3D_SFX_CORE_RAKARRACK_LIBSAMPLERATE
      case SfxCoreRakarrack::CompBand:
        SfxCoreUi::CompBand(instance);
        break;
      case SfxCoreRakarrack::OpticalTrem:
        SfxCoreUi::OpticalTrem(instance);
        break;
      case SfxCoreRakarrack::Vibe:
        SfxCoreUi::Vibe(instance);
        break;
      }
    }
    break;
#endif // SHR3D_SFX_CORE_RAKARRACK
#ifdef SHR3D_SFX_CORE_EXTENSION_V2
    case SfxSystem::coreExtensionV2:
      break;
#endif // SHR3D_SFX_CORE_EXTENSION_V2
    default:
      unreachable();
    }
  }
  nk_end(ctx);

  return showWindow;
}

#ifdef SHR3D_SFX_CORE_EXTENSION_V2
static bool sfxCoreExtensionWindow(i32 index, i32 instance, i32 resolutionWidth, i32 resolutionHeight)
{
  const i32 parameterCount = Global::sfxCoreExtensionV2SortedByRegistrationOrder[index]->getParameterCount();

  const char* sfxName = Sfx::names[SfxSystem::coreExtensionV2][index].c_str();
  char name[32];
  sprintf(name, "%s_%d", sfxName, instance);

  const bool showWindow = nk_begin_titled(ctx, name, sfxName, calcWindowRectCentered(resolutionWidth, resolutionHeight, 0, 0, 440, 80 + parameterCount * 26), NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_TITLE | NK_WINDOW_CLOSABLE | NK_WINDOW_SCALABLE);
  if (showWindow)
  {
    nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 60.0f * Settings::uiScale);
    nk_layout_row_template_push_dynamic(ctx);
    nk_layout_row_template_push_static(ctx, 60.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 50.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
    nk_layout_row_template_end(ctx);

    for (i32 i = 0; i < parameterCount; ++i)
    {
      {
        const char* parameterName = Global::sfxCoreExtensionV2SortedByRegistrationOrder[index]->getParameterName(i);
        nk_label(ctx, parameterName, NK_TEXT_LEFT);
      }
      {
        f32 value = Global::sfxCoreExtensionV2SortedByRegistrationOrder[index]->getInstance(instance)->getParameter(i);
        if (nk_slider_float(ctx, 0.0f, &value, 1.0f, 0.0001f))
          Global::sfxCoreExtensionV2SortedByRegistrationOrder[index]->getInstance(instance)->setParameter(i, value);
      }
      {
        char parameterDisplay[32];
        Global::sfxCoreExtensionV2SortedByRegistrationOrder[index]->getInstance(instance)->getParameterDisplay(i, parameterDisplay);
        nk_label(ctx, parameterDisplay, NK_TEXT_LEFT);
      }
      {
        const char* parameterLabel = Global::sfxCoreExtensionV2SortedByRegistrationOrder[index]->getParameterLabel(i);
        nk_label(ctx, parameterLabel, NK_TEXT_LEFT);
      }
      {
        const char* parameterLabel = Global::sfxCoreExtensionV2SortedByRegistrationOrder[index]->getParameterLabel(i);
        if (nk_button_label(ctx, "R"))
        {
          Global::sfxCoreExtensionV2SortedByRegistrationOrder[index]->getInstance(instance)->resetParameter(i);
        }
      }
    }
  }
  nk_end(ctx);

  return showWindow;
}
#endif // SHR3D_SFX_CORE_EXTENSION_V2

#endif // SHR3D_SFX_CORE

static bool sfxWindow(const SfxId sfxId, const i32 instance, const i32 resolutionWidth, const i32 resolutionHeight, const i32 effectChainIndex
#ifdef SHR3D_SFX_PLUGIN
  , bool& windowPluginHideUi, Shr3DWindow& windowPluginParentWindow, vec2& windowPluginPosition
#endif // SHR3D_SFX_PLUGIN
)
{
  ASSERT(sfxId.system != SfxSystem::empty);

  switch (sfxId.system)
  {
#ifdef SHR3D_SFX_CORE
    case SfxSystem::core:
#ifdef SHR3D_SFX_CORE_RAKARRACK
    case SfxSystem::coreRakarrack:
#endif // SHR3D_SFX_CORE_RAKARRACK
#ifdef SHR3D_SFX_CORE_AIRWINDOWS
    case SfxSystem::coreAirWindows:
#endif // SHR3D_SFX_CORE_AIRWINDOWS
      {
        return sfxCoreWindow(sfxId, instance, resolutionWidth, resolutionHeight);
      }
      break;
#ifdef SHR3D_SFX_CORE_EXTENSION_V2
    case SfxSystem::coreExtensionV2:
      {
        if (Global::sfxCoreExtensionV2SortedByRegistrationOrder[sfxId.sfxIndex]->uiCallback == nullptr)
          return sfxCoreExtensionWindow(sfxId.sfxIndex, instance, resolutionWidth, resolutionHeight);
        else
          return Global::sfxCoreExtensionV2SortedByRegistrationOrder[sfxId.sfxIndex]->uiCallback(ctx, Global::sfxCoreExtensionV2SortedByRegistrationOrder[sfxId.sfxIndex], instance);
      }
      break;
#endif // SHR3D_SFX_CORE_EXTENSION_V2
#endif // SHR3D_SFX_CORE

#ifdef SHR3D_SFX_PLUGIN
#ifdef SHR3D_SFX_PLUGIN_CLAP
    case SfxSystem::clap:
#endif // SHR3D_SFX_PLUGIN_CLAP
#ifdef SHR3D_SFX_PLUGIN_LV2
    case SfxSystem::lv2:
#endif // SHR3D_SFX_PLUGIN_LV2
#ifdef SHR3D_SFX_PLUGIN_VST
    case SfxSystem::vst:
#endif // SHR3D_SFX_PLUGIN_VST
#ifdef SHR3D_SFX_PLUGIN_VST3
    case SfxSystem::vst3:
#endif // SHR3D_SFX_PLUGIN_VST3
      {
        return effectChainWindowPluginParentWindow(sfxId, instance, resolutionWidth, resolutionHeight, windowPluginHideUi, windowPluginParentWindow, windowPluginPosition);
      }
      break;
#endif // SHR3D_SFX_PLUGIN
    default:
      unreachable();
  }
}

static void tunerWindow(i32 resolutionWidth, i32 resolutionHeight)
{
  if (Global::tunerPlugin.system == SfxSystem::empty)
    return;

  if (Global::inputTuner.toggled)
  {
    Global::inputTuner.toggled = sfxWindow(Global::tunerPlugin, 0, resolutionWidth, resolutionHeight, 0
#ifdef SHR3D_SFX_PLUGIN
      , Global::effectChainWindowPluginHideUiTuner, Global::effectChainWindowPluginParentWindowTuner, Global::effectChainWindowPluginPositionTuner
#endif // SHR3D_SFX_PLUGIN
    );
  }
#ifdef SHR3D_SFX_PLUGIN
  else if (!Global::inputTuner.heldDownLastFrame && Global::effectChainWindowPluginParentWindowTuner != nullptr) // hack: plugin window was still open when tuner got closed by the F1 shortcut
  {
    Sfx::closeSfxPluginWindow(Global::tunerPlugin, 0);
    Window_::destoryPluginHostWindow(Global::effectChainWindowPluginParentWindowTuner);
    Global::effectChainWindowPluginParentWindowTuner = nullptr;
    Global::effectChainWindowPluginHideUiTuner = false;
  }
#endif // SHR3D_SFX_PLUGIN
}

static void midiFromAudioWindow()
{
  if (Global::tunerMidiPlugin.system == SfxSystem::empty)
    return;

  static bool wasOpen = Global::midiFromAudioWindowOpen;

  if (Global::midiFromAudioWindowOpen != wasOpen)
  {
    if (Global::midiFromAudioWindowOpen)
    {
#ifdef SHR3D_SFX_CORE
#ifdef SHR3D_SFX_PLUGIN
      if (!Sfx::sfxSystemIsPlugin(Global::tunerMidiPlugin.system))
#endif // SHR3D_SFX_PLUGIN
      {
        //Global::pluginWindowIndex = Global::tunerMidiPlugin;
        //Global::pluginWindowInstance = 0;
        //Global::pluginWindowEffectChainPluginIndex = -1;
      }
#endif // SHR3D_SFX_CORE
#if defined(SHR3D_SFX_CORE) && defined(SHR3D_SFX_PLUGIN)
      else
#endif // SHR3D_SFX_CORE && SHR3D_SFX_PLUGIN
#ifdef SHR3D_SFX_PLUGIN
      {
        Sfx::openSfxPluginWindow(Global::tunerMidiPlugin, 0, nullptr);
      }
#endif // SHR3D_SFX_PLUGIN
    }
    else
    {
#ifdef SHR3D_SFX_CORE
#ifdef SHR3D_SFX_PLUGIN
      if (!Sfx::sfxSystemIsPlugin(Global::tunerMidiPlugin.system))
#endif // SHR3D_SFX_PLUGIN
      {
        //Global::pluginWindowIndex.system = SfxSystem::empty;
      }
#endif // SHR3D_SFX_CORE
#if defined(SHR3D_SFX_CORE) && defined(SHR3D_SFX_PLUGIN)
      else
#endif // SHR3D_SFX_CORE && SHR3D_SFX_PLUGIN
#ifdef SHR3D_SFX_PLUGIN
      {
        Sfx::closeSfxPluginWindow(Global::tunerMidiPlugin, 0);
      }
#endif // SHR3D_SFX_PLUGIN
    }
    wasOpen = Global::midiFromAudioWindowOpen;
  }
}
#endif // SHR3D_SFX

static const char* getInstrumentFlagsName(Instrument instrumentFlags)
{
  switch (instrumentFlags)
  {
  case Instrument::LeadGuitar:
    return "Lead";
    //case Instrument::LeadGuitar | Instrument::Second:
    //  return "_lead2";
    //case Instrument::LeadGuitar | Instrument::Third:
    //  return "_lead3";
  case Instrument::RhythmGuitar:
    return "Rhythm";
    //case Instrument::RhythmGuitar | Instrument::Second:
    //  return "_rhythm2";
    //case Instrument::RhythmGuitar | Instrument::Third:
    //  return "_rhythm3";
  case Instrument::BassGuitar:
    return "Bass";
    //case Instrument::BassGuitar | Instrument::Second:
    //  return "_bass2";
    //case Instrument::BassGuitar | Instrument::Third:
    //  return "_bass3";
    //case Instrument::Combo:
    //  return "_combo";
    //case Instrument::Combo | Instrument::Second:
    //  return "_combo2";
    //case Instrument::Combo | Instrument::Third:
    //  return "_combo3";
  }

  ASSERT(false);

  return {};
}

static ArrangementIndex findArrangementIndexForCurrentInstrument(const std::vector<Arrangement::Info>& arrangementInfos, const Instrument instrumentFlags)
{
  const char* instrumentFlagsName = getInstrumentFlagsName(instrumentFlags);

  for (ArrangementIndex i = 0; i < ArrangementIndex(arrangementInfos.size()); ++i)
  {
    if (arrangementInfos[i].arrangementName == instrumentFlagsName)
      return i;
  }
  for (ArrangementIndex i = 0; i < ArrangementIndex(arrangementInfos.size()); ++i)
  {
    if (arrangementInfos[i].arrangementName.find(instrumentFlagsName) != std::string::npos)
      return i;
  }
  for (ArrangementIndex i = 0; i < ArrangementIndex(arrangementInfos.size()); ++i)
  {
    if (arrangementInfos[i].arrangementName == "Lead")
      return i;
  }
  for (ArrangementIndex i = 0; i < ArrangementIndex(arrangementInfos.size()); ++i)
  {
    if (arrangementInfos[i].arrangementName.find("Lead") != std::string::npos)
      return i;
  }
  for (ArrangementIndex i = 0; i < ArrangementIndex(arrangementInfos.size()); ++i)
  {
    if (arrangementInfos[i].arrangementName == "Rhythm")
      return i;
  }
  for (ArrangementIndex i = 0; i < ArrangementIndex(arrangementInfos.size()); ++i)
  {
    if (arrangementInfos[i].arrangementName.find("Rhythm") != std::string::npos)
      return i;
  }
  for (ArrangementIndex i = 0; i < ArrangementIndex(arrangementInfos.size()); ++i)
  {
    if (arrangementInfos[i].arrangementName == "Bass")
      return i;
  }
  for (ArrangementIndex i = 0; i < ArrangementIndex(arrangementInfos.size()); ++i)
  {
    if (arrangementInfos[i].arrangementName.find("Bass") != std::string::npos)
      return i;
  }
  for (ArrangementIndex i = 0; i < ArrangementIndex(arrangementInfos.size()); ++i)
  {
    if (arrangementInfos[i].arrangementName == "Combo")
      return i;
  }
  for (ArrangementIndex i = 0; i < ArrangementIndex(arrangementInfos.size()); ++i)
  {
    if (arrangementInfos[i].arrangementName.find("Combo") != std::string::npos)
      return i;
  }

  DEBUG_PRINT("Instrument not found in arrangement: %s, arrangementInfos.size()=%d\n", instrumentFlagsName, i32(arrangementInfos.size()));
  ASSERT(false);
  return 0;
}

static std::vector<i32> sortArrangementIndices(const std::vector<Arrangement::Info>& arrangementInfos)
{
  std::vector<i32> sortedIndex(arrangementInfos.size());
  i32 j = 0;

  for (i32 i = 0; i < i32(arrangementInfos.size()); ++i)
    if (arrangementInfos[i].arrangementName == "Lead")
    {
      sortedIndex[j++] = i;
      break;
    }

  for (i32 i = 0; i < i32(arrangementInfos.size()); ++i)
    if (arrangementInfos[i].arrangementName == "Lead2")
    {
      sortedIndex[j++] = i;
      break;
    }

  for (i32 i = 0; i < i32(arrangementInfos.size()); ++i)
    if (arrangementInfos[i].arrangementName == "Lead3")
    {
      sortedIndex[j++] = i;
      break;
    }

  for (i32 i = 0; i < i32(arrangementInfos.size()); ++i)
    if (arrangementInfos[i].arrangementName == "Rhythm")
    {
      sortedIndex[j++] = i;
      break;
    }

  for (i32 i = 0; i < i32(arrangementInfos.size()); ++i)
    if (arrangementInfos[i].arrangementName == "Rhythm2")
    {
      sortedIndex[j++] = i;
      break;
    }

  for (i32 i = 0; i < i32(arrangementInfos.size()); ++i)
    if (arrangementInfos[i].arrangementName == "Rhythm3")
    {
      sortedIndex[j++] = i;
      break;
    }

  for (i32 i = 0; i < i32(arrangementInfos.size()); ++i)
    if (arrangementInfos[i].arrangementName == "Bass")
    {
      sortedIndex[j++] = i;
      break;
    }

  for (i32 i = 0; i < i32(arrangementInfos.size()); ++i)
    if (arrangementInfos[i].arrangementName == "Bass2")
    {
      sortedIndex[j++] = i;
      break;
    }

  for (i32 i = 0; i < i32(arrangementInfos.size()); ++i)
    if (arrangementInfos[i].arrangementName == "Bass3")
    {
      sortedIndex[j++] = i;
      break;
    }

  for (i32 i = 0; i < i32(arrangementInfos.size()); ++i)
    if (arrangementInfos[i].arrangementName == "Combo")
    {
      sortedIndex[j++] = i;
      break;
    }

  for (i32 i = 0; i < i32(arrangementInfos.size()); ++i)
    if (arrangementInfos[i].arrangementName == "Combo2")
    {
      sortedIndex[j++] = i;
      break;
    }

  for (i32 i = 0; i < i32(arrangementInfos.size()); ++i)
    if (arrangementInfos[i].arrangementName == "Combo3")
    {
      sortedIndex[j++] = i;
      break;
    }

  for (i32 i = 0; i < i32(arrangementInfos.size()); ++i)
    if (arrangementInfos[i].arrangementName == "Vocals")
    {
      sortedIndex[j++] = i;
      break;
    }

  ASSERT(j == i32(arrangementInfos.size()));

  return sortedIndex;
}

static void loadSongInfoAndUpdateArrangementIndexForCurrentInstrument(SongIndex songIndex, ArrangementIndex& arrangementIndexForCurrentInstrument)
{
  DEBUG_PRINT("loadSongInfoAndUpdateArrangementIndexForCurrentInstrument()\n");

  const std::string& filePath = Global::songInfos[songIndex].filepath;

#ifdef SHR3D_SHRED_AND_PSARC
  if (File::type(filePath.c_str()) == File::Type::shred)
#endif // SHR3D_SHRED_AND_PSARC
#ifdef SHR3D_SHRED
  {
#ifndef PLATFORM_EMSCRIPTEN
    if (Global::shredInfos.find(songIndex) == Global::shredInfos.end())
    {
      const std::vector<u8> shredContent = File::read(filePath.c_str());
      Global::shredInfos[songIndex] = Shred::parse(shredContent.data(), shredContent.size());
      if (Global::songInfos[songIndex].loadState == Song::LoadState::cache)
        Global::songInfos[songIndex] = Song::ShredParser::loadSongInfo(filePath.c_str(), Global::shredInfos[songIndex]);

      const std::string toneFilePath = File::replaceExtension(filePath.c_str(), ".tones.ini");
      if (!File::exists(toneFilePath.c_str()))
        Song::ShredParser::loadSongTones(songIndex, Global::songInfos[songIndex], Global::shredInfos[songIndex]);
    }
#endif // PLATFORM_EMSCRIPTEN
  }
#endif // SHR3D_SHRED
#ifdef SHR3D_SHRED_AND_PSARC
  else
#endif // SHR3D_SHRED_AND_PSARC
#ifdef SHR3D_PSARC
  {
    ASSERT(File::type(filePath.c_str()) == File::Type::psarc);

    if (Global::psarcInfos.find(songIndex) == Global::psarcInfos.end())
    {
      const std::vector<u8> fileData = File::read(filePath.c_str());

      Global::psarcInfos[songIndex] = Psarc::parse(fileData.data(), fileData.size());

      if (Global::songInfos[songIndex].loadState == Song::LoadState::cache)
        Global::songInfos[songIndex] = Song::PsarcParser::loadSongInfo(Global::psarcInfos[songIndex], filePath);
    }
  }
#endif // SHR3D_PSARC

  arrangementIndexForCurrentInstrument = findArrangementIndexForCurrentInstrument(Global::songInfos[songIndex].arrangementInfos, Settings::applicationInstrument);
}

static std::string tuningToSortText(const Tuning& tuning)
{
  // F-Standard == dddddd
  // E-Standard == eeeeee
  // DropD == eeeeeg
  // D-Standard == gggggg
  // ...
  std::string text;
  for (i32 i = 0; i < ARRAY_SIZE(tuning.string); ++i)
    text += 'e' - tuning.string[i];
  return text;
}

#ifdef PLATFORM_EMSCRIPTEN
static void downloadWindow()
{
  if (nk_begin(ctx, "Download", nk_rect(500.0f * Settings::uiScale, 400.0f * Settings::uiScale, 800.0f * Settings::uiScale, 100.0f * Settings::uiScale), NK_WINDOW_BORDER | NK_WINDOW_CLOSABLE | NK_WINDOW_MOVABLE | NK_WINDOW_TITLE | NK_WINDOW_NO_SCROLLBAR))
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);

    nk_label(ctx, Global::songInfos[Global::downloadSongIndexInProgress].filepath.c_str(), NK_TEXT_LEFT);

    nk_size downloadBytesInProgress = Global::downloadBytesInProgress;
    nk_progress(ctx, &downloadBytesInProgress, Global::downloadBytesFinished, NK_FIXED);
  }
  nk_end(ctx);
}
#endif // PLATFORM_EMSCRIPTEN

static void fillToneBanksFromSongToneFile(const SongIndex songIndex, const std::vector<Arrangement::Info>& arrangementInfos)
{
  { // add empty tonebanks for each arrangement
    for (const auto & arrangementInfo : arrangementInfos)
    {
      if (Global::songStats[arrangementInfo.persistentId].sfxBankIndex == -1)
      {
        Global::songStats[arrangementInfo.persistentId].sfxBankIndex = Global::firstEmptyNegativeToneBank;
        Global::bankIndex2SongIndex[Global::firstEmptyNegativeToneBank] = songIndex;
        --Global::firstEmptyNegativeToneBank;
      }
    }
  }

  const std::string& songFilePath = Global::songInfos.at(songIndex).filepath;
  const std::string toneFilePath = File::replaceExtension(songFilePath.c_str(), ".tones.ini");
  if (File::exists(toneFilePath.c_str()))
  {
    const std::vector<u8> toneFileData = File::read(toneFilePath.c_str());
    const std::map<std::string, std::vector<Ini::KeyValue>> iniContent = Ini::loadIniContent_keepKeyOrder(reinterpret_cast<const char*>(toneFileData.data()), toneFileData.size());
    if (iniContent.size() == 1)
    {
      Tones::loadSongToneFile(Global::songInfos[songIndex].arrangementInfos, iniContent);
    }
  }
}

static void prettyPrintSeconds(const i32 seconds, char prettyString[32])
{
  const i32 h = seconds / 3600;
  const i32 m = (seconds % 3600) / 60;
  const i32 s = seconds % 60;

  if (h > 0)
  {
    sprintf(prettyString, "%02d:%02d:%02d", h, m, s);
  }
  else
  {
    sprintf(prettyString, "%02d:%02d", m, s);
  }
}

static void addSongEntry(const SongIndex songIndex, i32& expandedIndex, f32& expandedHeight)
{
  ASSERT(songIndex >= 0);

  Song::Info& songInfo = Global::songInfos[songIndex];

  const bool isExpanded = expandedIndex == songIndex;
  if (isExpanded)
    nk_layout_row_dynamic(ctx, 258.0f * Settings::uiScale + expandedHeight, 1);
  else
    nk_layout_row_dynamic(ctx, 258.0f * Settings::uiScale, 1);

  if (nk_group_begin(ctx, "top", NK_WINDOW_NO_SCROLLBAR | NK_WINDOW_BORDER))
  {
    nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 256.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 120.0f * Settings::uiScale);
    nk_layout_row_template_push_dynamic(ctx);
    nk_layout_row_template_push_static(ctx, 120.0f * Settings::uiScale);
    nk_layout_row_template_end(ctx);

    if (songInfo.albumCoverTexture == 0)
    {
#ifdef PLATFORM_EMSCRIPTEN
      if (!songInfo.albumCoverUrlReadyToDownload.empty())
      {
        Collection::downloadAlbumCoverLoadAsTexture(&songInfo);
      }
#else // PLATFORM_EMSCRIPTEN
      if (Global::songInfos.find(songIndex) != Global::songInfos.end())
      {
        const char* filename_ = File::filename(Global::songInfos[songIndex].filepath.c_str());

        const std::string cachedDds = Settings::pathCache + File::replaceExtension(filename_, ".dds");
#ifdef SHR3D_SHRED
        const std::string cachedPng = File::replaceExtension(cachedDds.c_str(), ".png");
#endif // SHR3D_SHRED

        if (File::exists(cachedDds.c_str()))
        {
          const std::vector<u8> ddsData = File::read(cachedDds.c_str());
          songInfo.albumCoverTexture = Texture::openGlLoadTextureDds(ddsData.data(), i32(ddsData.size()));
        }
#ifdef SHR3D_SHRED
        else if (File::exists(cachedPng.c_str()))
        {
          const std::vector<u8> pngData = File::read(cachedPng.c_str());
          songInfo.albumCoverTexture = Texture::openGlLoadTexturePng(pngData.data(), i32(pngData.size()));
        }
#endif // SHR3D_SHRED
#ifdef SHR3D_SHRED
        else if (Global::shredInfos.find(songIndex) != Global::shredInfos.end())
        {
          songInfo.albumCoverTexture = Texture::openGlLoadTexturePng(Global::shredInfos[songIndex].albumPng.data(), i32(Global::shredInfos[songIndex].albumPng.size()));
        }
#endif // SHR3D_SHRED
#ifdef SHR3D_PSARC
        else if (songInfo.psarcAlbumCover256_tocIndex >= 1 && Global::psarcInfos.find(songIndex) != Global::psarcInfos.end())
        {
          songInfo.albumCoverTexture = Texture::openGlLoadTextureDds(Global::psarcInfos[songIndex].tocEntries[songInfo.psarcAlbumCover256_tocIndex].content.data(), i32(Global::psarcInfos[songIndex].tocEntries[songInfo.psarcAlbumCover256_tocIndex].content.size()));
        }
#endif // SHR3D_PSARC
        else
        {
          DEBUG_PRINT("missing cached album cover %s\n", cachedDds.c_str());
        }
      }
#endif // PLATFORM_EMSCRIPTEN
    }

    if (songInfo.albumCoverTexture != 0)
    {
      nk_command_buffer* canvas = nk_window_get_canvas(ctx);
      struct nk_rect window_content_region = nk_window_get_content_region(ctx);
      window_content_region.w = 256.0f * Settings::uiScale;
      window_content_region.h = 256.0f * Settings::uiScale;
      struct nk_image albumCover = nk_image_id(i32(songInfo.albumCoverTexture));
      nk_draw_image(canvas, window_content_region, &albumCover, nk_rgba(255, 255, 255, 255));
    }

    // arrangementIndex is likely wrong when using only the cached arrangement where only one Lead, Rhythm and Bass are available.
    // There might be additional arrangements that are available when the real arrangement is read.
    // Thas is why when loadPsarcAndSongInfoArrangementOnly is called, the arrangementIndex needs to be overwritten with the real arrangementIndex;
    ArrangementIndex arrangementIndexForCurrentInstrument = findArrangementIndexForCurrentInstrument(songInfo.arrangementInfos, Settings::applicationInstrument);

#ifdef PLATFORM_EMSCRIPTEN
    // Emscripten does async downloads. We do need to remember the action that we must do once the download is finished.
    static enum struct DownloadFinishedAction : i8
    {
      none,
      preview,
      more,
      play,
      fxChain
    } downloadFinishedAction;
#endif // PLATFORM_EMSCRIPTEN

    nk_spacing(ctx, 1);
    nk_label(ctx, "Title:", NK_TEXT_LEFT);
    nk_label(ctx, songInfo.songName.c_str(), NK_TEXT_LEFT);
    if (nk_button_label(ctx, "Preview")
#ifdef PLATFORM_EMSCRIPTEN
      || songIndex == Global::downloadSongIndexFinished && downloadFinishedAction == DownloadFinishedAction::preview // download finished?
#endif // PLATFORM_EMSCRIPTEN
      )
    {
#ifdef PLATFORM_EMSCRIPTEN
      if (Global::shredInfos.find(songIndex) == Global::shredInfos.end() && Global::psarcInfos.find(songIndex) == Global::psarcInfos.end())
      {
        downloadFinishedAction = DownloadFinishedAction::preview;
        Collection::downloadFullSongWithProgress(songIndex);
      }
      else
#endif // PLATFORM_EMSCRIPTEN
      {
        loadSongInfoAndUpdateArrangementIndexForCurrentInstrument(songIndex, arrangementIndexForCurrentInstrument);
        Player::playPreview(songIndex);
#ifdef PLATFORM_EMSCRIPTEN
        Global::downloadSongIndexFinished = -1;
        downloadFinishedAction = DownloadFinishedAction::none;
#endif // PLATFORM_EMSCRIPTEN
      }
    }
    nk_spacing(ctx, 1);
    nk_label(ctx, "Artist:", NK_TEXT_LEFT);
    nk_label(ctx, songInfo.artistName.c_str(), NK_TEXT_LEFT);
    nk_spacing(ctx, 2);
    nk_label(ctx, "Album:", NK_TEXT_LEFT);
    nk_label(ctx, songInfo.albumName.c_str(), NK_TEXT_LEFT);
    nk_spacing(ctx, 2);
    nk_label(ctx, "Year:", NK_TEXT_LEFT);
    char songYearStr[8];
    sprintf(songYearStr, "%d", songInfo.songYear);
    nk_label(ctx, songYearStr, NK_TEXT_LEFT);
    nk_spacing(ctx, 2);
    nk_label(ctx, "Length:", NK_TEXT_LEFT);
    {
      const i32 songLengthInSeconds = i32(TimeNS_To_Seconds(songInfo.shredDelayBegin + songInfo.songLength + songInfo.shredDelayEnd));
      char songLengthText[32];
      prettyPrintSeconds(songLengthInSeconds, songLengthText);

      nk_label(ctx, songLengthText, NK_TEXT_LEFT);
    }
    nk_spacing(ctx, 2);

    {
      u64 lastPlayedTimestamp = 0;
      TimeNS songTimePlayedNS = 0;
      for (const Arrangement::Info& arrangementInfo : songInfo.arrangementInfos)
      {
        const auto it = Global::songStats.find(arrangementInfo.persistentId);
        if (it != Global::songStats.cend())
        {
          lastPlayedTimestamp = max_(lastPlayedTimestamp, it->second.lastPlayed);
          songTimePlayedNS += it->second.timePlayed;
        }
      }

      const i32 songTimePlayedInSeconds = i32(TimeNS_To_Seconds(songTimePlayedNS));
      char timePlayedText[32];
      prettyPrintSeconds(songTimePlayedInSeconds, timePlayedText);

      {
        nk_label(ctx, "Last Played:", NK_TEXT_LEFT);
        if (lastPlayedTimestamp == 0)
        {
          nk_label(ctx, "Never", NK_TEXT_LEFT);
        }
        else
        {
          const time_t timeDiff = Global::startupTimestamp - lastPlayedTimestamp;
          if (timeDiff < 86400) // 24 hours
            nk_label(ctx, "Today", NK_TEXT_LEFT);
          else if (timeDiff < 604800) // 7 days
            nk_label(ctx, "A few days ago", NK_TEXT_LEFT);
          else if (timeDiff < 2592000) // ~30 days
            nk_label(ctx, "A week ago", NK_TEXT_LEFT);
          else if (timeDiff < 31536000) // ~365 days
            nk_label(ctx, "Over a year ago", NK_TEXT_LEFT);
        }
      }
      nk_spacing(ctx, 2);
      nk_label(ctx, "Played:", NK_TEXT_LEFT);
      nk_label(ctx, timePlayedText, NK_TEXT_LEFT);
    }

    nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 256.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 120.0f * Settings::uiScale);
    nk_layout_row_template_push_dynamic(ctx);
    nk_layout_row_template_push_static(ctx, 80.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 120.0f * Settings::uiScale);
    nk_layout_row_template_end(ctx);

    {
      if (!isExpanded)
      {
        nk_spacing(ctx, 1);
        {
          nk_label(ctx, "Tuning:", NK_TEXT_LEFT);

          if (songInfo.arrangementInfos[arrangementIndexForCurrentInstrument].centOffset == 0.0f)
          {
            nk_label(ctx, Song::tuningName(songInfo.arrangementInfos[arrangementIndexForCurrentInstrument].tuning), NK_TEXT_LEFT);
          }
          else
          {
            const f32 frequency = 440.0f * exp2f(songInfo.arrangementInfos[arrangementIndexForCurrentInstrument].centOffset / 1200.0f);
            const std::string tuningNameAndCents = std::string(Song::tuningName(songInfo.arrangementInfos[arrangementIndexForCurrentInstrument].tuning)) + ", A4@" + to_string(frequency);
            nk_label(ctx, tuningNameAndCents.c_str(), NK_TEXT_LEFT);
          }
        }
        nk_spacing(ctx, 1);
        if (nk_button_symbol_label(ctx, NK_SYMBOL_TRIANGLE_DOWN, "More", NK_TEXT_RIGHT)
#ifdef PLATFORM_EMSCRIPTEN
          || songIndex == Global::downloadSongIndexFinished && downloadFinishedAction == DownloadFinishedAction::more // download finished?
#endif // PLATFORM_EMSCRIPTEN
          )
        {
#ifdef PLATFORM_EMSCRIPTEN
          if (Global::shredInfos.find(songIndex) == Global::shredInfos.end() && Global::psarcInfos.find(songIndex) == Global::psarcInfos.end())
          {
            downloadFinishedAction = DownloadFinishedAction::more;
            Collection::downloadFullSongWithProgress(songIndex);
          }
          else
#endif // PLATFORM_EMSCRIPTEN
          {
            loadSongInfoAndUpdateArrangementIndexForCurrentInstrument(songIndex, arrangementIndexForCurrentInstrument);
            fillToneBanksFromSongToneFile(songIndex, songInfo.arrangementInfos);
            expandedIndex = songIndex;
            expandedHeight = max_((49.0f * f32(songInfo.arrangementInfos.size() - 1)) * Settings::uiScale, 0.0f);
#ifdef PLATFORM_EMSCRIPTEN
            Global::downloadSongIndexFinished = -1;
            downloadFinishedAction = DownloadFinishedAction::none;
#endif // PLATFORM_EMSCRIPTEN
          }
        }
        nk_spacing(ctx, 2);
        {
          SfxBankIndex sfxBankIndex = -1;
          const bool songStatsExists = Global::songStats.contains(songInfo.arrangementInfos[arrangementIndexForCurrentInstrument].persistentId);
          if (songStatsExists)
            sfxBankIndex = Global::songStats.at(songInfo.arrangementInfos[arrangementIndexForCurrentInstrument].persistentId).sfxBankIndex;

          const SfxBankIndex oldSfxBankIndex = sfxBankIndex;
          nk_property_int(ctx, "#Tone Bank:", Global::firstEmptyNegativeToneBank + 1, &sfxBankIndex, I32::max / Const::sfxToneTonesPerBank, 1, 1);
          if (oldSfxBankIndex != sfxBankIndex)
          {
            Global::songStats[songInfo.arrangementInfos[arrangementIndexForCurrentInstrument].persistentId].sfxBankIndex = sfxBankIndex;
          }
        }
        if (nk_button_label(ctx, "FX Chain")
#ifdef PLATFORM_EMSCRIPTEN
          || songIndex == Global::downloadSongIndexFinished && downloadFinishedAction == DownloadFinishedAction::fxChain // download finished?
#endif // PLATFORM_EMSCRIPTEN
          )
        {
#ifdef PLATFORM_EMSCRIPTEN
          if (Global::shredInfos.find(songIndex) == Global::shredInfos.end() && Global::psarcInfos.find(songIndex) == Global::psarcInfos.end())
          {
            downloadFinishedAction = DownloadFinishedAction::fxChain;
            Collection::downloadFullSongWithProgress(songIndex);
          }
          else
#endif // PLATFORM_EMSCRIPTEN
          {
            loadSongInfoAndUpdateArrangementIndexForCurrentInstrument(songIndex, arrangementIndexForCurrentInstrument);
            fillToneBanksFromSongToneFile(songIndex, songInfo.arrangementInfos);
            const SfxBankIndex sfxBankIndex = Global::songStats.at(songInfo.arrangementInfos[arrangementIndexForCurrentInstrument].persistentId).sfxBankIndex;

            Global::activeSfxToneIndex = sfxBankIndex * Const::sfxToneTonesPerBank;
            Global::inputSfxChain.toggled = true;
#ifdef PLATFORM_EMSCRIPTEN
            Global::downloadSongIndexFinished = -1;
            downloadFinishedAction = DownloadFinishedAction::none;
#endif // PLATFORM_EMSCRIPTEN
          }
        }
        if (songInfo.arrangementInfos.size() >= 1)
        {
          const std::string arrangementName = songInfo.arrangementInfos[arrangementIndexForCurrentInstrument].arrangementName;

          if (nk_button_label(ctx, arrangementName.c_str())
#ifdef PLATFORM_EMSCRIPTEN
            || songIndex == Global::downloadSongIndexFinished && downloadFinishedAction == DownloadFinishedAction::play // download finished?
#endif // PLATFORM_EMSCRIPTEN
            ) // THE PLAY BUTTON
          {

#if defined(PLATFORM_WINDOWS) || defined(PLATFORM_LINUX)
            Global::clipboardLastPlayedArrangementName = arrangementName;
#endif // PLATFORM_WINDOWS || PLATFORM_LINUX
#ifdef PLATFORM_EMSCRIPTEN
            if (Global::shredInfos.find(songIndex) == Global::shredInfos.end() && Global::psarcInfos.find(songIndex) == Global::psarcInfos.end())
            {
              downloadFinishedAction = DownloadFinishedAction::play;
              Collection::downloadFullSongWithProgress(songIndex);
            }
            else
#endif // PLATFORM_EMSCRIPTEN
            {
              loadSongInfoAndUpdateArrangementIndexForCurrentInstrument(songIndex, arrangementIndexForCurrentInstrument);
              fillToneBanksFromSongToneFile(songIndex, songInfo.arrangementInfos);
              Player::playSong(songIndex, arrangementIndexForCurrentInstrument);
#ifdef PLATFORM_EMSCRIPTEN
              Global::downloadSongIndexFinished = -1;
              downloadFinishedAction = DownloadFinishedAction::none;
#endif // PLATFORM_EMSCRIPTEN
            }
          }
        }
        else
        {
          nk_spacing(ctx, 1);
        }
      }
      else
      {
        const std::vector<ArrangementIndex> sortedIndices = sortArrangementIndices(songInfo.arrangementInfos);
        for (i32 i = 0; i < i32(sortedIndices.size()); ++i)
        {
          const ArrangementIndex arrangementIndex = sortedIndices[i];
          if (songInfo.arrangementInfos[arrangementIndex].arrangementName == "Vocals")
            continue;

          nk_spacing(ctx, 1);
          nk_label(ctx, "Tuning:", NK_TEXT_LEFT);

          if (songInfo.arrangementInfos[arrangementIndex].centOffset == 0.0f)
          {
            nk_label(ctx, Song::tuningName(songInfo.arrangementInfos[arrangementIndex].tuning), NK_TEXT_LEFT);
          }
          else
          {
            const f32 frequency = 440.0f * exp2f(songInfo.arrangementInfos[arrangementIndex].centOffset / 1200.0f);
            const std::string tuningNameAndCents = std::string(Song::tuningName(songInfo.arrangementInfos[arrangementIndex].tuning)) + ", A4@" + to_string(frequency);
            nk_label(ctx, tuningNameAndCents.c_str(), NK_TEXT_LEFT);
          }
          nk_spacing(ctx, 1);
          if (i == 0)
          {
            if (nk_button_symbol_label(ctx, NK_SYMBOL_TRIANGLE_UP, "Less", NK_TEXT_RIGHT))
            {
              expandedIndex = -1;
            }
            nk_spacing(ctx, 1);
          }
          else
          {
            nk_spacing(ctx, 2);
          }

          {
            SfxBankIndex sfxBankIndex = -1;
            const bool songStatsExists = Global::songStats.contains(songInfo.arrangementInfos[arrangementIndex].persistentId);
            if (songStatsExists)
              sfxBankIndex = Global::songStats[songInfo.arrangementInfos[arrangementIndex].persistentId].sfxBankIndex;

            nk_spacing(ctx, 1);

            const SfxBankIndex oldSfxBankIndex = sfxBankIndex;
            nk_property_int(ctx, "#Tone Bank:", Global::firstEmptyNegativeToneBank + 1, &sfxBankIndex, I32::max / Const::sfxToneTonesPerBank, 1, 1);
            if (oldSfxBankIndex != sfxBankIndex)
            {
              Global::songStats[songInfo.arrangementInfos.at(arrangementIndex).persistentId].sfxBankIndex = sfxBankIndex;
            }

            if (nk_button_label(ctx, "FX Chain"))
            {
              fillToneBanksFromSongToneFile(songIndex, songInfo.arrangementInfos);
              const SfxBankIndex sfxBankIndex_ = Global::songStats.at(songInfo.arrangementInfos[arrangementIndex].persistentId).sfxBankIndex;
              Global::activeSfxToneIndex = sfxBankIndex_ * Const::sfxToneTonesPerBank;
              Global::inputSfxChain.toggled = true;
            }
          }

          const std::string arrangementName = songInfo.arrangementInfos[arrangementIndex].arrangementName;

          if (nk_button_label(ctx, arrangementName.c_str()))
          {
#if defined(PLATFORM_WINDOWS) || defined(PLATFORM_LINUX)
            Global::clipboardLastPlayedArrangementName = arrangementName;
#endif // PLATFORM_WINDOWS || PLATFORM_LINUX
            Player::playSong(songIndex, arrangementIndex);
          }
        }
      }
    }

    nk_group_end(ctx);
  }
}

static void songWindow(i32 resolutionWidth, i32 resolutionHeight)
{
  if (nk_begin(ctx, "Songs", calcWindowRectCentered(resolutionWidth, resolutionHeight, 210, -85, 1120, 663), NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE | NK_WINDOW_MINIMIZABLE | NK_WINDOW_TITLE | NK_WINDOW_NO_SCROLLBAR))
  {
    nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 45.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 60.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 45.0f * Settings::uiScale);
    nk_layout_row_template_push_dynamic(ctx);
    nk_layout_row_template_push_static(ctx, 40.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 100.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
    nk_layout_row_template_end(ctx);

    { // current Instrument Selection
      nk_style_item normal_button_color = ctx->style.button.normal;
      if (Settings::applicationInstrument == Instrument::LeadGuitar)
        ctx->style.button.normal = ctx->style.button.active;
      else
        ctx->style.button.normal = normal_button_color;
      if (nk_button_label(ctx, "Lead"))
        Settings::applicationInstrument = Instrument::LeadGuitar;
      if (Settings::applicationInstrument == Instrument::RhythmGuitar)
        ctx->style.button.normal = ctx->style.button.active;
      else
        ctx->style.button.normal = normal_button_color;
      if (nk_button_label(ctx, "Rhythm"))
        Settings::applicationInstrument = Instrument::RhythmGuitar;
      if (Settings::applicationInstrument == Instrument::BassGuitar)
        ctx->style.button.normal = ctx->style.button.active;
      else
        ctx->style.button.normal = normal_button_color;
      if (nk_button_label(ctx, "Bass"))
        Settings::applicationInstrument = Instrument::BassGuitar;
      ctx->style.button.normal = normal_button_color;
    }

    static char songSearchText[256] = "";
    static i32 songSearchTextLength = 0;
    nk_edit_string(ctx, NK_EDIT_SIMPLE, songSearchText, &songSearchTextLength, sizeof(songSearchText), nk_filter_default);
    const std::string searchText(songSearchText, songSearchTextLength);

    nk_label(ctx, std::to_string(Global::songInfos.size()).c_str(), NK_TEXT_LEFT);

    static SongSortMode songSortMode = SongSortMode::unsorted;
    static std::map<std::string, SongIndex> sortedSongIndecies;
    {
      static const char* sortModeNames[] = {
        "Unsorted",
        "Tuning",
        "SongName",
        "ArtistName",
        "Album",
        "Year"
      };
      const SongSortMode previousSongSortMode = songSortMode;
      songSortMode = SongSortMode(nk_combo(ctx, sortModeNames, ARRAY_SIZE(sortModeNames), to_underlying_(songSortMode), 25, nk_vec2(200, 200)));
      if (previousSongSortMode != songSortMode)
      {
        sortedSongIndecies.clear();

        switch (songSortMode)
        {
        case SongSortMode::tuning:
          for (SongIndex songIndex = 0; songIndex < SongIndex(Global::songInfos.size()); ++songIndex)
          {
            const Song::Info& songInfo = Global::songInfos.at(songIndex);
            const ArrangementIndex arrangementIndex = findArrangementIndexForCurrentInstrument(songInfo.arrangementInfos, Settings::applicationInstrument);
            char songIndexStr[12];
            sprintf(songIndexStr, "%d", songIndex);
            sortedSongIndecies[tuningToSortText(songInfo.arrangementInfos[arrangementIndex].tuning) + songInfo.songName + songIndexStr] = songIndex;
          }
          break;
        case SongSortMode::songName:
          for (SongIndex songIndex = 0; songIndex < SongIndex(Global::songInfos.size()); ++songIndex)
          {
            const Song::Info& songInfo = Global::songInfos.at(songIndex);
            char songIndexStr[12];
            sprintf(songIndexStr, "%d", songIndex);
            sortedSongIndecies[songInfo.songName + songIndexStr] = songIndex;
          }
          break;
        case SongSortMode::artistName:
          for (SongIndex songIndex = 0; songIndex < SongIndex(Global::songInfos.size()); ++songIndex)
          {
            const Song::Info& songInfo = Global::songInfos.at(songIndex);
            char songIndexStr[12];
            sprintf(songIndexStr, "%d", songIndex);
            sortedSongIndecies[songInfo.artistName + songInfo.songName + songIndexStr] = songIndex;
          }
          break;
        case SongSortMode::albumName:
          for (SongIndex songIndex = 0; songIndex < SongIndex(Global::songInfos.size()); ++songIndex)
          {
            const Song::Info& songInfo = Global::songInfos.at(songIndex);
            char songIndexStr[12];
            sprintf(songIndexStr, "%d", songIndex);
            sortedSongIndecies[songInfo.albumName + songInfo.songName + songIndexStr] = songIndex;
          }
          break;
        case SongSortMode::year:
          for (SongIndex songIndex = 0; songIndex < SongIndex(Global::songInfos.size()); ++songIndex)
          {
            const Song::Info& songInfo = Global::songInfos.at(songIndex);
            char songIndexStr[12];
            sprintf(songIndexStr, "%d", songIndex);
            char songYearStr[12];
            sprintf(songYearStr, "%d", songInfo.songYear);
            sortedSongIndecies[songYearStr + songInfo.songName + songIndexStr] = songIndex;
          }
          break;
        case SongSortMode::unsorted:
          break;
        default:
          unreachable();
        }
      }
    }
    if (nk_button_label(ctx, "+"))
    {
#ifdef PLATFORM_EMSCRIPTEN
      EM_ASM(
        let file_selector = document.createElement('input');
      file_selector.setAttribute('type', 'file');
      file_selector.setAttribute('onchange', 'open_file(event)');
#ifdef SHR3D_SHRED_AND_PSARC
      file_selector.setAttribute('accept', '.shred,.psarc');
#else // SHR3D_SHRED_AND_PSARC
#ifdef SHR3D_SHRED
      file_selector.setAttribute('accept', '.shred');
#endif // SHR3D_SHRED
#ifdef SHR3D_PSARC
      file_selector.setAttribute('accept', '.psarc');
#endif // SHR3D_PSARC
#endif // SHR3D_SHRED_AND_PSARC
      file_selector.click();
        );
#else // PLATFORM_EMSCRIPTEN
      const std::vector<std::string> filepaths = File::openFileDialog(L""
#ifdef SHR3D_SHRED_AND_PSARC
        "Shr3D (*.shred, *.psarc)\0*.shred;*.psarc\0"
#endif // SHR3D_SHRED_AND_PSARC
#ifdef SHR3D_SHRED
        "Shred (*.shred)\0*.shred\0"
#endif // SHR3D_SHRED
#ifdef SHR3D_PSARC
        "Psarc (*.psarc)\0*.psarc\0"
#endif // SHR3D_PSARC
        "All (*.*)\0*.*\0", true);
      for (const std::string& filepath : filepaths)
      {
        const std::vector<u8> songData = File::read(filepath.c_str());
        Collection::addSongFile(filepath, songData.data(), songData.size());
      }
#endif // PLATFORM_EMSCRIPTEN
    }
    if (nk_button_label(ctx, "?"))
    {
      Global::uiAboutWindowOpen = !Global::uiAboutWindowOpen;
    }

    nk_layout_row_dynamic(ctx, nk_window_get_size(ctx).y - 72.0f, 1);
    if (nk_group_begin(ctx, "", NK_WINDOW_BORDER))
    {
#ifdef SHR3D_COLLECTION_WORKER_THREAD
      const std::lock_guard lock(Global::songInfosMutex);
#endif // SHR3D_COLLECTION_WORKER_THREAD

      static SongIndex expandedIndex = -1;
      static f32 expandedHeight = 0;

#if defined(PLATFORM_WINDOWS) || defined(__ANDROID__)
      if (Global::songInfos.size() == 0)
      {
        nk_layout_row_dynamic(ctx, 60.0f * Settings::uiScale, 1);

        nk_label(ctx, "Welcome to Shr3D v" VERSION_STR, NK_TEXT_CENTERED);

#ifdef __ANDROID__
        nk_label(ctx, "Your song list is empty. "
#ifdef PLATFORM_PICO_4
          ".psarc and .shred the downloads directory should be added automatically."
#else // PLATFORM_PICO_4
          "Add .psarc or .shred files by using the '+' in the upper right corner."
#endif // PLATFORM_PICO_4
          , NK_TEXT_CENTERED);
#endif // __ANDROID__

#ifdef PLATFORM_WINDOWS
        nk_layout_row_template_begin(ctx, 40.0f * Settings::uiScale);
        nk_layout_row_template_push_dynamic(ctx);
        nk_layout_row_template_push_static(ctx, 160.0f * Settings::uiScale);
        nk_layout_row_template_push_static(ctx, 160.0f * Settings::uiScale);
        nk_layout_row_template_end(ctx);

        nk_label_wrap(ctx, "Your song list is empty. Copy your song files into the fitting directories:");
#ifdef SHR3D_SHRED
        if (nk_button_label(ctx, "Show shred directory"))
        {
          std::string filePath = Settings::pathShred;
          std::replace(filePath.begin(), filePath.end(), '/', '\\');
          File::shellExecute(filePath.c_str());
        }
#endif // SHR3D_SHRED
#ifdef SHR3D_PSARC
        if (nk_button_label(ctx, "Show psarc directory"))
        {
          std::string filePath = Settings::pathPsarc;
          std::replace(filePath.begin(), filePath.end(), '/', '\\');
          File::shellExecute(filePath.c_str());
        }
#endif // SHR3D_PSARC
#endif // PLATFORM_WINDOWS

        nk_layout_row_dynamic(ctx, 60.0f * Settings::uiScale, 1);

        nk_layout_row_template_begin(ctx, 40.0f * Settings::uiScale);
        nk_layout_row_template_push_dynamic(ctx);
        nk_layout_row_template_push_static(ctx, 140.0f * Settings::uiScale);
        nk_layout_row_template_push_dynamic(ctx);
        nk_layout_row_template_end(ctx);

        nk_spacer(ctx);
        if (nk_button_label(ctx, "Visit shr3d.app"))
          File::shellExecute("https://shr3d.app/");
        nk_spacing(ctx, 2);
        //if (nk_button_label(ctx, "Join [Matrix]"))
        //  File::shellExecute("https://matrix.to/#/#space:matrix.shr3d.app");
        if (nk_button_label(ctx, "Join Discord"))
          File::shellExecute("https://discord.gg/QfmXCT5wRm");
      }
      else
#endif // PLATFORM_WINDOWS || __ANDROID__
      {
        if (songSortMode == SongSortMode::unsorted)
        {
          for (SongIndex songIndex = 0; songIndex < SongIndex(Global::songInfos.size()); ++songIndex)
          {
            const Song::Info& songInfo = Global::songInfos[songIndex];

            if (filterSongOut(songInfo, searchText.c_str()))
              continue;

            addSongEntry(songIndex, expandedIndex, expandedHeight);
          }
        }
        else
        {
          for (auto it = sortedSongIndecies.begin(); it != sortedSongIndecies.end(); ++it)
          {
            const SongIndex songIndex = it->second;
            const Song::Info& songInfo = Global::songInfos[songIndex];

            if (filterSongOut(songInfo, searchText.c_str()))
              continue;

            addSongEntry(it->second, expandedIndex, expandedHeight);
          }
        }
      }

      nk_group_end(ctx);
    }
  }
  nk_end(ctx);
}

static void toolbarWindow(i32 resolutionWidth, i32 resolutionHeight)
{
  if (nk_begin(ctx, "Toolbar", calcWindowRectCentered(resolutionWidth, resolutionHeight, 0, 334, 1536, 140), NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE | NK_WINDOW_MINIMIZABLE | NK_WINDOW_NO_SCROLLBAR))
  {
    nk_layout_row_template_begin(ctx, 54.0f * Settings::uiScale);
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // Tuner
#ifdef SHR3D_SFX
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // FX Chains
#endif // SHR3D_SFX
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // Toggle Metronome
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // Switch Instrument
    nk_layout_row_template_push_static(ctx, 20.0f * Settings::uiScale); // Spacer
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // Quick Repeat
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // Level
#ifdef SHR3D_ENVIRONMENT_MILK
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // Milk Background
#endif // SHR3D_ENVIRONMENT_MILK
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // Strum Direction
    nk_layout_row_template_push_static(ctx, 20.0f * Settings::uiScale); // Spacer
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // Debug Info
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // Noclip
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // Freeze Highway
#if defined(PLATFORM_WINDOWS) || defined(PLATFORM_LINUX)
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // Wireframe
#endif // PLATFORM_WINDOWS || PLATFORM_LINUX
    nk_layout_row_template_push_static(ctx, 20.0f * Settings::uiScale); // Spacer
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // Eject
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // Mute
#ifdef SHR3D_RECORDER
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // Recorder
#endif // SHR3D_RECORDER
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // Fullscreen
    nk_layout_row_template_push_static(ctx, 20.0f * Settings::uiScale); // Spacer
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // Manual Tone Switch
#ifdef SHR3D_SFX_CORE_HEXFIN
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // Highwy Tuner
#endif // SHR3D_SFX_CORE_HEXFIN
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // Highway VU Meter
    nk_layout_row_template_push_static(ctx, 20.0f * Settings::uiScale); // Spacer
#ifdef SHR3D_COOP
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // Co-Op
#endif // SHR3D_COOP
#ifdef SHR3D_OPENXR_PCVR
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // Virtual Reality
#endif // SHR3D_OPENXR_PCVR
#ifndef PLATFORM_EMSCRIPTEN
    nk_layout_row_template_push_dynamic(ctx);
    nk_layout_row_template_push_static(ctx, 54.0f * Settings::uiScale); // Quit
#endif // PLATFORM_EMSCRIPTEN
    nk_layout_row_template_end(ctx);

    if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
      nk_tooltip(ctx, "Tuner (F1)");
    {
      static struct nk_image tuningFork = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::tuningFork, 96, 96, GL_RGBA));
      nk_style_item normal_button_color = ctx->style.button.normal;
      if (Global::inputTuner.toggled)
        ctx->style.button.normal = ctx->style.button.active;
      if (nk_button_image(ctx, tuningFork))
        Global::inputTuner.toggled = !Global::inputTuner.toggled;
      ctx->style.button.normal = normal_button_color;
    }

#ifdef SHR3D_SFX
    if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
      nk_tooltip(ctx, "FX Chains (F2)");
    {
      static struct nk_image guitarAmp = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::guitarAmp, 96, 96, GL_RGBA));
      nk_style_item normal_button_color = ctx->style.button.normal;
      if (Global::inputSfxChain.toggled)
        ctx->style.button.normal = ctx->style.button.active;
      if (nk_button_image(ctx, guitarAmp))
        Global::inputSfxChain.toggled = !Global::inputSfxChain.toggled;
      ctx->style.button.normal = normal_button_color;
    }
#else
    nk_spacer(ctx);
#endif // SHR3D_SFX

    if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
      nk_tooltip(ctx, "Toggle Metronome (F3)");
    {
      static struct nk_image metronome = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::metronome, 96, 96, GL_RGBA));
      nk_style_item normal_button_color = ctx->style.button.normal;
      if (Settings::metronomeEnabled)
        ctx->style.button.normal = ctx->style.button.active;
      if (nk_button_image(ctx, metronome))
        Settings::metronomeEnabled = !Settings::metronomeEnabled;
      ctx->style.button.normal = normal_button_color;
    }

    if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
      nk_tooltip(ctx, "Switch Instrument (F4)");
    {
      static struct nk_image guitarStrings = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::guitarStrings, 96, 96, GL_RGBA));
      if (nk_button_image(ctx, guitarStrings))
        Global::inputSwitchInstrument.clickedInUiOrMidi = true;
    }

    nk_spacer(ctx);

    if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
      nk_tooltip(ctx, "Quick Repeat (F5)");
    {
      static struct nk_image repeat = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::repeat, 96, 96, GL_RGBA));
      if (nk_button_image(ctx, repeat))
        Global::inputQuickRepeater.clickedInUiOrMidi = true;
    }

    if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
      nk_tooltip(ctx, "Level (F6)");
    {
      static struct nk_image futures = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::futures, 96, 96, GL_RGBA));
      nk_style_item normal_button_color = ctx->style.button.normal;
      if (Global::inputLevel.toggled)
        ctx->style.button.normal = ctx->style.button.active;
      if (nk_button_image(ctx, futures))
        Global::inputLevel.toggled = !Global::inputLevel.toggled;
      ctx->style.button.normal = normal_button_color;
    }

#ifdef SHR3D_ENVIRONMENT_MILK
    if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
      nk_tooltip(ctx, "Milk Background (F7)");
    {
      static struct nk_image milk = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::milk, 96, 96, GL_RGBA));
      nk_style_item normal_button_color = ctx->style.button.normal;
      if (Settings::environmentMilk)
        ctx->style.button.normal = ctx->style.button.active;
      if (nk_button_image(ctx, milk))
        Settings::environmentMilk = !Settings::environmentMilk;
      ctx->style.button.normal = normal_button_color;
    }
#endif // SHR3D_ENVIRONMENT_MILK

    if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
      nk_tooltip(ctx, "Strum Direction (F8)");
    {
      static struct nk_image directions = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::directions, 96, 96, GL_RGBA));
      nk_style_item normal_button_color = ctx->style.button.normal;
      if (Global::inputStrumDirection.toggled)
        ctx->style.button.normal = ctx->style.button.active;
      if (nk_button_image(ctx, directions))
        Global::inputStrumDirection.toggled = !Global::inputStrumDirection.toggled;
      ctx->style.button.normal = normal_button_color;
    }

    nk_spacer(ctx);

    if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
      nk_tooltip(ctx, "Debug Info (F9)");
    {
      static struct nk_image debug = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::debug, 96, 96, GL_RGBA));
      nk_style_item normal_button_color = ctx->style.button.normal;
      if (Global::inputDebugInfo.toggled)
        ctx->style.button.normal = ctx->style.button.active;
      if (nk_button_image(ctx, debug))
        Global::inputDebugInfo.toggled = !Global::inputDebugInfo.toggled;
      ctx->style.button.normal = normal_button_color;
    }

    if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
      nk_tooltip(ctx, "Noclip (F10)");
    {
      static struct nk_image documentary = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::documentary, 96, 96, GL_RGBA));
      nk_style_item normal_button_color = ctx->style.button.normal;
      if (Global::inputNoclip.toggled)
        ctx->style.button.normal = ctx->style.button.active;
      if (nk_button_image(ctx, documentary))
        Global::inputNoclip.toggled = !Global::inputNoclip.toggled;
      ctx->style.button.normal = normal_button_color;
    }

    if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
      nk_tooltip(ctx, "Freeze Highway (F11)");
    {
      static struct nk_image snowflake = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::snowflake, 96, 96, GL_RGBA));
      nk_style_item normal_button_color = ctx->style.button.normal;
      if (Global::inputFreezeHighway.toggled)
        ctx->style.button.normal = ctx->style.button.active;
      if (nk_button_image(ctx, snowflake))
        Global::inputFreezeHighway.toggled = !Global::inputFreezeHighway.toggled;
      ctx->style.button.normal = normal_button_color;
    }

#if defined(PLATFORM_WINDOWS) || defined(PLATFORM_LINUX)
    if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
      nk_tooltip(ctx, "Wireframe (F12)");
    {
      static struct nk_image codePen = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::codePen, 96, 96, GL_RGBA));
      nk_style_item normal_button_color = ctx->style.button.normal;
      if (Global::inputWireframe.toggled)
        ctx->style.button.normal = ctx->style.button.active;
      if (nk_button_image(ctx, codePen))
        Global::inputWireframe.toggled = !Global::inputWireframe.toggled;
      ctx->style.button.normal = normal_button_color;
    }
#endif // PLATFORM_WINDOWS || PLATFORM_LINUX

    nk_spacer(ctx);

    if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
      nk_tooltip(ctx, "Eject (Backspace)");
    {
      static struct nk_image eject = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::eject, 96, 96, GL_RGBA));
      if (nk_button_image(ctx, eject))
        Global::inputEject.clickedInUiOrMidi = true;
    }

    if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
      nk_tooltip(ctx, "Mute (Pause)");
    {
      static struct nk_image mute = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::mute, 96, 96, GL_RGBA));
      nk_style_item normal_button_color = ctx->style.button.normal;
      if (Global::inputMute.toggled)
        ctx->style.button.normal = ctx->style.button.active;
      if (nk_button_image(ctx, mute))
        Global::inputMute.toggled = !Global::inputMute.toggled;
      ctx->style.button.normal = normal_button_color;
    }

#ifdef SHR3D_RECORDER
    if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
      nk_tooltip(ctx, "Recorder (Home)");
    {
      static struct nk_image record = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::record, 96, 96, GL_RGBA));
      nk_style_item normal_button_color = ctx->style.button.normal;
      if (Global::inputRecorder.toggled)
        ctx->style.button.normal = ctx->style.button.active;
      if (nk_button_image(ctx, record))
        Global::inputRecorder.toggled = !Global::inputRecorder.toggled;
      ctx->style.button.normal = normal_button_color;
    }
#endif // SHR3D_RECORDER

    if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
      nk_tooltip(ctx, "Fullscreen (Alt+Return)");
    {
      static struct nk_image fullscreen = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::fullscreen, 96, 96, GL_RGBA));
      nk_style_item normal_button_color = ctx->style.button.normal;
      if (Settings::graphicsFullscreen == FullscreenMode::borderless)
        ctx->style.button.normal = ctx->style.button.active;
      if (nk_button_image(ctx, fullscreen))
      {
#ifdef SHR3D_WINDOW_SDL // TODO: fix for WIN32
        Window_::toggleFullscreen(Global::window, Settings::graphicsFullscreen);
#endif // SHR3D_WINDOW_SDL
#ifdef SHR3D_WINDOW_WIN32
        Window_::toggleFullscreen(Global::window, Global::resolutionWidth, Global::resolutionHeight, Settings::graphicsFullscreen, Global::lastMainWindowPlacement);
#endif // SHR3D_WINDOW_WIN32
      }
      ctx->style.button.normal = normal_button_color;
    }

    nk_spacer(ctx);

    if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
      nk_tooltip(ctx, "Automatic Tone Switch (KP Divide)");
    {
      static struct nk_image unicast = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::unicast, 96, 96, GL_RGBA));
      nk_style_item normal_button_color = ctx->style.button.normal;
      if (Settings::applicationToneSwitch)
        ctx->style.button.normal = ctx->style.button.active;
      if (nk_button_image(ctx, unicast))
        Settings::applicationToneSwitch = !Settings::applicationToneSwitch;
      ctx->style.button.normal = normal_button_color;
    }

#ifdef SHR3D_SFX_CORE_HEXFIN
    if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
      nk_tooltip(ctx, "Highway Tuner");
    {
      static struct nk_image tasks = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::tasks, 96, 96, GL_RGBA));
      nk_style_item normal_button_color = ctx->style.button.normal;
      if (Settings::highwayTuner)
        ctx->style.button.normal = ctx->style.button.active;
      if (nk_button_image(ctx, tasks))
        Settings::highwayTuner = !Settings::highwayTuner;
      ctx->style.button.normal = normal_button_color;
    }
#endif // SHR3D_SFX_CORE_HEXFIN

    if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
      nk_tooltip(ctx, "Highway VU Meter");
    {
      static struct nk_image voltmeter = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::voltmeter, 96, 96, GL_RGBA));
      nk_style_item normal_button_color = ctx->style.button.normal;
      if (Settings::highwayVUMeter)
        ctx->style.button.normal = ctx->style.button.active;
      if (nk_button_image(ctx, voltmeter))
        Settings::highwayVUMeter = !Settings::highwayVUMeter;
      ctx->style.button.normal = normal_button_color;
    }

    nk_spacer(ctx);

#ifdef SHR3D_COOP
    if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
      nk_tooltip(ctx, "Co-Op (End)");
    {
      static struct nk_image newWindow = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::newWindow, 96, 96, GL_RGBA));
      nk_style_item normal_button_color = ctx->style.button.normal;
      if (Global::inputCoop.toggled)
        ctx->style.button.normal = ctx->style.button.active;
      if (nk_button_image(ctx, newWindow))
        Global::inputCoop.toggled = !Global::inputCoop.toggled;
      ctx->style.button.normal = normal_button_color;
    }
#endif // SHR3D_COOP

#ifdef SHR3D_OPENXR_PCVR
    if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
      nk_tooltip(ctx, "XR");
    {
      static struct nk_image virtualReality = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::virtualReality, 96, 96, GL_RGBA));
      nk_style_item normal_button_color = ctx->style.button.normal;
      if (Settings::xrEnabled)
        ctx->style.button.normal = ctx->style.button.active;
      if (nk_button_image(ctx, virtualReality))
        Settings::xrEnabled = !Settings::xrEnabled;
      ctx->style.button.normal = normal_button_color;
    }
#endif // SHR3D_OPENXR_PCVR

#ifndef PLATFORM_EMSCRIPTEN
    nk_spacer(ctx);
    {
      if (nk_input_is_mouse_hovering_rect(&ctx->input, nk_widget_bounds(ctx)))
        nk_tooltip(ctx, "Quit (Ctrl-Q)");
      static struct nk_image exit = nk_image_id(Texture::openGlLoadTextureRaw(Data::Texture::exit, 96, 96, GL_RGBA));
      if (nk_button_image(ctx, exit))
        Global::appQuit = true;
    }
#endif // PLATFORM_EMSCRIPTEN

    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 4);
    nk_property_float(ctx, "Music Volume:", 0.0f, &Settings::audioMusicVolume, 1.0f, 0.01f, 0.004f);
    nk_property_float(ctx, "Effect Volume:", 0.0f, &Settings::audioEffectVolume, 1.0f, 0.01f, 0.004f);
    nk_property_float(ctx, "Effect Volume Coop:", 0.0f, &Settings::audioEffectVolumeCoop, 1.0f, 0.01f, 0.004f);
    nk_property_float(ctx, "Metronome Volume:", 0.0f, &Settings::metronomeVolume, 1.0f, 0.01f, 0.004f);
  }
  nk_end(ctx);
}

static void uiRowColorSelect(const char* name, vec4& color)
{
  nk_label(ctx, name, NK_TEXT_LEFT);

  if (nk_combo_begin_color(ctx, nk_rgb_cf(*(nk_colorf*)(&color)), nk_vec2(200.0f * Settings::uiScale, 400.0f * Settings::uiScale))) {
    nk_layout_row_dynamic(ctx, 120.0f * Settings::uiScale, 1);
    *(nk_colorf*)(&color) = nk_color_picker(ctx, *(nk_colorf*)(&color), NK_RGBA);
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
    color.r = nk_propertyf(ctx, "#R:", 0, color.r, 1.0f, 0.01f, 0.005f);
    color.g = nk_propertyf(ctx, "#G:", 0, color.g, 1.0f, 0.01f, 0.005f);
    color.b = nk_propertyf(ctx, "#B:", 0, color.b, 1.0f, 0.01f, 0.005f);
    color.a = nk_propertyf(ctx, "#A:", 0, color.a, 1.0f, 0.01f, 0.005f);
    nk_combo_end(ctx);
  }
}

static void settingsWindow(i32 resolutionWidth, i32 resolutionHeight)
{
#ifdef SHR3D_SFX
  enum struct OpenSelectSfxWindowMode {
    closed,
    tuner,
    midiFromAudio
  };
  static OpenSelectSfxWindowMode showSelectSfxWindow = OpenSelectSfxWindowMode::closed;
#endif // SHR3D_SFX

  if (nk_begin(ctx, "Settings", calcWindowRectCentered(resolutionWidth, resolutionHeight, -570, -85, 400, 663), NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE | NK_WINDOW_MINIMIZABLE | NK_WINDOW_TITLE))
  {
#ifndef NDEBUG
    if (nk_tree_push(ctx, NK_TREE_TAB, "Debug", NK_MINIMIZED))
    {
      {
        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
        nk_slider_float(ctx, -5.0f, &Global::debugValue[0], 5.0f, 0.0001f);
        nk_slider_float(ctx, -5.0f, &Global::debugValue[1], 5.0f, 0.0001f);
        nk_slider_float(ctx, -5.0f, &Global::debugValue[2], 5.0f, 0.0001f);
        nk_slider_float(ctx, -5.0f, &Global::debugValue[3], 5.0f, 0.0001f);
      }
      nk_tree_pop(ctx);
    }
#endif // NDEBUG
    if (nk_tree_push(ctx, NK_TREE_TAB, "Application", NK_MINIMIZED))
    {
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      nk_label(ctx, "Instrument:", NK_TEXT_LEFT);
      {
        static const char* instrumentNames[] = {
          "Lead",
          "Rhythm",
          "Bass",
        };
        Settings::applicationInstrument = Instrument(nk_combo(ctx, instrumentNames, ARRAY_SIZE(instrumentNames), to_underlying_(Settings::applicationInstrument), 25, nk_vec2(200, 200)));
      }
      nk_label(ctx, "Backup Mode:", NK_TEXT_LEFT);
      {
        static const char* backupModeNames[] = {
          "off",
          "onceOnStart",
        };
        Settings::applicationBackupMode = BackupMode(nk_combo(ctx, backupModeNames, ARRAY_SIZE(backupModeNames), to_underlying_(Settings::applicationBackupMode), 25, nk_vec2(200, 200)));
      }
      nk_label(ctx, "End Of Song:", NK_TEXT_LEFT);
      {
        static const char* endOfSongNames[] = {
          "doNothing",
          "openMenu",
          "loopSong"
        };
        Settings::applicationEndOfSong = EndOfSong(nk_combo(ctx, endOfSongNames, ARRAY_SIZE(endOfSongNames), to_underlying_(Settings::applicationEndOfSong), 25, nk_vec2(200, 200)));
      }
      nk_label(ctx, "Tone Switch:", NK_TEXT_LEFT);
      {
        static const char* toneSwitchNames[] = {
          "manual",
          "automatic"
        };
        Settings::applicationToneSwitch = nk_combo(ctx, toneSwitchNames, ARRAY_SIZE(toneSwitchNames), int(Settings::applicationToneSwitch), 25, nk_vec2(200, 200));
      }
      nk_tree_pop(ctx);
    }
    if (nk_tree_push(ctx, NK_TREE_TAB, "Audio", NK_MINIMIZED))
    {
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_property_float(ctx, "Music Volume:", 0.0f, &Settings::audioMusicVolume, 1.0f, 0.01f, 0.00f);
      nk_property_float(ctx, "Instrument 0 Volume:", 0.0f, &Settings::audioEffectVolume, 1.0f, 0.01f, 0.01f);
      nk_property_float(ctx, "Instrument 1 Volume:", 0.0f, &Settings::audioEffectVolumeCoop, 1.0f, 0.01f, 0.01f);

      if constexpr (to_underlying_(AudioSystem::COUNT) >= 2)
      {
        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
        nk_label(ctx, "Audio System (req. restart)", NK_TEXT_LEFT);

        static const char* audioSystemNames[] = {
#ifdef SHR3D_AUDIO_AAUDIO
          "AAudio",
#endif // SHR3D_AUDIO_AAUDIO
#ifdef SHR3D_AUDIO_ASIO
          "ASIO",
#endif // SHR3D_AUDIO_ASIO
#ifdef SHR3D_AUDIO_JACK
          "JACK",
#endif // SHR3D_AUDIO_JACK
#ifdef SHR3D_AUDIO_PIPEWIRE
          "PipeWire",
#endif // SHR3D_AUDIO_PIPEWIRE
#ifdef SHR3D_AUDIO_SDL
          "SDL",
#endif // SHR3D_AUDIO_SDL
#ifdef SHR3D_AUDIO_SUPERPOWERED
          "Superpowered",
#endif // SHR3D_AUDIO_SUPERPOWERED
#ifdef SHR3D_AUDIO_WASAPI
          "WASAPI",
#endif // SHR3D_AUDIO_WASAPI
#ifdef SHR3D_AUDIO_WEBAUDIO
          "Webaudio",
#endif // SHR3D_AUDIO_WEBAUDIO  
        };
        static_assert(ARRAY_SIZE(audioSystemNames) == to_underlying_(AudioSystem::COUNT));
        Settings::audioSystem = AudioSystem(nk_combo(ctx, audioSystemNames, ARRAY_SIZE(audioSystemNames), to_underlying_(Settings::audioSystem), 25, nk_vec2(200, 200)));
      }
#ifdef SHR3D_AUDIO_AAUDIO
      if (nk_tree_push(ctx, NK_TREE_TAB, "AAudio", NK_MINIMIZED))
      {
        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
        {
          nk_label(ctx, "Device Input (req. restart)", NK_TEXT_LEFT);
          i32 selectedInputDevice = 0;
          std::vector<const char*> c_strs(Global::audioAAudioDevicesInput.size());
          for (i32 i = 0; i < i32(Global::audioAAudioDevicesInput.size()); ++i)
          {
            c_strs[i] = Global::audioAAudioDevicesInput[i].c_str();
            if (Settings::audioAAudioDeviceInput == Global::audioAAudioDevicesInput[i])
              selectedInputDevice = i;
          }
          selectedInputDevice = nk_combo(ctx, c_strs.data(), c_strs.size(), selectedInputDevice, 25, nk_vec2(200, 200));
          Settings::audioAAudioDeviceInput = Global::audioAAudioDevicesInput[selectedInputDevice];
        }
        {
          nk_label(ctx, "Device Output (req. restart)", NK_TEXT_LEFT);
          i32 selectedDevice = 0;
          std::vector<const char*> c_strs(Global::audioAAudioDevicesOutput.size());
          for (i32 i = 0; i < i32(Global::audioAAudioDevicesOutput.size()); ++i)
          {
            c_strs[i] = Global::audioAAudioDevicesOutput[i].c_str();
            if (Settings::audioAAudioDeviceOutput == Global::audioAAudioDevicesOutput[i])
              selectedDevice = i;
          }
          selectedDevice = nk_combo(ctx, c_strs.data(), c_strs.size(), selectedDevice, 25, nk_vec2(200, 200));
          Settings::audioAAudioDeviceOutput = Global::audioAAudioDevicesOutput[selectedDevice];
        }
        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
        {
          nk_label(ctx, "BlockSize (req. restart)", NK_TEXT_LEFT);
          static const char* audioBlockSizeNames[] = {
            "16", "32", "64", "128", "256", "512", "1024", "2048"
          };
          static const i32 audioBlockSize[] = {
            16, 32, 64, 128, 256, 512, 1024, 2048
          };
          i32 index = i32(log2(Settings::audioAAudioBlockSize)) - 4;
          ASSERT(index >= 0);
          ASSERT(index < ARRAY_SIZE(audioBlockSize));
          index = nk_combo(ctx, audioBlockSizeNames, ARRAY_SIZE(audioBlockSizeNames), index, 25, nk_vec2(200, 200));
          Settings::audioAAudioBlockSize = audioBlockSize[index];
        }
        {
          nk_label(ctx, "SampleRate (req. restart)", NK_TEXT_LEFT);
          static const char* audioSampleRateNames[] = {
            "44100",
            "48000",
            "96000",
            "192000"
          };
          static const i32 audioSamples[] = {
            44100,
            48000,
            96000,
            192000
          };
          i32 index = 0;
          switch (Settings::audioAAudioSampleRate)
          {
          case 44100:
            index = 0;
            break;
          case 48000:
            index = 1;
            break;
          case 96000:
            index = 2;
            break;
          case 192000:
            index = 3;
            break;
          default:
            unreachable();
          }
          index = nk_combo(ctx, audioSampleRateNames, ARRAY_SIZE(audioSampleRateNames), index, 25, nk_vec2(200, 200));
          Settings::audioAAudioSampleRate = audioSamples[index];
        }
        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
        nk_property_int(ctx, "Channel Input:", 0, &Settings::audioAAudioChannelInput, 1, 1, 1);
#ifdef SHR3D_COOP
        nk_property_int(ctx, "Channel Input Co-Op:", 0, &Settings::audioAAudioChannelInputCoop, 1, 1, 1);
#endif // SHR3D_COOP
        nk_checkbox_label(ctx, "Exclusive Mode (req. restart)", &Settings::audioAAudioExclusiveMode);
        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
        {
          nk_label(ctx, "Performance Mode (req. restart)", NK_TEXT_LEFT);
          static const char* audioBlockSizeNames[] = {
                  "None", "Low Latency", "Power Saving"
          };
          Settings::audioAAudioPerformanceMode = AAudioPerformanceMode(nk_combo(ctx, audioBlockSizeNames, ARRAY_SIZE(audioBlockSizeNames), to_underlying_(Settings::audioAAudioPerformanceMode), 25, nk_vec2(200, 200)));
        }
        nk_tree_pop(ctx);
      }
#endif // SHR3D_AUDIO_AAUDIO
#ifdef SHR3D_AUDIO_ASIO
      if (nk_tree_push(ctx, NK_TREE_TAB, "ASIO", NK_MINIMIZED))
      {

        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
        {
          nk_label(ctx, "Device (req. restart)", NK_TEXT_LEFT);
          i32 selectedDevice = 0;
          std::vector<const char*> c_strs(Global::audioAsioDevices.size());
          for (i32 i = 0; i < Global::audioAsioDevices.size(); ++i)
          {
            c_strs[i] = Global::audioAsioDevices[i].c_str();
            if (Settings::audioAsioDevice == Global::audioAsioDevices[i])
              selectedDevice = i;
          }
          selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(200, 200));
          Settings::audioAsioDevice = Global::audioAsioDevices[selectedDevice];
        }
        nk_spacer(ctx);
        if (nk_button_label(ctx, "Control Panel"))
          Sound::openControlPanel();
        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
        {
          nk_label(ctx, "SampleRate (req. restart)", NK_TEXT_LEFT);
          static const char* audioSampleRateNames[] = {
            "Default",
            "44100",
            "48000",
            "96000",
            "192000"
          };
          static const i32 audioSamples[] = {
            0,
            44100,
            48000,
            96000,
            192000
          };
          i32 index = 0;
          switch (Settings::audioAsioSampleRate)
          {
          case 0:
            index = 0;
            break;
          case 44100:
            index = 1;
            break;
          case 48000:
            index = 2;
            break;
          case 96000:
            index = 3;
            break;
          case 192000:
            index = 4;
            break;
          default:
            unreachable();
          }
          index = nk_combo(ctx, audioSampleRateNames, ARRAY_SIZE(audioSampleRateNames), index, 25, nk_vec2(200, 200));
          Settings::audioAsioSampleRate = audioSamples[index];
        }
        {
          nk_label(ctx, "BlockSize (req. restart)", NK_TEXT_LEFT);
          static std::vector<std::string> bufferSizeNames{ "Default" };
          static std::vector<i32> bufferSizes = { 0 };
          static i32 selectedBufferSizeIndex;
          { // rebuild BufferSize list
            static long audioAsioBufferMinSize;
            static long audioAsioBufferMaxSize;
            static long audioAsioBufferPreferredSize;
            static long audioAsioBufferGranularity;

            if (audioAsioBufferMinSize != Global::audioAsioBufferMinSize ||
              audioAsioBufferMaxSize != Global::audioAsioBufferMaxSize ||
              audioAsioBufferPreferredSize != Global::audioAsioBufferPreferredSize ||
              audioAsioBufferGranularity != Global::audioAsioBufferGranularity)
            {
              audioAsioBufferMinSize = Global::audioAsioBufferMinSize;
              audioAsioBufferMaxSize = Global::audioAsioBufferMaxSize;
              audioAsioBufferPreferredSize = Global::audioAsioBufferPreferredSize;
              audioAsioBufferGranularity = Global::audioAsioBufferGranularity;

              bufferSizes = { 0 };
              if (audioAsioBufferGranularity >= 1)
                for (long i = audioAsioBufferMinSize; i <= audioAsioBufferMaxSize; i += audioAsioBufferGranularity)
                  bufferSizes.push_back(i);
              else // this mode is more common, buffersizes look like 16, 32, 64, ...
                for (long i = audioAsioBufferMinSize; i <= audioAsioBufferMaxSize; i = i << 1)
                  bufferSizes.push_back(i);
              bufferSizeNames = { "Default" };
              for (i32 i = 1; i < bufferSizes.size(); ++i)
              {
                bufferSizeNames.push_back(std::to_string(bufferSizes[i]));
                if (bufferSizes[i] == Settings::audioAsioBlockSize)
                  selectedBufferSizeIndex = i;
              }
              if (selectedBufferSizeIndex == -1) // if user selected BlockSize is not possible, use the recommended size the driver returns
              {
                for (i32 i = 1; i < bufferSizes.size(); ++i)
                {
                  if (bufferSizes[i] == audioAsioBufferPreferredSize)
                    selectedBufferSizeIndex = i;
                }
              }
            }
          }
          ASSERT(selectedBufferSizeIndex >= 0);
          ASSERT(selectedBufferSizeIndex < bufferSizeNames.size());
          {
            std::vector<const char*> c_strs(bufferSizeNames.size());
            for (i32 i = 0; i < bufferSizeNames.size(); ++i)
              c_strs[i] = bufferSizeNames[i].c_str();
            selectedBufferSizeIndex = nk_combo(ctx, c_strs.data(), c_strs.size(), selectedBufferSizeIndex, 25, nk_vec2(200, 200));
            Settings::audioAsioBlockSize = bufferSizes[selectedBufferSizeIndex];
          }
        }
        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
        nk_property_int(ctx, "Channel Output (Stereo):", 0, &Settings::audioAsioChannelOutput, Global::audioAsioOutputCount - 2, 2, 2); // 2 because of stereo
        {
          const i32 oldInput0 = Settings::audioAsioChannelInput0;
          nk_property_int(ctx, "Channel Input 0 (Mono):", 0, &Settings::audioAsioChannelInput0, Global::audioAsioInputCount - 1, 1, 1);
          if (oldInput0 == Settings::audioAsioChannelInput1 && Settings::audioAsioChannelInput0 != Settings::audioAsioChannelInput1)
            Settings::audioAsioChannelInput1 = Settings::audioAsioChannelInput0;
        }
        nk_property_int(ctx, "Channel Input 1 (same as Input 0 for Mono):", 0, &Settings::audioAsioChannelInput1, Global::audioAsioInputCount - 1, 1, 1);
#ifdef SHR3D_COOP
        {
          const i32 oldInput0 = Settings::audioAsioChannelInputCoop0;
          nk_property_int(ctx, "Channel Input 0 Co-Op:", 0, &Settings::audioAsioChannelInputCoop0, Global::audioAsioInputCount - 1, 1, 1);
          if (oldInput0 == Settings::audioAsioChannelInputCoop1 && Settings::audioAsioChannelInputCoop0 != Settings::audioAsioChannelInputCoop1)
            Settings::audioAsioChannelInputCoop1 = Settings::audioAsioChannelInputCoop0;
        }
        nk_property_int(ctx, "Channel Input 1 Co-Op:", 0, &Settings::audioAsioChannelInputCoop1, Global::audioAsioInputCount - 1, 1, 1);
#endif // SHR3D_COOP
#ifdef SHR3D_SFX_CORE_HEXFIN_DIVIDED
        nk_checkbox_label(ctx, "Divided pickup", &Settings::audioAsioDividedPickup);
        if (nk_tree_push(ctx, NK_TREE_TAB, "Divided pickup", NK_MINIMIZED))
        {
#ifdef SHR3D_AUDIO_ASIO_SECOND_DEVICE_FOR_TUNER_DIVIDED
          nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
          {
            nk_label(ctx, "Device, divided pickup (req. restart)", NK_TEXT_LEFT);
            i32 selectedDevice = 0;
            std::vector<const char*> c_strs(Global::audioAsioDevices.size());
            for (i32 i = 0; i < Global::audioAsioDevices.size(); ++i)
            {
              c_strs[i] = Global::audioAsioDevices[i].c_str();
              if (Settings::audioAsioSecondDeviceForTunerDivided == Global::audioAsioDevices[i])
                selectedDevice = i;
            }
            const char* none = "main ASIO device";
            c_strs[0] = none;
            selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(450, 200));
            Settings::audioAsioSecondDeviceForTunerDivided = Global::audioAsioDevices[selectedDevice];
          }
#endif // SHR3D_AUDIO_ASIO_SECOND_DEVICE_FOR_TUNER_DIVIDED
          nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
          nk_checkbox_label(ctx, "Downmix into Channel Input 0 ('main ASIO device' selected)", &Settings::audioAsioDividedPickupAsMainInput);
#ifdef SHR3D_AUDIO_ASIO_SECOND_DEVICE_FOR_TUNER_DIVIDED
          if (!Settings::audioAsioSecondDeviceForTunerDivided.empty())
          {
            nk_property_int(ctx, "Channel String 0:", 0, &Settings::audioAsioDividedPickupChannelString0, Global::audioAsioSecondDeviceForTunerDividedInputCount - 1, 1, 1);
            nk_property_int(ctx, "Channel String 1:", 0, &Settings::audioAsioDividedPickupChannelString1, Global::audioAsioSecondDeviceForTunerDividedInputCount - 1, 1, 1);
            nk_property_int(ctx, "Channel String 2:", 0, &Settings::audioAsioDividedPickupChannelString2, Global::audioAsioSecondDeviceForTunerDividedInputCount - 1, 1, 1);
            nk_property_int(ctx, "Channel String 3:", 0, &Settings::audioAsioDividedPickupChannelString3, Global::audioAsioSecondDeviceForTunerDividedInputCount - 1, 1, 1);
            nk_property_int(ctx, "Channel String 4:", 0, &Settings::audioAsioDividedPickupChannelString4, Global::audioAsioSecondDeviceForTunerDividedInputCount - 1, 1, 1);
            nk_property_int(ctx, "Channel String 5:", 0, &Settings::audioAsioDividedPickupChannelString5, Global::audioAsioSecondDeviceForTunerDividedInputCount - 1, 1, 1);
          }
          else
#endif // SHR3D_AUDIO_ASIO_SECOND_DEVICE_FOR_TUNER_DIVIDED
          {
            nk_property_int(ctx, "Channel String 0:", 0, &Settings::audioAsioDividedPickupChannelString0, Global::audioAsioInputCount - 1, 1, 1);
            nk_property_int(ctx, "Channel String 1:", 0, &Settings::audioAsioDividedPickupChannelString1, Global::audioAsioInputCount - 1, 1, 1);
            nk_property_int(ctx, "Channel String 2:", 0, &Settings::audioAsioDividedPickupChannelString2, Global::audioAsioInputCount - 1, 1, 1);
            nk_property_int(ctx, "Channel String 3:", 0, &Settings::audioAsioDividedPickupChannelString3, Global::audioAsioInputCount - 1, 1, 1);
            nk_property_int(ctx, "Channel String 4:", 0, &Settings::audioAsioDividedPickupChannelString4, Global::audioAsioInputCount - 1, 1, 1);
            nk_property_int(ctx, "Channel String 5:", 0, &Settings::audioAsioDividedPickupChannelString5, Global::audioAsioInputCount - 1, 1, 1);
          }
          nk_tree_pop(ctx);
        }
#endif // SHR3D_SFX_CORE_HEXFIN_DIVIDED
        nk_tree_pop(ctx);
      }
#endif // SHR3D_AUDIO_ASIO
#ifdef SHR3D_AUDIO_SUPERPOWERED
      if (nk_tree_push(ctx, NK_TREE_TAB, "Superpowered", NK_MINIMIZED))
      {
        if (Global::audioSuperpoweredCofiguration.size() == 0)
        {
          nk_label(ctx, "No audio USB device found", NK_TEXT_LEFT);
        }
        else
        {
          nk_checkbox_label(ctx, "SustainedPerformanceMode", &Settings::audioSuperpoweredSustainedPerformanceMode);
          nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
          {
            nk_label(ctx, "Configuration", NK_TEXT_LEFT);
            i32 selectedConfiguration = 0;
            std::vector<const char*> c_strs(Global::audioSuperpoweredCofiguration.size());
            for (i32 i = 0; i < Global::audioSuperpoweredCofiguration.size(); ++i)
            {
              c_strs[i] = Global::audioSuperpoweredCofiguration[i].c_str();
              if (Settings::audioSuperpoweredCofiguration == Global::audioSuperpoweredCofiguration[i])
                selectedConfiguration = i;
            }
            selectedConfiguration = nk_combo(ctx, c_strs.data(), c_strs.size(), selectedConfiguration, 25, nk_vec2(200, 200));
            Settings::audioSuperpoweredCofiguration = Global::audioSuperpoweredCofiguration[selectedConfiguration];
          }

          if (Global::audioSuperpoweredDevicesInput.size() != 0)
          {
            nk_label(ctx, "Input", NK_TEXT_LEFT);
            i32 selectedConfiguration = 0;
            std::vector<const char*> c_strs(Global::audioSuperpoweredDevicesInput.size());
            for (i32 i = 0; i < Global::audioSuperpoweredDevicesInput.size(); ++i)
            {
              c_strs[i] = Global::audioSuperpoweredDevicesInput[i].c_str();
              if (Settings::audioSuperpoweredDeviceInput == Global::audioSuperpoweredDevicesInput[i])
                selectedConfiguration = i;
            }
            selectedConfiguration = nk_combo(ctx, c_strs.data(), c_strs.size(), selectedConfiguration, 25, nk_vec2(200, 200));
            Settings::audioSuperpoweredDeviceInput = Global::audioSuperpoweredDevicesInput[selectedConfiguration];
          }
          if (Global::audioSuperpoweredDevicesOutput.size() != 0)
          {
            nk_label(ctx, "Output", NK_TEXT_LEFT);
            i32 selectedConfiguration = 0;
            std::vector<const char*> c_strs(Global::audioSuperpoweredDevicesOutput.size());
            for (i32 i = 0; i < Global::audioSuperpoweredDevicesOutput.size(); ++i)
            {
              c_strs[i] = Global::audioSuperpoweredDevicesOutput[i].c_str();
              if (Settings::audioSuperpoweredDeviceOutput == Global::audioSuperpoweredDevicesOutput[i])
                selectedConfiguration = i;
            }
            selectedConfiguration = nk_combo(ctx, c_strs.data(), c_strs.size(), selectedConfiguration, 25, nk_vec2(200, 200));
            Settings::audioSuperpoweredDeviceOutput = Global::audioSuperpoweredDevicesOutput[selectedConfiguration];
          }
          {
            nk_label(ctx, "BlockSize (req. restart)", NK_TEXT_LEFT);
            static const char* audioBlockSizeNames[] = {
                    "128", "256", "512"
            };
            static const i32 audioBlockSize[] = {
                    128, 256, 512
            };
            i32 index = i32(log2(Settings::audioSuperpoweredBlockSize)) - 7;
            ASSERT(index >= 0);
            ASSERT(index < ARRAY_SIZE(audioBlockSize));
            index = nk_combo(ctx, audioBlockSizeNames, ARRAY_SIZE(audioBlockSizeNames), index, 25, nk_vec2(200, 200));
            Settings::audioSuperpoweredBlockSize = audioBlockSize[index];
          }
          nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
          nk_property_int(ctx, "Channel Input 0 (Mono):", 0, &Settings::audioSuperpoweredChannelInput, Global::audioSuperpoweredInputCount - 1, 1, 1);
          nk_property_int(ctx, "Channel Output (Stereo):", 0, &Settings::audioSuperpoweredChannelOutput, Global::audioSuperpoweredOutputCount - 2, 2, 2); // 2 because of stereo
          if (Settings::audioSuperpoweredInputVolume.size() != 0 || Settings::audioSuperpoweredOutputVolume.size() != 0)
          {
            if (nk_tree_push(ctx, NK_TREE_TAB, "Input Controlls", NK_MINIMIZED))
            {
              nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
              if (Global::audioSuperpoweredInputPaths.size() != 0)
              {
                nk_label(ctx, "Path", NK_TEXT_LEFT);
                i32 selectedPath = 0;
                std::vector<const char*> c_strs(Global::audioSuperpoweredInputPaths.size());
                for (i32 i = 0; i < Global::audioSuperpoweredInputPaths.size(); ++i)
                {
                  c_strs[i] = Global::audioSuperpoweredInputPaths[i].c_str();
                  if (Settings::audioSuperpoweredInputPathName == Global::audioSuperpoweredInputPaths[i])
                    selectedPath = i;
                }
                selectedPath = nk_combo(ctx, c_strs.data(), c_strs.size(), selectedPath, 25, nk_vec2(200, 200));
                Settings::audioSuperpoweredInputPathName = Global::audioSuperpoweredInputPaths[selectedPath];
              }
              {
                for (i32 i = 0; i < Settings::audioSuperpoweredInputVolume.size(); ++i)
                {
                  if (i == 0)
                  {
                    nk_checkbox_label(ctx, "Input Master", (bool*)&Settings::audioSuperpoweredInputMutes[i]);
                    nk_spacer(ctx);
                  }
                  else
                  {
                    nk_checkbox_label(ctx, (const char*)(std::string("Input Mute Channel ") + to_string(i) + ", " + to_string(Global::audioSuperpoweredInputMinVolumes[i]) + "db to " + to_string(Global::audioSuperpoweredInputMaxVolumes[i]) + "db, " + to_string(Settings::audioSuperpoweredInputVolume[i]) + "db").c_str(), (bool*)&Settings::audioSuperpoweredInputMutes[i]);
                    nk_property_float(ctx, (const char*)(std::string("Input Volume Channel ") + to_string(i)).c_str(), Global::audioSuperpoweredInputMinVolumes[i], &Settings::audioSuperpoweredInputVolume[i], Global::audioSuperpoweredInputMaxVolumes[i], 0.1f, 0.1f);
                  }
                }
                nk_tree_pop(ctx);
              }
            }
            {
              if (nk_tree_push(ctx, NK_TREE_TAB, "Output Controlls", NK_MINIMIZED))
              {
                nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
                if (Global::audioSuperpoweredOutputPaths.size() != 0)
                {
                  nk_label(ctx, "Path", NK_TEXT_LEFT);
                  i32 selectedPath = 0;
                  std::vector<const char*> c_strs(Global::audioSuperpoweredOutputPaths.size());
                  for (i32 i = 0; i < Global::audioSuperpoweredOutputPaths.size(); ++i)
                  {
                    c_strs[i] = Global::audioSuperpoweredOutputPaths[i].c_str();
                    if (Settings::audioSuperpoweredOutputPathName == Global::audioSuperpoweredOutputPaths[i])
                      selectedPath = i;
                  }
                  selectedPath = nk_combo(ctx, c_strs.data(), c_strs.size(), selectedPath, 25, nk_vec2(200, 200));
                  Settings::audioSuperpoweredOutputPathName = Global::audioSuperpoweredOutputPaths[selectedPath];
                }
                for (i32 i = 0; i < Settings::audioSuperpoweredOutputVolume.size(); ++i)
                {
                  if (i == 0)
                  {
                    nk_checkbox_label(ctx, "Output Master", (bool*)&Settings::audioSuperpoweredOutputMutes[i]);
                    nk_spacer(ctx);
                  }
                  else
                  {
                    nk_checkbox_label(ctx, (const char*)(std::string("Output Mute Channel ") + to_string(i) + ", " + to_string(Global::audioSuperpoweredOutputMinVolumes[i]) + "db to " + to_string(Global::audioSuperpoweredOutputMaxVolumes[i]) + "db, " + to_string(Settings::audioSuperpoweredOutputVolume[i]) + "db").c_str(), (bool*)&Settings::audioSuperpoweredOutputMutes[i]);
                    nk_property_float(ctx, (const char*)(std::string("Output Volume Channel ") + to_string(i)).c_str(), Global::audioSuperpoweredOutputMinVolumes[i], &Settings::audioSuperpoweredOutputVolume[i], Global::audioSuperpoweredOutputMaxVolumes[i], 0.1f, 0.1f);
                  }
                }
                nk_tree_pop(ctx);
              }
            }
#ifdef SHR3D_SFX_CORE_HEXFIN_DIVIDED
            nk_checkbox_label(ctx, "Divided pickup", &Settings::audioSuperpoweredDividedPickup);
            if (nk_tree_push(ctx, NK_TREE_TAB, "Divided pickup", NK_MINIMIZED))
            {
              nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
              {
                nk_property_int(ctx, "Channel String 0:", 0, &Settings::audioSuperpoweredDividedPickupChannelString0, Global::audioSuperpoweredInputCount - 1, 1, 1);
                nk_property_int(ctx, "Channel String 1:", 0, &Settings::audioSuperpoweredDividedPickupChannelString1, Global::audioSuperpoweredInputCount - 1, 1, 1);
                nk_property_int(ctx, "Channel String 2:", 0, &Settings::audioSuperpoweredDividedPickupChannelString2, Global::audioSuperpoweredInputCount - 1, 1, 1);
                nk_property_int(ctx, "Channel String 3:", 0, &Settings::audioSuperpoweredDividedPickupChannelString3, Global::audioSuperpoweredInputCount - 1, 1, 1);
                nk_property_int(ctx, "Channel String 4:", 0, &Settings::audioSuperpoweredDividedPickupChannelString4, Global::audioSuperpoweredInputCount - 1, 1, 1);
                nk_property_int(ctx, "Channel String 5:", 0, &Settings::audioSuperpoweredDividedPickupChannelString5, Global::audioSuperpoweredInputCount - 1, 1, 1);
              }
              nk_tree_pop(ctx);
            }
#endif // SHR3D_SFX_CORE_HEXFIN_DIVIDED
          }
        }
        nk_tree_pop(ctx);
      }
#endif // SHR3D_AUDIO_SUPERPOWERED
#ifdef SHR3D_AUDIO_JACK
      if (nk_tree_push(ctx, NK_TREE_TAB, "JACK", NK_MINIMIZED))
      {
#ifndef SHR3D_AUDIO_JACK_NO_DLOPEN
        if (!Global::audioJackLibraryLoaded)
        {
          nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
          nk_label(ctx, "Jack is not installed.", NK_TEXT_LEFT);
        }
        else
#endif // SHR3D_AUDIO_JACK_NO_DLOPEN
        {
          nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
          {
            std::vector<const char*> c_strs(Global::audioJackDevicesOutput.size());
            for (i32 i = 0; i < Global::audioJackDevicesOutput.size(); ++i)
              c_strs[i] = Global::audioJackDevicesOutput[i].c_str();
            {
              nk_label(ctx, "Output 0 (req. restart)", NK_TEXT_LEFT);
              i32 selectedDevice = 0;
              for (i32 i = 0; i < Global::audioJackDevicesOutput.size(); ++i)
              {
                if (Settings::audioJackOutputDevice0 == Global::audioJackDevicesOutput[i])
                {
                  selectedDevice = i;
                  break;
                }
              }
              selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
              Settings::audioJackOutputDevice0 = Global::audioJackDevicesOutput[selectedDevice];
            }
            {
              nk_label(ctx, "Output 1 (req. restart)", NK_TEXT_LEFT);
              i32 selectedDevice = 0;
              for (i32 i = 0; i < Global::audioJackDevicesOutput.size(); ++i)
              {
                if (Settings::audioJackOutputDevice1 == Global::audioJackDevicesOutput[i])
                {
                  selectedDevice = i;
                  break;
                }
              }
              selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
              Settings::audioJackOutputDevice1 = Global::audioJackDevicesOutput[selectedDevice];
            }
          }
          {
            std::vector<const char*> c_strs(Global::audioJackDevicesInput.size());
            for (i32 i = 0; i < Global::audioJackDevicesInput.size(); ++i)
              c_strs[i] = Global::audioJackDevicesInput[i].c_str();
            {
              nk_label(ctx, "Input 0 (req. restart)", NK_TEXT_LEFT);
              i32 selectedDevice = 0;
              for (i32 i = 0; i < Global::audioJackDevicesInput.size(); ++i)
              {
                if (Settings::audioJackInputDevice0 == Global::audioJackDevicesInput[i])
                {
                  selectedDevice = i;
                  break;
                }
              }
              selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
              Settings::audioJackInputDevice0 = Global::audioJackDevicesInput[selectedDevice];
            }
            {
              nk_label(ctx, "Input 1 (req. restart)", NK_TEXT_LEFT);
              i32 selectedDevice = 0;
              for (i32 i = 0; i < Global::audioJackDevicesInput.size(); ++i)
              {
                if (Settings::audioJackInputDevice1 == Global::audioJackDevicesInput[i])
                {
                  selectedDevice = i;
                  break;
                }
              }
              selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
              Settings::audioJackInputDevice1 = Global::audioJackDevicesInput[selectedDevice];
            }
#ifdef SHR3D_COOP
            {
              nk_label(ctx, "Input Coop 0 (req. restart)", NK_TEXT_LEFT);
              i32 selectedDevice = 0;
              for (i32 i = 0; i < Global::audioJackDevicesInput.size(); ++i)
              {
                if (Settings::audioJackInputDeviceCoop0 == Global::audioJackDevicesInput[i])
                {
                  selectedDevice = i;
                  break;
                }
              }
              selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
              Settings::audioJackInputDeviceCoop0 = Global::audioJackDevicesInput[selectedDevice];
            }
            {
              nk_label(ctx, "Input Coop 1 (req. restart)", NK_TEXT_LEFT);
              i32 selectedDevice = 0;
              for (i32 i = 0; i < Global::audioJackDevicesInput.size(); ++i)
              {
                if (Settings::audioJackInputDeviceCoop1 == Global::audioJackDevicesInput[i])
                {
                  selectedDevice = i;
                  break;
                }
              }
              selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
              Settings::audioJackInputDeviceCoop1 = Global::audioJackDevicesInput[selectedDevice];
            }
#endif // SHR3D_COOP
#ifdef SHR3D_SFX_CORE_HEXFIN_DIVIDED
            nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
            nk_checkbox_label(ctx, "Divided Pickup", &Settings::audioJackDividedPickup);
            if (nk_tree_push(ctx, NK_TREE_TAB, "Divided Pickup", NK_MINIMIZED))
            {
              nk_checkbox_label(ctx, "Use as main input, downmix into Channel Input 0, only on device: none, divided pickup plugged into main ASIO device", &Settings::audioJackDividedPickupAsMainInput);
              nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
              {
                nk_label(ctx, "Input String 0 (req. restart)", NK_TEXT_LEFT);
                i32 selectedDevice = 0;
                for (i32 i = 0; i < Global::audioJackDevicesInput.size(); ++i)
                {
                  if (Settings::audioJackDividedPickupChannelString0 == Global::audioJackDevicesInput[i])
                  {
                    selectedDevice = i;
                    break;
                  }
                }
                selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
                Settings::audioJackDividedPickupChannelString0 = Global::audioJackDevicesInput[selectedDevice];
              }
              {
                nk_label(ctx, "Input String 1 (req. restart)", NK_TEXT_LEFT);
                i32 selectedDevice = 0;
                for (i32 i = 0; i < Global::audioJackDevicesInput.size(); ++i)
                {
                  if (Settings::audioJackDividedPickupChannelString1 == Global::audioJackDevicesInput[i])
                  {
                    selectedDevice = i;
                    break;
                  }
                }
                selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
                Settings::audioJackDividedPickupChannelString1 = Global::audioJackDevicesInput[selectedDevice];
              }
              {
                nk_label(ctx, "Input String 2 (req. restart)", NK_TEXT_LEFT);
                i32 selectedDevice = 0;
                for (i32 i = 0; i < Global::audioJackDevicesInput.size(); ++i)
                {
                  if (Settings::audioJackDividedPickupChannelString2 == Global::audioJackDevicesInput[i])
                  {
                    selectedDevice = i;
                    break;
                  }
                }
                selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
                Settings::audioJackDividedPickupChannelString2 = Global::audioJackDevicesInput[selectedDevice];
              }
              {
                nk_label(ctx, "Input String 3 (req. restart)", NK_TEXT_LEFT);
                i32 selectedDevice = 0;
                for (i32 i = 0; i < Global::audioJackDevicesInput.size(); ++i)
                {
                  if (Settings::audioJackDividedPickupChannelString3 == Global::audioJackDevicesInput[i])
                  {
                    selectedDevice = i;
                    break;
                  }
                }
                selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
                Settings::audioJackDividedPickupChannelString3 = Global::audioJackDevicesInput[selectedDevice];
              }
              {
                nk_label(ctx, "Input String 4 (req. restart)", NK_TEXT_LEFT);
                i32 selectedDevice = 0;
                for (i32 i = 0; i < Global::audioJackDevicesInput.size(); ++i)
                {
                  if (Settings::audioJackDividedPickupChannelString4 == Global::audioJackDevicesInput[i])
                  {
                    selectedDevice = i;
                    break;
                  }
                }
                selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
                Settings::audioJackDividedPickupChannelString4 = Global::audioJackDevicesInput[selectedDevice];
              }
              {
                nk_label(ctx, "Input String 5 (req. restart)", NK_TEXT_LEFT);
                i32 selectedDevice = 0;
                for (i32 i = 0; i < Global::audioJackDevicesInput.size(); ++i)
                {
                  if (Settings::audioJackDividedPickupChannelString5 == Global::audioJackDevicesInput[i])
                  {
                    selectedDevice = i;
                    break;
                  }
                }
                selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
                Settings::audioJackDividedPickupChannelString5 = Global::audioJackDevicesInput[selectedDevice];
              }
              nk_tree_pop(ctx);
            }
#endif // SHR3D_SFX_CORE_HEXFIN_DIVIDED
          }
        }
        nk_tree_pop(ctx);
      }
#endif // SHR3D_AUDIO_JACK
#ifdef SHR3D_AUDIO_PIPEWIRE
      if (nk_tree_push(ctx, NK_TREE_TAB, "PipeWire", NK_MINIMIZED))
      {
#ifndef SHR3D_AUDIO_PIPEWIRE_NO_DLOPEN
        if (!Global::audioPipewireLibraryLoaded)
        {
          nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
          nk_label(ctx, "PipeWire is not installed.", NK_TEXT_LEFT);
        }
        else
#endif // SHR3D_AUDIO_PIPEWIRE_NO_DLOPEN
        {
          nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
          {
            std::vector<const char*> c_strs(Global::audioPipewireDevicesOutput.size());
            for (i32 i = 0; i < Global::audioPipewireDevicesOutput.size(); ++i)
              c_strs[i] = Global::audioPipewireDevicesOutput[i].c_str();
            {
              nk_label(ctx, "Output 0 (req. restart)", NK_TEXT_LEFT);
              i32 selectedDevice = 0;
              for (i32 i = 0; i < Global::audioPipewireDevicesOutput.size(); ++i)
              {
                if (Settings::audioPipewireOutputDevice0 == Global::audioPipewireDevicesOutput[i])
                {
                  selectedDevice = i;
                  break;
                }
              }
              selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
              Settings::audioPipewireOutputDevice0 = Global::audioPipewireDevicesOutput[selectedDevice];
            }
            {
              nk_label(ctx, "Output 1 (req. restart)", NK_TEXT_LEFT);
              i32 selectedDevice = 0;
              for (i32 i = 0; i < Global::audioPipewireDevicesOutput.size(); ++i)
              {
                if (Settings::audioPipewireOutputDevice1 == Global::audioPipewireDevicesOutput[i])
                {
                  selectedDevice = i;
                  break;
                }
              }
              selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
              Settings::audioPipewireOutputDevice1 = Global::audioPipewireDevicesOutput[selectedDevice];
            }
          }
          {
            std::vector<const char*> c_strs(Global::audioPipewireDevicesInput.size());
            for (i32 i = 0; i < Global::audioPipewireDevicesInput.size(); ++i)
              c_strs[i] = Global::audioPipewireDevicesInput[i].c_str();
            {
              nk_label(ctx, "Input 0 (req. restart)", NK_TEXT_LEFT);
              i32 selectedDevice = 0;
              for (i32 i = 0; i < Global::audioPipewireDevicesInput.size(); ++i)
              {
                if (Settings::audioPipewireInputDevice0 == Global::audioPipewireDevicesInput[i])
                {
                  selectedDevice = i;
                  break;
                }
              }
              selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
              Settings::audioPipewireInputDevice0 = Global::audioPipewireDevicesInput[selectedDevice];
            }
            {
              nk_label(ctx, "Input 1 (req. restart)", NK_TEXT_LEFT);
              i32 selectedDevice = 0;
              for (i32 i = 0; i < Global::audioPipewireDevicesInput.size(); ++i)
              {
                if (Settings::audioPipewireInputDevice1 == Global::audioPipewireDevicesInput[i])
                {
                  selectedDevice = i;
                  break;
                }
              }
              selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
              Settings::audioPipewireInputDevice1 = Global::audioPipewireDevicesInput[selectedDevice];
            }
#ifdef SHR3D_COOP
            {
              nk_label(ctx, "Input Coop 0 (req. restart)", NK_TEXT_LEFT);
              i32 selectedDevice = 0;
              for (i32 i = 0; i < Global::audioPipewireDevicesInput.size(); ++i)
              {
                if (Settings::audioPipewireInputDeviceCoop0 == Global::audioPipewireDevicesInput[i])
                {
                  selectedDevice = i;
                  break;
                }
              }
              selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
              Settings::audioPipewireInputDeviceCoop0 = Global::audioPipewireDevicesInput[selectedDevice];
            }
            {
              nk_label(ctx, "Input Coop 1 (req. restart)", NK_TEXT_LEFT);
              i32 selectedDevice = 0;
              for (i32 i = 0; i < Global::audioPipewireDevicesInput.size(); ++i)
              {
                if (Settings::audioPipewireInputDeviceCoop1 == Global::audioPipewireDevicesInput[i])
                {
                  selectedDevice = i;
                  break;
                }
              }
              selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
              Settings::audioPipewireInputDeviceCoop1 = Global::audioPipewireDevicesInput[selectedDevice];
            }
#endif // SHR3D_COOP
#ifdef SHR3D_SFX_CORE_HEXFIN_DIVIDED
            nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
            nk_checkbox_label(ctx, "Divided Pickup", &Settings::audioPipewireDividedPickup);
            if (nk_tree_push(ctx, NK_TREE_TAB, "Divided Pickup", NK_MINIMIZED))
            {
              nk_checkbox_label(ctx, "Use as main input, downmix into Channel Input 0, only on device: none, divided pickup plugged into main ASIO device", &Settings::audioPipewireDividedPickupAsMainInput);
              nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
              {
                nk_label(ctx, "Input String 0 (req. restart)", NK_TEXT_LEFT);
                i32 selectedDevice = 0;
                for (i32 i = 0; i < Global::audioPipewireDevicesInput.size(); ++i)
                {
                  if (Settings::audioPipewireDividedPickupChannelString0 == Global::audioPipewireDevicesInput[i])
                  {
                    selectedDevice = i;
                    break;
                  }
                }
                selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
                Settings::audioPipewireDividedPickupChannelString0 = Global::audioPipewireDevicesInput[selectedDevice];
              }
              {
                nk_label(ctx, "Input String 1 (req. restart)", NK_TEXT_LEFT);
                i32 selectedDevice = 0;
                for (i32 i = 0; i < Global::audioPipewireDevicesInput.size(); ++i)
                {
                  if (Settings::audioPipewireDividedPickupChannelString1 == Global::audioPipewireDevicesInput[i])
                  {
                    selectedDevice = i;
                    break;
                  }
                }
                selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
                Settings::audioPipewireDividedPickupChannelString1 = Global::audioPipewireDevicesInput[selectedDevice];
              }
              {
                nk_label(ctx, "Input String 2 (req. restart)", NK_TEXT_LEFT);
                i32 selectedDevice = 0;
                for (i32 i = 0; i < Global::audioPipewireDevicesInput.size(); ++i)
                {
                  if (Settings::audioPipewireDividedPickupChannelString2 == Global::audioPipewireDevicesInput[i])
                  {
                    selectedDevice = i;
                    break;
                  }
                }
                selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
                Settings::audioPipewireDividedPickupChannelString2 = Global::audioPipewireDevicesInput[selectedDevice];
              }
              {
                nk_label(ctx, "Input String 3 (req. restart)", NK_TEXT_LEFT);
                i32 selectedDevice = 0;
                for (i32 i = 0; i < Global::audioPipewireDevicesInput.size(); ++i)
                {
                  if (Settings::audioPipewireDividedPickupChannelString3 == Global::audioPipewireDevicesInput[i])
                  {
                    selectedDevice = i;
                    break;
                  }
                }
                selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
                Settings::audioPipewireDividedPickupChannelString3 = Global::audioPipewireDevicesInput[selectedDevice];
              }
              {
                nk_label(ctx, "Input String 4 (req. restart)", NK_TEXT_LEFT);
                i32 selectedDevice = 0;
                for (i32 i = 0; i < Global::audioPipewireDevicesInput.size(); ++i)
                {
                  if (Settings::audioPipewireDividedPickupChannelString4 == Global::audioPipewireDevicesInput[i])
                  {
                    selectedDevice = i;
                    break;
                  }
                }
                selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
                Settings::audioPipewireDividedPickupChannelString4 = Global::audioPipewireDevicesInput[selectedDevice];
              }
              {
                nk_label(ctx, "Input String 5 (req. restart)", NK_TEXT_LEFT);
                i32 selectedDevice = 0;
                for (i32 i = 0; i < Global::audioPipewireDevicesInput.size(); ++i)
                {
                  if (Settings::audioPipewireDividedPickupChannelString5 == Global::audioPipewireDevicesInput[i])
                  {
                    selectedDevice = i;
                    break;
                  }
                }
                selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(400, 200));
                Settings::audioPipewireDividedPickupChannelString5 = Global::audioPipewireDevicesInput[selectedDevice];
              }
              nk_tree_pop(ctx);
            }
#endif // SHR3D_SFX_CORE_HEXFIN_DIVIDED
          }
        }
        nk_tree_pop(ctx);
      }
#endif // SHR3D_AUDIO_PIPEWIRE
#ifdef SHR3D_AUDIO_SDL
      if (nk_tree_push(ctx, NK_TREE_TAB, "SDL", NK_MINIMIZED))
      {
        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
        {
          nk_label(ctx, "Device Input (req. restart)", NK_TEXT_LEFT);
          i32 selectedInputDevice = 0;
          std::vector<const char*> c_strs(Global::audioSdlDevicesInput.size());
          for (i32 i = 0; i < Global::audioSdlDevicesInput.size(); ++i)
          {
            c_strs[i] = Global::audioSdlDevicesInput[i].c_str();
            if (Settings::audioSdlDeviceInput == Global::audioSdlDevicesInput[i])
              selectedInputDevice = i;
          }
          selectedInputDevice = nk_combo(ctx, c_strs.data(), c_strs.size(), selectedInputDevice, 25, nk_vec2(200, 200));
          Settings::audioSdlDeviceInput = Global::audioSdlDevicesInput[selectedInputDevice];
        }
        {
          nk_label(ctx, "Device Output (req. restart)", NK_TEXT_LEFT);
          i32 selectedDevice = 0;
          std::vector<const char*> c_strs(Global::audioSdlDevicesOutput.size());
          for (i32 i = 0; i < Global::audioSdlDevicesOutput.size(); ++i)
          {
            c_strs[i] = Global::audioSdlDevicesOutput[i].c_str();
            if (Settings::audioSdlDeviceOutput == Global::audioSdlDevicesOutput[i])
              selectedDevice = i;
          }
          selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(200, 200));
          Settings::audioSdlDeviceOutput = Global::audioSdlDevicesOutput[selectedDevice];
        }
        {
          nk_label(ctx, "BlockSize (req. restart)", NK_TEXT_LEFT);
          static const char* audioBlockSizeNames[] = {
            "16", "32", "64", "128", "256", "512", "1024", "2048"
          };
          static const i32 audioBlockSize[] = {
            16, 32, 64, 128, 256, 512, 1024, 2048
          };
          i32 index = i32(log2(Settings::audioSdlBlockSize)) - 4;
          index = nk_combo(ctx, audioBlockSizeNames, ARRAY_SIZE(audioBlockSizeNames), index, 25, nk_vec2(200, 200));
          Settings::audioSdlBlockSize = audioBlockSize[index];
        }
        {
          nk_label(ctx, "SampleRate (req. restart)", NK_TEXT_LEFT);
          static const char* audioSampleRateNames[] = {
            "44100",
            "48000",
            "96000",
            "192000"
          };
          static const i32 audioSamples[] = {
            44100,
            48000,
            96000,
            192000
          };
          i32 index = 0;
          switch (Settings::audioSdlSampleRate)
          {
          case 44100:
            index = 0;
            break;
          case 48000:
            index = 1;
            break;
          case 96000:
            index = 2;
            break;
          case 192000:
            index = 3;
            break;
          default:
            unreachable();
          }
          index = nk_combo(ctx, audioSampleRateNames, ARRAY_SIZE(audioSampleRateNames), index, 25, nk_vec2(200, 200));
          Settings::audioSdlSampleRate = audioSamples[index];
        }
        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
        nk_property_int(ctx, "Channel Input:", 0, &Settings::audioSdlChannelInput, 1, 1, 1);
#ifdef SHR3D_COOP
        nk_property_int(ctx, "Channel Input Co-Op:", 0, &Settings::audioSdlChannelInputCoop, 1, 1, 1);
#endif // SHR3D_COOP
        nk_tree_pop(ctx);
      }
#endif // SHR3D_AUDIO_SDL
#ifdef SHR3D_AUDIO_WASAPI
      if (nk_tree_push(ctx, NK_TREE_TAB, "WASAPI", NK_MINIMIZED))
      {
        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
        {
          nk_label(ctx, "Device Input (req. restart)", NK_TEXT_LEFT);
          i32 selectedInputDevice = 0;
          std::vector<const char*> c_strs(Global::audioWasapiDevicesInput.size());
          for (i32 i = 0; i < i32(Global::audioWasapiDevicesInput.size()); ++i)
          {
            c_strs[i] = Global::audioWasapiDevicesInput[i].c_str();
            if (Settings::audioWasapiDeviceInput == Global::audioWasapiDevicesInput[i])
              selectedInputDevice = i;
          }
          selectedInputDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedInputDevice, 25, nk_vec2(200, 200));
          Settings::audioWasapiDeviceInput = Global::audioWasapiDevicesInput[selectedInputDevice];
        }
        {
          nk_label(ctx, "Device Output (req. restart)", NK_TEXT_LEFT);
          i32 selectedDevice = 0;
          std::vector<const char*> c_strs(Global::audioWasapiDevicesOutput.size());
          for (i32 i = 0; i < i32(Global::audioWasapiDevicesOutput.size()); ++i)
          {
            c_strs[i] = Global::audioWasapiDevicesOutput[i].c_str();
            if (Settings::audioWasapiDeviceOutput == Global::audioWasapiDevicesOutput[i])
              selectedDevice = i;
          }
          selectedDevice = nk_combo(ctx, c_strs.data(), i32(c_strs.size()), selectedDevice, 25, nk_vec2(200, 200));
          Settings::audioWasapiDeviceOutput = Global::audioWasapiDevicesOutput[selectedDevice];
        }
        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
        nk_property_int(ctx, "Channel Input 0:", 0, &Settings::audioWasapiChannelInput0, 1, 1, 1);
#ifdef SHR3D_COOP
        nk_property_int(ctx, "Channel Input Co-Op:", 0, &Settings::audioWasapiChannelInput1, 1, 1, 1);
#endif // SHR3D_COOP
#if 1
        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
        nk_checkbox_label(ctx, "Exlusive Mode", &Settings::audioWasapiExclusiveMode);
        if (nk_tree_push(ctx, NK_TREE_TAB, "Exlusive Mode", NK_MINIMIZED))
        {
          nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
          {
            nk_label(ctx, "BlockSize (req. restart)", NK_TEXT_LEFT);
            static const char* audioBlockSizeNames[] = {
              "16", "32", "64", "128", "256", "512", "1024", "2048"
            };
            static const i32 audioBlockSize[] = {
              16, 32, 64, 128, 256, 512, 1024, 2048
            };
            i32 index = i32(log2(Settings::audioWasapiBlockSize)) - 4;
            index = nk_combo(ctx, audioBlockSizeNames, ARRAY_SIZE(audioBlockSizeNames), index, 25, nk_vec2(200, 200));
            Settings::audioWasapiBlockSize = audioBlockSize[index];
          }
          {
            nk_label(ctx, "SampleRate (req. restart)", NK_TEXT_LEFT);
            static const char* audioSampleRateNames[] = {
              "44100",
              "48000",
              "96000",
              "192000"
            };
            static const i32 audioSamples[] = {
              44100,
              48000,
              96000,
              192000
            };
            i32 index = 0;
            switch (Settings::audioWasapiSampleRate)
            {
            case 44100:
              index = 0;
              break;
            case 48000:
              index = 1;
              break;
            case 96000:
              index = 2;
              break;
            case 192000:
              index = 3;
              break;
            default:
              unreachable();
            }
            index = nk_combo(ctx, audioSampleRateNames, ARRAY_SIZE(audioSampleRateNames), index, 25, nk_vec2(200, 200));
            Settings::audioWasapiSampleRate = audioSamples[index];
          }
          nk_tree_pop(ctx);
        }
#endif
        nk_tree_pop(ctx);
      }
#endif // SHR3D_AUDIO_WASAPI
#ifdef SHR3D_AUDIO_WEBAUDIO
      if (nk_tree_push(ctx, NK_TREE_TAB, "WebAudio", NK_MINIMIZED))
      {
        nk_property_int(ctx, "Channel Input 0:", 0, &Settings::audioWebAudioChannelInput0, 1, 1, 1);
        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
        {
          nk_label(ctx, "SampleRate (req. restart)", NK_TEXT_LEFT);
          static const char* audioSampleRateNames[] = {
            "44100",
            "48000",
            "96000",
            "192000"
          };
          static const i32 audioSamples[] = {
            44100,
            48000,
            96000,
            192000
          };
          i32 index = 0;
          switch (Settings::audioWebAudioSampleRate)
          {
          case 44100:
            index = 0;
            break;
          case 48000:
            index = 1;
            break;
          case 96000:
            index = 2;
            break;
          case 192000:
            index = 3;
            break;
          default:
            unreachable();
          }
          index = nk_combo(ctx, audioSampleRateNames, ARRAY_SIZE(audioSampleRateNames), index, 25, nk_vec2(200, 200));
          Settings::audioWebAudioSampleRate = audioSamples[index];
        }
        nk_tree_pop(ctx);
      }
#endif // SHR3D_AUDIO_WEBAUDIO
      nk_tree_pop(ctx);
    }
    if (nk_tree_push(ctx, NK_TREE_TAB, "Environment", NK_MINIMIZED))
    {
      {
        if (nk_tree_push(ctx, NK_TREE_TAB, "Clear Color", NK_MINIMIZED))
        {
          nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
          uiRowColorSelect("Clear Color", Settings::environmentClearColor);
          nk_tree_pop(ctx);
        }

#ifdef SHR3D_ENVIRONMENT_MILK
        if (nk_tree_push(ctx, NK_TREE_TAB, "Milk", NK_MINIMIZED))
        {
          nk_checkbox_label(ctx, "Enabled", &Settings::environmentMilk);
          static char milkSearchText[256] = "";
          static i32 milkSearchTextLength = 0;
          nk_edit_string(ctx, NK_EDIT_SIMPLE, milkSearchText, &milkSearchTextLength, sizeof(milkSearchText), nk_filter_default);
          const std::string searchText(milkSearchText, milkSearchTextLength);

          nk_layout_row_dynamic(ctx, 390, 2);

          if (nk_group_begin(ctx, "Available", NK_WINDOW_BORDER | NK_WINDOW_TITLE)) {
            nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
            nk_layout_row_template_push_dynamic(ctx);
            nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
            nk_layout_row_template_end(ctx);

            for (i32 i = 0; i < Global::milkPresetNames.size(); ++i)
            {
              if (filterTextOut(Global::milkPresetNames[i].c_str(), searchText.c_str()))
                continue;

              if (nk_button_label(ctx, Global::milkPresetNames[i].c_str()))
              {
                Global::milkCurrentPresetIndex = i;
              }

              if (nk_button_symbol(ctx, NK_SYMBOL_TRIANGLE_RIGHT))
              {
                Global::milkActivePresets.push_back(i);
                Global::milkCurrentPresetIndex = i;
              }
            }
            nk_group_end(ctx);
          }

          if (nk_group_begin(ctx, "Active", NK_WINDOW_BORDER | NK_WINDOW_TITLE)) {

            nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
            nk_layout_row_template_push_dynamic(ctx);
            nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
            nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
            nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
            nk_layout_row_template_end(ctx);

            for (i32 i = 0; i < Global::milkActivePresets.size(); ++i)
            {
              if (filterTextOut(Global::milkPresetNames[Global::milkActivePresets[i]].c_str(), searchText.c_str()))
                continue;

              if (nk_button_label(ctx, Global::milkPresetNames[Global::milkActivePresets[i]].c_str()))
              {
                Global::milkCurrentPresetIndex = Global::milkActivePresets[i];
              }
              if (nk_button_label(ctx, "X"))
              {
                Global::milkActivePresets.erase(Global::milkActivePresets.begin() + i);
                if (i >= 1)
                {
                  Global::milkCurrentPresetIndex = Global::milkActivePresets[i - 1];
                }
                else
                {
                  Global::milkCurrentPresetIndex = -1;
                }
              }
              if (i >= 1)
              {
                if (nk_button_symbol(ctx, NK_SYMBOL_TRIANGLE_UP))
                {
                  const i32 tmp = Global::milkActivePresets[i];
                  Global::milkActivePresets[i] = Global::milkActivePresets[i - 1];
                  Global::milkActivePresets[i - 1] = tmp;
                }
              }
              else
              {
                nk_spacing(ctx, 1);
              }
              if (i < Global::milkActivePresets.size() - 1)
              {
                if (nk_button_symbol(ctx, NK_SYMBOL_TRIANGLE_DOWN))
                {
                  const i32 tmp = Global::milkActivePresets[i];
                  Global::milkActivePresets[i] = Global::milkActivePresets[i + 1];
                  Global::milkActivePresets[i + 1] = tmp;
                }
              }
              else
              {
                nk_spacing(ctx, 1);
              }
            }
            nk_group_end(ctx);
          }
          nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
          nk_property_float(ctx, "BeatSensitivity:", 0.0f, &Settings::environmentMilkBeatSensitivity, 1.0f, 0.001f, 0.001f);
          nk_property_int(ctx, "Duration:", 0, &Settings::environmentMilkDuration, 3600, 1, 1);
          nk_property_int(ctx, "MeshSize:", 0, &Settings::environmentMilkMeshSize, 512, 1, 1);
          nk_property_int(ctx, "FrameSkip:", 0, &Settings::environmentMilkFrameSkip, 10, 1, 1);
          nk_property_float(ctx, "FrameSpeed:", 0.0f, &Settings::environmentMilkFrameSpeed, 10.0f, 0.001f, 0.001f);
          nk_checkbox_label(ctx, "Shuffle", &Settings::environmentMilkShuffle);
          nk_tree_pop(ctx);
        }
#endif // SHR3D_ENVIRONMENT_MILK
#ifdef SHR3D_ENVIRONMENT_SKYBOX
        if (nk_tree_push(ctx, NK_TREE_TAB, "Skybox", NK_MINIMIZED))
        {
          nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
          {
            nk_label(ctx, "Skybox", NK_TEXT_LEFT);
            std::vector<const char*> environmentSkyboxNames = { "" };
            environmentSkyboxNames.resize(Global::environmentSkyboxNames.size() + 1);
            i32 selectedSkybox = 0;
            for (i32 i = 0; i < i32(Global::environmentSkyboxNames.size()); ++i)
            {
              environmentSkyboxNames[i + 1] = Global::environmentSkyboxNames[i].c_str();
              if (Settings::environmentSkybox == Global::environmentSkyboxNames[i])
                selectedSkybox = i + 1;
            }
            selectedSkybox = nk_combo(ctx, environmentSkyboxNames.data(), i32(environmentSkyboxNames.size()), selectedSkybox, 25, nk_vec2(200, 200));
            if (selectedSkybox == 0)
              Settings::environmentSkybox.clear();
            else
              Settings::environmentSkybox = Global::environmentSkyboxNames[selectedSkybox - 1];
          }
          nk_label(ctx, "Rotation", NK_TEXT_LEFT);
          nk_slider_float(ctx, -0.5f, &Settings::environmentSkyboxRotation, 0.5f, 0.0001f);
          nk_tree_pop(ctx);
        }
#endif // SHR3D_ENVIRONMENT_SKYBOX
#ifdef SHR3D_ENVIRONMENT_STAGE
        if (nk_tree_push(ctx, NK_TREE_TAB, "Stage", NK_MINIMIZED))
        {
          nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
          {
            nk_label(ctx, "Stage", NK_TEXT_LEFT);
            std::vector<const char*> modelNames = { "" };
            modelNames.resize(Global::environmentStageNames.size() + 1);
            i32 selectedModel = 0;
            for (i32 j = 0; j < i32(Global::environmentStageNames.size()); ++j)
            {
              modelNames[j + 1] = Global::environmentStageNames[j].c_str();
              if (Settings::environmentStage == Global::environmentStageNames[j])
                selectedModel = j + 1;
            }
            selectedModel = nk_combo(ctx, modelNames.data(), i32(modelNames.size()), selectedModel, 25, nk_vec2(200, 200));
            if (selectedModel == 0)
              Settings::environmentStage.clear();
            else
              Settings::environmentStage = Global::environmentStageNames[selectedModel - 1];
          }
          nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
          nk_property_float(ctx, "Rotation", -0.5f, &Settings::environmentStageRotation, 0.5f, 0.001f, 0.001f);
#ifndef PLATFORM_OPENXR_ANDROID
          nk_property_float(ctx, "PlayerHeight", 0.001f, &Settings::environmentStagePlayerHeight, 1000.0f, 0.001f, 0.001f);
#endif // PLATFORM_OPENXR_ANDROID
          nk_property_float(ctx, "Scale", 0.001f, &Settings::environmentStageScale, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "X", -1000.0f, &Settings::environmentStageX, 1000.0f, 0.01f, 0.01f);
          nk_property_float(ctx, "Y", -1000.0f, &Settings::environmentStageY, 1000.0f, 0.01f, 0.01f);
          nk_property_float(ctx, "Z", -1000.0f, &Settings::environmentStageZ, 1000.0f, 0.01f, 0.01f);
          nk_tree_pop(ctx);
        }
#endif // SHR3D_ENVIRONMENT_STAGE
      }
      nk_tree_pop(ctx);
    }
    if (nk_tree_push(ctx, NK_TREE_TAB, "Camera", NK_MINIMIZED))
    {
      {
        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
        static const char* cameraModeNames[] = {
          "Fixed",
          "Parallax",
#ifdef SHR3D_OPENXR_PCVR
          "PCVR Parallax",
#endif // SHR3D_OPENXR_PCVR
        };
        nk_label(ctx, "Camera Mode", NK_TEXT_LEFT);
        Settings::cameraMode = CameraMode(nk_combo(ctx, cameraModeNames, ARRAY_SIZE(cameraModeNames), to_underlying_(Settings::cameraMode), 25, nk_vec2(200, 200)));
#ifdef SHR3D_OPENXR_PCVR
        nk_label(ctx, "Camera Mode PCVR", NK_TEXT_LEFT);
        Settings::cameraPcVrMode = CameraMode(nk_combo(ctx, cameraModeNames, ARRAY_SIZE(cameraModeNames), to_underlying_(Settings::cameraPcVrMode), 25, nk_vec2(200, 200)));
#endif // SHR3D_OPENXR_PCVR
#ifndef PLATFORM_OPENXR_ANDROID
        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
        nk_property_float(ctx, "Field of View:", 0.0f, &Settings::cameraFieldOfView, 360.0f, 0.001f, 0.001f);
#endif // PLATFORM_OPENXR_ANDROID
        if (nk_tree_push(ctx, NK_TREE_TAB, "Fixed Mode", NK_MINIMIZED))
        {
          nk_property_float(ctx, "X:", -1000.0f, &Settings::cameraFixedX, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "XRotation:", -1000.0f, &Settings::cameraFixedXRotation, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "Y:", -1000.0f, &Settings::cameraFixedY, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "YRotation:", -1000.0f, &Settings::cameraFixedYRotation, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "Z:", -1000.0f, &Settings::cameraFixedZ, 1000.0f, 0.001f, 0.001f);
          nk_tree_pop(ctx);
        }
        if (nk_tree_push(ctx, NK_TREE_TAB, "Parallax Mode", NK_MINIMIZED))
        {
          nk_property_float(ctx, "AnchorTackingDuration:", 0.0f, &Settings::cameraParallaxAnchorTackingDuration, 10.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "Highway Rotation:", -0.5f, &Settings::cameraParallaxHighwayRotation, 0.5f, 0.01f, 0.01f);
          nk_property_float(ctx, "Highway Scale:", 0.01f, &Settings::cameraParallaxHighwayScale, 100.0f, 0.01f, 0.01f);
          nk_property_float(ctx, "Highway X:", -1000.0f, &Settings::cameraParallaxHighwayX, 1000.0f, 0.01f, 0.01f);
          nk_property_float(ctx, "Highway XFactor:", -1000.0f, &Settings::cameraParallaxHighwayXFactor, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "Highway Y:", -1000.0f, &Settings::cameraParallaxHighwayY, 1000.0f, 0.01f, 0.01f);
          nk_property_float(ctx, "Highway YFactor:", -1000.0f, &Settings::cameraParallaxHighwayYFactor, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "Highway Z:", -1000.0f, &Settings::cameraParallaxHighwayZ, 1000.0f, 0.01f, 0.01f);
          nk_property_float(ctx, "Highway ZFactor:", -1000.0f, &Settings::cameraParallaxHighwayZFactor, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "X:", -1000.0f, &Settings::cameraParallaxX, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "XFactor:", -1000.0f, &Settings::cameraParallaxXFactor, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "XRotation:", -1000.0f, &Settings::cameraParallaxXRotation, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "Y:", -1000.0f, &Settings::cameraParallaxY, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "YFactor:", -1000.0f, &Settings::cameraParallaxYFactor, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "YRotation:", -1000.0f, &Settings::cameraParallaxYRotation, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "Z:", -1000.0f, &Settings::cameraParallaxZ, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "ZFactor:", -1000.0f, &Settings::cameraParallaxZFactor, 1000.0f, 0.001f, 0.001f);
          nk_tree_pop(ctx);
        }
#ifdef SHR3D_OPENXR_PCVR
        if (nk_tree_push(ctx, NK_TREE_TAB, "PCVR Parallax Mode", NK_MINIMIZED))
        {
          nk_property_float(ctx, "PCVR AnchorTackingDuration:", 0.0f, &Settings::cameraPcVrParallaxAnchorTackingDuration, 10.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "PCVR Highway Rotation:", -0.5f, &Settings::cameraPcVrParallaxHighwayRotation, 0.5f, 0.01f, 0.01f);
          nk_property_float(ctx, "PCVR Highway Scale:", 0.01f, &Settings::cameraPcVrParallaxHighwayScale, 100.0f, 0.01f, 0.01f);
          nk_property_float(ctx, "PCVR Highway X:", -1000.0f, &Settings::cameraPcVrParallaxHighwayX, 1000.0f, 0.01f, 0.01f);
          nk_property_float(ctx, "PCVR Highway XFactor:", -1000.0f, &Settings::cameraPcVrParallaxHighwayXFactor, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "PCVR Highway Y:", -1000.0f, &Settings::cameraPcVrParallaxHighwayY, 1000.0f, 0.01f, 0.01f);
          nk_property_float(ctx, "PCVR Highway YFactor:", -1000.0f, &Settings::cameraPcVrParallaxHighwayYFactor, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "PCVR Highway Z:", -1000.0f, &Settings::cameraPcVrParallaxHighwayZ, 1000.0f, 0.01f, 0.01f);
          nk_property_float(ctx, "PCVR Highway ZFactor:", -1000.0f, &Settings::cameraPcVrParallaxHighwayZFactor, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "PCVR X:", -1000.0f, &Settings::cameraPcVrParallaxX, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "PCVR XFactor:", -1000.0f, &Settings::cameraPcVrParallaxXFactor, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "PCVR XRotation:", -1000.0f, &Settings::cameraPcVrParallaxXRotation, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "PCVR Y:", -1000.0f, &Settings::cameraPcVrParallaxY, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "PCVR YFactor:", -1000.0f, &Settings::cameraPcVrParallaxYFactor, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "PCVR YRotation:", -1000.0f, &Settings::cameraPcVrParallaxYRotation, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "PCVR Z:", -1000.0f, &Settings::cameraPcVrParallaxZ, 1000.0f, 0.001f, 0.001f);
          nk_property_float(ctx, "PCVR ZFactor:", -1000.0f, &Settings::cameraPcVrParallaxZFactor, 1000.0f, 0.001f, 0.001f);
          nk_tree_pop(ctx);
        }
#endif // SHR3D_OPENXR_PCVR
      }
      nk_tree_pop(ctx);
    }
    if (nk_tree_push(ctx, NK_TREE_TAB, "Graphics", NK_MINIMIZED))
    {
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      {
#ifdef SHR3D_GRAPHICS_MSAA
        {
          nk_label(ctx, "MSAA (req. restart)", NK_TEXT_LEFT);
          static const char* msaaNames[] = {
            "Off",
            "2",
            "4",
#ifndef PLATFORM_OPENXR_ANDROID
            "8" // for unknown reason 8xMSAA does not work on the PICO 4
#endif // PLATFORM_OPENXR_ANDROID
          };
          Settings::graphicsMSAA = nk_combo(ctx, msaaNames, ARRAY_SIZE(msaaNames), Settings::graphicsMSAA, 25, nk_vec2(200, 200));
        }
#endif // SHR3D_GRAPHICS_MSAA
        {
          nk_label(ctx, "Fullscreen Mode", NK_TEXT_LEFT);
          static const char* fullscreenModeNames[] = {
            "Windowed",
            "Borderless"
          };
          const FullscreenMode previousFullScreenMode = Settings::graphicsFullscreen;
          Settings::graphicsFullscreen = FullscreenMode(nk_combo(ctx, fullscreenModeNames, ARRAY_SIZE(fullscreenModeNames), to_underlying_(Settings::graphicsFullscreen), 25, nk_vec2(200, 200)));
          if (previousFullScreenMode != Settings::graphicsFullscreen)
          {
#ifdef SHR3D_WINDOW_SDL
            switch (Settings::graphicsFullscreen)
            {
            case FullscreenMode::windowed:
              SDL_SetWindowFullscreen(Global::window, 0);
              break;
            case FullscreenMode::borderless:
              SDL_SetWindowFullscreen(Global::window, SDL_WINDOW_FULLSCREEN_DESKTOP);
              break;
            default:
              unreachable();
            }
#endif // SHR3D_WINDOW_SDL
          }
        }

#ifdef SHR3D_OPENGL_SPIR_V
        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
        nk_checkbox_label(ctx, "SPRI-V", &Settings::graphicsSpirV);
        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
#endif // SHR3D_OPENGL_SPIR_V

        {
          nk_label(ctx, "Scaling", NK_TEXT_LEFT);
          static const char* scalingModeNames[] = {
            "Hor+",
            "Strech (16:9)"
          };
          Settings::graphicsScaling = ScalingMode(nk_combo(ctx, scalingModeNames, ARRAY_SIZE(scalingModeNames), to_underlying_(Settings::graphicsScaling), 25, nk_vec2(200, 200)));
        }
#ifdef PLATFORM_QUEST_3
        {
          nk_label(ctx, "Refresh Rate (req. restart):", NK_TEXT_LEFT);
          std::vector<std::string> refreshRates(Global::graphicsRefreshRates.size());
          for (i32 i = 0; i < Global::graphicsRefreshRates.size(); ++i)
            refreshRates[i] = to_string(Global::graphicsRefreshRates[i]);
          std::vector<const char*> refreshRatesCStrs(Global::graphicsRefreshRates.size());
          for (i32 i = 0; i < Global::graphicsRefreshRates.size(); ++i)
            refreshRatesCStrs[i] = refreshRates[i].c_str();
          Settings::graphicsRefreshRate = nk_combo(ctx, refreshRatesCStrs.data(), i32(refreshRatesCStrs.size()), Settings::graphicsRefreshRate, 25, nk_vec2(200, 200));
        }
#endif // PLATFORM_QUEST_3
#if !defined(PLATFORM_EMSCRIPTEN) && !defined(PLATFORM_QUEST_3)
        {
          nk_label(ctx, "VSync", NK_TEXT_LEFT);
          static const char* vSyncModeNames[] = {
            "Off",
            "On",
            "Adaptive"
          };
          const VSyncMode previousVSyncMode = Settings::graphicsVSync;
          Settings::graphicsVSync = VSyncMode(nk_combo(ctx, vSyncModeNames, ARRAY_SIZE(vSyncModeNames), to_underlying_(Settings::graphicsVSync), 25, nk_vec2(200, 200)));
          if (previousVSyncMode != Settings::graphicsVSync)
          {
#ifdef SHR3D_WINDOW_SDL
            SDL_GL_SetSwapInterval(to_underlying_(Settings::graphicsVSync));
#endif // SHR3D_WINDOW_SDL
#ifdef SHR3D_WINDOW_WIN32
            wglSwapIntervalEXT(to_underlying_(Settings::graphicsVSync));
#endif // SHR3D_WINDOW_SDL
          }
        }
#endif // !PLATFORM_EMSCRIPTEN && !PLATFORM_QUEST_3
      }
      nk_tree_pop(ctx);
    }
    if (nk_tree_push(ctx, NK_TREE_TAB, "Highway", NK_MINIMIZED))
    {
#ifdef SHR3D_RENDERER_DEVELOPMENT
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      nk_label(ctx, "Renderer:", NK_TEXT_LEFT);
      {
        static const char* rendererNames[] = {
          "Production",
          "Development"
        };
        Settings::highwayRenderer = Renderer(nk_combo(ctx, rendererNames, ARRAY_SIZE(rendererNames), to_underlying_(Settings::highwayRenderer), 25, nk_vec2(200, 200)));
      }
#endif // SHR3D_RENDERER_DEVELOPMENT
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_property_float(ctx, "ViewDistance:", 10.0f, &Settings::highwayViewDistance, 1000.0f, 0.01f, 0.1f);
      nk_property_float(ctx, "FadeFarDistance:", 0.1f, &Settings::highwayFadeFarDistance, 1000.0f, 0.01f, 0.1f);
      nk_property_float(ctx, "FadeNearDistance:", 0.0f, &Settings::highwayFadeNearDistance, 10.0f, 0.01f, 0.1f);
      nk_property_float(ctx, "FadeNearStrength:", 0.0f, &Settings::highwayFadeNearStrength, 1.0f, 0.01f, 0.1f);
      nk_property_float(ctx, "AnchorExponent:", 0.0f, &Settings::highwayAnchorColorExponent, 5.0f, 0.01f, 0.01f);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      uiRowColorSelect("Anchor0", Settings::highwayAnchorColor[0]);
      uiRowColorSelect("Anchor1", Settings::highwayAnchorColor[1]);
      uiRowColorSelect("Anchor2", Settings::highwayAnchorColor[2]);
      uiRowColorSelect("Anchor3", Settings::highwayAnchorColor[3]);
      uiRowColorSelect("Capo", Settings::highwayCapoColor);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_checkbox_label(ctx, "Chord Box", &Settings::highwayChordBox);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      uiRowColorSelect("Chord Box", Settings::highwayChordBoxColor);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_checkbox_label(ctx, "Chord Box Arpeggio", &Settings::highwayChordBoxArpeggio);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      uiRowColorSelect("Chord Box Arpeggio", Settings::highwayChordBoxArpeggioColor);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_checkbox_label(ctx, "Chord Fret Numbers", &Settings::highwayChordFretNumbers);
      nk_checkbox_label(ctx, "Chord Name", &Settings::highwayChordName);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      uiRowColorSelect("Chord Name", Settings::highwayChordNameColor);
      uiRowColorSelect("Dot Inlay 0", Settings::highwayDotInlayColor[0]);
      uiRowColorSelect("Dot Inlay 1", Settings::highwayDotInlayColor[1]);
      uiRowColorSelect("Dot Inlay 2", Settings::highwayDotInlayColor[2]);
      uiRowColorSelect("Dot Inlay 3", Settings::highwayDotInlayColor[3]);
      uiRowColorSelect("Finger Number", Settings::highwayFingerNumberColor);
      uiRowColorSelect("Fretboard Fret", Settings::highwayFretboardFretColor);
      uiRowColorSelect("Fretboard Fret Number 0", Settings::highwayFretboardFretNumberColor[0]);
      uiRowColorSelect("Fretboard Fret Number 1", Settings::highwayFretboardFretNumberColor[1]);
      uiRowColorSelect("Fretboard Fret Number 2", Settings::highwayFretboardFretNumberColor[2]);
      uiRowColorSelect("Fretboard NoteName 0", Settings::highwayFretboardNoteNameColor[0]);
      uiRowColorSelect("Fretboard NoteName 1", Settings::highwayFretboardNoteNameColor[1]);
      uiRowColorSelect("Ground Fret 0", Settings::highwayGroundFretColor[0]);
      uiRowColorSelect("Ground Fret 1", Settings::highwayGroundFretColor[1]);
      uiRowColorSelect("Sustain", Settings::highwaySustainColor);
      if (nk_tree_push(ctx, NK_TREE_TAB, "Instrument", NK_MINIMIZED))
      {
        if (nk_tree_push(ctx, NK_TREE_TAB, "Bass", NK_MINIMIZED))
        {
          nk_property_int(ctx, "First Wound String:", 0, &Settings::highwayInstrumentBassFirstWoundString, Const::highwayInstrumentBassStringCount - 1, 1, 1);
          nk_property_float(ctx, "Fret Width:", 0.1f, &Settings::highwayInstrumentBassFretSpacing, 10.0f, 0.01f, 0.005f);
          nk_property_float(ctx, "Fret Width Factor:", 0.0f, &Settings::highwayInstrumentBassFretSpacingFactor, 1.0f, 0.005f, 0.005f);
          nk_property_float(ctx, "String Spacing:", 0.01f, &Settings::highwayInstrumentBassStringSpacing, 1.0f, 0.01f, 0.005f);
          nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
          for (i32 i = Const::highwayInstrumentBassStringCount - 1; i >= 0; --i)
          {
            uiRowColorSelect((std::string("String") + std::to_string(i)).c_str(), Settings::highwayInstrumentBassStringColor[i]);
          }
          nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
          nk_checkbox_label(ctx, "5StringHideString0", &Settings::highwayInstrumentBass5StringHideString0);
          nk_property_int(ctx, "5StringTuning0:", -20, &Settings::highwayInstrumentBass5StringTuning[0], 20, -1, -1);
          nk_property_int(ctx, "5StringTuning1:", -20, &Settings::highwayInstrumentBass5StringTuning[1], 20, -1, -1);
          nk_property_int(ctx, "5StringTuning2:", -20, &Settings::highwayInstrumentBass5StringTuning[2], 20, -1, -1);
          nk_property_int(ctx, "5StringTuning3:", -20, &Settings::highwayInstrumentBass5StringTuning[3], 20, -1, -1);
          nk_tree_pop(ctx);
        }
        if (nk_tree_push(ctx, NK_TREE_TAB, "Guitar", NK_MINIMIZED))
        {
          nk_property_int(ctx, "First Wound String:", 0, &Settings::highwayInstrumentGuitarFirstWoundString, Const::highwayInstrumentGuitarStringCount - 1, 1, 1);
          nk_property_float(ctx, "Fret Width:", 0.1f, &Settings::highwayInstrumentGuitarFretSpacing, 10.0f, 0.01f, 0.005f);
          nk_property_float(ctx, "Fret Width Factor:", 0.0f, &Settings::highwayInstrumentGuitarFretSpacingFactor, 1.0f, 0.005f, 0.005f);
          nk_property_float(ctx, "String Spacing:", 0.01f, &Settings::highwayInstrumentGuitarStringSpacing, 1.0f, 0.01f, 0.005f);
          nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
          for (i32 i = Const::highwayInstrumentGuitarStringCount - 1; i >= 0; --i)
          {
            uiRowColorSelect((std::string("String") + std::to_string(i)).c_str(), Settings::highwayInstrumentGuitarStringColor[i]);
          }
          nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
          nk_checkbox_label(ctx, "7StringHideString0", &Settings::highwayInstrumentGuitar7StringHideString0);
          nk_property_int(ctx, "7StringTuning0:", -20, &Settings::highwayInstrumentGuitar7StringTuning[0], 20, -1, -1);
          nk_property_int(ctx, "7StringTuning1:", -20, &Settings::highwayInstrumentGuitar7StringTuning[1], 20, -1, -1);
          nk_property_int(ctx, "7StringTuning2:", -20, &Settings::highwayInstrumentGuitar7StringTuning[2], 20, -1, -1);
          nk_property_int(ctx, "7StringTuning3:", -20, &Settings::highwayInstrumentGuitar7StringTuning[3], 20, -1, -1);
          nk_property_int(ctx, "7StringTuning4:", -20, &Settings::highwayInstrumentGuitar7StringTuning[4], 20, -1, -1);
          nk_property_int(ctx, "7StringTuning5:", -20, &Settings::highwayInstrumentGuitar7StringTuning[5], 20, -1, -1);
          nk_tree_pop(ctx);
        }
        nk_tree_pop(ctx);
      }
      nk_checkbox_label(ctx, "Beat", &Settings::highwayBeat);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      uiRowColorSelect("Beat On", Settings::highwayBeatColor[0]);
      uiRowColorSelect("Beat Off", Settings::highwayBeatColor[1]);
      uiRowColorSelect("Beat Strum Direction Down Stroke", Settings::highwayBeatStrumDirectionColor[0]);
      uiRowColorSelect("Beat Strum Direction Up Stroke", Settings::highwayBeatStrumDirectionColor[1]);
      nk_label(ctx, "Note Shape:", NK_TEXT_LEFT);
      {
        static const char* noteShapeNames[] = {
          "Hex",
          "Rect"
        };
        Settings::highwayNoteShape = NoteShape(nk_combo(ctx, noteShapeNames, ARRAY_SIZE(noteShapeNames), to_underlying_(Settings::highwayNoteShape), 25, nk_vec2(200, 200)));
      }
      if (nk_tree_push(ctx, NK_TREE_TAB, "Note Symbols", NK_MINIMIZED))
      {
        static const char* noteSymbolNames[] = {
          "None",
          "Fret Mute",
          "Hammer On",
          "Harmonic",
          "Palm Mute",
          "Pinch Harmonic",
          "Pop",
          "Pull Off",
          "Slap",
          "Tap",

          "Slim X",
          "Slim eye",
          "Triangle down",
          "Triangle up",
          "Wide X",
          "Wide V",
          "Wide upside V",
          "Wide offset V",
          "Wide eye",
          "Sine Right",
          "Sine Left",
        };
        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
        nk_label(ctx, "Fret Mute:", NK_TEXT_LEFT);
        Settings::highwayNoteSymbolFretMute = NoteSymbols(nk_combo(ctx, noteSymbolNames, ARRAY_SIZE(noteSymbolNames), to_underlying_(Settings::highwayNoteSymbolFretMute), 25, nk_vec2(200, 200)));
        nk_label(ctx, "Hammer On:", NK_TEXT_LEFT);
        Settings::highwayNoteSymbolHammerOn = NoteSymbols(nk_combo(ctx, noteSymbolNames, ARRAY_SIZE(noteSymbolNames), to_underlying_(Settings::highwayNoteSymbolHammerOn), 25, nk_vec2(200, 200)));
        nk_label(ctx, "Harmonic:", NK_TEXT_LEFT);
        Settings::highwayNoteSymbolHarmonic = NoteSymbols(nk_combo(ctx, noteSymbolNames, ARRAY_SIZE(noteSymbolNames), to_underlying_(Settings::highwayNoteSymbolHarmonic), 25, nk_vec2(200, 200)));
        nk_label(ctx, "Palm Mute:", NK_TEXT_LEFT);
        Settings::highwayNoteSymbolPalmMute = NoteSymbols(nk_combo(ctx, noteSymbolNames, ARRAY_SIZE(noteSymbolNames), to_underlying_(Settings::highwayNoteSymbolPalmMute), 25, nk_vec2(200, 200)));
        nk_label(ctx, "Pinch Harmonic:", NK_TEXT_LEFT);
        Settings::highwayNoteSymbolPinchHarmonic = NoteSymbols(nk_combo(ctx, noteSymbolNames, ARRAY_SIZE(noteSymbolNames), to_underlying_(Settings::highwayNoteSymbolPinchHarmonic), 25, nk_vec2(200, 200)));
        nk_label(ctx, "Pop:", NK_TEXT_LEFT);
        Settings::highwayNoteSymbolPop = NoteSymbols(nk_combo(ctx, noteSymbolNames, ARRAY_SIZE(noteSymbolNames), to_underlying_(Settings::highwayNoteSymbolPop), 25, nk_vec2(200, 200)));
        nk_label(ctx, "Pull Off:", NK_TEXT_LEFT);
        Settings::highwayNoteSymbolPullOff = NoteSymbols(nk_combo(ctx, noteSymbolNames, ARRAY_SIZE(noteSymbolNames), to_underlying_(Settings::highwayNoteSymbolPullOff), 25, nk_vec2(200, 200)));
        nk_label(ctx, "Slap:", NK_TEXT_LEFT);
        Settings::highwayNoteSymbolSlap = NoteSymbols(nk_combo(ctx, noteSymbolNames, ARRAY_SIZE(noteSymbolNames), to_underlying_(Settings::highwayNoteSymbolSlap), 25, nk_vec2(200, 200)));
        nk_label(ctx, "Tap:", NK_TEXT_LEFT);
        Settings::highwayNoteSymbolTap = NoteSymbols(nk_combo(ctx, noteSymbolNames, ARRAY_SIZE(noteSymbolNames), to_underlying_(Settings::highwayNoteSymbolTap), 25, nk_vec2(200, 200)));
        nk_tree_pop(ctx);
      }
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_checkbox_label(ctx, "Note Stand", &Settings::highwayNoteStand);
      nk_checkbox_label(ctx, "Note Stand Zero", &Settings::highwayNoteStandZero);
      nk_checkbox_label(ctx, "Finger Numbers", &Settings::highwayFingerNumbers);
      nk_checkbox_label(ctx, "Fretboard Frets", &Settings::highwayFretboardFrets);
      nk_checkbox_label(ctx, "Fretboard Note Names", &Settings::highwayFretboardNoteNames);
      nk_property_float(ctx, "Fretboard Note Height:", 0.5f, &Settings::highwayFretboardNoteHeight, 3.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Fretboard Note Width:", 0.5f, &Settings::highwayFretboardNoteWidth, 3.0f, 0.01f, 0.02f);
      nk_checkbox_label(ctx, "Fretboard Collision Notes", &Settings::highwayFretboardCollisionNotes);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      uiRowColorSelect("Fretboard Collision Notes 0", Settings::highwayFretboardCollisionNotesColor[0]);
      uiRowColorSelect("Fretboard Collision Notes 1", Settings::highwayFretboardCollisionNotesColor[1]);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_checkbox_label(ctx, "Fretboard Played Notes", &Settings::highwayFretboardPlayedNotesDot);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      uiRowColorSelect("Fretboard Played Notes Color 0", Settings::highwayFretboardPlayedNotesDotColor[0]);
      uiRowColorSelect("Fretboard Played Notes Color 1", Settings::highwayFretboardPlayedNotesDotColor[1]);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_checkbox_label(ctx, "Fretboard Strings", &Settings::highwayFretboardStrings);
      nk_checkbox_label(ctx, "Fretboard String Note Names", &Settings::highwayFretboardStringNoteNames);
#ifdef SHR3D_SFX_CORE_HEXFIN
      nk_checkbox_label(ctx, "Fretboard Played Notes Tuner", &Settings::highwayFretboardPlayedNotesTuner);
#endif // SHR3D_SFX_CORE_HEXFIN
      nk_property_float(ctx, "Note Bend Curve:", 0.0f, &Settings::highwayNoteBendCurve, 1.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Note Bend End Time:", 0.5f, &Settings::highwayNoteBendEndTime, 10.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Note Bend Speed:", 0.5f, &Settings::highwayNoteBendSpeed, 10.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Note Bend Hint Offset:", 0.01f, &Settings::highwayNoteBendHintOffset, 1.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Note Bend Hint Distance:", 0.01f, &Settings::highwayNoteBendHintDistance, 1.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Note Height:", 0.5f, &Settings::highwayNoteHeight, 3.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Note Width:", 0.5f, &Settings::highwayNoteWidth, 3.0f, 0.01f, 0.02f);
      nk_property_int(ctx, "Note Rotate:", -10, &Settings::highwayNoteRotate, 10, 1, 1);
      nk_property_float(ctx, "Note Rotate End Time:", 0.5f, &Settings::highwayNoteRotateEndTime, 10.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Note Rotate Speed:", 0.5f, &Settings::highwayNoteRotateSpeed, 10.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Note Sustain Curve Sample Distance:", 0.001f, &Settings::highwayNoteSustainCurveSampleDistance, 1.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Note Sustain Tremolo Sample Distanc:", 0.001f, &Settings::highwayNoteSustainTremoloSampleDistance, 3.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Note Sustain Tremolo Shake Strength:", 0.01f, &Settings::highwayNoteSustainTremoloShakeStrength, 3.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Note Sustain Width:", 0.1f, &Settings::highwayNoteSustainWidth, 3.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Note Sustain Width Zero:", 0.0f, &Settings::highwayNoteSustainWidthZero, 1.0f, 0.01f, 0.02f);
#ifdef SHR3D_PARTICLE
      nk_checkbox_label(ctx, "Particle Played Notes", &Settings::highwayParticlePlayedNotes);
      nk_checkbox_label(ctx, "Particle Collision Notes", &Settings::highwayParticleCollisionNotes);
      if (nk_tree_push(ctx, NK_TREE_TAB, "Particle", NK_MINIMIZED))
      {
        nk_property_int(ctx, "Shape:", 0, &Settings::highwayParticleShape, 2, 1, 1);
        nk_property_int(ctx, "Max Count:", 0, &Settings::highwayParticleMaxCount, Const::highwayParticleMaxCount, 1, 1);
        nk_property_float(ctx, "Spawns Per Second:", 0.0f, &Settings::highwayParticleSpawnsPerSecond, Const::highwayParticleMaxSpawnsPerSecond, 10.0f, 10.0f);
        nk_property_float(ctx, "Spawn Radius:", 0.1f, &Settings::highwayParticleSpawnRadius, 100.0f, 0.01f, 0.005f);
        nk_property_float(ctx, "Min Size:", 0.01f, &Settings::highwayParticleMinSize, 10.0f, 0.01f, 0.005f);
        nk_property_float(ctx, "Max Size:", 0.01f, &Settings::highwayParticleMaxSize, 10.0f, 0.01f, 0.005f);
        nk_property_float(ctx, "Min Life Time:", 0.01f, &Settings::highwayParticleMinLifeTime, 10.0f, 0.01f, 0.005f);
        nk_property_float(ctx, "Max Life Time:", 0.01f, &Settings::highwayParticleMaxLifeTime, 10.0f, 0.01f, 0.005f);
        nk_property_float(ctx, "Color Variation:", 0.0f, &Settings::highwayParticleColorVariation, 4.0f, 0.01f, 0.005f);
        nk_property_float(ctx, "Min Velocity X:", -10.0f, &Settings::highwayParticleMinVelocityX, 10.0f, 0.01f, 0.005f);
        nk_property_float(ctx, "Max Velocity X:", -10.0f, &Settings::highwayParticleMaxVelocityX, 10.0f, 0.01f, 0.005f);
        nk_property_float(ctx, "Min Velocity Y:", -10.0f, &Settings::highwayParticleMinVelocityY, 10.0f, 0.01f, 0.005f);
        nk_property_float(ctx, "Max Velocity Y:", -10.0f, &Settings::highwayParticleMaxVelocityY, 10.0f, 0.01f, 0.005f);
        nk_property_float(ctx, "Min Velocity Z:", -10.0f, &Settings::highwayParticleMinVelocityZ, 10.0f, 0.01f, 0.005f);
        nk_property_float(ctx, "Max Velocity Z:", -10.0f, &Settings::highwayParticleMaxVelocityZ, 10.0f, 0.01f, 0.005f);
        nk_property_float(ctx, "Acceleration X:", -10.0f, &Settings::highwayParticleAccelerationX, 10.0f, 0.01f, 0.005f);
        nk_property_float(ctx, "Acceleration Y:", -10.0f, &Settings::highwayParticleAccelerationY, 10.0f, 0.01f, 0.005f);
        nk_property_float(ctx, "Acceleration Z:", -10.0f, &Settings::highwayParticleAccelerationZ, 10.0f, 0.01f, 0.005f);
        nk_property_float(ctx, "Min Rotation Angle:", -10.0f, &Settings::highwayParticleMinRotationAngle, 10.0f, 0.01f, 0.005f);
        nk_property_float(ctx, "Max Rotation Angle:", -10.0f, &Settings::highwayParticleMaxRotationAngle, 10.0f, 0.01f, 0.005f);
        nk_tree_pop(ctx);
      }
#endif // SHR3D_PARTICLE
      nk_checkbox_label(ctx, "Reverse Strings", &Settings::highwayReverseStrings);
      nk_checkbox_label(ctx, "String Fade Unplayed", &Settings::highwayStringFadeUnplayed);
      nk_property_float(ctx, "Speed Multiplier:", 0.0f, &Settings::highwayScrollSpeed, 127.0f, 0.1f, 0.2f);
      nk_checkbox_label(ctx, "VU Meter", &Settings::highwayVUMeter);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      uiRowColorSelect("VU Meter Color 0", Settings::highwayVUMeterColor[0]);
      uiRowColorSelect("VU Meter Color 1", Settings::highwayVUMeterColor[1]);
      uiRowColorSelect("VU Meter Color 2", Settings::highwayVUMeterColor[2]);
      uiRowColorSelect("VU Meter Color 3", Settings::highwayVUMeterColor[3]);
      uiRowColorSelect("VU Meter Color 4", Settings::highwayVUMeterColor[4]);
#ifdef SHR3D_SFX_CORE_HEXFIN
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_checkbox_label(ctx, "Tuner", &Settings::highwayTuner);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      uiRowColorSelect("Tuner Color 0", Settings::highwayTunerColor[0]);
      uiRowColorSelect("Tuner Color 1", Settings::highwayTunerColor[1]);
      uiRowColorSelect("Tuner Color 2", Settings::highwayTunerColor[2]);
      uiRowColorSelect("Tuner Color 3", Settings::highwayTunerColor[3]);
#endif // SHR3D_SFX_CORE_HEXFIN
      nk_tree_pop(ctx);
    }
    if (nk_tree_push(ctx, NK_TREE_TAB, "Hud", NK_MINIMIZED))
    {
#ifdef SHR3D_HUD_DEVELOPMENT
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      nk_label(ctx, "Renderer:", NK_TEXT_LEFT);
      {
        static const char* rendererNames[] = {
          "Production",
          "Development"
        };
        Settings::hudRenderer = Renderer(nk_combo(ctx, rendererNames, ARRAY_SIZE(rendererNames), to_underlying_(Settings::hudRenderer), 25, nk_vec2(200, 200)));
      }
#endif // SHR3D_HUD_DEVELOPMENT
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_checkbox_label(ctx, "Lyrics", &Settings::hudLyrics);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      uiRowColorSelect("Lyrics Used", Settings::hudLyricsColor[0]);
      uiRowColorSelect("Lyrics Active", Settings::hudLyricsColor[1]);
      uiRowColorSelect("Lyrics Unused", Settings::hudLyricsColor[2]);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_property_float(ctx, "Lyrics Scale:", 0.3f, &Settings::hudLyricsScale, 10.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Lyrics X:", -1.0f, &Settings::hudLyricsX, 0.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Lyrics Y0:", -1.0f, &Settings::hudLyricsY[0], 1.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Lyrics Y1:", -1.0f, &Settings::hudLyricsY[1], 1.0f, 0.01f, 0.02f);
      //nk_checkbox_label(ctx, "New Highscore", &Settings::hudNewHighscore);
      nk_checkbox_label(ctx, "Timeline Difficulty", &Settings::hudTimelineLevel);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      uiRowColorSelect("Timeline Level Color 0", Settings::hudTimelineLevelColor[0]);
      uiRowColorSelect("Timeline Level Color 1", Settings::hudTimelineLevelColor[1]);
      uiRowColorSelect("Timeline Level Color 2", Settings::hudTimelineLevelColor[2]);
      uiRowColorSelect("Timeline Level Color 3", Settings::hudTimelineLevelColor[3]);
      uiRowColorSelect("Timeline Level Color 4", Settings::hudTimelineLevelColor[4]);
      uiRowColorSelect("Timeline Level Color 5", Settings::hudTimelineLevelColor[5]);
      uiRowColorSelect("Timeline Level Color 6", Settings::hudTimelineLevelColor[6]);
      uiRowColorSelect("Timeline Level Color 7", Settings::hudTimelineLevelColor[7]);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_checkbox_label(ctx, "Timeline Level Flip Y", &Settings::hudTimelineLevelFlipY);
      nk_property_float(ctx, "Timeline Level ScaleX:", 0.01f, &Settings::hudTimelineLevelScaleX, 1.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Level ScaleY:", 0.01f, &Settings::hudTimelineLevelScaleY, 1.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Level Spacing:", 0.0f, &Settings::hudTimelineLevelSpacing, 1.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Level X:", -1.0f, &Settings::hudTimelineLevelX, 1.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Level Y:", -1.0f, &Settings::hudTimelineLevelY, 1.0f, 0.001f, 0.002f);
#ifdef SHR3D_OPENXR
      nk_checkbox_label(ctx, "Timeline Level Xr Flip Y", &Settings::hudTimelineLevelXrFlipY);
      nk_property_float(ctx, "Timeline Level Xr ScaleX:", 0.01f, &Settings::hudTimelineLevelXrScaleX, 100.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Level Xr ScaleY:", 0.01f, &Settings::hudTimelineLevelXrScaleY, 100.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Level Xr Spacing:", 0.0f, &Settings::hudTimelineLevelXrSpacing, 100.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Level Xr X:", -100.0f, &Settings::hudTimelineLevelXrX, 100.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Level Xr Y:", -100.0f, &Settings::hudTimelineLevelXrY, 100.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Level Xr Z:", -100.0f, &Settings::hudTimelineLevelXrZ, 100.0f, 0.001f, 0.002f);
#endif // SHR3D_OPENXR
#ifdef SHR3D_MUSIC_STRETCHER
      nk_checkbox_label(ctx, "Timeline Music Stretcher", &Settings::hudTimelineMusicStretcher);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      uiRowColorSelect("Timeline Music Stretcher Color 0", Settings::hudTimelineMusicStretcherColor[0]);
      uiRowColorSelect("Timeline Music Stretcher Color 1", Settings::hudTimelineMusicStretcherColor[1]);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_property_float(ctx, "Timeline Music Stretcher Scale X:", 0.01f, &Settings::hudTimelineMusicStretcherScaleX, 1.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Music Stretcher Scale Y:", 0.01f, &Settings::hudTimelineMusicStretcherScaleY, 1.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Music Stretcher X:", -1.0f, &Settings::hudTimelineMusicStretcherX, 1.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Music Stretcher Y:", -1.0f, &Settings::hudTimelineMusicStretcherY, 1.0f, 0.001f, 0.002f);
#ifdef SHR3D_OPENXR
      nk_property_float(ctx, "Timeline Music Stretcher Xr Scale X:", 0.01f, &Settings::hudTimelineMusicStretcherXrScaleX, 100.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Music Stretcher Xr Scale Y:", 0.01f, &Settings::hudTimelineMusicStretcherXrScaleY, 100.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Music Stretcher Xr X:", -100.0f, &Settings::hudTimelineMusicStretcherXrX, 100.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Music Stretcher Xr Y:", -100.0f, &Settings::hudTimelineMusicStretcherXrY, 100.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Music Stretcher Xr Z:", -100.0f, &Settings::hudTimelineMusicStretcherXrZ, 100.0f, 0.001f, 0.002f);
#endif // SHR3D_OPENXR
#endif // SHR3D_MUSIC_STRETCHER
      nk_checkbox_label(ctx, "Timeline Tone", &Settings::hudTimelineTone);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      uiRowColorSelect("Timeline Tone Color 0", Settings::hudTimelineToneColor[0]);
      uiRowColorSelect("Timeline Tone Color 1", Settings::hudTimelineToneColor[1]);
      uiRowColorSelect("Timeline Tone Color 2", Settings::hudTimelineToneColor[2]);
      uiRowColorSelect("Timeline Tone Color 3", Settings::hudTimelineToneColor[3]);
      uiRowColorSelect("Timeline Tone Color 4", Settings::hudTimelineToneColor[4]);
      uiRowColorSelect("Timeline Tone Color 5", Settings::hudTimelineToneColor[5]);
      uiRowColorSelect("Timeline Tone Color 6", Settings::hudTimelineToneColor[6]);
      uiRowColorSelect("Timeline Tone Color 7", Settings::hudTimelineToneColor[7]);
      uiRowColorSelect("Timeline Tone Color 8", Settings::hudTimelineToneColor[8]);
      uiRowColorSelect("Timeline Tone Color 9", Settings::hudTimelineToneColor[9]);
      uiRowColorSelect("Timeline Tone Color 10", Settings::hudTimelineToneColor[10]);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_property_float(ctx, "Timeline Tone Scale X:", 0.01f, &Settings::hudTimelineToneScaleX, 1.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Tone Scale Y:", 0.01f, &Settings::hudTimelineToneScaleY, 1.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Tone X:", -1.0f, &Settings::hudTimelineToneX, 1.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Tone Y:", -1.0f, &Settings::hudTimelineToneY, 1.0f, 0.001f, 0.002f);
#ifdef SHR3D_OPENXR
      nk_property_float(ctx, "Timeline Tone Xr Scale X:", 0.01f, &Settings::hudTimelineToneXrScaleX, 100.0f, 0.1f, 0.2f);
      nk_property_float(ctx, "Timeline Tone Xr Scale Y:", 0.01f, &Settings::hudTimelineToneXrScaleY, 100.0f, 0.1f, 0.2f);
      nk_property_float(ctx, "Timeline Tone Xr X:", -100.0f, &Settings::hudTimelineToneXrX, 100.0f, 0.1f, 0.2f);
      nk_property_float(ctx, "Timeline Tone Xr Y:", -100.0f, &Settings::hudTimelineToneXrY, 100.0f, 0.1f, 0.2f);
      nk_property_float(ctx, "Timeline Tone Xr Z:", -2000.0f, &Settings::hudTimelineToneXrZ, 0.0f, 0.1f, 0.2f);
#endif // SHR3D_OPENXR
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      uiRowColorSelect("Timeline Quick Repeater Color 0", Settings::hudTimelineQuickRepeaterColor[0]);
      uiRowColorSelect("Timeline Quick Repeater Color 1", Settings::hudTimelineQuickRepeaterColor[1]);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_checkbox_label(ctx, "Timeline Quick Repeater Flip Y", &Settings::hudTimelineQuickRepeaterFlipY);
      nk_property_float(ctx, "Timeline Quick Repeater Scale Y:", 0.01f, &Settings::hudTimelineQuickRepeaterScaleY, 1.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Quick Repeater X:", -1.0f, &Settings::hudTimelineQuickRepeaterX, 1.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Quick Repeater Y:", -1.0f, &Settings::hudTimelineQuickRepeaterY, 1.0f, 0.001f, 0.002f);
#ifdef SHR3D_OPENXR
      nk_property_float(ctx, "Timeline Quick Repeater Xr Scale X:", 0.1f, &Settings::hudTimelineQuickRepeaterXrScaleX, 100.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Quick Repeater Xr Scale Y:", 0.1f, &Settings::hudTimelineQuickRepeaterXrScaleY, 10.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Quick Repeater Xr X:", -100.0f, &Settings::hudTimelineQuickRepeaterXrX, 100.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Quick Repeater Xr Y:", -100.0f, &Settings::hudTimelineQuickRepeaterXrY, 100.0f, 0.001f, 0.002f);
      nk_property_float(ctx, "Timeline Quick Repeater Xr Z:", -100.0f, &Settings::hudTimelineQuickRepeaterXrZ, 0.0f, 0.001f, 0.002f);
#endif // SHR3D_OPENXR
      nk_checkbox_label(ctx, "Score", &Settings::hudScore);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      uiRowColorSelect("Score Color 0", Settings::hudScoreColor[0]);
      uiRowColorSelect("Score Color 1", Settings::hudScoreColor[1]);
      uiRowColorSelect("Score Color 2", Settings::hudScoreColor[2]);
      uiRowColorSelect("Score Color 3", Settings::hudScoreColor[3]);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_checkbox_label(ctx, "Song Info", &Settings::hudSongInfo);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      uiRowColorSelect("Song Info Color 0", Settings::hudSongInfoColor[0]);
      uiRowColorSelect("Song Info Color 1", Settings::hudSongInfoColor[1]);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_property_float(ctx, "SongInfo Scale 0:", 0.3f, &Settings::hudSongInfoScale[0], 10.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "SongInfo Scale 1:", 0.3f, &Settings::hudSongInfoScale[1], 10.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "SongInfo X:", 0.0f, &Settings::hudSongInfoX, 1.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "SongInfo Y0:", -1.0f, &Settings::hudSongInfoY[0], 1.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "SongInfo Y1:", -1.0f, &Settings::hudSongInfoY[1], 1.0f, 0.01f, 0.02f);
      nk_checkbox_label(ctx, "Arrangement Switch", &Settings::hudArrangementSwitch);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      uiRowColorSelect("Arrangement Switch Color", Settings::hudArrangementSwitchColor);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_property_float(ctx, "Arrangement Switch Scale X:", 0.01f, &Settings::hudArrangementSwitchScaleX, 100.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Arrangement Switch Scale Y:", 0.01f, &Settings::hudArrangementSwitchScaleY, 100.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Arrangement Switch X:", -100.0f, &Settings::hudArrangementSwitchX, 100.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Arrangement Switch Y:", -100.0f, &Settings::hudArrangementSwitchY, 100.0f, 0.01f, 0.02f);
#ifdef SHR3D_OPENXR
      nk_property_float(ctx, "Arrangement Switch Xr Scale X:", 0.01f, &Settings::hudArrangementSwitchXrScaleX, 100.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Arrangement Switch Xr Scale Y:", 0.01f, &Settings::hudArrangementSwitchXrScaleY, 100.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Arrangement Switch Xr X:", -100.0f, &Settings::hudArrangementSwitchXrX, 100.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Arrangement Switch Xr Y:", -100.0f, &Settings::hudArrangementSwitchXrY, 100.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Arrangement Switch Xr Z:", -100.0f, &Settings::hudArrangementSwitchXrZ, 100.0f, 0.01f, 0.02f);
#endif // SHR3D_OPENXR
      nk_checkbox_label(ctx, "Tone Switch", &Settings::hudToneSwitch);
      nk_checkbox_label(ctx, "Tone Switch Timer", &Settings::hudToneSwitchTimer);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      uiRowColorSelect("Tone Switch", Settings::hudToneSwitchColor);
      uiRowColorSelect("Tone Switch Hint Color", Settings::hudToneSwitchHintColor);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_property_float(ctx, "Tone Switch Scale 0:", 0.3f, &Settings::hudToneSwitchScale[0], 10.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Tone Switch Scale 1:", 0.3f, &Settings::hudToneSwitchScale[1], 10.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Tone Switch X:", 0.0f, &Settings::hudToneSwitchX, 1.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Tone Switch Y0:", -1.0f, &Settings::hudToneSwitchY[0], 1.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Tone Switch Y1:", -1.0f, &Settings::hudToneSwitchY[1], 1.0f, 0.01f, 0.02f);
#ifdef SHR3D_OPENXR
      nk_property_float(ctx, "Tone Switch Xr Scale 0:", 0.3f, &Settings::hudToneSwitchXrScale[0], 10.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Tone Switch Xr Scale 1:", 0.3f, &Settings::hudToneSwitchXrScale[1], 10.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Tone Switch Xr X:", -100.0f, &Settings::hudToneSwitchXrX, 100.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Tone Switch Xr Y0:", -100.0f, &Settings::hudToneSwitchXrY[0], 100.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Tone Switch Xr Y1:", -100.0f, &Settings::hudToneSwitchXrY[1], 100.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Tone Switch Xr Z:", -200.0f, &Settings::hudToneSwitchXrZ, 0.0f, 0.01f, 0.02f);
#endif // SHR3D_OPENXR
      nk_checkbox_label(ctx, "Watermark", &Settings::hudWatermark);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      uiRowColorSelect("Watermark Color", Settings::hudWatermarkColor);
      nk_label(ctx, "Watermark Color:", NK_TEXT_LEFT);
#ifdef SHR3D_OPENXR
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_property_float(ctx, "Watermark Xr X:", -100.0f, &Settings::hudWatermarkXrX, 100.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Watermark Xr Y:", -100.0f, &Settings::hudWatermarkXrY, 100.0f, 0.01f, 0.02f);
      nk_property_float(ctx, "Watermark Xr Z:", -200.0f, &Settings::hudWatermarkXrZ, 0.0f, 0.01f, 0.02f);
#endif // SHR3D_OPENXR
      nk_tree_pop(ctx);
    }
    if (nk_tree_push(ctx, NK_TREE_TAB, "Metronome", NK_MINIMIZED))
    {
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);

      nk_checkbox_label(ctx, "Enabled", &Settings::metronomeEnabled);
      nk_property_float(ctx, "Volume:", 0.0f, &Settings::metronomeVolume, 1.0f, 0.01f, 0.01f);
      nk_checkbox_label(ctx, "Decay", &Settings::metronomeDecay);
      nk_property_float(ctx, "Frequency0:", 200.0f, &Settings::metronomeFrequency0, 10000.0f, 1.0f, 1.0f);
      nk_property_float(ctx, "Frequency1:", 200.0f, &Settings::metronomeFrequency1, 10000.0f, 1.0f, 1.0f);
      nk_property_int(ctx, "Click Length:", 1, &Settings::metronomeClickLength, 1000, 1, 1);
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      nk_label(ctx, "Side", NK_TEXT_LEFT);
      static const char* metronomeSideNames[] = {
        "Left",
        "Right",
        "Stereo"
      };
      Settings::metronomeSide = MetronomeSide(nk_combo(ctx, metronomeSideNames, ARRAY_SIZE(metronomeSideNames), to_underlying_(Settings::metronomeSide), 25, nk_vec2(200, 200)));

      nk_tree_pop(ctx);
    }
#ifdef SHR3D_MIDI
    if (nk_tree_push(ctx, NK_TREE_TAB, "Midi", NK_MINIMIZED))
    {
      nk_layout_row_dynamic(ctx, 140.0f * Settings::uiScale, 1);
      {
        if (nk_group_begin(ctx, "Devices", NK_WINDOW_BORDER | NK_WINDOW_TITLE))
        {
          nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
          for (i32 i = 0; i < Global::midiDeviceCount; ++i)
          {
            bool selected = Global::midiConnectedDevices[i] != 0;
            nk_selectable_label(ctx, selected ? (Global::midiDeviceNames[i] + " [connected]").c_str() : Global::midiDeviceNames[i].c_str(), NK_TEXT_LEFT, &selected);
            if (selected && Global::midiConnectedDevices[i] == 0)
            {
              Midi::openDevice(i);
            }
            else if (!selected && Global::midiConnectedDevices[i] != 0)
            {
              Midi::closeDevice(i);
            }
          }
          nk_group_end(ctx);
        }
      }
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_property_float(ctx, "Fine Value Factor", 0.00006103515f, &Settings::midiFineValueFactor, 1.0f, 0.001f, 0.001f);

      nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
      nk_layout_row_template_push_dynamic(ctx);
      nk_layout_row_template_push_static(ctx, 60.0f * Settings::uiScale);
      nk_layout_row_template_push_static(ctx, 80.0f * Settings::uiScale);
      nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
      nk_layout_row_template_end(ctx);

      static i32 learnSlot = -1;
      for (i32 i = 0; i < ARRAY_SIZE(Const::midiBindingsNames); ++i)
      {
        nk_label(ctx, Const::midiBindingsNames[i], NK_TEXT_LEFT);

        if (Settings::midiBinding[i] >= 0 && Settings::midiBinding[i] <= 127)
        {
          if (nk_button_label(ctx, std::to_string(Settings::midiBinding[i]).c_str()))
            learnSlot = i;
          static const char* midiNodeModeNames[] = {
            "press",
            "toggle",
            "analog"
          };
          Global::midiNoteMode[Settings::midiBinding[i]] = MidiNoteMode(nk_combo(ctx, midiNodeModeNames, ARRAY_SIZE(midiNodeModeNames), to_underlying_(Global::midiNoteMode[Settings::midiBinding[i]]), 25, nk_vec2(200, 200)));
          if (nk_button_label(ctx, "X"))
          {
            Global::midiNoteBinding[Settings::midiBinding[i]] = 0xFF;
            Global::midiNoteMode[Settings::midiBinding[i]] = MidiNoteMode::press;
            Settings::midiBinding[i] = 0xFF;
          }
        }
        else
        {
          if (nk_button_label(ctx, "Learn"))
            learnSlot = i;
          nk_spacing(ctx, 2);
        }
      }

      if (learnSlot != -1 && Global::midiLearnNote >= 0 && Global::midiLearnNote <= 127)
      {
        Settings::midiBinding[learnSlot] = Global::midiLearnNote;
        Global::midiNoteBinding[Global::midiLearnNote] = u8(learnSlot);
        learnSlot = -1;
      }

      Global::midiLearnNote = 0xFF;
      nk_tree_pop(ctx);
    }
#endif // SHR3D_MIDI
    if (nk_tree_push(ctx, NK_TREE_TAB, "Paths", NK_MINIMIZED))
    {
      nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
      nk_layout_row_template_push_static(ctx, 70.0f * Settings::uiScale);
      nk_layout_row_template_push_dynamic(ctx);
      nk_layout_row_template_push_static(ctx, 32.0f * Settings::uiScale);
      nk_layout_row_template_push_static(ctx, 32.0f * Settings::uiScale);
      nk_layout_row_template_end(ctx);
      //nk_label(ctx, "stats.ini", NK_TEXT_LEFT);
      //uiRowFileSelect(Settings::pathStatsIni, L"Ini (*.ini)\0*.ini\0All (*.*)\0*.*\0");
      nk_label(ctx, "tones.ini", NK_TEXT_LEFT);
      uiRowFileSelect(Settings::pathTonesIni, L"Ini (*.ini)\0*.ini\0All (*.*)\0*.*\0");
      nk_label(ctx, "backup", NK_TEXT_LEFT);
      uiRowPathSelect(Settings::pathBackup);
      nk_label(ctx, "cache", NK_TEXT_LEFT);
      uiRowPathSelect(Settings::pathCache);
#ifdef SHR3D_SFX_PLUGIN_CLAP
      nk_label(ctx, "clap", NK_TEXT_LEFT);
      uiRowPathSelect(Settings::pathClap);
#endif // SHR3D_SFX_PLUGIN_CLAP
#ifdef SHR3D_ENVIRONMENT_MILK
      nk_label(ctx, "milk", NK_TEXT_LEFT);
      uiRowPathSelect(Settings::pathMilk);
#endif // SHR3D_ENVIRONMENT_MILK
#ifdef SHR3D_SFX_CORE_NEURALAMPMODELER
      nk_label(ctx, "nam", NK_TEXT_LEFT);
      uiRowPathSelect(Settings::pathNam);
#endif // SHR3D_SFX_CORE_NEURALAMPMODELER
      nk_label(ctx, "psarc", NK_TEXT_LEFT);
      uiRowPathSelect(Settings::pathPsarc);
#ifdef SHR3D_SHRED
      nk_label(ctx, "shred", NK_TEXT_LEFT);
      uiRowPathSelect(Settings::pathShred);
#endif // SHR3D_SHRED
#ifdef SHR3D_RECORDER
      nk_label(ctx, "recorder", NK_TEXT_LEFT);
      uiRowPathSelect(Settings::pathRecorder);
#endif // SHR3D_RECORDER
#ifdef SHR3D_SFX_PLUGIN_VST
      nk_label(ctx, "vst", NK_TEXT_LEFT);
      uiRowPathSelect(Settings::pathVst);
#endif // SHR3D_SFX_PLUGIN_VST
#ifdef SHR3D_SFX_PLUGIN_VST3
      nk_label(ctx, "vst3", NK_TEXT_LEFT);
      uiRowPathSelect(Settings::pathVst3);
#endif // SHR3D_SFX_PLUGIN_VST3
#ifdef SHR3D_ENVIRONMENT_SKYBOX
      nk_label(ctx, "skybox", NK_TEXT_LEFT);
      uiRowPathSelect(Settings::pathSkybox);
#endif // SHR3D_ENVIRONMENT_SKYBOX
#ifdef SHR3D_ENVIRONMENT_STAGE
      nk_label(ctx, "stage", NK_TEXT_LEFT);
      uiRowPathSelect(Settings::pathStage);
#endif // SHR3D_ENVIRONMENT_STAGE

      nk_tree_pop(ctx);
    }
#ifdef SHR3D_SPOTIFY
    if (nk_tree_push(ctx, NK_TREE_TAB, "Spotify", NK_MINIMIZED))
    {
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      char tmp[512];
      i32 tmpLength;
      nk_label(ctx, "Client Secret", NK_TEXT_LEFT);
      strcpy(tmp, Settings::spotifyClientSecret.c_str());
      tmpLength = i32(Settings::spotifyClientSecret.size());
      nk_edit_string(ctx, NK_EDIT_SIMPLE, tmp, &tmpLength, sizeof(tmp), nk_filter_default);
      Settings::spotifyClientSecret = tmp;
      nk_label(ctx, "Client Id", NK_TEXT_LEFT);
      strcpy(tmp, Settings::spotifyClientId.c_str());
      tmpLength = i32(Settings::spotifyClientId.size());
      nk_edit_string(ctx, NK_EDIT_SIMPLE, tmp, &tmpLength, sizeof(tmp), nk_filter_default);
      Settings::spotifyClientId = tmp;
      nk_label(ctx, "Redirect URI", NK_TEXT_LEFT);
      strcpy(tmp, Settings::spotifyRedirectUri.c_str());
      tmpLength = i32(Settings::spotifyRedirectUri.size());
      nk_edit_string(ctx, NK_EDIT_SIMPLE, tmp, &tmpLength, sizeof(tmp), nk_filter_default);
      Settings::spotifyRedirectUri = tmp;
      nk_tree_pop(ctx);
    }
#endif // SHR3D_SPOTIFY
    if (nk_tree_push(ctx, NK_TREE_TAB, "Tuner", NK_MINIMIZED))
    {
#ifdef SHR3D_SFX
      {
        nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
        nk_layout_row_template_push_dynamic(ctx);
        nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
        nk_layout_row_template_end(ctx);
        if (nk_button_label(ctx, Global::tunerPlugin.system == SfxSystem::empty ? "..." : Sfx::names[Global::tunerPlugin.system][Global::tunerPlugin.sfxIndex].c_str()))
        {
          if (Global::tunerPlugin.system != SfxSystem::empty)
            Global::inputTuner.toggled = !Global::inputTuner.toggled;
          else
            showSelectSfxWindow = OpenSelectSfxWindowMode::tuner;
        }
        if (nk_button_label(ctx, "X"))
        {
          Settings::tunerPlugin.clear();
          Global::tunerPlugin.system = SfxSystem::empty;
        }

        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
        nk_label(ctx, "Note Detection:", NK_TEXT_LEFT);
        {
          static const char* tunerNoteDetectionNames[] = {
#ifdef SHR3D_SFX_CORE_HEXFIN_DIVIDED
            "(Divided) "
#endif // SHR3D_SFX_CORE_HEXFIN_DIVIDED
            "Pickup",
#ifdef SHR3D_MIDI
            "Midi Device",
#endif // SHR3D_MIDI
            "Midi Plugin"
          };
          Settings::tunerNoteDetectionSource = NoteDetectionSource(nk_combo(ctx, tunerNoteDetectionNames, ARRAY_SIZE(tunerNoteDetectionNames), to_underlying_(Settings::tunerNoteDetectionSource), 25, nk_vec2(200, 200)));
        }
#ifdef SHR3D_SFX_CORE_HEXFIN
        if (nk_tree_push(ctx, NK_TREE_TAB, "Hexfin", NK_MINIMIZED))
        {
          nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
          nk_property_float(ctx, "Onset Threshold db (req. restart):", -100.0f, &Settings::tunerHexfinOnsetThreshold, 0.0f, 0.1f, 0.1f);
          nk_property_float(ctx, "Release Threshold db (req. restart):", -100.0f, &Settings::tunerHexfinReleaseThreshold, 0.0f, 0.1f, 0.1f);
          nk_tree_pop(ctx);
        }
#endif // SHR3D_SFX_CORE_HEXFIN
#ifdef SHR3D_MIDI
        if (nk_tree_push(ctx, NK_TREE_TAB, "Midi Device", NK_MINIMIZED))
        {
          nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
          std::vector<const char*> connectedDevices = { "none" };
          for (i32 i = 0; i < Global::midiDeviceCount; ++i)
          {
            if (Global::midiConnectedDevices[i] != 0)
            {
              connectedDevices.push_back(Global::midiDeviceNames[i].c_str());
            }
          }
          static i32 selected = 0;
          selected = nk_combo(ctx, connectedDevices.data(), i32(connectedDevices.size()), selected, 25, nk_vec2(200, 200));
          Global::tunerMidiDevice = selected;
          nk_tree_pop(ctx);
        }
#endif // SHR3D_MIDI
        if (nk_tree_push(ctx, NK_TREE_TAB, "Midi Plugin", NK_MINIMIZED))
        {
          nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
          nk_layout_row_template_push_dynamic(ctx);
          nk_layout_row_template_push_static(ctx, 25.0f * Settings::uiScale);
          nk_layout_row_template_end(ctx);
          if (nk_button_label(ctx, Sfx::names[Global::tunerMidiPlugin.system][Global::tunerMidiPlugin.sfxIndex].c_str()))
          {
            if (Global::tunerMidiPlugin.system != SfxSystem::empty)
              Global::midiFromAudioWindowOpen = !Global::midiFromAudioWindowOpen;
            else
              showSelectSfxWindow = OpenSelectSfxWindowMode::midiFromAudio;
          }
          if (nk_button_label(ctx, "X"))
          {
            Settings::tunerMidiPlugin.clear();
            Global::tunerMidiPlugin.system = SfxSystem::empty;
          }

          nk_tree_pop(ctx);
        }
      }
#endif // SHR3D_SFX
      nk_tree_pop(ctx);
    }
    if (nk_tree_push(ctx, NK_TREE_TAB, "UI", NK_MINIMIZED))
    {
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_property_float(ctx, "Ui Scale (req. restart):", 0.1f, &Settings::uiScale, 10.0f, 0.01f, 0.01f);
#ifdef SHR3D_OPENXR
      nk_property_float(ctx, "Ui Xr Z:", -5.0f, &Settings::uiXrZ, 0.0f, 0.01f, 0.02f);
#endif // SHR3D_OPENXR
#ifdef SHR3D_CUSTOM_CURSOR
      nk_checkbox_label(ctx, "Cursor Custom (req. restart)", &Settings::uiCursorCustom);
      nk_property_int(ctx, "Cursor Size (req. restart):", 32, &Settings::uiCursorSize, 128, 1, 1);
#endif // SHR3D_CUSTOM_CURSOR
      {
        nk_color table[NK_COLOR_COUNT];
        static const char* colorNames[] = {
          "Text",
          "Window",
          "Header",
          "Border",
          "Button",
          "Button Hover",
          "Button Active",
          "Toggle",
          "Toggle Hover",
          "Toggle Cursor",
          "Select",
          "Select Active",
          "Slider",
          "Slider Cursor",
          "Slider Cursor Hover",
          "Slider Cursor Active",
          "Property",
          "Edit",
          "Edit Cursor",
          "Combo",
          "Chart",
          "Chart Color",
          "Chart Color Highligh",
          "Scrollbar",
          "Scrollbar Cursor",
          "Scrollbar Cursor Hover",
          "Scrollbar Cursor Active",
          "Tab Header",
          // additional custom colors:
          "Drawing Color 0",
          "Drawing Color 1"
        };
        nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
        for (i32 i = 0; i < NK_COLOR_COUNT; ++i)
        {
          uiRowColorSelect(colorNames[i], Settings::uiColor[i]);
          table[i] = nk_rgba_f(Settings::uiColor[i].r, Settings::uiColor[i].g, Settings::uiColor[i].b, Settings::uiColor[i].a);
        }
        nk_style_from_table(ctx, table);

        // additional custom colors
        for (i32 i = NK_COLOR_COUNT; i < ARRAY_SIZE(colorNames); ++i)
        {
          uiRowColorSelect(colorNames[i], Settings::uiColor[i]);
        }
      }
      nk_tree_pop(ctx);
    }
#ifdef SHR3D_OPENXR
    if (nk_tree_push(ctx, NK_TREE_TAB, "XR", NK_MINIMIZED))
    {
#ifdef SHR3D_OPENXR_PCVR
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
      nk_checkbox_label(ctx, "Enabled", &Settings::xrEnabled);
#endif // SHR3D_OPENXR_PCVR
#ifdef SHR3D_OPENXR_CONTROLLER_PICO4_AND_QUEST3
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      nk_label(ctx, "Controller:", NK_TEXT_LEFT);
      {
        static const char* controllerNames[] = {
          "Pico 4",
          "Quest 3",
          "Quest 3 Neon"
        };
        Settings::xrController = XrController(nk_combo(ctx, controllerNames, ARRAY_SIZE(controllerNames), to_underlying_(Settings::xrController), 25, nk_vec2(200, 200)));
      }
#endif // SHR3D_OPENXR_CONTROLLER_PICO4_AND_QUEST3
      nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
      uiRowColorSelect("Cursor Color 0", Settings::xrCursorColor[0]);
      uiRowColorSelect("Cursor Color 1", Settings::xrCursorColor[1]);
      uiRowColorSelect("Pointer Color", Settings::xrPointerColor);
      nk_tree_pop(ctx);
    }
#endif // SHR3D_OPENXR
  }
  nk_end(ctx);

#ifdef SHR3D_SFX
  switch (showSelectSfxWindow)
  {
  case OpenSelectSfxWindowMode::tuner:
  {
    const SfxId prevTuningPlugin = Global::tunerPlugin;
    if (selectSfxWindow(Global::tunerPlugin, resolutionWidth, resolutionHeight
#ifdef SHR3D_SFX_CORE_HEXFIN
      , true
#endif // SHR3D_SFX_CORE_HEXFIN
    ) == 0)
      showSelectSfxWindow = OpenSelectSfxWindowMode::closed;
    if (prevTuningPlugin.system != Global::tunerPlugin.system || prevTuningPlugin.sfxIndex != Global::tunerPlugin.sfxIndex)
    {
      showSelectSfxWindow = OpenSelectSfxWindowMode::closed;
      Settings::tunerPlugin = std::string(Sfx::sfxSystem2Name(Global::tunerPlugin.system)) + ',' + Sfx::names[Global::tunerPlugin.system][Global::tunerPlugin.sfxIndex];
    }
  }
  break;
  case OpenSelectSfxWindowMode::midiFromAudio:
  {
    const SfxId prevMidiPlugin = Global::tunerMidiPlugin;
    if (selectSfxWindow(Global::tunerMidiPlugin, resolutionWidth, resolutionHeight) == 0)
      showSelectSfxWindow = OpenSelectSfxWindowMode::closed;
    if (prevMidiPlugin.system != Global::tunerMidiPlugin.system || prevMidiPlugin.sfxIndex != Global::tunerMidiPlugin.sfxIndex)
    {
      showSelectSfxWindow = OpenSelectSfxWindowMode::closed;
      Settings::tunerMidiPlugin = std::string(Sfx::sfxSystem2Name(Global::tunerMidiPlugin.system)) + ',' + Sfx::names[Global::tunerMidiPlugin.system][Global::tunerMidiPlugin.sfxIndex];
    }
  }
  break;
  default:
    break;
  }
#endif // SHR3D_SFX
}

static void installWindow(const i32 resolutionWidth, const i32 resolutionHeight)
{
#ifndef __ANDROID__
  nk_begin(ctx, "Shr3D v" VERSION_STR, calcWindowRectCentered(resolutionWidth, resolutionHeight, 0, 0, 700, 482), NK_WINDOW_BORDER | NK_WINDOW_TITLE | NK_WINDOW_NO_SCROLLBAR);
  nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
  nk_label(ctx, "Shr3D needs to create the following files and directories:", NK_TEXT_LEFT);
  nk_label(ctx, " -settings.ini  <- for general settings like audio, graphics", NK_TEXT_LEFT);
  nk_label(ctx, " -stats.ini     <- for number of plays and high scores.", NK_TEXT_LEFT);
  nk_label(ctx, " -tones.ini     <- for tone assignments.", NK_TEXT_LEFT);
  nk_label(ctx, " -psarc/        <- copy your .psarc files into it.", NK_TEXT_LEFT);
#ifdef SHR3D_SHRED
  nk_label(ctx, " -shred/        <- copy your .shred files into it.", NK_TEXT_LEFT);
#endif // SHR3D_SHRED
#ifdef SHR3D_SFX_PLUGIN_CLAP
  nk_label(ctx, " -clap/         <- copy your .clap files of your clap plugins into it.", NK_TEXT_LEFT);
#endif // SHR3D_SFX_PLUGIN_CLAP
#ifdef SHR3D_SFX_PLUGIN_LV2
  nk_label(ctx, " -lv2/          <- copy your .lv2 files of your clap plugins into it.", NK_TEXT_LEFT);
#endif // SHR3D_SFX_PLUGIN_LV2
#ifdef SHR3D_SFX_PLUGIN_VST
  nk_label(ctx, " -vst/          <- copy your .dll files of your vst plugins into it.", NK_TEXT_LEFT);
#endif // SHR3D_SFX_PLUGIN_VST
#ifdef SHR3D_SFX_PLUGIN_VST3
  nk_label(ctx, " -vst3/         <- copy your .vst3 files of your vst3 plugins into it.", NK_TEXT_LEFT);
#endif // SHR3D_SFX_PLUGIN_VST3
#ifdef SHR3D_ENVIRONMENT_MILK
  nk_label(ctx, " -milk/         <- copy your .milk files into it for some trippy backgrounds.", NK_TEXT_LEFT);
#endif // SHR3D_ENVIRONMENT_MILK
#ifdef SHR3D_SFX_CORE_NEURALAMPMODELER
  nk_label(ctx, " -nam/          <- for neural amp modeler, copy your .nam files and .wav impulse responses into it", NK_TEXT_LEFT);
#endif // SHR3D_SFX_CORE_NEURALAMPMODELER
  nk_label(ctx, " -backup/       <- automatic backups of .ini files.", NK_TEXT_LEFT);
  nk_label(ctx, " -cache/        <- cache for song details and album covers to speed up song list loading.", NK_TEXT_LEFT);
#ifdef SHR3D_RECORDER
  nk_label(ctx, " -recorder/     <- output of played song as .wav files when record button is pressed.", NK_TEXT_LEFT);
#endif // SHR3D_RECORDER
#ifdef SHR3D_ENVIRONMENT_SKYBOX
  nk_label(ctx, " -skybox/       <- add a custom 3D skybox. Most usefull on XR.", NK_TEXT_LEFT);
#endif // SHR3D_ENVIRONMENT_SKYBOX
#ifdef SHR3D_ENVIRONMENT_STAGE
  nk_label(ctx, " -stage/        <- add a custom 3D environment. Most usefull on XR.", NK_TEXT_LEFT);
#endif // SHR3D_ENVIRONMENT_STAGE

  nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
  if (nk_button_label(ctx, "Continue without Saving"))
  {
    Global::installMode = InstallMode::continueWithoutSaving;
    Collection::init();
  }
  else if (nk_button_label(ctx, "Save and Continue"))
  {
    Global::installMode = InstallMode::installed;
    File::createPathDirectories();
    Collection::init();
  }
#else // __ANDROID__

#endif // __ANDROID__
  nk_end(ctx);
}

static void aboutWindow(const i32 resolutionWidth, const i32 resolutionHeight)
{
  Global::uiAboutWindowOpen = nk_begin(ctx, "About", calcWindowRectCentered(resolutionWidth, resolutionHeight, 0, 0, 300, 220), NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_TITLE | NK_WINDOW_CLOSABLE);
  if (Global::uiAboutWindowOpen)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
    nk_label(ctx, "Shr3D", NK_TEXT_CENTERED);
    nk_label(ctx, "Version " VERSION_STR, NK_TEXT_CENTERED);
    nk_label(ctx, "Created by AshRa", NK_TEXT_CENTERED);
    nk_label(ctx, "https://shr3d.app/", NK_TEXT_CENTERED);
    nk_label(ctx, "https://discord.gg/QfmXCT5wRm", NK_TEXT_CENTERED);
    //nk_label(ctx, "https://matrix.to/#/#space:shr3d.app", NK_TEXT_CENTERED);
    //nk_label(ctx, "Icons by https://icons8.com/", NK_TEXT_CENTERED);
  }
  nk_end(ctx);
}

static void trackLevelWindow(i32 resolutionWidth, i32 resolutionHeight)
{
  if (Global::selectedSongIndex < 0)
    return;

  Global::inputLevel.toggled = nk_begin(ctx, "Level", calcWindowRectCentered(resolutionWidth, resolutionHeight, 0, 0, 300, 250), NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE | NK_WINDOW_CLOSABLE | NK_WINDOW_TITLE);
  if (Global::inputLevel.toggled)
  {
#ifdef SHR3D_MUSIC_STRETCHER
    if (nk_tree_push(ctx, NK_TREE_TAB, "Speed", NK_MAXIMIZED))
    {
      nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
      nk_layout_row_template_push_dynamic(ctx);
      nk_layout_row_template_push_static(ctx, 100.0f);
      nk_layout_row_template_end(ctx);
      const f32 minMusicSpeed = 1.0f / Const::musicStretcherMaxStretchRatio;
      const f32 previousMusicSpeed = 1.0f / Global::musicStretchRatio;
      f32 musicSpeed = previousMusicSpeed;
      nk_slider_float(ctx, minMusicSpeed, &musicSpeed, 1.0f, 0.001f);
      nk_property_float(ctx, "", minMusicSpeed, &musicSpeed, 1.0f, 0.05f, 0.001f);
      if (musicSpeed != previousMusicSpeed)
        Global::musicStretchRatio = 1.0f / musicSpeed;

      nk_tree_pop(ctx);
    }
#endif // SHR3D_MUSIC_STRETCHER
    if (nk_tree_push(ctx, NK_TREE_TAB, "Level", NK_MAXIMIZED))
    {
      std::vector<i32>& sectionLevels = Global::songLevels[Global::selectedArrangementIndex].sectionLevels;
      {
        nk_label(ctx, "Overall:", NK_TEXT_LEFT);
        f32& overallLevel = Global::songLevels[Global::selectedArrangementIndex].overallLevel;
        const f32 previousOverallLevel = overallLevel;
        nk_slider_float(ctx, 0.0f, &overallLevel, 1.0f, 0.001f);
        if (previousOverallLevel != overallLevel)
        {
          for (i32 i = 0; i < i32(Global::songTracks[Global::selectedArrangementIndex].leveledSections.size()); ++i)
          {
            const Song::LeveledSection& leveledSections = Global::songTracks[Global::selectedArrangementIndex].leveledSections[i];
            sectionLevels[i] = i32(overallLevel * f32(leveledSections.maxLevel));
          }
          Global::songTrackLevelAdjusted[Global::selectedArrangementIndex] = Song::loadTrackLevelAdjusted(Global::songTracks[Global::selectedArrangementIndex], sectionLevels);
        }
      }
      if (nk_tree_push(ctx, NK_TREE_TAB, "Sections", NK_MINIMIZED))
      {
        nk_layout_row_template_begin(ctx, 22.0f * Settings::uiScale);
        nk_layout_row_template_push_dynamic(ctx);
        nk_layout_row_template_push_static(ctx, 48.0f);
        nk_layout_row_template_end(ctx);
        for (i32 i = 0; i < i32(sectionLevels.size()); ++i)
        {
          const Song::LeveledSection& leveledSections = Global::songTracks[Global::selectedArrangementIndex].leveledSections[i];

          if (leveledSections.maxLevel <= 0)
            continue;

          const i32 previousPhraseIterationLevel = sectionLevels[i];
          nk_slider_int(ctx, 0, &sectionLevels[i], leveledSections.maxLevel, 1);

          char levelStr[8];
          sprintf(levelStr, "%d/%d", sectionLevels[i], leveledSections.maxLevel);
          nk_label(ctx, levelStr, NK_TEXT_LEFT);

          if (previousPhraseIterationLevel != sectionLevels[i])
          {
            Global::songTrackLevelAdjusted[Global::selectedArrangementIndex] = Song::loadTrackLevelAdjusted(Global::songTracks[Global::selectedArrangementIndex], sectionLevels);
          }
        }
        nk_tree_pop(ctx);
      }
      nk_tree_pop(ctx);
    }
  }
  nk_end(ctx);
}

static void strumDirectionWindow(i32 resolutionWidth, i32 resolutionHeight)
{
  Global::inputStrumDirection.toggled = nk_begin(ctx, "Strum Direction", calcWindowRectCentered(resolutionWidth, resolutionHeight, 0, 0, 300, 250), NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE | NK_WINDOW_CLOSABLE | NK_WINDOW_TITLE);
  if (Global::inputStrumDirection.toggled)
  {
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
    nk_checkbox_label(ctx, "Beat Strum Direction", &Settings::highwayBeatStrumDirection);
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 2);
    static const char* strumDirection[] = {
      "Downstroke",
      "Upstroke"
    };
    nk_label(ctx, "Primary Beat", NK_TEXT_LEFT);
    Settings::highwayBeatStrumDirectionPrimary = StrumDirection(nk_combo(ctx, strumDirection, ARRAY_SIZE(strumDirection), to_underlying_(Settings::highwayBeatStrumDirectionPrimary), 25, nk_vec2(200, 200)));
    nk_label(ctx, "Next Beat", NK_TEXT_LEFT);
    Settings::highwayBeatStrumDirectionNext = StrumDirection(nk_combo(ctx, strumDirection, ARRAY_SIZE(strumDirection), to_underlying_(Settings::highwayBeatStrumDirectionNext), 25, nk_vec2(200, 200)));
    nk_layout_row_dynamic(ctx, 22.0f * Settings::uiScale, 1);
    nk_property_int(ctx, "Strums between Beats:", 0, &Settings::highwayBeatStrumsBetweenBeats, 12, 1, 1);
  }
  nk_end(ctx);
}

static void sfxChainWindows(const i32 resolutionWidth, const i32 resolutionHeight)
{
  for (i32 i = 0; i < ARRAY_SIZE(Global::effectChain); ++i)
  {
    if (!Global::effectChainWindowOpened[i])
      continue;

    i32 instance = 0;
    for (i32 j = 0; j < i; ++j)
      if (Global::effectChain[i].id.system == Global::effectChain[j].id.system && Global::effectChain[i].id.sfxIndex == Global::effectChain[j].id.sfxIndex)
        ++instance;

    Global::effectChainWindowOpened[i] = sfxWindow(Global::effectChain[i].id, instance, resolutionWidth, resolutionHeight, i
#ifdef SHR3D_SFX_PLUGIN
      , Global::effectChainWindowPluginHideUi[i], Global::effectChainWindowPluginParentWindow[i], Global::effectChainWindowPluginPosition[i]
#endif // SHR3D_SFX_PLUGIN
    );

    if (!Global::effectChainWindowOpened[i]) // windows was closed in this tick
    {
      Global::sfxParameters[Global::activeSfxToneIndex][i] = Sfx::saveParameters(Global::effectChain[i].id, instance);
    }
  }
}

static bool showUI()
{
  return !Global::inputHideMenu.toggled || Global::installMode == InstallMode::showInstaller || Global::inputTuner.toggled || (Global::inputTuner.pressed && !Global::inputTuner.heldDownLastFrame) || Global::midiFromAudioWindowOpen || Global::inputLevel.toggled || Global::inputStrumDirection.toggled;
}

void Ui::tick(const i32 resolutionWidth, const i32 resolutionHeight)
{
  if (!showUI())
    return;

  if (Global::installMode == InstallMode::showInstaller)
  {
    installWindow(resolutionWidth, resolutionHeight);
    return;
  }

#ifdef SHR3D_SFX
  tunerWindow(resolutionWidth, resolutionHeight);
  midiFromAudioWindow();
  //  if (Global::pluginWindowIndex.system != SfxSystem::empty)
  //  {
  //#ifdef SHR3D_SFX_CORE
  //    if (Global::pluginWindowIndex.system <= SfxSystem::coreAirWindows)
  //    {
  //      static bool openedLastFrame = true;
  //      bool open = sfxCoreWindow(Global::pluginWindowIndex, Global::pluginWindowInstance, resolutionWidth, resolutionHeight);
  //      if (!open && openedLastFrame) // Window was closed this frame. Save the Parameters
  //      {
  //        if (Global::pluginWindowEffectChainPluginIndex >= 0)
  //        {
  //          Global::sfxParameters[Global::activeSfxToneIndex][Global::pluginWindowEffectChainPluginIndex] = Sfx::saveParameters(Global::pluginWindowIndex, Global::pluginWindowInstance);
  //        }
  //        if (Global::inputTuner.toggled)
  //          Global::inputTuner.toggled = false;
  //        Global::pluginWindowIndex.system = SfxSystem::empty;
  //      }
  //      openedLastFrame = open;
  //    }
  //    else if (Global::pluginWindowIndex.system == SfxSystem::coreExtensionV2)
  //    {
  //      if (Global::sfxCoreExtensionV2SortedByRegistrationOrder[Global::pluginWindowIndex.sfxIndex]->uiCallback == nullptr)
  //      {
  //        static bool openedLastFrame = true;
  //        bool open = sfxCoreExtensionWindow(Global::pluginWindowIndex.sfxIndex, Global::pluginWindowInstance, resolutionWidth, resolutionHeight);
  //        if (!open && openedLastFrame) // Window was closed this frame. Save the Parameters
  //        {
  //          if (Global::pluginWindowEffectChainPluginIndex >= 0)
  //          {
  //            Global::sfxParameters[Global::activeSfxToneIndex][Global::pluginWindowEffectChainPluginIndex] = Sfx::saveParameters(Global::pluginWindowIndex, Global::pluginWindowInstance);
  //          }
  //          if (Global::inputTuner.toggled)
  //            Global::inputTuner.toggled = false;
  //          Global::pluginWindowIndex.system = SfxSystem::empty;
  //        }
  //        openedLastFrame = open;
  //      }
  //      else
  //      {
  //        static bool openedLastFrame = true;
  //        bool open = Global::sfxCoreExtensionV2SortedByRegistrationOrder[Global::pluginWindowIndex.sfxIndex]->uiCallback(ctx, Global::sfxCoreExtensionV2SortedByRegistrationOrder[Global::pluginWindowIndex.sfxIndex], Global::pluginWindowInstance);
  //        if (!open && openedLastFrame) // Window was closed this frame. Save the Parameters
  //        {
  //          Global::pluginWindowIndex.system = SfxSystem::empty;
  //        }
  //        openedLastFrame = open;
  //      }
  //    }
  //#endif // SHR3D_SFX_CORE
  //#if defined(SHR3D_SFX_CORE) && defined(SHR3D_SFX_PLUGIN)
  //    else
  //#endif // SHR3D_SFX_CORE && SHR3D_SFX_PLUGIN
  //#ifdef SHR3D_SFX_PLUGIN
  //    {
  //      static bool openedLastFrame = false;
  //
  //      bool open = effectChainWindowPluginParentWindow(Global::pluginWindowIndex, Global::pluginWindowInstance, resolutionWidth, resolutionHeight);
  //      if (!open && openedLastFrame) // Window was closed this frame. Save the Parameters
  //      {
  //#ifdef SHR3D_SFX_PLUGIN_GUI_WINDOW
  //        Sfx::closeSfxPluginWindow(Global::pluginWindowIndex, Global::pluginWindowInstance);
  //#endif // SHR3D_SFX_PLUGIN_GUI_WINDOW
  //        if (Global::pluginWindowEffectChainPluginIndex >= 0)
  //        {
  //          Global::sfxParameters[Global::activeSfxToneIndex][Global::pluginWindowEffectChainPluginIndex] = Sfx::saveParameters(Global::pluginWindowIndex, Global::pluginWindowInstance);
  //        }
  //        if (Global::inputTuner.toggled)
  //          Global::inputTuner.toggled = false;
  //        Global::pluginWindowIndex.system = SfxSystem::empty;
  //      }
  //      openedLastFrame = open;
  //    }
  //#endif // SHR3D_SFX_PLUGIN
  //  }
#endif // SHR3D_SFX

  if (Global::inputLevel.toggled)
    trackLevelWindow(resolutionWidth, resolutionHeight);

  if (Global::inputStrumDirection.toggled)
    strumDirectionWindow(resolutionWidth, resolutionHeight);

  if (!Global::inputHideMenu.toggled)
  {
    toolbarWindow(resolutionWidth, resolutionHeight);
    settingsWindow(resolutionWidth, resolutionHeight);
    songWindow(resolutionWidth, resolutionHeight);

#ifdef SHR3D_SFX
    if (Global::inputSfxChain.toggled)
      sfxChainEditorWindow(resolutionWidth, resolutionHeight);

    sfxChainWindows(resolutionWidth, resolutionHeight);
#endif // SHR3D_SFX

    if (Global::uiAboutWindowOpen)
      aboutWindow(resolutionWidth, resolutionHeight);
  }
#ifdef PLATFORM_EMSCRIPTEN
  else if (Global::downloadSongIndexFinished != -1)
  {
    ArrangementIndex arrangementIndex = -1;
    for (u64 i = 0; i < Global::songInfos[Global::downloadSongIndexFinished].arrangementInfos.size(); ++i)
    {
      if (Global::downloadAutoplayArrangement == Global::songInfos[Global::downloadSongIndexFinished].arrangementInfos[i].arrangementName)
      {
        arrangementIndex = i;
        break;
      }
    }
    if (arrangementIndex >= 0)
      Player::playSong(Global::downloadSongIndexFinished, arrangementIndex);
    Global::downloadSongIndexFinished = -1;
  }

  if (Global::downloadSongIndexInProgress >= 0)
    downloadWindow();
#endif // PLATFORM_EMSCRIPTEN
}

void Ui::render(i32 drawableWidth, i32 drawableHeight, const mat4* viewProjectionMat)
{
  if (!showUI())
    return;

  GL(glDisable(GL_CULL_FACE));
  GL(glDisable(GL_DEPTH_TEST));
#ifndef PLATFORM_OPENXR_ANDROID
#ifdef SHR3D_OPENXR_PCVR
  if (!Global::xrInitialized)
#endif // SHR3D_OPENXR_PCVR
  {
    GL(glEnable(GL_SCISSOR_TEST));
  }
#endif // PLATFORM_OPENXR_ANDROID
  nk_sdl_render(NK_ANTI_ALIASING_ON, MAX_VERTEX_MEMORY, MAX_ELEMENT_MEMORY, drawableWidth, drawableHeight, viewProjectionMat);
#ifndef PLATFORM_OPENXR_ANDROID
#ifdef SHR3D_OPENXR_PCVR
  if (!Global::xrInitialized)
#endif // SHR3D_OPENXR_PCVR
  {
    GL(glDisable(GL_SCISSOR_TEST));
  }
#endif // PLATFORM_OPENXR_ANDROID
  GL(glEnable(GL_CULL_FACE));
  GL(glEnable(GL_DEPTH_TEST));
  GL(glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), nullptr)); // vertex coords
  GL(glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (void*)(3 * sizeof(GLfloat)))); // uv coords
}

void Ui::renderClear()
{
  if (!showUI())
    return;

  nk_clear(&sdl.ctx);
  nk_buffer_clear(&(&sdl.ogl)->cmds);
}

void Ui::handleInputBegin()
{
  nk_input_begin(ctx);
}

#ifdef SHR3D_WINDOW_SDL
void Ui::handleInput(SDL_Event* event)
{
  nk_sdl_handle_event(event);
}
#endif // SHR3D_WINDOW_SDL
#ifdef SHR3D_WINDOW_WIN32
void Ui::handleInput(HWND hwnd, u32 msg, u64 wparam, u64 lparam)
{
  nk_win32_handle_event(hwnd, msg, wparam, lparam);
}
#endif // SHR3D_WINDOW_WIN32
#ifdef SHR3D_OPENXR
void Ui::handleInputXr()
{
  ASSERT(Global::inputXRActiveController != XrActiveController::mouseKeyboard);

  switch (Global::inputXRActiveController)
  {
  case XrActiveController::left:
    nk_input_motion(ctx, (Global::inputXrControllerLeftPosX + 1.0f) * 0.5f * Global::xrResolutionWidth, (-Global::inputXrControllerLeftPosY + 1.0f) * 0.5f * Global::xrResolutionHeight);
    break;
  case XrActiveController::right:
    nk_input_motion(ctx, (Global::inputXrControllerRightPosX + 1.0f) * 0.5f * Global::xrResolutionWidth, (-Global::inputXrControllerRightPosY + 1.0f) * 0.5f * Global::xrResolutionHeight);
    break;
  default:
    unreachable();
  }

  const XrControllerEvent& event = Global::inputXRControllerEvent[to_underlying_(Global::inputXRActiveController)];

  if (bool(event.controllerEventBit & ControllerEventBit::click_trigger))
  {
    if (event.click_trigger)
      nk_input_button(ctx, NK_BUTTON_LEFT, i32(ctx->input.mouse.pos.x), i32(ctx->input.mouse.pos.y), 1);
    else
    {
      nk_input_button(ctx, NK_BUTTON_DOUBLE, i32(ctx->input.mouse.pos.x), i32(ctx->input.mouse.pos.y), 0);
      nk_input_button(ctx, NK_BUTTON_LEFT, i32(ctx->input.mouse.pos.x), i32(ctx->input.mouse.pos.y), 0);
    }
  }

  if (abs(event.thumbstick_x) > 0.1f || abs(event.thumbstick_y) > 0.1f)
  {
    const f32 scrollSpeed = 20.0f * TimeNS_To_Seconds(Global::frameDelta);
    nk_input_scroll(ctx, nk_vec2(scrollSpeed * event.thumbstick_x, scrollSpeed * event.thumbstick_y));
  }
}
#endif // SHR3D_OPENXR

#ifdef SHR3D_WINDOW_ANDROID
void Ui::handleInput(int action, float x, float y)
{
#define ACTION_DOWN 0
#define ACTION_UP 1
#define ACTION_MOVE 2

  if (action == ACTION_DOWN)
  {
    nk_input_button(ctx, NK_BUTTON_LEFT, x, y, 1);
  }
  else if (action == ACTION_UP)
  {
    nk_input_button(ctx, NK_BUTTON_LEFT, x, y, 0);
  }
  else if (action == ACTION_MOVE)
  {
    nk_input_motion(ctx, x, y);
  }
}
#endif // SHR3D_WINDOW_ANDROID


void Ui::handleInputEnd()
{
  nk_input_end(ctx);
}

#ifdef PLATFORM_EMSCRIPTEN
extern "C"
{
  EMSCRIPTEN_KEEPALIVE int load_file(uint8_t* buffer, size_t size)
  {
    DEBUG_PRINT("uploaded file size %zu at %p\n", size, buffer);

    const std::vector<u32> alignedData(buffer, buffer + size - 1);
    DEBUG_PRINT("fixed pointer  %p\n", &alignedData);

    Collection::addSongFile("uploadedFile.psarc", (const u8*)alignedData.data(), size);

    return 1;
  }
}
#endif // PLATFORM_EMSCRIPTEN
