Click here to Skip to main content
15,880,392 members
Articles / Desktop Programming / MFC

Wrapper Library for Windows MIDI API

Rate me:
Please Sign up or sign in to vote.
4.94/5 (67 votes)
28 Jan 2008MIT8 min read 761.4K   16.1K   144  
A small library encapsulating the Windows MIDI API
#if !defined(AFX_PIANOCTRL_H__C84F71CE_FF29_11D6_865D_0030BD08B6D9__INCLUDED_)
#define AFX_PIANOCTRL_H__C84F71CE_FF29_11D6_865D_0030BD08B6D9__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// PianoCtrl.h : header file
//

/*

  PianoCtrl.h

  This header file contains the class declaration for the CPianoCtrl and
  its associated classes. The CPianoCtrl class is a custom MFC control 
  representing a piano keyboard display.

  Copyright (C) 2002 Leslie Sanford

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 
  USA

  Contact: Leslie Sanford (jabberdabber@hotmail.com)

  Last modified: 12/10/2002

*/


//---------------------------------------------------------------------
// Dependencies
//---------------------------------------------------------------------


#include <list>         // For holding the list of CPianoCtrlListeners
#include <vector>       // For holding the CPianoKey objects


//---------------------------------------------------------------------
// CPianoCtrlListener class
//
// This class represents an observer class. It receives notification
// for note on and note off events from the CPianoCtrl class. 
//---------------------------------------------------------------------


// Forward declaration
class CPianoCtrl;


class CPianoCtrlListener
{
public:
    virtual ~CPianoCtrlListener() {}

    virtual void OnNoteOn(CPianoCtrl &PianoCtrl, 
                          unsigned char NoteId) = 0;
    virtual void OnNoteOff(CPianoCtrl &PianoCtrl,
                           unsigned char NoteId) = 0;
};


