/*

  kbd_display.c - Keyboard display functions

  created  28/02/2021

*/

#include "main.h"
#include "kbd.h"
#include "wimp.h"
#include "lib.h"
#include "ro_main.h"
#include "midisyn.h"
#include "kbd_dsplay.h"

// screen (vdu) parameters
typedef struct
{
  int modeFlags;
  int Ncolour;
  int xeig;
  int yeig;
  int log2bpp;
  int width;      // pixels
  int height;
  char *start;    // top left address
  int eol;        // marker for end of vdu parameter list
  int xmag;       // frame x magnification, Q16
  int ymag;       // frame y magnification, Q16
}
  vdu_t;
static vdu_t vdu;

// assembler desktop screen plotting functions in synth.s
typedef void(*plot_fn_t)(int*);
static plot_fn_t fn_plot;
enum{PLOT_256, PLOT_32K_BGR, PLOT_32K_RGB, PLOT_64K_BGR, PLOT_64K_RGB,
     PLOT_16M_BGR, PLOT_16M_RGB, NUM_PLOTS};
plot_fn_t plot[NUM_PLOTS]; // synth.s
#define FRAC 16 // fractional bits in scale factor (frame x,y magnification)


// The following sizes are of the internal bitmaps, the image is stretched to the
// window size when plotted. There is an additional 1 pixel gap between each white note, coloured grey.
// Co-ordinates are top left (0,0)
// note. The width is actual size to get the note sizes correct but the height is
//       as small as possible but large enough to get the relative black note to
//       white note size ratio correct
#define WHT_WIDTH   23 // pixel width of white notes
#define KBD_HEIGHT   5 // pixel height of white notes (keyboard height)
#define BLK_HEIGHT   3 // pixel height of black notes
#define BLK_WIDTH   15 // pixel width of black notes, should be an odd number to sit centrally on the gap

#define OCTAVE_WIDTH ((WHT_WIDTH + 1)* 7) // pixel width of an octave (168)
#define KBD_WIDTH    (NUM_OCTAVES * OCTAVE_WIDTH + WHT_WIDTH)  // (1031)

#define WINDOW_HEIGHT 100 // height of keyboard on screen, black note height is 100*3/5 = 60

// black key offsets from central positions in pixels.
#define OS1  2 // C#(-ve),D#(+ve)
#define OS2  3 // F#(-ve),A#(+ve)

// key positions within an octave starting at C, leftmost pixel.
#define W1 0                        // white 1, C
#define G1 WHT_WIDTH                // grey gap
#define B1 (G1 - BLK_WIDTH/2 - OS1) // black 1, C#
#define W2 (G1 + 1)                 // white 2, D
#define G2 (W2 + WHT_WIDTH)         // grey gap
#define B2 (G2 - BLK_WIDTH/2 + OS1) // black 2, D#
#define W3 (G2 + 1)                 // white 3, E
#define G3 (W3 + WHT_WIDTH)         // grey gap
#define W4 (G3 + 1)                 // white 4, F
#define G4 (W4 + WHT_WIDTH)         // grey gap
#define B3 (G4 - BLK_WIDTH/2 - OS2) // black 3, F#
#define W5 (G4 + 1)                 // white 5, G
#define G5 (W5 + WHT_WIDTH)         // grey gap
#define B4 (G5 - BLK_WIDTH/2)       // black 4, G#
#define W6 (G5 + 1)                 // white 6, A
#define G6 (W6 + WHT_WIDTH)         // grey gap
#define B5 (G6 - BLK_WIDTH/2 + OS2) // black 5, A#
#define W7 (G6 + 1)                 // white 7, B
#define G7 (W7 + WHT_WIDTH)         // grey gap

// white key parts visible beside black keys, leftmost and rightmost pixels relative to key positions
#define W1R (WHT_WIDTH - BLK_WIDTH/2 - 1 - OS1) // C
#define W2L (BLK_WIDTH/2 - OS1)                 // D
#define W2R (WHT_WIDTH - BLK_WIDTH/2 - 1 + OS1) // D
#define W3L (BLK_WIDTH/2 + OS1)                 // E
#define W4R (WHT_WIDTH - BLK_WIDTH/2 - 1 - OS2) // F
#define W5L (BLK_WIDTH/2 - OS2)                 // G
#define W5R (WHT_WIDTH - BLK_WIDTH/2 - 1)       // G
#define W6L (BLK_WIDTH/2)                       // A
#define W6R (WHT_WIDTH - BLK_WIDTH/2 - 1 + OS2) // A
#define W7L (BLK_WIDTH/2 + OS2)                 // B

