/*
  !MidiPlay   A MIDI synthesiser and file player.

 main.c   riscos main entry

 craeted 23/12/21
*/

#include "main.h"
#include "wimp.h"
#include "lib.h"
#include "ro_main.h"
#include "midisyn.h"
#include "kbd_dsplay.h"
#include "editor.h"
#include "channel.h"


// editor menu
static menu_t editor_menu =
{
  "Editor",NULL,6, 7,2,7,0,0,44,0,
  {
    {0x100,-1,IC_DEF,"Keyboard...", NULL, 11},
    {    0,-1,IC_DEF,"Editor...",   NULL,  9},
    {0x080,-1,IC_DEF,"Help...",     NULL,  7}
  }
};

// interface menu
static menu_t interface_menu =
{
  "Interface",NULL,9, 7,2,7,0,0,44,0,
  {
    {0x100,-1,IC_DEF,"SharedSound",  NULL, 11},
    {0x080,-1,IC_DEF,"DiskSample",   NULL, 10}
  }
};

// synth buffer size menu
static menu_t buffer_menu =
{
  "Buffer Size",NULL,11, 7,2,7,0,0,44,0,
  {
    {0},{0},{0},{0},{0},{0}
  }
};

// synth sample rate menu
static menu_t rates_menu =
{
  "Sample Rate",NULL,11, 7,2,7,0,0,44,0,
  {
    {0},{0},{0},{0},{0}
  }
};

// synth menu
static menu_t synth_menu =
{
  "Synth",NULL,5, 7,2,7,0,0,44,0,
  {
    {0x100,(int)&interface_menu,IC_DEF,"Interface",   NULL,  9},
    {    0,(int)&buffer_menu,IC_DEF,"Buffer Size", NULL, 11},
    {0x080,(int)&rates_menu,IC_DEF,"Sample Rate", NULL, 11}
  }
};

// system menu
static menu_t system_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},
    {0},{0},{0},{0},{0},{0},{0},{0},{0},{0},
    {0},{0},{0},{0},{0},{0},{0},{0},{0},{0}
  }
};

// config menu
static menu_t config_menu =
{
  "Config",NULL,6, 7,2,7,0,0,44,0,
  {
    {0x100,(int)&synth_menu,IC_DEF,"Synth",   NULL,  5},
    {0x080,(int)&system_menu,IC_DEF,"System", NULL,  6}
  }
};

// iconbar menu
static menu_t iconbar_menu =
{
  "MidiPlay",NULL,8, 7,2,7,0,0,44,0,
  {
    {0x100,-1,IC_DEF,"Info",      NULL,  4},
    {    0,-1,IC_DEF,"Player...", NULL,  9},
    {    0,-1,IC_DEF,"Console...",NULL, 10},
    {    0,(int)&editor_menu,IC_DEF,"Editor",    NULL,  6},
    {    0,(int)&config_menu,IC_DEF,"Config",    NULL,  6},
    {    0,-1,IC_DEF,"Help...",   NULL,  7},
    {0x080,-1,IC_DEF,"Quit",      NULL,  4}
    }
};
#define MENU_ICONBAR_ITEMS 7

// controls menu
static menu_t controls_menu =
{
  "Controls",NULL,8, 7,2,7,0,0,44,0,
  {
    {0x100,-1,IC_DEF,"Loop",      NULL,  4},
    {    0,-1,IC_DEF,"Mono",      NULL,  4},
    {    0,-1,IC_DEF,"L/R Swap",  NULL,  8},
    {    0,-1,IC_DEF,"Banks",     NULL,  5},
    {    0,-1,IC_DEF,"Drum Kits", NULL,  9},
    {    0,-1,IC_DEF,"Tempo",     NULL,  5},
    {    0,-1,IC_DEF,"Pitch",     NULL,  5},
    {0x080,-1,IC_DEF,"Reset",     NULL,  5}
  }
};

// player menu
static menu_t player_menu =
{
  "Player",NULL,6, 7,2,7,0,0,44,0,
  {
    {0x100,-1,IC_DEF,"Console...",NULL, 10},
    {    0,-1,IC_DEF,"Effects...",NULL, 10},
    {    0,-1,IC_DEF,"Channels...",NULL,11},
    {    0,(int)&controls_menu,IC_DEF,"Controls",  NULL,  8},
    {    0,-1,IC_DEF,"Save",      NULL,  4},
    {    0,-1,IC_DEF,"Help...",   NULL,  7},
    {0x080,-1,IC_DEF,"Quit",      NULL,  4}
  }
};

