package com.frinika.voiceserver.voicetemplate; import com.frinika.voiceserver.Voice; import com.frinika.voiceserver.VoiceInterrupt; /* * Created on Jul 17, 2006 * * Copyright (c) 2004-2006 Peter Johan Salomonsen (http://www.petersalomonsen.com) * * 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 */ import com.frinika.voiceserver.VoiceServer; /** * A voice that can be synchronized from an outside timing source. Useful for e.g. audio playback to be synchronized * with a sequencer. * * @author Peter Johan Salomonsen */ public abstract class SynchronizedVoice extends Voice { long framePos = 0; /** * In case of a glitch - how many frames did we miss? */ int missedFrames = 0; /** * Number of milliseconds of glitch before synchronization if enforced * Windows recommendation: 50 ms, Linux 10-20 ms * TODO: Make this configurable from audio devices GUI */ int missedFramesToleranceMillis = 50; // Default 50 msecs protected VoiceServer voiceServer; /** * Construct a new Synchronized Voice. Note that in order for the initialFramePos to be valid, this voice * should be added to the voiceServer not too long after it's constructed * * Since glitches may "come and go" due to the missing accuracy in Java timing functions, synchronization is not * applied until a tolerance threshold is exceeded. * * @param voiceServer * @param initialFramePos - the initial position of playback start - relative to audio clip start */ public SynchronizedVoice(VoiceServer voiceServer, long initialFramePos) { this.voiceServer = voiceServer; this.framePos = initialFramePos; } /** * Number of milliseconds of glitch before synchronization if enforced */ public int getMissedFramesToleranceMillis() { return missedFramesToleranceMillis; } /** * Set number of milliseconds of glitch before synchronization if enforced * @param missedFramesToleranceMillis */ public void setMissedFramesToleranceMillis(int missedFramesToleranceMillis) { this.missedFramesToleranceMillis = missedFramesToleranceMillis; } /** * The external timing source should regularly notify with the current frame position here * @param framePos */ public void setFramePos(final long framePos) { voiceServer.interruptTransmitter(this, new VoiceInterrupt() { @Override public void doInterrupt() { // For a samplerate of 44100 the glitch must be at least 45 frames for a 1 msec glitch int glitchMS = ((int)(framePos - SynchronizedVoice.this.framePos) * 1000) / voiceServer.getSampleRate(); // Only enfore synchronization if the glitch is beyond the tolerance if(Math.abs(glitchMS)>missedFramesToleranceMillis) { missedFrames = (int) (framePos - SynchronizedVoice.this.framePos); SynchronizedVoice.this.framePos = framePos; } }}); } /** * Call this method from fillBufferSynchronized to get the framePos according to the external timing source * @return */ protected final long getFramePos() { return framePos; } /** * Call this method from fillBufferSynchronized to get the number of missing frames (glitch) after an external sync notification * NOTE: Your tolerance on missed frames should not be too low - since timing functions like System.currentTimeMillis might slide * up to 50 ms on some systems. Your number of missed frames tolerance should be thereafter before correcting your * framepos. * * @return */ protected final int getMissedFrames() { return missedFrames; } @Override public final void fillBuffer(int startBufferPos, int endBufferPos, float[] buffer) { fillBufferSynchronized(startBufferPos,endBufferPos,buffer); framePos += (endBufferPos - startBufferPos) / 2; // Update framePos according to number of requested samples to fillBuffer missedFrames = 0; // Reset missedframes until next notification } public abstract void fillBufferSynchronized(int startBufferPos, int endBufferPos, float[] buffer); }