enum{KBD_KEY,KBD_IMG}; // used to select the following 2 arrays
static unsigned int  kbd_img[KBD_WIDTH * KBD_HEIGHT]; // bitmap, copied to window.
static unsigned char kbd_key[KBD_WIDTH * KBD_HEIGHT]; // identical to above but data is note number, not colour.
static int kbd_player_select = 0;
static int kbd_chan_select = 0; // 0 = display non percussion channels, 1 = display percussion channels

#define Colour(r,g,b) ((r<<16)+(g<<8)+b)

#define BLACK      Colour( 0,   0,  0)
#define WHITE      Colour(255,255,255)
#define GREY       Colour(128,128,128)

// key highlight colour, per channel
enum{BLK_KEY,WHT_KEY}; // hlt_colour 1st index
static const unsigned int hlt_colour[2][NUM_MIDI_CHANS] =
{ // black key
  {Colour(255,160,160),Colour(255,207,137),Colour(255,234,143),Colour(255,255,147),
   Colour(211,255, 87),Colour(162,255,133),Colour(113,255,198),Colour(118,254,251),
   Colour(121,223,255),Colour(143,194,255),Colour(133,153,255),Colour(161,139,255),
   Colour(196,144,254),Colour(234,155,255),Colour(255,147,196),Colour(254,156,156)},
  // white key
  {Colour(255,  0,  0),Colour(255,150,  0),Colour(255,204,  0),Colour(244,238,  0),
   Colour(173,234,  0),Colour( 62,238,  0),Colour(  0,229,175),Colour(  0,225,220),
   Colour(  0,190,250),Colour( 63,150,255),Colour( 83,112,255),Colour(116, 83,255),
   Colour(169, 92,254),Colour(220, 91,255),Colour(255, 91,165),Colour(254, 94, 94)}
};


#define KEY_DISPLAY_TIME 100 // ms, keys are highlighted for a minimum of this period.
#define NUM_NOTES ((12*NUM_OCTAVES)+1) // number of keyboard display notes
static int key_timer[NUM_NOTES];
static unsigned char key_down[NUM_NOTES];

void reset_sleep_timer(void); // ro_audio.c


/*
 * rectangle
 * ---------
 * co-ordinates are x1,y1 = top-left and x2,y2 = bottom-right (inclusive)
 */
static void rectangle(int dst, int x1, int y1, int x2, int y2, int data)
{
  int x, y;

  if(dst == KBD_IMG)
    for(y=y1; y<=y2; y++)
      for(x=x1; x<=x2; x++)
        kbd_img[y * KBD_WIDTH + x] = data;
  else
    for(y=y1; y<=y2; y++)
      for(x=x1; x<=x2; x++)
        kbd_key[y * KBD_WIDTH + x] = data;
}


/*
 * Individual key drawing functions
 * --------------------------------
 * wht0() to wht7(), blk(), gap()
 */
static void wht0(int dst, int x, int data) // last C
{
  rectangle(dst, x, 0, x + WHT_WIDTH-1, KBD_HEIGHT-1, data);
}

static void wht1(int dst, int x, int data) // C
{
  rectangle(dst, x, 0, x + W1R, BLK_HEIGHT-1, data);
  rectangle(dst, x, BLK_HEIGHT, x + WHT_WIDTH-1, KBD_HEIGHT-1, data);
}

static void wht2(int dst, int x, int data) // D
{
  rectangle(dst, x + W2L, 0, x + W2R, BLK_HEIGHT-1, data);
  rectangle(dst, x, BLK_HEIGHT, x + WHT_WIDTH-1, KBD_HEIGHT-1, data);
}

