Can getNoteAtPosition be called successfully from a different component?

I have a custom Midi Keyboard component. As a simple test, I called getNoteAtPosition from the mouseEnter event handler. It works fine.

class CustomMidiKeyboard : public juce::MidiKeyboardComponent
{
      ...
      void mouseEnter(const MouseEvent& e) override
      {                 
             int note = getNoteAtPosition(e.position);

       }

       private:
           int note;
 
};

However, when I call getNoteAtPosition from a custom control, -1 is returned

class CustomMidiKeyboard  : public juce::MidiKeyboardComponent
{
public:
    

    //=============== MY NESTED CLASS =====================
    class MyComponent : public juce::Component
    {
    public:
        MyComponent()=default;
        ~MyComponent() = default;

        void mouseDown(const MouseEvent& e) override
		{
			dragger.startDraggingComponent(this, e);
		}
		
		void mouseDrag(const MouseEvent& e) override
		{
		    // Convert the coordinates to the coordinates
			// of the Midi Keyboard
			//
			MouseEvent midiEvent = e.getEventRelativeTo(midi);

			// Now I pass the "Point<float>" object using the coordinates
			// of the Midi Keyboard
			int note = midi->getNoteAtPosition(midiEvent.position);
			
			// The value returned is -1;

			

			dragger.dragComponent(this, e, nullptr);



		}
        void paint(Graphics&)override
		{
		    // Draw component
		}

        void addMidiKeyboard(CustomMidiKeyboard *kb)
		{
		    midi = kb;
		}

    private:
        ComponentDragger dragger;
        CustomMidiKeyboard * midi;

    };
    //============================================
protected:
    


         
    void mouseEnter(const MouseEvent&) override
    {        
       // This works perfectly fine
       //        
        int note = getNoteAtPosition(e.position);
     }


    
    
   
};

At first glance, the MouseEvent position you are using is relative to the nested MyComponent, and not to the parent CustomMidiKeyboard (as it should)?

Ooops, sorry i didn’t notice you already use getEventRelativeTo to manage that. :partying_face:

I even tried hardcoding the values.

If you look at the code above. You’ll see that I also overrode “mouseEnter”.
I took the values from mouseEnter and used them in my mouseDrag method and -1 was still returned.

Odd.

The returned value probably comes from there.

if (! reallyContains (pos.toInt(), false))
    return -1;

Anyway it is hard to make more investigations without a minimal project to debug.

I’d be more than happy to upload a project later today.

1 Like

Sample.zip (4.7 KB)

Sample code zipped and uploaded. It’s a minimal project.

CustomMidiKeyboard.h

		void mouseDrag(const juce::MouseEvent& e) override
	{
		// Convert the coordinates to the coordinates
		// of the Midi Keyboard
		//
		juce::MouseEvent midiEvent = e.getEventRelativeTo(midi);

		// Now I pass the "Point<float>" object using the coordinates
		// of the Midi Keyboard
		int note = midi->getNoteAtPosition(midiEvent.position);

		// The value returned is -1;



		dragger.dragComponent(this, e, nullptr);



	}

CustomMidiKeyboard.cpp
void CustomMidiKeyboard::mouseEnter(const juce::MouseEvent& e)
{
// Put a break point here.
// You’ll see a legit value returned
auto note = getNoteAtPosition(e.position);
}

1 Like

I don’t have the time right-now, i’ll have a deeper look tomorrow (if nobody find what’s hap before). :wink:

Your “MyComponent” is disposed in front of the keyboard. In the code i posted before …

if (! reallyContains (pos.toInt(), false))
    return -1;

… we can see that the keyboard component return false if the hit test match a child component.

At first glance i suppose it is related to that. But as i said i’ll make more investigations later!

No worries, mate. I’ll continue looking myself. I’m not laying it on your shoulders.

Your effort is very much appreciated.

Your draggable component is in front of the keyboard.

Thus here:

void mouseDrag(const juce::MouseEvent& e) override
{
    juce::MouseEvent midiEvent = e.getEventRelativeTo (midi);
    
    int note = midi->getNoteAtPosition (midiEvent.position);

    dragger.dragComponent(this, e, nullptr);
}

It calls ( from getNoteAtPosition):

int MidiKeyboardComponent::xyToNote (Point<float> pos, float& mousePositionVelocity)
{
    if (! reallyContains (pos.toInt(), false))
        return -1;

    ...
}

That calls:

bool Component::reallyContains (Point<int> point, bool returnTrueIfWithinAChild)
{
    if (! contains (point))     // <-- 1.
        return false;

    auto* top = getTopLevelComponent();
    auto* compAtPosition = top->getComponentAt (top->getLocalPoint (this, point));

    // 2.

    return (compAtPosition == this) || (returnTrueIfWithinAChild && isParentOf (compAtPosition));
}

It returns false because:

  1. As contains (point) is true the code continues.
  2. As compAtPosition == this is false, and returnTrueIfWithinAChild is false,
    it returns false.

That is why you always get -1. Tada!

The problem is that the keyboard component disallows the hit test to succeed if the mouse is inside another component over/than itself.

I don’t see any way to fix your problem without changing the JUCE code. :smile:

1 Like

Excellent!

Last night I was tracing through this code like a crazy person and found “HitTest” to be suspect. I don’t think there’s a defect in the code, but rather in my understanding of it’s functionality.

I came up with a slightly different conclusion from you:

   static bool hitTest (Component& comp, Point<int> localPoint)
  {
      return isPositiveAndBelow (localPoint.x, comp.getWidth())
          && isPositiveAndBelow (localPoint.y, comp.getHeight())
          && comp.hitTest (localPoint.x, localPoint.y);
 }

The call to comp.getWidth() returns 0 for the width of the MidiKeyboardComponent!!! This is probably due to what you’ve pointed out.

I’ll study your analysis a bit more. It looks like I could learn something from it.

THANK YOU!

I’m sure, I can find a work around for this.

1 Like

Notice that if you set true in the JUCE code if (! reallyContains (pos.toInt(), false)), your code should work fine (i mean instead of false as 2nd argument). Of course changing JUCE code like that is something nobody should do at home! :wink:

Actually, I have made changes to JUCE before that I will be submitting at some point. It allows me to get an incremental slider like the one pictured below without altering the basic flow of the slider logic.

image

Hey, thanks for going in on this with me. It was good having two brains on this ultimately reaching the same conclusion. I won’t have to second guess myself now and proceed to look for a work around.

1 Like