// console menu
static menu_t console_menu =
{
  "Console",NULL,7 ,7,2,7,0,0,44,0,
  {
    {0x100,-1,IC_DEF,"Player...", NULL,  9},
    {0x080,-1,IC_DEF,"Clear",     NULL,  5}
  }
};

// keyboard menu
static menu_t keyboard_menu =
{
  "Display",NULL,7, 7,2,7,0,0,44,0,
  {
  {0x100,-1,IC_DEF,"Player",    NULL,  6},
  {0x080,-1,IC_DEF,"Percussion",NULL, 10}
  }
};

ro_glbs_t ro =
{
  0,     // task handle
  {0},   // window handles
  0,     // current menu
  0,     // current editor pane
  0,     // flags
  1,     // start index for system sample rates menu
  0,     // sleep timer
  1000,  // sleep timeout in centiseconds
  &controls_menu,
  &synth_menu,
  &buffer_menu,
  {1024,2048,4096,8192,16384,32768}, // buffer sizes
  &rates_menu,
  {22050,24000,32000,44100,48000}, // sample rates
  &interface_menu,
  &sharedsound, // default audio interface
  {
    0,   // currently selected slider window
    0,   // currently selected slider icon
    { // slider definitions
      // player window
      {  0, 1000, HORIZONTAL |   PROGRESS_BAR}, // SLDR_POSN,     song position
      {  0, 1023, VERTICAL   | SLIDER_CONTROL}, // SLDR_VOLUME,   volume
      {-20,   20, VERTICAL   | SLIDER_CONTROL}, // SLDR_BALANCE,  balance
      {-15,   15, VERTICAL   | SLIDER_CONTROL}, // SLDR_BASS,     bass
      {-15,   15, VERTICAL   | SLIDER_CONTROL}, // SLDR_TREBLE,   treble
      // effects window
      {  0,  100, HORIZONTAL | SLIDER_CONTROL}, // SLDR_E_DELAY,  echo delay (log)
      {  0, 1023, HORIZONTAL | SLIDER_CONTROL}, // SLDR_E_SGAIN,  echo straight gain
      {  0, 1023, HORIZONTAL | SLIDER_CONTROL}, // SLDR_E_CGAIN,  echo cross gain
      {  0,  500, HORIZONTAL | SLIDER_CONTROL}, // SLDR_REVERB,   reverb gain
      {  0,  100, HORIZONTAL | SLIDER_CONTROL}, // SLDR_CHORUS,   chorus rate
      {  0,  100, HORIZONTAL | SLIDER_CONTROL}, // SLDR_FLANGE,   flanger rate (log)
      // editor, 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)

      {  0, METER_RANGE, VERTICAL | PROGRESS_BAR},  // SLDR_METER, level meter
      {  0,16383, HORIZONTAL | SLIDER_CONTROL}  // SLDR_CHN_VOL,  channel volume
    }
  }
};


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

  s = msg_load_menu("iconbar_m",  s, &iconbar_menu);
  s = msg_load_menu("player_m",   s, &player_menu);
  s = msg_load_menu("controls_m", s, &controls_menu);
  s = msg_load_menu("console_m",  s, &console_menu);
  s = msg_load_menu("editor_m",   s, &editor_menu);
  s = msg_load_menu("config_m",   s, &config_menu);
  s = msg_load_menu("keyboard_m", s, &keyboard_menu);
  s = msg_load_menu("synth_m",    s, &synth_menu);
  s = msg_load_menu("buffer_m",   s, &buffer_menu);
  s = create_numeric_menu(buffer_menu.item, NUM_SIZES, ro.buffer_size, s);
  s = msg_load_menu("rate_m",     s, &rates_menu);
  s = create_numeric_menu(rates_menu.item, NUM_RATES, ro.sample_rate, s);
  s = msg_load_menu("system_m",   s, &system_menu);
  s = create_sys_rate_menu(system_menu.item, s);

