1

I'm creating a midi piano roll editor. The Note class creates and contains a NOTE_ON object and an associated NOTE_OFF object as well as a rectangle which the user manipulates on the screen to manipulate the pitch, timing and duration of the notes. Below is the code for this class minus the code for the rectangle. I can't figure out why it's not working properly. The test program creates five of these Note objects and displays them on the piano roll grid, and they play properly. The method for changing pitch works properly too when a rectangle is dragged up or down. But the notes start misbehaving when I call the methods to change timing or duration. First of all, they don't play where they're told when moved, and then if the notes are dragged on top of or extended over each other, the moved note will prevent the notes below it from playing. My unit of movement sent to these methods as an arg is set to 16, so the note will always snap to a 16th beat position. Can anyone spot anything wrong with my code?

public class Note {
MidiEvent noteOn;
MidiEvent noteOff;

private int channel;
private int pitch;
private int vel;

// Constructor calls methods to create NOTE_ON, NOTE_OFF, and graphical rectangle
public Note(MidiMessage on, long tickPos) {
    noteOn = createNoteOn(on, tickPos);
    ShortMessage shortMessage = (ShortMessage) on;
    noteOff = createNoteOff(shortMessage.getChannel(), shortMessage.getData1(), tickPos);
}

public MidiEvent createNoteOn(MidiMessage on, long tickPos) {
    noteOn = new MidiEvent(on, tickPos);
    return noteOn;
}

public MidiEvent createNoteOff(int chan, int pitch, long tickPos) {
    try {
        noteOff = new MidiEvent(new ShortMessage(ShortMessage.NOTE_OFF, chan, pitch, 0), tickPos + 2);
    } catch (InvalidMidiDataException e) {
            e.printStackTrace();
    }
    return noteOff;
}

// Method for moving musical pitch of this note up or down for both NOTE_ON and NOTE_OFF
public void setPitch(int pitchUpOrDown) {
    MidiMessage message = noteOn.getMessage();
    ShortMessage shortMessage = (ShortMessage) message;
        channel = shortMessage.getChannel();
        pitch = shortMessage.getData1();
        vel = shortMessage.getData2();
        try {
            shortMessage.setMessage(ShortMessage.NOTE_ON, channel, pitch + pitchUpOrDown, vel);
        } catch (InvalidMidiDataException e) {
            e.printStackTrace();
        }
        message = noteOff.getMessage();
        shortMessage = (ShortMessage) message;
        try {
            shortMessage.setMessage(ShortMessage.NOTE_OFF, channel, pitch + pitchUpOrDown, 0);
        } catch (InvalidMidiDataException e) {
            e.printStackTrace();
        }
}

// Moves entire note without changing duration of note
public void shiftLocation(int diff) {
    noteOn.setTick(noteOn.getTick() + diff);
    noteOff.setTick(noteOff.getTick() + diff);
}

// Moves start time of note while leaving end time in place, changes duration of note
public void setStartTime(long start) {
    noteOn.setTick(start);
}

// Moves end time of note while leaving start time in place, changes duration of note
public void setDuration(int duration) {
    noteOff.setTick(noteOff.getTick() + duration);
}

The MIDI sequencer and synthesizer:

import javax.sound.midi.*;

public class MusicEngine {
Sequencer sequencer;
Sequence sequence;
Synthesizer synthesizer;
Track track;
MidiEvent event;
// PPQ, or ticks per beat
int ticksPerBeat = 16;
int tempoBPM = 120;

// Constructor
public MusicEngine() {      
    createMidi();
}

// Get sequencer and sequence and track
public void createMidi() {
    try {
        sequencer =  MidiSystem.getSequencer();
        if (sequencer == null) {
            System.out.println("Cannot get a sequencer");
            System.exit(0);
        }
        sequencer.open();

        // If sequencer is not the same as synthesizer, link the two 
        // (required in J2SE 5.0)
        if (!(sequencer instanceof Synthesizer)) {
            System.out.println("Linking the sequencer to the synthesizer");
            synthesizer = MidiSystem.getSynthesizer();
            synthesizer.open();
            Receiver synthReceiver = synthesizer.getReceiver();
            Transmitter seqTransmitter = sequencer.getTransmitter();
            seqTransmitter.setReceiver(synthReceiver);
        } else 
            synthesizer = (Synthesizer)sequencer;
        sequence = new Sequence(Sequence.PPQ, ticksPerBeat);
        track = sequence.createTrack();
        sequencer.setTempoInBPM(tempoBPM);
    } catch(MidiUnavailableException e) {
        System.out.println("No sequencer available");
        System.exit(0);
    } catch(Exception e) {
        e.printStackTrace();
        System.exit(0);
    }

}

// Create an individual MIDI event
public MidiEvent createEvent(int command, int channel, int one, int two, int tick) {
    event = null;
    try {
        ShortMessage a = new ShortMessage();
        a.setMessage(command, channel, one, two);
        event = new MidiEvent(a, tick);
    } catch(InvalidMidiDataException e) {
        e.printStackTrace();
    }
    return event;
}

public void add(MidiEvent event) {
    track.add(event);
}

public void playSong(int tempo) {
    try {
        sequencer.setSequence(sequence);
    }
    catch (InvalidMidiDataException e) {
        e.printStackTrace();
    }

    sequencer.start();
}

public void stopSong() {
    sequencer.stop();
}
}
Matthieu Brucher
  • 21,634
  • 7
  • 38
  • 62
Kyle Walker
  • 559
  • 3
  • 12
  • 25
  • Might help to post the code you´re using to play the sequence? Also, can you isolate things firstly to a simple test case where you create a track with a couple of notes, alter the time/duration of one of the notes, then play the sequence? – Neil Coffey Dec 04 '12 at 18:18
  • I've added the synth and sequencer engine. I've got to step away for a bit. When I come back, I'll try to simplify the code and extract a simple test code from my more elaborate code I've got going. – Kyle Walker Dec 04 '12 at 18:42

1 Answers1

0

I might be misunderstanding your code here, but it seems that you are only increasing the length of notes in your setDuration() method. Shouldn't it look like this instead?

public void setDuration(int duration) {
    noteOff.setTick(noteOn.getTick() + duration);
}
Nik Reiman
  • 39,067
  • 29
  • 104
  • 160
  • Yes, that's the intention of this method: to increase the length of the note. Just above it is a separate method for actually moving the note (setStartTime). I'm still working on providing a condensed form of the code, but I'm running into problems making that code work too. This is my first real project, so I beg your patience as I figure out how to do these things. – Kyle Walker Dec 05 '12 at 13:15