[JavaFX] Try to make a software MIDI keyboard Part 2 Slide your finger to change the scale

OpenningKey04.png

You can move the real piano to the next key while holding it down

The keyboard created in the first time implemented an event listener for each key. for that reason

--Even if you slide while touching and exceed the range of the key, the next key will not be pressed and the original key will remain pressed. --You must always release your finger once to press the next key.

The problem arose. This time, we will improve this process so that it can be expressed more like a real piano. I will also describe how to get child nodes from the parent node.

Solution flow

We will solve it step by step. First, let's move the event listener created last time.

Move TouchEvents to parent node

Move the "setOnTouchPressed" and "setOnTouchReleased" created in the key class last time to the parent node. The immediate parent is a Notes layout class that inherits AnchorPane, but considering the future, we will move it to the higher KeyboardMain layout class.

Here, one key has one scale (note) and receiver (UraReceiver), and the process of actually playing the sound and changing the style is performed by each key, and only the event listener that executes the process. I decided to move it to my parents.

Originally, it was created dynamically instead of xml, so this kind of movement was surprisingly easy.

KeyboardMain.java



public class KeyboardMain extends VBox implements UraOnTouchMovedListener, UraOnTouchReleasedListener, UraOnTouchPressedListener {
....

        this.setOnTouchReleased(touchEvent -> {
            onTouchReleasedListen(touchEvent, this);
        });
        this.setOnTouchPressed(touchEvent -> {
            onTouchPressedListen(touchEvent, this);
        });
....
 @Override
    public void onTouchReleasedListen(TouchEvent touchEvent, Node node) {
        try {
            //Get the key you are currently touching
            final Node targetNode = touchEvent.getTouchPoint().getPickResult().getIntersectedNode();
            if (targetNode == null) {
                return;
            }

            LOG.log("DBG Released Before [{}] getTouchPoint=({}), target=({})", touchEvent.getTouchPoint().getId(), touchEvent.getTouchPoint(), touchEvent.getTarget());
            if (UraKeyboard.class.isInstance(targetNode)) {
                //Executes the released event listener for the key if it is on the key object.
                this.touchReleasedKeyboardListen(touchEvent, UraKeyboard.class.cast(targetNode));
            }
            //Only when all caches are exhausted, search all keys again and stop the sound.
            this.resetKeyboardNotes(touchEvent);
        } finally {
            touchEvent.consume();
        }
    }
....
    @Override
    public void onTouchPressedListen(TouchEvent touchEvent, Node node) {
        try {
            //Get the key you are currently touching
            final Node targetNode = touchEvent.getTouchPoint().getPickResult().getIntersectedNode();
            if (targetNode == null) {
                return;
            }

            LOG.log("DBG Pressed Before [{}] getTouchPoint=({}), target=({})", touchEvent.getTouchPoint().getId(), touchEvent.getTouchPoint(), touchEvent.getTarget());
            if (UraKeyboard.class.isInstance(targetNode)) {
                //Execute the Pressed event listener for the key if it is on the key object
                this.touchPressedKeyboardListen(touchEvent, UraKeyboard.class.cast(targetNode));
            }
        } finally {
            touchEvent.consume();
        }
    }

Which key object is pressed

The event has moved to a higher class, but when a touch event occurs, how do we identify the key on the coordinates being touched? Although it is a brute force technique, there is also a method of brute force finding from the coordinates. Alternatively, although this is not the correct answer, there were some member variables that would find the target parent-child object from the event that touched the javafx.scene.input.TouchEvent class. Among them, this time we will use javafx.scene.input.PickResult that can be obtained from javafx.scene.input.TouchPoint. PickedResult will be described later, but it also flexibly supports the onTouchMoved event, and it is a nice guy that shows the destination object even if the point moves from the first pressed key object to the top of the next key object.

For example, touch on the key of do (60). Then the OnTouchPressed event of KeyboardMain will be executed. The argument of setOnTouchPressed is TouchEvent, which is the event object of the target point. The TouchPoint object inside. Furthermore, the PickeResult object obtained from TouchPoint contains the key object of the child you want to target. Specifically, use it like touchEvent.getTouchPoint (). GetPickResult (). GetIntersectedNode ().

After that, just like the last time, just turn on Note On to press the target key and make a sound.

KeyboarddMain.java


    /**
     *Sounds the target key and changes the display to the pressed display.
     * @param uraKeyboard
     */
    protected synchronized void noteOn(UraKeyboard uraKeyboard) {
        uraKeyboard.uraReceiver().noteOn(uraKeyboard.note());
        uraKeyboard.noteOn(true);
        uraKeyboard.noteOnView();
    }