//  myprintf("menu strings length %d\n", s - menu_strings);

  iconbar_menu.item[0].sub = ro.handle[WIN_INFO];
  player_menu.item[4].sub = ro.handle[WIN_SAVE];
  controls_menu.item[3].sub = ro.handle[WIN_BANKS];
  controls_menu.item[4].sub = ro.handle[WIN_KITS];
  controls_menu.item[5].sub = ro.handle[WIN_TEMPO];
  controls_menu.item[6].sub = ro.handle[WIN_PITCH];
}


/*
 * save_file
 * ---------
 * Initiates saving the loaded midi file as a wav file.
 * Uses the filename in the save dialog and the path if given. If not
 * given, uses the default directory.
 */
void save_file(char *path)
{
  _kernel_swi_regs regs;
  int blk[10];

  blk[0] = ro.handle[WIN_SAVE];
  blk[1] = ICON_WAV_NAME;
  regs.r[1] = (int)blk;
  _kernel_swi(Wimp_GetIconState, &regs, &regs);
  if(!path)
    midiPlayer_cmds("SAVE,"APP_DIR".wav.%s", (char *)(blk[7])); // save to default directory
  else
    midiPlayer_cmds("SAVE,%s%s", path, (char *)(blk[7])); // save to given directory
}


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

  static int delay;
  if(++delay < 10) // no need to action every poll idle
    return;
  delay = 0;

  _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(ro.slider.window == ro.handle[WIN_PLAYER]) // player window
    switch(ro.slider.icon)
    {
      case ICON_VOL_SLDR: midiPlayer_cmds("VOLUME,%d", slider_get_posn(blk, SLDR_VOLUME)); break; // volume
      case ICON_BAL_SLDR: midiPlayer_cmds("BALANCE,%d", slider_get_posn(blk, SLDR_BALANCE)); break; // balance
      case ICON_BAS_SLDR: midiPlayer_cmds("TONE,B,%d", slider_get_posn(blk, SLDR_BASS)); break; // bass
      case ICON_TRB_SLDR: midiPlayer_cmds("TONE,T,%d", slider_get_posn(blk, SLDR_TREBLE)); break; // treble
    }
  else if(ro.slider.window == ro.handle[WIN_EFFECTS]) // effects window
    switch(ro.slider.icon)
    {
      case ICON_E_DELAY_SLDR: midiPlayer_cmds("ECHO,E,%d", trb_to_param(SLDR_E_DELAY, slider_get_posn(blk, SLDR_E_DELAY))); break; // echo delay
      case ICON_E_SGAIN_SLDR: midiPlayer_cmds("ECHO,S,%d", slider_get_posn(blk, SLDR_E_SGAIN)); break; // echo striaight gain
      case ICON_E_CGAIN_SLDR: midiPlayer_cmds("ECHO,C,%d", slider_get_posn(blk, SLDR_E_CGAIN)); break; // echo cross gain
      case ICON_REVERB_SLDR:  midiPlayer_cmds("REVERB,%d", slider_get_posn(blk, SLDR_REVERB)); break; // reverb gain
      case ICON_CHORUS_SLDR:  midiPlayer_cmds("CHORUS,%d", slider_get_posn(blk, SLDR_CHORUS)); break; // chorus rate
      case ICON_FLANGE_SLDR:  midiPlayer_cmds("FLANGER,%d", trb_to_param(SLDR_FLANGE, slider_get_posn(blk, SLDR_FLANGE))); break; // flanger rate
    }
  else if(edt_slider_control(blk));
  else if(chn_slider_control(blk));
}


/*
 * mouse_click
 * -----------
 */