static void wht3(int dst, int x, int data) // E
{
  rectangle(dst, x + W3L, 0, x + WHT_WIDTH-1, KBD_HEIGHT-1, data);
  rectangle(dst, x, BLK_HEIGHT, x + WHT_WIDTH-1, KBD_HEIGHT-1, data);
}

static void wht4(int dst, int x, int data) // F
{
  rectangle(dst, x, 0, x + W4R, BLK_HEIGHT-1, data);
  rectangle(dst, x, BLK_HEIGHT, x + WHT_WIDTH-1, KBD_HEIGHT-1, data);
}

static void wht5(int dst, int x, int data) // G
{
  rectangle(dst, x + W5L, 0, x + W5R, BLK_HEIGHT-1, data);
  rectangle(dst, x, BLK_HEIGHT, x + WHT_WIDTH-1, KBD_HEIGHT-1, data);
}

static void wht6(int dst, int x, int data) // A
{
  rectangle(dst, x + W6L, 0, x + W6R, BLK_HEIGHT-1, data);
  rectangle(dst, x, BLK_HEIGHT, x + WHT_WIDTH-1, KBD_HEIGHT-1, data);
}

static void wht7(int dst, int x, int data) // B
{
  rectangle(dst, x + W7L, 0, x + WHT_WIDTH-1, KBD_HEIGHT-1, data);
  rectangle(dst, x, BLK_HEIGHT, x + WHT_WIDTH-1, KBD_HEIGHT-1, data);
}

static void blk(int dst, int x, int data) // blacks
{
  rectangle(dst, x, 0, x + BLK_WIDTH-1, BLK_HEIGHT-1, data);
}

static void gap(int dst, int x, int y, int data) // grey gaps
{
  if(dst == KBD_IMG)
    for(; y<KBD_HEIGHT; y++)
      kbd_img[y * KBD_WIDTH + x] = data;
  else
    for(; y<KBD_HEIGHT; y++)
      kbd_key[y * KBD_WIDTH + x] = data;
}


/*
 * init_img
 * --------
 * Initialises the bitmap image with the keyboard.
 * Initialises the note number image with the note numbers.
 */
void init_img(void)
{
  int i, y = BOTTOM_KEY;

  for(i=0; i<(NUM_OCTAVES * OCTAVE_WIDTH); i+=OCTAVE_WIDTH)
  {
    // keyboard image
    wht1(KBD_IMG, i+W1, WHITE); // C
    gap (KBD_IMG, i+G1, BLK_HEIGHT, GREY);
    blk (KBD_IMG, i+B1, BLACK); // C#
    wht2(KBD_IMG, i+W2, WHITE); // D
    gap (KBD_IMG, i+G2, BLK_HEIGHT, GREY);
    blk (KBD_IMG, i+B2, BLACK); // D#
    wht3(KBD_IMG, i+W3, WHITE); // E
    gap (KBD_IMG, i+G3, 0, GREY);
    wht4(KBD_IMG, i+W4, WHITE); // F
    gap (KBD_IMG, i+G4, BLK_HEIGHT, GREY);
    blk (KBD_IMG, i+B3, BLACK); // F#
    wht5(KBD_IMG, i+W5, WHITE); // G
    gap (KBD_IMG, i+G5, BLK_HEIGHT, GREY);
    blk (KBD_IMG, i+B4, BLACK); // G#
    wht6(KBD_IMG, i+W6, WHITE); // A
    gap (KBD_IMG, i+G6, BLK_HEIGHT, GREY);
    blk (KBD_IMG, i+B5, BLACK); // A#
    wht7(KBD_IMG, i+W7, WHITE); // B
    gap (KBD_IMG, i+G7, 0, GREY);
    // note number image
    wht1(KBD_KEY, i+W1, y);     // C
    gap (KBD_KEY, i+G1, BLK_HEIGHT, y++);
    blk (KBD_KEY, i+B1, y++);   // C#
    wht2(KBD_KEY, i+W2, y);     // D
    gap (KBD_KEY, i+G2, BLK_HEIGHT, y++);
    blk (KBD_KEY, i+B2, y++);   // D#
    wht3(KBD_KEY, i+W3, y);     // E
    gap (KBD_KEY, i+G3, 0, y++);
    wht4(KBD_KEY, i+W4, y);     // F
    gap (KBD_KEY, i+G4, BLK_HEIGHT, y++);
    blk (KBD_KEY, i+B3, y++);   // F#
    wht5(KBD_KEY, i+W5, y);     // G
    gap (KBD_KEY, i+G5, BLK_HEIGHT, y++);
    blk (KBD_KEY, i+B4, y++);   // G#
    wht6(KBD_KEY, i+W6, y);     // A
    gap (KBD_KEY, i+G6, BLK_HEIGHT, y++);
    blk (KBD_KEY, i+B5, y++);   // A#
    wht7(KBD_KEY, i+W7, y);     // B
    gap (KBD_KEY, i+G7, 0, y++);
  }
  wht0(KBD_IMG, i, WHITE); // last C
  wht0(KBD_KEY, i, y);     // last C
}



