Java Question Call a Java function when user close B4J

max123

Well-Known Member
Licensed User
Longtime User
Hi Java coders,

I ported this great Processing midi library to B4J using java on Eclipse
TheMidiBus Processing Midi Library

This library open system midi ports and permits to send/receive MIDI (Musical Instrument Didital Interface) to/from a physical or virtual midi device.

The original library register a callback dispose() that is called when the Processing main app closes, so the library automatically closes all midi devices when the user close the app.

See this relevant Processing code, here the original library register a dispose() callback that is called when the user closes the app, eg with X in the Form top-right corner, or by code:
Java:
/**
     * Registers an Object as the parent in order to receieve method callbacks as per {@link PApplet}.
     * Calling this will replace the previous parent Object if any was set.
     *
     * @param parent the object to register.
     * @return the previous parent object if any was set.
    */
    public Object registerParent(Object parent) {
        Object old_parent = this.parent;

        this.parent = parent;

        if (parent != null) {

            if (parent instanceof processing.core.PApplet) {
                ((processing.core.PApplet) parent).registerMethod("dispose", this);  // <<< CALLBACK call dispose() function
            }

            // Other callbacks not relevant here ...
            try {
                method_note_on = parent.getClass().getMethod("noteOn", new Class[] { Integer.TYPE, Integer.TYPE, Integer.TYPE });
            } catch(Exception e) {
                // no such method, or an error.. which is fine, just ignore
            }
 
            try {
                method_note_off = parent.getClass().getMethod("noteOff", new Class[] { Integer.TYPE, Integer.TYPE, Integer.TYPE });
            } catch(Exception e) {
                // no such method, or an error.. which is fine, just ignore
            }
 
            // etc....
            // .....................
            // .....................

This will call the dispose() function from Processing when app closes.

As you can see on this code the dispose() function just call the close() function that just call the closeAllMidiDevices(), so all devices are closed and resources freed.
Java:
/**
  * Closes this MidiBus and all connections it has with other MIDI devices.
  * This method exit as per standard Processing library syntax and is called automatically whenever the parent applet shuts down.
  * It is functionaly equivalent to close() and stop().
  *
  * @see #close()
  */
public void dispose() {
    close();
}

/**
  * Closes this MidiBus and all connections it has with other MIDI devices.
  * This method exists as per standard javax.sound.midi syntax.
  * It is functionaly equivalent to stop() and dispose().   *
  *
  * @see #dispose()
  */
public void close() {
    closeAllMidiDevices();
}

/**
  * Closes all MidiDevices, should only be called when closing the application,
  * will interrupt all MIDI I/O. Call publicly from stop(), close() or dispose()
  *
  * @see #close()
  * @see #dispose()
  */
void closeAllMidiDevices() {
    if (MidiBus.available_devices == null) findMidiDevices();

    if (MidiBus.available_devices == null) return;

    MidiDevice device;

    for (int i = 0;i < MidiBus.available_devices.length;i++) {
        try {
            device = MidiSystem.getMidiDevice(MidiBus.available_devices[i]);
            if (device == null) continue;
            if (device.isOpen()) device.close();
        } catch(MidiUnavailableException e) {
            //Device wasn't available, which is fine since we wanted to close it anyways
        }
    }
}

Currently on B4J app I have to manually call Close() to close all devices when exit:
B4X:
Sub MainForm_CloseRequest (EventData As Event)
    Log("Clear and Close all Midi devices ...")
    Midi.Close
End Sub

My question is ...

... is there a way to omit the Midi.Close() in the MainForm_Closed or MainForm_CloseRequest
and do it call automatically (as Processing do) when the user closes the B4J app ?

Thanks in advance for any help.
 
Last edited:

max123

Well-Known Member
Licensed User
Longtime User
Some advices please...
 

stevel05

Expert
Licensed User
Longtime User
You could probably create your own listener in the midi class if you just want save the user having to do it manually, but that would probably stop the sub in Main from being called unless you delegate it.
 

max123

Well-Known Member
Licensed User
Longtime User
Thanks for reply @stevel05 , so I cannot do it ?

In your midi library you close devices when exit the app ?

As you know any midi device should be closed before open it again. I do not know how B4X works in this case and if it already free all memory when closed (by even close midi devices)
 
Last edited:

max123

Well-Known Member
Licensed User
Longtime User
You could probably create your own listener in the midi class if you just want save the user having to do it manually, but that would probably stop the sub in Main from being called unless you delegate it.
The big problem here is that this library already use some listeners like StandardMidiListener, ObjectMidiListener etc... but I do not know much how Interfaces works.

I see that the library have 5-6 different listeners used to get midi inputs, I see tha there is a function that handle these to call callbacks on B4X but I do not know how it works...
 

stevel05

Expert
Licensed User
Longtime User
In my midi library there could any number of devices open, or a sequencer etc. Closing them all automatically would be a chore.

It depends what type of devices the MidiBus library can open whether it would be reasonable to do it.
 

max123

Well-Known Member
Licensed User
Longtime User
Actually MidiBus do not use Sequencer, no Synthetiser classes, maybe I will add in future, it only open MidiDevices ports.
Is similar to your library but without sequencer, like Arduino Midi Library. Send and receive messages. Note, ControlChanges, PithBend, ProgramChanges, I've added high and low Level RPN and NRPN.

You have a suggestion on how create a listener to call closeMidiDevices when B4J closes ?
 
Last edited:

stevel05

Expert
Licensed User
Longtime User
You would need to implement something like this in your midi class:
 

Attachments

  • CloseRequestClass.zip
    2.7 KB · Views: 92

max123

Well-Known Member
Licensed User
Longtime User

max123

Well-Known Member
Licensed User
Longtime User
I just seen your code on my phone Steve.

So you use javafx.event.EventHandler.

The problem here is that setOnCloseRequest refer to a Stage.

The java library do not know the B4X Form (I supposed it use as Stage on JFX ?), so I need to pass the Form to a Java library to create a listener ?

This should be trasparent on B4J side, just the user omit the Midi.Close and other code will remain unchanged.
 
Last edited:

stevel05

Expert
Licensed User
Longtime User
Sorry, I thought you had implemented it as inline java, the code could have gone in the class as is.

I am not totally sure about the java library as I haven't done anything in Java for years, I think it It would be far more complicated to implement as there would be no guarantee that the library is initialized in the Main code module, complicating the call back requirements.

I would document the requirement and leave the onus on the developer to call the close method when the app closes.
 
Last edited:

max123

Well-Known Member
Licensed User
Longtime User
Ok thanks, but I want to implement it, so I'll look for the solution some more...

Sorry for the misunderstanding, now I specified in the post 'using java with Eclipse'
 
Last edited:

max123

Well-Known Member
Licensed User
Longtime User
Tried to pass B4J Form as Object but without success ....

Java:
 public void Initialize(BA parent, Object form, String EventName, int in_device_num, int out_device_num) {
        this.parent = parent;
        this.eventName = (EventName.toLowerCase(BA.cul));
 
         Init(parent, null);
         AddInput(in_device_num);
         AddOutput(out_device_num);
 
         // Try to get a B4X Form
         Field[] fields = form.getClass().getFields();
 
         for (int i = 0; i < fields.length; i++) {
             
            if (fields[i] != null) {
                if (fields[i].getName() == "stage") {
                    Stage stage = new Stage();
                    //Stage stage = (Stage) fields[i];
                    stage.setOnCloseRequest((EventHandler<WindowEvent>) new EventHandler<WindowEvent>() {               
                        public void handle(WindowEvent e) {
                           CloseAllDevices();
                           // System.exit(0);
                        }
                    });
                }
            }
        }
 
    }

Please any suggestion are welcome
 
Last edited:

stevel05

Expert
Licensed User
Longtime User
This works from inline java, I'm sure you can convert it. Pass the MainForm.RootPane instead of the MainForm

B4X:
#if java

import javafx.stage.Stage;
import javafx.scene.layout.Pane;
import javafx.event.EventHandler;
import javafx.stage.WindowEvent;

public void setCloseListener(Pane node) {

       Stage stage = (Stage)node.getScene().getWindow();
     stage.setOnCloseRequest((EventHandler<WindowEvent>) new EventHandler<WindowEvent>() {
                        
        public void handle(WindowEvent e) {
          BA.Log("Close All Devices");
        }
    });


}

#End If

Alternatively you could pass the stage directly using the JavaObject code above.
 

stevel05

Expert
Licensed User
Longtime User
But you will still lose the call to MainForm_CloseRequest in the calling module. (B4j or B4xPages)
 

max123

Well-Known Member
Licensed User
Longtime User
But you will still lose the call to MainForm_CloseRequest in the calling module. (B4j or B4xPages)

Mmmmmmm I do not find a way to go....

Steve, this midi library is pretty simple to use and very powerful, even have a lots of callbacks to receive midi messages, like NoteOn, NoteOff, ShortMessage, ControlChange, any filtered to a different callback sub, or just a Raw where all midi messages arrives and is user responsibility to check it. On this sub even SySex messages can be received and parsed, even RPN and NRPN 14 bit controls (0-16383).

In past I used and even developed some midi libraries for various languages, bu this MidiBus is superb as semplicity of use.

Here a simple test that send and receive NoteOn, NoteOff in a loop changing Note, Velocity and Pan by sending it as ControlChange.
And you are musician like me, so I think you like it.
B4X:
Sub Process_Globals
    Dim myBus As MidiBus
    Dim MidiUtils As MidiBus_Utilities
End Sub

Sub AppStart (Args() As String)

    MainLoop
 
    StartMessageLoop
End Sub

Sub MainLoop
    Log("Basic Demo")
    Log(" ")
 
    myBus.ListDevices  ' List all available Midi devices on STDOUT. This will show each device's index and name.
 
    ' Either you can
    '                  Event  In Out
    '                     |     |  |
    ' myBus.Initialize("Midi", 0, 1)   ' Create a new MidiBus using the device index to select the Midi Input and Output devices respectively.
 
    ' or you can ...
    '                    Event           In                    Out
    '                      |              |                     |
    ' myBus.Initialize3("Midi", "IncomingDeviceName", "OutgoingDeviceName")   ' Create a new MidiBus using the device names to select the Midi input and output devices respectively.
 
    ' or for testing you could ...
    '                  Event   In            Out
    '                   |      |             |
    'myBus.Initialize6("Midi", -1, "Java Sound Synthesizer") ' Create a new MidiBus with no input device and the default Java Sound Synthesizer as the output device.
    '                                                        ' On my system I don't have 'Java Sound Synthesizer', intead I've Gervill as java synthetizer, but here we use 'Microsoft MIDI Mapper'
    ''''''''''myBus.Initialize6("Midi", -1, "Microsoft MIDI Mapper") ' Create a new MidiBus with no input device and the default Java Sound Synthesizer as the output device.
    myBus.Initialize3("Midi", "loopMIDI Port 1", myBus.AvailableOutputs(5)) ' You can even refer to static methods myBus.AvailableInputs(Index), myBus.AvailableOutputs(Index)
    '........  and more, there are 9 different types of initializers ........
 
 
    myBus.AddOutput2(myBus.AvailableOutputs(2))
 
    Wait For (Execute) Complete (Finish As Boolean)
 
'    Log("Clear and Close all Midi devices....")
'    myBus.ClearAll   '  Clears all Input and Output related Transmitters and Receivers. Use this only if the bus need to be reused.
'    myBus.Close

    ' We do not close the midi bus, instead we start a message loop queue, so we can continue to receive midi input messages

    Log("Done")
    StartMessageLoop
End Sub

Sub Execute As ResumableSub
 
    Dim Channel As Int = 0  ' (0-15)
    Dim Pitch As Int
    Dim Velocity As Int
    Dim Pan As Int
 
    Dim cc As ControlChange 'Ignore
'    cc.Initialize  ' Just to suppress IDE warning
 
    Dim Delay As Int

    For Pitch = 12 To 96
        Velocity =  MidiUtils.MapInt(Pitch, 12, 96, 0, 127)
        Delay = MidiUtils.MapInt(Pitch, 12, 96, 250, 10)
        Pan = MidiUtils.MapInt(Pitch, 12, 96, 0, 127)
 
        myBus.SendControlChange(0, cc.Pan_Position, Pan)
 
        Log(">>>> Send NoteOn/NoteOff    Note: " & Pitch & "   Velocity: " & Velocity & "   Pan: " & Pan)
        myBus.SendNoteOn(Channel, Pitch, Velocity)   ' Send a Midi NoteOn
        Sleep(Delay)
        myBus.SendNoteOff(Channel, Pitch, Velocity)  ' Send a Midi NodeOff
        Sleep(200)
    Next
 
    For Pitch = 96 To 12 Step -1
        Velocity = MidiUtils.MapInt(Pitch, 12, 96, 0, 127)
        Delay = MidiUtils.MapInt(Pitch, 12, 96, 250, 10)
        Pan = MidiUtils.MapInt(Pitch, 12, 96, 0, 127)
 
        myBus.SendControlChange(0, cc.Pan_Position, Pan)
 
        Log(">>>> Send NoteOn/NoteOff    Note: " & Pitch & "   Velocity: " & Velocity & "   Pan: " & Pan)
        myBus.SendNoteOn(Channel, Pitch, Velocity)   ' Send a Midi NoteOn
        Sleep(Delay)
        myBus.SendNoteOff(Channel, Pitch, Velocity)  ' Send a Midi NodeOff
        Sleep(200)
    Next

    Return True
End Sub
 
Sub Midi_NoteOn(Channel As Int, Note As Int, Velocity As Int)
    Log(" ")
    Log("<<<< Received Note On")
    Log("-------------------------")
    Log("Channel: " & (Channel + 1))  ' For convenience we print out channel 1-16 instead of 0-15
    Log("Note: " & Note)
    Log("Velocity: " & Velocity)
End Sub

Sub Midi_NoteOff(Channel As Int, Note As Int, Velocity As Int)
    Log(" ")
    Log("<<<< Received Note Off")
    Log("---------------------------")
    Log("Channel: " & (Channel + 1))  ' For convenience we print out channel 1-16 instead of 0-15
    Log("Note: " & Note)
    Log("Velocity: " & Velocity)
End Sub

Sub Midi_ControlChange(Channel As Int, Number As Int, Value As Int)
    Log(" ")
    Log("<<<< Received ControlChange")
    Log("----------------------------------")
    Log("Channel: " & (Channel + 1))  ' For convenience we print out channel 1-16 instead of 0-15
    Log("Number: " & Number)
    Log("Value: " & Value)
End Sub
MidiBus_Utilities is my addon class, have some good utilities to map a value from one range to another, constrain values, decode and encode 14bit by passing integer value or LSB and MSB and others.

If I do not encounter other problems I will release it next days.
 
Last edited:

stevel05

Expert
Licensed User
Longtime User
I can see the attraction, but I am very used to using Midi and have been since it first came out (in 1983). MidiBus is a wrapper for Javax midi which is a little more complicated but more controllable (they could have made it easier to use). I like to get into the nuts and bolts of the subject.
 

max123

Well-Known Member
Licensed User
Longtime User
The problem is even that I need to import javaFX on the library. Add to classpath.
I imported it now but serves just for this issue and consume memory.
 
Top