/*
Mjdj MIDI Morph - an extensible MIDI processor and translator.
Copyright (C) 2010 Confusionists, LLC (www.confusionists.com)
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 3 of the License, or (at your option) 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, see <http://www.gnu.org/licenses/>.
You may contact the author at mjdj_midi_morph [at] confusionists.com
*/
package com.confusionists.mjdj.midi.time;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import com.confusionists.mjdj.Universe;
import com.confusionists.mjdj.ui.Logger;
public class ClockHandler {
private long lastTick = -1;
private int ticks = -1;
public long differenceInMs = -1;
// these values are used to make sure that we're not following noise in
// clock differences
private long newDifferenceInMs = -1;
private float beatsPerMinute = -1;
private boolean suspendFlatlineTimer = false; // flatline timer is suspended
private List<BeatLockedTimerTask> tasks = new ArrayList<BeatLockedTimerTask>();
public ClockHandler() {
TimerTask task = new TimerTask() {
@Override
public void run() {
checkFlatline();
}
};
Timer timer = new Timer();
timer.scheduleAtFixedRate(task, 400, 400);
}
public void onClock() {
suspendFlatlineTimer = true;
long thisTick = System.currentTimeMillis();
if (lastTick == -1) {
lastTick = System.currentTimeMillis();
} else if (ticks == 23) {
long diff = (thisTick - lastTick) - 1;
boolean acceptNewDiff = false;
// if the value varies widely just
// once, we ignore it...newDifference is the last diff
if (differenceInMs == -1) {
acceptNewDiff = true;
} else if (diff != differenceInMs && diff == newDifferenceInMs) {
acceptNewDiff = true;
}
newDifferenceInMs = diff;
if (acceptNewDiff) {
differenceInMs = diff;
float newBeatsPerMinute = 60000 / newDifferenceInMs;
if (newBeatsPerMinute != beatsPerMinute) {
beatsPerMinute = newBeatsPerMinute;
Universe.instance.syncButton.setText(beatsPerMinute + "bpm");
Logger.log("Synced to host tempo: " + beatsPerMinute + "bpm");
}
}
lastTick = System.currentTimeMillis();
ticks = -1;
Universe.instance.syncButton.pulse(); // this must come before we set the text
// copy Tasks is just to avoid concurrently modifying the collection
// tasks
for (BeatLockedTimerTask task : tasks.toArray(new BeatLockedTimerTask[tasks.size()])) {
task.onBeat();
if (task.isDead())
tasks.remove(task);
}
Universe.instance.midiDriverManager.onBeat();
} else if (ticks == 5) {
Universe.instance.syncButton.hideBorder();
}
ticks++;
suspendFlatlineTimer = false;
}
public void checkFlatline() {
if (suspendFlatlineTimer || beatsPerMinute == -1)
return;
if (System.currentTimeMillis() - lastTick > 1000)
onFlatline();
}
public void onFlatline() {
if (beatsPerMinute == -1 && tasks.size() == 0)
return; // initial startup
Logger.log("Lost sync to host, clearing " + tasks.size() + " BeatLocked tasks.");
Universe.instance.syncButton.reset();
beatsPerMinute = -1;
differenceInMs = -1;
newDifferenceInMs = -1;
ticks = -1;
lastTick = -1;
tasks.clear(); // those that have already hit their beat before will
// fire, but then cannot reschedule. Others will be
// killed immediately.
}
public void sync() {
ticks = 0;
lastTick = System.currentTimeMillis();
}
private long getMsSinceLastBeat() {
return System.currentTimeMillis() - lastTick;
}
public float getSinceLastBeat() {
return (float) getMsSinceLastBeat() / (float) differenceInMs;
}
/**
* @param task
* @return true if scheduled, returns false if task cannot be scheduled
* (because there's no clock)
*/
public boolean pleaseCallMeOnEachBeat(BeatLockedTimerTask task) {
if (differenceInMs == -1)
return false;
tasks.add(task);
return true;
}
public void diagnose() {
Logger.log("ClockHandler checking in, BPM is at " + beatsPerMinute + " and we are holding " + tasks.size() + " tasks for firing.");
}
}