    /**
     *Mute the sound of the target key and make the display not pressed.
     * @param uraKeyboard
     */
    protected synchronized void noteOff(UraKeyboard uraKeyboard) {
        uraKeyboard.uraReceiver().noteOff(uraKeyboard.note());
        uraKeyboard.noteOn(false);
        uraKeyboard.noteOffView();
    }
....
    /**
     *Listener for Pressed events for keys
     * @param touchEvent
     * @param uraKeyboard
     */
    protected void touchPressedKeyboardListen(final TouchEvent touchEvent, final UraKeyboard uraKeyboard) {
     //Ring the target key
        this.noteOn(touchEvent.getTouchPoint(), uraKeyboard);
    }

Corresponds to the movement event while pressing

Next, add a TouchMoved event so that you can press the next key when you move to the next key while pressing it. Even a small movement will ignite, so for unrelated processing, it is necessary to finish the processing as soon as possible so that the operation does not slow down. For example, if the content of PickedResult is not a key object, or if it is the same as the key being pressed, the process is immediately returned to the caller.

When PickedResult becomes another (next) key object, it is OK if the same contents written in "setOnTouchPressed" and "setOnTouchReleased" are executed for another (next) key object.

But here's the problem.

I was able to put another (next) key object in a sounding state, At this timing, the sound of the key that was originally ringing must be returned to the stopped state at the same time. TouchEvent cannot get what the previous key object is, the previous TouchMoved event, etc.

I was troubled.

Manage the target key during touch

So, I tried to manage the key object currently touched for each point, although it is a little forced here. Create an administrative cache map with ConcurrentMap <Integer, UraKeyboard> and manage the key object that was pressed until just before.

--Add when pressed --Delete when you leave --In the case of move, delete the previous object and add the move destination object

I implemented it so that

Key object management map


    /**Keyboard object map being pressed (sounding)*/
    protected final ConcurrentMap<Integer, UraKeyboard> noteOnKeyCacheMap = newConcurrentHashMap(10, FACTOR.NONE);
....

        synchronized (noteOnKeyCacheMap) {
            //Synchronize with cache map just in case
            Integer CURRENT_TOUCH_ID = touchEvent.getTouchPoint().getId();
            final UraKeyboard oldNoteOnKey = noteOnKeyCacheMap.get(CURRENT_TOUCH_ID);
....
 
        if (!uraKeyboard.isNoteOn()) {
            LOG.log("DBG Pressed The event already Hover({}), ({}).", uraKeyboard, uraKeyboard);
            this.noteOn(uraKeyboard);
        }
        noteOnKeyCacheMap.putIfAbsent(touchPoint.getId(), uraKeyboard);
....

    /**
     *If there are no touch points on the target key, the sound is stopped. Also delete it from the cache.
     * @param touchPoint
     * @param uraKeyboard Target key
     */
    protected void noteOff(final TouchPoint touchPoint, final UraKeyboard uraKeyboard) {
        final Integer CURRENT_TOUCH_ID = touchPoint.getId();
        boolean isOtherKeyNoteOn = false;
        for (final Map.Entry<Integer, UraKeyboard> noteOnEntry : noteOnKeyCacheMap.entrySet()) {
            if (CURRENT_TOUCH_ID.equals(noteOnEntry.getKey())) {
                continue;
            }
            if (isOtherKeyNoteOn = uraKeyboard.equals(noteOnEntry.getValue())) {
                //If there is a touch point on the key other than the target, keep the sound sounding.
                LOG.log("DBG XXX oldNoteOnKey in map(oldNoteOnKey=[{}]({}), target=[{}]({}))", CURRENT_TOUCH_ID, uraKeyboard, noteOnEntry.getKey(), noteOnEntry.getValue());
                break;
            }
        }
        if (!isOtherKeyNoteOn) {
            //Stop the sound if the touch point is not found on the target key other than the target
            LOG.log("DBG Move&Pressed The event did not Hover({}), ({}).", touchPoint, uraKeyboard);
            this.noteOff(uraKeyboard);
        }
        noteOnKeyCacheMap.remove(CURRENT_TOUCH_ID);
        LOG.log("DBG Move DELETE({}), __CACHE__({}).", uraKeyboard, noteOnKeyCacheMap.entrySet());
    }

Only noteOff is special. This is the point you currently have, because even if the target is off the point, it will remain sounding if other points are on the same key.

Summary

I was able to improve it into a flowing keypad. Until I first found the PickedResult, I had a lot of trouble using the method of fetching by force using Collections.binarySearch. .. .. (I'm glad it was there.)

The source is also placed below https://github.com/syany/u-board/tree/0.2.3

By the way, I can't jar it so far, so even if you pick it up, you can't play! you can not I have to get into the Gradle task by the next time

Next time

The release date is undecided, but I will add a pitch bend & modulation ribbon.

Recommended Posts

[JavaFX] Try to make a software MIDI keyboard Part 2 Slide your finger to change the scale
Try to make a CS 3D tile from the Geographical Survey tile
Try to make a simple callback
Try to make a peepable iterator
Make a margin to the left of the TextField
Try to make a music player using Basic Player
The road to creating a Web service (Part 1)