I can't seem to get the instrument to change. I switch the value of the instrument but get nothing different on the output. I can only get a piano instrument to play no matter what value I try. Here is the simple code below. Does anyone have any suggestions? Or am I missing a fundamental of the instrument object?
import javax.sound.midi.*;
//import javax.sound.*;
public class Drum {
static int instrument = 45;
static int note = 100;
static int timbre = 0;
static int force = 100;
public static void main(String[] args) {
Synthesizer synth = null;
try {
synth = MidiSystem.getSynthesizer();
synth.open();
}
catch (Exception e) {
System.out.println(e);
}
Soundbank soundbank = synth.getDefaultSoundbank();
Instrument[] instr = soundbank.getInstruments();
synth.loadInstrument(instr[instrument]); //Changing this int (instrument) does nothing
MidiChannel[] mc = synth.getChannels();
mc[4].noteOn(note, force);
try { Thread.sleep(1000); }
catch(InterruptedException e) {}
System.out.println(instr[instrument].getName());
synth.close();
}
}
You need to tell the channel to use the instrument. I admit I've never used MIDI in Java, but something like mc.programChange(instr.getPatch().getProgram()) sounds promising.
To play the percussion instruments you have to use the channel 10, that channel is used only for percussion instruments. (http://en.wikipedia.org/wiki/General_MIDI)
For example:
int instrument = 36;
Sequence sequence = new Sequence(Sequence.PPQ, 1);
Track track = sequence.createTrack();
ShortMessage sm = new ShortMessage( );
sm.setMessage(ShortMessage.PROGRAM_CHANGE, 9, instrument, 0); //9 ==> is the channel 10.
track.add(new MidiEvent(sm, 0));
then every note you add it will sound with percussion.
You need to send a program change event to the sequencer. How? Send a short message.
sound.setMessage(ShortMessage.PROGRAM_CHANGE, channel, instrument, channel);
long timeStam1p = -1;
Receiver rcv1r = MidiSystem.getReceiver();
rcv1r.send(sound, timeStam1p);
sound.setMessage(ShortMessage.NOTE_ON, channel, note, velocity);
long timeStamp = -1;
Receiver rcvr = MidiSystem.getReceiver();
rcvr.send(sound, timeStamp);
Variables are channel (int) note (int), instrument (int), velocity (int).
Also, I suggest to learn midi events. Events are how a midi plays notes, stops notes, change instruments, tempo change, control changes, etc. I spent 2 years using a midi program.
Related
I'm considering using codenameone to implement a cross-platform app, Win/Mac/Android/iOS. I do my development on Windows, and am fairly clueless about Mac and iOS development. Two specific features that I use quite a bit, are the java.sound API and RandomAccessFile. I was curious what the support for these on the mobile platforms was like. Would the following code samples work on mobile platforms (particularly iOS) via codenameone?
Example 1:
Mixer.Info[] mixerInfo = AudioSystem.getMixerInfo();
for (int ii = 0; ii < mixerInfo.length; ii++) {
Mixer mixer = AudioSystem.getMixer(mixerInfo[ii]);
Info[] sli = mixer.getSourceLineInfo();
addSink(sinksModel, sli, mixer);
Info[] tli = mixer.getTargetLineInfo();
addSource(sourcesModel, tli, mixer);
}
}
Example 2:
/** Starts playing audio from the current file position. */
public void startPlaying() {
Runnable r = new Runnable() {
#Override
public void run() {
int framePos = sendLastCue();
playing = true;
stopPlaying = false;
while (!stopPlaying) {
try {
int bytes = raf.read(buffer);
if (bytes < 0) {
break;
}
outputLine.write(buffer, 0, bytes);
framePos = sendCues(framePos);
} catch (IOException e) {
e.printStackTrace();
}
}
playing = false;
stopPlaying = true;
for (RecordingListener l : listeners) {
l.playEnded();
}
}
};
new Thread(r).start();
}
Example 3:
/* raf is an instance of RandomAccessFile. */
private void writeDirect(byte[] buf, int offset, int length) throws IOException {
flush();
raf.seek(virtualFilePointer);
raf.write(buf, offset, length);
virtualFilePointer = raf.getFilePointer();
virtualLength = Math.max(virtualLength, raf.length());
virtualFilePointer = raf.getFilePointer();
directWriteCount++;
}
Thanks!
No.
Codename One supports a subset on those platforms and has no support for either one of those. Nor does it support NIO or similar API's that would be impractical to QA properly on native devices.
See the JavaDocs for the exact set of API's supported: https://www.codenameone.com/javadoc/
You can access native device functionality though thru native interfaces and do those things natively:
http://www.codenameone.com/how-do-i---access-native-device-functionality-invoke-native-interfaces.html
https://www.codenameone.com/manual/advanced-topics.html#_native_interfaces
Java Doc for Function
I can't seem to figure out how to use this function. I have a Java.Midi.Sequence and the File I want to write to, but I can't figure out what "int fileType" is. There are no static int's to reference in either MidiSystem, Sequence, or MidiFileWriter. Nor does 0 help.
The Error I get when using zero is so:
Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: seph.reed.effigy.MidiLoader$1 cannot be cast to javax.sound.midi.ShortMessage
at com.sun.media.sound.StandardMidiFileWriter.writeTrack(StandardMidiFileWriter.java:386)
at com.sun.media.sound.StandardMidiFileWriter.getFileStream(StandardMidiFileWriter.java:204)
at com.sun.media.sound.StandardMidiFileWriter.write(StandardMidiFileWriter.java:137)
at com.sun.media.sound.StandardMidiFileWriter.write(StandardMidiFileWriter.java:153)
at javax.sound.midi.MidiSystem.write(MidiSystem.java:1060)
at seph.reed.effigy.MidiLoader.saveClipAs(MidiLoader.java:197)
at seph.reed.effigy.EffigyMenuBar$2.onClick(EffigyMenuBar.java:47)
The personal function referenced is:
public void saveClipAs(File selectedFile) {
try {
Sequence out = new Sequence(Sequence.PPQ, 256);
Track toMe = out.createTrack();
Sequencer fromMe = ANCESTOR(Effigy.class).m_gui.getCurrentClip().m_sequencer;
//traverse linked list adding notes to track
for(MidiEventEntity ptr = fromMe.m_head; ptr != null; ptr = ptr.m_next) {
byte[] midiData = new byte[3];
midiData[0] = MidiToolBox.NOTE_ON;
midiData[1] = (byte)ptr.getNote();
midiData[2] = (byte)127;
long tick = (long) (256 * ptr.getBeat()); //256 ticks per 1/4 note
MidiEvent addMe = new MidiEvent(new MidiMessage(midiData) {
#Override
public Object clone() {
return null; }
}, tick);
toMe.add(addMe);
}
//THIS LINE BELOW
MidiSystem.write(out, 0, selectedFile);
}
catch (InvalidMidiDataException e) {
e.printStackTrace(); }
catch (IOException e) {
e.printStackTrace();
}
}
Thanks for any help. I'm utterly at a loss as to what int fileType is really asking for.
EDIT: removed a dumb secondary question.
EDIT: functional code:
for(MidiEventEntity ptr = fromMe.m_head; ptr != null; ptr = ptr.m_next) {
byte status = MidiToolBox.NOTE_ON;
byte note = (byte)ptr.getNote();
byte velocity = (byte)127;
long tick = (long) (256 * ptr.getBeat()); //256 ticks per 1/4 note
ShortMessage msg = new ShortMessage(status, note, velocity);
MidiEvent addMe = new MidiEvent(msg, tick);
toMe.add(addMe);
}
It looks like the int corresponds to Midi Type 0, Midi Type 1, Midi Type 2 (more details here)
In terms of how you go about determining what midi types your system supports it looks like you can call the MidiSystem.getMidiFileTypes(Sequence sequence) method.
According to https://docs.oracle.com/javase/tutorial/sound/SPI-providing-MIDI.html :
There are three standard MIDI file formats, all of which an implementation of the Java Sound API can support: Type 0, Type 1, and Type 2. These file formats differ in their internal representation of the MIDI sequence data in the file, and are appropriate for different kinds of sequences. If an implementation doesn't itself support all three types, a service provider can supply the support for the unimplemented ones. There are also variants of the standard MIDI file formats, some of them proprietary, which similarly could be supported by a third-party vendor.
Thus the fileType is either 0, 1, or 2.
What kinds of file types your implementation supports can be seen via MidiSystem.getMidiFileTypes().
The file type of a midi file can be identified via
MidiSystem.getMidiFileFormat() (see
http://docs.oracle.com/javase/7/docs/api/javax/sound/midi/MidiSystem.html#getMidiFileFormat%28java.io.File%29
and http://docs.oracle.com/javase/7/docs/api/javax/sound/midi/MidiFileFormat.html)
I have this app from Head First Java
import javax.sound.midi.*;
public class MiniMiniMusicApp{
public static void main(String[] args){
MiniMiniMusicApp mini = new MiniMiniMusicApp();
mini.play();
}
public void play(){
try{
Sequencer player = MidiSystem.getSequencer();
player.open();
Sequence seq = new Sequence(Sequence.PPQ,4);
Track track = seq.createTrack();
ShortMessage a = new ShortMessage();
a.setMessage(114,1,44,100);
MidiEvent noteOn = new MidiEvent(a,1);
track.add(noteOn);
ShortMessage b = new ShortMessage();
b.setMessage(128,1,44,100);
MidiEvent noteOff = new MidiEvent(b,16);
track.add(noteOff);
player.setSequence(seq);
player.start();
} catch(Exception ex){
ex.printStackTrace();
System.out.println("damn");
}
}
}
And this throws the following exception at runtime
javax.sound.midi.InvalidMidiDataException: command out of range: 0x72
at javax.sound.midi.ShortMessage.setMessage(ShortMessage.java:280)
at MiniMiniMusicApp.play(MiniMiniMusicApp.java:15)
at MiniMiniMusicApp.main(MiniMiniMusicApp.java:6)
I read some docs on setMessage and it seems the exception is thrown when you pass an invalid MidiMessage but I'm simply following the example from the book. I've read on other forums and it appears that this code works for other people.
Any idea on what is causing the issue? Can you try running this on your end and see if it works? At least that'll tell me if it's something on my environment.
You probably want to send a NoteOn message (144, not 114). The class ShortMessage contains constants which prevent such mistakes. You could use it like this:
import static javax.sound.midi.ShortMessage.*;
a.setMessage(NOTE_ON, 1, 44, 100);
Such code is probably easier to read for most people than directly using the numbers.
Figured it out
a.setMessage(114,1,44,100);
should be
a.setMessage(144,1,44,100);
I am building a Java application that programatically generates a MIDI Sequence that is then sent over the LoopBe Internal Midi Port so that I can use Ableton Live instruments for better sound playback quality.
Please correct me if I am wrong. What I need is to generate a Sequence, that will contain Tracks that will contains MidiEvents, that will contain MIDI messages with time information. That I think I got down.
The real problem is how to send it over the LoopBe MIDI Port. For that I supposedly need a Sequencer, but I don't know how I can get one rather than the default one, and I don't want that.
I guess a workaround would be to write the Sequence to a .mid file and then programatically play it back on the LoopBe Port.
So my question is: How can I obtain a non-default Sequencer?
You need method MidiSystem.getSequencer(boolean). When you call it with false parameter, it gives you unconnected sequencer.
Get Receiver instance from your target MIDI device and set it to sequencer with seq.getTransmitter().setReceiver(rec) call.
Example snippet:
MIDIDevice device = ... // obtain the MIDIDevice instance
Sequencer seq = MidiSystem.getSequencer(false);
Receiver rec = device.getReceiver();
seq.getTransmitter().setReceiver(rec)
For examples on Sequencer use, see tutorial on http://docs.oracle.com/javase/tutorial/sound/MIDI-seq-methods.html
For my own project I use LoopBe1 to send MIDI signals to REAPER.
Of course, LoopBe1 should already be installed.
In this example I iterate through the system's MIDI devices for the external MIDI port of LoopBe and then send the note C 10 times.
import javax.sound.midi.*;
public class Main {
public static void main(String[] args) throws MidiUnavailableException, InvalidMidiDataException, InterruptedException {
MidiDevice external;
MidiDevice.Info[] devices = MidiSystem.getMidiDeviceInfo();
//Iterate through the devices to get the External LoopBe MIDI port
for (MidiDevice.Info deviceInfo : devices) {
if(deviceInfo.getName().equals("LoopBe Internal MIDI")){
if(deviceInfo.getDescription().equals("External MIDI Port")){
external = MidiSystem.getMidiDevice(deviceInfo);
System.out.println("Device Name : " + deviceInfo.getName());
System.out.println("Device Description : " + deviceInfo.getDescription() + "\n");
external.open();
Receiver receiver = external.getReceiver();
ShortMessage message = new ShortMessage();
for (int i = 0; i < 10; i++) {
// Start playing the note Middle C (60),
// moderately loud (velocity = 93).
message.setMessage(ShortMessage.NOTE_ON, 0, 60, 93);
long timeStamp = -1;
receiver.send(message, timeStamp);
Thread.sleep(1000);
}
external.close();
}
}
}
}
}
For further information about the sending a MIDI signal, refer to this link:
https://docs.oracle.com/javase/tutorial/sound/MIDI-messages.html
I hope this helps!
I have this code:
Synthesizer synthesizer = MidiSystem.getSynthesizer();
synthesizer.open();
Instrument[] instrument = synthesizer.getDefaultSoundbank().getInstruments();
synthesizer.loadInstrument(instrument[29]);
MidiChannel[] channels = synthesizer.getChannels();
MidiChannel channel = channels[1];
channel.programChange(29);
channel.noteOn(noteNumber, 127);
Teszthang.sleep(2000);
channel.noteOff(noteNumber);
so this is an example, to play a sound in max volume (127) for 2 seconds. but i want to control the channel's volume, like after 2 seconds, the volume fade out in an another 2 seconds. How could I do that? I know these methods:
channel.controlChange(controller, value);
channel.setPolyPressure(noteNumber, pressure);
but these don't change any volume! I don't know how to use these methods. How could I change the channel's volume after the noteOn() while it has been playing?
You can use CC 7 for setting channel volume.
channel.controlChange(7, value);
see: http://improv.sapp.org/doc/class/MidiOutput/controllers/controllers.html
Sometimes you have some volume events in the midi file so you cannot change channel volume.
After getting the sequence, remove these events :
Track[] tracks = sequence.getTracks();
for (Track track : tracks){
for(int i = 0; i < track.size(); i++){
if(!track.remove(track.get(i))){
System.out.println("MIDI Event not removed");
}
}}