/*
 * update_kbd_window
 * ---------------------
 * Copy the frame to the window under the control of the wimp.
 * Requires the window co-ordinates of the rectangle to update.
 */
static void update_kbd_window(int *blk)
{
  _kernel_swi_regs regs;
  regs.r[1] = (int)blk;
//  myprintf("%d %d %d %d\n", blk[1], blk[2], blk[3], blk[4]);

  _kernel_swi(Wimp_UpdateWindow, &regs, &regs);
  while (regs.r[0])
  {
    plot_frame(blk);
    regs.r[1] = (int)blk;
    _kernel_swi(Wimp_GetRectangle, &regs, &regs);
  }
}


/*
 * highlight_key
 * -------------
 * action:
 *   bit 0: 0 = highlight off, 1 = highlight on
 *   bit 1: bit 0 also controls key up/down
 *   bit 2: key up, highlight timeout
 *
 * Returns true if updated
 */
int highlight_key(int key, int action, int chan)
{
  if(midiPlayer_status() & MP_RECORDING)
    return FALSE;

  reset_sleep_timer();

  if(action & 2)
  {
    int i = (key >> 5) & 3;
    unsigned int k = 1 << (key & 31);
    if(action & 1)
      syn.keys[CUR][i] |= k; // key down
    else
      syn.keys[CUR][i] &= ~k; // key up
  }

  if((key < BOTTOM_KEY) || (key > (BOTTOM_KEY + (12 * NUM_OCTAVES))))
    return FALSE;

  chan &= NUM_MIDI_CHANS - 1;
  key -= BOTTOM_KEY;
  int o = (key / 12) * OCTAVE_WIDTH;
  int n = key % 12;
  int w = (action & 1) ? hlt_colour[WHT_KEY][chan] : WHITE;
  int b = (action & 1) ? hlt_colour[BLK_KEY][chan] : BLACK;

  if(action & 1)
  { // start highlight timeout
    key_timer[key] = (syn.sample_rate*KEY_DISPLAY_TIME)/1000;
    key_down[key] = 1;
  }
  else if((action & 4) == 0)
  {
    key_down[key] = 0;
    return FALSE; // do no more here, it will be picked up by key_highlight_timer()
  }

  int br[11]; // bounding rectangle
  #define WH WINDOW_HEIGHT
  #define BH ((WINDOW_HEIGHT*BLK_HEIGHT)/KBD_HEIGHT)+1
  #define WW WHT_WIDTH
  #define BW BLK_WIDTH

  if(key < (12 * NUM_OCTAVES))
    switch(n)
    {
      case  0: br[1] = o+W1; wht1(KBD_IMG, br[1], w); br[3] = br[1]+WW; br[2] = WH; break; // C
      case  1: br[1] = o+B1; blk (KBD_IMG, br[1], b); br[3] = br[1]+BW; br[2] = BH; break; // C#
      case  2: br[1] = o+W2; wht2(KBD_IMG, br[1], w); br[3] = br[1]+WW; br[2] = WH; break; // D
      case  3: br[1] = o+B2; blk (KBD_IMG, br[1], b); br[3] = br[1]+BW; br[2] = BH; break; // D#
      case  4: br[1] = o+W3; wht3(KBD_IMG, br[1], w); br[3] = br[1]+WW; br[2] = WH; break; // E
      case  5: br[1] = o+W4; wht4(KBD_IMG, br[1], w); br[3] = br[1]+WW; br[2] = WH; break; // F
      case  6: br[1] = o+B3; blk (KBD_IMG, br[1], b); br[3] = br[1]+BW; br[2] = BH; break; // F#
      case  7: br[1] = o+W5; wht5(KBD_IMG, br[1], w); br[3] = br[1]+WW; br[2] = WH; break; // G
      case  8: br[1] = o+B4; blk (KBD_IMG, br[1], b); br[3] = br[1]+BW; br[2] = BH; break; // G#
      case  9: br[1] = o+W6; wht6(KBD_IMG, br[1], w); br[3] = br[1]+WW; br[2] = WH; break; // A
      case 10: br[1] = o+B5; blk (KBD_IMG, br[1], b); br[3] = br[1]+BW; br[2] = BH; break; // A#
      case 11: br[1] = o+W7; wht7(KBD_IMG, br[1], w); br[3] = br[1]+WW; br[2] = WH; break; // B
    }
  else
  {
    br[1] = o; wht0(KBD_IMG, br[1], w); br[3] = br[1]+WW; br[2] = WH; // last C
  }

  br[0] = ro.handle[WIN_KBD];
  br[1] <<= vdu.xeig;
  br[2] = -(br[2] << vdu.yeig);
  br[3] <<= vdu.xeig;
  br[4] = 0;

  update_kbd_window(br);

  return TRUE;
}


