/*
 main.c   riscos main entry

 created 23/12/21
*/

#include "common.h"
#include "wimp.h"
#include "lib.h"
#include "synth.h"
#include "main.h"
#include "editor.h"
#include "control.h"
#include "waveform.h"


// Synth control menu
menu_t control_menu =
{
  "Control",NULL,7, 7,2,7,0,0,44,0,
  {
    {256,-1,IC_DEF,"Help...",NULL,7},
    {128,-1,IC_DEF,"Quit",   NULL,4}
  }
};

// Sample rate menu (MAX_SYS_RATES items)
menu_t rate_menu =
{
  "Sample Rate",NULL,11, 7,2,7,0,0,44,0,
  {
   {0},{0},{0},{0},{0},{0},{0},{0},{0},{0},
   {0},{0},{0},{0},{0},{0},{0},{0},{0},{0}
  }
};

// Test sub menu
menu_t test_menu =
{
  "Test using",NULL,10, 7,2,7,0,0,44,0,
  {
    {256,-1,IC_DEF,"Oscillator 1",NULL,12},
    {  0,-1,IC_DEF,"Oscillator 2",NULL,12},
    {128,-1,IC_DEF,"Modulator",   NULL, 9}
  }
};

// Waveform editor menu
menu_t waveform_menu =
{
  "Waveform",NULL,8, 7,2,7,0,0,44,0,
  {
    {258,(int)&test_menu,IC_DEF,"Test",   NULL,4},
    {  2,-1,IC_DEF,             "Delete", NULL,6},
    {  0,-1,IC_DEF,             "Help...",NULL,7},
    {128,-1,IC_DEF,             "Quit",   NULL,4}
  }
};

// Sound Set sub menu
menu_t sound_set_menu =
{
  "Sound Set",NULL,9, 7,2,7,0,0,44,0,
  {
    {256,-1,IC_DEF,"Save",        NULL, 4},
    {128,-1,IC_DEF,"Load Default",NULL,12}
  }
};

// Delete sub menu
menu_t delete_menu =
{
  "Delete Instrument",NULL,17, 7,2,7,0,0,44,0,
  {
    {256,-1,IC_DEF,"Clear Bank Ref only",    NULL,19},
    {128,-1,IC_DEF,"Delete Instr & all Refs",NULL,23}
  }
};

// Instrument sub menu
menu_t instrument_menu =
{
  "Instrument",NULL,10, 7,2,7,0,0,44,0,
  {
    {256,-1,IC_DEF,               "Create",NULL,6},
    {128,(int)&delete_menu,IC_DEF,"Delete",NULL,6}
  }
};

// Instrument editor menu
menu_t editor_menu =
{
  "Editor",NULL,6, 7,2,7,0,0,44,0,
  {
    {256,(int)&instrument_menu,IC_DEF,"Instrument",     NULL,10},
    {  0,(int)&sound_set_menu, IC_DEF,"Sound Set",      NULL, 9},
    {  0,-1,IC_DEF,                   "Choices Dir...", NULL,14},
    {  0,-1,IC_DEF,                   "Help...",        NULL, 7},
    {128,-1,IC_DEF,                   "Quit",           NULL, 4}
  }
};

// Edit sub menu
menu_t edit_sub_menu =
{
  "Edit",NULL,7, 7,2,7,0,0,44,0,
  {
    {256,-1,IC_DEF,"Instruments...",NULL,14},
    {128,-1,IC_DEF,"Waveforms...",  NULL,12}
  }
};

// Iconbar menu
menu_t iconbar_menu =
{
  "MidiSyn",NULL,7, 7,2,7,0,0,44,0,
  {
    {256,-1,IC_DEF,                 "Info",      NULL, 4},
    {  0,-1,IC_DEF,                 "Control...",NULL,10},
    {  0,(int)&edit_sub_menu,IC_DEF,"Editors",   NULL, 7},
    {  0,-1,IC_DEF,                 "Choices Dir...", NULL,14},
    {  0,-1,IC_DEF,                 "Help...",   NULL, 7},
    {128,-1,IC_DEF,                 "Quit",      NULL, 4}
  }
};
#define MENU_ICONBAR_ITEMS 6

