/*
* Created on 5.3.2007
*
* Copyright (c) 2007 Karl Helgason
*
* http://www.frinika.com
*
* This file is part of Frinika.
*
* Frinika 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
* (at your option) any later version.
* Frinika 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 Frinika; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.frinika.renderer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiMessage;
import javax.sound.midi.ShortMessage;
import com.frinika.sequencer.FrinikaSequence;
import com.frinika.sequencer.FrinikaSequencer;
import com.frinika.sequencer.FrinikaTrackWrapper;
import com.frinika.sequencer.model.MidiPlayOptions;
class TrackIterator
{
FrinikaTrackWrapper track;
boolean used;
int pos = 0;
MidiMessage nextevent = null;
long nexttick = -1;
MidiPlayOptions opt;
public boolean isUsed()
{
return used;
}
public boolean hasNext()
{
return nextevent != null;
}
public long nextTick()
{
return nexttick;
}
public MidiMessage next()
{
MidiMessage event = nextevent;
pos++;
if(pos < track.size())
{
nextevent = track.get(pos).getMessage();
nexttick = track.get(pos).getTick();
}
else
{
nextevent = null;
nexttick = -1;
}
return event;
}
}
public class FrinikaMidiPacketProvider implements MidiPacketProvider {
FrinikaSequence seq;
TrackIterator[] tracks_iterator;
long packetlen;
int seqres;
int midi_channel = 0;
public FrinikaMidiPacketProvider(long packetlen, FrinikaSequencer seqr, FrinikaSequence seq, Collection<FrinikaTrackWrapper> tracks)
{
this.seq = seq;
this.packetlen = packetlen;
seqres = seq.getResolution();
tempo = seqr.getTempoInBPM();
ArrayList<FrinikaTrackWrapper> seqtracklist = new ArrayList<FrinikaTrackWrapper>();
Collection<FrinikaTrackWrapper> seqtracks;
if(seqr.getSoloFrinikaTrackWrappers().size()>0)
seqtracks = seqr.getSoloFrinikaTrackWrappers();
else
seqtracks = seq.getFrinikaTrackWrappers();
for(FrinikaTrackWrapper track : seqtracks)
{
if(track.getMidiDevice() != null)
{
MidiPlayOptions opt = seqr.getPlayOptions(track);
if(opt != null)
if(opt.muted)
{
continue;
}
seqtracklist.add(track);
}
}
this.tracks_iterator = new TrackIterator[seqtracklist.size()];
for (int i = 0; i < this.tracks_iterator.length; i++) {
FrinikaTrackWrapper track = seqtracklist.get(i);
this.tracks_iterator[i] = new TrackIterator();
this.tracks_iterator[i].track = track;
this.tracks_iterator[i].used = tracks.contains(track);
this.tracks_iterator[i].opt = seqr.getPlayOptions(track);
if(track.size() != 0)
{
this.tracks_iterator[i].nextevent = track.get(0).getMessage();
this.tracks_iterator[i].nexttick = track.get(0).getTick();
}
if(this.tracks_iterator[i].used)
if (track.getMidiChannel() != FrinikaTrackWrapper.CHANNEL_FROM_EVENT) {
midi_channel = track.getMidiChannel();
}
}
readNextEvent();
}
float tempo = 100; // in BPM
MidiMessage current_msg = null;
long current_tick_pos;
long current_event_pos; // In microsecond
public MidiMessage nextEvent()
{
while(true)
{
long nexttick = 0;
TrackIterator sel_iterator = null;
for(TrackIterator track_iterator : tracks_iterator)
if(track_iterator.hasNext())
if(sel_iterator == null || track_iterator.nextTick() < nexttick)
{
sel_iterator = track_iterator;
nexttick = track_iterator.nextTick();
}
if(sel_iterator == null) return null;
long tick = sel_iterator.nextTick();
MidiMessage event = sel_iterator.next();
if(tick != current_tick_pos)
{
long tickdiff = tick - current_tick_pos;
long timediff = (long)( tickdiff * (60000000f/(tempo*seqres)) );
current_tick_pos = tick;
current_event_pos += timediff;
}
byte[] msgBytes = event.getMessage();
if (msgBytes[0] == -1 && msgBytes[1] == 0x51
&& msgBytes[2] == 3) {
int mpq = ((msgBytes[3] & 0xff) << 16)
| ((msgBytes[4] & 0xff) << 8)
| (msgBytes[5] & 0xff);
// pjl removed cast to int for tempo
tempo = (60000000f / mpq);
}
if(sel_iterator.used)
{
if(event instanceof ShortMessage)
{
return processMessage(sel_iterator.track, sel_iterator.opt, (ShortMessage)event);
}
}
}
}
public void readNextEvent()
{
current_msg = nextEvent();
}
public void seek(int index)
{
if(index < (current_index-1))
{
current_index = -1;
}
while(index > (current_index+1))
{
if(current_msg == null) return;
while(current_event_pos < packetlen)
{
updateStatus(current_msg);
readNextEvent();
}
current_event_pos -= packetlen;
current_index++;
}
}
public LinkedList<Integer> activenotes = new LinkedList<Integer>();
public LinkedList<Integer> activenotes_velocity = new LinkedList<Integer>();
public LinkedList<Integer> controls = new LinkedList<Integer>();
public LinkedList<Integer> controls_values = new LinkedList<Integer>();
public int program = -1;
public int pitchbend_data1 = -1;
public int pitchbend_data2 = -1;
public MidiMessage processMessage(FrinikaTrackWrapper track, MidiPlayOptions opt, ShortMessage message)
{
if(opt == null) return message;
byte[] msgBytes = message.getMessage();
int ch = message.getChannel();
if (track.getMidiChannel() != FrinikaTrackWrapper.CHANNEL_FROM_EVENT) {
ch = track.getMidiChannel();
}
if ( (msgBytes.length > 2) && (((msgBytes[0] & 0xf0) == ShortMessage.NOTE_OFF || (msgBytes[0] & 0xf0) == ShortMessage.NOTE_ON ) && (
(opt.transpose != 0) || (opt.velocityOffset != 0) || (opt.velocityCompression != 0.0f)
)) ) {
// need to do some on-the-fly modifications of the event
int note = msgBytes[1];
note += opt.transpose;
if (note < 0) {
note = 0;
} else if (note > 127) {
note = 127;
}
int vel = msgBytes[2];
if(vel != 0)
{
if (opt.velocityCompression != 0.0f) {
float diff = (64 - vel) * opt.velocityCompression;
vel += diff;
}
vel += opt.velocityOffset;
if (vel < 1) {
vel = 1;
} else if (vel > 127) {
vel = 127;
}
}
ShortMessage shm = new ShortMessage();
try {
shm.setMessage(message.getCommand(), ch, note, vel);
} catch (InvalidMidiDataException e) {
e.printStackTrace();
}
return shm;
} else { // normalsend
ShortMessage shm = new ShortMessage();
try {
shm.setMessage(message.getCommand(), ch, message.getData1(), message.getData2());
} catch (InvalidMidiDataException e) {
e.printStackTrace();
}
return shm;
}
}
public void updateStatus(MidiMessage msg)
{
if(msg instanceof ShortMessage)
{
ShortMessage sms = (ShortMessage)msg;
switch (sms.getCommand()) {
case ShortMessage.NOTE_ON:
int ix = activenotes.indexOf(new Integer(sms.getData1()));
if(ix != -1)
{
activenotes.remove(ix);
activenotes_velocity.remove(ix);
}
if(sms.getData2()>0)
{
activenotes.add(new Integer(sms.getData1()));
activenotes_velocity.add(new Integer(sms.getData2()));
}
break;
case ShortMessage.NOTE_OFF:
ix = activenotes.indexOf(new Integer(sms.getData1()));
if(ix != -1)
{
activenotes.remove(ix);
activenotes_velocity.remove(ix);
}
break;
case ShortMessage.PROGRAM_CHANGE:
program = sms.getData1();
break;
case ShortMessage.PITCH_BEND:
pitchbend_data1 = sms.getData1();
pitchbend_data2 = sms.getData2();
break;
case ShortMessage.CONTROL_CHANGE:
ix = controls.indexOf(new Integer(sms.getData1()));
if(ix != -1)
{
controls.remove(ix);
controls_values.remove(ix);
}
controls.add(sms.getData1());
controls_values.add(sms.getData2());
break;
default:
break;
}
}
}
public MidiPacket createPacket()
{
MidiPacket packet = new MidiPacket();
Iterator<Integer> iter;
packet.activenotes = new int[activenotes.size()];
iter = activenotes.iterator();
for (int i = 0; i < packet.activenotes.length; i++) {
packet.activenotes[i] = iter.next();
}
packet.activenotes_velocity = new int[activenotes_velocity.size()];
iter = activenotes_velocity.iterator();
for (int i = 0; i < packet.activenotes_velocity.length; i++) {
packet.activenotes_velocity[i] = iter.next();
}
packet.controls = new int[controls.size()];
iter = controls.iterator();
for (int i = 0; i < packet.controls.length; i++) {
packet.controls[i] = iter.next();
}
packet.controls_values = new int[controls_values.size()];
iter = controls_values.iterator();
for (int i = 0; i < packet.controls_values.length; i++) {
packet.controls_values[i] = iter.next();
}
packet.program = program;
packet.pitchbend_data1 = pitchbend_data1;
packet.pitchbend_data2 = pitchbend_data2;
return packet;
}
public MidiPacket next()
{
if(current_msg == null) return null;
MidiPacket packet = createPacket();
ArrayList<MidiEvent> events = new ArrayList<MidiEvent>();
ShortMessage msg1 = new ShortMessage();
ShortMessage msg2 = new ShortMessage();
//System.out.println("-------------------------------------------------------------------------");
while(current_event_pos < packetlen)
{
if(current_msg == null) break;
updateStatus(current_msg);
/*
String cmd = "" + ((ShortMessage)current_msg).getCommand();
if(((ShortMessage)current_msg).getCommand() == ShortMessage.PROGRAM_CHANGE) cmd = "PROGRAM";
if(((ShortMessage)current_msg).getCommand() == ShortMessage.NOTE_OFF) cmd = "NOTE_OFF";
if(((ShortMessage)current_msg).getCommand() == ShortMessage.NOTE_ON) cmd = "NOTE_ON";
if(((ShortMessage)current_msg).getCommand() == ShortMessage.CONTROL_CHANGE) cmd = "CONTROL";
if(((ShortMessage)current_msg).getCommand() == ShortMessage.PITCH_BEND) cmd = "PITCH";
System.out.println(current_event_pos + " " + ((ShortMessage)current_msg).getChannel() + "." + cmd
+ "(" + ((ShortMessage)current_msg).getData1() + " , " + ((ShortMessage)current_msg).getData2() + ")");
*/
events.add(new MidiEvent(current_msg, current_event_pos));
readNextEvent();
}
current_event_pos -= packetlen;
packet.events = new MidiEvent[events.size()];
events.toArray(packet.events);
packet.channel = midi_channel;
current_index++;
return packet;
}
int current_index = -1;
public MidiPacket get(int index)
{
if((current_index+1 != index))
seek(index);
return next();
}
}