static void mouse_click(int blk[])
{
  _kernel_swi_regs regs;
  int b[10];
  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;
  char action = (state) ? 'I' : 'D'; // Initialise or Disable

  // Iconbar
  if(blk[3] == ICONBAR)
  {
    switch(blk[2])
    {
      case MOUSE_MENU:
        open_menu(blk[0] - 64, (44*MENU_ICONBAR_ITEMS)+96, MENU_ICONBAR, (int)&iconbar_menu);
        break;

      case MOUSE_SELECT: // open player window
        open_window(ro.handle[WIN_PLAYER]);
        break;

      case MOUSE_ADJUST: // open console window
        open_window(ro.handle[WIN_CONSOLE]);
        break;
    }
  }

  // Player window
  else if(blk[3] == ro.handle[WIN_PLAYER])
  {
    if(blk[2] == MOUSE_MENU)
    {
      if(midiPlayer_status() & MP_LOADED)
        player_menu.item[4].icon_flags &= ~IC_SHADED;
      else
        player_menu.item[4].icon_flags |= IC_SHADED;
      open_menu(blk[0], blk[1], MENU_PLAYER, (int)&player_menu);
    }
    else
    {
      switch(blk[4])
      {
        case ICON_POSN_BACK:
          blk[4]++;
        case ICON_POSN_VALUE: // player seek
          if(blk[2] == MOUSE_SELECT)
          {
            ro.slider.window = blk[3];
            ro.slider.icon = blk[4];
            ro.slider.type[SLDR_POSN].hi = 1000;
            midiPlayer_cmds("PLAYER,S,%d", slider_get_posn(blk, SLDR_POSN));
          }
          break;

        case ICON_VOL_BACK:
        case ICON_BAL_BACK:
        case ICON_BAS_BACK:
        case ICON_TRB_BACK:
          blk[4]++;
        case ICON_VOL_SLDR: // volume
        case ICON_BAL_SLDR: // balance
        case ICON_BAS_SLDR: // bass
        case ICON_TRB_SLDR: // treble
          if(blk[2] == MOUSE_SELECT)
          {
            ro.slider.window = blk[3];
            ro.slider.icon = blk[4];
            icon_state_change(1, blk[3], blk[4]);
          }
          break;

        case ICON_START:
          if(midiPlayer_status() & MP_PAUSED)
            midiPlayer_cmds("PLAYER,P");
          else
            midiPlayer_cmds("PLAYER,I");
          break;

        case ICON_PAUSE:
          midiPlayer_cmds("PLAYER,P");
          break;

        case ICON_STOP:
          midiPlayer_cmds("PLAYER,D");
          break;

        default: open_window(ro.handle[WIN_PLAYER]); break; // bring to front
      }
    }
  }

  // Console window
  else if(blk[3] == ro.handle[WIN_CONSOLE])
  {
    if(blk[2] == MOUSE_MENU)
      open_menu(blk[0], blk[1], MENU_CONSOLE, (int)&console_menu);
  }

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

      case ICON_WAV_OK:
        save_file(0); // save to default directory
        break;
    }
  }

  // Effects window
  else if(blk[3] == ro.handle[WIN_EFFECTS])
  {
    if(blk[2] == MOUSE_SELECT)
      switch(blk[4])
      {
        case ICON_ECHO:   midiPlayer_cmds("ECHO,%c",    action); break;
        case ICON_REVERB: midiPlayer_cmds("REVERB,%c",  action); break;
        case ICON_CHORUS: midiPlayer_cmds("CHORUS,%c",  action); break;
        case ICON_FLANGE: midiPlayer_cmds("FLANGER,%c", action); break;

        case ICON_E_DELAY_BACK:
        case ICON_E_SGAIN_BACK:
        case ICON_E_CGAIN_BACK:
        case ICON_REVERB_BACK:
        case ICON_CHORUS_BACK:
        case ICON_FLANGE_BACK:
          blk[4]++;
        case ICON_E_DELAY_SLDR:
        case ICON_E_SGAIN_SLDR:
        case ICON_E_CGAIN_SLDR:
        case ICON_REVERB_SLDR:
        case ICON_CHORUS_SLDR:
        case ICON_FLANGE_SLDR:
          ro.slider.window = blk[3];
          ro.slider.icon = blk[4];
          icon_state_change(1, blk[3], blk[4]);
          break;

        default: open_window(ro.handle[WIN_EFFECTS]); break; // bring to front
      }
  }

  // Bank control window
  else if(blk[3] == ro.handle[WIN_BANKS])
  {
    if(blk[2] == MOUSE_SELECT)
      if(blk[4] == ICON_ALL_BANKS)
        midiPlayer_cmds("PLAYER,B,%d", state);
  }

  // Drum Kit control window
  else if(blk[3] == ro.handle[WIN_KITS])
  {
    if(blk[2] == MOUSE_SELECT)
      if(blk[4] == ICON_ALL_KITS)
        midiPlayer_cmds("PLAYER,K,%d", state);
  }

  // Keyboard window
  else if(blk[3] == ro.handle[WIN_KBD])
  {
    if(blk[2] == MOUSE_MENU)
      open_menu(blk[0], blk[1], MENU_KEYBOARD, (int)&keyboard_menu);
  }

  // Editor windows
  else if(edt_mouse_click(blk, state));
  // Channels window
  else if(chn_mouse_click(blk, state));
}

