Introduction
A few months ago, I wrote a Code Project article about a class wrapper library I created for the low-level Windows MIDI API. To demonstrate my library, I created a simple application that included a piano display for playing the notes via the mouse. As it turns out, there was some interest in the piano control itself. Unfortunately, for a variety of reasons, the piano control I had created for that project wasn't suited for general use, so I decided to write the control over completely from scratch so that it can be used in many different contexts.
The CPianoCtrl and CPianoCtrlListener Classes
The CPianoCtrl
class represents a keyboard piano display. It is a custom control derived from the MFC CWnd
class. It displays an interactive keyboard which allows you to play notes with the mouse. By itself, the control doesn't produce any sound. It's up to you to connect a CPianoCtrl
object to a sound source. This is done through the CPianoCtrlListener
class.
The CPianoCtrlListener
is an abstract class. Derived classes can attach themselves to a CPianoCtrl
object. When a note is played or released, the CPianoCtrl
object will notify all of its listeners that a note event has taken place. For example, a CPianoCtrlListener
derived class could respond to an event by playing a MIDI note corresponding to the note played on a CPianoCtrl
object it is registered with.
Keys on a CPianoCtrl
object can be activated directly by calling the NoteOn
and NoteOff
functions. This can be useful when you want to turn on and off notes on the keyboard by a means other than the mouse. For example, you could parse a MIDI file and have the notes on the keyboard play to the notes in the file. Furthermore, since you can change the color of the keys that are being played, each MIDI track could have its own color.
Note Range and Characteristics
The CPianoCtrl
class can have up to 128 keys (0 - 127). The range is set with the Initialize
method or the SetNoteRange
method. You specify the lowest note of the range and the highest note of the range. The lowest note must be less than the highest note and both the lowest note and the highest note must be natural. This is an important point that bears repeating: The lowest note and the highest note on the keyboard must be natural. This limitation has to do with avoiding situations in which a black key is the lowest or highest note. In such cases, there would be a white key fragment leftover. This would not be impossible to deal with. However, it makes things easier and clearer to simply avoid this situation altogether.
Notes are represented by integer values. The number 0 is considered a C note. As you go up from 0, you are ascending the chromatic scale. Therefore, 0 equals C, 1 equals C#, 2 equals D, etc. After you've reached the B note (the first B note is number 11), the scale starts over from C.
A CPianoCtrl
can be displayed in one of three ways: Vertical left, vertical right, and horizontal. Vertical left displays the control vertically with the keys facing to the right, vertical right displays the control vertically with the keys facing left, and horizontal displays the control horizontally. The mode of display is determined by passing a constant to a control's constructor. Those constants are VERTICAL_LEFT
, VERTICAL_RIGHT
, and HORIZONTAL
.
Using the CPianoCtrl Class
To use the CPianoCtrl
class in a dialog based application, perform the following steps:
- In the resource editor, place a custom control onto the dialog box.
- Set the class name in the custom control's property box to match the
CPianoCtrl's
class name.
- Add a
CPianoCtrl
instance variable to the dialog class.
- Add a
DDX_Control
call in the DoDataExchange
method in the dialog class. For example, if your dialog class was named CPianoDlg
, the CPianoCtrl
variable was named m_PianoCtrl
, and the Id for the control was IDC_PIANOCTRL
, you would place the following in the dialog class' DoDataExchange
function:
void CPianoDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_PIANOCTRL, m_PianoCtrl);
}
- In the dialog class'
OnInitDialog
function, initialize the CPianoCtrl
object by calling its initialize
function. Here, you will pass the desired note range and optionally the note-on color.
To use the control dynamically within a dialog box or within a window, perform the following steps:
- Add a
CPianoCtrl
instance variable to your class. This can be a pointer to a CPianoCtrl
object, but if so, you will need to allocate memory for it before using it and deallocate its memory after you are done with it.
- Call the
CPianoCtrl
object's Create
function. Here, you will pass the parent window, the CPianoCtrl
's position and size, its Id, and optionally its window style.
- Call the
CPianoCtrl
object's Initialize
function. Here, you will pass the desired note range and optionally the note-on color.
The Demonstration Project
The VC++ demonstration project allows you to interact with three CPianoCtrl
controls. You can change their range dynamically. Also, you can change the color used to indicate that a note is being played. You will notice that as you click on the keys you will not hear any sound. That's because the control by itself does not produce a sound. It must be connected to a sound source. If you would like to see an example of this, check out my updated MIDI wrapper article. In that demonstration application, I derive a class from the CPianoCtrl
class specifically designed to work with my MIDI library and play MIDI notes.
I hope you find this control useful. Please let me know if you have any suggestions, bug reports, or would just like to tell me how you are using the control. Take care!
History
- 1st March, 2003 - Added the ability to display control vertically
- 14th March, 2008 - Updated source code and demo project, changed license to MIT