//---------------------------------------------------------------------
// CPianoCtrl class
//
// This class represents a piano keyboard. 
//---------------------------------------------------------------------
//
// To use the CPianoCtrl class in a dialog based application, use the 
// following steps:
//
//  1. In the resource editor, place a custom control onto the dialog
//     box.
//
//  2. Set the class name in the custom control's property box to match 
//     the CPianoCtrl's class name.
//
//  3. Add a CPianoCtrl instance variable to the dialog class.
//
//  4. 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);
//	        //{{AFX_DATA_MAP(CPianoDlg)
//		    // NOTE: the ClassWizard will add DDX and DDV calls here
//	        //}}AFX_DATA_MAP
//          DDX_Control(pDX, IDC_PIANOCTRL, m_PianoCtrl);
//      }
//
//  5. 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,
// use the following steps:
//
//  1. Add a CPianoCtrl instance variable to your class. This can be a 
//     pointer to a CPianoCtrl, but if so, you will need to allocate
//     memory for it before using it and deallocate its memory after you
//     are done with it.
//
//  2. 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.
//
//  3. Call the CPianoCtrl object's Initialize function. Here, you will 
//     pass the desired note range and optionally the note-on color. 
//
//---------------------------------------------------------------------
//
// Some important notes concerning the CPianoCtrl class:
//
//  It 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.
//
//  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.
//
//  To be notified of note on and note off events, derive a class from
//  CPianoCtrlListener. Implement its methods in your derived class 
//  and attach an object of that class to a CPianoCtrl object with the
//  AttachListener method. When the CPianoCtrl object receives note on
//  and note off events, it will notify all of its listeners.
//
//---------------------------------------------------------------------  
//
// CPianoCtrl Member Methods
//
//---------------------------------------------------------------------
//
//  Construction
//
//      CPianoCtrl()      
//
//          Constructs a CPianoCtrl object. It's important to note that
//          the object has not been fully constructed at this point.
//          A call to the Initialize method is still needed to ready
//          the object for use.
//
//      BOOL Create(CWnd *pParentWnd, const RECT &rect, UINT nID, 
//                  DWORD dwStyle = WS_VISIBLE)   
//
//          Creates an CPianoCtrl object dynamically. Returns TRUE if 
//          the operation was successful.
//
//              pParentWnd - Parent window for this control.
//              rect       - Rectangular coordinates for this control.
//              nID        - Identifier for this control.
//              dwStyle    - The window style for this control.   
//
//      BOOL Initialize(unsigned char LowNote, unsigned char HighNote,
//                      COLORREF NoteOnColor = DEF_NOTE_ON_COLOR)
//
//          Initializes this control. Returns true if the operation was
//          successful.
//
//              LowNote     - The lowest note on the keyboard. Must be 
//                            less than the highest note.
//              HighNote    - The highest note on the keyboard. Must be 
//                            less than MAX_NOTE_COUNT.
//              NoteOnColor - The color used to indicate that a key is 
//                            is being played.
//
//---------------------------------------------------------------------
//
//  Operations
//
//      void NoteOn(COLORREF NoteOnColor, unsigned char NoteId)
//      void NoteOn(unsigned char NoteId)
//
//          Turns a note on. 
//
//              NoteOnColor - The color indicating that this key is 
//                            being played.
//              NoteId      - The note to play.       
//
//      void NoteOff(unsigned char NoteId)
//
//          Turns a note off.
//
//              NoteId - The note to stop playing. 
//
//      void AttachListener(CPianoCtrlListener &Listener)
//      void DetachListener(CPianoCtrlListener &Listener)
//
//          Attaches/Detaches CPianoCtrlListener objects. When an 
//          CPianoCtrlListener object is attached to a CPianoCtrl 
//          object, the CPianoCtrl object will notify it when note-on 
//          and note-off events occur.
//
//              Listener - The CPianoCtrlListener to attach/detach.
//
//---------------------------------------------------------------------
//
//  Attributes
//
//      unsigned char GetLowNote() const
//
//          Returns the lowest note for this CPianoCtrl object.      
//
//      unsigned char GetHighNote() const
//
//          Returns the highest note for this CPianoCtrl object.
//
//      BOOL SetNoteRange(unsigned char LowNote, 
//                        unsigned char HighNote)
//      
//          Sets the note range for this CPianoCtrl object. Returns
//          TRUE if the operation was successful.
//
//              LowNote  - The lowest note on the keyboard. Must be 
//                         less than the highest note.
//              HighNote - The highest note on the keyboard. Must be 
//                         less than MAX_NOTE_COUNT.
//
//      COLORREF GetNoteOnColor() const
//
//          Returns the color that indicates a key is being played.
//
//      void SetNoteOnColor(COLORREF NoteOnColor) 
//
//          Sets the color to indicate a key is being played.
//
//              NoteOnColor - The color indicating that a key is 
//                            being played.  
//   
//---------------------------------------------------------------------


class CPianoCtrl : public CWnd
{
public:
    // Construction/Destruction
	CPianoCtrl();
    virtual ~CPianoCtrl();

    // Creates the CPianoCtrl
    BOOL Create(CWnd *pParentWnd, const RECT &rect, UINT nID, 
                DWORD dwStyle = WS_VISIBLE);

    // Initializes the CPianoCtrl - must be called before this 
    // CPianoCtrl object can be used.
    BOOL Initialize(unsigned char LowNote, unsigned char HighNote,
                    COLORREF NoteOnColor = DEF_NOTE_ON_COLOR);

    // Turns note on
    void NoteOn(unsigned char NoteId, COLORREF NoteOnColor);
    void NoteOn(unsigned char NoteId);

    // Turns note off
    void NoteOff(unsigned char NoteId);

    // Attach/Detach CPianoCtrlListener objects
    void AttachListener(CPianoCtrlListener &Listener);
    void DetachListener(CPianoCtrlListener &Listener);

    //
    // Accessors/Mutators
    //

    unsigned char GetLowNote() const { return m_LowNote; }
    unsigned char GetHighNote() const { return m_HighNote; }
    BOOL SetNoteRange(unsigned char LowNote, unsigned char HighNote);

    COLORREF GetNoteOnColor() const { return m_NoteOnColor; }
    void SetNoteOnColor(COLORREF NoteOnColor) 
        { m_NoteOnColor = NoteOnColor; }

// Private functions
private:
    bool IsNoteNatural(unsigned char Note);
    int GetNaturalNoteCount();
    double GetUnitLength();

    void CreatePianoKeys();
    void DestroyPianoKeys();

