PianoCtrl.cpp
上传用户:fs3633
上传日期:2021-05-14
资源大小:909k
文件大小:20k
- /*
- CPianoCtrl.cpp
- This file represents the implementation for the CPianoCtrl class
- 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/18/2002
- */
- //---------------------------------------------------------------------
- // Dependencies
- //---------------------------------------------------------------------
- #include <afxwin.h>
- #include "PianoCtrl.h"
- #ifdef _DEBUG
- #define new DEBUG_NEW
- #undef THIS_FILE
- static char THIS_FILE[] = __FILE__;
- #endif
- /////////////////////////////////////////////////////////////////////////////
- // CPianoCtrl
- //---------------------------------------------------------------------
- // Constants
- //---------------------------------------------------------------------
- // Class name for this control 設置注冊類的名字
- const char CPianoCtrl::CLASS_NAME[] = _T("PianoCtrl");
- // Default note-on color 默認的音符顏色
- const COLORREF CPianoCtrl::DEF_NOTE_ON_COLOR = RGB(0, 150, 250);
- // Maximum number of notes 音符的最大數值
- const int CPianoCtrl::MAX_NOTE_COUNT = 128;
- // Note table //音符表
- const int CPianoCtrl::NOTE_TABLE[] =
- {
- C, C_SHARP, D, D_SHARP, E, F, F_SHARP, G, G_SHARP, A, A_SHARP, B,
- C, C_SHARP, D, D_SHARP, E, F, F_SHARP, G, G_SHARP, A, A_SHARP, B,
- C, C_SHARP, D, D_SHARP, E, F, F_SHARP, G, G_SHARP, A, A_SHARP, B,
- C, C_SHARP, D, D_SHARP, E, F, F_SHARP, G, G_SHARP, A, A_SHARP, B,
- C, C_SHARP, D, D_SHARP, E, F, F_SHARP, G, G_SHARP, A, A_SHARP, B,
- C, C_SHARP, D, D_SHARP, E, F, F_SHARP, G, G_SHARP, A, A_SHARP, B,
- C, C_SHARP, D, D_SHARP, E, F, F_SHARP, G, G_SHARP, A, A_SHARP, B,
- C, C_SHARP, D, D_SHARP, E, F, F_SHARP, G, G_SHARP, A, A_SHARP, B,
- C, C_SHARP, D, D_SHARP, E, F, F_SHARP, G, G_SHARP, A, A_SHARP, B,
- C, C_SHARP, D, D_SHARP, E, F, F_SHARP, G, G_SHARP, A, A_SHARP, B,
- C, C_SHARP, D, D_SHARP, E, F, F_SHARP, G
- };
- // Range error messages//错误消息列表
- const CString CPianoCtrl::NOTE_RANGE_ERR = _T("Note out of range.");
- const CString CPianoCtrl::LOW_NOTE_RANGE_ERR = _T("Low note out of range.");
- const CString CPianoCtrl::HIGH_NOTE_RANGE_ERR = _T("High note out of range.");
- const CString CPianoCtrl::INVALID_RANGE_ERR = _T("Note range is invalid.");
- const CString CPianoCtrl::NATURAL_NOTE_ERR = _T("Notes must be natural.");
- // Flags whether or not this custom control class has been registered.
- bool CPianoCtrl::REGISTER_FLAG = false;
-
- //---------------------------------------------------------------------
- // CPianoCtrl class implementation
- //---------------------------------------------------------------------
- // Constructor
- CPianoCtrl::CPianoCtrl() :
- m_Keys(0),
- m_LowNote(0),
- m_HighNote(0),
- m_CurrKey(-1),
- m_IsInitialized(false),
- m_HasCapture(false)
- {
- // If this control has not been registered, do it here.
- //如果這個控件已經注冊成功了,那么就在這裡執行
- if(!REGISTER_FLAG)
- {
- RegisterWindowClass();
- }
- // Initialize critical section
- //初始化臨界面
- ::InitializeCriticalSection(&m_CriticalSection);
- }
- // Destructor
- CPianoCtrl::~CPianoCtrl()
- {
- DestroyPianoKeys();
- ::DeleteCriticalSection(&m_CriticalSection);
- }
- // Create function for creating the control dynamically
- //創建控件的函數
- BOOL CPianoCtrl::Create(CWnd *pParentWnd, const RECT &rect, UINT nId,
- DWORD dwStyle)
- {
- return CWnd::Create(CLASS_NAME, _T(""), dwStyle, rect, pParentWnd,
- nId);
- }
- // Initializes the control 初始化控件
- BOOL CPianoCtrl::Initialize(unsigned char LowNote,
- unsigned char HighNote,
- COLORREF NoteOnColor)
- {
- CRect ClientRect;
-
- // Get the rectangular coordinates of this control and get its
- // width and length
- //得到這個控件的坐標範圍,並且得到它的寬和長
- GetClientRect(&ClientRect);
- m_Width = ClientRect.bottom;
- m_Length = ClientRect.right - 1;
- // Initialize note-on color 初始化音符的顏色
- m_NoteOnColor = NoteOnColor;
- // If we successfully set the note range, we are finished
- // initializing this control. Indicate that it has been initialized.
- //如果我們成功的設置了音符的排列,我們就算完成了。
- //初始化這個控件,並且顯示出它已經被初始化了
- if(SetNoteRange(LowNote, HighNote))
- {
- m_IsInitialized = true;
- }
- return TRUE ? m_IsInitialized : FALSE;
- }
- // Turn on a note //开启一个音符
- void CPianoCtrl::NoteOn(unsigned char NoteId, COLORREF NoteOnColor)
- {
- // Make sure this control has been initialized//确定这个控件有没有初始化
- if(m_IsInitialized)
- {
- // Range checking//开始检查
- if(NoteId < m_LowNote || NoteId > m_HighNote)
- {
- MessageBox(NOTE_RANGE_ERR, "Error", MB_OK | MB_ICONSTOP);
- }
- // Turn on note and invalidate its area so that it is repainted.
- else
- {
- CRect InvalidRect;
- m_Keys[NoteId - m_LowNote]->NoteOn(NoteOnColor, InvalidRect);
- NotifyNoteOn(NoteId);
- InvalidateRect(InvalidRect);
- }
- }
-
-
- }
- // Turn on note 打开音符
- void CPianoCtrl::NoteOn(unsigned char NoteId)
- {
- NoteOn(NoteId, m_NoteOnColor);
- }
- // Turn off note
- void CPianoCtrl::NoteOff(unsigned char NoteId)
- {
- // Make sure this control has been initialized 这个控件必须初始化
- if(m_IsInitialized)
- {
- // Range checking 开始检查
- if(NoteId < m_LowNote || NoteId > m_HighNote)
- {
- MessageBox(NOTE_RANGE_ERR, "Error", MB_OK | MB_ICONSTOP);
- }
- // Turn note off and invalidate its area so that it can be
- // repainted 开启音符或者使它无效,只要在这个范围之内,它都能重新绘制
- else
- {
- CRect InvalidRect;
- m_Keys[NoteId - m_LowNote]->NoteOff(InvalidRect);
- NotifyNoteOff(NoteId);
- InvalidateRect(InvalidRect);
- }
- }
-
- }
- // Attach listener for event notification 为事件通知连接上收听者
- void CPianoCtrl::AttachListener(CPianoCtrlListener &Listener)//如果沒有這個函數,就沒聲音了
- {
- // Protect list with critical section
- ::EnterCriticalSection(&m_CriticalSection);
- m_Listeners.push_front(&Listener);
- ::LeaveCriticalSection(&m_CriticalSection);
-
- }
- // Detach listener 分离收听者
- void CPianoCtrl::DetachListener(CPianoCtrlListener &Listener)
- {
- ::EnterCriticalSection(&m_CriticalSection);
- m_Listeners.remove(&Listener);
- ::LeaveCriticalSection(&m_CriticalSection);
- }
- // Set note range 放置音符队列
- BOOL CPianoCtrl::SetNoteRange(unsigned char LowNote,
- unsigned char HighNote)
- {
- BOOL Result = FALSE;
- // Range checking 开始搜索
- if(LowNote < 0)
- {
- MessageBox(LOW_NOTE_RANGE_ERR, "Error", MB_OK | MB_ICONSTOP);
- }
- // High note beyond maximum number allowed 高音符在最高的数字
- else if(HighNote >= MAX_NOTE_COUNT)
- {
- MessageBox(HIGH_NOTE_RANGE_ERR, "Error", MB_OK | MB_ICONSTOP);
- }
- // Low note is higher than high note 否则低音符比高音符更高
- else if(LowNote >= HighNote)
- {
- MessageBox(INVALID_RANGE_ERR, "Error", MB_OK | MB_ICONSTOP);
- }
- // Either the low note or the high note is not natural
- //任意一个低音符或者高音符不是普通的音符
- else if(!IsNoteNatural(LowNote) || !IsNoteNatural(HighNote))
- {
- MessageBox(NATURAL_NOTE_ERR, "Error", MB_OK | MB_ICONSTOP);
- }
- // Initialize range and create keys 初始化队列并且创建琴键
- else
- {
- m_LowNote = LowNote;
- m_HighNote = HighNote;
- CreatePianoKeys();
- // Indicate that the operation was successful 否则说明哪个操作是成功的,返回TRUE
- Result = TRUE;
- }
- return Result;
-
-
- }
-
- // Determines if a note is natural 确定这个音符是否是它对应的本位号
- bool CPianoCtrl::IsNoteNatural(unsigned char Note)
- {
- bool Result = true;
- if(NOTE_TABLE[Note] == C_SHARP || NOTE_TABLE[Note] == D_SHARP ||
- NOTE_TABLE[Note] == F_SHARP || NOTE_TABLE[Note] == G_SHARP ||
- NOTE_TABLE[Note] == A_SHARP)
- {
- Result = false;
- }
-
- return Result;
- }
- // Get the number of natural notes in this control's range
- //在这个控件队列中得到音符对应的数字
- int CPianoCtrl::GetNaturalNoteCount()
- {
- int NatNoteCount = 0;
- for(unsigned char i = m_LowNote; i <= m_HighNote; i++)
- {
- if(IsNoteNatural(i))
- {
- NatNoteCount++;
- }
- }
- return NatNoteCount;
-
-
- }
- // Calculate the unit length for this control 计算这个控件的单位长度
- double CPianoCtrl::GetUnitLength()
- {
- // Determine the number of natural notes within this note range
- int NatNoteCount = GetNaturalNoteCount();
- double Result = static_cast<double>(m_Length) / NatNoteCount;
- // Return the length of each unit 返回每个单位的长度
- return Result / CPianoKey::UNIT_PER_NAT_KEY;
- }
- // Create the piano keys 创建琴键
- void CPianoCtrl::CreatePianoKeys()
- {
- double Position = 0;
- DestroyPianoKeys();
- m_Keys.resize(m_HighNote - m_LowNote + 1);
- m_UnitLength = GetUnitLength();
- //
- // The leftmost key is a special case. Take care of it first.
- // 最左边的琴键是特殊的,要首先考虑
- if(NOTE_TABLE[m_LowNote] == B || NOTE_TABLE[m_LowNote] == E)
- {
- m_Keys[0] = new CWhiteKeyFull(m_UnitLength, m_Width, 0);
- // Move position to the right 往右移动位置
- Position = m_UnitLength * CPianoKey::UNIT_PER_NAT_KEY;
- }
- else
- {
- m_Keys[0] = new CWhiteKeyLeft(m_UnitLength, m_Width, 0);
- // Move position to the right 继续右移
- Position = m_UnitLength * (CPianoKey::UNIT_PER_NAT_KEY - 1);
- }
- //
- // Create all but the rightmost key.
- //创建所有右边的琴键
- for(int i = m_LowNote + 1; i < m_HighNote; i++)
- {
- // CWhiteKeyLeft cases
- if(NOTE_TABLE[i] == C || NOTE_TABLE[i] == F)
- {
- m_Keys[i - m_LowNote] = new CWhiteKeyLeft(m_UnitLength,
- m_Width, Position);
- // Move position to the right
- Position += m_UnitLength * (CPianoKey::UNIT_PER_NAT_KEY - 1);
- }
- // CWhiteKeyMiddle cases
- else if(NOTE_TABLE[i] == G || NOTE_TABLE[i] == A ||
- NOTE_TABLE[i] == D)
- {
- m_Keys[i - m_LowNote] = new CWhiteKeyMiddle(m_UnitLength,
- m_Width, Position);
- // Move position to the right 移动到右边放置
- Position += m_UnitLength * (CPianoKey::UNIT_PER_NAT_KEY - 1);
- }
- // CWhiteKeyRight cases
- else if(NOTE_TABLE[i] == B || NOTE_TABLE[i] == E)
- {
- m_Keys[i - m_LowNote] = new CWhiteKeyRight(m_UnitLength,
- m_Width, Position);
- // Move position to the right
- Position += m_UnitLength * (CPianoKey::UNIT_PER_NAT_KEY);
- }
- // CBlackKey cases 黑色琴键的设置
- else
- {
- m_Keys[i - m_LowNote] = new CBlackKey(m_UnitLength,
- m_Width, Position);
- // Move position to the right
- Position += m_UnitLength;
- }
- }
- //
- // The rightmost key is a special case. Take care of it last.
- //最右面的琴键是特殊的,最后在考虑
- if(NOTE_TABLE[m_HighNote] == C ||
- NOTE_TABLE[m_HighNote] == F)
- {
- m_Keys[m_HighNote - m_LowNote] = new
- CWhiteKeyFull(m_UnitLength, m_Width, Position);
- }
- else
- {
- m_Keys[m_HighNote - m_LowNote] = new
- CWhiteKeyRight(m_UnitLength, m_Width, Position);
- }
- // Paint new keyboard 绘制新的键盘
- Invalidate();
-
- }
- // Destroy the piano keys 销毁这些琴键
- void CPianoCtrl::DestroyPianoKeys()
- {
- // Make sure there are keys to destroy 必须销毁一些琴键
- if(m_Keys.size() > 0)
- {
- for(int i = 0; i < m_Keys.size(); i++)
- {
- delete m_Keys[i];
- }
- }
- // Resise the container to 0 indicating that there are no keys in
- // the control at this time
- m_Keys.resize(0);
- }
- // Find a key in this control 在这个控件上面寻找一个琴键
- int CPianoCtrl::FindKey(CPoint &point)
- {
- bool FoundFlag = false;
- int Key = 0;
- // This algorithm does a simple linear search for the key that was
- // pressed. This is not an efficient way to search. However, it's
- // the simplest and works well enough in practise for this situation.
- //這個算法是一個簡單的線性搜索某個琴鍵是不是按下去了,這不是一個有效的
- //搜尋方法
- while(!FoundFlag && Key < m_Keys.size() - 1)
- {
- if(m_Keys[Key]->IsPointInKey(point))
- {
- FoundFlag = true;
- }
- else
- {
- Key++;
- }
- }
- return Key;
-
-
- }
- // Notify all listeners that a note-on event has occurred
- //通报所有的收听者有一个音符开启的事件发生了
- void CPianoCtrl::NotifyNoteOn(unsigned char NoteId)//如果沒有這個函數的話一樣沒有聲音
- {
- // Protect list with critical section
- ::EnterCriticalSection(&m_CriticalSection);
- std::list<CPianoCtrlListener *>::iterator i;
- for(i = m_Listeners.begin(); i != m_Listeners.end(); i++)
- {
- (*i)->OnNoteOn(*this, NoteId);
- }
- ::LeaveCriticalSection(&m_CriticalSection);
-
- }
- // Notify listeners that a note-off event has occurred
- //通报所有的收听者有一个音符关闭的事件发生了
- void CPianoCtrl::NotifyNoteOff(unsigned char NoteId)//如果沒有這個函數的話就會有回音
- {
- // Protect list with critical section
- ::EnterCriticalSection(&m_CriticalSection);
- std::list<CPianoCtrlListener *>::iterator i;
- for(i = m_Listeners.begin(); i != m_Listeners.end(); i++)
- {
- (*i)->OnNoteOff(*this, NoteId);
- }
- ::LeaveCriticalSection(&m_CriticalSection);
-
- }
- // Register this controls Window class
- //在Window窗口类里面注册一个控件
- void CPianoCtrl::RegisterWindowClass()
- {
-
- WNDCLASS WndClass;
- HINSTANCE hInst = ::AfxGetInstanceHandle();
- HCURSOR Cursor = ::AfxGetApp()->LoadStandardCursor(IDC_ARROW);
- WndClass.style = CS_HREDRAW | CS_VREDRAW;
- WndClass.lpfnWndProc = ::DefWindowProc;
- WndClass.cbClsExtra = WndClass.cbWndExtra = 0;
- WndClass.hInstance = hInst;
- WndClass.hIcon = NULL;
- WndClass.hCursor = Cursor;
- WndClass.hbrBackground = NULL;
- WndClass.lpszMenuName = NULL;
- WndClass.lpszClassName = CLASS_NAME;
-
-
- if (::AfxRegisterClass(&WndClass))
- {
- // Indicate that this Window class has now been registered
- //显示这个Windows类已经注册了
- REGISTER_FLAG = true;
- }
- else
- {
- ::AfxThrowResourceException();
- }
-
-
- }
- BEGIN_MESSAGE_MAP(CPianoCtrl, CWnd)
- //{{AFX_MSG_MAP(CPianoCtrl)
- ON_WM_PAINT()
- ON_WM_LBUTTONDOWN()
- ON_WM_LBUTTONUP()
- ON_WM_MOUSEMOVE()
- //}}AFX_MSG_MAP
- END_MESSAGE_MAP()
- /////////////////////////////////////////////////////////////////////////////
- // CPianoCtrl message handlers
- // Paints the control 绘制这个控件
- void CPianoCtrl::OnPaint()
- {
- PAINTSTRUCT ps;
- CDC *dc = BeginPaint(&ps);
- for(int i = 0; i < m_Keys.size(); i++)
- {
- m_Keys[i]->Paint(dc);
- }
- EndPaint(&ps);
- }
- // On left mouse button down 鼠标把左边的BUTTON按钮按下
- void CPianoCtrl::OnLButtonDown(UINT nFlags, CPoint point)
- {
- // Capture the mouse if we haven't already
- //判断计算机的鼠标是不是已经准备好
- if(!m_HasCapture)
- {
- SetCapture();
- m_HasCapture = true;
- }
- // Make sure this control has been initialized
- //必须先把这个控件初始化
- if(m_IsInitialized)
- {
- // Find the key to turn on. This will become the current key.
- //寻找开启的琴键,这个将改变当前的琴键
- // We keep track of it so that we can turn it off later
- //我们必须明白这个,因为我们随后要打开它
- m_CurrKey = FindKey(point);
- // Turn on note, get its area, and then invalidate so that the
- // control is repainted to indicate that this note is being
- // played 在这个区域开启一个音符,开启之后并且使之无效,因此这个控件必须重新绘制一
- //一次,以确定这个音符能响动
- CRect InvalidRect;
- m_Keys[m_CurrKey]->NoteOn(m_NoteOnColor, InvalidRect);
- NotifyNoteOn(m_LowNote+m_CurrKey);
- InvalidateRect(&InvalidRect);
- }
-
- CWnd::OnLButtonDown(nFlags, point);
-
- }
- // On left mouse button up 当鼠标松开左边这个BUTTON按钮的时候
- void CPianoCtrl::OnLButtonUp(UINT nFlags, CPoint point)
- {
- // If there is a key already being played by the mouse, turn it off
- //判断一个琴键是不是已经响动过一次,如果有的话必须关闭它
- if(m_CurrKey >= 0)
- {
- CRect InvalidRect;
- m_Keys[m_CurrKey]->NoteOff(InvalidRect);
- NotifyNoteOff(m_CurrKey + m_LowNote);
- // Indicate that there is no key active
- //显示不活动的琴键
- m_CurrKey = -1;
- InvalidateRect(&InvalidRect);
- }
-
- CWnd::OnLButtonUp(nFlags, point);
- }
- // On mouse move 鼠标的移动
- void CPianoCtrl::OnMouseMove(UINT nFlags, CPoint point)
- {
- // If the mouse has moved outside of this window, turn off any
- // currently active key and release the mouse.
- //如果鼠标移动到Windows的另一边,就要关闭所有活动的琴键,也要释放掉鼠标
- if(point.x <= 0 || point.x >= m_Length || point.y <= 0 ||
- point.y >= m_Width)
- {
- // If there is a key currently active key
- //判断当前活动着的琴键
- if(m_CurrKey >= 0)
- {
- CRect InvalidRect;
- m_Keys[m_CurrKey]->NoteOff(InvalidRect);
- NotifyNoteOff(m_CurrKey + m_LowNote);
- InvalidateRect(&InvalidRect);
- }
- ReleaseCapture();
- m_HasCapture = false;
- }
- // If this piano control has been intialized, and it has captured the
- // mouse, and the left mouse button is down...
- //如果要初始化这个钢琴的控件,鼠标,还有鼠标按下左面的BUTTON按钮
- if(m_IsInitialized && m_HasCapture && (nFlags & MK_LBUTTON))
- {
- // Find the key beneath the mouse cursor
- //寻找在琴键之下的鼠标指针
- int Key = FindKey(point);
- // If the mouse has moved to a new key
- //判断鼠标有没有移动到新的琴键
- if(Key != m_CurrKey)
- {
- CRect InvalidRect;
- // If the current key is valid, turn note off
- //判断当前的琴键是不是有效的,如果有就关闭它
- if(m_CurrKey >= 0)
- {
- m_Keys[m_CurrKey]->NoteOff(InvalidRect);
- NotifyNoteOff(m_CurrKey + m_LowNote);
- InvalidateRect(&InvalidRect);
- }
- // Turn new note on 开启新的音符
- m_CurrKey = Key;
- m_Keys[m_CurrKey]->NoteOn(m_NoteOnColor, InvalidRect);
- NotifyNoteOn(m_CurrKey + m_LowNote);
- InvalidateRect(&InvalidRect);
- }
- }
-
- CWnd::OnMouseMove(nFlags, point);
- }