/*
  !MidiKey
  Provides a keyboard note entry and nonitors channel activity.

  keyboard.c - Keyboard display functions
  created  28/02/2021
*/

#include "wimp.h"
#include "lib.h"
#include "main.h"
#include "midi_spec.h"
#include "kbd_dsplay.h"

/* DEBUG_TIMING:
 This is to provide a gate pulse on gpio pin 21 when a key is pressed and released.
 Used as a scope trigger. */
//#define DEBUG_TIMING // comment out to disable
#define GPIO_WriteData    0x58f81
#define GPIO_WriteOE      0x58f83

// Synth SWI number
#define MIDISynth_Note    0xCA342

midi_t midi;

vdu_t vdu;

// assembler desktop screen plotting functions in plot.s
typedef void(*plot_fn_t)(int*);
static plot_fn_t fn_plot;
enum{PLOT_256, PLOT_4K_BGR, PLOT_4K_RGB, 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]; // plot.s
#define FRAC 16 // fractional bits in scale factor (frame x,y magnification)
static void plot_null(int *blk){}


// 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. The height is as small as possible but
//       large enough to get the relative black note to white note height 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 so G# sits centrally on a gap

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

#define WINDOW_HEIGHT 100 // Height of keyboard on screen, black note height is 100*3/5 = 60.
#define KBD_TOP        68 // Position of keyboard top from work area top, in pixels.
#define KBD_LEFT       10 // Position of keyboard left from work area left, in pixels.

// 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.

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

#define BLACK      Colour(  0,  0,  0)
#define DARK_GREY  Colour( 64, 64, 64)
#define GREY       Colour(128,128,128)
#define LIGHT_GREY Colour(192,192,192)
#define WHITE      Colour(255,255,255)

// key highlight colour, per channel plus an additional "channel" for when the keyboard
// is driving the editor.
enum{BLK_KEY,WHT_KEY}; // hlt_colour 1st index
static const unsigned int hlt_colour[2][NUM_MIDI_CHANS+1] =
{ // 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),
   LIGHT_GREY},
  // 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),
   DARK_GREY}
};

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


/*
 * 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 = midi.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
  gap (KBD_IMG, i+WHT_WIDTH, 0, GREY);
  gap (KBD_KEY, i+WHT_WIDTH, 0, y);
}



/*
 * 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;

  _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)
{
  _kernel_swi_regs r;
  if(action & 2)
  {
    int i = (key >> 5) & 3;
    unsigned int k = 1 << (key & 31);
    int cmd = (action & 1) ? NOTE_ON : NOTE_OFF;
    if(action & 1)
      midi.keys[CUR][i] |= k; // key down
    else
      midi.keys[CUR][i] &= ~k; // key up
    if(midi.flags & (1 << KBD_EDITOR))
    {
      r.r[0] = cmd | (key << 8) | (midi.out.velocity << 16);
      _kernel_swi(MIDISynth_Note, &r, &r);
    }
    else
      midi_out_port(cmd | midi.out.channel, key, midi.out.velocity, 3);

#ifdef DEBUG_TIMING
    // provide scope trigger for latency timing tests
    _kernel_swi_regs regs;
    regs.r[0] = 21;
    regs.r[1] = action & 1;
    _kernel_swi(GPIO_WriteData, &regs, &regs);
#endif
  }

  if((key < midi.bottom_key) || (key > (midi.bottom_key + (12 * NUM_OCTAVES))))
    return FALSE;

  if(chan > NUM_MIDI_CHANS)
    chan = NUM_MIDI_CHANS;
  key -= midi.bottom_key;
  int o = (key / 12) * OCTAVE_WIDTH;
  int n = key % 12;
  int w, b;
  if(action & 1)
  {
    w = hlt_colour[WHT_KEY][chan];
    b = hlt_colour[BLK_KEY][chan];
  }
  else
  {
    w = WHITE;
    b = BLACK;
  }

  if(midi.flags & (1 << KEY_TIMER))
  {
    if(action & 1)
    { // start highlight timeout
      key_timer[key] = KEY_DISPLAY_TIME/10;
      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, array size to suit Wimp_UpdateWindow
  #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 // last C
  {
    br[1] = o;
    wht0(KBD_IMG, br[1], w);
    br[3] = br[1]+WW;
    br[2] = WH;
  }

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

  update_kbd_window(br);

  return TRUE;
}


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

  if(!(midi.flags & (1 << KEY_TIMER)))
    return;

  int chan = (midi.flags & (1 << KBD_EDITOR)) ? NUM_MIDI_CHANS : midi.out.channel;

  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+midi.bottom_key, 4, chan);
      }
    }
}


/*
 * 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 = (x >> vdu.xeig) - KBD_LEFT;
  y = (y >> vdu.yeig) - KBD_TOP;
  if((x < 0) || (x >= KBD_WIDTH) || (y < 0) || (y >= WINDOW_HEIGHT))
    return FALSE;

  int key = kbd_key[((y * KBD_HEIGHT) / WINDOW_HEIGHT) * KBD_WIDTH + x];

  static int prev_key;
  int chan = (midi.flags & (1 << KBD_EDITOR)) ? NUM_MIDI_CHANS : midi.out.channel;

  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(midi.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 keyboard image on the screen.
 * Scales x and y as required.
 * Requires block from GetRectangle.
 * note. This function only redraws the keyboard image.
 */