ro_glbs_t ro =
{
  0,     // task handle
  {0},   // window handles
  0,     // current menu
  0,     // current editor pane
  SYN_SAVE_FULL, // save type
  0,     // system sample rate start
  0,     // flags
  0,     // save patch
  0,     // current waveform
  NULL,  // waveform plot address
  0,0,0,0,0,0,0,0,0,0,0,0, // data returned by MIDISynth_Edit
  0,     // master volume
  0,     // master balance
  0,     // master coarse tuning
  0,     // master fine tuning
  {
    -1,  // window that has caret
    -1   // icon that has caret
  },
  {
    0,   // vdu width
    0    // vdu height
  },
  {
    0,   // currently selected slider window
    0,   // currently selected slider icon
    { // slider definitions
      // wave1 wave2 filter and noise panes, envelopes
      {  0,  100, HORIZONTAL | SLIDER_CONTROL}, // SLDR_ENV_DY,   envelope delay (log)
      {  0,  100, HORIZONTAL | SLIDER_CONTROL}, // SLDR_ENV_AR,   envelope attack step (log)
      {  0,  100, HORIZONTAL | SLIDER_CONTROL}, // SLDR_ENV_AT,   envelope attack target (log)
      {  0,  100, HORIZONTAL | SLIDER_CONTROL}, // SLDR_ENV_AH,   envelope attack hold (log)
      {  0,  100, HORIZONTAL | SLIDER_CONTROL}, // SLDR_ENV_DR,   envelope decay rate (log)
      {  0,  100, HORIZONTAL | SLIDER_CONTROL}, // SLDR_ENV_DT,   envelope decay target (log)
      {  0,  100, HORIZONTAL | SLIDER_CONTROL}, // SLDR_ENV_SR,   envelope sustain rate (log)
      {  0,  100, HORIZONTAL | SLIDER_CONTROL}, // SLDR_ENV_RR,   envelope release rate (log)
      // wave1 and wave2 panes
      {  0,  100, HORIZONTAL | SLIDER_CONTROL}, // SLDR_GLIDE,    glide time (log)
      // filter pane
      {  0,  100, HORIZONTAL | SLIDER_CONTROL}, // SLDR_RESONANCE, resonance (log)
      // general controls pane
      {  0,  100, HORIZONTAL | SLIDER_CONTROL}, // SLDR_FM,       FM depth (log)
      {  0, 1023, HORIZONTAL | SLIDER_CONTROL}, // SLDR_DETUNE,   wave1/2 detune
      {  0,  100, HORIZONTAL | SLIDER_CONTROL}, // SLDR_MOD_RATE, modulation rate (log)
      {  0,  100, HORIZONTAL | SLIDER_CONTROL}, // SLDR_MOD_DEPTH, modulation depth (log)
      {  0,  100, HORIZONTAL | SLIDER_CONTROL}, // SLDR_GEN_RETRIG, envelope retrigger rate (log)
      {  0,  100, HORIZONTAL | SLIDER_CONTROL}, // SLDR_GEN_GAIN, instrument gain (log)
      // synth control window
      {  0, 1023, VERTICAL   | SLIDER_CONTROL}, // SLDR_VOLUME,  master volume
      {  0, 1023, VERTICAL   | SLIDER_CONTROL}, // SLDR_BALANCE, master balance
      {-8192,8191,VERTICAL   | SLIDER_CONTROL}, // SLDR_FINE,    master fine tune
      // waveform edit window
      { -400,  0, VERTICAL   | PROGRESS_BAR  }, // SLDR_WED_AMP, harmonic amplitudes
      {  0,   64, HORIZONTAL | PROGRESS_BAR  }  // SLDR_ACTIVE, active generators bar
    }
  }
};


void display_user(void); // display.c
char *TimestampToString(unsigned int t); // display.c

void key_press(int *blk);


/*
 * display_active_gens
 * -------------------
 * Displays the number of active generators. Also checks that we are connected to
 * the synth module. This is important because we access it's memory directly.
 */
static void display_active_gens(void)
{
  #define GEN_SIZE 71 // size of generator structure, in int's
  #define GEN_ARRAY (GEN_SIZE * NUM_GENS) // size of generator array, in int's
  #define GEN_IDAT (8 + NUM_GENS) // offset between gen end and idat within syn structure, in int's
  #define GEN_SWIT 15 // switches offset within generator structure, in int's
  #define OFFSET (GEN_SWIT - GEN_IDAT - GEN_ARRAY)
  #define ACTIVE   31 // bit within gen.switches, set if gen active

  _kernel_swi_regs regs;
  int blk[13];
  static int timer;
  if(++timer < 3)
    return;
  timer = 0;

  regs.r[0] = 0;
  regs.r[1] = (int)blk;
  if(_kernel_swi(MIDISynth_Edit, &regs, &regs))
  {
    report_error("Synth module has stopped, must exit", 1);
    ro.flags |= (1<<QUIT);
    return;
  }
  // blk[7] = address of syn.idat
  int *g = (int *)blk[7] + OFFSET; // address of syn.gen[0].switches

  _kernel_swi(MIDISynth_Control, &regs, &regs);
  // blk[0] = user defined number of generators in use
  int i, *end = g + (GEN_SIZE * blk[0]);

  for (i = 0; (g < end); g += GEN_SIZE)
    if(*g & (1<<ACTIVE))
      i++;

  ro.slider.type[SLDR_ACTIVE].hi = blk[0];
  slider_display_value(i, SLDR_ACTIVE, WIN_SYN_CONTROL, ICON_ACTIVE_VAL);
}


/*
 * read_menus
 * ----------
 * load menu text from Messages file if possible
 */
static void read_menus(void)
{
  static char menu_strings[512]; // holds all menu strings
  char *s = menu_strings;

  s = msg_load_menu("iconbar_m",   s, &iconbar_menu);
  s = msg_load_menu("edit_sub_m",  s, &edit_sub_menu);
  s = msg_load_menu("editor_m",    s, &editor_menu);
  s = msg_load_menu("control_m",   s, &control_menu);
  s = msg_load_menu("sound_set_m", s, &sound_set_menu);
  s = msg_load_menu("instrument_m",s, &instrument_menu);
  s = msg_load_menu("delete_m",    s, &delete_menu);
  s = msg_load_menu("waveform_m",  s, &waveform_menu);
  s = msg_load_menu("test_m",      s, &test_menu);
  s = msg_load_menu("rate_m",      s, &rate_menu);
  s = create_sys_rate_menu(rate_menu.item, s);
/*
  {
    char str[64];
    sprintf(str, "Menu Strings length %d bytes", (int)(s-menu_strings));
    report_error(str,0);
  }
*/
  // load window handles
  iconbar_menu.item[0].sub = ro.handle[WIN_INFO];
  sound_set_menu.item[0].sub = ro.handle[WIN_SAVE];
  instrument_menu.item[0].sub = ro.handle[WIN_CREATE];
}


