|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2022 - Raw Material Software Limited
-
- JUCE is an open source library subject to commercial or open-source
- licensing.
-
- By using JUCE, you agree to the terms of both the JUCE 7 End-User License
- Agreement and JUCE Privacy Policy.
-
- End User License Agreement: www.juce.com/juce-7-licence
- Privacy Policy: www.juce.com/juce-privacy-policy
-
- Or: You may also use this code under the terms of the GPL v3 (see
- www.gnu.org/licenses).
-
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
-
- ==============================================================================
- */
-
- namespace juce
- {
-
- struct MPEKeyboardComponent::MPENoteComponent : public Component
- {
- MPENoteComponent (MPEKeyboardComponent& o, uint16 sID, uint8 initial, float noteOnVel, float press)
- : owner (o),
- radiusScale (owner.getKeyWidth() / 1.5f),
- noteOnVelocity (noteOnVel),
- pressure (press),
- sourceID (sID),
- initialNote (initial)
- {
- }
-
- float getStrikeRadius() const { return 5.0f + getNoteOnVelocity() * radiusScale * 2.0f; }
- float getPressureRadius() const { return 5.0f + getPressure() * radiusScale * 2.0f; }
-
- float getNoteOnVelocity() const { return noteOnVelocity; }
- float getPressure() const { return pressure; }
-
- Point<float> getCentrePos() const { return getBounds().toFloat().getCentre(); }
-
- void paint (Graphics& g) override
- {
- auto strikeSize = getStrikeRadius() * 2.0f;
- auto pressSize = getPressureRadius() * 2.0f;
- auto bounds = getLocalBounds().toFloat();
-
- g.setColour (owner.findColour (noteCircleFillColourId));
- g.fillEllipse (bounds.withSizeKeepingCentre (strikeSize, strikeSize));
-
- g.setColour (owner.findColour (noteCircleOutlineColourId));
- g.drawEllipse (bounds.withSizeKeepingCentre (pressSize, pressSize), 1.0f);
- }
-
- //==========================================================================
- MPEKeyboardComponent& owner;
-
- float radiusScale = 0.0f, noteOnVelocity = 0.0f, pressure = 0.5f;
- uint16 sourceID = 0;
- uint8 initialNote = 0;
- bool isLatched = true;
- };
-
- //==============================================================================
- MPEKeyboardComponent::MPEKeyboardComponent (MPEInstrument& instr, Orientation orientationToUse)
- : KeyboardComponentBase (orientationToUse),
- instrument (instr)
- {
- updateZoneLayout();
- colourChanged();
- setKeyWidth (25.0f);
-
- instrument.addListener (this);
- }
-
- MPEKeyboardComponent::~MPEKeyboardComponent()
- {
- instrument.removeListener (this);
- }
-
- //==============================================================================
- void MPEKeyboardComponent::drawKeyboardBackground (Graphics& g, Rectangle<float> area)
- {
- g.setColour (findColour (whiteNoteColourId));
- g.fillRect (area);
- }
-
- void MPEKeyboardComponent::drawWhiteKey (int midiNoteNumber, Graphics& g, Rectangle<float> area)
- {
- if (midiNoteNumber % 12 == 0)
- {
- auto fontHeight = jmin (12.0f, getKeyWidth() * 0.9f);
- auto text = MidiMessage::getMidiNoteName (midiNoteNumber, true, true, getOctaveForMiddleC());
-
- g.setColour (findColour (textLabelColourId));
- g.setFont (Font (fontHeight).withHorizontalScale (0.8f));
-
- switch (getOrientation())
- {
- case horizontalKeyboard:
- g.drawText (text, area.withTrimmedLeft (1.0f).withTrimmedBottom (2.0f),
- Justification::centredBottom, false);
- break;
- case verticalKeyboardFacingLeft:
- g.drawText (text, area.reduced (2.0f), Justification::centredLeft, false);
- break;
- case verticalKeyboardFacingRight:
- g.drawText (text, area.reduced (2.0f), Justification::centredRight, false);
- break;
- default:
- break;
- }
- }
- }
-
- void MPEKeyboardComponent::drawBlackKey (int /*midiNoteNumber*/, Graphics& g, Rectangle<float> area)
- {
- g.setColour (findColour (whiteNoteColourId));
- g.fillRect (area);
-
- g.setColour (findColour (blackNoteColourId));
-
- if (isHorizontal())
- {
- g.fillRoundedRectangle (area.toFloat().reduced ((area.getWidth() / 2.0f) - (getBlackNoteWidth() / 12.0f),
- area.getHeight() / 4.0f), 1.0f);
- }
- else
- {
- g.fillRoundedRectangle (area.toFloat().reduced (area.getWidth() / 4.0f,
- (area.getHeight() / 2.0f) - (getBlackNoteWidth() / 12.0f)), 1.0f);
- }
- }
-
- void MPEKeyboardComponent::colourChanged()
- {
- setOpaque (findColour (whiteNoteColourId).isOpaque());
- repaint();
- }
-
- //==========================================================================
- MPEValue MPEKeyboardComponent::mousePositionToPitchbend (int initialNote, Point<float> mousePos)
- {
- auto constrainedMousePos = [&]
- {
- auto horizontal = isHorizontal();
-
- auto posToCheck = jlimit (0.0f,
- horizontal ? (float) getWidth() - 1.0f : (float) getHeight(),
- horizontal ? mousePos.x : mousePos.y);
-
- auto bottomKeyRange = getRectangleForKey (jmax (getRangeStart(), initialNote - perNotePitchbendRange));
- auto topKeyRange = getRectangleForKey (jmin (getRangeEnd(), initialNote + perNotePitchbendRange));
-
- auto lowerLimit = horizontal ? bottomKeyRange.getCentreX()
- : getOrientation() == Orientation::verticalKeyboardFacingRight ? topKeyRange.getCentreY()
- : bottomKeyRange.getCentreY();
-
- auto upperLimit = horizontal ? topKeyRange.getCentreX()
- : getOrientation() == Orientation::verticalKeyboardFacingRight ? bottomKeyRange.getCentreY()
- : topKeyRange.getCentreY();
-
- posToCheck = jlimit (lowerLimit, upperLimit, posToCheck);
-
- return horizontal ? Point<float> (posToCheck, 0.0f)
- : Point<float> (0.0f, posToCheck);
- }();
-
- auto note = getNoteAndVelocityAtPosition (constrainedMousePos, true).note;
-
- if (note == -1)
- {
- jassertfalse;
- return {};
- }
-
- auto fractionalSemitoneBend = [&]
- {
- auto noteRect = getRectangleForKey (note);
-
- switch (getOrientation())
- {
- case horizontalKeyboard: return (constrainedMousePos.x - noteRect.getCentreX()) / noteRect.getWidth();
- case verticalKeyboardFacingRight: return (noteRect.getCentreY() - constrainedMousePos.y) / noteRect.getHeight();
- case verticalKeyboardFacingLeft: return (constrainedMousePos.y - noteRect.getCentreY()) / noteRect.getHeight();
- }
-
- jassertfalse;
- return 0.0f;
- }();
-
- auto totalNumSemitones = ((float) note + fractionalSemitoneBend) - (float) initialNote;
-
- return MPEValue::fromUnsignedFloat (jmap (totalNumSemitones, (float) -perNotePitchbendRange, (float) perNotePitchbendRange, 0.0f, 1.0f));
- }
-
- MPEValue MPEKeyboardComponent::mousePositionToTimbre (Point<float> mousePos)
- {
- auto delta = [mousePos, this]
- {
- switch (getOrientation())
- {
- case horizontalKeyboard: return mousePos.y;
- case verticalKeyboardFacingLeft: return (float) getWidth() - mousePos.x;
- case verticalKeyboardFacingRight: return mousePos.x;
- }
-
- jassertfalse;
- return 0.0f;
- }();
-
- return MPEValue::fromUnsignedFloat (jlimit (0.0f, 1.0f, 1.0f - (delta / getWhiteNoteLength())));
- }
-
- void MPEKeyboardComponent::mouseDown (const MouseEvent& e)
- {
- auto newNote = getNoteAndVelocityAtPosition (e.position).note;
-
- if (newNote >= 0)
- {
- auto channel = channelAssigner->findMidiChannelForNewNote (newNote);
-
- instrument.noteOn (channel, newNote, MPEValue::fromUnsignedFloat (velocity));
- sourceIDMap[e.source.getIndex()] = instrument.getNote (instrument.getNumPlayingNotes() - 1).noteID;
-
- instrument.pitchbend (channel, MPEValue::centreValue());
- instrument.timbre (channel, mousePositionToTimbre (e.position));
- instrument.pressure (channel, MPEValue::fromUnsignedFloat (e.isPressureValid()
- && useMouseSourcePressureForStrike ? e.pressure
- : pressure));
- }
- }
-
- void MPEKeyboardComponent::mouseDrag (const MouseEvent& e)
- {
- auto noteID = sourceIDMap[e.source.getIndex()];
- auto note = instrument.getNoteWithID (noteID);
-
- if (! note.isValid())
- return;
-
- auto noteComponent = std::find_if (noteComponents.begin(),
- noteComponents.end(),
- [noteID] (auto& comp) { return comp->sourceID == noteID; });
-
- if (noteComponent == noteComponents.end())
- return;
-
- if ((*noteComponent)->isLatched && std::abs (isHorizontal() ? e.getDistanceFromDragStartX()
- : e.getDistanceFromDragStartY()) > roundToInt (getKeyWidth() / 4.0f))
- {
- (*noteComponent)->isLatched = false;
- }
-
- auto channel = channelAssigner->findMidiChannelForExistingNote (note.initialNote);
-
- if (! (*noteComponent)->isLatched)
- instrument.pitchbend (channel, mousePositionToPitchbend (note.initialNote, e.position));
-
- instrument.timbre (channel, mousePositionToTimbre (e.position));
- instrument.pressure (channel, MPEValue::fromUnsignedFloat (e.isPressureValid()
- && useMouseSourcePressureForStrike ? e.pressure
- : pressure));
- }
-
- void MPEKeyboardComponent::mouseUp (const MouseEvent& e)
- {
- auto note = instrument.getNoteWithID (sourceIDMap[e.source.getIndex()]);
-
- if (! note.isValid())
- return;
-
- instrument.noteOff (channelAssigner->findMidiChannelForExistingNote (note.initialNote),
- note.initialNote, MPEValue::fromUnsignedFloat (lift));
- channelAssigner->noteOff (note.initialNote);
- sourceIDMap.erase (e.source.getIndex());
- }
-
- void MPEKeyboardComponent::focusLost (FocusChangeType)
- {
- for (auto& comp : noteComponents)
- {
- auto note = instrument.getNoteWithID (comp->sourceID);
-
- if (note.isValid())
- instrument.noteOff (channelAssigner->findMidiChannelForExistingNote (note.initialNote),
- note.initialNote, MPEValue::fromUnsignedFloat (lift));
- }
- }
-
- //==============================================================================
- void MPEKeyboardComponent::updateZoneLayout()
- {
- {
- const ScopedLock noteLock (activeNotesLock);
- activeNotes.clear();
- }
-
- noteComponents.clear();
-
- if (instrument.isLegacyModeEnabled())
- {
- channelAssigner = std::make_unique<MPEChannelAssigner> (instrument.getLegacyModeChannelRange());
- perNotePitchbendRange = instrument.getLegacyModePitchbendRange();
- }
- else
- {
- auto layout = instrument.getZoneLayout();
-
- if (layout.isActive())
- {
- auto zone = layout.getLowerZone().isActive() ? layout.getLowerZone()
- : layout.getUpperZone();
-
- channelAssigner = std::make_unique<MPEChannelAssigner> (zone);
- perNotePitchbendRange = zone.perNotePitchbendRange;
- }
- else
- {
- channelAssigner.reset();
- }
- }
- }
-
- void MPEKeyboardComponent::addNewNote (MPENote note)
- {
- noteComponents.push_back (std::make_unique<MPENoteComponent> (*this, note.noteID, note.initialNote,
- note.noteOnVelocity.asUnsignedFloat(),
- note.pressure.asUnsignedFloat()));
- auto& comp = noteComponents.back();
-
- addAndMakeVisible (*comp);
- comp->toBack();
- }
-
- void MPEKeyboardComponent::handleNoteOns (std::set<MPENote>& notesToUpdate)
- {
- for (auto& note : notesToUpdate)
- {
- if (! std::any_of (noteComponents.begin(),
- noteComponents.end(),
- [note] (auto& comp) { return comp->sourceID == note.noteID; }))
- {
- addNewNote (note);
- }
- }
- }
-
- void MPEKeyboardComponent::handleNoteOffs (std::set<MPENote>& notesToUpdate)
- {
- auto removePredicate = [¬esToUpdate] (std::unique_ptr<MPENoteComponent>& comp)
- {
- return std::none_of (notesToUpdate.begin(),
- notesToUpdate.end(),
- [&comp] (auto& note) { return comp->sourceID == note.noteID; });
- };
-
- noteComponents.erase (std::remove_if (std::begin (noteComponents),
- std::end (noteComponents),
- removePredicate),
- std::end (noteComponents));
-
- if (noteComponents.empty())
- stopTimer();
- }
-
- void MPEKeyboardComponent::updateNoteComponentBounds (const MPENote& note, MPENoteComponent& noteComponent)
- {
- auto xPos = [&]
- {
- const auto currentNote = note.initialNote + (float) note.totalPitchbendInSemitones;
- const auto noteBend = currentNote - std::floor (currentNote);
-
- const auto noteBounds = getRectangleForKey ((int) currentNote);
- const auto nextNoteBounds = getRectangleForKey ((int) currentNote + 1);
-
- const auto horizontal = isHorizontal();
-
- const auto distance = noteBend * (horizontal ? nextNoteBounds.getCentreX() - noteBounds.getCentreX()
- : nextNoteBounds.getCentreY() - noteBounds.getCentreY());
-
- return (horizontal ? noteBounds.getCentreX() : noteBounds.getCentreY()) + distance;
- }();
-
- auto yPos = [&]
- {
- const auto currentOrientation = getOrientation();
-
- const auto timbrePosition = (currentOrientation == horizontalKeyboard
- || currentOrientation == verticalKeyboardFacingRight ? 1.0f - note.timbre.asUnsignedFloat()
- : note.timbre.asUnsignedFloat());
-
- return timbrePosition * getWhiteNoteLength();
- }();
-
- const auto centrePos = (isHorizontal() ? Point<float> (xPos, yPos)
- : Point<float> (yPos, xPos));
-
- const auto radius = jmax (noteComponent.getStrikeRadius(), noteComponent.getPressureRadius());
-
- noteComponent.setBounds (Rectangle<float> (radius * 2.0f, radius * 2.0f)
- .withCentre (centrePos)
- .getSmallestIntegerContainer());
- }
-
- static bool operator< (const MPENote& n1, const MPENote& n2) noexcept { return n1.noteID < n2.noteID; }
-
- void MPEKeyboardComponent::updateNoteComponents()
- {
- std::set<MPENote> notesToUpdate;
-
- {
- ScopedLock noteLock (activeNotesLock);
-
- for (const auto& note : activeNotes)
- if (note.second)
- notesToUpdate.insert (note.first);
- };
-
- handleNoteOns (notesToUpdate);
- handleNoteOffs (notesToUpdate);
-
- for (auto& comp : noteComponents)
- {
- auto noteForComponent = std::find_if (notesToUpdate.begin(),
- notesToUpdate.end(),
- [&comp] (auto& note) { return note.noteID == comp->sourceID; });
-
- if (noteForComponent != notesToUpdate.end())
- {
- comp->pressure = noteForComponent->pressure.asUnsignedFloat();
- updateNoteComponentBounds (*noteForComponent, *comp);
-
- comp->repaint();
- }
- }
- }
-
- void MPEKeyboardComponent::timerCallback()
- {
- updateNoteComponents();
- }
-
- //==============================================================================
- void MPEKeyboardComponent::noteAdded (MPENote newNote)
- {
- {
- const ScopedLock noteLock (activeNotesLock);
- activeNotes.push_back ({ newNote, true });
- }
-
- startTimerHz (30);
- }
-
- void MPEKeyboardComponent::updateNoteData (MPENote& changedNote)
- {
- const ScopedLock noteLock (activeNotesLock);
-
- for (auto& note : activeNotes)
- {
- if (note.first.noteID == changedNote.noteID)
- {
- note.first = changedNote;
- note.second = true;
- return;
- }
- }
- }
-
- void MPEKeyboardComponent::notePressureChanged (MPENote changedNote)
- {
- updateNoteData (changedNote);
- }
-
- void MPEKeyboardComponent::notePitchbendChanged (MPENote changedNote)
- {
- updateNoteData (changedNote);
- }
-
- void MPEKeyboardComponent::noteTimbreChanged (MPENote changedNote)
- {
- updateNoteData (changedNote);
- }
-
- void MPEKeyboardComponent::noteReleased (MPENote finishedNote)
- {
- const ScopedLock noteLock (activeNotesLock);
-
- activeNotes.erase (std::remove_if (std::begin (activeNotes),
- std::end (activeNotes),
- [finishedNote] (auto& note) { return note.first.noteID == finishedNote.noteID; }),
- std::end (activeNotes));
- }
-
- void MPEKeyboardComponent::zoneLayoutChanged()
- {
- MessageManager::callAsync ([this] { updateZoneLayout(); });
- }
-
- } // namespace juce
|