/*
* 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 java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.Sequence;
public final class TimeFactor
{
public static double DEFAULT_BPM = 120.0d;
public static int QUARTER_DURATIONS_IN_WHOLE = 4;
public static final double getTimeFactor(Sequence sequence, double bpm)
{
double divisionType = sequence.getDivisionType();
double resolution = sequence.getResolution();
// If division type is PPQ, resolution is ticks per beat.
// Since a beat is the length of time given to a one quarter note, this essentially
// means that ticks per beat == pulses per quarter note (PPQ or PPQN)
if (divisionType == Sequence.PPQ)
{
// System.out.println("DivisionType is PPQ");
// System.out.println("Resolution is "+resolution);
} else {
// System.out.println("DivisionType is SMPTE");
}
// Useful resources: http://www.borg.com/~jglatt/tech/midifile/tempo.htm and http://www.borg.com/~jglatt/tech/midifile/ppqn.htm
// If bit 15 of division is a zero, the bits 14 thru 0 represent the number of delta-time ticks which make up a
// quarter-note. For instance, if division is 96, then a time interval of an eighth-note between two events
// in the file would be 48.
//
// If bit 15 of division is a one, delta-times in a file correspond to subdivisions of a second, in a way consistent
// with SMPTE and MIDI time code. Bits 14 thru 8 contain one of the four values -24, -25, -29, or -30, corresponding
// to the four standard SMPTE and MIDI time code formats (-29 corresponds to 30 drop frame), and represents the number
// of frames per second. These negative numbers are stored in two's complement form. The second byte (stored positive)
// is the resolution within a frame: typical values may be 4 (MIDI time code resolution), 8, 10, 80 (bit resolution),
// or 100. This system allows exact specification of time- code-based tracks, but also allows millisecond-based tracks
// by specifying 25 frames/sec and a resolution of 40 units per frame. If the events in a file are stored with bit
// resolution of thirty-frame time code, the division word would be E250 hex.
if (bpm == 0.0)
{
bpm = DEFAULT_BPM;
}
return 60000.0 / (resolution * bpm);
}
public static final byte[] convertToThreeTempoBytes(int tempo)
{
double tempoInMsPerBeat = TimeFactor.convertBPMToMicrosecondsPerBeat(tempo);
double d1 = Math.floor(tempoInMsPerBeat / 16384.0);
double d2 = Math.floor((tempoInMsPerBeat % 16384.0) / 128.0);
double d3 = Math.floor((tempoInMsPerBeat % 16384.0) % 128.0);
return new byte[] { (byte)d1, (byte)d2, (byte)d3 };
}
public static final int parseMicrosecondsPerBeat(MetaMessage message, long timestamp)
{
int tempo = message.getData()[0]*16384 + message.getData()[1]*128 + message.getData()[2];
int beatsPerMinute = (int)convertMicrosecondsPerBeatToBPM(tempo);
return beatsPerMinute;
}
/** Converts microseconds per beat to BPM -- and vice versa */
public static final double convertMicrosecondsPerBeatToBPM(double value) {
double microsecondsPerMinute = 60000000.0D;
if (value == 0.0d) {
return 0.0d;
}
return microsecondsPerMinute / value;
}
/** Converts microseconds per beat to BPM -- and vice versa */
public static final double convertBPMToMicrosecondsPerBeat(int bpm) {
double microsecondsPerMinute = 60000000.0D;
if (bpm == 0) {
return 0;
}
return microsecondsPerMinute / bpm;
}
/**
* Takes all of the MIDI events in the given Sequence, sorts them according to
* when they are to be played, and sends the events to the MidiMessageRecipient
* when the each event is ready to be played.
*
* @param sequence The Sequence with messages to sort and deliver
* @param recipient the handler of the delivered message
*/
public static final void sortAndDeliverMidiMessages(Sequence sequence, MidiMessageRecipient recipient)
{
double timeFactor = 1.0;
Map<Long, List<MidiEvent>> timeMap = new HashMap<Long, List<MidiEvent>>();
long longestTime = TimeEventManager.sortSequenceByTimestamp(sequence, timeMap);
long lastTime = 0;
for (long time=0; time < longestTime; time++)
{
List<MidiEvent> midiEventList = (List<MidiEvent>)timeMap.get(time);
if (midiEventList != null)
{
for (MidiEvent event : midiEventList)
{
MidiMessage message = event.getMessage();
if ((message.getMessage().length >= 2) && (message.getMessage()[1] == 0x51) && (message instanceof MetaMessage))
{
int bpm = parseMicrosecondsPerBeat((MetaMessage)message, time);
timeFactor = TimeFactor.getTimeFactor(sequence, bpm);
System.out.println("TimeFactor is "+timeFactor);
}
recipient.messageReady(message, time);
}
try
{
long sleepTime = (int)(((time - lastTime) * (TimeFactor.QUARTER_DURATIONS_IN_WHOLE+0.20)));
Thread.sleep(sleepTime); // (int) (1 * timeFactor));
lastTime = time;
} catch (Exception ex)
{
throw new JFugueException(JFugueException.ERROR_SLEEP);
}
}
}
}
}