/*
 * keyboard_entry
 * --------------
 * Processes mouse activity when over the keyboard window
 */
static void keyboard_entry(int *blk)
{
  _kernel_swi_regs regs;

  regs.r[1] = (int)blk;
  _kernel_swi(Wimp_GetPointerInfo, &regs, &regs);
  int x = blk[0];
  int y = blk[1];
  int buttons = blk[2];
  blk[0] = ro.handle[WIN_KBD];
  _kernel_swi(Wimp_GetWindowState, &regs, &regs);
  if((blk[8] & ((1<<16)|(1<<17))) ==  ((1<<16)|(1<<17))) // window open and uncovered
  {
    if((x >= blk[1]) && (x < blk[3]) && (y >= blk[2]) && (y < blk[4])) // pointer within window
    {
      x = x - blk[1];
      y = blk[4] - y - 1;
      static int prev_x, prev_y, prev_but;
      if(!(prev_but & MOUSE_SELECT) && (buttons & MOUSE_SELECT)) // left down
        KeyEntry(LBUTTONDOWN, x, y, buttons);
      else if(!(prev_but & MOUSE_ADJUST) && (buttons & MOUSE_ADJUST)) // right down
        KeyEntry(RBUTTONDOWN, x, y, buttons);
      else if((prev_but & MOUSE_SELECT) && !(buttons & MOUSE_SELECT)) // left up
        KeyEntry(LBUTTONUP, x, y, buttons);
      else if((prev_x != x) || (prev_y != y)) // moved
        KeyEntry(MOUSEMOVE, x, y, buttons);

      prev_x = x;
      prev_y = y;
      prev_but = buttons;
    }
  }

  key_scan(0); // scan the keyboard for keys used as note entry
}


/*
 * key_press
 * ---------
 */
void key_press(int *blk)
{
  _kernel_swi_regs regs;
  int key = blk[6];
  int n;
  regs.r[1] = (int)blk;
  _kernel_swi(Wimp_GetIconState, &regs, &regs);
  char *p = (char *)(blk[7]);

  // console command entry
  if(blk[0] == ro.handle[WIN_CONSOLE])
  {
    if((key == '\r') && (blk[1] == ICON_CMD))
    {
      for(;;)
      {
        n = p - (char*)(blk[7]); // string length
        if((*p < 32) || (n >= (blk[9])-1))
          break;
        p++;
      }
      *p = 0; // ensure string is properly terminated
      if(!n)
        midiPlayer_cmd_in('\r'); // print a newline when return with no command is entered
      else
        midiPlayer_cmds((char*)(blk[7])); // execute command
    }
  }

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

  // tempo
  else if(blk[0] == ro.handle[WIN_TEMPO])
  {
    if((key == '\r') && (blk[1] == ICON_TEMPO))
    {
      n = atoi(p);
      if(n < 10) n = 10;
      else if(n > 1000) n = 1000;
      midiPlayer_cmds("TEMPO,%d", n);
    }
  }

  // pitch
  else if(blk[0] == ro.handle[WIN_PITCH])
  {
    if((key == '\r') && (blk[1] == ICON_PITCH))
    {
      n = atoi(p);
      if(n < -12) n = -12;
      else if(n > 12) n = 12;
      midiPlayer_cmds("PITCH,%d", n);
    }
  }

  // bank control
  else if(blk[0] == ro.handle[WIN_BANKS])
  {
    if((key == '\r') && (blk[1] == ICON_BANK_OVERRIDE))
    {
      n = atoi(p);
      if(n < 0) n = 0;
      else if(n > 127) n = 127;
      midiPlayer_cmds("PLAYER,O,%d", n);
    }
  }

  // drum kit control
  else if(blk[0] == ro.handle[WIN_KITS])
  {
    if((key == '\r') && (blk[1] == ICON_KIT_OVERRIDE))
    {
      n = atoi(p);
      if(n < 0) n = 0;
      else if(n > 127) n = 127;
      midiPlayer_cmds("PLAYER,V,%d", n);
    }
  }

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

  regs.r[0] = key;
}


/*
 * menu_selection
 * --------------
 */