/*
 * save_file
 * ---------
 * Commands the synth to save the sound set.
 * Uses the filename in the save dialog and the path if given. If not
 * given, uses the default directory.
 */
void save_file(int window, char *path)
{
  _kernel_swi_regs regs;
  int blk[10];

  blk[0] = window;
  blk[1] = ICON_FILE_NAME;
  regs.r[1] = (int)blk;
  _kernel_swi(Wimp_GetIconState, &regs, &regs);

  static char s[256]; // 23/10/24 made static to see if it stopped bad things happening sometimes.
                      // If this comment is still here in a years time, it probably did.
  if(!path)
    sprintf(s, "<MidiSynthChoices$Dir>.%s", (char *)(blk[7])); // use default directory
  else
    sprintf(s, "%s%s", path, (char *)(blk[7])); // use given directory

  synth_command(ro.save_type, (int)s);
  icon_text_change(TimestampToString(ro.soundset_date), ro.handle[WIN_EDT], ICON_ED_SSET_DATE);
}


/*
 * slider_control
 * --------------
 * Handles dragging of slider controls
 */
static void slider_control(int blk[])
{
  if(!ro.slider.icon)
    return;

  _kernel_swi_regs regs;

  regs.r[1] = (int)blk;
  _kernel_swi(Wimp_GetPointerInfo, &regs, &regs);

  if(blk[2] == 0) // button released
  {
    icon_state_change(0, ro.slider.window, ro.slider.icon);
    ro.slider.icon = 0;
    return;
  }

  if(edt_slider_control(blk));

  else if(ctrl_slider_control(blk));

  else if(wave_slider_control(blk));
}


/*
 * mouse_click
 * -----------
 */