/*
 * key_highlight_timer
 * -------------------
 */
void key_highlight_timer(void)
{
  int i;

  for(i=0; i<=(12*NUM_OCTAVES); i++)
    if(key_timer[i])
    {
      if(key_timer[i] > 1)
        key_timer[i]--;

      if((key_timer[i] == 1) && !key_down[i])
      { // highlight timeout
        key_timer[i] = 0;
        highlight_key(i+BOTTOM_KEY, 4, syn.m_out.channel);
      }
    }
}


/*
 * KeyEntry
 * --------
 * x and y are os window co-ordinates
 * Returns true if highlight action has happened
 */
int KeyEntry(int msg, int x, int y, int buttons)
{
  x >>= vdu.xeig;
  y >>= vdu.yeig;
  int key = kbd_key[((y * KBD_HEIGHT) / WINDOW_HEIGHT) * KBD_WIDTH + x];

  static int prev_key;
  int chan = syn.m_out.channel;

//  myprintf("k %d, m %d, b %d\n", key, msg, buttons);

  switch(msg)
  {
    case LBUTTONDOWN:
    case RBUTTONDOWN:
      highlight_key(key, 3, chan);
      break;

    case LBUTTONUP:
      highlight_key(key, 2, chan);
      break;

    case MOUSEMOVE:
      if(prev_key != key)
      {
        if(buttons)
        {
          highlight_key(key, 3, chan);
          if(buttons & MOUSE_SELECT)
            highlight_key(prev_key, 2, chan);
        }
        else if(syn.keys[CUR][(key >> 5) & 3] & (1 << (key & 31)))
          highlight_key(key, 2, chan);
        else
        {
          prev_key = key;
          return FALSE;
        }
      }
      else
        return FALSE;
      break;

    default:
      return FALSE;
  }

  prev_key = key;

  return TRUE;
}


/*
 * plot_frame
 * ----------
 * Plots the required rectangle of the frame on the screen.
 * Scales x and y as required.
 * Requires block from GetRectangle
 */
void plot_frame(int *b)
{
  int x, y, w, blk[10];

  w=vdu.width<<(vdu.log2bpp-3);
  x=(b[7]>>vdu.xeig)<<(vdu.log2bpp-3);
  y=vdu.height-(b[10]>>vdu.yeig);

  blk[0]=vdu.xmag;                    // x scale factor
  blk[1]=vdu.ymag;                    // y scale factor
  blk[2]=(b[7]-b[1]+b[5])>>vdu.xeig;  // x offset in pixels
  blk[3]=(b[4]-b[10]-b[6])>>vdu.yeig; // y offset in pixels
  blk[4]=(b[9]-b[7])>>vdu.xeig;       // dst width in pixels
  blk[5]=(b[10]-b[8])>>vdu.yeig;      // dst height in pixels
  blk[6]=(int)vdu.start + w*y + x;    // dst top left address
  blk[7]=w;                           // screen width in bytes
  blk[8]=(int)kbd_img;                // sprite start address
  blk[9]=KBD_WIDTH*4;                 // sprite width in bytes

  if((blk[4]>0)&&(blk[5]>0))
    fn_plot(blk);
}