    int FindKey(CPoint &point);

    // Notify CPianoCtrlListener objects that a note on/off event has
    // occurred.
    void NotifyNoteOn(unsigned char NoteId);
    void NotifyNoteOff(unsigned char NoteId);

    // Register this control's window class
    static void RegisterWindowClass();

    //
    // Copying not allowed
    //

    CPianoCtrl(const CPianoCtrl &PianoCtrl);
    CPianoCtrl &operator = (const CPianoCtrl &PianoCtrl);

// Private classes
private:

    //------------------------------------------------------------------
    // Piano key classes
    //
    // The following classes represent the keys on the CPianoCtrl class. 
    //
    //------------------------------------------------------------------
    //
    // To understand how these classes work, first imagine a piano 
    // keyboard display with a range of 12 keys from C to B:
    //
    //                  ----------------------
    //                  | | || | | | || || | |
    //                  | |_||_| | |_||_||_| |
    //                  |  |  |  |  |  |  |  |
    //                  ----------------------
    //
    // We can divide the keys into 4 types:
    //
    //                  ----------------------
    //                  | | || | | | || || | |
    //                  | |_||_| | |_||_||_| |
    //                  |  |  |  |  |  |  |  |
    //                  ----------------------
    //                  /|\      /|\
    //                   |        | 
    //
    //                  White key left (C and F)
    //
    //
    //                  ----------------------
    //                  | | || | | | || || | |
    //                  | |_||_| | |_||_||_| |
    //                  |  |  |  |  |  |  |  |
    //                  ----------------------
    //                     /|\      /|\ /|\
    //                      |        |   |
    //
    //                White key middle (D, G, and A)
    //
    //
    //                  ----------------------
    //                  | | || | | | || || | |
    //                  | |_||_| | |_||_||_| |
    //                  |  |  |  |  |  |  |  |
    //                  ----------------------
    //                        /|\          /|\ 
    //                         |            |
    //
    //                  White key right (E and B)
    //
    //
    //                     |  |     |  |  |
    //                    \|/\|/   \|/\|/\|/
    //                  ----------------------
    //                  | | || | | | || || | |
    //                  | |_||_| | |_||_||_| |
    //                  |  |  |  |  |  |  |  |
    //                  ----------------------
    //                 
    //            Black key (C#, D#, F#, G#, and A#)
    //
    //
    // There is also a fifth type of key. This is a special key used 
    // when certain ranges are set. For example, look at a range of keys
    // from C to C one octave higher:
    //
    //                  ------------------------
    //                  | | || | | | || || | | | |
    //                  | |_||_| | |_||_||_| | |_|
    //                  |  |  |  |  |  |  |  |  |
    //                  -------------------------
    //
    // A problem occurs because we have half of a black key dangling off
    // the side of the keyboard. This really isn't acceptable, so we 
    // need an additional key to take care of this special case, the
    // white full key:
    //
    //                  -------------------------
    //                  | | || | | | || || | |  |
    //                  | |_||_| | |_||_||_| |  |
    //                  |  |  |  |  |  |  |  |  |
    //                  -------------------------
    //                                        /|\
    //                                         |
    //
    //                        White full key
    //
    // The white full key takes care of those cases in which there would
    // otherwise be a black key chopped in half.
    //
    // The five types of piano keys are each represented by their own 
    // class. Each class knows how to draw itself so that it has the 
    // proper shape on the piano keyboard. All of the classes derive 
    // from one parent class called CPianoKey. This class provides the 
    // interface for all piano key classes.
    //
    // The CPianoCtrl class only allows ranges that begin and end with 
    // natural notes (white keys). This was a judgement call on my part.
    // Ranges in which the client chose a sharp/flat key as the lowest
    // or highest note would create a strange situation in which a white
    // key would be chopped in half:
    //
    //                    --------------------
    //                    | || | | | || || | |
    //                    |_||_| | |_||_||_| |
    //                    ||  |  |  |  |  |  |
    //                    --------------------
    //                   /|\
    //                    |
    //      
    //              Half-white key?
    //
    // This situation could certainly be dealt with, but if the client
    // specified that C# should be the lowest key, they would have to 
    // deal with the extra half-white key that would actually be the 
    // lowest note, in this case C, and not the one they had specified.
    // I decided this situation was more trouble than it was worth, so
    // I limited the low and high end of the range to natural notes.
    //
    //------------------------------------------------------------------
    //
    // The keys are divided into "units." Units are used to determine 
    // how large to draw each key. The length of a unit is represented 
    // by the total length of the control divided by the number of 
    // natural keys in the range divided by 3. 
    //
    // So if the length of the control is 100 and the number of natural
    // keys in the range is 12, than the unit length would be 2.77778.
    //
    // Here is how the unit length is used:
    //
    // All of the white keys at their longest part take up 3 units:
    //
    //                          ----------
    //                          | | || | |
    //                          | |_||_| |
    //                          |  |  |  |
    //                          ----------
    //                          |<>|<>|<>|
    //                          
    //
    //                         3 Units Long
    //
    // The black keys are 2 units long and the white keys at their 
    // narrowest part are also 2 units long.
    //
    //                         2 Units Long
    //
    //                          \/ \/ \/
    //                          ----------
    //                          | | || | |
    //                          | |_||_| |
    //                          |  |  |  |
    //                          ----------
    //
    // The unit length is passed to each key object so that it can know
    // how large to draw itself. The position on the control is also 
    // passed to itself so that it can know where to draw itself.
    //
    //-----------------------------------------------------------------