void plot_frame(int *b)
{
  int x, y, w, blk[10];

  // restrict rectangle to within the keyboard image
  x = b[1] - b[5] + (KBD_LEFT<<vdu.xeig); // left
  if(b[7] < x)
    b[7] = x;
  x += KBD_WIDTH<<vdu.xeig; // right
  if(b[9] > x)
    b[9] = x;
  y = b[4] - b[6] - (KBD_TOP<<vdu.yeig); // top
  if(b[10] > y)
    b[10] = y;
  y -= WINDOW_HEIGHT<<vdu.yeig; // bottom
  if(b[8] < y)
    b[8] = y;

  x=(b[7]>>vdu.xeig)<<(vdu.log2bpp-3); // dst left offset in bytes
  y=vdu.height-(b[10]>>vdu.yeig);      // dst top offset in bytes
  w=vdu.width<<(vdu.log2bpp-3);        // display width in bytes

  blk[0]=vdu.xmag;                     // x scale factor
  blk[1]=vdu.ymag;                     // y scale factor
  blk[2]=((b[7]-b[1]+b[5])>>vdu.xeig) - KBD_LEFT; // dst rectangle x offset in pixels
  blk[3]=((b[4]-b[10]-b[6])>>vdu.yeig) - KBD_TOP; // dst rectangle y offset in pixels
  blk[4]=(b[9]-b[7])>>vdu.xeig;        // dst rectangle width in pixels
  blk[5]=(b[10]-b[8])>>vdu.yeig;       // dst rectangle height in pixels
  blk[6]=(int)vdu.start + w*y + x;     // dst rectangle 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);
}


