/* * 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()); } } }