View Single Post
Old 01-19-2010, 10:45 AM   #7
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 2,954
Default IKeyboardControl

Thanks!

--

Here's my keyboard control:

Code:
#define MIN_MIDI_NOTE 48 // C3
#define MAX_MIDI_NOTE 96 // C7

#define NUM_MIDI_NOTES (MAX_MIDI_NOTE - MIN_MIDI_NOTE + 1)

class IKeyboardControl: public IControl
{
public:
	IKeyboardControl(IPlugBase* pPlug, IRECT* pR, IBitmap* pWhiteKey, IBitmap* pBlackKey): IControl(pPlug, pR, -1),
		mWhiteKey(*pWhiteKey), mBlackKey(*pBlackKey), mOctaveWidth(mWhiteKey.W * 7), mBlackKeyOffset(mBlackKey.W / 2), mNumOctaves(mRECT.W() / mOctaveWidth), mKey(-1) {}
	~IKeyboardControl() {}

	void OnMouseDown(int x, int y, IMouseMod* pMod)
	{
		if (!pMod->L) return;

		// Skip if this key is already being played using the mouse.
		int key = GetMouseNote(x, y);
		if (key == mKey) return;

		// Send a Note Off for the previous key (if any).
		if (mKey != -1) SendNoteOff();
		// Send a Note On for the new key.
		mKey = key;
		if (mKey != -1) SendNoteOn();

		// Update the keyboard in the GUI.
		SetDirty();
	}

	void OnMouseUp(int x, int y, IMouseMod* pMod)
	{
		// Skip if no key is playing.
		if (mKey == -1) return;

		// Send a Note Off.
		SendNoteOff();
		mKey = -1;

		// Update the keyboard in the GUI.
		SetDirty();
	}

	void OnMouseDrag(int x, int y, int dX, int dY, IMouseMod* pMod) { OnMouseDown(x, y, pMod); }

	// Draws only the keys that are currently playing.
	bool Draw(IGraphics* pGraphics)
	{
		// Skip if no keys are playing.
		// if (((MyPlug*)mPlug)->HowManyKeys() == 0) return true;

		// White keys
		IRECT r(mRECT.L, mRECT.T, mRECT.L + mWhiteKey.W, mRECT.T + mWhiteKey.H);
		int note = 0;
		while (note < NUM_MIDI_NOTES)
		{
			// Draw the key.
			if (((MyPlug*)mPlug)->GetKeyStatus(note)) pGraphics->DrawBitmap(&mWhiteKey, &r, 0, &mBlend);

			// Next, please!
			int n = note % 12;
			if (n == 4 || n == 11) // E or B
				++note;
			else
				note += 2;
			r.L += mWhiteKey.W;
			r.R += mWhiteKey.W;
		}

		// Black keys
		r.L = mRECT.L + mWhiteKey.W - mBlackKeyOffset;
		if (mBlackKey.W % 2) --r.L;
		r.R = r.L + mBlackKey.W;
		r.B = mRECT.T + mBlackKey.H;
		note = 1;
		while (note < NUM_MIDI_NOTES)
		{
			// Draw the key.
			if (((MyPlug*)mPlug)->GetKeyStatus(note)) pGraphics->DrawBitmap(&mBlackKey, &r, 0, &mBlend);

			// Next, please!
			int n = note % 12;
			if (n == 3 || n == 10) // Eb or Bb
			{
				note += 3;
				r.L += 2 * mWhiteKey.W;
				r.R += 2 * mWhiteKey.W;
			}
			else
			{
				note += 2;
				r.L += mWhiteKey.W;
				r.R += mWhiteKey.W;
			}
		}

		return true;
	}

protected:
	// Returns the note (key) number at the (x, y) coordinates.
	int GetMouseNote(int x, int y)
	{
		// Skip if the coordinates are outside the keyboard's rectangle.
		if (x < mTargetRECT.L || x >= mTargetRECT.R || y < mTargetRECT.T || y >= mTargetRECT.B) return -1;
		x -= mTargetRECT.L;
		y -= mTargetRECT.T;

		// Calculate the octave.
		int octave = x / mOctaveWidth;
		x -= octave * mOctaveWidth;

		// Is it a black key?
		int note = -1;
		int h = mBlackKey.H;
		if (y < h && octave < mNumOctaves)
		{
			int n = (x - mBlackKeyOffset) / mWhiteKey.W;
			if (n >= 0 && n < 2) // C#..D#
			{
				int offset = (n + 1) * mWhiteKey.W - mBlackKeyOffset;
				if (x >= offset && x < offset + mBlackKey.W) note = n * 2 + 1;
			}
			else if (n >= 3 && n < 6) // F#..A#
			{
				int offset = (n + 1) * mWhiteKey.W - mBlackKeyOffset;
				if (x >= offset && x < offset + mBlackKey.W) note = n * 2;
			}
		}

		// If it's not a black key...
		if (note == -1)
		{
			// ... then it must be a white key.
			int n = x / mWhiteKey.W;
			if (n < 3) // C..E
				note = n * 2;
			else // F..B
				note = n * 2 - 1;
			h = mWhiteKey.H;
		}

		// Calculate the velocity depeding on the vertical coordinate
		// relative to the key height.
		mVelocity = 1 + int((double)y / (double)h * 126. + 0.5);

		return note + octave * 12;
	}

	// Sends a Note On/Off MIDI message to the plug-in.
	void SendNoteOn()  { mPlug->ProcessMidiMsg(&IMidiMsg(0, IMidiMsg::kNoteOn  << 4, MIN_MIDI_NOTE + mKey, mVelocity)); }
	void SendNoteOff() { mPlug->ProcessMidiMsg(&IMidiMsg(0, IMidiMsg::kNoteOff << 4, MIN_MIDI_NOTE + mKey, 64       )); }

	IBitmap mWhiteKey, mBlackKey;
	int mOctaveWidth, mBlackKeyOffset, mNumOctaves;
	int mKey, mVelocity;
};
The control assumes the keyboard (with all keys released) is already drawn in the GUI's background. So when attaching the conrol to the plug-in it needs only two bitmaps: one with a single depressed white key, and one with a black key. It also needs to know the rectangular area that holds the entire keyboard:

Code:
IBitmap white = pGraphics->LoadIBitmap(WHITE_KEY_ID, WHITE_KEY_FN, 1);
IBitmap black = pGraphics->LoadIBitmap(BLACK_KEY_ID, BLACK_KEY_FN, 1);
mGuiKeyboard = new IKeyboardControl(this, &IRECT(33, 301, 467, 393), &white, &black);
pGraphics->AttachControl(mGuiKeyboard);
As you can see I store a pointer to the keyboard control in a member variable (mGuiKeyboard). That way if I want to update the keyboard in the GUI from within my plug-in (e.g. because it's received a MIDI Note On/Off message), I can simply call mGuiKeyboard->SetDirty();.

The plug-in should keep track of which keys are playing, and provide a method that returns this status:

Code:
bool MyPlug::GetKeyStatus(int key)
{
	// To-do: Lookup the key's status, and return true if it's playing, false if it's not.
}
I think that about wraps it up... Any questions?
Tale is offline   Reply With Quote