mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
867 lines
27 KiB
C++
867 lines
27 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library - "Jules' Utility Class Extensions"
|
|
Copyright 2004-11 by Raw Material Software Ltd.
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
JUCE can be redistributed and/or modified under the terms of the GNU General
|
|
Public License (Version 2), as published by the Free Software Foundation.
|
|
A copy of the license is included in the JUCE distribution, or can be found
|
|
online at www.gnu.org/licenses.
|
|
|
|
JUCE 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 General Public License for more details.
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
To release a closed-source product which uses JUCE, commercial licenses are
|
|
available: visit www.rawmaterialsoftware.com/juce for more information.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
BEGIN_JUCE_NAMESPACE
|
|
|
|
//==============================================================================
|
|
class MidiKeyboardUpDownButton : public Button
|
|
{
|
|
public:
|
|
MidiKeyboardUpDownButton (MidiKeyboardComponent& owner_, const int delta_)
|
|
: Button (String::empty),
|
|
owner (owner_),
|
|
delta (delta_)
|
|
{
|
|
setOpaque (true);
|
|
}
|
|
|
|
void clicked()
|
|
{
|
|
int note = owner.getLowestVisibleKey();
|
|
|
|
if (delta < 0)
|
|
note = (note - 1) / 12;
|
|
else
|
|
note = note / 12 + 1;
|
|
|
|
owner.setLowestVisibleKey (note * 12);
|
|
}
|
|
|
|
void paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown)
|
|
{
|
|
owner.drawUpDownButton (g, getWidth(), getHeight(),
|
|
isMouseOverButton, isButtonDown,
|
|
delta > 0);
|
|
}
|
|
|
|
private:
|
|
MidiKeyboardComponent& owner;
|
|
const int delta;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE (MidiKeyboardUpDownButton);
|
|
};
|
|
|
|
//==============================================================================
|
|
MidiKeyboardComponent::MidiKeyboardComponent (MidiKeyboardState& state_,
|
|
const Orientation orientation_)
|
|
: state (state_),
|
|
xOffset (0),
|
|
blackNoteLength (1),
|
|
keyWidth (16.0f),
|
|
orientation (orientation_),
|
|
midiChannel (1),
|
|
midiInChannelMask (0xffff),
|
|
velocity (1.0f),
|
|
noteUnderMouse (-1),
|
|
mouseDownNote (-1),
|
|
rangeStart (0),
|
|
rangeEnd (127),
|
|
firstKey (12 * 4),
|
|
canScroll (true),
|
|
mouseDragging (false),
|
|
useMousePositionForVelocity (true),
|
|
keyMappingOctave (6),
|
|
octaveNumForMiddleC (3)
|
|
{
|
|
addChildComponent (scrollDown = new MidiKeyboardUpDownButton (*this, -1));
|
|
addChildComponent (scrollUp = new MidiKeyboardUpDownButton (*this, 1));
|
|
|
|
// initialise with a default set of querty key-mappings..
|
|
const char* const keymap = "awsedftgyhujkolp;";
|
|
|
|
for (int i = String (keymap).length(); --i >= 0;)
|
|
setKeyPressForNote (KeyPress (keymap[i], 0, 0), i);
|
|
|
|
setOpaque (true);
|
|
setWantsKeyboardFocus (true);
|
|
|
|
state.addListener (this);
|
|
}
|
|
|
|
MidiKeyboardComponent::~MidiKeyboardComponent()
|
|
{
|
|
state.removeListener (this);
|
|
jassert (mouseDownNote < 0 && keysPressed.countNumberOfSetBits() == 0); // leaving stuck notes!
|
|
}
|
|
|
|
//==============================================================================
|
|
void MidiKeyboardComponent::setKeyWidth (const float widthInPixels)
|
|
{
|
|
keyWidth = widthInPixels;
|
|
resized();
|
|
}
|
|
|
|
void MidiKeyboardComponent::setOrientation (const Orientation newOrientation)
|
|
{
|
|
if (orientation != newOrientation)
|
|
{
|
|
orientation = newOrientation;
|
|
resized();
|
|
}
|
|
}
|
|
|
|
void MidiKeyboardComponent::setAvailableRange (const int lowestNote,
|
|
const int highestNote)
|
|
{
|
|
jassert (lowestNote >= 0 && lowestNote <= 127);
|
|
jassert (highestNote >= 0 && highestNote <= 127);
|
|
jassert (lowestNote <= highestNote);
|
|
|
|
if (rangeStart != lowestNote || rangeEnd != highestNote)
|
|
{
|
|
rangeStart = jlimit (0, 127, lowestNote);
|
|
rangeEnd = jlimit (0, 127, highestNote);
|
|
firstKey = jlimit (rangeStart, rangeEnd, firstKey);
|
|
resized();
|
|
}
|
|
}
|
|
|
|
void MidiKeyboardComponent::setLowestVisibleKey (int noteNumber)
|
|
{
|
|
noteNumber = jlimit (rangeStart, rangeEnd, noteNumber);
|
|
|
|
if (noteNumber != firstKey)
|
|
{
|
|
firstKey = noteNumber;
|
|
sendChangeMessage();
|
|
resized();
|
|
}
|
|
}
|
|
|
|
void MidiKeyboardComponent::setScrollButtonsVisible (const bool canScroll_)
|
|
{
|
|
if (canScroll != canScroll_)
|
|
{
|
|
canScroll = canScroll_;
|
|
resized();
|
|
}
|
|
}
|
|
|
|
void MidiKeyboardComponent::colourChanged()
|
|
{
|
|
repaint();
|
|
}
|
|
|
|
//==============================================================================
|
|
void MidiKeyboardComponent::setMidiChannel (const int midiChannelNumber)
|
|
{
|
|
jassert (midiChannelNumber > 0 && midiChannelNumber <= 16);
|
|
|
|
if (midiChannel != midiChannelNumber)
|
|
{
|
|
resetAnyKeysInUse();
|
|
midiChannel = jlimit (1, 16, midiChannelNumber);
|
|
}
|
|
}
|
|
|
|
void MidiKeyboardComponent::setMidiChannelsToDisplay (const int midiChannelMask)
|
|
{
|
|
midiInChannelMask = midiChannelMask;
|
|
triggerAsyncUpdate();
|
|
}
|
|
|
|
void MidiKeyboardComponent::setVelocity (const float velocity_, const bool useMousePositionForVelocity_)
|
|
{
|
|
velocity = jlimit (0.0f, 1.0f, velocity_);
|
|
useMousePositionForVelocity = useMousePositionForVelocity_;
|
|
}
|
|
|
|
//==============================================================================
|
|
void MidiKeyboardComponent::getKeyPosition (int midiNoteNumber, const float keyWidth_, int& x, int& w) const
|
|
{
|
|
jassert (midiNoteNumber >= 0 && midiNoteNumber < 128);
|
|
|
|
static const float blackNoteWidth = 0.7f;
|
|
|
|
static const float notePos[] = { 0.0f, 1 - blackNoteWidth * 0.6f,
|
|
1.0f, 2 - blackNoteWidth * 0.4f,
|
|
2.0f, 3.0f, 4 - blackNoteWidth * 0.7f,
|
|
4.0f, 5 - blackNoteWidth * 0.5f,
|
|
5.0f, 6 - blackNoteWidth * 0.3f,
|
|
6.0f };
|
|
|
|
static const float widths[] = { 1.0f, blackNoteWidth,
|
|
1.0f, blackNoteWidth,
|
|
1.0f, 1.0f, blackNoteWidth,
|
|
1.0f, blackNoteWidth,
|
|
1.0f, blackNoteWidth,
|
|
1.0f };
|
|
|
|
const int octave = midiNoteNumber / 12;
|
|
const int note = midiNoteNumber % 12;
|
|
|
|
x = roundToInt (octave * 7.0f * keyWidth_ + notePos [note] * keyWidth_);
|
|
w = roundToInt (widths [note] * keyWidth_);
|
|
}
|
|
|
|
void MidiKeyboardComponent::getKeyPos (int midiNoteNumber, int& x, int& w) const
|
|
{
|
|
getKeyPosition (midiNoteNumber, keyWidth, x, w);
|
|
|
|
int rx, rw;
|
|
getKeyPosition (rangeStart, keyWidth, rx, rw);
|
|
|
|
x -= xOffset + rx;
|
|
}
|
|
|
|
int MidiKeyboardComponent::getKeyStartPosition (const int midiNoteNumber) const
|
|
{
|
|
int x, y;
|
|
getKeyPos (midiNoteNumber, x, y);
|
|
return x;
|
|
}
|
|
|
|
const uint8 MidiKeyboardComponent::whiteNotes[] = { 0, 2, 4, 5, 7, 9, 11 };
|
|
const uint8 MidiKeyboardComponent::blackNotes[] = { 1, 3, 6, 8, 10 };
|
|
|
|
int MidiKeyboardComponent::xyToNote (const Point<int>& pos, float& mousePositionVelocity)
|
|
{
|
|
if (! reallyContains (pos, false))
|
|
return -1;
|
|
|
|
Point<int> p (pos);
|
|
|
|
if (orientation != horizontalKeyboard)
|
|
{
|
|
p = Point<int> (p.getY(), p.getX());
|
|
|
|
if (orientation == verticalKeyboardFacingLeft)
|
|
p = Point<int> (p.getX(), getWidth() - p.getY());
|
|
else
|
|
p = Point<int> (getHeight() - p.getX(), p.getY());
|
|
}
|
|
|
|
return remappedXYToNote (p + Point<int> (xOffset, 0), mousePositionVelocity);
|
|
}
|
|
|
|
int MidiKeyboardComponent::remappedXYToNote (const Point<int>& pos, float& mousePositionVelocity) const
|
|
{
|
|
if (pos.getY() < blackNoteLength)
|
|
{
|
|
for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12)
|
|
{
|
|
for (int i = 0; i < 5; ++i)
|
|
{
|
|
const int note = octaveStart + blackNotes [i];
|
|
|
|
if (note >= rangeStart && note <= rangeEnd)
|
|
{
|
|
int kx, kw;
|
|
getKeyPos (note, kx, kw);
|
|
kx += xOffset;
|
|
|
|
if (pos.getX() >= kx && pos.getX() < kx + kw)
|
|
{
|
|
mousePositionVelocity = pos.getY() / (float) blackNoteLength;
|
|
return note;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12)
|
|
{
|
|
for (int i = 0; i < 7; ++i)
|
|
{
|
|
const int note = octaveStart + whiteNotes [i];
|
|
|
|
if (note >= rangeStart && note <= rangeEnd)
|
|
{
|
|
int kx, kw;
|
|
getKeyPos (note, kx, kw);
|
|
kx += xOffset;
|
|
|
|
if (pos.getX() >= kx && pos.getX() < kx + kw)
|
|
{
|
|
const int whiteNoteLength = (orientation == horizontalKeyboard) ? getHeight() : getWidth();
|
|
mousePositionVelocity = pos.getY() / (float) whiteNoteLength;
|
|
return note;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mousePositionVelocity = 0;
|
|
return -1;
|
|
}
|
|
|
|
//==============================================================================
|
|
void MidiKeyboardComponent::repaintNote (const int noteNum)
|
|
{
|
|
if (noteNum >= rangeStart && noteNum <= rangeEnd)
|
|
{
|
|
int x, w;
|
|
getKeyPos (noteNum, x, w);
|
|
|
|
if (orientation == horizontalKeyboard)
|
|
repaint (x, 0, w, getHeight());
|
|
else if (orientation == verticalKeyboardFacingLeft)
|
|
repaint (0, x, getWidth(), w);
|
|
else if (orientation == verticalKeyboardFacingRight)
|
|
repaint (0, getHeight() - x - w, getWidth(), w);
|
|
}
|
|
}
|
|
|
|
void MidiKeyboardComponent::paint (Graphics& g)
|
|
{
|
|
g.fillAll (Colours::white.overlaidWith (findColour (whiteNoteColourId)));
|
|
|
|
const Colour lineColour (findColour (keySeparatorLineColourId));
|
|
const Colour textColour (findColour (textLabelColourId));
|
|
|
|
int x, w, octave;
|
|
|
|
for (octave = 0; octave < 128; octave += 12)
|
|
{
|
|
for (int white = 0; white < 7; ++white)
|
|
{
|
|
const int noteNum = octave + whiteNotes [white];
|
|
|
|
if (noteNum >= rangeStart && noteNum <= rangeEnd)
|
|
{
|
|
getKeyPos (noteNum, x, w);
|
|
|
|
if (orientation == horizontalKeyboard)
|
|
drawWhiteNote (noteNum, g, x, 0, w, getHeight(),
|
|
state.isNoteOnForChannels (midiInChannelMask, noteNum),
|
|
noteUnderMouse == noteNum,
|
|
lineColour, textColour);
|
|
else if (orientation == verticalKeyboardFacingLeft)
|
|
drawWhiteNote (noteNum, g, 0, x, getWidth(), w,
|
|
state.isNoteOnForChannels (midiInChannelMask, noteNum),
|
|
noteUnderMouse == noteNum,
|
|
lineColour, textColour);
|
|
else if (orientation == verticalKeyboardFacingRight)
|
|
drawWhiteNote (noteNum, g, 0, getHeight() - x - w, getWidth(), w,
|
|
state.isNoteOnForChannels (midiInChannelMask, noteNum),
|
|
noteUnderMouse == noteNum,
|
|
lineColour, textColour);
|
|
}
|
|
}
|
|
}
|
|
|
|
float x1 = 0.0f, y1 = 0.0f, x2 = 0.0f, y2 = 0.0f;
|
|
|
|
if (orientation == verticalKeyboardFacingLeft)
|
|
{
|
|
x1 = getWidth() - 1.0f;
|
|
x2 = getWidth() - 5.0f;
|
|
}
|
|
else if (orientation == verticalKeyboardFacingRight)
|
|
x2 = 5.0f;
|
|
else
|
|
y2 = 5.0f;
|
|
|
|
g.setGradientFill (ColourGradient (Colours::black.withAlpha (0.3f), x1, y1,
|
|
Colours::transparentBlack, x2, y2, false));
|
|
|
|
getKeyPos (rangeEnd, x, w);
|
|
x += w;
|
|
|
|
if (orientation == verticalKeyboardFacingLeft)
|
|
g.fillRect (getWidth() - 5, 0, 5, x);
|
|
else if (orientation == verticalKeyboardFacingRight)
|
|
g.fillRect (0, 0, 5, x);
|
|
else
|
|
g.fillRect (0, 0, x, 5);
|
|
|
|
g.setColour (lineColour);
|
|
|
|
if (orientation == verticalKeyboardFacingLeft)
|
|
g.fillRect (0, 0, 1, x);
|
|
else if (orientation == verticalKeyboardFacingRight)
|
|
g.fillRect (getWidth() - 1, 0, 1, x);
|
|
else
|
|
g.fillRect (0, getHeight() - 1, x, 1);
|
|
|
|
const Colour blackNoteColour (findColour (blackNoteColourId));
|
|
|
|
for (octave = 0; octave < 128; octave += 12)
|
|
{
|
|
for (int black = 0; black < 5; ++black)
|
|
{
|
|
const int noteNum = octave + blackNotes [black];
|
|
|
|
if (noteNum >= rangeStart && noteNum <= rangeEnd)
|
|
{
|
|
getKeyPos (noteNum, x, w);
|
|
|
|
if (orientation == horizontalKeyboard)
|
|
drawBlackNote (noteNum, g, x, 0, w, blackNoteLength,
|
|
state.isNoteOnForChannels (midiInChannelMask, noteNum),
|
|
noteUnderMouse == noteNum,
|
|
blackNoteColour);
|
|
else if (orientation == verticalKeyboardFacingLeft)
|
|
drawBlackNote (noteNum, g, getWidth() - blackNoteLength, x, blackNoteLength, w,
|
|
state.isNoteOnForChannels (midiInChannelMask, noteNum),
|
|
noteUnderMouse == noteNum,
|
|
blackNoteColour);
|
|
else if (orientation == verticalKeyboardFacingRight)
|
|
drawBlackNote (noteNum, g, 0, getHeight() - x - w, blackNoteLength, w,
|
|
state.isNoteOnForChannels (midiInChannelMask, noteNum),
|
|
noteUnderMouse == noteNum,
|
|
blackNoteColour);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MidiKeyboardComponent::drawWhiteNote (int midiNoteNumber,
|
|
Graphics& g, int x, int y, int w, int h,
|
|
bool isDown, bool isOver,
|
|
const Colour& lineColour,
|
|
const Colour& textColour)
|
|
{
|
|
Colour c (Colours::transparentWhite);
|
|
|
|
if (isDown)
|
|
c = findColour (keyDownOverlayColourId);
|
|
|
|
if (isOver)
|
|
c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
|
|
|
|
g.setColour (c);
|
|
g.fillRect (x, y, w, h);
|
|
|
|
const String text (getWhiteNoteText (midiNoteNumber));
|
|
|
|
if (! text.isEmpty())
|
|
{
|
|
g.setColour (textColour);
|
|
|
|
Font f (jmin (12.0f, keyWidth * 0.9f));
|
|
f.setHorizontalScale (0.8f);
|
|
g.setFont (f);
|
|
Justification justification (Justification::centredBottom);
|
|
|
|
if (orientation == verticalKeyboardFacingLeft)
|
|
justification = Justification::centredLeft;
|
|
else if (orientation == verticalKeyboardFacingRight)
|
|
justification = Justification::centredRight;
|
|
|
|
g.drawFittedText (text, x + 2, y + 2, w - 4, h - 4, justification, 1);
|
|
}
|
|
|
|
g.setColour (lineColour);
|
|
|
|
if (orientation == horizontalKeyboard)
|
|
g.fillRect (x, y, 1, h);
|
|
else if (orientation == verticalKeyboardFacingLeft)
|
|
g.fillRect (x, y, w, 1);
|
|
else if (orientation == verticalKeyboardFacingRight)
|
|
g.fillRect (x, y + h - 1, w, 1);
|
|
|
|
if (midiNoteNumber == rangeEnd)
|
|
{
|
|
if (orientation == horizontalKeyboard)
|
|
g.fillRect (x + w, y, 1, h);
|
|
else if (orientation == verticalKeyboardFacingLeft)
|
|
g.fillRect (x, y + h, w, 1);
|
|
else if (orientation == verticalKeyboardFacingRight)
|
|
g.fillRect (x, y - 1, w, 1);
|
|
}
|
|
}
|
|
|
|
void MidiKeyboardComponent::drawBlackNote (int /*midiNoteNumber*/,
|
|
Graphics& g, int x, int y, int w, int h,
|
|
bool isDown, bool isOver,
|
|
const Colour& noteFillColour)
|
|
{
|
|
Colour c (noteFillColour);
|
|
|
|
if (isDown)
|
|
c = c.overlaidWith (findColour (keyDownOverlayColourId));
|
|
|
|
if (isOver)
|
|
c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId));
|
|
|
|
g.setColour (c);
|
|
g.fillRect (x, y, w, h);
|
|
|
|
if (isDown)
|
|
{
|
|
g.setColour (noteFillColour);
|
|
g.drawRect (x, y, w, h);
|
|
}
|
|
else
|
|
{
|
|
const int xIndent = jmax (1, jmin (w, h) / 8);
|
|
|
|
g.setColour (c.brighter());
|
|
|
|
if (orientation == horizontalKeyboard)
|
|
g.fillRect (x + xIndent, y, w - xIndent * 2, 7 * h / 8);
|
|
else if (orientation == verticalKeyboardFacingLeft)
|
|
g.fillRect (x + w / 8, y + xIndent, w - w / 8, h - xIndent * 2);
|
|
else if (orientation == verticalKeyboardFacingRight)
|
|
g.fillRect (x, y + xIndent, 7 * w / 8, h - xIndent * 2);
|
|
}
|
|
}
|
|
|
|
void MidiKeyboardComponent::setOctaveForMiddleC (const int octaveNumForMiddleC_)
|
|
{
|
|
octaveNumForMiddleC = octaveNumForMiddleC_;
|
|
repaint();
|
|
}
|
|
|
|
String MidiKeyboardComponent::getWhiteNoteText (const int midiNoteNumber)
|
|
{
|
|
if (keyWidth > 14.0f && midiNoteNumber % 12 == 0)
|
|
return MidiMessage::getMidiNoteName (midiNoteNumber, true, true, octaveNumForMiddleC);
|
|
|
|
return String::empty;
|
|
}
|
|
|
|
void MidiKeyboardComponent::drawUpDownButton (Graphics& g, int w, int h,
|
|
const bool isMouseOver_,
|
|
const bool isButtonDown,
|
|
const bool movesOctavesUp)
|
|
{
|
|
g.fillAll (findColour (upDownButtonBackgroundColourId));
|
|
|
|
float angle;
|
|
|
|
if (orientation == MidiKeyboardComponent::horizontalKeyboard)
|
|
angle = movesOctavesUp ? 0.0f : 0.5f;
|
|
else if (orientation == MidiKeyboardComponent::verticalKeyboardFacingLeft)
|
|
angle = movesOctavesUp ? 0.25f : 0.75f;
|
|
else
|
|
angle = movesOctavesUp ? 0.75f : 0.25f;
|
|
|
|
Path path;
|
|
path.lineTo (0.0f, 1.0f);
|
|
path.lineTo (1.0f, 0.5f);
|
|
path.closeSubPath();
|
|
|
|
path.applyTransform (AffineTransform::rotation (float_Pi * 2.0f * angle, 0.5f, 0.5f));
|
|
|
|
g.setColour (findColour (upDownButtonArrowColourId)
|
|
.withAlpha (isButtonDown ? 1.0f : (isMouseOver_ ? 0.6f : 0.4f)));
|
|
|
|
g.fillPath (path, path.getTransformToScaleToFit (1.0f, 1.0f,
|
|
w - 2.0f,
|
|
h - 2.0f,
|
|
true));
|
|
}
|
|
|
|
void MidiKeyboardComponent::resized()
|
|
{
|
|
int w = getWidth();
|
|
int h = getHeight();
|
|
|
|
if (w > 0 && h > 0)
|
|
{
|
|
if (orientation != horizontalKeyboard)
|
|
std::swap (w, h);
|
|
|
|
blackNoteLength = roundToInt (h * 0.7f);
|
|
|
|
int kx2, kw2;
|
|
getKeyPos (rangeEnd, kx2, kw2);
|
|
|
|
kx2 += kw2;
|
|
|
|
if (firstKey != rangeStart)
|
|
{
|
|
int kx1, kw1;
|
|
getKeyPos (rangeStart, kx1, kw1);
|
|
|
|
if (kx2 - kx1 <= w)
|
|
{
|
|
firstKey = rangeStart;
|
|
sendChangeMessage();
|
|
repaint();
|
|
}
|
|
}
|
|
|
|
const bool showScrollButtons = canScroll && (firstKey > rangeStart || kx2 > w + xOffset * 2);
|
|
|
|
scrollDown->setVisible (showScrollButtons);
|
|
scrollUp->setVisible (showScrollButtons);
|
|
|
|
xOffset = 0;
|
|
|
|
if (showScrollButtons)
|
|
{
|
|
const int scrollButtonW = jmin (12, w / 2);
|
|
|
|
if (orientation == horizontalKeyboard)
|
|
{
|
|
scrollDown->setBounds (0, 0, scrollButtonW, getHeight());
|
|
scrollUp->setBounds (getWidth() - scrollButtonW, 0, scrollButtonW, getHeight());
|
|
}
|
|
else if (orientation == verticalKeyboardFacingLeft)
|
|
{
|
|
scrollDown->setBounds (0, 0, getWidth(), scrollButtonW);
|
|
scrollUp->setBounds (0, getHeight() - scrollButtonW, getWidth(), scrollButtonW);
|
|
}
|
|
else if (orientation == verticalKeyboardFacingRight)
|
|
{
|
|
scrollDown->setBounds (0, getHeight() - scrollButtonW, getWidth(), scrollButtonW);
|
|
scrollUp->setBounds (0, 0, getWidth(), scrollButtonW);
|
|
}
|
|
|
|
int endOfLastKey, kw;
|
|
getKeyPos (rangeEnd, endOfLastKey, kw);
|
|
endOfLastKey += kw;
|
|
|
|
float mousePositionVelocity;
|
|
const int spaceAvailable = w - scrollButtonW * 2;
|
|
const int lastStartKey = remappedXYToNote (Point<int> (endOfLastKey - spaceAvailable, 0), mousePositionVelocity) + 1;
|
|
|
|
if (lastStartKey >= 0 && firstKey > lastStartKey)
|
|
{
|
|
firstKey = jlimit (rangeStart, rangeEnd, lastStartKey);
|
|
sendChangeMessage();
|
|
}
|
|
|
|
int newOffset = 0;
|
|
getKeyPos (firstKey, newOffset, kw);
|
|
xOffset = newOffset - scrollButtonW;
|
|
}
|
|
else
|
|
{
|
|
firstKey = rangeStart;
|
|
}
|
|
|
|
timerCallback();
|
|
repaint();
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
void MidiKeyboardComponent::handleNoteOn (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/, float /*velocity*/)
|
|
{
|
|
triggerAsyncUpdate();
|
|
}
|
|
|
|
void MidiKeyboardComponent::handleNoteOff (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/)
|
|
{
|
|
triggerAsyncUpdate();
|
|
}
|
|
|
|
void MidiKeyboardComponent::handleAsyncUpdate()
|
|
{
|
|
for (int i = rangeStart; i <= rangeEnd; ++i)
|
|
{
|
|
if (keysCurrentlyDrawnDown[i] != state.isNoteOnForChannels (midiInChannelMask, i))
|
|
{
|
|
keysCurrentlyDrawnDown.setBit (i, state.isNoteOnForChannels (midiInChannelMask, i));
|
|
repaintNote (i);
|
|
}
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
void MidiKeyboardComponent::resetAnyKeysInUse()
|
|
{
|
|
if (keysPressed.countNumberOfSetBits() > 0 || mouseDownNote > 0)
|
|
{
|
|
state.allNotesOff (midiChannel);
|
|
keysPressed.clear();
|
|
mouseDownNote = -1;
|
|
}
|
|
}
|
|
|
|
void MidiKeyboardComponent::updateNoteUnderMouse (const Point<int>& pos)
|
|
{
|
|
float mousePositionVelocity = 0.0f;
|
|
const int newNote = (mouseDragging || isMouseOver())
|
|
? xyToNote (pos, mousePositionVelocity) : -1;
|
|
|
|
if (noteUnderMouse != newNote)
|
|
{
|
|
if (mouseDownNote >= 0)
|
|
{
|
|
state.noteOff (midiChannel, mouseDownNote);
|
|
mouseDownNote = -1;
|
|
}
|
|
|
|
if (mouseDragging && newNote >= 0)
|
|
{
|
|
if (! useMousePositionForVelocity)
|
|
mousePositionVelocity = 1.0f;
|
|
|
|
state.noteOn (midiChannel, newNote, mousePositionVelocity * velocity);
|
|
mouseDownNote = newNote;
|
|
}
|
|
|
|
repaintNote (noteUnderMouse);
|
|
noteUnderMouse = newNote;
|
|
repaintNote (noteUnderMouse);
|
|
}
|
|
else if (mouseDownNote >= 0 && ! mouseDragging)
|
|
{
|
|
state.noteOff (midiChannel, mouseDownNote);
|
|
mouseDownNote = -1;
|
|
}
|
|
}
|
|
|
|
void MidiKeyboardComponent::mouseMove (const MouseEvent& e)
|
|
{
|
|
updateNoteUnderMouse (e.getPosition());
|
|
stopTimer();
|
|
}
|
|
|
|
void MidiKeyboardComponent::mouseDrag (const MouseEvent& e)
|
|
{
|
|
float mousePositionVelocity;
|
|
const int newNote = xyToNote (e.getPosition(), mousePositionVelocity);
|
|
|
|
if (newNote >= 0)
|
|
mouseDraggedToKey (newNote, e);
|
|
|
|
updateNoteUnderMouse (e.getPosition());
|
|
}
|
|
|
|
bool MidiKeyboardComponent::mouseDownOnKey (int /*midiNoteNumber*/, const MouseEvent&)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void MidiKeyboardComponent::mouseDraggedToKey (int /*midiNoteNumber*/, const MouseEvent&)
|
|
{
|
|
}
|
|
|
|
void MidiKeyboardComponent::mouseDown (const MouseEvent& e)
|
|
{
|
|
float mousePositionVelocity;
|
|
const int newNote = xyToNote (e.getPosition(), mousePositionVelocity);
|
|
mouseDragging = false;
|
|
|
|
if (newNote >= 0 && mouseDownOnKey (newNote, e))
|
|
{
|
|
repaintNote (noteUnderMouse);
|
|
noteUnderMouse = -1;
|
|
mouseDragging = true;
|
|
|
|
updateNoteUnderMouse (e.getPosition());
|
|
startTimer (500);
|
|
}
|
|
}
|
|
|
|
void MidiKeyboardComponent::mouseUp (const MouseEvent& e)
|
|
{
|
|
mouseDragging = false;
|
|
updateNoteUnderMouse (e.getPosition());
|
|
|
|
stopTimer();
|
|
}
|
|
|
|
void MidiKeyboardComponent::mouseEnter (const MouseEvent& e)
|
|
{
|
|
updateNoteUnderMouse (e.getPosition());
|
|
}
|
|
|
|
void MidiKeyboardComponent::mouseExit (const MouseEvent& e)
|
|
{
|
|
updateNoteUnderMouse (e.getPosition());
|
|
}
|
|
|
|
void MidiKeyboardComponent::mouseWheelMove (const MouseEvent&, float ix, float iy)
|
|
{
|
|
setLowestVisibleKey (getLowestVisibleKey() + roundToInt ((ix != 0 ? ix : iy) * 5.0f));
|
|
}
|
|
|
|
void MidiKeyboardComponent::timerCallback()
|
|
{
|
|
updateNoteUnderMouse (getMouseXYRelative());
|
|
}
|
|
|
|
//==============================================================================
|
|
void MidiKeyboardComponent::clearKeyMappings()
|
|
{
|
|
resetAnyKeysInUse();
|
|
keyPressNotes.clear();
|
|
keyPresses.clear();
|
|
}
|
|
|
|
void MidiKeyboardComponent::setKeyPressForNote (const KeyPress& key,
|
|
const int midiNoteOffsetFromC)
|
|
{
|
|
removeKeyPressForNote (midiNoteOffsetFromC);
|
|
|
|
keyPressNotes.add (midiNoteOffsetFromC);
|
|
keyPresses.add (key);
|
|
}
|
|
|
|
void MidiKeyboardComponent::removeKeyPressForNote (const int midiNoteOffsetFromC)
|
|
{
|
|
for (int i = keyPressNotes.size(); --i >= 0;)
|
|
{
|
|
if (keyPressNotes.getUnchecked (i) == midiNoteOffsetFromC)
|
|
{
|
|
keyPressNotes.remove (i);
|
|
keyPresses.remove (i);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MidiKeyboardComponent::setKeyPressBaseOctave (const int newOctaveNumber)
|
|
{
|
|
jassert (newOctaveNumber >= 0 && newOctaveNumber <= 10);
|
|
|
|
keyMappingOctave = newOctaveNumber;
|
|
}
|
|
|
|
bool MidiKeyboardComponent::keyStateChanged (const bool /*isKeyDown*/)
|
|
{
|
|
bool keyPressUsed = false;
|
|
|
|
for (int i = keyPresses.size(); --i >= 0;)
|
|
{
|
|
const int note = 12 * keyMappingOctave + keyPressNotes.getUnchecked (i);
|
|
|
|
if (keyPresses.getReference(i).isCurrentlyDown())
|
|
{
|
|
if (! keysPressed [note])
|
|
{
|
|
keysPressed.setBit (note);
|
|
state.noteOn (midiChannel, note, velocity);
|
|
keyPressUsed = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (keysPressed [note])
|
|
{
|
|
keysPressed.clearBit (note);
|
|
state.noteOff (midiChannel, note);
|
|
keyPressUsed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return keyPressUsed;
|
|
}
|
|
|
|
void MidiKeyboardComponent::focusLost (FocusChangeType)
|
|
{
|
|
resetAnyKeysInUse();
|
|
}
|
|
|
|
|
|
END_JUCE_NAMESPACE
|