/*
 !MidiPlay   A MIDI synthesiser and file player.

 ro_display.c  - Interface functions required by the player to
 provide display and output.

 created  2/2022
*/

#include "wimp.h"
#include "lib.h"
#include "ro_main.h"
#include "midisyn.h"
#include "main.h"
#include "kbd.h"
#include "exp.h" // log conversion tables for trackbars (sliders)


/*
 * inv
 * ---
 * Conversion from linear values to log. Used for slider controls.
 */
static int inv(const unsigned short *tab, int in)
{
  int out = 0;

  while((tab[out] < in) && (out < (LEN-1)))
    out++;

  return out;
}


/*
 * trb_to_param
 * ------------
 * id = control id offset
 * Converts trackbar values to parameter values
 */
int trb_to_param(int slider, int n)
{
  switch(slider)
  {
    // effects window
    case SLDR_E_DELAY:    return e10k[n];    // echo delay
    case SLDR_FLANGE:     return e1k[n];     // flanger rate
    // envelopes, wave1, wave2, filter, and noise panes
    case SLDR_ENV_DY:     return e4k[n];     // envelope delay
    case SLDR_ENV_AR:     return e32k[n];    // envelope attack step
    case SLDR_ENV_AT:     return e32k[n];    // envelope attack target
    case SLDR_ENV_AH:     return e4k[n];     // envelope attack hold
    case SLDR_ENV_DR:     return e32k[n];    // envelope decay step
    case SLDR_ENV_DT:     return e32k[n];    // envelope decay target
    case SLDR_ENV_SR:     return e32k[n];    // envelope sustain step
    case SLDR_ENV_RR:     return e32k[n];    // 2 envelope release step
  // wave 1 and wave2 panes, additional sliders
    case SLDR_GLIDE:      return e32ki[n];   // glide time
  // filter pane, additional sliders
    case SLDR_RESONANCE:  return e1ki[n];    // filter resonance
  // general controls pane
    case SLDR_FM:         return e32k[n];    // fm depth
    case SLDR_MOD_RATE:   return e8k[n];     // modulation rate
    case SLDR_MOD_DEPTH:  return e32k[n];    // modulation depth
    case SLDR_GEN_RETRIG: return e1k[n];     // envelope retrigger
    case SLDR_GEN_GAIN:   return e4k20db[n]; // instrument gain
  }
  return n; // others are a direct (linear) relationship
}


/*
 * param_to_trb
 * ------------
 * id = control id offset
 * Converts parameter values to trackbar values
 */
int param_to_trb(int slider, int n)
{
  switch(slider)
  {
    // effects window
    case SLDR_E_DELAY:    return inv(e10k, n);    // echo delay
    case SLDR_FLANGE:     return inv(e1k, n);     // flanger rate
    // envelopes, wave1, wave2, filter, and noise panes
    case SLDR_ENV_DY:     return inv(e4k, n);     // envelope delay
    case SLDR_ENV_AR:     return inv(e32k, n);    // envelope attack step
    case SLDR_ENV_AT:     return inv(e32k, n);    // envelope attack target
    case SLDR_ENV_AH:     return inv(e4k, n);     // envelope attack hold
    case SLDR_ENV_DR:     return inv(e32k, n);    // envelope decay step
    case SLDR_ENV_DT:     return inv(e32k, n);    // envelope decay target
    case SLDR_ENV_SR:     return inv(e32k, n);    // envelope sustain step
    case SLDR_ENV_RR:     return inv(e32k, n);    // 2 envelope release step
  // wave 1 and wave2 panes, additional sliders
    case SLDR_GLIDE:      return inv(e32ki, n);   // glide time
  // filter pane, additional sliders
    case SLDR_RESONANCE:  return inv(e1ki, n);    // filter resonance
  // general controls pane
    case SLDR_FM:         return inv(e32k, n);    // fm depth
    case SLDR_MOD_RATE:   return inv(e8k, n);     // modulation rate
    case SLDR_MOD_DEPTH:  return inv(e32k, n);    // modulation depth
    case SLDR_GEN_RETRIG: return inv(e1k, n);     // envelope retrigger
    case SLDR_GEN_GAIN:   return inv(e4k20db, n); // instrument gain
  }
  return n; // others are a direct (linear) relationship
}


