/*
* JFugue - API for Music Programming
* Copyright (C) 2003-2008 David Koelle
*
* http://www.jfugue.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
package org.jfugue;
import javax.sound.midi.Sequence;
import javax.sound.midi.ShortMessage;
/**
* This class takes a Pattern, and turns it into wonderful music.
*
* <p>
* Playing music is only one thing that can be done by rendering a pattern. You
* could also create your own renderer that draws sheet music based on a
* pattern. Or, you could create a graphical light show based on the musical
* notes in the pattern.
* </p>
*
* <p>
* This was named Renderer in previous versions of JFugue. The name has been
* changed to differentiate it from other types of renderers.
* </p>
*
* @author David Koelle
* @version 2.0
* @version 3.0 - Renderer renamed to MidiRenderer
*/
public final class MidiRenderer extends ParserListenerAdapter {
private MidiEventManager eventManager;
long initialNoteTime = 0;
private float sequenceTiming;
private int resolution;
/**
* Instantiates a Renderer
*/
public MidiRenderer(float sequenceTiming, int resolution) {
reset(sequenceTiming, resolution);
}
/**
* Creates a new MidiEventManager. If this isn't called, events from
* multiple calls to render() will be added to the same eventManager, which
* means that the second time render() is called, it will contain music left
* over from the first time it was called. (This wasn't a problem with Java
* 1.4)
*
* @since 3.0
*/
public void reset(float sequenceTiming, int resolution) {
this.sequenceTiming = sequenceTiming;
this.resolution = resolution;
this.eventManager = new MidiEventManager(sequenceTiming, resolution);
}
/**
* Creates a new MidiEventManager using the sequenceTiming and resolution
* already used to create this MidiRenderer. If this isn't called, events
* from multiple calls to render() will be added to the same eventManager,
* which means that the second time render() is called, it will contain
* music left over from the first time it was called. (This wasn't a problem
* with Java 1.4)
*
* @since 3.2
*/
public void reset() {
this.eventManager = new MidiEventManager(this.sequenceTiming,
this.resolution);
}
/**
* Returns the last sequence generated by this renderer
*/
public Sequence getSequence() {
return this.eventManager.getSequence();
}
// ParserListener methods
////////////////////////////
@Override
public void voiceEvent(Voice voice) {
this.eventManager.setCurrentTrack(voice.getVoice());
}
@Override
public void tempoEvent(Tempo tempo) {
byte[] threeTempoBytes = TimeFactor
.convertToThreeTempoBytes(tempo.getTempo());
this.eventManager.addMetaMessage(0x51, threeTempoBytes);
}
@Override
public void instrumentEvent(Instrument instrument) {
this.eventManager.addEvent(ShortMessage.PROGRAM_CHANGE,
instrument.getInstrument(), 0);
}
@Override
public void layerEvent(Layer layer) {
this.eventManager.setCurrentLayer(layer.getLayer());
}
@Override
public void timeEvent(Time time) {
this.eventManager.setTrackTimer(time.getTime());
}
@Override
public void measureEvent(Measure measure) {
// No MIDI is generated when a measure indicator is identified.
}
@Override
public void keySignatureEvent(KeySignature keySig) {
this.eventManager.addMetaMessage(0x59,
new byte[] { keySig.getKeySig(), keySig.getScale() });
}
@Override
public void controllerEvent(Controller controller) {
this.eventManager.addEvent(ShortMessage.CONTROL_CHANGE,
controller.getIndex(), controller.getValue());
}
@Override
public void channelPressureEvent(ChannelPressure channelPressure) {
this.eventManager.addEvent(ShortMessage.CHANNEL_PRESSURE,
channelPressure.getPressure());
}
@Override
public void polyphonicPressureEvent(PolyphonicPressure polyphonicPressure) {
this.eventManager.addEvent(ShortMessage.POLY_PRESSURE,
polyphonicPressure.getKey(), polyphonicPressure.getPressure());
}
@Override
public void pitchBendEvent(PitchBend pitchBend) {
this.eventManager.addEvent(ShortMessage.PITCH_BEND,
pitchBend.getBend()[0], pitchBend.getBend()[1]);
}
@Override
public void noteEvent(Note note) {
// Remember the current track time, so we can flip back to it
// if there are other notes to play in parallel
this.initialNoteTime = this.eventManager.getTrackTimer();
long duration = note.getDuration();
// If there is no duration, don't add this note to the event manager
// TODO: This is a special case as of v4.0.3 that should be re-thought
// if a new noteEvent callback is created in v5.0
if (duration == 0) {
return;
}
// Add messages to the track
if (note.isRest()) {
this.eventManager.advanceTrackTimer(duration);
} else {
initialNoteTime = eventManager.getTrackTimer();
byte attackVelocity = note.getAttackVelocity();
byte decayVelocity = note.getDecayVelocity();
this.eventManager.addNoteEvent(note.getValue(), attackVelocity,
decayVelocity, duration, !note.isEndOfTie(),
!note.isStartOfTie());
}
}
@Override
public void sequentialNoteEvent(Note note) {
long duration = note.getDuration();
if (note.isRest()) {
this.eventManager.advanceTrackTimer(duration);
} else {
byte attackVelocity = note.getAttackVelocity();
byte decayVelocity = note.getDecayVelocity();
this.eventManager.addNoteEvent(note.getValue(), attackVelocity,
decayVelocity, duration, !note.isEndOfTie(),
!note.isStartOfTie());
}
}
@Override
public void parallelNoteEvent(Note note) {
long duration = note.getDuration();
this.eventManager.setTrackTimer(this.initialNoteTime);
if (note.isRest()) {
this.eventManager.advanceTrackTimer(duration);
} else {
byte attackVelocity = note.getAttackVelocity();
byte decayVelocity = note.getDecayVelocity();
this.eventManager.addNoteEvent(note.getValue(), attackVelocity,
decayVelocity, duration, !note.isEndOfTie(),
!note.isStartOfTie());
}
}
}