/*
* JFugue, an Application Programming Interface (API) for Music Programming
* http://www.jfugue.org
*
* Copyright (C) 2003-2014 David Koelle
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jfugue.midi;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jp.kshoji.javax.sound.midi.MetaMessage;
import jp.kshoji.javax.sound.midi.MidiEvent;
import jp.kshoji.javax.sound.midi.MidiMessage;
import jp.kshoji.javax.sound.midi.Receiver;
import jp.kshoji.javax.sound.midi.Sequence;
import jp.kshoji.javax.sound.midi.Track;
public class MidiTools {
/**
* Returns a Map where the keys are MIDI ticks and the values are Lists of MidiMessages
* that are declared for that tick.
*/
public static final Map<Long, List<MidiMessage>> sortMessagesByTick(Sequence sequence) {
Map<Long, List<MidiMessage>> sortedMessages = new HashMap<Long, List<MidiMessage>>();
for (Track track : sequence.getTracks()) {
for (int i = 0; i < track.size(); i++) {
MidiEvent event = track.get(i);
List<MidiMessage> messagesAtTick = null;
if (sortedMessages.containsKey(event.getTick())) {
messagesAtTick = sortedMessages.get(event.getTick());
} else {
messagesAtTick = new ArrayList<MidiMessage>();
sortedMessages.put(event.getTick(), messagesAtTick);
}
messagesAtTick.add(event.getMessage());
}
}
return sortedMessages;
}
/**
* Returns the largest key for the given Map. While this can be used for any Map,
* it is included here specifically to find the greatest tick in a Map<Long, List<MidiMessageWithTrack>>
*/
public static <K extends Comparable<K>, V> K getLargestKey(Map<K, V> map) {
K currentLargestKey = null;
for (K key : map.keySet()) {
if ((currentLargestKey == null) || (key.compareTo(currentLargestKey) > 0)) {
currentLargestKey = key;
}
}
return currentLargestKey;
}
private static int calculateTicksPerSecondFromMidiSetTempoMessageData(byte[] data, float sequenceResolution) {
// The "Set Tempo" MIDI Message sets the tempo of a sequence in microseconds per quarter note
int microsecondsPerQuarterNote = ((data[0] & 0xff) << 16) | ((data[1] & 0xff) << 8) | (data[2] & 0xff);
int bpm = (int)(60000000.0D / microsecondsPerQuarterNote);
return (int)(sequenceResolution * bpm / 60.0D);
}
private static int calculateTime(long deltaTick, int ticksPerSecond) {
return (int)(deltaTick * 1000.0D / ticksPerSecond);
}
public static void sendSortedMidiMessagesToReceiver(Map<Long, List<MidiMessage>> sortedMidiMessages, float sequenceDivisionType, int sequenceResolution, Receiver receiver) {
int bpm = MidiDefaults.DEFAULT_TEMPO_BEATS_PER_MINUTE;
int ticksPerSecond;
long prevTick = 0L;
long msTime = 0L;
long largestTick = getLargestKey(sortedMidiMessages);
if (sequenceDivisionType == Sequence.PPQ) {
ticksPerSecond = (int)(sequenceResolution * bpm / 60.0D);
} else {
double framesPerSecond =
(sequenceDivisionType == Sequence.SMPTE_24 ? 24
: (sequenceDivisionType == Sequence.SMPTE_25 ? 25
: (sequenceDivisionType == Sequence.SMPTE_30 ? 30
: (sequenceDivisionType == Sequence.SMPTE_30DROP ? 29.97 : 24))));
ticksPerSecond = (int)(sequenceResolution * framesPerSecond);
}
for (long tick = 0; tick <= largestTick; tick++) {
if (sortedMidiMessages.containsKey(tick)) {
msTime = calculateTime(tick - prevTick, ticksPerSecond);
List<MidiMessage> messages = sortedMidiMessages.get(tick);
for (MidiMessage message : messages) {
if ((message instanceof MetaMessage) && (sequenceDivisionType == Sequence.PPQ) && (((MetaMessage)message).getType() == MidiDefaults.SET_TEMPO_MESSAGE_TYPE)) {
ticksPerSecond = calculateTicksPerSecondFromMidiSetTempoMessageData(((MetaMessage)message).getData(), sequenceResolution);
msTime = calculateTime(tick - prevTick, ticksPerSecond);
} else {
receiver.send(message, msTime);
}
}
try {
Thread.sleep(msTime);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
prevTick = tick;
}
}
}
/**
* Convenience method for a commonly-used idiom
*/
public static void sendSequenceToReceiver(Sequence sequence, Receiver receiver) {
sendSortedMidiMessagesToReceiver(sortMessagesByTick(sequence), sequence.getDivisionType(), sequence.getResolution(), receiver);
}
public static byte getLSB(int value) {
return (byte)(value & 0x7F);
}
public static byte getMSB(int value) {
return (byte)((value >> 7) & 0x7F);
}
}