static void mouse_click(int blk[])
{
  int n;
  _kernel_swi_regs regs;
  int b[13];
  b[0] = blk[3]; // window
  b[1] = blk[4]; // icon
  regs.r[1] = (int)b;
  _kernel_swi(Wimp_GetIconState, &regs, &regs);
  int state = (b[6] >> 21) & 1;

  // catch caret location change
  if(blk[2] != MOUSE_MENU)
    if((b[6] & (15<<12)) == (14<<12)) // writable, drag
      if(ro.caret.window >= 0)
        if((ro.caret.window != b[0]) || (ro.caret.icon != b[1]))
        {
          b[0] = ro.caret.window;
          b[1] = ro.caret.icon;
          b[6] = '\r';
          key_press(b);
          return;
        }

  // Iconbar
  if(blk[3] == ICONBAR)
  {
    switch(blk[2])
    {
      case MOUSE_SELECT: // open control window
        update_control_window();
        open_window(ro.handle[WIN_SYN_CONTROL]);
        break;

      case MOUSE_MENU:
        open_menu(blk[0] - 64, (44*MENU_ICONBAR_ITEMS)+96, MENU_ICONBAR, (int)&iconbar_menu);
        break;

      case MOUSE_ADJUST: // close control window
        blk[0] = ro.handle[WIN_SYN_CONTROL];
        regs.r[1] = (int)blk;
        _kernel_swi(Wimp_CloseWindow, &regs, &regs);
        break;
    }
  }

  // Save file window
  else if(blk[3] == ro.handle[WIN_SAVE])
  {
    if((blk[2] == 4*16) && (blk[4] == ICON_FILE_SPRITE))
      drag_start(blk[3], blk[4]); // start a drag

    else if((blk[2] == MOUSE_SELECT) || (blk[2] == MOUSE_ADJUST))
      switch(blk[4])
      {
        case ICON_FILE_OK:
          save_file(blk[3], 0); // save to default directory
          if(blk[2] == MOUSE_SELECT)
            open_menu(0, 0, ro.cur_menu, -1);
          break;

        case ICON_SS_DATA:
          icon_sprite_change(blk[3], ICON_FILE_SPRITE, "Sfile_ffd");
          ro.save_type = SYN_SAVE_FULL;
          break;

        case ICON_BANK_MAP:
          icon_sprite_change(blk[3], ICON_FILE_SPRITE, "Sfile_dfe");
          ro.save_type = SYN_SAVE_SRC + (0x100 << CSV_MAP);
          break;

        case ICON_PATCH_LIST:
          icon_sprite_change(blk[3], ICON_FILE_SPRITE, "Sfile_fff");
          ro.save_type = SYN_SAVE_SRC + (0x100 << TXT_PATCH);
          break;

        case ICON_INSTR_LIST:
          icon_sprite_change(blk[3], ICON_FILE_SPRITE, "Sfile_fff");
          ro.save_type = SYN_SAVE_SRC + (0x100 << TXT_LST);
          break;

        case ICON_C_SOURCE:
          icon_sprite_change(blk[3], ICON_FILE_SPRITE, "Sfile_fff");
          ro.save_type = SYN_SAVE_SRC + (0x100 << C_SRC);
          break;
      }
  }

  // Create Instrument window
  else if(blk[3] == ro.handle[WIN_CREATE])
  {
    int inc = 0;
    if(blk[2] == MOUSE_SELECT)
      inc = 1;
    else if (blk[2] == MOUSE_ADJUST)
      inc = -1;

    if(blk[2] != MOUSE_MENU)
      switch (blk[4])
      {
        case ICON_CA_MELODIC:
          icon_text_change("Program", blk[3], ICON_CA_PROG);
          icon_text_change("Bank", blk[3], ICON_CA_BANK);
          ro.save_patch &= ~0xff;
          break;

        case ICON_CA_PERCUSSION:
          icon_text_change("Kit", blk[3], ICON_CA_PROG);
          icon_text_change("Key", blk[3], ICON_CA_BANK);
          ro.save_patch |= PERCUSSION_INST;
          break;

        case ICON_CA_PROG_DEC:
          inc = -inc;
        case ICON_CA_PROG_INC:
          n = (read_numeric_value(blk[3], ICON_CA_PROG_VAL) + inc) & 127;
          icon_text_change(itoa(n), blk[3], ICON_CA_PROG_VAL);
          ro.save_patch = (ro.save_patch & ~(0xff << 16)) | (n << 16);
          break;

        case ICON_CA_BANK_DEC:
          inc = -inc;
        case ICON_CA_BANK_INC:
          n = (read_numeric_value(blk[3], ICON_CA_BANK_VAL) + inc) & 127;
          icon_text_change(itoa(n), blk[3], ICON_CA_BANK_VAL);
          ro.save_patch = (ro.save_patch & ~(0xff << 8)) | (n << 8);
          break;

        case ICON_CA_CREATE:
          b[0] = ro.caret.window;
          b[1] = ro.caret.icon;
          b[6] = '\r';
          key_press(b);
          synth_command(SYN_SAVE_INST + (CREATE_NEW << 8), ro.save_patch);
          if(blk[2] == MOUSE_SELECT)
            open_menu(0, 0, ro.cur_menu, -1);
          break;

        case ICON_CA_COPY:
          b[0] = ro.caret.window;
          b[1] = ro.caret.icon;
          b[6] = '\r';
          key_press(b);
          synth_command(SYN_SAVE_INST + (COPY_INSTR << 8), ro.save_patch);
          if(blk[2] == MOUSE_SELECT)
            open_menu(0, 0, ro.cur_menu, -1);
          break;

        case ICON_CA_MOVE:
          if(ro.patch_valid)
          {
            b[0] = ro.caret.window;
            b[1] = ro.caret.icon;
            b[6] = '\r';
            key_press(b);
            synth_command(SYN_SAVE_INST + (MOVE_INSTR << 8), ro.save_patch);
          }
          else
            report_error("Source patch is invalid",0);
          if(blk[2] == MOUSE_SELECT)
            open_menu(0, 0, ro.cur_menu, -1);
          break;
      }
  }

  // Editor windows
  else if(edt_mouse_click(blk, state));

  // Control window
  else if(ctrl_mouse_click(blk, state));

  // Waveform editor window
  else if(wave_mouse_click(blk, state));
}


/*
 * key_press
 * ---------
 */
void key_press(int *blk)
{
  _kernel_swi_regs regs;
  regs.r[1] = (int)blk;
  char *p = "";
  int n = 0;
  int key = blk[6];

  if((key == 0x18e) || (key == 0x18f) || (key == 0x18a) || (key == 0x19a)) // Down, Up, Tab, Shift+Tab
    if((blk[0] != ro.handle[WIN_WAVEFORMS]) || (blk[1] != ICON_WED_WAV_VAL)) // to avoid accidentally loosing an edit
      key = '\r';

  _kernel_swi(Wimp_GetIconState, &regs, &regs);
  if((blk[6] & 0x101) == 0x101) // indirected text
  {
    p = (char *)(blk[7]);
    n = atoi(p);
  }

  ro.caret.window = blk[0];
  ro.caret.icon = blk[1];

  // file save
  if(blk[0] == ro.handle[WIN_SAVE])
  {
    if((key == '\r') && (blk[1] == ICON_FILE_NAME))
    {
      save_file(blk[0], 0); // save to default directory
      open_menu(0, 0, ro.cur_menu, -1);
    }
  }

  // create instrument
  else if(blk[0] == ro.handle[WIN_CREATE])
  {
    if(key == '\r')
      switch(blk[1])
      {
        case ICON_CA_PROG_VAL: // program
          LIMIT(0, n, 127);
          ro.save_patch = (ro.save_patch & ~(0xff << 16)) | (n << 16);
          icon_text_change(itoa(n), blk[0], blk[1]);
          break;

        case ICON_CA_BANK_VAL: // bank
          LIMIT(0, n, 127);
          ro.save_patch = (ro.save_patch & ~(0xff << 8)) | (n << 8);
          icon_text_change(itoa(n), blk[0], blk[1]);
          break;
      }
  }

  // editor
  else if(edt_key_press(blk, key, p));

  // control
  else if(ctrl_key_press(blk, key, p));

  // waveforms
  else if(wave_key_press(blk, key, p));

  else
  {
    regs.r[0] = key;
    _kernel_swi(Wimp_ProcessKey, &regs, &regs);
  }
}