/*
 * midiPlayer_vprintf
 * ------------------
 * prints to our stdout.
 * If the format string is NULL, clear the screen
 */
int midiPlayer_vprintf(const char *format, va_list arg)
{
  #define COLS 80
  #define ROWS 50
  static int col, row, old_row;
  static char text[ROWS][COLS];
  int i, n;

  if(!format) // clear the screen
  {
    // clear text output area
    for(i=0; i<ROWS; i++)
    {
      text[i][0] = 0;
      icon_text_change(text[i] , ro.handle[WIN_CONSOLE], ICON_OUT+i);
    }
    n = col = row = old_row = 0;
    // clear command entry
    icon_text_change("" , ro.handle[WIN_CONSOLE], ICON_CMD);
  }

  else // provide a stdout display
  {
    char s[2048];
    n = vsprintf(s, format, arg);
    for(i=0; i<n; i++)
      if(s[i] != '\r') // ignore carriage returns
      {
        if(s[i] == '\n') // new line
        {
          if(++row >= ROWS)
            row = 0;
          col = 0;
        }
        else if(col >= (COLS-1)) // text wrap
        {
          if(++row >= ROWS)
            row = 0;
          text[row][0] = s[i];
          col = 1;
        }
        else
          text[row][col++] = s[i];
        text[row][col] = 0; // string terminator
      }

    // write the text rows to the window
    if(old_row != row)
    { // update all rows as a scroll has occurred
      old_row = row;
      for(i=0; i<ROWS; i++)
      {
        int r = row + i + 1;
        if(r >= ROWS)
          r -= ROWS;
        icon_text_change(text[r] , ro.handle[WIN_CONSOLE], ICON_OUT+i);
      }
    }
    else // only need to update the current row
      icon_text_change(text[row] , ro.handle[WIN_CONSOLE], ICON_OUT+ROWS-1);
  }
  return n;
}


/*
 * slider_change
 * ---------
 * used by the editor, updates a slider position and it's associated numerical value
 */
static void slider_change(int n, int slider, int window, int icon)
{
  slider_display_value(param_to_trb(slider, n), slider, window, icon);
  icon_text_change(itoa(n), ro.handle[window], icon + 1);
}


/*
 * midiPlayer_display
 * ------------------
 * displays/updates various items within the player window.
 * data contents vary according to the item number.
 * items and data that are not supported are ignored.
 */
