Java getting input from MIDI keyboard

2019-01-17 08:42发布

问题:

I have designed my own synthesizer in java and I now want to connect it with a midi keyboard. My class below searches through all the midi devices that have transmitters. It successfully finds my midi keyboard. I add my own receivers to each transmitter for each device so that it should pick up everything possible. From reading all the help documents and java doc I know that a Transmitter sends MidiEvents to a Receiver which then handles them with the send method. So I wrote my own inner class implementing Receiver and just used a println statement to check if there was anything detected at all in the send method. However nothing is picked up at all. There seems to be very little help to do such a simple thing and I have looked at every help file, javadoc and forum. I'm sure it must be something really obvious I have somehow missed.

My synthesizer should not be confused with the interface Synthesizer and it is not a midi instrument. It uses a synthesis algorithm and has a playback method. Basically I just need to get the midi keyboard sending a note on event which will invoke the playback method.

import javax.sound.midi.*;
import java.util.ArrayList;
import java.util.List;
import java.io.*;
public class MidiHandler
{
    //ArrayList of MidiDevices
    private ArrayList<MidiDevice> devices = new ArrayList<MidiDevice>();

    public MidiHandler()
    {
        MidiDevice device;
        MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo();
        for (int i = 0; i < infos.length; i++) {
            try {
                device = MidiSystem.getMidiDevice(infos[i]);
                //does the device have any transmitters?
                if (device.getTransmitters().size() > 0) {
                    //if it does, add it to the device list
                    System.out.println(infos[i] + ": " + device.getTransmitters().size());
                    devices.add(device);
                }
            } catch (MidiUnavailableException e) {}
        }
        //if any transmitting devices were found
        if(devices.size()>0) {
            //for each device
            for(int i = 0; i<devices.size(); i++) {
                try {
                    //get all transmitters
                    List<Transmitter> transmitters = devices.get(i).getTransmitters();
                    //and for each transmitter
                    for(int j = 0; j<transmitters.size();j++) {
                        //create a new receiver
                        transmitters.get(i).setReceiver(
                            //using my own MidiInputReceiver
                            new MidiInputReceiver(devices.get(i).getDeviceInfo().toString())
                        );
                    }
                    //open each device
                    devices.get(i).open();
                    //if code gets this far without throwing an exception
                    //print a success message
                    System.out.println(devices.get(i).getDeviceInfo()+" Was Opened");
                } catch (MidiUnavailableException e) {}
            }
        }
    }
    //tried to write my own class. I thought the send method handles an MidiEvents sent to it
    public class MidiInputReceiver implements Receiver {
        public String name;
        public MidiInputReceiver(String name) {
            this.name = name;
        }
        public void send(MidiMessage msg, long timeStamp) {
            System.out.println("midi received");
        }
        public void close() {}
    }
}

NOTE: I have already seen this: Java MIDI - getting data from piano?.

and this: http://www.jsresources.org/examples/MidiInDump.html

interface Sequencer looked way to complicated for what I want also.

回答1:

I've found that the MidiDevice getTransmitters() appears to return the list of currently already-open transmitters, not transmitters that are available to be opened. I believe the way to open a new transmitter is via the getTransmitter() method. I've modified your code to do this:

import javax.sound.midi.*;
import java.util.ArrayList;
import java.util.List;
import java.io.*;
    public class MidiHandler
{

    public MidiHandler()
    {
        MidiDevice device;
        MidiDevice.Info[] infos = MidiSystem.getMidiDeviceInfo();
        for (int i = 0; i < infos.length; i++) {
            try {
            device = MidiSystem.getMidiDevice(infos[i]);
            //does the device have any transmitters?
            //if it does, add it to the device list
            System.out.println(infos[i]);

            //get all transmitters
            List<Transmitter> transmitters = device.getTransmitters();
            //and for each transmitter

            for(int j = 0; j<transmitters.size();j++) {
                //create a new receiver
                transmitters.get(j).setReceiver(
                        //using my own MidiInputReceiver
                        new MidiInputReceiver(device.getDeviceInfo().toString())
                );
            }

            Transmitter trans = device.getTransmitter();
            trans.setReceiver(new MidiInputReceiver(device.getDeviceInfo().toString()));

            //open each device
            device.open();
            //if code gets this far without throwing an exception
            //print a success message
            System.out.println(device.getDeviceInfo()+" Was Opened");


        } catch (MidiUnavailableException e) {}
    }


}
//tried to write my own class. I thought the send method handles an MidiEvents sent to it
public class MidiInputReceiver implements Receiver {
    public String name;
    public MidiInputReceiver(String name) {
        this.name = name;
    }
    public void send(MidiMessage msg, long timeStamp) {
        System.out.println("midi received");
    }
    public void close() {}
    }
}

On my hardware (I have a simple USB MIDI controller plugged in), the code correctly prints out "midi received" after an instance of MidiHandler is created.

Hope this helps!



回答2:

I've found that you have to open the device before calling setRecceiver(), otherwise the receiver's send() method gets called with any garbage MIDI data from the last time you ran the application.

device.open();

Transmitter trans = device.getTransmitter();    
// set new receiver after opening so that the input buffer will be flushed
trans.setReceiver(new MidiInputReceiver(device.getDeviceInfo().toString()));