    // Parent piano key class
    class CPianoKey
    {
    public:
        CPianoKey() : m_NoteOnFlag(false) {}
        virtual ~CPianoKey() {}

        // Hit detection
        virtual bool IsPointInKey(const CPoint &pt) const = 0;

        // Turns note on/off
        virtual void NoteOn(COLORREF NoteOnColor, CRect &Rect) = 0;
        virtual void NoteOff(CRect &Rect) = 0;

        // Paints this key
        virtual void Paint(CDC *dc) = 0;

    public:

        // Units per natural key
        static const int UNIT_PER_NAT_KEY;

        // Determines black keys' width
        static const double BLACK_KEY_OFFSET;

    protected:
        // Function for determining if a point is inside a rectangle.
        // The CRect method PtInRect is inadequate in this context 
        // because it does not count a point on the bottom or on the 
        // right side of the rectangle as being within the rectangle.
        static bool IsPointInRect(const CPoint &pt, const CRect &Rect)
        { 
            return (pt.x >= Rect.left && pt.x <= Rect.right &&
                  pt.y >= Rect.top && pt.y <= Rect.bottom);
        }

    protected:
        bool m_NoteOnFlag;
        COLORREF m_NoteOnColor;
    };


    // CWhiteKeyLeft class
    class CWhiteKeyLeft : public CPianoKey
    {
    public:
        CWhiteKeyLeft(double UnitLength, int Width, double Position);

        bool IsPointInKey(const CPoint &pt) const;

        void NoteOn(COLORREF NoteOnColor, CRect &Rect);
        void NoteOff(CRect &Rect);

        void Paint(CDC *dc);

    private:
        void CalcInvalidRect(CRect &Rect);

    private:
        CRect m_UpperRect;
        CRect m_LowerRect;
    };


    // CWhiteKeyMiddle class
    class CWhiteKeyMiddle : public CPianoKey
    {
    public:
        CWhiteKeyMiddle(double UnitLength, int Width, double Position);

        bool IsPointInKey(const CPoint &pt) const;

        void NoteOn(COLORREF NoteOnColor, CRect &Rect);
        void NoteOff(CRect &Rect);

        void Paint(CDC *dc);

    private:
        void CalcInvalidRect(CRect &Rect);

    private:
        CRect m_UpperRect;
        CRect m_LowerRect;
    };


    // CWhiteKeyRight class
    class CWhiteKeyRight : public CPianoKey
    {
    public:
        CWhiteKeyRight(double UnitLength, int Width, double Position);

        bool IsPointInKey(const CPoint &pt) const;

        void NoteOn(COLORREF NoteOnColor, CRect &Rect);
        void NoteOff(CRect &Rect);

        void Paint(CDC *dc);

    private:
        void CalcInvalidRect(CRect &Rect);

    private:
        CRect m_UpperRect;
        CRect m_LowerRect;
    };