/*
 * 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];
      }
}


/*
 * read_vdu_vars
 * -------------
 * get current screen parameters and select the screen
 * plotting function for the current screen mode.
 */
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++;

  unsigned int n = vdu.Ncolour;
  int log2col = 0;
  if((n & (n + 1)) == 0) // check that n = (power of 2) - 1
    while(n)
    {
      n >>= 1;
      log2col++;
    }
  n = (vdu.log2bpp << 24) | (log2col << 16) | (vdu.modeFlags & 0xf280); // combine variables
  switch(n)
  {
    case 0x03060000: // C64
    case 0x03080080: // C256
    case 0x03080280: fn_plot =  plot[PLOT_256]; make_rgb2gcol_table(); break; // G256
    case 0x040c0000: fn_plot =  plot[PLOT_4K_BGR]; break; // C4K
    case 0x040c4000: fn_plot =  plot[PLOT_4K_RGB]; break; // C4K LTRGB
    case 0x04100000: fn_plot =  plot[PLOT_32K_BGR]; break; // C32K
    case 0x04104000: fn_plot =  plot[PLOT_32K_RGB]; break; // C32K LTRGB
    case 0x04100080: fn_plot =  plot[PLOT_64K_BGR]; break; // C64K
    case 0x04104080: fn_plot =  plot[PLOT_64K_RGB]; break; // C64K LTRGB
    case 0x05200000: fn_plot =  plot[PLOT_16M_BGR]; break; // C16M
    case 0x05204000: fn_plot =  plot[PLOT_16M_RGB]; break; // C16M LTRGB

    default: fn_plot = &plot_null; ro.flags |= (1<<QUIT); break; // Unsupported formats
  }
}


/*
 * set_bottom_key
 * --------------
 */
void set_bottom_key(void)
{
  int new_bottom = (midi.shift + 2) * 12;

  if(new_bottom == midi.bottom_key)
    return;

  _kernel_swi_regs regs;
  int b[10];
  int shift = ((new_bottom - midi.bottom_key) / 12) * (OCTAVE_WIDTH << vdu.xeig);
  midi.bottom_key = new_bottom;

  regs.r[1] = (int)b;
  b[0] = ro.handle[WIN_KBD];
  b[1] = ICON_MIDDLE_C;
  _kernel_swi(Wimp_GetIconState, &regs, &regs);
  regs.r[0] = b[0];
  regs.r[1] = b[1];
  regs.r[2] = b[2] - shift;
  regs.r[3] = b[3];
  regs.r[4] = b[4] - shift;
  regs.r[5] = b[5];
  _kernel_swi(Wimp_ResizeIcon, &regs, &regs);
  regs.r[0] = b[0];
  regs.r[1] = b[2];
  regs.r[2] = b[3];
  regs.r[3] = b[4];
  regs.r[4] = b[5];
  _kernel_swi(Wimp_ForceRedraw, &regs, &regs);

  clear_kbd_display();
}


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

  midi.flags =
    (0 << KEY_TIMER) |
    (0 << KBD_EDITOR) |
    (0xfdff << MONITOR_CHAN); // enable monitor of all melodic channels
  midi.out.channel = 0;
  midi.out.velocity = 127;
  midi.out.bank_hi = 0;
  midi.out.bank_lo = 0;
  midi.out.program = 0;
  midi.bottom_key = BOTTOM_KEY;
  midi.shift = 0;

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

#ifdef DEBUG_TIMING
  // testing, setup gpio pin to provide output for timing tests
  _kernel_swi_regs regs;
  regs.r[0] = 21;
  regs.r[1] = 0; // output
  _kernel_swi(GPIO_WriteOE, &regs, &regs);
#endif

  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

  int chan = (midi.flags & (1 << KBD_EDITOR)) ? NUM_MIDI_CHANS : midi.out.channel;

  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, chan);
      }
    }
    else if(keys[j] & k)
    {
      keys[j] &= ~k; // key released
      highlight_key(note, 2, chan);
    }
  }
  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(midi.flags & (1 << (channel + MONITOR_CHAN)))
    highlight_key(pitch, state, channel);
}


/*
 * all_notes_off
 * -------------
 * Turns off highlight for all notes on a channel
 */
void all_notes_off(int channel)
{
  int colw = hlt_colour[WHT_KEY][channel];
  int colb = hlt_colour[BLK_KEY][channel];
  int colk, x, key;
  for(key = midi.bottom_key, x = 7; key <= (midi.bottom_key+(NUM_OCTAVES*12)); key++, x += 14)
  {
    colk = kbd_img[x];
    if((colk == colw) || (colk == colb))
      highlight_key(key, 0, channel);
  }
}

