diff --git a/src/channel/heartbeat/Heartbeat.java b/src/channel/heartbeat/Heartbeat.java
new file mode 100644
index 00000000..4799754e
--- /dev/null
+++ b/src/channel/heartbeat/Heartbeat.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2017 Dennis Sheirer
+ *
+ * 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
+ *
+ ******************************************************************************/
+package channel.heartbeat;
+
+public class Heartbeat
+{
+ /**
+ * Periodic pulse that is broadcast to subscribing modules within the demodulating chain so that
+ * monitoring of state and other attributes can occur on the primary demodulation and decoding
+ * thread.
+ */
+ public Heartbeat()
+ {
+ }
+}
diff --git a/src/channel/heartbeat/IHeartbeatListener.java b/src/channel/heartbeat/IHeartbeatListener.java
new file mode 100644
index 00000000..904d0501
--- /dev/null
+++ b/src/channel/heartbeat/IHeartbeatListener.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2017 Dennis Sheirer
+ *
+ * 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
+ *
+ ******************************************************************************/
+package channel.heartbeat;
+
+import sample.Listener;
+
+public interface IHeartbeatListener
+{
+ public Listener getHeartbeatListener();
+}
diff --git a/src/channel/heartbeat/IHeartbeatProvider.java b/src/channel/heartbeat/IHeartbeatProvider.java
new file mode 100644
index 00000000..3583cd42
--- /dev/null
+++ b/src/channel/heartbeat/IHeartbeatProvider.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2017 Dennis Sheirer
+ *
+ * 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
+ *
+ ******************************************************************************/
+package channel.heartbeat;
+
+import sample.Listener;
+
+public interface IHeartbeatProvider
+{
+ public void setHeartbeatListener(Listener listener);
+
+ public void removeHeartbeatListener();
+}
diff --git a/src/channel/state/ChannelState.java b/src/channel/state/ChannelState.java
index 1f116dd4..30d7d796 100644
--- a/src/channel/state/ChannelState.java
+++ b/src/channel/state/ChannelState.java
@@ -20,6 +20,8 @@ package channel.state;
import audio.squelch.ISquelchStateProvider;
import audio.squelch.SquelchState;
+import channel.heartbeat.Heartbeat;
+import channel.heartbeat.IHeartbeatListener;
import channel.metadata.Attribute;
import channel.metadata.AttributeChangeRequest;
import channel.metadata.IAttributeChangeRequestListener;
@@ -39,16 +41,13 @@ import sample.real.IOverflowListener;
import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.IFrequencyChangeListener;
-import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
import static source.tuner.frequency.FrequencyChangeEvent.Event.NOTIFICATION_FREQUENCY_CHANGE;
public class ChannelState extends Module implements ICallEventProvider, IDecoderStateEventListener,
- IDecoderStateEventProvider, IFrequencyChangeListener, ISquelchStateProvider, IAttributeChangeRequestListener,
- IOverflowListener
+ IDecoderStateEventProvider, IFrequencyChangeListener, IHeartbeatListener, ISquelchStateProvider,
+ IAttributeChangeRequestListener, IOverflowListener
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelState.class);
@@ -61,8 +60,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
private Listener mDecoderStateListener;
private Listener mSquelchStateListener;
private DecoderStateEventReceiver mDecoderStateEventReceiver = new DecoderStateEventReceiver();
- private StateMonitor mStateMonitor = new StateMonitor();
- private ScheduledFuture> mStateMonitorFuture;
+ private HeartbeatReceiver mHeartbeatReceiver = new HeartbeatReceiver();
private ChannelType mChannelType;
private TrafficChannelManager mTrafficChannelEndListener;
private CallEvent mTrafficChannelCallEvent;
@@ -79,30 +77,21 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
/**
- * Channel state tracks the overall state of all processing modules and decoders
- * configured for the channel and provides squelch control and decoder state
- * reset events.
+ * Channel state tracks the overall state of all processing modules and decoders configured for the channel and
+ * provides squelch control and decoder state reset events.
*
- * Uses a state enumeration that defines allowable channel state transitions in
- * order to track a call or data decode event from start to finish. Uses a
- * timer to monitor for inactivity and to provide a FADE period that indicates
- * to the user that the activity has stopped while continuing to provide details
- * about the call, before the state is reset to IDLE.
- *
- * Since this class is multi-threaded between decoder events and the internal
- * timer that monitors for inactivity, this class uses the mState object for
- * thread synchronization.
+ * Uses a state enumeration that defines allowable channel state transitions in order to track a call or data decode
+ * event from start to finish. Uses a timer to monitor for inactivity and to provide a FADE period that indicates
+ * to the user that the activity has stopped while continuing to provide details about the call, before the state is
+ * reset to IDLE.
*
* State Descriptions:
- * IDLE Normal state. No voice or data call activity
- * CALL/DATA/ENCRYPTED/CONTROL
- * Decoding states.
- * FADE The phase after a voice or data call when either an explicit
- * call end has been received, or when no new signalling updates
- * have been received, and the fade timer has expired. This phase
- * allows for gui updates to signal to the user that the call is
- * ended, while continuing to display the call details for the user
- * TEARDOWN Indicates a traffic channel that will be torn down for reuse.
+ * IDLE: Normal state. No voice or data call activity
+ * CALL/DATA/ENCRYPTED/CONTROL: Decoding states.
+ * FADE: The phase after a voice or data call when either an explicit call end has been received, or when no new
+ * signalling updates have been received, and the fade timer has expired. This phase allows for gui updates to
+ * signal to the user that the call is ended, while continuing to display the call details for the user
+ * TEARDOWN: Indicates a traffic channel that will be torn down for reuse.
*/
public ChannelState(ChannelType channelType)
{
@@ -137,23 +126,11 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
@Override
public void start(ScheduledExecutorService executor)
{
- if(mStateMonitorFuture == null && mStateMonitor != null)
+ mMutableMetadata.receive(new AttributeChangeRequest(Attribute.CHANNEL_STATE, mState));
+
+ if(mTrafficChannelEndListener != null)
{
- mMutableMetadata.receive(new AttributeChangeRequest(Attribute.CHANNEL_STATE, mState));
-
- if(mTrafficChannelEndListener != null)
- {
- setState(State.CALL);
- }
-
- try
- {
- mStateMonitorFuture = executor.scheduleAtFixedRate(mStateMonitor, 0, 20, TimeUnit.MILLISECONDS);
- }
- catch(RejectedExecutionException ree)
- {
- mLog.error("state monitor scheduled task rejected", ree);
- }
+ setState(State.CALL);
}
}
@@ -162,16 +139,6 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
{
mMutableMetadata.resetAllAttributes();
- if(mStateMonitorFuture != null)
- {
- boolean success = mStateMonitorFuture.cancel(true);
-
- if(!success)
- {
- mLog.error("Couldn't stop monitoring scheduled future");
- }
- }
-
mTrafficChannelEndListener = null;
if(mTrafficChannelCallEvent != null)
@@ -182,8 +149,6 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
mTrafficChannelCallEvent = null;
- mStateMonitorFuture = null;
-
mSquelchLocked = false;
}
@@ -192,7 +157,6 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
mCallEventListener = null;
mDecoderStateListener = null;
mSquelchStateListener = null;
- mStateMonitor = null;
}
@Override
@@ -312,70 +276,74 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
}
/**
- * Sets the channel state to the specified state, or updates the timeout
- * values so that the state monitor will not change state. Broadcasts a
- * squelch event when the state changes and the audio squelch state should
- * change. Also broadcasts changed attribute and decoder state events so
- * that external processes can maintain sync with this channel state.
+ * Sets the channel state to the specified state, or updates the timeout values so that the state monitor will not
+ * change state. Broadcasts a squelch event when the state changes and the audio squelch state should change. Also
+ * broadcasts changed attribute and decoder state events so that external processes can maintain sync with this
+ * channel state.
*/
protected void setState(State state)
{
- synchronized(mState)
+ if(state == mState)
{
- if(state == mState)
+ if(State.CALL_STATES.contains(state))
{
- if(State.CALL_STATES.contains(state))
- {
- updateFadeTimeout();
- }
+ updateFadeTimeout();
}
- else if(state != mState && mState.canChangeTo(state))
+ }
+ else if(mState.canChangeTo(state))
+ {
+ switch(state)
{
- switch(state)
- {
- case CONTROL:
- //Don't allow traffic channels to be control channels,
- //otherwise they can't transition to call tear down
- if(isStandardChannel())
- {
- broadcast(SquelchState.SQUELCH);
- updateFadeTimeout();
- mState = state;
- }
- break;
- case DATA:
- case ENCRYPTED:
+ case CONTROL:
+ //Don't allow traffic channels to be control channels, otherwise they can't transition to teardown
+ if(isStandardChannel())
+ {
broadcast(SquelchState.SQUELCH);
updateFadeTimeout();
mState = state;
- break;
- case CALL:
- broadcast(SquelchState.UNSQUELCH);
- updateFadeTimeout();
- mState = state;
- break;
- case FADE:
- processFadeState();
- break;
- case IDLE:
- processIdleState();
- break;
- case TEARDOWN:
- processTeardownState();
- break;
- case RESET:
- mState = State.IDLE;
- break;
- default:
- break;
- }
-
- mMutableMetadata.receive(new AttributeChangeRequest(Attribute.CHANNEL_STATE,
- mSourceOverflow ? State.OVERFLOW : mState));
+ }
+ break;
+ case DATA:
+ case ENCRYPTED:
+ broadcast(SquelchState.SQUELCH);
+ updateFadeTimeout();
+ mState = state;
+ break;
+ case CALL:
+ broadcast(SquelchState.UNSQUELCH);
+ updateFadeTimeout();
+ mState = state;
+ break;
+ case FADE:
+ processFadeState();
+ break;
+ case IDLE:
+ processIdleState();
+ break;
+ case TEARDOWN:
+ processTeardownState();
+ break;
+ case RESET:
+ mState = State.IDLE;
+ break;
+ default:
+ break;
}
+
+ mMutableMetadata.receive(new AttributeChangeRequest(Attribute.CHANNEL_STATE,
+ mSourceOverflow ? State.OVERFLOW : mState));
}
}
+ /**
+ * Receiver inner class that implements the IHeartbeatListener interface to receive heartbeat messages.
+ */
+ @Override
+ public Listener getHeartbeatListener()
+ {
+ return mHeartbeatReceiver;
+ }
+
/**
* This method is invoked if the source buffer provider goes into overflow state. Since this is an external state,
* we use the mSourceOverflow variable to override the internal state reported to external listeners.
@@ -625,39 +593,35 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
}
/**
- * Monitors decoder state events to automatically transition the channel
- * state to IDLE (standard channel) or to TEARDOWN (traffic channel) when
- * decoding stops or the monitored channel returns to a no signal state.
+ * Processes periodic heartbeats received from the processing chain to perform state monitoring and cleanup
+ * functions.
*
- * Provides a FADE transition state to allow for momentary decoding dropouts
- * and to allow the user access to call details for a fade period upon
- * call end.
+ * Monitors decoder state events to automatically transition the channel state to IDLE (standard channel) or to
+ * TEARDOWN (traffic channel) when decoding stops or the monitored channel returns to a no signal state.
+ *
+ * Provides a FADE transition state to allow for momentary decoding dropouts and to allow the user access to call
+ * details for a fade period upon call end.
*/
- public class StateMonitor implements Runnable
+ public class HeartbeatReceiver implements Listener
{
@Override
- public void run()
+ public void receive(Heartbeat heartbeat)
{
try
{
- synchronized(mState)
+ if(State.CALL_STATES.contains(mState) && mFadeTimeout <= System.currentTimeMillis())
{
- if(State.CALL_STATES.contains(mState) &&
- mFadeTimeout <= System.currentTimeMillis())
+ processFadeState();
+ }
+ else if(mState == State.FADE && mEndTimeout <= System.currentTimeMillis())
+ {
+ if(isTrafficChannel())
{
- processFadeState();
+ processTeardownState();
}
- else if(mState == State.FADE &&
- mEndTimeout <= System.currentTimeMillis())
+ else
{
- if(isTrafficChannel())
- {
- processTeardownState();
- }
- else
- {
- processIdleState();
- }
+ processIdleState();
}
}
}
diff --git a/src/module/ProcessingChain.java b/src/module/ProcessingChain.java
index 7c0bf5cd..5eaa4cbb 100644
--- a/src/module/ProcessingChain.java
+++ b/src/module/ProcessingChain.java
@@ -24,16 +24,17 @@ import audio.IAudioPacketProvider;
import audio.squelch.ISquelchStateListener;
import audio.squelch.ISquelchStateProvider;
import audio.squelch.SquelchState;
+import channel.heartbeat.Heartbeat;
+import channel.heartbeat.IHeartbeatListener;
+import channel.heartbeat.IHeartbeatProvider;
import channel.metadata.AttributeChangeRequest;
import channel.metadata.IAttributeChangeRequestListener;
import channel.metadata.IAttributeChangeRequestProvider;
import channel.state.ChannelState;
import channel.state.DecoderState;
import channel.state.DecoderStateEvent;
-import channel.state.DecoderStateEvent.Event;
import channel.state.IDecoderStateEventListener;
import channel.state.IDecoderStateEventProvider;
-import channel.state.State;
import controller.channel.Channel.ChannelType;
import controller.channel.ChannelEvent;
import controller.channel.IChannelEventListener;
@@ -63,8 +64,6 @@ import sample.real.RealBuffer;
import source.ComplexSource;
import source.RealSource;
import source.Source;
-import source.SourceException;
-import source.tuner.TunerChannelSource;
import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.IFrequencyChangeListener;
import source.tuner.frequency.IFrequencyChangeProvider;
@@ -103,6 +102,7 @@ public class ProcessingChain implements IChannelEventListener
private Broadcaster mComplexBufferBroadcaster = new Broadcaster<>();
private Broadcaster mDecoderStateEventBroadcaster = new Broadcaster<>();
private Broadcaster mFrequencyChangeEventBroadcaster = new Broadcaster<>();
+ private Broadcaster mHeartbeatBroadcaster = new Broadcaster<>();
private Broadcaster mMessageBroadcaster = new Broadcaster<>();
private Broadcaster mFilteredRealBufferBroadcaster = new Broadcaster<>();
private Broadcaster mUnFilteredRealBufferBroadcaster = new Broadcaster<>();
@@ -329,6 +329,11 @@ public class ProcessingChain implements IChannelEventListener
}
}
+ if(module instanceof IHeartbeatListener)
+ {
+ mHeartbeatBroadcaster.addListener(((IHeartbeatListener)module).getHeartbeatListener());
+ }
+
if(module instanceof IMessageListener)
{
mMessageBroadcaster.addListener(((IMessageListener) module).getMessageListener());
@@ -391,6 +396,11 @@ public class ProcessingChain implements IChannelEventListener
mFrequencyChangeEventBroadcaster.removeListener(((IFrequencyChangeListener) module).getFrequencyChangeListener());
}
+ if(module instanceof IHeartbeatListener)
+ {
+ mHeartbeatBroadcaster.removeListener(((IHeartbeatListener)module).getHeartbeatListener());
+ }
+
if(module instanceof IMessageListener)
{
mMessageBroadcaster.removeListener(((IMessageListener) module).getMessageListener());
@@ -448,6 +458,11 @@ public class ProcessingChain implements IChannelEventListener
((IFrequencyChangeProvider) module).setFrequencyChangeListener(mFrequencyChangeEventBroadcaster);
}
+ if(module instanceof IHeartbeatProvider)
+ {
+ ((IHeartbeatProvider)module).setHeartbeatListener(mHeartbeatBroadcaster);
+ }
+
if(module instanceof IMessageProvider)
{
((IMessageProvider) module).setMessageListener(mMessageBroadcaster);
@@ -505,6 +520,11 @@ public class ProcessingChain implements IChannelEventListener
((IFrequencyChangeProvider) module).setFrequencyChangeListener(null);
}
+ if(module instanceof IHeartbeatProvider)
+ {
+ ((IHeartbeatProvider)module).removeHeartbeatListener();
+ }
+
if(module instanceof IMessageProvider)
{
((IMessageProvider) module).setMessageListener(null);
diff --git a/src/module/decode/p25/P25DecoderState.java b/src/module/decode/p25/P25DecoderState.java
index 8dc31d68..5072bc31 100644
--- a/src/module/decode/p25/P25DecoderState.java
+++ b/src/module/decode/p25/P25DecoderState.java
@@ -1713,33 +1713,41 @@ public class P25DecoderState extends DecoderState
.build());
break;
case SNDCP_RF_CONFIRMED_DATA:
- SNDCPUserData sud = (SNDCPUserData)pduc;
-
- StringBuilder sbFrom = new StringBuilder();
- StringBuilder sbTo = new StringBuilder();
-
- sbFrom.append(sud.getSourceIPAddress());
- sbTo.append(sud.getDestinationIPAddress());
-
- if(sud.getIPProtocol() == IPProtocol.UDP)
+ if(pduc instanceof SNDCPUserData)
{
- sbFrom.append(":");
- sbFrom.append(sud.getUDPSourcePort());
- sbTo.append(":");
- sbTo.append(sud.getUDPDestinationPort());
+ SNDCPUserData sud = (SNDCPUserData)pduc;
+
+ StringBuilder sbFrom = new StringBuilder();
+ StringBuilder sbTo = new StringBuilder();
+
+ sbFrom.append(sud.getSourceIPAddress());
+ sbTo.append(sud.getDestinationIPAddress());
+
+ if(sud.getIPProtocol() == IPProtocol.UDP)
+ {
+ sbFrom.append(":");
+ sbFrom.append(sud.getUDPSourcePort());
+ sbTo.append(":");
+ sbTo.append(sud.getUDPDestinationPort());
+ }
+
+ broadcast(new DecoderStateEvent(this, Event.START, State.DATA));
+
+ broadcast(new P25CallEvent.Builder(CallEventType.DATA_CALL)
+ .aliasList(getAliasList())
+ .channel(mCurrentChannel)
+ .details("DATA: " + sud.getPayload() +
+ " RADIO IP:" + sbTo.toString())
+ .frequency(mCurrentChannelFrequency)
+ .from(sbFrom.toString())
+ .to(pduc.getLogicalLinkID())
+ .build());
+ }
+ else
+ {
+ mLog.error("Error - expected SNDCPUserData instance but class was: " + pduc.getClass() +
+ " and PDU Type:" + pduc.getPDUType().name());
}
-
- broadcast(new DecoderStateEvent(this, Event.START, State.DATA));
-
- broadcast(new P25CallEvent.Builder(CallEventType.DATA_CALL)
- .aliasList(getAliasList())
- .channel(mCurrentChannel)
- .details("DATA: " + sud.getPayload() +
- " RADIO IP:" + sbTo.toString())
- .frequency(mCurrentChannelFrequency)
- .from(sbFrom.toString())
- .to(pduc.getLogicalLinkID())
- .build());
break;
case SNDCP_RF_UNCONFIRMED_DATA:
break;
diff --git a/src/source/Source.java b/src/source/Source.java
index 932c2749..9b253fa5 100644
--- a/src/source/Source.java
+++ b/src/source/Source.java
@@ -17,6 +17,7 @@
******************************************************************************/
package source;
+import channel.heartbeat.IHeartbeatProvider;
import module.Module;
import sample.SampleType;
import sample.real.IOverflowListener;
@@ -26,7 +27,8 @@ import source.tuner.frequency.IFrequencyChangeProvider;
/**
* Abstract class to define the minimum functionality of a sample data provider.
*/
-public abstract class Source extends Module implements IFrequencyChangeListener, IFrequencyChangeProvider
+public abstract class Source extends Module implements IFrequencyChangeListener, IFrequencyChangeProvider,
+ IHeartbeatProvider
{
protected SampleType mSampleType;
protected IOverflowListener mOverflowListener;
diff --git a/src/source/mixer/RealMixerSource.java b/src/source/mixer/RealMixerSource.java
index cfc96997..7f0dfba2 100644
--- a/src/source/mixer/RealMixerSource.java
+++ b/src/source/mixer/RealMixerSource.java
@@ -1,174 +1,191 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 Dennis Sheirer
- *
+ *
* 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
******************************************************************************/
package source.mixer;
-import java.util.Iterator;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import javax.sound.sampled.AudioFormat;
-import javax.sound.sampled.AudioSystem;
-import javax.sound.sampled.LineUnavailableException;
-import javax.sound.sampled.TargetDataLine;
-
+import channel.heartbeat.Heartbeat;
+import dsp.gain.AutomaticGainControl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-
import sample.Listener;
import sample.adapter.ISampleAdapter;
import sample.real.RealBuffer;
import source.RealSource;
import source.SourceException;
-import dsp.gain.AutomaticGainControl;
import source.tuner.frequency.FrequencyChangeEvent;
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.LineUnavailableException;
+import javax.sound.sampled.TargetDataLine;
+import java.util.Iterator;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.atomic.AtomicBoolean;
+
public class RealMixerSource extends RealSource
{
- private final static Logger mLog = LoggerFactory.getLogger( RealMixerSource.class );
+ private final static Logger mLog = LoggerFactory.getLogger(RealMixerSource.class);
- private long mFrequency = 0;
- private int mBufferSize = 16384;
-
- private BufferReader mBufferReader = new BufferReader();
- private TargetDataLine mTargetDataLine;
- private AudioFormat mAudioFormat;
- private int mBytesPerFrame = 0;
- private ISampleAdapter mSampleAdapter;
- private AutomaticGainControl mAGC = new AutomaticGainControl();
-
- CopyOnWriteArrayList> mSampleListeners = new CopyOnWriteArrayList>();
-
- /**
- * Real Mixer Source - constructs a reader on the mixer/sound card target
- * data line using the specified audio format (sample size, sample rate )
- * and broadcasts real buffers (float arrays) to all registered listeners.
- * Reads buffers sized to 10% of the sample rate specified in audio format.
- *
- * @param targetDataLine - mixer or sound card to be used
- *
- * @param format - audio format
- *
- * @param name - token name to use for this source
- *
- * @param sampleAdapter - adapter to convert byte array data read from the
- * mixer into float array data. Can optionally invert the channel data if
- * the left/right stereo channels are inverted.
- */
- public RealMixerSource( TargetDataLine targetDataLine,
- AudioFormat format,
- ISampleAdapter sampleAdapter )
+ private long mFrequency = 0;
+ private int mBufferSize = 16384;
+
+ private BufferReader mBufferReader = new BufferReader();
+ private TargetDataLine mTargetDataLine;
+ private AudioFormat mAudioFormat;
+ private int mBytesPerFrame = 0;
+ private ISampleAdapter mSampleAdapter;
+ private AutomaticGainControl mAGC = new AutomaticGainControl();
+ private Listener mHeartbeatListener;
+ private static final Heartbeat HEARTBEAT = new Heartbeat();
+
+ private CopyOnWriteArrayList> mSampleListeners = new CopyOnWriteArrayList>();
+
+ /**
+ * Real Mixer Source - constructs a reader on the mixer/sound card target
+ * data line using the specified audio format (sample size, sample rate )
+ * and broadcasts real buffers (float arrays) to all registered listeners.
+ * Reads buffers sized to 10% of the sample rate specified in audio format.
+ *
+ * @param targetDataLine - mixer or sound card to be used
+ * @param format - audio format
+ * @param sampleAdapter - adapter to convert byte array data read from the
+ * mixer into float array data. Can optionally invert the channel data if
+ * the left/right stereo channels are inverted.
+ */
+ public RealMixerSource(TargetDataLine targetDataLine,
+ AudioFormat format,
+ ISampleAdapter sampleAdapter)
{
- mTargetDataLine = targetDataLine;
+ mTargetDataLine = targetDataLine;
mAudioFormat = format;
mSampleAdapter = sampleAdapter;
}
- @Override
- public void setFrequencyChangeListener(Listener listener)
- {
- //Not implemented
- }
-
- @Override
- public void removeFrequencyChangeListener()
- {
- //Not implemented
- }
-
- @Override
- public Listener getFrequencyChangeListener()
- {
- //Not implemented
- return null;
- }
-
- @Override
- public void reset()
- {
- }
-
- @Override
- public void start( ScheduledExecutorService executor )
- {
- }
-
- @Override
- public void stop()
- {
- }
-
- public void setListener( Listener listener )
+ @Override
+ public void setFrequencyChangeListener(Listener listener)
{
- mSampleListeners.add( listener );
-
- /* If this is the first listener, start the reader thread */
- if( !mBufferReader.isRunning() )
- {
- Thread thread = new Thread( mBufferReader );
- thread.setDaemon( true );
- thread.setName( "Sample Reader" );
- thread.start();
- }
+ //Not implemented
}
- public void removeListener( Listener listener )
+ @Override
+ public void removeFrequencyChangeListener()
{
- mSampleListeners.remove( listener );
+ //Not implemented
+ }
+
+ @Override
+ public Listener getFrequencyChangeListener()
+ {
+ //Not implemented
+ return null;
+ }
+
+ /**
+ * Registers the listener to receive heartbeats from this source.
+ *
+ * @param listener to receive heartbeats
+ */
+ @Override
+ public void setHeartbeatListener(Listener listener)
+ {
+ mHeartbeatListener = listener;
+ }
+
+ /**
+ * Removes the currently registered heartbeat listener
+ */
+ @Override
+ public void removeHeartbeatListener()
+ {
+ mHeartbeatListener = null;
+ }
+
+ @Override
+ public void reset()
+ {
+ }
+
+ @Override
+ public void start(ScheduledExecutorService executor)
+ {
+ }
+
+ @Override
+ public void stop()
+ {
+ }
+
+ public void setListener(Listener listener)
+ {
+ mSampleListeners.add(listener);
+
+ /* If this is the first listener, start the reader thread */
+ if(!mBufferReader.isRunning())
+ {
+ Thread thread = new Thread(mBufferReader);
+ thread.setDaemon(true);
+ thread.setName("Sample Reader");
+ thread.start();
+ }
+ }
+
+ public void removeListener(Listener listener)
+ {
+ mSampleListeners.remove(listener);
/* If this is the laster listener, stop the reader thread */
- if( mSampleListeners.isEmpty() )
- {
- mBufferReader.stop();
- }
+ if(mSampleListeners.isEmpty())
+ {
+ mBufferReader.stop();
+ }
}
- public void broadcast( RealBuffer samples )
+ public void broadcast(RealBuffer samples)
{
- Iterator> it = mSampleListeners.iterator();
-
- while( it.hasNext() )
- {
- Listener next = it.next();
+ Iterator> it = mSampleListeners.iterator();
+
+ while(it.hasNext())
+ {
+ Listener next = it.next();
/* if this is the last (or only) listener, send him the original
* buffer, otherwise send him a copy of the buffer */
- if( it.hasNext() )
- {
- next.receive( samples.copyOf() );
- }
- else
- {
- next.receive( samples );
- }
- }
+ if(it.hasNext())
+ {
+ next.receive(samples.copyOf());
+ }
+ else
+ {
+ next.receive(samples);
+ }
+ }
}
-
+
public int getSampleRate()
{
- if( mTargetDataLine != null )
- {
- return (int)mTargetDataLine.getFormat().getSampleRate();
- }
- else
- {
- return 0;
- }
+ if(mTargetDataLine != null)
+ {
+ return (int)mTargetDataLine.getFormat().getSampleRate();
+ }
+ else
+ {
+ return 0;
+ }
}
/**
@@ -176,7 +193,7 @@ public class RealMixerSource extends RealSource
*/
public long getFrequency() throws SourceException
{
- return mFrequency;
+ return mFrequency;
}
/**
@@ -184,122 +201,128 @@ public class RealMixerSource extends RealSource
* be useful if you are streaming an external audio source in through the
* sound card and you want to specify a frequency for that source
*/
- public void setFrequency( long frequency )
+ public void setFrequency(long frequency)
{
- mFrequency = frequency;
+ mFrequency = frequency;
}
- /**
- * Reader thread. Performs blocking read against the mixer target data
- * line, converts the samples to an array of floats using the specified
- * adapter. Dispatches float arrays to all registered listeners.
- */
- public class BufferReader implements Runnable
- {
- private AtomicBoolean mRunning = new AtomicBoolean();
-
- @Override
+ /**
+ * Reader thread. Performs blocking read against the mixer target data
+ * line, converts the samples to an array of floats using the specified
+ * adapter. Dispatches float arrays to all registered listeners.
+ */
+ public class BufferReader implements Runnable
+ {
+ private AtomicBoolean mRunning = new AtomicBoolean();
+
+ @Override
public void run()
{
- if( mRunning.compareAndSet( false, true ) )
- {
- if( mTargetDataLine == null )
- {
- mRunning.set( false );
+ if(mRunning.compareAndSet(false, true))
+ {
+ //Send a heartbeat every time we run
+ if(mHeartbeatListener != null)
+ {
+ mHeartbeatListener.receive(HEARTBEAT);
+ }
- mLog.error( "ComplexMixerSource - mixer target data line"
- + " is null" );
- }
- else
- {
- mBytesPerFrame = mAudioFormat.getSampleSizeInBits() / 8;
+ if(mTargetDataLine == null)
+ {
+ mRunning.set(false);
- if( mBytesPerFrame == AudioSystem.NOT_SPECIFIED )
- {
- mBytesPerFrame = 2;
- }
+ mLog.error("ComplexMixerSource - mixer target data line"
+ + " is null");
+ }
+ else
+ {
+ mBytesPerFrame = mAudioFormat.getSampleSizeInBits() / 8;
+
+ if(mBytesPerFrame == AudioSystem.NOT_SPECIFIED)
+ {
+ mBytesPerFrame = 2;
+ }
/* Set buffer size to 1/10 second of samples */
- mBufferSize = (int)( mAudioFormat.getSampleRate()
- * 0.05 ) * mBytesPerFrame;
+ mBufferSize = (int)(mAudioFormat.getSampleRate()
+ * 0.05) * mBytesPerFrame;
/* We'll reuse the same buffer for each read */
- byte[] buffer = new byte[ mBufferSize ];
-
- try
- {
- mTargetDataLine.open( mAudioFormat );
+ byte[] buffer = new byte[mBufferSize];
- mTargetDataLine.start();
- }
- catch ( LineUnavailableException e )
+ try
{
- mLog.error( "ComplexMixerSource - mixer target data line"
- + "not available to read data from", e );
-
- mRunning.set( false );
+ mTargetDataLine.open(mAudioFormat);
+
+ mTargetDataLine.start();
}
-
- while( mRunning.get() )
- {
- try
+ catch(LineUnavailableException e)
+ {
+ mLog.error("ComplexMixerSource - mixer target data line"
+ + "not available to read data from", e);
+
+ mRunning.set(false);
+ }
+
+ while(mRunning.get())
+ {
+ try
{
/* Blocking read - waits until the buffer fills */
- mTargetDataLine.read( buffer, 0, buffer.length );
+ mTargetDataLine.read(buffer, 0, buffer.length);
/* Convert samples to float array */
- float[] samples =
- mSampleAdapter.convert( buffer );
+ float[] samples =
+ mSampleAdapter.convert(buffer);
/* Dispatch samples to registered listeners */
- broadcast( new RealBuffer( samples ) );
+ broadcast(new RealBuffer(samples));
}
- catch ( Exception e )
+ catch(Exception e)
{
- mLog.error( "ComplexMixerSource - error while reading"
- + "from the mixer target data line", e );
-
- mRunning.set( false );
+ mLog.error("ComplexMixerSource - error while reading"
+ + "from the mixer target data line", e);
+
+ mRunning.set(false);
}
- }
- }
+ }
+ }
/* Close the data line if it is still open */
- if( mTargetDataLine != null && mTargetDataLine.isOpen() )
- {
- mTargetDataLine.close();
- }
- }
+ if(mTargetDataLine != null && mTargetDataLine.isOpen())
+ {
+ mTargetDataLine.close();
+ }
+ }
}
- /**
- * Stops the reader thread
- */
- public void stop()
- {
- if( mRunning.compareAndSet( true, false ) )
- {
- mTargetDataLine.stop();
- mTargetDataLine.close();
- }
- }
+ /**
+ * Stops the reader thread
+ */
+ public void stop()
+ {
+ if(mRunning.compareAndSet(true, false))
+ {
+ mTargetDataLine.stop();
+ mTargetDataLine.close();
+ }
+ }
- /**
- * Indicates if the reader thread is running
- */
- public boolean isRunning()
- {
- return mRunning.get();
- }
- }
+ /**
+ * Indicates if the reader thread is running
+ */
+ public boolean isRunning()
+ {
+ return mRunning.get();
+ }
+ }
- @Override
+ @Override
public void dispose()
{
- if( mBufferReader != null )
- {
- mBufferReader.stop();
- mSampleListeners.clear();
- }
+ if(mBufferReader != null)
+ {
+ mBufferReader.stop();
+ mSampleListeners.clear();
+ }
}
}
diff --git a/src/source/tuner/TunerChannelSource.java b/src/source/tuner/TunerChannelSource.java
index 7556e7b5..ba7900b8 100644
--- a/src/source/tuner/TunerChannelSource.java
+++ b/src/source/tuner/TunerChannelSource.java
@@ -18,6 +18,8 @@
******************************************************************************/
package source.tuner;
+import channel.heartbeat.Heartbeat;
+import channel.heartbeat.IHeartbeatProvider;
import dsp.filter.FilterFactory;
import dsp.filter.Window.WindowType;
import dsp.filter.cic.ComplexPrimeCICDecimate;
@@ -58,6 +60,7 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
private static int CHANNEL_RATE = 48000;
private static int CHANNEL_PASS_FREQUENCY = 12000;
+ private static final Heartbeat HEARTBEAT = new Heartbeat();
private OverflowableTransferQueue mBuffer;
@@ -66,6 +69,7 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
private Oscillator mMixer;
private ComplexPrimeCICDecimate mDecimationFilter;
private Listener mListener;
+ private Listener mHeartbeatListener;
private IFrequencyChangeProcessor mFrequencyChangeProcessor;
private DownstreamProcessor mDownstreamFrequencyEventProcessor = new DownstreamProcessor();
private ScheduledFuture> mTaskHandle;
@@ -352,6 +356,25 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
return mDownstreamFrequencyEventProcessor.getFrequencyChangeListener();
}
+ /**
+ * Registers the listener to receive heartbeats from this source.
+ * @param listener to receive heartbeats
+ */
+ @Override
+ public void setHeartbeatListener(Listener listener)
+ {
+ mHeartbeatListener = listener;
+ }
+
+ /**
+ * Removes the currently registered heartbeat listener
+ */
+ @Override
+ public void removeHeartbeatListener()
+ {
+ mHeartbeatListener = null;
+ }
+
/**
* Managers frequency change requests and notifications from/to any downstream component. Downstream
* components are those that receive samples from this tuner channel source. These downstream components will be
@@ -477,6 +500,13 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
{
if(mProcessing)
{
+ //Send a heartbeat every time this runs to allow downstream components to perform periodic
+ //state monitoring functions on this thread
+ if(mHeartbeatListener != null)
+ {
+ mHeartbeatListener.receive(HEARTBEAT);
+ }
+
mBuffer.drainTo(mSampleBuffers, 20);
for(Buffer buffer : mSampleBuffers)
diff --git a/src/source/wave/ComplexWaveSource.java b/src/source/wave/ComplexWaveSource.java
index 8d80ce1d..39e6ef26 100644
--- a/src/source/wave/ComplexWaveSource.java
+++ b/src/source/wave/ComplexWaveSource.java
@@ -18,19 +18,9 @@
******************************************************************************/
package source.wave;
-import java.io.File;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.concurrent.ScheduledExecutorService;
-
-import javax.sound.sampled.AudioFormat;
-import javax.sound.sampled.AudioInputStream;
-import javax.sound.sampled.AudioSystem;
-import javax.sound.sampled.UnsupportedAudioFileException;
-
+import channel.heartbeat.Heartbeat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-
import sample.ConversionUtils;
import sample.Listener;
import sample.complex.ComplexBuffer;
@@ -39,22 +29,31 @@ import source.IControllableFileSource;
import source.IFrameLocationListener;
import source.tuner.frequency.FrequencyChangeEvent;
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.UnsupportedAudioFileException;
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.concurrent.ScheduledExecutorService;
+
public class ComplexWaveSource extends ComplexSource implements IControllableFileSource
{
- private final static Logger mLog =
- LoggerFactory.getLogger( ComplexWaveSource.class );
+ private final static Logger mLog =
+ LoggerFactory.getLogger(ComplexWaveSource.class);
- private IFrameLocationListener mFrameLocationListener;
- private int mBytesPerFrame;
- private int mFrameCounter = 0;
+ private IFrameLocationListener mFrameLocationListener;
+ private int mBytesPerFrame;
+ private int mFrameCounter = 0;
private long mFrequency = 0;
private Listener mListener;
private AudioInputStream mInputStream;
private File mFile;
-
- public ComplexWaveSource( File file ) throws IOException
+
+ public ComplexWaveSource(File file) throws IOException
{
- mFile = file;
+ mFile = file;
}
@Override
@@ -76,89 +75,105 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil
return null;
}
+ /**
+ * Not implemented
+ */
@Override
- public void reset()
- {
- stop();
- start( null );
- }
-
- @Override
- public void start( ScheduledExecutorService executor )
- {
- try
- {
- open();
- }
- catch ( IOException | UnsupportedAudioFileException e )
- {
- mLog.error( "Error starting complex wave source" );
- }
- }
-
- @Override
- public void stop()
- {
- try
- {
- close();
- }
- catch ( IOException e )
- {
- mLog.error( "Error stopping complex wave source" );
- }
- }
-
- @Override
- public long getFrameCount() throws IOException
- {
- // TODO Auto-generated method stub
- return 0;
- }
-
- @Override
- public int getSampleRate()
+ public void setHeartbeatListener(Listener listener)
{
- if( mInputStream != null )
- {
- return (int)mInputStream.getFormat().getSampleRate();
- }
-
- return 0;
}
- /**
- * Returns the frequency set for this file. Normally returns zero, but
- * the value can be set with setFrequency() method.
- */
+ /**
+ * Not implemented
+ */
+ @Override
+ public void removeHeartbeatListener()
+ {
+ }
+
+ @Override
+ public void reset()
+ {
+ stop();
+ start(null);
+ }
+
+ @Override
+ public void start(ScheduledExecutorService executor)
+ {
+ try
+ {
+ open();
+ }
+ catch(IOException | UnsupportedAudioFileException e)
+ {
+ mLog.error("Error starting complex wave source");
+ }
+ }
+
+ @Override
+ public void stop()
+ {
+ try
+ {
+ close();
+ }
+ catch(IOException e)
+ {
+ mLog.error("Error stopping complex wave source");
+ }
+ }
+
+ @Override
+ public long getFrameCount() throws IOException
+ {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public int getSampleRate()
+ {
+ if(mInputStream != null)
+ {
+ return (int)mInputStream.getFormat().getSampleRate();
+ }
+
+ return 0;
+ }
+
+ /**
+ * Returns the frequency set for this file. Normally returns zero, but
+ * the value can be set with setFrequency() method.
+ */
public long getFrequency()
{
- return mFrequency;
+ return mFrequency;
}
/**
* Changes the value returned from getFrequency() for this source.
*/
- public void setFrequency( long frequency )
+ public void setFrequency(long frequency)
{
- mFrequency = frequency;
+ mFrequency = frequency;
}
-
+
/**
* Closes the source file
*/
public void close() throws IOException
{
- if( mInputStream != null )
- {
- mInputStream.close();
- }
- else
- {
- throw new IOException( "Can't close wave source - was not opened" );
- }
-
- mInputStream = null;
+ if(mInputStream != null)
+ {
+ mInputStream.close();
+ }
+ else
+ {
+ throw new IOException("Can't close wave source - was not opened");
+ }
+
+ mInputStream = null;
}
/**
@@ -166,121 +181,121 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil
*/
public void open() throws IOException, UnsupportedAudioFileException
{
- if( mInputStream == null )
- {
- mInputStream = AudioSystem.getAudioInputStream( mFile );
- }
- else
- {
- throw new IOException( "Can't open wave source - is already opened" );
- }
+ if(mInputStream == null)
+ {
+ mInputStream = AudioSystem.getAudioInputStream(mFile);
+ }
+ else
+ {
+ throw new IOException("Can't open wave source - is already opened");
+ }
AudioFormat format = mInputStream.getFormat();
-
+
mBytesPerFrame = format.getFrameSize();
- if( format.getChannels() != 2 || format.getSampleSizeInBits() != 16 )
+ if(format.getChannels() != 2 || format.getSampleSizeInBits() != 16)
{
- throw new IOException( "Unsupported Wave Format - EXPECTED: 2 " +
- "channels 16-bit samples FOUND: " +
- mInputStream.getFormat().getChannels() + " channels " +
- mInputStream.getFormat().getSampleSizeInBits() + "-bit samples" );
+ throw new IOException("Unsupported Wave Format - EXPECTED: 2 " +
+ "channels 16-bit samples FOUND: " +
+ mInputStream.getFormat().getChannels() + " channels " +
+ mInputStream.getFormat().getSampleSizeInBits() + "-bit samples");
}
/* Broadcast that we're at frame location 0 */
- broadcast( 0 );
+ broadcast(0);
}
/**
* Reads the number of frames and sends a buffer to the listener
*/
- @Override
- public void next( int frames ) throws IOException
- {
- next( frames, true );
- }
-
- /**
- * Reads the number of frames and optionally sends the buffer to the listener
- */
- public void next( int frames, boolean broadcast ) throws IOException
+ @Override
+ public void next(int frames) throws IOException
{
- if( mInputStream != null )
+ next(frames, true);
+ }
+
+ /**
+ * Reads the number of frames and optionally sends the buffer to the listener
+ */
+ public void next(int frames, boolean broadcast) throws IOException
+ {
+ if(mInputStream != null)
{
- byte[] buffer = new byte[ mBytesPerFrame * frames ];
-
+ byte[] buffer = new byte[mBytesPerFrame * frames];
+
/* Fill the buffer with samples from the file */
- int samplesRead = mInputStream.read( buffer );
+ int samplesRead = mInputStream.read(buffer);
- mFrameCounter += samplesRead;
-
- broadcast( mFrameCounter );
-
- if( broadcast && mListener != null )
- {
- if( samplesRead < buffer.length )
- {
- buffer = Arrays.copyOf( buffer, samplesRead );
- }
+ mFrameCounter += samplesRead;
- float[] samples = ConversionUtils
- .convertFromSigned16BitSamples( buffer );
-
- mListener.receive( new ComplexBuffer( samples ) );
- }
+ broadcast(mFrameCounter);
+
+ if(broadcast && mListener != null)
+ {
+ if(samplesRead < buffer.length)
+ {
+ buffer = Arrays.copyOf(buffer, samplesRead);
+ }
+
+ float[] samples = ConversionUtils
+ .convertFromSigned16BitSamples(buffer);
+
+ mListener.receive(new ComplexBuffer(samples));
+ }
}
}
-
+
/**
- * Registers the listener to receive sample buffers as they are read from
+ * Registers the listener to receive sample buffers as they are read from
* the wave file
*/
- @Override
- public void setListener( Listener listener )
- {
- mListener = listener;
- }
-
- /**
- * Unregisters the listener from receiving sample buffers
- */
- public void removeListener( Listener listener )
+ @Override
+ public void setListener(Listener listener)
{
- mListener = null;
+ mListener = listener;
}
- @Override
+ /**
+ * Unregisters the listener from receiving sample buffers
+ */
+ public void removeListener(Listener listener)
+ {
+ mListener = null;
+ }
+
+ @Override
public void dispose()
{
- mListener = null;
+ mListener = null;
}
- @Override
- public File getFile()
- {
- return mFile;
- }
+ @Override
+ public File getFile()
+ {
+ return mFile;
+ }
- private void broadcast( int byteLocation )
- {
- int frameLocation = (int)( byteLocation / mBytesPerFrame );
-
- if( mFrameLocationListener != null )
- {
- mFrameLocationListener.frameLocationUpdated( frameLocation );
- }
- }
-
- @Override
- public void setListener( IFrameLocationListener listener )
- {
- mFrameLocationListener = listener;
- }
+ private void broadcast(int byteLocation)
+ {
+ int frameLocation = (int)(byteLocation / mBytesPerFrame);
- @Override
- public void removeListener( IFrameLocationListener listener )
- {
- mFrameLocationListener = null;
- }
+ if(mFrameLocationListener != null)
+ {
+ mFrameLocationListener.frameLocationUpdated(frameLocation);
+ }
+ }
+
+ @Override
+ public void setListener(IFrameLocationListener listener)
+ {
+ mFrameLocationListener = listener;
+ }
+
+ @Override
+ public void removeListener(IFrameLocationListener listener)
+ {
+ mFrameLocationListener = null;
+ }
}
diff --git a/src/source/wave/RealWaveSource.java b/src/source/wave/RealWaveSource.java
index 4b9b120b..a17efb49 100644
--- a/src/source/wave/RealWaveSource.java
+++ b/src/source/wave/RealWaveSource.java
@@ -1,35 +1,25 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
- *
+ *
* 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
******************************************************************************/
package source.wave;
-import java.io.File;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.concurrent.ScheduledExecutorService;
-
-import javax.sound.sampled.AudioFormat;
-import javax.sound.sampled.AudioInputStream;
-import javax.sound.sampled.AudioSystem;
-import javax.sound.sampled.UnsupportedAudioFileException;
-
+import channel.heartbeat.Heartbeat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-
import sample.ConversionUtils;
import sample.Listener;
import sample.real.RealBuffer;
@@ -38,131 +28,154 @@ import source.IFrameLocationListener;
import source.RealSource;
import source.tuner.frequency.FrequencyChangeEvent;
-public class RealWaveSource extends RealSource
- implements IControllableFileSource, AutoCloseable
-{
- private final static Logger mLog =
- LoggerFactory.getLogger( RealWaveSource.class );
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioInputStream;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.UnsupportedAudioFileException;
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.concurrent.ScheduledExecutorService;
- private IFrameLocationListener mFrameLocationListener;
- private int mBytesPerFrame;
- private int mFrameCounter = 0;
+public class RealWaveSource extends RealSource implements IControllableFileSource, AutoCloseable
+{
+ private final static Logger mLog = LoggerFactory.getLogger(RealWaveSource.class);
+
+ private IFrameLocationListener mFrameLocationListener;
+ private int mBytesPerFrame;
+ private int mFrameCounter = 0;
private long mFrequency = 0;
private Listener mListener;
private AudioInputStream mInputStream;
private File mFile;
-
- public RealWaveSource( File file ) throws IOException
+
+ public RealWaveSource(File file) throws IOException
{
- mFile = file;
+ mFile = file;
}
- @Override
- public void setFrequencyChangeListener(Listener listener)
- {
- //Not implemented
- }
+ @Override
+ public void setFrequencyChangeListener(Listener listener)
+ {
+ //Not implemented
+ }
- @Override
- public void removeFrequencyChangeListener()
- {
- //Not implemented
- }
+ @Override
+ public void removeFrequencyChangeListener()
+ {
+ //Not implemented
+ }
- @Override
- public Listener getFrequencyChangeListener()
- {
- //Not implemented
- return null;
- }
+ @Override
+ public Listener getFrequencyChangeListener()
+ {
+ //Not implemented
+ return null;
+ }
+
+ /**
+ * Not implemented
+ */
+ @Override
+ public void setHeartbeatListener(Listener listener)
+ {
+ }
+
+ /**
+ * Not implemented
+ */
+ @Override
+ public void removeHeartbeatListener()
+ {
+ }
- @Override
- public void reset()
- {
- stop();
- start( null );
- }
+ @Override
+ public void reset()
+ {
+ stop();
+ start(null);
+ }
- @Override
- public void start( ScheduledExecutorService executor )
- {
- try
- {
- open();
- }
- catch ( IOException | UnsupportedAudioFileException e )
- {
- mLog.error( "Error starting real wave source", e );
- }
- }
+ @Override
+ public void start(ScheduledExecutorService executor)
+ {
+ try
+ {
+ open();
+ }
+ catch(IOException | UnsupportedAudioFileException e)
+ {
+ mLog.error("Error starting real wave source", e);
+ }
+ }
- @Override
- public void stop()
- {
- try
- {
- close();
- }
- catch ( IOException e )
- {
- mLog.error( "Error stopping real wave source", e );
- }
- }
+ @Override
+ public void stop()
+ {
+ try
+ {
+ close();
+ }
+ catch(IOException e)
+ {
+ mLog.error("Error stopping real wave source", e);
+ }
+ }
- @Override
- public long getFrameCount() throws IOException
- {
- // TODO Auto-generated method stub
- return 0;
- }
+ @Override
+ public long getFrameCount() throws IOException
+ {
+ // TODO Auto-generated method stub
+ return 0;
+ }
- @Override
+ @Override
public int getSampleRate()
{
- if( mInputStream != null )
- {
- return (int)mInputStream.getFormat().getSampleRate();
- }
-
- return 0;
+ if(mInputStream != null)
+ {
+ return (int)mInputStream.getFormat().getSampleRate();
+ }
+
+ return 0;
}
- /**
- * Returns the frequency set for this file. Normally returns zero, but
- * the value can be set with setFrequency() method.
- */
+ /**
+ * Returns the frequency set for this file. Normally returns zero, but
+ * the value can be set with setFrequency() method.
+ */
public long getFrequency()
{
- return mFrequency;
+ return mFrequency;
}
/**
* Changes the value returned from getFrequency() for this source.
*/
- public void setFrequency( long frequency )
+ public void setFrequency(long frequency)
{
- mFrequency = frequency;
+ mFrequency = frequency;
}
-
+
/**
* Closes the source file
*/
public void close() throws IOException
{
- if( mInputStream != null )
- {
- mInputStream.close();
- }
- else
- {
- throw new IOException( "Can't close wave source - was not opened" );
- }
-
- mInputStream = null;
+ if(mInputStream != null)
+ {
+ mInputStream.close();
+ }
+ else
+ {
+ throw new IOException("Can't close wave source - was not opened");
+ }
+
+ mInputStream = null;
}
/**
@@ -170,126 +183,126 @@ public class RealWaveSource extends RealSource
*/
public void open() throws IOException, UnsupportedAudioFileException
{
- if( mInputStream == null )
- {
- mInputStream = AudioSystem.getAudioInputStream( mFile );
- }
- else
- {
- throw new IOException( "Can't open wave source - is already opened" );
- }
+ if(mInputStream == null)
+ {
+ mInputStream = AudioSystem.getAudioInputStream(mFile);
+ }
+ else
+ {
+ throw new IOException("Can't open wave source - is already opened");
+ }
AudioFormat format = mInputStream.getFormat();
-
+
mBytesPerFrame = format.getFrameSize();
- if( format.getChannels() != 1 || format.getSampleSizeInBits() != 16 )
+ if(format.getChannels() != 1 || format.getSampleSizeInBits() != 16)
{
- throw new IOException( "Unsupported Wave Format - EXPECTED: 1 " +
- "channel 16-bit samples FOUND: " +
- mInputStream.getFormat().getChannels() + " channels " +
- mInputStream.getFormat().getSampleSizeInBits() + "-bit samples" );
+ throw new IOException("Unsupported Wave Format - EXPECTED: 1 " +
+ "channel 16-bit samples FOUND: " +
+ mInputStream.getFormat().getChannels() + " channels " +
+ mInputStream.getFormat().getSampleSizeInBits() + "-bit samples");
}
/* Broadcast that we're at frame location 0 */
- broadcast( 0 );
+ broadcast(0);
}
/**
* Reads the number of frames and sends a buffer to the listener
*/
- @Override
- public void next( int frames ) throws IOException
- {
- next( frames, true );
- }
-
- /**
- * Reads the number of frames and optionally sends the buffer to the listener
- */
- public void next( int frames, boolean broadcast ) throws IOException
+ @Override
+ public void next(int frames) throws IOException
{
- if( mInputStream != null )
- {
- byte[] buffer = new byte[ mBytesPerFrame * frames ];
-
- /* Fill the buffer with samples from the file */
- int samplesRead = mInputStream.read( buffer );
-
- mFrameCounter += samplesRead;
-
- broadcast( mFrameCounter );
-
- if( broadcast && mListener != null )
- {
- if( samplesRead < buffer.length )
- {
- if( samplesRead == -1 )
- {
- throw new IOException( "End of recording" );
- }
-
- buffer = Arrays.copyOf( buffer, samplesRead );
- }
+ next(frames, true);
+ }
- float[] samples = ConversionUtils
- .convertFromSigned16BitSamples( buffer );
-
- mListener.receive( new RealBuffer( samples ) );
- }
+ /**
+ * Reads the number of frames and optionally sends the buffer to the listener
+ */
+ public void next(int frames, boolean broadcast) throws IOException
+ {
+ if(mInputStream != null)
+ {
+ byte[] buffer = new byte[mBytesPerFrame * frames];
+
+ /* Fill the buffer with samples from the file */
+ int samplesRead = mInputStream.read(buffer);
+
+ mFrameCounter += samplesRead;
+
+ broadcast(mFrameCounter);
+
+ if(broadcast && mListener != null)
+ {
+ if(samplesRead < buffer.length)
+ {
+ if(samplesRead == -1)
+ {
+ throw new IOException("End of recording");
+ }
+
+ buffer = Arrays.copyOf(buffer, samplesRead);
+ }
+
+ float[] samples = ConversionUtils
+ .convertFromSigned16BitSamples(buffer);
+
+ mListener.receive(new RealBuffer(samples));
+ }
}
}
-
+
/**
- * Registers the listener to receive sample buffers as they are read from
+ * Registers the listener to receive sample buffers as they are read from
* the wave file
*/
- @Override
- public void setListener( Listener listener )
- {
- mListener = listener;
- }
-
- /**
- * Unregisters the listener from receiving sample buffers
- */
- public void removeListener( Listener listener )
+ @Override
+ public void setListener(Listener listener)
{
- mListener = null;
+ mListener = listener;
}
- @Override
+ /**
+ * Unregisters the listener from receiving sample buffers
+ */
+ public void removeListener(Listener listener)
+ {
+ mListener = null;
+ }
+
+ @Override
public void dispose()
{
- mListener = null;
+ mListener = null;
}
- @Override
- public File getFile()
- {
- return mFile;
- }
-
- private void broadcast( int byteLocation )
- {
- int frameLocation = (int)( byteLocation / mBytesPerFrame );
-
- if( mFrameLocationListener != null )
- {
- mFrameLocationListener.frameLocationUpdated( frameLocation );
- }
- }
-
- @Override
- public void setListener( IFrameLocationListener listener )
- {
- mFrameLocationListener = listener;
- }
+ @Override
+ public File getFile()
+ {
+ return mFile;
+ }
- @Override
- public void removeListener( IFrameLocationListener listener )
- {
- mFrameLocationListener = null;
- }
+ private void broadcast(int byteLocation)
+ {
+ int frameLocation = (int)(byteLocation / mBytesPerFrame);
+
+ if(mFrameLocationListener != null)
+ {
+ mFrameLocationListener.frameLocationUpdated(frameLocation);
+ }
+ }
+
+ @Override
+ public void setListener(IFrameLocationListener listener)
+ {
+ mFrameLocationListener = listener;
+ }
+
+ @Override
+ public void removeListener(IFrameLocationListener listener)
+ {
+ mFrameLocationListener = null;
+ }
}