context: #region License
  
  /* Copyright (c) 2006 Leslie Sanford
   * 
   * Permission is hereby granted, free of charge, to any person obtaining a copy 
   * of this software and associated documentation files (the "Software"), to 
   * deal in the Software without restriction, including without limitation the 
   * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 
   * sell copies of the Software, and to permit persons to whom the Software is 
   * furnished to do so, subject to the following conditions:
   * 
   * The above copyright notice and this permission notice shall be included in 
   * all copies or substantial portions of the Software. 
   * 
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
   * THE SOFTWARE.
   */
  
  #endregion
  
  #region Contact
  
  /*
   * Leslie Sanford
   * Email: jabberdabber@hotmail.com
   */
  
  #endregion
  
  using System;
  using System.Collections;
  using System.Drawing;
  using System.Threading;
  using System.Windows.Forms;
  
  namespace Sanford.Multimedia.Midi.UI
  {
      public partial class PianoControl : Control
      {
          private enum KeyType
          {
              White,
              Black
          }
  
          private readonly static Hashtable keyTable = new Hashtable();
  
          private static readonly KeyType[] KeyTypeTable = 
              {
                  KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White,
                  KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White,
                  KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White,
                  KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White,
                  KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White,
                  KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White,
                  KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White,
                  KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White,
                  KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White,
                  KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White,
                  KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White
              };
  
          private delegate void NoteMessageCallback(ChannelMessage message);
  
          private const int DefaultLowNoteID = 21;
  
          private const int DefaultHighNoteID = 109;
  
          private const double BlackKeyScale = 0.666666666;
  
          private SynchronizationContext context;
  
          private int lowNoteID = DefaultLowNoteID;
  
          private int highNoteID = DefaultHighNoteID;
  
          private int octaveOffset = 5;
  
          private Color noteOnColor = Color.SkyBlue;
  
          private PianoKey[] keys = null;
  
          private int whiteKeyCount;
  
          private NoteMessageCallback noteOnCallback;
  
          private NoteMessageCallback noteOffCallback;
  
          public event EventHandler<PianoKeyEventArgs> PianoKeyDown;
  
          public event EventHandler<PianoKeyEventArgs> PianoKeyUp;
  
          static PianoControl()
          {
              keyTable.Add(Keys.A, 0);
              keyTable.Add(Keys.W, 1);
              keyTable.Add(Keys.S, 2);
              keyTable.Add(Keys.E, 3);
              keyTable.Add(Keys.D, 4);
              keyTable.Add(Keys.F, 5);
              keyTable.Add(Keys.T, 6);
              keyTable.Add(Keys.G, 7);
              keyTable.Add(Keys.Y, 8);
              keyTable.Add(Keys.H, 9);
              keyTable.Add(Keys.U, 10);
              keyTable.Add(Keys.J, 11);
              keyTable.Add(Keys.K, 12);
              keyTable.Add(Keys.O, 13);
              keyTable.Add(Keys.L, 14);
              keyTable.Add(Keys.P, 15);
          }
  
          public PianoControl()
          {
              CreatePianoKeys();
              InitializePianoKeys();
  
              context = SynchronizationContext.Current;
  
              noteOnCallback = delegate(ChannelMessage message)
              {
                  if(message.Data2 > 0)
                  {
                      keys[message.Data1 - lowNoteID].PressPianoKey();
                  }
                  else
                  {
                      keys[message.Data1 - lowNoteID].ReleasePianoKey();
                  }
              };
  
              noteOffCallback = delegate(ChannelMessage message)
              {
                  keys[message.Data1 - lowNoteID].ReleasePianoKey();
              };
          }
  
          private void CreatePianoKeys()
          {
              // If piano keys have already been created.
              if(keys != null)
              {
                  // Remove and dispose of current piano keys.
                  foreach(PianoKey key in keys)
                  {
                      Controls.Remove(key);
                      key.Dispose();
                  }
              }
  
              keys = new PianoKey[HighNoteID - LowNoteID];
  
              whiteKeyCount = 0;
  
              for(int i = 0; i < keys.Length; i++)
              {
                  keys[i] = new PianoKey(this);
                  keys[i].NoteID = i + LowNoteID;
  
                  if(KeyTypeTable[keys[i].NoteID] == KeyType.White)
                  {
                      whiteKeyCount++;
                  }
                  else
                  {
                      keys[i].NoteOffColor = Color.Black;
                      keys[i].BringToFront();
                  }
  
                  keys[i].NoteOnColor = NoteOnColor;
  
                  Controls.Add(keys[i]);
              }
          }
  
          private void InitializePianoKeys()
          {
              #region Guard
  
              if(keys.Length == 0)
              {
                  return;
              }
  
              #endregion
  
              int whiteKeyWidth = Width / whiteKeyCount;
              int blackKeyWidth = (int)(whiteKeyWidth * BlackKeyScale);
              int blackKeyHeight = (int)(Height * BlackKeyScale);
              int offset = whiteKeyWidth - blackKeyWidth / 2;
              int n = 0;
              int w = 0;
  
              while(n < keys.Length)
              {
                  if(KeyTypeTable[keys[n].NoteID] == KeyType.White)
                  {
                      keys[n].Height = Height;
                      keys[n].Width = whiteKeyWidth;
                      keys[n].Location = new Point(w * whiteKeyWidth, 0);
                      w++;
                      n++;
                  }
                  else
                  {
                      keys[n].Height = blackKeyHeight;
  
gt:                     keys[n].Width = blackKeyWidth;