/* 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.Timer; import java.util.TimerTask; import javax.sound.midi.ShortMessage; import com.confusionists.mjdj.Universe; import com.confusionists.mjdj.ui.InternalClockUi; import com.confusionists.mjdjApi.midi.MessageWrapper; import com.confusionists.mjdjApi.midiDevice.AbstractTransmitterDeviceWrapper; import com.confusionists.mjdjApi.midiDevice.DeviceUnavailableException; /** * This clock is for diagnostic purposes only * @author DanielRosenstark [at_sign] confusionists.com * */ public class InternalClock extends AbstractTransmitterDeviceWrapper { Timer timer; private int ticks = 0; public int bpm = 120; private InternalClockUi ui; @Override public void close() { if (timer != null) timer.cancel(); // idempotent } @Override public String getName() { return "Internal Clock"; } @Override public void open() throws DeviceUnavailableException { } public void resetBpm(int bpm) { this.bpm = bpm; setActive(isActive()); } @Override public void setActive(boolean active) { super.setActive(active); if (active) start(); } private void start() { close(); // kill any old timers float bpm = this.bpm; // milliseconds are too imprecise to do this, so we use a small number long smallTickMs = 1; // this needs to be much finer than (1000f/(bpm/60f))/24f) since the timers do not fire each other timer = new Timer(); timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { if (ticks < 23) { InternalClock.this.sendBeat(); ticks++; } } }, 0, smallTickMs); long bigTickMs = Math.round(1000 / (bpm / 60)); timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { InternalClock.this.sendBeat(); ticks = 0; } }, smallTickMs, bigTickMs); } protected void sendBeat() { if (!isActive() || !isClockSource()) return; Universe.instance.clockHandler.onClock(); // internally, we MUST call this directly, there is no other way in try { ShortMessage message = new ShortMessage(); message.setMessage(ShortMessage.TIMING_CLOCK); // TODO: allow choosing ports for this service.send(MessageWrapper.newInstance(message)); } catch (Exception e) { throw new RuntimeException(e); } } @Override public void toggleUi() { super.toggleUi(); if (ui == null) ui = new InternalClockUi(this); else ui.setVisible(true); } @Override public void makeNewId(ArrayList<String> existingIds) { throw new RuntimeException("How did we get here?"); } }