void midiPlayer_display(int item, int *data)
{
  char s[32], *p;
  int i;
  static int old_posn = -1;

  switch(item)
  {
    case ITEM_START: // data[0] = state, 0 or 1
      icon_state_change(data[0], ro.handle[WIN_PLAYER], ICON_START);
      break;

    case ITEM_PAUSE: // data[0] = state, 0 or 1
      icon_state_change(data[0], ro.handle[WIN_PLAYER], ICON_PAUSE);
      break;

    case ITEM_STOP: // data[0] = state, 0 or 1
      icon_state_change(data[0], ro.handle[WIN_PLAYER], ICON_STOP);
      break;

    case ITEM_LOOP: // data[0] = state, 0 or 1
      ro.controls->item[0].item_flags = (ro.controls->item[0].item_flags & ~IT_TICKED) | data[0];
      break;

    case ITEM_MONO: // data[0] = state, 0 or 1
      ro.controls->item[1].item_flags = (ro.controls->item[1].item_flags & ~IT_TICKED) | data[0];
      break;

    case ITEM_SWAP: // data[0] = state, 0 or 1
      ro.controls->item[2].item_flags = (ro.controls->item[2].item_flags & ~IT_TICKED) | data[0];
      break;

    case ITEM_BANKS: // data[0] = state, 0 or 1
      icon_state_change(data[0], ro.handle[WIN_BANKS], ICON_ALL_BANKS);
      break;

    case ITEM_KITS: // data[0] = state, 0 or 1
      icon_state_change(data[0], ro.handle[WIN_KITS], ICON_ALL_KITS);
      break;

    case ITEM_BANK_OVERRIDE: // data[0] = bank override 0..127
      icon_text_change(itoa(data[0]), ro.handle[WIN_BANKS], ICON_BANK_OVERRIDE);
      break;

    case ITEM_KIT_OVERRIDE: // data[0] = drum kit override 0..127
      icon_text_change(itoa(data[0]), ro.handle[WIN_KITS], ICON_KIT_OVERRIDE);
      break;

    case ITEM_BUFFER: // data[0] = buffer size in samples
      for(i=0; i<NUM_SIZES; i++)
        if(ro.buffer_size[i] == data[0])
          update_menu(ro.buffer->item, NUM_SIZES, i);
      break;

    case ITEM_RATE: // data[0] = synth sample rate
      for(i=0; i<NUM_RATES; i++)
        if(ro.sample_rate[i] == data[0])
          update_menu(ro.rates->item, NUM_RATES, i);
      break;

    case ITEM_INTERFACE: // data[0] = audio interface, 0 = SharedSound, 1 = DiskSample
      if(data[0] == 0)
      {
        ro.interface->item[0].item_flags |= IT_TICKED;
        ro.interface->item[1].item_flags &= ~IT_TICKED;
        ro.synth->item[1].icon_flags &= ~IC_SHADED;
      }
      else
      {
        ro.interface->item[1].item_flags |= IT_TICKED;
        ro.interface->item[0].item_flags &= ~IT_TICKED;
        ro.synth->item[1].icon_flags |= IC_SHADED;
      }
      break;

    case ITEM_CHORUS: // data[0] = state, 0 or 1
                      // data[1] = rate, 0 to 1000
      icon_state_change(data[0], ro.handle[WIN_EFFECTS], ICON_CHORUS);
      slider_display_value(data[1], SLDR_CHORUS, WIN_EFFECTS, ICON_CHORUS_SLDR);
      break;

    case ITEM_REVERB: // data[0] = state, 0 or 1
                      // data[1] = gain, 0 to 500
      icon_state_change(data[0], ro.handle[WIN_EFFECTS], ICON_REVERB);
      slider_display_value(data[1], SLDR_REVERB, WIN_EFFECTS, ICON_REVERB_SLDR);
      break;

    case ITEM_FLANGER: // data[0] = state, 0 or 1
                       // data[1] = rate, 0 to 1023
      icon_state_change(data[0], ro.handle[WIN_EFFECTS], ICON_FLANGE);
      slider_display_value(param_to_trb(SLDR_FLANGE, data[1]), SLDR_FLANGE, WIN_EFFECTS, ICON_FLANGE_SLDR);
      break;

    case ITEM_ECHO: // data[0] = state, 0 or 1
                    // data[1] = delay, 1 to 16383
                    // data[2] = straight gain, 0 to 1023
                    // data[3] = cross gain, 0 to 1023
      icon_state_change(data[0], ro.handle[WIN_EFFECTS], ICON_ECHO);
      slider_display_value(param_to_trb(SLDR_E_DELAY, data[1]), SLDR_E_DELAY, WIN_EFFECTS, ICON_E_DELAY_SLDR);
      slider_display_value(data[2], SLDR_E_SGAIN, WIN_EFFECTS, ICON_E_SGAIN_SLDR);
      slider_display_value(data[3], SLDR_E_CGAIN, WIN_EFFECTS, ICON_E_CGAIN_SLDR);
      break;

    case ITEM_TEMPO: // data[0] = tempo, 10 to 1000, percent
      icon_text_change(itoa(data[0]), ro.handle[WIN_TEMPO], 0);
      break;

    case ITEM_PITCH: // data[0] = pitch, -12 to +12, semitones
      icon_text_change(itoa(data[0]), ro.handle[WIN_PITCH], 0);
      break;

    case ITEM_VOLUME: // data[0] = volume, 0 to 1023
      slider_display_value(data[0], SLDR_VOLUME, WIN_PLAYER, ICON_VOL_SLDR);
      break;

    case ITEM_BALANCE: // data[0] = left right channel balance, -20 to +20
      slider_display_value(data[0], SLDR_BALANCE, WIN_PLAYER, ICON_BAL_SLDR);
      break;

    case ITEM_BASS: // data[0] = bass, -15 to +15, dB
      slider_display_value(data[0], SLDR_BASS, WIN_PLAYER, ICON_BAS_SLDR);
      break;

    case ITEM_TREBLE: // data[0] = treble, -15 to +15, dB
      slider_display_value(data[0], SLDR_TREBLE, WIN_PLAYER, ICON_TRB_SLDR);
      break;

    case ITEM_POSN: // data[0] = position
                    // data[1] = song length, both in seconds
      //  if position is negative, the position is blanked
      if(data[0] < 0)
      {
        slider_display_value(0, SLDR_POSN, WIN_PLAYER, ICON_POSN_VALUE);
        old_posn = -1;
        s[0] = 0;
      }
      else
      {
        if (old_posn != data[0])
        {
          ro.slider.type[SLDR_POSN].hi = data[1];
          slider_display_value(data[0], SLDR_POSN, WIN_PLAYER, ICON_POSN_VALUE);
          old_posn = data[0];
        }
        sprintf(s, "%d:%02d", data[0] / 60, data[0] % 60);
      }
      icon_text_change(s, ro.handle[WIN_PLAYER], ICON_FILE_POSN);
      break;

    case ITEM_LEN: // data[0] = (char*) song length, mm:ss
      icon_text_change((char*)data[0], ro.handle[WIN_PLAYER], ICON_FILE_DURATION);
      break;

    case ITEM_NAME: // data[0] = (char*) song name
      icon_text_change((char*)data[0], ro.handle[WIN_PLAYER], ICON_FILE_NAME);
      icon_text_change((char*)data[0], ro.handle[WIN_SAVE], ICON_WAV_NAME);
      break;

    case ITEM_VERSION: // data[0] = (char*)version string
      // alter version string to a riscos info string
      // Source string: MidiPlay 0.14, Aug 4 2022, 10:03:44
      // Required Info string: 0.14 (4 Aug 2022)
      {
        char day[4], month[4], year[8], version[8];
        i = 0;
        int j = 0;
        p = (char*)data[0];
        while(p[i++] != '.');
        i -=2;
        while(p[i] != ',') version[j++] = p[i++];
        version[j] = 0;
        while(p[++i] == ' ');
        j = 0;
        while(p[i] != ' ') month[j++] = p[i++];
        month[j] = 0;
        while(p[i] == ' ')i++;
        j = 0;
        while(p[i] != ' ') day[j++] = p[i++];
        day[j] = 0;
        while(p[i] == ' ')i++;
        j = 0;
        while(p[i] != ',') year[j++] = p[i++];
        year[j] = 0;
        sprintf(s, "%s (%s %s %s)", version, day, month, year);
      }
      icon_text_change(s, ro.handle[WIN_INFO], 0);
      icon_text_change(APP_NAME, ro.handle[WIN_INFO], 3);
      break;

    case ITEM_CHN_VOL: // data[0] = channel, data[1] = volume
      slider_display_value(data[1], SLDR_CHN_VOL, WIN_CHANNELS, ICON_CHN_VOL_SLDR + (7 * data[0]));
      break;

    case ITEM_CHN_PRG: // data[0] = channel, data[1] = program, data[2] = &name
      icon_text_change(itoa(data[1]), ro.handle[WIN_CHANNELS], ICON_CHN_PRG_VAL + (7 * data[0]));
      icon_text_change((char *)(data[2]), ro.handle[WIN_CHANNELS], ICON_CHN_PRG_NAME + (7 * data[0]));
      break;

    case ITEM_USER: // data[0] = instrument number
                    // data[1] = pointer to bank name
                    // data[2] = pointer to instrument definition
                    // data[3] = program
                    // data[4] = bank hi
                    // data[5] = bank lo
      {
        ins_t *i = (ins_t *)(data[2]);
        // editor window
        icon_text_change((char *)(data[6]), ro.handle[WIN_EDT], ICON_ED_SOUND_SET);
        icon_text_change(itoa(data[0]), ro.handle[WIN_EDT], ICON_ED_PATCH);
        icon_text_change(i->name, ro.handle[WIN_EDT], ICON_ED_INST_NAME);
        icon_text_change((char *)(data[1]), ro.handle[WIN_EDT], ICON_ED_BANK_NAME);
        icon_text_change(itoa(data[3]), ro.handle[WIN_EDT], ICON_ED_PRG_VAL);
        icon_text_change(itoa(data[4]), ro.handle[WIN_EDT], ICON_ED_BHI_VAL);
        icon_text_change(itoa(data[5]), ro.handle[WIN_EDT], ICON_ED_BLO_VAL);
        if(data[4] < 128) // melodic edit
        {
          icon_text_change("Program", ro.handle[WIN_EDT], ICON_ED_PRG_NAME);
          icon_text_change("Bank Lo", ro.handle[WIN_EDT], ICON_ED_BLO_NAME);
        }
        else // percussion edit, or non editable drum kit
        {
          icon_text_change("Kit", ro.handle[WIN_EDT], ICON_ED_PRG_NAME);
          icon_text_change("Key", ro.handle[WIN_EDT], ICON_ED_BLO_NAME);
        }

        // wave 1 pane
        slider_change(i->wave[0].env.delay,         SLDR_ENV_DY, WIN_ED_WAVE1, ICON_ENV_DY_SLDR);
        slider_change(i->wave[0].env.attack_step,   SLDR_ENV_AR, WIN_ED_WAVE1, ICON_ENV_AR_SLDR);
        slider_change(i->wave[0].env.attack_target, SLDR_ENV_AT, WIN_ED_WAVE1, ICON_ENV_AT_SLDR);
        slider_change(i->wave[0].env.hold,          SLDR_ENV_AH, WIN_ED_WAVE1, ICON_ENV_AH_SLDR);
        slider_change(i->wave[0].env.decay_step,    SLDR_ENV_DR, WIN_ED_WAVE1, ICON_ENV_DR_SLDR);
        slider_change(i->wave[0].env.decay_target,  SLDR_ENV_DT, WIN_ED_WAVE1, ICON_ENV_DT_SLDR);
        slider_change(i->wave[0].env.sustain_step,  SLDR_ENV_SR, WIN_ED_WAVE1, ICON_ENV_SR_SLDR);
        slider_change(i->wave[0].env.release_step,  SLDR_ENV_RR, WIN_ED_WAVE1, ICON_ENV_RR_SLDR);
        slider_change(i->wave[0].glide,             SLDR_GLIDE,  WIN_ED_WAVE1, ICON_WAV_GT_SLDR);
        icon_text_change(syn.idat.harm[i->wave[0].number].name, ro.handle[WIN_ED_WAVE1], ICON_WAV_NUM_NAME);
        icon_text_change(itoa(i->wave[0].number),          ro.handle[WIN_ED_WAVE1], ICON_WAV_NUM_VAL);
        icon_text_change(itoa(i->wave[0].pitch),           ro.handle[WIN_ED_WAVE1], ICON_WAV_FPO_VAL);
        icon_text_change(itoa(i->wave[0].initial_pitch),   ro.handle[WIN_ED_WAVE1], ICON_WAV_IPO_VAL);
        icon_state_change((i->switches >> PITCH1_MOD) & 1, ro.handle[WIN_ED_WAVE1], ICON_WAV_PM);
        icon_state_change((i->switches >> AMP1_MOD) & 1,   ro.handle[WIN_ED_WAVE1], ICON_WAV_AUM);
        icon_state_change((i->switches >> AMP1_RING) & 1,  ro.handle[WIN_ED_WAVE1], ICON_WAV_ASM);
        // wave 2 pane
        slider_change(i->wave[1].env.delay,         SLDR_ENV_DY, WIN_ED_WAVE2, ICON_ENV_DY_SLDR);
        slider_change(i->wave[1].env.attack_step,   SLDR_ENV_AR, WIN_ED_WAVE2, ICON_ENV_AR_SLDR);
        slider_change(i->wave[1].env.attack_target, SLDR_ENV_AT, WIN_ED_WAVE2, ICON_ENV_AT_SLDR);
        slider_change(i->wave[1].env.hold,          SLDR_ENV_AH, WIN_ED_WAVE2, ICON_ENV_AH_SLDR);
        slider_change(i->wave[1].env.decay_step,    SLDR_ENV_DR, WIN_ED_WAVE2, ICON_ENV_DR_SLDR);
        slider_change(i->wave[1].env.decay_target,  SLDR_ENV_DT, WIN_ED_WAVE2, ICON_ENV_DT_SLDR);
        slider_change(i->wave[1].env.sustain_step,  SLDR_ENV_SR, WIN_ED_WAVE2, ICON_ENV_SR_SLDR);
        slider_change(i->wave[1].env.release_step,  SLDR_ENV_RR, WIN_ED_WAVE2, ICON_ENV_RR_SLDR);
        slider_change(i->wave[1].glide,             SLDR_GLIDE,  WIN_ED_WAVE2, ICON_WAV_GT_SLDR);
        icon_text_change(syn.idat.harm[i->wave[1].number].name, ro.handle[WIN_ED_WAVE2], ICON_WAV_NUM_NAME);
        icon_text_change(itoa(i->wave[1].number), ro.handle[WIN_ED_WAVE2], ICON_WAV_NUM_VAL);
        icon_text_change(itoa(i->wave[1].pitch),           ro.handle[WIN_ED_WAVE2], ICON_WAV_FPO_VAL);
        icon_text_change(itoa(i->wave[1].initial_pitch),   ro.handle[WIN_ED_WAVE2], ICON_WAV_IPO_VAL);
        icon_state_change((i->switches >> PITCH2_MOD) & 1, ro.handle[WIN_ED_WAVE2], ICON_WAV_PM);
        icon_state_change((i->switches >> AMP2_MOD) & 1,   ro.handle[WIN_ED_WAVE2], ICON_WAV_AUM);
        icon_state_change((i->switches >> AMP2_RING) & 1,  ro.handle[WIN_ED_WAVE2], ICON_WAV_ASM);
        // filter pane
        slider_change(i->filter_env.delay,        SLDR_ENV_DY, WIN_ED_FILTER, ICON_ENV_DY_SLDR);
        slider_change(i->filter_env.attack_step,  SLDR_ENV_AR, WIN_ED_FILTER, ICON_ENV_AR_SLDR);
        slider_change(i->filter_env.attack_target,SLDR_ENV_AT, WIN_ED_FILTER, ICON_ENV_AT_SLDR);
        slider_change(i->filter_env.hold,         SLDR_ENV_AH, WIN_ED_FILTER, ICON_ENV_AH_SLDR);
        slider_change(i->filter_env.decay_step,   SLDR_ENV_DR, WIN_ED_FILTER, ICON_ENV_DR_SLDR);
        slider_change(i->filter_env.decay_target, SLDR_ENV_DT, WIN_ED_FILTER, ICON_ENV_DT_SLDR);
        slider_change(i->filter_env.sustain_step, SLDR_ENV_SR, WIN_ED_FILTER, ICON_ENV_SR_SLDR);
        slider_change(i->filter_env.release_step, SLDR_ENV_RR, WIN_ED_FILTER, ICON_ENV_RR_SLDR);
        slider_change(i->filter_q,             SLDR_RESONANCE, WIN_ED_FILTER, ICON_FLT_RES_SLDR);
        icon_text_change(itoa(i->filter_fc), ro.handle[WIN_ED_FILTER], ICON_FLT_CF_VAL);
        icon_state_change((i->switches >> LOWPASS) & 1,  ro.handle[WIN_ED_FILTER], ICON_FLT_LOW_PASS);
        icon_state_change((i->switches >> BANDPASS) & 1,  ro.handle[WIN_ED_FILTER], ICON_FLT_BAND_PASS);
        icon_state_change((i->switches >> HIGHPASS) & 1, ro.handle[WIN_ED_FILTER], ICON_FLT_HIGH_PASS);
        icon_state_change((i->switches >> FILTER_RETRIGGER) & 1, ro.handle[WIN_ED_FILTER], ICON_FLT_RETRIG);
        icon_state_change((i->switches >> INV_FILTER_ENV) & 1,  ro.handle[WIN_ED_FILTER], ICON_FLT_INVERT);
        icon_state_change((i->switches >> FILTER_MOD) & 1,  ro.handle[WIN_ED_FILTER], ICON_FLT_PM);
        icon_state_change((i->switches >> FILTER_TRACK) & 1,  ro.handle[WIN_ED_FILTER], ICON_FLT_PT);
        icon_state_change((i->switches >> FILTERED_TONE) & 1,  ro.handle[WIN_ED_FILTER], ICON_FLT_WF);
        icon_state_change((i->switches >> FILTER_ENV_TRACK) & 1,  ro.handle[WIN_ED_FILTER], ICON_FLT_ET);
        // noise pane
        slider_change(i->noise_env.delay,         SLDR_ENV_DY, WIN_ED_NOISE, ICON_ENV_DY_SLDR);
        slider_change(i->noise_env.attack_step,   SLDR_ENV_AR, WIN_ED_NOISE, ICON_ENV_AR_SLDR);
        slider_change(i->noise_env.attack_target, SLDR_ENV_AT, WIN_ED_NOISE, ICON_ENV_AT_SLDR);
        slider_change(i->noise_env.hold,          SLDR_ENV_AH, WIN_ED_NOISE, ICON_ENV_AH_SLDR);
        slider_change(i->noise_env.decay_step,    SLDR_ENV_DR, WIN_ED_NOISE, ICON_ENV_DR_SLDR);
        slider_change(i->noise_env.decay_target,  SLDR_ENV_DT, WIN_ED_NOISE, ICON_ENV_DT_SLDR);
        slider_change(i->noise_env.sustain_step,  SLDR_ENV_SR, WIN_ED_NOISE, ICON_ENV_SR_SLDR);
        slider_change(i->noise_env.release_step,  SLDR_ENV_RR, WIN_ED_NOISE, ICON_ENV_RR_SLDR);
        icon_state_change((i->switches >> NOISE_MOD) & 1,  ro.handle[WIN_ED_NOISE], ICON_NOISE_AM);
        icon_state_change((i->switches >> NOISE_RETRIGGER) & 1,  ro.handle[WIN_ED_NOISE], ICON_NOISE_RETRIG);
        icon_state_change((i->switches >> NOISE_ENV_TRACK) & 1,  ro.handle[WIN_ED_NOISE], ICON_NOISE_ET);
        // general pane
        slider_change(i->fm_depth,  SLDR_FM,         WIN_ED_GENERAL, ICON_COM_FM_SLDR);
        slider_change(i->detune,    SLDR_DETUNE,     WIN_ED_GENERAL, ICON_COM_DT_SLDR);
        slider_change(i->mod_rate,  SLDR_MOD_RATE,   WIN_ED_GENERAL, ICON_MOD_RATE_SLDR);
        slider_change(i->mod_depth, SLDR_MOD_DEPTH,  WIN_ED_GENERAL, ICON_MOD_DPTH_SLDR);
        slider_change(i->retrig,    SLDR_GEN_RETRIG, WIN_ED_GENERAL, ICON_GEN_ERT_SLDR);
        slider_change(i->gain,      SLDR_GEN_GAIN,   WIN_ED_GENERAL, ICON_GEN_GAIN_SLDR);
        icon_text_change(syn.idat.harm[i->mod_wave].name, ro.handle[WIN_ED_GENERAL], ICON_MOD_NUM_NAME);
        icon_text_change(itoa(i->mod_wave), ro.handle[WIN_ED_GENERAL], ICON_MOD_NUM_VAL);
        icon_state_change((i->switches >> PITCH_TRACK) & 1,  ro.handle[WIN_ED_GENERAL], ICON_COM_PT);
        icon_state_change((i->switches >> WAVE_ENV_TRACK) & 1,  ro.handle[WIN_ED_GENERAL], ICON_COM_ET);
        icon_state_change((i->switches >> WAVE_RETRIGGER) & 1,  ro.handle[WIN_ED_GENERAL], ICON_COM_RETRIG);
        icon_state_change((i->switches >> TONE1_OUT) & 1,  ro.handle[WIN_ED_GENERAL], ICON_COM_W1_OUT);
        icon_state_change((i->switches >> TONE2_OUT) & 1,  ro.handle[WIN_ED_GENERAL], ICON_COM_W2_OUT);
        icon_state_change((i->switches >> TONE_RING_MOD) & 1,  ro.handle[WIN_ED_GENERAL], ICON_COM_WRM);
        icon_state_change((i->switches >> DEFINED_LENGTH) & 1,  ro.handle[WIN_ED_GENERAL], ICON_GEN_DLE);
        icon_state_change((i->switches >> MONO_SCAN) & 1,  ro.handle[WIN_ED_GENERAL], ICON_GEN_MONO);
      }
      break;
  }
}