    // CWhiteKeyFull class
    class CWhiteKeyFull : public CPianoKey
    {
    public:
        CWhiteKeyFull(double UnitLength, int Width, double Position);

        bool IsPointInKey(const CPoint &pt) const;

        void NoteOn(COLORREF NoteOnColor, CRect &Rect);
        void NoteOff(CRect &Rect);

        void Paint(CDC *dc);

    private:
        CRect m_Rect;
    };


    // CBlackKey class
    class CBlackKey : public CPianoKey
    {
    public:
        CBlackKey(double UnitLength, int Width, double Position);

        bool IsPointInKey(const CPoint &pt) const;

        void NoteOn(COLORREF NoteOnColor, CRect &Rect);
        void NoteOff(CRect &Rect);

        void Paint(CDC *dc);

    private:
        CRect m_Rect;
    };


// Public attributes
public:
    // The class name of this control
    static const char CLASS_NAME[];

    // Default color used for indicating that a note is being played
    static const COLORREF DEF_NOTE_ON_COLOR;

    // The maximum number of notes this piano control can have
    static const int MAX_NOTE_COUNT;

    // Note identifiers
    enum NoteId { C, C_SHARP, D, D_SHARP, E, F, F_SHARP, G, G_SHARP, A, 
                  A_SHARP, B };

    // Table of note identifiers
    static const int NOTE_TABLE[];

// Private attributes
private:
    // For protecting access to the list of listeners
    CRITICAL_SECTION m_CriticalSection;

    // Color indicating a key is being played
    COLORREF m_NoteOnColor;

    // Lenght of this control
    int m_Length;

    // Width of this control
    int m_Width;            
    
    // Low note of the range
    unsigned char m_LowNote;

    // High note of the range
    unsigned char m_HighNote;      

    // Unit length for each key
    double m_UnitLength;

    // Flag for indicating whether or not this object has been 
    // intialized.
    bool m_IsInitialized;

    // Flag for indicating whether or not this window has captured the 
    // mouse. 
    bool m_HasCapture;    

    // Collection of piano key objects
    std::vector<CPianoKey *> m_Keys;

    // The current active key (triggered by the mouse)
    int m_CurrKey;

    // Collection of piano control listeners
    std::list<CPianoCtrlListener *> m_Listeners; 

    // Flag for whether or note this custom control's Window class has 
    // been registered.
    static bool REGISTER_FLAG;

    // Messages for range errors
    static const CString NOTE_RANGE_ERR;
    static const CString LOW_NOTE_RANGE_ERR;
    static const CString HIGH_NOTE_RANGE_ERR;
    static const CString INVALID_RANGE_ERR;
    static const CString NATURAL_NOTE_ERR;


//----------------------------------------------------------------------
// Wizard generated code 
//----------------------------------------------------------------------


public:

// Overrides
	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CPianoCtrl)
	//}}AFX_VIRTUAL

	// Generated message map functions
protected:
	//{{AFX_MSG(CPianoCtrl)
	afx_msg void OnPaint();
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
	afx_msg void OnMouseMove(UINT nFlags, CPoint point);
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_PIANOCTRL_H__C84F71CE_FF29_11D6_865D_0030BD08B6D9__INCLUDED_)

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
United States United States
Aside from dabbling in BASIC on his old Atari 1040ST years ago, Leslie's programming experience didn't really begin until he discovered the Internet in the late 90s. There he found a treasure trove of information about two of his favorite interests: MIDI and sound synthesis.

After spending a good deal of time calculating formulas he found on the Internet for creating new sounds by hand, he decided that an easier way would be to program the computer to do the work for him. This led him to learn C. He discovered that beyond using programming as a tool for synthesizing sound, he loved programming in and of itself.

Eventually he taught himself C++ and C#, and along the way he immersed himself in the ideas of object oriented programming. Like many of us, he gotten bitten by the design patterns bug and a copy of GOF is never far from his hands.

Now his primary interest is in creating a complete MIDI toolkit using the C# language. He hopes to create something that will become an indispensable tool for those wanting to write MIDI applications for the .NET framework.

Besides programming, his other interests are photography and playing his Les Paul guitars.

Comments and Discussions