jm.midi.MidiSynth Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jmusic Show documentation
Show all versions of jmusic Show documentation
JMusic - Java Music Library
The newest version!
/*
< This Java Class is part of the jMusic API>
Copyright (C) 2000 Andrew Sorensen & Andrew Brown
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or any
later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package jm.midi;
import jm.JMC;
import jm.music.data.Note;
import jm.music.data.Part;
import jm.music.data.Phrase;
import jm.music.data.Score;
import javax.sound.midi.*;
import javax.sound.midi.Track;
import java.util.Enumeration;
import java.util.Stack;
/**
* MidiSynth.java
*
* Created: Mon May 07 11:21:30 2001
*
* @author Mark Elston (enhanced by Andrew Brown)
*/
public class MidiSynth implements JMC, MetaEventListener {
private final static int StopType = 47; // End of track
/**
* Pulses per quarter note value
*/
private short m_ppqn;
/**
* The Synthesizer we are using
*/
private Synthesizer m_synth;
/**
* The Sequence we are using
*/
private Sequence m_seq;
/**
* The Sequencer we are using
*/
private Sequencer m_sequencer;
/**
* The current tempo value
*/
private float m_currentTempo;
/**
* The overall (Score) tempo value
*/
private float m_masterTempo;
/**
* All previous tempos
*/
private Stack m_tempoHistory;
/**
* The diff. beteen the score and part tempi
*/
private double trackTempoRatio = 1.0;
/**
* The diff. between the score and phrase tempi
*/
private double elementTempoRatio = 1.0;
/**
* The name of the jMusic score
*/
private String scoreTitle;
/**
* Sequence is playiong flag
*/
private boolean isPlaying = false;
private Boolean msCycle = false;
private Boolean update = false;
private Score updateScore;
public MidiSynth() {
this((short) 480);
}
public MidiSynth(short ppqn) {
m_ppqn = ppqn;
m_synth = null;
m_tempoHistory = new Stack();
}
/**
* Create a Note On Event
*
* @param int channel is the channel to change
* @param int pitch is the pitch of the note
* @param int velocity is the velocity of the note
* @param long tick is the time this event occurs
*/
protected static MidiEvent createNoteOnEvent(int channel,
int pitch, int velocity,
long tick)
throws InvalidMidiDataException {
ShortMessage msg = new ShortMessage();
msg.setMessage(0x90 + channel, pitch, velocity);
MidiEvent evt = new MidiEvent(msg, tick);
return evt;
}
/**
* Create a Note Off Event
*
* @param int channel is the channel to change
* @param int pitch is the pitch of the note
* @param int velocity is the velocity of the note
* @param long tick is the time this event occurs
*/
protected static MidiEvent createNoteOffEvent(int channel,
int pitch, int velocity,
long tick)
throws InvalidMidiDataException {
ShortMessage msg = new ShortMessage();
msg.setMessage(0x80 + channel, pitch, velocity);
MidiEvent evt = new MidiEvent(msg, tick);
return evt;
}
/**
* Create a Program Change Event
*
* @param int channel is the channel to change
* @param int value is the new value to use
* @param long tick is the time this event occurs
*/
protected static MidiEvent createProgramChangeEvent(int channel,
int value,
long tick)
throws InvalidMidiDataException {
ShortMessage msg = new ShortMessage();
msg.setMessage(0xC0 + channel, value, 0);
MidiEvent evt = new MidiEvent(msg, tick);
return evt;
}
/**
* Create a Control Change event
*
* @param int channel is the channel to use
* @param int controlNum is the control change number to use
* @param int value is the value of the control change
*/
protected static MidiEvent createCChangeEvent(int channel,
int controlNum,
int value,
long tick)
throws InvalidMidiDataException {
ShortMessage msg = new ShortMessage();
msg.setMessage(0xB0 + channel, controlNum, value);
MidiEvent evt = new MidiEvent(msg, tick);
return evt;
}
public boolean isPlaying() {
return isPlaying;
}
/**
* Plays the jMusic score data via the JavaSound MIDI synthesizer
*
* @param Score score - data to change to SMF
* @throws Exception
*/
public void play(Score score) throws InvalidMidiDataException {
if (null == m_sequencer) {
if (!initSynthesizer()) {
return;
}
}
scoreTitle = score.getTitle();
m_masterTempo = (float) score.getTempo();
if (null == m_seq) {
m_seq = scoreToSeq(score);
} else {
// empty seq and refill
Track[] tracks = m_seq.getTracks();
int t_len = tracks.length;
for (int i = 0; i < t_len; i++) {
m_seq.deleteTrack(tracks[i]);
}
m_seq = scoreToSeq(score);
}
if (null != m_seq) {
try {
m_sequencer.open();
} catch (MidiUnavailableException e) {
System.err.println("MIDI System Unavailable:" + e);
return;
}
m_sequencer.setSequence(m_seq);
m_sequencer.addMetaEventListener(this);
m_sequencer.setMicrosecondPosition(0l);
m_sequencer.setTempoInBPM(m_masterTempo);
//printSeqInfo(seq);
m_sequencer.start();
isPlaying = true;
}
}
public void updateSeq(Score score) throws InvalidMidiDataException {
update = true;
updateScore = score;
}
public void setCycle(Boolean val) {
msCycle = val;
}
/**
* Plays back the already computed sequencer via a MIDI synthesizer
*
* @throws Exception
*/
private void rePlay() {
if (null == m_sequencer) {
if (!initSynthesizer()) {
return;
}
}
if (update) {
System.out.println("Updating playback sequence");
//Sequence seq;
try {
m_seq = scoreToSeq(updateScore);
if (null != m_seq) {
try {
m_sequencer.open();
} catch (MidiUnavailableException e) {
System.err.println("MIDI System Unavailable:" + e);
return;
}
m_sequencer.setSequence(m_seq);
}
} catch (InvalidMidiDataException e) {
System.err.println("MIDISynth updating sequence error:" + e);
return;
}
update = false;
}
m_sequencer.setMicrosecondPosition(0l);
m_sequencer.setTempoInBPM(m_masterTempo);
m_sequencer.start();
}
protected void printSeqInfo(Sequence seq) {
//System.out.println("Score Title: " + scoreTitle);
//System.out.println("Score TempoEvent: " + m_currentTempo + " BPM");
//System.out.print("Sequence Division Type = ");
float type = seq.getDivisionType();
/*
if (Sequence.PPQ == type)
System.out.println("PPQ");
else if (Sequence.SMPTE_24 == type)
System.out.println("SMPTE 24 (24 fps)");
else if (Sequence.SMPTE_25 == type)
System.out.println("SMPTE 25 (25 fps)");
else if (Sequence.SMPTE_30 == type)
System.out.println("SMPTE 30 (30 fps)");
else if (Sequence.SMPTE_30DROP == type)
System.out.println("SMPTE 30 Drop (29.97 fps)");
else
System.out.println("Unknown");
System.out.println("Sequence Resolution = " +
seq.getResolution());
System.out.println("Sequence TickLength = " +
seq.getTickLength());
System.out.println("Sequence Microsecond Length = " +
seq.getMicrosecondLength());
System.out.println("Sequencer TempoEvent (BPM) = " +
m_sequencer.getTempoInBPM());
System.out.println("Sequencer TempoEvent (MPQ) = " +
m_sequencer.getTempoInMPQ());
System.out.println("Sequencer TempoFactor = " +
m_sequencer.getTempoFactor());
*/
}
/**
* Invoked when a Sequencer has encountered and processed a MetaMessage
* in the Sequence it is processing.
*
* @param MetaMessage meta - the meta-message that the sequencer
* encountered
*/
public void meta(MetaMessage metaEvent) {
//System.out.println("JavaSound sequencer sent meta event");
if (metaEvent.getType() == StopType) {
if (msCycle) {
rePlay();
} else {
stop();
}
}
}
/**
* Close JavaSound sequencer and synthesizer objects
*/
public void stop() {
msCycle = false;
isPlaying = false;
if (m_sequencer != null & m_sequencer.isOpen()) {
m_sequencer.stop();
}
System.out.println("jMusic MidiSynth: Stopped JavaSound MIDI playback");
}
/**
* Converts jmusic score data into a MIDI Sequence
*
* @param Score score - data to play
* @return Sequence to be played
* @throws Exception
*/
protected Sequence scoreToSeq(Score score)
throws InvalidMidiDataException {
//Create the Sequence
Sequence sequence = new Sequence(Sequence.PPQ, m_ppqn);
if (null == sequence) {
return null;
}
m_masterTempo = m_currentTempo =
new Float(score.getTempo()).floatValue();
//System.out.println("jMusic MidiSynth notification: Score TempoEvent (BPM) = " + score.getTempo());
Track longestTrack = null;
double longestTime = 0.0;
double longestRatio = 1.0;
Enumeration parts = score.getPartList().elements();
while (parts.hasMoreElements()) {
Part inst = (Part) parts.nextElement();
int currChannel = inst.getChannel();
if (currChannel > 16) {
throw new
InvalidMidiDataException(inst.getTitle() +
" - Invalid Channel Number: " +
currChannel);
}
m_tempoHistory.push(new Float(m_currentTempo));
float tempo = new Float(inst.getTempo()).floatValue();
//System.out.println("jMusic MidiSynth notification: Part TempoEvent (BPM) = " + tempo);
if (tempo != Part.DEFAULT_TEMPO) {
m_currentTempo = tempo;
} else if (tempo < Part.DEFAULT_TEMPO)
System.out.println("jMusic MidiSynth error: Part TempoEvent (BPM) too low = " + tempo);
trackTempoRatio = m_masterTempo / m_currentTempo;
int instrument = inst.getInstrument();
if (instrument == NO_INSTRUMENT) instrument = 0;
Enumeration phrases = inst.getPhraseList().elements();
double max = 0;
double currentTime = 0.0;
//
// One track per Part
/////////////
Track currTrack = sequence.createTrack();
while (phrases.hasMoreElements()) {
/////////////////////////////////////////////////
// Each phrase represents a new Track element
// Err no
// There is a 65? track limit
// ////////////////////////////
Phrase phrase = (Phrase) phrases.nextElement();
//Track currTrack = sequence.createTrack();
currentTime = phrase.getStartTime();
long phraseTick = (long) (currentTime * m_ppqn * trackTempoRatio);
MidiEvent evt;
if (phrase.getInstrument() != NO_INSTRUMENT) instrument = phrase.getInstrument();
evt = createProgramChangeEvent(currChannel, instrument, phraseTick);
currTrack.add(evt);
m_tempoHistory.push(new Float(m_currentTempo));
tempo = new Float(phrase.getTempo()).floatValue();
if (tempo != Phrase.DEFAULT_TEMPO) {
m_currentTempo = tempo;
//System.out.println("jMusic MidiSynth notification: Phrase TempoEvent (BPM) = " + tempo);
}
elementTempoRatio = m_masterTempo / m_currentTempo;
double lastPanPosition = -1.0;
int offSetTime = 0;
/// Each note
Enumeration notes = phrase.getNoteList().elements();
while (notes.hasMoreElements()) {
Note note = (Note) notes.nextElement();
// deal with offset
offSetTime = (int) (note.getOffset() * m_ppqn * elementTempoRatio);
//handle frequency pitch types
int pitch = -1;
if (note.getPitchType() == Note.MIDI_PITCH) {
pitch = note.getPitch();
} else {
pitch = Note.freqToMidiPitch(note.getFrequency());
}
int dynamic = note.getDynamic();
if (pitch == Note.REST) {
phraseTick += note.getRhythmValue() * m_ppqn * elementTempoRatio;
continue;
}
long onTick = (long) (phraseTick);
// pan
if (note.getPan() != lastPanPosition) {
evt = createCChangeEvent(currChannel, 10, (int) (note.getPan() * 127), onTick);
currTrack.add(evt);
lastPanPosition = note.getPan();
}
evt = createNoteOnEvent(currChannel, pitch, dynamic, onTick + offSetTime);
currTrack.add(evt);
long offTick = (long) (phraseTick + note.getDuration() * m_ppqn * elementTempoRatio);
evt = createNoteOffEvent(currChannel, pitch, dynamic, offTick + offSetTime);
currTrack.add(evt);
phraseTick += note.getRhythmValue() * m_ppqn * elementTempoRatio;
// TODO: Should this be ticks since we have tempo stuff
// to worry about
//System.out.println("offtick = " + offTick + " ppq = " +
// m_ppqn + " score length = " + score.getEndTime() +
// " length * ppq = " + (m_ppqn * score.getEndTime()));
if ((double) offTick > longestTime) {
longestTime = (double) offTick;
longestTrack = currTrack;
//longestRatio = trackTempoRatio;
}
}
Float d = (Float) m_tempoHistory.pop();
m_currentTempo = d.floatValue();
} // while(phrases.hasMoreElements())
Float d = (Float) m_tempoHistory.pop();
m_currentTempo = d.floatValue();
} // while(parts.hasMoreElements())
// add a meta event to indicate the end of the sequence.
if (longestTime > 0.0 && longestTrack != null) {
MetaMessage msg = new MetaMessage();
byte[] data = new byte[0];
msg.setMessage(StopType, data, 0);
MidiEvent evt = new MidiEvent(msg, (long) longestTime); //+ 100 if you want leave some space for reverb tail
longestTrack.add(evt);
}
return sequence;
}
private boolean initSynthesizer() {
if (null == m_synth) {
try {
if (MidiSystem.getSequencer() == null) {
System.err.println("MidiSystem Sequencer Unavailable");
return false;
}
m_synth = MidiSystem.getSynthesizer();
m_synth.open();
m_sequencer = MidiSystem.getSequencer();
//m_sequencer.open();
} catch (MidiUnavailableException e) {
System.err.println("Midi System Unavailable:" + e);
return false;
}
}
return true;
}
/**
* Close off all open JavaSound playback objects
*/
public void finalize() {
m_seq = null;
m_sequencer.close();
m_synth.close();
}
}// MidiSynth