package org.herac.tuxguitar.midiinput;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Map.Entry;
import org.apache.log4j.Logger;
import org.herac.tuxguitar.gui.TuxGuitar;
import org.herac.tuxguitar.gui.editors.tab.TGBeatImpl;
import org.herac.tuxguitar.gui.editors.tab.TGNoteImpl;
import org.herac.tuxguitar.song.managers.TGSongManager;
import org.herac.tuxguitar.song.managers.TGTrackManager;
import org.herac.tuxguitar.song.models.TGBeat;
import org.herac.tuxguitar.song.models.TGDuration;
import org.herac.tuxguitar.song.models.TGMeasure;
import org.herac.tuxguitar.song.models.TGMeasureHeader;
import org.herac.tuxguitar.song.models.TGNote;
import org.herac.tuxguitar.song.models.TGTrack;
class MiStaff {
/** The Logger for this class. */
public static final transient Logger LOG = Logger.getLogger(MiStaff.class);
static long ticksToTimestamp(int inTempo, long inTicks) {
long timeStamp = (inTicks * 60000000L)
/ (inTempo * TGDuration.QUARTER_TIME);
return (timeStamp);
}
static long timestampToTicks(int inTempo, long inTimeStamp) {
long ticks = (inTimeStamp * inTempo * TGDuration.QUARTER_TIME) / 60000000L;
return (ticks);
}
private boolean f_Dump_Input = false; // for debugging...
private boolean f_Dump_TrackGeneration = false; // for debugging...
private Map<Long, MiStaffEvent> f_Events = new TreeMap<Long, MiStaffEvent>(); // staff
// events
// map
private TGTrack f_TgTrack = null; // work track
MiStaff(final List<MiNote> inBufferNotes, // MIDI input notes from buffer
// [microseconds]
int inTempo, // quarters per minute
long inStartTime, // first MIDI time stamp [microseconds]
long inStopTime, // last MIDI time stamp [microseconds]
long inStartPosition, // recording start position [ticks]
String inTrackName) // name for the TuxGuitar track to be created
{
List<MiNote> midiNotes = new ArrayList<MiNote>();
// make a deep copy of input buffer notes
for (final MiNote note : inBufferNotes) {
midiNotes.add(new MiNote(note));
}
// convert time stamps from absolute microseconds to song relative ticks
for (final MiNote note : midiNotes) {
long timeOn = note.getTimeOn(), timeOff = note.getTimeOff();
// absolute to relative time stamps
timeOn -= inStartTime;
timeOff -= inStartTime;
// time stamps to ticks
timeOn = inStartPosition + timestampToTicks(inTempo, timeOn);
timeOff = inStartPosition + timestampToTicks(inTempo, timeOff);
// update values
note.setTimeOn(timeOn);
note.setTimeOff(timeOff);
}
if (this.f_Dump_Input) {
MiBuffer.dump(inBufferNotes, "input buffer MIDI notes");
MiBuffer.dump(midiNotes, "converted MIDI notes");
}
TGSongManager tgSongMgr = TuxGuitar.instance().getSongManager();
long startTick = inStartPosition, stopTick = inStartPosition
+ timestampToTicks(inTempo, inStopTime - inStartTime);
TGMeasureHeader mh = tgSongMgr.getMeasureHeaderAt(startTick);
long firstBarTick = mh.getStart();
// insert bars into staff
for (long tick = firstBarTick; tick <= stopTick; tick += 4 * TGDuration.QUARTER_TIME)
addBar(tick);
// insert note events into staff
for (final MiNote note : midiNotes) {
this.addNote(note);
}
// generate bars
createMeasures();
// generate beats
insertNotesIntoTrack(inTrackName);
}
void addBar(long inTime) {
MiStaffEvent se = this.f_Events.get(new Long(inTime));
if (se == null) {
se = new MiStaffEvent(inTime);
this.f_Events.put(new Long(inTime), se);
}
se.markAsBar();
}
void addNote(MiNote inNote) {
MiStaffEvent se = this.f_Events.get(new Long(inNote.getTimeOn()));
if (se == null) {
se = new MiStaffEvent(inNote.getTimeOn());
this.f_Events.put(new Long(inNote.getTimeOn()), se);
}
se.addNoteOn(inNote);
}
private void addTiedNote(long inTime, MiStaffNote inSN,
long inResidualDuration) {
MiStaffEvent se = this.f_Events.get(new Long(inTime));
if (se == null) {
se = new MiStaffEvent(inTime);
this.f_Events.put(new Long(inTime), se);
}
MiStaffNote sn = new MiStaffNote(inSN);
sn.setDuration(inResidualDuration);
se.addTiedNote(sn);
}
void createMeasures() {
TGSongManager tgSongMgr = TuxGuitar.instance().getSongManager();
for (final Entry<Long, MiStaffEvent> entry : this.f_Events.entrySet()) {
final Long key = entry.getKey();
final MiStaffEvent se = entry.getValue();
if (se.isBar() && tgSongMgr.getMeasureHeaderAt(key.longValue()) == null) {
tgSongMgr.addNewMeasure(tgSongMgr.getSong().countMeasureHeaders() + 1);
}
}
}
private void dump(String inTitle) {
LOG.debug("");
LOG.debug("MiStaff dump " + inTitle + "...");
LOG.debug("");
for (final MiStaffEvent se : this.f_Events.values()) {
LOG.debug(se);
}
}
private void generateTrack(String inTrackName) {
TGSongManager tgSongMgr = TuxGuitar.instance().getSongManager();
TGTrack tgTrack = tgSongMgr.createTrack();
if (this.f_Dump_TrackGeneration) {
LOG.debug("");
LOG.debug("generating track: " + inTrackName + "...");
LOG.debug("");
}
tgTrack.setName(inTrackName);
this.f_TgTrack = tgTrack;
for (final MiStaffEvent se : this.f_Events.values()) {
se.setBeat(null);
}
// generate TuxGuitar track
for (final MiStaffEvent se : this.f_Events.values()) {
if (se.isOnBeat() || se.isTieBeat()) {
final TGBeat tgBeat = getEventBeat(se.getBeginTime());
for (final MiStaffNote sn : se.getNotes()) {
insertNoteIntoTrack(tgBeat, sn);
}
}
}
}
private TGBeat getEventBeat(long inTime) {
MiStaffEvent se = this.f_Events.get(new Long(inTime));
TGBeat tgBeat = se.getBeat();
// creates a TGBeat if needed
if (tgBeat == null) {
TGSongManager tgSongMgr = TuxGuitar.instance().getSongManager();
TGTrackManager tgTrackMgr = tgSongMgr.getTrackManager();
TGMeasure tgMeasure = tgTrackMgr.getMeasureAt(this.f_TgTrack, inTime);
if (tgMeasure != null) {
tgBeat = new TGBeatImpl();
tgBeat.setStart(inTime);
tgMeasure.addBeat(tgBeat);
se.setBeat(tgBeat);
}
}
return (tgBeat);
}
private void insertNoteIntoTrack(TGBeat inTgBeat, MiStaffNote inSN) {
// TGSongManager tgSongMgr = TuxGuitar.instance().getSongManager();
TGNote tgNote = new TGNoteImpl();
TGDuration tgDuration = new TGDuration();
tgNote.setString(inSN.getString());
tgNote.setValue(inSN.getFret());
tgNote.setVelocity(inSN.getVelocity());
tgNote.setTiedNote(inSN.isTied());
int noteType = MiStaffNote.durationToNoteType(inSN.getNominalDuration());
tgDuration.setValue(noteType);
tgDuration.setDotted(inSN.getDotCount() == 1);
tgDuration.setDoubleDotted(inSN.getDotCount() == 2);
if (this.f_Dump_TrackGeneration) {
LOG.debug(""
+ inTgBeat.getMeasure().getNumber()
+ " "
+ inTgBeat.getStart()
+ " ("
+ tgNote.getString()
+ ","
+ tgNote.getValue()
+ ","
+ tgNote.getVelocity()
+ ") "
+ "1/"
+ tgDuration.getValue()
+ ", d: "
+ tgDuration.getTime()
+ (tgNote.isTiedNote() ? " (tied)" : "")
+ (tgDuration.getTime() != inSN.getOverallDuration() ? " snDur="
+ inSN.getOverallDuration() : ""));
}
// here we probably should choose the voice
// it would be nice to have one voice for each string...
inTgBeat.getVoice(0).setDuration(tgDuration);
inTgBeat.getVoice(0).addNote(tgNote);
}
void insertNotesIntoTrack(String inTrackName) {
// normalize beats
SortedMap<Long, MiStaffEvent> normalizedEvents = new TreeMap<Long, MiStaffEvent>();
for (final MiStaffEvent se : this.f_Events.values()) {
se.normalizeBeat(TGDuration.SIXTY_FOURTH);
mergeEvent(normalizedEvents, se);
}
this.f_Events = normalizedEvents;
dump("after beat normalization");
generateTrack("after beat normalization");
// add tie events due to bar crossing at the beginning of each crossed bar
boolean keepGoing = true;
while (keepGoing) {
long nextBarBeginTime = 0;
keepGoing = false;
for (final MiStaffEvent se : this.f_Events.values()) {
if (se.isBar())
nextBarBeginTime = se.getBeginTime() + 4 * TGDuration.QUARTER_TIME; // bar
// length
// should
// be
// a
// MiStaff
// member
for (final MiStaffNote sn : se.getNotes()) {
if (se.getBeginTime() + sn.getOverallDuration() > nextBarBeginTime) {
long limitedDuration = (nextBarBeginTime - se.getBeginTime()), residualDuration = sn
.getOverallDuration()
- limitedDuration;
sn.setDuration(limitedDuration);
addTiedNote(nextBarBeginTime, sn, residualDuration);
keepGoing = true;
break;
}
}
if (keepGoing)
break;
}
}
dump("after tied due to bar crossing");
generateTrack("after tied due to bar crossing");
// normalize durations
for (final MiStaffEvent se : this.f_Events.values()) {
se.normalizeDurations();
}
dump("after duration normalization");
generateTrack("after duration normalization");
// add tie events due to note crossing
keepGoing = true;
while (keepGoing) {
keepGoing = false;
Iterator<Long> eventsIt2 = this.f_Events.keySet().iterator();
if (eventsIt2.hasNext())
eventsIt2.next();
for (Iterator<Entry<Long, MiStaffEvent>> eventsIt = this.f_Events
.entrySet().iterator(); eventsIt.hasNext();) {
Entry<Long, MiStaffEvent> me = eventsIt.next();
MiStaffEvent se = (MiStaffEvent) me.getValue();
if (eventsIt.hasNext() && eventsIt2.hasNext()) {
long nextTime = eventsIt2.next();
for (final MiStaffNote sn : se.getNotes()) {
if (se.getBeginTime() + sn.getOverallDuration() > nextTime) {
long limitedDuration = (nextTime - se.getBeginTime()), residualDuration = sn
.getOverallDuration()
- limitedDuration;
sn.setDuration(limitedDuration);
addTiedNote(nextTime, sn, residualDuration);
keepGoing = true;
break;
}
}
if (keepGoing)
break;
}
}
}
dump("after tied due to note crossing");
generateTrack("after tied due to note crossing");
// normalize durations
for (final MiStaffEvent se : this.f_Events.values()) {
se.normalizeDurations();
}
dump("after duration normalization 2");
generateTrack("after duration normalization 2");
}
private void mergeEvent(SortedMap<Long, MiStaffEvent> inEventsMap,
MiStaffEvent inSE) {
final MiStaffEvent se = inEventsMap.get(new Long(inSE.getBeginTime()));
if (se == null)
inEventsMap.put(new Long(inSE.getBeginTime()), inSE);
else
se.merge(inSE);
}
}