/*
 * lose_caret
 * ----------
 */
static void lose_caret(int blk[])
{
  ro.caret.window = -1;
  ro.caret.icon = -1;

  if((blk[0] != ro.handle[WIN_SAVE]) &&
     (blk[0] != ro.handle[WIN_CREATE]))
  {
    blk[6] = '\r';
    key_press(blk);
  }
}


/*
 * gain_caret
 * ----------
 */
static void gain_caret(int blk[])
{
  ro.caret.window = blk[0];
  ro.caret.icon = blk[1];
}


/*
 * menu_selection
 * --------------
 */
static void menu_selection(int blk[])
{
  int b[7];
  _kernel_swi_regs regs;
  char s[128];

  regs.r[1] = (int)b;
  _kernel_swi(Wimp_GetPointerInfo, &regs, &regs);

  switch(ro.cur_menu)
  {
    case MENU_ICONBAR:
      switch(blk[0])
      {
        case 1: // control
          update_control_window();
          open_window(ro.handle[WIN_SYN_CONTROL]);
          break;

        case 2: // edit
          switch(blk[1])
          {
            case 0: // edit instruments
              open_editor();
              break;

            case 1: // edit waveforms
              update_waveforms_window();
              open_window(ro.handle[WIN_WAVEFORMS]);
              break;
          }
          break;

        case 3: // Open default directory
          sprintf(s, "Filer_OpenDir <MidiSynthChoices$Dir> %d %d", b[0], b[1]);
          _kernel_oscli(s);
          break;

        case 4: // help
          _kernel_oscli("Filer_Run "RESOURCES_DIR".Docs.guide/htm");
          break;

        case 5: // quit
          ro.flags |= (1<<QUIT);
          break;
      }
      if(b[2] == 1) // adjust
        open_menu(0, 0, ro.cur_menu, (int)&iconbar_menu);
      break;

    case MENU_EDITOR:
      switch(blk[0])
      {
        case 0: // Instrument
          switch(blk[1])
          {
            case 1: // Delete
              switch(blk[2])
              {
                case 0: // Clear Bank Ref
//                  memset(ro.user, 0, sizeof(ins_t));
                  synth_command(SYN_DELETE, 1);
                  display_user();
                  break;

                case 1: // Delete Instrument & all Refs
//                  memset(ro.user, 0, sizeof(ins_t));
                  synth_command(SYN_DELETE, 0);
                  display_user();
                  break;
              }
              break;
          }
          break;

        case 1: // Sound Set
          switch(blk[1])
          {
            case 0: // Save SoundSet to default directory with default file name (Quick Save)
              if(ro.caret.window == ro.handle[WIN_EDT])
              {
                b[0] = ro.caret.window;
                b[1] = ro.caret.icon;
                b[6] = '\r';
                key_press(b);
              }
              synth_command(SYN_SAVE_FULL, (int)"<MidiSynthChoices$Dir>.SoundSet");
              icon_text_change(TimestampToString(ro.soundset_date), ro.handle[WIN_EDT], ICON_ED_SSET_DATE);
              break;

            case 1: // Load Default
              synth_command(SYN_LOAD_DEF, 0);
              display_user();
              break;
          }
          break;

        case 2: // Open default directory
          sprintf(s, "Filer_OpenDir <MidiSynthChoices$Dir> %d %d", b[0], b[1]);
          _kernel_oscli(s);
          break;

        case 3: // help
          _kernel_oscli("Filer_Run "RESOURCES_DIR".Docs.guide/htm");
          break;

        case 4: // quit
          ro.flags |= (1<<QUIT);
          break;
      }
      if(b[2] == 1) // adjust
        open_menu(0, 0, ro.cur_menu, (int)&editor_menu);
      break;

    case MENU_CONTROL:
      switch(blk[0])
      {
        case 0: // help
          _kernel_oscli("Filer_Run "RESOURCES_DIR".Docs.guide/htm");
          break;

        case 1: // quit
          ro.flags |= (1<<QUIT);
          break;
      }
      if(b[2] == 1) // adjust
        open_menu(0, 0, ro.cur_menu, (int)&control_menu);
      break;

    case MENU_RATE:
      regs.r[0] = 3; // set current rate
      regs.r[1] = blk[0] + ro.rate_start;
      _kernel_swi(Sound_SampleRate, &regs, &regs);
      rate_menu.item[regs.r[1] - ro.rate_start].item_flags &= ~IT_TICKED;
      rate_menu.item[blk[0]].item_flags |= IT_TICKED;

      if(b[2] == 1) // adjust
        open_menu(0, 0, ro.cur_menu, (int)&rate_menu);
      break;

    case MENU_WAVEFORM:
      switch(blk[0])
      {
        case 0: // test
          {
            int state;
            if(blk[1] != -1)
                state = (test_menu.item[blk[1]].item_flags ^= IT_TICKED) & IT_TICKED;
            switch(blk[1])
            {
              case 0: // oscillator 1
                send_edit_wave(EDIT_WAVE_OSC1, state);
                if(state)
                {
                  icon_text_change("Test", ro.handle[WIN_ED_WAVE1], ICON_WAV_NUM_NAME);
                  icon_text_change("", ro.handle[WIN_ED_WAVE1], ICON_WAV_NUM_VAL);
                }
                else
                {
                  int n = ro.user->wave[0].number;
                  icon_text_change(ro.harm[n].name, ro.handle[WIN_ED_WAVE1], ICON_WAV_NUM_NAME);
                  icon_text_change(itoa(n), ro.handle[WIN_ED_WAVE1], ICON_WAV_NUM_VAL);
                }
                break;

              case 1: // oscillator 2
                send_edit_wave(EDIT_WAVE_OSC2, state);
                if(state)
                {
                  icon_text_change("Test", ro.handle[WIN_ED_WAVE2], ICON_WAV_NUM_NAME);
                  icon_text_change("", ro.handle[WIN_ED_WAVE2], ICON_WAV_NUM_VAL);
                }
                else
                {
                  int n = ro.user->wave[1].number;
                  icon_text_change(ro.harm[n].name, ro.handle[WIN_ED_WAVE2], ICON_WAV_NUM_NAME);
                  icon_text_change(itoa(n), ro.handle[WIN_ED_WAVE2], ICON_WAV_NUM_VAL);
                }
                break;

              case 2: // modulator
                send_edit_wave(EDIT_WAVE_MOD, state);
                if(state)
                {
                  icon_text_change("Test", ro.handle[WIN_ED_GENERAL], ICON_MOD_NUM_NAME);
                  icon_text_change("", ro.handle[WIN_ED_GENERAL], ICON_MOD_NUM_VAL);
                }
                else
                {
                  int n = ro.user->mod_wave;
                  icon_text_change(ro.harm[n].name, ro.handle[WIN_ED_GENERAL], ICON_MOD_NUM_NAME);
                  icon_text_change(itoa(n), ro.handle[WIN_ED_GENERAL], ICON_MOD_NUM_VAL);
                }
                break;
            }
          }
          break;

        case 1: // delete
          save_wave(ro.cur_wav, NULL);
          break;

        case 2: // help
          _kernel_oscli("Filer_Run "RESOURCES_DIR".Docs.guide/htm");
          break;

        case 3: // quit
          ro.flags |= (1<<QUIT);
          break;
       }
      if(b[2] == 1) // adjust
        open_menu(0, 0, ro.cur_menu, (int)&waveform_menu);
      break;
  }
}