/*
 * read_vdu_vars
 * -------------
 * get current screen parameters
 */
static void read_vdu_vars(void)
{
  _kernel_swi_regs regs;

  vdu.modeFlags = 0;
  vdu.Ncolour = 3;
  vdu.xeig = 4;
  vdu.yeig = 5;
  vdu.log2bpp = 9;
  vdu.width = 11;
  vdu.height = 12;
  vdu.start = (char *)149;
  vdu.eol = -1;
  regs.r[0] = (int)&vdu;
  regs.r[1] = (int)&vdu;
  _kernel_swi(OS_ReadVduVariables, &regs, &regs);
  vdu.width++;
  vdu.height++;
  vdu.Ncolour++;

  if(vdu.log2bpp == 4)
  {
    if((vdu.modeFlags & 0xf280) == 0x4080)
      vdu.Ncolour = (1<<16);
    else
      vdu.Ncolour = (1<<15);
  }
}


/*
 * make_rgb2gcol_table
 * -------------------
 */
unsigned char rgb2gcol[16*16*16]; // used by assembler 8bpp renderer
static void make_rgb2gcol_table(void)
{
  _kernel_swi_regs regs;
  int r, g, b;
  unsigned char *table = rgb2gcol;

  // make rgb444 to 8bpp colour table for rendering function
  for (b = 0; b < 16; b++)
    for (g = 0; g < 16; g++)
      for (r = 0; r < 16; r++)
      {
        regs.r[0] = ((b*17)<<24) | ((g*17)<<16) | ((r*17)<<8);
        _kernel_swi(ColourTrans_ReturnColourNumber, &regs, &regs);
        *table++ = regs.r[0];
      }
}


/*
 * init_kbd_display
 * ----------------
 */
int init_kbd_display(void)
{
  read_vdu_vars();

//  _kernel_swi_regs regs;
//  int blk[11];
//  blk[0] = ro.handle[WIN_KBD];
//  regs.r[1] = (int)blk;
//  _kernel_swi(Wimp_GetWindowState, &regs, &regs);
//  int window_height = (blk[4] - blk[2] - blk[6]) >> vdu.yeig;
//  int window_width =  (blk[3] - blk[1] + blk[5]) >> vdu.xeig;
//  myprintf("height %d width %d\n", window_height, window_width);
//  vdu.ymag = ((1 << FRAC) * KBD_HEIGHT) / window_height; // magnify kbd bitmap height to window height
//  vdu.xmag = ((1 << FRAC) * KBD_WIDTH) / window_width; // (width mag should be x1, both widths 1031)

  vdu.ymag = ((1 << FRAC) * KBD_HEIGHT) / WINDOW_HEIGHT; // magnify kbd bitmap height to window height
  vdu.xmag = (1 << FRAC); // width needs no adjustment

  // select the screen plotting function for the current screen mode
  if(vdu.log2bpp == 3)
  {
    fn_plot = plot[PLOT_256];
    make_rgb2gcol_table();
  }
  else if(vdu.log2bpp == 4)
  {
    if(vdu.Ncolour == (1<<15))
      fn_plot = plot[PLOT_32K_BGR];
    else
      fn_plot = plot[PLOT_64K_RGB];
  }
  else if(vdu.log2bpp == 5)
    fn_plot = plot[PLOT_16M_BGR];
  else
    ro.flags |= (1<<QUIT); // Unsupported colour depth

  init_img(); // populate the source keyboard bitmap image

  return 0;
}


/*
 * key_scan
 * --------
 * This function is needed because of the lack of a key up event.
 * start = 1 if a key pressed event called us
 *       = 0 if regular key scanning, following a key pressed event
 * After being started by a key pressed event, scanning continues
 * on a regular basis until all scanned keys are up.
 */