static void menu_selection(int blk[])
{
  int b[5];
  _kernel_swi_regs regs;

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

  switch(ro.cur_menu)
  {
    case MENU_ICONBAR:
      switch(blk[0])
      {
        case 1: // open player window
          open_window(ro.handle[WIN_PLAYER]);
          break;

        case 2: // open console window
          open_window(ro.handle[WIN_CONSOLE]);
          break;

        case 3: // editor
          switch(blk[1])
          {
            case 0: // keyboard
              open_window(ro.handle[WIN_KBD]);
              regs.r[0] = ro.handle[WIN_KBD];
              regs.r[1] = -1;
              regs.r[2] = 0;
              regs.r[3] = 0;
              regs.r[4] = 40 | (1<<25); // invisible
              regs.r[5] = 0;
              _kernel_swi(Wimp_SetCaretPosition, &regs, &regs);
              break;

            case 1: // editor
              open_editor();
              break;

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

        case 4: // config
          switch(blk[1])
          {
            case 0: // synth
              switch(blk[2])
              {
                case 0: // synth audio interface
                  midiPlayer_cmds("INTERFACE,%c", (blk[3] == 0) ? 'S' : 'D');
                  break;

                case 1: // synth buffer size
                  midiPlayer_cmds("BUFFER,%d", ro.buffer_size[blk[3]]);
                  break;

                case 2: // synth sample rate
                  midiPlayer_cmds("RATE,%d", ro.sample_rate[blk[3]]);
                  break;
              }
              break;

            case 1: // system, sample rate
              regs.r[0] = 3; // set current rate
              regs.r[1] = blk[2] + ro.sys_start;
              _kernel_swi(Sound_SampleRate, &regs, &regs);
              system_menu.item[regs.r[1] - ro.sys_start].item_flags &= ~IT_TICKED;
              system_menu.item[blk[2]].item_flags |= IT_TICKED;
              break;
          }
          break;

        case 5: // help (player)
          _kernel_oscli("Filer_Run "RESOURCES_DIR".Docs.player/htm");
          break;

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

    case MENU_PLAYER:
      switch(blk[0])
      {
        case 0: // console
          open_window(ro.handle[WIN_CONSOLE]);
          break;

        case 1: // effects
          open_window(ro.handle[WIN_EFFECTS]);
          break;

        case 2: // channels
          open_window(ro.handle[WIN_CHANNELS]);
          break;

        case 3: // controls
          switch(blk[1])
          {
            case 0: // loop
              midiPlayer_cmds("PLAYER,L,%d", (controls_menu.item[0].item_flags & IT_TICKED) ^ IT_TICKED);
              break;

            case 1: // Stereo/Mono
              midiPlayer_cmds("AUDIO,M,%d", (controls_menu.item[1].item_flags & IT_TICKED) ^ IT_TICKED);
              break;

            case 2: // Normal/L/R swapped
              midiPlayer_cmds("AUDIO,S,%d", (controls_menu.item[2].item_flags & IT_TICKED) ^ IT_TICKED);
              break;

            case 7: // reset
              midiPlayer_cmds("RESET,1");
              break;
          }
          break;

        case 5: // help (player)
          _kernel_oscli("Filer_Run "RESOURCES_DIR".Docs.player/htm");
          break;

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

    case MENU_CONSOLE:
      switch(blk[0])
      {
        case 0: // open player window
          open_window(ro.handle[WIN_PLAYER]);
          break;

        case 1: // clear screen
          midiPlayer_vprintf(0,0);
          break;
      }
      if(b[2] == 1) // adjust
        open_menu(0, 0, ro.cur_menu, (int)&console_menu);
      break;

    case MENU_KEYBOARD:
      switch(blk[0])
      {
        case 0: // Player display on/off
          midiPlayer_cmds("KEYBOARD,P,%d\n"
                          "KEYBOARD,H", (keyboard_menu.item[0].item_flags ^= IT_TICKED) & IT_TICKED);
          break;

        case 1: // Melodic / Percussion display
          midiPlayer_cmds("KEYBOARD,C,%d\n"
                          "KEYBOARD,H", (keyboard_menu.item[1].item_flags ^= IT_TICKED) & IT_TICKED);
          break;
      }
      if(b[2] == 1) // adjust
        open_menu(0, 0, ro.cur_menu, (int)&keyboard_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_PLAYER])  // Player window
  {
    if((ok = msg_lookup("main_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("main_wh", -1, (char *)&b[5]);
  }
  else 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_CONSOLE])  // Console window
  {
    if((ok = msg_lookup("console_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("console_wh", -1, (char *)&b[5]);
  }
  else if(b[8] == ro.handle[WIN_EFFECTS])  // Effects window
  {
    if((ok = msg_lookup("effects_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("effects_wh", -1, (char *)&b[5]);
  }
  else if(b[8] == ro.handle[WIN_KBD])  // Keyboard window
  {
    if((ok = msg_lookup("keyboard_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("keyboard_wh", -1, (char *)&b[5]);
  }
  else if(b[8] == ro.handle[WIN_EDT])  // 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_BANKS])  // Bank control window
  {
    if((ok = msg_lookup("banks_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("banks_wh", -1, (char *)&b[5]);
  }
  else if(b[8] == ro.handle[WIN_KITS])  // Drum Kit window
  {
    if((ok = msg_lookup("kits_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("kits_wh", -1, (char *)&b[5]);
  }
  else if(b[8] == ro.handle[WIN_SAVE])  // Save file window
    ok = msg_lookup("save_wh", b[9], (char *)&b[5]);
  else if(b[8] == ro.handle[WIN_TEMPO])  // Tempo window
    ok = msg_lookup("tempo_wh", -1, (char *)&b[5]);
  else if(b[8] == ro.handle[WIN_PITCH])  // Pitch window
    ok = msg_lookup("pitch_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] == 3)
            ok = msg_lookup("editor_mh", blk[1], (char *)&b[5]);
          else if(blk[0] == 4)
          {
            ok = msg_lookup("config_mh", blk[1], (char *)&b[5]);
            if(ok && (blk[2] != -1))
            {
              if(blk[1] == 0)
              {
                if((blk[2] == 1) && (synth_menu.item[1].icon_flags & IC_SHADED))
                  ok = msg_lookup("synth_mhg", blk[2], (char *)&b[5]);
                else
                  ok = msg_lookup("synth_mh", blk[2], (char *)&b[5]);
                if(ok && (blk[3] != -1))
                {
                  if(blk[2] == 0)
                    ok = msg_lookup("synth_iface_mh", blk[3], (char *)&b[5]);
                  else if(blk[2] == 1)
                    ok = msg_lookup("synth_buff_mh", blk[3], (char *)&b[5]);
                  else if(blk[2] == 2)
                    ok = msg_lookup("synth_rate_mh", blk[3], (char *)&b[5]);
                }
              }
              else if(blk[1] == 1)
                ok = msg_lookup("system_mh", blk[2], (char *)&b[5]);
            }
          }
        }
        break;

      case MENU_PLAYER:
        if((blk[0] == 4) && (player_menu.item[4].icon_flags & IC_SHADED))
          ok = msg_lookup("player_mhg", blk[0], (char *)&b[5]);
        else
          ok = msg_lookup("player_mh", blk[0], (char *)&b[5]);
        if(ok && (blk[1] != -1))
        {
          if(blk[0] == 3)
            ok = msg_lookup("controls_mh", blk[1], (char *)&b[5]);
        }
        break;

      case MENU_CONSOLE:
        ok = msg_lookup("console_mh", blk[0], (char *)&b[5]);
        break;

      case MENU_KEYBOARD:
        ok = msg_lookup("keyboard_mh", blk[0], (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;
  FILE *f;
  char c;

  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 MIDI: // play a MIDI file
          midiPlayer_cmds("LOAD,%s", (char *)&blk[11]);
          break;

        case DATA: // load a DATA sound set
          // synth must be reset because instrument and bank pointers may change
          // so if the player is playing, all instruments will become Acoustic Grand Piano's !
          midiPlayer_cmds("RESTORE,F,%s\n"
                          "RESET,1", (char *)&blk[11]);
          break;

        case TEXT: // execute a TEXT command file
          if((f = fopen((char*)&blk[11], "r")) != 0)
          {
            while(fread(&c, 1, 1, f))
              midiPlayer_cmd_in(c);
            midiPlayer_cmd_in(0);
            fclose(f);
          }
          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 song
      save_file((char *)&blk[11]); // use new path
      break;

    case MESSAGE_MODECHANGE:
      init_kbd_display();
      break;

    case MESSAGE_HELPREQUEST:
      msg_help(blk, ro.cur_menu);
      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] =
  { {"Player",1},
    {"Info",0},
    {"Console",0},
    {"Save",0},
    {"Tempo",0},
    {"Pitch",0},
    {"Effects",1},
    {"Keyboard",0},
    {"Editor",1},
    {"Ed_wave1",1},
    {"Ed_wave2",1},
    {"Ed_filter",1},
    {"Ed_noise",1},
    {"Ed_general",1},
    {"Channels",1},
    {"Banks",0},
    {"Kits",0}
  };
  _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);
  }

  if(!err)
    slider_display_value(0, SLDR_POSN, WIN_PLAYER, ICON_POSN_VALUE);

  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))
    {
      midiPlayer_term();
      ro.audio->term();
    }
    msg_close();
  }

  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 handle = 0;
  int err = 0;
//  int sec_timer = 0;

  // find out if we are already running
  regs.r[0] = 0;
  while((regs.r[0] >= 0) && !handle)
  {
    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)
      handle = blk[0];
  }

  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];

  if(handle)
  {
    if(argc > 1)
    {
      // send midi file to existing instance
      regs.r[0] = 0;
      regs.r[1] = (int)blk;
      _kernel_swi(Wimp_Poll, &regs, &regs);
      regs.r[0] = 17; // User message
      regs.r[1] = (int)blk;
      regs.r[2] = handle;
      blk[0] = sizeof(blk);
      blk[3] = 0;
      blk[4] = MESSAGE_DATALOAD;
      blk[10] = MIDI;
      strncpy((char *)&blk[11], argv[1], sizeof(blk)-44);
      _kernel_swi(Wimp_SendMessage, &regs, &regs);
    }
    regs.r[0] = ro.task_handle;
    regs.r[1] = *(int *)"TASK";
    _kernel_swi(Wimp_CloseDown, &regs, &regs);
    return 0;
  }

  /* 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);
    if((err = loadtemplates(RESOURCES_DIR".Templates")) != 0)
      report_error_number(err, 1);
    else
    {
      ro.flags |= (1<<TEMPLATES_OK);
      read_menus();
      midiPlayer_new();
      ro.flags |= (1<<PLAYER_OK);
      ro.audio->init();
      blk[0] = ro.audio == &disksample;
      midiPlayer_display(ITEM_INTERFACE, blk);
      ro.cur_pane = ro.handle[WIN_ED_GENERAL];
      icon_state_change(1, ro.handle[WIN_EDT], ICON_ED_GENERAL);
//      midi_check_module();

      if(argc > 1)
        midiPlayer_cmds("LOAD,%s", argv[1]);

      while (!(ro.flags & (1<<QUIT)))
      {
        _kernel_swi(OS_ReadMonotonicTime, &regs, &regs);
        regs.r[2] = regs.r[0] + 1;
        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, this should happen no more than 100 times a second
            ro.audio->fill();       // playback through audio output
            midiPlayer_recording(); // saving file as wav
            slider_control(blk);    // dragging sliders
            keyboard_entry(blk);    // mouse activity in keyboard window
//            midi_in_port();         // received midi data
//            if(++sec_timer >= 100)
//            {
//              sec_timer = 0;
//              midi_check_port(); // check once a second for usb midi device plug/unplug
//            }
            break;

          case 1: // redraw window
            if(blk[0] == ro.handle[WIN_KBD])
            {
              _kernel_swi(Wimp_RedrawWindow, &regs, &regs);
              while (regs.r[0])
              {
                plot_frame(blk);
                regs.r[1] = (int)blk;
                _kernel_swi(Wimp_GetRectangle, &regs, &regs);
              }
            }
            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);
            if(blk[0] == ro.handle[WIN_KBD])
            {
              regs.r[0] = ro.handle[WIN_KBD];
              regs.r[1] = -1;
              regs.r[2] = 0;
              regs.r[3] = 0;
              regs.r[4] = 40 | (1<<25); // invisible
              regs.r[5] = 0;
              _kernel_swi(Wimp_SetCaretPosition, &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 17:
          case 18:
          case 19: wimp_msg(blk, regs.r[0]); break;
        }
      }
    }
  }

  return 0;
}