/*
 * msg_help
 * --------
 * Supplies help text. Requires the HelpRequest message block and current menu tag.
 */
static void msg_help(int *b, int cur_menu)
{
  _kernel_swi_regs regs;
  int blk[10], ok = 0;

  if(b[8] == ro.handle[WIN_INFO])  // Info window
    ok = msg_lookup("info_wh", -1, (char *)&b[5]);
  else if(b[8] == ro.handle[WIN_EDT])  // Instrument Editor window
  {
    if((ok = msg_lookup("editor_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("editor_wh", -1, (char *)&b[5]);
  }
  else if((b[8] == ro.handle[WIN_ED_WAVE1]) || (b[8] == ro.handle[WIN_ED_WAVE2]))  // Editor, wave1/2 windows
  {
    if((ok = msg_lookup("wave_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("wave_wh", -1, (char *)&b[5]);
  }
  else if(b[8] == ro.handle[WIN_ED_FILTER])  // Editor, filter window
  {
    if((ok = msg_lookup("filter_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("filter_wh", -1, (char *)&b[5]);
  }
  else if(b[8] == ro.handle[WIN_ED_NOISE])  // Editor, noise window
  {
    if((ok = msg_lookup("noise_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("noise_wh", -1, (char *)&b[5]);
  }
  else if(b[8] == ro.handle[WIN_ED_GENERAL])  // Editor, general window
  {
    if((ok = msg_lookup("general_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("general_wh", -1, (char *)&b[5]);
  }
  else if(b[8] == ro.handle[WIN_SYN_CONTROL])  // Synthesiser Control window
  {
    if((ok = msg_lookup("control_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("control_wh", -1, (char *)&b[5]);
  }
  else if(b[8] == ro.handle[WIN_SAVE])  // Save file window
  {
    if((ok = msg_lookup("save_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("save_wh", -1, (char *)&b[5]);
  }
  else if(b[8] == ro.handle[WIN_CREATE])  // Create Instrument window
  {
    if((ok = msg_lookup("create_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("create_wh", -1, (char *)&b[5]);
  }
  else if(b[8] == ro.handle[WIN_WAVEFORMS])  // Waveform Editor window
  {
    if((b[9] >= ICON_WED_AMP_BACK) &&
       (b[9] <= (ICON_WED_AMP_BACK + 63)))
      b[9] = ICON_WED_AMP_BACK;
    if((ok = msg_lookup("waveform_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("waveform_wh", -1, (char *)&b[5]);
  }

  // unknown window, check for a menu
  else if(b[8] != -2)
  {
    regs.r[0] = 1;
    regs.r[1] = (int)blk;
    regs.r[2] = b[8]; // window handle
    regs.r[3] = b[9]; // icon handle
    _kernel_swi(Wimp_GetMenuState, &regs, &regs);
    switch(cur_menu)
    {
      case MENU_ICONBAR:
        ok = msg_lookup("iconbar_mh", blk[0], (char *)&b[5]);
        if(ok && (blk[1] != -1))
        {
          if(blk[0] == 2)
            ok = msg_lookup("edit_sub_mh", blk[1], (char *)&b[5]);
        }
        break;

      case MENU_EDITOR:
        if((blk[0] == 0) && (editor_menu.item[0].icon_flags & IC_SHADED))
          ok = msg_lookup("editor_mhg", -1, (char *)&b[5]);
        else
          ok = msg_lookup("editor_mh", blk[0], (char *)&b[5]);
        if(ok && (blk[1] != -1))
        {
          if(blk[0] == 0)
          {
            ok = msg_lookup("instrument_mh", blk[1], (char *)&b[5]);
            if(ok && (blk[2] != -1))
              ok = msg_lookup("delete_mh", blk[2], (char *)&b[5]);
          }
          else if(blk[0] == 1)
            ok = msg_lookup("soundset_mh", blk[1], (char *)&b[5]);
        }
        break;

      case MENU_WAVEFORM:
        ok = msg_lookup("waveform_mh", blk[0], (char *)&b[5]);
        if(ok && (blk[1] != -1))
        {
          if(blk[0] == 0)
            ok = msg_lookup("test_mh", blk[1], (char *)&b[5]);
        }
        break;
    }
  }

  if(!ok)
    return; // no help available

  // send help text to Help application
  regs.r[0] = 17;     // User message
  regs.r[1] = (int)b;
  regs.r[2] = b[1];   // sender task handle
  b[0] = 256; // buffer length
  b[3] = b[2];
  b[4] = MESSAGE_HELPREPLY;
  _kernel_swi(Wimp_SendMessage, &regs, &regs);
}


/*
 * wimp_msg
 * --------
 * Handles all messages
 */
static void wimp_msg(int blk[], int msg)
{
  _kernel_swi_regs regs;

  switch (blk[4]) // message action
  {
    case MESSAGE_QUIT:
      ro.flags |= (1<<QUIT);
      break;

    case MESSAGE_DATALOAD: // file dropped on window or Iconbar icon
      switch (blk[10])
      {
        case DATA: // load sound set
          synth_command(SYN_LOAD_SET, (int)&blk[11]);
          display_user();
          break;

        case CSV: // load csv file
          synth_command(SYN_LOAD_CSV, (int)&blk[11]);
          display_user();
          break;

        default: return;
      }

      if(msg == 18)
      {
        regs.r[0] = 19;                // user message acknowledge
        regs.r[1] = (int)blk;
        regs.r[2] = blk[1];            // task handle of sender
        blk[3] = blk[2];               // my ref
        blk[4] = MESSAGE_DATALOADACK;  // action
        _kernel_swi(Wimp_SendMessage, &regs, &regs);
      }
      break;

    case MESSAGE_DATASAVEACK: // end of drag, save sound set
      if(ro.flags & (1<<DRAG_ACTIVE))
      {
        save_file(ro.handle[WIN_SAVE], (char *)&blk[11]);
        ro.flags &= ~(1<<DRAG_ACTIVE);
      }
      break;

    case MESSAGE_HELPREQUEST:
      msg_help(blk, ro.cur_menu);
      break;

    case 12345: // sent from the synth when system parameters have been changed externally
      if(msg == 17)
      {
        icon_text_change(itoa(blk[5]), ro.handle[WIN_SYN_CONTROL], ICON_SAMPLE_RATE);
        icon_text_change(itoa(blk[6] + 1), ro.handle[WIN_SYN_CONTROL], ICON_CUR_DRUM_CHAN);
        slider_display_value(blk[7], SLDR_VOLUME, WIN_SYN_CONTROL, ICON_VOLUME_SLDR);
        slider_display_value(blk[8], SLDR_BALANCE, WIN_SYN_CONTROL, ICON_BALANCE_SLDR);
        icon_text_change(itoa(blk[9]), ro.handle[WIN_SYN_CONTROL], ICON_COR_TUNE_VAL);
        ro.master_coarse = blk[9];
        slider_display_value(blk[10], SLDR_FINE, WIN_SYN_CONTROL, ICON_FIN_TUNE_SLDR);
        ro.master_fine = blk[10];
      }
      break;
  }
}


/*
 * loadtemplates
 * -------------
 * returns 0 if ok or error number
 */
static int loadtemplates(char * name)
{
  const struct template_s
  {
    const char *name;
    int spr; // true if sprites required
  } tmpl[NUM_WINDOWS] =
  { {"Info",0},
    {"Editor",1},
    {"Ed_wave1",1},
    {"Ed_wave2",1},
    {"Ed_filter",1},
    {"Ed_noise",1},
    {"Ed_general",1},
    {"Syn_Control",1},
    {"Save",0},
    {"Create",0},
    {"Waveform",1}
  };
  _kernel_swi_regs regs;
  int *sprites, i, err = 0;

  regs.r[0] = READ_CATINFO;
  regs.r[1] = (int)name;
  _kernel_swi(OS_FILE,&regs,&regs);
  if(regs.r[0] != IS_FILE)
    err = 10; // Cannot open templates file
  else
  {
    regs.r[1] = (int)name;
    _kernel_swi(Wimp_OpenTemplate, &regs, &regs);

    if((sprites = loadsprites(RESOURCES_DIR".Sprites")) == 0)
      err = 11; // Cannot open Sprites file
    else
      for(i=0; i<NUM_WINDOWS; i++)
        if((ro.handle[i] = loadtemplate(tmpl[i].name, (tmpl[i].spr) ? sprites : 0)) == 0)
        {
          err = 12 + i; // Cannot find template
          break;
        }
    _kernel_swi(Wimp_CloseTemplate, &regs, &regs);
  }

  return err;
}


/*
 * terminate_app
 * -------------
 */
static void terminate_app(void)
{
  _kernel_swi_regs regs;

  if(ro.flags & (1<<MESSAGES_OPEN))
  {
    if(ro.flags & (1<<TEMPLATES_OK))
    {
    }
    msg_close();
  }

  if(ro.task_handle)
  {
    regs.r[0] = ro.task_handle;
    regs.r[1] = *(int *)"TASK";
    _kernel_swi(Wimp_CloseDown, &regs, &regs);
  }
}


/*
 * main
 * ----
 * Main Wimp Poll loop.
 */
int main(int argc, char *argv[])
{
  _kernel_swi_regs regs;
  int blk[64];
  int msglist[] = {0};
  int err = 0;

  // exit if we are already running
  regs.r[0] = 0;
  while(regs.r[0] >= 0)
  {
    regs.r[1] = (int)blk;
    regs.r[2] = 4*sizeof(int);
    _kernel_swi(TaskManager_EnumerateTasks, &regs, &regs);
    if(strcmp((char *)blk[1], APP_NAME) == 0)
      return 0;
  }

  // exit if the synth module is not running
  if(synth_command(SYN_NULL, 0))
  {
    report_error("Synth module not running", 1);
    return 0;
  }

  // read command line options
  int i = 0;
  while(++i < argc)
    if(argv[i][0] == '-') // option
      switch (argv[i][1])
      {
        case 'c': // centralise windows on startup
          ro.flags |= (1<<CENTRALISE);
          break;
      }

  regs.r[0] = 350;
  regs.r[1] = *(int *)"TASK";
  regs.r[2] = (int)APP_NAME;
  regs.r[3] = (int)msglist;
  _kernel_swi(Wimp_Initialise, &regs, &regs);
  ro.task_handle = regs.r[1];

  /* create iconbar icon */
  blk[0] = -1; // r/h icon
  blk[1] = 0;
  blk[2] = 0;
  blk[3] = 68;
  blk[4] = 68;
  blk[5] = 0x0000301a;
  strcpy((char *)&blk[6], "!"APP_SPRITE);
  regs.r[0] = 0;
  regs.r[1] = (int)blk;
  _kernel_swi(Wimp_CreateIcon, &regs, &regs);

  atexit(terminate_app);

  if(!msg_open(RESOURCES_DIR".Messages"))
    report_error("Cannot open messages file", 1);
  else
  {
    ro.flags |= (1<<MESSAGES_OPEN);
    read_vdu_vars();

    if((err = loadtemplates(RESOURCES_DIR".Templates")) != 0)
      report_error_number(err, 1);
    else
    {
      ro.flags |= (1<<TEMPLATES_OK);
      read_menus();
      display_user();
      ro.cur_pane = ro.handle[WIN_ED_GENERAL];
      icon_state_change(1, ro.handle[WIN_EDT], ICON_ED_GENERAL);
      icon_state_change(1, ro.handle[WIN_SAVE], ICON_SS_DATA);
      update_control_window();

      while (!(ro.flags & (1<<QUIT)))
      {
        _kernel_swi(OS_ReadMonotonicTime, &regs, &regs);
        regs.r[2] = regs.r[0] + 4;
        regs.r[0] = 0; // poll mask
        regs.r[1] = (int)blk;
        _kernel_swi(Wimp_PollIdle, &regs, &regs);
        switch (regs.r[0]) // result
        {
          case 0: // null reason
            slider_control(blk); // dragging sliders
            display_active_gens();
            break;

          case 1: // redraw window
            break;

          case 2: // open window
            if ((blk[0] == ro.handle[WIN_EDT]) && (ro.cur_pane != -1))
            {
              blk[0] = ro.cur_pane;
              _kernel_swi(Wimp_OpenWindow, &regs, &regs);
              blk[0] = ro.handle[WIN_EDT];
              blk[7] = ro.cur_pane;
            }
            _kernel_swi(Wimp_OpenWindow, &regs, &regs);
            break;

          case 3: //close window
            if (blk[0] == ro.handle[WIN_EDT])
              close_editor();
            else
              _kernel_swi(Wimp_CloseWindow, &regs, &regs);
            break;

          case 6: mouse_click(blk); break;
          case 7: drag_return(); break;
          case 8: key_press(blk); break;
          case 9: menu_selection(blk); break;
          case 11: lose_caret(blk); break;
          case 12: gain_caret(blk); break;
          case 17:
          case 18:
          case 19: wimp_msg(blk, regs.r[0]); break;
        }
      }
    }
  }

  return 0;
}

