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