void key_scan(int start)
{
  #define SCAN_LEN 39

  static const unsigned char kbd_keys[SCAN_LEN] = // list of internal key numbers to check
  {94,65,97,81,66,82,67,99,83,100,84,85,101,70,102,86,103,104,
   48,16,49,33,17,34,51,19,35,52,68,53,21,37,38,54,39,55,56,93,88};
  static unsigned int keys[4]; // to detect changes to each keys state
  static int timer, enabled;
  _kernel_swi_regs regs;

  if(start)
    enabled = 1;
  if(!enabled)
    return;
  if(++timer < 10) // no need to scan that often
    return;
  timer = 0;
  int i, all_up = 1;

  // check for shift
  regs.r[0] = 121;
  regs.r[1] = 0x80;
  _kernel_swi(OS_Byte, &regs, &regs);
  int shift = BOTTOM_KEY;
  if(regs.r[1] == 0xff)
    shift += 24; // 2 octaves higher when either SHIFT key is held down

  for(i = SCAN_LEN - 1; i >= 0; i--) // highest note first
  {
    regs.r[1] = kbd_keys[i] ^ 0x80;
    _kernel_swi(OS_Byte, &regs, &regs);
    int note = i + shift;
    int j = (note >> 5) & 3;
    unsigned int k = 1 << (note & 31);
    if(regs.r[1] == 0xff)
    {
      all_up = 0;
      if(!(keys[j] & k))
      {
        keys[j] |= k; // key pressed
        highlight_key(note, 3, syn.m_out.channel);
      }
    }
    else if(keys[j] & k)
    {
      keys[j] &= ~k; // key released
      highlight_key(note, 2, syn.m_out.channel);
    }
  }
  if(all_up)
    enabled = 0; // no more scanning until a key pressed event kicks it off again
}


/*
 * clear_kbd_display
 * -----------------
 * cancels any highlghted keys
 */
void clear_kbd_display(void)
{
  _kernel_swi_regs regs;
  int blk[11];

  blk[0] = ro.handle[WIN_KBD];
  regs.r[1] = (int)blk;
  _kernel_swi(Wimp_GetWindowState, &regs, &regs);
  blk[3] -= blk[1] - blk[5];
  blk[1] = blk[5];
  blk[2] -= blk[4] - blk[6];
  blk[4] = blk[6];

  init_img();
  update_kbd_window(blk);
}


/*
 * highlight_midi_key
 * ------------------
 * highlights keys from the midi input stream
 * state = 0 off/key up, 1 on/key down
 */
void highlight_midi_key(int pitch, int state, int channel)
{
  if(kbd_player_select)
  {
    if((kbd_chan_select == (channel == PERCUSSION_CHAN-1)) && !(midiPlayer_status() & MP_RECORDING))
      highlight_key(pitch, state, channel);
  }
}


/*
 * cmd_keyboard
 * ------------
 * Sets options for the keyboard display
 */
int cmd_keyboard(char *msg, int len)
{
  if(len == 0) // display status
    myprintf("Keyboard, player highlight %s, %s channels\n",
             kbd_player_select ? "enabled" : "disabled",
              kbd_chan_select ? "percussion" : "melodic");

  else if(msg[0] == '?') // display help
    myprintf(" Sets options for the keyboard display.\n"
             "  ,P,<0|1>   enable/disable highlighting player notes\n"
             "  ,C,<0|1>   melodic or percussion channel highlighting from the player\n"
             "  ,H         clear any highlighted keys\n");

  else if(msg[0] == '!') // report for controller
    myprintf("KEYBOARD,%d,%d\n", kbd_player_select, kbd_chan_select);

  else
    switch(msg[1])
    {
      case 'P': case 'p': // player highlighting on/off
        kbd_player_select = msg[3] & 1;
        break;

      case 'C': case 'c': // melodic/percussion channels select
        kbd_chan_select = msg[3] & 1;
        break;

      case 'H': case 'h': // clear highlights
        clear_kbd_display();
        break;

      default:
        return -PARAMETER_ERROR;
    }

  return NO_ERROR_;
}





