Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
wisp
GitHub Repository: wisp/impinj-reader-app
Path: blob/master/Sanford.Multimedia.Midi/UI/PianoControl.cs
180 views
#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: [email protected]
 */

#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;
                    keys[n].Width = blackKeyWidth;
                    keys[n].Location = new Point(offset + (w - 1) * whiteKeyWidth);
                    keys[n].BringToFront();
                    n++;
                }
            }
        }

        public void Send(ChannelMessage message)
        {
            if(message.Command == ChannelCommand.NoteOn &&
                message.Data1 >= LowNoteID && message.Data1 <= HighNoteID)
            {
                if(InvokeRequired)
                {
                    BeginInvoke(noteOnCallback, message);
                }
                else
                {
                    noteOnCallback(message);
                }
            }
            else if(message.Command == ChannelCommand.NoteOff &&
                message.Data1 >= LowNoteID && message.Data1 <= HighNoteID)
            {
                if(InvokeRequired)
                {
                    BeginInvoke(noteOffCallback, message);
                }
                else
                {
                    noteOffCallback(message);
                }
            }
        }

        public void PressPianoKey(int noteID)
        {
            #region Require

            if(noteID < lowNoteID || noteID > highNoteID)
            {
                throw new ArgumentOutOfRangeException();
            }

            #endregion

            keys[noteID - lowNoteID].PressPianoKey();
        }

        public void ReleasePianoKey(int noteID)
        {
            #region Require

            if(noteID < lowNoteID || noteID > highNoteID)
            {
                throw new ArgumentOutOfRangeException();
            }

            #endregion

            keys[noteID - lowNoteID].ReleasePianoKey();
        }

        public void PressPianoKey(Keys k)
        {
            if(!Focused)
            {
                return;
            }

            if(keyTable.Contains(k))
            {
                int noteID = (int)keyTable[k] + 12 * octaveOffset;

                if(noteID >= LowNoteID && noteID <= HighNoteID)
                {
                    if(!keys[noteID - lowNoteID].IsPianoKeyPressed)
                    {
                        keys[noteID - lowNoteID].PressPianoKey();
                    }
                }
            }
            else
            {
                if(k == Keys.D0)
                {
                    octaveOffset = 0;
                }
                else if(k == Keys.D1)
                {
                    octaveOffset = 1;
                }
                else if(k == Keys.D2)
                {
                    octaveOffset = 2;
                }
                else if(k == Keys.D3)
                {
                    octaveOffset = 3;
                }
                else if(k == Keys.D4)
                {
                    octaveOffset = 4;
                }
                else if(k == Keys.D5)
                {
                    octaveOffset = 5;
                }
                else if(k == Keys.D6)
                {
                    octaveOffset = 6;
                }
                else if(k == Keys.D7)
                {
                    octaveOffset = 7;
                }
                else if(k == Keys.D8)
                {
                    octaveOffset = 8;
                }
                else if(k == Keys.D9)
                {
                    octaveOffset = 9;
                }
            }
        }

        public void ReleasePianoKey(Keys k)
        {
            #region Guard

            if(!keyTable.Contains(k))
            {
                return;
            }

            #endregion            

            int noteID = (int)keyTable[k] + 12 * octaveOffset;

            if(noteID >= LowNoteID && noteID <= HighNoteID)
            {
                keys[noteID - lowNoteID].ReleasePianoKey();
            }
        }

        protected override void OnResize(EventArgs e)
        {
            InitializePianoKeys();

            base.OnResize(e);
        }

        protected override void Dispose(bool disposing)
        {
            if(disposing)
            {
                foreach(PianoKey key in keys)
                {
                    key.Dispose();
                }
            }

            base.Dispose(disposing);
        }

        protected virtual void OnPianoKeyDown(PianoKeyEventArgs e)
        {
            EventHandler<PianoKeyEventArgs> handler = PianoKeyDown;

            if(handler != null)
            {
                handler(this, e);
            }
        }

        protected virtual void OnPianoKeyUp(PianoKeyEventArgs e)
        {
            EventHandler<PianoKeyEventArgs> handler = PianoKeyUp;

            if(handler != null)
            {
                handler(this, e);
            }
        }

        public int LowNoteID
        {
            get
            {
                return lowNoteID;
            }
            set
            {
                #region Require

                if(value < 0 || value > ShortMessage.DataMaxValue)
                {
                    throw new ArgumentOutOfRangeException("LowNoteID", value,
                        "Low note ID out of range.");
                }

                #endregion

                #region Guard

                if(value == lowNoteID)
                {
                    return;
                }

                #endregion

                lowNoteID = value;

                if(lowNoteID > highNoteID)
                {
                    highNoteID = lowNoteID;
                }

                CreatePianoKeys();
                InitializePianoKeys();
            }
        }

        public int HighNoteID
        {
            get
            {
                return highNoteID;
            }
            set
            {
                #region Require

                if(value < 0 || value > ShortMessage.DataMaxValue)
                {
                    throw new ArgumentOutOfRangeException("HighNoteID", value,
                        "High note ID out of range.");
                }

                #endregion

                #region Guard

                if(value == highNoteID)
                {
                    return;
                }

                #endregion

                highNoteID = value;

                if(highNoteID < lowNoteID)
                {
                    lowNoteID = highNoteID;
                }

                CreatePianoKeys();
                InitializePianoKeys();
            }
        }

        public Color NoteOnColor
        {
            get
            {
                return noteOnColor;
            }
            set
            {
                #region Guard

                if(value == noteOnColor)
                {
                    return;
                }

                #endregion

                noteOnColor = value;

                foreach(PianoKey key in keys)
                {
                    key.NoteOnColor = noteOnColor;
                }
            }
        }
    }
}